For a more comprehensive, step-by-step guide with specific implementation details, please refer to our Ethereum <> Starknet Yet Another Bridge in-depth tutorial at: https://github.com/HerodotusDev/yab-herodotus/tree/main
This tutorial provides an overview of using Storage Proofs to create a secure bridge between EVM-compatible blockchain networks. We'll cover the core concepts and provide code examples for building a bridge using Storage Proofs and the FactsRegistry.
Prerequisites
Understanding of smart contracts and blockchain concepts
Familiarity with Solidity and JavaScript/TypeScript
Access to the Storage Proof API
Step 1: Identify the Contract Storage Layout
First, determine the storage layout of the contract you want to prove. Let's say we're interested in a transfers mapping:
mapping(bytes32=> struct Transfer) public transfers;
To obtain the storage layout:
Use a Solidity storage layout tool or plugin
Or use an online service like storage.herodotus.dev
Note the slot number for the transfers mapping from the output.
Step 2: Mapping Slot Index
For mappings, we need to calculate the exact storage slot for a specific key. Here's a generic JavaScript function to do this:
constTRANSFERS_SLOT=5; // obtained from storage layoutconstkey='0x1234...'; // your specific keyconstslot=getSlot(TRANSFERS_SLOT, key);console.log('Calculated slot:', slot);
Step 3: Request a Storage Proof
With the correct storage slot, request a Storage Proof. Here's a generic example using fetch:
To use the Storage Proof in your bridge contract, implement a verification function using the FactsRegistry. Here's a Solidity example:
pragmasolidity ^0.8.0;import"@openzeppelin/contracts/token/ERC20/IERC20.sol";interface IFactsRegistry {functionverifyStorage(address account,uint256 blockNumber,bytes32 slot,bytesmemory storageSlotTrieProof ) externalviewreturns (bytes32 slotValue);}contract Bridge { IFactsRegistry public factsRegistry; IERC20 public token;uint256public sourceChainId;addresspublic sourceTokenContract;constructor(address_factsRegistry,address_token,uint256_sourceChainId,address_sourceTokenContract) { factsRegistry =IFactsRegistry(_factsRegistry); token =IERC20(_token); sourceChainId = _sourceChainId; sourceTokenContract = _sourceTokenContract; }functionbridgeTransfer(bytes32 transferId,uint256 blockNumber,bytesmemory storageSlotTrieProof ) external {bytes32 slot =keccak256(abi.encode(transferId,uint256(5))); // Assuming transfers mapping is at slot 5bytes32 slotValue = factsRegistry.verifyStorage( sourceTokenContract, blockNumber, slot, storageSlotTrieProof ); (address recipient,uint256 amount) =decodeSlotValue(slotValue);require(recipient !=address(0),"Invalid recipient");require(amount >0,"Invalid amount");// Perform bridge logic token.transfer(recipient, amount);emitTransferBridged(transferId, recipient, amount); }functiondecodeSlotValue(bytes32 slotValue) internalpurereturns (address recipient,uint256 amount) {// Implement decoding logic based on your data structure// This is a simplified example recipient =address(uint160(uint256(slotValue))); amount =uint256(slotValue) >>160; }eventTransferBridged(bytes32indexed transferId, addressindexed recipient, uint256 amount);}
This implementation uses the verifyStorage function from the FactsRegistry to verify the Storage Proof on-chain. The bridgeTransfer function takes the necessary parameters to verify the storage slot and execute the bridge logic.
Conclusion
By following these steps and using the FactsRegistry, you can create a bridge that uses Storage Proofs to securely transfer and verify data between EVM-compatible chains. This approach enhances security by cryptographically proving the state of one chain on another, reducing trust assumptions compared to traditional oracle-based bridges.
Remember to adapt these examples to your specific environment, contract structure, and chosen libraries. Always thoroughly test your implementation and consider potential edge cases specific to your use case.