# Fraxtal Merkle Proof Oracles

Fraxtal Merkle Oracles (Fraxtal-MOs) are a class of oracles that utilize the [`eth_getProof`](https://docs.alchemy.com/reference/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.

[**FraxchainL1Block**](https://fraxscan.com/address/0x4200000000000000000000000000000000000015#code)

Predeploy contract which serves as a registry of L1 block hashes on the L2.

[**StateRootOracle**](https://fraxscan.com/address/0xeD403d48e2bC946438B5686AA1AD65056Ccf9512)

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.

[**MerkleProofPriceSource**](https://fraxscan.com/address/0xe25d8aaa6df41b94a415ee39ccee0df6673b9bdb#code)

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.

[**FraxtalERC4626TransportOracle**](https://fraxscan.com/address/0x1b680f4385f24420d264d78cab7c58365ed3f1ff#readContract)

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:

```solidity
    /// @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`
    function latestRoundData()
        external
        view
    returns (
        uint80 _roundId, 
        int256 _answer, 
        uint256 _startedAt, 
        uint256 _updatedAt, 
        uint80 _answeredInRound
    )

```

```solidity
/// @return _pricePerShare The current exchange rate of the vault token 
///                        denominated in the underlying vault asset
function pricePerShare() public view returns (uint256 _pricePerShare);
```

#### Architecture

The Process of transporting/proving the L1 data onto the L2:

Step 1: `Prove the L1 blockHeader on the L2`

![Screenshot 2024-09-23 at 11.04.48 AM](https://hackmd.io/_uploads/HkR4EZyAR.png)

Step 2: `Submit the Storage proof for a predefined L1 address and Slots`

![Screenshot 2024-09-23 at 11.05.00 AM](https://hackmd.io/_uploads/H1ih4-J0C.png)

#### 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.

Documentation surrounding the RPC methods: [`eth_getBlockByNumber`](https://docs.alchemy.com/reference/eth-getblockbynumber) && [`eth_getProof`](https://docs.alchemy.com/reference/eth-getproof)

**To generate the L1 Block Header**

```typescript
async function getHeaderFromBlock(provider, blockL1) {
    let block = await provider.send("eth_getBlockByNumber", [blockL1, false])
    let headerFields = [];
    headerFields.push(block.parentHash);
    headerFields.push(block.sha3Uncles);
    headerFields.push(block.miner);
    headerFields.push(block.stateRoot);
    headerFields.push(block.transactionsRoot);
    headerFields.push(block.receiptsRoot);
    headerFields.push(block.logsBloom);
    headerFields.push(block.difficulty);
    headerFields.push(block.number);
    headerFields.push(block.gasLimit);
    headerFields.push(block.gasUsed);
    headerFields.push(block.timestamp);
    headerFields.push(block.extraData);
    headerFields.push(block.mixHash);
    headerFields.push(block.nonce);
    headerFields.push(block.baseFeePerGas);
    if (block.withdrawalsRoot) {
        headerFields.push(block.withdrawalsRoot);
    }
    if (block.blobGasUsed) {
        headerFields.push(block.blobGasUsed);
    }
    if (block.excessBlobGas) {
        headerFields.push(block.excessBlobGas);
    }
    if (block.parentBeaconBlockRoot) {
        headerFields.push(block.parentBeaconBlockRoot);
    }
    convertHeaderFields(headerFields);
    let header = ethers.utils.RLP.encode(headerFields);
    return header
}
```

**To generate a storage proof**

```typescript
        let blockToProof = "0x"+blockL1.toHexString().substring(2).replace(/^0+/, "");

        let sfrax_proof = await mainnetProvider.send("eth_getProof", 
        [
            // L1 address to generate proofs for
            SFRAX_MAINNET,
            // Slots to proof 
            [
                "0x0000000000000000000000000000000000000000000000000000000000000002",
                "0x0000000000000000000000000000000000000000000000000000000000000009",
                "0x0000000000000000000000000000000000000000000000000000000000000008",
                "0x0000000000000000000000000000000000000000000000000000000000000006",
                "0x0000000000000000000000000000000000000000000000000000000000000007"
            ], 
            blockToProof
        ]);
    
        // Format the proof info returned from `eth_getProof`
        let proof: Proof = {} as Proof;
        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 = await proover.addRoundDataSfrax(
            SFRAX_L2_ORACLE,
            blockL1.toString(),
            proof
        )
```

#### Deployed Contracts

| Description    | Oracle                                                                                                                       | MerkleProofPriceSource                                                                                                       |
| -------------- | ---------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| sFrax/Frax     | [`0x1b680f4385f24420d264d78cab7c58365ed3f1ff`](https://fraxscan.com/address/0x1b680f4385f24420d264d78cab7c58365ed3f1ff#code) | [`0xe25d8aaa6df41b94a415ee39ccee0df6673b9bdb`](https://fraxscan.com/address/0xe25d8aaa6df41b94a415ee39ccee0df6673b9bdb#code) |
| sfrxEth/frxEth | [`0xEE095b7d9191603126Da584a1179BB403a027c3A`](https://fraxscan.com/address/0xEE095b7d9191603126Da584a1179BB403a027c3A#code) | [`0xabca0b314d15B3e28F24AC0ee84A63001d1b44Db`](https://fraxscan.com/address/0xabca0b314d15B3e28F24AC0ee84A63001d1b44Db#code) |
| FPI/USD        | [`0x0f50beeE2d2506634b1e6230F3867e30763CbB02`](https://fraxscan.com/address/0x0f50beeE2d2506634b1e6230F3867e30763CbB02#code) | [`0x8fc7425Cd36D7e4605650198099e4539238e9c37`](https://fraxscan.com/address/0x8fc7425Cd36D7e4605650198099e4539238e9c37#code) |
