diff --git a/.gitignore b/.gitignore index 2a2e31f..a191362 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,8 @@ node_modules cache artifacts +#Foundry files +out +cache_foundry + yarn.lock 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/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" +``` diff --git a/foundry.toml b/foundry.toml new file mode 100644 index 0000000..be3c942 --- /dev/null +++ b/foundry.toml @@ -0,0 +1,16 @@ + +[profile.default] +src = 'contracts' +out = 'out' +cache_path = 'cache_foundry' +libs = ['lib'] + +solc = '0.8.16' +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/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 diff --git a/package-lock.json b/package-lock.json index 7531d91..abf9a9d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,16 @@ { "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", + "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", @@ -9047,6 +9108,7 @@ }, "node_modules/ganache-core/node_modules/keccak": { "version": "3.0.1", + "dev": true, "hasInstallScript": true, "inBundle": true, "license": "MIT", @@ -9619,6 +9681,7 @@ }, "node_modules/ganache-core/node_modules/node-addon-api": { "version": "2.0.2", + "dev": true, "inBundle": true, "license": "MIT" }, @@ -9632,6 +9695,7 @@ }, "node_modules/ganache-core/node_modules/node-gyp-build": { "version": "4.2.3", + "dev": true, "inBundle": true, "license": "MIT", "bin": { @@ -17716,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", @@ -17963,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", @@ -23143,6 +23251,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 +23681,8 @@ }, "node-addon-api": { "version": "2.0.2", - "bundled": true + "bundled": true, + "dev": true }, "node-fetch": { "version": "2.1.2", @@ -23580,7 +23690,8 @@ }, "node-gyp-build": { "version": "4.2.3", - "bundled": true + "bundled": true, + "dev": true }, "normalize-url": { "version": "4.5.0", diff --git a/package.json b/package.json index cafa67e..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", diff --git a/remappings.txt b/remappings.txt new file mode 100644 index 0000000..b82056c --- /dev/null +++ b/remappings.txt @@ -0,0 +1,5 @@ +ds-test/=lib/forge-std/lib/ds-test/src/ +forge-std/=lib/forge-std/src/ +@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.t.sol b/test/VM.t.sol new file mode 100644 index 0000000..ba2e07d --- /dev/null +++ b/test/VM.t.sol @@ -0,0 +1,259 @@ +// 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/test/Struct.sol"; +import "../contracts/Libraries/Events.sol"; +import "../contracts/Libraries/Math.sol"; + +contract TestWeiVM is Test { + event LogAddress(address message); + + TestableVM weiVM; + 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 { + 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) + ); + } + + 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 index 1 + 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); + } + + 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, // 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, "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, + 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/VMDeployed.t.sol b/test/VMDeployed.t.sol new file mode 100644 index 0000000..76bd7bf --- /dev/null +++ b/test/VMDeployed.t.sol @@ -0,0 +1,109 @@ +// 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..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"); }); diff --git a/test/weiroll.sol b/test/weiroll.sol new file mode 100644 index 0000000..34b3b3f --- /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)) << 216; + uint256 input = uint256(uint48(_input)) << 168; + uint256 output = uint256(uint8(_output)) << 160; + uint256 target = uint256(uint160(_target)); + + return bytes32(selector ^ flags ^ input ^ output ^ target); + } +}