Files

220 lines
8.6 KiB
Solidity

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "forge-std/Test.sol";
import {MockMulticall, RevertContract} from "./mocks/MockMulticall.sol";
contract MulticallTest is Test {
MockMulticall multicall;
function setUp() public {
multicall = new MockMulticall();
}
function test_multicall() public {
bytes[] memory calls = new bytes[](2);
calls[0] = abi.encodeWithSelector(MockMulticall(multicall).functionThatReturnsTuple.selector, 10, 20);
calls[1] = abi.encodeWithSelector(MockMulticall(multicall).functionThatReturnsTuple.selector, 1, 2);
bytes[] memory results = multicall.multicall(calls);
(uint256 a, uint256 b) = abi.decode(results[0], (uint256, uint256));
assertEq(a, 10);
assertEq(b, 20);
(a, b) = abi.decode(results[1], (uint256, uint256));
assertEq(a, 1);
assertEq(b, 2);
}
function test_multicall_firstRevert() public {
bytes[] memory calls = new bytes[](2);
calls[0] = abi.encodeWithSelector(
MockMulticall(multicall).functionThatRevertsWithString.selector, "First call failed"
);
calls[1] = abi.encodeWithSelector(MockMulticall(multicall).functionThatReturnsTuple.selector, 1, 2);
vm.expectRevert("First call failed");
multicall.multicall(calls);
}
function test_multicall_secondRevert() public {
bytes[] memory calls = new bytes[](2);
calls[0] = abi.encodeWithSelector(MockMulticall(multicall).functionThatReturnsTuple.selector, 1, 2);
calls[1] = abi.encodeWithSelector(
MockMulticall(multicall).functionThatRevertsWithString.selector, "Second call failed"
);
vm.expectRevert("Second call failed");
multicall.multicall(calls);
}
function test_multicall_payableStoresMsgValue() public {
assertEq(address(multicall).balance, 0);
bytes[] memory calls = new bytes[](1);
calls[0] = abi.encodeWithSelector(MockMulticall(multicall).payableStoresMsgValue.selector);
multicall.multicall{value: 100}(calls);
assertEq(address(multicall).balance, 100);
assertEq(multicall.msgValue(), 100);
}
function test_multicall_returnSender() public {
bytes[] memory calls = new bytes[](1);
calls[0] = abi.encodeWithSelector(MockMulticall(multicall).returnSender.selector);
bytes[] memory results = multicall.multicall(calls);
address sender = abi.decode(results[0], (address));
assertEq(sender, address(this));
}
function test_multicall_returnSender_prank() public {
address alice = makeAddr("ALICE");
bytes[] memory calls = new bytes[](1);
calls[0] = abi.encodeWithSelector(MockMulticall(multicall).returnSender.selector, alice);
vm.prank(alice);
bytes[] memory results = multicall.multicall(calls);
address sender = abi.decode(results[0], (address));
assertEq(sender, alice);
}
function test_multicall_double_send() public {
bytes[] memory calls = new bytes[](2);
calls[0] = abi.encodeWithSelector(MockMulticall(multicall).payableStoresMsgValue.selector);
calls[1] = abi.encodeWithSelector(MockMulticall(multicall).payableStoresMsgValue.selector);
multicall.multicall{value: 100}(calls);
assertEq(address(multicall).balance, 100);
assertEq(multicall.msgValue(), 100);
}
function test_multicall_unpayableRevert() public {
// first call is payable, second is not which causes a revert
bytes[] memory calls = new bytes[](2);
calls[0] = abi.encodeWithSelector(MockMulticall(multicall).payableStoresMsgValue.selector);
calls[1] = abi.encodeWithSelector(MockMulticall(multicall).functionThatReturnsTuple.selector, 10, 20);
vm.expectRevert();
multicall.multicall{value: 100}(calls);
}
function test_multicall_bothPayable() public {
// msg.value is provided to both calls
bytes[] memory calls = new bytes[](2);
calls[0] = abi.encodeWithSelector(MockMulticall(multicall).payableStoresMsgValue.selector);
calls[1] = abi.encodeWithSelector(MockMulticall(multicall).payableStoresMsgValueDouble.selector);
multicall.multicall{value: 100}(calls);
assertEq(address(multicall).balance, 100);
assertEq(multicall.msgValue(), 100);
assertEq(multicall.msgValueDouble(), 200);
}
// revert bubbling
function test_multicall_bubbleRevert_string() public {
bytes[] memory calls = new bytes[](1);
calls[0] =
abi.encodeWithSelector(MockMulticall(multicall).functionThatRevertsWithString.selector, "errorString");
vm.expectRevert("errorString");
multicall.multicall(calls);
}
function test_multicall_bubbleRevert_4bytes() public {
bytes[] memory calls = new bytes[](1);
calls[0] = abi.encodeWithSelector(MockMulticall(multicall).revertWith4Bytes.selector);
// revert is caught
vm.expectRevert(MockMulticall.Error4Bytes.selector);
multicall.multicall(calls);
// confirm expected length of the revert
try multicall.revertWith4Bytes() {}
catch (bytes memory reason) {
assertEq(reason.length, 4);
}
}
function test_fuzz_multicall_bubbleRevert_36bytes(uint8 num) public {
bytes[] memory calls = new bytes[](1);
calls[0] = abi.encodeWithSelector(MockMulticall(multicall).revertWith36Bytes.selector, num);
// revert is caught
vm.expectRevert(abi.encodeWithSelector(MockMulticall.Error36Bytes.selector, num));
multicall.multicall(calls);
// confirm expected length of the revert
try multicall.revertWith36Bytes(num) {}
catch (bytes memory reason) {
assertEq(reason.length, 36);
}
}
function test_fuzz_multicall_bubbleRevert_68bytes(uint256 a, uint256 b) public {
bytes[] memory calls = new bytes[](1);
calls[0] = abi.encodeWithSelector(MockMulticall(multicall).revertWith68Bytes.selector, a, b);
// revert is caught
vm.expectRevert(abi.encodeWithSelector(MockMulticall.Error68Bytes.selector, a, b));
multicall.multicall(calls);
// confirm expected length of the revert
try multicall.revertWith68Bytes(a, b) {}
catch (bytes memory reason) {
assertEq(reason.length, 68);
}
}
function test_fuzz_multicall_bubbleRevert_arbitraryBytes(uint16 length) public {
length = uint16(bound(length, 0, 4096));
bytes memory data = new bytes(length);
for (uint256 i = 0; i < data.length; i++) {
data[i] = bytes1(uint8(i));
}
bytes[] memory calls = new bytes[](1);
calls[0] = abi.encodeWithSelector(MockMulticall(multicall).revertWithBytes.selector, data);
// revert is caught
vm.expectRevert(abi.encodeWithSelector(MockMulticall.ErrorBytes.selector, data));
multicall.multicall(calls);
// confirm expected length of the revert
try multicall.revertWithBytes(data) {}
catch (bytes memory reason) {
// errors with 0 bytes are by default 64 bytes of data (length & pointer?) + 4 bytes of selector
if (length == 0) {
assertEq(reason.length, 68);
} else {
uint256 expectedLength = 64 + 4; // default length + selector
// 32 bytes added to the reason for each 32 bytes of data
expectedLength += (((data.length - 1) / 32) + 1) * 32;
assertEq(reason.length, expectedLength);
}
}
}
function test_multicall_bubbleRevert_externalRevertString() public {
bytes[] memory calls = new bytes[](2);
calls[0] = abi.encodeWithSelector(MockMulticall(multicall).externalRevertString.selector, "errorString");
vm.expectRevert("errorString");
multicall.multicall(calls);
}
function test_multicall_bubbleRevert_externalRevertSimple() public {
bytes[] memory calls = new bytes[](1);
calls[0] = abi.encodeWithSelector(MockMulticall(multicall).externalRevertError1.selector);
vm.expectRevert(RevertContract.Error1.selector);
multicall.multicall(calls);
}
function test_multicall_bubbleRevert_externalRevertWithParams(uint256 a, uint256 b) public {
bytes[] memory calls = new bytes[](1);
calls[0] = abi.encodeWithSelector(MockMulticall(multicall).externalRevertError2.selector, a, b);
vm.expectRevert(abi.encodeWithSelector(RevertContract.Error2.selector, a, b));
multicall.multicall(calls);
}
}