Skip to content

Commit c15ddf6

Browse files
authored
Synapse gas oracle v1 (#2295)
* 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
1 parent 91d68c3 commit c15ddf6

11 files changed

+857
-3
lines changed
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
{
22
"executorEOA": "0xD0D45E468ADF3aCB8A98391ff47267783220ba6F",
3-
"gasOracleName": "SynapseGasOracleMock"
3+
"gasOracleName": "SynapseGasOracleV1"
44
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"eth_sepolia": {
3+
"calldataPrice": 0,
4+
"gasPrice": 10000000000,
5+
"nativePrice": 1000000000000000000
6+
},
7+
"op_sepolia": {
8+
"calldataPrice": 0,
9+
"gasPrice": 100000000,
10+
"nativePrice": 1000000000000000000
11+
}
12+
}

packages/contracts-communication/configs/global/SynapseModule.testnet.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"claimFeeBPS": 10,
33
"feeCollector": "0xD0D45E468ADF3aCB8A98391ff47267783220ba6F",
4-
"gasOracleName": "SynapseGasOracleMock",
4+
"gasOracleName": "SynapseGasOracleV1",
55
"threshold": 6,
66
"verifiers": [
77
"0x0E399F695d72033d598aC2911B8A5e9986d6cbaD",
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.0;
3+
4+
abstract contract SynapseGasOracleV1Events {
5+
event CalldataPriceSet(uint256 indexed chainId, uint256 calldataPrice);
6+
event GasPriceSet(uint256 indexed chainId, uint256 gasPrice);
7+
event NativePriceSet(uint256 indexed chainId, uint256 nativePrice);
8+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.0;
3+
4+
import {ISynapseGasOracle} from "./ISynapseGasOracle.sol";
5+
6+
interface ISynapseGasOracleV1 is ISynapseGasOracle {
7+
/// @notice Struct defining the gas data for the remote chain.
8+
/// @param calldataPrice The price of 1 byte of calldata in the remote chain's wei.
9+
/// @param gasPrice The gas price of the remote chain, in remote chain's wei.
10+
/// @param nativePrice The price of the remote chain's native token in Ethereum Mainnet's wei.
11+
struct RemoteGasData {
12+
uint256 calldataPrice;
13+
uint256 gasPrice;
14+
uint256 nativePrice;
15+
}
16+
17+
error SynapseGasOracleV1__NotRemoteChainId(uint256 chainId);
18+
error SynapseGasOracleV1__NativePriceNotSet(uint256 chainId);
19+
error SynapseGasOracleV1__NativePriceZero();
20+
21+
/// @notice Allows the contract owner to set the native token price of the local chain.
22+
/// @dev Could only be called by the contract owner. Will revert if the native token price is 0.
23+
/// @param nativePrice The price of the local chain's native token in Ethereum Mainnet's wei.
24+
function setLocalNativePrice(uint256 nativePrice) external;
25+
26+
/// @notice Allows the contract owner to set the gas data for a remote chain.
27+
/// @dev Could only be called by the contract owner.
28+
/// Will revert if the native token price is 0, or if the chain id is not a remote chain id.
29+
/// @param chainId The chain id of the remote chain.
30+
/// @param data The gas data for the remote chain.
31+
function setRemoteGasData(uint256 chainId, RemoteGasData memory data) external;
32+
33+
/// @notice Allows the contract owner to set the price of remote chain's calldata.
34+
/// @dev Could only be called by the contract owner.
35+
/// Will revert if the chain id is not a remote chain id, or if native token price for the chain is 0.
36+
/// @param chainId The chain id of the remote chain.
37+
/// @param calldataPrice The price of 1 byte of calldata in the remote chain's wei.
38+
function setRemoteCallDataPrice(uint256 chainId, uint256 calldataPrice) external;
39+
40+
/// @notice Allows the contract owner to set the gas price of the remote chain.
41+
/// @dev Could only be called by the contract owner.
42+
/// Will revert if the chain id is not a remote chain id, or if native token price for the chain is 0.
43+
/// @param chainId The chain id of the remote chain.
44+
/// @param gasPrice The gas price of the remote chain, in remote chain's wei.
45+
function setRemoteGasPrice(uint256 chainId, uint256 gasPrice) external;
46+
47+
/// @notice Allows the contract owner to set the price of the remote chain's native token.
48+
/// @dev Could only be called by the contract owner.
49+
/// Will revert if the chain id is not a remote chain id, or if the price is 0.
50+
/// @param chainId The chain id of the remote chain.
51+
/// @param nativePrice The price of the remote chain's native token in Ethereum Mainnet's wei.
52+
function setRemoteNativePrice(uint256 chainId, uint256 nativePrice) external;
53+
54+
/// @notice Gets the price of the local chain's native token in Ethereum Mainnet's wei.
55+
function getLocalNativePrice() external view returns (uint256);
56+
57+
/// @notice Gets the gas data for a remote chain.
58+
/// @dev Will revert if the chain id is not a remote chain id.
59+
/// @param chainId The chain id of the remote chain.
60+
function getRemoteGasData(uint256 chainId) external view returns (RemoteGasData memory);
61+
}
Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity 0.8.20;
3+
4+
import {SynapseGasOracleV1Events} from "../events/SynapseGasOracleV1Events.sol";
5+
import {ISynapseGasOracle, IGasOracle} from "../interfaces/ISynapseGasOracle.sol";
6+
import {ISynapseGasOracleV1} from "../interfaces/ISynapseGasOracleV1.sol";
7+
8+
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
9+
10+
contract SynapseGasOracleV1 is Ownable, SynapseGasOracleV1Events, ISynapseGasOracleV1 {
11+
uint256 internal _localNativePrice;
12+
mapping(uint256 chainId => RemoteGasData data) internal _remoteGasData;
13+
14+
/// @dev Checks that the chain ID is not the local chain ID.
15+
modifier onlyRemoteChainId(uint256 chainId) {
16+
if (block.chainid == chainId) {
17+
revert SynapseGasOracleV1__NotRemoteChainId(chainId);
18+
}
19+
_;
20+
}
21+
22+
/// @dev Checks that the native token price is set for a remote chain ID.
23+
modifier onlyNativePriceSet(uint256 chainId) {
24+
if (_remoteGasData[chainId].nativePrice == 0) {
25+
revert SynapseGasOracleV1__NativePriceNotSet(chainId);
26+
}
27+
_;
28+
}
29+
30+
/// @dev Checks that the native token price is non-zero.
31+
modifier onlyNonZeroNativePrice(uint256 nativePrice) {
32+
if (nativePrice == 0) {
33+
revert SynapseGasOracleV1__NativePriceZero();
34+
}
35+
_;
36+
}
37+
38+
constructor(address owner_) Ownable(owner_) {}
39+
40+
// ════════════════════════════════════════════════ ONLY OWNER ═════════════════════════════════════════════════════
41+
42+
/// @inheritdoc ISynapseGasOracleV1
43+
function setLocalNativePrice(uint256 nativePrice) external onlyOwner onlyNonZeroNativePrice(nativePrice) {
44+
if (_localNativePrice != nativePrice) {
45+
_localNativePrice = nativePrice;
46+
emit NativePriceSet(block.chainid, nativePrice);
47+
}
48+
}
49+
50+
/// @inheritdoc ISynapseGasOracleV1
51+
function setRemoteGasData(
52+
uint256 chainId,
53+
RemoteGasData memory data
54+
)
55+
external
56+
onlyOwner
57+
onlyRemoteChainId(chainId)
58+
onlyNonZeroNativePrice(data.nativePrice)
59+
{
60+
_setRemoteCallDataPrice(chainId, data.calldataPrice);
61+
_setRemoteGasPrice(chainId, data.gasPrice);
62+
_setRemoteNativePrice(chainId, data.nativePrice);
63+
}
64+
65+
/// @inheritdoc ISynapseGasOracleV1
66+
function setRemoteCallDataPrice(
67+
uint256 chainId,
68+
uint256 calldataPrice
69+
)
70+
external
71+
onlyOwner
72+
onlyRemoteChainId(chainId)
73+
onlyNativePriceSet(chainId)
74+
{
75+
_setRemoteCallDataPrice(chainId, calldataPrice);
76+
}
77+
78+
/// @inheritdoc ISynapseGasOracleV1
79+
function setRemoteGasPrice(
80+
uint256 chainId,
81+
uint256 gasPrice
82+
)
83+
external
84+
onlyOwner
85+
onlyRemoteChainId(chainId)
86+
onlyNativePriceSet(chainId)
87+
{
88+
_setRemoteGasPrice(chainId, gasPrice);
89+
}
90+
91+
/// @inheritdoc ISynapseGasOracleV1
92+
function setRemoteNativePrice(
93+
uint256 chainId,
94+
uint256 nativePrice
95+
)
96+
external
97+
onlyOwner
98+
onlyRemoteChainId(chainId)
99+
onlyNonZeroNativePrice(nativePrice)
100+
{
101+
_setRemoteNativePrice(chainId, nativePrice);
102+
}
103+
104+
// ════════════════════════════════════════════════ ONLY MODULE ════════════════════════════════════════════════════
105+
106+
// solhint-disable no-empty-blocks
107+
/// @inheritdoc ISynapseGasOracle
108+
function receiveRemoteGasData(uint256 srcChainId, bytes calldata data) external {
109+
// The V1 version has this function as a no-op, hence we skip the permission check.
110+
}
111+
112+
// ═══════════════════════════════════════════════════ VIEWS ═══════════════════════════════════════════════════════
113+
114+
/// @inheritdoc ISynapseGasOracle
115+
function getLocalGasData() external view returns (bytes memory) {
116+
// The V1 version has this function as a no-op.
117+
}
118+
// solhint-enable no-empty-blocks
119+
120+
/// @inheritdoc IGasOracle
121+
function convertRemoteValueToLocalUnits(
122+
uint256 remoteChainId,
123+
uint256 value
124+
)
125+
external
126+
view
127+
onlyRemoteChainId(remoteChainId)
128+
onlyNativePriceSet(remoteChainId)
129+
returns (uint256)
130+
{
131+
// This will revert if the local native price is not set.
132+
return _convertRemoteValueToLocalUnits(remoteChainId, value);
133+
}
134+
135+
/// @inheritdoc IGasOracle
136+
function estimateTxCostInLocalUnits(
137+
uint256 remoteChainId,
138+
uint256 gasLimit,
139+
uint256 calldataSize
140+
)
141+
external
142+
view
143+
onlyRemoteChainId(remoteChainId)
144+
onlyNativePriceSet(remoteChainId)
145+
returns (uint256)
146+
{
147+
uint256 remoteTxCost = _estimateTxCostInRemoteUnits(remoteChainId, gasLimit, calldataSize);
148+
// This will revert if the local native price is not set.
149+
return _convertRemoteValueToLocalUnits(remoteChainId, remoteTxCost);
150+
}
151+
152+
/// @inheritdoc IGasOracle
153+
function estimateTxCostInRemoteUnits(
154+
uint256 remoteChainId,
155+
uint256 gasLimit,
156+
uint256 calldataSize
157+
)
158+
external
159+
view
160+
onlyRemoteChainId(remoteChainId)
161+
onlyNativePriceSet(remoteChainId)
162+
returns (uint256)
163+
{
164+
// This will NOT revert if the local native price is not set, and we are fine with that.
165+
return _estimateTxCostInRemoteUnits(remoteChainId, gasLimit, calldataSize);
166+
}
167+
168+
/// @inheritdoc ISynapseGasOracleV1
169+
function getLocalNativePrice() external view returns (uint256) {
170+
return _localNativePrice;
171+
}
172+
173+
/// @inheritdoc ISynapseGasOracleV1
174+
function getRemoteGasData(uint256 chainId) external view returns (RemoteGasData memory) {
175+
return _remoteGasData[chainId];
176+
}
177+
178+
// ══════════════════════════════════════════════ INTERNAL LOGIC ═══════════════════════════════════════════════════
179+
180+
/// @dev Updates the calldata price for the given remote chain, no-op if the price is already set.
181+
function _setRemoteCallDataPrice(uint256 chainId, uint256 calldataPrice) internal {
182+
if (_remoteGasData[chainId].calldataPrice != calldataPrice) {
183+
_remoteGasData[chainId].calldataPrice = calldataPrice;
184+
emit CalldataPriceSet(chainId, calldataPrice);
185+
}
186+
}
187+
188+
/// @dev Updates the gas price for the given remote chain, no-op if the price is already set.
189+
function _setRemoteGasPrice(uint256 chainId, uint256 gasPrice) internal {
190+
if (_remoteGasData[chainId].gasPrice != gasPrice) {
191+
_remoteGasData[chainId].gasPrice = gasPrice;
192+
emit GasPriceSet(chainId, gasPrice);
193+
}
194+
}
195+
196+
/// @dev Updates the native token price for the given remote chain, no-op if the price is already set.
197+
function _setRemoteNativePrice(uint256 chainId, uint256 nativePrice) internal {
198+
if (_remoteGasData[chainId].nativePrice != nativePrice) {
199+
_remoteGasData[chainId].nativePrice = nativePrice;
200+
emit NativePriceSet(chainId, nativePrice);
201+
}
202+
}
203+
204+
/// @dev Converts value denominated in remote chain's units to local chain's units.
205+
/// Note: the check for non-zero remote native token price is done outside this function.
206+
function _convertRemoteValueToLocalUnits(
207+
uint256 remoteChainId,
208+
uint256 remoteValue
209+
)
210+
internal
211+
view
212+
returns (uint256)
213+
{
214+
if (_localNativePrice == 0) {
215+
revert SynapseGasOracleV1__NativePriceNotSet(block.chainid);
216+
}
217+
return (remoteValue * _remoteGasData[remoteChainId].nativePrice) / _localNativePrice;
218+
}
219+
220+
/// @dev Estimates the transaction cost in remote chain's units.
221+
/// Note: the check for non-zero remote native token price is done outside this function.
222+
function _estimateTxCostInRemoteUnits(
223+
uint256 remoteChainId,
224+
uint256 gasLimit,
225+
uint256 calldataSize
226+
)
227+
internal
228+
view
229+
returns (uint256)
230+
{
231+
return gasLimit * _remoteGasData[remoteChainId].gasPrice
232+
+ calldataSize * _remoteGasData[remoteChainId].calldataPrice;
233+
}
234+
}

0 commit comments

Comments
 (0)