diff --git a/build.rs b/build.rs index 52577281d..c50c6dea6 100644 --- a/build.rs +++ b/build.rs @@ -28,7 +28,7 @@ fn generate_contract_bindings() -> Result<(), Box> { abigen_of("SimpleAccountFactory")?, abigen_of("VerifyingPaymaster")?, abigen_of("NodeInterface")?, - abigen_of("OVM_GasPriceOracle")?, + abigen_of("GasPriceOracle")?, ]) .build()? .write_to_module("src/common/contracts", false)?; diff --git a/contracts/src/optimism/GasPriceOracle.sol b/contracts/src/optimism/GasPriceOracle.sol new file mode 100644 index 000000000..52118746e --- /dev/null +++ b/contracts/src/optimism/GasPriceOracle.sol @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +// From https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/GasPriceOracle.sol + +import { Predeploys } from "./Predeploys.sol"; +import { L1Block } from "./L1Block.sol"; + +/// @custom:proxied +/// @custom:predeploy 0x420000000000000000000000000000000000000F +/// @title GasPriceOracle +/// @notice This contract maintains the variables responsible for computing the L1 portion of the +/// total fee charged on L2. Before Bedrock, this contract held variables in state that were +/// read during the state transition function to compute the L1 portion of the transaction +/// fee. After Bedrock, this contract now simply proxies the L1Block contract, which has +/// the values used to compute the L1 portion of the fee in its state. +/// +/// The contract exposes an API that is useful for knowing how large the L1 portion of the +/// transaction fee will be. The following events were deprecated with Bedrock: +/// - event OverheadUpdated(uint256 overhead); +/// - event ScalarUpdated(uint256 scalar); +/// - event DecimalsUpdated(uint256 decimals); +contract GasPriceOracle { + /// @notice Number of decimals used in the scalar. + uint256 public constant DECIMALS = 6; + + /// @notice Computes the L1 portion of the fee based on the size of the rlp encoded input + /// transaction, the current L1 base fee, and the various dynamic parameters. + /// @param _data Unsigned fully RLP-encoded transaction to get the L1 fee for. + /// @return L1 fee that should be paid for the tx + function getL1Fee(bytes memory _data) external view returns (uint256) { + uint256 l1GasUsed = getL1GasUsed(_data); + uint256 l1Fee = l1GasUsed * l1BaseFee(); + uint256 divisor = 10 ** DECIMALS; + uint256 unscaled = l1Fee * scalar(); + uint256 scaled = unscaled / divisor; + return scaled; + } + + /// @notice Retrieves the current gas price (base fee). + /// @return Current L2 gas price (base fee). + function gasPrice() public view returns (uint256) { + return block.basefee; + } + + /// @notice Retrieves the current base fee. + /// @return Current L2 base fee. + function baseFee() public view returns (uint256) { + return block.basefee; + } + + /// @notice Retrieves the current fee overhead. + /// @return Current fee overhead. + function overhead() public view returns (uint256) { + return L1Block(Predeploys.L1_BLOCK_ATTRIBUTES).l1FeeOverhead(); + } + + /// @notice Retrieves the current fee scalar. + /// @return Current fee scalar. + function scalar() public view returns (uint256) { + return L1Block(Predeploys.L1_BLOCK_ATTRIBUTES).l1FeeScalar(); + } + + /// @notice Retrieves the latest known L1 base fee. + /// @return Latest known L1 base fee. + function l1BaseFee() public view returns (uint256) { + return L1Block(Predeploys.L1_BLOCK_ATTRIBUTES).basefee(); + } + + /// @custom:legacy + /// @notice Retrieves the number of decimals used in the scalar. + /// @return Number of decimals used in the scalar. + function decimals() public pure returns (uint256) { + return DECIMALS; + } + + /// @notice Computes the amount of L1 gas used for a transaction. Adds the overhead which + /// represents the per-transaction gas overhead of posting the transaction and state + /// roots to L1. Adds 68 bytes of padding to account for the fact that the input does + /// not have a signature. + /// @param _data Unsigned fully RLP-encoded transaction to get the L1 gas for. + /// @return Amount of L1 gas used to publish the transaction. + function getL1GasUsed(bytes memory _data) public view returns (uint256) { + uint256 total = 0; + uint256 length = _data.length; + for (uint256 i = 0; i < length; i++) { + if (_data[i] == 0) { + total += 4; + } else { + total += 16; + } + } + uint256 unsigned = total + overhead(); + return unsigned + (68 * 16); + } +} \ No newline at end of file diff --git a/contracts/src/optimism/L1Block.sol b/contracts/src/optimism/L1Block.sol new file mode 100644 index 000000000..a5780db83 --- /dev/null +++ b/contracts/src/optimism/L1Block.sol @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +// From https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/L1Block.sol + +/// @custom:proxied +/// @custom:predeploy 0x4200000000000000000000000000000000000015 +/// @title L1Block +/// @notice The L1Block predeploy gives users access to information about the last known L1 block. +/// Values within this contract are updated once per epoch (every L1 block) and can only be +/// set by the "depositor" account, a special system address. Depositor account transactions +/// are created by the protocol whenever we move to a new epoch. +contract L1Block { + /// @notice Address of the special depositor account. + address public constant DEPOSITOR_ACCOUNT = 0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001; + + /// @notice The latest L1 block number known by the L2 system. + uint64 public number; + + /// @notice The latest L1 timestamp known by the L2 system. + uint64 public timestamp; + + /// @notice The latest L1 basefee. + uint256 public basefee; + + /// @notice The latest L1 blockhash. + bytes32 public hash; + + /// @notice The number of L2 blocks in the same epoch. + uint64 public sequenceNumber; + + /// @notice The versioned hash to authenticate the batcher by. + bytes32 public batcherHash; + + /// @notice The overhead value applied to the L1 portion of the transaction fee. + uint256 public l1FeeOverhead; + + /// @notice The scalar value applied to the L1 portion of the transaction fee. + uint256 public l1FeeScalar; + + /// @notice Updates the L1 block values. + /// @param _number L1 blocknumber. + /// @param _timestamp L1 timestamp. + /// @param _basefee L1 basefee. + /// @param _hash L1 blockhash. + /// @param _sequenceNumber Number of L2 blocks since epoch start. + /// @param _batcherHash Versioned hash to authenticate batcher by. + /// @param _l1FeeOverhead L1 fee overhead. + /// @param _l1FeeScalar L1 fee scalar. + function setL1BlockValues( + uint64 _number, + uint64 _timestamp, + uint256 _basefee, + bytes32 _hash, + uint64 _sequenceNumber, + bytes32 _batcherHash, + uint256 _l1FeeOverhead, + uint256 _l1FeeScalar + ) + external + { + require(msg.sender == DEPOSITOR_ACCOUNT, "L1Block: only the depositor account can set L1 block values"); + + number = _number; + timestamp = _timestamp; + basefee = _basefee; + hash = _hash; + sequenceNumber = _sequenceNumber; + batcherHash = _batcherHash; + l1FeeOverhead = _l1FeeOverhead; + l1FeeScalar = _l1FeeScalar; + } +} diff --git a/contracts/src/optimism/OVM_GasPriceOracle.sol b/contracts/src/optimism/OVM_GasPriceOracle.sol deleted file mode 100644 index caacc8be6..000000000 --- a/contracts/src/optimism/OVM_GasPriceOracle.sol +++ /dev/null @@ -1,164 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.9; - -// From https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts/contracts/L2/predeploys/OVM_GasPriceOracle.sol - -/* External Imports */ -import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; - -/** - * @title OVM_GasPriceOracle - * @dev This contract exposes the current l2 gas price, a measure of how congested the network - * currently is. This measure is used by the Sequencer to determine what fee to charge for - * transactions. When the system is more congested, the l2 gas price will increase and fees - * will also increase as a result. - * - * All public variables are set while generating the initial L2 state. The - * constructor doesn't run in practice as the L2 state generation script uses - * the deployed bytecode instead of running the initcode. - */ -contract OVM_GasPriceOracle is Ownable { - /************* - * Variables * - *************/ - - // Current L2 gas price - uint256 public gasPrice; - // Current L1 base fee - uint256 public l1BaseFee; - // Amortized cost of batch submission per transaction - uint256 public overhead; - // Value to scale the fee up by - uint256 public scalar; - // Number of decimals of the scalar - uint256 public decimals; - - /*************** - * Constructor * - ***************/ - - /** - * @param _owner Address that will initially own this contract. - */ - constructor(address _owner) Ownable() { - transferOwnership(_owner); - } - - /********** - * Events * - **********/ - - event GasPriceUpdated(uint256); - event L1BaseFeeUpdated(uint256); - event OverheadUpdated(uint256); - event ScalarUpdated(uint256); - event DecimalsUpdated(uint256); - - /******************** - * Public Functions * - ********************/ - - /** - * Allows the owner to modify the l2 gas price. - * @param _gasPrice New l2 gas price. - */ - // slither-disable-next-line external-function - function setGasPrice(uint256 _gasPrice) public onlyOwner { - gasPrice = _gasPrice; - emit GasPriceUpdated(_gasPrice); - } - - /** - * Allows the owner to modify the l1 base fee. - * @param _baseFee New l1 base fee - */ - // slither-disable-next-line external-function - function setL1BaseFee(uint256 _baseFee) public onlyOwner { - l1BaseFee = _baseFee; - emit L1BaseFeeUpdated(_baseFee); - } - - /** - * Allows the owner to modify the overhead. - * @param _overhead New overhead - */ - // slither-disable-next-line external-function - function setOverhead(uint256 _overhead) public onlyOwner { - overhead = _overhead; - emit OverheadUpdated(_overhead); - } - - /** - * Allows the owner to modify the scalar. - * @param _scalar New scalar - */ - // slither-disable-next-line external-function - function setScalar(uint256 _scalar) public onlyOwner { - scalar = _scalar; - emit ScalarUpdated(_scalar); - } - - /** - * Allows the owner to modify the decimals. - * @param _decimals New decimals - */ - // slither-disable-next-line external-function - function setDecimals(uint256 _decimals) public onlyOwner { - decimals = _decimals; - emit DecimalsUpdated(_decimals); - } - - /** - * Computes the L1 portion of the fee - * based on the size of the RLP encoded tx - * and the current l1BaseFee - * @param _data Unsigned RLP encoded tx, 6 elements - * @return L1 fee that should be paid for the tx - */ - // slither-disable-next-line external-function - function getL1Fee(bytes memory _data) public view returns (uint256) { - uint256 l1GasUsed = getL1GasUsed(_data); - uint256 l1Fee = l1GasUsed * l1BaseFee; - uint256 divisor = 10**decimals; - uint256 unscaled = l1Fee * scalar; - uint256 scaled = unscaled / divisor; - return scaled; - } - - // solhint-disable max-line-length - /** - * Computes the amount of L1 gas used for a transaction - * The overhead represents the per batch gas overhead of - * posting both transaction and state roots to L1 given larger - * batch sizes. - * 4 gas for 0 byte - * https://github.com/ethereum/go-ethereum/blob/9ada4a2e2c415e6b0b51c50e901336872e028872/params/protocol_params.go#L33 - * 16 gas for non zero byte - * https://github.com/ethereum/go-ethereum/blob/9ada4a2e2c415e6b0b51c50e901336872e028872/params/protocol_params.go#L87 - * This will need to be updated if calldata gas prices change - * Account for the transaction being unsigned - * Padding is added to account for lack of signature on transaction - * 1 byte for RLP V prefix - * 1 byte for V - * 1 byte for RLP R prefix - * 32 bytes for R - * 1 byte for RLP S prefix - * 32 bytes for S - * Total: 68 bytes of padding - * @param _data Unsigned RLP encoded tx, 6 elements - * @return Amount of L1 gas used for a transaction - */ - // solhint-enable max-line-length - function getL1GasUsed(bytes memory _data) public view returns (uint256) { - uint256 total = 0; - for (uint256 i = 0; i < _data.length; i++) { - if (_data[i] == 0) { - total += 4; - } else { - total += 16; - } - } - uint256 unsigned = total + overhead; - return unsigned + (68 * 16); - } -} \ No newline at end of file diff --git a/contracts/src/optimism/Predeploys.sol b/contracts/src/optimism/Predeploys.sol new file mode 100644 index 000000000..6c1b78a6a --- /dev/null +++ b/contracts/src/optimism/Predeploys.sol @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +// From https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/libraries/Predeploys.sol + +/// @title Predeploys +/// @notice Contains constant addresses for contracts that are pre-deployed to the L2 system. +library Predeploys { + /// @notice Address of the L2ToL1MessagePasser predeploy. + address internal constant L2_TO_L1_MESSAGE_PASSER = 0x4200000000000000000000000000000000000016; + + /// @notice Address of the L2CrossDomainMessenger predeploy. + address internal constant L2_CROSS_DOMAIN_MESSENGER = 0x4200000000000000000000000000000000000007; + + /// @notice Address of the L2StandardBridge predeploy. + address internal constant L2_STANDARD_BRIDGE = 0x4200000000000000000000000000000000000010; + + /// @notice Address of the L2ERC721Bridge predeploy. + address internal constant L2_ERC721_BRIDGE = 0x4200000000000000000000000000000000000014; + + //// @notice Address of the SequencerFeeWallet predeploy. + address internal constant SEQUENCER_FEE_WALLET = 0x4200000000000000000000000000000000000011; + + /// @notice Address of the OptimismMintableERC20Factory predeploy. + address internal constant OPTIMISM_MINTABLE_ERC20_FACTORY = 0x4200000000000000000000000000000000000012; + + /// @notice Address of the OptimismMintableERC721Factory predeploy. + address internal constant OPTIMISM_MINTABLE_ERC721_FACTORY = 0x4200000000000000000000000000000000000017; + + /// @notice Address of the L1Block predeploy. + address internal constant L1_BLOCK_ATTRIBUTES = 0x4200000000000000000000000000000000000015; + + /// @notice Address of the GasPriceOracle predeploy. Includes fee information + /// and helpers for computing the L1 portion of the transaction fee. + address internal constant GAS_PRICE_ORACLE = 0x420000000000000000000000000000000000000F; + + /// @custom:legacy + /// @notice Address of the L1MessageSender predeploy. Deprecated. Use L2CrossDomainMessenger + /// or access tx.origin (or msg.sender) in a L1 to L2 transaction instead. + address internal constant L1_MESSAGE_SENDER = 0x4200000000000000000000000000000000000001; + + /// @custom:legacy + /// @notice Address of the DeployerWhitelist predeploy. No longer active. + address internal constant DEPLOYER_WHITELIST = 0x4200000000000000000000000000000000000002; + + /// @custom:legacy + /// @notice Address of the LegacyERC20ETH predeploy. Deprecated. Balances are migrated to the + /// state trie as of the Bedrock upgrade. Contract has been locked and write functions + /// can no longer be accessed. + address internal constant LEGACY_ERC20_ETH = 0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000; + + /// @custom:legacy + /// @notice Address of the L1BlockNumber predeploy. Deprecated. Use the L1Block predeploy + /// instead, which exposes more information about the L1 state. + address internal constant L1_BLOCK_NUMBER = 0x4200000000000000000000000000000000000013; + + /// @custom:legacy + /// @notice Address of the LegacyMessagePasser predeploy. Deprecate. Use the updated + /// L2ToL1MessagePasser contract instead. + address internal constant LEGACY_MESSAGE_PASSER = 0x4200000000000000000000000000000000000000; + + /// @notice Address of the ProxyAdmin predeploy. + address internal constant PROXY_ADMIN = 0x4200000000000000000000000000000000000018; + + /// @notice Address of the BaseFeeVault predeploy. + address internal constant BASE_FEE_VAULT = 0x4200000000000000000000000000000000000019; + + /// @notice Address of the L1FeeVault predeploy. + address internal constant L1_FEE_VAULT = 0x420000000000000000000000000000000000001A; + + /// @notice Address of the GovernanceToken predeploy. + address internal constant GOVERNANCE_TOKEN = 0x4200000000000000000000000000000000000042; + + /// @notice Address of the SchemaRegistry predeploy. + address internal constant SCHEMA_REGISTRY = 0x4200000000000000000000000000000000000020; + + /// @notice Address of the EAS predeploy. + address internal constant EAS = 0x4200000000000000000000000000000000000021; +} diff --git a/src/builder/bundle_proposer.rs b/src/builder/bundle_proposer.rs index c97454da8..c2431c5e0 100644 --- a/src/builder/bundle_proposer.rs +++ b/src/builder/bundle_proposer.rs @@ -896,12 +896,10 @@ mod tests { #[tokio::test] async fn test_drops_but_not_rejects_op_with_too_low_max_fee_per_gas() { - // Required max_fee_per_gas is the base fee increased by 12.5% plus the - // required priority fee. let base_fee = U256::from(1000); let max_priority_fee_per_gas = U256::from(50); - let op1 = op_with_sender_and_fees(address(1), 1179.into(), 55.into()); - let op2 = op_with_sender_and_fees(address(2), 1180.into(), 55.into()); + let op1 = op_with_sender_and_fees(address(1), 1054.into(), 55.into()); + let op2 = op_with_sender_and_fees(address(2), 1055.into(), 55.into()); let bundle = make_bundle( vec![ MockOp { @@ -923,7 +921,7 @@ mod tests { assert_eq!( bundle.gas_fees, GasFees { - max_fee_per_gas: 1175.into(), + max_fee_per_gas: 1050.into(), max_priority_fee_per_gas: 50.into(), } ); diff --git a/src/common/gas.rs b/src/common/gas.rs index e7c6809e1..175aa0db9 100644 --- a/src/common/gas.rs +++ b/src/common/gas.rs @@ -184,11 +184,9 @@ impl FeeEstimator

{ self.bundle_priority_fee_overhead_percent, )); - // Give enough leeway for the base fee to increase by the maximum - // possible amount for a single block (12.5%). let max_fee_per_gas = required_fees .max_fee_per_gas - .max((base_fee * 9 / 8) + max_priority_fee_per_gas); + .max(base_fee + max_priority_fee_per_gas); Ok(GasFees { max_fee_per_gas, max_priority_fee_per_gas, diff --git a/src/common/mod.rs b/src/common/mod.rs index 66e976e19..4982084b0 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -1,5 +1,6 @@ pub mod block_watcher; pub mod context; +#[allow(non_snake_case)] pub mod contracts; pub mod dev; pub mod emit; diff --git a/src/common/types/provider_like.rs b/src/common/types/provider_like.rs index a636fcd8e..229e97e7a 100644 --- a/src/common/types/provider_like.rs +++ b/src/common/types/provider_like.rs @@ -4,7 +4,10 @@ use anyhow::Context; use ethers::{ contract::ContractError, providers::{JsonRpcClient, Middleware, Provider}, - types::{Address, Block, BlockId, BlockNumber, Bytes, Filter, Log, H160, H256, U256}, + types::{ + Address, Block, BlockId, BlockNumber, Bytes, Eip1559TransactionRequest, Filter, Log, H160, + H256, U256, U64, + }, }; #[cfg(test)] use mockall::automock; @@ -12,8 +15,8 @@ use tonic::async_trait; use crate::common::{ contracts::{ - i_aggregator::IAggregator, i_entry_point::IEntryPoint, node_interface::NodeInterface, - ovm_gas_price_oracle::OVM_GasPriceOracle, + gas_price_oracle::GasPriceOracle, i_aggregator::IAggregator, i_entry_point::IEntryPoint, + node_interface::NodeInterface, }, types::UserOperation, }; @@ -107,12 +110,12 @@ impl ProviderLike for Provider { } async fn get_base_fee(&self) -> anyhow::Result { - Middleware::get_block(self, BlockNumber::Latest) + Middleware::get_block(self, BlockNumber::Pending) .await - .context("should load latest block to get base fee")? - .context("latest block should exist")? + .context("should load pending block to get base fee")? + .context("pending block should exist")? .base_fee_per_gas - .context("latest block should have a nonempty base fee") + .context("pending block should have a nonempty base fee") } async fn get_max_priority_fee(&self) -> anyhow::Result { @@ -184,17 +187,31 @@ impl ProviderLike for Provider { .calldata() .context("should get calldata for entry point handle ops")?; + // construct an unsigned transaction with default values just for L1 gas estimation + let tx = Eip1559TransactionRequest::new() + .from(Address::random()) + .to(entry_point_address) + .gas(U256::from(1_000_000)) + .max_priority_fee_per_gas(U256::from(100_000_000)) + .max_fee_per_gas(U256::from(100_000_000)) + .value(U256::from(0)) + .data(data) + .nonce(U256::from(100_000)) + .chain_id(U64::from(100_000)) + .rlp(); + let gas_oracle = - OVM_GasPriceOracle::new(OPTIMISM_BEDROCK_GAS_ORACLE_ADDRESS, Arc::clone(&self)); + GasPriceOracle::new(OPTIMISM_BEDROCK_GAS_ORACLE_ADDRESS, Arc::clone(&self)); - let (l1_gas, l2_gas_fee) = tokio::try_join!( + let (l1_fee, l2_base_fee, l2_priority_fee) = tokio::try_join!( async { - let l1_gas = gas_oracle.get_l1_fee(data).call().await?; + let l1_gas = gas_oracle.get_l1_fee(tx).call().await?; Ok(l1_gas) }, - self.get_base_fee() + self.get_base_fee(), + self.get_max_priority_fee(), )?; - Ok(l1_gas / l2_gas_fee) + Ok(l1_fee / (l2_base_fee + l2_priority_fee)) } }