diff --git a/contracts/src/_testing/unit/libraries/TestEip1559RlpEncoder.sol b/contracts/src/_testing/unit/libraries/TestEip1559RlpEncoder.sol new file mode 100644 index 0000000000..33b95e5436 --- /dev/null +++ b/contracts/src/_testing/unit/libraries/TestEip1559RlpEncoder.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity 0.8.28; + +import { Eip1559RlpEncoder } from "../../../libraries/Eip1559RlpEncoder.sol"; + +contract TestEip1559RlpEncoder { + uint256 chainId; + + constructor(uint256 _chainId) { + chainId = _chainId; + } + + function encodeEip1559Transaction( + Eip1559RlpEncoder.Eip1559Transaction calldata _transaction + ) external view returns (bytes memory rlpEncodedTransaction, bytes32 transactionHash) { + (rlpEncodedTransaction, transactionHash) = Eip1559RlpEncoder._encodeEip1559Transaction(chainId, _transaction); + } +} diff --git a/contracts/src/_testing/unit/libraries/TestRlpEncoder.sol b/contracts/src/_testing/unit/libraries/TestRlpEncoder.sol new file mode 100644 index 0000000000..d2a2394697 --- /dev/null +++ b/contracts/src/_testing/unit/libraries/TestRlpEncoder.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity 0.8.28; + +import { RlpEncoder } from "../../../libraries/RlpEncoder.sol"; + +contract TestRlpEncoder { + function encodeBool(bool _boolIn) external pure returns (bytes memory encodedBytes) { + return RlpEncoder._encodeBool(_boolIn); + } + + function encodeString(string memory _stringIn) external pure returns (bytes memory encodedBytes) { + return RlpEncoder._encodeString(_stringIn); + } + + function encodeInt(int _intIn) external pure returns (bytes memory encodedBytes) { + return RlpEncoder._encodeInt(_intIn); + } +} diff --git a/contracts/src/libraries/Eip1559RlpEncoder.sol b/contracts/src/libraries/Eip1559RlpEncoder.sol new file mode 100644 index 0000000000..dddc9e98a0 --- /dev/null +++ b/contracts/src/libraries/Eip1559RlpEncoder.sol @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: Apache-2.0 + +/** + * @title Library for RLP Encoding EIP-1559 transactions. + * @author ConsenSys Software Inc. + * @custom:security-contact security-report@linea.build + */ +pragma solidity ^0.8.28; + +import { RlpEncoder } from "./RlpEncoder.sol"; + +/** + * @title Library for RLP Encoding a type 2 EIP-1559 transactions. + * @author ConsenSys Software Inc. + * @custom:security-contact security-report@linea.build + */ +library Eip1559RlpEncoder { + /** + * @notice Supporting data for encoding an EIP-1559 transaction. + * @dev NB: ChainId is not on the struct to allow flexibility by the consuming contract. + * @dev NB: Access lists is assumed empty and does not appear here. + * @dev nonce The sender's nonce. + * @dev maxPriorityFeePerGas The max priority fee the user will pay. + * @dev maxFeePerGas The max fee per gas the user will pay. + * @dev gasLimit The limit of gas that the user is prepared to spend. + * @dev input The calldata input for the transaction. + * @dev accessList The access list used. + * @dev yParity The yParity in the signature. + * @dev r The r in the signature. + * @dev s The s in the signature. + */ + struct Eip1559Transaction { + uint256 nonce; + uint256 maxPriorityFeePerGas; + uint256 maxFeePerGas; + uint256 gasLimit; + address to; + uint256 value; + bytes input; + RlpEncoder.AccessList[] accessList; + uint8 yParity; + uint256 r; + uint256 s; + } + + /** + * @notice Internal function that encodes bytes correctly with length data. + * @param _chainId The chainId to encode with. + * @param _transaction The EIP-1559 transaction excluding chainId. + * @return rlpEncodedTransaction The RLP encoded transaction for submitting. + * @return transactionHash The expected transaction hash. + */ + function _encodeEip1559Transaction( + uint256 _chainId, + Eip1559Transaction memory _transaction + ) internal pure returns (bytes memory rlpEncodedTransaction, bytes32 transactionHash) { + bytes[] memory fields = new bytes[](12); + + fields[0] = RlpEncoder._encodeUint(_chainId); + fields[1] = RlpEncoder._encodeUint(_transaction.nonce); + fields[2] = RlpEncoder._encodeUint(_transaction.maxPriorityFeePerGas); + fields[3] = RlpEncoder._encodeUint(_transaction.maxFeePerGas); + fields[4] = RlpEncoder._encodeUint(_transaction.gasLimit); + fields[5] = RlpEncoder._encodeAddress(_transaction.to); + fields[6] = RlpEncoder._encodeUint(_transaction.value); + fields[7] = RlpEncoder._encodeBytes(_transaction.input); + fields[8] = RlpEncoder._encodeAccessList(_transaction.accessList); + fields[9] = RlpEncoder._encodeUint(_transaction.yParity); + fields[10] = RlpEncoder._encodeUint(_transaction.r); + fields[11] = RlpEncoder._encodeUint(_transaction.s); + + rlpEncodedTransaction = abi.encodePacked(hex"02", RlpEncoder._encodeList(fields)); + transactionHash = keccak256(rlpEncodedTransaction); + } +} diff --git a/contracts/src/libraries/RlpEncoder.sol b/contracts/src/libraries/RlpEncoder.sol new file mode 100644 index 0000000000..1fae812f49 --- /dev/null +++ b/contracts/src/libraries/RlpEncoder.sol @@ -0,0 +1,268 @@ +// SPDX-License-Identifier: Apache-2.0 + +pragma solidity ^0.8.28; + +/** + * @title Library for RLP Encoding data. + * @author ConsenSys Software Inc. + * @custom:security-contact security-report@linea.build + * @custom:attribution https://github.com/bakaoh/solidity-rlp-encode + * @dev The internals have been significantly modified from the original for readability and gas. + */ +library RlpEncoder { + /** + * @notice Supporting data for encoding an EIP-2930/1559 access lists. + * @dev contractAddress is the address where the storageKeys will be accessed. + * @dev storageKeys contains the list of keys expected to be accessed at contractAddress. + */ + struct AccessList { + address contractAddress; + bytes32[] storageKeys; + } + + /** + * @notice Internal function that encodes bytes correctly with length data. + * @param _bytesIn The bytes to be encoded. + * @return encodedBytes The bytes RLP encoded. + */ + function _encodeBytes(bytes memory _bytesIn) internal pure returns (bytes memory encodedBytes) { + if (_bytesIn.length == 1 && uint8(_bytesIn[0]) < 0x80) return _bytesIn; + + bytes memory lengthPrefix = _encodeLength(_bytesIn.length, 128); + uint256 prefixLen = lengthPrefix.length; + uint256 dataLen = _bytesIn.length; + + assembly { + let totalLen := add(prefixLen, dataLen) + encodedBytes := mload(0x40) + mstore(encodedBytes, totalLen) + + let dest := add(encodedBytes, 0x20) + let src := add(lengthPrefix, 0x20) + mcopy(dest, src, prefixLen) + + dest := add(dest, prefixLen) + src := add(_bytesIn, 0x20) + mcopy(dest, src, dataLen) + + mstore(0x40, add(add(encodedBytes, 0x20), totalLen)) + } + } + + /** + * @notice Internal function that encodes a uint value as bytes. + * @param _uintIn The uint to be encoded. + * @return encodedBytes The uint encoded as bytes. + */ + function _encodeUint(uint256 _uintIn) internal pure returns (bytes memory encodedBytes) { + encodedBytes = _encodeBytes(_toBinary(_uintIn)); + } + + /** + * @notice Internal function that encodes an int value as bytes. + * @param _intIn The int to encode. + * @return encodedBytes The int encoded as bytes. + */ + function _encodeInt(int256 _intIn) internal pure returns (bytes memory encodedBytes) { + encodedBytes = _encodeUint(uint256(_intIn)); + } + + /** + * @notice Internal function that encodes a address value as bytes. + * @param _addressIn The address to be encoded. + * @return encodedBytes The address encoded as bytes. + */ + function _encodeAddress(address _addressIn) internal pure returns (bytes memory encodedBytes) { + encodedBytes = new bytes(20); + assembly { + mstore(add(encodedBytes, 0x20), shl(96, _addressIn)) + } + encodedBytes = _encodeBytes(encodedBytes); + } + + /** + * @notice Internal function that encodes a string value as bytes. + * @param _stringIn The string to be encoded. + * @return encodedBytes The string encoded as bytes. + */ + function _encodeString(string memory _stringIn) internal pure returns (bytes memory encodedBytes) { + encodedBytes = _encodeBytes(bytes(_stringIn)); + } + + /** + * @notice Internal function that encodes a bool value as bytes. + * @param _boolIn The bool to be encoded. + * @return encodedBytes The bool encoded as bytes. + */ + function _encodeBool(bool _boolIn) internal pure returns (bytes memory encodedBytes) { + encodedBytes = new bytes(1); + encodedBytes[0] = (_boolIn ? bytes1(0x01) : bytes1(0x80)); + } + + /** + * @notice Internal function that flattens a bytes array and encodes it. + * @param _bytesToEncode The bytes array to be encoded. + * @return encodedBytes The bytes array encoded as bytes. + */ + function _encodeList(bytes[] memory _bytesToEncode) internal pure returns (bytes memory encodedBytes) { + encodedBytes = _flatten(_bytesToEncode); + encodedBytes = abi.encodePacked(_encodeLength(encodedBytes.length, 192), encodedBytes); + } + + /** + * @notice Internal function that encodes an access list as bytes. + * @param _accesslist The access list to be encoded. + * @return encodedBytes The AccessList encoded as bytes. + */ + function _encodeAccessList(AccessList[] memory _accesslist) internal pure returns (bytes memory encodedBytes) { + uint256 listLength = _accesslist.length; + bytes[] memory encodedAccessList = new bytes[](listLength); + + for (uint256 i; i < listLength; i++) { + bytes32[] memory storageKeys = _accesslist[i].storageKeys; + uint256 keyCount = storageKeys.length; + + bytes[] memory encodedKeys = new bytes[](keyCount); + for (uint256 j; j < keyCount; j++) { + encodedKeys[j] = _encodeBytes(abi.encodePacked(storageKeys[j])); + } + + bytes[] memory accountTuple = new bytes[](2); + accountTuple[0] = _encodeAddress(_accesslist[i].contractAddress); + accountTuple[1] = _encodeList(encodedKeys); + + encodedAccessList[i] = _encodeList(accountTuple); + } + + encodedBytes = _encodeList(encodedAccessList); + } + + /** + * @notice Private function that encodes an integer in big endian binary form with no leading zeroes. + * @dev The zero length check is critical as the case is not considered in the rest of the checking. + * @param _uintValue The uint value to be encoded. + * @return encodedBytes The encoded uint. + */ + function _toBinary(uint256 _uintValue) private pure returns (bytes memory encodedBytes) { + if (_uintValue == 0) return new bytes(0); + + assembly { + encodedBytes := mload(0x40) + let sectionStart := 0 + let sectionLength := 128 + for { + + } gt(sectionLength, 7) { + sectionLength := shr(1, sectionLength) + } { + if iszero(shr(sub(256, add(sectionLength, sectionStart)), _uintValue)) { + sectionStart := add(sectionStart, sectionLength) + continue + } + } + + let length := div(sub(256, sectionStart), 8) + mstore(encodedBytes, length) + + let writePtr := add(encodedBytes, 0x20) + + for { + let j := 0 + } lt(j, length) { + j := add(j, 1) + } { + mstore8(add(writePtr, j), and(shr(mul(sub(length, add(j, 1)), 8), _uintValue), 0xff)) + } + + mstore(0x40, add(add(encodedBytes, 0x20), length)) + } + } + + /** + * @notice Private function that encodes length. + * @param _itemLength The length of the item. + * @param _offset The item's offset. + * @return encodedBytes The bytes of the length encoded. + */ + function _encodeLength(uint256 _itemLength, uint256 _offset) private pure returns (bytes memory encodedBytes) { + assembly { + encodedBytes := mload(0x40) + + switch lt(_itemLength, 56) + case 1 { + mstore8(add(encodedBytes, 0x20), add(_itemLength, _offset)) + mstore(encodedBytes, 1) + mstore(0x40, add(encodedBytes, 0x21)) + } + default { + let temp := _itemLength + let lengthOfLength := 0 + + for { + + } gt(temp, 0) { + + } { + lengthOfLength := add(lengthOfLength, 1) + temp := shr(8, temp) + } + + mstore8(add(encodedBytes, 0x20), add(add(lengthOfLength, _offset), 55)) + + for { + let i := 0 + } lt(i, lengthOfLength) { + i := add(i, 1) + } { + mstore8(add(add(encodedBytes, 0x21), i), and(shr(mul(8, sub(lengthOfLength, add(i, 1))), _itemLength), 0xff)) + } + + let totalLen := add(lengthOfLength, 1) + mstore(encodedBytes, totalLen) + mstore(0x40, add(add(encodedBytes, 0x20), totalLen)) + } + } + } + + /** + * @custom:attribution https://github.com/sammayo/solidity-rlp-encoder + * @notice Private function that flattens a list of byte strings into one byte string. + * @dev mcopy is used for the Cancun EVM fork. See original for other forks. + * @param _bytesList List of byte strings to flatten. + * @return flattenedBytes The flattened byte string. + */ + function _flatten(bytes[] memory _bytesList) private pure returns (bytes memory flattenedBytes) { + uint256 _bytesListLength = _bytesList.length; + if (_bytesListLength == 0) return new bytes(0); + + assembly { + flattenedBytes := mload(0x40) + let totalLen := 0 + let offset := add(_bytesList, 0x20) + + for { + let i := 0 + } lt(i, _bytesListLength) { + i := add(i, 1) + } { + totalLen := add(totalLen, mload(mload(add(offset, mul(i, 0x20))))) + } + + mstore(flattenedBytes, totalLen) + let writePtr := add(flattenedBytes, 0x20) + + for { + let i := 0 + } lt(i, _bytesListLength) { + i := add(i, 1) + } { + let ptr := mload(add(offset, mul(i, 0x20))) + let len := mload(ptr) + mcopy(writePtr, add(ptr, 0x20), len) + writePtr := add(writePtr, len) + } + + mstore(0x40, add(flattenedBytes, add(0x20, totalLen))) + } + } +} diff --git a/contracts/src/security/pausing/PauseManager.sol b/contracts/src/security/pausing/PauseManager.sol index 9e9d7d64b0..150dd42303 100644 --- a/contracts/src/security/pausing/PauseManager.sol +++ b/contracts/src/security/pausing/PauseManager.sol @@ -150,7 +150,7 @@ abstract contract PauseManager is IPauseManager, AccessControlUpgradeable { if (isPaused(_pauseType)) { revert IsPaused(_pauseType); } - + unchecked { if (hasRole(SECURITY_COUNCIL_ROLE, _msgSender())) { pauseExpiryTimestamp = type(uint256).max - COOLDOWN_DURATION; @@ -193,9 +193,7 @@ abstract contract PauseManager is IPauseManager, AccessControlUpgradeable { * @dev Throws if UNUSED pause type is used, or the pause expiry period has not passed. * @param _pauseType The pause type value. */ - function unPauseByExpiredType( - PauseType _pauseType - ) external onlyUsedPausedTypes(_pauseType) { + function unPauseByExpiredType(PauseType _pauseType) external onlyUsedPausedTypes(_pauseType) { if (!isPaused(_pauseType)) { revert IsNotPaused(_pauseType); } diff --git a/contracts/test/foundry/LineaRollup.t.sol b/contracts/test/foundry/LineaRollup.t.sol index de76190220..57b4e3a9a9 100644 --- a/contracts/test/foundry/LineaRollup.t.sol +++ b/contracts/test/foundry/LineaRollup.t.sol @@ -10,7 +10,7 @@ import { ILineaRollup } from "src/rollup/interfaces/ILineaRollup.sol"; import { IPauseManager } from "src/security/pausing/interfaces/IPauseManager.sol"; import { IPermissionsManager } from "src/security/access/interfaces/IPermissionsManager.sol"; import { AccessControlUpgradeable } from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol"; -import { ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; contract LineaRollupTestHelper is LineaRollup { function calculateY(bytes calldata data, bytes32 dataEvaluationPoint) external pure returns (bytes32) { @@ -24,7 +24,7 @@ contract LineaRollupTestHelper is LineaRollup { bytes32 _dataEvaluationPoint, bytes32 _dataEvaluationClaim ) external pure returns (bytes32 shnarf) { - return _computeShnarf(_parentShnarf, _snarkHash, _finalStateRootHash, _dataEvaluationPoint, _dataEvaluationClaim); + return _computeShnarf(_parentShnarf, _snarkHash, _finalStateRootHash, _dataEvaluationPoint, _dataEvaluationClaim); } } @@ -95,17 +95,14 @@ contract LineaRollupTest is Test { // Adjust compressedData to start with 0x00 submission.compressedData = hex"00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"; - bytes32 dataEvaluationPoint = EfficientLeftRightKeccak._efficientKeccak(submission.snarkHash, keccak256(submission.compressedData)); + bytes32 dataEvaluationPoint = EfficientLeftRightKeccak._efficientKeccak( + submission.snarkHash, + keccak256(submission.compressedData) + ); bytes32 dataEvaluationClaim = lineaRollup.calculateY(submission.compressedData, dataEvaluationPoint); - bytes32 parentShnarf = lineaRollup.computeShnarf( - 0x0, - 0x0, - 0x0, - 0x0, - 0x0 - ); + bytes32 parentShnarf = lineaRollup.computeShnarf(0x0, 0x0, 0x0, 0x0, 0x0); bytes32 expectedShnarf = keccak256( abi.encodePacked( diff --git a/contracts/test/hardhat/_testData/eip1559RlpEncoderTransactions/withCalldata.json b/contracts/test/hardhat/_testData/eip1559RlpEncoderTransactions/withCalldata.json new file mode 100644 index 0000000000..8f1144d53b --- /dev/null +++ b/contracts/test/hardhat/_testData/eip1559RlpEncoderTransactions/withCalldata.json @@ -0,0 +1,27 @@ +{ + "jsonrpc": "2.0", + "id": 1, + "result": { + "blockHash": "0xeb8235afc4b60d415a9567f36c0c5a5e4662f0b8c93f5343beb61af105dba6c2", + "blockNumber": "0x151b18c", + "from": "0x13fb49bd78e18fff091c0b9019f052f1dd6d7433", + "gas": "0x18fd1", + "gasPrice": "0x2b1c92c6", + "maxFeePerGas": "0x35a4e900", + "maxPriorityFeePerGas": "0x2faf080", + "hash": "0xd147a5f40f4224c0ed73777ba74f590c683ab0a4c8553b195d84bc5dfbd3ae0b", + "input": "0x9f3ce55a00000000000000000000000013fb49bd78e18fff091c0b9019f052f1dd6d743300000000000000000000000000000000000000000000000000000a400e08caa400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x8b", + "to": "0xd19d4b5d358258f05d7b411e21a1460d11b0876f", + "transactionIndex": "0xaa", + "value": "0x166dd3710594aa4", + "type": "0x2", + "accessList": [], + "chainId": "0x1", + "v": "0x1", + "r": "0x1fdb8dea465b1fa7c8e77b09a26cbfe1aa2b9b5110a78e39c3414ad516243621", + "s": "0x716097a1f1e0d8489b9e9cdd91dca9e6207832e26b350b4146dacec914b238dd", + "yParity": "0x1" + }, + "rlpEncoded": "0x02f8f901818b8402faf0808435a4e90083018fd194d19d4b5d358258f05d7b411e21a1460d11b0876f880166dd3710594aa4b8849f3ce55a00000000000000000000000013fb49bd78e18fff091c0b9019f052f1dd6d743300000000000000000000000000000000000000000000000000000a400e08caa400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000c001a01fdb8dea465b1fa7c8e77b09a26cbfe1aa2b9b5110a78e39c3414ad516243621a0716097a1f1e0d8489b9e9cdd91dca9e6207832e26b350b4146dacec914b238dd" +} \ No newline at end of file diff --git a/contracts/test/hardhat/_testData/eip1559RlpEncoderTransactions/withCalldataAndAccessList.json b/contracts/test/hardhat/_testData/eip1559RlpEncoderTransactions/withCalldataAndAccessList.json new file mode 100644 index 0000000000..aa0e1add8b --- /dev/null +++ b/contracts/test/hardhat/_testData/eip1559RlpEncoderTransactions/withCalldataAndAccessList.json @@ -0,0 +1,34 @@ +{ + "jsonrpc": "2.0", + "id": 1, + "result": { + "accessList": [ + { + "address": "0x2926ec066455a9c519a19ac6f641aa40c829d482", + "storageKeys": [ + "0x872009bf950ed1cb0457060a0d6d958528670bae633670177c017a074b0779c3" + ] + } + ], + "blockHash": "0x1c252c8c5bd155c4b3819a61f282d08722e46894875deb7c5c137bd6424bacd9", + "blockNumber": "0x79f253", + "chainId": "0xaa36a7", + "from": "0xfc8769485986fe00760a40747d0d2207bc92c86d", + "gas": "0xc10c", + "gasPrice": "0xcf4f6533", + "hash": "0x59f686557e2f5ce5a774645bb47044b93b3b4cd0d8bc37dba2d244c1d7bdc0af", + "input": "0xf1bca7a400000000000000000000000000000000000000000000000000000000075bcd15", + "maxFeePerGas": "0x1b0a5ce2c", + "maxPriorityFeePerGas": "0xf4240", + "nonce": "0x4", + "r": "0x41b1e39b0b9720e0ada0a9053edfa5e486ec9589bafceb38656d9fa1d6618b8b", + "s": "0x7e15abc0dbd2b96124e70b615142f888576875d9944b2abae284b1d82cd90c21", + "to": "0x5ad9369254f29b724d98f6ce98cb7bad729969f3", + "transactionIndex": "0x60", + "type": "0x2", + "v": "0x1", + "value": "0x0", + "yParity": "0x1" + }, + "rlpEncoded" : "0x02f8ca83aa36a704830f42408501b0a5ce2c82c10c945ad9369254f29b724d98f6ce98cb7bad729969f380a4f1bca7a400000000000000000000000000000000000000000000000000000000075bcd15f838f7942926ec066455a9c519a19ac6f641aa40c829d482e1a0872009bf950ed1cb0457060a0d6d958528670bae633670177c017a074b0779c301a041b1e39b0b9720e0ada0a9053edfa5e486ec9589bafceb38656d9fa1d6618b8ba07e15abc0dbd2b96124e70b615142f888576875d9944b2abae284b1d82cd90c21" +} \ No newline at end of file diff --git a/contracts/test/hardhat/_testData/eip1559RlpEncoderTransactions/withLargeCalldata.json b/contracts/test/hardhat/_testData/eip1559RlpEncoderTransactions/withLargeCalldata.json new file mode 100644 index 0000000000..0b9a412475 --- /dev/null +++ b/contracts/test/hardhat/_testData/eip1559RlpEncoderTransactions/withLargeCalldata.json @@ -0,0 +1,27 @@ +{ + "jsonrpc": "2.0", + "id": 1, + "result": { + "blockHash": "0xa82b9ee49c14ea79ba6983a2cf91f86bce8f32b887bde513576459c7b0299386", + "blockNumber": "0x151b841", + "from": "0x92e12ebea85129568bc2a5566bea367c036946c8", + "gas": "0x7191b", + "gasPrice": "0x2ba2d83d", + "maxFeePerGas": "0x540ae480", + "maxPriorityFeePerGas": "0x4b571c0", + "hash": "0xdd2c0345d1c559797592d1deaf20a04ca1571129bcc0117814e2343cb38d4f0c", + "input": "0x2213bc0b0000000000000000000000000d0e364aa7852291883c162b22d6d81f6355428f000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000000f42400000000000000000000000000d0e364aa7852291883c162b22d6d81f6355428f00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000009c41fff991f00000000000000000000000092e12ebea85129568bc2a5566bea367c036946c800000000000000000000000095ad61b0a150d79219dcf64e1e6cc01f0b64c4ce000000000000000000000000000000000000000000000e0784d9c87d94a8117000000000000000000000000000000000000000000000000000000000000000a0e39d71a20266d3a1242141272068d40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000038000000000000000000000000000000000000000000000000000000000000004c0000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000007a000000000000000000000000000000000000000000000000000000000000000e4c1fb425e0000000000000000000000000d0e364aa7852291883c162b22d6d81f6355428f000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000000f424000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000067e444dd00000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000164af72634f0000000000000000000000000d0e364aa7852291883c162b22d6d81f6355428f000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000000000000000000000000000000000000000271000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000ffffffffffffffc5000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034271001eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee0001f400000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010438c9c147000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee0000000000000000000000000000000000000000000000000000000000002710000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000024d0e30db000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010438c9c147000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000000000000002710000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000242e1a7d4d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000164af72634f0000000000000000000000000d0e364aa7852291883c162b22d6d81f6355428f000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee000000000000000000000000000000000000000000000000000000000000271000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000ffffffffffffffc500000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003427100195ad61b0a150d79219dcf64e1e6cc01f0b64c4ce0027100000c8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000012438c9c14700000000000000000000000095ad61b0a150d79219dcf64e1e6cc01f0b64c4ce000000000000000000000000000000000000000000000000000000000000005500000000000000000000000095ad61b0a150d79219dcf64e1e6cc01f0b64c4ce000000000000000000000000000000000000000000000000000000000000002400000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000044a9059cbb0000000000000000000000007afa9d836d2fccf172b66622625e56404e465dbd0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x4", + "to": "0x0000000000001ff3684f28c67538d4d072c22734", + "transactionIndex": "0x35", + "value": "0x0", + "type": "0x2", + "accessList": [], + "chainId": "0x1", + "v": "0x1", + "r": "0x84f9fa394af52883b66c7b65f921aceac201a7b0f7ffbc78fbeea7695a109ad5", + "s": "0x5ca82e86c3174e09e1bfb23b3e91f3d707ed1cd1181cd920c5dc5fbb8ecba914", + "yParity": "0x1" + }, + "rlpEncoded" : "0x02f90b1101048404b571c084540ae4808307191b940000000000001ff3684f28c67538d4d072c2273480b90aa42213bc0b0000000000000000000000000d0e364aa7852291883c162b22d6d81f6355428f000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000000f42400000000000000000000000000d0e364aa7852291883c162b22d6d81f6355428f00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000009c41fff991f00000000000000000000000092e12ebea85129568bc2a5566bea367c036946c800000000000000000000000095ad61b0a150d79219dcf64e1e6cc01f0b64c4ce000000000000000000000000000000000000000000000e0784d9c87d94a8117000000000000000000000000000000000000000000000000000000000000000a0e39d71a20266d3a1242141272068d40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000038000000000000000000000000000000000000000000000000000000000000004c0000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000007a000000000000000000000000000000000000000000000000000000000000000e4c1fb425e0000000000000000000000000d0e364aa7852291883c162b22d6d81f6355428f000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000000f424000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000067e444dd00000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000164af72634f0000000000000000000000000d0e364aa7852291883c162b22d6d81f6355428f000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000000000000000000000000000000000000000271000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000ffffffffffffffc5000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034271001eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee0001f400000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010438c9c147000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee0000000000000000000000000000000000000000000000000000000000002710000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000024d0e30db000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010438c9c147000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000000000000002710000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000242e1a7d4d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000164af72634f0000000000000000000000000d0e364aa7852291883c162b22d6d81f6355428f000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee000000000000000000000000000000000000000000000000000000000000271000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000ffffffffffffffc500000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003427100195ad61b0a150d79219dcf64e1e6cc01f0b64c4ce0027100000c8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000012438c9c14700000000000000000000000095ad61b0a150d79219dcf64e1e6cc01f0b64c4ce000000000000000000000000000000000000000000000000000000000000005500000000000000000000000095ad61b0a150d79219dcf64e1e6cc01f0b64c4ce000000000000000000000000000000000000000000000000000000000000002400000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000044a9059cbb0000000000000000000000007afa9d836d2fccf172b66622625e56404e465dbd0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c001a084f9fa394af52883b66c7b65f921aceac201a7b0f7ffbc78fbeea7695a109ad5a05ca82e86c3174e09e1bfb23b3e91f3d707ed1cd1181cd920c5dc5fbb8ecba914" +} \ No newline at end of file diff --git a/contracts/test/hardhat/_testData/eip1559RlpEncoderTransactions/withoutCalldata.json b/contracts/test/hardhat/_testData/eip1559RlpEncoderTransactions/withoutCalldata.json new file mode 100644 index 0000000000..831189bf12 --- /dev/null +++ b/contracts/test/hardhat/_testData/eip1559RlpEncoderTransactions/withoutCalldata.json @@ -0,0 +1,27 @@ +{ + "jsonrpc": "2.0", + "id": 1, + "result": { + "accessList": [], + "blockHash": "0xd20ab77f23688dbd42e3c8fa2d448c070bb4dc9ce5cae8f393e99907fde41755", + "blockNumber": "0x151b88e", + "chainId": "0x1", + "from": "0x0d85129b29781bab8a422b1bcd60b68f45dd2c40", + "gas": "0x5208", + "gasPrice": "0x1fcf0b58", + "hash": "0xd4d9a0482cf4f43d2f712ee6edcf7f6824aed99b5a339b1fae9d093e59c2b394", + "input": "0x", + "maxFeePerGas": "0x2835231b", + "maxPriorityFeePerGas": "0xe195f", + "nonce": "0x87", + "r": "0x266af86d7a0de8ad3cad80493401207ac2c7470fd66be304a2efbb33fbabb8a5", + "s": "0x7e15a5b7a18360eaebfc2635f84abecfbce441037afcacbc39f46af810e9fa5d", + "to": "0xdfaa75323fb721e5f29d43859390f62cc4b600b8", + "transactionIndex": "0x61", + "type": "0x2", + "v": "0x0", + "value": "0x20e0e27022b718", + "yParity": "0x0" + }, + "rlpEncoded": "0x02f871018187830e195f842835231b82520894dfaa75323fb721e5f29d43859390f62cc4b600b88720e0e27022b71880c080a0266af86d7a0de8ad3cad80493401207ac2c7470fd66be304a2efbb33fbabb8a5a07e15a5b7a18360eaebfc2635f84abecfbce441037afcacbc39f46af810e9fa5d" +} \ No newline at end of file diff --git a/contracts/test/hardhat/common/helpers/hashing.ts b/contracts/test/hardhat/common/helpers/hashing.ts index 7c8815cca3..6de667c5cb 100644 --- a/contracts/test/hardhat/common/helpers/hashing.ts +++ b/contracts/test/hardhat/common/helpers/hashing.ts @@ -1,6 +1,8 @@ -import { ethers } from "ethers"; +import { BytesLike, ethers } from "ethers"; import { encodeData } from "./encoding"; +export const generateKeccak256BytesDirectly = (data: BytesLike) => ethers.keccak256(data); + export const generateKeccak256Hash = (str: string) => generateKeccak256(["string"], [str], true); export const generateKeccak256 = (types: string[], values: unknown[], packed?: boolean) => diff --git a/contracts/test/hardhat/common/helpers/index.ts b/contracts/test/hardhat/common/helpers/index.ts index 20415e13c0..6ceaceaa24 100644 --- a/contracts/test/hardhat/common/helpers/index.ts +++ b/contracts/test/hardhat/common/helpers/index.ts @@ -6,3 +6,4 @@ export * from "./dataGeneration"; export * from "./dataLoader"; export * from "./expectations"; export * from "./time"; +export * from "./typedTransactionBuilding"; diff --git a/contracts/test/hardhat/common/helpers/typedTransactionBuilding.ts b/contracts/test/hardhat/common/helpers/typedTransactionBuilding.ts new file mode 100644 index 0000000000..d8a64e1e6f --- /dev/null +++ b/contracts/test/hardhat/common/helpers/typedTransactionBuilding.ts @@ -0,0 +1,23 @@ +import { AccessListEntryInput, Eip1559Transaction } from "../types"; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function buildEip1559Transaction(data: any): Eip1559Transaction { + const accessList: AccessListEntryInput[] = data.accessList; + + return { + nonce: data.nonce, + maxPriorityFeePerGas: data.maxPriorityFeePerGas, + maxFeePerGas: data.maxFeePerGas, + gasLimit: data.gas, + to: data.to, + value: data.value, + input: data.input, + accessList: accessList.map(({ address, storageKeys }) => ({ + contractAddress: address, + storageKeys, + })), + yParity: data.yParity, + r: data.r, + s: data.s, + }; +} diff --git a/contracts/test/hardhat/common/types.ts b/contracts/test/hardhat/common/types.ts index a33a30abd0..eaa1708ecd 100644 --- a/contracts/test/hardhat/common/types.ts +++ b/contracts/test/hardhat/common/types.ts @@ -84,3 +84,27 @@ export type FinalizationData = { }; export type ShnarfDataGenerator = (blobParentShnarfIndex: number, isMultiple?: boolean) => ShnarfData; + +export type Eip1559Transaction = { + nonce: bigint; + maxPriorityFeePerGas: bigint; + maxFeePerGas: bigint; + gasLimit: bigint; + to: string; + value: bigint; + input: string; + accessList: AccessList[]; + yParity: bigint; + r: bigint; + s: bigint; +}; + +export type AccessList = { + contractAddress: string; + storageKeys: string[]; +}; + +export type AccessListEntryInput = { + address: string; + storageKeys: string[]; +}; diff --git a/contracts/test/hardhat/libraries/Eip1559RlpEncoder.ts b/contracts/test/hardhat/libraries/Eip1559RlpEncoder.ts new file mode 100644 index 0000000000..b599610373 --- /dev/null +++ b/contracts/test/hardhat/libraries/Eip1559RlpEncoder.ts @@ -0,0 +1,67 @@ +import { expect } from "chai"; +import { TestEip1559RlpEncoder } from "../../../typechain-types"; +import { deployFromFactory } from "../common/deployment"; +import { loadFixture } from "@nomicfoundation/hardhat-network-helpers"; +import transactionWithoutCalldata from "../_testData/eip1559RlpEncoderTransactions/withoutCalldata.json"; +import transactionWithCalldata from "../_testData/eip1559RlpEncoderTransactions/withCalldata.json"; +import transactionWithLargeCalldata from "../_testData/eip1559RlpEncoderTransactions/withLargeCalldata.json"; +import transactionWithCalldataAndAccessList from "../_testData/eip1559RlpEncoderTransactions/withCalldataAndAccessList.json"; +import { generateKeccak256BytesDirectly } from "../common/helpers"; +import { buildEip1559Transaction } from "../common/helpers/typedTransactionBuilding"; + +describe("Eip1559RlpEncoder Library", () => { + let contract: TestEip1559RlpEncoder; + + async function deployTestEip1559RlpEncoderFixture() { + return deployFromFactory("TestEip1559RlpEncoder", 1); + } + beforeEach(async () => { + contract = (await loadFixture(deployTestEip1559RlpEncoderFixture)) as TestEip1559RlpEncoder; + }); + + describe("RLP Encoding and hashing", () => { + it("Succeeds for a transaction without calldata", async () => { + const { rlpEncodedTransaction, transactionHash } = await contract.encodeEip1559Transaction( + buildEip1559Transaction(transactionWithoutCalldata.result), + ); + + expect(transactionWithoutCalldata.result.hash).equal(transactionHash); + expect(transactionWithoutCalldata.rlpEncoded).equal(rlpEncodedTransaction); + expect(generateKeccak256BytesDirectly(rlpEncodedTransaction)).equal(transactionWithoutCalldata.result.hash); + }); + + it("Succeeds for a transaction with calldata", async () => { + const { rlpEncodedTransaction, transactionHash } = await contract.encodeEip1559Transaction( + buildEip1559Transaction(transactionWithCalldata.result), + ); + + expect(transactionWithCalldata.result.hash).equal(transactionHash); + expect(transactionWithCalldata.rlpEncoded).equal(rlpEncodedTransaction); + expect(generateKeccak256BytesDirectly(rlpEncodedTransaction)).equal(transactionWithCalldata.result.hash); + }); + + it("Succeeds for a transaction with large calldata", async () => { + const { rlpEncodedTransaction, transactionHash } = await contract.encodeEip1559Transaction( + buildEip1559Transaction(transactionWithLargeCalldata.result), + ); + + expect(transactionWithLargeCalldata.result.hash).equal(transactionHash); + expect(transactionWithLargeCalldata.rlpEncoded).equal(rlpEncodedTransaction); + expect(generateKeccak256BytesDirectly(rlpEncodedTransaction)).equal(transactionWithLargeCalldata.result.hash); + }); + + it("Succeeds for a transaction with calldata and an access list", async () => { + const sepoliaContract = (await deployFromFactory("TestEip1559RlpEncoder", 11155111)) as TestEip1559RlpEncoder; + + const { rlpEncodedTransaction, transactionHash } = await sepoliaContract.encodeEip1559Transaction( + buildEip1559Transaction(transactionWithCalldataAndAccessList.result), + ); + + expect(transactionWithCalldataAndAccessList.result.hash).equal(transactionHash); + expect(transactionWithCalldataAndAccessList.rlpEncoded).equal(rlpEncodedTransaction); + expect(generateKeccak256BytesDirectly(rlpEncodedTransaction)).equal( + transactionWithCalldataAndAccessList.result.hash, + ); + }); + }); +}); diff --git a/contracts/test/hardhat/libraries/RlpEncoder.ts b/contracts/test/hardhat/libraries/RlpEncoder.ts new file mode 100644 index 0000000000..36982c10c1 --- /dev/null +++ b/contracts/test/hardhat/libraries/RlpEncoder.ts @@ -0,0 +1,89 @@ +import { expect } from "chai"; +import { TestRlpEncoder } from "../../../typechain-types"; +import { deployFromFactory } from "../common/deployment"; +import { loadFixture } from "@nomicfoundation/hardhat-network-helpers"; + +describe("RlpEncoder Library", () => { + let contract: TestRlpEncoder; + + async function deployTestEip1559RlpEncoderFixture() { + return deployFromFactory("TestRlpEncoder"); + } + beforeEach(async () => { + contract = (await loadFixture(deployTestEip1559RlpEncoderFixture)) as TestRlpEncoder; + }); + + describe("RLP Encoding", () => { + describe("Bool Encoding", () => { + it("Encodes false correctly", async () => { + const encoded = await contract.encodeBool(false); + expect(encoded).equal("0x80"); + }); + + it("Encodes true correctly", async () => { + const encoded = await contract.encodeBool(true); + expect(encoded).equal("0x01"); + }); + }); + + describe("String Encoding", () => { + it("Encodes blank string correctly", async () => { + const encoded = await contract.encodeString(""); + expect(encoded).equal("0x80"); + }); + + it("Encodes a short string correctly", async () => { + const encoded = await contract.encodeString("short"); + const expected = "0x8573686f7274"; + expect(encoded).equal(expected); + }); + + it("Encodes a long string correctly", async () => { + const encoded = await contract.encodeString( + "This is a string that is quite long and needs some different encoding", + ); + const expected = + "0xb84554686973206973206120737472696e672074686174206973207175697465206c6f6e6720616e64206e6565647320736f6d6520646966666572656e7420656e636f64696e67"; + expect(encoded).equal(expected); + }); + }); + }); + + describe("Int Encoding", () => { + it("Encodes a negative int correctly", async () => { + const encoded = await contract.encodeInt(-123456789n); + expect(encoded).equal("0xa0fffffffffffffffffffffffffffffffffffffffffffffffffffffffff8a432eb"); + }); + it("Encodes a positive int correctly", async () => { + const encoded = await contract.encodeInt(123456789n); + expect(encoded).equal("0x84075bcd15"); + }); + + // TODO random fuzz type tests + it("Encodes a very large positive int correctly", async () => { + const encoded = + await contract.encodeInt(1234567891234567567891234567789123456789123456789123456789123456789123456789n); + expect(encoded).equal("0xa002babd9c27f528a06ee127601e68ddbe8c982496f253de820f6f70b684045f15"); + }); + + it("Encodes a large positive int correctly", async () => { + const encoded = await contract.encodeInt(123456789123456756789123456723456789123456789n); + expect(encoded).equal("0x93058936e53d139a968065bc45c0d9d0540c5f15"); + }); + + it("Encodes 0 correctly", async () => { + const encoded = await contract.encodeInt(0n); + expect(encoded).equal("0x80"); + }); + + it("Encodes 1 correctly", async () => { + const encoded = await contract.encodeInt(1n); + expect(encoded).equal("0x01"); + }); + + it("Encodes 255 correctly", async () => { + const encoded = await contract.encodeInt(255n); + expect(encoded).equal("0x81ff"); + }); + }); +});