diff --git a/contracts/src/v0.8/functions/dev/v1_X/FunctionsBilling.sol b/contracts/src/v0.8/functions/dev/v1_X/FunctionsBilling.sol index c973f55a715..9be1c9707e5 100644 --- a/contracts/src/v0.8/functions/dev/v1_X/FunctionsBilling.sol +++ b/contracts/src/v0.8/functions/dev/v1_X/FunctionsBilling.sol @@ -124,7 +124,6 @@ abstract contract FunctionsBilling is Routable, IFunctionsBilling { /// @inheritdoc IFunctionsBilling function getWeiPerUnitLink() public view returns (uint256) { (, int256 weiPerUnitLink, , uint256 timestamp, ) = s_linkToNativeFeed.latestRoundData(); - // Only fallback if feedStalenessSeconds is set // solhint-disable-next-line not-rely-on-time if (s_config.feedStalenessSeconds < block.timestamp - timestamp && s_config.feedStalenessSeconds > 0) { return s_config.fallbackNativePerUnitLink; @@ -144,7 +143,6 @@ abstract contract FunctionsBilling is Routable, IFunctionsBilling { /// @inheritdoc IFunctionsBilling function getUsdPerUnitLink() public view returns (uint256, uint8) { (, int256 usdPerUnitLink, , uint256 timestamp, ) = s_linkToUsdFeed.latestRoundData(); - // Only fallback if feedStalenessSeconds is set // solhint-disable-next-line not-rely-on-time if (s_config.feedStalenessSeconds < block.timestamp - timestamp && s_config.feedStalenessSeconds > 0) { return (s_config.fallbackUsdPerUnitLink, s_config.fallbackUsdPerUnitLinkDecimals); @@ -199,13 +197,14 @@ abstract contract FunctionsBilling is Routable, IFunctionsBilling { gasPriceWei = s_config.minimumEstimateGasPriceWei; } - uint256 gasPriceWithOverestimation = gasPriceWei + - ((gasPriceWei * s_config.fulfillmentGasPriceOverEstimationBP) / 10_000); - /// @NOTE: Basis Points are 1/100th of 1%, divide by 10_000 to bring back to original units - uint256 executionGas = s_config.gasOverheadBeforeCallback + s_config.gasOverheadAfterCallback + callbackGasLimit; - uint256 l1FeeWei = ChainSpecificUtil._getCurrentTxL1GasFees(msg.data); - uint96 estimatedGasReimbursementJuels = _getJuelsFromWei((gasPriceWithOverestimation * executionGas) + l1FeeWei); + uint256 l1FeeWei = ChainSpecificUtil._getL1FeeUpperLimit(s_config.transmitTxSizeBytes); + + uint256 totalFeeWei = (gasPriceWei * executionGas) + l1FeeWei; + // Basis Points are 1/100th of 1%, divide by 10_000 to bring back to original units + uint256 totalFeeWeiWithOverestimate = totalFee + ((totalFee * s_config.fulfillmentGasPriceOverEstimationBP) / 10_000); + + uint96 estimatedGasReimbursementJuels = _getJuelsFromWei(totalFeeWeiWithOverestimate); uint96 feesJuels = uint96(donFeeJuels) + uint96(adminFeeJuels) + uint96(operationFeeJuels); @@ -298,7 +297,7 @@ abstract contract FunctionsBilling is Routable, IFunctionsBilling { FunctionsResponse.Commitment memory commitment = abi.decode(onchainMetadata, (FunctionsResponse.Commitment)); uint256 gasOverheadWei = (commitment.gasOverheadBeforeCallback + commitment.gasOverheadAfterCallback) * tx.gasprice; - uint256 l1FeeShareWei = ChainSpecificUtil._getCurrentTxL1GasFees(msg.data) / reportBatchSize; + uint256 l1FeeShareWei = ChainSpecificUtil._getL1FeeUpperLimit(msg.data.length) / reportBatchSize; // Gas overhead without callback uint96 gasOverheadJuels = _getJuelsFromWei(gasOverheadWei + l1FeeShareWei); uint96 juelsPerGas = _getJuelsFromWei(tx.gasprice); @@ -422,10 +421,6 @@ abstract contract FunctionsBilling is Routable, IFunctionsBilling { revert NoTransmittersSet(); } uint96 feePoolShare = s_feePool / uint96(numberOfTransmitters); - if (feePoolShare == 0) { - // Dust cannot be evenly distributed to all transmitters - return; - } // Bounded by "maxNumOracles" on OCR2Abstract.sol for (uint256 i = 0; i < numberOfTransmitters; ++i) { s_withdrawableTokens[transmitters[i]] += feePoolShare; diff --git a/contracts/src/v0.8/functions/dev/v1_X/FunctionsCoordinator.sol b/contracts/src/v0.8/functions/dev/v1_X/FunctionsCoordinator.sol index f0bec7c3e9c..69d6f3a7b0a 100644 --- a/contracts/src/v0.8/functions/dev/v1_X/FunctionsCoordinator.sol +++ b/contracts/src/v0.8/functions/dev/v1_X/FunctionsCoordinator.sol @@ -16,7 +16,7 @@ contract FunctionsCoordinator is OCR2Base, IFunctionsCoordinator, FunctionsBilli using FunctionsResponse for FunctionsResponse.FulfillResult; /// @inheritdoc ITypeAndVersion - string public constant override typeAndVersion = "Functions Coordinator v1.3.0"; + string public constant override typeAndVersion = "Functions Coordinator v1.3.1"; event OracleRequest( bytes32 indexed requestId, diff --git a/contracts/src/v0.8/functions/dev/v1_X/interfaces/IFunctionsBilling.sol b/contracts/src/v0.8/functions/dev/v1_X/interfaces/IFunctionsBilling.sol index ecf15c68f0d..66a9f24577f 100644 --- a/contracts/src/v0.8/functions/dev/v1_X/interfaces/IFunctionsBilling.sol +++ b/contracts/src/v0.8/functions/dev/v1_X/interfaces/IFunctionsBilling.sol @@ -59,7 +59,7 @@ interface IFunctionsBilling { struct FunctionsBillingConfig { uint32 fulfillmentGasPriceOverEstimationBP; // ══╗ Percentage of gas price overestimation to account for changes in gas price between request and response. Held as basis points (one hundredth of 1 percentage point) - uint32 feedStalenessSeconds; // ║ How long before we consider the feed price to be stale and fallback to fallbackNativePerUnitLink. Default of 0 means no fallback. + uint32 feedStalenessSeconds; // ║ How long before we consider the feed price to be stale and fallback to fallbackNativePerUnitLink. uint32 gasOverheadBeforeCallback; // ║ Represents the average gas execution cost before the fulfillment callback. This amount is always billed for every request. uint32 gasOverheadAfterCallback; // ║ Represents the average gas execution cost after the fulfillment callback. This amount is always billed for every request. uint40 minimumEstimateGasPriceWei; // ║ The lowest amount of wei that will be used as the tx.gasprice when estimating the cost to fulfill the request @@ -69,5 +69,6 @@ struct FunctionsBillingConfig { uint224 fallbackNativePerUnitLink; // ═══════════╗ Fallback NATIVE CURRENCY / LINK conversion rate if the data feed is stale uint32 requestTimeoutSeconds; // ════════════════╝ How many seconds it takes before we consider a request to be timed out uint16 donFeeCentsUsd; // ═══════════════════════════════╗ Additional flat fee (denominated in cents of USD, paid as LINK) that will be split between Node Operators. - uint16 operationFeeCentsUsd; // ═════════════════════════╝ Additional flat fee (denominated in cents of USD, paid as LINK) that will be paid to the owner of the Coordinator contract. + uint16 operationFeeCentsUsd; // ║ Additional flat fee (denominated in cents of USD, paid as LINK) that will be paid to the owner of the Coordinator contract. + uint24 transmitTxSizeBytes; // ══════════════════════════╝ The size of the transmit transaction in bytes assuming a single 256 byte response payload. Used to estimate L1 cost for fulfillments on L2 chains. } diff --git a/contracts/src/v0.8/functions/dev/v1_X/libraries/ChainSpecificUtil.sol b/contracts/src/v0.8/functions/dev/v1_X/libraries/ChainSpecificUtil.sol index 574d1bf1645..b6d9ae32418 100644 --- a/contracts/src/v0.8/functions/dev/v1_X/libraries/ChainSpecificUtil.sol +++ b/contracts/src/v0.8/functions/dev/v1_X/libraries/ChainSpecificUtil.sol @@ -2,13 +2,15 @@ pragma solidity ^0.8.19; import {ArbGasInfo} from "../../../../vendor/@arbitrum/nitro-contracts/src/precompiles/ArbGasInfo.sol"; -import {GasPriceOracle} from "../../../../vendor/@eth-optimism/contracts-bedrock/v0.16.2/src/L2/GasPriceOracle.sol"; +import {L1Block} from "../../../../vendor/@eth-optimism/contracts-bedrock/v0.17.1/src/L2/L1Block.sol"; /// @dev A library that abstracts out opcodes that behave differently across chains. /// @dev The methods below return values that are pertinent to the given chain. library ChainSpecificUtil { // ------------ Start Arbitrum Constants ------------ + /// @dev ARB_L1_FEE_DATA_PADDING_SIZE is the L1 data padding for Optimism + uint256 private const ARB_L1_FEE_DATA_PADDING_SIZE = 140; /// @dev ARBGAS_ADDR is the address of the ArbGasInfo precompile on Arbitrum. /// @dev reference: https://github.com/OffchainLabs/nitro/blob/v2.0.14/contracts/src/precompiles/ArbGasInfo.sol#L10 address private constant ARBGAS_ADDR = address(0x000000000000000000000000000000000000006C); @@ -21,13 +23,11 @@ library ChainSpecificUtil { // ------------ End Arbitrum Constants ------------ // ------------ Start Optimism Constants ------------ - /// @dev L1_FEE_DATA_PADDING includes 35 bytes for L1 data padding for Optimism - bytes internal constant L1_FEE_DATA_PADDING = - "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"; - /// @dev OVM_GASPRICEORACLE_ADDR is the address of the GasPriceOracle precompile on Optimism. - /// @dev reference: https://community.optimism.io/docs/developers/build/transaction-fees/#estimating-the-l1-data-fee - address private constant OVM_GASPRICEORACLE_ADDR = address(0x420000000000000000000000000000000000000F); - GasPriceOracle private constant OVM_GASPRICEORACLE = GasPriceOracle(OVM_GASPRICEORACLE_ADDR); + /// @dev OP_L1_FEE_DATA_PADDING_SIZE is the L1 data padding for Optimism + uint256 private const OP_L1_FEE_DATA_PADDING_SIZE = 35; + /// @dev L1BLOCK_ADDR is the address of the L1Block precompile on Optimism. + address private constant L1BLOCK_ADDR = address(0x4200000000000000000000000000000000000015); + L1Block private constant L1BLOCK = L1Block(L1BLOCK_ADDR); uint256 private constant OP_MAINNET_CHAIN_ID = 10; uint256 private constant OP_GOERLI_CHAIN_ID = 420; @@ -40,18 +40,23 @@ library ChainSpecificUtil { // ------------ End Optimism Constants ------------ - /// @notice Returns the L1 fees in wei that will be paid for the current transaction, given any calldata - /// @notice for the current transaction. - /// @notice When on a known Arbitrum chain, it uses ArbGas.getCurrentTxL1GasFees to get the fees. - /// @notice On Arbitrum, the provided calldata is not used to calculate the fees. - /// @notice On Optimism, the provided calldata is passed to the GasPriceOracle predeploy - /// @notice and getL1Fee is called to get the fees. - function _getCurrentTxL1GasFees(bytes memory txCallData) internal view returns (uint256 l1FeeWei) { + /// @notice Returns the upper limit estimate of the L1 fees in wei that will be paid for L2 chains + /// @notice based on the size of the transaction data and the current gas conditions. + function _getL1FeeUpperLimit(uint256 dataSizeBytes) internal view returns (uint256 l1FeeWei) { uint256 chainid = block.chainid; if (_isArbitrumChainId(chainid)) { - return ARBGAS.getCurrentTxL1GasFees(); + // https://docs.arbitrum.io/build-decentralized-apps/how-to-estimate-gas#where-do-we-get-all-this-information-from + (, uint256 l1PricePerByte, , , , ) = ARBGAS.getPricesInWei(); + return l1PricePerByte * (dataSizeBytes + ARB_L1_FEE_DATA_PADDING_SIZE); } else if (_isOptimismChainId(chainid)) { - return OVM_GASPRICEORACLE.getL1Fee(bytes.concat(txCallData, L1_FEE_DATA_PADDING)); + // https://docs.optimism.io/stack/transactions/fees#ecotone + // note we conservatively assume all non-zero bytes: tx_compressed_size = tx_data_size_bytes + uint256 l1BaseFeeWei = L1BLOCK.baseFee(); + uint256 l1BaseFeeScalar = L1BLOCK.baseFeeScalar(); + uint256 l1BlobBaseFeeWei = L1BLOCK.blobBaseFee(); + uint256 l1BlobBaseFeeScalar = L1BLOCK.blobBaseFeeScalar(); + uint256 weightedGasPrice = 16 * l1BaseFeeScalar * l1BaseFee + l1BlobBaseFeeScalar * l1BlobBaseFeeWei; + return weightedGasPrice * dataSizeBytes; } return 0; }