Skip to content

Commit

Permalink
make gas fee staleness threshold configurable per chain
Browse files Browse the repository at this point in the history
  • Loading branch information
RyanRHall committed Oct 7, 2024
1 parent f77ff21 commit bd87220
Show file tree
Hide file tree
Showing 3 changed files with 38 additions and 18 deletions.
42 changes: 32 additions & 10 deletions contracts/src/v0.8/ccip/FeeQuoter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@ contract FeeQuoter is AuthorizedCallers, IFeeQuoter, ITypeAndVersion, IReceiver,
struct StaticConfig {
uint96 maxFeeJuelsPerMsg; // ─╮ Maximum fee that can be charged for a message
address linkToken; // ────────╯ LINK token address
uint32 stalenessThreshold; // The amount of time a gas price can be stale before it is considered invalid.
// The amount of time a token price can be stale before it is considered invalid (gas price staleness is configured per dest chain)
uint32 stalenessThreshold;
}

/// @dev The struct representing the received CCIP feed report from keystone IReceiver.onReport()
Expand All @@ -103,6 +104,7 @@ contract FeeQuoter is AuthorizedCallers, IFeeQuoter, ITypeAndVersion, IReceiver,
uint32 defaultTxGasLimit; //─────────────────╮ Default gas limit for a tx
uint64 gasMultiplierWeiPerEth; // │ Multiplier for gas costs, 1e18 based so 11e17 = 10% extra cost.
uint32 networkFeeUSDCents; // │ Flat network fee to charge for messages, multiples of 0.01 USD
uint32 gasPriceStalenessThreshold; // │ The amount of time a gas price can be stale before it is considered invalid (0 means disabled)
bool enforceOutOfOrder; // │ Whether to enforce the allowOutOfOrderExecution extraArg value to be true.
bytes4 chainFamilySelector; // ──────────────╯ Selector that identifies the destination chain's family. Used to determine the correct validations to perform for the dest chain.
}
Expand Down Expand Up @@ -202,7 +204,7 @@ contract FeeQuoter is AuthorizedCallers, IFeeQuoter, ITypeAndVersion, IReceiver,

/// @dev Subset of tokens which prices tracked by this registry which are fee tokens.
EnumerableSet.AddressSet private s_feeTokens;
/// @dev The amount of time a gas price can be stale before it is considered invalid.
/// @dev The amount of time a token price can be stale before it is considered invalid.
uint32 private immutable i_stalenessThreshold;

constructor(
Expand Down Expand Up @@ -306,13 +308,7 @@ contract FeeQuoter is AuthorizedCallers, IFeeQuoter, ITypeAndVersion, IReceiver,
address token,
uint64 destChainSelector
) public view returns (uint224 tokenPrice, uint224 gasPriceValue) {
Internal.TimestampedPackedUint224 memory gasPrice = s_usdPerUnitGasByDestChainSelector[destChainSelector];
// We do allow a gas price of 0, but no stale or unset gas prices
if (gasPrice.timestamp == 0) revert ChainNotSupported(destChainSelector);
uint256 timePassed = block.timestamp - gasPrice.timestamp;
if (timePassed > i_stalenessThreshold) revert StaleGasPrice(destChainSelector, i_stalenessThreshold, timePassed);

return (_getValidatedTokenPrice(token), gasPrice.value);
return _getTokenAndGasPrices(token, destChainSelector, s_destChainConfigs[destChainSelector]);
}

/// @notice Convert a given token amount to target token amount.
Expand Down Expand Up @@ -374,6 +370,31 @@ contract FeeQuoter is AuthorizedCallers, IFeeQuoter, ITypeAndVersion, IReceiver,
return Internal.TimestampedPackedUint224({value: rebasedValue, timestamp: uint32(block.timestamp)});
}

/// @notice Gets the fee token price and the gas price, both denominated in dollars.
/// @param token The source token to get the price for.
/// @param destChainSelector The destination chain to get the gas price for.
/// @param destChainConfig The config struct for the dest chain selector.
/// @return tokenPrice The price of the feeToken in 1e18 dollars per base unit.
/// @return gasPriceValue The price of gas in 1e18 dollars per base unit.
function _getTokenAndGasPrices(
address token,
uint64 destChainSelector,
DestChainConfig memory destChainConfig
) private view returns (uint224 tokenPrice, uint224 gasPriceValue) {
if (!destChainConfig.isEnabled) revert ChainNotSupported(destChainSelector);
Internal.TimestampedPackedUint224 memory gasPrice = s_usdPerUnitGasByDestChainSelector[destChainSelector];
// If the staleness threshold is 0, we consider the gas price to be always valid
if (destChainConfig.gasPriceStalenessThreshold != 0) {
// We do allow a gas price of 0, but no stale or unset gas prices
uint256 timePassed = block.timestamp - gasPrice.timestamp;
if (timePassed > destChainConfig.gasPriceStalenessThreshold) {
revert StaleGasPrice(destChainSelector, destChainConfig.gasPriceStalenessThreshold, timePassed);
}
}

return (_getValidatedTokenPrice(token), gasPrice.value);
}

// ================================================================
// │ Fee tokens │
// ================================================================
Expand Down Expand Up @@ -507,7 +528,8 @@ contract FeeQuoter is AuthorizedCallers, IFeeQuoter, ITypeAndVersion, IReceiver,
_validateMessage(destChainConfig, message.data.length, numberOfTokens, message.receiver);

// The below call asserts that feeToken is a supported token
(uint224 feeTokenPrice, uint224 packedGasPrice) = getTokenAndGasPrices(message.feeToken, destChainSelector);
(uint224 feeTokenPrice, uint224 packedGasPrice) =
_getTokenAndGasPrices(message.feeToken, destChainSelector, destChainConfig);

// Calculate premiumFee in USD with 18 decimals precision first.
// If message-only and no token transfers, a flat network fee is charged.
Expand Down
13 changes: 5 additions & 8 deletions contracts/src/v0.8/ccip/test/feeQuoter/FeeQuoter.t.sol
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.24;

import {IFeeQuoter} from "../../interfaces/IFeeQuoter.sol";

import {KeystoneFeedsPermissionHandler} from "../../../keystone/KeystoneFeedsPermissionHandler.sol";
import {AuthorizedCallers} from "../../../shared/access/AuthorizedCallers.sol";
import {MockV3Aggregator} from "../../../tests/MockV3Aggregator.sol";
Expand Down Expand Up @@ -598,16 +596,15 @@ contract FeeQuoter_getTokenAndGasPrices is FeeQuoterSetup {

function test_ZeroGasPrice_Success() public {
uint64 zeroGasDestChainSelector = 345678;
Internal.GasPriceUpdate[] memory gasPriceUpdates = new Internal.GasPriceUpdate[](1);
gasPriceUpdates[0] = Internal.GasPriceUpdate({destChainSelector: zeroGasDestChainSelector, usdPerUnitGas: 0});
FeeQuoter.DestChainConfigArgs[] memory destChainConfigArgs = _generateFeeQuoterDestChainConfigArgs();
destChainConfigArgs[0].destChainSelector = zeroGasDestChainSelector;
destChainConfigArgs[0].destChainConfig.gasPriceStalenessThreshold = 0;

Internal.PriceUpdates memory priceUpdates =
Internal.PriceUpdates({tokenPriceUpdates: new Internal.TokenPriceUpdate[](0), gasPriceUpdates: gasPriceUpdates});
s_feeQuoter.updatePrices(priceUpdates);
s_feeQuoter.applyDestChainConfigUpdates(destChainConfigArgs);

(, uint224 gasPrice) = s_feeQuoter.getTokenAndGasPrices(s_sourceFeeToken, zeroGasDestChainSelector);

assertEq(gasPrice, priceUpdates.gasPriceUpdates[0].usdPerUnitGas);
assertEq(gasPrice, 0);
}

function test_UnsupportedChain_Revert() public {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,7 @@ contract FeeQuoterSetup is TokenSetup {
defaultTxGasLimit: GAS_LIMIT,
gasMultiplierWeiPerEth: 5e17,
networkFeeUSDCents: 1_00,
gasPriceStalenessThreshold: uint32(TWELVE_HOURS),
enforceOutOfOrder: false,
chainFamilySelector: Internal.CHAIN_FAMILY_SELECTOR_EVM
})
Expand Down

0 comments on commit bd87220

Please sign in to comment.