diff --git a/.gitignore b/.gitignore index a529e00..cdfe0f9 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,7 @@ target .DS_Store l1/broadcast/** + +.snfoundry_cache +.snfoundry_trace +profile.pb.gz diff --git a/.tool-versions b/.tool-versions index 2175a33..c7435d1 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1,2 @@ -scarb 0.7.0 +scarb nightly-2024-04-20 +starknet-foundry 0.23.0 diff --git a/README.md b/README.md index dea72bf..04ca96f 100644 --- a/README.md +++ b/README.md @@ -4,18 +4,58 @@ This repository contains and implements smart contracts deployed on Starknet. This repository contains the following modules: -- Core - Implements the core logic behind Herodotus. -- Remappers - Implements a util allowing to map arbitrary timestamps to L1 block numbers. -- Turbo - Acts as a frontend to the Core contracts, provides great UX to developers and simplifies the integration. -- L1 - Smart contracts deployed on Ethereum L1 responsible for synchronizing with L1. +- Core - Implements the core logic behind Herodotus. +- Remappers - Implements a util allowing to map arbitrary timestamps to L1 block numbers. +- Turbo - Acts as a frontend to the Core contracts, provides great UX to developers and simplifies the integration. +- L1 - Smart contracts deployed on Ethereum L1 responsible for synchronizing with L1. # Core This module is responsible for: -- Processing new block headers and growing the MMR. -- Receiving and handling L1 messages containing blockhashes and Poseidon roots of the MMR which generation has been SHARP proven. -- Verifying state proofs and saving the proven values in the `FactsRegistry` +- Processing new block headers and growing the MMR. +- Receiving and handling L1 messages containing blockhashes and Poseidon roots of the MMR which generation has been SHARP proven. +- Verifying state proofs and saving the proven values in the `FactsRegistry` + +## Error codes + +### Headers Store + +- `ONLY_COMMITMENTS_INBOX` - Only commitments inbox (address saved in `commitments_inbox` variable) can send messages to this function. + +- `SRC_MMR_NOT_FOUND` - Source MMR (one from which the branch is created) with provided MMR ID does not exist in the store. + +- `SRC_MMR_ID_0_NOT_ALLOWED` - Source MMR (one from which the branch is created) with ID 0 is not allowed. + +- `NEW_MMR_ID_0_NOT_ALLOWED` - New MMR (one that is created from source branch) with ID 0 is not allowed. + +- `ROOT_0_NOT_ALLOWED` - Creating MMR with root 0 is not allowed. + +- `NEW_MMR_ALREADY_EXISTS` - New MMR (one that is created from source branch) with provided ID already exists in the store. + +- `MMR_NOT_FOUND` - MMR with provided ID does not exist in the store. + +- `PROOF_AND_REF_BLOCK_NOT_ALLOWED` - `process_batch` can't be called with both proof and reference block. Please select either one. + +- `INVALID_HEADER_RLP` - Provided header RLP is invalid. + +- `INVALID_MMR_PROOF` - Provided MMR proof (`proof` or `peaks` or both) is invalid. + +- `INVALID_START_BLOCK` - Cannot read block number from the first header RLP. + +- `BLOCK_NOT_RECEIVED` - Block which was referenced in `process_batch` was not written to the store with `receive_hash` function. + +- `INVALID_INITIAL_HEADER_RLP` - First header RLP didn't match the reference block. + +- `MMR_APPEND_FAILED` - Append to MMR function failed, most likely due to invalid peaks. + +- `INVALID_PARENT_HASH_RLP` - Could not read parent hash from the provided header RLP. + +### Commitments Inbox + +- `ONLY_OWNER` - Only owner can call this function. + +- `ONLY_L1_MESSAGE_SENDER` - Only L1 message sender can call this function. # Timestamps to block numbers mapper diff --git a/Scarb.lock b/Scarb.lock new file mode 100644 index 0000000..04fd00d --- /dev/null +++ b/Scarb.lock @@ -0,0 +1,20 @@ +# Code generated by scarb DO NOT EDIT. +version = 1 + +[[package]] +name = "cairo_lib" +version = "0.2.0" +source = "git+https://github.com/HerodotusDev/cairo-lib.git?branch=update-cairo#0db686d55a4f52835c2127539953178bd01e19f3" + +[[package]] +name = "herodotus_eth_starknet" +version = "0.1.0" +dependencies = [ + "cairo_lib", + "snforge_std", +] + +[[package]] +name = "snforge_std" +version = "0.23.0" +source = "git+https://github.com/foundry-rs/starknet-foundry.git?tag=v0.23.0#f2bff8f796763ada77fe6033ec1b034ceee22abd" diff --git a/Scarb.toml b/Scarb.toml index ee6ebc7..95f5040 100644 --- a/Scarb.toml +++ b/Scarb.toml @@ -10,9 +10,9 @@ casm = true # See more keys and their definitions at https://docs.swmansion.com/scarb/docs/reference/manifest [dependencies] -starknet = "2.2.0" -cairo_lib = { git = "https://github.com/HerodotusDev/cairo-lib.git" } -snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry.git", tag = "v0.4.1" } +starknet = "2.6.3" +cairo_lib = { git = "https://github.com/HerodotusDev/cairo-lib.git", branch = "update-cairo" } +snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry.git", tag = "v0.23.0" } [tool.snforge] exit_first = true @@ -21,4 +21,4 @@ exit_first = true [tool.sncast] account = "herodotus" network = "testnet" -url = "https://starknet-testnet.public.blastapi.io" +url = "https://starknet-sepolia.public.blastapi.io" diff --git a/l1/.env.example b/l1/.env.example deleted file mode 100644 index cbf22bf..0000000 --- a/l1/.env.example +++ /dev/null @@ -1,6 +0,0 @@ -DEPLOY_RPC_URL= -STARKNET_CORE_ADDRESS= -L2_RECIPIENT_ADDRESS= -AGGREGATORS_FACTORY_ADDRESS= -ETHERSCAN_API_KEY= -PRIVATE_KEY= diff --git a/l1/.github/workflows/test.yml b/l1/.github/workflows/test.yml deleted file mode 100644 index 09880b1..0000000 --- a/l1/.github/workflows/test.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: test - -on: workflow_dispatch - -env: - FOUNDRY_PROFILE: ci - -jobs: - check: - strategy: - fail-fast: true - - name: Foundry project - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - with: - submodules: recursive - - - name: Install Foundry - uses: foundry-rs/foundry-toolchain@v1 - with: - version: nightly - - - name: Run Forge build - run: | - forge --version - forge build --sizes - id: build - - - name: Run Forge tests - run: | - forge test -vvv - id: test diff --git a/l1/.gitignore b/l1/.gitignore deleted file mode 100644 index 742c440..0000000 --- a/l1/.gitignore +++ /dev/null @@ -1,16 +0,0 @@ -# Compiler files -cache/ -out/ - -# Ignores development broadcast logs -!/broadcast -/broadcast/*/31337/ -/broadcast/**/dry-run/ - -broadcast/ - -# Docs -docs/ - -# Dotenv file -.env diff --git a/l1/README.md b/l1/README.md deleted file mode 100644 index 8a7f999..0000000 --- a/l1/README.md +++ /dev/null @@ -1,57 +0,0 @@ -## L1MessagesSender - -## Introduction - -This is a simple contract that can send L1 block hashes, Poseidon Merkle Mountain Range (MMR) and Keccak MMR tree root hashes alongside tree sizes read from one of our aggregators contract. - -The recipient is sitting on the other side on L2 (Starknet). We are using the native messaging system to communicate between those two layers. - -### Build - -```shell -$ forge build -``` - -### Deploy - -```shell -$ source .env; forge script script/L1MessagesSender.s.sol:L1MessagesSenderDeployer --rpc-url $DEPLOY_RPC_URL --broadcast --verify -vvvv -``` - -### Test - -```shell -$ forge test -``` - -### Format - -```shell -$ forge fmt -``` - -### Gas Snapshots - -```shell -$ forge snapshot -``` - -### Anvil - -```shell -$ anvil -``` - -### Cast - -```shell -$ cast -``` - -### Help - -```shell -$ forge --help -$ anvil --help -$ cast --help -``` diff --git a/l1/foundry.toml b/l1/foundry.toml deleted file mode 100644 index 03c33f8..0000000 --- a/l1/foundry.toml +++ /dev/null @@ -1,15 +0,0 @@ -[profile.default] -src = "src" -out = "out" -libs = ["lib"] -gas_reports = ["*"] -optimizer = true -optimizer_runs = 20000 - -[rpc_endpoints] -goerli = "https://rpc.ankr.com/eth_goerli" - -[etherscan] -goerli = { key = "${ETHERSCAN_API_KEY}" } - -# See more config options https://github.com/foundry-rs/foundry/tree/master/config diff --git a/l1/lib/forge-std b/l1/lib/forge-std deleted file mode 160000 index 74cfb77..0000000 --- a/l1/lib/forge-std +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 74cfb77e308dd188d2f58864aaf44963ae6b88b1 diff --git a/l1/lib/openzeppelin-contracts b/l1/lib/openzeppelin-contracts deleted file mode 160000 index fd81a96..0000000 --- a/l1/lib/openzeppelin-contracts +++ /dev/null @@ -1 +0,0 @@ -Subproject commit fd81a96f01cc42ef1c9a5399364968d0e07e9e90 diff --git a/l1/remappings.txt b/l1/remappings.txt deleted file mode 100644 index 3f2cd54..0000000 --- a/l1/remappings.txt +++ /dev/null @@ -1,5 +0,0 @@ -ds-test/=lib/forge-std/lib/ds-test/src/ -erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/ -forge-std/=lib/forge-std/src/ -openzeppelin-contracts/=lib/openzeppelin-contracts/ -openzeppelin/=lib/openzeppelin-contracts/contracts/ diff --git a/l1/script/L1MessagesSender.s.sol b/l1/script/L1MessagesSender.s.sol deleted file mode 100644 index 1a29ec4..0000000 --- a/l1/script/L1MessagesSender.s.sol +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import "forge-std/Script.sol"; -import "forge-std/console.sol"; - -import {L1MessagesSender} from "../src/L1MessagesSender.sol"; -import {IStarknetCore} from "../src/interfaces/IStarknetCore.sol"; - -contract L1MessagesSenderDeployer is Script { - function run() external { - uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); - vm.startBroadcast(deployerPrivateKey); - - L1MessagesSender l1MessagesSender = new L1MessagesSender( - IStarknetCore(vm.envAddress("STARKNET_CORE_ADDRESS")), - vm.envUint("L2_RECIPIENT_ADDRESS"), - vm.envAddress("AGGREGATORS_FACTORY_ADDRESS") - ); - - console.log("L1MessagesSender address: %s", address(l1MessagesSender)); - - vm.stopBroadcast(); - } -} diff --git a/l1/src/L1MessagesSender.sol b/l1/src/L1MessagesSender.sol deleted file mode 100644 index f1ddcb0..0000000 --- a/l1/src/L1MessagesSender.sol +++ /dev/null @@ -1,131 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 -pragma solidity 0.8.21; - -import {Ownable} from "openzeppelin/access/Ownable.sol"; - -import {IStarknetCore} from "./interfaces/IStarknetCore.sol"; - -import {IAggregatorsFactory} from "./interfaces/IAggregatorsFactory.sol"; -import {IAggregator} from "./interfaces/IAggregator.sol"; - -import {Uint256Splitter} from "./lib/Uint256Splitter.sol"; - -contract L1MessagesSender is Ownable { - using Uint256Splitter for uint256; - - IStarknetCore public immutable starknetCore; - - uint256 public l2RecipientAddr; - - IAggregatorsFactory public aggregatorsFactory; - - /// @dev L2 "receive_commitment" L1 handler selector - uint256 constant RECEIVE_COMMITMENT_L1_HANDLER_SELECTOR = - 0x3fa70707d0e831418fb142ca8fb7483611b84e89c0c42bf1fc2a7a5c40890ad; - - /// @dev L2 "receive_mmr" L1 handler selector - uint256 constant RECEIVE_MMR_L1_HANDLER_SELECTOR = - 0x36c76e67f1d589956059cbd9e734d42182d1f8a57d5876390bb0fcfe1090bb4; - - /// @param starknetCore_ a StarknetCore address to send and consume messages on/from L2 - /// @param l2RecipientAddr_ a L2 recipient address that is the recipient contract on L2. - /// @param aggregatorsFactoryAddr_ Herodotus aggregators factory address (where MMR trees are referenced) - constructor( - IStarknetCore starknetCore_, - uint256 l2RecipientAddr_, - address aggregatorsFactoryAddr_ - ) { - starknetCore = starknetCore_; - l2RecipientAddr = l2RecipientAddr_; - aggregatorsFactory = IAggregatorsFactory(aggregatorsFactoryAddr_); - } - - /// @notice Send an exact L1 parent hash to L2 - /// @param blockNumber_ the child block of the requested parent hash - function sendExactParentHashToL2(uint256 blockNumber_) external payable { - bytes32 parentHash = blockhash(blockNumber_ - 1); - require(parentHash != bytes32(0), "ERR_INVALID_BLOCK_NUMBER"); - - _sendBlockHashToL2(parentHash, blockNumber_); - } - - /// @notice Send the L1 latest parent hash to L2 - function sendLatestParentHashToL2() external payable { - bytes32 parentHash = blockhash(block.number - 1); - _sendBlockHashToL2(parentHash, block.number); - } - - /// @param aggregatorId The id of a tree previously created by the aggregators factory - function sendPoseidonMMRTreeToL2(uint256 aggregatorId) external payable { - address existingAggregatorAddr = aggregatorsFactory.aggregatorsById( - aggregatorId - ); - - require(existingAggregatorAddr != address(0), "Unknown aggregator"); - - IAggregator aggregator = IAggregator(existingAggregatorAddr); - bytes32 poseidonMMRRoot = aggregator.getMMRPoseidonRoot(); - uint256 mmrSize = aggregator.getMMRSize(); - - require(mmrSize >= 1, "Invalid tree size"); - require(poseidonMMRRoot != bytes32(0), "Invalid root (Poseidon)"); - - _sendPoseidonMMRTreeToL2(poseidonMMRRoot, mmrSize, aggregatorId); - } - - function _sendBlockHashToL2( - bytes32 parentHash_, - uint256 blockNumber_ - ) internal { - uint256[] memory message = new uint256[](4); - (uint256 parentHashLow, uint256 parentHashHigh) = uint256(parentHash_) - .split128(); - (uint256 blockNumberLow, uint256 blockNumberHigh) = blockNumber_ - .split128(); - message[0] = parentHashLow; - message[1] = parentHashHigh; - message[2] = blockNumberLow; - message[3] = blockNumberHigh; - - starknetCore.sendMessageToL2{value: msg.value}( - l2RecipientAddr, - RECEIVE_COMMITMENT_L1_HANDLER_SELECTOR, - message - ); - } - - function _sendPoseidonMMRTreeToL2( - bytes32 poseidonMMRRoot, - uint256 mmrSize, - uint256 aggregatorId - ) internal { - uint256[] memory message = new uint256[](3); - - message[0] = uint256(poseidonMMRRoot); - message[1] = mmrSize; - message[2] = aggregatorId; - - // Pass along msg.value - starknetCore.sendMessageToL2{value: msg.value}( - l2RecipientAddr, - RECEIVE_MMR_L1_HANDLER_SELECTOR, - message - ); - } - - /// @notice Set the L2 recipient address - /// @param newL2RecipientAddr_ The new L2 recipient address - function setL2RecipientAddr( - uint256 newL2RecipientAddr_ - ) external onlyOwner { - l2RecipientAddr = newL2RecipientAddr_; - } - - /// @notice Set the aggregators factory address - /// @param newAggregatorsFactoryAddr_ The new aggregators factory address - function setAggregatorsFactoryAddr( - address newAggregatorsFactoryAddr_ - ) external onlyOwner { - aggregatorsFactory = IAggregatorsFactory(newAggregatorsFactoryAddr_); - } -} diff --git a/l1/src/interfaces/IAggregator.sol b/l1/src/interfaces/IAggregator.sol deleted file mode 100644 index 48243c3..0000000 --- a/l1/src/interfaces/IAggregator.sol +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 -pragma solidity 0.8.21; - -interface IAggregator { - /// @notice Returns the current root hash of the Keccak Merkle Mountain Range (MMR) tree - function getMMRKeccakRoot() external view returns (bytes32); - - /// @notice Returns the current root hash of the Poseidon Merkle Mountain Range (MMR) tree - function getMMRPoseidonRoot() external view returns (bytes32); - - /// @notice Returns the current size of the Merkle Mountain Range (MMR) trees - function getMMRSize() external view returns (uint256); -} diff --git a/l1/src/interfaces/IAggregatorsFactory.sol b/l1/src/interfaces/IAggregatorsFactory.sol deleted file mode 100644 index d474a2c..0000000 --- a/l1/src/interfaces/IAggregatorsFactory.sol +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 -pragma solidity 0.8.21; - -import {IAggregator} from "./IAggregator.sol"; - -interface IAggregatorsFactory { - function aggregatorsById(uint256 id) external view returns (address); -} diff --git a/l1/src/interfaces/IStarknetCore.sol b/l1/src/interfaces/IStarknetCore.sol deleted file mode 100644 index a3f6240..0000000 --- a/l1/src/interfaces/IStarknetCore.sol +++ /dev/null @@ -1,55 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 -pragma solidity 0.8.21; - -interface IStarknetCore { - /** - Sends a message to an L2 contract. - This function is payable, the payed amount is the message fee. - - Returns the hash of the message and the nonce of the message. - */ - function sendMessageToL2( - uint256 toAddress, - uint256 selector, - uint256[] calldata payload - ) external payable returns (bytes32, uint256); - - /** - Consumes a message that was sent from an L2 contract. - - Returns the hash of the message. - */ - function consumeMessageFromL2( - uint256 fromAddress, - uint256[] calldata payload - ) external returns (bytes32); - - /** - Starts the cancellation of an L1 to L2 message. - A message can be canceled messageCancellationDelay() seconds after this function is called. - - Note: This function may only be called for a message that is currently pending and the caller - must be the sender of the that message. - */ - function startL1ToL2MessageCancellation( - uint256 toAddress, - uint256 selector, - uint256[] calldata payload, - uint256 nonce - ) external returns (bytes32); - - /** - Cancels an L1 to L2 message, this function should be called at least - messageCancellationDelay() seconds after the call to startL1ToL2MessageCancellation(). - A message may only be cancelled by its sender. - If the message is missing, the call will revert. - - Note that the message fee is not refunded. - */ - function cancelL1ToL2Message( - uint256 toAddress, - uint256 selector, - uint256[] calldata payload, - uint256 nonce - ) external returns (bytes32); -} diff --git a/l1/src/lib/Uint256Splitter.sol b/l1/src/lib/Uint256Splitter.sol deleted file mode 100644 index c68730f..0000000 --- a/l1/src/lib/Uint256Splitter.sol +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.8.0; - -library Uint256Splitter { - uint256 constant _MASK = type(uint128).max; - - /// @notice Splits a uint256 into two uint128s (low, high) represented as uint256s. - /// @param a The uint256 to split. - function split128( - uint256 a - ) internal pure returns (uint256 lower, uint256 upper) { - return (a & _MASK, a >> 128); - } - - /// @notice Merges two uint128s (low, high) into one uint256. - /// @param lower The lower uint256. The caller is required to pass a value that is less than 2^128 - 1. - /// @param upper The upper uint256. - function merge128( - uint256 lower, - uint256 upper - ) internal pure returns (uint256 a) { - require(lower <= _MASK, "Uint256Splitter: lower exceeds uint128"); - // return (upper << 128) | lower; - assembly { - a := or(shl(128, upper), lower) - } - } -} diff --git a/l1/test/L1MessagesSender.t.sol b/l1/test/L1MessagesSender.t.sol deleted file mode 100644 index 3f51e87..0000000 --- a/l1/test/L1MessagesSender.t.sol +++ /dev/null @@ -1,41 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import "forge-std/Test.sol"; - -import {L1MessagesSender} from "../src/L1MessagesSender.sol"; -import {IStarknetCore} from "../src/interfaces/IStarknetCore.sol"; - -contract L1MessagesSenderTest is Test { - L1MessagesSender public sender; - - function setUp() public { - vm.createSelectFork(vm.rpcUrl("goerli")); - - sender = new L1MessagesSender( - IStarknetCore(0xde29d060D45901Fb19ED6C6e959EB22d8626708e), - 0x07bf6b32382276bFF5341f810A6811233A9591228642F60160129629448a21b6, - 0xB8Cb7707b5160eaE8931e0cf02B563a5CeA75F09 - ); - } - - function testSendExactParentHashToL2() public { - uint256 prevBlock = block.number - 1; - - // Value must be greater than 0 - sender.sendExactParentHashToL2{value: 1}(prevBlock); - } - - function testSendLatestParentHashToL2() public { - // Value must be greater than 0 - sender.sendLatestParentHashToL2{value: 1}(); - } - - function testSendPoseidonMMRTreeToL2() public { - // This aggregator id must exist in the factory - uint256 aggregatorId = 1; - - // Value must be greater than 0 - sender.sendPoseidonMMRTreeToL2{value: 1}(aggregatorId); - } -} diff --git a/multicall/deploy.toml b/multicall/deploy.toml index a0e6694..281bc17 100644 --- a/multicall/deploy.toml +++ b/multicall/deploy.toml @@ -1,25 +1,25 @@ [[call]] call_type = "deploy" -class_hash = "0x4618004201b8cc18d82ef9e909e8d653cd5ef0327f4b9f40d014eaeb93d8598" +class_hash = "0x18c9ce7ffa88f15bd1fcda1350cb66cc5c369bc924e5dc108be1c9317298c99" inputs = [ "0x1", - "0x18E4A8e2badB5f5950758F46f8108E2C5d357b07", + "0x30DdE9bC96D800fF70383c74a95141E156Db1DF0", "0x0", - "0x007327d012f432a9f940228ae6b01032ea4742d8bba8599d7a34e1d5c120e983", + "0x5a6c0f84179d695f0b598cc5d0be50421c247da95cfe63e4cd66fc27f32dfe6", ] id = "commitments_inbox" unique = false [[call]] call_type = "deploy" -class_hash = "0x229789b99574e92c01f99e9d6f16a3cc6326085733504a8b7ca367606b40ab4" +class_hash = "0x48d1f93626722872832416241a30c20bb77403b48249e65bebae67ab7a5329" inputs = ["commitments_inbox"] id = "headers_store" unique = false [[call]] call_type = "deploy" -class_hash = "0x4438ef289f10b9c687089b5ad218d8465a9266af74834356dbd78a35cfaf618" +class_hash = "0x5bae175b97cd8d6ce988ee862be7081397d36a8044a4d7e9b1bd810532daba9" inputs = ["headers_store"] id = "evm_facts_registry" unique = false @@ -29,3 +29,10 @@ call_type = "invoke" contract_address = "commitments_inbox" function = "set_headers_store" inputs = ["headers_store"] + +[[call]] +call_type = "deploy" +class_hash = "0x2e7de0c6a2fd1759b02cc59998f6267e5d1b73852d1ec556594269c9bc48b63" +inputs = ["headers_store"] +id = "timestamp_remappers" +unique = false \ No newline at end of file diff --git a/multicall/set-l1-msg-sender.toml b/multicall/set-l1-msg-sender.toml new file mode 100644 index 0000000..1bdc4f1 --- /dev/null +++ b/multicall/set-l1-msg-sender.toml @@ -0,0 +1,5 @@ +[[call]] +call_type = "invoke" +contract_address = "0x0763c1a0ec1d64afe2d8d0a2c0cab6fd494dcb26d08ef1020b27aa5695761e21" +function = "set_l1_message_sender" +inputs = ["0x423F7744017600727cE4789933E4648068835E28"] \ No newline at end of file diff --git a/snfoundry.toml b/snfoundry.toml new file mode 100644 index 0000000..2d23787 --- /dev/null +++ b/snfoundry.toml @@ -0,0 +1,4 @@ +# Default profile +[sncast.default] +account = "herodotus" +url = "https://free-rpc.nethermind.io/sepolia-juno" diff --git a/src/core.cairo b/src/core.cairo index a2a21a1..db2f374 100644 --- a/src/core.cairo +++ b/src/core.cairo @@ -1,3 +1,4 @@ +mod common; mod commitments_inbox; mod headers_store; mod evm_facts_registry; diff --git a/src/core/commitments_inbox.cairo b/src/core/commitments_inbox.cairo index 1c9ecf9..fa7327a 100644 --- a/src/core/commitments_inbox.cairo +++ b/src/core/commitments_inbox.cairo @@ -33,19 +33,25 @@ trait ICommitmentsInbox { // @dev This function is only callable by the owner fn renounce_ownership(ref self: TContractState); - // @notice receives a parent blockhash and the corresponding block number, simulating L1 messaging, and sends it to the HeadersStore - // @dev This function is only callable by the owner, ownership will be renounced in mainnet + // @notice receives a parent blockhash and the corresponding block number, simulating L1 + // messaging, and sends it to the HeadersStore + // @dev This function is only callable by the owner, + // ownership will be renounced in mainnet fn receive_commitment_owner(ref self: TContractState, parent_hash: u256, block_number: u256); } -// @notice The contract that receives the commitments from L1, both individual blocks and proven MMRs, sending them to the HeadersStore -// @dev The contract ownership will be renounced in mainnet, it is only used for testing purposes +// @notice The contract that receives the commitments from L1, both individual blocks and proven +// MMRs, sending them to the HeadersStore +// @dev The contract ownership will be renounced in mainnet, +// it is only used for testing purposes #[starknet::contract] mod CommitmentsInbox { use starknet::{ContractAddress, get_caller_address, EthAddress}; use herodotus_eth_starknet::core::headers_store::{ IHeadersStoreDispatcherTrait, IHeadersStoreDispatcher }; + use herodotus_eth_starknet::core::common::{MmrId, AggregatorId}; + use cairo_lib::data_structures::mmr::mmr::{MmrSize, MmrElement}; #[storage] struct Storage { @@ -82,9 +88,9 @@ mod CommitmentsInbox { #[derive(Drop, starknet::Event)] struct MMRReceived { - root: felt252, - last_pos: usize, - aggregator_id: usize + root: MmrElement, + last_pos: MmrSize, + aggregator_id: AggregatorId } #[constructor] @@ -103,7 +109,7 @@ mod CommitmentsInbox { }; } - #[external(v0)] + #[abi(embed_v0)] impl CommitmentsInbox of super::ICommitmentsInbox { // @inheritdoc ICommitmentsInbox fn get_headers_store(self: @ContractState) -> ContractAddress { @@ -123,21 +129,21 @@ mod CommitmentsInbox { // @inheritdoc ICommitmentsInbox fn set_headers_store(ref self: ContractState, headers_store: ContractAddress) { let caller = get_caller_address(); - assert(self.owner.read() == caller, 'Only owner'); + assert(self.owner.read() == caller, 'ONLY_OWNER'); self.headers_store.write(headers_store); } // @inheritdoc ICommitmentsInbox fn set_l1_message_sender(ref self: ContractState, l1_message_sender: EthAddress) { let caller = get_caller_address(); - assert(self.owner.read() == caller, 'Only owner'); + assert(self.owner.read() == caller, 'ONLY_OWNER'); self.l1_message_sender.write(l1_message_sender); } // @inheritdoc ICommitmentsInbox fn transfer_ownership(ref self: ContractState, new_owner: ContractAddress) { let caller = get_caller_address(); - assert(self.owner.read() == caller, 'Only owner'); + assert(self.owner.read() == caller, 'ONLY_OWNER'); self.owner.write(new_owner); if new_owner.is_zero() { @@ -156,7 +162,7 @@ mod CommitmentsInbox { // @inheritdoc ICommitmentsInbox fn renounce_ownership(ref self: ContractState) { let caller = get_caller_address(); - assert(self.owner.read() == caller, 'Only owner'); + assert(self.owner.read() == caller, 'ONLY_OWNER'); self.owner.write(Zeroable::zero()); self.emit(Event::OwnershipRenounced(OwnershipRenounced { previous_owner: caller })); @@ -167,7 +173,7 @@ mod CommitmentsInbox { ref self: ContractState, parent_hash: u256, block_number: u256 ) { let caller = get_caller_address(); - assert(self.owner.read() == caller, 'Only owner'); + assert(self.owner.read() == caller, 'ONLY_OWNER'); let contract_address = self.headers_store.read(); IHeadersStoreDispatcher { contract_address }.receive_hash(parent_hash, block_number); @@ -176,15 +182,17 @@ mod CommitmentsInbox { } } - // @notice receives a parent blockhash and the corresponding block number from L1, and sends it to the HeadersStore - // @param from_address The address of the sender, checking that it is the L1 contract + // @notice receives a parent blockhash and the corresponding block number from L1, and sends it + // to the HeadersStore + // @param from_address The address of the sender, checking that it is the L1 + // contract // @param blockhash The parent blockhash of the block // @param block_number The block number of the block #[l1_handler] fn receive_commitment( ref self: ContractState, from_address: felt252, parent_hash: u256, block_number: u256 ) { - assert(from_address == self.l1_message_sender.read().into(), 'Invalid sender'); + assert(from_address == self.l1_message_sender.read().into(), 'ONLY_L1_MESSAGE_SENDER'); let contract_address = self.headers_store.read(); IHeadersStoreDispatcher { contract_address }.receive_hash(parent_hash, block_number); @@ -198,19 +206,21 @@ mod CommitmentsInbox { // @param root The root of the MMR // @param last_pos The last position of the MMR // @param aggregator_id The aggregator id of the proven MMR + // @param mmr_id The id of the MMR #[l1_handler] fn receive_mmr( ref self: ContractState, from_address: felt252, - root: felt252, - last_pos: usize, - aggregator_id: usize + root: MmrElement, + last_pos: MmrSize, + aggregator_id: AggregatorId, + mmr_id: MmrId ) { - assert(from_address == self.l1_message_sender.read().into(), 'Invalid sender'); + assert(from_address == self.l1_message_sender.read().into(), 'ONLY_L1_MESSAGE_SENDER'); let contract_address = self.headers_store.read(); IHeadersStoreDispatcher { contract_address } - .create_branch_from_message(root, last_pos, aggregator_id); + .create_branch_from_message(root, last_pos, aggregator_id, mmr_id); self.emit(Event::MMRReceived(MMRReceived { root, last_pos, aggregator_id })); } diff --git a/src/core/common.cairo b/src/core/common.cairo new file mode 100644 index 0000000..da94a28 --- /dev/null +++ b/src/core/common.cairo @@ -0,0 +1,2 @@ +type MmrId = u256; +type AggregatorId = u256; diff --git a/src/core/evm_facts_registry.cairo b/src/core/evm_facts_registry.cairo index dac33fa..7f91eda 100644 --- a/src/core/evm_facts_registry.cairo +++ b/src/core/evm_facts_registry.cairo @@ -1,7 +1,9 @@ use starknet::ContractAddress; +use cairo_lib::data_structures::mmr::mmr::MmrSize; use cairo_lib::data_structures::mmr::proof::Proof; use cairo_lib::data_structures::mmr::peaks::Peaks; use cairo_lib::utils::types::words64::Words64; +use herodotus_eth_starknet::core::common::MmrId; #[derive(Drop, Serde)] enum AccountField { @@ -52,11 +54,11 @@ trait IEVMFactsRegistry { block_header_rlp: Words64, account: felt252, mpt_proof: Span, - mmr_index: usize, + mmr_index: MmrSize, mmr_peaks: Peaks, mmr_proof: Proof, - mmr_id: usize, - last_pos: usize, + mmr_id: MmrId, + last_pos: MmrSize, ) -> Span; // @notice Gets a storage slot from a proven account @@ -85,11 +87,11 @@ trait IEVMFactsRegistry { block_header_rlp: Words64, account: felt252, mpt_proof: Span, - mmr_index: usize, + mmr_index: MmrSize, mmr_peaks: Peaks, mmr_proof: Proof, - mmr_id: usize, - last_pos: usize, + mmr_id: MmrId, + last_pos: MmrSize, ); // @notice Proves a storage slot at a given block @@ -108,11 +110,13 @@ trait IEVMFactsRegistry { ); } -// @notice Contract that stores all the proven facts, entrypoint for applications using with Herodotus +// @notice Contract that stores all the proven facts, entrypoint for applications using with +// Herodotus #[starknet::contract] mod EVMFactsRegistry { use starknet::ContractAddress; use super::AccountField; + use cairo_lib::data_structures::mmr::mmr::MmrSize; use cairo_lib::data_structures::mmr::proof::Proof; use cairo_lib::data_structures::mmr::peaks::Peaks; use cairo_lib::hashing::poseidon::hash_words64; @@ -121,6 +125,7 @@ mod EVMFactsRegistry { use cairo_lib::utils::types::words64::{ Words64, Words64Trait, reverse_endianness_u64, bytes_used_u64 }; + use herodotus_eth_starknet::core::common::MmrId; use herodotus_eth_starknet::core::headers_store::{ IHeadersStoreDispatcherTrait, IHeadersStoreDispatcher }; @@ -167,7 +172,7 @@ mod EVMFactsRegistry { self.headers_store.write(headers_store); } - #[external(v0)] + #[abi(embed_v0)] impl EVMFactsRegistry of super::IEVMFactsRegistry { // @inheritdoc IEVMFactsRegistry fn get_headers_store(self: @ContractState) -> ContractAddress { @@ -200,11 +205,11 @@ mod EVMFactsRegistry { block_header_rlp: Words64, account: felt252, mpt_proof: Span, - mmr_index: usize, + mmr_index: MmrSize, mmr_peaks: Peaks, mmr_proof: Proof, - mmr_id: usize, - last_pos: usize, + mmr_id: MmrId, + last_pos: MmrSize, ) -> Span { let (_, fields) = InternalFunctions::get_account( self, @@ -231,7 +236,7 @@ mod EVMFactsRegistry { mpt_proof: Span ) -> u256 { let storage_hash = reverse_endianness_u256( - self.storage_hash.read((account, block)).expect('Storage hash not proven') + self.storage_hash.read((account, block)).expect('STORAGE_HASH_NOT_PROVEN') ); // Split the slot into 4 64 bit words @@ -254,19 +259,19 @@ mod EVMFactsRegistry { let key = reverse_endianness_u256(keccak_cairo_words64(words, 8)); let mpt = MPTTrait::new(storage_hash); - let rlp_value = mpt.verify(key, 64, mpt_proof).expect('MPT verification failed'); + let rlp_value = mpt.verify(key, 64, mpt_proof).expect('MPT_VERIFICATION_FAILED'); if rlp_value.is_empty() { return 0; } - let (item, _) = rlp_decode(rlp_value).expect('Invalid RLP value'); + let (item, _) = rlp_decode(rlp_value).expect('INVALID_RLP_VALUE'); match item { RLPItem::Bytes((value, value_len)) => value .as_u256_be(value_len) - .expect('Invalid value'), - RLPItem::List(_) => panic_with_felt252('Invalid header rlp') + .expect('INVALID_RLP_VALUE'), + RLPItem::List(_) => panic_with_felt252('INVALID_HEADER_RLP') } } @@ -277,11 +282,11 @@ mod EVMFactsRegistry { block_header_rlp: Words64, account: felt252, mpt_proof: Span, - mmr_index: usize, + mmr_index: MmrSize, mmr_peaks: Peaks, mmr_proof: Proof, - mmr_id: usize, - last_pos: usize, + mmr_id: MmrId, + last_pos: MmrSize, ) { let (block, field_values) = InternalFunctions::get_account( @self, @@ -309,15 +314,9 @@ mod EVMFactsRegistry { AccountField::StorageHash(_) => { self.storage_hash.write((account, block), value); }, - AccountField::CodeHash(_) => { - self.code_hash.write((account, block), value); - }, - AccountField::Balance(_) => { - self.balance.write((account, block), value); - }, - AccountField::Nonce(_) => { - self.nonce.write((account, block), value); - } + AccountField::CodeHash(_) => { self.code_hash.write((account, block), value); }, + AccountField::Balance(_) => { self.balance.write((account, block), value); }, + AccountField::Nonce(_) => { self.nonce.write((account, block), value); } }; i += 1; @@ -350,11 +349,11 @@ mod EVMFactsRegistry { block_header_rlp: Words64, account: felt252, mpt_proof: Span, - mmr_index: usize, + mmr_index: MmrSize, mmr_peaks: Peaks, mmr_proof: Proof, - mmr_id: usize, - last_pos: usize, + mmr_id: MmrId, + last_pos: MmrSize, ) -> (u256, Span) { let blockhash = hash_words64(block_header_rlp); @@ -363,20 +362,20 @@ mod EVMFactsRegistry { .verify_historical_mmr_inclusion( mmr_index, blockhash, mmr_peaks, mmr_proof, mmr_id, last_pos ); - assert(mmr_inclusion, 'MMR inclusion not proven'); + assert(mmr_inclusion, 'INVALID_MMR_PROOF'); let (decoded_rlp, _) = rlp_decode_list_lazy(block_header_rlp, array![3, 8].span()) - .expect('Invalid header rlp'); + .expect('INVALID_HEADER_RLP'); let mut state_root: u256 = 0; let mut block_number: u256 = 0; match decoded_rlp { - RLPItem::Bytes(_) => panic_with_felt252('Invalid header rlp'), + RLPItem::Bytes(_) => panic_with_felt252('INVALID_HEADER_RLP'), RLPItem::List(l) => { let (state_root_words, _) = *l.at(0); - state_root = state_root_words.as_u256_le(32).unwrap(); + state_root = state_root_words.as_u256_le().unwrap(); let (block_number_words, block_number_byte_len) = *l.at(1); - assert(block_number_words.len() == 1, 'Invalid block number'); + assert(block_number_words.len() == 1, 'INVALID_BLOCK_NUMBER'); let block_number_le = *block_number_words.at(0); block_number = @@ -406,7 +405,7 @@ mod EVMFactsRegistry { .span(); let key = reverse_endianness_u256(keccak_cairo_words64(words, 4)); - let rlp_account = mpt.verify(key, 64, mpt_proof).expect('MPT verification failed'); + let rlp_account = mpt.verify(key, 64, mpt_proof).expect('MPT_VERIFICATION_FAILED'); let mut account_fields = ArrayTrait::new(); if rlp_account.is_empty() { @@ -421,9 +420,9 @@ mod EVMFactsRegistry { i += 1; }; } else { - let (decoded_account, _) = rlp_decode(rlp_account).expect('Invalid account rlp'); + let (decoded_account, _) = rlp_decode(rlp_account).expect('INVALID_ACCOUNT_RLP'); match decoded_account { - RLPItem::Bytes(_) => panic_with_felt252('Invalid account rlp'), + RLPItem::Bytes(_) => panic_with_felt252('INVALID_ACCOUNT_RLP'), RLPItem::List(l) => { let mut i: usize = 0; loop { @@ -433,18 +432,10 @@ mod EVMFactsRegistry { let field = fields.at(i); let (field_value, field_value_len) = match field { - AccountField::StorageHash(_) => { - *l.at(2) - }, - AccountField::CodeHash(_) => { - *l.at(3) - }, - AccountField::Balance(_) => { - *l.at(1) - }, - AccountField::Nonce(_) => { - *l.at(0) - }, + AccountField::StorageHash(_) => { *l.at(2) }, + AccountField::CodeHash(_) => { *l.at(3) }, + AccountField::Balance(_) => { *l.at(1) }, + AccountField::Nonce(_) => { *l.at(0) }, }; account_fields.append(field_value.as_u256_be(field_value_len).unwrap()); diff --git a/src/core/headers_store.cairo b/src/core/headers_store.cairo index 153b516..bb6b8d7 100644 --- a/src/core/headers_store.cairo +++ b/src/core/headers_store.cairo @@ -2,7 +2,8 @@ use starknet::ContractAddress; use cairo_lib::data_structures::mmr::peaks::Peaks; use cairo_lib::data_structures::mmr::proof::Proof; use cairo_lib::utils::types::words64::Words64; -use cairo_lib::data_structures::mmr::mmr::MMR; +use cairo_lib::data_structures::mmr::mmr::{MMR, MmrSize, MmrElement}; +use herodotus_eth_starknet::core::common::{MmrId, AggregatorId}; #[starknet::interface] trait IHeadersStore { @@ -13,31 +14,29 @@ trait IHeadersStore { // @notice Returns the MMR with a given id // @param mmr_id The id of the MMR // @return The MMR with the given id - fn get_mmr(self: @TContractState, mmr_id: usize) -> MMR; + fn get_mmr(self: @TContractState, mmr_id: MmrId) -> MMR; // @notice Returns the root of the MMR with a given id // @param mmr_id The id of the MMR // @return The root of the MMR with the given id - fn get_mmr_root(self: @TContractState, mmr_id: usize) -> felt252; + fn get_mmr_root(self: @TContractState, mmr_id: MmrId) -> MmrElement; // @notice Returns the size of the MMR with a given id // @param mmr_id The id of the MMR // @return The size of the MMR with the given id - fn get_mmr_size(self: @TContractState, mmr_id: usize) -> usize; + fn get_mmr_size(self: @TContractState, mmr_id: MmrId) -> MmrSize; - // @notice Returns the parent blockhash of a given block number, received from L1 and send throught the CommitmentsInbox + // @notice Returns the parent blockhash of a given block number, received from L1 and send + // throught the CommitmentsInbox fn get_received_block(self: @TContractState, block_number: u256) -> u256; - // @notice Returns the latest MMR id - // @dev MMR IDs are incremental - fn get_latest_mmr_id(self: @TContractState) -> usize; - // @notice Returns the root of the MMR with a given id and size - // @dev The reason why we need to get historical roots is because we don't want MMR proofs to expire + // @dev The reason why we need to get historical roots is because we don't want MMR proofs to + // expire // @param mmr_id The id of the MMR // @param size The size of the MMR // @return The root of the MMR with the given id and size - fn get_historical_root(self: @TContractState, mmr_id: usize, size: usize) -> felt252; + fn get_historical_root(self: @TContractState, mmr_id: MmrId, size: MmrSize) -> MmrElement; // @notice Receives a parent blockhash and the corresponding block number from L1 and saves it // @dev This function can only be called by the CommitmentsInbox contract @@ -53,11 +52,11 @@ trait IHeadersStore { // @return True if the proof is valid and the element is present, false otherwise fn verify_mmr_inclusion( self: @TContractState, - index: usize, - poseidon_blockhash: felt252, + index: MmrSize, + poseidon_blockhash: MmrElement, peaks: Peaks, proof: Proof, - mmr_id: usize, + mmr_id: MmrId, ) -> bool; // @notice Verifies an inclusion proof in an MMR @@ -71,30 +70,33 @@ trait IHeadersStore { // @return True if the proof is valid and the element is present, false otherwise fn verify_historical_mmr_inclusion( self: @TContractState, - index: usize, - poseidon_blockhash: felt252, + index: MmrSize, + poseidon_blockhash: MmrElement, peaks: Peaks, proof: Proof, - mmr_id: usize, - last_pos: usize, + mmr_id: MmrId, + last_pos: MmrSize, ) -> bool; - // @notice Appends a batch of block hashes to the MMR starting from a specific block, either from a hash received from L1 or from an MMR element - // @param headers_rlp The RLP-encoded headers - // @param mmr_peaks The peaks of the MMR + // @notice Appends a batch of block hashes to the MMR starting from a specific block, either + // from a hash received from L1 or from an MMR element @param headers_rlp The RLP-encoded + // headers @param mmr_peaks The peaks of the MMR // @param mmr_id The id of the MMR - // @param reference_block A block whose hash was receiven from L1 (if starting from MMR element, None) + // @param reference_block A block whose hash was receiven from L1 (if starting from MMR element, + // None) // @param mmr_index The index of the starting blockhash in the MMR (if starting from L1, None) - // @param mmr_proof The MMR inclusion porrof of the starting blockhash (if starting from L1, None) - // @dev If the starting blockhash was received from L1, then reference_block must be provided, and mmr_index and mmr_proof must be None - // @dev If the starting blockhash is present in the MMR, then mmr_index and mmr_proof must be provided, and reference_block must be None + // @param mmr_proof The MMR inclusion porrof of the starting blockhash (if starting from L1, + // None) + // @dev If the starting blockhash was received from L1, then reference_block must be provided, + // and mmr_index and mmr_proof must be None @dev If the starting blockhash is present in the + // MMR, then mmr_index and mmr_proof must be provided, and reference_block must be None fn process_batch( ref self: TContractState, headers_rlp: Span, mmr_peaks: Peaks, - mmr_id: usize, + mmr_id: MmrId, reference_block: Option, - mmr_index: Option, + mmr_index: Option, mmr_proof: Option, ); @@ -102,9 +104,14 @@ trait IHeadersStore { // @param root The root of the MMR // @param last_pos The size of the MMR // @param aggregator_id The id of the L1 aggregator + // @param mmr_id The id of the new MMR // @dev This function can only be called by the CommitmentsInbox contract fn create_branch_from_message( - ref self: TContractState, root: felt252, last_pos: usize, aggregator_id: usize + ref self: TContractState, + root: MmrElement, + last_pos: MmrSize, + aggregator_id: AggregatorId, + new_mmr_id: MmrId ); @@ -117,28 +124,36 @@ trait IHeadersStore { // @param last_pos last_pos of the given MMR fn create_branch_single_element( ref self: TContractState, - index: usize, - initial_poseidon_blockhash: felt252, + index: MmrSize, + initial_poseidon_blockhash: MmrElement, peaks: Peaks, proof: Proof, - mmr_id: usize, - last_pos: usize + mmr_id: MmrId, + last_pos: MmrSize, + new_mmr_id: MmrId ); // @notice Creates a new MMR that is a clone of an already existing MMR + // or an empty MMR if mmr_id is 0 (in that case last_pos is ignored) // @param mmr_id The id of the MMR to clone // @param last_pos last_pos of the given MMR - fn create_branch_from(ref self: TContractState, mmr_id: usize, last_pos: usize); + // @param new_mmr_id The id of the new MMR + // @dev Notice that to prevent overwriting existing MMRs, a check if the MMR with the new id + // already exists is performed. Because of that MMR root = 0 is reserved for non-existing MMRs, + // so 0 cannot be used as a valid mmr root anywhere. + fn create_branch_from( + ref self: TContractState, mmr_id: MmrId, last_pos: MmrSize, new_mmr_id: MmrId + ); } // @notice Contract responsible for storing all the block hashes // @dev The contract keeps track of multiple MMRs (refered to as branches), each with a different id -// @dev The contract also keeps track of historical roots and corresponding sizes of every MMR, +// @dev The contract also keeps track of historical roots and corresponding sizes of every MMR, #[starknet::contract] mod HeadersStore { use starknet::{ContractAddress, get_caller_address}; - use cairo_lib::data_structures::mmr::mmr::{MMR, MMRTrait}; + use cairo_lib::data_structures::mmr::mmr::{MMR, MMRTrait, MmrSize, MmrElement}; use cairo_lib::data_structures::mmr::peaks::Peaks; use cairo_lib::data_structures::mmr::proof::Proof; use cairo_lib::utils::types::words64::{ @@ -148,19 +163,20 @@ mod HeadersStore { use cairo_lib::hashing::poseidon::hash_words64; use cairo_lib::utils::bitwise::reverse_endianness_u256; use cairo_lib::encoding::rlp::{RLPItem, rlp_decode_list_lazy}; + use herodotus_eth_starknet::core::common::{MmrId, AggregatorId}; - const MMR_INITIAL_ROOT: felt252 = + const MMR_INITIAL_ROOT: MmrElement = 0x6759138078831011e3bc0b4a135af21c008dda64586363531697207fb5a2bae; #[storage] struct Storage { commitments_inbox: ContractAddress, - mmr: LegacyMap::, - // (id, size) => root - mmr_history: LegacyMap::<(usize, usize), felt252>, + // MMR root = 0 means that MMR doesn't exist + mmr: LegacyMap::, + // MMR root = 0 means that MMR doesn't exist + mmr_history: LegacyMap::<(MmrId, MmrSize), MmrElement>, // block_number => parent blockhash received_blocks: LegacyMap::, - latest_mmr_id: usize } #[event] @@ -183,59 +199,52 @@ mod HeadersStore { #[derive(Drop, starknet::Event)] struct ProcessedBlock { block_number: u256, - new_root: felt252, - new_size: usize, - mmr_id: usize + new_root: MmrElement, + new_size: MmrSize, + mmr_id: MmrId } #[derive(Drop, starknet::Event)] struct ProcessedBatch { block_start: u256, block_end: u256, - new_root: felt252, - new_size: usize, - mmr_id: usize + new_root: MmrElement, + new_size: MmrSize, + mmr_id: MmrId } #[derive(Drop, starknet::Event)] struct BranchCreatedFromElement { - mmr_id: usize, - root: felt252, - last_pos: usize, - detached_from_mmr_id: usize, - mmr_index: usize + mmr_id: MmrId, + root: MmrElement, + last_pos: MmrSize, + detached_from_mmr_id: MmrId, + mmr_index: MmrSize } #[derive(Drop, starknet::Event)] struct BranchCreatedFromL1 { - mmr_id: usize, - root: felt252, - last_pos: usize, - aggregator_id: usize + mmr_id: MmrId, + root: MmrElement, + last_pos: MmrSize, + aggregator_id: AggregatorId } #[derive(Drop, starknet::Event)] struct BranchCreatedClone { - mmr_id: usize, - root: felt252, - last_pos: usize, - detached_from_mmr_id: usize + mmr_id: MmrId, + root: MmrElement, + last_pos: MmrSize, + detached_from_mmr_id: MmrId } #[constructor] fn constructor(ref self: ContractState, commitments_inbox: ContractAddress) { self.commitments_inbox.write(commitments_inbox); - - let mmr: MMR = MMRTrait::new(MMR_INITIAL_ROOT, 1); - let root = mmr.root; - - self.mmr.write(0, mmr); - self.mmr_history.write((0, 1), root); - self.latest_mmr_id.write(0); } - #[external(v0)] + #[abi(embed_v0)] impl HeadersStore of super::IHeadersStore { // @inheritdoc IHeadersStore fn get_commitments_inbox(self: @ContractState) -> ContractAddress { @@ -243,17 +252,17 @@ mod HeadersStore { } // @inheritdoc IHeadersStore - fn get_mmr(self: @ContractState, mmr_id: usize) -> MMR { + fn get_mmr(self: @ContractState, mmr_id: MmrId) -> MMR { self.mmr.read(mmr_id) } // @inheritdoc IHeadersStore - fn get_mmr_root(self: @ContractState, mmr_id: usize) -> felt252 { + fn get_mmr_root(self: @ContractState, mmr_id: MmrId) -> MmrElement { self.mmr.read(mmr_id).root } // @inheritdoc IHeadersStore - fn get_mmr_size(self: @ContractState, mmr_id: usize) -> usize { + fn get_mmr_size(self: @ContractState, mmr_id: MmrId) -> MmrSize { self.mmr.read(mmr_id).last_pos } @@ -263,19 +272,14 @@ mod HeadersStore { } // @inheritdoc IHeadersStore - fn get_latest_mmr_id(self: @ContractState) -> usize { - self.latest_mmr_id.read() - } - - // @inheritdoc IHeadersStore - fn get_historical_root(self: @ContractState, mmr_id: usize, size: usize) -> felt252 { + fn get_historical_root(self: @ContractState, mmr_id: MmrId, size: MmrSize) -> MmrElement { self.mmr_history.read((mmr_id, size)) } // @inheritdoc IHeadersStore fn receive_hash(ref self: ContractState, parent_hash: u256, block_number: u256) { let caller = get_caller_address(); - assert(caller == self.commitments_inbox.read(), 'Only CommitmentsInbox'); + assert(caller == self.commitments_inbox.read(), 'ONLY_COMMITMENTS_INBOX'); self.received_blocks.write(block_number, parent_hash); @@ -287,43 +291,43 @@ mod HeadersStore { ref self: ContractState, headers_rlp: Span, mmr_peaks: Peaks, - mmr_id: usize, + mmr_id: MmrId, reference_block: Option, - mmr_index: Option, + mmr_index: Option, mmr_proof: Option, ) { let mut mmr = self.mmr.read(mmr_id); + assert(mmr.root != 0, 'SRC_MMR_NOT_FOUND'); let poseidon_hash = hash_words64(*headers_rlp.at(0)); let mut peaks = mmr_peaks; - let mut start_block = 0; - let mut end_block = 0; + let mut start_block: u256 = 0; + let mut end_block: u256 = 0; - let (mut decoded_rlp, mut rlp_byte_len) = (RLPItem::Bytes((array![].span(), 0)), 0); + let mut decoded_rlp = RLPItem::Bytes((array![].span(), 0)); + let mut rlp_byte_len = 0; if mmr_proof.is_some() { - assert(reference_block.is_none(), 'Cannot use proof AND ref block'); - assert(headers_rlp.len() >= 2, 'Invalid headers rlp'); + assert(reference_block.is_none(), 'PROOF_AND_REF_BLOCK_NOT_ALLOWED'); + assert(headers_rlp.len() >= 2, 'INVALID_HEADER_RLP'); match rlp_decode_list_lazy(*headers_rlp.at(0), array![0, 8].span()) { Result::Ok((d, d_l)) => { decoded_rlp = d; rlp_byte_len = d_l; }, - Result::Err(_) => { - panic_with_felt252('Invalid header rlp'); - } + Result::Err(_) => { panic_with_felt252('INVALID_HEADER_RLP'); } }; let valid_proof = mmr .verify_proof(mmr_index.unwrap(), poseidon_hash, mmr_peaks, mmr_proof.unwrap()) - .expect('MMR proof verification failed'); - assert(valid_proof, 'Invalid proof'); + .expect('INVALID_MMR_PROOF'); + assert(valid_proof, 'INVALID_MMR_PROOF'); match @decoded_rlp { - RLPItem::Bytes(_) => panic_with_felt252('Invalid header rlp'), + RLPItem::Bytes(_) => panic_with_felt252('INVALID_HEADER_RLP'), RLPItem::List(l) => { let (start_block_words, start_block_byte_len) = *(*l).at(1); - assert(start_block_words.len() == 1, 'Invalid start_block'); + assert(start_block_words.len() == 1, 'INVALID_START_BLOCK'); let start_block_le = *start_block_words.at(0); start_block = @@ -333,28 +337,26 @@ mod HeadersStore { .into() - 1; - end_block = start_block - headers_rlp.len().into() + 2; + end_block = (start_block + 2) - headers_rlp.len().into(); } }; } else { - assert(headers_rlp.len() >= 1, 'Invalid headers rlp'); + assert(headers_rlp.len() >= 1, 'INVALID_HEADER_RLP'); match rlp_decode_list_lazy(*headers_rlp.at(0), array![0].span()) { Result::Ok((d, d_l)) => { decoded_rlp = d; rlp_byte_len = d_l; }, - Result::Err(_) => { - panic_with_felt252('Invalid header rlp'); - } + Result::Err(_) => { panic_with_felt252('INVALID_HEADER_RLP'); } }; let reference_block = reference_block.unwrap(); start_block = reference_block - 1; - end_block = start_block - headers_rlp.len().into() + 1; + end_block = (start_block + 1) - headers_rlp.len().into(); let initial_blockhash = self.received_blocks.read(reference_block); - assert(initial_blockhash != Zeroable::zero(), 'Block not received'); + assert(initial_blockhash != Zeroable::zero(), 'BLOCK_NOT_RECEIVED'); let mut last_word_byte_len = rlp_byte_len % 8; if last_word_byte_len == 0 { @@ -363,9 +365,9 @@ mod HeadersStore { let rlp_hash = InternalFunctions::keccak_hash_rlp( *headers_rlp.at(0), last_word_byte_len, true ); - assert(rlp_hash == initial_blockhash, 'Invalid initial header rlp'); + assert(rlp_hash == initial_blockhash, 'INVALID_INITIAL_HEADER_RLP'); - let (_, p) = mmr.append(poseidon_hash, mmr_peaks).expect('Failed to append to MMR'); + let (_, p) = mmr.append(poseidon_hash, mmr_peaks).expect('MMR_APPEND_FAILED'); peaks = p; } @@ -376,11 +378,11 @@ mod HeadersStore { } let parent_hash: u256 = match decoded_rlp { - RLPItem::Bytes(_) => panic_with_felt252('Invalid header rlp'), + RLPItem::Bytes(_) => panic_with_felt252('INVALID_HEADER_RLP'), RLPItem::List(l) => { let (words, words_byte_len) = *l.at(0); - assert(words.len() == 4 && words_byte_len == 32, 'Invalid parent_hash rlp'); - words.as_u256_le(32).unwrap() + assert(words.len() == 4 && words_byte_len == 32, 'INVALID_PARENT_HASH_RLP'); + words.as_u256_le().unwrap() }, }; @@ -391,9 +393,7 @@ mod HeadersStore { decoded_rlp = d; rlp_byte_len = d_l; }, - Result::Err(_) => { - panic_with_felt252('Invalid header rlp'); - } + Result::Err(_) => { panic_with_felt252('INVALID_HEADER_RLP'); } }; let mut last_word_byte_len = rlp_byte_len % 8; @@ -403,11 +403,11 @@ mod HeadersStore { let current_hash = InternalFunctions::keccak_hash_rlp( current_rlp, last_word_byte_len, false ); - assert(current_hash == parent_hash, 'Invalid header rlp'); + assert(current_hash == parent_hash, 'INVALID_HEADER_RLP'); let poseidon_hash = hash_words64(current_rlp); - let (_, p) = mmr.append(poseidon_hash, peaks).expect('Failed to append to MMR'); + let (_, p) = mmr.append(poseidon_hash, peaks).expect('MMR_APPEND_FAILED'); peaks = p; i += 1; @@ -433,54 +433,60 @@ mod HeadersStore { // @inheritdoc IHeadersStore fn verify_mmr_inclusion( self: @ContractState, - index: usize, - poseidon_blockhash: felt252, + index: MmrSize, + poseidon_blockhash: MmrElement, peaks: Peaks, proof: Proof, - mmr_id: usize, + mmr_id: MmrId, ) -> bool { let mmr = self.mmr.read(mmr_id); + assert(mmr.root != 0, 'MMR_NOT_FOUND'); - mmr - .verify_proof(index, poseidon_blockhash, peaks, proof) - .expect('MMR proof verification failed') + mmr.verify_proof(index, poseidon_blockhash, peaks, proof).expect('INVALID_MMR_PROOF') } // @inheritdoc IHeadersStore fn verify_historical_mmr_inclusion( self: @ContractState, - index: usize, - poseidon_blockhash: felt252, + index: MmrSize, + poseidon_blockhash: MmrElement, peaks: Peaks, proof: Proof, - mmr_id: usize, - last_pos: usize, + mmr_id: MmrId, + last_pos: MmrSize, ) -> bool { let root = self.mmr_history.read((mmr_id, last_pos)); + assert(root != 0, 'MMR_NOT_FOUND'); + let mmr = MMRTrait::new(root, last_pos); - mmr - .verify_proof(index, poseidon_blockhash, peaks, proof) - .expect('MMR proof verification failed') + mmr.verify_proof(index, poseidon_blockhash, peaks, proof).expect('INVALID_MMR_PROOF') } // @inheritdoc IHeadersStore fn create_branch_from_message( - ref self: ContractState, root: felt252, last_pos: usize, aggregator_id: usize + ref self: ContractState, + root: MmrElement, + last_pos: MmrSize, + aggregator_id: AggregatorId, + new_mmr_id: MmrId ) { + assert(new_mmr_id != 0, 'NEW_MMR_ID_0_NOT_ALLOWED'); + assert(root != 0, 'ROOT_0_NOT_ALLOWED'); + let caller = get_caller_address(); - assert(caller == self.commitments_inbox.read(), 'Only CommitmentsInbox'); + assert(caller == self.commitments_inbox.read(), 'ONLY_COMMITMENTS_INBOX'); + + assert(self.mmr.read(new_mmr_id).root == 0, 'NEW_MMR_ALREADY_EXISTS'); - let mmr_id = self.latest_mmr_id.read() + 1; let mmr = MMRTrait::new(root, last_pos); - self.mmr.write(mmr_id, mmr); - self.mmr_history.write((mmr_id, last_pos), root); - self.latest_mmr_id.write(mmr_id); + self.mmr.write(new_mmr_id, mmr); + self.mmr_history.write((new_mmr_id, last_pos), root); self .emit( Event::BranchCreatedFromL1( - BranchCreatedFromL1 { mmr_id, root, last_pos, aggregator_id } + BranchCreatedFromL1 { mmr_id: new_mmr_id, root, last_pos, aggregator_id } ) ); } @@ -488,38 +494,42 @@ mod HeadersStore { // @inheritdoc IHeadersStore fn create_branch_single_element( ref self: ContractState, - index: usize, - initial_poseidon_blockhash: felt252, + index: MmrSize, + initial_poseidon_blockhash: MmrElement, peaks: Peaks, proof: Proof, - mmr_id: usize, - last_pos: usize + mmr_id: MmrId, + last_pos: MmrSize, + new_mmr_id: MmrId ) { + assert(mmr_id != 0, 'SRC_MMR_ID_0_NOT_ALLOWED'); + assert(new_mmr_id != 0, 'NEW_MMR_ID_0_NOT_ALLOWED'); + + assert(self.mmr.read(new_mmr_id).root == 0, 'NEW_MMR_ALREADY_EXISTS'); + assert( HeadersStore::verify_historical_mmr_inclusion( @self, index, initial_poseidon_blockhash, peaks, proof, mmr_id, last_pos ), - 'Invalid proof' + 'INVALID_MMR_PROOF' ); let mut mmr: MMR = Default::default(); - mmr.append(initial_poseidon_blockhash, array![].span()); + mmr.append(initial_poseidon_blockhash, array![].span()).expect('MMR_APPEND_FAILED'); - let root = mmr.root; - let last_pos = mmr.last_pos; + let new_root = mmr.root; + let new_last_pos = mmr.last_pos; - let latest_mmr_id = self.latest_mmr_id.read() + 1; - self.mmr.write(latest_mmr_id, mmr); - self.mmr_history.write((latest_mmr_id, last_pos), root); - self.latest_mmr_id.write(latest_mmr_id); + self.mmr.write(new_mmr_id, mmr); + self.mmr_history.write((new_mmr_id, new_last_pos), new_root); self .emit( Event::BranchCreatedFromElement( BranchCreatedFromElement { - mmr_id: latest_mmr_id, - root, - last_pos, + mmr_id: new_mmr_id, + root: new_root, + last_pos: new_last_pos, detached_from_mmr_id: mmr_id, mmr_index: index } @@ -528,21 +538,31 @@ mod HeadersStore { } // @inheritdoc IHeadersStore - fn create_branch_from(ref self: ContractState, mmr_id: usize, last_pos: usize) { - let latest_mmr_id = self.latest_mmr_id.read() + 1; - let root = self.mmr_history.read((mmr_id, last_pos)); + fn create_branch_from( + ref self: ContractState, mmr_id: MmrId, mut last_pos: MmrSize, new_mmr_id: MmrId + ) { + assert(new_mmr_id != 0, 'NEW_MMR_ID_0_NOT_ALLOWED'); - let mmr = MMRTrait::new(root, last_pos); + assert(self.mmr.read(new_mmr_id).root == 0, 'NEW_MMR_ALREADY_EXISTS'); + + let root = if mmr_id == 0 { + last_pos = 1; + MMR_INITIAL_ROOT + } else { + self.mmr_history.read((mmr_id, last_pos)) + }; + + assert(root != 0, 'SRC_MMR_NOT_FOUND'); + let new_mmr = MMRTrait::new(root, last_pos); - self.mmr.write(latest_mmr_id, mmr); - self.mmr_history.write((latest_mmr_id, last_pos), root); - self.latest_mmr_id.write(latest_mmr_id); + self.mmr.write(new_mmr_id, new_mmr); + self.mmr_history.write((new_mmr_id, last_pos), root); self .emit( Event::BranchCreatedClone( BranchCreatedClone { - mmr_id: latest_mmr_id, root, last_pos, detached_from_mmr_id: mmr_id + mmr_id: new_mmr_id, root, last_pos, detached_from_mmr_id: mmr_id } ) ); diff --git a/src/core/tests/test_facts_registry.cairo b/src/core/tests/test_facts_registry.cairo index a9199ff..0ed52e4 100644 --- a/src/core/tests/test_facts_registry.cairo +++ b/src/core/tests/test_facts_registry.cairo @@ -1,4 +1,4 @@ -use snforge_std::{declare, PreparedContract, deploy, start_prank, stop_prank}; +use snforge_std::{declare, ContractClassTrait, start_prank, stop_prank, CheatTarget}; use herodotus_eth_starknet::core::headers_store::{ IHeadersStoreDispatcherTrait, IHeadersStoreDispatcher @@ -9,38 +9,32 @@ use herodotus_eth_starknet::core::evm_facts_registry::{ use starknet::ContractAddress; use cairo_lib::utils::types::words64::Words64; use cairo_lib::hashing::poseidon::{hash_words64, PoseidonHasher}; -use cairo_lib::data_structures::mmr::{proof::Proof, peaks::Peaks}; +use cairo_lib::data_structures::mmr::{proof::Proof, peaks::Peaks, mmr::MmrSize}; const COMMITMENTS_INBOX_ADDRESS: felt252 = 0x123; const TEST_MMR_ROOT: felt252 = 0x37a31db9c80c54ec632f04f7984155dc43591a3f8c891adfbf34e75331e0eec; -const TEST_MMR_SIZE: usize = 8; +const TEST_MMR_SIZE: MmrSize = 8; const TEST_ACCOUNT: felt252 = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; const TEST_BLOCK: u256 = 17000000; fn helper_create_facts_registry( - mmr_root: felt252, mmr_size: usize + mmr_root: felt252, mmr_size: MmrSize ) -> (IEVMFactsRegistryDispatcher, ContractAddress) { - let class_hash = declare('HeadersStore'); - let prepared = PreparedContract { - class_hash: class_hash, constructor_calldata: @array![COMMITMENTS_INBOX_ADDRESS] - }; - let contract_address = deploy(prepared).unwrap(); + let contract = declare("HeadersStore").unwrap(); + let (contract_address, _) = contract.deploy(@array![COMMITMENTS_INBOX_ADDRESS]).unwrap(); let mut headers_store = IHeadersStoreDispatcher { contract_address }; - start_prank(contract_address, COMMITMENTS_INBOX_ADDRESS.try_into().unwrap()); - headers_store.create_branch_from_message(mmr_root, mmr_size, 0); - stop_prank(contract_address); + start_prank(CheatTarget::One(contract_address), COMMITMENTS_INBOX_ADDRESS.try_into().unwrap()); + headers_store.create_branch_from_message(mmr_root, mmr_size, 0, 1); + stop_prank(CheatTarget::One(contract_address)); - let class_hash = declare('EVMFactsRegistry'); - let prepared = PreparedContract { - class_hash: class_hash, constructor_calldata: @array![contract_address.into()] - }; - let contract_address = deploy(prepared).unwrap(); + let contract = declare("EVMFactsRegistry").unwrap(); + let (contract_address, _) = contract.deploy(@array![contract_address.into()]).unwrap(); (IEVMFactsRegistryDispatcher { contract_address }, contract_address) } #[test] fn test_prove_account() { - let (dispatcher, contract_address) = helper_create_facts_registry(TEST_MMR_ROOT, TEST_MMR_SIZE); + let (dispatcher, _) = helper_create_facts_registry(TEST_MMR_ROOT, TEST_MMR_SIZE); // Testing block 17000000 let block_header_rlp = *helper_get_headers_rlp().at(0); @@ -85,8 +79,9 @@ fn test_prove_account() { #[test] fn test_prove_storage() { - // Expected key based on proof: 0x290decd | 39548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563 - let (dispatcher, contract_address) = helper_create_facts_registry(TEST_MMR_ROOT, TEST_MMR_SIZE); + // Expected key based on proof: 0x290decd | + // 39548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563 + let (dispatcher, _) = helper_create_facts_registry(TEST_MMR_ROOT, TEST_MMR_SIZE); // Testing block 17000000 let block_header_rlp = *helper_get_headers_rlp().at(0); @@ -120,8 +115,9 @@ fn test_prove_storage() { #[test] fn test_prove_storage_empty() { - // Expected key based on proof: 0x290decd | 39548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563 - let (dispatcher, contract_address) = helper_create_facts_registry(TEST_MMR_ROOT, TEST_MMR_SIZE); + // Expected key based on proof: 0x290decd | + // 39548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563 + let (dispatcher, _) = helper_create_facts_registry(TEST_MMR_ROOT, TEST_MMR_SIZE); // Testing block 17000000 let block_header_rlp = *helper_get_headers_rlp().at(0); diff --git a/src/core/tests/test_headers_store.cairo b/src/core/tests/test_headers_store.cairo index b32bf39..9921d6f 100644 --- a/src/core/tests/test_headers_store.cairo +++ b/src/core/tests/test_headers_store.cairo @@ -1,33 +1,31 @@ // SPDX-License-Identifier: GPL-3.0 -use snforge_std::{declare, PreparedContract, deploy, start_prank, stop_prank}; +use snforge_std::{declare, ContractClassTrait, start_prank, stop_prank, CheatTarget}; use herodotus_eth_starknet::core::headers_store::{ IHeadersStoreDispatcherTrait, IHeadersStoreDispatcher, IHeadersStoreSafeDispatcherTrait, IHeadersStoreSafeDispatcher }; +use herodotus_eth_starknet::core::common::MmrId; use starknet::ContractAddress; use cairo_lib::utils::types::words64::Words64; +use cairo_lib::data_structures::mmr::mmr::{MMR, MMRTrait, MmrSize, MmrElement}; +use debug::PrintTrait; const COMMITMENTS_INBOX_ADDRESS: felt252 = 0x123; -const MMR_INITIAL_ELEMENT: felt252 = +const MMR_INITIAL_ELEMENT: MmrElement = 0x02241b3b7f1c4b9cf63e670785891de91f7237b1388f6635c1898ae397ad32dd; -const MMR_INITIAL_ROOT: felt252 = 0x6759138078831011e3bc0b4a135af21c008dda64586363531697207fb5a2bae; +const MMR_INITIAL_ROOT: MmrElement = + 0x6759138078831011e3bc0b4a135af21c008dda64586363531697207fb5a2bae; fn helper_create_headers_store() -> (IHeadersStoreDispatcher, ContractAddress) { - let class_hash = declare('HeadersStore'); - let prepared = PreparedContract { - class_hash: class_hash, constructor_calldata: @array![COMMITMENTS_INBOX_ADDRESS] - }; - let contract_address = deploy(prepared).unwrap(); + let contract = declare("HeadersStore").unwrap(); + let (contract_address, _) = contract.deploy(@array![COMMITMENTS_INBOX_ADDRESS]).unwrap(); (IHeadersStoreDispatcher { contract_address }, contract_address) } fn helper_create_safe_headers_store() -> (IHeadersStoreSafeDispatcher, ContractAddress) { - let class_hash = declare('HeadersStore'); - let prepared = PreparedContract { - class_hash: class_hash, constructor_calldata: @array![COMMITMENTS_INBOX_ADDRESS] - }; - let contract_address = deploy(prepared).unwrap(); + let contract = declare("HeadersStore").unwrap(); + let (contract_address, _) = contract.deploy(@array![COMMITMENTS_INBOX_ADDRESS]).unwrap(); (IHeadersStoreSafeDispatcher { contract_address }, contract_address) } @@ -37,12 +35,183 @@ fn helper_receive_hash( dispatcher: IHeadersStoreDispatcher, contract_address: ContractAddress ) { - start_prank(contract_address, COMMITMENTS_INBOX_ADDRESS.try_into().unwrap()); + start_prank(CheatTarget::One(contract_address), COMMITMENTS_INBOX_ADDRESS.try_into().unwrap()); dispatcher.receive_hash(blockhash, block_number); - stop_prank(contract_address); + stop_prank(CheatTarget::One(contract_address)); } #[test] +fn test_header_block_1() { + let headers_rlp = array![ + array![ + 0x64dd60e4a0fc01f9, + 0x4c54f284013c491f, + 0xcf69fedcb4e3cf9b, + 0x0d1b29ed8a4f05a8, + 0x4dcc1da05e02a0dd, + 0xb585ab7a5dc7dee8, + 0x4512d31ad4ccb667, + 0x42a1f013748a941b, + 0x0042944793d440fd, + 0x0000000000000000, + 0x0000000000000000, + 0xedfc36fc32a01100, + 0x9319629f76c5000d, + 0x2a57fdbe486254cd, + 0x3dc181f13929b4f5, + 0xb5350f77a0c70890, + 0x58aff8b90f301ca1, + 0x2a7c97ac1a555144, + 0x27ab16def38f5914, + 0x82468fa0ce42063b, + 0x7bf6ce1c3a5b5782, + 0xb7e9521cac238426, + 0x697018cba3bda32c, + 0x0001b93b44a845dd, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x8280c3c901840280, + 0x80b0dbd6648429b7, + 0x5c67599946e432a0, + 0xa80937f4a9d1d3ee, + 0xf84428719d685d05, + 0x289ef4c9db34aa20, + 0x000000000000886c, + 0x555e4239840000 + ] + .span(), + array![ + 0xffe62d10a0fc01f9, + 0x48b5b8c90c4801b0, + 0xe46af4d44cc305fd, + 0xea90db93937591aa, + 0x4dcc1da07d880904, + 0xb585ab7a5dc7dee8, + 0x4512d31ad4ccb667, + 0x42a1f013748a941b, + 0x0042944793d440fd, + 0x0000000000000000, + 0x0000000000000000, + 0x8350600f4ca01100, + 0x56f3150a8a78d66f, + 0x73d2b84cef2d199b, + 0x42bed64a1ea39943, + 0xc494f04da0dcb31e, + 0xb948bfaaea99f413, + 0x5c0bb3233cd8d16f, + 0x2d6bc9f9efa595ec, + 0x5d713ca075e82e5d, + 0xde6fd4cc97256dd9, + 0x5b0a3eb1e4a56d04, + 0xc9e63e0cf62f0a7d, + 0x0001b9ea0096ee2f, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x0000000000000000, + 0x8280c3c901840180, + 0x80aedbd66484f5f9, + 0x5c67599946e432a0, + 0xa80937f4a9d1d3ee, + 0xf84428719d685d05, + 0x289ef4c9db34aa20, + 0x000000000000886c, + 0x009d693a840000 + ] + .span() + ] + .span(); + + let mut mmr = MMRTrait::new( + root: 0x78ece8884698aadc91f067cf2d0d54a955e458ab6cd2ebc18fe815a3aafb43, last_pos: 263 + ); + let mmr_peaks = array![ + 0x735d9916958a088b58e320e015ba24e93ad034159fe0551c31cbb69d5be0a05, + 0x0e4829e42415b71f12d9d936cb22bc50cd97f4a9737852454deeca9b49c59a2, + 0x38aaa5bd29a41a3818b28eff66365d8ea7dd20380456f27b832f1091503a961 + ] + .span(); + let mmr_proof = array![].span(); + let mmr_id = 1; + + let (dispatcher, contract_address) = helper_create_headers_store(); + + start_prank(CheatTarget::One(contract_address), COMMITMENTS_INBOX_ADDRESS.try_into().unwrap()); + dispatcher.create_branch_from_message(mmr.root, mmr.last_pos, 0, mmr_id); + stop_prank(CheatTarget::One(contract_address)); + assert(dispatcher.get_mmr_root(mmr_id) == mmr.root, 'Root not set'); + + dispatcher + .process_batch( + headers_rlp, + mmr_peaks, + mmr_id, + Option::None, + Option::Some(mmr.last_pos), + Option::Some(mmr_proof) + ); +} + +#[test] +#[feature("safe_dispatcher")] fn test_receive_hash_wrong_address() { let (safe_dispatcher, _) = helper_create_safe_headers_store(); @@ -65,10 +234,168 @@ fn test_receive_hash() { } #[test] -fn test_initial_tree() { - let (dispatcher, contract_address) = helper_create_headers_store(); +fn test_create_branch_from_message() { + let (dispatcher, contract_address) = helper_create_safe_headers_store(); + + let mmr_id_1 = 0x5a93; + let mmr_id_2 = 0xcd82; + let root = 0x123123123; + let size = 10; + + start_prank(CheatTarget::One(contract_address), COMMITMENTS_INBOX_ADDRESS.try_into().unwrap()); + + // Create MMR. + assert(dispatcher.get_mmr_size(mmr_id_1).unwrap() == 0, 'Initial mmr size should be 0'); + dispatcher.create_branch_from_message(root, size, 0, mmr_id_1).unwrap(); + assert(dispatcher.get_mmr_size(mmr_id_1).unwrap() == size, 'Mmr size mismatch'); + assert(dispatcher.get_mmr_root(mmr_id_1).unwrap() == root, 'Mmr root mismatch'); + + // Creating MMR with ID 0 is not allowed. + assert( + dispatcher.create_branch_from_message(root, size, 0, 0).is_err(), 'Mmr ID 0 should fail' + ); + + // Root equal to 0 is not allowed. + assert( + dispatcher.create_branch_from_message(0, 1, 0, mmr_id_2).is_err(), 'Root = 0 should fail' + ); + + // Creating MMR with the same ID should fail. + assert( + dispatcher.create_branch_from_message(root, size, 0, mmr_id_1).is_err(), + 'MMR already exists should fail' + ); - let mmr_id = 0; + // Create another MMR. + assert(dispatcher.get_mmr_size(mmr_id_2).unwrap() == 0, 'Initial mmr size should be 0'); + dispatcher.create_branch_from_message(root, size, 0, mmr_id_2).unwrap(); + assert(dispatcher.get_mmr_size(mmr_id_2).unwrap() == size, 'Mmr size mismatch'); + assert(dispatcher.get_mmr_root(mmr_id_2).unwrap() == root, 'Mmr root mismatch'); + + stop_prank(CheatTarget::One(contract_address)); + + let mmr_id_3 = 0x8124a; + + // Sender other than commitments inbox should fail. + assert( + dispatcher.create_branch_from_message(root, size, 0, mmr_id_3).is_err(), + 'only commitments inbox' + ); +} + +fn helper_create_mmr_with_items(mut items: Span) -> MMR { + let mut mmr: MMR = Default::default(); + let mut peaks = array![].span(); + loop { + match items.pop_front() { + Option::Some(item) => { + let (_root, new_peaks) = mmr.append(*item, peaks).unwrap(); + peaks = new_peaks; + }, + Option::None => { break; } + } + }; + mmr +} + +#[test] +fn test_create_branch_single_element() { + let (dispatcher, contract_address) = helper_create_safe_headers_store(); + + // Setup mmr with 7 elements + let mmr_id = 1; + let items = array![MMR_INITIAL_ELEMENT, 0x4AF3, 0xB1C2, 0x68D0, 0xE923, 0x0F4B, 0x37A8]; + let mmr = helper_create_mmr_with_items(items.span()); + start_prank(CheatTarget::One(contract_address), COMMITMENTS_INBOX_ADDRESS.try_into().unwrap()); + dispatcher.create_branch_from_message(mmr.root, mmr.last_pos, 0, mmr_id).unwrap(); + stop_prank(CheatTarget::One(contract_address)); + + // Create branch with 3rd element + let new_mmr_id = 10; + let index = 4; + let hash = 0xB1C2; + let proof = array![0x68D0, 0x5e58373c626c427a3d2b417634424a93ca5efa8cde09ac9747aa03f3afecb8d] + .span(); + let peaks = array![ + 0x7cd5f93b55c504e2919127e13b025c86cbb135e14efb41a97c56a3f61bc48d8, + 0x73cc8b5fc3ab909c2b7f33c34bd341df3b1d328f29c59bbe97dc53a17bbc33f, + 0x37A8 + ] + .span(); + + // New MMR with ID 0 should fail + assert( + dispatcher + .create_branch_single_element(index, hash, peaks, proof, mmr_id, mmr.last_pos, 0) + .is_err(), + 'new mmr id 0 should fail' + ); + + // Source MMR with ID 0 should fail + assert( + dispatcher + .create_branch_single_element(index, hash, peaks, proof, 0, mmr.last_pos, new_mmr_id) + .is_err(), + 'src mmr id 0 should fail' + ); + + // Source MMR must exist + assert( + dispatcher + .create_branch_single_element(index, hash, peaks, proof, 2, mmr.last_pos, new_mmr_id) + .is_err(), + 'no src mmr should fail' + ); + + // Invalid proof should fail + assert( + dispatcher + .create_branch_single_element( + index, + hash, + peaks, + array![ + 0x68D0, + 0x5e58373c626c427a3d2b417634424a93ca5efa8cde09ac9747aa03f3afecb8d, + 0x1234 + ] + .span(), + mmr_id, + mmr.last_pos, + new_mmr_id + ) + .is_err(), + 'invalid proof should fail' + ); + + // Valid proof should succeed + dispatcher + .create_branch_single_element(index, hash, peaks, proof, mmr_id, mmr.last_pos, new_mmr_id) + .unwrap(); + let new_mmr = dispatcher.get_mmr(new_mmr_id).unwrap(); + assert( + new_mmr.root == 0x2f9bb49a56c6119deabb24612297842a5ec873bd67e71e7b87b94c8e1b95d7a, + 'new mmr root mismatch' + ); + assert(new_mmr.last_pos == 1, 'new mmr last_pos mismatch'); + + // New MMR that already exists should fail + assert( + dispatcher + .create_branch_single_element( + index, hash, peaks, proof, mmr_id, mmr.last_pos, new_mmr_id + ) + .is_err(), + 'mmr alrd exists should fail' + ); +} + +#[test] +fn test_create_branch_new() { + let (dispatcher, _) = helper_create_headers_store(); + + let mmr_id = 1; + dispatcher.create_branch_from(0, 0, mmr_id); let mmr = dispatcher.get_mmr(mmr_id); let expected_root = MMR_INITIAL_ROOT; @@ -79,6 +406,25 @@ fn test_initial_tree() { assert(historical_root == expected_root, 'Wrong initial historical root'); } +#[test] +fn test_create_branch_from() { + let (dispatcher, contract_address) = helper_create_safe_headers_store(); + + // Setup mmr with 7 elements + let mmr_id = 0x4a02; + let items = array![MMR_INITIAL_ELEMENT, 0x4AF3, 0xB1C2, 0x68D0, 0xE923, 0x0F4B, 0x37A8]; + let mmr = helper_create_mmr_with_items(items.span()); + start_prank(CheatTarget::One(contract_address), COMMITMENTS_INBOX_ADDRESS.try_into().unwrap()); + dispatcher.create_branch_from_message(mmr.root, mmr.last_pos, 0, mmr_id).unwrap(); + stop_prank(CheatTarget::One(contract_address)); + + let new_mmr_id = 0x8cae; + dispatcher.create_branch_from(mmr_id, mmr.last_pos, new_mmr_id).unwrap(); + let new_mmr = dispatcher.get_mmr(new_mmr_id).unwrap(); + assert(new_mmr.root == mmr.root, 'new mmr root mismatch'); + assert(new_mmr.last_pos == mmr.last_pos, 'new mmr last_pos mismatch'); +} + #[test] fn test_process_batch_form_message() { let (dispatcher, contract_address) = helper_create_headers_store(); @@ -89,7 +435,8 @@ fn test_process_batch_form_message() { let headers_rlp = helper_get_headers_rlp(); - let mmr_id = 0; + let mmr_id = 1; + dispatcher.create_branch_from(0, 0, mmr_id); dispatcher .process_batch( headers_rlp, diff --git a/src/remappers/interface.cairo b/src/remappers/interface.cairo index 33ed27b..db5f5f1 100644 --- a/src/remappers/interface.cairo +++ b/src/remappers/interface.cairo @@ -4,18 +4,21 @@ // Interface types // +use cairo_lib::data_structures::mmr::mmr::{MmrSize, MmrElement}; use cairo_lib::data_structures::mmr::proof::Proof; use cairo_lib::data_structures::mmr::peaks::Peaks; use cairo_lib::utils::types::words64::Words64; +use herodotus_eth_starknet::core::common::MmrId; type Headers = Span; +type MapperId = u256; #[derive(Drop, Serde)] struct OriginElement { - tree_id: usize, - last_pos: usize, - leaf_idx: usize, - leaf_value: felt252, + tree_id: MmrId, + last_pos: MmrSize, + leaf_idx: MmrSize, + leaf_value: MmrElement, inclusion_proof: Proof, peaks: Peaks, header: Words64 @@ -23,15 +26,15 @@ struct OriginElement { #[derive(Drop, Serde)] struct ProofElement { - index: usize, + index: MmrSize, value: u256, proof: Proof, } #[derive(Drop, Serde)] struct BinarySearchTree { - mapper_id: usize, - last_pos: usize, // last_pos in mapper's MMR + mapper_id: MapperId, + last_pos: MmrSize, // last_pos in mapper's MMR peaks: Peaks, proofs: Span, // Midpoint elements inclusion proofs left_neighbor: Option, // Optional left neighbor inclusion proof @@ -44,12 +47,12 @@ struct BinarySearchTree { #[starknet::interface] trait ITimestampRemappers { // Creates a new mapper and returns its ID. - fn create_mapper(ref self: TContractState, start_block: u256) -> usize; + fn create_mapper(ref self: TContractState, start_block: u256, mapper_id: MapperId) -> MapperId; // Adds elements from other trusted data sources to the given mapper. fn reindex_batch( ref self: TContractState, - mapper_id: usize, + mapper_id: MapperId, mapper_peaks: Peaks, origin_elements: Span ); @@ -60,5 +63,5 @@ trait ITimestampRemappers { ) -> Result, felt252>; // Getter for the last timestamp of a given mapper. - fn get_last_mapper_timestamp(self: @TContractState, mapper_id: usize) -> u256; + fn get_last_mapper_timestamp(self: @TContractState, mapper_id: MapperId) -> u256; } diff --git a/src/remappers/tests/test_timestamp_remappers.cairo b/src/remappers/tests/test_timestamp_remappers.cairo index 1ded3a3..5e14d1a 100644 --- a/src/remappers/tests/test_timestamp_remappers.cairo +++ b/src/remappers/tests/test_timestamp_remappers.cairo @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0 -use snforge_std::{declare, PreparedContract, deploy, start_prank, stop_prank}; +use snforge_std::{declare, ContractClassTrait, start_prank, stop_prank, CheatTarget}; use starknet::ContractAddress; use cairo_lib::data_structures::mmr::mmr::MMR; use cairo_lib::data_structures::mmr::mmr::MMRTrait; @@ -8,34 +8,29 @@ use cairo_lib::utils::bitwise::bit_length; use cairo_lib::utils::math::pow; use herodotus_eth_starknet::remappers::interface::{ ITimestampRemappersDispatcherTrait, ITimestampRemappersDispatcher, BinarySearchTree, - ProofElement, OriginElement + ProofElement, OriginElement, MapperId }; use herodotus_eth_starknet::core::headers_store::{ IHeadersStoreDispatcherTrait, IHeadersStoreDispatcher }; +use herodotus_eth_starknet::core::common::MmrId; fn deploy_headers_store() -> ContractAddress { - let class_hash = declare('HeadersStore'); + let contract = declare("HeadersStore").unwrap(); let mut constructor_calldata = ArrayTrait::new(); constructor_calldata.append(0); - let prepared = PreparedContract { - class_hash: class_hash, constructor_calldata: @constructor_calldata - }; - - deploy(prepared).unwrap() + let (address, _) = contract.deploy(@constructor_calldata).unwrap(); + address } fn deploy_timestamp_remappers(headers_store: ContractAddress) -> ContractAddress { - let class_hash = declare('TimestampRemappers'); + let contract = declare("TimestampRemappers").unwrap(); let mut constructor_calldata: Array = ArrayTrait::new(); constructor_calldata.append(headers_store.into()); - let prepared = PreparedContract { - class_hash: class_hash, constructor_calldata: @constructor_calldata - }; - - deploy(prepared).unwrap() + let (address, _) = contract.deploy(@constructor_calldata).unwrap(); + address } fn inner_test_proof(mmr: @MMR) { @@ -78,7 +73,7 @@ fn prepare_mmr() -> MMR { let mut peaks_2 = ArrayTrait::new(); peaks_2.append(0x5d44a3decb2b2e0cc71071f7b802f45dd792d064f0fc7316c46514f70f9891a); - mmr.append(4, peaks_2.span()); + mmr.append(4, peaks_2.span()).unwrap(); peaks_2.append(4); mmr.append(5, peaks_2.span()).unwrap(); @@ -91,7 +86,7 @@ fn prepare_mmr() -> MMR { } fn inner_test_reindex_batch( - remapper_dispatcher: ITimestampRemappersDispatcher, mapper_id: usize, mmr_id: usize + remapper_dispatcher: ITimestampRemappersDispatcher, mapper_id: MapperId, mmr_id: MmrId ) { let header = array![ 0x7323c119a02202f9, @@ -354,7 +349,8 @@ fn inner_test_reindex_batch( }, ]; remapper_dispatcher.reindex_batch(mapper_id, ArrayTrait::new().span(), origin_elements.span()); -// From this point on, mapper_peaks has root 0x215ea4dbc30f0b14338d306f0035277c856c486126cd34966a82ead2a0a1c01 and 4 as elements count +// From this point on, mapper_peaks has root +// 0x215ea4dbc30f0b14338d306f0035277c856c486126cd34966a82ead2a0a1c01 and 4 as elements count } #[test] @@ -366,7 +362,7 @@ fn test_remappers() { contract_address: timestamp_remappers }; let start_block: u256 = 9260751; // Mainnet block - let mapper_id = remapper_dispatcher.create_mapper(start_block); + let mapper_id = remapper_dispatcher.create_mapper(start_block, 0); assert(mapper_id == 0, 'Invalid mapper id'); // An MMR containing { 1, 2, 4, 5, 8 } as string with Starknet Poseidon as a hasher. @@ -374,14 +370,14 @@ fn test_remappers() { inner_test_proof(@mmr); let headers_store_dispatcher = IHeadersStoreDispatcher { contract_address: headers_store }; - let mmr_id = 1; // First MMR + let mmr_id = 0x123; // First MMR - start_prank(headers_store, 0.try_into().unwrap()); + start_prank(CheatTarget::One(headers_store), 0.try_into().unwrap()); headers_store_dispatcher .create_branch_from_message( - 0x25aedbc0ddea804ce21d29a39f00358f68df0e462114f75b0576182d08db0, 4, 1 + 0x25aedbc0ddea804ce21d29a39f00358f68df0e462114f75b0576182d08db0, 4, 1, mmr_id ); - stop_prank(headers_store); + stop_prank(CheatTarget::One(headers_store)); inner_test_reindex_batch(remapper_dispatcher, mapper_id, mmr_id); diff --git a/src/remappers/timestamp_remappers.cairo b/src/remappers/timestamp_remappers.cairo index f276733..0b6c514 100644 --- a/src/remappers/timestamp_remappers.cairo +++ b/src/remappers/timestamp_remappers.cairo @@ -6,13 +6,14 @@ #[starknet::contract] mod TimestampRemappers { + use herodotus_eth_starknet::core::common::MmrId; use herodotus_eth_starknet::remappers::interface::{ ITimestampRemappers, Headers, OriginElement, Proof, Peaks, Words64, ProofElement, - BinarySearchTree + BinarySearchTree, MapperId }; use starknet::ContractAddress; use cairo_lib::hashing::poseidon::{PoseidonHasher, hash_words64}; - use cairo_lib::data_structures::mmr::mmr::{MMR, MMRTrait}; + use cairo_lib::data_structures::mmr::mmr::{MMR, MMRTrait, MmrSize, MmrElement}; use cairo_lib::data_structures::mmr::utils::{leaf_index_to_mmr_index}; use cairo_lib::encoding::rlp::{RLPItem, rlp_decode_list_lazy}; use cairo_lib::utils::types::words64::{reverse_endianness_u64, bytes_used_u64}; @@ -33,17 +34,17 @@ mod TimestampRemappers { #[derive(Drop, starknet::Event)] struct MapperCreated { - mapper_id: usize, + mapper_id: MapperId, start_block: u256 } #[derive(Drop, starknet::Event)] struct RemappedBlocks { - mapper_id: usize, + mapper_id: MapperId, start_block: u256, end_block: u256, - mmr_root: felt252, - mmr_size: usize + mmr_root: MmrElement, + mmr_size: MmrSize } // @@ -64,32 +65,30 @@ mod TimestampRemappers { #[storage] struct Storage { headers_store: ContractAddress, - // id => mapper - mappers: LegacyMap::, - mappers_count: usize, - // id => mmr - mappers_mmrs: LegacyMap::, - // (id, size) => root - mappers_mmrs_history: LegacyMap::<(usize, usize), felt252>, + mappers: LegacyMap::, + mappers_mmrs: LegacyMap::, + mappers_mmrs_history: LegacyMap::<(MapperId, MmrSize), MmrElement>, } #[constructor] fn constructor(ref self: ContractState, headers_store: ContractAddress) { self.headers_store.write(headers_store); - self.mappers_count.write(0); } // // External // - #[external(v0)] + #[abi(embed_v0)] impl TimestampRemappers of ITimestampRemappers { // Creates a new mapper and returns its ID. - fn create_mapper(ref self: ContractState, start_block: u256) -> usize { + fn create_mapper( + ref self: ContractState, start_block: u256, mapper_id: MapperId + ) -> MapperId { let mmr: MMR = Default::default(); - let mapper_id = self.mappers_count.read(); + assert(start_block != 0, 'START_BLOCK_0_NOT_ALLOWED'); + assert(self.mappers.read(mapper_id).start_block == 0, 'MAPPER_ID_ALREADY_EXISTS'); self.mappers_mmrs_history.write((mapper_id, 0), mmr.root); self.mappers_mmrs.write(mapper_id, mmr); @@ -97,8 +96,6 @@ mod TimestampRemappers { let mapper = Mapper { start_block, elements_count: 0, last_timestamp: 0 }; self.mappers.write(mapper_id, mapper); - self.mappers_count.write(mapper_id + 1); - self.emit(Event::MapperCreated(MapperCreated { mapper_id, start_block })); mapper_id @@ -107,16 +104,17 @@ mod TimestampRemappers { // Adds elements from other trusted data sources to the given mapper. fn reindex_batch( ref self: ContractState, - mapper_id: usize, + mapper_id: MapperId, mapper_peaks: Peaks, origin_elements: Span ) { let len = origin_elements.len(); // Count of elements in the batch to append - assert(len != 0, 'Empty batch'); + assert(len != 0, 'EMPTY_BATCH'); // Fetch from storage let headers_store_addr = self.headers_store.read(); let mut mapper = self.mappers.read(mapper_id); + assert(mapper.start_block != 0, 'MAPPER_DOES_NOT_EXIST'); let mut mapper_mmr = self.mappers_mmrs.read(mapper_id); // Determine the expected block number of the first element in the batch @@ -130,17 +128,18 @@ mod TimestampRemappers { break (); } - // 1. Verify that the block number is correct (i.e., matching with the expected block) + // 1. Verify that the block number is correct (i.e., matching with the expected + // block) let origin_element: @OriginElement = origin_elements.at(idx); let (origin_element_block_number, origin_element_timestamp) = InternalFunctions::extract_header_block_number_and_timestamp( *origin_element.header ); - assert(origin_element_block_number == expected_block, 'Unexpected block number'); + assert(origin_element_block_number == expected_block, 'UNEXPECTED_BLOCK_NUMBER'); // 2. Verify that the header rlp is correct (i.e., matching with the leaf value) let current_hash = hash_words64(*origin_element.header); - assert(current_hash == *origin_element.leaf_value.into(), 'Invalid header rlp'); + assert(current_hash == *origin_element.leaf_value.into(), 'INVALID_HEADER_RLP'); // 3. Verify that the inclusion proof of the leaf is valid let is_valid_proof = IHeadersStoreDispatcher { @@ -154,7 +153,7 @@ mod TimestampRemappers { *origin_element.tree_id, *origin_element.last_pos ); - assert(is_valid_proof, 'Invalid proof'); + assert(is_valid_proof, 'INVALID_MMR_PROOF'); // Add the block timestamp to the mapper MMR so we can binary search it later let (_, p) = mapper_mmr @@ -218,7 +217,7 @@ mod TimestampRemappers { } // Getter for the last timestamp of a given mapper. - fn get_last_mapper_timestamp(self: @ContractState, mapper_id: usize) -> u256 { + fn get_last_mapper_timestamp(self: @ContractState, mapper_id: MapperId) -> u256 { let mapper = self.mappers.read(mapper_id); mapper.last_timestamp } @@ -246,9 +245,7 @@ mod TimestampRemappers { let ((block_number, block_number_byte_len), (timestamp, timestamp_byte_len)) = match decoded_rlp { RLPItem::Bytes(_) => panic_with_felt252('Invalid header rlp'), - RLPItem::List(l) => { - (*l.at(0), *l.at(1)) - }, + RLPItem::List(l) => { (*l.at(0), *l.at(1)) }, }; ( reverse_endianness_u64(*block_number.at(0), Option::Some(block_number_byte_len)) @@ -301,7 +298,8 @@ mod TimestampRemappers { mid = (left + right) / 2; let proof_element: @ProofElement = proofs.at(proof_idx); assert( - (*proof_element.index).into() == leaf_index_to_mmr_index(mid + 1), + (*proof_element.index) + .into() == leaf_index_to_mmr_index(mid.try_into().unwrap() + 1), 'Unexpected proof index' ); @@ -314,7 +312,7 @@ mod TimestampRemappers { proof: *proof_element.proof, ) .unwrap(); - assert(is_valid_proof, 'Invalid proof'); + assert(is_valid_proof, 'INVALID_MMR_PROOF'); if x >= mid_val { left = mid + 1; @@ -333,7 +331,9 @@ mod TimestampRemappers { // Verify the proof if it has not already been checked let tree_closest_low_val = tree.left_neighbor.unwrap(); assert( - tree_closest_low_val.index.into() == leaf_index_to_mmr_index(closest_idx + 1), + tree_closest_low_val + .index + .into() == leaf_index_to_mmr_index(closest_idx.try_into().unwrap() + 1), 'Unexpected proof index (c)' ); @@ -346,7 +346,7 @@ mod TimestampRemappers { tree_closest_low_val.proof, ) .unwrap(); - assert(is_valid_low_proof, 'Invalid proof'); + assert(is_valid_low_proof, 'INVALID_MMR_PROOF'); } return Option::Some(closest_idx);