How does State Sync work?

State management sends the state from the Ethereum chain to the Bor chain. It is called state-sync.

State transfer from Ethereum to Bor happens through system call.

For example, a user deposits USDC to the deposit manager on Ethereum, validators listen to those events and validates and store them in Heimdall state. Bor gets the latest state-sync records and updates the Bor state (mints equal amount of USDC on Bor) using a system call.

State sender


To sync state, the contract calls following method state sender contract on Ethereum chain.

contract StateSender {
* Emits `stateSynced` events to start sync process on Ethereum chain
* @param receiver Target contract on Bor chain
* @param data Data to send
function syncState (
address receiver,
bytes calldata data
) external;

receiver contract must be present on the child chain, which receives state data once the process is complete. syncState emits StateSynced event on Ethereum, which is the following:

* Emits `stateSynced` events to start sync process on Ethereum chain
* @param id State id
* @param contractAddress Target contract address on Bor
* @param data Data to send to Bor chain for Target contract address
event StateSynced (
uint256 indexed id,
address indexed contractAddress,
bytes data

Once the StateSynced event emitted on the stateSender contract on the Ethereum chain, Heimdall listens to those events and adds to the Heimdall state after 2/3+ validators agree on the.

After every sprint (currently 64 blocks on Bor), Bor fetches new state-sync record and updates the state using a system call. Here is the code for the same:

During commitState, Bor executes onStateReceive, with stateId and data as args, on target contract.

State receiver interface on Bor

receiver contract on Bor chain must implement following interface.

// IStateReceiver represents interface to receive state
interface IStateReceiver {
function onStateReceive(uint256 stateId, bytes calldata data) external;

Only 0x0000000000000000000000000000000000001001StateReceiver.sol, must be allowed to call onStateReceive function on target contract.

System call

Only system address, 2^160-2, allows making a system call. Bor calls it internally with the system address as msg.sender. It changes the contract state and updates the state root for a particular block. Inspired from and

System call is helpful to change state to contract without making any transaction.

State-sync logs and Bor Block Receipt

Events emitted by system calls are handled in a different way than normal logs. Here is code:

Bor produces a new tx/receipt just for the client and includes all logs for state-sync in it. Tx hash is derived from block number and block hash (last block at that sprint):

keccak256("matic-bor-receipt-" + block number + block hash)

This doesn't change any consensus logic, only client changes. eth_getBlockByNumber , eth_getTransactionReceipt and eth_getLogs include state-sync logs with derived. Note that the bloom filter on the block doesn't include inclusion for state-sync logs. It also doesn't include derived tx in transactionRoot or receiptRoot