The Multicall contract uses the delegatecall (which takes user-provided calldata) in a payable function within a loop. This means that each delegatecall within the for loop will retain the msg.value of the transaction
Proof of Concept
function multicall(bytes[] calldata data) public payable returns (bytes[] memory results) {
results = new bytes[](data.length);
for (uint256 i = 0; i < data.length; ) {
(bool success, bytes memory result) = address(this).delegatecall(data[i]);
if (!success) {
// Bubble up the revert reason
// The bytes type is ABI encoded as a length-prefixed byte array
// So we simply need to add 32 to the pointer to get the start of the data
// And then revert with the size loaded from the first 32 bytes
// Other solutions will do work to differentiate the revert reasons and provide paranthetical information
// However, we have chosen to simply replicate the the normal behavior of the call
// NOTE: memory-safe because it reads from memory already allocated by solidity (the bytes memory result)
assembly ("memory-safe") {
revert(add(result, 32), mload(result))
}
}
results[i] = result;
unchecked {
++i;
}
}
}
The protocol does not currently use the msg.value in any meaningful way. it could be exploited to tamper with the system arithmetic.
Lines of code
https://github.com/code-423n4/2024-04-panoptic/blob/833312ebd600665b577fbd9c03ffa0daf250ed24/contracts/multicall/Multicall.sol#L15-L15
Vulnerability details
Impact
The Multicall contract uses the
delegatecall
(which takes user-provided calldata) in apayable
function within a loop. This means that eachdelegatecall
within the for loop will retain themsg.value
of the transactionProof of Concept
The protocol does not currently use the
msg.value
in any meaningful way. it could be exploited to tamper with the system arithmetic.Tools Used
Manual Review
Assessed type
call/delegatecall