1+ // SPDX-License-Identifier: BUSL-1.1
2+ pragma solidity 0.8.24 ;
3+
4+ import {ITypeAndVersion} from "../shared/interfaces/ITypeAndVersion.sol " ;
5+ import {IPriceRegistry} from "./interfaces/IPriceRegistry.sol " ;
6+
7+ import {OwnerIsCreator} from "./../shared/access/OwnerIsCreator.sol " ;
8+ import {Internal} from "./libraries/Internal.sol " ;
9+ import {USDPriceWith18Decimals} from "./libraries/USDPriceWith18Decimals.sol " ;
10+
11+ import {EnumerableSet} from "../vendor/openzeppelin-solidity/v4.8.3/contracts/utils/structs/EnumerableSet.sol " ;
12+
13+ /// @notice The PriceRegistry contract responsibility is to store the current gas price in USD for a given destination chain,
14+ /// and the price of a token in USD allowing the owner or priceUpdater to update this value.
15+ contract PriceRegistry is IPriceRegistry , OwnerIsCreator , ITypeAndVersion {
16+ using EnumerableSet for EnumerableSet.AddressSet;
17+ using USDPriceWith18Decimals for uint224 ;
18+
19+ error TokenNotSupported (address token );
20+ error ChainNotSupported (uint64 chain );
21+ error OnlyCallableByUpdaterOrOwner ();
22+ error StaleGasPrice (uint64 destChainSelector , uint256 threshold , uint256 timePassed );
23+ error StaleTokenPrice (address token , uint256 threshold , uint256 timePassed );
24+ error InvalidStalenessThreshold ();
25+
26+ event PriceUpdaterSet (address indexed priceUpdater );
27+ event PriceUpdaterRemoved (address indexed priceUpdater );
28+ event FeeTokenAdded (address indexed feeToken );
29+ event FeeTokenRemoved (address indexed feeToken );
30+ event UsdPerUnitGasUpdated (uint64 indexed destChain , uint256 value , uint256 timestamp );
31+ event UsdPerTokenUpdated (address indexed token , uint256 value , uint256 timestamp );
32+
33+ // solhint-disable-next-line chainlink-solidity/all-caps-constant-storage-variables
34+ string public constant override typeAndVersion = "PriceRegistry 1.2.0 " ;
35+
36+ /// @dev The gas price per unit of gas for a given destination chain, in USD with 18 decimals.
37+ /// Multiple gas prices can be encoded into the same value. Each price takes {Internal.GAS_PRICE_BITS} bits.
38+ /// For example, if Optimism is the destination chain, gas price can include L1 base fee and L2 gas price.
39+ /// Logic to parse the price components is chain-specific, and should live in OnRamp.
40+ /// @dev Price of 1e18 is 1 USD. Examples:
41+ /// Very Expensive: 1 unit of gas costs 1 USD -> 1e18
42+ /// Expensive: 1 unit of gas costs 0.1 USD -> 1e17
43+ /// Cheap: 1 unit of gas costs 0.000001 USD -> 1e12
44+ mapping (uint64 destChainSelector = > Internal.TimestampedPackedUint224 price )
45+ private s_usdPerUnitGasByDestChainSelector;
46+
47+ /// @dev The price, in USD with 18 decimals, per 1e18 of the smallest token denomination.
48+ /// @dev Price of 1e18 represents 1 USD per 1e18 token amount.
49+ /// 1 USDC = 1.00 USD per full token, each full token is 1e6 units -> 1 * 1e18 * 1e18 / 1e6 = 1e30
50+ /// 1 ETH = 2,000 USD per full token, each full token is 1e18 units -> 2000 * 1e18 * 1e18 / 1e18 = 2_000e18
51+ /// 1 LINK = 5.00 USD per full token, each full token is 1e18 units -> 5 * 1e18 * 1e18 / 1e18 = 5e18
52+ mapping (address token = > Internal.TimestampedPackedUint224 price ) private s_usdPerToken;
53+
54+ // Price updaters are allowed to update the prices.
55+ EnumerableSet.AddressSet private s_priceUpdaters;
56+ // Subset of tokens which prices tracked by this registry which are fee tokens.
57+ EnumerableSet.AddressSet private s_feeTokens;
58+ // The amount of time a price can be stale before it is considered invalid.
59+ uint32 private immutable i_stalenessThreshold;
60+
61+ constructor (address [] memory priceUpdaters , address [] memory feeTokens , uint32 stalenessThreshold ) {
62+ _applyPriceUpdatersUpdates (priceUpdaters, new address [](0 ));
63+ _applyFeeTokensUpdates (feeTokens, new address [](0 ));
64+ if (stalenessThreshold == 0 ) revert InvalidStalenessThreshold ();
65+ i_stalenessThreshold = stalenessThreshold;
66+ }
67+
68+ // ================================================================
69+ // │ Price calculations │
70+ // ================================================================
71+
72+ // @inheritdoc IPriceRegistry
73+ function getTokenPrice (address token ) public view override returns (Internal.TimestampedPackedUint224 memory ) {
74+ return s_usdPerToken[token];
75+ }
76+
77+ // @inheritdoc IPriceRegistry
78+ function getValidatedTokenPrice (address token ) external view override returns (uint224 ) {
79+ return _getValidatedTokenPrice (token);
80+ }
81+
82+ // @inheritdoc IPriceRegistry
83+ function getTokenPrices (
84+ address [] calldata tokens
85+ ) external view override returns (Internal.TimestampedPackedUint224[] memory ) {
86+ uint256 length = tokens.length ;
87+ Internal.TimestampedPackedUint224[] memory tokenPrices = new Internal.TimestampedPackedUint224 [](length);
88+ for (uint256 i = 0 ; i < length; ++ i) {
89+ tokenPrices[i] = getTokenPrice (tokens[i]);
90+ }
91+ return tokenPrices;
92+ }
93+
94+ /// @notice Get the staleness threshold.
95+ /// @return stalenessThreshold The staleness threshold.
96+ function getStalenessThreshold () external view returns (uint128 ) {
97+ return i_stalenessThreshold;
98+ }
99+
100+ // @inheritdoc IPriceRegistry
101+ function getDestinationChainGasPrice (
102+ uint64 destChainSelector
103+ ) external view override returns (Internal.TimestampedPackedUint224 memory ) {
104+ return s_usdPerUnitGasByDestChainSelector[destChainSelector];
105+ }
106+
107+ function getTokenAndGasPrices (
108+ address token ,
109+ uint64 destChainSelector
110+ ) external view override returns (uint224 tokenPrice , uint224 gasPriceValue ) {
111+ Internal.TimestampedPackedUint224 memory gasPrice = s_usdPerUnitGasByDestChainSelector[destChainSelector];
112+ // We do allow a gas price of 0, but no stale or unset gas prices
113+ if (gasPrice.timestamp == 0 ) revert ChainNotSupported (destChainSelector);
114+ uint256 timePassed = block .timestamp - gasPrice.timestamp;
115+ if (timePassed > i_stalenessThreshold) revert StaleGasPrice (destChainSelector, i_stalenessThreshold, timePassed);
116+
117+ return (_getValidatedTokenPrice (token), gasPrice.value);
118+ }
119+
120+ /// @inheritdoc IPriceRegistry
121+ /// @dev this function assumes that no more than 1e59 dollars are sent as payment.
122+ /// If more is sent, the multiplication of feeTokenAmount and feeTokenValue will overflow.
123+ /// Since there isn't even close to 1e59 dollars in the world economy this is safe.
124+ function convertTokenAmount (
125+ address fromToken ,
126+ uint256 fromTokenAmount ,
127+ address toToken
128+ ) external view override returns (uint256 ) {
129+ /// Example:
130+ /// fromTokenAmount: 1e18 // 1 ETH
131+ /// ETH: 2_000e18
132+ /// LINK: 5e18
133+ /// return: 1e18 * 2_000e18 / 5e18 = 400e18 (400 LINK)
134+ return (fromTokenAmount * _getValidatedTokenPrice (fromToken)) / _getValidatedTokenPrice (toToken);
135+ }
136+
137+ /// @notice Gets the token price for a given token and revert if the token is either
138+ /// not supported or the price is stale.
139+ /// @param token The address of the token to get the price for
140+ /// @return the token price
141+ function _getValidatedTokenPrice (address token ) internal view returns (uint224 ) {
142+ Internal.TimestampedPackedUint224 memory tokenPrice = s_usdPerToken[token];
143+ if (tokenPrice.timestamp == 0 || tokenPrice.value == 0 ) revert TokenNotSupported (token);
144+ uint256 timePassed = block .timestamp - tokenPrice.timestamp;
145+ if (timePassed > i_stalenessThreshold) revert StaleTokenPrice (token, i_stalenessThreshold, timePassed);
146+ return tokenPrice.value;
147+ }
148+
149+ // ================================================================
150+ // │ Fee tokens │
151+ // ================================================================
152+
153+ /// @notice Get the list of fee tokens.
154+ /// @return The tokens set as fee tokens.
155+ function getFeeTokens () external view returns (address [] memory ) {
156+ return s_feeTokens.values ();
157+ }
158+
159+ /// @notice Add and remove tokens from feeTokens set.
160+ /// @param feeTokensToAdd The addresses of the tokens which are now considered fee tokens
161+ /// and can be used to calculate fees.
162+ /// @param feeTokensToRemove The addresses of the tokens which are no longer considered feeTokens.
163+ function applyFeeTokensUpdates (
164+ address [] memory feeTokensToAdd ,
165+ address [] memory feeTokensToRemove
166+ ) external onlyOwner {
167+ _applyFeeTokensUpdates (feeTokensToAdd, feeTokensToRemove);
168+ }
169+
170+ /// @notice Add and remove tokens from feeTokens set.
171+ /// @param feeTokensToAdd The addresses of the tokens which are now considered fee tokens
172+ /// and can be used to calculate fees.
173+ /// @param feeTokensToRemove The addresses of the tokens which are no longer considered feeTokens.
174+ function _applyFeeTokensUpdates (address [] memory feeTokensToAdd , address [] memory feeTokensToRemove ) private {
175+ for (uint256 i = 0 ; i < feeTokensToAdd.length ; ++ i) {
176+ if (s_feeTokens.add (feeTokensToAdd[i])) {
177+ emit FeeTokenAdded (feeTokensToAdd[i]);
178+ }
179+ }
180+ for (uint256 i = 0 ; i < feeTokensToRemove.length ; ++ i) {
181+ if (s_feeTokens.remove (feeTokensToRemove[i])) {
182+ emit FeeTokenRemoved (feeTokensToRemove[i]);
183+ }
184+ }
185+ }
186+
187+ // ================================================================
188+ // │ Price updates │
189+ // ================================================================
190+
191+ // @inheritdoc IPriceRegistry
192+ function updatePrices (Internal.PriceUpdates calldata priceUpdates ) external override requireUpdaterOrOwner {
193+ uint256 tokenUpdatesLength = priceUpdates.tokenPriceUpdates.length ;
194+
195+ for (uint256 i = 0 ; i < tokenUpdatesLength; ++ i) {
196+ Internal.TokenPriceUpdate memory update = priceUpdates.tokenPriceUpdates[i];
197+ s_usdPerToken[update.sourceToken] = Internal.TimestampedPackedUint224 ({
198+ value: update.usdPerToken,
199+ timestamp: uint32 (block .timestamp )
200+ });
201+ emit UsdPerTokenUpdated (update.sourceToken, update.usdPerToken, block .timestamp );
202+ }
203+
204+ uint256 gasUpdatesLength = priceUpdates.gasPriceUpdates.length ;
205+
206+ for (uint256 i = 0 ; i < gasUpdatesLength; ++ i) {
207+ Internal.GasPriceUpdate memory update = priceUpdates.gasPriceUpdates[i];
208+ s_usdPerUnitGasByDestChainSelector[update.destChainSelector] = Internal.TimestampedPackedUint224 ({
209+ value: update.usdPerUnitGas,
210+ timestamp: uint32 (block .timestamp )
211+ });
212+ emit UsdPerUnitGasUpdated (update.destChainSelector, update.usdPerUnitGas, block .timestamp );
213+ }
214+ }
215+
216+ // ================================================================
217+ // │ Access │
218+ // ================================================================
219+
220+ /// @notice Get the list of price updaters.
221+ /// @return The price updaters.
222+ function getPriceUpdaters () external view returns (address [] memory ) {
223+ return s_priceUpdaters.values ();
224+ }
225+
226+ /// @notice Adds new priceUpdaters and remove existing ones.
227+ /// @param priceUpdatersToAdd The addresses of the priceUpdaters that are now allowed
228+ /// to send fee updates.
229+ /// @param priceUpdatersToRemove The addresses of the priceUpdaters that are no longer allowed
230+ /// to send fee updates.
231+ function applyPriceUpdatersUpdates (
232+ address [] memory priceUpdatersToAdd ,
233+ address [] memory priceUpdatersToRemove
234+ ) external onlyOwner {
235+ _applyPriceUpdatersUpdates (priceUpdatersToAdd, priceUpdatersToRemove);
236+ }
237+
238+ /// @notice Adds new priceUpdaters and remove existing ones.
239+ /// @param priceUpdatersToAdd The addresses of the priceUpdaters that are now allowed
240+ /// to send fee updates.
241+ /// @param priceUpdatersToRemove The addresses of the priceUpdaters that are no longer allowed
242+ /// to send fee updates.
243+ function _applyPriceUpdatersUpdates (
244+ address [] memory priceUpdatersToAdd ,
245+ address [] memory priceUpdatersToRemove
246+ ) private {
247+ for (uint256 i = 0 ; i < priceUpdatersToAdd.length ; ++ i) {
248+ if (s_priceUpdaters.add (priceUpdatersToAdd[i])) {
249+ emit PriceUpdaterSet (priceUpdatersToAdd[i]);
250+ }
251+ }
252+ for (uint256 i = 0 ; i < priceUpdatersToRemove.length ; ++ i) {
253+ if (s_priceUpdaters.remove (priceUpdatersToRemove[i])) {
254+ emit PriceUpdaterRemoved (priceUpdatersToRemove[i]);
255+ }
256+ }
257+ }
258+
259+ /// @notice Require that the caller is the owner or a fee updater.
260+ modifier requireUpdaterOrOwner () {
261+ if (msg .sender != owner () && ! s_priceUpdaters.contains (msg .sender )) revert OnlyCallableByUpdaterOrOwner ();
262+ _;
263+ }
264+ }
0 commit comments