diff --git a/foundry_test/modules/utils/L2CompressorHuffReadTx.t.sol b/foundry_test/modules/utils/L2CompressorHuffReadTx.t.sol index eff7934..e1c5e2c 100644 --- a/foundry_test/modules/utils/L2CompressorHuffReadTx.t.sol +++ b/foundry_test/modules/utils/L2CompressorHuffReadTx.t.sol @@ -9,8 +9,196 @@ 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; +function requiredBytesFor(bytes32 value) pure returns (uint8) { + return requiredBytesFor(uint256(value)); +} + +function requiredBytesFor(uint256 value) pure returns (uint8) { + if (value <= type(uint8).max) { + return 1; + } else if (value <= type(uint16).max) { + return 2; + } else if (value <= type(uint24).max) { + return 3; + } else if (value <= type(uint32).max) { + return 4; + } else if (value <= type(uint40).max) { + return 5; + } else if (value <= type(uint48).max) { + return 6; + } else if (value <= type(uint56).max) { + return 7; + } else if (value <= type(uint64).max) { + return 8; + } else if (value <= type(uint72).max) { + return 9; + } else if (value <= type(uint80).max) { + return 10; + } else if (value <= type(uint88).max) { + return 11; + } else if (value <= type(uint96).max) { + return 12; + } else if (value <= type(uint104).max) { + return 13; + } else if (value <= type(uint112).max) { + return 14; + } else if (value <= type(uint120).max) { + return 15; + } else if (value <= type(uint128).max) { + return 16; + } else if (value <= type(uint136).max) { + return 17; + } else if (value <= type(uint144).max) { + return 18; + } else if (value <= type(uint152).max) { + return 19; + } else if (value <= type(uint160).max) { + return 20; + } else if (value <= type(uint168).max) { + return 21; + } else if (value <= type(uint176).max) { + return 22; + } else if (value <= type(uint184).max) { + return 23; + } else if (value <= type(uint192).max) { + return 24; + } else if (value <= type(uint200).max) { + return 25; + } else if (value <= type(uint208).max) { + return 26; + } else if (value <= type(uint216).max) { + return 27; + } else if (value <= type(uint224).max) { + return 28; + } else if (value <= type(uint232).max) { + return 29; + } else if (value <= type(uint240).max) { + return 30; + } else if (value <= type(uint248).max) { + return 31; + } + + return 32; +} + +function packToBytes(bytes32 value, uint256 b) pure returns (bytes memory) { + return packToBytes(uint256(value), b); +} + +function packToBytes(uint256 value, uint256 b) pure returns (bytes memory) { + if (b == 1) { + return abi.encodePacked(uint8(value)); + } else if (b == 2) { + return abi.encodePacked(uint16(value)); + } else if (b == 3) { + return abi.encodePacked(uint24(value)); + } else if (b == 4) { + return abi.encodePacked(uint32(value)); + } else if (b == 5) { + return abi.encodePacked(uint40(value)); + } else if (b == 6) { + return abi.encodePacked(uint48(value)); + } else if (b == 7) { + return abi.encodePacked(uint56(value)); + } else if (b == 8) { + return abi.encodePacked(uint64(value)); + } else if (b == 9) { + return abi.encodePacked(uint72(value)); + } else if (b == 10) { + return abi.encodePacked(uint80(value)); + } else if (b == 11) { + return abi.encodePacked(uint88(value)); + } else if (b == 12) { + return abi.encodePacked(uint96(value)); + } else if (b == 13) { + return abi.encodePacked(uint104(value)); + } else if (b == 14) { + return abi.encodePacked(uint112(value)); + } else if (b == 15) { + return abi.encodePacked(uint120(value)); + } else if (b == 16) { + return abi.encodePacked(uint128(value)); + } else if (b == 17) { + return abi.encodePacked(uint136(value)); + } else if (b == 18) { + return abi.encodePacked(uint144(value)); + } else if (b == 19) { + return abi.encodePacked(uint152(value)); + } else if (b == 20) { + return abi.encodePacked(uint160(value)); + } else if (b == 21) { + return abi.encodePacked(uint168(value)); + } else if (b == 22) { + return abi.encodePacked(uint176(value)); + } else if (b == 23) { + return abi.encodePacked(uint184(value)); + } else if (b == 24) { + return abi.encodePacked(uint192(value)); + } else if (b == 25) { + return abi.encodePacked(uint200(value)); + } else if (b == 26) { + return abi.encodePacked(uint208(value)); + } else if (b == 27) { + return abi.encodePacked(uint216(value)); + } else if (b == 28) { + return abi.encodePacked(uint224(value)); + } else if (b == 29) { + return abi.encodePacked(uint232(value)); + } else if (b == 30) { + return abi.encodePacked(uint240(value)); + } else if (b == 31) { + return abi.encodePacked(uint248(value)); + } else if (b == 32) { + return abi.encodePacked(uint256(value)); + } else { + revert("Invalid number of bytes"); + } +} + +function encodeWord(uint256 _value) pure returns (bytes memory) { + uint8 b = requiredBytesFor(_value); + return abi.encodePacked(b, packToBytes(_value, b)); +} + +function build_flag(bool _delegateCall, bool _revertOnError, bool _hasGasLimit, bool _hasValue, bool _hasData) pure returns (uint8) { + uint8 res = 0; + + if (_delegateCall) { + res |= 128; + } + + if (_revertOnError) { + res |= 64; + } + + if (_hasGasLimit) { + res |= 32; + } + + if (_hasValue) { + res |= 16; + } + + // Hasdata uses first bit + if (_hasData) { + res |= 1; + } + + return res; +} + +function encode_raw_address(address _addr) pure returns (bytes memory) { + return encodeWord(uint256(uint160(_addr))); +} + +function encode_bytes_n(bytes memory _data) pure returns (bytes memory) { + return abi.encodePacked(uint8(0x2b), encodeWord(_data.length), _data); +} + contract L2CompressorHuffReadTxTests is AdvTest { address public imp; @@ -23,14 +211,74 @@ contract L2CompressorHuffReadTxTests is AdvTest { ); } - function test_read_simple_transaction(bytes32 _data) external { - (bool s, bytes memory r) = imp.staticcall( - abi.encodePacked(uint8(0xc0), uint8(0x14), address(0x999999cf1046e68e36E1aA2E0E07105eDDD1f08E)) + function test_read_simple_transaction(address _addr) external { + bytes memory encoded = abi.encodePacked(build_flag(true, true, false, false, false), encode_raw_address(_addr)); + + (bool s, bytes memory r) = imp.staticcall(encoded); + + assertTrue(s); + (uint256 rindex, uint256 windex, bytes memory res) = abi.decode(r, (uint256, uint256, bytes)); + + IModuleCalls.Transaction memory t; + t.delegateCall = true; + t.revertOnError = true; + t.target = _addr; + + assertEq(rindex, encoded.length); + assertEq(windex, res.length + FMS); + + // Abi encode prefixes with the point on which the data starts + // we don't do it on the compressor, so we need to append 32 + assertEq(abi.encodePacked(abi.encode(32), res), abi.encode(t)); + } + + function test_read_simple_transaction_with_data(address _addr, bytes memory _data) external { + bytes memory encoded = abi.encodePacked( + build_flag(true, true, false, false, true), + encode_raw_address(_addr), + encode_bytes_n(_data) + ); + + (bool s, bytes memory r) = imp.staticcall(encoded); + + assertTrue(s); + (uint256 rindex, uint256 windex, bytes memory res) = abi.decode(r, (uint256, uint256, bytes)); + + IModuleCalls.Transaction memory t; + t.delegateCall = true; + t.revertOnError = true; + t.target = _addr; + t.data = _data; + + assertEq(rindex, encoded.length); + assertEq(windex, res.length + FMS); + + // Abi encode prefixes with the point on which the data starts + // we don't do it on the compressor, so we need to append 32 + assertEq(abi.encodePacked(abi.encode(32), res), abi.encode(t)); + } + + function test_read_transaction(IModuleCalls.Transaction memory _tx) external { + bytes memory encoded = abi.encodePacked( + build_flag(_tx.delegateCall, _tx.revertOnError, _tx.gasLimit != 0, _tx.value != 0, _tx.data.length != 0), + _tx.gasLimit != 0 ? encodeWord(_tx.gasLimit) : bytes(""), + encode_raw_address(_tx.target), + _tx.value != 0 ? encodeWord(_tx.value) : bytes(""), + _tx.data.length != 0 ? encode_bytes_n(_tx.data) : bytes("") ); + console.logBytes(encoded); + + (bool s, bytes memory r) = imp.staticcall(encoded); + assertTrue(s); (uint256 rindex, uint256 windex, bytes memory res) = abi.decode(r, (uint256, uint256, bytes)); - console.logBytes(res); + assertEq(rindex, encoded.length); + assertEq(windex, res.length + FMS); + + // Abi encode prefixes with the point on which the data starts + // we don't do it on the compressor, so we need to append 32 + assertEq(abi.encodePacked(abi.encode(32), res), abi.encode(_tx)); } } diff --git a/src/L2Compressor.huff b/src/L2Compressor.huff index db4edf6..662adf1 100644 --- a/src/L2Compressor.huff +++ b/src/L2Compressor.huff @@ -143,7 +143,11 @@ #define constant HIGHEST_FLAG = 0x32 #define macro READ_FLAG() = takes (2) returns (2) { - nested_read_flag_start: + nrfs: + FN_READ_FLAG(nrfs) +} + +#define macro FN_READ_FLAG(nrfs) = takes (2) returns (2) { // input stack: [windex, rindex] dup2 // [rindex, windex, rindex] @@ -292,7 +296,7 @@ end jump JUMP_READ_N_BYTES: - READ_N_BYTES(nested_read_flag_start) // [windex, rindex] + READ_N_BYTES(nrfs) // [windex, rindex] end jump JUMP_READ_POWER_OF_2: @@ -300,25 +304,25 @@ end jump FLAG_ABI_0_PARAM: - READ_ABI(nested_read_flag_start, 0x00) // [windex, rindex] + READ_ABI(nrfs, 0x00) // [windex, rindex] end jump FLAG_ABI_1_PARAM: - READ_ABI(nested_read_flag_start, 0x01) // [windex, rindex] + READ_ABI(nrfs, 0x01) // [windex, rindex] end jump FLAG_ABI_2_PARAMS: - READ_ABI(nested_read_flag_start, 0x02) // [windex, rindex] + READ_ABI(nrfs, 0x02) // [windex, rindex] end jump FLAG_ABI_3_PARAMS: - READ_ABI(nested_read_flag_start, 0x03) // [windex, rindex] + READ_ABI(nrfs, 0x03) // [windex, rindex] end jump FLAG_ABI_4_PARAMS: - READ_ABI(nested_read_flag_start, 0x04) // [windex, rindex] + READ_ABI(nrfs, 0x04) // [windex, rindex] end jump FLAG_ABI_5_PARAMS: - READ_ABI(nested_read_flag_start, 0x05) // [windex, rindex] + READ_ABI(nrfs, 0x05) // [windex, rindex] end jump FLAG_ABI_6_PARAMS: - READ_ABI(nested_read_flag_start, 0x06) // [windex, rindex] + READ_ABI(nrfs, 0x06) // [windex, rindex] end jump default: @@ -442,7 +446,15 @@ 0x03 eq ASSERT() // [] } -#define macro READ_TRANSACTION() = takes (2) returns (2) { +#define macro READ_TRANSACTION_STANDALONE() = takes (2) returns (2) { + skip jump + rf: + READ_FLAG() + skip: + READ_TRANSACTION(rf) +} + +#define macro READ_TRANSACTION(nrfs) = takes (2) returns (2) { // input stack: [windex, rindex] // The first byte gives us information about what the transaction contains @@ -469,7 +481,7 @@ dup2 // [tflag, windex, tflag, rindex] 0x06 shr // [tflag >> 0x06, windex, tflag, rindex] 0x01 and // [tflag >> 0x06 & 0x01, windex, tflag, rindex] - dup3 // [windex, tflag >> 0x06 & 0x01, windex, tflag, rindex] + dup2 // [windex, tflag >> 0x06 & 0x01, windex, tflag, rindex] mstore // [windex, tflag, rindex] 0x20 add // [windex + 0x20, tflag, rindex] @@ -504,14 +516,14 @@ swap2 // [rindex, windex, tflag] swap1 // [windex, rindex, tflag] - READ_FLAG() // [windex, rindex, tflag] + PERFORM_NESTED_READ_FLAG() // [windex, rindex, tflag] end_gas_Limit_if: // All transactions must define an address // this is simple, as it is just one more flag - READ_FLAG() + PERFORM_NESTED_READ_FLAG() // 4th bit of the flag determines if the transaction has a defined value @@ -532,7 +544,7 @@ // Read advanced; this should only increase 32 bytes // but we don't check that, buyer beware - READ_FLAG() // [windex, rindex, tflag] + PERFORM_NESTED_READ_FLAG() // [windex, rindex, tflag] end_value_if: @@ -550,36 +562,65 @@ // The transaction has no data, we still need to write 0s // both for the pointer and size - dup1 // [windex, windex, rindex] - 0x20 add // [windex + 0x20, windex, rindex] - dup1 // [windex + 0x20, windex + 0x20, windex, rindex] - swap2 // [windex, windex + 0x20, windex + 0x20, rindex] - mstore // [windex + 0x20, rindex] - 0x00 // [0x00, windex + 0x20, rindex] - dup2 // [windex + 0x20, 0x00, windex + 0x20, rindex] - mstore // [windex + 0x20, rindex] - 0x20 add // [windex + 0x40, rindex] + // All tx strucs have the same number of parameters, so 0xc0 is always the correct + //place for the start of the bytes data + + 0xc0 // [0xc0, windex, rindex] + dup2 // [windex, 0xc0, windex, rindex] + mstore // [windex, rindex] + + 0x20 // [0x20, windex, rindex] + add // [(0x20 + windex), rindex] + 0x00 // [0x00, (0x20 + windex), rindex] + dup2 // [(0x20 + windex), 0x00, (0x20 + windex), rindex] + mstore // [windex, rindex] + + 0x20 // [0x20, windex, rindex] + add // [(0x20 + windex), rindex] end_data_if jump has_data: // [windex, rindex] - swap1 // [rindex, windex] - dup2 // [windex, rindex, prev_windex] + // All tx strucs have the same number of parameters, so 0xc0 is always the correct + //place for the start of the bytes data + + 0xc0 // [0xc0, windex, rindex] + dup2 // [windex, 0xc0, windex, rindex] + mstore // [windex, rindex] + 0x20 add // [windex, rindex] // Leave some room to store the size of the data 0x20 add // [windex + 0x20, rindex, prev_windex] - READ_FLAG() // [windex, rindex, prev_windex] + swap1 // [rindex, windex] + dup2 // [windex, rindex, prev_windex] + + PERFORM_NESTED_READ_FLAG() // [windex, rindex, prev_windex] dup3 // [prev_windex, windex, rindex, prev_windex] dup2 // [windex, prev_windex, windex, rindex, prev_windex] - sub // [(windex - prev_windex), windex, rindex, prev_windex] - 0x20 add // [size, windex, rindex, prev_windex] + sub // [size, windex, rindex, prev_windex] - dup4 // [prev_windex, size, windex, rindex, prev_windex] - mstore // [windex, rindex, prev_windex] - swap2 // [prev_windex, rindex, windex] + dup1 // [size, size, windex, rindex, prev_windex] + + 0x20 // [0x20, size, size, windex, rindex, prev_windex] + dup6 // [prev_windex, 0x20, size, size, windex, rindex, prev_windex] + sub // [(prev_windex - 0x20), size, size, windex, rindex, prev_windex] + mstore // [size, windex, rindex, prev_windex] + + // Write some zeros just in case + 0x00 // [0x00, size, windex, rindex, prev_windex] + dup3 // [windex, 0x00, size, windex, rindex, prev_windex] + mstore // [size, windex, rindex, prev_windex] + + // Advance the windex enough so index becomes divisible by 32 + 0x1f and // [size % 32, windex, rindex, prev_windex] + 0x20 sub // [pad_diff, windex, rindex, prev_windex] + 0x1f and // [pad_diff % 32, windex, rindex, prev_windex] + add // [windex + pad_diff, rindex, prev_windex] + + swap2 // [prev_windex, rindex, windex + pad_diff] pop // [rindex, windex] swap1 // [windex, rindex] @@ -645,6 +686,7 @@ 0x1f and // [size % 32, size, rindex, windex, prev_index] 0x20 sub // [32 - size % 32, size, rindex, windex, prev_index] + 0x1f and // [(32 - size % 32) % 32, size, rindex, windex, prev_index] // Zero out the memory, just in case @@ -1183,4 +1225,4 @@ 0x7ef5838bb8 eq ASSERT() // [rindex] 0x09 eq ASSERT() // [] -} \ No newline at end of file +} diff --git a/src/imps/L2CompressorReadTx.huff b/src/imps/L2CompressorReadTx.huff index 3aa9e75..7b81f2e 100644 --- a/src/imps/L2CompressorReadTx.huff +++ b/src/imps/L2CompressorReadTx.huff @@ -10,7 +10,7 @@ 0x00 // [rindex] [FMS] // [windex, rindex] - READ_TRANSACTION() // [windex, rindex] + READ_TRANSACTION_STANDALONE() // [windex, rindex] [FMS] // [0xa0, windex, rindex] dup2 // [windex, 0xa0, windex, rindex]