From 9590b5a5d67c099dde33e878bf87167b4a9a83b7 Mon Sep 17 00:00:00 2001 From: nicolas <48695862+merklefruit@users.noreply.github.com> Date: Wed, 11 Sep 2024 09:01:49 +0200 Subject: [PATCH] wip: challenge proof components poc --- bolt-contracts/.gitignore | 3 + .../src/contracts/BoltChallenger.sol | 87 ++ bolt-contracts/src/lib/BytesUtils.sol | 189 ++++ bolt-contracts/src/lib/rlp/RLPReader.sol | 415 ++++++++ bolt-contracts/src/lib/rlp/RLPWriter.sol | 225 +++++ bolt-contracts/src/lib/trie/MerkleTrie.sol | 758 +++++++++++++++ .../src/lib/trie/SecureMerkleTrie.sol | 127 +++ bolt-contracts/test/BoltChallenger.t.sol | 45 + bolt-contracts/test/testdata/encode_proof.js | 23 + bolt-contracts/test/testdata/eth_proof.json | 22 + .../test/testdata/package-lock.json | 895 ++++++++++++++++++ bolt-contracts/test/testdata/package.json | 16 + .../test/testdata/tx_mpt_proof.json | 14 + 13 files changed, 2819 insertions(+) create mode 100644 bolt-contracts/src/contracts/BoltChallenger.sol create mode 100644 bolt-contracts/src/lib/BytesUtils.sol create mode 100644 bolt-contracts/src/lib/rlp/RLPReader.sol create mode 100644 bolt-contracts/src/lib/rlp/RLPWriter.sol create mode 100644 bolt-contracts/src/lib/trie/MerkleTrie.sol create mode 100644 bolt-contracts/src/lib/trie/SecureMerkleTrie.sol create mode 100644 bolt-contracts/test/BoltChallenger.t.sol create mode 100644 bolt-contracts/test/testdata/encode_proof.js create mode 100644 bolt-contracts/test/testdata/eth_proof.json create mode 100644 bolt-contracts/test/testdata/package-lock.json create mode 100644 bolt-contracts/test/testdata/package.json create mode 100644 bolt-contracts/test/testdata/tx_mpt_proof.json diff --git a/bolt-contracts/.gitignore b/bolt-contracts/.gitignore index fb58f1dfa..5ffcbef2f 100644 --- a/bolt-contracts/.gitignore +++ b/bolt-contracts/.gitignore @@ -3,3 +3,6 @@ out/ broadcast/ .env + +node_modules/ +target/ diff --git a/bolt-contracts/src/contracts/BoltChallenger.sol b/bolt-contracts/src/contracts/BoltChallenger.sol new file mode 100644 index 000000000..fe506b1ba --- /dev/null +++ b/bolt-contracts/src/contracts/BoltChallenger.sol @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.25; + +import {SecureMerkleTrie} from "../lib/trie/SecureMerkleTrie.sol"; +import {RLPReader} from "../lib/rlp/RLPReader.sol"; + +contract BoltChallenger { + using RLPReader for bytes; + using RLPReader for RLPReader.RLPItem; + + error BlockIsTooOld(); + error InvalidBlockHash(); + error AccountDoesNotExist(); + + constructor() {} + + function openChallenge() public { + // unimplemented!(); + } + + function resolveChallenge( + uint256 challengeId + ) public { + // unimplemented!(); + } + + /// @dev Only works with block headers that are less than 256 blocks old. + function proveRecentBlockHeaderData( + bytes calldata header + ) + public + view + returns ( + bytes32 transactionsRoot, + uint256 blockNumber, + uint256 gasLimit, + uint256 gasUsed, + uint256 timestamp, + uint256 baseFee + ) + { + // RLP decode the header + // https://github.com/ethereum/go-ethereum/blob/master/core/types/block.go + RLPReader.RLPItem[] memory headerFields = header.toRLPItem().readList(); + transactionsRoot = headerFields[4].readBytes32(); + blockNumber = headerFields[8].readUint256(); + gasLimit = headerFields[9].readUint256(); + gasUsed = headerFields[10].readUint256(); + timestamp = headerFields[11].readUint256(); + baseFee = headerFields[15].readUint256(); + + if (blockhash(blockNumber) == bytes32(0) || blockNumber < block.number - 256) { + revert BlockIsTooOld(); + } + + // verify that the block hash matches the one in the EVM + if (keccak256(header) != blockhash(blockNumber)) { + revert InvalidBlockHash(); + } + } + + /// @notice Prove the account data of an account at a given state root. + /// @dev This function assumes that the provided state root and account proof match. + /// @param account The account address to prove. + /// @param stateRoot The TRUSTED state root to prove against. Checking how the state root is obtained + /// is the responsibility of the caller. + /// @param accountProof The MPT account proof to prove the account data. + /// @return nonce The nonce of the account at the given state root height. + /// @return balance The balance of the account at the given state root height. + function proveAccountData( + address account, + bytes32 stateRoot, + bytes calldata accountProof + ) public returns (uint256 nonce, uint256 balance) { + (bool exists, bytes memory accountRLP) = + SecureMerkleTrie.get(abi.encodePacked(account), accountProof, stateRoot); + + if (!exists) { + revert AccountDoesNotExist(); + } + + // decode the account RLP into nonce and balance + RLPReader.RLPItem[] memory accountFields = accountRLP.toRLPItem().readList(); + nonce = accountFields[0].readUint256(); + balance = accountFields[1].readUint256(); + } +} diff --git a/bolt-contracts/src/lib/BytesUtils.sol b/bolt-contracts/src/lib/BytesUtils.sol new file mode 100644 index 000000000..704060059 --- /dev/null +++ b/bolt-contracts/src/lib/BytesUtils.sol @@ -0,0 +1,189 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.25; + +/** + * @title BytesUtils + */ +library BytesUtils { + /** + * + * Internal Functions * + * + */ + function slice(bytes memory _bytes, uint256 _start, uint256 _length) internal pure returns (bytes memory) { + unchecked { + require(_length + 31 >= _length, "slice_overflow"); + require(_start + _length >= _start, "slice_overflow"); + require(_bytes.length >= _start + _length, "slice_outOfBounds"); + + bytes memory tempBytes; + + assembly { + switch iszero(_length) + case 0 { + // Get a location of some free memory and store it in tempBytes as + // Solidity does for memory variables. + tempBytes := mload(0x40) + + // The first word of the slice result is potentially a partial + // word read from the original array. To read it, we calculate + // the length of that partial word and start copying that many + // bytes into the array. The first word we copy will start with + // data we don't care about, but the last `lengthmod` bytes will + // land at the beginning of the contents of the new array. When + // we're done copying, we overwrite the full first word with + // the actual length of the slice. + let lengthmod := and(_length, 31) + + // The multiplication in the next line is necessary + // because when slicing multiples of 32 bytes (lengthmod == 0) + // the following copy loop was copying the origin's length + // and then ending prematurely not copying everything it should. + let mc := add(add(tempBytes, lengthmod), mul(0x20, iszero(lengthmod))) + let end := add(mc, _length) + + for { + // The multiplication in the next line has the same exact purpose + // as the one above. + let cc := add(add(add(_bytes, lengthmod), mul(0x20, iszero(lengthmod))), _start) + } lt(mc, end) { + mc := add(mc, 0x20) + cc := add(cc, 0x20) + } { mstore(mc, mload(cc)) } + + mstore(tempBytes, _length) + + //update free-memory pointer + //allocating the array padded to 32 bytes like the compiler does now + mstore(0x40, and(add(mc, 31), not(31))) + } + //if we want a zero-length slice let's just return a zero-length array + default { + tempBytes := mload(0x40) + + //zero out the 32 bytes slice we are about to return + //we need to do it because Solidity does not garbage collect + mstore(tempBytes, 0) + + mstore(0x40, add(tempBytes, 0x20)) + } + } + + return tempBytes; + } + } + + function slice(bytes memory _bytes, uint256 _start) internal pure returns (bytes memory) { + unchecked { + if (_bytes.length - _start == 0) { + return bytes(""); + } + + return slice(_bytes, _start, _bytes.length - _start); + } + } + + function toBytes32PadLeft( + bytes memory _bytes + ) internal pure returns (bytes32) { + unchecked { + bytes32 ret; + uint256 len = _bytes.length <= 32 ? _bytes.length : 32; + assembly { + ret := shr(mul(sub(32, len), 8), mload(add(_bytes, 32))) + } + return ret; + } + } + + function toBytes32( + bytes memory _bytes + ) internal pure returns (bytes32) { + unchecked { + if (_bytes.length < 32) { + bytes32 ret; + assembly { + ret := mload(add(_bytes, 32)) + } + return ret; + } + + return abi.decode(_bytes, (bytes32)); // will truncate if input length > 32 bytes + } + } + + function toUint256( + bytes memory _bytes + ) internal pure returns (uint256) { + return uint256(toBytes32(_bytes)); + } + + function toUint24(bytes memory _bytes, uint256 _start) internal pure returns (uint24) { + require(_start + 3 >= _start, "toUint24_overflow"); + require(_bytes.length >= _start + 3, "toUint24_outOfBounds"); + uint24 tempUint; + + assembly { + tempUint := mload(add(add(_bytes, 0x3), _start)) + } + + return tempUint; + } + + function toUint8(bytes memory _bytes, uint256 _start) internal pure returns (uint8) { + require(_start + 1 >= _start, "toUint8_overflow"); + require(_bytes.length >= _start + 1, "toUint8_outOfBounds"); + uint8 tempUint; + + assembly { + tempUint := mload(add(add(_bytes, 0x1), _start)) + } + + return tempUint; + } + + function toAddress(bytes memory _bytes, uint256 _start) internal pure returns (address) { + require(_start + 20 >= _start, "toAddress_overflow"); + require(_bytes.length >= _start + 20, "toAddress_outOfBounds"); + address tempAddress; + + assembly { + tempAddress := div(mload(add(add(_bytes, 0x20), _start)), 0x1000000000000000000000000) + } + + return tempAddress; + } + + function toNibbles( + bytes memory _bytes + ) internal pure returns (bytes memory) { + unchecked { + bytes memory nibbles = new bytes(_bytes.length * 2); + + for (uint256 i = 0; i < _bytes.length; i++) { + nibbles[i * 2] = _bytes[i] >> 4; + nibbles[i * 2 + 1] = bytes1(uint8(_bytes[i]) % 16); + } + + return nibbles; + } + } + + function fromNibbles( + bytes memory _bytes + ) internal pure returns (bytes memory) { + unchecked { + bytes memory ret = new bytes(_bytes.length / 2); + + for (uint256 i = 0; i < ret.length; i++) { + ret[i] = (_bytes[i * 2] << 4) | (_bytes[i * 2 + 1]); + } + + return ret; + } + } + + function equal(bytes memory _bytes, bytes memory _other) internal pure returns (bool) { + return keccak256(_bytes) == keccak256(_other); + } +} diff --git a/bolt-contracts/src/lib/rlp/RLPReader.sol b/bolt-contracts/src/lib/rlp/RLPReader.sol new file mode 100644 index 000000000..87813fbb6 --- /dev/null +++ b/bolt-contracts/src/lib/rlp/RLPReader.sol @@ -0,0 +1,415 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.25; + +/** + * @title RLPReader + * @dev Adapted from "RLPReader" by Hamdi Allam (hamdi.allam97@gmail.com). + */ +library RLPReader { + /** + * + * Constants * + * + */ + uint256 internal constant MAX_LIST_LENGTH = 32; + + /** + * + * Enums * + * + */ + enum RLPItemType { + DATA_ITEM, + LIST_ITEM + } + + /** + * + * Structs * + * + */ + struct RLPItem { + uint256 length; + uint256 ptr; + } + + /** + * + * Internal Functions * + * + */ + + /** + * Converts bytes to a reference to memory position and length. + * @param _in Input bytes to convert. + * @return Output memory reference. + */ + function toRLPItem( + bytes memory _in + ) internal pure returns (RLPItem memory) { + uint256 ptr; + assembly { + ptr := add(_in, 32) + } + + return RLPItem({length: _in.length, ptr: ptr}); + } + + /** + * Reads an RLP list value into a list of RLP items. + * @param _in RLP list value. + * @return Decoded RLP list items. + */ + function readList( + RLPItem memory _in + ) internal pure returns (RLPItem[] memory) { + (uint256 listOffset,, RLPItemType itemType) = _decodeLength(_in); + + require(itemType == RLPItemType.LIST_ITEM, "Invalid RLP list value."); + + // Solidity in-memory arrays can't be increased in size, but *can* be decreased in size by + // writing to the length. Since we can't know the number of RLP items without looping over + // the entire input, we'd have to loop twice to accurately size this array. It's easier to + // simply set a reasonable maximum list length and decrease the size before we finish. + RLPItem[] memory out = new RLPItem[](MAX_LIST_LENGTH); + + uint256 itemCount = 0; + uint256 offset = listOffset; + while (offset < _in.length) { + require(itemCount < MAX_LIST_LENGTH, "Provided RLP list exceeds max list length."); + + (uint256 itemOffset, uint256 itemLength,) = + _decodeLength(RLPItem({length: _in.length - offset, ptr: _in.ptr + offset})); + + out[itemCount] = RLPItem({length: itemLength + itemOffset, ptr: _in.ptr + offset}); + + itemCount += 1; + offset += itemOffset + itemLength; + } + + // Decrease the array size to match the actual item count. + assembly { + mstore(out, itemCount) + } + + return out; + } + + /** + * Reads an RLP list value into a list of RLP items. + * @param _in RLP list value. + * @return Decoded RLP list items. + */ + function readList( + bytes memory _in + ) internal pure returns (RLPItem[] memory) { + return readList(toRLPItem(_in)); + } + + /** + * Reads an RLP bytes value into bytes. + * @param _in RLP bytes value. + * @return Decoded bytes. + */ + function readBytes( + RLPItem memory _in + ) internal pure returns (bytes memory) { + (uint256 itemOffset, uint256 itemLength, RLPItemType itemType) = _decodeLength(_in); + + require(itemType == RLPItemType.DATA_ITEM, "Invalid RLP bytes value."); + + return _copy(_in.ptr, itemOffset, itemLength); + } + + /** + * Reads an RLP bytes value into bytes. + * @param _in RLP bytes value. + * @return Decoded bytes. + */ + function readBytes( + bytes memory _in + ) internal pure returns (bytes memory) { + return readBytes(toRLPItem(_in)); + } + + /** + * Reads an RLP string value into a string. + * @param _in RLP string value. + * @return Decoded string. + */ + function readString( + RLPItem memory _in + ) internal pure returns (string memory) { + return string(readBytes(_in)); + } + + /** + * Reads an RLP string value into a string. + * @param _in RLP string value. + * @return Decoded string. + */ + function readString( + bytes memory _in + ) internal pure returns (string memory) { + return readString(toRLPItem(_in)); + } + + /** + * Reads an RLP bytes32 value into a bytes32. + * @param _in RLP bytes32 value. + * @return Decoded bytes32. + */ + function readBytes32( + RLPItem memory _in + ) internal pure returns (bytes32) { + require(_in.length <= 33, "Invalid RLP bytes32 value."); + + (uint256 itemOffset, uint256 itemLength, RLPItemType itemType) = _decodeLength(_in); + + require(itemType == RLPItemType.DATA_ITEM, "Invalid RLP bytes32 value."); + + uint256 ptr = _in.ptr + itemOffset; + bytes32 out; + assembly { + out := mload(ptr) + + // Shift the bytes over to match the item size. + if lt(itemLength, 32) { out := div(out, exp(256, sub(32, itemLength))) } + } + + return out; + } + + /** + * Reads an RLP bytes32 value into a bytes32. + * @param _in RLP bytes32 value. + * @return Decoded bytes32. + */ + function readBytes32( + bytes memory _in + ) internal pure returns (bytes32) { + return readBytes32(toRLPItem(_in)); + } + + /** + * Reads an RLP uint256 value into a uint256. + * @param _in RLP uint256 value. + * @return Decoded uint256. + */ + function readUint256( + RLPItem memory _in + ) internal pure returns (uint256) { + return uint256(readBytes32(_in)); + } + + /** + * Reads an RLP uint256 value into a uint256. + * @param _in RLP uint256 value. + * @return Decoded uint256. + */ + function readUint256( + bytes memory _in + ) internal pure returns (uint256) { + return readUint256(toRLPItem(_in)); + } + + /** + * Reads an RLP bool value into a bool. + * @param _in RLP bool value. + * @return Decoded bool. + */ + function readBool( + RLPItem memory _in + ) internal pure returns (bool) { + require(_in.length == 1, "Invalid RLP boolean value."); + + uint256 ptr = _in.ptr; + uint256 out; + assembly { + out := byte(0, mload(ptr)) + } + + require(out == 0 || out == 1, "RLPReader: Invalid RLP boolean value, must be 0 or 1"); + + return out != 0; + } + + /** + * Reads an RLP bool value into a bool. + * @param _in RLP bool value. + * @return Decoded bool. + */ + function readBool( + bytes memory _in + ) internal pure returns (bool) { + return readBool(toRLPItem(_in)); + } + + /** + * Reads an RLP address value into a address. + * @param _in RLP address value. + * @return Decoded address. + */ + function readAddress( + RLPItem memory _in + ) internal pure returns (address) { + if (_in.length == 1) { + return address(0); + } + + require(_in.length == 21, "Invalid RLP address value."); + + return address(uint160(readUint256(_in))); + } + + /** + * Reads an RLP address value into a address. + * @param _in RLP address value. + * @return Decoded address. + */ + function readAddress( + bytes memory _in + ) internal pure returns (address) { + return readAddress(toRLPItem(_in)); + } + + /** + * Reads the raw bytes of an RLP item. + * @param _in RLP item to read. + * @return Raw RLP bytes. + */ + function readRawBytes( + RLPItem memory _in + ) internal pure returns (bytes memory) { + return _copy(_in); + } + + /** + * + * Private Functions * + * + */ + + /** + * Decodes the length of an RLP item. + * @param _in RLP item to decode. + * @return Offset of the encoded data. + * @return Length of the encoded data. + * @return RLP item type (LIST_ITEM or DATA_ITEM). + */ + function _decodeLength( + RLPItem memory _in + ) private pure returns (uint256, uint256, RLPItemType) { + unchecked { + require(_in.length > 0, "RLP item cannot be null."); + + uint256 ptr = _in.ptr; + uint256 prefix; + assembly { + prefix := byte(0, mload(ptr)) + } + + if (prefix <= 0x7f) { + // Single byte. + + return (0, 1, RLPItemType.DATA_ITEM); + } else if (prefix <= 0xb7) { + // Short string. + + uint256 strLen = prefix - 0x80; + + require(_in.length > strLen, "Invalid RLP short string."); + + return (1, strLen, RLPItemType.DATA_ITEM); + } else if (prefix <= 0xbf) { + // Long string. + uint256 lenOfStrLen = prefix - 0xb7; + + require(_in.length > lenOfStrLen, "Invalid RLP long string length."); + + uint256 strLen; + assembly { + // Pick out the string length. + strLen := div(mload(add(ptr, 1)), exp(256, sub(32, lenOfStrLen))) + } + + require(_in.length > lenOfStrLen + strLen, "Invalid RLP long string."); + + return (1 + lenOfStrLen, strLen, RLPItemType.DATA_ITEM); + } else if (prefix <= 0xf7) { + // Short list. + uint256 listLen = prefix - 0xc0; + + require(_in.length > listLen, "Invalid RLP short list."); + + return (1, listLen, RLPItemType.LIST_ITEM); + } else { + // Long list. + uint256 lenOfListLen = prefix - 0xf7; + + require(_in.length > lenOfListLen, "Invalid RLP long list length."); + + uint256 listLen; + assembly { + // Pick out the list length. + listLen := div(mload(add(ptr, 1)), exp(256, sub(32, lenOfListLen))) + } + + require(_in.length > lenOfListLen + listLen, "Invalid RLP long list."); + + return (1 + lenOfListLen, listLen, RLPItemType.LIST_ITEM); + } + } + } + + /** + * Copies the bytes from a memory location. + * @param _src Pointer to the location to read from. + * @param _offset Offset to start reading from. + * @param _length Number of bytes to read. + * @return Copied bytes. + */ + function _copy(uint256 _src, uint256 _offset, uint256 _length) private pure returns (bytes memory) { + unchecked { + bytes memory out = new bytes(_length); + if (out.length == 0) { + return out; + } + + uint256 src = _src + _offset; + uint256 dest; + assembly { + dest := add(out, 32) + } + + // Copy over as many complete words as we can. + for (uint256 i = 0; i < _length / 32; i++) { + assembly { + mstore(dest, mload(src)) + } + + src += 32; + dest += 32; + } + + // Pick out the remaining bytes. + uint256 mask = 256 ** (32 - (_length % 32)) - 1; + assembly { + mstore(dest, or(and(mload(src), not(mask)), and(mload(dest), mask))) + } + + return out; + } + } + + /** + * Copies an RLP item into bytes. + * @param _in RLP item to copy. + * @return Copied bytes. + */ + function _copy( + RLPItem memory _in + ) private pure returns (bytes memory) { + return _copy(_in.ptr, 0, _in.length); + } +} diff --git a/bolt-contracts/src/lib/rlp/RLPWriter.sol b/bolt-contracts/src/lib/rlp/RLPWriter.sol new file mode 100644 index 000000000..bf3563fb5 --- /dev/null +++ b/bolt-contracts/src/lib/rlp/RLPWriter.sol @@ -0,0 +1,225 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.25; +pragma experimental ABIEncoderV2; + +/* Library Imports */ +// import {BytesUtil} from "../BytesUtil.sol"; + +/** + * @title RLPWriter + * @author Bakaoh (with modifications) + */ +library RLPWriter { + /** + * + * Internal Functions * + * + */ + + /** + * RLP encodes a byte string. + * @param _in The byte string to encode. + * @return _out The RLP encoded string in bytes. + */ + function writeBytes( + bytes memory _in + ) internal pure returns (bytes memory _out) { + bytes memory encoded; + + if (_in.length == 1 && uint8(_in[0]) < 128) { + encoded = _in; + } else { + encoded = abi.encodePacked(_writeLength(_in.length, 128), _in); + } + + return encoded; + } + + /** + * RLP encodes a list of RLP encoded byte byte strings. + * @param _in The list of RLP encoded byte strings. + * @return _out The RLP encoded list of items in bytes. + */ + function writeList( + bytes[] memory _in + ) internal pure returns (bytes memory _out) { + bytes memory list = _flatten(_in); + return abi.encodePacked(_writeLength(list.length, 192), list); + } + + /** + * RLP encodes a string. + * @param _in The string to encode. + * @return _out The RLP encoded string in bytes. + */ + function writeString( + string memory _in + ) internal pure returns (bytes memory _out) { + return writeBytes(bytes(_in)); + } + + /** + * RLP encodes an address. + * @param _in The address to encode. + * @return _out The RLP encoded address in bytes. + */ + function writeAddress( + address _in + ) internal pure returns (bytes memory _out) { + return writeBytes(abi.encodePacked(_in)); + } + + /** + * RLP encodes a uint. + * @param _in The uint256 to encode. + * @return _out The RLP encoded uint256 in bytes. + */ + function writeUint( + uint256 _in + ) internal pure returns (bytes memory _out) { + return writeBytes(_toBinary(_in)); + } + + /** + * RLP encodes a bool. + * @param _in The bool to encode. + * @return _out The RLP encoded bool in bytes. + */ + function writeBool( + bool _in + ) internal pure returns (bytes memory _out) { + bytes memory encoded = new bytes(1); + encoded[0] = (_in ? bytes1(0x01) : bytes1(0x80)); + return encoded; + } + + /** + * + * Private Functions * + * + */ + + /** + * Encode the first byte, followed by the `len` in binary form if `length` is more than 55. + * @param _len The length of the string or the payload. + * @param _offset 128 if item is string, 192 if item is list. + * @return _encoded RLP encoded bytes. + */ + function _writeLength(uint256 _len, uint256 _offset) private pure returns (bytes memory _encoded) { + bytes memory encoded; + + if (_len < 56) { + encoded = new bytes(1); + encoded[0] = bytes1(uint8(_len) + uint8(_offset)); + } else { + uint256 lenLen; + uint256 i = 1; + while (_len / i != 0) { + lenLen++; + i *= 256; + } + + encoded = new bytes(lenLen + 1); + encoded[0] = bytes1(uint8(lenLen) + uint8(_offset) + 55); + for (i = 1; i <= lenLen; i++) { + encoded[i] = bytes1(uint8((_len / (256 ** (lenLen - i))) % 256)); + } + } + + return encoded; + } + + /** + * Encode integer in big endian binary form with no leading zeroes. + * @notice TODO: This should be optimized with assembly to save gas costs. + * @param _x The integer to encode. + * @return _binary RLP encoded bytes. + */ + function _toBinary( + uint256 _x + ) private pure returns (bytes memory _binary) { + bytes memory b = abi.encodePacked(_x); + + uint256 i = 0; + for (; i < 32; i++) { + if (b[i] != 0) { + break; + } + } + + bytes memory res = new bytes(32 - i); + for (uint256 j = 0; j < res.length; j++) { + res[j] = b[i++]; + } + + return res; + } + + /** + * Copies a piece of memory to another location. + * @notice From: https://github.com/Arachnid/solidity-stringutils/blob/master/src/strings.sol. + * @param _dest Destination location. + * @param _src Source location. + * @param _len Length of memory to copy. + */ + function _memcpy(uint256 _dest, uint256 _src, uint256 _len) private pure { + uint256 dest = _dest; + uint256 src = _src; + uint256 len = _len; + + for (; len >= 32; len -= 32) { + assembly { + mstore(dest, mload(src)) + } + dest += 32; + src += 32; + } + + uint256 mask = 256 ** (32 - len) - 1; + assembly { + let srcpart := and(mload(src), not(mask)) + let destpart := and(mload(dest), mask) + mstore(dest, or(destpart, srcpart)) + } + } + + /** + * Flattens a list of byte strings into one byte string. + * @notice From: https://github.com/sammayo/solidity-rlp-encoder/blob/master/RLPEncode.sol. + * @param _list List of byte strings to flatten. + * @return _flattened The flattened byte string. + */ + function _flatten( + bytes[] memory _list + ) private pure returns (bytes memory _flattened) { + if (_list.length == 0) { + return new bytes(0); + } + + uint256 len; + uint256 i = 0; + for (; i < _list.length; i++) { + len += _list[i].length; + } + + bytes memory flattened = new bytes(len); + uint256 flattenedPtr; + assembly { + flattenedPtr := add(flattened, 0x20) + } + + for (i = 0; i < _list.length; i++) { + bytes memory item = _list[i]; + + uint256 listPtr; + assembly { + listPtr := add(item, 0x20) + } + + _memcpy(flattenedPtr, listPtr, item.length); + flattenedPtr += _list[i].length; + } + + return flattened; + } +} diff --git a/bolt-contracts/src/lib/trie/MerkleTrie.sol b/bolt-contracts/src/lib/trie/MerkleTrie.sol new file mode 100644 index 000000000..8824e7f83 --- /dev/null +++ b/bolt-contracts/src/lib/trie/MerkleTrie.sol @@ -0,0 +1,758 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.25; + +/* Library Imports */ +import {BytesUtils} from "../BytesUtils.sol"; +import {RLPReader} from "../rlp/RLPReader.sol"; +import {RLPWriter} from "../rlp/RLPWriter.sol"; + +/** + * @title MerkleTrie + */ +library MerkleTrie { + /** + * + * Data Structures * + * + */ + enum NodeType { + BranchNode, + ExtensionNode, + LeafNode + } + + struct TrieNode { + bytes encoded; + RLPReader.RLPItem[] decoded; + } + + /** + * + * Contract Constants * + * + */ + + // TREE_RADIX determines the number of elements per branch node. + uint256 constant TREE_RADIX = 16; + // Branch nodes have TREE_RADIX elements plus an additional `value` slot. + uint256 constant BRANCH_NODE_LENGTH = TREE_RADIX + 1; + // Leaf nodes and extension nodes always have two elements, a `path` and a `value`. + uint256 constant LEAF_OR_EXTENSION_NODE_LENGTH = 2; + + // Prefixes are prepended to the `path` within a leaf or extension node and + // allow us to differentiate between the two node types. `ODD` or `EVEN` is + // determined by the number of nibbles within the unprefixed `path`. If the + // number of nibbles if even, we need to insert an extra padding nibble so + // the resulting prefixed `path` has an even number of nibbles. + uint8 constant PREFIX_EXTENSION_EVEN = 0; + uint8 constant PREFIX_EXTENSION_ODD = 1; + uint8 constant PREFIX_LEAF_EVEN = 2; + uint8 constant PREFIX_LEAF_ODD = 3; + + // Just a utility constant. RLP represents `NULL` as 0x80. + bytes1 constant RLP_NULL = bytes1(0x80); + bytes constant RLP_NULL_BYTES = hex"80"; + bytes32 internal constant KECCAK256_RLP_NULL_BYTES = keccak256(RLP_NULL_BYTES); + + /** + * + * Internal Functions * + * + */ + + /** + * @notice Verifies a proof that a given key/value pair is present in the + * Merkle trie. + * @param _key Key of the node to search for, as a hex string. + * @param _value Value of the node to search for, as a hex string. + * @param _proof Merkle trie inclusion proof for the desired node. Unlike + * traditional Merkle trees, this proof is executed top-down and consists + * of a list of RLP-encoded nodes that make a path down to the target node. + * @param _root Known root of the Merkle trie. Used to verify that the + * included proof is correctly constructed. + * @return _verified `true` if the k/v pair exists in the trie, `false` otherwise. + */ + function verifyInclusionProof( + bytes memory _key, + bytes memory _value, + bytes memory _proof, + bytes32 _root + ) internal pure returns (bool _verified) { + (bool exists, bytes memory value) = get(_key, _proof, _root); + + return (exists && BytesUtils.equal(_value, value)); + } + + /** + * @notice Verifies a proof that a given key is *not* present in + * the Merkle trie. + * @param _key Key of the node to search for, as a hex string. + * @param _proof Merkle trie inclusion proof for the node *nearest* the + * target node. + * @param _root Known root of the Merkle trie. Used to verify that the + * included proof is correctly constructed. + * @return _verified `true` if the key is absent in the trie, `false` otherwise. + */ + function verifyExclusionProof( + bytes memory _key, + bytes memory _proof, + bytes32 _root + ) internal pure returns (bool _verified) { + (bool exists,) = get(_key, _proof, _root); + + return exists == false; + } + + /** + * @notice Updates a Merkle trie and returns a new root hash. + * @param _key Key of the node to update, as a hex string. + * @param _value Value of the node to update, as a hex string. + * @param _proof Merkle trie inclusion proof for the node *nearest* the + * target node. If the key exists, we can simply update the value. + * Otherwise, we need to modify the trie to handle the new k/v pair. + * @param _root Known root of the Merkle trie. Used to verify that the + * included proof is correctly constructed. + * @return _updatedRoot Root hash of the newly constructed trie. + */ + function update( + bytes memory _key, + bytes memory _value, + bytes memory _proof, + bytes32 _root + ) internal pure returns (bytes32 _updatedRoot) { + // Special case when inserting the very first node. + if (_root == KECCAK256_RLP_NULL_BYTES) { + return getSingleNodeRootHash(_key, _value); + } + + TrieNode[] memory proof = _parseProof(_proof); + (uint256 pathLength, bytes memory keyRemainder,) = _walkNodePath(proof, _key, _root); + TrieNode[] memory newPath = _getNewPath(proof, pathLength, keyRemainder, _value); + + return _getUpdatedTrieRoot(newPath, _key); + } + + /** + * @notice Retrieves the value associated with a given key. + * @param _key Key to search for, as hex bytes. + * @param _proof Merkle trie inclusion proof for the key. + * @param _root Known root of the Merkle trie. + * @return _exists Whether or not the key exists. + * @return _value Value of the key if it exists. + */ + function get( + bytes memory _key, + bytes memory _proof, + bytes32 _root + ) internal pure returns (bool _exists, bytes memory _value) { + TrieNode[] memory proof = _parseProof(_proof); + (uint256 pathLength, bytes memory keyRemainder, bool isFinalNode) = _walkNodePath(proof, _key, _root); + + bool exists = keyRemainder.length == 0; + + require(exists || isFinalNode, "Provided proof is invalid."); + + bytes memory value = exists ? _getNodeValue(proof[pathLength - 1]) : bytes(""); + + return (exists, value); + } + + /** + * Computes the root hash for a trie with a single node. + * @param _key Key for the single node. + * @param _value Value for the single node. + * @return _updatedRoot Hash of the trie. + */ + function getSingleNodeRootHash( + bytes memory _key, + bytes memory _value + ) internal pure returns (bytes32 _updatedRoot) { + return keccak256(_makeLeafNode(BytesUtils.toNibbles(_key), _value).encoded); + } + + /** + * + * Private Functions * + * + */ + + /** + * @notice Walks through a proof using a provided key. + * @param _proof Inclusion proof to walk through. + * @param _key Key to use for the walk. + * @param _root Known root of the trie. + * @return _pathLength Length of the final path + * @return _keyRemainder Portion of the key remaining after the walk. + * @return _isFinalNode Whether or not we've hit a dead end. + */ + function _walkNodePath( + TrieNode[] memory _proof, + bytes memory _key, + bytes32 _root + ) private pure returns (uint256 _pathLength, bytes memory _keyRemainder, bool _isFinalNode) { + uint256 pathLength = 0; + bytes memory key = BytesUtils.toNibbles(_key); + + bytes32 currentNodeID = _root; + uint256 currentKeyIndex = 0; + uint256 currentKeyIncrement = 0; + TrieNode memory currentNode; + + // Proof is top-down, so we start at the first element (root). + for (uint256 i = 0; i < _proof.length; i++) { + currentNode = _proof[i]; + currentKeyIndex += currentKeyIncrement; + + // Keep track of the proof elements we actually need. + // It's expensive to resize arrays, so this simply reduces gas costs. + pathLength += 1; + + if (currentKeyIndex == 0) { + // First proof element is always the root node. + require(keccak256(currentNode.encoded) == currentNodeID, "Invalid root hash"); + } else if (currentNode.encoded.length >= 32) { + // Nodes 32 bytes or larger are hashed inside branch nodes. + require(keccak256(currentNode.encoded) == currentNodeID, "Invalid large internal hash"); + } else { + // Nodes smaller than 31 bytes aren't hashed. + require(BytesUtils.toBytes32(currentNode.encoded) == currentNodeID, "Invalid internal node hash"); + } + + if (currentNode.decoded.length == BRANCH_NODE_LENGTH) { + if (currentKeyIndex == key.length) { + // We've hit the end of the key, meaning the value should be within this branch node. + break; + } else { + // We're not at the end of the key yet. + // Figure out what the next node ID should be and continue. + uint8 branchKey = uint8(key[currentKeyIndex]); + RLPReader.RLPItem memory nextNode = currentNode.decoded[branchKey]; + currentNodeID = _getNodeID(nextNode); + currentKeyIncrement = 1; + continue; + } + } else if (currentNode.decoded.length == LEAF_OR_EXTENSION_NODE_LENGTH) { + bytes memory path = _getNodePath(currentNode); + uint8 prefix = uint8(path[0]); + uint8 offset = 2 - prefix % 2; + bytes memory pathRemainder = BytesUtils.slice(path, offset); + bytes memory keyRemainder = BytesUtils.slice(key, currentKeyIndex); + uint256 sharedNibbleLength = _getSharedNibbleLength(pathRemainder, keyRemainder); + + if (prefix == PREFIX_LEAF_EVEN || prefix == PREFIX_LEAF_ODD) { + if (pathRemainder.length == sharedNibbleLength && keyRemainder.length == sharedNibbleLength) { + // The key within this leaf matches our key exactly. + // Increment the key index to reflect that we have no remainder. + currentKeyIndex += sharedNibbleLength; + } + + // We've hit a leaf node, so our next node should be NULL. + currentNodeID = bytes32(RLP_NULL); + break; + } else if (prefix == PREFIX_EXTENSION_EVEN || prefix == PREFIX_EXTENSION_ODD) { + if (sharedNibbleLength == 0) { + // Our extension node doesn't share any part of our key. + // We've hit the end of this path, updates will need to modify this extension. + currentNodeID = bytes32(RLP_NULL); + break; + } else { + // Our extension shares some nibbles. + // Carry on to the next node. + currentNodeID = _getNodeID(currentNode.decoded[1]); + currentKeyIncrement = sharedNibbleLength; + continue; + } + } else { + revert("Received a node with an unknown prefix"); + } + } else { + revert("Received an unparseable node."); + } + } + + // If our node ID is NULL, then we're at a dead end. + bool isFinalNode = currentNodeID == bytes32(RLP_NULL); + return (pathLength, BytesUtils.slice(key, currentKeyIndex), isFinalNode); + } + + /** + * @notice Creates new nodes to support a k/v pair insertion into a given + * Merkle trie path. + * @param _path Path to the node nearest the k/v pair. + * @param _pathLength Length of the path. Necessary because the provided + * path may include additional nodes (e.g., it comes directly from a proof) + * and we can't resize in-memory arrays without costly duplication. + * @param _keyRemainder Portion of the initial key that must be inserted + * into the trie. + * @param _value Value to insert at the given key. + * @return _newPath A new path with the inserted k/v pair and extra supporting nodes. + */ + function _getNewPath( + TrieNode[] memory _path, + uint256 _pathLength, + bytes memory _keyRemainder, + bytes memory _value + ) private pure returns (TrieNode[] memory _newPath) { + bytes memory keyRemainder = _keyRemainder; + + // Most of our logic depends on the status of the last node in the path. + TrieNode memory lastNode = _path[_pathLength - 1]; + NodeType lastNodeType = _getNodeType(lastNode); + + // Create an array for newly created nodes. + // We need up to three new nodes, depending on the contents of the last node. + // Since array resizing is expensive, we'll keep track of the size manually. + // We're using an explicit `totalNewNodes += 1` after insertions for clarity. + TrieNode[] memory newNodes = new TrieNode[](3); + uint256 totalNewNodes = 0; + + if (keyRemainder.length == 0 && lastNodeType == NodeType.LeafNode) { + // We've found a leaf node with the given key. + // Simply need to update the value of the node to match. + newNodes[totalNewNodes] = _makeLeafNode(_getNodeKey(lastNode), _value); + totalNewNodes += 1; + } else if (lastNodeType == NodeType.BranchNode) { + if (keyRemainder.length == 0) { + // We've found a branch node with the given key. + // Simply need to update the value of the node to match. + newNodes[totalNewNodes] = _editBranchValue(lastNode, _value); + totalNewNodes += 1; + } else { + // We've found a branch node, but it doesn't contain our key. + // Reinsert the old branch for now. + newNodes[totalNewNodes] = lastNode; + totalNewNodes += 1; + // Create a new leaf node, slicing our remainder since the first byte points + // to our branch node. + newNodes[totalNewNodes] = _makeLeafNode(BytesUtils.slice(keyRemainder, 1), _value); + totalNewNodes += 1; + } + } else { + // Our last node is either an extension node or a leaf node with a different key. + bytes memory lastNodeKey = _getNodeKey(lastNode); + uint256 sharedNibbleLength = _getSharedNibbleLength(lastNodeKey, keyRemainder); + + if (sharedNibbleLength != 0) { + // We've got some shared nibbles between the last node and our key remainder. + // We'll need to insert an extension node that covers these shared nibbles. + bytes memory nextNodeKey = BytesUtils.slice(lastNodeKey, 0, sharedNibbleLength); + newNodes[totalNewNodes] = _makeExtensionNode(nextNodeKey, _getNodeHash(_value)); + totalNewNodes += 1; + + // Cut down the keys since we've just covered these shared nibbles. + lastNodeKey = BytesUtils.slice(lastNodeKey, sharedNibbleLength); + keyRemainder = BytesUtils.slice(keyRemainder, sharedNibbleLength); + } + + // Create an empty branch to fill in. + TrieNode memory newBranch = _makeEmptyBranchNode(); + + if (lastNodeKey.length == 0) { + // Key remainder was larger than the key for our last node. + // The value within our last node is therefore going to be shifted into + // a branch value slot. + newBranch = _editBranchValue(newBranch, _getNodeValue(lastNode)); + } else { + // Last node key was larger than the key remainder. + // We're going to modify some index of our branch. + uint8 branchKey = uint8(lastNodeKey[0]); + // Move on to the next nibble. + lastNodeKey = BytesUtils.slice(lastNodeKey, 1); + + if (lastNodeType == NodeType.LeafNode) { + // We're dealing with a leaf node. + // We'll modify the key and insert the old leaf node into the branch index. + TrieNode memory modifiedLastNode = _makeLeafNode(lastNodeKey, _getNodeValue(lastNode)); + newBranch = _editBranchIndex(newBranch, branchKey, _getNodeHash(modifiedLastNode.encoded)); + } else if (lastNodeKey.length != 0) { + // We're dealing with a shrinking extension node. + // We need to modify the node to decrease the size of the key. + TrieNode memory modifiedLastNode = _makeExtensionNode(lastNodeKey, _getNodeValue(lastNode)); + newBranch = _editBranchIndex(newBranch, branchKey, _getNodeHash(modifiedLastNode.encoded)); + } else { + // We're dealing with an unnecessary extension node. + // We're going to delete the node entirely. + // Simply insert its current value into the branch index. + newBranch = _editBranchIndex(newBranch, branchKey, _getNodeValue(lastNode)); + } + } + + if (keyRemainder.length == 0) { + // We've got nothing left in the key remainder. + // Simply insert the value into the branch value slot. + newBranch = _editBranchValue(newBranch, _value); + // Push the branch into the list of new nodes. + newNodes[totalNewNodes] = newBranch; + totalNewNodes += 1; + } else { + // We've got some key remainder to work with. + // We'll be inserting a leaf node into the trie. + // First, move on to the next nibble. + keyRemainder = BytesUtils.slice(keyRemainder, 1); + // Push the branch into the list of new nodes. + newNodes[totalNewNodes] = newBranch; + totalNewNodes += 1; + // Push a new leaf node for our k/v pair. + newNodes[totalNewNodes] = _makeLeafNode(keyRemainder, _value); + totalNewNodes += 1; + } + } + + // Finally, join the old path with our newly created nodes. + // Since we're overwriting the last node in the path, we use `_pathLength - 1`. + return _joinNodeArrays(_path, _pathLength - 1, newNodes, totalNewNodes); + } + + /** + * @notice Computes the trie root from a given path. + * @param _nodes Path to some k/v pair. + * @param _key Key for the k/v pair. + * @return _updatedRoot Root hash for the updated trie. + */ + function _getUpdatedTrieRoot( + TrieNode[] memory _nodes, + bytes memory _key + ) private pure returns (bytes32 _updatedRoot) { + bytes memory key = BytesUtils.toNibbles(_key); + + // Some variables to keep track of during iteration. + TrieNode memory currentNode; + NodeType currentNodeType; + bytes memory previousNodeHash; + + // Run through the path backwards to rebuild our root hash. + for (uint256 i = _nodes.length; i > 0; i--) { + // Pick out the current node. + currentNode = _nodes[i - 1]; + currentNodeType = _getNodeType(currentNode); + + if (currentNodeType == NodeType.LeafNode) { + // Leaf nodes are already correctly encoded. + // Shift the key over to account for the nodes key. + bytes memory nodeKey = _getNodeKey(currentNode); + key = BytesUtils.slice(key, 0, key.length - nodeKey.length); + } else if (currentNodeType == NodeType.ExtensionNode) { + // Shift the key over to account for the nodes key. + bytes memory nodeKey = _getNodeKey(currentNode); + key = BytesUtils.slice(key, 0, key.length - nodeKey.length); + + // If this node is the last element in the path, it'll be correctly encoded + // and we can skip this part. + if (previousNodeHash.length > 0) { + // Re-encode the node based on the previous node. + currentNode = _makeExtensionNode(nodeKey, previousNodeHash); + } + } else if (currentNodeType == NodeType.BranchNode) { + // If this node is the last element in the path, it'll be correctly encoded + // and we can skip this part. + if (previousNodeHash.length > 0) { + // Re-encode the node based on the previous node. + uint8 branchKey = uint8(key[key.length - 1]); + key = BytesUtils.slice(key, 0, key.length - 1); + currentNode = _editBranchIndex(currentNode, branchKey, previousNodeHash); + } + } + + // Compute the node hash for the next iteration. + previousNodeHash = _getNodeHash(currentNode.encoded); + } + + // Current node should be the root at this point. + // Simply return the hash of its encoding. + return keccak256(currentNode.encoded); + } + + /** + * @notice Parses an RLP-encoded proof into something more useful. + * @param _proof RLP-encoded proof to parse. + * @return _parsed Proof parsed into easily accessible structs. + */ + function _parseProof( + bytes memory _proof + ) private pure returns (TrieNode[] memory _parsed) { + RLPReader.RLPItem[] memory nodes = RLPReader.readList(_proof); + TrieNode[] memory proof = new TrieNode[](nodes.length); + + for (uint256 i = 0; i < nodes.length; i++) { + bytes memory encoded = RLPReader.readBytes(nodes[i]); + proof[i] = TrieNode({encoded: encoded, decoded: RLPReader.readList(encoded)}); + } + + return proof; + } + + /** + * @notice Picks out the ID for a node. Node ID is referred to as the + * "hash" within the specification, but nodes < 32 bytes are not actually + * hashed. + * @param _node Node to pull an ID for. + * @return _nodeID ID for the node, depending on the size of its contents. + */ + function _getNodeID( + RLPReader.RLPItem memory _node + ) private pure returns (bytes32 _nodeID) { + bytes memory nodeID; + + if (_node.length < 32) { + // Nodes smaller than 32 bytes are RLP encoded. + nodeID = RLPReader.readRawBytes(_node); + } else { + // Nodes 32 bytes or larger are hashed. + nodeID = RLPReader.readBytes(_node); + } + + return BytesUtils.toBytes32(nodeID); + } + + /** + * @notice Gets the path for a leaf or extension node. + * @param _node Node to get a path for. + * @return _path Node path, converted to an array of nibbles. + */ + function _getNodePath( + TrieNode memory _node + ) private pure returns (bytes memory _path) { + return BytesUtils.toNibbles(RLPReader.readBytes(_node.decoded[0])); + } + + /** + * @notice Gets the key for a leaf or extension node. Keys are essentially + * just paths without any prefix. + * @param _node Node to get a key for. + * @return _key Node key, converted to an array of nibbles. + */ + function _getNodeKey( + TrieNode memory _node + ) private pure returns (bytes memory _key) { + return _removeHexPrefix(_getNodePath(_node)); + } + + /** + * @notice Gets the path for a node. + * @param _node Node to get a value for. + * @return _value Node value, as hex bytes. + */ + function _getNodeValue( + TrieNode memory _node + ) private pure returns (bytes memory _value) { + return RLPReader.readBytes(_node.decoded[_node.decoded.length - 1]); + } + + /** + * @notice Computes the node hash for an encoded node. Nodes < 32 bytes + * are not hashed, all others are keccak256 hashed. + * @param _encoded Encoded node to hash. + * @return _hash Hash of the encoded node. Simply the input if < 32 bytes. + */ + function _getNodeHash( + bytes memory _encoded + ) private pure returns (bytes memory _hash) { + if (_encoded.length < 32) { + return _encoded; + } else { + return abi.encodePacked(keccak256(_encoded)); + } + } + + /** + * @notice Determines the type for a given node. + * @param _node Node to determine a type for. + * @return _type Type of the node; BranchNode/ExtensionNode/LeafNode. + */ + function _getNodeType( + TrieNode memory _node + ) private pure returns (NodeType _type) { + if (_node.decoded.length == BRANCH_NODE_LENGTH) { + return NodeType.BranchNode; + } else if (_node.decoded.length == LEAF_OR_EXTENSION_NODE_LENGTH) { + bytes memory path = _getNodePath(_node); + uint8 prefix = uint8(path[0]); + + if (prefix == PREFIX_LEAF_EVEN || prefix == PREFIX_LEAF_ODD) { + return NodeType.LeafNode; + } else if (prefix == PREFIX_EXTENSION_EVEN || prefix == PREFIX_EXTENSION_ODD) { + return NodeType.ExtensionNode; + } + } + + revert("Invalid node type"); + } + + /** + * @notice Utility; determines the number of nibbles shared between two + * nibble arrays. + * @param _a First nibble array. + * @param _b Second nibble array. + * @return _shared Number of shared nibbles. + */ + function _getSharedNibbleLength(bytes memory _a, bytes memory _b) private pure returns (uint256 _shared) { + uint256 i = 0; + while (_a.length > i && _b.length > i && _a[i] == _b[i]) { + i++; + } + return i; + } + + /** + * @notice Utility; converts an RLP-encoded node into our nice struct. + * @param _raw RLP-encoded node to convert. + * @return _node Node as a TrieNode struct. + */ + function _makeNode( + bytes[] memory _raw + ) private pure returns (TrieNode memory _node) { + bytes memory encoded = RLPWriter.writeList(_raw); + + return TrieNode({encoded: encoded, decoded: RLPReader.readList(encoded)}); + } + + /** + * @notice Utility; converts an RLP-decoded node into our nice struct. + * @param _items RLP-decoded node to convert. + * @return _node Node as a TrieNode struct. + */ + function _makeNode( + RLPReader.RLPItem[] memory _items + ) private pure returns (TrieNode memory _node) { + bytes[] memory raw = new bytes[](_items.length); + for (uint256 i = 0; i < _items.length; i++) { + raw[i] = RLPReader.readRawBytes(_items[i]); + } + return _makeNode(raw); + } + + /** + * @notice Creates a new extension node. + * @param _key Key for the extension node, unprefixed. + * @param _value Value for the extension node. + * @return _node New extension node with the given k/v pair. + */ + function _makeExtensionNode(bytes memory _key, bytes memory _value) private pure returns (TrieNode memory _node) { + bytes[] memory raw = new bytes[](2); + bytes memory key = _addHexPrefix(_key, false); + raw[0] = RLPWriter.writeBytes(BytesUtils.fromNibbles(key)); + raw[1] = RLPWriter.writeBytes(_value); + return _makeNode(raw); + } + + /** + * @notice Creates a new leaf node. + * @dev This function is essentially identical to `_makeExtensionNode`. + * Although we could route both to a single method with a flag, it's + * more gas efficient to keep them separate and duplicate the logic. + * @param _key Key for the leaf node, unprefixed. + * @param _value Value for the leaf node. + * @return _node New leaf node with the given k/v pair. + */ + function _makeLeafNode(bytes memory _key, bytes memory _value) private pure returns (TrieNode memory _node) { + bytes[] memory raw = new bytes[](2); + bytes memory key = _addHexPrefix(_key, true); + raw[0] = RLPWriter.writeBytes(BytesUtils.fromNibbles(key)); + raw[1] = RLPWriter.writeBytes(_value); + return _makeNode(raw); + } + + /** + * @notice Creates an empty branch node. + * @return _node Empty branch node as a TrieNode struct. + */ + function _makeEmptyBranchNode() private pure returns (TrieNode memory _node) { + bytes[] memory raw = new bytes[](BRANCH_NODE_LENGTH); + for (uint256 i = 0; i < raw.length; i++) { + raw[i] = RLP_NULL_BYTES; + } + return _makeNode(raw); + } + + /** + * @notice Modifies the value slot for a given branch. + * @param _branch Branch node to modify. + * @param _value Value to insert into the branch. + * @return _updatedNode Modified branch node. + */ + function _editBranchValue( + TrieNode memory _branch, + bytes memory _value + ) private pure returns (TrieNode memory _updatedNode) { + bytes memory encoded = RLPWriter.writeBytes(_value); + _branch.decoded[_branch.decoded.length - 1] = RLPReader.toRLPItem(encoded); + return _makeNode(_branch.decoded); + } + + /** + * @notice Modifies a slot at an index for a given branch. + * @param _branch Branch node to modify. + * @param _index Slot index to modify. + * @param _value Value to insert into the slot. + * @return _updatedNode Modified branch node. + */ + function _editBranchIndex( + TrieNode memory _branch, + uint8 _index, + bytes memory _value + ) private pure returns (TrieNode memory _updatedNode) { + bytes memory encoded = _value.length < 32 ? _value : RLPWriter.writeBytes(_value); + _branch.decoded[_index] = RLPReader.toRLPItem(encoded); + return _makeNode(_branch.decoded); + } + + /** + * @notice Utility; adds a prefix to a key. + * @param _key Key to prefix. + * @param _isLeaf Whether or not the key belongs to a leaf. + * @return _prefixedKey Prefixed key. + */ + function _addHexPrefix(bytes memory _key, bool _isLeaf) private pure returns (bytes memory _prefixedKey) { + uint8 prefix = _isLeaf ? uint8(0x02) : uint8(0x00); + uint8 offset = uint8(_key.length % 2); + bytes memory prefixed = new bytes(2 - offset); + prefixed[0] = bytes1(prefix + offset); + return abi.encodePacked(prefixed, _key); + } + + /** + * @notice Utility; removes a prefix from a path. + * @param _path Path to remove the prefix from. + * @return _unprefixedKey Unprefixed key. + */ + function _removeHexPrefix( + bytes memory _path + ) private pure returns (bytes memory _unprefixedKey) { + if (uint8(_path[0]) % 2 == 0) { + return BytesUtils.slice(_path, 2); + } else { + return BytesUtils.slice(_path, 1); + } + } + + /** + * @notice Utility; combines two node arrays. Array lengths are required + * because the actual lengths may be longer than the filled lengths. + * Array resizing is extremely costly and should be avoided. + * @param _a First array to join. + * @param _aLength Length of the first array. + * @param _b Second array to join. + * @param _bLength Length of the second array. + * @return _joined Combined node array. + */ + function _joinNodeArrays( + TrieNode[] memory _a, + uint256 _aLength, + TrieNode[] memory _b, + uint256 _bLength + ) private pure returns (TrieNode[] memory _joined) { + TrieNode[] memory ret = new TrieNode[](_aLength + _bLength); + + // Copy elements from the first array. + for (uint256 i = 0; i < _aLength; i++) { + ret[i] = _a[i]; + } + + // Copy elements from the second array. + for (uint256 i = 0; i < _bLength; i++) { + ret[i + _aLength] = _b[i]; + } + + return ret; + } +} diff --git a/bolt-contracts/src/lib/trie/SecureMerkleTrie.sol b/bolt-contracts/src/lib/trie/SecureMerkleTrie.sol new file mode 100644 index 000000000..9ba468c0e --- /dev/null +++ b/bolt-contracts/src/lib/trie/SecureMerkleTrie.sol @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.25; +pragma experimental ABIEncoderV2; + +/* Library Imports */ +import {MerkleTrie} from "./MerkleTrie.sol"; + +/** + * @title SecureMerkleTrie + */ +library SecureMerkleTrie { + /** + * + * Internal Functions * + * + */ + + /** + * @notice Verifies a proof that a given key/value pair is present in the + * Merkle trie. + * @param _key Key of the node to search for, as a hex string. + * @param _value Value of the node to search for, as a hex string. + * @param _proof Merkle trie inclusion proof for the desired node. Unlike + * traditional Merkle trees, this proof is executed top-down and consists + * of a list of RLP-encoded nodes that make a path down to the target node. + * @param _root Known root of the Merkle trie. Used to verify that the + * included proof is correctly constructed. + * @return _verified `true` if the k/v pair exists in the trie, `false` otherwise. + */ + function verifyInclusionProof( + bytes memory _key, + bytes memory _value, + bytes memory _proof, + bytes32 _root + ) internal pure returns (bool _verified) { + bytes memory key = _getSecureKey(_key); + return MerkleTrie.verifyInclusionProof(key, _value, _proof, _root); + } + + /** + * @notice Verifies a proof that a given key is *not* present in + * the Merkle trie. + * @param _key Key of the node to search for, as a hex string. + * @param _proof Merkle trie inclusion proof for the node *nearest* the + * target node. + * @param _root Known root of the Merkle trie. Used to verify that the + * included proof is correctly constructed. + * @return _verified `true` if the key is not present in the trie, `false` otherwise. + */ + function verifyExclusionProof( + bytes memory _key, + bytes memory _proof, + bytes32 _root + ) internal pure returns (bool _verified) { + bytes memory key = _getSecureKey(_key); + return MerkleTrie.verifyExclusionProof(key, _proof, _root); + } + + /** + * @notice Updates a Merkle trie and returns a new root hash. + * @param _key Key of the node to update, as a hex string. + * @param _value Value of the node to update, as a hex string. + * @param _proof Merkle trie inclusion proof for the node *nearest* the + * target node. If the key exists, we can simply update the value. + * Otherwise, we need to modify the trie to handle the new k/v pair. + * @param _root Known root of the Merkle trie. Used to verify that the + * included proof is correctly constructed. + * @return _updatedRoot Root hash of the newly constructed trie. + */ + function update( + bytes memory _key, + bytes memory _value, + bytes memory _proof, + bytes32 _root + ) internal pure returns (bytes32 _updatedRoot) { + bytes memory key = _getSecureKey(_key); + return MerkleTrie.update(key, _value, _proof, _root); + } + + /** + * @notice Retrieves the value associated with a given key. + * @param _key Key to search for, as hex bytes. + * @param _proof Merkle trie inclusion proof for the key. + * @param _root Known root of the Merkle trie. + * @return _exists Whether or not the key exists. + * @return _value Value of the key if it exists. + */ + function get( + bytes memory _key, + bytes memory _proof, + bytes32 _root + ) internal pure returns (bool _exists, bytes memory _value) { + bytes memory key = _getSecureKey(_key); + return MerkleTrie.get(key, _proof, _root); + } + + /** + * Computes the root hash for a trie with a single node. + * @param _key Key for the single node. + * @param _value Value for the single node. + * @return _updatedRoot Hash of the trie. + */ + function getSingleNodeRootHash( + bytes memory _key, + bytes memory _value + ) internal pure returns (bytes32 _updatedRoot) { + bytes memory key = _getSecureKey(_key); + return MerkleTrie.getSingleNodeRootHash(key, _value); + } + + /** + * + * Private Functions * + * + */ + + /** + * Computes the secure counterpart to a key. + * @param _key Key to get a secure key from. + * @return _secureKey Secure version of the key. + */ + function _getSecureKey( + bytes memory _key + ) private pure returns (bytes memory _secureKey) { + return abi.encodePacked(keccak256(_key)); + } +} diff --git a/bolt-contracts/test/BoltChallenger.t.sol b/bolt-contracts/test/BoltChallenger.t.sol new file mode 100644 index 000000000..73cf8dbf7 --- /dev/null +++ b/bolt-contracts/test/BoltChallenger.t.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.25; + +import {Test, console} from "forge-std/Test.sol"; + +import {BoltChallenger} from "../src/contracts/BoltChallenger.sol"; + +contract BoltChallengerTest is Test { + BoltChallenger boltChallenger; + + address challenger = makeAddr("challenger"); + address resolver = makeAddr("resolver"); + + function setUp() public { + boltChallenger = new BoltChallenger(); + } + + // TODO: to fix this test we need to have the right block hash on the test evm + // function testProveHeaderData() public { + // bytes memory headerRLP = + // hex"f90262a05fec8a42a343a05f3768385b69fded75042c9bcf5b159d1d75afb195533dd1b0a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347944838b106fce9647bdf1e7877bf73ce8b0bad5f97a05e9f1c386d2c33a9bc1a0c09b506cf1610833e986a0f4c6b5e6419691a54ee5ca03d513346b4f4f7de4017e9f0775fe7b20a8eb83115d1d6924327d8d34a1e0a53a0c8e276fa4db7cb4d12804058dc68da18341be2235b4c606cf627ae933923db4cb90100fbe169767f663d41d2b4da71eabafb6dd1dd2aa1eb6564b2a3db4641b53b8d0a07519542c67a34bd4e013f652f3e8df2ab3b8f208d137ebc079da44e452fc92b75f2a5b87c0a9d7bdaf65d2ff06094aac197d3f5544c4981ea965f4d8dff44d11ee9f7f82a63a9faa11c7bbad30c7cf5ad2fcf1f2cd4a737ce22de3e4809b5028cf2b659bbe43d5d29d9b163eeeeead0ef67e5cb99e1de3c682103517ff0fca8b7aff16a52d3a3c37a345ad5ac04ad6eeeaf463aa83d07140f64d9062c9885747b5fa3a6a9100b5357104df7634bbb96decbbf921e43f8951dee57d66d6fff9ffc71a9e9a00ab458276cd7b5555a29a6625ed298f9b07eda2137e1eb5dc5fee78084013c2cc38401c9c380840129b0ad8466e058ef98546974616e2028746974616e6275696c6465722e78797a29a06e7ff2a061a2b7ac1029c8a6ce1975bf32cf9030f8a447a8e11f3dc2d830539a88000000000000000085017aacac89a0a6c1c6fbce582b1401ec7ba51c3ad0510c96eb1b73bb5f42526c77d117418be780831a0000a0a4bd43aa3cbc5ebc7b805509b825a50cb7cf4bdbf14fd4125d21f36d1fb45dff"; + // + // ( + // bytes32 transactionsRoot, + // uint256 blockNumber, + // uint256 gasLimit, + // uint256 gasUsed, + // uint256 timestamp, + // uint256 baseFee + // ) = boltChallenger.proveRecentBlockHeaderData(headerRLP); + // + // + // assertEq(transactionsRoot, 0x5fec8a42a343a05f3768385b69fded75042c9bcf5b159d1d75afb195533dd1b0); + // } + + function testProveAccountData() public { + address accountToProve = 0x0D9f5045B604bA0c050b5eb06D0b25d01c525Ea5; + bytes32 stateRootAtBlock = 0x5e9f1c386d2c33a9bc1a0c09b506cf1610833e986a0f4c6b5e6419691a54ee5c; + bytes memory accountProof = hex"00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000e96f90e93b90214f90211a0811c959d191db81cf1929f44231271707897d34acbdf1ff395ebd1664ed0d402a08cb5b5fcff8a8af29bfa184a62bc0eb5c4e13ddee909a5df288d0ba9f502250da0b9ce97b6d45b73111c420162db054697d67ae3330f7e3160474c4633bf32d3b9a03c65835e979a0d0fa9abc98638ee8ec1caa1cc11afad2d3014449ee881846c07a0de57c186ef9f1ed6fbfbdc0dd1e093bb017bab02a74f6bb2f55dff8276bd84eca01a8c58f62a77ac22822b90de03da14aa4e5ae109397db3bc037072e0a471e8f3a0eeb33f25372db45d01b38a73e981ac2a6fd2edb47bbbfb35c211bfadcdab98eea06afcd2aebc853ab00b624c9131713496beb4c31caf8fe8de668c4cc6a2fc402ea07102458c5da434a6b10dc534a1af09734076bb508f75d06b5c176a27203a6052a02fe0924a5eab75a28d4d7c654e1001fb9f31f8dcd3771c7bebd4d2d268a83761a0dd07a648c3b8308bf0df970717940ae5fac547d721c8adb2aea7521c33a5343aa0a4767329f798db09bb9708644db469bfc82f75d14fa7acffb6e8873b093bc328a00d86b3bc4dcf43b26948a38722327332729d1cb3b8ea7a9bd886cc39bf81c582a07f263804cc9465bc39205072c4390cf7494e959ead1604c325b6276a20a63f13a080df7adfb547dfe0bafbe8e43b75196d5efd1b4c2b89d6826e18def24a4982dca040d60c99fad8c70721ec6980c5ef0ab9c972c629ff19c1291763f8c001cf66c580b90214f90211a0597a26d8e55321031130772bd0edbaa9d93fb4fd5cb4ee386d5abca4c3d8aeeba07315915d766e641e0f59f890ebe999f6b3c4eb5e2f13b11bdd7d64cc622d8982a0c3d88c88b821718ad36846afc28dc27b57c9b3e814028f2677229196569a61efa09b98e5103e974df3c43ae46cc220c2aabf2801756cbde9017673f84615e3d588a09d4f0f18222d9fc2a7fe5e2272f1d6c0fc812bc88026b8ae3231d7ed41ec3c6da079e6650c8cead0a00f3be7282a317ea433b46c546233d50b342c1adf2f1c7b79a0e1dce0d24b41a927cef8ab6020fd16072a7ddfe5b330be4c0076ed084467c520a05fe0f634e8bcb450c6bea7093ea0a30fa88696af71547bcc061dd7f621a98b0ca0124ca7aa20069313f9c410714558a5961dbc53ae3b20674c2abccc35daaa91bda05accd06a38bb150686d79a6737daeaeac5b8aee70095c9de86d142ff5d3c4986a0cebd5d3771b0e15d91032172479fbeec48b579fbe42e8b58aee4dfa9fcd4d49ea07fdde85d73410096140cf9fd4eefffc7227c6eea1b304de828dc263e188aa09fa0a23e70c2d889bafc64cc9f696875c6a1cf8766b0408da93f944af663ca7ec7d2a0811e2a3befc6e6a6c8707f2b0166b1781903fde6bb65f01f5a6e529bbe0b601ca00f6489b50499b169ba4358084612e62f2797cdaa3550b916ab8c61f832678ee2a0f95d1980186fec7e4fe217bb47ec88413c126c05a14dd261b7be6570ea56d37580b90214f90211a0b23630acb1cf17e67160d18f74cdc571484064791078c1362f890cfb6bf74bc3a05b508b8ad59481fd45191ff9b7936fd624689766fc70706ab55fe7d8c16f26bea00a29bf174aec93658aeb90e92356bd6e7f7073962632db7a70b4985323432f18a0ddd49fc927ba19be8101e579d7dfb57645ab00f2451cc7b5663e3272da1d8ffda0fdd0c640a3f5c8f762d77b7ed4519f97444409be455ee2a88b3dea653bc8cc39a0929eefa2d310115c3209b60e7914a4c883e64e8781e57238ed892f0af5db4f34a0de4ca72eb7c28a07b9921f7f1e5b212615efb4cb34c2df6438bfa1d152270f6da021d21fb733f01b782023012b274a8bb81c3f008ff955e764e4bd373082cbc72ba068d350091322a3bef93717faf262f781a3689877c1400faa4486dab88a8c4909a0f7fb928e1b39505bd26891f731da8dbaa13afcdf5067b6800911dbb78568d4d8a0008db459bacce07bb24b2e75af21fdecf092e4ff4f69dd8893794d436ac8786da0b0be97cd51be4677263d35fefcabbdae9ec4ab1c98371c1825c2872ede26b17fa0eb6793de3fc77d5e5027d234d18c3969a279a59d5252546931f7220aa4d3c97ca0f0fbe59827d354c7a7555ba1302cf02947b044074fd74d8e5495b8843ade53c9a086980fa6a05caa8760b03a3a3a2777922f1747bd01a9225cd03465db3510164da08bee10930d7fa04169dd090682ce2f5fde1cab5d66bb752a9117372fd636700a80b90214f90211a042bc1721fc28b2b79306afe12500390b51a50505f8829e1aa81c2783f2003d56a0820a5842f1a3e836030584ddabf6689ff84b5046e01bee1c296e43692faf80faa05e4bea7a554535f76b941361a789c5b99f37df6353e81c7d2feb476781a6cd9da01fc8f103f35d728eee5d485b757a404b24f5382af63f2d6cbc412fa4120e3006a0a834e3a096fe2315fcfb159523a6569bf4ba08189d2c8296d0605809d3c08097a065d516c01f6066f33cd0d9e3218bcd175db534c44eb8a3719ef5dfaac22e419aa0c480208593ca766aa98eace44f642ddb997b895f59a53e088a5b5cd3923d67faa0fdc9f7a3a9001e58160de5e076ff90400f28ab46d6e7387dd0beae0dea1d7200a080ff08dede6c23e1c111ce8b4f9636d840c3a97041f1dd014ee205e8abe3c138a0dc839033ddc21e21033c44c06c0afc8ca1874c4d9d822b40f9aaa211f25372f6a0aba944735c5037760821093d75922fac3df506c18fa1815bb6bb2e2fc1182a72a0c140e23529c4ed7a532f5e1a3403c5da0d3b7b2781234ab52e9c0984ba047ebca0a64e02adb3000f493f8dc41f06c5c8251b0968f1810934c656b7507b2819e2fda0091a3f0609c1bbbc59925080392c7919c3b2515b90e7b16c41907c6307485f84a0c1dc1c449bbb7ab4a66301e831853443d94cb35f05e5688b5c7545ae6266635ca0e038f538492afb65eb990acfc020034f812ef3f91751bb4a988e3961ac72e4ce80b90214f90211a07accb50d92289f7c6870a958c7f5824727df1b7782c37284e2380964fe49bae3a0ebdda520a5bf8f960206839269df0955c666ae4010cabfa7594e50b5a47dd0e0a06652a79c42cf0d23532b84d47d58b6c898076d88147030581ff121d25407e25ba0a2791815cb8473ff7c0f293eac0365234c50f1a70fa52f529260390e9b6297e9a06519a8d5bf8febb422a4c7fc06895943b71e4f67b9ae0e3044330708e759323ea0be93e6dbe55fb8ebf349086832612d149c3af5f9066820fae13a230661392e74a02c187bea6ca54bacc98acd7e276ade111b0a14aef78be2f9dce829108c6b09a9a0982809a6ca9598bd67e2e39b214498a9acc5642011bbd5f68b5f8699e32e57baa0b268789c19f0615e9c6bb66579c729ed0c3e4b701457c219880fcbe5fbc5040ba00cf1ef59072b5c248e76221d5481c21e57eab944a5723e66a9d374e192c18aada0ceca857660555cc33f19ce530213ebfb2a9b0d9c34f9ed43d6d223d072255369a0cb41c230b31455fa5c830291faf053548f3273d6484345cc21723bb491470e24a0eb6e2079c07e7eadaede73e95ed1a23f9437ee7f4e9ae66d42a47916c4bcefdaa0d5533c62c8318a7e0bfb0bc5196f94c90f065da0668c4fb78f12c6a49d3d7fdfa0c8960e5b4f03757ebb594f634f0616602a78addddcc6da641917ff14ffac72cca0e35f2f148bfa3778d313b25ef0c8cc30c070a1ac0417ad33b07889bb526b5f1f80b90214f90211a023c9514b2fac99cd172f93d2db4d6578ec69fc2e0ef5c79fc76499ded09130eda07963b5c7dce6d662dee90bf64ce349a36d05df5268ff851bd1e1907f77bc0966a01206d33e26bffa7991db600d20e1cdebd28c623698e8d0481b37b47be68edd9ea0a6b7b10c53412201d9ca7461b7210d6457a6b275b95033e2d8ca9778e5071c05a03e6f50fedb2b9f9f963356fc2899800e46a38169a794a1e6906c02ce8e8f0d59a0d4cec6a796cbab7e9cb28063423fcf157bac02de0cf1f0931303a701006019a2a0fb7e5187937b4203cf97bce2f345c9161bfbb9eb431a105356009e6ce18a1fbca0c347aa16bba7f3d962b693f6cbb1c72fa14b8efa60c55097695c11502493fbe6a01c91aad4f4d391df268007fee771caedccc43369e07d6eb22fd45dab6beb0986a0672ca6ccfd8ac83f8974afefc9c9c2b58376991580158b16fd98cb9076cf8ea9a06cb3234503ad7116bb0e66257c646b2bce7603d774474a4f100cf93dde3eb594a085d0406435a6562f3370dd4b5de47a2b7d1084b9f25f0dbf49f54d2f46a2fc32a08bba9249e073397de4c46dd27688e9467b20fb8e61e04fd2594136cae26cd5c7a0a43fd38e14c38b64b84bb579b9a3cfab05cd5cc69c3a812a68e458acf72ea96ca0d848b40a2dbd5d8a4179e89fe8533f21db91982a0b8ecc1542c08ea27aec522aa0e03bc889898881f7ecb7caf0db5fb95125e26ff9e26bb85c33cbe360b50c9f1f80b90194f90191a017e9f741d070bfe3d975719f75663dabd5acdeb3678c50bd4872b817d7713d0b80a03e1570ab8528237e5c36ecce18a601c0d490eacbde79d7f29607e44b6f1f3168a0363162c748099d6ed114ead87c481972c1b0c124730b341c4ef1f963e0835a9880a09ef90c2a9ad234a08cbf4ea4ced535f227bc48becd22c1b05f2cbd586f018b73a0ae59576b0c1089b2828db7e54602ad0fabe4eff312ee5e0c21a04ac09b0e04d180a0fdbde0b4dbc02bb2ec472517c6f543de3673d33a6246a602479b43042ae9222da0c8d33ec13b89d8fd0ba487fe9cd61d18deff49bed48dc073384dc086a7361273a021253bb1110de615cede8c708a94cd6b898a61b5b1aaef8760b3341f232d63f0a06e7fbd4a6404e4acae007bb6147c58aebf6f19b3914a8d48b831bc3e50e5c05ea0c72d502ed522a796f823edefb721e64ff9d38904c9913b5be76f1e759c899b9da0fcc3a12a432daa29cb33f4a198624f6391124579ab7cbfae1eb30de076cfdd7980a0ab5019c60fef28581a93f0707739bbb24dcfbc2ffc550fb57aea59d91c79c04780b870f86e9d3a1698215d94a53d304dacf1ae39aec65065a3307b0d96cd2acd29de94b84ef84c81ea874f28d58d92e405a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a47000000000000000000000"; + + (uint256 nonce, uint256 balance) = boltChallenger.proveAccountData(accountToProve, stateRootAtBlock, accountProof); + console.log(nonce); + console.log(balance); + } +} diff --git a/bolt-contracts/test/testdata/encode_proof.js b/bolt-contracts/test/testdata/encode_proof.js new file mode 100644 index 000000000..4530a2072 --- /dev/null +++ b/bolt-contracts/test/testdata/encode_proof.js @@ -0,0 +1,23 @@ +const RLP = require("rlp"); +const { utils } = require("ethers"); + +function main() { + const accountProof = [ + "0xf90211a0811c959d191db81cf1929f44231271707897d34acbdf1ff395ebd1664ed0d402a08cb5b5fcff8a8af29bfa184a62bc0eb5c4e13ddee909a5df288d0ba9f502250da0b9ce97b6d45b73111c420162db054697d67ae3330f7e3160474c4633bf32d3b9a03c65835e979a0d0fa9abc98638ee8ec1caa1cc11afad2d3014449ee881846c07a0de57c186ef9f1ed6fbfbdc0dd1e093bb017bab02a74f6bb2f55dff8276bd84eca01a8c58f62a77ac22822b90de03da14aa4e5ae109397db3bc037072e0a471e8f3a0eeb33f25372db45d01b38a73e981ac2a6fd2edb47bbbfb35c211bfadcdab98eea06afcd2aebc853ab00b624c9131713496beb4c31caf8fe8de668c4cc6a2fc402ea07102458c5da434a6b10dc534a1af09734076bb508f75d06b5c176a27203a6052a02fe0924a5eab75a28d4d7c654e1001fb9f31f8dcd3771c7bebd4d2d268a83761a0dd07a648c3b8308bf0df970717940ae5fac547d721c8adb2aea7521c33a5343aa0a4767329f798db09bb9708644db469bfc82f75d14fa7acffb6e8873b093bc328a00d86b3bc4dcf43b26948a38722327332729d1cb3b8ea7a9bd886cc39bf81c582a07f263804cc9465bc39205072c4390cf7494e959ead1604c325b6276a20a63f13a080df7adfb547dfe0bafbe8e43b75196d5efd1b4c2b89d6826e18def24a4982dca040d60c99fad8c70721ec6980c5ef0ab9c972c629ff19c1291763f8c001cf66c580", + "0xf90211a0597a26d8e55321031130772bd0edbaa9d93fb4fd5cb4ee386d5abca4c3d8aeeba07315915d766e641e0f59f890ebe999f6b3c4eb5e2f13b11bdd7d64cc622d8982a0c3d88c88b821718ad36846afc28dc27b57c9b3e814028f2677229196569a61efa09b98e5103e974df3c43ae46cc220c2aabf2801756cbde9017673f84615e3d588a09d4f0f18222d9fc2a7fe5e2272f1d6c0fc812bc88026b8ae3231d7ed41ec3c6da079e6650c8cead0a00f3be7282a317ea433b46c546233d50b342c1adf2f1c7b79a0e1dce0d24b41a927cef8ab6020fd16072a7ddfe5b330be4c0076ed084467c520a05fe0f634e8bcb450c6bea7093ea0a30fa88696af71547bcc061dd7f621a98b0ca0124ca7aa20069313f9c410714558a5961dbc53ae3b20674c2abccc35daaa91bda05accd06a38bb150686d79a6737daeaeac5b8aee70095c9de86d142ff5d3c4986a0cebd5d3771b0e15d91032172479fbeec48b579fbe42e8b58aee4dfa9fcd4d49ea07fdde85d73410096140cf9fd4eefffc7227c6eea1b304de828dc263e188aa09fa0a23e70c2d889bafc64cc9f696875c6a1cf8766b0408da93f944af663ca7ec7d2a0811e2a3befc6e6a6c8707f2b0166b1781903fde6bb65f01f5a6e529bbe0b601ca00f6489b50499b169ba4358084612e62f2797cdaa3550b916ab8c61f832678ee2a0f95d1980186fec7e4fe217bb47ec88413c126c05a14dd261b7be6570ea56d37580", + "0xf90211a0b23630acb1cf17e67160d18f74cdc571484064791078c1362f890cfb6bf74bc3a05b508b8ad59481fd45191ff9b7936fd624689766fc70706ab55fe7d8c16f26bea00a29bf174aec93658aeb90e92356bd6e7f7073962632db7a70b4985323432f18a0ddd49fc927ba19be8101e579d7dfb57645ab00f2451cc7b5663e3272da1d8ffda0fdd0c640a3f5c8f762d77b7ed4519f97444409be455ee2a88b3dea653bc8cc39a0929eefa2d310115c3209b60e7914a4c883e64e8781e57238ed892f0af5db4f34a0de4ca72eb7c28a07b9921f7f1e5b212615efb4cb34c2df6438bfa1d152270f6da021d21fb733f01b782023012b274a8bb81c3f008ff955e764e4bd373082cbc72ba068d350091322a3bef93717faf262f781a3689877c1400faa4486dab88a8c4909a0f7fb928e1b39505bd26891f731da8dbaa13afcdf5067b6800911dbb78568d4d8a0008db459bacce07bb24b2e75af21fdecf092e4ff4f69dd8893794d436ac8786da0b0be97cd51be4677263d35fefcabbdae9ec4ab1c98371c1825c2872ede26b17fa0eb6793de3fc77d5e5027d234d18c3969a279a59d5252546931f7220aa4d3c97ca0f0fbe59827d354c7a7555ba1302cf02947b044074fd74d8e5495b8843ade53c9a086980fa6a05caa8760b03a3a3a2777922f1747bd01a9225cd03465db3510164da08bee10930d7fa04169dd090682ce2f5fde1cab5d66bb752a9117372fd636700a80", + "0xf90211a042bc1721fc28b2b79306afe12500390b51a50505f8829e1aa81c2783f2003d56a0820a5842f1a3e836030584ddabf6689ff84b5046e01bee1c296e43692faf80faa05e4bea7a554535f76b941361a789c5b99f37df6353e81c7d2feb476781a6cd9da01fc8f103f35d728eee5d485b757a404b24f5382af63f2d6cbc412fa4120e3006a0a834e3a096fe2315fcfb159523a6569bf4ba08189d2c8296d0605809d3c08097a065d516c01f6066f33cd0d9e3218bcd175db534c44eb8a3719ef5dfaac22e419aa0c480208593ca766aa98eace44f642ddb997b895f59a53e088a5b5cd3923d67faa0fdc9f7a3a9001e58160de5e076ff90400f28ab46d6e7387dd0beae0dea1d7200a080ff08dede6c23e1c111ce8b4f9636d840c3a97041f1dd014ee205e8abe3c138a0dc839033ddc21e21033c44c06c0afc8ca1874c4d9d822b40f9aaa211f25372f6a0aba944735c5037760821093d75922fac3df506c18fa1815bb6bb2e2fc1182a72a0c140e23529c4ed7a532f5e1a3403c5da0d3b7b2781234ab52e9c0984ba047ebca0a64e02adb3000f493f8dc41f06c5c8251b0968f1810934c656b7507b2819e2fda0091a3f0609c1bbbc59925080392c7919c3b2515b90e7b16c41907c6307485f84a0c1dc1c449bbb7ab4a66301e831853443d94cb35f05e5688b5c7545ae6266635ca0e038f538492afb65eb990acfc020034f812ef3f91751bb4a988e3961ac72e4ce80", + "0xf90211a07accb50d92289f7c6870a958c7f5824727df1b7782c37284e2380964fe49bae3a0ebdda520a5bf8f960206839269df0955c666ae4010cabfa7594e50b5a47dd0e0a06652a79c42cf0d23532b84d47d58b6c898076d88147030581ff121d25407e25ba0a2791815cb8473ff7c0f293eac0365234c50f1a70fa52f529260390e9b6297e9a06519a8d5bf8febb422a4c7fc06895943b71e4f67b9ae0e3044330708e759323ea0be93e6dbe55fb8ebf349086832612d149c3af5f9066820fae13a230661392e74a02c187bea6ca54bacc98acd7e276ade111b0a14aef78be2f9dce829108c6b09a9a0982809a6ca9598bd67e2e39b214498a9acc5642011bbd5f68b5f8699e32e57baa0b268789c19f0615e9c6bb66579c729ed0c3e4b701457c219880fcbe5fbc5040ba00cf1ef59072b5c248e76221d5481c21e57eab944a5723e66a9d374e192c18aada0ceca857660555cc33f19ce530213ebfb2a9b0d9c34f9ed43d6d223d072255369a0cb41c230b31455fa5c830291faf053548f3273d6484345cc21723bb491470e24a0eb6e2079c07e7eadaede73e95ed1a23f9437ee7f4e9ae66d42a47916c4bcefdaa0d5533c62c8318a7e0bfb0bc5196f94c90f065da0668c4fb78f12c6a49d3d7fdfa0c8960e5b4f03757ebb594f634f0616602a78addddcc6da641917ff14ffac72cca0e35f2f148bfa3778d313b25ef0c8cc30c070a1ac0417ad33b07889bb526b5f1f80", + "0xf90211a023c9514b2fac99cd172f93d2db4d6578ec69fc2e0ef5c79fc76499ded09130eda07963b5c7dce6d662dee90bf64ce349a36d05df5268ff851bd1e1907f77bc0966a01206d33e26bffa7991db600d20e1cdebd28c623698e8d0481b37b47be68edd9ea0a6b7b10c53412201d9ca7461b7210d6457a6b275b95033e2d8ca9778e5071c05a03e6f50fedb2b9f9f963356fc2899800e46a38169a794a1e6906c02ce8e8f0d59a0d4cec6a796cbab7e9cb28063423fcf157bac02de0cf1f0931303a701006019a2a0fb7e5187937b4203cf97bce2f345c9161bfbb9eb431a105356009e6ce18a1fbca0c347aa16bba7f3d962b693f6cbb1c72fa14b8efa60c55097695c11502493fbe6a01c91aad4f4d391df268007fee771caedccc43369e07d6eb22fd45dab6beb0986a0672ca6ccfd8ac83f8974afefc9c9c2b58376991580158b16fd98cb9076cf8ea9a06cb3234503ad7116bb0e66257c646b2bce7603d774474a4f100cf93dde3eb594a085d0406435a6562f3370dd4b5de47a2b7d1084b9f25f0dbf49f54d2f46a2fc32a08bba9249e073397de4c46dd27688e9467b20fb8e61e04fd2594136cae26cd5c7a0a43fd38e14c38b64b84bb579b9a3cfab05cd5cc69c3a812a68e458acf72ea96ca0d848b40a2dbd5d8a4179e89fe8533f21db91982a0b8ecc1542c08ea27aec522aa0e03bc889898881f7ecb7caf0db5fb95125e26ff9e26bb85c33cbe360b50c9f1f80", + "0xf90191a017e9f741d070bfe3d975719f75663dabd5acdeb3678c50bd4872b817d7713d0b80a03e1570ab8528237e5c36ecce18a601c0d490eacbde79d7f29607e44b6f1f3168a0363162c748099d6ed114ead87c481972c1b0c124730b341c4ef1f963e0835a9880a09ef90c2a9ad234a08cbf4ea4ced535f227bc48becd22c1b05f2cbd586f018b73a0ae59576b0c1089b2828db7e54602ad0fabe4eff312ee5e0c21a04ac09b0e04d180a0fdbde0b4dbc02bb2ec472517c6f543de3673d33a6246a602479b43042ae9222da0c8d33ec13b89d8fd0ba487fe9cd61d18deff49bed48dc073384dc086a7361273a021253bb1110de615cede8c708a94cd6b898a61b5b1aaef8760b3341f232d63f0a06e7fbd4a6404e4acae007bb6147c58aebf6f19b3914a8d48b831bc3e50e5c05ea0c72d502ed522a796f823edefb721e64ff9d38904c9913b5be76f1e759c899b9da0fcc3a12a432daa29cb33f4a198624f6391124579ab7cbfae1eb30de076cfdd7980a0ab5019c60fef28581a93f0707739bbb24dcfbc2ffc550fb57aea59d91c79c04780", + "0xf86e9d3a1698215d94a53d304dacf1ae39aec65065a3307b0d96cd2acd29de94b84ef84c81ea874f28d58d92e405a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + ]; + + const coder = new utils.AbiCoder(); + + const trieProofRLP = "0x" + RLP.encode(accountProof).toString("hex"); + const encodedProof = coder.encode(["bytes"], [trieProofRLP]); + console.log(encodedProof); +} + +main(); diff --git a/bolt-contracts/test/testdata/eth_proof.json b/bolt-contracts/test/testdata/eth_proof.json new file mode 100644 index 000000000..c6020c201 --- /dev/null +++ b/bolt-contracts/test/testdata/eth_proof.json @@ -0,0 +1,22 @@ +{ + "jsonrpc": "2.0", + "id": 1, + "result": { + "address": "0x0d9f5045b604ba0c050b5eb06d0b25d01c525ea5", + "accountProof": [ + "0xf90211a0811c959d191db81cf1929f44231271707897d34acbdf1ff395ebd1664ed0d402a08cb5b5fcff8a8af29bfa184a62bc0eb5c4e13ddee909a5df288d0ba9f502250da0b9ce97b6d45b73111c420162db054697d67ae3330f7e3160474c4633bf32d3b9a03c65835e979a0d0fa9abc98638ee8ec1caa1cc11afad2d3014449ee881846c07a0de57c186ef9f1ed6fbfbdc0dd1e093bb017bab02a74f6bb2f55dff8276bd84eca01a8c58f62a77ac22822b90de03da14aa4e5ae109397db3bc037072e0a471e8f3a0eeb33f25372db45d01b38a73e981ac2a6fd2edb47bbbfb35c211bfadcdab98eea06afcd2aebc853ab00b624c9131713496beb4c31caf8fe8de668c4cc6a2fc402ea07102458c5da434a6b10dc534a1af09734076bb508f75d06b5c176a27203a6052a02fe0924a5eab75a28d4d7c654e1001fb9f31f8dcd3771c7bebd4d2d268a83761a0dd07a648c3b8308bf0df970717940ae5fac547d721c8adb2aea7521c33a5343aa0a4767329f798db09bb9708644db469bfc82f75d14fa7acffb6e8873b093bc328a00d86b3bc4dcf43b26948a38722327332729d1cb3b8ea7a9bd886cc39bf81c582a07f263804cc9465bc39205072c4390cf7494e959ead1604c325b6276a20a63f13a080df7adfb547dfe0bafbe8e43b75196d5efd1b4c2b89d6826e18def24a4982dca040d60c99fad8c70721ec6980c5ef0ab9c972c629ff19c1291763f8c001cf66c580", + "0xf90211a0597a26d8e55321031130772bd0edbaa9d93fb4fd5cb4ee386d5abca4c3d8aeeba07315915d766e641e0f59f890ebe999f6b3c4eb5e2f13b11bdd7d64cc622d8982a0c3d88c88b821718ad36846afc28dc27b57c9b3e814028f2677229196569a61efa09b98e5103e974df3c43ae46cc220c2aabf2801756cbde9017673f84615e3d588a09d4f0f18222d9fc2a7fe5e2272f1d6c0fc812bc88026b8ae3231d7ed41ec3c6da079e6650c8cead0a00f3be7282a317ea433b46c546233d50b342c1adf2f1c7b79a0e1dce0d24b41a927cef8ab6020fd16072a7ddfe5b330be4c0076ed084467c520a05fe0f634e8bcb450c6bea7093ea0a30fa88696af71547bcc061dd7f621a98b0ca0124ca7aa20069313f9c410714558a5961dbc53ae3b20674c2abccc35daaa91bda05accd06a38bb150686d79a6737daeaeac5b8aee70095c9de86d142ff5d3c4986a0cebd5d3771b0e15d91032172479fbeec48b579fbe42e8b58aee4dfa9fcd4d49ea07fdde85d73410096140cf9fd4eefffc7227c6eea1b304de828dc263e188aa09fa0a23e70c2d889bafc64cc9f696875c6a1cf8766b0408da93f944af663ca7ec7d2a0811e2a3befc6e6a6c8707f2b0166b1781903fde6bb65f01f5a6e529bbe0b601ca00f6489b50499b169ba4358084612e62f2797cdaa3550b916ab8c61f832678ee2a0f95d1980186fec7e4fe217bb47ec88413c126c05a14dd261b7be6570ea56d37580", + "0xf90211a0b23630acb1cf17e67160d18f74cdc571484064791078c1362f890cfb6bf74bc3a05b508b8ad59481fd45191ff9b7936fd624689766fc70706ab55fe7d8c16f26bea00a29bf174aec93658aeb90e92356bd6e7f7073962632db7a70b4985323432f18a0ddd49fc927ba19be8101e579d7dfb57645ab00f2451cc7b5663e3272da1d8ffda0fdd0c640a3f5c8f762d77b7ed4519f97444409be455ee2a88b3dea653bc8cc39a0929eefa2d310115c3209b60e7914a4c883e64e8781e57238ed892f0af5db4f34a0de4ca72eb7c28a07b9921f7f1e5b212615efb4cb34c2df6438bfa1d152270f6da021d21fb733f01b782023012b274a8bb81c3f008ff955e764e4bd373082cbc72ba068d350091322a3bef93717faf262f781a3689877c1400faa4486dab88a8c4909a0f7fb928e1b39505bd26891f731da8dbaa13afcdf5067b6800911dbb78568d4d8a0008db459bacce07bb24b2e75af21fdecf092e4ff4f69dd8893794d436ac8786da0b0be97cd51be4677263d35fefcabbdae9ec4ab1c98371c1825c2872ede26b17fa0eb6793de3fc77d5e5027d234d18c3969a279a59d5252546931f7220aa4d3c97ca0f0fbe59827d354c7a7555ba1302cf02947b044074fd74d8e5495b8843ade53c9a086980fa6a05caa8760b03a3a3a2777922f1747bd01a9225cd03465db3510164da08bee10930d7fa04169dd090682ce2f5fde1cab5d66bb752a9117372fd636700a80", + "0xf90211a042bc1721fc28b2b79306afe12500390b51a50505f8829e1aa81c2783f2003d56a0820a5842f1a3e836030584ddabf6689ff84b5046e01bee1c296e43692faf80faa05e4bea7a554535f76b941361a789c5b99f37df6353e81c7d2feb476781a6cd9da01fc8f103f35d728eee5d485b757a404b24f5382af63f2d6cbc412fa4120e3006a0a834e3a096fe2315fcfb159523a6569bf4ba08189d2c8296d0605809d3c08097a065d516c01f6066f33cd0d9e3218bcd175db534c44eb8a3719ef5dfaac22e419aa0c480208593ca766aa98eace44f642ddb997b895f59a53e088a5b5cd3923d67faa0fdc9f7a3a9001e58160de5e076ff90400f28ab46d6e7387dd0beae0dea1d7200a080ff08dede6c23e1c111ce8b4f9636d840c3a97041f1dd014ee205e8abe3c138a0dc839033ddc21e21033c44c06c0afc8ca1874c4d9d822b40f9aaa211f25372f6a0aba944735c5037760821093d75922fac3df506c18fa1815bb6bb2e2fc1182a72a0c140e23529c4ed7a532f5e1a3403c5da0d3b7b2781234ab52e9c0984ba047ebca0a64e02adb3000f493f8dc41f06c5c8251b0968f1810934c656b7507b2819e2fda0091a3f0609c1bbbc59925080392c7919c3b2515b90e7b16c41907c6307485f84a0c1dc1c449bbb7ab4a66301e831853443d94cb35f05e5688b5c7545ae6266635ca0e038f538492afb65eb990acfc020034f812ef3f91751bb4a988e3961ac72e4ce80", + "0xf90211a07accb50d92289f7c6870a958c7f5824727df1b7782c37284e2380964fe49bae3a0ebdda520a5bf8f960206839269df0955c666ae4010cabfa7594e50b5a47dd0e0a06652a79c42cf0d23532b84d47d58b6c898076d88147030581ff121d25407e25ba0a2791815cb8473ff7c0f293eac0365234c50f1a70fa52f529260390e9b6297e9a06519a8d5bf8febb422a4c7fc06895943b71e4f67b9ae0e3044330708e759323ea0be93e6dbe55fb8ebf349086832612d149c3af5f9066820fae13a230661392e74a02c187bea6ca54bacc98acd7e276ade111b0a14aef78be2f9dce829108c6b09a9a0982809a6ca9598bd67e2e39b214498a9acc5642011bbd5f68b5f8699e32e57baa0b268789c19f0615e9c6bb66579c729ed0c3e4b701457c219880fcbe5fbc5040ba00cf1ef59072b5c248e76221d5481c21e57eab944a5723e66a9d374e192c18aada0ceca857660555cc33f19ce530213ebfb2a9b0d9c34f9ed43d6d223d072255369a0cb41c230b31455fa5c830291faf053548f3273d6484345cc21723bb491470e24a0eb6e2079c07e7eadaede73e95ed1a23f9437ee7f4e9ae66d42a47916c4bcefdaa0d5533c62c8318a7e0bfb0bc5196f94c90f065da0668c4fb78f12c6a49d3d7fdfa0c8960e5b4f03757ebb594f634f0616602a78addddcc6da641917ff14ffac72cca0e35f2f148bfa3778d313b25ef0c8cc30c070a1ac0417ad33b07889bb526b5f1f80", + "0xf90211a023c9514b2fac99cd172f93d2db4d6578ec69fc2e0ef5c79fc76499ded09130eda07963b5c7dce6d662dee90bf64ce349a36d05df5268ff851bd1e1907f77bc0966a01206d33e26bffa7991db600d20e1cdebd28c623698e8d0481b37b47be68edd9ea0a6b7b10c53412201d9ca7461b7210d6457a6b275b95033e2d8ca9778e5071c05a03e6f50fedb2b9f9f963356fc2899800e46a38169a794a1e6906c02ce8e8f0d59a0d4cec6a796cbab7e9cb28063423fcf157bac02de0cf1f0931303a701006019a2a0fb7e5187937b4203cf97bce2f345c9161bfbb9eb431a105356009e6ce18a1fbca0c347aa16bba7f3d962b693f6cbb1c72fa14b8efa60c55097695c11502493fbe6a01c91aad4f4d391df268007fee771caedccc43369e07d6eb22fd45dab6beb0986a0672ca6ccfd8ac83f8974afefc9c9c2b58376991580158b16fd98cb9076cf8ea9a06cb3234503ad7116bb0e66257c646b2bce7603d774474a4f100cf93dde3eb594a085d0406435a6562f3370dd4b5de47a2b7d1084b9f25f0dbf49f54d2f46a2fc32a08bba9249e073397de4c46dd27688e9467b20fb8e61e04fd2594136cae26cd5c7a0a43fd38e14c38b64b84bb579b9a3cfab05cd5cc69c3a812a68e458acf72ea96ca0d848b40a2dbd5d8a4179e89fe8533f21db91982a0b8ecc1542c08ea27aec522aa0e03bc889898881f7ecb7caf0db5fb95125e26ff9e26bb85c33cbe360b50c9f1f80", + "0xf90191a017e9f741d070bfe3d975719f75663dabd5acdeb3678c50bd4872b817d7713d0b80a03e1570ab8528237e5c36ecce18a601c0d490eacbde79d7f29607e44b6f1f3168a0363162c748099d6ed114ead87c481972c1b0c124730b341c4ef1f963e0835a9880a09ef90c2a9ad234a08cbf4ea4ced535f227bc48becd22c1b05f2cbd586f018b73a0ae59576b0c1089b2828db7e54602ad0fabe4eff312ee5e0c21a04ac09b0e04d180a0fdbde0b4dbc02bb2ec472517c6f543de3673d33a6246a602479b43042ae9222da0c8d33ec13b89d8fd0ba487fe9cd61d18deff49bed48dc073384dc086a7361273a021253bb1110de615cede8c708a94cd6b898a61b5b1aaef8760b3341f232d63f0a06e7fbd4a6404e4acae007bb6147c58aebf6f19b3914a8d48b831bc3e50e5c05ea0c72d502ed522a796f823edefb721e64ff9d38904c9913b5be76f1e759c899b9da0fcc3a12a432daa29cb33f4a198624f6391124579ab7cbfae1eb30de076cfdd7980a0ab5019c60fef28581a93f0707739bbb24dcfbc2ffc550fb57aea59d91c79c04780", + "0xf86e9d3a1698215d94a53d304dacf1ae39aec65065a3307b0d96cd2acd29de94b84ef84c81ea874f28d58d92e405a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" + ], + "balance": "0x4f28d58d92e405", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "nonce": "0xea", + "storageHash": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "storageProof": [] + } +} diff --git a/bolt-contracts/test/testdata/package-lock.json b/bolt-contracts/test/testdata/package-lock.json new file mode 100644 index 000000000..4b14c8dc2 --- /dev/null +++ b/bolt-contracts/test/testdata/package-lock.json @@ -0,0 +1,895 @@ +{ + "name": "testdata", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "testdata", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "ethers": "^5", + "rlp": "^2" + } + }, + "node_modules/@ethersproject/abi": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.7.0.tgz", + "integrity": "sha512-351ktp42TiRcYB3H1OP8yajPeAQstMW/yCFokj/AthP9bLHzQFPlOrxOcwYEDkUAICmOHljvN4K39OMTMUa9RA==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/address": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/hash": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/strings": "^5.7.0" + } + }, + "node_modules/@ethersproject/abstract-provider": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/abstract-provider/-/abstract-provider-5.7.0.tgz", + "integrity": "sha512-R41c9UkchKCpAqStMYUpdunjo3pkEvZC3FAwZn5S5MGbXoMQOHIdHItezTETxAO5bevtMApSyEhn9+CHcDsWBw==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/networks": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/transactions": "^5.7.0", + "@ethersproject/web": "^5.7.0" + } + }, + "node_modules/@ethersproject/abstract-signer": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/abstract-signer/-/abstract-signer-5.7.0.tgz", + "integrity": "sha512-a16V8bq1/Cz+TGCkE2OPMTOUDLS3grCpdjoJCYNnVBbdYEMSgKrU0+B90s8b6H+ByYTBZN7a3g76jdIJi7UfKQ==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/abstract-provider": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0" + } + }, + "node_modules/@ethersproject/address": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/address/-/address-5.7.0.tgz", + "integrity": "sha512-9wYhYt7aghVGo758POM5nqcOMaE168Q6aRLJZwUmiqSrAungkG74gSSeKEIR7ukixesdRZGPgVqme6vmxs1fkA==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/rlp": "^5.7.0" + } + }, + "node_modules/@ethersproject/base64": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/base64/-/base64-5.7.0.tgz", + "integrity": "sha512-Dr8tcHt2mEbsZr/mwTPIQAf3Ai0Bks/7gTw9dSqk1mQvhW3XvRlmDJr/4n+wg1JmCl16NZue17CDh8xb/vZ0sQ==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.7.0" + } + }, + "node_modules/@ethersproject/basex": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/basex/-/basex-5.7.0.tgz", + "integrity": "sha512-ywlh43GwZLv2Voc2gQVTKBoVQ1mti3d8HK5aMxsfu/nRDnMmNqaSJ3r3n85HBByT8OpoY96SXM1FogC533T4zw==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/properties": "^5.7.0" + } + }, + "node_modules/@ethersproject/bignumber": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.7.0.tgz", + "integrity": "sha512-n1CAdIHRWjSucQO3MC1zPSVgV/6dy/fjL9pMrPP9peL+QxEg9wOsVqwD4+818B6LUEtaXzVHQiuivzRoxPxUGw==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "bn.js": "^5.2.1" + } + }, + "node_modules/@ethersproject/bytes": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.7.0.tgz", + "integrity": "sha512-nsbxwgFXWh9NyYWo+U8atvmMsSdKJprTcICAkvbBffT75qDocbuggBU0SJiVK2MuTrp0q+xvLkTnGMPK1+uA9A==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/logger": "^5.7.0" + } + }, + "node_modules/@ethersproject/constants": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.7.0.tgz", + "integrity": "sha512-DHI+y5dBNvkpYUMiRQyxRBYBefZkJfo70VUkUAsRjcPs47muV9evftfZ0PJVCXYbAiCgght0DtcF9srFQmIgWA==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bignumber": "^5.7.0" + } + }, + "node_modules/@ethersproject/contracts": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/contracts/-/contracts-5.7.0.tgz", + "integrity": "sha512-5GJbzEU3X+d33CdfPhcyS+z8MzsTrBGk/sc+G+59+tPa9yFkl6HQ9D6L0QMgNTA9q8dT0XKxxkyp883XsQvbbg==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/abi": "^5.7.0", + "@ethersproject/abstract-provider": "^5.7.0", + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/address": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/transactions": "^5.7.0" + } + }, + "node_modules/@ethersproject/hash": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/hash/-/hash-5.7.0.tgz", + "integrity": "sha512-qX5WrQfnah1EFnO5zJv1v46a8HW0+E5xuBBDTwMFZLuVTx0tbU2kkx15NqdjxecrLGatQN9FGQKpb1FKdHCt+g==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/address": "^5.7.0", + "@ethersproject/base64": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/strings": "^5.7.0" + } + }, + "node_modules/@ethersproject/hdnode": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/hdnode/-/hdnode-5.7.0.tgz", + "integrity": "sha512-OmyYo9EENBPPf4ERhR7oj6uAtUAhYGqOnIS+jE5pTXvdKBS99ikzq1E7Iv0ZQZ5V36Lqx1qZLeak0Ra16qpeOg==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/basex": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/pbkdf2": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/sha2": "^5.7.0", + "@ethersproject/signing-key": "^5.7.0", + "@ethersproject/strings": "^5.7.0", + "@ethersproject/transactions": "^5.7.0", + "@ethersproject/wordlists": "^5.7.0" + } + }, + "node_modules/@ethersproject/json-wallets": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/json-wallets/-/json-wallets-5.7.0.tgz", + "integrity": "sha512-8oee5Xgu6+RKgJTkvEMl2wDgSPSAQ9MB/3JYjFV9jlKvcYHUXZC+cQp0njgmxdHkYWn8s6/IqIZYm0YWCjO/0g==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/address": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/hdnode": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/pbkdf2": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/random": "^5.7.0", + "@ethersproject/strings": "^5.7.0", + "@ethersproject/transactions": "^5.7.0", + "aes-js": "3.0.0", + "scrypt-js": "3.0.1" + } + }, + "node_modules/@ethersproject/keccak256": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.7.0.tgz", + "integrity": "sha512-2UcPboeL/iW+pSg6vZ6ydF8tCnv3Iu/8tUmLLzWWGzxWKFFqOBQFLo6uLUv6BDrLgCDfN28RJ/wtByx+jZ4KBg==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "js-sha3": "0.8.0" + } + }, + "node_modules/@ethersproject/logger": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.7.0.tgz", + "integrity": "sha512-0odtFdXu/XHtjQXJYA3u9G0G8btm0ND5Cu8M7i5vhEcE8/HmF4Lbdqanwyv4uQTr2tx6b7fQRmgLrsnpQlmnig==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT" + }, + "node_modules/@ethersproject/networks": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.7.1.tgz", + "integrity": "sha512-n/MufjFYv3yFcUyfhnXotyDlNdFb7onmkSy8aQERi2PjNcnWQ66xXxa3XlS8nCcA8aJKJjIIMNJTC7tu80GwpQ==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/logger": "^5.7.0" + } + }, + "node_modules/@ethersproject/pbkdf2": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/pbkdf2/-/pbkdf2-5.7.0.tgz", + "integrity": "sha512-oR/dBRZR6GTyaofd86DehG72hY6NpAjhabkhxgr3X2FpJtJuodEl2auADWBZfhDHgVCbu3/H/Ocq2uC6dpNjjw==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/sha2": "^5.7.0" + } + }, + "node_modules/@ethersproject/properties": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.7.0.tgz", + "integrity": "sha512-J87jy8suntrAkIZtecpxEPxY//szqr1mlBaYlQ0r4RCaiD2hjheqF9s1LVE8vVuJCXisjIP+JgtK/Do54ej4Sw==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/logger": "^5.7.0" + } + }, + "node_modules/@ethersproject/providers": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/@ethersproject/providers/-/providers-5.7.2.tgz", + "integrity": "sha512-g34EWZ1WWAVgr4aptGlVBF8mhl3VWjv+8hoAnzStu8Ah22VHBsuGzP17eb6xDVRzw895G4W7vvx60lFFur/1Rg==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/abstract-provider": "^5.7.0", + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/address": "^5.7.0", + "@ethersproject/base64": "^5.7.0", + "@ethersproject/basex": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/hash": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/networks": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/random": "^5.7.0", + "@ethersproject/rlp": "^5.7.0", + "@ethersproject/sha2": "^5.7.0", + "@ethersproject/strings": "^5.7.0", + "@ethersproject/transactions": "^5.7.0", + "@ethersproject/web": "^5.7.0", + "bech32": "1.1.4", + "ws": "7.4.6" + } + }, + "node_modules/@ethersproject/random": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/random/-/random-5.7.0.tgz", + "integrity": "sha512-19WjScqRA8IIeWclFme75VMXSBvi4e6InrUNuaR4s5pTF2qNhcGdCUwdxUVGtDDqC00sDLCO93jPQoDUH4HVmQ==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0" + } + }, + "node_modules/@ethersproject/rlp": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/rlp/-/rlp-5.7.0.tgz", + "integrity": "sha512-rBxzX2vK8mVF7b0Tol44t5Tb8gomOHkj5guL+HhzQ1yBh/ydjGnpw6at+X6Iw0Kp3OzzzkcKp8N9r0W4kYSs9w==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0" + } + }, + "node_modules/@ethersproject/sha2": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/sha2/-/sha2-5.7.0.tgz", + "integrity": "sha512-gKlH42riwb3KYp0reLsFTokByAKoJdgFCwI+CCiX/k+Jm2mbNs6oOaCjYQSlI1+XBVejwH2KrmCbMAT/GnRDQw==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "hash.js": "1.1.7" + } + }, + "node_modules/@ethersproject/signing-key": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/signing-key/-/signing-key-5.7.0.tgz", + "integrity": "sha512-MZdy2nL3wO0u7gkB4nA/pEf8lu1TlFswPNmy8AiYkfKTdO6eXBJyUdmHO/ehm/htHw9K/qF8ujnTyUAD+Ry54Q==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "bn.js": "^5.2.1", + "elliptic": "6.5.4", + "hash.js": "1.1.7" + } + }, + "node_modules/@ethersproject/solidity": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/solidity/-/solidity-5.7.0.tgz", + "integrity": "sha512-HmabMd2Dt/raavyaGukF4XxizWKhKQ24DoLtdNbBmNKUOPqwjsKQSdV9GQtj9CBEea9DlzETlVER1gYeXXBGaA==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/sha2": "^5.7.0", + "@ethersproject/strings": "^5.7.0" + } + }, + "node_modules/@ethersproject/strings": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.7.0.tgz", + "integrity": "sha512-/9nu+lj0YswRNSH0NXYqrh8775XNyEdUQAuf3f+SmOrnVewcJ5SBNAjF7lpgehKi4abvNNXyf+HX86czCdJ8Mg==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/logger": "^5.7.0" + } + }, + "node_modules/@ethersproject/transactions": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/transactions/-/transactions-5.7.0.tgz", + "integrity": "sha512-kmcNicCp1lp8qanMTC3RIikGgoJ80ztTyvtsFvCYpSCfkjhD0jZ2LOrnbcuxuToLIUYYf+4XwD1rP+B/erDIhQ==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/address": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/rlp": "^5.7.0", + "@ethersproject/signing-key": "^5.7.0" + } + }, + "node_modules/@ethersproject/units": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/units/-/units-5.7.0.tgz", + "integrity": "sha512-pD3xLMy3SJu9kG5xDGI7+xhTEmGXlEqXU4OfNapmfnxLVY4EMSSRp7j1k7eezutBPH7RBN/7QPnwR7hzNlEFeg==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/logger": "^5.7.0" + } + }, + "node_modules/@ethersproject/wallet": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/wallet/-/wallet-5.7.0.tgz", + "integrity": "sha512-MhmXlJXEJFBFVKrDLB4ZdDzxcBxQ3rLyCkhNqVu3CDYvR97E+8r01UgrI+TI99Le+aYm/in/0vp86guJuM7FCA==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/abstract-provider": "^5.7.0", + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/address": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/hash": "^5.7.0", + "@ethersproject/hdnode": "^5.7.0", + "@ethersproject/json-wallets": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/random": "^5.7.0", + "@ethersproject/signing-key": "^5.7.0", + "@ethersproject/transactions": "^5.7.0", + "@ethersproject/wordlists": "^5.7.0" + } + }, + "node_modules/@ethersproject/web": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/@ethersproject/web/-/web-5.7.1.tgz", + "integrity": "sha512-Gueu8lSvyjBWL4cYsWsjh6MtMwM0+H4HvqFPZfB6dV8ctbP9zFAO73VG1cMWae0FLPCtz0peKPpZY8/ugJJX2w==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/base64": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/strings": "^5.7.0" + } + }, + "node_modules/@ethersproject/wordlists": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/wordlists/-/wordlists-5.7.0.tgz", + "integrity": "sha512-S2TFNJNfHWVHNE6cNDjbVlZ6MgE17MIxMbMg2zv3wn+3XSJGosL1m9ZVv3GXCf/2ymSsQ+hRI5IzoMJTG6aoVA==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/hash": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/strings": "^5.7.0" + } + }, + "node_modules/aes-js": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-3.0.0.tgz", + "integrity": "sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw==", + "license": "MIT" + }, + "node_modules/bech32": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz", + "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==", + "license": "MIT" + }, + "node_modules/bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", + "license": "MIT" + }, + "node_modules/brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", + "license": "MIT" + }, + "node_modules/elliptic": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", + "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", + "license": "MIT", + "dependencies": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/elliptic/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "license": "MIT" + }, + "node_modules/ethers": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-5.7.2.tgz", + "integrity": "sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/abi": "5.7.0", + "@ethersproject/abstract-provider": "5.7.0", + "@ethersproject/abstract-signer": "5.7.0", + "@ethersproject/address": "5.7.0", + "@ethersproject/base64": "5.7.0", + "@ethersproject/basex": "5.7.0", + "@ethersproject/bignumber": "5.7.0", + "@ethersproject/bytes": "5.7.0", + "@ethersproject/constants": "5.7.0", + "@ethersproject/contracts": "5.7.0", + "@ethersproject/hash": "5.7.0", + "@ethersproject/hdnode": "5.7.0", + "@ethersproject/json-wallets": "5.7.0", + "@ethersproject/keccak256": "5.7.0", + "@ethersproject/logger": "5.7.0", + "@ethersproject/networks": "5.7.1", + "@ethersproject/pbkdf2": "5.7.0", + "@ethersproject/properties": "5.7.0", + "@ethersproject/providers": "5.7.2", + "@ethersproject/random": "5.7.0", + "@ethersproject/rlp": "5.7.0", + "@ethersproject/sha2": "5.7.0", + "@ethersproject/signing-key": "5.7.0", + "@ethersproject/solidity": "5.7.0", + "@ethersproject/strings": "5.7.0", + "@ethersproject/transactions": "5.7.0", + "@ethersproject/units": "5.7.0", + "@ethersproject/wallet": "5.7.0", + "@ethersproject/web": "5.7.1", + "@ethersproject/wordlists": "5.7.0" + } + }, + "node_modules/hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "node_modules/hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", + "license": "MIT", + "dependencies": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/js-sha3": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", + "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==", + "license": "MIT" + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "license": "ISC" + }, + "node_modules/minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==", + "license": "MIT" + }, + "node_modules/rlp": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/rlp/-/rlp-2.2.7.tgz", + "integrity": "sha512-d5gdPmgQ0Z+AklL2NVXr/IoSjNZFfTVvQWzL/AM2AOcSzYP2xjlb0AC8YyCLc41MSNf6P6QVtjgPdmVtzb+4lQ==", + "license": "MPL-2.0", + "dependencies": { + "bn.js": "^5.2.0" + }, + "bin": { + "rlp": "bin/rlp" + } + }, + "node_modules/scrypt-js": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz", + "integrity": "sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==", + "license": "MIT" + }, + "node_modules/ws": { + "version": "7.4.6", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", + "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", + "license": "MIT", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + } + } +} diff --git a/bolt-contracts/test/testdata/package.json b/bolt-contracts/test/testdata/package.json new file mode 100644 index 000000000..f7738b06a --- /dev/null +++ b/bolt-contracts/test/testdata/package.json @@ -0,0 +1,16 @@ +{ + "name": "testdata", + "version": "1.0.0", + "main": "encode_proof.js", + "scripts": { + "run": "node encode_proof.js" + }, + "keywords": [], + "author": "", + "license": "ISC", + "description": "", + "dependencies": { + "ethers": "^5", + "rlp": "^2" + } +} diff --git a/bolt-contracts/test/testdata/tx_mpt_proof.json b/bolt-contracts/test/testdata/tx_mpt_proof.json new file mode 100644 index 000000000..45978752f --- /dev/null +++ b/bolt-contracts/test/testdata/tx_mpt_proof.json @@ -0,0 +1,14 @@ +{ + "hash": "0xdf15fd0565b9f0519259aaf6fef098189c21739ccdf05c31d5a6e13fd9acb669", + "blockNumber": 20720835, + + "root": "0x3d513346b4f4f7de4017e9f0775fe7b20a8eb83115d1d6924327d8d34a1e0a53", + "proof": [ + "f90131a0fff1df6db431237ff802fcd881eecc9f5f1116d21473b560e4f1d9aaa670a835a0ee925dd5225525bce5d52e0cbf90d81ba12f02d33b501861aecd832cb55f9eaea0ef39d66a50adb23be43bc6d9196d14ef3f40255b98d5a0163ab5f37597216d7ca0a1618c3abafded0ff0105a3cb3bc5d04eb78afc30563d680677b0ce37b04a906a0f628713e5420ee742def581f7af85b32dd00e81b9ff461a0437f0e5dcb4dfe58a0de177d68ab56dcbafac9bb4c1b056d01d524c63d3062a448d04923e4dd22d202a0c4a31e8f432b4c7a1392c707d39c7b61a87dcabc79723b0b356280616e53b5b0a01000f7cc217910db9de166836ffc6f17e0e746e08350654492aec3ae2926202ba0836ca53f46aa3e19c3cf84d13b74f9d01ff68b490cbcea4cf143617135d1a4d78080808080808080", + "f851a0ff8610ed0aa0bfe4272c8c042336df1857d06aed681463874988e7d53ee70157a0cf582609d5d9ee7af953e96c4a9d708eab5ab008b6e40cd8dfc2c99fe2eb3911808080808080808080808080808080", + "f8d18080808080808080a0f2ce9c96c90c14a2da6ad09561e64282782e4925278fcba73c16c0b64a792926a0c072cdeabb4e525eb4491cd767c99b6b05b18712f02e0d8306ac99a370089b8aa057c7576be2dd7220a49d10548c2351eefc192c7b395d4631cc88834df4f17588a0141b714d3217c153aca9f4118d3266e66bb5929ac32b23c9be2fe485aee066eea0b872feada4eb8b574a8935467893a3de0cd846475aac3bb03c0ecfd9b9a6e8c9a0b666c6ccf7cc46251f8e59600b4cfc95aad84ecf78c4a785765720bde87baa7f808080", + "f90211a058ba8f050c572752c3105020cb89a4a2158f820859192a83af40cb33d19f6600a040174cd836ef22113d8219ac54f7969e00086b0c52c4a601e4311376f4334462a0ae8e3a5c939f73ab2b5c505907895322ffacebdb67caa97dbc102f950b4e95d3a095e3742dbdaf586ce8abc1a301aa6bcc75ec3e686d89220f63ab4b383cf0c1eea0d31eabd7931b4c0c6d5920f9a320edf7c7ab39f11760996f03686c29de761c56a08f515653cbddfc1d96bcac43de1d7a0e1658cef8e4f2933092c10ede3a616126a086c502d57489ef289945d4b47bcd2bd9d9fdc25cd8e7db750d54439af078f607a0e2bbcd55da37b8a86b39a3070af15bce77962bbec932446accd60710661c08f4a082b120504fa878c9b2a7fdaa13e2057527109ad1b310b93308c1f17ae28aa1f4a0321fb9ca8fc75015b6e95c746ddb5d0eacbdcc3592ec3aaa01f77619b868900ba0bbe4d5f44a1dfb6c338e1a44894871c5bcb0ea8031e4d60b11300e93cfb036c7a07a70d8af218d4c92bd54497ec04a711d614c0b38fd3653f6283143668c62fe92a0baaa2edcd2f4ae79845b8059011f7b0b8de7a9833d444cc8c85b85b2d893bdb4a06b106605d4e95179a0739871ee45d1365916ed70a8ae080cc731d836b0b64579a016a8444ade348004761d160522a4b23ddbe56829a486c233f4c3cc527baa87f7a0bf6e144781950cb2bf948b4b15773c3f66bb75e9b952f1beccdb700ecbd1378380", + "f87a20b87702f8740181e9843a926e4b850261515bd6825208940ff71973b5243005b192d5bcf552fc2532b7bdec8801bfa45519b3000080c080a013ba80f6fd2d1f462a9421468774d6f246f8b3f39abdab5adef06d0b479c26d2a05a8d6b94b5c33bb5162c7118d1ed3823403133a0103a36d76b49b7c082e46cb0" + ], + "index": 149 +}