diff --git a/contracts/crowdfund/ContributionRouter.sol b/contracts/crowdfund/ContributionRouter.sol index 30e8dea7..d9218875 100644 --- a/contracts/crowdfund/ContributionRouter.sol +++ b/contracts/crowdfund/ContributionRouter.sol @@ -15,6 +15,8 @@ contract ContributionRouter { event ClaimedFees(address indexed partyDao, address indexed recipient, uint256 amount); error OnlyOwner(); + /// @notice Thrown if the target is not a smart contract. + error InvalidTarget(); /// @notice The address allowed to claim fees from the contract. address public immutable OWNER; @@ -73,8 +75,13 @@ contract ContributionRouter { uint256 feeAmount = _storage.feePerMint; _storage.caller = msg.sender; address target; + uint256 targetCodeSize; assembly { target := shr(96, calldataload(sub(calldatasize(), 20))) + targetCodeSize := extcodesize(target) + } + if (targetCodeSize == 0) { + revert InvalidTarget(); } if ( msg.sig == InitialETHCrowdfund.batchContributeFor.selector || diff --git a/test/crowdfund/ContributionRouter.t.sol b/test/crowdfund/ContributionRouter.t.sol index 2b82aeaf..fb6d7bc0 100644 --- a/test/crowdfund/ContributionRouter.t.sol +++ b/test/crowdfund/ContributionRouter.t.sol @@ -40,6 +40,22 @@ contract ContributionRouterTest is TestUtils { assertEq(address(router).balance, feeAmount); } + function test_invalidTarget() external { + MockPayableContract target = new MockPayableContract(); + uint256 amount = 1 ether; + vm.deal(address(this), amount); + //vm.expectRevert(ContributionRouter.InvalidTarget.selector); + (bool success, bytes memory res) = address(router).call{ value: amount }( + abi.encodePacked( + abi.encodeWithSelector(MockPayableContract.pay.selector), + target, + hex"1234" + ) + ); + assertEq(success, false); + assertEq(res, abi.encodePacked(ContributionRouter.InvalidTarget.selector)); + } + function test_fallback_insufficientFee() public { MockPayableContract target = new MockPayableContract(); uint256 amount = feePerMint - 1;