Skip to main content
State sync is the mechanism by which contract state on Ethereum is propagated to the Bor chain. It allows dApps on Polygon PoS to read and react to events that originate on Ethereum.

How state sync works

State transfer from Ethereum to Bor happens through a system call:
  1. A contract on Ethereum calls syncState on the StateSender.sol contract, emitting a StateSynced event.
  2. Heimdall validators listen for StateSynced events. One validator sends a state-sync transaction to Heimdall.
  3. Once the transaction is included in a Heimdall block, it is added to the pending state-sync list.
  4. After every sprint on Bor (currently 16 blocks), the Bor node fetches pending state-sync records from Heimdall via API.
  5. Bor commits these records at the start of each sprint by calling onStateReceive on the target contract. This is a system call from address 2^160-2.
The receiver contract on Bor must implement IStateReceiver. The onStateReceive function can only be called by StateReceiver.sol at address 0x0000000000000000000000000000000000001001.

State sender contract

Source: StateSender.sol The StateSender contract on Ethereum exposes a syncState function:
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;
}
Calling syncState emits the following event:
/**
 * 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 is emitted, Heimdall listens for it. After 2/3+ validators agree, the state-sync record is added to Heimdall state. Bor fetches and applies it at the start of the next sprint.

State receiver interface on Bor

The target contract on Bor must implement the following interface:
// IStateReceiver represents interface to receive state
interface IStateReceiver {
  function onStateReceive(uint256 stateId, bytes calldata data) external;
}
Only StateReceiver.sol at address 0x0000000000000000000000000000000000001001 is permitted to call onStateReceive on target contracts.

System calls

The state sync commit on Bor uses a system call. Only the system address 2^160-2 can make a system call. Bor calls it internally with the system address as msg.sender, which changes contract state and updates the state root for the block, without requiring a user-initiated transaction. This is inspired by EIP-210.

State-sync logs and Bor block receipts

Events emitted by system calls are handled differently from normal logs. Bor produces a synthetic transaction and receipt for each state-sync event, so clients can query these events through standard Ethereum JSON-RPC methods. The transaction hash is derived from:
keccak256("matic-bor-receipt-" + block number + block hash)
This synthetic transaction does not affect consensus logic. eth_getBlockByNumber, eth_getTransactionReceipt, and eth_getLogs include state-sync logs from the derived transaction. Note that the block bloom filter does not include state-sync log entries, and the derived transaction is not included in transactionRoot or receiptRoot.