Fraxtal Merkle Oracles (Fraxtal-MOs) are a class of oracles that utilize the eth_getProof rpc method to proof L1 State on Ethereum L2s such as Fraxtal.
Overview
Fraxtal-MOs utilize 4 Key contracts in order to verify and transport state.
Contract which verifies a given blockheader against the L1 Blockhash relayed via the FraxchainL1Block contract. This contract is responsible for storing the stateroot hash as well as the L1 timestamp.
Contract which performs the state root verification. Given a merkle proof, constructed off chain via the eth_getProof rpc method. This contract will verify the and extract the storage slot values "proofed" onto the L2 blockchain. These "proofed" values are then relayed to the oracle itself.
The process of relaying these "proofed" values is trustless in the sense that anyone may submit a valid merkle proof corresponding to the pre-approved slots for a given Ethereum L1 address.
Contract which accepts "proofed" L1 data from the MerkleProofPriceSoure contract.
In the case of sFrax and other ERC4626 vaults. These "proofed" values define the current slope of the value accrual function of the vault token on the L1.
These oracles expose the following funtions in order to allow the user to query the price of the asset in question:
/// @dev Adheres to chainlink's AggregatorV3Interface `latestRoundData()`/// @return _roundId The l1Block corresponding to the last time the oracle was proofed/// @return _answer The price of Sfrax in frax/// @return _startedAt The L1 timestamp corresponding to most recent proof/// @return _updatedAt Equivalent to `_startedAt`/// @return _answeredInRound Equivalent to `_roundId`functionlatestRoundData()externalviewreturns (uint80_roundId,int256_answer,uint256_startedAt,uint256_updatedAt,uint80_answeredInRound )
/// @return _pricePerShare The current exchange rate of the vault token /// denominated in the underlying vault assetfunctionpricePerShare() publicviewreturns (uint256_pricePerShare);
Architecture
The Process of transporting/proving the L1 data onto the L2:
Step 1: Prove the L1 blockHeader on the L2
Step 2: Submit the Storage proof for a predefined L1 address and Slots
Demo Client
The following code should serve as an example of how to generate the function arguments that are accepted by the fraxtal smart contracts detailed above.
let blockToProof ="0x"+blockL1.toHexString().substring(2).replace(/^0+/,"");let sfrax_proof =awaitmainnetProvider.send("eth_getProof", [// L1 address to generate proofs forSFRAX_MAINNET,// Slots to proof ["0x0000000000000000000000000000000000000000000000000000000000000002","0x0000000000000000000000000000000000000000000000000000000000000009","0x0000000000000000000000000000000000000000000000000000000000000008","0x0000000000000000000000000000000000000000000000000000000000000006","0x0000000000000000000000000000000000000000000000000000000000000007" ], blockToProof ]);// Format the proof info returned from `eth_getProof`let proof:Proof= {} asProof;proof._accountProofSfrax =sfrax_proof.accountProof;proof._storageProofTotalSupply =sfrax_proof.storageProof[0].proof;proof._storageProofTotalAssets =sfrax_proof.storageProof[1].proof;proof._storageProofLastDist =sfrax_proof.storageProof[2].proof;proof._storageProofRewardsPacked =sfrax_proof.storageProof[3].proof;proof._storageProofRewardsCycleAmount =sfrax_proof.storageProof[4].proof;let txn =awaitproover.addRoundDataSfrax(SFRAX_L2_ORACLE,blockL1.toString(), proof )