Ethereum to Shibarium

In order to natively read Ethereum data from the Shibarium EVM chain, the mechanism utilized is known as 'State Sync'. This process facilitates the transfer of arbitrary data from the Ethereum chain to the Shibarium chain. The essential steps involved include validators on the Heimdall layer listening for a specific event called 'StateSynced' from a Sender contract. Once this event is detected, the associated data is written to the Receiver contract. More details about this mechanism can be found here.

To enable this functionality, it is imperative to map the Sender and Receiver contracts on Ethereum. The StateSender.sol contract must be aware of each sender and receiver, and a mapping request can be initiated here.

In the subsequent walkthrough, we will deploy a Sender contract on Goerli (Ethereum testnet) and a Receiver contract on Puppynet (Shibarium's testnet). Following that, we will transmit data from the Sender and read the data on the Receiver using web3 calls in a node script.

  1. Deploy Sender Contract: The Sender contract's primary function is to call the 'syncState' function on the StateSender contract, which is Bone's state syncer contract. The deployed addresses for Goerli and Ethereum Mainnet are as follows:

    • Goerli: 0xEAa852323826C71cd7920C3b4c007184234c3945

    • Ethereum Mainnet: 0x28e4F3a7f651294B9564800b2D01f35189A5bFbE

    The Sender.sol contract code is as follows:

    // Sender.sol
    
    pragma solidity ^0.5.11;
    
    contract IStateSender {
      function syncState(address receiver, bytes calldata data) external;
      function register(address sender, address receiver) public;
    }
    
    contract sender {
      address public stateSenderContract = 0xEAa852323826C71cd7920C3b4c007184234c3945;
      address public receiver = 0x83bB46B64b311c89bEF813A534291e155459579e;
    
      uint public states = 0;
    
      function sendState(bytes calldata data) external {
        states = states + 1 ;
        IStateSender(stateSenderContract).syncState(receiver, data);
      }
    }

    Use Remix to deploy the contract and note the address and ABI.

  2. Deploy Receiver Contract: The Receiver contract is invoked by a Validator when the 'StateSynced' event is emitted. The Validator triggers the 'onStateReceive' function on the Receiver contract to submit the data.

    The Receiver.sol contract code is as follows:

    // receiver.sol
    
    pragma solidity ^0.5.11;
    
    // IStateReceiver represents interface to receive state
    interface IStateReceiver {
      function onStateReceive(uint256 stateId, bytes calldata data) external;
    }
    
    contract receiver {
      uint public lastStateId;
      bytes public lastChildData;
    
      function onStateReceive(uint256 stateId, bytes calldata data) external {
        lastStateId = stateId;
        lastChildData = data;
      }
    }

    Deploy Receiver.sol on Puppynet and note the address and ABI.

  3. Mapping Sender and Receiver: Use the deployed addresses or deploy custom contracts and request a mapping here.

  4. Sending and Receiving Data: Write a node script to send arbitrary hex bytes, receive them on Puppynet, and interpret the data.

    The complete script is as follows:

    // test.js
    
    const Web3 = require('web3');
    const Network = require("@maticnetwork/meta/network");
    
    const network = new Network('testnet', 'puppynet');
    
    const main = new Web3(network.Main.RPC);
    const matic = new Web3(network.Matic.RPC);
    
    // ... (private key, contract addresses, and ABIs)
    
    // Data to sync
    function getData(string) {
      let data = matic.utils.asciiToHex(string);
      return data;
    }
    
    // Send data via Sender
    async function sendData(data) {
      let r = await sender.methods
        .sendState(getData(data))
        .send({
          from: main.eth.accounts.wallet[0].address,
          gas: 8000000
        });
      console.log('Sent data from root, ', r.transactionHash);
    }
    
    // ... (functions to check sender and receiver)
    
    async function test() {
      await sendData('Hello World!');
      await checkSender();
      // Add a timeout here to allow a time gap for the state to sync
      await checkReceiver();
    }
    
    test();

    Run the script using node test. A successful execution will provide an output similar to:

    Sent data from root 0x4f64ae4ab4d2b2d2dc82cdd9ddae73af026e5a9c46c086b13bd75e38009e5204
    Number of states sent from sender: 1
    Last state id: 453 and last data: 0x48656c6c6f20576f726c642021
    Interpreted data: Hello World!

Last updated