-
Notifications
You must be signed in to change notification settings - Fork 30
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* 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
1 parent
91d68c3
commit c15ddf6
Showing
11 changed files
with
857 additions
and
3 deletions.
There are no files selected for viewing
2 changes: 1 addition & 1 deletion
2
packages/contracts-communication/configs/global/ExecutionService.testnet.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
{ | ||
"executorEOA": "0xD0D45E468ADF3aCB8A98391ff47267783220ba6F", | ||
"gasOracleName": "SynapseGasOracleMock" | ||
"gasOracleName": "SynapseGasOracleV1" | ||
} |
12 changes: 12 additions & 0 deletions
12
packages/contracts-communication/configs/global/SynapseGasOracleV1.testnet.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
} |
2 changes: 1 addition & 1 deletion
2
packages/contracts-communication/configs/global/SynapseModule.testnet.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
8 changes: 8 additions & 0 deletions
8
packages/contracts-communication/contracts/events/SynapseGasOracleV1Events.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} |
61 changes: 61 additions & 0 deletions
61
packages/contracts-communication/contracts/interfaces/ISynapseGasOracleV1.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} |
234 changes: 234 additions & 0 deletions
234
packages/contracts-communication/contracts/oracles/SynapseGasOracleV1.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
Oops, something went wrong.