From a8f3ab2996a56fa1af4fee3dfebac45573d1983b Mon Sep 17 00:00:00 2001 From: Gavin Pacini Date: Mon, 28 Nov 2022 15:11:50 -0500 Subject: [PATCH 01/10] add Foundry --- .gitignore | 3 +++ foundry.toml | 12 ++++++++++++ package-lock.json | 14 ++++++++++---- remappings.txt | 1 + 4 files changed, 26 insertions(+), 4 deletions(-) create mode 100644 foundry.toml create mode 100644 remappings.txt diff --git a/.gitignore b/.gitignore index 2a2e31f..cdd8cf4 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,7 @@ node_modules cache artifacts +#Foundry files +out + yarn.lock diff --git a/foundry.toml b/foundry.toml new file mode 100644 index 0000000..e6c78de --- /dev/null +++ b/foundry.toml @@ -0,0 +1,12 @@ + +[profile.default] +src = 'contracts' +out = 'out' +libs = ['lib'] + +solc = '0.8.16' +via_ir = true +optimizer = true +optimizer_runs = 100000 + +# See more config options https://github.com/foundry-rs/foundry/tree/master/config \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 7531d91..d20ca53 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@ensofinance/weiroll", - "version": "1.3.1", + "version": "1.4.1-alpha.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@ensofinance/weiroll", - "version": "1.3.1", + "version": "1.4.1-alpha.1", "devDependencies": { "@ensofinance/weiroll.js": "0.4.1-alpha.1", "@nomiclabs/hardhat-ethers": "^2.2.1", @@ -9047,6 +9047,7 @@ }, "node_modules/ganache-core/node_modules/keccak": { "version": "3.0.1", + "dev": true, "hasInstallScript": true, "inBundle": true, "license": "MIT", @@ -9619,6 +9620,7 @@ }, "node_modules/ganache-core/node_modules/node-addon-api": { "version": "2.0.2", + "dev": true, "inBundle": true, "license": "MIT" }, @@ -9632,6 +9634,7 @@ }, "node_modules/ganache-core/node_modules/node-gyp-build": { "version": "4.2.3", + "dev": true, "inBundle": true, "license": "MIT", "bin": { @@ -23143,6 +23146,7 @@ "keccak": { "version": "3.0.1", "bundled": true, + "dev": true, "requires": { "node-addon-api": "^2.0.0", "node-gyp-build": "^4.2.0" @@ -23572,7 +23576,8 @@ }, "node-addon-api": { "version": "2.0.2", - "bundled": true + "bundled": true, + "dev": true }, "node-fetch": { "version": "2.1.2", @@ -23580,7 +23585,8 @@ }, "node-gyp-build": { "version": "4.2.3", - "bundled": true + "bundled": true, + "dev": true }, "normalize-url": { "version": "4.5.0", diff --git a/remappings.txt b/remappings.txt new file mode 100644 index 0000000..678c55d --- /dev/null +++ b/remappings.txt @@ -0,0 +1 @@ +@openzeppelin/contracts=node_modules/@openzeppelin/contracts \ No newline at end of file From df00f503847354719832753e7b0ff92bfe8be9fa Mon Sep 17 00:00:00 2001 From: Gavin Pacini Date: Mon, 28 Nov 2022 15:21:49 -0500 Subject: [PATCH 02/10] forge install: forge-std --- .gitmodules | 3 +++ lib/forge-std | 1 + 2 files changed, 4 insertions(+) create mode 100644 .gitmodules create mode 160000 lib/forge-std diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..888d42d --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "lib/forge-std"] + path = lib/forge-std + url = https://github.com/foundry-rs/forge-std diff --git a/lib/forge-std b/lib/forge-std new file mode 160000 index 0000000..038555b --- /dev/null +++ b/lib/forge-std @@ -0,0 +1 @@ +Subproject commit 038555b121db862b6177de28dd9b071d41da41a8 From e588997f09f804d9b6618b8fe6d9682930c063fb Mon Sep 17 00:00:00 2001 From: Gavin Pacini Date: Mon, 28 Nov 2022 17:34:24 -0500 Subject: [PATCH 03/10] first fuzz test --- .gitignore | 1 + foundry.toml | 1 + remappings.txt | 2 + test/VM.js | 4 ++ test/VM.t.sol | 107 +++++++++++++++++++++++++++++++++++++++++++++++ test/weiroll.sol | 22 ++++++++++ 6 files changed, 137 insertions(+) create mode 100644 test/VM.t.sol create mode 100644 test/weiroll.sol diff --git a/.gitignore b/.gitignore index cdd8cf4..a191362 100644 --- a/.gitignore +++ b/.gitignore @@ -7,5 +7,6 @@ artifacts #Foundry files out +cache_foundry yarn.lock diff --git a/foundry.toml b/foundry.toml index e6c78de..0cd83d0 100644 --- a/foundry.toml +++ b/foundry.toml @@ -2,6 +2,7 @@ [profile.default] src = 'contracts' out = 'out' +cache_path = 'cache_foundry' libs = ['lib'] solc = '0.8.16' diff --git a/remappings.txt b/remappings.txt index 678c55d..ca2beab 100644 --- a/remappings.txt +++ b/remappings.txt @@ -1 +1,3 @@ +ds-test/=lib/forge-std/lib/ds-test/src/ +forge-std/=lib/forge-std/src/ @openzeppelin/contracts=node_modules/@openzeppelin/contracts \ No newline at end of file diff --git a/test/VM.js b/test/VM.js index 11223a5..be794b4 100644 --- a/test/VM.js +++ b/test/VM.js @@ -77,6 +77,10 @@ describe("VM", function () { planner.add(events.logAddress(msgSender)); const { commands, state } = planner.plan(); + console.log(sender.address); + console.log(events.address); + console.log({commands}); + console.log({state}); const tx = await vm.execute(commands, state); await expect(tx) diff --git a/test/VM.t.sol b/test/VM.t.sol new file mode 100644 index 0000000..57eee12 --- /dev/null +++ b/test/VM.t.sol @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.16; + +import "forge-std/Test.sol"; + +import "./weiroll.sol"; + +import "../contracts/test/TestableVM.sol"; +import "../contracts/test/Sender.sol"; +import "../contracts/Libraries/Events.sol"; + +contract TestWeiVM is Test { + event LogAddress(address message); + + TestableVM weiVM; + Sender sender; + Events events; + + function setUp() public { + weiVM = new TestableVM(); + sender = new Sender(); + events = new Events(); + } + + function testShouldReturnMsgSender() public { + bytes32[] memory commands = new bytes32[](2); + + commands[0] = WeirollPlanner.buildCommand( + sender.sender.selector, + 0x00, // delegate call + 0xffffffffffff, // no inputs + 0x00, // store fixed size result at index 0 of state + address(sender) + ); + + commands[1] = WeirollPlanner.buildCommand( + events.logAddress.selector, + 0x00, // delegate call + 0x00ffffffffff, // use fixed size var at index 0 of state as input + 0xff, // no output + address(events) + ); + + // only 1 value in state + bytes[] memory state = new bytes[](1); + + // event is emitted + vm.expectEmit(true, true, true, true); + emit LogAddress(address(this)); + bytes[] memory returnedState = weiVM.execute(commands, state); + + // index 0 of state contains the correct address + assertEq( + address(uint160(uint256(bytes32(returnedState[0])))), + address(this) + ); + } + + function testShouldReturnMsgSenderAtIndex1() public { + bytes32[] memory commands = new bytes32[](1); + + commands[0] = WeirollPlanner.buildCommand( + sender.sender.selector, + 0x00, // delegate call + 0xffffffffffff, // no inputs + 0x01, // store fixed size result at index 1 of state + address(sender) + ); + + // we only have 1 value in state, but we put it at index 1 so state needs to be sized for 2 bytes + bytes[] memory state = new bytes[](2); + + bytes[] memory returnedState = weiVM.execute(commands, state); + + // index 0 of state should be empty + assertEq(uint256(bytes32(returnedState[0])), 0); + // index 1 of state contains the correct address + assertEq( + address(uint160(uint256(bytes32(returnedState[1])))), + address(this) + ); + } + + function testShouldReturnMsgSenderAtIndexFuzz(uint8 _index) public { + bytes1 index = bytes1(uint8(bound(_index, 1, 127) - 1)); + bytes32[] memory commands = new bytes32[](1); + + commands[0] = WeirollPlanner.buildCommand( + sender.sender.selector, + 0x00, // delegate call + 0xffffffffffff, // no inputs + index, // store fixed size result at fuzzed index + address(sender) + ); + + // state needs to be large enough to store the result at fuzzed index + bytes[] memory state = new bytes[](uint8(index) + 1); + + bytes[] memory returnedState = weiVM.execute(commands, state); + + assertEq( + address(uint160(uint256(bytes32(returnedState[uint8(index)])))), + address(this) + ); + } +} diff --git a/test/weiroll.sol b/test/weiroll.sol new file mode 100644 index 0000000..6343949 --- /dev/null +++ b/test/weiroll.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.16; + +library WeirollPlanner { + + function buildCommand( + bytes4 _selector, + bytes1 _flags, + bytes6 _input, + bytes1 _output, + address _target + ) internal pure returns (bytes32) { + uint256 selector = uint256(bytes32(_selector)); + uint256 flags = uint256(uint8(_flags)) << 220; + uint256 input = uint256(uint48(_input)) << 168; + uint256 output = uint256(uint8(_output)) << 160; + uint256 target = uint256(uint160(_target)); + + return bytes32(selector ^ flags ^ input ^ output ^ target); + } +} From faed6387fcc11ef27c8363fa87cda19e358c2ec0 Mon Sep 17 00:00:00 2001 From: Gavin Pacini Date: Mon, 28 Nov 2022 17:41:36 -0500 Subject: [PATCH 04/10] Add Foundry instructions to README --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index 8236a55..10c0978 100644 --- a/README.md +++ b/README.md @@ -159,3 +159,14 @@ Next, the vm calls the target contract with the encoded input data. A `delegatec ### Output decoding Finally, the return data is decoded by following the output argument specifier, in the same fashion as the 'input encoding' stage. Only one return value is supported. + +## Foundry Testing - Fuzz Tests + +In order to run tests using [Foundry](https://getfoundry.sh/), first setup the dependencies: +```bash +forge install +``` +then run: +```bash +forge test --match-path "./test/**/*.t.sol" +``` From 0a27ef4f918d76fec73893a63f8f303de08609fc Mon Sep 17 00:00:00 2001 From: Gavin Pacini Date: Mon, 28 Nov 2022 18:24:02 -0500 Subject: [PATCH 05/10] fuzz add test --- test/VM.js | 4 ---- test/VM.t.sol | 48 ++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 46 insertions(+), 6 deletions(-) diff --git a/test/VM.js b/test/VM.js index be794b4..11223a5 100644 --- a/test/VM.js +++ b/test/VM.js @@ -77,10 +77,6 @@ describe("VM", function () { planner.add(events.logAddress(msgSender)); const { commands, state } = planner.plan(); - console.log(sender.address); - console.log(events.address); - console.log({commands}); - console.log({state}); const tx = await vm.execute(commands, state); await expect(tx) diff --git a/test/VM.t.sol b/test/VM.t.sol index 57eee12..bceaa98 100644 --- a/test/VM.t.sol +++ b/test/VM.t.sol @@ -9,6 +9,7 @@ import "./weiroll.sol"; import "../contracts/test/TestableVM.sol"; import "../contracts/test/Sender.sol"; import "../contracts/Libraries/Events.sol"; +import "../contracts/Libraries/Math.sol"; contract TestWeiVM is Test { event LogAddress(address message); @@ -16,14 +17,16 @@ contract TestWeiVM is Test { TestableVM weiVM; Sender sender; Events events; + Math math; function setUp() public { weiVM = new TestableVM(); sender = new Sender(); events = new Events(); + math = new Math(); } - function testShouldReturnMsgSender() public { + function testShouldReturnAndEmitMsgSender() public { bytes32[] memory commands = new bytes32[](2); commands[0] = WeirollPlanner.buildCommand( @@ -90,7 +93,7 @@ contract TestWeiVM is Test { sender.sender.selector, 0x00, // delegate call 0xffffffffffff, // no inputs - index, // store fixed size result at fuzzed index + index, // store fixed size result at fuzzed index address(sender) ); @@ -104,4 +107,45 @@ contract TestWeiVM is Test { address(this) ); } + + function testSimpleAdd() public { + bytes32[] memory commands = new bytes32[](1); + + commands[0] = WeirollPlanner.buildCommand( + math.add.selector, + 0x00, // delegate call + 0x0000ffffffff, // use index 0 and index 0 as inputs + 0x01, // store fixed size result at index 1 of state + address(math) + ); + + // state needs to be large enough to store the result at fuzzed index + bytes[] memory state = new bytes[](2); + state[0] = abi.encodePacked(uint256(1)); + + bytes[] memory returnedState = weiVM.execute(commands, state); + + assertEq(uint256(bytes32(returnedState[1])), 2); + } + + function testFuzzAdd(uint128 _a, uint128 _b) public { + bytes32[] memory commands = new bytes32[](1); + + commands[0] = WeirollPlanner.buildCommand( + math.add.selector, + 0x00, // delegate call + 0x0001ffffffff, // use index 0 and index 1 as inputs + 0x02, // store fixed size result at index 2 of state + address(math) + ); + + // state needs to be large enough to store the result at index 2 + bytes[] memory state = new bytes[](3); + state[0] = abi.encodePacked(uint256(_a)); + state[1] = abi.encodePacked(uint256(_b)); + + bytes[] memory returnedState = weiVM.execute(commands, state); + + assertEq(uint256(bytes32(returnedState[2])), uint256(_a) + _b); + } } From b6f1d3967c4f4d72e39b0de7e241480cc5d73743 Mon Sep 17 00:00:00 2001 From: Gavin Pacini Date: Mon, 28 Nov 2022 20:55:41 -0500 Subject: [PATCH 06/10] working UniswapV3 test --- foundry.toml | 3 ++ package-lock.json | 105 +++++++++++++++++++++++++++++++++++++++ package.json | 4 ++ remappings.txt | 4 +- test/VM.js | 5 ++ test/VM.t.sol | 2 +- test/VMDeployed.t.sol | 108 +++++++++++++++++++++++++++++++++++++++++ test/commandbuilder.js | 4 ++ test/weiroll.sol | 2 +- 9 files changed, 234 insertions(+), 3 deletions(-) create mode 100644 test/VMDeployed.t.sol diff --git a/foundry.toml b/foundry.toml index 0cd83d0..be3c942 100644 --- a/foundry.toml +++ b/foundry.toml @@ -10,4 +10,7 @@ via_ir = true optimizer = true optimizer_runs = 100000 +[rpc_endpoints] +mainnet = "https://eth-rpc.gateway.pokt.network" + # See more config options https://github.com/foundry-rs/foundry/tree/master/config \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index d20ca53..abf9a9d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,6 +7,10 @@ "": { "name": "@ensofinance/weiroll", "version": "1.4.1-alpha.1", + "dependencies": { + "@uniswap/v3-core": "^1.0.1", + "@uniswap/v3-periphery": "^1.4.3" + }, "devDependencies": { "@ensofinance/weiroll.js": "0.4.1-alpha.1", "@nomiclabs/hardhat-ethers": "^2.2.1", @@ -2160,6 +2164,58 @@ "@types/underscore": "*" } }, + "node_modules/@uniswap/lib": { + "version": "4.0.1-alpha", + "resolved": "https://registry.npmjs.org/@uniswap/lib/-/lib-4.0.1-alpha.tgz", + "integrity": "sha512-f6UIliwBbRsgVLxIaBANF6w09tYqc6Y/qXdsrbEmXHyFA7ILiKrIwRFXe1yOg8M3cksgVsO9N7yuL2DdCGQKBA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/@uniswap/v2-core": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@uniswap/v2-core/-/v2-core-1.0.1.tgz", + "integrity": "sha512-MtybtkUPSyysqLY2U210NBDeCHX+ltHt3oADGdjqoThZaFRDKwM6k1Nb3F0A3hk5hwuQvytFWhrWHOEq6nVJ8Q==", + "engines": { + "node": ">=10" + } + }, + "node_modules/@uniswap/v3-core": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@uniswap/v3-core/-/v3-core-1.0.1.tgz", + "integrity": "sha512-7pVk4hEm00j9tc71Y9+ssYpO6ytkeI0y7WE9P6UcmNzhxPePwyAxImuhVsTqWK9YFvzgtvzJHi64pBl4jUzKMQ==", + "engines": { + "node": ">=10" + } + }, + "node_modules/@uniswap/v3-periphery": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@uniswap/v3-periphery/-/v3-periphery-1.4.3.tgz", + "integrity": "sha512-80c+wtVzl5JJT8UQskxVYYG3oZb4pkhY0zDe0ab/RX4+8f9+W5d8wI4BT0wLB0wFQTSnbW+QdBSpkHA/vRyGBA==", + "dependencies": { + "@openzeppelin/contracts": "3.4.2-solc-0.7", + "@uniswap/lib": "^4.0.1-alpha", + "@uniswap/v2-core": "1.0.1", + "@uniswap/v3-core": "1.0.0", + "base64-sol": "1.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@uniswap/v3-periphery/node_modules/@openzeppelin/contracts": { + "version": "3.4.2-solc-0.7", + "resolved": "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-3.4.2-solc-0.7.tgz", + "integrity": "sha512-W6QmqgkADuFcTLzHL8vVoNBtkwjvQRpYIAom7KiUNoLKghyx3FgH0GBjt8NRvigV1ZmMOBllvE1By1C+bi8WpA==" + }, + "node_modules/@uniswap/v3-periphery/node_modules/@uniswap/v3-core": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@uniswap/v3-core/-/v3-core-1.0.0.tgz", + "integrity": "sha512-kSC4djMGKMHj7sLMYVnn61k9nu+lHjMIxgg9CDQT+s2QYLoA56GbSK9Oxr+qJXzzygbkrmuY6cwgP6cW2JXPFA==", + "engines": { + "node": ">=10" + } + }, "node_modules/@yarnpkg/lockfile": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", @@ -2485,6 +2541,11 @@ } ] }, + "node_modules/base64-sol": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/base64-sol/-/base64-sol-1.0.1.tgz", + "integrity": "sha512-ld3cCNMeXt4uJXmLZBHFGMvVpK9KsLVEhPpFRXnvSVAqABKbuNZg/+dsq3NuM+wxFLb/UrVkz7m1ciWmkMfTbg==" + }, "node_modules/bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", @@ -17719,6 +17780,45 @@ "@types/underscore": "*" } }, + "@uniswap/lib": { + "version": "4.0.1-alpha", + "resolved": "https://registry.npmjs.org/@uniswap/lib/-/lib-4.0.1-alpha.tgz", + "integrity": "sha512-f6UIliwBbRsgVLxIaBANF6w09tYqc6Y/qXdsrbEmXHyFA7ILiKrIwRFXe1yOg8M3cksgVsO9N7yuL2DdCGQKBA==" + }, + "@uniswap/v2-core": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@uniswap/v2-core/-/v2-core-1.0.1.tgz", + "integrity": "sha512-MtybtkUPSyysqLY2U210NBDeCHX+ltHt3oADGdjqoThZaFRDKwM6k1Nb3F0A3hk5hwuQvytFWhrWHOEq6nVJ8Q==" + }, + "@uniswap/v3-core": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@uniswap/v3-core/-/v3-core-1.0.1.tgz", + "integrity": "sha512-7pVk4hEm00j9tc71Y9+ssYpO6ytkeI0y7WE9P6UcmNzhxPePwyAxImuhVsTqWK9YFvzgtvzJHi64pBl4jUzKMQ==" + }, + "@uniswap/v3-periphery": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@uniswap/v3-periphery/-/v3-periphery-1.4.3.tgz", + "integrity": "sha512-80c+wtVzl5JJT8UQskxVYYG3oZb4pkhY0zDe0ab/RX4+8f9+W5d8wI4BT0wLB0wFQTSnbW+QdBSpkHA/vRyGBA==", + "requires": { + "@openzeppelin/contracts": "3.4.2-solc-0.7", + "@uniswap/lib": "^4.0.1-alpha", + "@uniswap/v2-core": "1.0.1", + "@uniswap/v3-core": "1.0.0", + "base64-sol": "1.0.1" + }, + "dependencies": { + "@openzeppelin/contracts": { + "version": "3.4.2-solc-0.7", + "resolved": "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-3.4.2-solc-0.7.tgz", + "integrity": "sha512-W6QmqgkADuFcTLzHL8vVoNBtkwjvQRpYIAom7KiUNoLKghyx3FgH0GBjt8NRvigV1ZmMOBllvE1By1C+bi8WpA==" + }, + "@uniswap/v3-core": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@uniswap/v3-core/-/v3-core-1.0.0.tgz", + "integrity": "sha512-kSC4djMGKMHj7sLMYVnn61k9nu+lHjMIxgg9CDQT+s2QYLoA56GbSK9Oxr+qJXzzygbkrmuY6cwgP6cW2JXPFA==" + } + } + }, "@yarnpkg/lockfile": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", @@ -17966,6 +18066,11 @@ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", "dev": true }, + "base64-sol": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/base64-sol/-/base64-sol-1.0.1.tgz", + "integrity": "sha512-ld3cCNMeXt4uJXmLZBHFGMvVpK9KsLVEhPpFRXnvSVAqABKbuNZg/+dsq3NuM+wxFLb/UrVkz7m1ciWmkMfTbg==" + }, "bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", diff --git a/package.json b/package.json index cafa67e..329650e 100644 --- a/package.json +++ b/package.json @@ -19,5 +19,9 @@ "scripts": { "test": "hardhat test", "format": "prettier --write 'contracts/**/*.sol'" + }, + "dependencies": { + "@uniswap/v3-core": "^1.0.1", + "@uniswap/v3-periphery": "^1.4.3" } } diff --git a/remappings.txt b/remappings.txt index ca2beab..b82056c 100644 --- a/remappings.txt +++ b/remappings.txt @@ -1,3 +1,5 @@ ds-test/=lib/forge-std/lib/ds-test/src/ forge-std/=lib/forge-std/src/ -@openzeppelin/contracts=node_modules/@openzeppelin/contracts \ No newline at end of file +@openzeppelin/contracts=node_modules/@openzeppelin/contracts +@uniswap/v3-core/contracts/=node_modules/@uniswap/v3-core/contracts/ +@uniswap/v3-periphery/contracts/=node_modules/@uniswap/v3-periphery/contracts/ \ No newline at end of file diff --git a/test/VM.js b/test/VM.js index 11223a5..67bda6e 100644 --- a/test/VM.js +++ b/test/VM.js @@ -400,6 +400,11 @@ describe("VM", function () { ])); const {commands, state} = planner.plan(); + // console.log(math.address); + // console.log(struct.address); + // console.log({commands}); + // console.log({state}); + const tx = await vm.execute(commands, state); await expect(tx) .to.emit(eventsContract.attach(vm.address), "LogUint") diff --git a/test/VM.t.sol b/test/VM.t.sol index bceaa98..e2edc52 100644 --- a/test/VM.t.sol +++ b/test/VM.t.sol @@ -119,7 +119,7 @@ contract TestWeiVM is Test { address(math) ); - // state needs to be large enough to store the result at fuzzed index + // state needs to be large enough to store the result at index 1 bytes[] memory state = new bytes[](2); state[0] = abi.encodePacked(uint256(1)); diff --git a/test/VMDeployed.t.sol b/test/VMDeployed.t.sol new file mode 100644 index 0000000..065a6fe --- /dev/null +++ b/test/VMDeployed.t.sol @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.16; + +import "forge-std/Test.sol"; + +import "./weiroll.sol"; + +import "../contracts/test/TestableVM.sol"; +import "../contracts/test/Sender.sol"; +import "../contracts/Libraries/Events.sol"; +import "../contracts/Libraries/Math.sol"; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol"; + +contract TestWeiVMDeployed is Test { + address public constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F; + address public constant WETH9 = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + address public constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; + ISwapRouter public constant swapRouter = + ISwapRouter(0xE592427A0AEce92De3Edee1F18E0157C05861564); + + uint24 public constant poolFee = 3000; + + TestableVM weiVM; + Sender sender; + Events events; + Math math; + + function setUp() public { + vm.createSelectFork("mainnet"); + + weiVM = new TestableVM(); + sender = new Sender(); + events = new Events(); + math = new Math(); + } + + function testUniswapV3Swap() public { + assertEq(IERC20(DAI).balanceOf(address(this)), 0); + assertEq(IERC20(WETH9).balanceOf(address(this)), 0); + + deal(DAI, address(this), 1 ether); + IERC20(DAI).approve(address(swapRouter), 1 ether); + + assertEq(IERC20(DAI).balanceOf(address(this)), 1 ether); + + ISwapRouter.ExactInputSingleParams memory params = ISwapRouter + .ExactInputSingleParams({ + tokenIn: DAI, + tokenOut: WETH9, + fee: poolFee, + recipient: address(this), + deadline: block.timestamp, + amountIn: 1 ether, + amountOutMinimum: 0, + sqrtPriceLimitX96: 0 + }); + + bytes32[] memory commands = new bytes32[](2); + + commands[0] = WeirollPlanner.buildCommand( + swapRouter.exactInputSingle.selector, + 0x41, // call with ext bit set + 0x000000000000, // ignored and instead use the following bytes32 in commands + 0x08, // store fixed size result at index 8 of state + address(swapRouter) + ); + + // extended in indices + commands[1] = 0x0001020304050607ffffffffffffffffffffffffffffffffffffffffffffffff; + + bytes[] memory state = new bytes[](9); + bytes memory paramBytes = abi.encode(params); + + // Slice the Uniswap struct into the state bytes array + for (uint256 i; i < paramBytes.length / 32; ) { + { + bytes32 extracted; + /// @solidity memory-safe-assembly + assembly { + extracted := mload(add(paramBytes, mul(0x20, add(i, 1)))) + } + state[i] = abi.encodePacked(extracted); + } + + unchecked { + ++i; + } + } + + (bool success, bytes memory data) = address(weiVM) + .delegatecall( + abi.encodeWithSelector(weiVM.execute.selector, commands, state) + ); + + assertTrue(success); + + assertLt(IERC20(DAI).balanceOf(address(this)), 1 ether); + assertGt(IERC20(WETH9).balanceOf(address(this)), 0); + + bytes[] memory returnedState = abi.decode(data, (bytes[])); + + uint256 amountOut = uint256(bytes32(returnedState[8])); + assertEq(amountOut, IERC20(WETH9).balanceOf(address(this))); + } +} diff --git a/test/commandbuilder.js b/test/commandbuilder.js index 6854763..dc8cd9e 100644 --- a/test/commandbuilder.js +++ b/test/commandbuilder.js @@ -235,6 +235,10 @@ describe("CommandBuilder", function () { const {commands, state} = planner.plan(); + console.log(struct.address); + console.log({commands}); + console.log({state}); + await executeBuildInputs(commands, state, abiout, "Struct.returnMixedStruct"); }); diff --git a/test/weiroll.sol b/test/weiroll.sol index 6343949..34b3b3f 100644 --- a/test/weiroll.sol +++ b/test/weiroll.sol @@ -12,7 +12,7 @@ library WeirollPlanner { address _target ) internal pure returns (bytes32) { uint256 selector = uint256(bytes32(_selector)); - uint256 flags = uint256(uint8(_flags)) << 220; + uint256 flags = uint256(uint8(_flags)) << 216; uint256 input = uint256(uint48(_input)) << 168; uint256 output = uint256(uint8(_output)) << 160; uint256 target = uint256(uint160(_target)); From 12cfcb39168870ffd96bcaaf5145bff244c619f6 Mon Sep 17 00:00:00 2001 From: Gavin Pacini Date: Mon, 28 Nov 2022 20:59:35 -0500 Subject: [PATCH 07/10] remove some logs and clean up package.json --- package.json | 6 ++---- test/VM.js | 5 ----- test/commandbuilder.js | 4 ---- 3 files changed, 2 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index 329650e..c735906 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,8 @@ "@nomiclabs/hardhat-ethers": "^2.2.1", "@nomiclabs/hardhat-waffle": "^2.0.3", "@openzeppelin/contracts": "^4.1.0", + "@uniswap/v3-core": "^1.0.1", + "@uniswap/v3-periphery": "^1.4.3", "chai": "^4.3.4", "ethereum-waffle": "^3.4.4", "ethers": "^5.3.1", @@ -19,9 +21,5 @@ "scripts": { "test": "hardhat test", "format": "prettier --write 'contracts/**/*.sol'" - }, - "dependencies": { - "@uniswap/v3-core": "^1.0.1", - "@uniswap/v3-periphery": "^1.4.3" } } diff --git a/test/VM.js b/test/VM.js index 67bda6e..11223a5 100644 --- a/test/VM.js +++ b/test/VM.js @@ -400,11 +400,6 @@ describe("VM", function () { ])); const {commands, state} = planner.plan(); - // console.log(math.address); - // console.log(struct.address); - // console.log({commands}); - // console.log({state}); - const tx = await vm.execute(commands, state); await expect(tx) .to.emit(eventsContract.attach(vm.address), "LogUint") diff --git a/test/commandbuilder.js b/test/commandbuilder.js index dc8cd9e..6854763 100644 --- a/test/commandbuilder.js +++ b/test/commandbuilder.js @@ -235,10 +235,6 @@ describe("CommandBuilder", function () { const {commands, state} = planner.plan(); - console.log(struct.address); - console.log({commands}); - console.log({state}); - await executeBuildInputs(commands, state, abiout, "Struct.returnMixedStruct"); }); From 5ed13877ac53e85698cff3208b9096be6bd43aca Mon Sep 17 00:00:00 2001 From: Gavin Pacini Date: Tue, 29 Nov 2022 00:28:29 -0500 Subject: [PATCH 08/10] add struct specific test --- test/VM.t.sol | 58 ++++++++++++++++++++++++++++++++++++++++++ test/commandbuilder.js | 3 +++ 2 files changed, 61 insertions(+) diff --git a/test/VM.t.sol b/test/VM.t.sol index e2edc52..7615dc0 100644 --- a/test/VM.t.sol +++ b/test/VM.t.sol @@ -8,6 +8,7 @@ import "./weiroll.sol"; import "../contracts/test/TestableVM.sol"; import "../contracts/test/Sender.sol"; +import "../contracts/test/Struct.sol"; import "../contracts/Libraries/Events.sol"; import "../contracts/Libraries/Math.sol"; @@ -18,12 +19,14 @@ contract TestWeiVM is Test { Sender sender; Events events; Math math; + Struct structer; function setUp() public { weiVM = new TestableVM(); sender = new Sender(); events = new Events(); math = new Math(); + structer = new Struct(); } function testShouldReturnAndEmitMsgSender() public { @@ -148,4 +151,59 @@ contract TestWeiVM is Test { assertEq(uint256(bytes32(returnedState[2])), uint256(_a) + _b); } + + function testReturnStringStruct() public { + Struct.StringStruct memory stringStruct = Struct.StringStruct({ + a: "Hello", + b: "World" + }); + + bytes32[] memory commands = new bytes32[](1); + + commands[0] = WeirollPlanner.buildCommand( + structer.returnStringStruct.selector, + 0x81, // delegate call + 0x80ffffffffff, // use index 0 as variable input + 0x01, // store into index 1 + address(structer) + ); + + // state needs to be large enough to store the result at index 1 + bytes[] memory state = new bytes[](2); + + // abi encode + bytes memory state0 = abi.encode(stringStruct); + + // strip the double abi encoding, not need for arguments + state[0] = new bytes(state0.length - 0x20); + memcpy(state0, 0x20, state[0], 0, state0.length - 0x20); + + bytes[] memory returnedState = weiVM.execute(commands, state); + + // console2.logBytes(returnedState[1]); + + // (string memory a, ) = abi.decode(returnedState[1], (string, string)); + // assertEq(a, "Hello"); + } + + function memcpy( + bytes memory src, + uint256 srcIdx, + bytes memory dest, + uint256 destIdx, + uint256 len + ) internal view { + assembly { + pop( + staticcall( + gas(), + 4, + add(add(src, 32), srcIdx), + len, + add(add(dest, 32), destIdx), + len + ) + ) + } + } } diff --git a/test/commandbuilder.js b/test/commandbuilder.js index 6854763..0392ce6 100644 --- a/test/commandbuilder.js +++ b/test/commandbuilder.js @@ -134,6 +134,9 @@ describe("CommandBuilder", function () { const {commands, state} = planner.plan(); + console.log({commands}); + console.log({state}); + await executeBuildInputs(commands, state, abiout, "Struct.returnStringStruct"); }); From 0b2d8ceae8848d42bf7d6261a95b77a8aabc9649 Mon Sep 17 00:00:00 2001 From: Gavin Pacini Date: Tue, 29 Nov 2022 00:45:03 -0500 Subject: [PATCH 09/10] fix formatting and returnStringStruct test --- test/VM.t.sol | 18 +++++++++++++----- test/VMDeployed.t.sol | 11 ++++++----- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/test/VM.t.sol b/test/VM.t.sol index 7615dc0..8764768 100644 --- a/test/VM.t.sol +++ b/test/VM.t.sol @@ -162,7 +162,7 @@ contract TestWeiVM is Test { commands[0] = WeirollPlanner.buildCommand( structer.returnStringStruct.selector, - 0x81, // delegate call + 0x81, // call and set tup bit 0x80ffffffffff, // use index 0 as variable input 0x01, // store into index 1 address(structer) @@ -174,16 +174,24 @@ contract TestWeiVM is Test { // abi encode bytes memory state0 = abi.encode(stringStruct); - // strip the double abi encoding, not need for arguments + // strip the double abi encoding, not needed for arguments state[0] = new bytes(state0.length - 0x20); memcpy(state0, 0x20, state[0], 0, state0.length - 0x20); bytes[] memory returnedState = weiVM.execute(commands, state); - // console2.logBytes(returnedState[1]); + // for some reason, we need to get rid of the first 32 bytes. What's in there? + bytes memory returnedState1 = new bytes(returnedState[1].length - 0x20); + memcpy( + returnedState[1], + 0x20, + returnedState1, + 0, + returnedState[1].length - 0x20 + ); - // (string memory a, ) = abi.decode(returnedState[1], (string, string)); - // assertEq(a, "Hello"); + (string memory a, ) = abi.decode(returnedState1, (string, string)); + assertEq(a, "Hello"); } function memcpy( diff --git a/test/VMDeployed.t.sol b/test/VMDeployed.t.sol index 065a6fe..76bd7bf 100644 --- a/test/VMDeployed.t.sol +++ b/test/VMDeployed.t.sol @@ -69,7 +69,9 @@ contract TestWeiVMDeployed is Test { ); // extended in indices - commands[1] = 0x0001020304050607ffffffffffffffffffffffffffffffffffffffffffffffff; + commands[ + 1 + ] = 0x0001020304050607ffffffffffffffffffffffffffffffffffffffffffffffff; bytes[] memory state = new bytes[](9); bytes memory paramBytes = abi.encode(params); @@ -90,10 +92,9 @@ contract TestWeiVMDeployed is Test { } } - (bool success, bytes memory data) = address(weiVM) - .delegatecall( - abi.encodeWithSelector(weiVM.execute.selector, commands, state) - ); + (bool success, bytes memory data) = address(weiVM).delegatecall( + abi.encodeWithSelector(weiVM.execute.selector, commands, state) + ); assertTrue(success); From 9f865108e00942fd06c2015d048b090664f8a9c2 Mon Sep 17 00:00:00 2001 From: Gavin Pacini Date: Tue, 29 Nov 2022 00:47:46 -0500 Subject: [PATCH 10/10] fuzz return string struct --- test/VM.t.sol | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/test/VM.t.sol b/test/VM.t.sol index 8764768..ba2e07d 100644 --- a/test/VM.t.sol +++ b/test/VM.t.sol @@ -194,6 +194,48 @@ contract TestWeiVM is Test { assertEq(a, "Hello"); } + function testFuzzReturnStringStruct(string memory _a, string memory _b) public { + Struct.StringStruct memory stringStruct = Struct.StringStruct({ + a: _a, + b: _b + }); + + bytes32[] memory commands = new bytes32[](1); + + commands[0] = WeirollPlanner.buildCommand( + structer.returnStringStruct.selector, + 0x81, // call and set tup bit + 0x80ffffffffff, // use index 0 as variable input + 0x01, // store into index 1 + address(structer) + ); + + // state needs to be large enough to store the result at index 1 + bytes[] memory state = new bytes[](2); + + // abi encode + bytes memory state0 = abi.encode(stringStruct); + + // strip the double abi encoding, not needed for arguments + state[0] = new bytes(state0.length - 0x20); + memcpy(state0, 0x20, state[0], 0, state0.length - 0x20); + + bytes[] memory returnedState = weiVM.execute(commands, state); + + // for some reason, we need to get rid of the first 32 bytes. What's in there? + bytes memory returnedState1 = new bytes(returnedState[1].length - 0x20); + memcpy( + returnedState[1], + 0x20, + returnedState1, + 0, + returnedState[1].length - 0x20 + ); + + (string memory a, ) = abi.decode(returnedState1, (string, string)); + assertEq(a, _a); + } + function memcpy( bytes memory src, uint256 srcIdx,