diff --git a/.gas-snapshot b/.gas-snapshot index 92b3688..8c247f2 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -3,29 +3,40 @@ OrderOriginPermit2Test:test_fillPermit2() (gas: 174849) OrderOriginPermit2Test:test_fillPermit2_multi() (gas: 510712) OrderOriginPermit2Test:test_initiatePermit2() (gas: 187207) OrderOriginPermit2Test:test_initiatePermit2_multi() (gas: 471069) -OrdersTest:test_fill_ERC20() (gas: 76399) -OrdersTest:test_fill_ETH() (gas: 73682) +OrdersFuzzTest:test_fill((address,uint256,address,uint32)) (runs: 278, μ: 30260, ~: 30007) +OrdersFuzzTest:test_fill_underflowETH(uint256,address,uint32) (runs: 275, μ: 59271, ~: 59271) +OrdersFuzzTest:test_fill_zeroETH(address,uint32) (runs: 278, μ: 23940, ~: 23940) +OrdersFuzzTest:test_initiate(uint256,(address,uint256),(address,uint256,address,uint32)) (runs: 278, μ: 34841, ~: 34841) +OrdersFuzzTest:test_orderExpired(uint256) (runs: 257, μ: 12317, ~: 12317) +OrdersFuzzTest:test_sweepERC20(address,address,uint256) (runs: 279, μ: 18790, ~: 18790) +OrdersFuzzTest:test_sweepETH(uint256,uint256,address,(address,uint256,address,uint32)) (runs: 277, μ: 74763, ~: 74902) +OrdersFuzzTest:test_underflowETH(uint256,uint256,(address,uint256,address,uint32)) (runs: 277, μ: 27227, ~: 27276) +OrdersTest:test_fill_ERC20() (gas: 76443) +OrdersTest:test_fill_ETH() (gas: 73637) OrdersTest:test_fill_both() (gas: 179085) OrdersTest:test_fill_multiETH() (gas: 141643) -OrdersTest:test_fill_underflowETH() (gas: 120697) -OrdersTest:test_initiate_ERC20() (gas: 90714) -OrdersTest:test_initiate_ETH() (gas: 53343) -OrdersTest:test_initiate_both() (gas: 130284) -OrdersTest:test_initiate_multiERC20() (gas: 186577) -OrdersTest:test_initiate_multiETH() (gas: 86465) -OrdersTest:test_orderExpired() (gas: 32938) -OrdersTest:test_sweepERC20() (gas: 63091) +OrdersTest:test_fill_underflowETH() (gas: 120741) +OrdersTest:test_initiate_ERC20() (gas: 90669) +OrdersTest:test_initiate_ETH() (gas: 53387) +OrdersTest:test_initiate_both() (gas: 130306) +OrdersTest:test_initiate_empty() (gas: 19547) +OrdersTest:test_initiate_emptyInputs() (gas: 34868) +OrdersTest:test_initiate_emptyOutputs() (gas: 75393) +OrdersTest:test_initiate_multiERC20() (gas: 186599) +OrdersTest:test_initiate_multiETH() (gas: 86422) +OrdersTest:test_orderExpired() (gas: 32872) +OrdersTest:test_sweepERC20() (gas: 63113) OrdersTest:test_sweepETH() (gas: 90901) -OrdersTest:test_underflowETH() (gas: 69496) -PassageFuzzTest:test_disallowedEnter(address,address,uint256) (runs: 266, μ: 19249, ~: 19249) -PassageFuzzTest:test_enter(uint256,address,uint256) (runs: 264, μ: 20920, ~: 20920) -PassageFuzzTest:test_enterToken(uint256,address,uint256) (runs: 264, μ: 66342, ~: 66342) -PassageFuzzTest:test_enterToken_defaultChain(address,uint256) (runs: 262, μ: 67125, ~: 67125) -PassageFuzzTest:test_enter_defaultChain(address,uint56) (runs: 267, μ: 21804, ~: 21804) -PassageFuzzTest:test_fallback(uint256,bytes) (runs: 264, μ: 21921, ~: 21921) -PassageFuzzTest:test_onlyTokenAdmin(address,address,bool,address,uint256) (runs: 267, μ: 16507, ~: 16507) -PassageFuzzTest:test_receive(uint256) (runs: 264, μ: 20719, ~: 20719) -PassageFuzzTest:test_withdraw(address,uint256) (runs: 262, μ: 67480, ~: 67501) +OrdersTest:test_underflowETH() (gas: 69429) +PassageFuzzTest:test_disallowedEnter(address,address,uint256) (runs: 278, μ: 19249, ~: 19249) +PassageFuzzTest:test_enter(uint256,address,uint256) (runs: 275, μ: 20920, ~: 20920) +PassageFuzzTest:test_enterToken(uint256,address,uint256) (runs: 275, μ: 66342, ~: 66342) +PassageFuzzTest:test_enterToken_defaultChain(address,uint256) (runs: 272, μ: 67125, ~: 67125) +PassageFuzzTest:test_enter_defaultChain(address,uint56) (runs: 279, μ: 21804, ~: 21804) +PassageFuzzTest:test_fallback(uint256,bytes) (runs: 272, μ: 21921, ~: 21921) +PassageFuzzTest:test_onlyTokenAdmin(address,address,bool,address,uint256) (runs: 279, μ: 16507, ~: 16507) +PassageFuzzTest:test_receive(uint256) (runs: 272, μ: 20719, ~: 20719) +PassageFuzzTest:test_withdraw(address,uint256) (runs: 272, μ: 67480, ~: 67503) PassagePermit2Test:test_disallowedEnterPermit2() (gas: 1130279) PassagePermit2Test:test_enterTokenPermit2() (gas: 150920) PassageTest:test_configureEnter() (gas: 141961) @@ -44,17 +55,17 @@ RollupPassageTest:test_exit() (gas: 23114) RollupPassageTest:test_exitToken() (gas: 53367) RollupPassageTest:test_fallback() (gas: 20654) RollupPassageTest:test_receive() (gas: 20206) -TransactFuzzTest:test_configureGas(uint256,uint256) (runs: 267, μ: 27723, ~: 27867) -TransactFuzzTest:test_enterTransact_defaultChain_emitsEnter(uint256,address,address,bytes,uint256,uint256,uint256,uint256) (runs: 260, μ: 70502, ~: 70928) -TransactFuzzTest:test_enterTransact_emitsEnter(uint256,address,bytes,uint256,uint256,uint256,uint256) (runs: 261, μ: 70250, ~: 70379) -TransactFuzzTest:test_enterTransact_emitsTransact(uint256,address,bytes,uint256,uint256,uint256,uint256) (runs: 261, μ: 70528, ~: 70634) -TransactFuzzTest:test_onlyGasAdmin(address,uint256,uint256) (runs: 267, μ: 12907, ~: 12907) -TransactFuzzTest:test_transact(uint256,address,address,bytes,uint256,uint256,uint256) (runs: 261, μ: 51025, ~: 51391) -TransactFuzzTest:test_transactWithValue_defaultChain_emitsEnter(address,bytes,uint256,uint256,uint256,uint256) (runs: 261, μ: 71793, ~: 72071) -TransactFuzzTest:test_transactWithValue_defaultChain_emitsTransact(address,bytes,uint256,uint256,uint256,uint256) (runs: 261, μ: 72160, ~: 72413) -TransactFuzzTest:test_transactWithValue_defaultChain_emitsTransact(uint256,address,address,bytes,uint256,uint256,uint256,uint256) (runs: 260, μ: 70765, ~: 71159) -TransactFuzzTest:test_transact_defaultChain(address,address,bytes,uint256,uint256,uint256) (runs: 262, μ: 52898, ~: 53103) -TransactFuzzTest:test_transact_perTransactGasLimit(uint256,address,bytes,uint256,uint256,uint256) (runs: 262, μ: 15980, ~: 15980) +TransactFuzzTest:test_configureGas(uint256,uint256) (runs: 279, μ: 27729, ~: 27867) +TransactFuzzTest:test_enterTransact_defaultChain_emitsEnter(uint256,address,address,bytes,uint256,uint256,uint256,uint256) (runs: 271, μ: 70519, ~: 70928) +TransactFuzzTest:test_enterTransact_emitsEnter(uint256,address,bytes,uint256,uint256,uint256,uint256) (runs: 271, μ: 70255, ~: 70379) +TransactFuzzTest:test_enterTransact_emitsTransact(uint256,address,bytes,uint256,uint256,uint256,uint256) (runs: 271, μ: 70532, ~: 70634) +TransactFuzzTest:test_onlyGasAdmin(address,uint256,uint256) (runs: 279, μ: 12907, ~: 12907) +TransactFuzzTest:test_transact(uint256,address,address,bytes,uint256,uint256,uint256) (runs: 272, μ: 51039, ~: 51391) +TransactFuzzTest:test_transactWithValue_defaultChain_emitsEnter(address,bytes,uint256,uint256,uint256,uint256) (runs: 272, μ: 71804, ~: 72071) +TransactFuzzTest:test_transactWithValue_defaultChain_emitsTransact(address,bytes,uint256,uint256,uint256,uint256) (runs: 272, μ: 72171, ~: 72413) +TransactFuzzTest:test_transactWithValue_defaultChain_emitsTransact(uint256,address,address,bytes,uint256,uint256,uint256,uint256) (runs: 271, μ: 70781, ~: 71159) +TransactFuzzTest:test_transact_defaultChain(address,address,bytes,uint256,uint256,uint256) (runs: 272, μ: 52905, ~: 53103) +TransactFuzzTest:test_transact_perTransactGasLimit(uint256,address,bytes,uint256,uint256,uint256) (runs: 264, μ: 15980, ~: 15980) TransactTest:test_configureGas() (gas: 27486) TransactTest:test_enterTransact() (gas: 127464) TransactTest:test_onlyGasAdmin() (gas: 9895) @@ -65,9 +76,9 @@ TransactTest:test_transact_globalGasLimit() (gas: 117828) TransactTest:test_transact_perTransactGasLimit() (gas: 34574) ZenithFuzzTest:test_addSequencer(uint256,(uint256,uint256,uint256,address,bytes32),bytes) (runs: 256, μ: 90812, ~: 90812) ZenithFuzzTest:test_badSignature((uint256,uint256,uint256,address,bytes32),(uint256,uint256,uint256,address,bytes32),bytes) (runs: 256, μ: 40335, ~: 40334) -ZenithFuzzTest:test_incorrectHostBlock((uint256,uint256,uint256,address,bytes32),bytes) (runs: 267, μ: 22120, ~: 22115) +ZenithFuzzTest:test_incorrectHostBlock((uint256,uint256,uint256,address,bytes32),bytes) (runs: 279, μ: 22120, ~: 22115) ZenithFuzzTest:test_notSequencer(uint256,(uint256,uint256,uint256,address,bytes32),bytes) (runs: 256, μ: 32184, ~: 32183) -ZenithFuzzTest:test_notSequencerAdmin(address,address) (runs: 267, μ: 14423, ~: 14423) +ZenithFuzzTest:test_notSequencerAdmin(address,address) (runs: 279, μ: 14423, ~: 14423) ZenithFuzzTest:test_submitBlock((uint256,uint256,uint256,address,bytes32),bytes) (runs: 256, μ: 61351, ~: 61351) ZenithTest:test_addSequencer() (gas: 98616) ZenithTest:test_badSignature() (gas: 46355) diff --git a/test/fuzz-rollup/OrdersFuzz.t.sol b/test/fuzz-rollup/OrdersFuzz.t.sol new file mode 100644 index 0000000..a6fc928 --- /dev/null +++ b/test/fuzz-rollup/OrdersFuzz.t.sol @@ -0,0 +1,188 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.26; + +// test contracts +import {IOrders} from "../../src/orders/IOrders.sol"; +import {RollupOrders} from "../../src/orders/RollupOrders.sol"; +import {OrderOrigin} from "../../src/orders/OrderOrigin.sol"; +// utils +import {TestERC20} from "../Helpers.t.sol"; +import {SignetStdTest} from "../SignetStdTest.t.sol"; +import {ERC20} from "openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; +import {Test, console2} from "forge-std/Test.sol"; + +contract OrdersFuzzTest is SignetStdTest { + RollupOrders public target; + + event Filled(IOrders.Output[] outputs); + + event Order(uint256 deadline, IOrders.Input[] inputs, IOrders.Output[] outputs); + + event Sweep(address indexed recipient, address indexed token, uint256 amount); + + function setUp() public virtual { + target = ROLLUP_ORDERS; + } + + // input ERC20 + function test_initiate(uint256 deadline, IOrders.Input memory input, IOrders.Output memory output) public { + vm.assume(deadline >= block.timestamp); + + uint256 ethAmount = 0; + + if (input.token == address(0)) { + ethAmount += input.amount; + } else { + vm.mockCall( + input.token, + abi.encodeWithSelector(ERC20.transferFrom.selector, address(this), address(target), input.amount), + abi.encode(true) + ); + vm.expectCall( + input.token, + abi.encodeWithSelector(ERC20.transferFrom.selector, address(this), address(target), input.amount) + ); + } + + IOrders.Input[] memory inputs = new IOrders.Input[](1); + inputs[0] = input; + IOrders.Output[] memory outputs = new IOrders.Output[](1); + outputs[0] = output; + + vm.deal(address(this), ethAmount + 1 ether); // give contract some ETH + + vm.expectEmit(); + emit Order(deadline, inputs, outputs); + target.initiate{value: ethAmount}(deadline, inputs, outputs); + + assertEq(address(target).balance, ethAmount); + } + + function test_underflowETH(uint256 deadline, uint256 amount, IOrders.Output memory output) public { + vm.assume(deadline >= block.timestamp); + vm.assume(amount < type(uint256).max); // prevent overflow in vm.deal + vm.deal(address(this), amount); // give contract some ETH + + IOrders.Input[] memory inputs = new IOrders.Input[](2); + inputs[0] = IOrders.Input(address(0), amount); + inputs[1] = IOrders.Input(address(0), 1); + + IOrders.Output[] memory outputs = new IOrders.Output[](1); + outputs[0] = output; + + // total ETH inputs should be amount + 1; function should underflow only sending amount + vm.expectRevert(); + target.initiate{value: amount}(deadline, inputs, outputs); + } + + function test_orderExpired(uint256 deadline) public { + vm.assume(deadline < block.timestamp); + + IOrders.Input[] memory inputs = new IOrders.Input[](0); + IOrders.Output[] memory outputs = new IOrders.Output[](0); + + vm.expectRevert(OrderOrigin.OrderExpired.selector); + target.initiate(deadline, inputs, outputs); + } + + function test_sweepETH(uint256 deadline, uint256 amount, address recipient, IOrders.Output memory output) public { + vm.assume(deadline >= block.timestamp); + vm.assume(amount < type(uint256).max - 1000 ether); // prevent overflow in vm.deal + vm.assume(recipient.code.length == 0 && uint160(recipient) > 0x09); // recipient is non-precompile EOA + vm.assume(address(recipient).balance == 0); // recipient starts with zero balance + vm.deal(address(this), amount); // give contract some ETH + + // initiate an ETH order + IOrders.Input[] memory inputs = new IOrders.Input[](1); + inputs[0] = IOrders.Input(address(0), amount); + IOrders.Output[] memory outputs = new IOrders.Output[](1); + outputs[0] = output; + target.initiate{value: amount}(deadline, inputs, outputs); + + assertEq(address(target).balance, amount); + + // sweep ETH + vm.expectEmit(); + emit Sweep(recipient, address(0), amount); + target.sweep(recipient, address(0), amount); + + assertEq(address(target).balance, 0); + assertEq(recipient.balance, amount); + } + + function test_sweepERC20(address recipient, address token, uint256 amount) public { + vm.assume(token != address(0)); + + vm.mockCall( + token, abi.encodeWithSelector(ERC20.transfer.selector, address(recipient), amount), abi.encode(true) + ); + vm.expectCall(token, abi.encodeWithSelector(ERC20.transfer.selector, recipient, amount)); + + // sweep ERC20 + vm.expectEmit(); + emit Sweep(recipient, token, amount); + target.sweep(recipient, token, amount); + } + + function test_fill(IOrders.Output memory output) public { + vm.assume(output.amount < type(uint256).max - 1000 ether); // prevent overflow in vm.deal + vm.assume(output.recipient.code.length == 0 && uint160(output.recipient) > 0x09); // recipient is non-precompile EOA + vm.assume(output.token != address(vm)); + vm.deal(address(this), output.amount); // give contract some ETH + + uint256 ethAmount = 0; + if (output.token == address(0)) { + ethAmount += output.amount; + } else { + vm.mockCall( + output.token, + abi.encodeWithSelector( + ERC20.transferFrom.selector, address(this), address(output.recipient), output.amount + ), + abi.encode(true) + ); + vm.expectCall( + output.token, + abi.encodeWithSelector( + ERC20.transferFrom.selector, address(this), address(output.recipient), output.amount + ) + ); + } + + IOrders.Output[] memory outputs = new IOrders.Output[](1); + outputs[0] = output; + + vm.expectEmit(); + emit Filled(outputs); + target.fill{value: ethAmount}(outputs); + + // ETH is transferred to recipient + assertEq(output.recipient.balance, ethAmount); + } + + function test_fill_underflowETH(uint256 amount, address recipient, uint32 chainId) public { + vm.assume(amount > 0 && amount < type(uint256).max - 1000 ether); // prevent overflow in vm.deal + vm.assume(recipient.code.length == 0 && uint160(recipient) > 0x09); // recipient is non-precompile EOA + vm.deal(address(this), amount); // give contract some ETH + + IOrders.Output[] memory outputs = new IOrders.Output[](2); + outputs[0] = IOrders.Output(address(0), amount, recipient, chainId); + outputs[1] = IOrders.Output(address(0), 1, recipient, chainId); + + // total ETH outputs should be `amount` + 1; function should underflow only sending `amount` + vm.expectRevert(); + target.fill{value: amount}(outputs); + } + + function test_fill_zeroETH(address recipient, uint32 chainId) public { + vm.assume(recipient.code.length == 0 && uint160(recipient) > 0x09); // recipient is non-precompile EOA + + IOrders.Output memory output = IOrders.Output(address(0), 0, recipient, chainId); + IOrders.Output[] memory outputs = new IOrders.Output[](1); + outputs[0] = output; + + vm.expectEmit(); + emit Filled(outputs); + target.fill(outputs); + } +} diff --git a/test/rollup/Orders.t.sol b/test/rollup/Orders.t.sol index e1626d5..4e876ce 100644 --- a/test/rollup/Orders.t.sol +++ b/test/rollup/Orders.t.sol @@ -55,6 +55,40 @@ contract OrdersTest is SignetStdTest { deadline = block.timestamp; } + // zero inputs or outptus + function test_initiate_empty() public { + IOrders.Input[] memory emptyInputs = new IOrders.Input[](0); + IOrders.Output[] memory emptyOutputs = new IOrders.Output[](0); + + // expect Order event is initiated, ERC20 is transferred + vm.expectEmit(); + emit Order(deadline, emptyInputs, emptyOutputs); + target.initiate(deadline, emptyInputs, emptyOutputs); + } + + // empty inputs, non-empty outputs + function test_initiate_emptyInputs() public { + IOrders.Input[] memory emptyInputs = new IOrders.Input[](0); + + // expect Order event is initiated, ERC20 is transferred + vm.expectEmit(); + emit Order(deadline, emptyInputs, outputs); + target.initiate(deadline, emptyInputs, outputs); + } + + // empty outputs, non-empty inputs + function test_initiate_emptyOutputs() public { + IOrders.Output[] memory emptyOutputs = new IOrders.Output[](0); + + // expect Order event is initiated, ERC20 is transferred + vm.expectEmit(); + emit Order(deadline, inputs, emptyOutputs); + vm.expectCall( + token, abi.encodeWithSelector(ERC20.transferFrom.selector, address(this), address(target), amount) + ); + target.initiate(deadline, inputs, emptyOutputs); + } + // input ERC20 function test_initiate_ERC20() public { // expect Order event is initiated, ERC20 is transferred