Skip to content

Commit

Permalink
AUTO-8804: create chain specific modules for l1 gas calculations (sma…
Browse files Browse the repository at this point in the history
…rtcontractkit#11896)

* AUTO-8804: added a chain specific module for automation

* add modules

* create specific modules for different chains

* implement modules

* addressed some feedbacks

* update tests

* generate wrappers

* fix foundry

* run yarn prettier:write

* remove unnecessary import

* remove unnecessary checks

* update gas overheads to pass tests

* regen wrappers

* fix sonarcube issues

* address some comments

* adjust gas overheads

* prettier

* remove only

* adjust gas overhead again

* dont use const

* rebase to latest and add chainmodule getter

---------

Co-authored-by: lei shi <[email protected]>
Co-authored-by: Akshay Aggarwal <[email protected]>
  • Loading branch information
3 people authored Feb 14, 2024
1 parent efdb8af commit 9a687b0
Show file tree
Hide file tree
Showing 21 changed files with 1,002 additions and 244 deletions.
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;
}
}
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));
}
}
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.
// 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.

19 changes: 19 additions & 0 deletions contracts/src/v0.8/automation/dev/interfaces/v2_2/IChainModule.sol
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);
}
34 changes: 12 additions & 22 deletions contracts/src/v0.8/automation/dev/test/AutomationRegistry2_2.t.sol
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();
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

0 comments on commit 9a687b0

Please sign in to comment.