Platform: Code4rena
Start Date: 07/04/2023
Pot Size: $47,000 USDC
Total HM: 20
Participants: 120
Period: 6 days
Judge: GalloDaSballo
Total Solo HM: 4
Id: 230
League: ETH
Rank: 107/120
Findings: 1
Award: $8.51
🌟 Selected for report: 0
🚀 Solo Findings: 0
8.511 USDC - $8.51
Fund loss
Lines 254 to 293 have a vulnerability called "Dangerous Usage of msg.value inside a Loop". The function change accepts an array of Change struct as input and executes them one by one in a loop. Inside the loop, there is a call to PrivatePool.change function at line 273 which transfers ETH to the PrivatePool contract. The amount of ETH transferred is equal to msg.value which is set to the value of the value field in the transaction that calls the change function. Since the change function is called in a loop, the value of msg.value can be different for each iteration of the loop. This can lead to unexpected behavior and result in a loss of funds.
To fix this vulnerability, we should move the transfer of ETH outside the loop and calculate the total amount of ETH to be transferred before calling the PrivatePool.change function. We can use a variable to keep track of the total amount of ETH to be transferred and update it inside the loop. Then, we can transfer the total amount of ETH to the PrivatePool contract after the loop.
Here is an example of the fixed code:
function change(Change[] calldata changes, uint256 deadline) public payable { // check deadline has not passed (if any) if (block.timestamp > deadline && deadline != 0) { revert DeadlinePassed(); }
uint256 totalValue = 0; // loop through and execute the changes for (uint256 i = 0; i < changes.length; i++) { Change memory _change = changes[i]; // transfer NFTs from caller for (uint256 j = 0; j < changes[i].inputTokenIds.length; j++) { ERC721(_change.nft).safeTransferFrom(msg.sender, address(this), _change.inputTokenIds[j]); } // approve pair to transfer NFTs from router ERC721(_change.nft).setApprovalForAll(_change.pool, true); // calculate the value to be transferred uint256 changeValue = calculateChangeValue(_change); // add the value to the total value totalValue += changeValue; // execute change PrivatePool(_change.pool).change{value: changeValue}( _change.inputTokenIds, _change.inputTokenWeights, _change.inputProof, _change.stolenNftProofs, _change.outputTokenIds, _change.outputTokenWeights, _change.outputProof ); // transfer NFTs to caller for (uint256 j = 0; j < changes[i].outputTokenIds.length; j++) { ERC721(_change.nft).safeTransferFrom(address(this), msg.sender, _change.outputTokenIds[j]); } } // transfer the total value to the PrivatePool contract if (totalValue > 0) { (bool success, ) = address(PrivatePool).call{value: totalValue}(""); require(success, "Transfer failed"); } // refund any surplus ETH to the caller if (address(this).balance > 0) { msg.sender.safeTransferETH(address(this).balance); }
}
function calculateChangeValue(Change memory _change) internal view returns (uint256) { // calculate the value to be transferred uint256 inputValue = 0; for (uint256 i = 0; i < _change.inputTokenIds.length; i++) { inputValue += getTokenValue(_change.inputTokenIds[i], _change.inputTokenWeights[i]); }
uint256 outputValue = 0; for (uint256 i = 0; i < _change.outputTokenIds.length; i++) { outputValue += getTokenValue(_change.outputTokenIds[i], _change.outputTokenWeights[i]); } return inputValue > outputValue ? inputValue - outputValue : 0;
}
function getTokenValue(uint256 tokenId, uint256 tokenWeight) internal view returns (uint256) { // calculate the value of the token // ... }
#0 - c4-pre-sort
2023-04-20T19:31:03Z
0xSorryNotSorry marked the issue as primary issue
#1 - c4-sponsor
2023-04-22T09:47:25Z
outdoteth marked the issue as sponsor confirmed
#2 - outdoteth
2023-04-22T16:07:50Z
#3 - c4-judge
2023-04-30T15:36:15Z
GalloDaSballo changed the severity to 2 (Med Risk)
#4 - c4-judge
2023-04-30T15:36:28Z
GalloDaSballo marked the issue as duplicate of #873
#5 - GalloDaSballo
2023-04-30T15:37:40Z
The finding is valid and correctly flagged
The finding itself seems to be generated via a tool and it's unclear that the Warden understood the impact
Because the cause is correct, I'll award the finding
Because the impact is incorrect, I'm awarding 25%
The reality is that tx will revert if change uses any value, which was not explained here
#6 - c4-judge
2023-04-30T15:37:45Z
GalloDaSballo marked the issue as partial-50
#7 - c4-judge
2023-05-01T07:12:56Z
GalloDaSballo marked the issue as partial-25