Skip to content

Commit

Permalink
feature: support ovm in ChainSpecificUtil (#10757)
Browse files Browse the repository at this point in the history
* feature: support ovm in ChainSpecificUtil

So far ChainSpecificUtil.sol has only supported arbitrum. This change
adds support for OVM/OP stack blockchains by using the
OVM_GasPriceOracle predeploy.

* feature: support OP L1 data fees

* Update chain-specific util with optimism fees and optimism tests

* Fix scripts

* Fix wrappers after merge

* Reduce upgraded version contract size

---------

Co-authored-by: Chris <[email protected]>
  • Loading branch information
makramkd and vreff authored Oct 5, 2023
1 parent f560107 commit edbcda7
Show file tree
Hide file tree
Showing 16 changed files with 657 additions and 114 deletions.
3 changes: 2 additions & 1 deletion contracts/scripts/native_solc_compile_all_vrf
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ compileContract mocks/VRFCoordinatorMock.sol

# VRF V2
compileContract vrf/VRFConsumerBaseV2.sol
compileContract vrf/testhelpers/ChainSpecificUtilHelper.sol
compileContract vrf/testhelpers/VRFConsumerV2.sol
compileContract vrf/testhelpers/VRFMaliciousConsumerV2.sol
compileContract vrf/testhelpers/VRFTestHelper.sol
Expand All @@ -57,7 +58,7 @@ compileContract vrf/VRFOwner.sol
# VRF V2Plus
compileContract dev/interfaces/IVRFCoordinatorV2PlusInternal.sol
compileContract dev/vrf/testhelpers/VRFV2PlusConsumerExample.sol
compileContractAltOpts dev/vrf/VRFCoordinatorV2_5.sol 500
compileContractAltOpts dev/vrf/VRFCoordinatorV2_5.sol 50
compileContract dev/vrf/BatchVRFCoordinatorV2Plus.sol
compileContract dev/vrf/VRFV2PlusWrapper.sol
compileContract dev/vrf/testhelpers/VRFConsumerV2PlusUpgradeableExample.sol
Expand Down
99 changes: 95 additions & 4 deletions contracts/src/v0.8/ChainSpecificUtil.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,56 @@ pragma solidity ^0.8.4;

import {ArbSys} from "./vendor/@arbitrum/nitro-contracts/src/precompiles/ArbSys.sol";
import {ArbGasInfo} from "./vendor/@arbitrum/nitro-contracts/src/precompiles/ArbGasInfo.sol";
import {OVM_GasPriceOracle} from "./vendor/@eth-optimism/contracts/v0.8.6/contracts/L2/predeploys/OVM_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.
//@dev For instance, ChainSpecificUtil.getBlockNumber() returns L2 block number in L2 chains
/// @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.
/// @dev For instance, ChainSpecificUtil.getBlockNumber() returns L2 block number in L2 chains
library ChainSpecificUtil {
// ------------ Start Arbitrum Constants ------------

/// @dev ARBSYS_ADDR is the address of the ArbSys precompile on Arbitrum.
/// @dev reference: https://github.com/OffchainLabs/nitro/blob/v2.0.14/contracts/src/precompiles/ArbSys.sol#L10
address private constant ARBSYS_ADDR = address(0x0000000000000000000000000000000000000064);
ArbSys private constant ARBSYS = ArbSys(ARBSYS_ADDR);

/// @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);

uint256 private constant ARB_MAINNET_CHAIN_ID = 42161;
uint256 private constant ARB_GOERLI_TESTNET_CHAIN_ID = 421613;
uint256 private constant ARB_SEPOLIA_TESTNET_CHAIN_ID = 421614;

// ------------ 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 OVM_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);
OVM_GasPriceOracle private constant OVM_GASPRICEORACLE = OVM_GasPriceOracle(OVM_GASPRICEORACLE_ADDR);

uint256 private constant OP_MAINNET_CHAIN_ID = 10;
uint256 private constant OP_GOERLI_CHAIN_ID = 420;
uint256 private constant OP_SEPOLIA_CHAIN_ID = 11155420;

/// @dev Base is a OP stack based rollup and follows the same L1 pricing logic as Optimism.
uint256 private constant BASE_MAINNET_CHAIN_ID = 8453;
uint256 private constant BASE_GOERLI_CHAIN_ID = 84531;

// ------------ End Optimism Constants ------------

/**
* @notice Returns the blockhash for the given blockNumber.
* @notice If the blockNumber is more than 256 blocks in the past, returns the empty string.
* @notice When on a known Arbitrum chain, it uses ArbSys.arbBlockHash to get the blockhash.
* @notice Otherwise, it uses the blockhash opcode.
* @notice Note that the blockhash opcode will return the L2 blockhash on Optimism.
*/
function getBlockhash(uint64 blockNumber) internal view returns (bytes32) {
uint256 chainid = block.chainid;
if (isArbitrumChainId(chainid)) {
Expand All @@ -27,6 +64,12 @@ library ChainSpecificUtil {
return blockhash(blockNumber);
}

/**
* @notice Returns the block number of the current block.
* @notice When on a known Arbitrum chain, it uses ArbSys.arbBlockNumber to get the block number.
* @notice Otherwise, it uses the block.number opcode.
* @notice Note that the block.number opcode will return the L2 block number on Optimism.
*/
function getBlockNumber() internal view returns (uint256) {
uint256 chainid = block.chainid;
if (isArbitrumChainId(chainid)) {
Expand All @@ -35,10 +78,20 @@ library ChainSpecificUtil {
return block.number;
}

function getCurrentTxL1GasFees() internal view returns (uint256) {
/**
* @notice Returns the L1 fees 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 OVM_GasPriceOracle predeploy
* @notice and getL1Fee is called to get the fees.
*/
function getCurrentTxL1GasFees(bytes memory txCallData) internal view returns (uint256) {
uint256 chainid = block.chainid;
if (isArbitrumChainId(chainid)) {
return ARBGAS.getCurrentTxL1GasFees();
} else if (isOptimismChainId(chainid)) {
return OVM_GASPRICEORACLE.getL1Fee(bytes.concat(txCallData, L1_FEE_DATA_PADDING));
}
return 0;
}
Expand All @@ -54,6 +107,8 @@ library ChainSpecificUtil {
// see https://developer.arbitrum.io/devs-how-tos/how-to-estimate-gas#where-do-we-get-all-this-information-from
// for the justification behind the 140 number.
return l1PricePerByte * (calldataSizeBytes + 140);
} else if (isOptimismChainId(chainid)) {
return calculateOptimismL1DataFee(calldataSizeBytes);
}
return 0;
}
Expand All @@ -67,4 +122,40 @@ library ChainSpecificUtil {
chainId == ARB_GOERLI_TESTNET_CHAIN_ID ||
chainId == ARB_SEPOLIA_TESTNET_CHAIN_ID;
}

/**
* @notice Return true if and only if the provided chain ID is an Optimism chain ID.
* @notice Note that optimism chain id's are also OP stack chain id's.
*/
function isOptimismChainId(uint256 chainId) internal pure returns (bool) {
return
chainId == OP_MAINNET_CHAIN_ID ||
chainId == OP_GOERLI_CHAIN_ID ||
chainId == OP_SEPOLIA_CHAIN_ID ||
chainId == BASE_MAINNET_CHAIN_ID ||
chainId == BASE_GOERLI_CHAIN_ID;
}

function calculateOptimismL1DataFee(uint256 calldataSizeBytes) internal view returns (uint256) {
// from: https://community.optimism.io/docs/developers/build/transaction-fees/#the-l1-data-fee
// l1_data_fee = l1_gas_price * (tx_data_gas + fixed_overhead) * dynamic_overhead
// tx_data_gas = count_zero_bytes(tx_data) * 4 + count_non_zero_bytes(tx_data) * 16
// note we conservatively assume all non-zero bytes.
uint256 l1BaseFeeWei = OVM_GASPRICEORACLE.l1BaseFee();
uint256 numZeroBytes = 0;
uint256 numNonzeroBytes = calldataSizeBytes - numZeroBytes;
uint256 txDataGas = numZeroBytes * 4 + numNonzeroBytes * 16;
uint256 fixedOverhead = OVM_GASPRICEORACLE.overhead();

// The scalar is some value like 0.684, but is represented as
// that times 10 ^ number of scalar decimals.
// e.g scalar = 0.684 * 10^6
// The divisor is used to divide that and have a net result of the true scalar.
uint256 scalar = OVM_GASPRICEORACLE.scalar();
uint256 scalarDecimals = OVM_GASPRICEORACLE.decimals();
uint256 divisor = 10 ** scalarDecimals;

uint256 l1DataFee = (l1BaseFeeWei * (txDataGas + fixedOverhead) * scalar) / divisor;
return l1DataFee;
}
}
4 changes: 2 additions & 2 deletions contracts/src/v0.8/dev/vrf/VRFCoordinatorV2_5.sol
Original file line number Diff line number Diff line change
Expand Up @@ -483,7 +483,7 @@ contract VRFCoordinatorV2_5 is VRF, SubscriptionAPI, IVRFCoordinatorV2Plus {
uint256 weiPerUnitGas
) internal view returns (uint96) {
// Will return non-zero on chains that have this enabled
uint256 l1CostWei = ChainSpecificUtil.getCurrentTxL1GasFees();
uint256 l1CostWei = ChainSpecificUtil.getCurrentTxL1GasFees(msg.data);
// calculate the payment without the premium
uint256 baseFeeWei = weiPerUnitGas * (gasAfterPaymentCalculation + startGas - gasleft());
// calculate the flat fee in wei
Expand All @@ -505,7 +505,7 @@ contract VRFCoordinatorV2_5 is VRF, SubscriptionAPI, IVRFCoordinatorV2Plus {
revert InvalidLinkWeiPrice(weiPerUnitLink);
}
// Will return non-zero on chains that have this enabled
uint256 l1CostWei = ChainSpecificUtil.getCurrentTxL1GasFees();
uint256 l1CostWei = ChainSpecificUtil.getCurrentTxL1GasFees(msg.data);
// (1e18 juels/link) ((wei/gas * gas) + l1wei) / (wei/link) = juels
uint256 paymentNoFee = (1e18 * (weiPerUnitGas * (gasAfterPaymentCalculation + startGas - gasleft()) + l1CostWei)) /
uint256(weiPerUnitLink);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ contract VRFCoordinatorV2PlusUpgradedVersion is
error ProvingKeyAlreadyRegistered(bytes32 keyHash);
error NoSuchProvingKey(bytes32 keyHash);
error InvalidLinkWeiPrice(int256 linkWei);
error InsufficientGasForConsumer(uint256 have, uint256 want);
error NoCorrespondingRequest();
error IncorrectCommitment();
error BlockhashNotInStore(uint256 blockNum);
Expand All @@ -57,7 +56,7 @@ contract VRFCoordinatorV2PlusUpgradedVersion is
bytes extraArgs;
}

mapping(bytes32 => address) /* keyHash */ /* oracle */ public s_provingKeys;
mapping(bytes32 => address) /* keyHash */ /* oracle */ internal s_provingKeys;
bytes32[] public s_provingKeyHashes;
mapping(uint256 => bytes32) /* requestID */ /* commitment */ public s_requestCommitments;

Expand All @@ -81,9 +80,9 @@ contract VRFCoordinatorV2PlusUpgradedVersion is
bool success
);

int256 public s_fallbackWeiPerUnitLink;
int256 internal s_fallbackWeiPerUnitLink;

FeeConfig public s_feeConfig;
FeeConfig internal s_feeConfig;

struct FeeConfig {
// Flat fee charged per fulfillment in millionths of link
Expand Down Expand Up @@ -476,7 +475,7 @@ contract VRFCoordinatorV2PlusUpgradedVersion is
uint256 weiPerUnitGas
) internal view returns (uint96) {
// Will return non-zero on chains that have this enabled
uint256 l1CostWei = ChainSpecificUtil.getCurrentTxL1GasFees();
uint256 l1CostWei = ChainSpecificUtil.getCurrentTxL1GasFees(msg.data);
// calculate the payment without the premium
uint256 baseFeeWei = weiPerUnitGas * (gasAfterPaymentCalculation + startGas - gasleft());
// calculate the flat fee in wei
Expand All @@ -498,7 +497,7 @@ contract VRFCoordinatorV2PlusUpgradedVersion is
revert InvalidLinkWeiPrice(weiPerUnitLink);
}
// Will return non-zero on chains that have this enabled
uint256 l1CostWei = ChainSpecificUtil.getCurrentTxL1GasFees();
uint256 l1CostWei = ChainSpecificUtil.getCurrentTxL1GasFees(msg.data);
// (1e18 juels/link) ((wei/gas * gas) + l1wei) / (wei/link) = juels
uint256 paymentNoFee = (1e18 * (weiPerUnitGas * (gasAfterPaymentCalculation + startGas - gasleft()) + l1CostWei)) /
uint256(weiPerUnitLink);
Expand Down Expand Up @@ -670,7 +669,7 @@ contract VRFCoordinatorV2PlusUpgradedVersion is
}

function migrationVersion() public pure returns (uint8 version) {
return 1;
return 2;
}

/**
Expand Down
2 changes: 1 addition & 1 deletion contracts/src/v0.8/vrf/VRFCoordinatorV2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -581,7 +581,7 @@ contract VRFCoordinatorV2 is VRF, ConfirmedOwner, TypeAndVersionInterface, VRFCo
revert InvalidLinkWeiPrice(weiPerUnitLink);
}
// Will return non-zero on chains that have this enabled
uint256 l1CostWei = ChainSpecificUtil.getCurrentTxL1GasFees();
uint256 l1CostWei = ChainSpecificUtil.getCurrentTxL1GasFees(msg.data);
// (1e18 juels/link) ((wei/gas * gas) + l1wei) / (wei/link) = juels
uint256 paymentNoFee = (1e18 * (weiPerUnitGas * (gasAfterPaymentCalculation + startGas - gasleft()) + l1CostWei)) /
uint256(weiPerUnitLink);
Expand Down
23 changes: 23 additions & 0 deletions contracts/src/v0.8/vrf/testhelpers/ChainSpecificUtilHelper.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "../../ChainSpecificUtil.sol";

/// @dev A helper contract that exposes ChainSpecificUtil methods for testing
contract ChainSpecificUtilHelper {
function getBlockhash(uint64 blockNumber) external view returns (bytes32) {
return ChainSpecificUtil.getBlockhash(blockNumber);
}

function getBlockNumber() external view returns (uint256) {
return ChainSpecificUtil.getBlockNumber();
}

function getCurrentTxL1GasFees(string memory txCallData) external view returns (uint256) {
return ChainSpecificUtil.getCurrentTxL1GasFees(bytes(txCallData));
}

function getL1CalldataGasCost(uint256 calldataSize) external view returns (uint256) {
return ChainSpecificUtil.getL1CalldataGasCost(calldataSize);
}
}
Loading

0 comments on commit edbcda7

Please sign in to comment.