Skip to content

Commit

Permalink
devsvcs-130: add an optimism module v2 to reduce gas cost to estimate… (
Browse files Browse the repository at this point in the history
#14151)

* devsvcs-168: fix chain module l1 fee calculation

* Update gethwrappers

* devsvcs-130: add an optimism module v2 to reduce gas cost to estimate l1 fee

* Update gethwrappers

* fix version

* go gen

* update

* update

* update

* remove op module abi

---------

Co-authored-by: app-token-issuer-infra-releng[bot] <120227048+app-token-issuer-infra-releng[bot]@users.noreply.github.com>
  • Loading branch information
1 parent 5b9d92d commit c098805
Show file tree
Hide file tree
Showing 15 changed files with 1,035 additions and 74 deletions.
2 changes: 1 addition & 1 deletion contracts/scripts/native_solc_compile_all_automation
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ compileContract automation/v2_2/AutomationUtils2_2.sol
compileContract automation/interfaces/v2_2/IAutomationRegistryMaster.sol
compileContract automation/chains/ArbitrumModule.sol
compileContract automation/chains/ChainModuleBase.sol
compileContract automation/chains/OptimismModule.sol
compileContract automation/chains/OptimismModuleV2.sol
compileContract automation/chains/ScrollModule.sol
compileContract automation/interfaces/IChainModule.sol
compileContract automation/interfaces/IAutomationV21PlusCommon.sol
Expand Down
4 changes: 2 additions & 2 deletions contracts/src/v0.8/automation/chains/ChainModuleBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ contract ChainModuleBase is IChainModule {
return blockhash(n);
}

function getCurrentL1Fee(uint256) external view virtual returns (uint256) {
function getCurrentL1Fee(uint256) external view virtual returns (uint256 l1Fee) {
return 0;
}

function getMaxL1Fee(uint256) external view virtual returns (uint256) {
function getMaxL1Fee(uint256) external view virtual returns (uint256 maxL1Fee) {
return 0;
}

Expand Down
11 changes: 10 additions & 1 deletion contracts/src/v0.8/automation/chains/OptimismModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ pragma solidity 0.8.19;
import {OVM_GasPriceOracle} from "../../vendor/@eth-optimism/contracts/v0.8.9/contracts/L2/predeploys/OVM_GasPriceOracle.sol";
import {ChainModuleBase} from "./ChainModuleBase.sol";

/**
* @notice This contract is deprecated. Please use OptimismModuleV2 which utilizes the most recent offerings from OP
* and can estimate L1 fee with much lower cost.
*/
contract OptimismModule is ChainModuleBase {
/// @dev OP_L1_DATA_FEE_PADDING includes 80 bytes for L1 data padding for Optimism and BASE
bytes private constant OP_L1_DATA_FEE_PADDING =
Expand All @@ -25,10 +29,15 @@ contract OptimismModule is ChainModuleBase {
return _getL1Fee(dataSize);
}

/* @notice this function provides an estimation for L1 fee incurred by calldata of a certain size
* @dev this function uses the getL1Fee function in OP gas price oracle. it estimates the exact L1 fee but it costs
* a lot of gas to call.
* @param dataSize the size of calldata
* @return l1Fee the L1 fee
*/
function _getL1Fee(uint256 dataSize) internal view returns (uint256) {
// fee is 4 per 0 byte, 16 per non-zero byte. Worst case we can have all non zero-bytes.
// Instead of setting bytes to non-zero, we initialize 'new bytes' of length 4*dataSize to cover for zero bytes.
// this is the same as OP.
bytes memory txCallData = new bytes(4 * dataSize);
return OVM_GASPRICEORACLE.getL1Fee(bytes.concat(txCallData, OP_L1_DATA_FEE_PADDING));
}
Expand Down
78 changes: 78 additions & 0 deletions contracts/src/v0.8/automation/chains/OptimismModuleV2.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.19;

import {GasPriceOracle as OVM_GasPriceOracle} from "../../vendor/@eth-optimism/contracts-bedrock/v0.17.3/src/L2/GasPriceOracle.sol";
import {ChainModuleBase} from "./ChainModuleBase.sol";
import {ConfirmedOwner} from "../../shared/access/ConfirmedOwner.sol";

/**
* @notice OptimismModuleV2 provides a cost-efficient way to get L1 fee on OP stack.
* After EIP-4844 is implemented in OP stack, the new OP upgrade includes a new function getL1FeeUpperBound to estimate
* the upper bound of current transaction's L1 fee.
*/
contract OptimismModuleV2 is ChainModuleBase, ConfirmedOwner {
error InvalidL1FeeCoefficient(uint8 coefficient);
event L1FeeCoefficientSet(uint8 coefficient);

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

/// @dev L1 fee coefficient is used to account for the impact of data compression on the l1 fee
/// getL1FeeUpperBound returns the upper bound of l1 fee so this configurable coefficient will help
/// charge a predefined percentage of the upper bound.
uint8 private s_l1FeeCoefficient = 100;
uint256 private constant FIXED_GAS_OVERHEAD = 28_000;
uint256 private constant PER_CALLDATA_BYTE_GAS_OVERHEAD = 0;

constructor() ConfirmedOwner(msg.sender) {}

function getCurrentL1Fee(uint256 dataSize) external view override returns (uint256) {
return (s_l1FeeCoefficient * _getL1Fee(dataSize)) / 100;
}

function getMaxL1Fee(uint256 dataSize) external view override returns (uint256) {
return _getL1Fee(dataSize);
}

/* @notice this function provides an estimation for L1 fee incurred by calldata of a certain size
* @dev this function uses the newly provided getL1FeeUpperBound function in OP gas price oracle. this helps
* estimate L1 fee with much lower cost
* @param dataSize the size of calldata
* @return l1Fee the L1 fee
*/
function _getL1Fee(uint256 dataSize) internal view returns (uint256) {
return OVM_GASPRICEORACLE.getL1FeeUpperBound(dataSize);
}

function getGasOverhead()
external
pure
override
returns (uint256 chainModuleFixedOverhead, uint256 chainModulePerByteOverhead)
{
return (FIXED_GAS_OVERHEAD, PER_CALLDATA_BYTE_GAS_OVERHEAD);
}

/* @notice this function sets a new coefficient for L1 fee estimation.
* @dev this function can only be invoked by contract owner
* @param coefficient the new coefficient
*/
function setL1FeeCalculation(uint8 coefficient) external onlyOwner {
if (coefficient > 100) {
revert InvalidL1FeeCoefficient(coefficient);
}

s_l1FeeCoefficient = coefficient;

emit L1FeeCoefficientSet(coefficient);
}

/* @notice this function returns the s_l1FeeCoefficient
* @return coefficient the current s_l1FeeCoefficient in effect
*/
function getL1FeeCoefficient() public view returns (uint256 coefficient) {
return s_l1FeeCoefficient;
}
}
19 changes: 17 additions & 2 deletions contracts/src/v0.8/automation/chains/ScrollModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ contract ScrollModule is ChainModuleBase, ConfirmedOwner {
IScrollL1GasPriceOracle private constant SCROLL_ORACLE = IScrollL1GasPriceOracle(SCROLL_ORACLE_ADDR);

/// @dev L1 fee coefficient can be applied to reduce possibly inflated gas cost
uint8 public s_l1FeeCoefficient = 100;
uint8 private s_l1FeeCoefficient = 100;
uint256 private constant FIXED_GAS_OVERHEAD = 45_000;
uint256 private constant PER_CALLDATA_BYTE_GAS_OVERHEAD = 170;

Expand All @@ -34,7 +34,11 @@ contract ScrollModule is ChainModuleBase, ConfirmedOwner {
return _getL1Fee(dataSize);
}

function _getL1Fee(uint256 dataSize) internal view returns (uint256) {
/* @notice this function provides an estimation for L1 fee incurred by calldata of a certain size
* @param dataSize the size of calldata
* @return l1Fee the L1 fee
*/
function _getL1Fee(uint256 dataSize) internal view returns (uint256 l1Fee) {
// fee is 4 per 0 byte, 16 per non-zero byte. Worst case we can have all non zero-bytes.
// Instead of setting bytes to non-zero, we initialize 'new bytes' of length 4*dataSize to cover for zero bytes.
// this is the same as OP.
Expand All @@ -51,6 +55,10 @@ contract ScrollModule is ChainModuleBase, ConfirmedOwner {
return (FIXED_GAS_OVERHEAD, PER_CALLDATA_BYTE_GAS_OVERHEAD);
}

/* @notice this function sets a new coefficient for L1 fee estimation.
* @dev this function can only be invoked by contract owner
* @param coefficient the new coefficient
*/
function setL1FeeCalculation(uint8 coefficient) external onlyOwner {
if (coefficient > 100) {
revert InvalidL1FeeCoefficient(coefficient);
Expand All @@ -60,4 +68,11 @@ contract ScrollModule is ChainModuleBase, ConfirmedOwner {

emit L1FeeCoefficientSet(coefficient);
}

/* @notice this function returns the s_l1FeeCoefficient
* @return coefficient the current s_l1FeeCoefficient in effect
*/
function getL1FeeCoefficient() public view returns (uint256 coefficient) {
return s_l1FeeCoefficient;
}
}
45 changes: 29 additions & 16 deletions contracts/src/v0.8/automation/interfaces/IChainModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,39 @@
pragma solidity ^0.8.0;

interface IChainModule {
// retrieve the native block number of a chain. e.g. L2 block number on Arbitrum
function blockNumber() external view returns (uint256);
/* @notice this function provides the block number of current chain.
* @dev certain chains have its own function to retrieve block number, e.g. Arbitrum
* @return blockNumber the block number of the current chain.
*/
function blockNumber() external view returns (uint256 blockNumber);

// retrieve the native block hash of a chain.
function blockHash(uint256) external view returns (bytes32);
/* @notice this function provides the block hash of a block number.
* @dev this function can usually look back 256 blocks at most, unless otherwise specified
* @param blockNumber the block number
* @return blockHash the block hash of the input block number
*/
function blockHash(uint256 blockNumber) external view returns (bytes32 blockHash);

// retrieve the L1 data fee for a L2 transaction. it should return 0 for L1 chains and
// L2 chains which don't have L1 fee component. it uses msg.data to estimate L1 data so
// it must be used with a transaction. Return value in wei.
function getCurrentL1Fee(uint256 dataSize) external view returns (uint256);
/* @notice this function provides the L1 fee of current transaction.
* @dev retrieve the L1 data fee for a L2 transaction. it should return 0 for L1 chains. it should
* return 0 for L2 chains if they don't have L1 fee component.
* @param dataSize the calldata size of the current transaction
* @return l1Fee the L1 fee in wei incurred by calldata of this data size
*/
function getCurrentL1Fee(uint256 dataSize) external view returns (uint256 l1Fee);

// retrieve the L1 data fee for a L2 simulation. it should return 0 for L1 chains and
// L2 chains which don't have L1 fee component. Return value in wei.
function getMaxL1Fee(uint256 dataSize) external view returns (uint256);
/* @notice this function provides the max possible L1 fee of current transaction.
* @dev retrieve the max possible L1 data fee for a L2 transaction. it should return 0 for L1 chains. it should
* return 0 for L2 chains if they don't have L1 fee component.
* @param dataSize the calldata size of the current transaction
* @return maxL1Fee the max possible L1 fee in wei incurred by calldata of this data size
*/
function getMaxL1Fee(uint256 dataSize) external view returns (uint256 maxL1Fee);

// Returns an upper bound on execution gas cost for one invocation of blockNumber(),
// one invocation of blockHash() and one invocation of getCurrentL1Fee().
// Returns two values, first value indicates a fixed cost and the second value is
// the cost per msg.data byte (As some chain module's getCurrentL1Fee execution cost
// scales with calldata size)
/* @notice this function provides the overheads of calling this chain module.
* @return chainModuleFixedOverhead the fixed overhead incurred by calling this chain module
* @return chainModulePerByteOverhead the fixed overhead per byte incurred by calling this chain module with calldata
*/
function getGasOverhead()
external
view
Expand Down
7 changes: 6 additions & 1 deletion contracts/src/v0.8/tests/MockOVMGasPriceOracle.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.6;

contract MockOVMGasPriceOracle {
function getL1Fee(bytes memory _data) public view returns (uint256) {
function getL1Fee(bytes memory) public pure returns (uint256) {
return 2000000;
}

function getL1FeeUpperBound(uint256) public pure returns (uint256) {
return 2000000;
}
}
13 changes: 7 additions & 6 deletions contracts/test/v0.8/automation/AutomationRegistry2_2.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { MockArbGasInfo__factory as MockArbGasInfoFactory } from '../../../typec
import { MockOVMGasPriceOracle__factory as MockOVMGasPriceOracleFactory } from '../../../typechain/factories/MockOVMGasPriceOracle__factory'
import { ChainModuleBase__factory as ChainModuleBaseFactory } from '../../../typechain/factories/ChainModuleBase__factory'
import { ArbitrumModule__factory as ArbitrumModuleFactory } from '../../../typechain/factories/ArbitrumModule__factory'
import { OptimismModule__factory as OptimismModuleFactory } from '../../../typechain/factories/OptimismModule__factory'
import { OptimismModuleV2__factory as OptimismModuleV2Factory } from '../../../typechain/factories/OptimismModuleV2__factory'
import { ILogAutomation__factory as ILogAutomationactory } from '../../../typechain/factories/ILogAutomation__factory'
import { IAutomationForwarder__factory as IAutomationForwarderFactory } from '../../../typechain/factories/IAutomationForwarder__factory'
import { MockArbSys__factory as MockArbSysFactory } from '../../../typechain/factories/MockArbSys__factory'
Expand All @@ -35,7 +35,7 @@ import { MockV3Aggregator } from '../../../typechain/MockV3Aggregator'
import { UpkeepMock } from '../../../typechain/UpkeepMock'
import { ChainModuleBase } from '../../../typechain/ChainModuleBase'
import { ArbitrumModule } from '../../../typechain/ArbitrumModule'
import { OptimismModule } from '../../../typechain/OptimismModule'
import { OptimismModuleV2 } from '../../../typechain/OptimismModuleV2'
import { UpkeepTranscoder } from '../../../typechain/UpkeepTranscoder'
import { IChainModule, UpkeepAutoFunder } from '../../../typechain'
import {
Expand Down Expand Up @@ -148,7 +148,7 @@ let upkeepMockFactory: UpkeepMockFactory
let upkeepAutoFunderFactory: UpkeepAutoFunderFactory
let chainModuleBaseFactory: ChainModuleBaseFactory
let arbitrumModuleFactory: ArbitrumModuleFactory
let optimismModuleFactory: OptimismModuleFactory
let optimismModuleV2Factory: OptimismModuleV2Factory
let streamsLookupUpkeepFactory: StreamsLookupUpkeepFactory
let personas: Personas

Expand All @@ -169,7 +169,7 @@ let ltUpkeep: MockContract
let transcoder: UpkeepTranscoder
let chainModuleBase: ChainModuleBase
let arbitrumModule: ArbitrumModule
let optimismModule: OptimismModule
let optimismModule: OptimismModuleV2
let streamsLookupUpkeep: StreamsLookupUpkeep
let automationUtils: AutomationCompatibleUtils

Expand Down Expand Up @@ -430,7 +430,8 @@ describe('AutomationRegistry2_2', () => {
await ethers.getContractFactory('UpkeepAutoFunder')
chainModuleBaseFactory = await ethers.getContractFactory('ChainModuleBase')
arbitrumModuleFactory = await ethers.getContractFactory('ArbitrumModule')
optimismModuleFactory = await ethers.getContractFactory('OptimismModule')
optimismModuleV2Factory =
await ethers.getContractFactory('OptimismModuleV2')
streamsLookupUpkeepFactory = await ethers.getContractFactory(
'StreamsLookupUpkeep',
)
Expand Down Expand Up @@ -844,7 +845,7 @@ describe('AutomationRegistry2_2', () => {
.deploy()
chainModuleBase = await chainModuleBaseFactory.connect(owner).deploy()
arbitrumModule = await arbitrumModuleFactory.connect(owner).deploy()
optimismModule = await optimismModuleFactory.connect(owner).deploy()
optimismModule = await optimismModuleV2Factory.connect(owner).deploy()
streamsLookupUpkeep = await streamsLookupUpkeepFactory
.connect(owner)
.deploy(
Expand Down
10 changes: 5 additions & 5 deletions contracts/test/v0.8/automation/AutomationRegistry2_3.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { MockArbGasInfo__factory as MockArbGasInfoFactory } from '../../../typec
import { MockOVMGasPriceOracle__factory as MockOVMGasPriceOracleFactory } from '../../../typechain/factories/MockOVMGasPriceOracle__factory'
import { ChainModuleBase__factory as ChainModuleBaseFactory } from '../../../typechain/factories/ChainModuleBase__factory'
import { ArbitrumModule__factory as ArbitrumModuleFactory } from '../../../typechain/factories/ArbitrumModule__factory'
import { OptimismModule__factory as OptimismModuleFactory } from '../../../typechain/factories/OptimismModule__factory'
import { OptimismModuleV2__factory as OptimismModuleV2Factory } from '../../../typechain/factories/OptimismModuleV2__factory'
import { ILogAutomation__factory as ILogAutomationactory } from '../../../typechain/factories/ILogAutomation__factory'
import { MockArbSys__factory as MockArbSysFactory } from '../../../typechain/factories/MockArbSys__factory'
import { AutomationCompatibleUtils } from '../../../typechain/AutomationCompatibleUtils'
Expand All @@ -34,7 +34,7 @@ import { MockV3Aggregator } from '../../../typechain/MockV3Aggregator'
import { UpkeepMock } from '../../../typechain/UpkeepMock'
import { ChainModuleBase } from '../../../typechain/ChainModuleBase'
import { ArbitrumModule } from '../../../typechain/ArbitrumModule'
import { OptimismModule } from '../../../typechain/OptimismModule'
import { OptimismModuleV2 } from '../../../typechain/OptimismModuleV2'
import { UpkeepTranscoder } from '../../../typechain/UpkeepTranscoder'
import { IChainModule, UpkeepAutoFunder } from '../../../typechain'
import {
Expand Down Expand Up @@ -152,7 +152,7 @@ let upkeepMockFactory: UpkeepMockFactory
let upkeepAutoFunderFactory: UpkeepAutoFunderFactory
let chainModuleBaseFactory: ChainModuleBaseFactory
let arbitrumModuleFactory: ArbitrumModuleFactory
let optimismModuleFactory: OptimismModuleFactory
let optimismModuleFactory: OptimismModuleV2Factory
let streamsLookupUpkeepFactory: StreamsLookupUpkeepFactory
let personas: Personas

Expand All @@ -174,7 +174,7 @@ let ltUpkeep: MockContract
let transcoder: UpkeepTranscoder
let chainModuleBase: ChainModuleBase
let arbitrumModule: ArbitrumModule
let optimismModule: OptimismModule
let optimismModule: OptimismModuleV2
let streamsLookupUpkeep: StreamsLookupUpkeep
let automationUtils: AutomationCompatibleUtils
let automationUtils2_3: AutomationUtils2_3
Expand Down Expand Up @@ -442,7 +442,7 @@ describe('AutomationRegistry2_3', () => {
await ethers.getContractFactory('UpkeepAutoFunder')
chainModuleBaseFactory = await ethers.getContractFactory('ChainModuleBase')
arbitrumModuleFactory = await ethers.getContractFactory('ArbitrumModule')
optimismModuleFactory = await ethers.getContractFactory('OptimismModule')
optimismModuleFactory = await ethers.getContractFactory('OptimismModuleV2')
streamsLookupUpkeepFactory = await ethers.getContractFactory(
'StreamsLookupUpkeep',
)
Expand Down
Loading

0 comments on commit c098805

Please sign in to comment.