// SPDX-License-Identifier: MIT pragma solidity ^0.8.9; 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.9/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 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; uint256 private constant BASE_SEPOLIA_CHAIN_ID = 84532; // ------------ 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)) { if ((_getBlockNumber() - blockNumber) > 256 || blockNumber >= _getBlockNumber()) { return ""; } return ARBSYS.arbBlockHash(blockNumber); } 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)) { return ARBSYS.arbBlockNumber(); } return block.number; } /** * @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, there's no need to pass the provided calldata * @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; } /** * @notice Returns the gas cost in wei of calldataSizeBytes of calldata being posted * @notice to L1. */ function _getL1CalldataGasCost(uint256 calldataSizeBytes) internal view returns (uint256) { uint256 chainid = block.chainid; if (_isArbitrumChainId(chainid)) { (, uint256 l1PricePerByte, , , , ) = ARBGAS.getPricesInWei(); // 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; } /** * @notice Return true if and only if the provided chain ID is an Arbitrum chain ID. */ function _isArbitrumChainId(uint256 chainId) internal pure returns (bool) { return chainId == ARB_MAINNET_CHAIN_ID || 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 (e.g. Base). */ 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 || chainId == BASE_SEPOLIA_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; } }