Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AUTO-8804: create chain specific modules for l1 gas calculations #11896

Merged
merged 23 commits into from
Feb 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions contracts/src/v0.8/automation/dev/chains/ArbitrumModule.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.19;

import {ArbSys} from "../../../vendor/@arbitrum/nitro-contracts/src/precompiles/ArbSys.sol";
import {ArbGasInfo} from "../../../vendor/@arbitrum/nitro-contracts/src/precompiles/ArbGasInfo.sol";
import {ChainModuleBase} from "./ChainModuleBase.sol";

contract ArbitrumModule is ChainModuleBase {
/// @dev ARB_SYS_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 ARB_SYS_ADDR = 0x0000000000000000000000000000000000000064;
ArbSys private constant ARB_SYS = ArbSys(ARB_SYS_ADDR);

/// @dev ARB_GAS_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 ARB_GAS_ADDR = 0x000000000000000000000000000000000000006C;
ArbGasInfo private constant ARB_GAS = ArbGasInfo(ARB_GAS_ADDR);

function blockHash(uint256 n) external view override returns (bytes32) {
uint256 blockNum = ARB_SYS.arbBlockNumber();
if (n >= blockNum || blockNum - n > 256) {
return "";
}
return ARB_SYS.arbBlockHash(n);
}

function blockNumber() external view override returns (uint256) {
return ARB_SYS.arbBlockNumber();
}

function getCurrentL1Fee() external view override returns (uint256) {
return ARB_GAS.getCurrentTxL1GasFees();
}

function getMaxL1Fee(uint256 dataSize) external view override returns (uint256) {
(, uint256 perL1CalldataUnit, , , , ) = ARB_GAS.getPricesInWei();
return perL1CalldataUnit * dataSize * 16;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why the multiplication by 16 here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will add a comment, this is ARB specific.

fee is 4 per 0 byte, 16 per non-zero byte - we assume all non-zero and
max data size to calculate max payment

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we add that comment back in here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's refine all chain modules in separate PRs, I think this can be improved further by multiplying by the scalar value, will investigate more

}
}
25 changes: 25 additions & 0 deletions contracts/src/v0.8/automation/dev/chains/ChainModuleBase.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.19;

import {IChainModule} from "../interfaces/v2_2/IChainModule.sol";

contract ChainModuleBase is IChainModule {
function blockNumber() external view virtual returns (uint256) {
return block.number;
}

function blockHash(uint256 n) external view virtual returns (bytes32) {
if (n >= block.number || block.number - n > 256) {
return "";
}
return blockhash(n);
}

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

function getMaxL1Fee(uint256) external view virtual returns (uint256) {
return 0;
}
}
26 changes: 26 additions & 0 deletions contracts/src/v0.8/automation/dev/chains/OptimismModule.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// SPDX-License-Identifier: BUSL-1.1
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";

contract OptimismModule is ChainModuleBase {
/// @dev OP_L1_DATA_FEE_PADDING includes 35 bytes for L1 data padding for Optimism and BASE
bytes private constant OP_L1_DATA_FEE_PADDING =
hex"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff";
/// @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 = 0x420000000000000000000000000000000000000F;
OVM_GasPriceOracle private constant OVM_GASPRICEORACLE = OVM_GasPriceOracle(OVM_GASPRICEORACLE_ADDR);

function getCurrentL1Fee() external view override returns (uint256) {
return OVM_GASPRICEORACLE.getL1Fee(bytes.concat(msg.data, OP_L1_DATA_FEE_PADDING));
}

function getMaxL1Fee(uint256 dataSize) external view override 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.
bytes memory txCallData = new bytes(4 * dataSize);
return OVM_GASPRICEORACLE.getL1Fee(bytes.concat(txCallData, OP_L1_DATA_FEE_PADDING));
Comment on lines +21 to +24
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not implement similar to arbitrum to get per unit fee and multiply by data size? That will be cheaper?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if optimism exposes the per-unit fee? If so, can we create a ticket and explore in a separate thread?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sg, we can refine all chain modules in separate PRs

}
}
29 changes: 29 additions & 0 deletions contracts/src/v0.8/automation/dev/chains/ScrollModule.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.19;

import {IScrollL1GasPriceOracle} from "../../../vendor/@scroll-tech/contracts/src/L2/predeploys/IScrollL1GasPriceOracle.sol";
import {ChainModuleBase} from "./ChainModuleBase.sol";

contract ScrollModule is ChainModuleBase {
/// @dev SCROLL_L1_FEE_DATA_PADDING includes 120 bytes for L1 data padding for Optimism
/// @dev according to testing, this padding allows automation registry to properly estimates L1 data fee with 3-5% buffer
/// @dev this MAY NOT work for a different product and this may get out of date if transmit function is changed
bytes private constant SCROLL_L1_FEE_DATA_PADDING =
hex"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff";
/// @dev SCROLL_ORACLE_ADDR is the address of the L1GasPriceOracle precompile on Optimism.
/// @dev reference: https://docs.scroll.io/en/developers/transaction-fees-on-scroll/#estimating-the-l1-data-fee
address private constant SCROLL_ORACLE_ADDR = 0x5300000000000000000000000000000000000002;
IScrollL1GasPriceOracle private constant SCROLL_ORACLE = IScrollL1GasPriceOracle(SCROLL_ORACLE_ADDR);

function getCurrentL1Fee() external view override returns (uint256) {
return SCROLL_ORACLE.getL1Fee(bytes.concat(msg.data, SCROLL_L1_FEE_DATA_PADDING));
}

function getMaxL1Fee(uint256 dataSize) external view override 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.
FelixFan1992 marked this conversation as resolved.
Show resolved Hide resolved
// this is the same as OP.
bytes memory txCallData = new bytes(4 * dataSize);
return SCROLL_ORACLE.getL1Fee(bytes.concat(txCallData, SCROLL_L1_FEE_DATA_PADDING));
}
}

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// SPDX-License-Identifier: BUSL-1.1
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);

// retrieve the native block hash of a chain.
function blockHash(uint256) external view returns (bytes32);

// 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.
function getCurrentL1Fee() external view returns (uint256);

// 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.
function getMaxL1Fee(uint256 dataSize) external view returns (uint256);
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {AutomationRegistryBase2_2} from "../v2_2/AutomationRegistryBase2_2.sol";
import {AutomationRegistryLogicA2_2} from "../v2_2/AutomationRegistryLogicA2_2.sol";
import {AutomationRegistryLogicB2_2} from "../v2_2/AutomationRegistryLogicB2_2.sol";
import {IAutomationRegistryMaster} from "../interfaces/v2_2/IAutomationRegistryMaster.sol";
import {AutomationCompatibleInterface} from "../../interfaces/AutomationCompatibleInterface.sol";
import {ChainModuleBase} from "../chains/ChainModuleBase.sol";

contract AutomationRegistry2_2_SetUp is BaseTest {
address internal constant LINK_ETH_FEED = 0x1111111111111111111111111111111111111110;
Expand All @@ -29,6 +29,8 @@ contract AutomationRegistry2_2_SetUp is BaseTest {
address[] internal s_valid_transmitters;
address[] internal s_registrars;

IAutomationRegistryMaster internal registryMaster;

function setUp() public override {
s_valid_transmitters = new address[](4);
for (uint160 i = 0; i < 4; ++i) {
Expand All @@ -43,32 +45,25 @@ contract AutomationRegistry2_2_SetUp is BaseTest {

s_registrars = new address[](1);
s_registrars[0] = 0x3a0eDE26aa188BFE00b9A0C9A431A1a0CA5f7966;
}

function deployRegistry2_2(AutomationRegistryBase2_2.Mode mode) public returns (IAutomationRegistryMaster) {
AutomationForwarderLogic forwarderLogic = new AutomationForwarderLogic();
AutomationRegistryLogicB2_2 logicB2_2 = new AutomationRegistryLogicB2_2(
mode,
LINK_TOKEN,
LINK_ETH_FEED,
FAST_GAS_FEED,
address(forwarderLogic),
ZERO_ADDRESS
);
AutomationRegistryLogicA2_2 logicA2_2 = new AutomationRegistryLogicA2_2(logicB2_2);
IAutomationRegistryMaster registry2_2 = IAutomationRegistryMaster(
registryMaster = IAutomationRegistryMaster(
address(new AutomationRegistry2_2(AutomationRegistryLogicB2_2(address(logicA2_2))))
);
return registry2_2;
}
}

contract AutomationRegistry2_2_LatestConfigDetails is AutomationRegistry2_2_SetUp {
function testGet() public {
IAutomationRegistryMaster registry = IAutomationRegistryMaster(
address(deployRegistry2_2(AutomationRegistryBase2_2.Mode(0)))
);
(uint32 configCount, uint32 blockNumber, bytes32 configDigest) = registry.latestConfigDetails();
(uint32 configCount, uint32 blockNumber, bytes32 configDigest) = registryMaster.latestConfigDetails();
assertEq(configCount, 0);
assertEq(blockNumber, 0);
assertEq(configDigest, "");
Expand All @@ -77,17 +72,13 @@ contract AutomationRegistry2_2_LatestConfigDetails is AutomationRegistry2_2_SetU

contract AutomationRegistry2_2_CheckUpkeep is AutomationRegistry2_2_SetUp {
function testPreventExecutionOnCheckUpkeep() public {
IAutomationRegistryMaster registry = IAutomationRegistryMaster(
address(deployRegistry2_2(AutomationRegistryBase2_2.Mode(0)))
);

uint256 id = 1;
bytes memory triggerData = abi.encodePacked("trigger_data");

// The tx.origin is the DEFAULT_SENDER (0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38) of foundry
// Expecting a revert since the tx.origin is not address(0)
vm.expectRevert(abi.encodeWithSelector(IAutomationRegistryMaster.OnlySimulatedBackend.selector));
registry.checkUpkeep(id, triggerData);
registryMaster.checkUpkeep(id, triggerData);
}
}

Expand All @@ -105,11 +96,9 @@ contract AutomationRegistry2_2_SetConfig is AutomationRegistry2_2_SetUp {
);

function testSetConfigSuccess() public {
IAutomationRegistryMaster registry = IAutomationRegistryMaster(
address(deployRegistry2_2(AutomationRegistryBase2_2.Mode(0)))
);
(uint32 configCount, , ) = registry.latestConfigDetails();
(uint32 configCount, , ) = registryMaster.latestConfigDetails();
assertEq(configCount, 0);
ChainModuleBase module = new ChainModuleBase();

AutomationRegistryBase2_2.OnchainConfig memory cfg = AutomationRegistryBase2_2.OnchainConfig({
paymentPremiumPPB: 10_000,
Expand All @@ -127,6 +116,7 @@ contract AutomationRegistry2_2_SetConfig is AutomationRegistry2_2_SetUp {
transcoder: 0xB1e66855FD67f6e85F0f0fA38cd6fBABdf00923c,
registrars: s_registrars,
upkeepPrivilegeManager: 0xD9c855F08A7e460691F41bBDDe6eC310bc0593D8,
chainModule: module,
reorgProtectionEnabled: true
});
bytes memory onchainConfigBytes = abi.encode(cfg);
Expand All @@ -136,7 +126,7 @@ contract AutomationRegistry2_2_SetConfig is AutomationRegistry2_2_SetUp {
bytes memory offchainConfigBytes = abi.encode(a, b);
bytes32 configDigest = _configDigestFromConfigData(
block.chainid,
address(registry),
address(registryMaster),
++configCount,
s_valid_signers,
s_valid_transmitters,
Expand All @@ -159,7 +149,7 @@ contract AutomationRegistry2_2_SetConfig is AutomationRegistry2_2_SetUp {
offchainConfigBytes
);

registry.setConfig(
registryMaster.setConfig(
s_valid_signers,
s_valid_transmitters,
F,
Expand All @@ -168,7 +158,7 @@ contract AutomationRegistry2_2_SetConfig is AutomationRegistry2_2_SetUp {
offchainConfigBytes
);

(, , address[] memory signers, address[] memory transmitters, uint8 f) = registry.getState();
(, , address[] memory signers, address[] memory transmitters, uint8 f) = registryMaster.getState();

assertEq(signers, s_valid_signers);
assertEq(transmitters, s_valid_transmitters);
Expand Down
26 changes: 14 additions & 12 deletions contracts/src/v0.8/automation/dev/v2_2/AutomationRegistry2_2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ contract AutomationRegistry2_2 is AutomationRegistryBase2_2, OCR2Abstract, Chain

/**
* @notice versions:
* AutomationRegistry 2.2.0: moves chain-spicific integration code into a separate module
* AutomationRegistry 2.2.0: moves chain-specific integration code into a separate module
* KeeperRegistry 2.1.0: introduces support for log triggers
* removes the need for "wrapped perform data"
* KeeperRegistry 2.0.2: pass revert bytes as performData when target contract reverts
Expand Down Expand Up @@ -48,7 +48,6 @@ contract AutomationRegistry2_2 is AutomationRegistryBase2_2, OCR2Abstract, Chain
AutomationRegistryLogicB2_2 logicA
)
AutomationRegistryBase2_2(
logicA.getMode(),
logicA.getLinkAddress(),
logicA.getLinkNativeFeedAddress(),
logicA.getFastGasFeedAddress(),
Expand Down Expand Up @@ -88,13 +87,18 @@ contract AutomationRegistry2_2 is AutomationRegistryBase2_2, OCR2Abstract, Chain
uint40 epochAndRound = uint40(uint256(reportContext[1]));
uint32 epoch = uint32(epochAndRound >> 8);

_handleReport(hotVars, report, gasOverhead, epoch);
_handleReport(hotVars, report, gasOverhead);

if (epoch > hotVars.latestEpoch) {
s_hotVars.latestEpoch = epoch;
}
}

function _handleReport(HotVars memory hotVars, Report memory report, uint256 gasOverhead, uint32 epoch) private {
function _handleReport(HotVars memory hotVars, Report memory report, uint256 gasOverhead) private {
UpkeepTransmitInfo[] memory upkeepTransmitInfo = new UpkeepTransmitInfo[](report.upkeepIds.length);
uint16 numUpkeepsPassedChecks;

uint256 blocknumber = hotVars.chainModule.blockNumber();
infiloop2 marked this conversation as resolved.
Show resolved Hide resolved
for (uint256 i = 0; i < report.upkeepIds.length; i++) {
upkeepTransmitInfo[i].upkeep = s_upkeep[report.upkeepIds[i]];
upkeepTransmitInfo[i].triggerType = _getTriggerType(report.upkeepIds[i]);
Expand All @@ -109,6 +113,7 @@ contract AutomationRegistry2_2 is AutomationRegistryBase2_2, OCR2Abstract, Chain
);
(upkeepTransmitInfo[i].earlyChecksPassed, upkeepTransmitInfo[i].dedupID) = _prePerformChecks(
report.upkeepIds[i],
blocknumber,
report.triggers[i],
upkeepTransmitInfo[i],
hotVars
Expand All @@ -131,7 +136,7 @@ contract AutomationRegistry2_2 is AutomationRegistryBase2_2, OCR2Abstract, Chain
gasOverhead -= upkeepTransmitInfo[i].gasUsed;

// Store last perform block number / deduping key for upkeep
_updateTriggerMarker(report.upkeepIds[i], upkeepTransmitInfo[i]);
_updateTriggerMarker(report.upkeepIds[i], blocknumber, upkeepTransmitInfo[i]);
}
// No upkeeps to be performed in this report
if (numUpkeepsPassedChecks == 0) {
Expand All @@ -141,7 +146,7 @@ contract AutomationRegistry2_2 is AutomationRegistryBase2_2, OCR2Abstract, Chain
// This is the overall gas overhead that will be split across performed upkeeps
// Take upper bound of 16 gas per callData bytes, which is approximated to be reportLength
// Rest of msg.data is accounted for in accounting overheads
// NOTE in process of changing acounting, so pre-emptively changed reportLength to msg.data.length
// NOTE in process of changing accounting, so pre-emptively changed reportLength to msg.data.length
gasOverhead =
(gasOverhead - gasleft() + 16 * msg.data.length) +
ACCOUNTING_FIXED_GAS_OVERHEAD +
Expand Down Expand Up @@ -187,10 +192,6 @@ contract AutomationRegistry2_2 is AutomationRegistryBase2_2, OCR2Abstract, Chain
// record payments
s_transmitters[msg.sender].balance += totalReimbursement;
s_hotVars.totalPremium += totalPremium;

if (epoch > hotVars.latestEpoch) {
s_hotVars.latestEpoch = epoch;
}
}

/**
Expand Down Expand Up @@ -320,7 +321,8 @@ contract AutomationRegistry2_2 is AutomationRegistryBase2_2, OCR2Abstract, Chain
reentrancyGuard: s_hotVars.reentrancyGuard,
totalPremium: totalPremium,
latestEpoch: 0, // DON restarts epoch
reorgProtectionEnabled: onchainConfig.reorgProtectionEnabled
reorgProtectionEnabled: onchainConfig.reorgProtectionEnabled,
chainModule: onchainConfig.chainModule
});

s_storage = Storage({
Expand All @@ -341,7 +343,7 @@ contract AutomationRegistry2_2 is AutomationRegistryBase2_2, OCR2Abstract, Chain
s_fallbackLinkPrice = onchainConfig.fallbackLinkPrice;

uint32 previousConfigBlockNumber = s_storage.latestConfigBlockNumber;
s_storage.latestConfigBlockNumber = uint32(_blockNum());
s_storage.latestConfigBlockNumber = uint32(onchainConfig.chainModule.blockNumber());
s_storage.configCount += 1;

bytes memory onchainConfigBytes = abi.encode(onchainConfig);
Expand Down
Loading
Loading