Skip to content

Commit

Permalink
Synapse gas oracle v1 (#2295)
Browse files Browse the repository at this point in the history
* Scaffold V1 version of synapse gas oracle

* Add unit tests for getters and management

* GasOracle: track local native token price

* GasOracle: track remote gas data

* GasOracle: value estimation/conversion

* Add configuration script for GasOracle

* Update deploy/config workflows
  • Loading branch information
ChiTimesChi authored Mar 18, 2024
1 parent 91d68c3 commit c15ddf6
Show file tree
Hide file tree
Showing 11 changed files with 857 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"executorEOA": "0xD0D45E468ADF3aCB8A98391ff47267783220ba6F",
"gasOracleName": "SynapseGasOracleMock"
"gasOracleName": "SynapseGasOracleV1"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"eth_sepolia": {
"calldataPrice": 0,
"gasPrice": 10000000000,
"nativePrice": 1000000000000000000
},
"op_sepolia": {
"calldataPrice": 0,
"gasPrice": 100000000,
"nativePrice": 1000000000000000000
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"claimFeeBPS": 10,
"feeCollector": "0xD0D45E468ADF3aCB8A98391ff47267783220ba6F",
"gasOracleName": "SynapseGasOracleMock",
"gasOracleName": "SynapseGasOracleV1",
"threshold": 6,
"verifiers": [
"0x0E399F695d72033d598aC2911B8A5e9986d6cbaD",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

abstract contract SynapseGasOracleV1Events {
event CalldataPriceSet(uint256 indexed chainId, uint256 calldataPrice);
event GasPriceSet(uint256 indexed chainId, uint256 gasPrice);
event NativePriceSet(uint256 indexed chainId, uint256 nativePrice);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {ISynapseGasOracle} from "./ISynapseGasOracle.sol";

interface ISynapseGasOracleV1 is ISynapseGasOracle {
/// @notice Struct defining the gas data for the remote chain.
/// @param calldataPrice The price of 1 byte of calldata in the remote chain's wei.
/// @param gasPrice The gas price of the remote chain, in remote chain's wei.
/// @param nativePrice The price of the remote chain's native token in Ethereum Mainnet's wei.
struct RemoteGasData {
uint256 calldataPrice;
uint256 gasPrice;
uint256 nativePrice;
}

error SynapseGasOracleV1__NotRemoteChainId(uint256 chainId);
error SynapseGasOracleV1__NativePriceNotSet(uint256 chainId);
error SynapseGasOracleV1__NativePriceZero();

/// @notice Allows the contract owner to set the native token price of the local chain.
/// @dev Could only be called by the contract owner. Will revert if the native token price is 0.
/// @param nativePrice The price of the local chain's native token in Ethereum Mainnet's wei.
function setLocalNativePrice(uint256 nativePrice) external;

/// @notice Allows the contract owner to set the gas data for a remote chain.
/// @dev Could only be called by the contract owner.
/// Will revert if the native token price is 0, or if the chain id is not a remote chain id.
/// @param chainId The chain id of the remote chain.
/// @param data The gas data for the remote chain.
function setRemoteGasData(uint256 chainId, RemoteGasData memory data) external;

/// @notice Allows the contract owner to set the price of remote chain's calldata.
/// @dev Could only be called by the contract owner.
/// Will revert if the chain id is not a remote chain id, or if native token price for the chain is 0.
/// @param chainId The chain id of the remote chain.
/// @param calldataPrice The price of 1 byte of calldata in the remote chain's wei.
function setRemoteCallDataPrice(uint256 chainId, uint256 calldataPrice) external;

/// @notice Allows the contract owner to set the gas price of the remote chain.
/// @dev Could only be called by the contract owner.
/// Will revert if the chain id is not a remote chain id, or if native token price for the chain is 0.
/// @param chainId The chain id of the remote chain.
/// @param gasPrice The gas price of the remote chain, in remote chain's wei.
function setRemoteGasPrice(uint256 chainId, uint256 gasPrice) external;

/// @notice Allows the contract owner to set the price of the remote chain's native token.
/// @dev Could only be called by the contract owner.
/// Will revert if the chain id is not a remote chain id, or if the price is 0.
/// @param chainId The chain id of the remote chain.
/// @param nativePrice The price of the remote chain's native token in Ethereum Mainnet's wei.
function setRemoteNativePrice(uint256 chainId, uint256 nativePrice) external;

/// @notice Gets the price of the local chain's native token in Ethereum Mainnet's wei.
function getLocalNativePrice() external view returns (uint256);

/// @notice Gets the gas data for a remote chain.
/// @dev Will revert if the chain id is not a remote chain id.
/// @param chainId The chain id of the remote chain.
function getRemoteGasData(uint256 chainId) external view returns (RemoteGasData memory);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;

import {SynapseGasOracleV1Events} from "../events/SynapseGasOracleV1Events.sol";
import {ISynapseGasOracle, IGasOracle} from "../interfaces/ISynapseGasOracle.sol";
import {ISynapseGasOracleV1} from "../interfaces/ISynapseGasOracleV1.sol";

import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";

contract SynapseGasOracleV1 is Ownable, SynapseGasOracleV1Events, ISynapseGasOracleV1 {
uint256 internal _localNativePrice;
mapping(uint256 chainId => RemoteGasData data) internal _remoteGasData;

/// @dev Checks that the chain ID is not the local chain ID.
modifier onlyRemoteChainId(uint256 chainId) {
if (block.chainid == chainId) {
revert SynapseGasOracleV1__NotRemoteChainId(chainId);
}
_;
}

/// @dev Checks that the native token price is set for a remote chain ID.
modifier onlyNativePriceSet(uint256 chainId) {
if (_remoteGasData[chainId].nativePrice == 0) {
revert SynapseGasOracleV1__NativePriceNotSet(chainId);
}
_;
}

/// @dev Checks that the native token price is non-zero.
modifier onlyNonZeroNativePrice(uint256 nativePrice) {
if (nativePrice == 0) {
revert SynapseGasOracleV1__NativePriceZero();
}
_;
}

constructor(address owner_) Ownable(owner_) {}

// ════════════════════════════════════════════════ ONLY OWNER ═════════════════════════════════════════════════════

/// @inheritdoc ISynapseGasOracleV1
function setLocalNativePrice(uint256 nativePrice) external onlyOwner onlyNonZeroNativePrice(nativePrice) {
if (_localNativePrice != nativePrice) {
_localNativePrice = nativePrice;
emit NativePriceSet(block.chainid, nativePrice);
}
}

/// @inheritdoc ISynapseGasOracleV1
function setRemoteGasData(
uint256 chainId,
RemoteGasData memory data
)
external
onlyOwner
onlyRemoteChainId(chainId)
onlyNonZeroNativePrice(data.nativePrice)
{
_setRemoteCallDataPrice(chainId, data.calldataPrice);
_setRemoteGasPrice(chainId, data.gasPrice);
_setRemoteNativePrice(chainId, data.nativePrice);
}

/// @inheritdoc ISynapseGasOracleV1
function setRemoteCallDataPrice(
uint256 chainId,
uint256 calldataPrice
)
external
onlyOwner
onlyRemoteChainId(chainId)
onlyNativePriceSet(chainId)
{
_setRemoteCallDataPrice(chainId, calldataPrice);
}

/// @inheritdoc ISynapseGasOracleV1
function setRemoteGasPrice(
uint256 chainId,
uint256 gasPrice
)
external
onlyOwner
onlyRemoteChainId(chainId)
onlyNativePriceSet(chainId)
{
_setRemoteGasPrice(chainId, gasPrice);
}

/// @inheritdoc ISynapseGasOracleV1
function setRemoteNativePrice(
uint256 chainId,
uint256 nativePrice
)
external
onlyOwner
onlyRemoteChainId(chainId)
onlyNonZeroNativePrice(nativePrice)
{
_setRemoteNativePrice(chainId, nativePrice);
}

// ════════════════════════════════════════════════ ONLY MODULE ════════════════════════════════════════════════════

// solhint-disable no-empty-blocks
/// @inheritdoc ISynapseGasOracle
function receiveRemoteGasData(uint256 srcChainId, bytes calldata data) external {
// The V1 version has this function as a no-op, hence we skip the permission check.
}

// ═══════════════════════════════════════════════════ VIEWS ═══════════════════════════════════════════════════════

/// @inheritdoc ISynapseGasOracle
function getLocalGasData() external view returns (bytes memory) {
// The V1 version has this function as a no-op.
}
// solhint-enable no-empty-blocks

/// @inheritdoc IGasOracle
function convertRemoteValueToLocalUnits(
uint256 remoteChainId,
uint256 value
)
external
view
onlyRemoteChainId(remoteChainId)
onlyNativePriceSet(remoteChainId)
returns (uint256)
{
// This will revert if the local native price is not set.
return _convertRemoteValueToLocalUnits(remoteChainId, value);
}

/// @inheritdoc IGasOracle
function estimateTxCostInLocalUnits(
uint256 remoteChainId,
uint256 gasLimit,
uint256 calldataSize
)
external
view
onlyRemoteChainId(remoteChainId)
onlyNativePriceSet(remoteChainId)
returns (uint256)
{
uint256 remoteTxCost = _estimateTxCostInRemoteUnits(remoteChainId, gasLimit, calldataSize);
// This will revert if the local native price is not set.
return _convertRemoteValueToLocalUnits(remoteChainId, remoteTxCost);
}

/// @inheritdoc IGasOracle
function estimateTxCostInRemoteUnits(
uint256 remoteChainId,
uint256 gasLimit,
uint256 calldataSize
)
external
view
onlyRemoteChainId(remoteChainId)
onlyNativePriceSet(remoteChainId)
returns (uint256)
{
// This will NOT revert if the local native price is not set, and we are fine with that.
return _estimateTxCostInRemoteUnits(remoteChainId, gasLimit, calldataSize);
}

/// @inheritdoc ISynapseGasOracleV1
function getLocalNativePrice() external view returns (uint256) {
return _localNativePrice;
}

/// @inheritdoc ISynapseGasOracleV1
function getRemoteGasData(uint256 chainId) external view returns (RemoteGasData memory) {
return _remoteGasData[chainId];
}

// ══════════════════════════════════════════════ INTERNAL LOGIC ═══════════════════════════════════════════════════

/// @dev Updates the calldata price for the given remote chain, no-op if the price is already set.
function _setRemoteCallDataPrice(uint256 chainId, uint256 calldataPrice) internal {
if (_remoteGasData[chainId].calldataPrice != calldataPrice) {
_remoteGasData[chainId].calldataPrice = calldataPrice;
emit CalldataPriceSet(chainId, calldataPrice);
}
}

/// @dev Updates the gas price for the given remote chain, no-op if the price is already set.
function _setRemoteGasPrice(uint256 chainId, uint256 gasPrice) internal {
if (_remoteGasData[chainId].gasPrice != gasPrice) {
_remoteGasData[chainId].gasPrice = gasPrice;
emit GasPriceSet(chainId, gasPrice);
}
}

/// @dev Updates the native token price for the given remote chain, no-op if the price is already set.
function _setRemoteNativePrice(uint256 chainId, uint256 nativePrice) internal {
if (_remoteGasData[chainId].nativePrice != nativePrice) {
_remoteGasData[chainId].nativePrice = nativePrice;
emit NativePriceSet(chainId, nativePrice);
}
}

/// @dev Converts value denominated in remote chain's units to local chain's units.
/// Note: the check for non-zero remote native token price is done outside this function.
function _convertRemoteValueToLocalUnits(
uint256 remoteChainId,
uint256 remoteValue
)
internal
view
returns (uint256)
{
if (_localNativePrice == 0) {
revert SynapseGasOracleV1__NativePriceNotSet(block.chainid);
}
return (remoteValue * _remoteGasData[remoteChainId].nativePrice) / _localNativePrice;
}

/// @dev Estimates the transaction cost in remote chain's units.
/// Note: the check for non-zero remote native token price is done outside this function.
function _estimateTxCostInRemoteUnits(
uint256 remoteChainId,
uint256 gasLimit,
uint256 calldataSize
)
internal
view
returns (uint256)
{
return gasLimit * _remoteGasData[remoteChainId].gasPrice
+ calldataSize * _remoteGasData[remoteChainId].calldataPrice;
}
}
Loading

0 comments on commit c15ddf6

Please sign in to comment.