L1OracleRelay

Description:

Smart contract deployed on Ethereum with Factory, Oracle features.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "src/L1OracleRelay.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
// Copyright (c) 2025 Maseer LTD
//
// This file is subject to the Business Source License 1.1.
// You may not use this file except in compliance with the License.
//
// You may obtain a copy of the License at:
// https://github.com/maseer-finance/maseer-oracles/blob/master/LICENSE
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
pragma solidity ^0.8.28;

interface AggregatorV3Interface {
    function latestRoundData()
        external
        view
        returns (
            uint80 roundId,
            int256 answer,
            uint256 startedAt,
            uint256 updatedAt,
            uint80 answeredInRound
        );
}

interface IL1CrossDomainMessenger {
    function sendMessage(
        address _target,
        bytes memory _message,
        uint32 _minGasLimit
    ) external payable;

    /**
     * @notice Computes the amount of gas required to guarantee that a given message will be
     *         received on the other side without running out of gas. Guarantees that the
     *         message cannot be reverted due to running out of gas.
     * @param _message Message to compute the amount of required gas for.
     * @param _minGasLimit Minimum desired gas limit when message goes to target.
     * @return Amount of gas required to guarantee message receipt.
     */
    function baseGas(bytes memory _message, uint32 _minGasLimit) external pure returns (uint64);
}

/**
 * @title L1OracleRelay
 * @notice Relays oracle price data from L1 mainnet to Base L2
 * @dev Reads data from MaseerAggregatorLens and sends it to Base via L1CrossDomainMessenger
 *
 * Architecture:
 * L1: MaseerAggregatorLens -> L1OracleRelay -> L1CrossDomainMessenger
 * L2: L2CrossDomainMessenger -> L2Oracle (stores data)
 *
 * Key Features:
 * - Fully permissionless (anyone can trigger updates)
 * - Immutable configuration (trustless)
 * - Fixed gas limit for L2 execution
 * - Emits events for monitoring
 */
contract L1OracleRelay {
    /// @notice Thrown when an invalid address (zero address) is provided
    error InvalidAddress();

    /// @notice Emitted when price data is relayed to L2
    event PriceRelayed(
        uint80 indexed roundId,
        int256 indexed answer,
        uint256 timestamp,
        uint32 gasLimit
    );

    /// @notice The L1 oracle to read price data from
    AggregatorV3Interface public immutable l1Oracle;

    /// @notice The L1CrossDomainMessenger contract for Base
    IL1CrossDomainMessenger public immutable messenger;

    /// @notice The L2 oracle contract address on Base that will receive the data
    address public immutable l2Oracle;

    /// @notice Gas limit for L2 execution of updatePrice (storage writes + event)
    /// @dev Set to 200,000 which provides ~50% margin over estimated 130k gas needed
    uint32 public constant MIN_GAS_LIMIT = 200_000;

    /**
     * @notice Initializes the relay contract
     * @param l1Oracle_ Address of the MaseerAggregatorLens on L1
     * @param messenger_ Address of the L1CrossDomainMessenger for Base (0x866E82a600A1414e583f7F13623F1aC5d58b0Afa on mainnet)
     * @param l2Oracle_ Address of the L2Oracle contract on Base
     */
    constructor(
        address l1Oracle_,
        address messenger_,
        address l2Oracle_
    ) {
        if (l1Oracle_ == address(0)) revert InvalidAddress();
        if (messenger_ == address(0)) revert InvalidAddress();
        if (l2Oracle_ == address(0)) revert InvalidAddress();

        l1Oracle = AggregatorV3Interface(l1Oracle_);
        messenger = IL1CrossDomainMessenger(messenger_);
        l2Oracle = l2Oracle_;
    }

    /**
     * @notice Returns the required msg.value when calling relayPrice()
     * @dev For Base, L2 execution costs are covered by burning L1 gas, not by sending ETH.
     *      Since L2Oracle.updatePrice() is not payable, no ETH should be sent.
     *      Any ETH sent would remain in the messenger contract unused.
     * @return The required amount of ETH to send (0 wei)
     */
    function getRecommendedValue() public pure returns (uint256) {
        return 0; // No ETH needed - gas paid via L1 gas burning
    }

    /**
     * @notice Calculates the total L1 gas overhead for relaying an oracle update message to L2
     * @dev Uses the messenger's baseGas function to estimate the total L1 gas cost.
     *      This includes constant relay overhead, calldata costs, and dynamic EIP-150 overhead.
     *
     *      IMPORTANT: This is for cost estimation only. The value returned should NOT be passed
     *      to sendMessage(). The sendMessage() function automatically adds this overhead on top
     *      of the _minGasLimit parameter you provide.
     *
     * @return The estimated total L1 gas cost for sending the oracle update message
     */
    function calculateMinimumGas() public pure returns (uint64) {
        // For a 164-byte message with MIN_GAS_LIMIT of 200,000:
        // baseGas returns approximately 515,958 gas
        // This includes:
        // - Constant relay overhead
        // - Calldata costs (164 bytes)
        // - Dynamic EIP-150 overhead
        // - MIN_GAS_LIMIT for L2 execution
        return 515_958;
    }

    /**
     * @notice Relays the latest oracle price data to L2
     * @dev Can be called by anyone. Reads from l1Oracle and sends to l2Oracle via messenger.
     *      The L2 transaction will be executed with MIN_GAS_LIMIT (200k) gas. The messenger
     *      automatically adds relay overhead on top of this value for the L1 transaction cost.
     *
     *      Gas Payment: For Base/Optimism Bedrock, L2 execution costs are paid by burning
     *      L1 gas during the transaction, NOT by sending ETH. The function is non-payable
     *      to prevent accidental ETH loss (since L2Oracle.updatePrice() cannot accept ETH).
     *
     *      Gas Cost Estimate (paid as L1 transaction gas fees):
     *      - L1 gas for relayPrice() execution: ~100-150k gas
     *      - L1 gas burned for L2 execution: ~515k gas (via baseGas calculation)
     *      - Total L1 gas needed: ~615-665k gas
     *      - At 20 gwei: ~0.012-0.013 ETH in gas fees
     *
     *      Example via Etherscan:
     *      1. Navigate to this contract on Etherscan
     *      2. Connect your wallet
     *      3. Call relayPrice() with:
     *         - Gas Limit: ~700,000 (auto-estimated should work)
     *         - Ensure wallet has ~0.015-0.02 ETH for gas fees
     *
     * @custom:emits PriceRelayed event with the relayed data
     */
    function relayPrice() external {

        // Read latest data from L1 oracle
        (
            uint80 roundId,
            int256 answer,
            uint256 startedAt,
            uint256 updatedAt,
            uint80 answeredInRound
        ) = l1Oracle.latestRoundData();

        // Encode the updatePrice call for L2
        bytes memory message = abi.encodeWithSignature(
            "updatePrice(uint80,int256,uint256,uint256,uint80)",
            roundId,
            answer,
            startedAt,
            updatedAt,
            answeredInRound
        );

        // Send message to L2 via CrossDomainMessenger
        // Pass MIN_GAS_LIMIT directly - the messenger will automatically add relay overhead
        messenger.sendMessage(l2Oracle, message, MIN_GAS_LIMIT);

        emit PriceRelayed(roundId, answer, updatedAt, MIN_GAS_LIMIT);
    }
}
"
    }
  },
  "settings": {
    "remappings": [
      "forge-std/=lib/forge-std/src/",
      "maseer-one/=lib/maseer-one/src/",
      "@morpho-blue-oracles/=lib/morpho-blue-oracles/src/",
      "prb-math/=lib/prb-math/",
      "@openzeppelin/contracts/=lib/morpho-blue-oracles/lib/openzeppelin-contracts/contracts/",
      "ds-test/=lib/morpho-blue-oracles/lib/forge-std/lib/ds-test/src/",
      "erc4626-tests/=lib/morpho-blue-oracles/lib/openzeppelin-contracts/lib/erc4626-tests/",
      "morpho-blue-oracles/=lib/morpho-blue-oracles/src/",
      "morpho-blue/=lib/morpho-blue-oracles/lib/morpho-blue/",
      "openzeppelin-contracts/=lib/morpho-blue-oracles/lib/openzeppelin-contracts/"
    ],
    "optimizer": {
      "enabled": true,
      "runs": 21000
    },
    "metadata": {
      "useLiteralContent": false,
      "bytecodeHash": "ipfs",
      "appendCBOR": true
    },
    "outputSelection": {
      "*": {
        "*": [
          "evm.bytecode",
          "evm.deployedBytecode",
          "devdoc",
          "userdoc",
          "metadata",
          "abi"
        ]
      }
    },
    "evmVersion": "prague",
    "viaIR": false
  }
}}

Tags:
Factory, Oracle|addr:0x204ec04cb96c9ea64bf9330c03093e291cc1f63f|verified:true|block:23693000|tx:0x7d1ed79f8861330fc3be0693c9596f667bc3e9281de449ef4ac0bc091c42c482|first_check:1761904331

Submitted on: 2025-10-31 10:52:11

Comments

Log in to comment.

No comments yet.