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.
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 should be initiated.
In the subsequent walkthrough, we will deploy a Sender contract on Sepolia (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.
-
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 Sepolia and Ethereum Mainnet are as follows:- Sepolia: 0x0844c0ca42F24972e89233F476B033F763C2355a
- Ethereum Mainnet: 0x3a122785bC4d951D132B2CAD31FC187D6DC7A21C
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 = 0x3a122785bC4d951D132B2CAD31FC187D6DC7A21C;
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.
-
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.
-
Mapping Sender and Receiver: Use the deployed addresses or deploy custom contracts and request a mapping here.
-
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!