diff --git a/foundry_test/modules/utils/L2CompressorHuffReadExecute.sol b/foundry_test/modules/utils/L2CompressorHuffReadExecute.sol new file mode 100644 index 0000000..eb98d4a --- /dev/null +++ b/foundry_test/modules/utils/L2CompressorHuffReadExecute.sol @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "foundry_test/base/AdvTest.sol"; + +import "forge-std/console.sol"; +import "forge-std/console2.sol"; + +import { HuffConfig } from "foundry-huff/HuffConfig.sol"; +import { HuffDeployer } from "foundry-huff/HuffDeployer.sol"; + +import "contracts/modules/commons/interfaces/IModuleCalls.sol"; + +uint256 constant FMS = 0xa0; + +import "./L2CompressorEncoder.sol"; + +contract L2CompressorHuffReadExecuteTest is AdvTest { + address public imp; + + function setUp() public { + imp = address( + HuffDeployer + .config() + .with_evm_version("paris") + .deploy("imps/L2CompressorReadExecute") + ); + } + + function test_read_simple_execute( + IModuleCalls.Transaction[] calldata _txs, + uint160 _space, + uint96 _nonce, + bytes calldata _signature + ) external { + vm.assume(_txs.length != 0 && _txs.length <= type(uint8).max); + + bytes32 packedNonce = abi.decode(abi.encodePacked(_space, _nonce), (bytes32)); + + bytes memory encoded = abi.encodePacked( + encodeWord(_space), encodeWord(_nonce), + uint8(_txs.length) + ); + + for (uint256 i = 0; i < _txs.length; i++) { + IModuleCalls.Transaction memory t = _txs[i]; + + encoded = abi.encodePacked( + encoded, + build_flag(t.delegateCall, t.revertOnError, t.gasLimit != 0, t.value != 0, t.data.length != 0), + t.gasLimit != 0 ? encodeWord(t.gasLimit) : bytes(""), + encode_raw_address(t.target), + t.value != 0 ? encodeWord(t.value) : bytes(""), + t.data.length != 0 ? encode_bytes_n(t.data) : bytes("") + ); + } + + encoded = abi.encodePacked( + encoded, + encode_bytes_n(_signature) + ); + + (bool s, bytes memory r) = imp.staticcall(encoded); + + assertTrue(s); + (uint256 rindex, uint256 windex, bytes memory res) = abi.decode(r, (uint256, uint256, bytes)); + + assertEq(rindex, encoded.length); + assertEq(windex, res.length + FMS); + + // Encode using solidity + bytes memory solidityEncoded = abi.encodeWithSelector(IModuleCalls.execute.selector, _txs, packedNonce, _signature); + assertEq(solidityEncoded, res); + } +} diff --git a/src/L2Compressor.huff b/src/L2Compressor.huff index 554868c..e0fcc23 100644 --- a/src/L2Compressor.huff +++ b/src/L2Compressor.huff @@ -13,12 +13,17 @@ #define constant BYTES32_SMV = 0x80 #define constant ADDRESS_SMV = 0x01 -// #define macro MAIN() = takes (0) returns (0) { -// 0x00 // [rindex] -// [FMS] // [windex, rindex] +// #define jumptable FLAG_SELECTORS { +// execute_many_transactions: +// execute_transaction: +// read_address: +// read_bytes32: +// sizes: + +// } -// 0x01 // [flag, windex, rindex] -// READ_FLAG() // [windex, rindex] +// #define macro MAIN() = takes (0) returns (0) { + // } #define macro ADDRESSES_NUM() = takes (0) returns (1) { @@ -140,7 +145,7 @@ FLAG_ABI_6_PARAMS // 0x33 } -#define constant HIGHEST_FLAG = 0x32 +#define constant HIGHEST_FLAG = 0x33 #define macro READ_FLAG() = takes (2) returns (2) { nrfs: @@ -451,6 +456,96 @@ 0x03 eq ASSERT() // [] } +#define macro READ_EXECUTE_STANDALONE() = takes (2) returns (2) { + skip jump + rf: + READ_FLAG() + skip: + READ_EXECUTE(rf) +} + +#define macro READ_EXECUTE(nrfs) = takes (2) returns (2) { + // input stack: [windex, rindex] + + // The execution function signature of Sequence is 0x7a9a1628 + + __RIGHTPAD(0x7a9a1628) // [0x7a9a1628, windex, rindex] + dup2 // [windex, 0x7a9a1628, windex, rindex] + mstore // [windex, rindex] + 0x04 add // [windex, rindex] + + // The first value is always where do the list of transactions starts + // this is always the same, as the list of transactions is the first + // dynamic type + + 0x60 // [0x60, windex, rindex] + dup2 // [windex, 0x60, windex, rindex] + mstore // [windex, rindex] + 0x20 add // [windex, rindex] + + // Reading the nonce is the simplest one, it is just a value + + READ_NONCE(nrfs) // [windex, rindex] + + // We can't know when the signature will start, since we need to + // read the list of transactions first. So we leave a copy of the pointer + // to write it later. + + swap1 // [rindex, windex] + dup2 // [windex, rindex, prev_windex] + 0x20 add // [windex, rindex, prev_windex] + + // We start reading the transactions, the macro takes care of writting the + // internal pointers for them (and the number of transactions) + + READ_TRANSACTIONS(nrfs) // [windex, rindex, prev_windex] + + // The signature starts at windex - prev_windex + 0x20 + // and the pointer needs to be written to prev_windex + + swap1 // [rindex, windex, prev_windex] + swap2 // [prev_windex, windex, rindex] + dup1 // [prev_windex, prev_windex, windex, rindex] + dup3 // [windex, prev_windex, prev_windex, windex, rindex] + sub // [(windex - prev_windex), prev_windex, windex, rindex] + 0x40 add // [sig_starts, prev_windex, windex, rindex] + swap1 // [prev_windex, sig_starts, windex, rindex] + + mstore // [windex, rindex] + + // Now we can read the signature, we just read a nested flag, it can generate + // a Sequence signature. We only need to take care of the size and the padding + + 0x20 add // [windex, rindex] + swap1 // [rindex, windex] + dup2 // [windex, rindex, prev_index] + + PERFORM_NESTED_READ_FLAG(nrfs) + + swap1 // [rindex, windex, prev_windex] + swap2 // [prev_windex, windex, rindex] + dup1 // [prev_windex, prev_windex, windex, rindex] + dup3 // [windex, prev_windex, prev_windex, windex, rindex] + sub // [size, prev_windex, windex, rindex] + dup1 // [size, size, prev_windex, windex, rindex] + swap2 // [prev_windex, size, size, windex, rindex] + 0x20 swap1 sub // [size_place, size, size, windex, rindex] + mstore // [size, windex, rindex] + + // Last thing is handling the padding, bytes need to be multiple of 0x20 + + 0x00 // [0x00, size, windex, rindex] + dup3 // [windex, 0x00, size, windex, rindex] + mstore // [size, windex, rindex] + + 0x1f and // [size % 32, windex, rindex] + 0x20 sub // [pad_diff, windex, rindex] + 0x1f and // [pad_diff % 32, windex, rindex] + add // [(padd_diff + windex), rindex] + + // output stack: [windex, rindex] +} + #define macro READ_NONCE_STANDALONE() = takes (2) returns (2) { skip jump rf: @@ -579,7 +674,17 @@ // easily get the i and compare it with the len of transactions to know if we must continue or not dup7 // [tx_num, i, windex, rindex, pos, ts_index, windex, tx_num, i] - xor do_tx jumpi // [windex, rindex, pos, ts_index, windex, tx_num] + xor do_tx jumpi // [windex, rindex, pos, ts_index, windex, tx_num, i] + + pop // [rindex, pos, ts_index, windex, tx_num, i] + swap5 // [i, pos, ts_index, windex, tx_num, rindex] + pop // [pos, ts_index, windex, tx_num, rindex] + pop // [ts_index, windex, tx_num, rindex] + pop // [windex, tx_num, rindex] + swap1 // [tx_num, windex, rindex] + pop // [windex, rindex] + + // output stack: [windex, rindex] } #define macro READ_TRANSACTION_STANDALONE() = takes (2) returns (2) { diff --git a/src/imps/L2CompressorReadExecute.huff b/src/imps/L2CompressorReadExecute.huff new file mode 100644 index 0000000..b2eb67e --- /dev/null +++ b/src/imps/L2CompressorReadExecute.huff @@ -0,0 +1,30 @@ +#include "../L2Compressor.huff" + +#define constant FMS = 0xa0 + +// Function Dispatching +#define macro MAIN() = takes (1) returns (1) { + // readAdvanced with whatever calldata is passed + // first 32 bytes returns the new rindex and the next 32 bytes returns the new windex + + 0x00 // [rindex] + [FMS] // [windex, rindex] + + READ_EXECUTE_STANDALONE() // [windex, rindex] + + [FMS] // [0xa0, windex, rindex] + dup2 // [windex, 0xa0, windex, rindex] + sub // [len, windex, rindex] + + swap2 // [rindex, windex, len] + + 0x80 [FMS] sub mstore // [windex, len] + 0x60 [FMS] sub mstore // [len] + + 0x60 0x40 [FMS] sub mstore // [len] + dup1 0x20 [FMS] sub mstore // [len] + + 0x80 add // [len + 0x80] + + 0x80 [FMS] sub return +}