Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved L1->L2 fee calculation logic for Functions #13268

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/neat-peas-reflect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"chainlink": patch
---

#internal Updated wrappers for improved L1 -> L2 fee calculation for Functions
5 changes: 5 additions & 0 deletions contracts/.changeset/serious-cats-fold.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@chainlink/contracts": patch
---

Implemented improved L1 fee calculation for L2 chains in Functions contracts
92 changes: 46 additions & 46 deletions contracts/gas-snapshots/functions.gas-snapshot

Large diffs are not rendered by default.

16 changes: 9 additions & 7 deletions contracts/src/v0.8/functions/dev/v1_X/FunctionsBilling.sol
Original file line number Diff line number Diff line change
Expand Up @@ -199,13 +199,15 @@ 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 = totalFeeWei +
((totalFeeWei * s_config.fulfillmentGasPriceOverEstimationBP) / 10_000);

uint96 estimatedGasReimbursementJuels = _getJuelsFromWei(totalFeeWeiWithOverestimate);

uint96 feesJuels = uint96(donFeeJuels) + uint96(adminFeeJuels) + uint96(operationFeeJuels);

Expand Down Expand Up @@ -298,7 +300,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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
uint16 transmitTxSizeBytes; // ══════════════════════════╝ The size of the calldata for the transmit transaction in bytes assuming a single 256 byte response payload. Used to estimate L1 cost for fulfillments on L2 chains.
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,19 @@
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 {GasPriceOracle} from "../../../../vendor/@eth-optimism/contracts-bedrock/v0.17.3/src/L2/GasPriceOracle.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 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);
ArbGasInfo private constant ARBGAS = ArbGasInfo(ARBGAS_ADDR);
/// @dev ARB_DATA_PADDING_SIZE is the max size of the "static" data on Arbitrum for the transaction which refers to the tx data that is not the calldata (signature, etc.)
/// @dev reference: https://docs.arbitrum.io/build-decentralized-apps/how-to-estimate-gas#where-do-we-get-all-this-information-from
uint256 private constant ARB_DATA_PADDING_SIZE = 140;

uint256 private constant ARB_MAINNET_CHAIN_ID = 42161;
uint256 private constant ARB_GOERLI_TESTNET_CHAIN_ID = 421613;
Expand All @@ -21,13 +23,9 @@ 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 GAS_PRICE_ORACLE_ADDR is the address of the GasPriceOracle precompile on Optimism.
address private constant GAS_PRICE_ORACLE_ADDR = address(0x420000000000000000000000000000000000000F);
GasPriceOracle private constant GAS_PRICE_ORACLE = GasPriceOracle(GAS_PRICE_ORACLE_ADDR);

uint256 private constant OP_MAINNET_CHAIN_ID = 10;
uint256 private constant OP_GOERLI_CHAIN_ID = 420;
Expand All @@ -40,18 +38,17 @@ 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.
KuphJr marked this conversation as resolved.
Show resolved Hide resolved
/// @notice This is an "upper limit" as it assumes the transaction data is uncompressed when posted on L1.
function _getL1FeeUpperLimit(uint256 calldataSizeBytes) 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 * (calldataSizeBytes + ARB_DATA_PADDING_SIZE);
} else if (_isOptimismChainId(chainid)) {
return OVM_GASPRICEORACLE.getL1Fee(bytes.concat(txCallData, L1_FEE_DATA_PADDING));
return GAS_PRICE_ORACLE.getL1FeeUpperBound(calldataSizeBytes);
}
return 0;
}
Expand Down
Loading
Loading