Platform: Code4rena
Start Date: 31/10/2023
Pot Size: $60,500 USDC
Total HM: 9
Participants: 65
Period: 10 days
Judge: gzeon
Total Solo HM: 2
Id: 301
League: ETH
Rank: 4/65
Findings: 2
Award: $1,532.79
🌟 Selected for report: 1
🚀 Solo Findings: 0
1380.4196 USDC - $1,380.42
The executeProposal()
function in ProposalExecutionEngine.sol
is used to execute different types of proposal call. One of this type is ArbitraryCallsProposal
and this type should be able to send ether from the Party's balance or the forwarded ETH attached to this call - msg.value. However, the problem is that the executeProposal()
function is not marked as payable and all calls that are sending ether and using msg.value will fail because of that.
Note: Even though this function is delegatecalled from PartyGovernance.sol::execute()
which is payable the msg.value is still preserved when delegatecall is used and the function called needs to be payable too.
All proposal calls that are using attached ether to it will fail and Party's wont be able to execute these types of proposals which can be important.
146: function executeProposal( 147: ExecuteProposalParams memory params 148: ) external onlyDelegateCall returns (bytes memory nextProgressData) {
As you can see the executeProposal()
function is not payable however later it then calls _executeArbitraryCalls()
:
72: // If we're not allowing arbitrary calls to spend the Party's ETH, only 73: // allow forwarded ETH attached to the call to be spent. 74: uint256 ethAvailable = allowArbCallsToSpendPartyEth ? address(this).balance : msg.value;
As you can see and as the comment suggests the call should be able to use the forwarded ether attached to the call and ethAvailable
is then used in _executeSingleArbitraryCall()
but because the function is not payable the call will revert.
Manual Review
Make the executeProposal()
function payable
Payable
#0 - ydspa
2023-11-11T14:30:46Z
seems right, EVM source code
File: core\vm\contract.go 134: func (c *Contract) AsDelegate() *Contract { 135: // NOTE: caller must, at all times be a contract. It should never happen 136: // that caller is something other than a Contract. 137: parent := c.caller.(*Contract) 138: c.CallerAddress = parent.CallerAddress 139: c.value = parent.value 140: 141: return c 142: }
#1 - c4-pre-sort
2023-11-11T14:30:56Z
ydspa marked the issue as insufficient quality report
#2 - c4-pre-sort
2023-11-11T15:09:56Z
ydspa marked the issue as sufficient quality report
#3 - c4-pre-sort
2023-11-12T08:24:25Z
ydspa marked the issue as primary issue
#4 - c4-sponsor
2023-11-14T20:11:27Z
arr00 (sponsor) confirmed
#5 - c4-judge
2023-11-19T14:45:41Z
gzeon-c4 marked the issue as selected for report
#6 - c4-judge
2023-11-19T14:45:45Z
gzeon-c4 marked the issue as satisfactory
🌟 Selected for report: TresDelinquentes
Also found by: 3docSec, Arz, Bauchibred, D1r3Wolf, J4X, Neon2835, Pechenite, adeolu, chainsnake
152.3655 USDC - $152.37
Because of a finding in the previous contest, the minContribution
check is done after the amount is potentially reduced if refunding excess contribution. However this can be a problem if the maxTotalContributions
- minTotalContributions
is smaller than the minContribution
and in some cases the crowdfund can be lost because of this.
Example:
minTotalContributions = 100 ether maxTotalContributions = 102 ether The current totalContributions = 99 ether minContribution amount = 4 ether
When the last contributor wants to contribute he calls the function with 4 ether however the reduced amount will then be 3 ether and the tx will revert because of the minContribution
check and the crowdfund will be lost.
There will be no way to finalize the crowdfund and because of that it will be lost even though it was supposed to be finalized.
240: amount -= refundAmount;
256: if (amount < minContribution_) { 257: revert BelowMinimumContributionsError(amount, minContribution_); 258: }
As you can see the amount is reduced and the check is done after it, the problem arises when the maxTotalContributions
- minTotalContributions
is smaller than the minContribution
which will cause the crowdfund to be lost.
Manual Review
Because we cant move the check before the refund logic, the only way to prevent this is to check if the minContribution
is bigger than the maxTotalContributions
- minTotalContributions
in the initialize function
Other
#0 - ydspa
2023-11-11T06:24:20Z
Obviously it's a design choice, reference: https://github.com/code-423n4/2023-10-party/blob/b23c65d62a20921c709582b0b76b387f2bb9ebb5/contracts/crowdfund/ETHCrowdfundBase.sol#L247-L254
Invalid: design decision
#1 - c4-pre-sort
2023-11-11T06:24:34Z
ydspa marked the issue as insufficient quality report
#2 - c4-pre-sort
2023-11-11T08:32:57Z
ydspa marked the issue as primary issue
#3 - c4-judge
2023-11-19T14:33:07Z
gzeon-c4 marked the issue as unsatisfactory: Invalid
#4 - c4-judge
2023-11-19T14:40:16Z
gzeon-c4 marked the issue as unsatisfactory: Out of scope
#5 - c4-judge
2023-11-23T14:16:10Z
gzeon-c4 marked the issue as unsatisfactory: Out of scope
#6 - c4-judge
2023-11-23T14:20:08Z
gzeon-c4 marked the issue as satisfactory
#7 - c4-judge
2023-11-23T14:22:52Z
gzeon-c4 marked issue #127 as primary and marked this issue as a duplicate of 127
#8 - gzeon-c4
2023-11-23T14:41:52Z