Skip to content

Commit

Permalink
Merge 43ae009 into 53312f0
Browse files Browse the repository at this point in the history
  • Loading branch information
KuphJr authored May 18, 2024
2 parents 53312f0 + 43ae009 commit 4da58a0
Show file tree
Hide file tree
Showing 14 changed files with 1,069 additions and 65 deletions.
21 changes: 8 additions & 13 deletions contracts/src/v0.8/functions/dev/v1_X/FunctionsBilling.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
Expand Down Expand Up @@ -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 = 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 +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);
Expand Down Expand Up @@ -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;
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 @@ -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
Expand All @@ -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.
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 constant 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);
Expand All @@ -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 constant 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;
Expand All @@ -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 * l1BaseFeeWei + l1BlobBaseFeeScalar * l1BlobBaseFeeWei;
return weightedGasPrice * dataSizeBytes;
}
return 0;
}
Expand Down
103 changes: 74 additions & 29 deletions contracts/src/v0.8/functions/tests/v1_X/ChainSpecificUtil.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,29 @@ import {FunctionsResponse} from "../../dev/v1_X/libraries/FunctionsResponse.sol"
import {FunctionsFulfillmentSetup} from "./Setup.t.sol";

import {ArbGasInfo} from "../../../vendor/@arbitrum/nitro-contracts/src/precompiles/ArbGasInfo.sol";
import {OVM_GasPriceOracle} from "../../../vendor/@eth-optimism/contracts/v0.8.9/contracts/L2/predeploys/OVM_GasPriceOracle.sol";
import {L1Block} from "../../../vendor/@eth-optimism/contracts-bedrock/v0.17.1/src/L2/L1Block.sol";

/// @notice #_getCurrentTxL1GasFees Arbitrum
/// @notice #_getL1FeeUpperLimit Arbitrum
/// @dev Arbitrum gas formula = L2 Gas Price * (Gas used on L2 + Extra Buffer for L1 cost)
/// @dev where Extra Buffer for L1 cost = (L1 Estimated Cost / L2 Gas Price)
contract ChainSpecificUtil__getCurrentTxL1GasFees_Arbitrum is FunctionsFulfillmentSetup {
contract ChainSpecificUtil__getL1FeeUpperLimit_Arbitrum is FunctionsFulfillmentSetup {
address private constant ARBGAS_ADDR = address(0x000000000000000000000000000000000000006C);
uint256 private constant L1_FEE_WEI = 15_818_209_764_247;

uint96 l1FeeJuels = uint96((1e18 * L1_FEE_WEI) / uint256(LINK_ETH_RATE));

function setUp() public virtual override {
vm.mockCall(ARBGAS_ADDR, abi.encodeWithSelector(ArbGasInfo.getCurrentTxL1GasFees.selector), abi.encode(L1_FEE_WEI));
uint256 unused = 0;
uint256 gasPerL1CalldataByte = 4;
vm.mockCall(
ARBGAS_ADDR,
abi.encodeWithSelector(
ArbGasInfo.getPricesInWei.selector),
abi.encode(unused, gasPerL1CalldataByte, unused, unused, unused, unused)
);
}

function test__getCurrentTxL1GasFees_SuccessWhenArbitrumMainnet() public {
function test__getL1FeeUpperLimit_SuccessWhenArbitrumMainnet() public {
// Set the chainID
vm.chainId(42161);

Expand All @@ -43,7 +50,7 @@ contract ChainSpecificUtil__getCurrentTxL1GasFees_Arbitrum is FunctionsFulfillme
assertEq(s_responses[1].totalCostJuels, expectedTotalCostJuels);
}

function test__getCurrentTxL1GasFees_SuccessWhenArbitrumGoerli() public {
function test__getL1FeeUpperLimit_SuccessWhenArbitrumGoerli() public {
// Set the chainID
vm.chainId(421613);

Expand All @@ -60,7 +67,7 @@ contract ChainSpecificUtil__getCurrentTxL1GasFees_Arbitrum is FunctionsFulfillme
assertEq(s_responses[1].totalCostJuels, expectedTotalCostJuels);
}

function test__getCurrentTxL1GasFees_SuccessWhenArbitrumSepolia() public {
function test__getL1FeeUpperLimit_SuccessWhenArbitrumSepolia() public {
// Set the chainID
vm.chainId(421614);

Expand All @@ -78,24 +85,43 @@ contract ChainSpecificUtil__getCurrentTxL1GasFees_Arbitrum is FunctionsFulfillme
}
}

/// @notice #_getCurrentTxL1GasFees Optimism
/// @notice #_getL1FeeUpperLimit Optimism
/// @dev Optimism gas formula = ((l2_base_fee + l2_priority_fee) * l2_gas_used) + L1 data fee
/// @dev where L1 data fee = l1_gas_price * ((count_zero_bytes(tx_data) * 4 + count_non_zero_bytes(tx_data) * 16) + fixed_overhead + noncalldata_gas) * dynamic_overhead
contract ChainSpecificUtil__getCurrentTxL1GasFees_Optimism is FunctionsFulfillmentSetup {
address private constant OVM_GASPRICEORACLE_ADDR = address(0x420000000000000000000000000000000000000F);
uint256 private constant L1_FEE_WEI = 15_818_209_764_247;
contract ChainSpecificUtil__getL1FeeUpperLimit_Optimism is FunctionsFulfillmentSetup {
address private constant L1BLOCK_ADDR = address(0x4200000000000000000000000000000000000015);
L1Block private constant L1BLOCK = L1Block(L1BLOCK_ADDR);
uint256 private constant L1_BASE_FEE_WEI = 15_818_209;
uint256 private constant L1_BASE_FEE_SCALAR = 2;
uint256 private constant L1_BLOB_BASE_FEE_WEI = 15_818_209;
uint256 private constant L1_BLOB_BASE_FEE_SCALAR = 2;

uint96 l1FeeJuels = uint96((1e18 * L1_FEE_WEI) / uint256(LINK_ETH_RATE));
uint96 l1FeeJuels = uint96((1e18 * L1_BASE_FEE_WEI) / uint256(LINK_ETH_RATE));

function setUp() public virtual override {
vm.mockCall(
OVM_GASPRICEORACLE_ADDR,
abi.encodeWithSelector(OVM_GasPriceOracle.getL1Fee.selector),
abi.encode(L1_FEE_WEI)
L1BLOCK_ADDR,
abi.encodeWithSelector(L1BLOCK.basefee.selector),
abi.encode(L1_BASE_FEE_WEI)
);
vm.mockCall(
L1BLOCK_ADDR,
abi.encodeWithSelector(L1BLOCK.baseFeeScalar.selector),
abi.encode(L1_BASE_FEE_SCALAR)
);
vm.mockCall(
L1BLOCK_ADDR,
abi.encodeWithSelector(L1BLOCK.blobBaseFee.selector),
abi.encode(L1_BLOB_BASE_FEE_WEI)
);
vm.mockCall(
L1BLOCK_ADDR,
abi.encodeWithSelector(L1BLOCK.blobBaseFeeScalar.selector),
abi.encode(L1_BLOB_BASE_FEE_SCALAR)
);
}

function test__getCurrentTxL1GasFees_SuccessWhenOptimismMainnet() public {
function test__getL1FeeUpperLimit_SuccessWhenOptimismMainnet() public {
// Set the chainID
vm.chainId(10);

Expand All @@ -112,7 +138,7 @@ contract ChainSpecificUtil__getCurrentTxL1GasFees_Optimism is FunctionsFulfillme
assertEq(s_responses[1].totalCostJuels, expectedTotalCostJuels);
}

function test__getCurrentTxL1GasFees_SuccessWhenOptimismGoerli() public {
function test__getL1FeeUpperLimit_SuccessWhenOptimismGoerli() public {
// Set the chainID
vm.chainId(420);

Expand All @@ -129,7 +155,7 @@ contract ChainSpecificUtil__getCurrentTxL1GasFees_Optimism is FunctionsFulfillme
assertEq(s_responses[1].totalCostJuels, expectedTotalCostJuels);
}

function test__getCurrentTxL1GasFees_SuccessWhenOptimismSepolia() public {
function test__getL1FeeUpperLimit_SuccessWhenOptimismSepolia() public {
// Set the chainID
vm.chainId(11155420);

Expand All @@ -147,24 +173,43 @@ contract ChainSpecificUtil__getCurrentTxL1GasFees_Optimism is FunctionsFulfillme
}
}

/// @notice #_getCurrentTxL1GasFees Base
/// @notice #_getL1FeeUpperLimit Base
/// @dev Base gas formula uses Optimism formula = ((l2_base_fee + l2_priority_fee) * l2_gas_used) + L1 data fee
/// @dev where L1 data fee = l1_gas_price * ((count_zero_bytes(tx_data) * 4 + count_non_zero_bytes(tx_data) * 16) + fixed_overhead + noncalldata_gas) * dynamic_overhead
contract ChainSpecificUtil__getCurrentTxL1GasFees_Base is FunctionsFulfillmentSetup {
address private constant OVM_GASPRICEORACLE_ADDR = address(0x420000000000000000000000000000000000000F);
uint256 private constant L1_FEE_WEI = 15_818_209_764_247;
contract ChainSpecificUtil__getL1FeeUpperLimit_Base is FunctionsFulfillmentSetup {
address private constant L1BLOCK_ADDR = address(0x4200000000000000000000000000000000000015);
L1Block private constant L1BLOCK = L1Block(L1BLOCK_ADDR);
uint256 private constant L1_BASE_FEE_WEI = 15_818_209;
uint256 private constant L1_BASE_FEE_SCALAR = 2;
uint256 private constant L1_BLOB_BASE_FEE_WEI = 15_818_209;
uint256 private constant L1_BLOB_BASE_FEE_SCALAR = 2;

uint96 l1FeeJuels = uint96((1e18 * L1_FEE_WEI) / uint256(LINK_ETH_RATE));
uint96 l1FeeJuels = uint96((1e18 * L1_BASE_FEE_WEI) / uint256(LINK_ETH_RATE));

function setUp() public virtual override {
vm.mockCall(
OVM_GASPRICEORACLE_ADDR,
abi.encodeWithSelector(OVM_GasPriceOracle.getL1Fee.selector),
abi.encode(L1_FEE_WEI)
L1BLOCK_ADDR,
abi.encodeWithSelector(L1BLOCK.basefee.selector),
abi.encode(L1_BASE_FEE_WEI)
);
vm.mockCall(
L1BLOCK_ADDR,
abi.encodeWithSelector(L1BLOCK.baseFeeScalar.selector),
abi.encode(L1_BASE_FEE_SCALAR)
);
vm.mockCall(
L1BLOCK_ADDR,
abi.encodeWithSelector(L1BLOCK.blobBaseFee.selector),
abi.encode(L1_BLOB_BASE_FEE_WEI)
);
vm.mockCall(
L1BLOCK_ADDR,
abi.encodeWithSelector(L1BLOCK.blobBaseFeeScalar.selector),
abi.encode(L1_BLOB_BASE_FEE_SCALAR)
);
}

function test__getCurrentTxL1GasFees_SuccessWhenBaseMainnet() public {
function test__getL1FeeUpperLimit_SuccessWhenBaseMainnet() public {
// Set the chainID
vm.chainId(8453);

Expand All @@ -181,7 +226,7 @@ contract ChainSpecificUtil__getCurrentTxL1GasFees_Base is FunctionsFulfillmentSe
assertEq(s_responses[1].totalCostJuels, expectedTotalCostJuels);
}

function test__getCurrentTxL1GasFees_SuccessWhenBaseGoerli() public {
function test__getL1FeeUpperLimit_SuccessWhenBaseGoerli() public {
// Set the chainID
vm.chainId(84531);

Expand All @@ -198,7 +243,7 @@ contract ChainSpecificUtil__getCurrentTxL1GasFees_Base is FunctionsFulfillmentSe
assertEq(s_responses[1].totalCostJuels, expectedTotalCostJuels);
}

function test__getCurrentTxL1GasFees_SuccessWhenBaseSepolia() public {
function test__getL1FeeUpperLimit_SuccessWhenBaseSepolia() public {
// Set the chainID
vm.chainId(84532);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ contract FunctionsBilling_UpdateConfig is FunctionsRouterSetup {
fallbackNativePerUnitLink: getCoordinatorConfig().fallbackNativePerUnitLink * 2,
fallbackUsdPerUnitLink: getCoordinatorConfig().fallbackUsdPerUnitLink * 2,
fallbackUsdPerUnitLinkDecimals: getCoordinatorConfig().fallbackUsdPerUnitLinkDecimals * 2,
minimumEstimateGasPriceWei: getCoordinatorConfig().minimumEstimateGasPriceWei * 2
minimumEstimateGasPriceWei: getCoordinatorConfig().minimumEstimateGasPriceWei * 2,
transmitTxSizeBytes: getCoordinatorConfig().transmitTxSizeBytes * 2
});
}

Expand Down
Loading

0 comments on commit 4da58a0

Please sign in to comment.