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
}
}}
Submitted on: 2025-10-31 10:52:11
Comments
Log in to comment.
No comments yet.