Skip to content

Commit b5c1663

Browse files
authored
feat: Orders fuzz tests (#91)
* feat: Orders fuzz tests * update snapshot
1 parent fe0dbae commit b5c1663

File tree

3 files changed

+266
-33
lines changed

3 files changed

+266
-33
lines changed

.gas-snapshot

Lines changed: 44 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -3,29 +3,40 @@ OrderOriginPermit2Test:test_fillPermit2() (gas: 174849)
33
OrderOriginPermit2Test:test_fillPermit2_multi() (gas: 510712)
44
OrderOriginPermit2Test:test_initiatePermit2() (gas: 187207)
55
OrderOriginPermit2Test:test_initiatePermit2_multi() (gas: 471069)
6-
OrdersTest:test_fill_ERC20() (gas: 76399)
7-
OrdersTest:test_fill_ETH() (gas: 73682)
6+
OrdersFuzzTest:test_fill((address,uint256,address,uint32)) (runs: 278, μ: 30260, ~: 30007)
7+
OrdersFuzzTest:test_fill_underflowETH(uint256,address,uint32) (runs: 275, μ: 59271, ~: 59271)
8+
OrdersFuzzTest:test_fill_zeroETH(address,uint32) (runs: 278, μ: 23940, ~: 23940)
9+
OrdersFuzzTest:test_initiate(uint256,(address,uint256),(address,uint256,address,uint32)) (runs: 278, μ: 34841, ~: 34841)
10+
OrdersFuzzTest:test_orderExpired(uint256) (runs: 257, μ: 12317, ~: 12317)
11+
OrdersFuzzTest:test_sweepERC20(address,address,uint256) (runs: 279, μ: 18790, ~: 18790)
12+
OrdersFuzzTest:test_sweepETH(uint256,uint256,address,(address,uint256,address,uint32)) (runs: 277, μ: 74763, ~: 74902)
13+
OrdersFuzzTest:test_underflowETH(uint256,uint256,(address,uint256,address,uint32)) (runs: 277, μ: 27227, ~: 27276)
14+
OrdersTest:test_fill_ERC20() (gas: 76443)
15+
OrdersTest:test_fill_ETH() (gas: 73637)
816
OrdersTest:test_fill_both() (gas: 179085)
917
OrdersTest:test_fill_multiETH() (gas: 141643)
10-
OrdersTest:test_fill_underflowETH() (gas: 120697)
11-
OrdersTest:test_initiate_ERC20() (gas: 90714)
12-
OrdersTest:test_initiate_ETH() (gas: 53343)
13-
OrdersTest:test_initiate_both() (gas: 130284)
14-
OrdersTest:test_initiate_multiERC20() (gas: 186577)
15-
OrdersTest:test_initiate_multiETH() (gas: 86465)
16-
OrdersTest:test_orderExpired() (gas: 32938)
17-
OrdersTest:test_sweepERC20() (gas: 63091)
18+
OrdersTest:test_fill_underflowETH() (gas: 120741)
19+
OrdersTest:test_initiate_ERC20() (gas: 90669)
20+
OrdersTest:test_initiate_ETH() (gas: 53387)
21+
OrdersTest:test_initiate_both() (gas: 130306)
22+
OrdersTest:test_initiate_empty() (gas: 19547)
23+
OrdersTest:test_initiate_emptyInputs() (gas: 34868)
24+
OrdersTest:test_initiate_emptyOutputs() (gas: 75393)
25+
OrdersTest:test_initiate_multiERC20() (gas: 186599)
26+
OrdersTest:test_initiate_multiETH() (gas: 86422)
27+
OrdersTest:test_orderExpired() (gas: 32872)
28+
OrdersTest:test_sweepERC20() (gas: 63113)
1829
OrdersTest:test_sweepETH() (gas: 90901)
19-
OrdersTest:test_underflowETH() (gas: 69496)
20-
PassageFuzzTest:test_disallowedEnter(address,address,uint256) (runs: 266, μ: 19249, ~: 19249)
21-
PassageFuzzTest:test_enter(uint256,address,uint256) (runs: 264, μ: 20920, ~: 20920)
22-
PassageFuzzTest:test_enterToken(uint256,address,uint256) (runs: 264, μ: 66342, ~: 66342)
23-
PassageFuzzTest:test_enterToken_defaultChain(address,uint256) (runs: 262, μ: 67125, ~: 67125)
24-
PassageFuzzTest:test_enter_defaultChain(address,uint56) (runs: 267, μ: 21804, ~: 21804)
25-
PassageFuzzTest:test_fallback(uint256,bytes) (runs: 264, μ: 21921, ~: 21921)
26-
PassageFuzzTest:test_onlyTokenAdmin(address,address,bool,address,uint256) (runs: 267, μ: 16507, ~: 16507)
27-
PassageFuzzTest:test_receive(uint256) (runs: 264, μ: 20719, ~: 20719)
28-
PassageFuzzTest:test_withdraw(address,uint256) (runs: 262, μ: 67480, ~: 67501)
30+
OrdersTest:test_underflowETH() (gas: 69429)
31+
PassageFuzzTest:test_disallowedEnter(address,address,uint256) (runs: 278, μ: 19249, ~: 19249)
32+
PassageFuzzTest:test_enter(uint256,address,uint256) (runs: 275, μ: 20920, ~: 20920)
33+
PassageFuzzTest:test_enterToken(uint256,address,uint256) (runs: 275, μ: 66342, ~: 66342)
34+
PassageFuzzTest:test_enterToken_defaultChain(address,uint256) (runs: 272, μ: 67125, ~: 67125)
35+
PassageFuzzTest:test_enter_defaultChain(address,uint56) (runs: 279, μ: 21804, ~: 21804)
36+
PassageFuzzTest:test_fallback(uint256,bytes) (runs: 272, μ: 21921, ~: 21921)
37+
PassageFuzzTest:test_onlyTokenAdmin(address,address,bool,address,uint256) (runs: 279, μ: 16507, ~: 16507)
38+
PassageFuzzTest:test_receive(uint256) (runs: 272, μ: 20719, ~: 20719)
39+
PassageFuzzTest:test_withdraw(address,uint256) (runs: 272, μ: 67480, ~: 67503)
2940
PassagePermit2Test:test_disallowedEnterPermit2() (gas: 1130279)
3041
PassagePermit2Test:test_enterTokenPermit2() (gas: 150920)
3142
PassageTest:test_configureEnter() (gas: 141961)
@@ -44,17 +55,17 @@ RollupPassageTest:test_exit() (gas: 23114)
4455
RollupPassageTest:test_exitToken() (gas: 53367)
4556
RollupPassageTest:test_fallback() (gas: 20654)
4657
RollupPassageTest:test_receive() (gas: 20206)
47-
TransactFuzzTest:test_configureGas(uint256,uint256) (runs: 267, μ: 27723, ~: 27867)
48-
TransactFuzzTest:test_enterTransact_defaultChain_emitsEnter(uint256,address,address,bytes,uint256,uint256,uint256,uint256) (runs: 260, μ: 70502, ~: 70928)
49-
TransactFuzzTest:test_enterTransact_emitsEnter(uint256,address,bytes,uint256,uint256,uint256,uint256) (runs: 261, μ: 70250, ~: 70379)
50-
TransactFuzzTest:test_enterTransact_emitsTransact(uint256,address,bytes,uint256,uint256,uint256,uint256) (runs: 261, μ: 70528, ~: 70634)
51-
TransactFuzzTest:test_onlyGasAdmin(address,uint256,uint256) (runs: 267, μ: 12907, ~: 12907)
52-
TransactFuzzTest:test_transact(uint256,address,address,bytes,uint256,uint256,uint256) (runs: 261, μ: 51025, ~: 51391)
53-
TransactFuzzTest:test_transactWithValue_defaultChain_emitsEnter(address,bytes,uint256,uint256,uint256,uint256) (runs: 261, μ: 71793, ~: 72071)
54-
TransactFuzzTest:test_transactWithValue_defaultChain_emitsTransact(address,bytes,uint256,uint256,uint256,uint256) (runs: 261, μ: 72160, ~: 72413)
55-
TransactFuzzTest:test_transactWithValue_defaultChain_emitsTransact(uint256,address,address,bytes,uint256,uint256,uint256,uint256) (runs: 260, μ: 70765, ~: 71159)
56-
TransactFuzzTest:test_transact_defaultChain(address,address,bytes,uint256,uint256,uint256) (runs: 262, μ: 52898, ~: 53103)
57-
TransactFuzzTest:test_transact_perTransactGasLimit(uint256,address,bytes,uint256,uint256,uint256) (runs: 262, μ: 15980, ~: 15980)
58+
TransactFuzzTest:test_configureGas(uint256,uint256) (runs: 279, μ: 27729, ~: 27867)
59+
TransactFuzzTest:test_enterTransact_defaultChain_emitsEnter(uint256,address,address,bytes,uint256,uint256,uint256,uint256) (runs: 271, μ: 70519, ~: 70928)
60+
TransactFuzzTest:test_enterTransact_emitsEnter(uint256,address,bytes,uint256,uint256,uint256,uint256) (runs: 271, μ: 70255, ~: 70379)
61+
TransactFuzzTest:test_enterTransact_emitsTransact(uint256,address,bytes,uint256,uint256,uint256,uint256) (runs: 271, μ: 70532, ~: 70634)
62+
TransactFuzzTest:test_onlyGasAdmin(address,uint256,uint256) (runs: 279, μ: 12907, ~: 12907)
63+
TransactFuzzTest:test_transact(uint256,address,address,bytes,uint256,uint256,uint256) (runs: 272, μ: 51039, ~: 51391)
64+
TransactFuzzTest:test_transactWithValue_defaultChain_emitsEnter(address,bytes,uint256,uint256,uint256,uint256) (runs: 272, μ: 71804, ~: 72071)
65+
TransactFuzzTest:test_transactWithValue_defaultChain_emitsTransact(address,bytes,uint256,uint256,uint256,uint256) (runs: 272, μ: 72171, ~: 72413)
66+
TransactFuzzTest:test_transactWithValue_defaultChain_emitsTransact(uint256,address,address,bytes,uint256,uint256,uint256,uint256) (runs: 271, μ: 70781, ~: 71159)
67+
TransactFuzzTest:test_transact_defaultChain(address,address,bytes,uint256,uint256,uint256) (runs: 272, μ: 52905, ~: 53103)
68+
TransactFuzzTest:test_transact_perTransactGasLimit(uint256,address,bytes,uint256,uint256,uint256) (runs: 264, μ: 15980, ~: 15980)
5869
TransactTest:test_configureGas() (gas: 27486)
5970
TransactTest:test_enterTransact() (gas: 127464)
6071
TransactTest:test_onlyGasAdmin() (gas: 9895)
@@ -65,9 +76,9 @@ TransactTest:test_transact_globalGasLimit() (gas: 117828)
6576
TransactTest:test_transact_perTransactGasLimit() (gas: 34574)
6677
ZenithFuzzTest:test_addSequencer(uint256,(uint256,uint256,uint256,address,bytes32),bytes) (runs: 256, μ: 90812, ~: 90812)
6778
ZenithFuzzTest:test_badSignature((uint256,uint256,uint256,address,bytes32),(uint256,uint256,uint256,address,bytes32),bytes) (runs: 256, μ: 40335, ~: 40334)
68-
ZenithFuzzTest:test_incorrectHostBlock((uint256,uint256,uint256,address,bytes32),bytes) (runs: 267, μ: 22120, ~: 22115)
79+
ZenithFuzzTest:test_incorrectHostBlock((uint256,uint256,uint256,address,bytes32),bytes) (runs: 279, μ: 22120, ~: 22115)
6980
ZenithFuzzTest:test_notSequencer(uint256,(uint256,uint256,uint256,address,bytes32),bytes) (runs: 256, μ: 32184, ~: 32183)
70-
ZenithFuzzTest:test_notSequencerAdmin(address,address) (runs: 267, μ: 14423, ~: 14423)
81+
ZenithFuzzTest:test_notSequencerAdmin(address,address) (runs: 279, μ: 14423, ~: 14423)
7182
ZenithFuzzTest:test_submitBlock((uint256,uint256,uint256,address,bytes32),bytes) (runs: 256, μ: 61351, ~: 61351)
7283
ZenithTest:test_addSequencer() (gas: 98616)
7384
ZenithTest:test_badSignature() (gas: 46355)

test/fuzz-rollup/OrdersFuzz.t.sol

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
// SPDX-License-Identifier: MIT OR Apache-2.0
2+
pragma solidity 0.8.26;
3+
4+
// test contracts
5+
import {IOrders} from "../../src/orders/IOrders.sol";
6+
import {RollupOrders} from "../../src/orders/RollupOrders.sol";
7+
import {OrderOrigin} from "../../src/orders/OrderOrigin.sol";
8+
// utils
9+
import {TestERC20} from "../Helpers.t.sol";
10+
import {SignetStdTest} from "../SignetStdTest.t.sol";
11+
import {ERC20} from "openzeppelin-contracts/contracts/token/ERC20/ERC20.sol";
12+
import {Test, console2} from "forge-std/Test.sol";
13+
14+
contract OrdersFuzzTest is SignetStdTest {
15+
RollupOrders public target;
16+
17+
event Filled(IOrders.Output[] outputs);
18+
19+
event Order(uint256 deadline, IOrders.Input[] inputs, IOrders.Output[] outputs);
20+
21+
event Sweep(address indexed recipient, address indexed token, uint256 amount);
22+
23+
function setUp() public virtual {
24+
target = ROLLUP_ORDERS;
25+
}
26+
27+
// input ERC20
28+
function test_initiate(uint256 deadline, IOrders.Input memory input, IOrders.Output memory output) public {
29+
vm.assume(deadline >= block.timestamp);
30+
31+
uint256 ethAmount = 0;
32+
33+
if (input.token == address(0)) {
34+
ethAmount += input.amount;
35+
} else {
36+
vm.mockCall(
37+
input.token,
38+
abi.encodeWithSelector(ERC20.transferFrom.selector, address(this), address(target), input.amount),
39+
abi.encode(true)
40+
);
41+
vm.expectCall(
42+
input.token,
43+
abi.encodeWithSelector(ERC20.transferFrom.selector, address(this), address(target), input.amount)
44+
);
45+
}
46+
47+
IOrders.Input[] memory inputs = new IOrders.Input[](1);
48+
inputs[0] = input;
49+
IOrders.Output[] memory outputs = new IOrders.Output[](1);
50+
outputs[0] = output;
51+
52+
vm.deal(address(this), ethAmount + 1 ether); // give contract some ETH
53+
54+
vm.expectEmit();
55+
emit Order(deadline, inputs, outputs);
56+
target.initiate{value: ethAmount}(deadline, inputs, outputs);
57+
58+
assertEq(address(target).balance, ethAmount);
59+
}
60+
61+
function test_underflowETH(uint256 deadline, uint256 amount, IOrders.Output memory output) public {
62+
vm.assume(deadline >= block.timestamp);
63+
vm.assume(amount < type(uint256).max); // prevent overflow in vm.deal
64+
vm.deal(address(this), amount); // give contract some ETH
65+
66+
IOrders.Input[] memory inputs = new IOrders.Input[](2);
67+
inputs[0] = IOrders.Input(address(0), amount);
68+
inputs[1] = IOrders.Input(address(0), 1);
69+
70+
IOrders.Output[] memory outputs = new IOrders.Output[](1);
71+
outputs[0] = output;
72+
73+
// total ETH inputs should be amount + 1; function should underflow only sending amount
74+
vm.expectRevert();
75+
target.initiate{value: amount}(deadline, inputs, outputs);
76+
}
77+
78+
function test_orderExpired(uint256 deadline) public {
79+
vm.assume(deadline < block.timestamp);
80+
81+
IOrders.Input[] memory inputs = new IOrders.Input[](0);
82+
IOrders.Output[] memory outputs = new IOrders.Output[](0);
83+
84+
vm.expectRevert(OrderOrigin.OrderExpired.selector);
85+
target.initiate(deadline, inputs, outputs);
86+
}
87+
88+
function test_sweepETH(uint256 deadline, uint256 amount, address recipient, IOrders.Output memory output) public {
89+
vm.assume(deadline >= block.timestamp);
90+
vm.assume(amount < type(uint256).max - 1000 ether); // prevent overflow in vm.deal
91+
vm.assume(recipient.code.length == 0 && uint160(recipient) > 0x09); // recipient is non-precompile EOA
92+
vm.assume(address(recipient).balance == 0); // recipient starts with zero balance
93+
vm.deal(address(this), amount); // give contract some ETH
94+
95+
// initiate an ETH order
96+
IOrders.Input[] memory inputs = new IOrders.Input[](1);
97+
inputs[0] = IOrders.Input(address(0), amount);
98+
IOrders.Output[] memory outputs = new IOrders.Output[](1);
99+
outputs[0] = output;
100+
target.initiate{value: amount}(deadline, inputs, outputs);
101+
102+
assertEq(address(target).balance, amount);
103+
104+
// sweep ETH
105+
vm.expectEmit();
106+
emit Sweep(recipient, address(0), amount);
107+
target.sweep(recipient, address(0), amount);
108+
109+
assertEq(address(target).balance, 0);
110+
assertEq(recipient.balance, amount);
111+
}
112+
113+
function test_sweepERC20(address recipient, address token, uint256 amount) public {
114+
vm.assume(token != address(0));
115+
116+
vm.mockCall(
117+
token, abi.encodeWithSelector(ERC20.transfer.selector, address(recipient), amount), abi.encode(true)
118+
);
119+
vm.expectCall(token, abi.encodeWithSelector(ERC20.transfer.selector, recipient, amount));
120+
121+
// sweep ERC20
122+
vm.expectEmit();
123+
emit Sweep(recipient, token, amount);
124+
target.sweep(recipient, token, amount);
125+
}
126+
127+
function test_fill(IOrders.Output memory output) public {
128+
vm.assume(output.amount < type(uint256).max - 1000 ether); // prevent overflow in vm.deal
129+
vm.assume(output.recipient.code.length == 0 && uint160(output.recipient) > 0x09); // recipient is non-precompile EOA
130+
vm.assume(output.token != address(vm));
131+
vm.deal(address(this), output.amount); // give contract some ETH
132+
133+
uint256 ethAmount = 0;
134+
if (output.token == address(0)) {
135+
ethAmount += output.amount;
136+
} else {
137+
vm.mockCall(
138+
output.token,
139+
abi.encodeWithSelector(
140+
ERC20.transferFrom.selector, address(this), address(output.recipient), output.amount
141+
),
142+
abi.encode(true)
143+
);
144+
vm.expectCall(
145+
output.token,
146+
abi.encodeWithSelector(
147+
ERC20.transferFrom.selector, address(this), address(output.recipient), output.amount
148+
)
149+
);
150+
}
151+
152+
IOrders.Output[] memory outputs = new IOrders.Output[](1);
153+
outputs[0] = output;
154+
155+
vm.expectEmit();
156+
emit Filled(outputs);
157+
target.fill{value: ethAmount}(outputs);
158+
159+
// ETH is transferred to recipient
160+
assertEq(output.recipient.balance, ethAmount);
161+
}
162+
163+
function test_fill_underflowETH(uint256 amount, address recipient, uint32 chainId) public {
164+
vm.assume(amount > 0 && amount < type(uint256).max - 1000 ether); // prevent overflow in vm.deal
165+
vm.assume(recipient.code.length == 0 && uint160(recipient) > 0x09); // recipient is non-precompile EOA
166+
vm.deal(address(this), amount); // give contract some ETH
167+
168+
IOrders.Output[] memory outputs = new IOrders.Output[](2);
169+
outputs[0] = IOrders.Output(address(0), amount, recipient, chainId);
170+
outputs[1] = IOrders.Output(address(0), 1, recipient, chainId);
171+
172+
// total ETH outputs should be `amount` + 1; function should underflow only sending `amount`
173+
vm.expectRevert();
174+
target.fill{value: amount}(outputs);
175+
}
176+
177+
function test_fill_zeroETH(address recipient, uint32 chainId) public {
178+
vm.assume(recipient.code.length == 0 && uint160(recipient) > 0x09); // recipient is non-precompile EOA
179+
180+
IOrders.Output memory output = IOrders.Output(address(0), 0, recipient, chainId);
181+
IOrders.Output[] memory outputs = new IOrders.Output[](1);
182+
outputs[0] = output;
183+
184+
vm.expectEmit();
185+
emit Filled(outputs);
186+
target.fill(outputs);
187+
}
188+
}

test/rollup/Orders.t.sol

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,40 @@ contract OrdersTest is SignetStdTest {
5555
deadline = block.timestamp;
5656
}
5757

58+
// zero inputs or outptus
59+
function test_initiate_empty() public {
60+
IOrders.Input[] memory emptyInputs = new IOrders.Input[](0);
61+
IOrders.Output[] memory emptyOutputs = new IOrders.Output[](0);
62+
63+
// expect Order event is initiated, ERC20 is transferred
64+
vm.expectEmit();
65+
emit Order(deadline, emptyInputs, emptyOutputs);
66+
target.initiate(deadline, emptyInputs, emptyOutputs);
67+
}
68+
69+
// empty inputs, non-empty outputs
70+
function test_initiate_emptyInputs() public {
71+
IOrders.Input[] memory emptyInputs = new IOrders.Input[](0);
72+
73+
// expect Order event is initiated, ERC20 is transferred
74+
vm.expectEmit();
75+
emit Order(deadline, emptyInputs, outputs);
76+
target.initiate(deadline, emptyInputs, outputs);
77+
}
78+
79+
// empty outputs, non-empty inputs
80+
function test_initiate_emptyOutputs() public {
81+
IOrders.Output[] memory emptyOutputs = new IOrders.Output[](0);
82+
83+
// expect Order event is initiated, ERC20 is transferred
84+
vm.expectEmit();
85+
emit Order(deadline, inputs, emptyOutputs);
86+
vm.expectCall(
87+
token, abi.encodeWithSelector(ERC20.transferFrom.selector, address(this), address(target), amount)
88+
);
89+
target.initiate(deadline, inputs, emptyOutputs);
90+
}
91+
5892
// input ERC20
5993
function test_initiate_ERC20() public {
6094
// expect Order event is initiated, ERC20 is transferred

0 commit comments

Comments
 (0)