diff --git a/crates/era-cheatcodes/src/cheatcodes.rs b/crates/era-cheatcodes/src/cheatcodes.rs index e4075a889..44801807b 100644 --- a/crates/era-cheatcodes/src/cheatcodes.rs +++ b/crates/era-cheatcodes/src/cheatcodes.rs @@ -1676,6 +1676,7 @@ impl CheatcodeTracer { exception_handler, }), ); + self.next_return_action = None; } RetOpcode::Ok => { let Some(exception_handler) = *exception_handler else { @@ -1690,11 +1691,10 @@ impl CheatcodeTracer { exception_handler, }); } + self.next_return_action = None; } RetOpcode::Panic => return, } - - // self.recurring_actions.push(action.action.clone()); } } diff --git a/crates/era-cheatcodes/tests/src/cheatcodes/Addr.t.sol b/crates/era-cheatcodes/tests/src/cheatcodes/Addr.t.sol new file mode 100644 index 000000000..74b2f6397 --- /dev/null +++ b/crates/era-cheatcodes/tests/src/cheatcodes/Addr.t.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Test, console2 as console} from "../../lib/forge-std/src/Test.sol"; +import {Constants} from "./Constants.sol"; + +contract AddrTest is Test { + function testAddr() public { + uint256 pk = 77814517325470205911140941194401928579557062014761831930645393041380819009408; + address expected = 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266; + + (bool success, bytes memory data) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature("addr(uint256)", pk) + ); + require(success, "addr failed"); + address addr = abi.decode(data, (address)); + assertEq(addr, expected, "expected address did not match"); + } +} \ No newline at end of file diff --git a/crates/era-cheatcodes/tests/src/cheatcodes/Deal.t.sol b/crates/era-cheatcodes/tests/src/cheatcodes/Deal.t.sol new file mode 100644 index 000000000..4d8064bfc --- /dev/null +++ b/crates/era-cheatcodes/tests/src/cheatcodes/Deal.t.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Test, console2 as console} from "../../lib/forge-std/src/Test.sol"; +import {Constants} from "./Constants.sol"; + +contract CheatcodeDealTest is Test { + address constant TEST_ADDRESS = 0x6Eb28604685b1F182dAB800A1Bfa4BaFdBA8a79a; + uint256 constant NEW_BALANCE = 10; + + function testDeal() public { + uint256 balanceBefore = address(TEST_ADDRESS).balance; + console.log("balance before:", balanceBefore); + + (bool success, ) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature( + "deal(address,uint256)", + TEST_ADDRESS, + NEW_BALANCE + ) + ); + uint256 balanceAfter = address(TEST_ADDRESS).balance; + console.log("balance after :", balanceAfter); + + require(balanceAfter == NEW_BALANCE, "balance mismatch"); + require(balanceAfter != balanceBefore, "balance unchanged"); + require(success, "deal failed"); + } +} diff --git a/crates/era-cheatcodes/tests/src/cheatcodes/Etch.t.sol b/crates/era-cheatcodes/tests/src/cheatcodes/Etch.t.sol new file mode 100644 index 000000000..4dccad7c1 --- /dev/null +++ b/crates/era-cheatcodes/tests/src/cheatcodes/Etch.t.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Test, console2 as console} from "../../lib/forge-std/src/Test.sol"; +import {Constants} from "./Constants.sol"; + +contract CheatcodeEtchTest is Test { + address constant TEST_ADDRESS = 0x6Eb28604685b1F182dAB800A1Bfa4BaFdBA8a79a; + bytes constant GREETER_CODE = + hex"00050000000000020000000003010019000000600330027000000060033001970000000102200190000000950000c13d000000040230008c000000e00000413d000000000201043b0000006702200197000000680220009c000000e00000c13d0000000002000416000000000202004b000000e00000c13d000000040230008a000000200220008c000000e00000413d0000000402100370000000000502043b000000640250009c000000e00000213d00000023025000390000006904000041000000000632004b000000000600001900000000060480190000006902200197000000000702004b0000000004008019000000690220009c00000000020600190000000002046019000000000202004b000000e00000c13d0000000406500039000000000261034f000000000402043b000000640240009c0000009d0000213d0000001f07400039000000200200008a000000000727016f000000bf07700039000000000727016f0000006a087000410000006b0880009c0000009d0000413d000000400070043f000000800040043f00000000054500190000002405500039000000000335004b000000e00000213d0000002003600039000000000131034f0000001f0340018f0000000505400272000000440000613d00000000060000190000000507600210000000000871034f000000000808043b000000a00770003900000000008704350000000106600039000000000756004b0000003c0000413d000000000603004b000000530000613d0000000505500210000000000151034f0000000303300210000000a005500039000000000605043300000000063601cf000000000636022f000000000101043b0000010003300089000000000131022f00000000013101cf000000000161019f0000000000150435000000a0014000390000000000010435000000800100043d000000640310009c0000009d0000213d000000000400041a000000010340019000000001034002700000007f0330618f0000001f0530008c00000000050000190000000105002039000000000454013f0000000104400190000000fc0000c13d000000200430008c000000740000413d0000001f0410003900000005044002700000006c044000410000006c05000041000000200610008c000000000405401900000000000004350000001f0330003900000005033002700000006c03300041000000000534004b000000740000813d000000000004041b0000000104400039000000000534004b000000700000413d0000001f0310008c000001000000a13d0000000003210170000000a0040000390000006c020000410000000000000435000000880000613d0000006c0200004100000020060000390000000004000019000000000506001900000080065000390000000006060433000000000062041b000000200650003900000001022000390000002004400039000000000734004b0000007e0000413d000000a004500039000000000313004b000000920000813d0000000303100210000000f80330018f000000010500008a000000000335022f000000000353013f0000000004040433000000000334016f000000000032041b000000010200003900000001031002100000010a0000013d0000008002000039000000400020043f0000000002000416000000000202004b000000e00000c13d0000006102300041000000620220009c000000a30000213d0000006d0100004100000000001004350000004101000039000000040010043f0000006e010000410000017b000104300000009f023000390000006302200197000000400020043f0000001f0230018f0000000504300272000000b20000613d00000000050000190000000506500210000000000761034f000000000707043b000000800660003900000000007604350000000105500039000000000645004b000000aa0000413d000000000502004b000000c10000613d0000000504400210000000000141034f00000003022002100000008004400039000000000504043300000000052501cf000000000525022f000000000101043b0000010002200089000000000121022f00000000012101cf000000000151019f0000000000140435000000200130008c000000e00000413d000000800400043d000000640140009c000000e00000213d00000080033000390000009f01400039000000000131004b000000e00000813d00000080024000390000000001020433000000640510009c0000009d0000213d0000003f05100039000000200900008a000000000595016f000000400800043d0000000005580019000000000685004b00000000060000190000000106004039000000640750009c0000009d0000213d00000001066001900000009d0000c13d000000400050043f00000000061804360000000004140019000000a004400039000000000334004b000000e20000a13d00000000010000190000017b00010430000000000301004b000000ec0000613d000000000300001900000000046300190000002003300039000000000523001900000000050504330000000000540435000000000413004b000000e50000413d000000000116001900000000000104350000000004080433000000640140009c0000009d0000213d000000000100041a000000010210019000000001011002700000007f0310018f000000000301c0190000001f0130008c00000000010000190000000101002039000000010110018f000000000112004b0000010e0000613d0000006d0100004100000000001004350000002201000039000000a00000013d000000000201004b0000000002000019000001040000613d000000a00200043d0000000303100210000000010400008a000000000334022f000000000343013f000000000332016f0000000102100210000000000123019f000000000010041b00000000010000190000017a0001042e000000200130008c000001340000413d000100000003001d000300000004001d000000000000043500000060010000410000000002000414000000600320009c0000000001024019000000c00110021000000065011001c70000801002000039000500000008001d000400000009001d000200000006001d017901740000040f0000000206000029000000040900002900000005080000290000000102200190000000e00000613d00000003040000290000001f024000390000000502200270000000200340008c0000000002004019000000000301043b00000001010000290000001f01100039000000050110027000000000011300190000000002230019000000000312004b000001340000813d000000000002041b0000000102200039000000000312004b000001300000413d0000001f0140008c000001630000a13d000300000004001d000000000000043500000060010000410000000002000414000000600320009c0000000001024019000000c00110021000000065011001c70000801002000039000500000008001d000400000009001d017901740000040f000000040300002900000005060000290000000102200190000000e00000613d000000030700002900000000033701700000002002000039000000000101043b000001550000613d0000002002000039000000000400001900000000056200190000000005050433000000000051041b000000200220003900000001011000390000002004400039000000000534004b0000014d0000413d000000000373004b000001600000813d0000000303700210000000f80330018f000000010400008a000000000334022f000000000343013f00000000026200190000000002020433000000000232016f000000000021041b000000010100003900000001027002100000016d0000013d000000000104004b0000000001000019000001670000613d00000000010604330000000302400210000000010300008a000000000223022f000000000232013f000000000221016f0000000101400210000000000112019f000000000010041b00000020010000390000010000100443000001200000044300000066010000410000017a0001042e00000177002104230000000102000039000000000001042d0000000002000019000000000001042d00000179000004320000017a0001042e0000017b00010430000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000009fffffffffffffffffffffffffffffffffffffffffffffffff000000000000007f00000000000000000000000000000000000000000000000000000001ffffffe0000000000000000000000000000000000000000000000000ffffffffffffffff02000000000000000000000000000000000000200000000000000000000000000000000200000000000000000000000000000040000001000000000000000000ffffffff00000000000000000000000000000000000000000000000000000000a4136862000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000ffffffffffffffffffffffffffffffffffffffffffffffff0000000000000080290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5634e487b71000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051358bfd296e885430dddecb908ce82d20e6832374027da7514aebac3689d51f"; + + function testEtch() public { + (bool success, ) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature( + "etch(address,bytes)", + TEST_ADDRESS, + GREETER_CODE + ) + ); + require(success, "etch failed"); + + (success, ) = TEST_ADDRESS.call( + abi.encodeWithSignature("setGreeting(string)", "hello world") + ); + require(success, "setGreeting failed"); + } +} diff --git a/crates/era-cheatcodes/tests/src/cheatcodes/ExpectCall.t.sol b/crates/era-cheatcodes/tests/src/cheatcodes/ExpectCall.t.sol new file mode 100644 index 000000000..64cac8608 --- /dev/null +++ b/crates/era-cheatcodes/tests/src/cheatcodes/ExpectCall.t.sol @@ -0,0 +1,830 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Test, console2 as console} from "../../lib/forge-std/src/Test.sol"; +import {Constants} from "./Constants.sol"; + +contract Contract { + function numberA() public pure returns (uint256) { + return 1; + } + + function numberB() public pure returns (uint256) { + return 2; + } + + function add(uint256 a, uint256 b) public pure returns (uint256) { + return a + b; + } + + function pay(uint256 a) public payable returns (uint256) { + return a; + } +} + +contract NestedContract { + Contract private inner; + + constructor(Contract _inner) { + inner = _inner; + } + + function sum() public view returns (uint256) { + return inner.numberA() + inner.numberB(); + } + + function forwardPay() public payable returns (uint256) { + return inner.pay{gas: 50_000, value: 1}(1); + } + + function addHardGasLimit() public view returns (uint256) { + return inner.add{gas: 50_000}(1, 1); + } + + function hello() public pure returns (string memory) { + return "hi"; + } + + function sumInPlace(uint256 a, uint256 b) public pure returns (uint256) { + return a + b + 42; + } +} + +contract ExpectCallTest is Test { + function exposed_callTargetNTimes( + Contract target, + uint256 a, + uint256 b, + uint256 times + ) public pure { + for (uint256 i = 0; i < times; i++) { + target.add(a, b); + } + } + + function exposed_expectCallWithValue( + Contract target, + uint256 value, + uint256 amount + ) public { + target.pay{value: value}(amount); + } + + function testExpectCallWithData() public { + Contract target = new Contract(); + + (bool success, ) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature( + "expectCall(address,bytes)", + address(target), + abi.encodeWithSelector(target.add.selector, 1, 2) + ) + ); + require(success, "expectCall failed"); + + this.exposed_callTargetNTimes(target, 1, 2, 1); + } + + function testExpectMultipleCallsWithData() public { + Contract target = new Contract(); + + (bool success, ) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature( + "expectCall(address,bytes)", + address(target), + abi.encodeWithSelector(target.add.selector, 1, 2) + ) + ); + require(success, "expectCall failed"); + + // Even though we expect one call, we're using additive behavior, so getting more than one call is okay. + this.exposed_callTargetNTimes(target, 1, 2, 2); + } + + function testExpectMultipleCallsWithDataAdditive() public { + Contract target = new Contract(); + + (bool success, ) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature( + "expectCall(address,bytes)", + address(target), + abi.encodeWithSelector(target.add.selector, 1, 2) + ) + ); + require(success, "expectCall failed"); + (success, ) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature( + "expectCall(address,bytes)", + address(target), + abi.encodeWithSelector(target.add.selector, 1, 2) + ) + ); + require(success, "expectCall failed"); + + this.exposed_callTargetNTimes(target, 1, 2, 2); + } + + function testExpectMultipleCallsWithDataAdditiveLowerBound() public { + Contract target = new Contract(); + + (bool success, ) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature( + "expectCall(address,bytes)", + address(target), + abi.encodeWithSelector(target.add.selector, 1, 2) + ) + ); + require(success, "expectCall failed"); + (success, ) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature( + "expectCall(address,bytes)", + address(target), + abi.encodeWithSelector(target.add.selector, 1, 2) + ) + ); + require(success, "expectCall failed"); + + this.exposed_callTargetNTimes(target, 1, 2, 3); + } + + // TODO: uncomment once we have working reverts + // function testFailExpectMultipleCallsWithDataAdditive() public { + // Contract target = new Contract(); + + // (bool success, ) = Constants.CHEATCODE_ADDRESS.call( + // abi.encodeWithSignature( + // "expectCall(address,bytes)", + // address(target), + // abi.encodeWithSelector(target.add.selector, 1, 2) + // ) + // ); + // require(success, "expectCall failed"); + // (success, ) = Constants.CHEATCODE_ADDRESS.call( + // abi.encodeWithSignature( + // "expectCall(address,bytes)", + // address(target), + // abi.encodeWithSelector(target.add.selector, 1, 2) + // ) + // ); + // require(success, "expectCall failed"); + // (success, ) = Constants.CHEATCODE_ADDRESS.call( + // abi.encodeWithSignature( + // "expectCall(address,bytes)", + // address(target), + // abi.encodeWithSelector(target.add.selector, 1, 2) + // ) + // ); + // require(success, "expectCall failed"); + + // // Not enough calls to satisfy the additive expectCall, which expects 3 calls. + // this.exposed_callTargetNTimes(target, 1, 2, 2); + // } + + // TODO: uncomment once we have working reverts + // function testFailExpectCallWithData() public { + // Contract target = new Contract(); + + // (bool success, ) = Constants.CHEATCODE_ADDRESS.call( + // abi.encodeWithSignature( + // "expectCall(address,bytes,uint64)", + // address(target), + // abi.encodeWithSelector(target.add.selector, 1, 2), + // 1 + // ) + // ); + // require(success, "expectCall failed"); + + // this.exposed_callTargetNTimes(target, 3, 3, 1); + // } + + function testExpectInnerCall() public { + Contract inner = new Contract(); + NestedContract target = new NestedContract(inner); + + (bool success, ) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature( + "expectCall(address,bytes)", + address(inner), + abi.encodeWithSelector(inner.numberB.selector) + ) + ); + require(success, "expectCall failed"); + + this.exposed_expectInnerCall(target); + } + + function exposed_expectInnerCall(NestedContract target) public view { + target.sum(); + } + + // TODO: uncomment once we have working reverts + // function testFailExpectInnerCall() public { + // Contract inner = new Contract(); + // NestedContract target = new NestedContract(inner); + + // (bool success, ) = Constants.CHEATCODE_ADDRESS.call( + // abi.encodeWithSignature( + // "expectCall(address,bytes)", + // address(inner), + // abi.encodeWithSelector(inner.numberB.selector) + // ) + // ); + // require(success, "expectCall failed"); + + // this.exposed_failExpectInnerCall(target); + // } + + function exposed_failExpectInnerCall(NestedContract target) public pure { + // this function does not call inner + target.hello(); + } + + // We should be able to match whichever function is called inside of the next call. + // Even multiple functions. + function testExpectCallMultipleFunctions() public { + Contract inner = new Contract(); + NestedContract target = new NestedContract(inner); + + (bool success, ) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature( + "expectCall(address,bytes)", + address(target), + abi.encodeWithSelector(target.forwardPay.selector) + ) + ); + require(success, "expectCall failed"); + (success, ) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature( + "expectCall(address,bytes)", + address(inner), + abi.encodeWithSelector(inner.pay.selector) + ) + ); + require(success, "expectCall failed"); + + this.exposed_forwardPay(target); + } + + // We should also be able to match multiple functions that happen one after another, + // but inside the next call. + function testExpectCallMultipleFunctionsFlattened() public { + Contract inner = new Contract(); + NestedContract target = new NestedContract(inner); + + (bool success, ) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature( + "expectCall(address,bytes)", + address(target), + abi.encodeWithSelector(target.sumInPlace.selector) + ) + ); + require(success, "expectCall failed"); + (success, ) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature( + "expectCall(address,bytes)", + address(inner), + abi.encodeWithSelector(inner.add.selector) + ) + ); + require(success, "expectCall failed"); + + this.exposed_expectCallMultipleFunctionsFlattened(target, inner); + } + + function exposed_expectCallMultipleFunctionsFlattened( + NestedContract target, + Contract inner + ) public pure { + target.sumInPlace(1, 1); + inner.add(1, 1); + } + + function testExpectSelectorCall() public { + Contract target = new Contract(); + + (bool success, ) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature( + "expectCall(address,bytes)", + address(target), + abi.encodeWithSelector(target.add.selector) + ) + ); + require(success, "expectCall failed"); + + this.exposed_callTargetNTimes(target, 5, 5, 1); + } + + // TODO: uncomment once we have working reverts + // function testFailExpectSelectorCall() public { + // Contract target = new Contract(); + + // (bool success, ) = Constants.CHEATCODE_ADDRESS.call( + // abi.encodeWithSignature( + // "expectCall(address,bytes)", + // address(target), + // abi.encodeWithSelector(target.add.selector) + // ) + // ); + // require(success, "expectCall failed"); + // } + + // TODO: uncomment once we have working reverts + // function testFailExpectCallWithMoreParameters() public { + // Contract target = new Contract(); + + // (bool success, ) = Constants.CHEATCODE_ADDRESS.call( + // abi.encodeWithSignature( + // "expectCall(address,bytes)", + // address(target), + // abi.encodeWithSelector(target.add.selector, 3, 3, 3) + // ) + // ); + // require(success, "expectCall failed"); + + // target.add(3, 3); + // this.exposed_callTargetNTimes(target, 3, 3, 1); + // } + + function testExpectCallWithValue() public { + Contract target = new Contract(); + + (bool success, ) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature( + "expectCall(address,uint256,bytes)", + address(target), + 1, + abi.encodeWithSelector(target.pay.selector, 2) + ) + ); + require(success, "expectCall failed"); + + this.exposed_expectCallWithValue(target, 1, 2); + } + + // TODO: uncomment once we have working reverts + // function testFailExpectCallValue() public { + // Contract target = new Contract(); + + // (bool success, ) = Constants.CHEATCODE_ADDRESS.call( + // abi.encodeWithSignature( + // "expectCall(address,uint256,bytes)", + // address(target), + // 1, + // abi.encodeWithSelector(target.pay.selector, 2) + // ) + // ); + // require(success, "expectCall failed"); + // } + + function testExpectCallWithValueWithoutParameters() public { + Contract target = new Contract(); + + (bool success, ) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature( + "expectCall(address,uint256,bytes)", + address(target), + 3, + abi.encodeWithSelector(target.pay.selector) + ) + ); + require(success, "expectCall failed"); + + this.exposed_expectCallWithValue(target, 3, 100); + } + + // function testExpectCallWithValueAndGas() public { + // Contract inner = new Contract(); + // NestedContract target = new NestedContract(inner); + // vm.expectCall(address(inner), 1, 50_000, abi.encodeWithSelector(inner.pay.selector, 1)); + // this.exposed_forwardPay(target); + // } + + function exposed_forwardPay(NestedContract target) public { + target.forwardPay{value: 1}(); + } + + // function testExpectCallWithNoValueAndGas() public { + // Contract inner = new Contract(); + // NestedContract target = new NestedContract(inner); + // vm.expectCall(address(inner), 0, 50_000, abi.encodeWithSelector(inner.add.selector, 1, 1)); + // this.exposed_addHardGasLimit(target); + // } + + // function exposed_addHardGasLimit(NestedContract target) public { + // target.addHardGasLimit(); + // } + + // function testFailExpectCallWithNoValueAndWrongGas() public { + // Contract inner = new Contract(); + // NestedContract target = new NestedContract(inner); + // vm.expectCall(address(inner), 0, 25_000, abi.encodeWithSelector(inner.add.selector, 1, 1)); + // this.exposed_addHardGasLimit(target); + // } + + // function testExpectCallWithValueAndMinGas() public { + // Contract inner = new Contract(); + // NestedContract target = new NestedContract(inner); + // vm.expectCallMinGas(address(inner), 1, 50_000, abi.encodeWithSelector(inner.pay.selector, 1)); + // this.exposed_forwardPay(target); + // } + + // function testExpectCallWithNoValueAndMinGas() public { + // Contract inner = new Contract(); + // NestedContract target = new NestedContract(inner); + // vm.expectCallMinGas(address(inner), 0, 25_000, abi.encodeWithSelector(inner.add.selector, 1, 1)); + // this.exposed_addHardGasLimit(target); + // } + + // function testFailExpectCallWithNoValueAndWrongMinGas() public { + // Contract inner = new Contract(); + // NestedContract target = new NestedContract(inner); + // vm.expectCallMinGas(address(inner), 0, 50_001, abi.encodeWithSelector(inner.add.selector, 1, 1)); + // this.exposed_addHardGasLimit(target); + // } + + // /// Ensure that you cannot use expectCall with an expectRevert. + // function testFailExpectCallWithRevertDisallowed() public { + // Contract target = new Contract(); + // vm.expectRevert(); + // vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector)); + // this.exposed_callTargetNTimes(target, 5, 5, 1); + // } +} + +contract ExpectCallCountTest is Test { + function testExpectCallCountWithData() public { + Contract target = new Contract(); + + (bool success, ) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature( + "expectCall(address,bytes,uint64)", + address(target), + abi.encodeWithSelector(target.add.selector, 1, 2), + 3 + ) + ); + require(success, "expectCall failed"); + + this.exposed_expectCallCountWithData(target); + } + + function exposed_expectCallCountWithData(Contract target) public pure { + target.add(1, 2); + target.add(1, 2); + target.add(1, 2); + } + + function testExpectZeroCallCountAssert() public { + Contract target = new Contract(); + + (bool success, ) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature( + "expectCall(address,bytes,uint64)", + address(target), + abi.encodeWithSelector(target.add.selector, 1, 2), + 0 + ) + ); + require(success, "expectCall failed"); + target.add(3, 3); + } + + // TODO: uncomment once we have working reverts + // function testFailExpectCallCountWithWrongCount() public { + // Contract target = new Contract(); + + // (bool success, ) = Constants.CHEATCODE_ADDRESS.call( + // abi.encodeWithSignature( + // "expectCall(address,bytes,uint64)", + // address(target), + // abi.encodeWithSelector(target.add.selector, 1, 2), + // 2 + // ) + // ); + // require(success, "expectCall failed"); + + // target.add(1, 2); + // } + + function testExpectCountInnerCall() public { + Contract inner = new Contract(); + NestedContract target = new NestedContract(inner); + + (bool success, ) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature( + "expectCall(address,bytes,uint64)", + address(inner), + abi.encodeWithSelector(inner.numberB.selector), + 1 + ) + ); + require(success, "expectCall failed"); + + target.sum(); + } + + // TODO: uncomment once we have working reverts + // function testFailExpectCountInnerCall() public { + // Contract inner = new Contract(); + // NestedContract target = new NestedContract(inner); + + // (bool success, ) = Constants.CHEATCODE_ADDRESS.call( + // abi.encodeWithSignature( + // "expectCall(address,bytes,uint64)", + // address(inner), + // abi.encodeWithSelector(inner.numberB.selector), + // 1 + // ) + // ); + // require(success, "expectCall failed"); + + // // this function does not call inner + // target.hello(); + // } + + function testExpectCountInnerAndOuterCalls() public { + Contract inner = new Contract(); + NestedContract target = new NestedContract(inner); + + (bool success, ) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature( + "expectCall(address,bytes,uint64)", + address(inner), + abi.encodeWithSelector(inner.numberB.selector), + 2 + ) + ); + require(success, "expectCall failed"); + + this.exposed_expectCountInnerAndOuterCalls(inner, target); + } + + function exposed_expectCountInnerAndOuterCalls( + Contract inner, + NestedContract target + ) public view { + inner.numberB(); + target.sum(); + } + + function exposed_pay( + Contract target, + uint256 value, + uint256 amount + ) public payable { + target.pay{value: value}(amount); + } + + function testExpectCallCountWithValue() public { + Contract target = new Contract(); + + (bool success, ) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature( + "expectCall(address,uint256,bytes,uint64)", + address(target), + 1, + abi.encodeWithSelector(target.pay.selector, 2), + 1 + ) + ); + require(success, "expectCall failed"); + + this.exposed_pay{value: 1}(target, 1, 2); + } + + function testExpectZeroCallCountValue() public { + Contract target = new Contract(); + + (bool success, ) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature( + "expectCall(address,uint256,bytes,uint64)", + address(target), + 1, + abi.encodeWithSelector(target.pay.selector, 2), + 0 + ) + ); + require(success, "expectCall failed"); + + this.exposed_pay{value: 2}(target, 2, 2); + } + + // TODO: uncomment once we have working reverts + // function testFailExpectCallCountValue() public { + // Contract target = new Contract(); + + // (bool success, ) = Constants.CHEATCODE_ADDRESS.call( + // abi.encodeWithSignature( + // "expectCall(address,uint256,bytes,uint64)", + // address(target), + // 1, + // abi.encodeWithSelector(target.pay.selector, 2), + // 1 + // ) + // ); + // require(success, "expectCall failed"); + + // this.exposed_pay{value: 2}(target, 2, 2); + // } + + function testExpectCallCountWithValueWithoutParameters() public { + Contract target = new Contract(); + + (bool success, ) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature( + "expectCall(address,uint256,bytes,uint64)", + address(target), + 3, + abi.encodeWithSelector(target.pay.selector), + 3 + ) + ); + require(success, "expectCall failed"); + + this.exposed_expectCallCountWithValueWithoutParameters(target); + } + + function exposed_expectCallCountWithValueWithoutParameters( + Contract target + ) public { + target.pay{value: 3}(100); + target.pay{value: 3}(100); + target.pay{value: 3}(100); + } + + // function testExpectCallCountWithValueAndGas() public { + // Contract inner = new Contract(); + // NestedContract target = new NestedContract(inner); + // vm.expectCall(address(inner), 1, 50_000, abi.encodeWithSelector(inner.pay.selector, 1), 2); + // this.exposed_expectCallCountWithValueAndGas(target); + // } + + // function exposed_expectCallCountWithValueAndGas(NestedContract target) public { + // target.forwardPay{value: 1}(); + // target.forwardPay{value: 1}(); + // } + + // function exposed_addHardGasLimit(NestedContract target, uint256 times) public { + // for (uint256 i = 0; i < times; i++) { + // target.addHardGasLimit(); + // } + // } + + // function testExpectCallCountWithNoValueAndGas() public { + // Contract inner = new Contract(); + // NestedContract target = new NestedContract(inner); + // vm.expectCall(address(inner), 0, 50_000, abi.encodeWithSelector(inner.add.selector, 1, 1), 1); + // this.exposed_addHardGasLimit(target, 1); + // } + + // function testExpectZeroCallCountWithNoValueAndWrongGas() public { + // Contract inner = new Contract(); + // NestedContract target = new NestedContract(inner); + // vm.expectCall(address(inner), 0, 25_000, abi.encodeWithSelector(inner.add.selector, 1, 1), 0); + // this.exposed_addHardGasLimit(target, 1); + // } + + // function testFailExpectCallCountWithNoValueAndWrongGas() public { + // Contract inner = new Contract(); + // NestedContract target = new NestedContract(inner); + // vm.expectCall(address(inner), 0, 25_000, abi.encodeWithSelector(inner.add.selector, 1, 1), 2); + // this.exposed_addHardGasLimit(target, 2); + // } + + // function testExpectCallCountWithValueAndMinGas() public { + // Contract inner = new Contract(); + // NestedContract target = new NestedContract(inner); + // vm.expectCallMinGas(address(inner), 1, 50_000, abi.encodeWithSelector(inner.pay.selector, 1), 1); + // this.exposed_forwardPay(target); + // } + + // function exposed_forwardPay(NestedContract target) public { + // target.forwardPay{value: 1}(); + // } + + // function testExpectCallCountWithNoValueAndMinGas() public { + // Contract inner = new Contract(); + // NestedContract target = new NestedContract(inner); + // vm.expectCallMinGas(address(inner), 0, 25_000, abi.encodeWithSelector(inner.add.selector, 1, 1), 2); + // this.exposed_addHardGasLimit(target, 2); + // } + + // function testExpectCallZeroCountWithNoValueAndWrongMinGas() public { + // Contract inner = new Contract(); + // NestedContract target = new NestedContract(inner); + // vm.expectCallMinGas(address(inner), 0, 50_001, abi.encodeWithSelector(inner.add.selector, 1, 1), 0); + // this.exposed_addHardGasLimit(target, 1); + // } + + // function testFailExpectCallCountWithNoValueAndWrongMinGas() public { + // Contract inner = new Contract(); + // NestedContract target = new NestedContract(inner); + // vm.expectCallMinGas(address(inner), 0, 50_001, abi.encodeWithSelector(inner.add.selector, 1, 1), 1); + // this.exposed_addHardGasLimit(target, 1); + // } +} + +contract ExpectCallMixedTest is Test { + function exposed_callTargetNTimes( + Contract target, + uint256 a, + uint256 b, + uint256 times + ) public pure { + for (uint256 i = 0; i < times; i++) { + target.add(a, b); + } + } + + // function testFailOverrideNoCountWithCount() public { + // Contract target = new Contract(); + // vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector, 1, 2)); + // // You should not be able to overwrite a expectCall that had no count with some count. + // vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector, 1, 2), 2); + // this.exposed_callTargetNTimes(target, 1, 2, 2); + // } + + // function testFailOverrideCountWithCount() public { + // Contract target = new Contract(); + // vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector, 1, 2), 2); + // // You should not be able to overwrite a expectCall that had a count with some count. + // vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector, 1, 2), 1); + // target.add(1, 2); + // target.add(1, 2); + // } + + // function testFailOverrideCountWithNoCount() public { + // Contract target = new Contract(); + // vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector, 1, 2), 2); + // // You should not be able to overwrite a expectCall that had a count with no count. + // vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector, 1, 2)); + // target.add(1, 2); + // target.add(1, 2); + // } + + function testExpectMatchPartialAndFull() public { + Contract target = new Contract(); + + (bool success, ) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature( + "expectCall(address,bytes,uint64)", + address(target), + abi.encodeWithSelector(target.add.selector), + 2 + ) + ); + require(success, "expectCall failed"); + + // Even if a partial match is specified, you should still be able to look for full matches + // as one does not override the other. + (success, ) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature( + "expectCall(address,bytes)", + address(target), + abi.encodeWithSelector(target.add.selector, 1, 2) + ) + ); + require(success, "expectCall failed"); + + this.exposed_expectMatchPartialAndFull(target); + } + + function exposed_expectMatchPartialAndFull(Contract target) public pure { + target.add(1, 2); + target.add(1, 2); + } + + function testExpectMatchPartialAndFullFlipped() public { + Contract target = new Contract(); + + (bool success, ) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature( + "expectCall(address,bytes)", + address(target), + abi.encodeWithSelector(target.add.selector) + ) + ); + require(success, "expectCall failed"); + + // Even if a partial match is specified, you should still be able to look for full matches + // as one does not override the other. + (success, ) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature( + "expectCall(address,bytes,uint64)", + address(target), + abi.encodeWithSelector(target.add.selector, 1, 2), + 2 + ) + ); + require(success, "expectCall failed"); + + this.exposed_expectMatchPartialAndFullFlipped(target); + } + + function exposed_expectMatchPartialAndFullFlipped( + Contract target + ) public pure { + target.add(1, 2); + target.add(1, 2); + } +} diff --git a/crates/era-cheatcodes/tests/src/cheatcodes/ExpectEmit.t.sol b/crates/era-cheatcodes/tests/src/cheatcodes/ExpectEmit.t.sol new file mode 100644 index 000000000..250832fdb --- /dev/null +++ b/crates/era-cheatcodes/tests/src/cheatcodes/ExpectEmit.t.sol @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Test, console2 as console} from "../../lib/forge-std/src/Test.sol"; +import {Constants} from "./Constants.sol"; + +struct Log { + bytes32[] topics; + bytes data; + address emitter; +} + +contract ExpectEmitTest is Test { + event LogTopic1(uint256 indexed topic1, uint256 indexed topic2, uint256 indexed topic3, bytes data); + + event LogTopic2(uint256 indexed topic1, bytes data); + + function testExpectEmit() public { + bytes memory testData1 = "test"; + + Emitter emitter = new Emitter(); + + (bool success, ) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature("expectEmit()") + ); + + require(success, "expectEmit failed"); + + emit LogTopic1(2, 2, 1, testData1); + + emitter.emitEvent(2, 2, 1, testData1); + } + + function testExpectEmitWithAddress() public { + address EMITTER_ADDRESS = 0xB669bd94992d4dfb4DE8EAbc32c0c9AfFedF4774; + bytes memory testData1 = "test2"; + + Emitter emitter = new Emitter(); + + (bool success, ) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature("expectEmit(bool,bool,bool,bool,address)", true, true, true, true, EMITTER_ADDRESS) + ); + + require(success, "expectEmit failed"); + + emit LogTopic1(2, 2, 1, testData1); + + emitter.emitEvent(2, 2, 1, testData1); + } + + function trimReturnBytes( + bytes memory rawData + ) internal pure returns (bytes memory) { + uint256 lengthStartingPos = rawData.length - 32; + bytes memory lengthSlice = new bytes(32); + + for (uint256 i = 0; i < 32; i++) { + lengthSlice[i] = rawData[lengthStartingPos + i]; + } + + uint256 length = abi.decode(lengthSlice, (uint256)); + bytes memory data = new bytes(length); + + for (uint256 i = 0; i < length; i++) { + data[i] = rawData[i]; + } + + return data; + } +} + +contract Emitter { + uint256 public thing; + + event LogTopic1( + uint256 indexed topic1, + uint256 indexed topic2, + uint256 indexed topic3, + bytes data + ); + + function emitEvent( + uint256 topic1, + uint256 topic2, + uint256 topic3, + bytes memory data + ) public { + emit LogTopic1(topic1, topic2, topic3, data); + } +} diff --git a/crates/era-cheatcodes/tests/src/cheatcodes/Ffi.t.sol b/crates/era-cheatcodes/tests/src/cheatcodes/Ffi.t.sol new file mode 100644 index 000000000..1323df547 --- /dev/null +++ b/crates/era-cheatcodes/tests/src/cheatcodes/Ffi.t.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Test, console2 as console} from "../../lib/forge-std/src/Test.sol"; +import {Constants} from "./Constants.sol"; +import {Utils} from "./Utils.sol"; + +contract FfiTest is Test { + function testFfi() public { + string[] memory inputs = new string[](3); + inputs[0] = "bash"; + inputs[1] = "-c"; + inputs[ + 2 + ] = "echo -n 0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000966666920776f726b730000000000000000000000000000000000000000000000"; + + (bool success, bytes memory rawData) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature("ffi(string[])", inputs) + ); + require(success, "ffi failed"); + + bytes memory data = Utils.trimReturnBytes(rawData); + string memory output = abi.decode(data, (string)); + require( + keccak256(bytes(output)) == keccak256(bytes("ffi works")), + "ffi failed" + ); + } + + function testFfiString() public { + string[] memory inputs = new string[](3); + inputs[0] = "echo"; + inputs[1] = "-n"; + inputs[2] = "gm"; + + (bool success, bytes memory rawData) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature("ffi(string[])", inputs) + ); + require(success, "ffi failed"); + bytes memory data = Utils.trimReturnBytes(rawData); + require(keccak256(data) == keccak256(bytes("gm")), "ffi failed"); + } +} diff --git a/crates/era-cheatcodes/tests/src/cheatcodes/Fork.t.sol b/crates/era-cheatcodes/tests/src/cheatcodes/Fork.t.sol new file mode 100644 index 000000000..35a632be6 --- /dev/null +++ b/crates/era-cheatcodes/tests/src/cheatcodes/Fork.t.sol @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Test, console2 as console} from "../../lib/forge-std/src/Test.sol"; +import {Constants} from "./Constants.sol"; + +contract ForkTest is Test { + /// USDC TOKEN + address constant TOKEN_ADDRESS = 0x3355df6D4c9C3035724Fd0e3914dE96A5a83aaf4; + uint256 constant TOKEN_DECIMALS = 6; + uint256 constant FORK_BLOCK = 19579636; + + function setUp() public { + /// USDC TOKEN doesn't exists locally + (bool success, bytes memory data) = TOKEN_ADDRESS.call( + abi.encodeWithSignature("decimals()") + ); + require(success, "decimals() failed"); + uint256 decimals_before = uint256(bytes32(data)); + require( + block.number < 1000, + "Local node doesn't have blocks above 1000" + ); + (bool success2, ) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature( + "createSelectFork(string,uint256)", + "mainnet", + FORK_BLOCK + ) + ); + require(decimals_before == 0, "Contract exists locally"); + require(success2, "fork failed"); + } + + function testFork() public { + /// After createSelect fork the decimals should exist + (bool success, bytes memory data2) = TOKEN_ADDRESS.call( + abi.encodeWithSignature("decimals()") + ); + require(success, "decimals() failed"); + uint256 decimals_after = uint256(bytes32(data2)); + console.log("decimals_after", decimals_after); + require( + decimals_after == TOKEN_DECIMALS, + "Contract dosent exists in fork" + ); + require( + block.number == FORK_BLOCK + 1, + "ENV for blocks is not set correctly" + ); + } + + function testCreateSelectFork() public { + (bool success, bytes memory data) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature( + "createFork(string,uint256)", + "mainnet", + FORK_BLOCK + 100 + ) + ); + require(success, "fork failed"); + + uint256 forkId = uint256(bytes32(data)); + (bool success1, ) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature("selectFork(uint256)", forkId) + ); + require(success1, "select fork failed"); + + /// After createSelect fork the decimals should exist + (bool success2, bytes memory data2) = TOKEN_ADDRESS.call( + abi.encodeWithSignature("decimals()") + ); + require(success2, "decimals() failed"); + uint256 decimals_after = uint256(bytes32(data2)); + console.log("decimals_after", decimals_after); + console.log("block ", block.number); + require( + decimals_after == TOKEN_DECIMALS, + "Contract dosent exists in fork" + ); + require( + block.number == FORK_BLOCK + 100, + "ENV for blocks is not set correctly" + ); + } +} diff --git a/crates/era-cheatcodes/tests/src/cheatcodes/Fs.t.sol b/crates/era-cheatcodes/tests/src/cheatcodes/Fs.t.sol new file mode 100644 index 000000000..5f32c3836 --- /dev/null +++ b/crates/era-cheatcodes/tests/src/cheatcodes/Fs.t.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Test, console2 as console} from "../../lib/forge-std/src/Test.sol"; +import {Constants} from "./Constants.sol"; +import {Utils} from "./Utils.sol"; + +contract FsTest is Test { + function testReadFile() public { + string memory path = "src/fixtures/File/read.txt"; + + (bool success, bytes memory rawData) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature("readFile(string)", path) + ); + require(success, "readFile failed"); + + bytes memory data = Utils.trimReturnBytes(rawData); + + require( + keccak256(data) == + keccak256("hello readable world\nthis is the second line!\n"), + "read data did not match expected data" + ); + } + + function testWriteFile() public { + string memory path = "src/fixtures/File/write_file.txt"; + string memory writeData = "hello writable world"; + + (bool success, ) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature("writeFile(string,string)", path, writeData) + ); + require(success, "writeFile failed"); + + bytes memory readRawData; + (success, readRawData) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature("readFile(string)", path) + ); + require(success, "readFile failed"); + + bytes memory readData = Utils.trimReturnBytes(readRawData); + + require( + keccak256(readData) == keccak256(bytes(writeData)), + "read data did not match write data" + ); + } +} diff --git a/crates/era-cheatcodes/tests/src/cheatcodes/GetNonce.t.sol b/crates/era-cheatcodes/tests/src/cheatcodes/GetNonce.t.sol new file mode 100644 index 000000000..b7618aa3c --- /dev/null +++ b/crates/era-cheatcodes/tests/src/cheatcodes/GetNonce.t.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Test, console2 as console} from "../../lib/forge-std/src/Test.sol"; +import {Constants} from "./Constants.sol"; + +contract CheatcodeSetNonceTest is Test { + address constant TEST_ADDRESS = 0x6Eb28604685b1F182dAB800A1Bfa4BaFdBA8a79a; + uint256 constant NEW_NONCE = uint256(123456); + + function testSetNonce() public { + (bool success, ) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature( + "setNonce(address,uint64)", + TEST_ADDRESS, + NEW_NONCE + ) + ); + require(success, "setNonce failed"); + + //test getNonce + (bool success2, bytes memory data2) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature("getNonce(address)", TEST_ADDRESS) + ); + require(success2, "getNonce failed"); + uint256 nonce = abi.decode(data2, (uint256)); + console.log("nonce: 0x", nonce); + require(nonce == NEW_NONCE, "nonce was not changed"); + } +} + + diff --git a/crates/era-cheatcodes/tests/src/cheatcodes/Load.t.sol b/crates/era-cheatcodes/tests/src/cheatcodes/Load.t.sol new file mode 100644 index 000000000..a99a123d9 --- /dev/null +++ b/crates/era-cheatcodes/tests/src/cheatcodes/Load.t.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Test, console2 as console} from "../../lib/forge-std/src/Test.sol"; +import {Constants} from "./Constants.sol"; + +contract Storage { + uint256 slot0 = 10; +} + +contract LoadTest is Test { + address constant TEST_ADDRESS = 0x6Eb28604685b1F182dAB800A1Bfa4BaFdBA8a79a; + uint256 slot0 = 20; + Storage store; + + function setUp() public { + store = new Storage(); + } + + function testLoadOwnStorage() public { + uint256 slot; + assembly { + slot := slot0.slot + } + (bool success, bytes memory data) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature( + "load(address,bytes32)", + address(this), + bytes32(slot) + ) + ); + require(success, "load failed"); + uint256 val = abi.decode(data, (uint256)); + assertEq(val, 20, "load failed"); + } + + function testLoadOtherStorage() public { + (bool success, bytes memory data) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature( + "load(address,bytes32)", + address(store), + bytes32(0) + ) + ); + require(success, "load failed"); + uint256 val = abi.decode(data, (uint256)); + assertEq(val, 10, "load failed"); + } +} diff --git a/crates/era-cheatcodes/tests/src/cheatcodes/Logs.t.sol b/crates/era-cheatcodes/tests/src/cheatcodes/Logs.t.sol new file mode 100644 index 000000000..d41923927 --- /dev/null +++ b/crates/era-cheatcodes/tests/src/cheatcodes/Logs.t.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Test, console2 as console} from "../../lib/forge-std/src/Test.sol"; +import {Constants} from "./Constants.sol"; + +struct Log { + bytes32[] topics; + bytes data; + address emitter; +} + +contract LogsTest is Test { + event LogTopic1(uint256 indexed topic1, bytes data); + + function testRecordAndGetLogs() public { + bytes memory testData1 = "test"; + + (bool success, ) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature("recordLogs()") + ); + require(success, "recordLogs failed"); + + emit LogTopic1(1, testData1); + + (bool success2, bytes memory rawData) = Constants + .CHEATCODE_ADDRESS + .call(abi.encodeWithSignature("getRecordedLogs()")); + + require(success2, "getRecordedLogs failed"); + + Log[] memory logs = abi.decode(rawData, (Log[])); + console.log("logs length: %d", logs.length); + require(logs.length == 1, "wrong number of logs"); + } +} \ No newline at end of file diff --git a/crates/era-cheatcodes/tests/src/cheatcodes/ReadCallers.t.sol b/crates/era-cheatcodes/tests/src/cheatcodes/ReadCallers.t.sol new file mode 100644 index 000000000..8d73d33ef --- /dev/null +++ b/crates/era-cheatcodes/tests/src/cheatcodes/ReadCallers.t.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Test, console2 as console} from "../../lib/forge-std/src/Test.sol"; +import {Constants} from "./Constants.sol"; + +contract CheatcodeReadCallers is Test { + address constant TEST_ADDRESS = 0x6Eb28604685b1F182dAB800A1Bfa4BaFdBA8a79a; + address constant TEST_ORIGIN = 0xdEBe90b7BFD87Af696B1966082F6515a6E72F3d8; + + // enum CallerMode { + // None, + // Broadcast, + // RecurrentBroadcast, + // Prank, + // RecurrentPrank + // } + + function testNormalReadCallers() public { + (bool success, bytes memory data) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature("readCallers()")); + require(success, "readCallers failed"); + + (uint8 mode, address sender, address origin) = abi.decode(data, (uint8, address, address)); + require(mode == 0, "normal call mode"); + require(sender == msg.sender, "sender not overridden"); + require(origin == tx.origin, "origin not overridden"); + } + + function testPrankedReadCallers() public { + (bool success1, ) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature("startPrank(address)", TEST_ADDRESS) + ); + require(success1, "startPrank failed"); + + (bool success, bytes memory data) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature("readCallers()")); + require(success, "readCallers failed"); + + (uint8 mode, address sender, address origin) = abi.decode(data, (uint8, address, address)); + require(mode == 4, "recurrent prank call mode"); + require(sender == TEST_ADDRESS, "sender overridden"); + require(origin == tx.origin, "origin not overridden"); + } + + function testFullyPrankedReadCallers() public { + (bool success1, ) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature("startPrank(address,address)", TEST_ADDRESS, TEST_ORIGIN) + ); + require(success1, "startPrank failed"); + + (bool success, bytes memory data) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature("readCallers()")); + require(success, "readCallers failed"); + + (uint8 mode, address sender, address origin) = abi.decode(data, (uint8, address, address)); + + require(mode == 4, "recurrent prank call mode"); + require(sender == TEST_ADDRESS, "sender overridden"); + require(origin == TEST_ORIGIN, "origin overridden"); + } +} diff --git a/crates/era-cheatcodes/tests/src/cheatcodes/Roll.t.sol b/crates/era-cheatcodes/tests/src/cheatcodes/Roll.t.sol new file mode 100644 index 000000000..c26381931 --- /dev/null +++ b/crates/era-cheatcodes/tests/src/cheatcodes/Roll.t.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Test, console2 as console} from "../../lib/forge-std/src/Test.sol"; +import {Constants} from "./Constants.sol"; + +contract CheatcodeRollTest is Test { + address constant TEST_ADDRESS = 0x6Eb28604685b1F182dAB800A1Bfa4BaFdBA8a79a; + uint256 constant NEW_BLOCK_NUMBER = 10; + + function testRoll() public { + uint256 initialBlockNumber = block.number; + console.log("blockNumber before:", initialBlockNumber); + + require( + NEW_BLOCK_NUMBER != initialBlockNumber, + "block number must be different than current block number" + ); + + (bool success, ) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature("roll(uint256)", NEW_BLOCK_NUMBER) + ); + require(success, "roll failed"); + uint256 finalBlockNumber = block.number; + console.log("blockNumber after :", finalBlockNumber); + + require( + finalBlockNumber == NEW_BLOCK_NUMBER, + "block number was not changed" + ); + } +} diff --git a/crates/era-cheatcodes/tests/src/cheatcodes/RpcUrls.t.sol b/crates/era-cheatcodes/tests/src/cheatcodes/RpcUrls.t.sol new file mode 100644 index 000000000..c2d528dbd --- /dev/null +++ b/crates/era-cheatcodes/tests/src/cheatcodes/RpcUrls.t.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Test, console2 as console} from "../../lib/forge-std/src/Test.sol"; +import {Constants} from "./Constants.sol"; +import {Utils} from "./Utils.sol"; + +contract RpcUrlsTest is Test { + function testRpcUrl() public { + (bool success, bytes memory rawData) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature("rpcUrl(string)", "mainnet") + ); + + bytes memory return_data = Utils.trimReturnBytes(rawData); + string memory rpc_url = string(return_data); + console.log("rpc_url", rpc_url); + require(success, "rpcUrl() failed"); + require( + keccak256(abi.encodePacked(rpc_url)) == + keccak256( + abi.encodePacked("https://mainnet.era.zksync.io:443") + ), + "rpc url retrieved does not match expected value" + ); + } + + function testRpcUrls() public { + (bool success, bytes memory rawData2) = Constants + .CHEATCODE_ADDRESS + .call(abi.encodeWithSignature("rpcUrls()")); + + bytes memory return_data2 = Utils.trimReturnBytes(rawData2); + string memory rpc_urls = string(return_data2); + + console.log("rpc_urls", rpc_urls); + + require(success, "rpcUrls() failed"); + require( + keccak256(abi.encodePacked(rpc_urls)) == + keccak256( + abi.encodePacked( + "local,mainnet:https://mainnet.era.zksync.io:443,testnet:https://testnet.era.zksync.dev:443" + ) + ), + "rpc urls retrieved does not match expected value" + ); + } +} diff --git a/crates/era-cheatcodes/tests/src/cheatcodes/Serialize.t.sol b/crates/era-cheatcodes/tests/src/cheatcodes/Serialize.t.sol new file mode 100644 index 000000000..6ee5069f7 --- /dev/null +++ b/crates/era-cheatcodes/tests/src/cheatcodes/Serialize.t.sol @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Test, console2 as console} from "../../lib/forge-std/src/Test.sol"; +import {Constants} from "./Constants.sol"; +import {Utils} from "./Utils.sol"; + +contract CheatcodeSerializeTest is Test { + address constant TEST_ADDRESS = 0x6Eb28604685b1F182dAB800A1Bfa4BaFdBA8a79a; + + function testSerializeAddress() external { + (bool success, bytes memory rawData) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature( + "serializeAddress(string,string,address)", + "obj1", + "address", + TEST_ADDRESS + ) + ); + require(success, "serializeAddress failed"); + bytes memory data = Utils.trimReturnBytes(rawData); + string memory testString = string(abi.encodePacked(data)); + require( + keccak256(bytes(testString)) == + keccak256(bytes("0x6Eb28604685b1F182dAB800A1Bfa4BaFdBA8a79a")), + "serializeAddress mismatch" + ); + } + + function testSerializeBool() external { + (bool success, bytes memory rawData) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature( + "serializeBool(string,string,bool)", + "obj1", + "boolean", + true + ) + ); + require(success, "serializeBool failed"); + bytes memory data = Utils.trimReturnBytes(rawData); + string memory testString = string(abi.encodePacked(data)); + require( + keccak256(bytes(testString)) == keccak256(bytes("true")), + "serializeBool mismatch" + ); + } + + function testSerializeUint() external { + (bool success, bytes memory rawData) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature( + "serializeUint(string,string,uint256)", + "obj1", + "uint", + 99 + ) + ); + require(success, "serializeUint failed"); + bytes memory data = Utils.trimReturnBytes(rawData); + string memory testString = string(abi.encodePacked(data)); + require( + keccak256(bytes(testString)) == keccak256(bytes("99")), + "serializeUint mismatch" + ); + } +} diff --git a/crates/era-cheatcodes/tests/src/cheatcodes/SetNonce.t.sol b/crates/era-cheatcodes/tests/src/cheatcodes/SetNonce.t.sol new file mode 100644 index 000000000..c6bb4ff63 --- /dev/null +++ b/crates/era-cheatcodes/tests/src/cheatcodes/SetNonce.t.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Test, console2 as console} from "../../lib/forge-std/src/Test.sol"; +import {Constants} from "./Constants.sol"; + +contract CheatcodeSetNonceTest is Test { + address constant TEST_ADDRESS = 0x6Eb28604685b1F182dAB800A1Bfa4BaFdBA8a79a; + uint256 constant NEW_NONCE = uint256(123456); + + function testSetNonce() public { + (bool success, ) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature( + "setNonce(address,uint64)", + TEST_ADDRESS, + NEW_NONCE + ) + ); + require(success, "setNonce failed"); + } +} diff --git a/crates/era-cheatcodes/tests/src/cheatcodes/Snapshot.t.sol b/crates/era-cheatcodes/tests/src/cheatcodes/Snapshot.t.sol new file mode 100644 index 000000000..59948965d --- /dev/null +++ b/crates/era-cheatcodes/tests/src/cheatcodes/Snapshot.t.sol @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Test, Vm, console2 as console} from "../../lib/forge-std/src/Test.sol"; +import {Constants} from "./Constants.sol"; +import {Utils} from "./Utils.sol"; + +struct Storage { + uint256 slot0; + uint256 slot1; +} + +contract SnapshotTest is Test { + Storage store; + + function setUp() public { + store.slot0 = 10; + store.slot1 = 20; + } + + function testSnapshot() public { + console.log("calling snapshot"); + + store.slot0 = 10; + store.slot1 = 20; + + (bool success, bytes memory data) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature("snapshot()") + ); + require(success, "snapshot failed"); + + uint256 snapshot = abi.decode(data, (uint256)); + + console.log("snapshot ", snapshot); + + console.log("store values: ", store.slot0, store.slot1); + + store.slot0 = 300; + store.slot1 = 400; + + assertEq(store.slot0, 300); + assertEq(store.slot1, 400); + + console.log("store values: ", store.slot0, store.slot1); + console.log("calling revertTo"); + + (success, ) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature("revertTo(uint256)", snapshot) + ); + require(success, "revertTo failed"); + + console.log("store values: ", store.slot0, store.slot1); + + assertEq(store.slot0, 10, "snapshot revert for slot 0 unsuccessful"); + assertEq(store.slot1, 20, "snapshot revert for slot 1 unsuccessful"); + } + + function testBlockValues() public { + uint256 num = block.number; + uint256 time = block.timestamp; + + (bool success, bytes memory data) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature("snapshot()") + ); + require(success, "snapshot failed"); + + uint256 snapshot = abi.decode(data, (uint256)); + + (success, ) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature("warp(uint256)", 1337) + ); + require(success, "warp failed"); + assertEq(block.timestamp, 1337); + + (success, ) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature("roll(uint256)", 99) + ); + require(success, "roll failed"); + assertEq(block.number, 99); + + (success, ) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature("revertTo(uint256)", snapshot) + ); + require(success, "revertTo failed"); + + assertEq( + block.number, + num, + "snapshot revert for block.number unsuccessful" + ); + assertEq( + block.timestamp, + time, + "snapshot revert for block.timestamp unsuccessful" + ); + } +} diff --git a/crates/era-cheatcodes/tests/src/cheatcodes/StartPrank.t.sol b/crates/era-cheatcodes/tests/src/cheatcodes/StartPrank.t.sol new file mode 100644 index 000000000..b527b7f58 --- /dev/null +++ b/crates/era-cheatcodes/tests/src/cheatcodes/StartPrank.t.sol @@ -0,0 +1,143 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Test, console2 as console} from "../../lib/forge-std/src/Test.sol"; +import {Constants} from "./Constants.sol"; + +contract CheatcodeStartPrankTest is Test { + address constant TEST_ADDRESS = 0x6Eb28604685b1F182dAB800A1Bfa4BaFdBA8a79a; + address constant TEST_ORIGIN = 0xdEBe90b7BFD87Af696B1966082F6515a6E72F3d8; + + function testStartPrank() public { + address original_msg_sender = msg.sender; + address original_tx_origin = tx.origin; + + PrankVictim victim = new PrankVictim(); + + // Verify that the victim is set up correctly + victim.assertCallerAndOrigin( + address(this), + "startPrank failed: victim.assertCallerAndOrigin failed", + original_tx_origin, + "startPrank failed: victim.assertCallerAndOrigin failed" + ); + + // Start prank without tx.origin + (bool success1, ) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature("startPrank(address)", TEST_ADDRESS) + ); + require(success1, "startPrank failed"); + + require( + msg.sender == TEST_ADDRESS, + "startPrank failed: msg.sender unchanged" + ); + require( + tx.origin == original_tx_origin, + "startPrank failed tx.origin changed" + ); + victim.assertCallerAndOrigin( + TEST_ADDRESS, + "startPrank failed: victim.assertCallerAndOrigin failed", + original_tx_origin, + "startPrank failed: victim.assertCallerAndOrigin failed" + ); + + // Stop prank + (bool success2, ) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature("stopPrank()") + ); + require(success2, "stopPrank failed"); + + require( + msg.sender == original_msg_sender, + "stopPrank failed: msg.sender didn't return to original" + ); + require( + tx.origin == original_tx_origin, + "stopPrank failed tx.origin changed" + ); + victim.assertCallerAndOrigin( + address(this), + "startPrank failed: victim.assertCallerAndOrigin failed", + original_tx_origin, + "startPrank failed: victim.assertCallerAndOrigin failed" + ); + + } + + function testStartPrankWithOrigin() external { + address original_msg_sender = msg.sender; + address original_tx_origin = tx.origin; + + PrankVictim victim = new PrankVictim(); + + // Verify that the victim is set up correctly + victim.assertCallerAndOrigin( + address(this), + "startPrank failed: victim.assertCallerAndOrigin failed", + original_tx_origin, + "startPrank failed: victim.assertCallerAndOrigin failed" + ); + + // Start prank with tx.origin + (bool success1, ) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature( + "startPrank(address,address)", + TEST_ADDRESS, + TEST_ORIGIN + ) + ); + require(success1, "startPrank failed"); + + require( + msg.sender == TEST_ADDRESS, + "startPrank failed: msg.sender unchanged" + ); + require( + tx.origin == TEST_ORIGIN, + "startPrank failed: tx.origin unchanged" + ); + victim.assertCallerAndOrigin( + TEST_ADDRESS, + "startPrank failed: victim.assertCallerAndOrigin failed", + TEST_ORIGIN, + "startPrank failed: victim.assertCallerAndOrigin failed" + ); + + // Stop prank + (bool success2, ) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature("stopPrank()") + ); + require(success2, "stopPrank failed"); + + require( + msg.sender == original_msg_sender, + "stopPrank failed: msg.sender didn't return to original" + ); + require( + tx.origin == original_tx_origin, + "stopPrank failed: tx.origin didn't return to original" + ); + victim.assertCallerAndOrigin( + address(this), + "startPrank failed: victim.assertCallerAndOrigin failed", + original_tx_origin, + "startPrank failed: victim.assertCallerAndOrigin failed" + ); + } +} + +contract PrankVictim { + function assertCallerAndOrigin( + address expectedSender, + string memory senderMessage, + address expectedOrigin, + string memory originMessage + ) public view { + console.log("msg.sender", msg.sender); + console.log("tx.origin", tx.origin); + require(msg.sender == expectedSender, senderMessage); + require(tx.origin == expectedOrigin, originMessage); + } +} diff --git a/crates/era-cheatcodes/tests/src/cheatcodes/Store.t.sol b/crates/era-cheatcodes/tests/src/cheatcodes/Store.t.sol new file mode 100644 index 000000000..e620d0f5d --- /dev/null +++ b/crates/era-cheatcodes/tests/src/cheatcodes/Store.t.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.13; + +import {Test, console2 as console} from "../../lib/forge-std/src/Test.sol"; +import {Constants} from "./Constants.sol"; + +contract Storage { + uint256 public slot0 = 10; + uint256 public slot1 = 20; +} + +contract StoreTest is Test { + address constant TEST_ADDRESS = 0x6Eb28604685b1F182dAB800A1Bfa4BaFdBA8a79a; + Storage store; + + function setUp() public { + store = new Storage(); + } + + function testStore() public { + assertEq(store.slot0(), 10, "initial value for slot 0 is incorrect"); + assertEq(store.slot1(), 20, "initial value for slot 1 is incorrect"); + + (bool success, ) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature( + "store(address,bytes32,bytes32)", + address(store), + bytes32(0), + bytes32(uint256(1)) + ) + ); + require(success, "store failed"); + assertEq(store.slot0(), 1, "store failed"); + assertEq(store.slot1(), 20, "store failed"); + } +} diff --git a/crates/era-cheatcodes/tests/src/cheatcodes/ToString.t.sol b/crates/era-cheatcodes/tests/src/cheatcodes/ToString.t.sol new file mode 100644 index 000000000..01cc8f0ca --- /dev/null +++ b/crates/era-cheatcodes/tests/src/cheatcodes/ToString.t.sol @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Test, console2 as console} from "../../lib/forge-std/src/Test.sol"; +import {Constants} from "./Constants.sol"; +import {Utils} from "./Utils.sol"; + +contract CheatcodeToStringTest is Test { + function testToStringFromAddress() external { + address testAddress = 0x413D15117be7a498e68A64FcfdB22C6e2AaE1808; + (bool success, bytes memory rawData) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature("toString(address)", testAddress) + ); + require(success, "toString failed"); + bytes memory data = Utils.trimReturnBytes(rawData); + string memory testString = string(abi.encodePacked(data)); + require( + keccak256(bytes(testString)) == + keccak256(bytes("0x413D15117be7a498e68A64FcfdB22C6e2AaE1808")), + "toString mismatch" + ); + } + + function testToStringFromBool() external { + (bool success, bytes memory rawData) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature("toString(bool)", false) + ); + require(success, "toString failed"); + bytes memory data = Utils.trimReturnBytes(rawData); + string memory testString = string(abi.encodePacked(data)); + require( + keccak256(bytes(testString)) == keccak256(bytes("false")), + "toString mismatch" + ); + + (success, rawData) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature("toString(bool)", true) + ); + require(success, "toString failed"); + data = Utils.trimReturnBytes(rawData); + testString = string(abi.encodePacked(data)); + require( + keccak256(bytes(testString)) == keccak256(bytes("true")), + "toString mismatch" + ); + } + + function testToStringFromUint256() external { + uint256 value = 99; + string memory stringValue = "99"; + (bool success, bytes memory rawData) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature("toString(uint256)", value) + ); + require(success, "toString failed"); + bytes memory data = Utils.trimReturnBytes(rawData); + string memory testString = string(abi.encodePacked(data)); + require( + keccak256(bytes(testString)) == keccak256(bytes(stringValue)), + "toString mismatch" + ); + } + + function testToStringFromInt256() external { + int256 value = -99; + string memory stringValue = "-99"; + (bool success, bytes memory rawData) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature("toString(int256)", value) + ); + require(success, "toString failed"); + bytes memory data = Utils.trimReturnBytes(rawData); + string memory testString = string(abi.encodePacked(data)); + require( + keccak256(bytes(testString)) == keccak256(bytes(stringValue)), + "toString mismatch" + ); + } + + function testToStringFromBytes32() external { + bytes32 testBytes = hex"4ec893b0a778b562e893cee722869c3e924e9ee46ec897cabda6b765a6624324"; + (bool success, bytes memory rawData) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature("toString(bytes32)", testBytes) + ); + require(success, "toString failed"); + bytes memory data = Utils.trimReturnBytes(rawData); + string memory testString = string(abi.encodePacked(data)); + require( + keccak256(bytes(testString)) == + keccak256( + bytes( + "0x4ec893b0a778b562e893cee722869c3e924e9ee46ec897cabda6b765a6624324" + ) + ), + "toString mismatch" + ); + } + + function testToStringFromBytes() external { + bytes + memory testBytes = hex"89987299ea14decf0e11d068474a6e459439802edca8aacf9644222e490d8ef6db"; + (bool success, bytes memory rawData) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature("toString(bytes)", testBytes) + ); + require(success, "toString failed"); + bytes memory data = Utils.trimReturnBytes(rawData); + string memory testString = string(abi.encodePacked(data)); + require( + keccak256(bytes(testString)) == + keccak256( + bytes( + "0x89987299ea14decf0e11d068474a6e459439802edca8aacf9644222e490d8ef6db" + ) + ), + "toString mismatch" + ); + } +} diff --git a/crates/era-cheatcodes/tests/src/cheatcodes/TryFfi.t.sol b/crates/era-cheatcodes/tests/src/cheatcodes/TryFfi.t.sol new file mode 100644 index 000000000..feb29769b --- /dev/null +++ b/crates/era-cheatcodes/tests/src/cheatcodes/TryFfi.t.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Test, console2 as console} from "../../lib/forge-std/src/Test.sol"; +import {Constants} from "./Constants.sol"; +import {Utils} from "./Utils.sol"; + +contract FfiTest is Test { + struct FfiResult { + int32 exitCode; + bytes stdout; + bytes stderr; + } + + function testTryFfi() public { + string[] memory inputs = new string[](3); + inputs[0] = "bash"; + inputs[1] = "-c"; + inputs[ + 2 + ] = "echo -n 0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000966666920776f726b730000000000000000000000000000000000000000000000"; + + (bool success, bytes memory data) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature("tryFfi(string[])", inputs) + ); + require(success, "tryFfi failed"); + + FfiResult memory f = abi.decode(data, (FfiResult)); + string memory output = abi.decode(f.stdout, (string)); + + require( + keccak256(bytes(output)) == keccak256(bytes("ffi works")), + "ffi failed" + ); + require(f.exitCode == 0, "ffi failed"); + } + + function testTryFfiFail() public { + string[] memory inputs = new string[](2); + inputs[0] = "ls"; + inputs[1] = "wad"; + + (bool success, bytes memory data) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature("tryFfi(string[])", inputs) + ); + require(success, "tryFfi failed"); + + FfiResult memory f = abi.decode(data, (FfiResult)); + require(f.exitCode != 0, "ffi failed"); + } +} diff --git a/crates/era-cheatcodes/tests/src/cheatcodes/Utils.sol b/crates/era-cheatcodes/tests/src/cheatcodes/Utils.sol new file mode 100644 index 000000000..a33c9152d --- /dev/null +++ b/crates/era-cheatcodes/tests/src/cheatcodes/Utils.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +library Utils { + function trimReturnBytes( + bytes memory rawData + ) internal pure returns (bytes memory) { + uint256 lengthStartingPos = rawData.length - 32; + bytes memory lengthSlice = new bytes(32); + for (uint256 i = 0; i < 32; i++) { + lengthSlice[i] = rawData[lengthStartingPos + i]; + } + uint256 length = abi.decode(lengthSlice, (uint256)); + bytes memory data = new bytes(length); + for (uint256 i = 0; i < length; i++) { + data[i] = rawData[i]; + } + return data; + } +} diff --git a/crates/era-cheatcodes/tests/src/cheatcodes/Warp.t.sol b/crates/era-cheatcodes/tests/src/cheatcodes/Warp.t.sol new file mode 100644 index 000000000..1d66940e7 --- /dev/null +++ b/crates/era-cheatcodes/tests/src/cheatcodes/Warp.t.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Test, console2 as console} from "../../lib/forge-std/src/Test.sol"; +import {Constants} from "./Constants.sol"; + +contract CheatcodeWarpTest is Test { + uint256 constant NEW_BLOCK_TIMESTAMP = uint256(10000); + + function testWarp() public { + uint256 initialTimestamp = block.timestamp; + console.log("timestamp before:", initialTimestamp); + require( + NEW_BLOCK_TIMESTAMP != initialTimestamp, + "timestamp must be different than current block timestamp" + ); + + (bool success, ) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature("warp(uint256)", NEW_BLOCK_TIMESTAMP) + ); + require(success, "warp failed"); + + uint256 finalTimestamp = block.timestamp; + console.log("timestamp after:", finalTimestamp); + require( + finalTimestamp == NEW_BLOCK_TIMESTAMP, + "timestamp was not changed" + ); + } +} diff --git a/crates/era-cheatcodes/tests/src/cheatcodes/WriteJson.t.sol b/crates/era-cheatcodes/tests/src/cheatcodes/WriteJson.t.sol new file mode 100644 index 000000000..91f46c3f1 --- /dev/null +++ b/crates/era-cheatcodes/tests/src/cheatcodes/WriteJson.t.sol @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Test, console2 as console} from "../../lib/forge-std/src/Test.sol"; +import {Constants} from "./Constants.sol"; +import {Utils} from "./Utils.sol"; + +contract FsTest is Test { + function testWriteJson() public { + string + memory json = '{"boolean": true, "number": 342, "object": { "title": "finally json serialization" } }'; + string memory path = "src/fixtures/Json/write_test.json"; + + // Write json to file + (bool success, ) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature("writeJson(string,string)", json, path) + ); + require(success, "writeJson failed"); + + bytes memory readRawData; + (success, readRawData) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature("readFile(string)", path) + ); + require(success, "readFile failed"); + bytes memory readData = Utils.trimReturnBytes(readRawData); + + require( + keccak256(readData) == + keccak256( + bytes( + '{\n "boolean": true,\n "number": 342,\n "object": {\n "title": "finally json serialization"\n }\n}' + ) + ), + "read data did not match write data" + ); + + // Write json to key b + (success, ) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature( + "writeJson(string,string,string)", + json, + path, + "b" + ) + ); + require(success, "writeJson to key failed"); + + (success, readRawData) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature("readFile(string)", path) + ); + require(success, "readFile failed"); + readData = Utils.trimReturnBytes(readRawData); + + require( + keccak256(readData) == + keccak256( + bytes( + '{\n "boolean": true,\n "number": 342,\n "object": {\n "title": "finally json serialization"\n },\n "b": {\n "boolean": true,\n "number": 342,\n "object": {\n "title": "finally json serialization"\n }\n }\n}' + ) + ), + "read data did not match write data" + ); + + // Replace the key b with single value + (success, ) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature( + "writeJson(string,string,string)", + '"test"', + path, + "b" + ) + ); + require(success, "writeJson to key failed"); + + (success, readRawData) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature("readFile(string)", path) + ); + require(success, "readFile failed"); + readData = Utils.trimReturnBytes(readRawData); + + require( + keccak256(readData) == + keccak256( + bytes( + '{\n "boolean": true,\n "number": 342,\n "object": {\n "title": "finally json serialization"\n },\n "b": "test"\n}' + ) + ), + "read data did not match write data" + ); + } +} diff --git a/crates/era-cheatcodes/tests/test.sh b/crates/era-cheatcodes/tests/test.sh index 8eac7c7c7..86e6e76ca 100755 --- a/crates/era-cheatcodes/tests/test.sh +++ b/crates/era-cheatcodes/tests/test.sh @@ -6,7 +6,7 @@ set -o pipefail -e REPO_ROOT="../../.." SOLC_VERSION=${SOLC_VERSION:-"v0.8.20"} SOLC="solc-${SOLC_VERSION}" -BINARY_PATH="${REPO_ROOT}/target/debug/zkforge" +BINARY_PATH="${REPO_ROOT}/target/release/zkforge" function download_solc() { case "$(uname -s)" in @@ -35,7 +35,7 @@ function wait_for_build() { # See https://unix.stackexchange.com/questions/312631/bash-script-with-set-e-doesnt-stop-on-command function build_zkforge() { echo "Building ${1}..." - cargo build --manifest-path="${1}/Cargo.toml" + cargo build --release --manifest-path="${1}/Cargo.toml" wait_for_build 30 } diff --git a/crates/evm/core/src/decode.rs b/crates/evm/core/src/decode.rs index d0096cdfe..3d09bb51b 100644 --- a/crates/evm/core/src/decode.rs +++ b/crates/evm/core/src/decode.rs @@ -18,7 +18,7 @@ pub fn decode_console_logs(logs: &[Log]) -> Vec { /// /// This function returns [None] if it is not a DSTest log or the result of a Hardhat /// `console.log`. -// #[instrument(level = "debug", skip_all, fields(topics=?log.topics, data=%log.data), ret)] +#[instrument(level = "debug", skip_all, fields(topics=?log.topics, data=%log.data), ret)] pub fn decode_console_log(log: &Log) -> Option { let topics = log.topics.as_slice(); // SAFETY: Same type