arrow-left

All pages
gitbookPowered by GitBook
1 of 13

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Getting the Storage Proof

First we need to fetch the storage proof from Optimism using the block number of a Optimism block which header that has already been propagated to Hashi. This block number ensures that Hashi can verify the proof against the just mentioned block header propagated to Gnosis Chain.

hashtag
Fetching the Proof

Use the following curl command or javascript script to fetch the account and storage proof for the USDC total supply on Optimism:

  • chainId: 10 (Optimism).

  • address: The address of the USDC contract on Optimism: 0x0b2c639c533813f4aa9d7837caf62653d097ff85.

  • storageKeys: The storage key for the USDC total supply: 0xb.

This curl command returns the account and storage proof for the USDC total supply, which will be used for verification on Gnosis Chain.

hashtag
Use the SP1 storage proof generator

Hashi support .

blockNumber: The block number on Optimism, 126086800, which must have already been propagated to Gnosis Chain via Hashi for the proof to be valid.

SP1 zkVM to generate storage proofs
curl --location --request POST 'https://jsonrpc.hashi-explorer.xyz/v1' \
--header 'Content-Type: application/json' \
--data-raw '{
  "id": 1,
  "jsonrpc": "2.0",
  "method": "hashi_getAccountAndStorageProof",
  "params": {
      "chainId": 10,
      "address": "0x0b2c639c533813f4aa9d7837caf62653d097ff85",
      "storageKeys": ["0x00000000000000000000000000000000000000000000000000000000000000b"],
      "blockNumber": 126086800
  }
}'
import axios from "axios";

const main = async () => {

  // fetch storage proof from Hashi Prover
  console.log("Fetching storage proof from Hashi prover...");
  const result = await axios.post(
    "http://jsonrpc.hashi-explorer.xyz:3000/v1",
    {
      jsonrpc: "2.0",
      method: "hashi_getAccountAndStorageProof",
      params: {
          chainId: 10,
          address: "0x0b2c639c533813f4aa9d7837caf62653d097ff85",
          storageKeys: ["0x00000000000000000000000000000000000000000000000000000000000000b"],
          blockNumber: 126086800
      },
      id: 1,
    },
    {
      headers: {
        "Content-Type": "application/json",
      },
    }
  );
  console.log("Storage proof result", result.data.result.proof);
};

main();
RUST_LOG=info cargo run --release -- --execute \
    --rpc-url https://mainnet.infura.io/v3/YOUR-PROJECT-ID \
    --reference-block-number 0x7B60EA3 \
    --proof-block-number 0x783EE90 \
    --account 0x0b2c639c533813f4aa9d7837caf62653d097ff85 \
    --storage-key 0x000000000000000000000000000000000000000000000000000000000000000b

Setting the scene

What we have:

  • A ERC20 Transfer Event emitted in Sepolia USDC contract.

What we need:

  • The verification on Chiado

chain of the actual
Sepolia
Transfer
event
.

How Hashi enables the Event verification:

  • With HashiAPI: a offchain component used to retrieve the Event Proof for the specific Sepolia Event emitted.

  • With HashiProver: a onchain contract provided by Hashi which can verify the block proof provided by HashiAPI. HashiProver must be deployed on the target chain (Chiado in this case) and can be used in a custom contract which can use the Event proof verification provided by HashiProver itself.

Steps followed in this example:

  • Fetch the event proof for Transfer event from Sepolia.

  • Deploy a custom contract on Chiado that will verify the event proof using HashiProver.

  • Submitting the proof to the custom contract.

Validating your statements

Once the proof has been fetched, we can verify it using a custom contract on Chiado. The contract will use the HashiProver helper library to check the proof against the block header stored on Chiado.

Deploying the Custom Verification Contract on Chiado

Here is a custom contract that deployed on Chiado can be used to read and verify the Transfer event on Sepolia:

hashtag
Deployment:

  • HashiProver & ShoyuBashi are already deployed on a that can be called from the custom verification contract (this contract). In order to configure a custom oracle set for it is possible to deploy a new ShoyuBashi contract, inherit HashiProver contract and configure your oracle set in the ShoyuBashi contract.

  • In this example, pass to the custom verification contract constructor <ShoyuBashi contract address on Chiado, the ERC20 contract address on Sepolia, from which the Transfer event is emitted, chain ID of the ERC20Contract (Sepolia: 11155111)>

verifyForeignEvent explanation:

Inputs:

  • expectedRlpEncodedEvent : an bytes encoded format with the Event information: check out the in order to generate it starting from the Event topics and data.

  • proof: the HashiAPI retreived proof

Logic:

  • check that the proof chainID matches the origin chain chainId (Sepolia in this case).

  • The verifyForeignEvent function validates the event proof against the block header stored on Chiado, which was relayed from Sepolia.

  • check that rlpEncodedEvent returned from

Check verifyForeignEvent implementation .

Submitting the Proof

Once the contract is deployed on Chiado, you can submit the proof fetched from the API using the following command:

This will trigger the contract to verify the proof using the block header stored in Hashi from Sepolia. If the proof is valid, the rplEncodedEvent will be emitted.

hashtag
References

Reading foreign state

This example demonstrates the verification of the total supply of USDC on Optimism from Gnosis Chain using a storage proof. The proof will be fetched via HashiAPI and fed to a contract deployed on Gnosis Chain. The contract will use HashiProver to validate the proof against the last Optimism reported block header stored in Hashi.

Quick Start

The Quick Start guide will walk you through two core use cases of Hashi: verifying storage proofs or event proofs against a block header and pushing messages across chains. We’ll go through the core use cases with three examples:

  • verify a foreign event

  • read a foreign state via storage proofs

  • push a message to foreign chain.

The code can be found in. This repository serves as a template for developers to kickstart a Hashi project using Hardhat/Foundry.

Getting the Event Proof

The first step is to fetch the Event Proof from Sepolia. An important parameter is the block number which ensures that Hashi can verify the proof against the block header stored on Chiado. This is possible if the block header related the emitted event on the origin chain (Sepolia in this case) has already been propagated to the target chain (Chiado in this case). To check the block propagation status is possible to use the .

Fetching the Proof

Use the following curl command to fetch the account and storage proof for the following ERC20 Transaction : .

Setting the scene

What we have:

------

What we need:

  • The verification on Gnosis Chain chain of USDC total supply on Optimism.

How Hashi enables the Event verification:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import { HashiProver } from "./HashiProver.sol";

contract MockERC20Prover {

    event TransferEventVerified(uint256 indexed chainID, bytes rlpEncodedEvent);

    HashiProver public hashiProver;
    address public erc20Contract;
    uint256 public chainID;

    constructor(address shoyuBashi_, address erc20Contract_, uint256 chainID_) {
        hashiProver = HashiProver(shoyuBashi_);
        erc20Contract = erc20Contract_;
        chainID = chainID_;
    }

    function verifyTransferEvent(HashiProver.ReceiptProof calldata proof, bytes memory expectedRlpEncodedEvent) external {
        require(proof.chainId == chainID, "Invalid chain id"); 

        bytes memory rlpEncodedEvent = hashiProver.verifyForeignEvent(proof);

        require(keccak256(rlpEncodedEvent) == keccak256(expectedRlpEncodedEvent), "invalid event");
        emit TransferEventVerified(proof.chainId , rlpEncodedEvent);

        // TODO: Define custom logic here

    }
}
With HashiAPI: a offchain component used to retrieve the Storage Proof of a specific storage slot (the one holding the USDC total supply).
  • With HashiProver: a onchain contract provided by Hashi which can verify the block proof provided by HashiAPI. HashiProver must be deployed on the target chain (Gnosis Chain in this case) and can be used in a custom contract which can use the Event proof verification provided by HashiProver itself.

  • The proof will be verified using the latest block header that was relayed to Hashi from Optimism to Gnosis Chain (check the Hashi explorer).

  • Steps followed in this example:

    • Fetch the storage proof for the USDC total supply on Optimism.

    • Deploy a custom contract on Gnosis Chain that will verify the event proof using HashiProver.

    • Submitting the proof to the custom contract.

    Hashi templatearrow-up-right

    Pushing a message

    In this example, we’ll send a message from a contract on Sepolia to another contract on Chiado, using Hashi to relay and execute the message. The callback will be triggered and validated on Chiado using the provided onMessage function.

    HashiProver.verifyForeignEvent
    matches the expectedRlpEncodedEvent that we passed as the function argument. This is useful to check that the Event infos encoded in the proof just verified actually match the Event we expect.
  • If the proof is valid, the contract emits the TransferEventVerified event. You may define your own logic after the proof has been verified.

  • MockERC20Prover contract address on Chiadoarrow-up-right

  • HashiProver & ShoyuBashi addresses

  • list of networks
    helper script from Hashi-templatearrow-up-right
    herearrow-up-right
    RLP encode event helper scriptarrow-up-right
    MockERC20Prover contractarrow-up-right
    HashiProver contractarrow-up-right
    ShoyuBashi contractarrow-up-right
    // Example of submitting the proof in Solidity (or via web3 call)
    HashiProver.ReceiptProof memory proof = { /* fetched proof from curl command */ };
    MockERC20Prover.verifyTransferEvent(proof, expectedRlpEncodedEvent);

    • chainId: 11155111 (Sepolia).

    • txHash: The tx-hash that emitted the Transfer Event on Sepolia: tx-examplearrow-up-right.

    • logIndex: The log index for the event.

    • blockNumber: The block number on Sepolia, 7016999, which must have already been propagated to Chiado via Hashi for the proof to be valid. The block number must reference a block that appears after the block containing the event. In this case >

    This curl command / script returns the Event Proof for the Transfer Event on Sepolia, which will be used for verification on Chiado.

    hashtag
    References

    1. Receipt proof request scriptarrow-up-right

    Hashi Explorer
    tx-examplearrow-up-right
    curl --location --request POST 'https://jsonrpc.hashi-explorer.xyz/v1' \
    --header 'Content-Type: application/json' \
    --data-raw '{
      "id": 1,
      "jsonrpc": "2.0",
      "method": "hashi_getReceiptProof",
      "params": {
        "logIndex": 397,
        "blockNumber": 7016999,
        "chainId": 11155111,
        "transactionHash": "0x25a6a5c138f3b5a434a3a2b5d6bf7bdf97cb700bd7515f801ecfb71f1d965e7b"
      }
    }'
    import axios from "axios";
    
    const main = async () => {
      // fetch event proof from Hashi Prover
      console.log("Fetching event proof from Hashi prover...");
      const result = await axios.post(
        `http://jsonrpc.hashi-explorer.xyz:3000/v1`,
        {
          jsonrpc: "2.0",
          method: "hashi_getReceiptProof",
          params: {
            logIndex: 397,
            blockNumber: 7016999,
            chainId: 11155111,
            transactionHash: "0x25a6a5c138f3b5a434a3a2b5d6bf7bdf97cb700bd7515f801ecfb71f1d965e7b",
          },
          id: 1,
        },
        {
          headers: {
            "Content-Type": "application/json",
          },
        }
      );
      console.log("Event proof result", result.data.result.proof);
    };
    
    main();

    Verifying foreign event

    This example demonstrates the validation of a Transfer Event from an ERC-20 contract on the Sepolia network using an event proof on the Chiado network. The proof is retrieved via an API call and provided to a custom contract to be deployed on Chiado. The contract utilizes the HashiProver helper library to verify the proof against the latest block header recorded in Hashi.

    Writing your message sending contract

    Here’s how you write a contract on Sepolia that dispatches a message using Hashi’s Yaho contract. This message will be relayed to Chiado and trigger the callback.

    Explanation:

    • The contract interacts with the Yaho contract on Sepolia to dispatch a message to Chiado using dispatchMessageToAdapters.

    • The sendMessageToChiado function takes the target chain ID(10200 for Chiado), target address (ChiadoReceiver contract on Chiado), and the message to be sent.

    • The reporters, adapters, and threshold are passed into the Yaho contract for cross-chain message validation and relaying.

    Deployment:

    • Deploy this contract on Sepolia, passing the reporters, adapters, and threshold into the constructor.

    • A list of reporters and adapters contract can be found in:

    • Yaho contract address can be found in

    Implementing your callback

    Now, we’ll write the callback function on Chiado using the onMessage implementation you provided. This contract will handle the message sent from Sepolia.

    Explanation:

    • The onMessage function implements the callback, which is triggered when the Yaru contract on Chiado relays the message.

    • The function checks that the Yaru contract on Chiado is the sender, that the chainId, sender address, and threshold match what was expected, and that the list of adapters matches the expected hash.

    • After the validations pass, the message is decoded and stored in the contract.

    Deployment:

    • Deploy this contract on Chiado. Ensure that the values like SOURCE_SENDER_ADDRESS (Sepolia sender contract), EXPECTED_THRESHOLD, and ADAPTERS are correctly configured.

    • Yaru address with respect to the source chain can be found in

    • A list of adapters on the target chain can be found in

    Validating your statements

    Once the proof has been fetched, we can now verify it using a custom contract on Gnosis Chain. The contract will use the HashiProver helper library to check the proof against the block header stored on Gnosis Chain.

    Deploying the custom Verification Contract on Gnosis Chain

    Here is a contract that deployed on Gnosis Chain can be used to read and verify the USDC total supply on Optimism:

    hashtag
    Deployment:

    Waiting for the cross-chain execution

    Here’s what happens after you send the message:

    1. Message Dispatch on Sepolia:

      • When sendMessageToChiado is called on the SepoliaSender contract, the message is dispatched through Yaho on Sepolia.

    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.0;
    
    interface IYaho {
        function dispatchMessageToAdapters(
            uint256 targetChainId,
            uint256 threshold,
            address targetAddress,
            bytes memory data,
            address[] memory reporters,
            address[] memory adapters
        ) external;
    }
    
    contract SepoliaSender {
        IYaho public yaho = IYaho(0x21eAB033C7D2DF6A67AeF6C5Bda9A7F151eB9f52);  // Yaho address on Sepolia
        uint256 public threshold;
        address[] public reporters;
        address[] public adapters;
    
        constructor(address _yaho, uint256 _threshold, address[] memory _reporters, address[] memory _adapters) {
            threshold = _threshold;
            reporters = _reporters;
            adapters = _adapters;
        }
    
        // Function to send a cross-chain message to Chiado
        function sendMessageToChiado(
            uint256 targetChainId, 
            address targetAddress, 
            string memory _message
        ) public {
            bytes memory data = abi.encode(_message);
            yaho.dispatchMessageToAdapters(targetChainId, threshold, targetAddress, data, reporters, adapters);
        }
    }
    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.0;
    
    contract ChiadoReceiver {
        /// @dev Yaru contract on the Chiado w.r.t the Sepolia
        address public YARU = 0xBA9165973963a6E5608f03b9648c34A737E48f68;  
        /// @dev Source chain ID of Sepolia
        uint256 public SOURCE_CHAIN_ID = 11155111;
        /// @dev Expected threshold to be met by the adapters
        uint256 public EXPECTED_THRESHOLD;
        /// @dev Sender SepoliaSender contract address
        address public SOURCE_SENDER_ADDRESS;
        /// @dev hash of adapters address 
        bytes32 public ADAPTERS_HASH;
        /// @dev message that is last received from sender contract's sendMessageToTarget function
        string public lastReceivedMessage;
        
    
        constructor(uint256 expectedThreshold, address sourceSender, address[] memory adapters){
            EXPECTED_THRESHOLD = expectedThreshold;
            SOURCE_SENDER_ADDRESS = sourceSender;
            ADAPTERS_HASH = keccak256(abi.encodePacked(adapters));
        }
    
        /// @dev Function that gets triggered when the message is relayed, called by Yaru contract
        /// @param chainId chainId of the chain where message is sending from
        /// @param sender sender contract
        /// @param threshold threshold of the message that should be met by adapters
        /// @param adapters an array of adapters to check the threshold with
        /// @param data abi-encoded message
        /// @return 
        function onMessage(
            uint256, /*messageId*/
            uint256 chainId,
            address sender,
            uint256 threshold,
            address[] memory adapters,
            bytes memory data
        ) external returns (bytes memory) {
            require(msg.sender == YARU, "only called by Yaru");
            require(chainId == SOURCE_CHAIN_ID, "invalid source chain ID");
            require(threshold == EXPECTED_THRESHOLD, "invalid number of threshold");
            require(sender == SOURCE_SENDER_ADDRESS, "invalid sender address from source chain");
            require( keccak256(abi.encodePacked(adapters)) == ADAPTERS_HASH, "invalid adapters hash");
        
            // Decode the message and store it
            (string memory message) = abi.decode(data, (string));
            lastReceivedMessage = message;
    
            return "";
        }
    }
    7016999arrow-up-right
    7016956arrow-up-right

    The message is sent with the targetChainId, threshold, reporters, and adapters, and the data (in this case, the message string).

  • Oracles and Relaying:

    • Hashi’s oracles pick up the message and relay it to Chiado.

    • The oracles validate the message and check the block headers for consensus.

  • Message Receipt and Callback on Chiado:

    • On Chiado, the Yaru contract receives the message and triggers the onMessage function on the ChiadoReceiver contract.

    • The callback validates the message by ensuring that the message is from the expected Yaru contract, the source chain, and the correct sender.

    • After validation, the message is decoded and stored in the contract.


  • hashtag
    Example Scenario

    • You deploy SepoliaSender on Sepolia and configure it with the appropriate Yaho contract and oracle parameters.

    • You deploy ChiadoReceiver on Chiado to handle the incoming message and validate it against Hashi’s oracle system.

    • When you call sendMessageToChiado from SepoliaSender, the message is dispatched to Chiado, where the callback function processes it and updates the state.

  • HashiProver & ShoyuBashi are already deployed on a list of networks that can be called from the custom verification contract (this contract). In order to configure a custom oracle set for it is possible to deploy a new ShoyuBashi contract, inherit HashiProver contract and configure your oracle set in the ShoyuBashi contract.

  • In this example, pass to the custom verification contract constructor <ShoyuBashi contract address on Gnosis Chain>

  • readTotalSupply explanation:

    Inputs:

    • proof: the HashiAPI retreived storage proof.

    Logic:

    • check that the proof chainID matches the origin chain chainID (Optimism in this case).

    • check that the USDC contract address.

    • check that storage key match the expected value.

    • This contract uses HashiProver to verify on Gnosis Chain the storage proof coming from Optimism fetched earlier.

    • It checks that the proof is from Optimism (chain ID 10), and that the USDC contract address and storage key match the expected values.

    • The verifyForeignStorage function validates the proof against the block header stored on Gnosis Chain, which was relayed from Optimism.

    • If the proof is valid, the contract emits the total supply of USDC on Optimism.

    Check verifyForeignStorage implementation herearrow-up-right.

    Submitting the Proof

    Once the contract is deployed on Gnosis Chain, you can submit the proof fetched from the API using the following command:

    This will trigger the contract to verify the proof using the block header stored in Hashi from Optimism. If the proof is valid, the total supply of USDC will be emitted.

    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.20;
    
    import { HashiProver } from "https://github.com/gnosis/hashi/blob/feat/hashi-prover/packages/evm/contracts/prover/HashiProver.sol";
    
    contract UsdcTotalSupplyReader is HashiProver {
        event UsdcTotalSupply(uint256 totalSupply);
    
        constructor(address shoyuBashi) HashiProver(shoyuBashi) {}
    
        function readTotalSupply(HashiProver.AccountAndStorageProof calldata proof) external {
            require(proof.chainId == 10, "Invalid chain id"); // Optimism
            require(proof.account == 0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85, "Invalid account");
            require(proof.storageKeys.length == 1 && proof.storageKeys[0] == bytes32(uint256(0xb)), "Invalid storage key");
    
            // Verify the proof against the latest block header stored in Hashi
            uint256 totalSupply = uint256(bytes32(verifyForeignStorage(proof)[0]));
            emit UsdcTotalSupply(totalSupply);
        }
    }
    // Example of submitting the proof in Solidity (or via web3 call)
    HashiProver.AccountAndStorageProof memory proof = { /* fetched proof from curl command */ };
    usdcTotalSupplyReader.readTotalSupply(proof);

    Threshold must equal to expectedThreshold in Receiver contract.

    Oracles
    Oracles
    Yaho & Yaru
    Yaho & Yaru