From 9f28d59c50394bffb23fb5be6ac2604b0046af99 Mon Sep 17 00:00:00 2001 From: FelixFan1992 Date: Tue, 18 Jun 2024 10:57:34 -0400 Subject: [PATCH 01/14] AUTO-11081: zksync automation test --- .../v0.8/automation/AutomationForwarder.sol | 20 ++++- .../v0.8/automation/chains/ZKSyncModule.sol | 27 ++++++ .../v0.8/automation/test/ZKSyncHashTester.sol | 81 ++++++++++++++++++ .../automation/test/ZKSyncStoreTester.sol | 82 +++++++++++++++++++ .../v0.8/automation/testhelpers/MockFeed.sol | 16 ++++ .../automation/v2_2/AutomationRegistry2_2.sol | 7 ++ .../v2_2/AutomationRegistryBase2_2.sol | 11 +++ 7 files changed, 242 insertions(+), 2 deletions(-) create mode 100644 contracts/src/v0.8/automation/chains/ZKSyncModule.sol create mode 100644 contracts/src/v0.8/automation/test/ZKSyncHashTester.sol create mode 100644 contracts/src/v0.8/automation/test/ZKSyncStoreTester.sol create mode 100644 contracts/src/v0.8/automation/testhelpers/MockFeed.sol diff --git a/contracts/src/v0.8/automation/AutomationForwarder.sol b/contracts/src/v0.8/automation/AutomationForwarder.sol index 58707e96276..c596655e948 100644 --- a/contracts/src/v0.8/automation/AutomationForwarder.sol +++ b/contracts/src/v0.8/automation/AutomationForwarder.sol @@ -5,6 +5,13 @@ import {IAutomationRegistryConsumer} from "./interfaces/IAutomationRegistryConsu uint256 constant PERFORM_GAS_CUSHION = 5_000; +interface ISystemContext { + function gasPerPubdataByte() external view returns (uint256 gasPerPubdataByte); + function getCurrentPubdataSpent() external view returns (uint256 currentPubdataSpent); +} + +ISystemContext constant SYSTEM_CONTEXT_CONTRACT = ISystemContext(address(0x800b)); + /** * @title AutomationForwarder is a relayer that sits between the registry and the customer's target contract * @dev The purpose of the forwarder is to give customers a consistent address to authorize against, @@ -20,6 +27,8 @@ contract AutomationForwarder { IAutomationRegistryConsumer private s_registry; + event GasDetails(uint256 indexed pubdataUsed, uint256 indexed gasPerPubdataByte, uint256 indexed executionGasUsed, uint256 p1, uint256 p2, uint256 g1, uint256 g2); + constructor(address target, address registry, address logic) { s_registry = IAutomationRegistryConsumer(registry); i_target = target; @@ -35,7 +44,8 @@ contract AutomationForwarder { function forward(uint256 gasAmount, bytes memory data) external returns (bool success, uint256 gasUsed) { if (msg.sender != address(s_registry)) revert(); address target = i_target; - gasUsed = gasleft(); + uint256 g1 = gasleft(); + uint256 p1 = SYSTEM_CONTEXT_CONTRACT.getCurrentPubdataSpent(); assembly { let g := gas() // Compute g -= PERFORM_GAS_CUSHION and check for underflow @@ -55,7 +65,13 @@ contract AutomationForwarder { // call with exact gas success := call(gasAmount, target, 0, add(data, 0x20), mload(data), 0, 0) } - gasUsed = gasUsed - gasleft(); + uint256 p2 = SYSTEM_CONTEXT_CONTRACT.getCurrentPubdataSpent(); + // need to check for 0 + uint256 pubdataUsed = p2 - p1; + uint256 gasPerPubdataByte = SYSTEM_CONTEXT_CONTRACT.gasPerPubdataByte(); + uint256 g2 = gasleft(); + gasUsed = g1 - g2 + pubdataUsed * gasPerPubdataByte; + emit GasDetails(pubdataUsed, gasPerPubdataByte, g1 - g2, p1, p2, g1, g2); return (success, gasUsed); } diff --git a/contracts/src/v0.8/automation/chains/ZKSyncModule.sol b/contracts/src/v0.8/automation/chains/ZKSyncModule.sol new file mode 100644 index 00000000000..8f87288ce70 --- /dev/null +++ b/contracts/src/v0.8/automation/chains/ZKSyncModule.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.19; + +import {ChainModuleBase} from "./ChainModuleBase.sol"; + +ISystemContext constant SYSTEM_CONTEXT_CONTRACT = ISystemContext(address(0x800b)); + +interface ISystemContext { + function gasPrice() external view returns (uint256); +} + +contract ZKSyncModule is ChainModuleBase { + uint256 private constant FIXED_GAS_OVERHEAD = 5_000; + + function getMaxL1Fee(uint256 maxCalldataSize) external view override returns (uint256) { + return maxCalldataSize * SYSTEM_CONTEXT_CONTRACT.gasPrice(); + } + + function getGasOverhead() + external + view + override + returns (uint256 chainModuleFixedOverhead, uint256 chainModulePerByteOverhead) + { + return (FIXED_GAS_OVERHEAD, 0); + } +} diff --git a/contracts/src/v0.8/automation/test/ZKSyncHashTester.sol b/contracts/src/v0.8/automation/test/ZKSyncHashTester.sol new file mode 100644 index 00000000000..703c3dcbf34 --- /dev/null +++ b/contracts/src/v0.8/automation/test/ZKSyncHashTester.sol @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +contract ZKSyncHashTester { + event PerformingUpkeep( + address indexed from, + uint256 initialTimestamp, + uint256 lastTimestamp, + uint256 previousBlock, + uint256 counter + ); + + uint256 public testRange; + uint256 public interval; + uint256 public lastTimestamp; + uint256 public previousPerformBlock; + uint256 public initialTimestamp; + uint256 public counter; + + uint32 public iterations; + bytes public data; + bytes32 public storedHash; + + constructor() { + testRange = 10000; + interval = 200; + previousPerformBlock = 0; + lastTimestamp = block.timestamp; + initialTimestamp = 0; + counter = 0; + iterations = 100; + data = "0xff"; + } + + function storeHash() public { + bytes32 h = keccak256(data); + for (uint32 i = 0; i < iterations - 1; i++) { + h = keccak256(abi.encode(h)); + } + storedHash = h; + } + + function setIterations(uint32 _i) external { + iterations = _i; + } + + function setData(bytes calldata _data) external { + data = _data; + } + + function checkUpkeep(bytes calldata _data) external view returns (bool, bytes memory) { + return (eligible(), _data); + } + + function performUpkeep(bytes calldata performData) external { + if (initialTimestamp == 0) { + initialTimestamp = block.timestamp; + } + storeHash(); + lastTimestamp = block.timestamp; + counter = counter + 1; + performData; + emit PerformingUpkeep(tx.origin, initialTimestamp, lastTimestamp, previousPerformBlock, counter); + previousPerformBlock = lastTimestamp; + } + + function eligible() public view returns (bool) { + if (initialTimestamp == 0) { + return true; + } + + return (block.timestamp - initialTimestamp) < testRange && (block.timestamp - lastTimestamp) >= interval; + } + + function setSpread(uint256 _testRange, uint256 _interval) external { + testRange = _testRange; + interval = _interval; + initialTimestamp = 0; + counter = 0; + } +} diff --git a/contracts/src/v0.8/automation/test/ZKSyncStoreTester.sol b/contracts/src/v0.8/automation/test/ZKSyncStoreTester.sol new file mode 100644 index 00000000000..9320954ab0a --- /dev/null +++ b/contracts/src/v0.8/automation/test/ZKSyncStoreTester.sol @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +contract ZKSyncStoreTester { + event PerformingUpkeep( + address indexed from, + uint256 initialTimestamp, + uint256 lastTimestamp, + uint256 previousBlock, + uint256 counter + ); + + uint256 public testRange; + uint256 public interval; + uint256 public lastTimestamp; + uint256 public previousPerformBlock; + uint256 public initialTimestamp; + uint256 public counter; + + uint32 public iterations; + bytes public storedData; + bool public reset; + bytes[] public data; + + constructor() { + testRange = 10000; + interval = 200; + previousPerformBlock = 0; + lastTimestamp = block.timestamp; + initialTimestamp = 0; + counter = 0; + + iterations = 100; + } + + function storeData() public { + data = new bytes[](iterations); + storedData = new bytes(iterations); + bytes1 d = 0xff; + if (reset) { + d = 0x00; + } + for (uint32 i = 0; i < iterations; i++) { + storedData[i] = d; + } + reset = !reset; + } + + function setIterations(uint32 _i) external { + iterations = _i; + } + + function checkUpkeep(bytes calldata data) external view returns (bool, bytes memory) { + return (eligible(), data); + } + + function performUpkeep(bytes calldata) external { + if (initialTimestamp == 0) { + initialTimestamp = block.timestamp; + } + storeData(); + lastTimestamp = block.timestamp; + counter = counter + 1; + emit PerformingUpkeep(tx.origin, initialTimestamp, lastTimestamp, previousPerformBlock, counter); + previousPerformBlock = lastTimestamp; + } + + function eligible() public view returns (bool) { + if (initialTimestamp == 0) { + return true; + } + + return (block.timestamp - initialTimestamp) < testRange && (block.timestamp - lastTimestamp) >= interval; + } + + function setSpread(uint256 _testRange, uint256 _interval) external { + testRange = _testRange; + interval = _interval; + initialTimestamp = 0; + counter = 0; + } +} diff --git a/contracts/src/v0.8/automation/testhelpers/MockFeed.sol b/contracts/src/v0.8/automation/testhelpers/MockFeed.sol new file mode 100644 index 00000000000..cf0260b2c56 --- /dev/null +++ b/contracts/src/v0.8/automation/testhelpers/MockFeed.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +contract MockFeed { + function latestRoundData() + external + view + returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound) + { + roundId = 0; + answer = 0; + startedAt = 0; + updatedAt = 0; + answeredInRound = 0; + } +} diff --git a/contracts/src/v0.8/automation/v2_2/AutomationRegistry2_2.sol b/contracts/src/v0.8/automation/v2_2/AutomationRegistry2_2.sol index f0c703679ca..712f7ac3562 100644 --- a/contracts/src/v0.8/automation/v2_2/AutomationRegistry2_2.sol +++ b/contracts/src/v0.8/automation/v2_2/AutomationRegistry2_2.sol @@ -163,6 +163,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 + // this place will underflow gasOverhead = (gasOverhead - gasleft()) + (16 * msg.data.length) + ACCOUNTING_FIXED_GAS_OVERHEAD; gasOverhead = gasOverhead / transmitVars.numUpkeepsPassedChecks + ACCOUNTING_PER_UPKEEP_GAS_OVERHEAD; @@ -191,6 +192,12 @@ contract AutomationRegistry2_2 is AutomationRegistryBase2_2, OCR2Abstract, Chain gasOverhead, report.triggers[i] ); + + emit UpkeepPerformedDetails( + reimbursement + premium, + upkeepTransmitInfo[i].gasUsed, + gasOverhead + ); } } } diff --git a/contracts/src/v0.8/automation/v2_2/AutomationRegistryBase2_2.sol b/contracts/src/v0.8/automation/v2_2/AutomationRegistryBase2_2.sol index 6903f55212a..8fcaa748bfc 100644 --- a/contracts/src/v0.8/automation/v2_2/AutomationRegistryBase2_2.sol +++ b/contracts/src/v0.8/automation/v2_2/AutomationRegistryBase2_2.sol @@ -381,6 +381,17 @@ abstract contract AutomationRegistryBase2_2 is ConfirmedOwner { uint256 gasOverhead, bytes trigger ); + event UpkeepPerformedDetails( + uint96 indexed totalPayment, + uint256 indexed gasUsed, + uint256 indexed gasOverhead + ); + +// 0x00000000000000000000000000000000000000000000000000257a2909289c77 => 10548890804198519 => 0.0105 LINK +// 0x0000000000000000000000000000000000000000000000000000000000000e8b => 3723 gas +// 0x000000000000000000000000000000000000000000000000000000000001412c => 82220 gas +// 0x0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000002beaa42db6cf5e34edd2e0c09b6c24262d5ad261e77818db987d0f0669367962415c34 +// 132681 - 82220 = 50461 event UpkeepPrivilegeConfigSet(uint256 indexed id, bytes privilegeConfig); event UpkeepReceived(uint256 indexed id, uint256 startingBalance, address importedFrom); event UpkeepRegistered(uint256 indexed id, uint32 performGas, address admin); From 0fecead2e68303f1b2fdb6e0b1c8c3bbedaf84aa Mon Sep 17 00:00:00 2001 From: "app-token-issuer-infra-releng[bot]" <120227048+app-token-issuer-infra-releng[bot]@users.noreply.github.com> Date: Tue, 18 Jun 2024 15:04:56 +0000 Subject: [PATCH 02/14] Update gethwrappers --- ...nerated-wrapper-dependency-versions-do-not-edit.txt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/core/gethwrappers/generation/generated-wrapper-dependency-versions-do-not-edit.txt b/core/gethwrappers/generation/generated-wrapper-dependency-versions-do-not-edit.txt index e46f86ba591..cd2aa12d775 100644 --- a/core/gethwrappers/generation/generated-wrapper-dependency-versions-do-not-edit.txt +++ b/core/gethwrappers/generation/generated-wrapper-dependency-versions-do-not-edit.txt @@ -9,12 +9,12 @@ automation_consumer_benchmark: ../../contracts/solc/v0.8.16/AutomationConsumerBe automation_forwarder_logic: ../../contracts/solc/v0.8.16/AutomationForwarderLogic/AutomationForwarderLogic.abi ../../contracts/solc/v0.8.16/AutomationForwarderLogic/AutomationForwarderLogic.bin 15ae0c367297955fdab4b552dbb10e1f2be80a8fde0efec4a4d398693e9d72b5 automation_registrar_wrapper2_1: ../../contracts/solc/v0.8.16/AutomationRegistrar2_1/AutomationRegistrar2_1.abi ../../contracts/solc/v0.8.16/AutomationRegistrar2_1/AutomationRegistrar2_1.bin eb06d853aab39d3196c593b03e555851cbe8386e0fe54a74c2479f62d14b3c42 automation_registrar_wrapper2_3: ../../contracts/solc/v0.8.19/AutomationRegistrar2_3/AutomationRegistrar2_3.abi ../../contracts/solc/v0.8.19/AutomationRegistrar2_3/AutomationRegistrar2_3.bin 41f4b045cb783a8ad6e786b54216cc3666101abe75851784252e71aae3b00a99 -automation_registry_logic_a_wrapper_2_2: ../../contracts/solc/v0.8.19/AutomationRegistryLogicA2_2/AutomationRegistryLogicA2_2.abi ../../contracts/solc/v0.8.19/AutomationRegistryLogicA2_2/AutomationRegistryLogicA2_2.bin 2f267fb8467a15c587ce4586ac56069f7229344ad3936430d7c7624c0528a171 -automation_registry_logic_a_wrapper_2_3: ../../contracts/solc/v0.8.19/AutomationRegistryLogicA2_3/AutomationRegistryLogicA2_3.abi ../../contracts/solc/v0.8.19/AutomationRegistryLogicA2_3/AutomationRegistryLogicA2_3.bin 73b5cc3ece642abbf6f2a4c9188335b71404f4dd0ad10b761390b6397af6f1c8 -automation_registry_logic_b_wrapper_2_2: ../../contracts/solc/v0.8.19/AutomationRegistryLogicB2_2/AutomationRegistryLogicB2_2.abi ../../contracts/solc/v0.8.19/AutomationRegistryLogicB2_2/AutomationRegistryLogicB2_2.bin a6d33dfbbfb0ff253eb59a51f4f6d6d4c22ea5ec95aae52d25d49a312b37a22f +automation_registry_logic_a_wrapper_2_2: ../../contracts/solc/v0.8.19/AutomationRegistryLogicA2_2/AutomationRegistryLogicA2_2.abi ../../contracts/solc/v0.8.19/AutomationRegistryLogicA2_2/AutomationRegistryLogicA2_2.bin 5f3bfb68076db800219d00b37e16f1bfc0b391b0e6255a34d5846791d81333c4 +automation_registry_logic_a_wrapper_2_3: ../../contracts/solc/v0.8.19/AutomationRegistryLogicA2_3/AutomationRegistryLogicA2_3.abi ../../contracts/solc/v0.8.19/AutomationRegistryLogicA2_3/AutomationRegistryLogicA2_3.bin 5c23da8d3773a8cbd48cd7639e14e0c82b3395edacd3f610e019f6451db679f9 +automation_registry_logic_b_wrapper_2_2: ../../contracts/solc/v0.8.19/AutomationRegistryLogicB2_2/AutomationRegistryLogicB2_2.abi ../../contracts/solc/v0.8.19/AutomationRegistryLogicB2_2/AutomationRegistryLogicB2_2.bin 8024d943c8c2d380687a17e8816afdbba8c84ba1e27a5c80639e2df8fadad64d automation_registry_logic_b_wrapper_2_3: ../../contracts/solc/v0.8.19/AutomationRegistryLogicB2_3/AutomationRegistryLogicB2_3.abi ../../contracts/solc/v0.8.19/AutomationRegistryLogicB2_3/AutomationRegistryLogicB2_3.bin fbf6f6cf4e6858855ff5da847c3baa4859dd997cfae51f2fa0651e4fa15b92c9 automation_registry_logic_c_wrapper_2_3: ../../contracts/solc/v0.8.19/AutomationRegistryLogicC2_3/AutomationRegistryLogicC2_3.abi ../../contracts/solc/v0.8.19/AutomationRegistryLogicC2_3/AutomationRegistryLogicC2_3.bin 6bfe0f54fa7a587a83b6981ffdef28b3cb5e24cae1c95becdf59eed21147d289 -automation_registry_wrapper_2_2: ../../contracts/solc/v0.8.19/AutomationRegistry2_2/AutomationRegistry2_2.abi ../../contracts/solc/v0.8.19/AutomationRegistry2_2/AutomationRegistry2_2.bin de60f69878e9b32a291a001c91fc8636544c2cfbd9b507c8c1a4873b602bfb62 +automation_registry_wrapper_2_2: ../../contracts/solc/v0.8.19/AutomationRegistry2_2/AutomationRegistry2_2.abi ../../contracts/solc/v0.8.19/AutomationRegistry2_2/AutomationRegistry2_2.bin 4c296e1d2857f63f30e735ee67c4297f45ba82c3d2406f8c2f1b0dd2293ef11a automation_registry_wrapper_2_3: ../../contracts/solc/v0.8.19/AutomationRegistry2_3/AutomationRegistry2_3.abi ../../contracts/solc/v0.8.19/AutomationRegistry2_3/AutomationRegistry2_3.bin f8f920a225fdb1e36948dd95bae3aa46ecc2b01fd113480e111960b5e5f95624 automation_utils_2_1: ../../contracts/solc/v0.8.16/AutomationUtils2_1/AutomationUtils2_1.abi ../../contracts/solc/v0.8.16/AutomationUtils2_1/AutomationUtils2_1.bin 815b17b63f15d26a0274b962eefad98cdee4ec897ead58688bbb8e2470e585f5 automation_utils_2_2: ../../contracts/solc/v0.8.19/AutomationUtils2_2/AutomationUtils2_2.abi ../../contracts/solc/v0.8.19/AutomationUtils2_2/AutomationUtils2_2.bin 8743f6231aaefa3f2a0b2d484258070d506e2d0860690e66890dccc3949edb2e @@ -44,7 +44,7 @@ keeper_registrar_wrapper1_2_mock: ../../contracts/solc/v0.8.6/KeeperRegistrar1_2 keeper_registrar_wrapper2_0: ../../contracts/solc/v0.8.6/KeeperRegistrar2_0/KeeperRegistrar2_0.abi ../../contracts/solc/v0.8.6/KeeperRegistrar2_0/KeeperRegistrar2_0.bin 647f125c2f0dafabcdc545cb77b15dc2ec3ea9429357806813179b1fd555c2d2 keeper_registry_logic1_3: ../../contracts/solc/v0.8.6/KeeperRegistryLogic1_3/KeeperRegistryLogic1_3.abi ../../contracts/solc/v0.8.6/KeeperRegistryLogic1_3/KeeperRegistryLogic1_3.bin 903f8b9c8e25425ca6d0b81b89e339d695a83630bfbfa24a6f3b38869676bc5a keeper_registry_logic2_0: ../../contracts/solc/v0.8.6/KeeperRegistryLogic2_0/KeeperRegistryLogic2_0.abi ../../contracts/solc/v0.8.6/KeeperRegistryLogic2_0/KeeperRegistryLogic2_0.bin d69d2bc8e4844293dbc2d45abcddc50b84c88554ecccfa4fa77c0ca45ec80871 -keeper_registry_logic_a_wrapper_2_1: ../../contracts/solc/v0.8.16/KeeperRegistryLogicA2_1/KeeperRegistryLogicA2_1.abi ../../contracts/solc/v0.8.16/KeeperRegistryLogicA2_1/KeeperRegistryLogicA2_1.bin a2327779e652a2bbd2ed2f4a7b904b696dbefd5b16e73d39be87188bde654d61 +keeper_registry_logic_a_wrapper_2_1: ../../contracts/solc/v0.8.16/KeeperRegistryLogicA2_1/KeeperRegistryLogicA2_1.abi ../../contracts/solc/v0.8.16/KeeperRegistryLogicA2_1/KeeperRegistryLogicA2_1.bin bb0c40b839d2ff6b722ab146e438d3c0f74a118bdccd79d1ba0f80a3a1fa1b20 keeper_registry_logic_b_wrapper_2_1: ../../contracts/solc/v0.8.16/KeeperRegistryLogicB2_1/KeeperRegistryLogicB2_1.abi ../../contracts/solc/v0.8.16/KeeperRegistryLogicB2_1/KeeperRegistryLogicB2_1.bin 83b0cc20c6aa437b824f424b3e16ddcb18ab08bfa64398f143dbbf78f953dfef keeper_registry_wrapper1_2: ../../contracts/solc/v0.8.6/KeeperRegistry1_2/KeeperRegistry1_2.abi ../../contracts/solc/v0.8.6/KeeperRegistry1_2/KeeperRegistry1_2.bin f6f48cc6a4e03ffc987a017041417a1db78566275ec8ed7673fbfc9052ce0215 keeper_registry_wrapper1_3: ../../contracts/solc/v0.8.6/KeeperRegistry1_3/KeeperRegistry1_3.abi ../../contracts/solc/v0.8.6/KeeperRegistry1_3/KeeperRegistry1_3.bin d4dc760b767ae274ee25c4a604ea371e1fa603a7b6421b69efb2088ad9e8abb3 From 894328daadc49d0580ac242d24454ed5b62ba41d Mon Sep 17 00:00:00 2001 From: FelixFan1992 Date: Thu, 20 Jun 2024 16:24:13 -0400 Subject: [PATCH 03/14] create a zksync version and handle L1 calculation --- .../automation/AutomationZKSyncForwarder.sol | 112 +++ .../interfaces/IAutomationZKSyncForwarder.sol | 15 + .../AutomationRegistryLogicA2_2.sol | 433 +++++++++ .../AutomationRegistryLogicB2_2.sol | 540 +++++++++++ .../ZKSyncAutomationRegistry2_2.sol | 425 +++++++++ .../ZKSyncAutomationRegistryBase2_2.sol | 874 ++++++++++++++++++ 6 files changed, 2399 insertions(+) create mode 100644 contracts/src/v0.8/automation/AutomationZKSyncForwarder.sol create mode 100644 contracts/src/v0.8/automation/interfaces/IAutomationZKSyncForwarder.sol create mode 100644 contracts/src/v0.8/automation/v2_2_zksync/AutomationRegistryLogicA2_2.sol create mode 100644 contracts/src/v0.8/automation/v2_2_zksync/AutomationRegistryLogicB2_2.sol create mode 100644 contracts/src/v0.8/automation/v2_2_zksync/ZKSyncAutomationRegistry2_2.sol create mode 100644 contracts/src/v0.8/automation/v2_2_zksync/ZKSyncAutomationRegistryBase2_2.sol diff --git a/contracts/src/v0.8/automation/AutomationZKSyncForwarder.sol b/contracts/src/v0.8/automation/AutomationZKSyncForwarder.sol new file mode 100644 index 00000000000..ae61e9f6355 --- /dev/null +++ b/contracts/src/v0.8/automation/AutomationZKSyncForwarder.sol @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.16; + +import {IAutomationRegistryConsumer} from "./interfaces/IAutomationRegistryConsumer.sol"; + +uint256 constant PERFORM_GAS_CUSHION = 5_000; + +interface ISystemContext { + function gasPerPubdataByte() external view returns (uint256 gasPerPubdataByte); + function getCurrentPubdataSpent() external view returns (uint256 currentPubdataSpent); +} + +ISystemContext constant SYSTEM_CONTEXT_CONTRACT = ISystemContext(address(0x800b)); + +/** + * @title AutomationZKSyncForwarder is a relayer that sits between the registry and the customer's target contract + * @dev The purpose of the forwarder is to give customers a consistent address to authorize against, + * which stays consistent between migrations. The Forwarder also exposes the registry address, so that users who + * want to programatically interact with the registry (ie top up funds) can do so. + */ +contract AutomationZKSyncForwarder { + /// @notice the user's target contract address + address private immutable i_target; + + /// @notice the shared logic address + address private immutable i_logic; + + IAutomationRegistryConsumer private s_registry; + + event GasDetails(uint256 indexed pubdataUsed, uint256 indexed gasPerPubdataByte, uint256 indexed executionGasUsed, uint256 p1, uint256 p2, uint256 g1, uint256 g2); + + constructor(address target, address registry, address logic) { + s_registry = IAutomationRegistryConsumer(registry); + i_target = target; + i_logic = logic; + } + + /** + * @notice forward is called by the registry and forwards the call to the target + * @param gasAmount is the amount of gas to use in the call + * @param data is the 4 bytes function selector + arbitrary function data + * @return success indicating whether the target call succeeded or failed + */ + function forward(uint256 gasAmount, bytes memory data) external returns (bool success, uint256 gasUsed, uint256 l1GasUsed) { + if (msg.sender != address(s_registry)) revert(); + address target = i_target; + uint256 g1 = gasleft(); + uint256 p1 = SYSTEM_CONTEXT_CONTRACT.getCurrentPubdataSpent(); + assembly { + let g := gas() + // Compute g -= PERFORM_GAS_CUSHION and check for underflow + if lt(g, PERFORM_GAS_CUSHION) { + revert(0, 0) + } + g := sub(g, PERFORM_GAS_CUSHION) + // if g - g//64 <= gasAmount, revert + // (we subtract g//64 because of EIP-150) + if iszero(gt(sub(g, div(g, 64)), gasAmount)) { + revert(0, 0) + } + // solidity calls check that a contract actually exists at the destination, so we do the same + if iszero(extcodesize(target)) { + revert(0, 0) + } + // call with exact gas + success := call(gasAmount, target, 0, add(data, 0x20), mload(data), 0, 0) + } + uint256 p2 = SYSTEM_CONTEXT_CONTRACT.getCurrentPubdataSpent(); + // pubdata size can be less than 0 + uint256 pubdataUsed; + if (p2 - p1 > 0) { + pubdataUsed = p2 - p1; + } + + uint256 gasPerPubdataByte = SYSTEM_CONTEXT_CONTRACT.gasPerPubdataByte(); + uint256 g2 = gasleft(); + gasUsed = g1 - g2 + pubdataUsed * gasPerPubdataByte; + emit GasDetails(pubdataUsed, gasPerPubdataByte, g1 - g2, p1, p2, g1, g2); + return (success, gasUsed, pubdataUsed * gasPerPubdataByte); + } + + function getTarget() external view returns (address) { + return i_target; + } + + fallback() external { + // copy to memory for assembly access + address logic = i_logic; + // copied directly from OZ's Proxy contract + assembly { + // Copy msg.data. We take full control of memory in this inline assembly + // block because it will not return to Solidity code. We overwrite the + // Solidity scratch pad at memory position 0. + calldatacopy(0, 0, calldatasize()) + + // out and outsize are 0 because we don't know the size yet. + let result := delegatecall(gas(), logic, 0, calldatasize(), 0, 0) + + // Copy the returned data. + returndatacopy(0, 0, returndatasize()) + + switch result + // delegatecall returns 0 on error. + case 0 { + revert(0, returndatasize()) + } + default { + return(0, returndatasize()) + } + } + } +} diff --git a/contracts/src/v0.8/automation/interfaces/IAutomationZKSyncForwarder.sol b/contracts/src/v0.8/automation/interfaces/IAutomationZKSyncForwarder.sol new file mode 100644 index 00000000000..4f30ec097af --- /dev/null +++ b/contracts/src/v0.8/automation/interfaces/IAutomationZKSyncForwarder.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {ITypeAndVersion} from "../../shared/interfaces/ITypeAndVersion.sol"; +import {IAutomationRegistryConsumer} from "./IAutomationRegistryConsumer.sol"; + +interface IAutomationZKSyncForwarder is ITypeAndVersion { + function forward(uint256 gasAmount, bytes memory data) external returns (bool success, uint256 gasUsed, uint256 l1GasUsed); + + function updateRegistry(address newRegistry) external; + + function getRegistry() external view returns (IAutomationRegistryConsumer); + + function getTarget() external view returns (address); +} diff --git a/contracts/src/v0.8/automation/v2_2_zksync/AutomationRegistryLogicA2_2.sol b/contracts/src/v0.8/automation/v2_2_zksync/AutomationRegistryLogicA2_2.sol new file mode 100644 index 00000000000..5bd523ee085 --- /dev/null +++ b/contracts/src/v0.8/automation/v2_2_zksync/AutomationRegistryLogicA2_2.sol @@ -0,0 +1,433 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.19; + +import {EnumerableSet} from "../../vendor/openzeppelin-solidity/v4.7.3/contracts/utils/structs/EnumerableSet.sol"; +import {Address} from "../../vendor/openzeppelin-solidity/v4.7.3/contracts/utils/Address.sol"; +import {ZKSyncAutomationRegistryBase2_2} from "./ZKSyncAutomationRegistryBase2_2.sol"; +import {AutomationRegistryLogicB2_2} from "./AutomationRegistryLogicB2_2.sol"; +import {Chainable} from "../Chainable.sol"; +import {AutomationZKSyncForwarder} from "../AutomationZKSyncForwarder.sol"; +import {IAutomationZKSyncForwarder} from "../interfaces/IAutomationZKSyncForwarder.sol"; +import {UpkeepTranscoderInterfaceV2} from "../interfaces/UpkeepTranscoderInterfaceV2.sol"; +import {MigratableKeeperRegistryInterfaceV2} from "../interfaces/MigratableKeeperRegistryInterfaceV2.sol"; + +/** + * @notice Logic contract, works in tandem with AutomationRegistry as a proxy + */ +contract AutomationRegistryLogicA2_2 is ZKSyncAutomationRegistryBase2_2, Chainable { + using Address for address; + using EnumerableSet for EnumerableSet.UintSet; + using EnumerableSet for EnumerableSet.AddressSet; + + /** + * @param logicB the address of the second logic contract + */ + constructor( + AutomationRegistryLogicB2_2 logicB + ) + ZKSyncAutomationRegistryBase2_2( + logicB.getLinkAddress(), + logicB.getLinkNativeFeedAddress(), + logicB.getFastGasFeedAddress(), + logicB.getAutomationForwarderLogic(), + logicB.getAllowedReadOnlyAddress() + ) + Chainable(address(logicB)) + {} + + /** + * @notice called by the automation DON to check if work is needed + * @param id the upkeep ID to check for work needed + * @param triggerData extra contextual data about the trigger (not used in all code paths) + * @dev this one of the core functions called in the hot path + * @dev there is a 2nd checkUpkeep function (below) that is being maintained for backwards compatibility + * @dev there is an incongruency on what gets returned during failure modes + * ex sometimes we include price data, sometimes we omit it depending on the failure + */ + function checkUpkeep( + uint256 id, + bytes memory triggerData + ) + public + returns ( + bool upkeepNeeded, + bytes memory performData, + UpkeepFailureReason upkeepFailureReason, + uint256 gasUsed, + uint256 gasLimit, + uint256 fastGasWei, + uint256 linkNative + ) + { + _preventExecution(); + + Trigger triggerType = _getTriggerType(id); + HotVars memory hotVars = s_hotVars; + Upkeep memory upkeep = s_upkeep[id]; + + if (hotVars.paused) return (false, bytes(""), UpkeepFailureReason.REGISTRY_PAUSED, 0, upkeep.performGas, 0, 0); + if (upkeep.maxValidBlocknumber != UINT32_MAX) + return (false, bytes(""), UpkeepFailureReason.UPKEEP_CANCELLED, 0, upkeep.performGas, 0, 0); + if (upkeep.paused) return (false, bytes(""), UpkeepFailureReason.UPKEEP_PAUSED, 0, upkeep.performGas, 0, 0); + + (fastGasWei, linkNative) = _getFeedData(hotVars); + uint96 maxLinkPayment = _getMaxLinkPayment(hotVars, triggerType, upkeep.performGas, fastGasWei, linkNative); + if (upkeep.balance < maxLinkPayment) { + return (false, bytes(""), UpkeepFailureReason.INSUFFICIENT_BALANCE, 0, upkeep.performGas, 0, 0); + } + + bytes memory callData = _checkPayload(id, triggerType, triggerData); + + gasUsed = gasleft(); + // solhint-disable-next-line avoid-low-level-calls + (bool success, bytes memory result) = upkeep.forwarder.getTarget().call{gas: s_storage.checkGasLimit}(callData); + gasUsed = gasUsed - gasleft(); + + if (!success) { + // User's target check reverted. We capture the revert data here and pass it within performData + if (result.length > s_storage.maxRevertDataSize) { + return ( + false, + bytes(""), + UpkeepFailureReason.REVERT_DATA_EXCEEDS_LIMIT, + gasUsed, + upkeep.performGas, + fastGasWei, + linkNative + ); + } + return ( + upkeepNeeded, + result, + UpkeepFailureReason.TARGET_CHECK_REVERTED, + gasUsed, + upkeep.performGas, + fastGasWei, + linkNative + ); + } + + (upkeepNeeded, performData) = abi.decode(result, (bool, bytes)); + if (!upkeepNeeded) + return ( + false, + bytes(""), + UpkeepFailureReason.UPKEEP_NOT_NEEDED, + gasUsed, + upkeep.performGas, + fastGasWei, + linkNative + ); + + if (performData.length > s_storage.maxPerformDataSize) + return ( + false, + bytes(""), + UpkeepFailureReason.PERFORM_DATA_EXCEEDS_LIMIT, + gasUsed, + upkeep.performGas, + fastGasWei, + linkNative + ); + + return (upkeepNeeded, performData, upkeepFailureReason, gasUsed, upkeep.performGas, fastGasWei, linkNative); + } + + /** + * @notice see other checkUpkeep function for description + * @dev this function may be deprecated in a future version of chainlink automation + */ + function checkUpkeep( + uint256 id + ) + external + returns ( + bool upkeepNeeded, + bytes memory performData, + UpkeepFailureReason upkeepFailureReason, + uint256 gasUsed, + uint256 gasLimit, + uint256 fastGasWei, + uint256 linkNative + ) + { + return checkUpkeep(id, bytes("")); + } + + /** + * @dev checkCallback is used specifically for automation data streams lookups (see StreamsLookupCompatibleInterface.sol) + * @param id the upkeepID to execute a callback for + * @param values the values returned from the data streams lookup + * @param extraData the user-provided extra context data + */ + function checkCallback( + uint256 id, + bytes[] memory values, + bytes calldata extraData + ) + external + returns (bool upkeepNeeded, bytes memory performData, UpkeepFailureReason upkeepFailureReason, uint256 gasUsed) + { + bytes memory payload = abi.encodeWithSelector(CHECK_CALLBACK_SELECTOR, values, extraData); + return executeCallback(id, payload); + } + + /** + * @notice this is a generic callback executor that forwards a call to a user's contract with the configured + * gas limit + * @param id the upkeepID to execute a callback for + * @param payload the data (including function selector) to call on the upkeep target contract + */ + function executeCallback( + uint256 id, + bytes memory payload + ) + public + returns (bool upkeepNeeded, bytes memory performData, UpkeepFailureReason upkeepFailureReason, uint256 gasUsed) + { + _preventExecution(); + + Upkeep memory upkeep = s_upkeep[id]; + gasUsed = gasleft(); + // solhint-disable-next-line avoid-low-level-calls + (bool success, bytes memory result) = upkeep.forwarder.getTarget().call{gas: s_storage.checkGasLimit}(payload); + gasUsed = gasUsed - gasleft(); + if (!success) { + return (false, bytes(""), UpkeepFailureReason.CALLBACK_REVERTED, gasUsed); + } + (upkeepNeeded, performData) = abi.decode(result, (bool, bytes)); + if (!upkeepNeeded) { + return (false, bytes(""), UpkeepFailureReason.UPKEEP_NOT_NEEDED, gasUsed); + } + if (performData.length > s_storage.maxPerformDataSize) { + return (false, bytes(""), UpkeepFailureReason.PERFORM_DATA_EXCEEDS_LIMIT, gasUsed); + } + return (upkeepNeeded, performData, upkeepFailureReason, gasUsed); + } + + /** + * @notice adds a new upkeep + * @param target address to perform upkeep on + * @param gasLimit amount of gas to provide the target contract when + * performing upkeep + * @param admin address to cancel upkeep and withdraw remaining funds + * @param triggerType the trigger for the upkeep + * @param checkData data passed to the contract when checking for upkeep + * @param triggerConfig the config for the trigger + * @param offchainConfig arbitrary offchain config for the upkeep + */ + function registerUpkeep( + address target, + uint32 gasLimit, + address admin, + Trigger triggerType, + bytes calldata checkData, + bytes memory triggerConfig, + bytes memory offchainConfig + ) public returns (uint256 id) { + if (msg.sender != owner() && !s_registrars.contains(msg.sender)) revert OnlyCallableByOwnerOrRegistrar(); + if (!target.isContract()) revert NotAContract(); + id = _createID(triggerType); + IAutomationZKSyncForwarder forwarder = IAutomationZKSyncForwarder( + address(new AutomationZKSyncForwarder(target, address(this), i_automationForwarderLogic)) + ); + _createUpkeep( + id, + Upkeep({ + performGas: gasLimit, + balance: 0, + maxValidBlocknumber: UINT32_MAX, + lastPerformedBlockNumber: 0, + amountSpent: 0, + paused: false, + forwarder: forwarder + }), + admin, + checkData, + triggerConfig, + offchainConfig + ); + s_storage.nonce++; + emit UpkeepRegistered(id, gasLimit, admin); + emit UpkeepCheckDataSet(id, checkData); + emit UpkeepTriggerConfigSet(id, triggerConfig); + emit UpkeepOffchainConfigSet(id, offchainConfig); + return (id); + } + + /** + * @notice this function registers a conditional upkeep, using a backwards compatible function signature + * @dev this function is backwards compatible with versions <=2.0, but may be removed in a future version + */ + function registerUpkeep( + address target, + uint32 gasLimit, + address admin, + bytes calldata checkData, + bytes calldata offchainConfig + ) external returns (uint256 id) { + return registerUpkeep(target, gasLimit, admin, Trigger.CONDITION, checkData, bytes(""), offchainConfig); + } + + /** + * @notice cancels an upkeep + * @param id the upkeepID to cancel + * @dev if a user cancels an upkeep, their funds are locked for CANCELLATION_DELAY blocks to + * allow any pending performUpkeep txs time to get confirmed + */ + function cancelUpkeep(uint256 id) external { + Upkeep memory upkeep = s_upkeep[id]; + bool isOwner = msg.sender == owner(); + + uint256 height = s_hotVars.chainModule.blockNumber(); + if (upkeep.maxValidBlocknumber == 0) revert CannotCancel(); + if (upkeep.maxValidBlocknumber != UINT32_MAX) revert UpkeepCancelled(); + if (!isOwner && msg.sender != s_upkeepAdmin[id]) revert OnlyCallableByOwnerOrAdmin(); + + if (!isOwner) { + height = height + CANCELLATION_DELAY; + } + s_upkeep[id].maxValidBlocknumber = uint32(height); + s_upkeepIDs.remove(id); + + // charge the cancellation fee if the minUpkeepSpend is not met + uint96 minUpkeepSpend = s_storage.minUpkeepSpend; + uint96 cancellationFee = 0; + // cancellationFee is supposed to be min(max(minUpkeepSpend - amountSpent,0), amountLeft) + if (upkeep.amountSpent < minUpkeepSpend) { + cancellationFee = minUpkeepSpend - upkeep.amountSpent; + if (cancellationFee > upkeep.balance) { + cancellationFee = upkeep.balance; + } + } + s_upkeep[id].balance = upkeep.balance - cancellationFee; + s_storage.ownerLinkBalance = s_storage.ownerLinkBalance + cancellationFee; + + emit UpkeepCanceled(id, uint64(height)); + } + + /** + * @notice adds fund to an upkeep + * @param id the upkeepID + * @param amount the amount of LINK to fund, in jules (jules = "wei" of LINK) + */ + function addFunds(uint256 id, uint96 amount) external { + Upkeep memory upkeep = s_upkeep[id]; + if (upkeep.maxValidBlocknumber != UINT32_MAX) revert UpkeepCancelled(); + s_upkeep[id].balance = upkeep.balance + amount; + s_expectedLinkBalance = s_expectedLinkBalance + amount; + i_link.transferFrom(msg.sender, address(this), amount); + emit FundsAdded(id, msg.sender, amount); + } + + /** + * @notice migrates upkeeps from one registry to another + * @param ids the upkeepIDs to migrate + * @param destination the destination registry address + * @dev a transcoder must be set in order to enable migration + * @dev migration permissions must be set on *both* sending and receiving registries + * @dev only an upkeep admin can migrate their upkeeps + */ + function migrateUpkeeps(uint256[] calldata ids, address destination) external { + if ( + s_peerRegistryMigrationPermission[destination] != MigrationPermission.OUTGOING && + s_peerRegistryMigrationPermission[destination] != MigrationPermission.BIDIRECTIONAL + ) revert MigrationNotPermitted(); + if (s_storage.transcoder == ZERO_ADDRESS) revert TranscoderNotSet(); + if (ids.length == 0) revert ArrayHasNoEntries(); + uint256 id; + Upkeep memory upkeep; + uint256 totalBalanceRemaining; + address[] memory admins = new address[](ids.length); + Upkeep[] memory upkeeps = new Upkeep[](ids.length); + bytes[] memory checkDatas = new bytes[](ids.length); + bytes[] memory triggerConfigs = new bytes[](ids.length); + bytes[] memory offchainConfigs = new bytes[](ids.length); + for (uint256 idx = 0; idx < ids.length; idx++) { + id = ids[idx]; + upkeep = s_upkeep[id]; + _requireAdminAndNotCancelled(id); + upkeep.forwarder.updateRegistry(destination); + upkeeps[idx] = upkeep; + admins[idx] = s_upkeepAdmin[id]; + checkDatas[idx] = s_checkData[id]; + triggerConfigs[idx] = s_upkeepTriggerConfig[id]; + offchainConfigs[idx] = s_upkeepOffchainConfig[id]; + totalBalanceRemaining = totalBalanceRemaining + upkeep.balance; + delete s_upkeep[id]; + delete s_checkData[id]; + delete s_upkeepTriggerConfig[id]; + delete s_upkeepOffchainConfig[id]; + // nullify existing proposed admin change if an upkeep is being migrated + delete s_proposedAdmin[id]; + s_upkeepIDs.remove(id); + emit UpkeepMigrated(id, upkeep.balance, destination); + } + s_expectedLinkBalance = s_expectedLinkBalance - totalBalanceRemaining; + bytes memory encodedUpkeeps = abi.encode( + ids, + upkeeps, + new address[](ids.length), + admins, + checkDatas, + triggerConfigs, + offchainConfigs + ); + MigratableKeeperRegistryInterfaceV2(destination).receiveUpkeeps( + UpkeepTranscoderInterfaceV2(s_storage.transcoder).transcodeUpkeeps( + UPKEEP_VERSION_BASE, + MigratableKeeperRegistryInterfaceV2(destination).upkeepVersion(), + encodedUpkeeps + ) + ); + i_link.transfer(destination, totalBalanceRemaining); + } + + /** + * @notice received upkeeps migrated from another registry + * @param encodedUpkeeps the raw upkeep data to import + * @dev this function is never called directly, it is only called by another registry's migrate function + */ + function receiveUpkeeps(bytes calldata encodedUpkeeps) external { + if ( + s_peerRegistryMigrationPermission[msg.sender] != MigrationPermission.INCOMING && + s_peerRegistryMigrationPermission[msg.sender] != MigrationPermission.BIDIRECTIONAL + ) revert MigrationNotPermitted(); + ( + uint256[] memory ids, + Upkeep[] memory upkeeps, + address[] memory targets, + address[] memory upkeepAdmins, + bytes[] memory checkDatas, + bytes[] memory triggerConfigs, + bytes[] memory offchainConfigs + ) = abi.decode(encodedUpkeeps, (uint256[], Upkeep[], address[], address[], bytes[], bytes[], bytes[])); + for (uint256 idx = 0; idx < ids.length; idx++) { + if (address(upkeeps[idx].forwarder) == ZERO_ADDRESS) { + upkeeps[idx].forwarder = IAutomationZKSyncForwarder( + address(new AutomationZKSyncForwarder(targets[idx], address(this), i_automationForwarderLogic)) + ); + } + _createUpkeep( + ids[idx], + upkeeps[idx], + upkeepAdmins[idx], + checkDatas[idx], + triggerConfigs[idx], + offchainConfigs[idx] + ); + emit UpkeepReceived(ids[idx], upkeeps[idx].balance, msg.sender); + } + } + + /** + * @notice sets the upkeep trigger config + * @param id the upkeepID to change the trigger for + * @param triggerConfig the new trigger config + */ + function setUpkeepTriggerConfig(uint256 id, bytes calldata triggerConfig) external { + _requireAdminAndNotCancelled(id); + s_upkeepTriggerConfig[id] = triggerConfig; + emit UpkeepTriggerConfigSet(id, triggerConfig); + } +} diff --git a/contracts/src/v0.8/automation/v2_2_zksync/AutomationRegistryLogicB2_2.sol b/contracts/src/v0.8/automation/v2_2_zksync/AutomationRegistryLogicB2_2.sol new file mode 100644 index 00000000000..9aa44850513 --- /dev/null +++ b/contracts/src/v0.8/automation/v2_2_zksync/AutomationRegistryLogicB2_2.sol @@ -0,0 +1,540 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.19; + +import {ZKSyncAutomationRegistryBase2_2} from "./ZKSyncAutomationRegistryBase2_2.sol"; +import {EnumerableSet} from "../../vendor/openzeppelin-solidity/v4.7.3/contracts/utils/structs/EnumerableSet.sol"; +import {Address} from "../../vendor/openzeppelin-solidity/v4.7.3/contracts/utils/Address.sol"; +import {UpkeepFormat} from "../interfaces/UpkeepTranscoderInterface.sol"; +import {IAutomationZKSyncForwarder} from "../interfaces/IAutomationZKSyncForwarder.sol"; +import {IChainModule} from "../interfaces/IChainModule.sol"; +import {IAutomationV21PlusCommon} from "../interfaces/IAutomationV21PlusCommon.sol"; + +contract AutomationRegistryLogicB2_2 is ZKSyncAutomationRegistryBase2_2 { + using Address for address; + using EnumerableSet for EnumerableSet.UintSet; + using EnumerableSet for EnumerableSet.AddressSet; + + /** + * @dev see AutomationRegistry master contract for constructor description + */ + constructor( + address link, + address linkNativeFeed, + address fastGasFeed, + address automationForwarderLogic, + address allowedReadOnlyAddress + ) ZKSyncAutomationRegistryBase2_2(link, linkNativeFeed, fastGasFeed, automationForwarderLogic, allowedReadOnlyAddress) {} + + // ================================================================ + // | UPKEEP MANAGEMENT | + // ================================================================ + + /** + * @notice transfers the address of an admin for an upkeep + */ + function transferUpkeepAdmin(uint256 id, address proposed) external { + _requireAdminAndNotCancelled(id); + if (proposed == msg.sender) revert ValueNotChanged(); + + if (s_proposedAdmin[id] != proposed) { + s_proposedAdmin[id] = proposed; + emit UpkeepAdminTransferRequested(id, msg.sender, proposed); + } + } + + /** + * @notice accepts the transfer of an upkeep admin + */ + function acceptUpkeepAdmin(uint256 id) external { + Upkeep memory upkeep = s_upkeep[id]; + if (upkeep.maxValidBlocknumber != UINT32_MAX) revert UpkeepCancelled(); + if (s_proposedAdmin[id] != msg.sender) revert OnlyCallableByProposedAdmin(); + address past = s_upkeepAdmin[id]; + s_upkeepAdmin[id] = msg.sender; + s_proposedAdmin[id] = ZERO_ADDRESS; + + emit UpkeepAdminTransferred(id, past, msg.sender); + } + + /** + * @notice pauses an upkeep - an upkeep will be neither checked nor performed while paused + */ + function pauseUpkeep(uint256 id) external { + _requireAdminAndNotCancelled(id); + Upkeep memory upkeep = s_upkeep[id]; + if (upkeep.paused) revert OnlyUnpausedUpkeep(); + s_upkeep[id].paused = true; + s_upkeepIDs.remove(id); + emit UpkeepPaused(id); + } + + /** + * @notice unpauses an upkeep + */ + function unpauseUpkeep(uint256 id) external { + _requireAdminAndNotCancelled(id); + Upkeep memory upkeep = s_upkeep[id]; + if (!upkeep.paused) revert OnlyPausedUpkeep(); + s_upkeep[id].paused = false; + s_upkeepIDs.add(id); + emit UpkeepUnpaused(id); + } + + /** + * @notice updates the checkData for an upkeep + */ + function setUpkeepCheckData(uint256 id, bytes calldata newCheckData) external { + _requireAdminAndNotCancelled(id); + if (newCheckData.length > s_storage.maxCheckDataSize) revert CheckDataExceedsLimit(); + s_checkData[id] = newCheckData; + emit UpkeepCheckDataSet(id, newCheckData); + } + + /** + * @notice updates the gas limit for an upkeep + */ + function setUpkeepGasLimit(uint256 id, uint32 gasLimit) external { + if (gasLimit < PERFORM_GAS_MIN || gasLimit > s_storage.maxPerformGas) revert GasLimitOutsideRange(); + _requireAdminAndNotCancelled(id); + s_upkeep[id].performGas = gasLimit; + + emit UpkeepGasLimitSet(id, gasLimit); + } + + /** + * @notice updates the offchain config for an upkeep + */ + function setUpkeepOffchainConfig(uint256 id, bytes calldata config) external { + _requireAdminAndNotCancelled(id); + s_upkeepOffchainConfig[id] = config; + emit UpkeepOffchainConfigSet(id, config); + } + + /** + * @notice withdraws LINK funds from an upkeep + * @dev note that an upkeep must be cancelled first!! + */ + function withdrawFunds(uint256 id, address to) external nonReentrant { + if (to == ZERO_ADDRESS) revert InvalidRecipient(); + Upkeep memory upkeep = s_upkeep[id]; + if (s_upkeepAdmin[id] != msg.sender) revert OnlyCallableByAdmin(); + if (upkeep.maxValidBlocknumber > s_hotVars.chainModule.blockNumber()) revert UpkeepNotCanceled(); + uint96 amountToWithdraw = s_upkeep[id].balance; + s_expectedLinkBalance = s_expectedLinkBalance - amountToWithdraw; + s_upkeep[id].balance = 0; + i_link.transfer(to, amountToWithdraw); + emit FundsWithdrawn(id, amountToWithdraw, to); + } + + // ================================================================ + // | NODE MANAGEMENT | + // ================================================================ + + /** + * @notice transfers the address of payee for a transmitter + */ + function transferPayeeship(address transmitter, address proposed) external { + if (s_transmitterPayees[transmitter] != msg.sender) revert OnlyCallableByPayee(); + if (proposed == msg.sender) revert ValueNotChanged(); + + if (s_proposedPayee[transmitter] != proposed) { + s_proposedPayee[transmitter] = proposed; + emit PayeeshipTransferRequested(transmitter, msg.sender, proposed); + } + } + + /** + * @notice accepts the transfer of the payee + */ + function acceptPayeeship(address transmitter) external { + if (s_proposedPayee[transmitter] != msg.sender) revert OnlyCallableByProposedPayee(); + address past = s_transmitterPayees[transmitter]; + s_transmitterPayees[transmitter] = msg.sender; + s_proposedPayee[transmitter] = ZERO_ADDRESS; + + emit PayeeshipTransferred(transmitter, past, msg.sender); + } + + /** + * @notice withdraws LINK received as payment for work performed + */ + function withdrawPayment(address from, address to) external { + if (to == ZERO_ADDRESS) revert InvalidRecipient(); + if (s_transmitterPayees[from] != msg.sender) revert OnlyCallableByPayee(); + uint96 balance = _updateTransmitterBalanceFromPool(from, s_hotVars.totalPremium, uint96(s_transmittersList.length)); + s_transmitters[from].balance = 0; + s_expectedLinkBalance = s_expectedLinkBalance - balance; + i_link.transfer(to, balance); + emit PaymentWithdrawn(from, balance, to, msg.sender); + } + + // ================================================================ + // | OWNER / MANAGER ACTIONS | + // ================================================================ + + /** + * @notice sets the privilege config for an upkeep + */ + function setUpkeepPrivilegeConfig(uint256 upkeepId, bytes calldata newPrivilegeConfig) external { + if (msg.sender != s_storage.upkeepPrivilegeManager) { + revert OnlyCallableByUpkeepPrivilegeManager(); + } + s_upkeepPrivilegeConfig[upkeepId] = newPrivilegeConfig; + emit UpkeepPrivilegeConfigSet(upkeepId, newPrivilegeConfig); + } + + /** + * @notice withdraws the owner's LINK balance + */ + function withdrawOwnerFunds() external onlyOwner { + uint96 amount = s_storage.ownerLinkBalance; + s_expectedLinkBalance = s_expectedLinkBalance - amount; + s_storage.ownerLinkBalance = 0; + emit OwnerFundsWithdrawn(amount); + i_link.transfer(msg.sender, amount); + } + + /** + * @notice allows the owner to withdraw any LINK accidentally sent to the contract + */ + function recoverFunds() external onlyOwner { + uint256 total = i_link.balanceOf(address(this)); + i_link.transfer(msg.sender, total - s_expectedLinkBalance); + } + + /** + * @notice sets the payees for the transmitters + */ + function setPayees(address[] calldata payees) external onlyOwner { + if (s_transmittersList.length != payees.length) revert ParameterLengthError(); + for (uint256 i = 0; i < s_transmittersList.length; i++) { + address transmitter = s_transmittersList[i]; + address oldPayee = s_transmitterPayees[transmitter]; + address newPayee = payees[i]; + if ( + (newPayee == ZERO_ADDRESS) || (oldPayee != ZERO_ADDRESS && oldPayee != newPayee && newPayee != IGNORE_ADDRESS) + ) revert InvalidPayee(); + if (newPayee != IGNORE_ADDRESS) { + s_transmitterPayees[transmitter] = newPayee; + } + } + emit PayeesUpdated(s_transmittersList, payees); + } + + /** + * @notice sets the migration permission for a peer registry + * @dev this must be done before upkeeps can be migrated to/from another registry + */ + function setPeerRegistryMigrationPermission(address peer, MigrationPermission permission) external onlyOwner { + s_peerRegistryMigrationPermission[peer] = permission; + } + + /** + * @notice pauses the entire registry + */ + function pause() external onlyOwner { + s_hotVars.paused = true; + emit Paused(msg.sender); + } + + /** + * @notice unpauses the entire registry + */ + function unpause() external onlyOwner { + s_hotVars.paused = false; + emit Unpaused(msg.sender); + } + + /** + * @notice sets a generic bytes field used to indicate the privilege that this admin address had + * @param admin the address to set privilege for + * @param newPrivilegeConfig the privileges that this admin has + */ + function setAdminPrivilegeConfig(address admin, bytes calldata newPrivilegeConfig) external { + if (msg.sender != s_storage.upkeepPrivilegeManager) { + revert OnlyCallableByUpkeepPrivilegeManager(); + } + s_adminPrivilegeConfig[admin] = newPrivilegeConfig; + emit AdminPrivilegeConfigSet(admin, newPrivilegeConfig); + } + + // ================================================================ + // | GETTERS | + // ================================================================ + + function getConditionalGasOverhead() external pure returns (uint256) { + return REGISTRY_CONDITIONAL_OVERHEAD; + } + + function getLogGasOverhead() external pure returns (uint256) { + return REGISTRY_LOG_OVERHEAD; + } + + function getPerPerformByteGasOverhead() external pure returns (uint256) { + return REGISTRY_PER_PERFORM_BYTE_GAS_OVERHEAD; + } + + function getPerSignerGasOverhead() external pure returns (uint256) { + return REGISTRY_PER_SIGNER_GAS_OVERHEAD; + } + + function getTransmitCalldataFixedBytesOverhead() external pure returns (uint256) { + return TRANSMIT_CALLDATA_FIXED_BYTES_OVERHEAD; + } + + function getTransmitCalldataPerSignerBytesOverhead() external pure returns (uint256) { + return TRANSMIT_CALLDATA_PER_SIGNER_BYTES_OVERHEAD; + } + + function getCancellationDelay() external pure returns (uint256) { + return CANCELLATION_DELAY; + } + + function getLinkAddress() external view returns (address) { + return address(i_link); + } + + function getLinkNativeFeedAddress() external view returns (address) { + return address(i_linkNativeFeed); + } + + function getFastGasFeedAddress() external view returns (address) { + return address(i_fastGasFeed); + } + + function getAutomationForwarderLogic() external view returns (address) { + return i_automationForwarderLogic; + } + + function getAllowedReadOnlyAddress() external view returns (address) { + return i_allowedReadOnlyAddress; + } + + function upkeepTranscoderVersion() public pure returns (UpkeepFormat) { + return UPKEEP_TRANSCODER_VERSION_BASE; + } + + function upkeepVersion() public pure returns (uint8) { + return UPKEEP_VERSION_BASE; + } + + /** + * @notice read all of the details about an upkeep + * @dev this function may be deprecated in a future version of automation in favor of individual + * getters for each field + */ + function getUpkeep(uint256 id) external view returns (IAutomationV21PlusCommon.UpkeepInfoLegacy memory upkeepInfo) { + Upkeep memory reg = s_upkeep[id]; + address target = address(reg.forwarder) == address(0) ? address(0) : reg.forwarder.getTarget(); + upkeepInfo = IAutomationV21PlusCommon.UpkeepInfoLegacy({ + target: target, + performGas: reg.performGas, + checkData: s_checkData[id], + balance: reg.balance, + admin: s_upkeepAdmin[id], + maxValidBlocknumber: reg.maxValidBlocknumber, + lastPerformedBlockNumber: reg.lastPerformedBlockNumber, + amountSpent: reg.amountSpent, + paused: reg.paused, + offchainConfig: s_upkeepOffchainConfig[id] + }); + return upkeepInfo; + } + + /** + * @notice retrieve active upkeep IDs. Active upkeep is defined as an upkeep which is not paused and not canceled. + * @param startIndex starting index in list + * @param maxCount max count to retrieve (0 = unlimited) + * @dev the order of IDs in the list is **not guaranteed**, therefore, if making successive calls, one + * should consider keeping the blockheight constant to ensure a holistic picture of the contract state + */ + function getActiveUpkeepIDs(uint256 startIndex, uint256 maxCount) external view returns (uint256[] memory) { + uint256 numUpkeeps = s_upkeepIDs.length(); + if (startIndex >= numUpkeeps) revert IndexOutOfRange(); + uint256 endIndex = startIndex + maxCount; + endIndex = endIndex > numUpkeeps || maxCount == 0 ? numUpkeeps : endIndex; + uint256[] memory ids = new uint256[](endIndex - startIndex); + for (uint256 idx = 0; idx < ids.length; idx++) { + ids[idx] = s_upkeepIDs.at(idx + startIndex); + } + return ids; + } + + /** + * @notice returns the upkeep's trigger type + */ + function getTriggerType(uint256 upkeepId) external pure returns (Trigger) { + return _getTriggerType(upkeepId); + } + + /** + * @notice returns the trigger config for an upkeeep + */ + function getUpkeepTriggerConfig(uint256 upkeepId) public view returns (bytes memory) { + return s_upkeepTriggerConfig[upkeepId]; + } + + /** + * @notice read the current info about any transmitter address + */ + function getTransmitterInfo( + address query + ) external view returns (bool active, uint8 index, uint96 balance, uint96 lastCollected, address payee) { + Transmitter memory transmitter = s_transmitters[query]; + + uint96 pooledShare = 0; + if (transmitter.active) { + uint96 totalDifference = s_hotVars.totalPremium - transmitter.lastCollected; + pooledShare = totalDifference / uint96(s_transmittersList.length); + } + + return ( + transmitter.active, + transmitter.index, + (transmitter.balance + pooledShare), + transmitter.lastCollected, + s_transmitterPayees[query] + ); + } + + /** + * @notice read the current info about any signer address + */ + function getSignerInfo(address query) external view returns (bool active, uint8 index) { + Signer memory signer = s_signers[query]; + return (signer.active, signer.index); + } + + /** + * @notice read the current state of the registry + * @dev this function is deprecated + */ + function getState() + external + view + returns ( + IAutomationV21PlusCommon.StateLegacy memory state, + IAutomationV21PlusCommon.OnchainConfigLegacy memory config, + address[] memory signers, + address[] memory transmitters, + uint8 f + ) + { + state = IAutomationV21PlusCommon.StateLegacy({ + nonce: s_storage.nonce, + ownerLinkBalance: s_storage.ownerLinkBalance, + expectedLinkBalance: s_expectedLinkBalance, + totalPremium: s_hotVars.totalPremium, + numUpkeeps: s_upkeepIDs.length(), + configCount: s_storage.configCount, + latestConfigBlockNumber: s_storage.latestConfigBlockNumber, + latestConfigDigest: s_latestConfigDigest, + latestEpoch: s_hotVars.latestEpoch, + paused: s_hotVars.paused + }); + + config = IAutomationV21PlusCommon.OnchainConfigLegacy({ + paymentPremiumPPB: s_hotVars.paymentPremiumPPB, + flatFeeMicroLink: s_hotVars.flatFeeMicroLink, + checkGasLimit: s_storage.checkGasLimit, + stalenessSeconds: s_hotVars.stalenessSeconds, + gasCeilingMultiplier: s_hotVars.gasCeilingMultiplier, + minUpkeepSpend: s_storage.minUpkeepSpend, + maxPerformGas: s_storage.maxPerformGas, + maxCheckDataSize: s_storage.maxCheckDataSize, + maxPerformDataSize: s_storage.maxPerformDataSize, + maxRevertDataSize: s_storage.maxRevertDataSize, + fallbackGasPrice: s_fallbackGasPrice, + fallbackLinkPrice: s_fallbackLinkPrice, + transcoder: s_storage.transcoder, + registrars: s_registrars.values(), + upkeepPrivilegeManager: s_storage.upkeepPrivilegeManager + }); + + return (state, config, s_signersList, s_transmittersList, s_hotVars.f); + } + + /** + * @notice get the chain module + */ + function getChainModule() external view returns (IChainModule chainModule) { + return s_hotVars.chainModule; + } + + /** + * @notice if this registry has reorg protection enabled + */ + function getReorgProtectionEnabled() external view returns (bool reorgProtectionEnabled) { + return s_hotVars.reorgProtectionEnabled; + } + + /** + * @notice calculates the minimum balance required for an upkeep to remain eligible + * @param id the upkeep id to calculate minimum balance for + */ + function getBalance(uint256 id) external view returns (uint96 balance) { + return s_upkeep[id].balance; + } + + /** + * @notice calculates the minimum balance required for an upkeep to remain eligible + * @param id the upkeep id to calculate minimum balance for + */ + function getMinBalance(uint256 id) external view returns (uint96) { + return getMinBalanceForUpkeep(id); + } + + /** + * @notice calculates the minimum balance required for an upkeep to remain eligible + * @param id the upkeep id to calculate minimum balance for + * @dev this will be deprecated in a future version in favor of getMinBalance + */ + function getMinBalanceForUpkeep(uint256 id) public view returns (uint96 minBalance) { + return getMaxPaymentForGas(_getTriggerType(id), s_upkeep[id].performGas); + } + + /** + * @notice calculates the maximum payment for a given gas limit + * @param gasLimit the gas to calculate payment for + */ + function getMaxPaymentForGas(Trigger triggerType, uint32 gasLimit) public view returns (uint96 maxPayment) { + HotVars memory hotVars = s_hotVars; + (uint256 fastGasWei, uint256 linkNative) = _getFeedData(hotVars); + return _getMaxLinkPayment(hotVars, triggerType, gasLimit, fastGasWei, linkNative); + } + + /** + * @notice retrieves the migration permission for a peer registry + */ + function getPeerRegistryMigrationPermission(address peer) external view returns (MigrationPermission) { + return s_peerRegistryMigrationPermission[peer]; + } + + /** + * @notice returns the upkeep privilege config + */ + function getUpkeepPrivilegeConfig(uint256 upkeepId) external view returns (bytes memory) { + return s_upkeepPrivilegeConfig[upkeepId]; + } + + /** + * @notice returns the upkeep privilege config + */ + function getAdminPrivilegeConfig(address admin) external view returns (bytes memory) { + return s_adminPrivilegeConfig[admin]; + } + + /** + * @notice returns the upkeep's forwarder contract + */ + function getForwarder(uint256 upkeepID) external view returns (IAutomationZKSyncForwarder) { + return s_upkeep[upkeepID].forwarder; + } + + /** + * @notice returns the upkeep's forwarder contract + */ + function hasDedupKey(bytes32 dedupKey) external view returns (bool) { + return s_dedupKeys[dedupKey]; + } +} diff --git a/contracts/src/v0.8/automation/v2_2_zksync/ZKSyncAutomationRegistry2_2.sol b/contracts/src/v0.8/automation/v2_2_zksync/ZKSyncAutomationRegistry2_2.sol new file mode 100644 index 00000000000..cffed10da80 --- /dev/null +++ b/contracts/src/v0.8/automation/v2_2_zksync/ZKSyncAutomationRegistry2_2.sol @@ -0,0 +1,425 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.19; + +import {EnumerableSet} from "../../vendor/openzeppelin-solidity/v4.7.3/contracts/utils/structs/EnumerableSet.sol"; +import {Address} from "../../vendor/openzeppelin-solidity/v4.7.3/contracts/utils/Address.sol"; +import {ZKSyncAutomationRegistryBase2_2} from "./ZKSyncAutomationRegistryBase2_2.sol"; +import {AutomationRegistryLogicB2_2} from "./AutomationRegistryLogicB2_2.sol"; +import {Chainable} from "../Chainable.sol"; +import {IERC677Receiver} from "../../shared/interfaces/IERC677Receiver.sol"; +import {OCR2Abstract} from "../../shared/ocr2/OCR2Abstract.sol"; + +/** + * @notice Registry for adding work for Chainlink nodes to perform on client + * contracts. Clients must support the AutomationCompatibleInterface interface. + */ +contract ZKSyncAutomationRegistry2_2 is ZKSyncAutomationRegistryBase2_2, OCR2Abstract, Chainable, IERC677Receiver { + using Address for address; + using EnumerableSet for EnumerableSet.UintSet; + using EnumerableSet for EnumerableSet.AddressSet; + + /** + * @notice versions: + * 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 + * fixes issue with arbitrum block number + * does an early return in case of stale report instead of revert + * KeeperRegistry 2.0.1: implements workaround for buggy migrate function in 1.X + * KeeperRegistry 2.0.0: implement OCR interface + * KeeperRegistry 1.3.0: split contract into Proxy and Logic + * account for Arbitrum and Optimism L1 gas fee + * allow users to configure upkeeps + * KeeperRegistry 1.2.0: allow funding within performUpkeep + * allow configurable registry maxPerformGas + * add function to let admin change upkeep gas limit + * add minUpkeepSpend requirement + * upgrade to solidity v0.8 + * KeeperRegistry 1.1.0: added flatFeeMicroLink + * KeeperRegistry 1.0.0: initial release + */ + string public constant override typeAndVersion = "ZKSyncAutomationRegistry 2.2.0"; + + /** + * @param logicA the address of the first logic contract, but cast as logicB in order to call logicB functions + */ + constructor( + AutomationRegistryLogicB2_2 logicA + ) + ZKSyncAutomationRegistryBase2_2( + logicA.getLinkAddress(), + logicA.getLinkNativeFeedAddress(), + logicA.getFastGasFeedAddress(), + logicA.getAutomationForwarderLogic(), + logicA.getAllowedReadOnlyAddress() + ) + Chainable(address(logicA)) + {} + + /** + * @notice holds the variables used in the transmit function, necessary to avoid stack too deep errors + */ + // solhint-disable-next-line gas-struct-packing + struct TransmitVars { + uint16 numUpkeepsPassedChecks; + uint256 totalCalldataWeight; + uint96 totalReimbursement; + uint96 totalPremium; + } + + // ================================================================ + // | ACTIONS | + // ================================================================ + + /** + * @inheritdoc OCR2Abstract + */ + function transmit( + bytes32[3] calldata reportContext, + bytes calldata rawReport, + bytes32[] calldata rs, + bytes32[] calldata ss, + bytes32 rawVs + ) external override { + uint256 gasOverhead = gasleft(); + HotVars memory hotVars = s_hotVars; + + if (hotVars.paused) revert RegistryPaused(); + if (!s_transmitters[msg.sender].active) revert OnlyActiveTransmitters(); + + // Verify signatures + if (s_latestConfigDigest != reportContext[0]) revert ConfigDigestMismatch(); + if (rs.length != hotVars.f + 1 || rs.length != ss.length) revert IncorrectNumberOfSignatures(); + _verifyReportSignature(reportContext, rawReport, rs, ss, rawVs); + + Report memory report = _decodeReport(rawReport); + + uint40 epochAndRound = uint40(uint256(reportContext[1])); + uint32 epoch = uint32(epochAndRound >> 8); + + _handleReport(hotVars, report, gasOverhead); + + if (epoch > hotVars.latestEpoch) { + s_hotVars.latestEpoch = epoch; + } + } + + function _handleReport(HotVars memory hotVars, Report memory report, uint256 gasOverhead) private { + UpkeepTransmitInfo[] memory upkeepTransmitInfo = new UpkeepTransmitInfo[](report.upkeepIds.length); + TransmitVars memory transmitVars = TransmitVars({ + numUpkeepsPassedChecks: 0, + totalCalldataWeight: 0, + totalReimbursement: 0, + totalPremium: 0 + }); + + uint256 blocknumber = hotVars.chainModule.blockNumber(); + uint256 l1Fee = hotVars.chainModule.getCurrentL1Fee(); + + for (uint256 i = 0; i < report.upkeepIds.length; i++) { + upkeepTransmitInfo[i].upkeep = s_upkeep[report.upkeepIds[i]]; + upkeepTransmitInfo[i].triggerType = _getTriggerType(report.upkeepIds[i]); + + (upkeepTransmitInfo[i].earlyChecksPassed, upkeepTransmitInfo[i].dedupID) = _prePerformChecks( + report.upkeepIds[i], + blocknumber, + report.triggers[i], + upkeepTransmitInfo[i], + hotVars + ); + + if (upkeepTransmitInfo[i].earlyChecksPassed) { + transmitVars.numUpkeepsPassedChecks += 1; + } else { + continue; + } + + // Actually perform the target upkeep + (upkeepTransmitInfo[i].performSuccess, upkeepTransmitInfo[i].gasUsed, upkeepTransmitInfo[i].l1GasUsed) = _performUpkeep( + upkeepTransmitInfo[i].upkeep.forwarder, + report.gasLimits[i], + report.performDatas[i] + ); + + // To split L1 fee across the upkeeps, assign a weight to this upkeep based on the length + // of the perform data and calldata overhead + upkeepTransmitInfo[i].calldataWeight = + report.performDatas[i].length + + TRANSMIT_CALLDATA_FIXED_BYTES_OVERHEAD + + (TRANSMIT_CALLDATA_PER_SIGNER_BYTES_OVERHEAD * (hotVars.f + 1)); + transmitVars.totalCalldataWeight += upkeepTransmitInfo[i].calldataWeight; + + // Deduct that gasUsed by upkeep from our running counter + // for zksync, the L1 gas is deducted at the end of a transaction but gasUsed here already has all the cost + // if we don't add l1GasUsed here for zksync, `gasOverhead - gasleft()` will underflow + gasOverhead -= upkeepTransmitInfo[i].gasUsed + upkeepTransmitInfo[i].l1GasUsed; + + // Store last perform block number / deduping key for upkeep + _updateTriggerMarker(report.upkeepIds[i], blocknumber, upkeepTransmitInfo[i]); + } + // No upkeeps to be performed in this report + if (transmitVars.numUpkeepsPassedChecks == 0) { + return; + } + + // This is the overall gas overhead that will be split across performed upkeeps + // Take upper bound of 16 gas per callData bytes + // for zksync, this place will underflow if we don't add back l1GasUsed + gasOverhead = (gasOverhead - gasleft()) + (16 * msg.data.length) + ACCOUNTING_FIXED_GAS_OVERHEAD; + gasOverhead = gasOverhead / transmitVars.numUpkeepsPassedChecks + ACCOUNTING_PER_UPKEEP_GAS_OVERHEAD; + + { + uint96 reimbursement; + uint96 premium; + for (uint256 i = 0; i < report.upkeepIds.length; i++) { + if (upkeepTransmitInfo[i].earlyChecksPassed) { + (reimbursement, premium) = _postPerformPayment( + hotVars, + report.upkeepIds[i], + upkeepTransmitInfo[i].gasUsed, + report.fastGasWei, + report.linkNative, + gasOverhead, + (l1Fee * upkeepTransmitInfo[i].calldataWeight) / transmitVars.totalCalldataWeight + ); + transmitVars.totalPremium += premium; + transmitVars.totalReimbursement += reimbursement; + + emit UpkeepPerformed( + report.upkeepIds[i], + upkeepTransmitInfo[i].performSuccess, + reimbursement + premium, + upkeepTransmitInfo[i].gasUsed, + gasOverhead, + report.triggers[i] + ); + + emit UpkeepPerformedDetails( + reimbursement + premium, + upkeepTransmitInfo[i].gasUsed, + gasOverhead + ); + } + } + } + // record payments + s_transmitters[msg.sender].balance += transmitVars.totalReimbursement; + s_hotVars.totalPremium += transmitVars.totalPremium; + } + + /** + * @notice simulates the upkeep with the perform data returned from checkUpkeep + * @param id identifier of the upkeep to execute the data with. + * @param performData calldata parameter to be passed to the target upkeep. + * @return success whether the call reverted or not + * @return gasUsed the amount of gas the target contract consumed + */ + function simulatePerformUpkeep( + uint256 id, + bytes calldata performData + ) external returns (bool success, uint256 gasUsed) { + _preventExecution(); + + if (s_hotVars.paused) revert RegistryPaused(); + Upkeep memory upkeep = s_upkeep[id]; + (success, gasUsed,) = _performUpkeep(upkeep.forwarder, upkeep.performGas, performData); + return (success, gasUsed); + } + + /** + * @notice uses LINK's transferAndCall to LINK and add funding to an upkeep + * @dev safe to cast uint256 to uint96 as total LINK supply is under UINT96MAX + * @param sender the account which transferred the funds + * @param amount number of LINK transfer + */ + function onTokenTransfer(address sender, uint256 amount, bytes calldata data) external override { + if (msg.sender != address(i_link)) revert OnlyCallableByLINKToken(); + if (data.length != 32) revert InvalidDataLength(); + uint256 id = abi.decode(data, (uint256)); + if (s_upkeep[id].maxValidBlocknumber != UINT32_MAX) revert UpkeepCancelled(); + s_upkeep[id].balance = s_upkeep[id].balance + uint96(amount); + s_expectedLinkBalance = s_expectedLinkBalance + amount; + emit FundsAdded(id, sender, uint96(amount)); + } + + // ================================================================ + // | SETTERS | + // ================================================================ + + /** + * @inheritdoc OCR2Abstract + * @dev prefer the type-safe version of setConfig (below) whenever possible. The OnchainConfig could differ between registry versions + */ + function setConfig( + address[] memory signers, + address[] memory transmitters, + uint8 f, + bytes memory onchainConfigBytes, + uint64 offchainConfigVersion, + bytes memory offchainConfig + ) external override { + setConfigTypeSafe( + signers, + transmitters, + f, + abi.decode(onchainConfigBytes, (OnchainConfig)), + offchainConfigVersion, + offchainConfig + ); + } + + function setConfigTypeSafe( + address[] memory signers, + address[] memory transmitters, + uint8 f, + OnchainConfig memory onchainConfig, + uint64 offchainConfigVersion, + bytes memory offchainConfig + ) public onlyOwner { + if (signers.length > MAX_NUM_ORACLES) revert TooManyOracles(); + if (f == 0) revert IncorrectNumberOfFaultyOracles(); + if (signers.length != transmitters.length || signers.length <= 3 * f) revert IncorrectNumberOfSigners(); + + // move all pooled payments out of the pool to each transmitter's balance + uint96 totalPremium = s_hotVars.totalPremium; + uint96 oldLength = uint96(s_transmittersList.length); + for (uint256 i = 0; i < oldLength; i++) { + _updateTransmitterBalanceFromPool(s_transmittersList[i], totalPremium, oldLength); + } + + // remove any old signer/transmitter addresses + address signerAddress; + address transmitterAddress; + for (uint256 i = 0; i < oldLength; i++) { + signerAddress = s_signersList[i]; + transmitterAddress = s_transmittersList[i]; + delete s_signers[signerAddress]; + // Do not delete the whole transmitter struct as it has balance information stored + s_transmitters[transmitterAddress].active = false; + } + delete s_signersList; + delete s_transmittersList; + + // add new signer/transmitter addresses + { + Transmitter memory transmitter; + address temp; + for (uint256 i = 0; i < signers.length; i++) { + if (s_signers[signers[i]].active) revert RepeatedSigner(); + if (signers[i] == ZERO_ADDRESS) revert InvalidSigner(); + s_signers[signers[i]] = Signer({active: true, index: uint8(i)}); + + temp = transmitters[i]; + if (temp == ZERO_ADDRESS) revert InvalidTransmitter(); + transmitter = s_transmitters[temp]; + if (transmitter.active) revert RepeatedTransmitter(); + transmitter.active = true; + transmitter.index = uint8(i); + // new transmitters start afresh from current totalPremium + // some spare change of premium from previous pool will be forfeited + transmitter.lastCollected = totalPremium; + s_transmitters[temp] = transmitter; + } + } + s_signersList = signers; + s_transmittersList = transmitters; + + s_hotVars = HotVars({ + f: f, + paymentPremiumPPB: onchainConfig.paymentPremiumPPB, + flatFeeMicroLink: onchainConfig.flatFeeMicroLink, + stalenessSeconds: onchainConfig.stalenessSeconds, + gasCeilingMultiplier: onchainConfig.gasCeilingMultiplier, + paused: s_hotVars.paused, + reentrancyGuard: s_hotVars.reentrancyGuard, + totalPremium: totalPremium, + latestEpoch: 0, // DON restarts epoch + reorgProtectionEnabled: onchainConfig.reorgProtectionEnabled, + chainModule: onchainConfig.chainModule + }); + + s_storage = Storage({ + checkGasLimit: onchainConfig.checkGasLimit, + minUpkeepSpend: onchainConfig.minUpkeepSpend, + maxPerformGas: onchainConfig.maxPerformGas, + transcoder: onchainConfig.transcoder, + maxCheckDataSize: onchainConfig.maxCheckDataSize, + maxPerformDataSize: onchainConfig.maxPerformDataSize, + maxRevertDataSize: onchainConfig.maxRevertDataSize, + upkeepPrivilegeManager: onchainConfig.upkeepPrivilegeManager, + nonce: s_storage.nonce, + configCount: s_storage.configCount, + latestConfigBlockNumber: s_storage.latestConfigBlockNumber, + ownerLinkBalance: s_storage.ownerLinkBalance + }); + s_fallbackGasPrice = onchainConfig.fallbackGasPrice; + s_fallbackLinkPrice = onchainConfig.fallbackLinkPrice; + + uint32 previousConfigBlockNumber = s_storage.latestConfigBlockNumber; + s_storage.latestConfigBlockNumber = uint32(onchainConfig.chainModule.blockNumber()); + s_storage.configCount += 1; + + bytes memory onchainConfigBytes = abi.encode(onchainConfig); + + s_latestConfigDigest = _configDigestFromConfigData( + block.chainid, + address(this), + s_storage.configCount, + signers, + transmitters, + f, + onchainConfigBytes, + offchainConfigVersion, + offchainConfig + ); + + for (uint256 idx = 0; idx < s_registrars.length(); idx++) { + s_registrars.remove(s_registrars.at(idx)); + } + + for (uint256 idx = 0; idx < onchainConfig.registrars.length; idx++) { + s_registrars.add(onchainConfig.registrars[idx]); + } + + emit ConfigSet( + previousConfigBlockNumber, + s_latestConfigDigest, + s_storage.configCount, + signers, + transmitters, + f, + onchainConfigBytes, + offchainConfigVersion, + offchainConfig + ); + } + + // ================================================================ + // | GETTERS | + // ================================================================ + + /** + * @inheritdoc OCR2Abstract + */ + function latestConfigDetails() + external + view + override + returns (uint32 configCount, uint32 blockNumber, bytes32 configDigest) + { + return (s_storage.configCount, s_storage.latestConfigBlockNumber, s_latestConfigDigest); + } + + /** + * @inheritdoc OCR2Abstract + */ + function latestConfigDigestAndEpoch() + external + view + override + returns (bool scanLogs, bytes32 configDigest, uint32 epoch) + { + return (false, s_latestConfigDigest, s_hotVars.latestEpoch); + } +} diff --git a/contracts/src/v0.8/automation/v2_2_zksync/ZKSyncAutomationRegistryBase2_2.sol b/contracts/src/v0.8/automation/v2_2_zksync/ZKSyncAutomationRegistryBase2_2.sol new file mode 100644 index 00000000000..0a689503a60 --- /dev/null +++ b/contracts/src/v0.8/automation/v2_2_zksync/ZKSyncAutomationRegistryBase2_2.sol @@ -0,0 +1,874 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.19; + +import {EnumerableSet} from "../../vendor/openzeppelin-solidity/v4.7.3/contracts/utils/structs/EnumerableSet.sol"; +import {Address} from "../../vendor/openzeppelin-solidity/v4.7.3/contracts/utils/Address.sol"; +import {StreamsLookupCompatibleInterface} from "../interfaces/StreamsLookupCompatibleInterface.sol"; +import {ILogAutomation, Log} from "../interfaces/ILogAutomation.sol"; +import {IAutomationZKSyncForwarder} from "../interfaces/IAutomationZKSyncForwarder.sol"; +import {ConfirmedOwner} from "../../shared/access/ConfirmedOwner.sol"; +import {AggregatorV3Interface} from "../../shared/interfaces/AggregatorV3Interface.sol"; +import {LinkTokenInterface} from "../../shared/interfaces/LinkTokenInterface.sol"; +import {KeeperCompatibleInterface} from "../interfaces/KeeperCompatibleInterface.sol"; +import {UpkeepFormat} from "../interfaces/UpkeepTranscoderInterface.sol"; +import {IChainModule} from "../interfaces/IChainModule.sol"; + +/** + * @notice Base Keeper Registry contract, contains shared logic between + * AutomationRegistry and AutomationRegistryLogic + * @dev all errors, events, and internal functions should live here + */ +// solhint-disable-next-line max-states-count +abstract contract ZKSyncAutomationRegistryBase2_2 is ConfirmedOwner { + using Address for address; + using EnumerableSet for EnumerableSet.UintSet; + using EnumerableSet for EnumerableSet.AddressSet; + + address internal constant ZERO_ADDRESS = address(0); + address internal constant IGNORE_ADDRESS = 0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF; + bytes4 internal constant CHECK_SELECTOR = KeeperCompatibleInterface.checkUpkeep.selector; + bytes4 internal constant PERFORM_SELECTOR = KeeperCompatibleInterface.performUpkeep.selector; + bytes4 internal constant CHECK_CALLBACK_SELECTOR = StreamsLookupCompatibleInterface.checkCallback.selector; + bytes4 internal constant CHECK_LOG_SELECTOR = ILogAutomation.checkLog.selector; + uint256 internal constant PERFORM_GAS_MIN = 2_300; + uint256 internal constant CANCELLATION_DELAY = 50; + uint256 internal constant PERFORM_GAS_CUSHION = 5_000; + uint256 internal constant PPB_BASE = 1_000_000_000; + uint32 internal constant UINT32_MAX = type(uint32).max; + uint96 internal constant LINK_TOTAL_SUPPLY = 1e27; + // The first byte of the mask can be 0, because we only ever have 31 oracles + uint256 internal constant ORACLE_MASK = 0x0001010101010101010101010101010101010101010101010101010101010101; + /** + * @dev UPKEEP_TRANSCODER_VERSION_BASE is temporary necessity for backwards compatibility with + * MigratableAutomationRegistryInterfaceV1 - it should be removed in future versions in favor of + * UPKEEP_VERSION_BASE and MigratableAutomationRegistryInterfaceV2 + */ + UpkeepFormat internal constant UPKEEP_TRANSCODER_VERSION_BASE = UpkeepFormat.V1; + uint8 internal constant UPKEEP_VERSION_BASE = 3; + + // Next block of constants are only used in maxPayment estimation during checkUpkeep simulation + // These values are calibrated using hardhat tests which simulates various cases and verifies that + // the variables result in accurate estimation + uint256 internal constant REGISTRY_CONDITIONAL_OVERHEAD = 60_000; // Fixed gas overhead for conditional upkeeps + uint256 internal constant REGISTRY_LOG_OVERHEAD = 85_000; // Fixed gas overhead for log upkeeps + uint256 internal constant REGISTRY_PER_SIGNER_GAS_OVERHEAD = 5_600; // Value scales with f + uint256 internal constant REGISTRY_PER_PERFORM_BYTE_GAS_OVERHEAD = 24; // Per perform data byte overhead + + // The overhead (in bytes) in addition to perform data for upkeep sent in calldata + // This includes overhead for all struct encoding as well as report signatures + // There is a fixed component and a per signer component. This is calculated exactly by doing abi encoding + uint256 internal constant TRANSMIT_CALLDATA_FIXED_BYTES_OVERHEAD = 932; + uint256 internal constant TRANSMIT_CALLDATA_PER_SIGNER_BYTES_OVERHEAD = 64; + + // Next block of constants are used in actual payment calculation. We calculate the exact gas used within the + // tx itself, but since payment processing itself takes gas, and it needs the overhead as input, we use fixed constants + // to account for gas used in payment processing. These values are calibrated using hardhat tests which simulates various cases and verifies that + // the variables result in accurate estimation + uint256 internal constant ACCOUNTING_FIXED_GAS_OVERHEAD = 22_000; // Fixed overhead per tx + uint256 internal constant ACCOUNTING_PER_UPKEEP_GAS_OVERHEAD = 7_000; // Overhead per upkeep performed in batch + + LinkTokenInterface internal immutable i_link; + AggregatorV3Interface internal immutable i_linkNativeFeed; + AggregatorV3Interface internal immutable i_fastGasFeed; + address internal immutable i_automationForwarderLogic; + address internal immutable i_allowedReadOnlyAddress; + + /** + * @dev - The storage is gas optimised for one and only one function - transmit. All the storage accessed in transmit + * is stored compactly. Rest of the storage layout is not of much concern as transmit is the only hot path + */ + + // Upkeep storage + EnumerableSet.UintSet internal s_upkeepIDs; + mapping(uint256 => Upkeep) internal s_upkeep; // accessed during transmit + mapping(uint256 => address) internal s_upkeepAdmin; + mapping(uint256 => address) internal s_proposedAdmin; + mapping(uint256 => bytes) internal s_checkData; + mapping(bytes32 => bool) internal s_dedupKeys; + // Registry config and state + EnumerableSet.AddressSet internal s_registrars; + mapping(address => Transmitter) internal s_transmitters; + mapping(address => Signer) internal s_signers; + address[] internal s_signersList; // s_signersList contains the signing address of each oracle + address[] internal s_transmittersList; // s_transmittersList contains the transmission address of each oracle + mapping(address => address) internal s_transmitterPayees; // s_payees contains the mapping from transmitter to payee. + mapping(address => address) internal s_proposedPayee; // proposed payee for a transmitter + bytes32 internal s_latestConfigDigest; // Read on transmit path in case of signature verification + HotVars internal s_hotVars; // Mixture of config and state, used in transmit + Storage internal s_storage; // Mixture of config and state, not used in transmit + uint256 internal s_fallbackGasPrice; + uint256 internal s_fallbackLinkPrice; + uint256 internal s_expectedLinkBalance; // Used in case of erroneous LINK transfers to contract + mapping(address => MigrationPermission) internal s_peerRegistryMigrationPermission; // Permissions for migration to and fro + mapping(uint256 => bytes) internal s_upkeepTriggerConfig; // upkeep triggers + mapping(uint256 => bytes) internal s_upkeepOffchainConfig; // general config set by users for each upkeep + mapping(uint256 => bytes) internal s_upkeepPrivilegeConfig; // general config set by an administrative role for an upkeep + mapping(address => bytes) internal s_adminPrivilegeConfig; // general config set by an administrative role for an admin + + error ArrayHasNoEntries(); + error CannotCancel(); + error CheckDataExceedsLimit(); + error ConfigDigestMismatch(); + error DuplicateEntry(); + error DuplicateSigners(); + error GasLimitCanOnlyIncrease(); + error GasLimitOutsideRange(); + error IncorrectNumberOfFaultyOracles(); + error IncorrectNumberOfSignatures(); + error IncorrectNumberOfSigners(); + error IndexOutOfRange(); + error InvalidDataLength(); + error InvalidTrigger(); + error InvalidPayee(); + error InvalidRecipient(); + error InvalidReport(); + error InvalidSigner(); + error InvalidTransmitter(); + error InvalidTriggerType(); + error MaxCheckDataSizeCanOnlyIncrease(); + error MaxPerformDataSizeCanOnlyIncrease(); + error MigrationNotPermitted(); + error NotAContract(); + error OnlyActiveSigners(); + error OnlyActiveTransmitters(); + error OnlyCallableByAdmin(); + error OnlyCallableByLINKToken(); + error OnlyCallableByOwnerOrAdmin(); + error OnlyCallableByOwnerOrRegistrar(); + error OnlyCallableByPayee(); + error OnlyCallableByProposedAdmin(); + error OnlyCallableByProposedPayee(); + error OnlyCallableByUpkeepPrivilegeManager(); + error OnlyPausedUpkeep(); + error OnlySimulatedBackend(); + error OnlyUnpausedUpkeep(); + error ParameterLengthError(); + error PaymentGreaterThanAllLINK(); + error ReentrantCall(); + error RegistryPaused(); + error RepeatedSigner(); + error RepeatedTransmitter(); + error TargetCheckReverted(bytes reason); + error TooManyOracles(); + error TranscoderNotSet(); + error UpkeepAlreadyExists(); + error UpkeepCancelled(); + error UpkeepNotCanceled(); + error UpkeepNotNeeded(); + error ValueNotChanged(); + + enum MigrationPermission { + NONE, + OUTGOING, + INCOMING, + BIDIRECTIONAL + } + + enum Trigger { + CONDITION, + LOG + } + + enum UpkeepFailureReason { + NONE, + UPKEEP_CANCELLED, + UPKEEP_PAUSED, + TARGET_CHECK_REVERTED, + UPKEEP_NOT_NEEDED, + PERFORM_DATA_EXCEEDS_LIMIT, + INSUFFICIENT_BALANCE, + CALLBACK_REVERTED, + REVERT_DATA_EXCEEDS_LIMIT, + REGISTRY_PAUSED + } + + /** + * @notice OnchainConfig of the registry + * @dev used only in setConfig() + * @member paymentPremiumPPB payment premium rate oracles receive on top of + * being reimbursed for gas, measured in parts per billion + * @member flatFeeMicroLink flat fee paid to oracles for performing upkeeps, + * priced in MicroLink; can be used in conjunction with or independently of + * paymentPremiumPPB + * @member checkGasLimit gas limit when checking for upkeep + * @member stalenessSeconds number of seconds that is allowed for feed data to + * be stale before switching to the fallback pricing + * @member gasCeilingMultiplier multiplier to apply to the fast gas feed price + * when calculating the payment ceiling for keepers + * @member minUpkeepSpend minimum LINK that an upkeep must spend before cancelling + * @member maxPerformGas max performGas allowed for an upkeep on this registry + * @member maxCheckDataSize max length of checkData bytes + * @member maxPerformDataSize max length of performData bytes + * @member maxRevertDataSize max length of revertData bytes + * @member fallbackGasPrice gas price used if the gas price feed is stale + * @member fallbackLinkPrice LINK price used if the LINK price feed is stale + * @member transcoder address of the transcoder contract + * @member registrars addresses of the registrar contracts + * @member upkeepPrivilegeManager address which can set privilege for upkeeps + * @member reorgProtectionEnabled if this registry enables re-org protection checks + * @member chainModule the chain specific module + */ + // solhint-disable-next-line gas-struct-packing + struct OnchainConfig { + uint32 paymentPremiumPPB; + uint32 flatFeeMicroLink; // min 0.000001 LINK, max 4294 LINK + uint32 checkGasLimit; + uint24 stalenessSeconds; + uint16 gasCeilingMultiplier; + uint96 minUpkeepSpend; + uint32 maxPerformGas; + uint32 maxCheckDataSize; + uint32 maxPerformDataSize; + uint32 maxRevertDataSize; + uint256 fallbackGasPrice; + uint256 fallbackLinkPrice; + address transcoder; + address[] registrars; + address upkeepPrivilegeManager; + IChainModule chainModule; + bool reorgProtectionEnabled; + } + + /** + * @notice relevant state of an upkeep which is used in transmit function + * @member paused if this upkeep has been paused + * @member performGas the gas limit of upkeep execution + * @member maxValidBlocknumber until which block this upkeep is valid + * @member forwarder the forwarder contract to use for this upkeep + * @member amountSpent the amount this upkeep has spent + * @member balance the balance of this upkeep + * @member lastPerformedBlockNumber the last block number when this upkeep was performed + */ + struct Upkeep { + bool paused; + uint32 performGas; + uint32 maxValidBlocknumber; + IAutomationZKSyncForwarder forwarder; + // 0 bytes left in 1st EVM word - not written to in transmit + uint96 amountSpent; + uint96 balance; + uint32 lastPerformedBlockNumber; + // 2 bytes left in 2nd EVM word - written in transmit path + } + + /// @dev Config + State storage struct which is on hot transmit path + struct HotVars { + uint96 totalPremium; // ─────────╮ total historical payment to oracles for premium + uint32 paymentPremiumPPB; // │ premium percentage charged to user over tx cost + uint32 flatFeeMicroLink; // │ flat fee charged to user for every perform + uint32 latestEpoch; // │ latest epoch for which a report was transmitted + uint24 stalenessSeconds; // │ Staleness tolerance for feeds + uint16 gasCeilingMultiplier; // │ multiplier on top of fast gas feed for upper bound + uint8 f; // │ maximum number of faulty oracles + bool paused; // │ pause switch for all upkeeps in the registry + bool reentrancyGuard; // ────────╯ guard against reentrancy + bool reorgProtectionEnabled; // if this registry should enable re-org protection mechanism + IChainModule chainModule; // the interface of chain specific module + } + + /// @dev Config + State storage struct which is not on hot transmit path + struct Storage { + uint96 minUpkeepSpend; // Minimum amount an upkeep must spend + address transcoder; // Address of transcoder contract used in migrations + // 1 EVM word full + uint96 ownerLinkBalance; // Balance of owner, accumulates minUpkeepSpend in case it is not spent + uint32 checkGasLimit; // Gas limit allowed in checkUpkeep + uint32 maxPerformGas; // Max gas an upkeep can use on this registry + uint32 nonce; // Nonce for each upkeep created + uint32 configCount; // incremented each time a new config is posted, The count + // is incorporated into the config digest to prevent replay attacks. + uint32 latestConfigBlockNumber; // makes it easier for offchain systems to extract config from logs + // 2 EVM word full + uint32 maxCheckDataSize; // max length of checkData bytes + uint32 maxPerformDataSize; // max length of performData bytes + uint32 maxRevertDataSize; // max length of revertData bytes + address upkeepPrivilegeManager; // address which can set privilege for upkeeps + // 3 EVM word full + } + + /// @dev Report transmitted by OCR to transmit function + struct Report { + uint256 fastGasWei; + uint256 linkNative; + uint256[] upkeepIds; + uint256[] gasLimits; + bytes[] triggers; + bytes[] performDatas; + } + + /** + * @dev This struct is used to maintain run time information about an upkeep in transmit function + * @member upkeep the upkeep struct + * @member earlyChecksPassed whether the upkeep passed early checks before perform + * @member performSuccess whether the perform was successful + * @member triggerType the type of trigger + * @member gasUsed gasUsed by this upkeep in perform + * @member l1GasUsed gas used by L1 + * @member calldataWeight weight assigned to this upkeep for its contribution to calldata. It is used to split L1 fee + * @member dedupID unique ID used to dedup an upkeep/trigger combo + */ + struct UpkeepTransmitInfo { + Upkeep upkeep; + bool earlyChecksPassed; + bool performSuccess; + Trigger triggerType; + uint256 gasUsed; + uint256 l1GasUsed; // specially for zksync + uint256 calldataWeight; + bytes32 dedupID; + } + + struct Transmitter { + bool active; + uint8 index; // Index of oracle in s_signersList/s_transmittersList + uint96 balance; + uint96 lastCollected; + } + + struct Signer { + bool active; + // Index of oracle in s_signersList/s_transmittersList + uint8 index; + } + + /** + * @notice the trigger structure conditional trigger type + */ + struct ConditionalTrigger { + uint32 blockNum; + bytes32 blockHash; + } + + /** + * @notice the trigger structure of log upkeeps + * @dev NOTE that blockNum / blockHash describe the block used for the callback, + * not necessarily the block number that the log was emitted in!!!! + */ + struct LogTrigger { + bytes32 logBlockHash; + bytes32 txHash; + uint32 logIndex; + uint32 blockNum; + bytes32 blockHash; + } + + event AdminPrivilegeConfigSet(address indexed admin, bytes privilegeConfig); + event CancelledUpkeepReport(uint256 indexed id, bytes trigger); + event ChainSpecificModuleUpdated(address newModule); + event DedupKeyAdded(bytes32 indexed dedupKey); + event FundsAdded(uint256 indexed id, address indexed from, uint96 amount); + event FundsWithdrawn(uint256 indexed id, uint256 amount, address to); + event InsufficientFundsUpkeepReport(uint256 indexed id, bytes trigger); + event OwnerFundsWithdrawn(uint96 amount); + event Paused(address account); + event PayeesUpdated(address[] transmitters, address[] payees); + event PayeeshipTransferRequested(address indexed transmitter, address indexed from, address indexed to); + event PayeeshipTransferred(address indexed transmitter, address indexed from, address indexed to); + event PaymentWithdrawn(address indexed transmitter, uint256 indexed amount, address indexed to, address payee); + event ReorgedUpkeepReport(uint256 indexed id, bytes trigger); + event StaleUpkeepReport(uint256 indexed id, bytes trigger); + event UpkeepAdminTransferred(uint256 indexed id, address indexed from, address indexed to); + event UpkeepAdminTransferRequested(uint256 indexed id, address indexed from, address indexed to); + event UpkeepCanceled(uint256 indexed id, uint64 indexed atBlockHeight); + event UpkeepCheckDataSet(uint256 indexed id, bytes newCheckData); + event UpkeepGasLimitSet(uint256 indexed id, uint96 gasLimit); + event UpkeepMigrated(uint256 indexed id, uint256 remainingBalance, address destination); + event UpkeepOffchainConfigSet(uint256 indexed id, bytes offchainConfig); + event UpkeepPaused(uint256 indexed id); + event UpkeepPerformed( + uint256 indexed id, + bool indexed success, + uint96 totalPayment, + uint256 gasUsed, + uint256 gasOverhead, + bytes trigger + ); + event UpkeepPerformedDetails( + uint96 indexed totalPayment, + uint256 indexed gasUsed, + uint256 indexed gasOverhead + ); + +// 0x00000000000000000000000000000000000000000000000000257a2909289c77 => 10548890804198519 => 0.0105 LINK +// 0x0000000000000000000000000000000000000000000000000000000000000e8b => 3723 gas +// 0x000000000000000000000000000000000000000000000000000000000001412c => 82220 gas +// 0x0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000002beaa42db6cf5e34edd2e0c09b6c24262d5ad261e77818db987d0f0669367962415c34 +// 132681 - 82220 = 50461 + event UpkeepPrivilegeConfigSet(uint256 indexed id, bytes privilegeConfig); + event UpkeepReceived(uint256 indexed id, uint256 startingBalance, address importedFrom); + event UpkeepRegistered(uint256 indexed id, uint32 performGas, address admin); + event UpkeepTriggerConfigSet(uint256 indexed id, bytes triggerConfig); + event UpkeepUnpaused(uint256 indexed id); + event Unpaused(address account); + + /** + * @param link address of the LINK Token + * @param linkNativeFeed address of the LINK/Native price feed + * @param fastGasFeed address of the Fast Gas price feed + * @param automationForwarderLogic the address of automation forwarder logic + * @param allowedReadOnlyAddress the address of the allowed read only address + */ + constructor( + address link, + address linkNativeFeed, + address fastGasFeed, + address automationForwarderLogic, + address allowedReadOnlyAddress + ) ConfirmedOwner(msg.sender) { + i_link = LinkTokenInterface(link); + i_linkNativeFeed = AggregatorV3Interface(linkNativeFeed); + i_fastGasFeed = AggregatorV3Interface(fastGasFeed); + i_automationForwarderLogic = automationForwarderLogic; + i_allowedReadOnlyAddress = allowedReadOnlyAddress; + } + + // ================================================================ + // | INTERNAL FUNCTIONS ONLY | + // ================================================================ + + /** + * @dev creates a new upkeep with the given fields + * @param id the id of the upkeep + * @param upkeep the upkeep to create + * @param admin address to cancel upkeep and withdraw remaining funds + * @param checkData data which is passed to user's checkUpkeep + * @param triggerConfig the trigger config for this upkeep + * @param offchainConfig the off-chain config of this upkeep + */ + function _createUpkeep( + uint256 id, + Upkeep memory upkeep, + address admin, + bytes memory checkData, + bytes memory triggerConfig, + bytes memory offchainConfig + ) internal { + if (s_hotVars.paused) revert RegistryPaused(); + if (checkData.length > s_storage.maxCheckDataSize) revert CheckDataExceedsLimit(); + if (upkeep.performGas < PERFORM_GAS_MIN || upkeep.performGas > s_storage.maxPerformGas) + revert GasLimitOutsideRange(); + if (address(s_upkeep[id].forwarder) != address(0)) revert UpkeepAlreadyExists(); + s_upkeep[id] = upkeep; + s_upkeepAdmin[id] = admin; + s_checkData[id] = checkData; + s_expectedLinkBalance = s_expectedLinkBalance + upkeep.balance; + s_upkeepTriggerConfig[id] = triggerConfig; + s_upkeepOffchainConfig[id] = offchainConfig; + s_upkeepIDs.add(id); + } + + /** + * @dev creates an ID for the upkeep based on the upkeep's type + * @dev the format of the ID looks like this: + * ****00000000000X**************** + * 4 bytes of entropy + * 11 bytes of zeros + * 1 identifying byte for the trigger type + * 16 bytes of entropy + * @dev this maintains the same level of entropy as eth addresses, so IDs will still be unique + * @dev we add the "identifying" part in the middle so that it is mostly hidden from users who usually only + * see the first 4 and last 4 hex values ex 0x1234...ABCD + */ + function _createID(Trigger triggerType) internal view returns (uint256) { + bytes1 empty; + IChainModule chainModule = s_hotVars.chainModule; + bytes memory idBytes = abi.encodePacked( + keccak256(abi.encode(chainModule.blockHash((chainModule.blockNumber() - 1)), address(this), s_storage.nonce)) + ); + for (uint256 idx = 4; idx < 15; idx++) { + idBytes[idx] = empty; + } + idBytes[15] = bytes1(uint8(triggerType)); + return uint256(bytes32(idBytes)); + } + + /** + * @dev retrieves feed data for fast gas/native and link/native prices. if the feed + * data is stale it uses the configured fallback price. Once a price is picked + * for gas it takes the min of gas price in the transaction or the fast gas + * price in order to reduce costs for the upkeep clients. + */ + function _getFeedData(HotVars memory hotVars) internal view returns (uint256 gasWei, uint256 linkNative) { + uint32 stalenessSeconds = hotVars.stalenessSeconds; + bool staleFallback = stalenessSeconds > 0; + uint256 timestamp; + int256 feedValue; + (, feedValue, , timestamp, ) = i_fastGasFeed.latestRoundData(); + if ( + feedValue <= 0 || block.timestamp < timestamp || (staleFallback && stalenessSeconds < block.timestamp - timestamp) + ) { + gasWei = s_fallbackGasPrice; + } else { + gasWei = uint256(feedValue); + } + (, feedValue, , timestamp, ) = i_linkNativeFeed.latestRoundData(); + if ( + feedValue <= 0 || block.timestamp < timestamp || (staleFallback && stalenessSeconds < block.timestamp - timestamp) + ) { + linkNative = s_fallbackLinkPrice; + } else { + linkNative = uint256(feedValue); + } + return (gasWei, linkNative); + } + + /** + * @dev calculates LINK paid for gas spent plus a configure premium percentage + * @param gasLimit the amount of gas used + * @param gasOverhead the amount of gas overhead + * @param l1CostWei the amount to be charged for L1 fee in wei + * @param fastGasWei the fast gas price + * @param linkNative the exchange ratio between LINK and Native token + * @param isExecution if this is triggered by a perform upkeep function + */ + function _calculatePaymentAmount( + HotVars memory hotVars, + uint256 gasLimit, + uint256 gasOverhead, + uint256 l1CostWei, + uint256 fastGasWei, + uint256 linkNative, + bool isExecution + ) internal view returns (uint96, uint96) { + uint256 gasWei = fastGasWei * hotVars.gasCeilingMultiplier; + // in case it's actual execution use actual gas price, capped by fastGasWei * gasCeilingMultiplier + if (isExecution && tx.gasprice < gasWei) { + gasWei = tx.gasprice; + } + uint256 gasPayment = ((gasWei * (gasLimit + gasOverhead) + l1CostWei) * 1e18) / linkNative; + uint256 premium = (((gasWei * gasLimit) + l1CostWei) * 1e9 * hotVars.paymentPremiumPPB) / + linkNative + + uint256(hotVars.flatFeeMicroLink) * + 1e12; + // LINK_TOTAL_SUPPLY < UINT96_MAX + if (gasPayment + premium > LINK_TOTAL_SUPPLY) revert PaymentGreaterThanAllLINK(); + return (uint96(gasPayment), uint96(premium)); + } + + /** + * @dev calculates the max LINK payment for an upkeep. Called during checkUpkeep simulation and assumes + * maximum gas overhead, L1 fee + */ + function _getMaxLinkPayment( + HotVars memory hotVars, + Trigger triggerType, + uint32 performGas, + uint256 fastGasWei, + uint256 linkNative + ) internal view returns (uint96) { + uint256 maxGasOverhead; + if (triggerType == Trigger.CONDITION) { + maxGasOverhead = REGISTRY_CONDITIONAL_OVERHEAD; + } else if (triggerType == Trigger.LOG) { + maxGasOverhead = REGISTRY_LOG_OVERHEAD; + } else { + revert InvalidTriggerType(); + } + uint256 maxCalldataSize = s_storage.maxPerformDataSize + + TRANSMIT_CALLDATA_FIXED_BYTES_OVERHEAD + + (TRANSMIT_CALLDATA_PER_SIGNER_BYTES_OVERHEAD * (hotVars.f + 1)); + (uint256 chainModuleFixedOverhead, uint256 chainModulePerByteOverhead) = s_hotVars.chainModule.getGasOverhead(); + maxGasOverhead += + (REGISTRY_PER_SIGNER_GAS_OVERHEAD * (hotVars.f + 1)) + + ((REGISTRY_PER_PERFORM_BYTE_GAS_OVERHEAD + chainModulePerByteOverhead) * maxCalldataSize) + + chainModuleFixedOverhead; + + uint256 maxL1Fee = hotVars.gasCeilingMultiplier * hotVars.chainModule.getMaxL1Fee(maxCalldataSize); + + (uint96 reimbursement, uint96 premium) = _calculatePaymentAmount( + hotVars, + performGas, + maxGasOverhead, + maxL1Fee, + fastGasWei, + linkNative, + false //isExecution + ); + + return reimbursement + premium; + } + + /** + * @dev move a transmitter's balance from total pool to withdrawable balance + */ + function _updateTransmitterBalanceFromPool( + address transmitterAddress, + uint96 totalPremium, + uint96 payeeCount + ) internal returns (uint96) { + Transmitter memory transmitter = s_transmitters[transmitterAddress]; + + if (transmitter.active) { + uint96 uncollected = totalPremium - transmitter.lastCollected; + uint96 due = uncollected / payeeCount; + transmitter.balance += due; + transmitter.lastCollected += due * payeeCount; + s_transmitters[transmitterAddress] = transmitter; + } + + return transmitter.balance; + } + + /** + * @dev gets the trigger type from an upkeepID (trigger type is encoded in the middle of the ID) + */ + function _getTriggerType(uint256 upkeepId) internal pure returns (Trigger) { + bytes32 rawID = bytes32(upkeepId); + bytes1 empty = bytes1(0); + for (uint256 idx = 4; idx < 15; idx++) { + if (rawID[idx] != empty) { + // old IDs that were created before this standard and migrated to this registry + return Trigger.CONDITION; + } + } + return Trigger(uint8(rawID[15])); + } + + function _checkPayload( + uint256 upkeepId, + Trigger triggerType, + bytes memory triggerData + ) internal view returns (bytes memory) { + if (triggerType == Trigger.CONDITION) { + return abi.encodeWithSelector(CHECK_SELECTOR, s_checkData[upkeepId]); + } else if (triggerType == Trigger.LOG) { + Log memory log = abi.decode(triggerData, (Log)); + return abi.encodeWithSelector(CHECK_LOG_SELECTOR, log, s_checkData[upkeepId]); + } + revert InvalidTriggerType(); + } + + /** + * @dev _decodeReport decodes a serialized report into a Report struct + */ + function _decodeReport(bytes calldata rawReport) internal pure returns (Report memory) { + Report memory report = abi.decode(rawReport, (Report)); + uint256 expectedLength = report.upkeepIds.length; + if ( + report.gasLimits.length != expectedLength || + report.triggers.length != expectedLength || + report.performDatas.length != expectedLength + ) { + revert InvalidReport(); + } + return report; + } + + /** + * @dev Does some early sanity checks before actually performing an upkeep + * @return bool whether the upkeep should be performed + * @return bytes32 dedupID for preventing duplicate performances of this trigger + */ + function _prePerformChecks( + uint256 upkeepId, + uint256 blocknumber, + bytes memory rawTrigger, + UpkeepTransmitInfo memory transmitInfo, + HotVars memory hotVars + ) internal returns (bool, bytes32) { + bytes32 dedupID; + if (transmitInfo.triggerType == Trigger.CONDITION) { + if (!_validateConditionalTrigger(upkeepId, blocknumber, rawTrigger, transmitInfo, hotVars)) + return (false, dedupID); + } else if (transmitInfo.triggerType == Trigger.LOG) { + bool valid; + (valid, dedupID) = _validateLogTrigger(upkeepId, blocknumber, rawTrigger, hotVars); + if (!valid) return (false, dedupID); + } else { + revert InvalidTriggerType(); + } + if (transmitInfo.upkeep.maxValidBlocknumber <= blocknumber) { + // Can happen when an upkeep got cancelled after report was generated. + // However we have a CANCELLATION_DELAY of 50 blocks so shouldn't happen in practice + emit CancelledUpkeepReport(upkeepId, rawTrigger); + return (false, dedupID); + } + return (true, dedupID); + } + + /** + * @dev Does some early sanity checks before actually performing an upkeep + */ + function _validateConditionalTrigger( + uint256 upkeepId, + uint256 blocknumber, + bytes memory rawTrigger, + UpkeepTransmitInfo memory transmitInfo, + HotVars memory hotVars + ) internal returns (bool) { + ConditionalTrigger memory trigger = abi.decode(rawTrigger, (ConditionalTrigger)); + if (trigger.blockNum < transmitInfo.upkeep.lastPerformedBlockNumber) { + // Can happen when another report performed this upkeep after this report was generated + emit StaleUpkeepReport(upkeepId, rawTrigger); + return false; + } + if ( + (hotVars.reorgProtectionEnabled && + (trigger.blockHash != bytes32("") && hotVars.chainModule.blockHash(trigger.blockNum) != trigger.blockHash)) || + trigger.blockNum >= blocknumber + ) { + // There are two cases of reorged report + // 1. trigger block number is in future: this is an edge case during extreme deep reorgs of chain + // which is always protected against + // 2. blockHash at trigger block number was same as trigger time. This is an optional check which is + // applied if DON sends non empty trigger.blockHash. Note: It only works for last 256 blocks on chain + // when it is sent + emit ReorgedUpkeepReport(upkeepId, rawTrigger); + return false; + } + return true; + } + + function _validateLogTrigger( + uint256 upkeepId, + uint256 blocknumber, + bytes memory rawTrigger, + HotVars memory hotVars + ) internal returns (bool, bytes32) { + LogTrigger memory trigger = abi.decode(rawTrigger, (LogTrigger)); + bytes32 dedupID = keccak256(abi.encodePacked(upkeepId, trigger.logBlockHash, trigger.txHash, trigger.logIndex)); + if ( + (hotVars.reorgProtectionEnabled && + (trigger.blockHash != bytes32("") && hotVars.chainModule.blockHash(trigger.blockNum) != trigger.blockHash)) || + trigger.blockNum >= blocknumber + ) { + // Reorg protection is same as conditional trigger upkeeps + emit ReorgedUpkeepReport(upkeepId, rawTrigger); + return (false, dedupID); + } + if (s_dedupKeys[dedupID]) { + emit StaleUpkeepReport(upkeepId, rawTrigger); + return (false, dedupID); + } + return (true, dedupID); + } + + /** + * @dev Verify signatures attached to report + */ + function _verifyReportSignature( + bytes32[3] calldata reportContext, + bytes calldata report, + bytes32[] calldata rs, + bytes32[] calldata ss, + bytes32 rawVs + ) internal view { + bytes32 h = keccak256(abi.encode(keccak256(report), reportContext)); + // i-th byte counts number of sigs made by i-th signer + uint256 signedCount = 0; + + Signer memory signer; + address signerAddress; + for (uint256 i = 0; i < rs.length; i++) { + signerAddress = ecrecover(h, uint8(rawVs[i]) + 27, rs[i], ss[i]); + signer = s_signers[signerAddress]; + if (!signer.active) revert OnlyActiveSigners(); + unchecked { + signedCount += 1 << (8 * signer.index); + } + } + + if (signedCount & ORACLE_MASK != signedCount) revert DuplicateSigners(); + } + + /** + * @dev updates a storage marker for this upkeep to prevent duplicate and out of order performances + * @dev for conditional triggers we set the latest block number, for log triggers we store a dedupID + */ + function _updateTriggerMarker( + uint256 upkeepID, + uint256 blocknumber, + UpkeepTransmitInfo memory upkeepTransmitInfo + ) internal { + if (upkeepTransmitInfo.triggerType == Trigger.CONDITION) { + s_upkeep[upkeepID].lastPerformedBlockNumber = uint32(blocknumber); + } else if (upkeepTransmitInfo.triggerType == Trigger.LOG) { + s_dedupKeys[upkeepTransmitInfo.dedupID] = true; + emit DedupKeyAdded(upkeepTransmitInfo.dedupID); + } + } + + /** + * @dev calls the Upkeep target with the performData param passed in by the + * transmitter and the exact gas required by the Upkeep + */ + function _performUpkeep( + IAutomationZKSyncForwarder forwarder, + uint256 performGas, + bytes memory performData + ) internal nonReentrant returns (bool success, uint256 gasUsed, uint256 l1GasUsed) { + performData = abi.encodeWithSelector(PERFORM_SELECTOR, performData); + return forwarder.forward(performGas, performData); + } + + /** + * @dev does postPerform payment processing for an upkeep. Deducts upkeep's balance and increases + * amount spent. + */ + function _postPerformPayment( + HotVars memory hotVars, + uint256 upkeepId, + uint256 gasUsed, + uint256 fastGasWei, + uint256 linkNative, + uint256 gasOverhead, + uint256 l1Fee + ) internal returns (uint96 gasReimbursement, uint96 premium) { + (gasReimbursement, premium) = _calculatePaymentAmount( + hotVars, + gasUsed, + gasOverhead, + l1Fee, + fastGasWei, + linkNative, + true // isExecution + ); + + uint96 balance = s_upkeep[upkeepId].balance; + uint96 payment = gasReimbursement + premium; + + // this shouldn't happen, but in rare edge cases, we charge the full balance in case the user + // can't cover the amount owed + if (balance < gasReimbursement) { + payment = balance; + gasReimbursement = balance; + premium = 0; + } else if (balance < payment) { + payment = balance; + premium = payment - gasReimbursement; + } + + s_upkeep[upkeepId].balance -= payment; + s_upkeep[upkeepId].amountSpent += payment; + + return (gasReimbursement, premium); + } + + /** + * @dev ensures the upkeep is not cancelled and the caller is the upkeep admin + */ + function _requireAdminAndNotCancelled(uint256 upkeepId) internal view { + if (msg.sender != s_upkeepAdmin[upkeepId]) revert OnlyCallableByAdmin(); + if (s_upkeep[upkeepId].maxValidBlocknumber != UINT32_MAX) revert UpkeepCancelled(); + } + + /** + * @dev replicates Open Zeppelin's ReentrancyGuard but optimized to fit our storage + */ + modifier nonReentrant() { + if (s_hotVars.reentrancyGuard) revert ReentrantCall(); + s_hotVars.reentrancyGuard = true; + _; + s_hotVars.reentrancyGuard = false; + } + + /** + * @notice only allows a pre-configured address to initiate offchain read + */ + function _preventExecution() internal view { + // solhint-disable-next-line avoid-tx-origin + if (tx.origin != i_allowedReadOnlyAddress) { + revert OnlySimulatedBackend(); + } + } +} From 035324f12514de81d4ca5c9448a54a56b6567ee5 Mon Sep 17 00:00:00 2001 From: FelixFan1992 Date: Thu, 20 Jun 2024 16:40:43 -0400 Subject: [PATCH 04/14] update --- .../automation/v2_2_zksync/ZKSyncAutomationRegistry2_2.sol | 4 ++-- ...yLogicA2_2.sol => ZKSyncAutomationRegistryLogicA2_2.sol} | 6 +++--- ...yLogicB2_2.sol => ZKSyncAutomationRegistryLogicB2_2.sol} | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) rename contracts/src/v0.8/automation/v2_2_zksync/{AutomationRegistryLogicA2_2.sol => ZKSyncAutomationRegistryLogicA2_2.sol} (98%) rename contracts/src/v0.8/automation/v2_2_zksync/{AutomationRegistryLogicB2_2.sol => ZKSyncAutomationRegistryLogicB2_2.sol} (99%) diff --git a/contracts/src/v0.8/automation/v2_2_zksync/ZKSyncAutomationRegistry2_2.sol b/contracts/src/v0.8/automation/v2_2_zksync/ZKSyncAutomationRegistry2_2.sol index cffed10da80..d7b588f1cba 100644 --- a/contracts/src/v0.8/automation/v2_2_zksync/ZKSyncAutomationRegistry2_2.sol +++ b/contracts/src/v0.8/automation/v2_2_zksync/ZKSyncAutomationRegistry2_2.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.19; import {EnumerableSet} from "../../vendor/openzeppelin-solidity/v4.7.3/contracts/utils/structs/EnumerableSet.sol"; import {Address} from "../../vendor/openzeppelin-solidity/v4.7.3/contracts/utils/Address.sol"; import {ZKSyncAutomationRegistryBase2_2} from "./ZKSyncAutomationRegistryBase2_2.sol"; -import {AutomationRegistryLogicB2_2} from "./AutomationRegistryLogicB2_2.sol"; +import {ZKSyncAutomationRegistryLogicB2_2} from "./ZKSyncAutomationRegistryLogicB2_2.sol"; import {Chainable} from "../Chainable.sol"; import {IERC677Receiver} from "../../shared/interfaces/IERC677Receiver.sol"; import {OCR2Abstract} from "../../shared/ocr2/OCR2Abstract.sol"; @@ -45,7 +45,7 @@ contract ZKSyncAutomationRegistry2_2 is ZKSyncAutomationRegistryBase2_2, OCR2Abs * @param logicA the address of the first logic contract, but cast as logicB in order to call logicB functions */ constructor( - AutomationRegistryLogicB2_2 logicA + ZKSyncAutomationRegistryLogicB2_2 logicA ) ZKSyncAutomationRegistryBase2_2( logicA.getLinkAddress(), diff --git a/contracts/src/v0.8/automation/v2_2_zksync/AutomationRegistryLogicA2_2.sol b/contracts/src/v0.8/automation/v2_2_zksync/ZKSyncAutomationRegistryLogicA2_2.sol similarity index 98% rename from contracts/src/v0.8/automation/v2_2_zksync/AutomationRegistryLogicA2_2.sol rename to contracts/src/v0.8/automation/v2_2_zksync/ZKSyncAutomationRegistryLogicA2_2.sol index 5bd523ee085..2c3d957fb77 100644 --- a/contracts/src/v0.8/automation/v2_2_zksync/AutomationRegistryLogicA2_2.sol +++ b/contracts/src/v0.8/automation/v2_2_zksync/ZKSyncAutomationRegistryLogicA2_2.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.19; import {EnumerableSet} from "../../vendor/openzeppelin-solidity/v4.7.3/contracts/utils/structs/EnumerableSet.sol"; import {Address} from "../../vendor/openzeppelin-solidity/v4.7.3/contracts/utils/Address.sol"; import {ZKSyncAutomationRegistryBase2_2} from "./ZKSyncAutomationRegistryBase2_2.sol"; -import {AutomationRegistryLogicB2_2} from "./AutomationRegistryLogicB2_2.sol"; +import {ZKSyncAutomationRegistryLogicB2_2} from "./ZKSyncAutomationRegistryLogicB2_2.sol"; import {Chainable} from "../Chainable.sol"; import {AutomationZKSyncForwarder} from "../AutomationZKSyncForwarder.sol"; import {IAutomationZKSyncForwarder} from "../interfaces/IAutomationZKSyncForwarder.sol"; @@ -14,7 +14,7 @@ import {MigratableKeeperRegistryInterfaceV2} from "../interfaces/MigratableKeepe /** * @notice Logic contract, works in tandem with AutomationRegistry as a proxy */ -contract AutomationRegistryLogicA2_2 is ZKSyncAutomationRegistryBase2_2, Chainable { +contract ZKSyncAutomationRegistryLogicA2_2 is ZKSyncAutomationRegistryBase2_2, Chainable { using Address for address; using EnumerableSet for EnumerableSet.UintSet; using EnumerableSet for EnumerableSet.AddressSet; @@ -23,7 +23,7 @@ contract AutomationRegistryLogicA2_2 is ZKSyncAutomationRegistryBase2_2, Chainab * @param logicB the address of the second logic contract */ constructor( - AutomationRegistryLogicB2_2 logicB + ZKSyncAutomationRegistryLogicB2_2 logicB ) ZKSyncAutomationRegistryBase2_2( logicB.getLinkAddress(), diff --git a/contracts/src/v0.8/automation/v2_2_zksync/AutomationRegistryLogicB2_2.sol b/contracts/src/v0.8/automation/v2_2_zksync/ZKSyncAutomationRegistryLogicB2_2.sol similarity index 99% rename from contracts/src/v0.8/automation/v2_2_zksync/AutomationRegistryLogicB2_2.sol rename to contracts/src/v0.8/automation/v2_2_zksync/ZKSyncAutomationRegistryLogicB2_2.sol index 9aa44850513..cb910c8fcb4 100644 --- a/contracts/src/v0.8/automation/v2_2_zksync/AutomationRegistryLogicB2_2.sol +++ b/contracts/src/v0.8/automation/v2_2_zksync/ZKSyncAutomationRegistryLogicB2_2.sol @@ -9,7 +9,7 @@ import {IAutomationZKSyncForwarder} from "../interfaces/IAutomationZKSyncForward import {IChainModule} from "../interfaces/IChainModule.sol"; import {IAutomationV21PlusCommon} from "../interfaces/IAutomationV21PlusCommon.sol"; -contract AutomationRegistryLogicB2_2 is ZKSyncAutomationRegistryBase2_2 { +contract ZKSyncAutomationRegistryLogicB2_2 is ZKSyncAutomationRegistryBase2_2 { using Address for address; using EnumerableSet for EnumerableSet.UintSet; using EnumerableSet for EnumerableSet.AddressSet; From 1f7460798b7508ca58c2474fa98fd11ae986631e Mon Sep 17 00:00:00 2001 From: FelixFan1992 Date: Thu, 20 Jun 2024 17:53:04 -0400 Subject: [PATCH 05/14] update --- .../v0.8/automation/AutomationForwarder.sol | 20 ++----------------- .../automation/AutomationZKSyncForwarder.sol | 6 ++++++ .../automation/v2_2/AutomationRegistry2_2.sol | 7 ------- .../v2_2/AutomationRegistryBase2_2.sol | 11 ---------- .../ZKSyncAutomationRegistry2_2.sol | 2 +- 5 files changed, 9 insertions(+), 37 deletions(-) diff --git a/contracts/src/v0.8/automation/AutomationForwarder.sol b/contracts/src/v0.8/automation/AutomationForwarder.sol index c596655e948..58707e96276 100644 --- a/contracts/src/v0.8/automation/AutomationForwarder.sol +++ b/contracts/src/v0.8/automation/AutomationForwarder.sol @@ -5,13 +5,6 @@ import {IAutomationRegistryConsumer} from "./interfaces/IAutomationRegistryConsu uint256 constant PERFORM_GAS_CUSHION = 5_000; -interface ISystemContext { - function gasPerPubdataByte() external view returns (uint256 gasPerPubdataByte); - function getCurrentPubdataSpent() external view returns (uint256 currentPubdataSpent); -} - -ISystemContext constant SYSTEM_CONTEXT_CONTRACT = ISystemContext(address(0x800b)); - /** * @title AutomationForwarder is a relayer that sits between the registry and the customer's target contract * @dev The purpose of the forwarder is to give customers a consistent address to authorize against, @@ -27,8 +20,6 @@ contract AutomationForwarder { IAutomationRegistryConsumer private s_registry; - event GasDetails(uint256 indexed pubdataUsed, uint256 indexed gasPerPubdataByte, uint256 indexed executionGasUsed, uint256 p1, uint256 p2, uint256 g1, uint256 g2); - constructor(address target, address registry, address logic) { s_registry = IAutomationRegistryConsumer(registry); i_target = target; @@ -44,8 +35,7 @@ contract AutomationForwarder { function forward(uint256 gasAmount, bytes memory data) external returns (bool success, uint256 gasUsed) { if (msg.sender != address(s_registry)) revert(); address target = i_target; - uint256 g1 = gasleft(); - uint256 p1 = SYSTEM_CONTEXT_CONTRACT.getCurrentPubdataSpent(); + gasUsed = gasleft(); assembly { let g := gas() // Compute g -= PERFORM_GAS_CUSHION and check for underflow @@ -65,13 +55,7 @@ contract AutomationForwarder { // call with exact gas success := call(gasAmount, target, 0, add(data, 0x20), mload(data), 0, 0) } - uint256 p2 = SYSTEM_CONTEXT_CONTRACT.getCurrentPubdataSpent(); - // need to check for 0 - uint256 pubdataUsed = p2 - p1; - uint256 gasPerPubdataByte = SYSTEM_CONTEXT_CONTRACT.gasPerPubdataByte(); - uint256 g2 = gasleft(); - gasUsed = g1 - g2 + pubdataUsed * gasPerPubdataByte; - emit GasDetails(pubdataUsed, gasPerPubdataByte, g1 - g2, p1, p2, g1, g2); + gasUsed = gasUsed - gasleft(); return (success, gasUsed); } diff --git a/contracts/src/v0.8/automation/AutomationZKSyncForwarder.sol b/contracts/src/v0.8/automation/AutomationZKSyncForwarder.sol index ae61e9f6355..0080c3d17a1 100644 --- a/contracts/src/v0.8/automation/AutomationZKSyncForwarder.sol +++ b/contracts/src/v0.8/automation/AutomationZKSyncForwarder.sol @@ -79,6 +79,12 @@ contract AutomationZKSyncForwarder { return (success, gasUsed, pubdataUsed * gasPerPubdataByte); } +/* +0x0000000000000000000000000000000000000000000000000000000000000013 +0x0000000000000000000000000000000000000000000000000000000000000167 +0x00000000000000000000000000000000000000000000000000000000004ed2cb +0x00000000000000000000000000000000000000000000000000000000004d93f9 +*/ function getTarget() external view returns (address) { return i_target; } diff --git a/contracts/src/v0.8/automation/v2_2/AutomationRegistry2_2.sol b/contracts/src/v0.8/automation/v2_2/AutomationRegistry2_2.sol index 712f7ac3562..f0c703679ca 100644 --- a/contracts/src/v0.8/automation/v2_2/AutomationRegistry2_2.sol +++ b/contracts/src/v0.8/automation/v2_2/AutomationRegistry2_2.sol @@ -163,7 +163,6 @@ 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 - // this place will underflow gasOverhead = (gasOverhead - gasleft()) + (16 * msg.data.length) + ACCOUNTING_FIXED_GAS_OVERHEAD; gasOverhead = gasOverhead / transmitVars.numUpkeepsPassedChecks + ACCOUNTING_PER_UPKEEP_GAS_OVERHEAD; @@ -192,12 +191,6 @@ contract AutomationRegistry2_2 is AutomationRegistryBase2_2, OCR2Abstract, Chain gasOverhead, report.triggers[i] ); - - emit UpkeepPerformedDetails( - reimbursement + premium, - upkeepTransmitInfo[i].gasUsed, - gasOverhead - ); } } } diff --git a/contracts/src/v0.8/automation/v2_2/AutomationRegistryBase2_2.sol b/contracts/src/v0.8/automation/v2_2/AutomationRegistryBase2_2.sol index 8fcaa748bfc..6903f55212a 100644 --- a/contracts/src/v0.8/automation/v2_2/AutomationRegistryBase2_2.sol +++ b/contracts/src/v0.8/automation/v2_2/AutomationRegistryBase2_2.sol @@ -381,17 +381,6 @@ abstract contract AutomationRegistryBase2_2 is ConfirmedOwner { uint256 gasOverhead, bytes trigger ); - event UpkeepPerformedDetails( - uint96 indexed totalPayment, - uint256 indexed gasUsed, - uint256 indexed gasOverhead - ); - -// 0x00000000000000000000000000000000000000000000000000257a2909289c77 => 10548890804198519 => 0.0105 LINK -// 0x0000000000000000000000000000000000000000000000000000000000000e8b => 3723 gas -// 0x000000000000000000000000000000000000000000000000000000000001412c => 82220 gas -// 0x0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000002beaa42db6cf5e34edd2e0c09b6c24262d5ad261e77818db987d0f0669367962415c34 -// 132681 - 82220 = 50461 event UpkeepPrivilegeConfigSet(uint256 indexed id, bytes privilegeConfig); event UpkeepReceived(uint256 indexed id, uint256 startingBalance, address importedFrom); event UpkeepRegistered(uint256 indexed id, uint32 performGas, address admin); diff --git a/contracts/src/v0.8/automation/v2_2_zksync/ZKSyncAutomationRegistry2_2.sol b/contracts/src/v0.8/automation/v2_2_zksync/ZKSyncAutomationRegistry2_2.sol index d7b588f1cba..4c20601d4c5 100644 --- a/contracts/src/v0.8/automation/v2_2_zksync/ZKSyncAutomationRegistry2_2.sol +++ b/contracts/src/v0.8/automation/v2_2_zksync/ZKSyncAutomationRegistry2_2.sol @@ -153,7 +153,7 @@ contract ZKSyncAutomationRegistry2_2 is ZKSyncAutomationRegistryBase2_2, OCR2Abs // Deduct that gasUsed by upkeep from our running counter // for zksync, the L1 gas is deducted at the end of a transaction but gasUsed here already has all the cost // if we don't add l1GasUsed here for zksync, `gasOverhead - gasleft()` will underflow - gasOverhead -= upkeepTransmitInfo[i].gasUsed + upkeepTransmitInfo[i].l1GasUsed; + gasOverhead = gasOverhead - upkeepTransmitInfo[i].gasUsed + upkeepTransmitInfo[i].l1GasUsed; // Store last perform block number / deduping key for upkeep _updateTriggerMarker(report.upkeepIds[i], blocknumber, upkeepTransmitInfo[i]); From 3370a96c6a5b38cd1a78e9f4bb0822b2e4ed6050 Mon Sep 17 00:00:00 2001 From: "app-token-issuer-infra-releng[bot]" <120227048+app-token-issuer-infra-releng[bot]@users.noreply.github.com> Date: Thu, 20 Jun 2024 21:58:38 +0000 Subject: [PATCH 06/14] Update gethwrappers --- ...nerated-wrapper-dependency-versions-do-not-edit.txt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/core/gethwrappers/generation/generated-wrapper-dependency-versions-do-not-edit.txt b/core/gethwrappers/generation/generated-wrapper-dependency-versions-do-not-edit.txt index cd2aa12d775..e46f86ba591 100644 --- a/core/gethwrappers/generation/generated-wrapper-dependency-versions-do-not-edit.txt +++ b/core/gethwrappers/generation/generated-wrapper-dependency-versions-do-not-edit.txt @@ -9,12 +9,12 @@ automation_consumer_benchmark: ../../contracts/solc/v0.8.16/AutomationConsumerBe automation_forwarder_logic: ../../contracts/solc/v0.8.16/AutomationForwarderLogic/AutomationForwarderLogic.abi ../../contracts/solc/v0.8.16/AutomationForwarderLogic/AutomationForwarderLogic.bin 15ae0c367297955fdab4b552dbb10e1f2be80a8fde0efec4a4d398693e9d72b5 automation_registrar_wrapper2_1: ../../contracts/solc/v0.8.16/AutomationRegistrar2_1/AutomationRegistrar2_1.abi ../../contracts/solc/v0.8.16/AutomationRegistrar2_1/AutomationRegistrar2_1.bin eb06d853aab39d3196c593b03e555851cbe8386e0fe54a74c2479f62d14b3c42 automation_registrar_wrapper2_3: ../../contracts/solc/v0.8.19/AutomationRegistrar2_3/AutomationRegistrar2_3.abi ../../contracts/solc/v0.8.19/AutomationRegistrar2_3/AutomationRegistrar2_3.bin 41f4b045cb783a8ad6e786b54216cc3666101abe75851784252e71aae3b00a99 -automation_registry_logic_a_wrapper_2_2: ../../contracts/solc/v0.8.19/AutomationRegistryLogicA2_2/AutomationRegistryLogicA2_2.abi ../../contracts/solc/v0.8.19/AutomationRegistryLogicA2_2/AutomationRegistryLogicA2_2.bin 5f3bfb68076db800219d00b37e16f1bfc0b391b0e6255a34d5846791d81333c4 -automation_registry_logic_a_wrapper_2_3: ../../contracts/solc/v0.8.19/AutomationRegistryLogicA2_3/AutomationRegistryLogicA2_3.abi ../../contracts/solc/v0.8.19/AutomationRegistryLogicA2_3/AutomationRegistryLogicA2_3.bin 5c23da8d3773a8cbd48cd7639e14e0c82b3395edacd3f610e019f6451db679f9 -automation_registry_logic_b_wrapper_2_2: ../../contracts/solc/v0.8.19/AutomationRegistryLogicB2_2/AutomationRegistryLogicB2_2.abi ../../contracts/solc/v0.8.19/AutomationRegistryLogicB2_2/AutomationRegistryLogicB2_2.bin 8024d943c8c2d380687a17e8816afdbba8c84ba1e27a5c80639e2df8fadad64d +automation_registry_logic_a_wrapper_2_2: ../../contracts/solc/v0.8.19/AutomationRegistryLogicA2_2/AutomationRegistryLogicA2_2.abi ../../contracts/solc/v0.8.19/AutomationRegistryLogicA2_2/AutomationRegistryLogicA2_2.bin 2f267fb8467a15c587ce4586ac56069f7229344ad3936430d7c7624c0528a171 +automation_registry_logic_a_wrapper_2_3: ../../contracts/solc/v0.8.19/AutomationRegistryLogicA2_3/AutomationRegistryLogicA2_3.abi ../../contracts/solc/v0.8.19/AutomationRegistryLogicA2_3/AutomationRegistryLogicA2_3.bin 73b5cc3ece642abbf6f2a4c9188335b71404f4dd0ad10b761390b6397af6f1c8 +automation_registry_logic_b_wrapper_2_2: ../../contracts/solc/v0.8.19/AutomationRegistryLogicB2_2/AutomationRegistryLogicB2_2.abi ../../contracts/solc/v0.8.19/AutomationRegistryLogicB2_2/AutomationRegistryLogicB2_2.bin a6d33dfbbfb0ff253eb59a51f4f6d6d4c22ea5ec95aae52d25d49a312b37a22f automation_registry_logic_b_wrapper_2_3: ../../contracts/solc/v0.8.19/AutomationRegistryLogicB2_3/AutomationRegistryLogicB2_3.abi ../../contracts/solc/v0.8.19/AutomationRegistryLogicB2_3/AutomationRegistryLogicB2_3.bin fbf6f6cf4e6858855ff5da847c3baa4859dd997cfae51f2fa0651e4fa15b92c9 automation_registry_logic_c_wrapper_2_3: ../../contracts/solc/v0.8.19/AutomationRegistryLogicC2_3/AutomationRegistryLogicC2_3.abi ../../contracts/solc/v0.8.19/AutomationRegistryLogicC2_3/AutomationRegistryLogicC2_3.bin 6bfe0f54fa7a587a83b6981ffdef28b3cb5e24cae1c95becdf59eed21147d289 -automation_registry_wrapper_2_2: ../../contracts/solc/v0.8.19/AutomationRegistry2_2/AutomationRegistry2_2.abi ../../contracts/solc/v0.8.19/AutomationRegistry2_2/AutomationRegistry2_2.bin 4c296e1d2857f63f30e735ee67c4297f45ba82c3d2406f8c2f1b0dd2293ef11a +automation_registry_wrapper_2_2: ../../contracts/solc/v0.8.19/AutomationRegistry2_2/AutomationRegistry2_2.abi ../../contracts/solc/v0.8.19/AutomationRegistry2_2/AutomationRegistry2_2.bin de60f69878e9b32a291a001c91fc8636544c2cfbd9b507c8c1a4873b602bfb62 automation_registry_wrapper_2_3: ../../contracts/solc/v0.8.19/AutomationRegistry2_3/AutomationRegistry2_3.abi ../../contracts/solc/v0.8.19/AutomationRegistry2_3/AutomationRegistry2_3.bin f8f920a225fdb1e36948dd95bae3aa46ecc2b01fd113480e111960b5e5f95624 automation_utils_2_1: ../../contracts/solc/v0.8.16/AutomationUtils2_1/AutomationUtils2_1.abi ../../contracts/solc/v0.8.16/AutomationUtils2_1/AutomationUtils2_1.bin 815b17b63f15d26a0274b962eefad98cdee4ec897ead58688bbb8e2470e585f5 automation_utils_2_2: ../../contracts/solc/v0.8.19/AutomationUtils2_2/AutomationUtils2_2.abi ../../contracts/solc/v0.8.19/AutomationUtils2_2/AutomationUtils2_2.bin 8743f6231aaefa3f2a0b2d484258070d506e2d0860690e66890dccc3949edb2e @@ -44,7 +44,7 @@ keeper_registrar_wrapper1_2_mock: ../../contracts/solc/v0.8.6/KeeperRegistrar1_2 keeper_registrar_wrapper2_0: ../../contracts/solc/v0.8.6/KeeperRegistrar2_0/KeeperRegistrar2_0.abi ../../contracts/solc/v0.8.6/KeeperRegistrar2_0/KeeperRegistrar2_0.bin 647f125c2f0dafabcdc545cb77b15dc2ec3ea9429357806813179b1fd555c2d2 keeper_registry_logic1_3: ../../contracts/solc/v0.8.6/KeeperRegistryLogic1_3/KeeperRegistryLogic1_3.abi ../../contracts/solc/v0.8.6/KeeperRegistryLogic1_3/KeeperRegistryLogic1_3.bin 903f8b9c8e25425ca6d0b81b89e339d695a83630bfbfa24a6f3b38869676bc5a keeper_registry_logic2_0: ../../contracts/solc/v0.8.6/KeeperRegistryLogic2_0/KeeperRegistryLogic2_0.abi ../../contracts/solc/v0.8.6/KeeperRegistryLogic2_0/KeeperRegistryLogic2_0.bin d69d2bc8e4844293dbc2d45abcddc50b84c88554ecccfa4fa77c0ca45ec80871 -keeper_registry_logic_a_wrapper_2_1: ../../contracts/solc/v0.8.16/KeeperRegistryLogicA2_1/KeeperRegistryLogicA2_1.abi ../../contracts/solc/v0.8.16/KeeperRegistryLogicA2_1/KeeperRegistryLogicA2_1.bin bb0c40b839d2ff6b722ab146e438d3c0f74a118bdccd79d1ba0f80a3a1fa1b20 +keeper_registry_logic_a_wrapper_2_1: ../../contracts/solc/v0.8.16/KeeperRegistryLogicA2_1/KeeperRegistryLogicA2_1.abi ../../contracts/solc/v0.8.16/KeeperRegistryLogicA2_1/KeeperRegistryLogicA2_1.bin a2327779e652a2bbd2ed2f4a7b904b696dbefd5b16e73d39be87188bde654d61 keeper_registry_logic_b_wrapper_2_1: ../../contracts/solc/v0.8.16/KeeperRegistryLogicB2_1/KeeperRegistryLogicB2_1.abi ../../contracts/solc/v0.8.16/KeeperRegistryLogicB2_1/KeeperRegistryLogicB2_1.bin 83b0cc20c6aa437b824f424b3e16ddcb18ab08bfa64398f143dbbf78f953dfef keeper_registry_wrapper1_2: ../../contracts/solc/v0.8.6/KeeperRegistry1_2/KeeperRegistry1_2.abi ../../contracts/solc/v0.8.6/KeeperRegistry1_2/KeeperRegistry1_2.bin f6f48cc6a4e03ffc987a017041417a1db78566275ec8ed7673fbfc9052ce0215 keeper_registry_wrapper1_3: ../../contracts/solc/v0.8.6/KeeperRegistry1_3/KeeperRegistry1_3.abi ../../contracts/solc/v0.8.6/KeeperRegistry1_3/KeeperRegistry1_3.bin d4dc760b767ae274ee25c4a604ea371e1fa603a7b6421b69efb2088ad9e8abb3 From 5aae9fff85af2382292a8d4a9dde5ae17193a201 Mon Sep 17 00:00:00 2001 From: FelixFan1992 Date: Fri, 21 Jun 2024 00:00:27 -0400 Subject: [PATCH 07/14] update --- .../automation/v2_2_zksync/ZKSyncAutomationRegistry2_2.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/src/v0.8/automation/v2_2_zksync/ZKSyncAutomationRegistry2_2.sol b/contracts/src/v0.8/automation/v2_2_zksync/ZKSyncAutomationRegistry2_2.sol index 4c20601d4c5..c7a4f92de4b 100644 --- a/contracts/src/v0.8/automation/v2_2_zksync/ZKSyncAutomationRegistry2_2.sol +++ b/contracts/src/v0.8/automation/v2_2_zksync/ZKSyncAutomationRegistry2_2.sol @@ -153,7 +153,7 @@ contract ZKSyncAutomationRegistry2_2 is ZKSyncAutomationRegistryBase2_2, OCR2Abs // Deduct that gasUsed by upkeep from our running counter // for zksync, the L1 gas is deducted at the end of a transaction but gasUsed here already has all the cost // if we don't add l1GasUsed here for zksync, `gasOverhead - gasleft()` will underflow - gasOverhead = gasOverhead - upkeepTransmitInfo[i].gasUsed + upkeepTransmitInfo[i].l1GasUsed; + gasOverhead = upkeepTransmitInfo[i].l1GasUsed + gasOverhead - upkeepTransmitInfo[i].gasUsed; // Store last perform block number / deduping key for upkeep _updateTriggerMarker(report.upkeepIds[i], blocknumber, upkeepTransmitInfo[i]); @@ -166,7 +166,7 @@ contract ZKSyncAutomationRegistry2_2 is ZKSyncAutomationRegistryBase2_2, OCR2Abs // This is the overall gas overhead that will be split across performed upkeeps // Take upper bound of 16 gas per callData bytes // for zksync, this place will underflow if we don't add back l1GasUsed - gasOverhead = (gasOverhead - gasleft()) + (16 * msg.data.length) + ACCOUNTING_FIXED_GAS_OVERHEAD; + gasOverhead = (16 * msg.data.length) + ACCOUNTING_FIXED_GAS_OVERHEAD + gasOverhead - gasleft(); gasOverhead = gasOverhead / transmitVars.numUpkeepsPassedChecks + ACCOUNTING_PER_UPKEEP_GAS_OVERHEAD; { From 925dcaefcd440037ad6e1e41231a5ffcb8b0388e Mon Sep 17 00:00:00 2001 From: FelixFan1992 Date: Fri, 21 Jun 2024 04:15:17 -0400 Subject: [PATCH 08/14] update --- .../automation/AutomationZKSyncForwarder.sol | 4 +-- .../ZKSyncAutomationRegistry2_2.sol | 31 ++++++++++++------- .../ZKSyncAutomationRegistryBase2_2.sol | 14 +++++++-- 3 files changed, 34 insertions(+), 15 deletions(-) diff --git a/contracts/src/v0.8/automation/AutomationZKSyncForwarder.sol b/contracts/src/v0.8/automation/AutomationZKSyncForwarder.sol index 0080c3d17a1..a0c156ff26d 100644 --- a/contracts/src/v0.8/automation/AutomationZKSyncForwarder.sol +++ b/contracts/src/v0.8/automation/AutomationZKSyncForwarder.sol @@ -74,8 +74,8 @@ contract AutomationZKSyncForwarder { uint256 gasPerPubdataByte = SYSTEM_CONTEXT_CONTRACT.gasPerPubdataByte(); uint256 g2 = gasleft(); - gasUsed = g1 - g2 + pubdataUsed * gasPerPubdataByte; - emit GasDetails(pubdataUsed, gasPerPubdataByte, g1 - g2, p1, p2, g1, g2); + gasUsed = g1 - g2; + emit GasDetails(pubdataUsed, gasPerPubdataByte, gasUsed, p1, p2, g1, g2); return (success, gasUsed, pubdataUsed * gasPerPubdataByte); } diff --git a/contracts/src/v0.8/automation/v2_2_zksync/ZKSyncAutomationRegistry2_2.sol b/contracts/src/v0.8/automation/v2_2_zksync/ZKSyncAutomationRegistry2_2.sol index c7a4f92de4b..b1b688ff890 100644 --- a/contracts/src/v0.8/automation/v2_2_zksync/ZKSyncAutomationRegistry2_2.sol +++ b/contracts/src/v0.8/automation/v2_2_zksync/ZKSyncAutomationRegistry2_2.sol @@ -63,7 +63,7 @@ contract ZKSyncAutomationRegistry2_2 is ZKSyncAutomationRegistryBase2_2, OCR2Abs // solhint-disable-next-line gas-struct-packing struct TransmitVars { uint16 numUpkeepsPassedChecks; - uint256 totalCalldataWeight; +// uint256 totalCalldataWeight; uint96 totalReimbursement; uint96 totalPremium; } @@ -109,13 +109,13 @@ contract ZKSyncAutomationRegistry2_2 is ZKSyncAutomationRegistryBase2_2, OCR2Abs UpkeepTransmitInfo[] memory upkeepTransmitInfo = new UpkeepTransmitInfo[](report.upkeepIds.length); TransmitVars memory transmitVars = TransmitVars({ numUpkeepsPassedChecks: 0, - totalCalldataWeight: 0, +// totalCalldataWeight: 0, totalReimbursement: 0, totalPremium: 0 }); uint256 blocknumber = hotVars.chainModule.blockNumber(); - uint256 l1Fee = hotVars.chainModule.getCurrentL1Fee(); +// uint256 l1Fee = hotVars.chainModule.getCurrentL1Fee(); for (uint256 i = 0; i < report.upkeepIds.length; i++) { upkeepTransmitInfo[i].upkeep = s_upkeep[report.upkeepIds[i]]; @@ -144,16 +144,20 @@ contract ZKSyncAutomationRegistry2_2 is ZKSyncAutomationRegistryBase2_2, OCR2Abs // To split L1 fee across the upkeeps, assign a weight to this upkeep based on the length // of the perform data and calldata overhead - upkeepTransmitInfo[i].calldataWeight = - report.performDatas[i].length + - TRANSMIT_CALLDATA_FIXED_BYTES_OVERHEAD + - (TRANSMIT_CALLDATA_PER_SIGNER_BYTES_OVERHEAD * (hotVars.f + 1)); - transmitVars.totalCalldataWeight += upkeepTransmitInfo[i].calldataWeight; +// upkeepTransmitInfo[i].calldataWeight = +// report.performDatas[i].length + +// TRANSMIT_CALLDATA_FIXED_BYTES_OVERHEAD + +// (TRANSMIT_CALLDATA_PER_SIGNER_BYTES_OVERHEAD * (hotVars.f + 1)); +// transmitVars.totalCalldataWeight += upkeepTransmitInfo[i].calldataWeight; // Deduct that gasUsed by upkeep from our running counter // for zksync, the L1 gas is deducted at the end of a transaction but gasUsed here already has all the cost // if we don't add l1GasUsed here for zksync, `gasOverhead - gasleft()` will underflow - gasOverhead = upkeepTransmitInfo[i].l1GasUsed + gasOverhead - upkeepTransmitInfo[i].gasUsed; + if (upkeepTransmitInfo[i].gasUsed > gasOverhead) { + emit WrongArithmetics(gasOverhead, upkeepTransmitInfo[i].gasUsed, 0); + return; + } + gasOverhead -= upkeepTransmitInfo[i].gasUsed; // Store last perform block number / deduping key for upkeep _updateTriggerMarker(report.upkeepIds[i], blocknumber, upkeepTransmitInfo[i]); @@ -166,6 +170,10 @@ contract ZKSyncAutomationRegistry2_2 is ZKSyncAutomationRegistryBase2_2, OCR2Abs // This is the overall gas overhead that will be split across performed upkeeps // Take upper bound of 16 gas per callData bytes // for zksync, this place will underflow if we don't add back l1GasUsed + if ((16 * msg.data.length) + ACCOUNTING_FIXED_GAS_OVERHEAD + gasOverhead < gasleft()) { + emit WrongArithmetics((16 * msg.data.length) + ACCOUNTING_FIXED_GAS_OVERHEAD + gasOverhead, gasleft(), 1); + return; + } gasOverhead = (16 * msg.data.length) + ACCOUNTING_FIXED_GAS_OVERHEAD + gasOverhead - gasleft(); gasOverhead = gasOverhead / transmitVars.numUpkeepsPassedChecks + ACCOUNTING_PER_UPKEEP_GAS_OVERHEAD; @@ -181,7 +189,7 @@ contract ZKSyncAutomationRegistry2_2 is ZKSyncAutomationRegistryBase2_2, OCR2Abs report.fastGasWei, report.linkNative, gasOverhead, - (l1Fee * upkeepTransmitInfo[i].calldataWeight) / transmitVars.totalCalldataWeight + upkeepTransmitInfo[i].l1GasUsed * tx.gasprice ); transmitVars.totalPremium += premium; transmitVars.totalReimbursement += reimbursement; @@ -198,7 +206,8 @@ contract ZKSyncAutomationRegistry2_2 is ZKSyncAutomationRegistryBase2_2, OCR2Abs emit UpkeepPerformedDetails( reimbursement + premium, upkeepTransmitInfo[i].gasUsed, - gasOverhead + gasOverhead, + upkeepTransmitInfo[i].l1GasUsed ); } } diff --git a/contracts/src/v0.8/automation/v2_2_zksync/ZKSyncAutomationRegistryBase2_2.sol b/contracts/src/v0.8/automation/v2_2_zksync/ZKSyncAutomationRegistryBase2_2.sol index 0a689503a60..6082c3653a3 100644 --- a/contracts/src/v0.8/automation/v2_2_zksync/ZKSyncAutomationRegistryBase2_2.sol +++ b/contracts/src/v0.8/automation/v2_2_zksync/ZKSyncAutomationRegistryBase2_2.sol @@ -314,7 +314,7 @@ abstract contract ZKSyncAutomationRegistryBase2_2 is ConfirmedOwner { Trigger triggerType; uint256 gasUsed; uint256 l1GasUsed; // specially for zksync - uint256 calldataWeight; +// uint256 calldataWeight; bytes32 dedupID; } @@ -386,8 +386,10 @@ abstract contract ZKSyncAutomationRegistryBase2_2 is ConfirmedOwner { event UpkeepPerformedDetails( uint96 indexed totalPayment, uint256 indexed gasUsed, - uint256 indexed gasOverhead + uint256 indexed gasOverhead, + uint256 l1GasUsed ); + event WrongArithmetics(uint256 indexed n1, uint256 indexed n2, uint256 indexed n3); // 0x00000000000000000000000000000000000000000000000000257a2909289c77 => 10548890804198519 => 0.0105 LINK // 0x0000000000000000000000000000000000000000000000000000000000000e8b => 3723 gas @@ -835,9 +837,17 @@ abstract contract ZKSyncAutomationRegistryBase2_2 is ConfirmedOwner { premium = 0; } else if (balance < payment) { payment = balance; + if (payment < gasReimbursement) { + emit WrongArithmetics(payment, gasReimbursement, 2); + return (0, 0); + } premium = payment - gasReimbursement; } + if (s_upkeep[upkeepId].balance < payment) { + emit WrongArithmetics(s_upkeep[upkeepId].balance, payment, 3); + return (0, 0); + } s_upkeep[upkeepId].balance -= payment; s_upkeep[upkeepId].amountSpent += payment; From e8145628bdbe9649ad9c4fb1f98c9676053aa9ac Mon Sep 17 00:00:00 2001 From: FelixFan1992 Date: Fri, 21 Jun 2024 05:03:22 -0400 Subject: [PATCH 09/14] update --- .../automation/test/ZKSyncStoreTester.sol | 46 +++++++++++++++---- 1 file changed, 37 insertions(+), 9 deletions(-) diff --git a/contracts/src/v0.8/automation/test/ZKSyncStoreTester.sol b/contracts/src/v0.8/automation/test/ZKSyncStoreTester.sol index 9320954ab0a..853d1636c4b 100644 --- a/contracts/src/v0.8/automation/test/ZKSyncStoreTester.sol +++ b/contracts/src/v0.8/automation/test/ZKSyncStoreTester.sol @@ -20,7 +20,14 @@ contract ZKSyncStoreTester { uint32 public iterations; bytes public storedData; bool public reset; - bytes[] public data; + bytes public data0; + bytes public data1; + bytes public data2; + bytes public data3; + bytes public data4; + bytes public data5; + bytes public full = hex"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"; + bytes public constant empty = hex"00"; constructor() { testRange = 10000; @@ -30,22 +37,43 @@ contract ZKSyncStoreTester { initialTimestamp = 0; counter = 0; - iterations = 100; + iterations = 1; } function storeData() public { - data = new bytes[](iterations); - storedData = new bytes(iterations); - bytes1 d = 0xff; - if (reset) { - d = 0x00; - } +// data = new bytes[](iterations); +// storedData = new bytes(iterations); +// bytes1 d = 0xff; +// if (reset) { +// d = 0x00; +// } +// for (uint32 i = 0; i < iterations; i++) { +// storedData[i] = d; +// } for (uint32 i = 0; i < iterations; i++) { - storedData[i] = d; + if (reset) { + data0 = empty; + data1 = empty; + data2 = empty; + data3 = empty; + data4 = empty; + data5 = empty; + } else { + data0 = full; + data1 = full; + data2 = full; + data3 = full; + data4 = full; + data5 = full; + } } reset = !reset; } + function setFull(bytes calldata d) external { + full = d; + } + function setIterations(uint32 _i) external { iterations = _i; } From fbe5a62ff369e132610102ca877e2052dd6e7067 Mon Sep 17 00:00:00 2001 From: FelixFan1992 Date: Fri, 21 Jun 2024 05:06:35 -0400 Subject: [PATCH 10/14] update --- .../v0.8/automation/{test => testhelpers}/ZKSyncHashTester.sol | 0 .../v0.8/automation/{test => testhelpers}/ZKSyncStoreTester.sol | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename contracts/src/v0.8/automation/{test => testhelpers}/ZKSyncHashTester.sol (100%) rename contracts/src/v0.8/automation/{test => testhelpers}/ZKSyncStoreTester.sol (100%) diff --git a/contracts/src/v0.8/automation/test/ZKSyncHashTester.sol b/contracts/src/v0.8/automation/testhelpers/ZKSyncHashTester.sol similarity index 100% rename from contracts/src/v0.8/automation/test/ZKSyncHashTester.sol rename to contracts/src/v0.8/automation/testhelpers/ZKSyncHashTester.sol diff --git a/contracts/src/v0.8/automation/test/ZKSyncStoreTester.sol b/contracts/src/v0.8/automation/testhelpers/ZKSyncStoreTester.sol similarity index 100% rename from contracts/src/v0.8/automation/test/ZKSyncStoreTester.sol rename to contracts/src/v0.8/automation/testhelpers/ZKSyncStoreTester.sol From 2106f663714b8e0a2a66648e95156c69194e21ff Mon Sep 17 00:00:00 2001 From: FelixFan1992 Date: Fri, 21 Jun 2024 14:00:59 -0400 Subject: [PATCH 11/14] update --- contracts/src/v0.8/automation/AutomationZKSyncForwarder.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/src/v0.8/automation/AutomationZKSyncForwarder.sol b/contracts/src/v0.8/automation/AutomationZKSyncForwarder.sol index a0c156ff26d..e4f566bec18 100644 --- a/contracts/src/v0.8/automation/AutomationZKSyncForwarder.sol +++ b/contracts/src/v0.8/automation/AutomationZKSyncForwarder.sol @@ -68,7 +68,7 @@ contract AutomationZKSyncForwarder { uint256 p2 = SYSTEM_CONTEXT_CONTRACT.getCurrentPubdataSpent(); // pubdata size can be less than 0 uint256 pubdataUsed; - if (p2 - p1 > 0) { + if (p2 > p1) { pubdataUsed = p2 - p1; } From adb2fb0361435d60d770737337c6177c35b62c6b Mon Sep 17 00:00:00 2001 From: FelixFan1992 Date: Fri, 21 Jun 2024 16:44:50 -0400 Subject: [PATCH 12/14] update --- .../v0.8/automation/AutomationZKSyncForwarder.sol | 12 ++++++------ .../v2_2_zksync/ZKSyncAutomationRegistry2_2.sol | 8 -------- .../v2_2_zksync/ZKSyncAutomationRegistryBase2_2.sol | 9 --------- 3 files changed, 6 insertions(+), 23 deletions(-) diff --git a/contracts/src/v0.8/automation/AutomationZKSyncForwarder.sol b/contracts/src/v0.8/automation/AutomationZKSyncForwarder.sol index e4f566bec18..16f7f72453f 100644 --- a/contracts/src/v0.8/automation/AutomationZKSyncForwarder.sol +++ b/contracts/src/v0.8/automation/AutomationZKSyncForwarder.sol @@ -27,7 +27,7 @@ contract AutomationZKSyncForwarder { IAutomationRegistryConsumer private s_registry; - event GasDetails(uint256 indexed pubdataUsed, uint256 indexed gasPerPubdataByte, uint256 indexed executionGasUsed, uint256 p1, uint256 p2, uint256 g1, uint256 g2); + event GasDetails(uint256 indexed pubdataUsed, uint256 indexed gasPerPubdataByte, uint256 indexed executionGasUsed, uint256 p1, uint256 p2, uint256 gasprice); constructor(address target, address registry, address logic) { s_registry = IAutomationRegistryConsumer(registry); @@ -75,15 +75,15 @@ contract AutomationZKSyncForwarder { uint256 gasPerPubdataByte = SYSTEM_CONTEXT_CONTRACT.gasPerPubdataByte(); uint256 g2 = gasleft(); gasUsed = g1 - g2; - emit GasDetails(pubdataUsed, gasPerPubdataByte, gasUsed, p1, p2, g1, g2); + emit GasDetails(pubdataUsed, gasPerPubdataByte, gasUsed, p1, p2, tx.gasprice); return (success, gasUsed, pubdataUsed * gasPerPubdataByte); } /* -0x0000000000000000000000000000000000000000000000000000000000000013 -0x0000000000000000000000000000000000000000000000000000000000000167 -0x00000000000000000000000000000000000000000000000000000000004ed2cb -0x00000000000000000000000000000000000000000000000000000000004d93f9 +0x000000000000000000000000000000000000000000000000000000000000000a +0x0000000000000000000000000000000000000000000000000000000000000000 +0x0000000000000000000000000000000000000000000000000000000000d89056 +0x0000000000000000000000000000000000000000000000000000000000d093ec */ function getTarget() external view returns (address) { return i_target; diff --git a/contracts/src/v0.8/automation/v2_2_zksync/ZKSyncAutomationRegistry2_2.sol b/contracts/src/v0.8/automation/v2_2_zksync/ZKSyncAutomationRegistry2_2.sol index b1b688ff890..2e97329ee09 100644 --- a/contracts/src/v0.8/automation/v2_2_zksync/ZKSyncAutomationRegistry2_2.sol +++ b/contracts/src/v0.8/automation/v2_2_zksync/ZKSyncAutomationRegistry2_2.sol @@ -153,10 +153,6 @@ contract ZKSyncAutomationRegistry2_2 is ZKSyncAutomationRegistryBase2_2, OCR2Abs // Deduct that gasUsed by upkeep from our running counter // for zksync, the L1 gas is deducted at the end of a transaction but gasUsed here already has all the cost // if we don't add l1GasUsed here for zksync, `gasOverhead - gasleft()` will underflow - if (upkeepTransmitInfo[i].gasUsed > gasOverhead) { - emit WrongArithmetics(gasOverhead, upkeepTransmitInfo[i].gasUsed, 0); - return; - } gasOverhead -= upkeepTransmitInfo[i].gasUsed; // Store last perform block number / deduping key for upkeep @@ -170,10 +166,6 @@ contract ZKSyncAutomationRegistry2_2 is ZKSyncAutomationRegistryBase2_2, OCR2Abs // This is the overall gas overhead that will be split across performed upkeeps // Take upper bound of 16 gas per callData bytes // for zksync, this place will underflow if we don't add back l1GasUsed - if ((16 * msg.data.length) + ACCOUNTING_FIXED_GAS_OVERHEAD + gasOverhead < gasleft()) { - emit WrongArithmetics((16 * msg.data.length) + ACCOUNTING_FIXED_GAS_OVERHEAD + gasOverhead, gasleft(), 1); - return; - } gasOverhead = (16 * msg.data.length) + ACCOUNTING_FIXED_GAS_OVERHEAD + gasOverhead - gasleft(); gasOverhead = gasOverhead / transmitVars.numUpkeepsPassedChecks + ACCOUNTING_PER_UPKEEP_GAS_OVERHEAD; diff --git a/contracts/src/v0.8/automation/v2_2_zksync/ZKSyncAutomationRegistryBase2_2.sol b/contracts/src/v0.8/automation/v2_2_zksync/ZKSyncAutomationRegistryBase2_2.sol index 6082c3653a3..a1889b8f895 100644 --- a/contracts/src/v0.8/automation/v2_2_zksync/ZKSyncAutomationRegistryBase2_2.sol +++ b/contracts/src/v0.8/automation/v2_2_zksync/ZKSyncAutomationRegistryBase2_2.sol @@ -389,7 +389,6 @@ abstract contract ZKSyncAutomationRegistryBase2_2 is ConfirmedOwner { uint256 indexed gasOverhead, uint256 l1GasUsed ); - event WrongArithmetics(uint256 indexed n1, uint256 indexed n2, uint256 indexed n3); // 0x00000000000000000000000000000000000000000000000000257a2909289c77 => 10548890804198519 => 0.0105 LINK // 0x0000000000000000000000000000000000000000000000000000000000000e8b => 3723 gas @@ -837,17 +836,9 @@ abstract contract ZKSyncAutomationRegistryBase2_2 is ConfirmedOwner { premium = 0; } else if (balance < payment) { payment = balance; - if (payment < gasReimbursement) { - emit WrongArithmetics(payment, gasReimbursement, 2); - return (0, 0); - } premium = payment - gasReimbursement; } - if (s_upkeep[upkeepId].balance < payment) { - emit WrongArithmetics(s_upkeep[upkeepId].balance, payment, 3); - return (0, 0); - } s_upkeep[upkeepId].balance -= payment; s_upkeep[upkeepId].amountSpent += payment; From 4cd8f6f7d5240468baadc8c169a8ac194f1add2e Mon Sep 17 00:00:00 2001 From: FelixFan1992 Date: Mon, 24 Jun 2024 21:04:16 -0400 Subject: [PATCH 13/14] update --- .../ZKSyncAutomationRegistry2_2.sol | 8 ------ .../ZKSyncAutomationRegistryBase2_2.sol | 25 ++++++++++++++++--- .../ZKSyncAutomationRegistryLogicA2_2.sol | 12 ++++----- .../ZKSyncAutomationRegistryLogicB2_2.sol | 4 +-- 4 files changed, 29 insertions(+), 20 deletions(-) diff --git a/contracts/src/v0.8/automation/v2_2_zksync/ZKSyncAutomationRegistry2_2.sol b/contracts/src/v0.8/automation/v2_2_zksync/ZKSyncAutomationRegistry2_2.sol index 2e97329ee09..dc0cece9f06 100644 --- a/contracts/src/v0.8/automation/v2_2_zksync/ZKSyncAutomationRegistry2_2.sol +++ b/contracts/src/v0.8/automation/v2_2_zksync/ZKSyncAutomationRegistry2_2.sol @@ -142,14 +142,6 @@ contract ZKSyncAutomationRegistry2_2 is ZKSyncAutomationRegistryBase2_2, OCR2Abs report.performDatas[i] ); - // To split L1 fee across the upkeeps, assign a weight to this upkeep based on the length - // of the perform data and calldata overhead -// upkeepTransmitInfo[i].calldataWeight = -// report.performDatas[i].length + -// TRANSMIT_CALLDATA_FIXED_BYTES_OVERHEAD + -// (TRANSMIT_CALLDATA_PER_SIGNER_BYTES_OVERHEAD * (hotVars.f + 1)); -// transmitVars.totalCalldataWeight += upkeepTransmitInfo[i].calldataWeight; - // Deduct that gasUsed by upkeep from our running counter // for zksync, the L1 gas is deducted at the end of a transaction but gasUsed here already has all the cost // if we don't add l1GasUsed here for zksync, `gasOverhead - gasleft()` will underflow diff --git a/contracts/src/v0.8/automation/v2_2_zksync/ZKSyncAutomationRegistryBase2_2.sol b/contracts/src/v0.8/automation/v2_2_zksync/ZKSyncAutomationRegistryBase2_2.sol index a1889b8f895..275a13d1312 100644 --- a/contracts/src/v0.8/automation/v2_2_zksync/ZKSyncAutomationRegistryBase2_2.sol +++ b/contracts/src/v0.8/automation/v2_2_zksync/ZKSyncAutomationRegistryBase2_2.sol @@ -5,7 +5,7 @@ import {EnumerableSet} from "../../vendor/openzeppelin-solidity/v4.7.3/contracts import {Address} from "../../vendor/openzeppelin-solidity/v4.7.3/contracts/utils/Address.sol"; import {StreamsLookupCompatibleInterface} from "../interfaces/StreamsLookupCompatibleInterface.sol"; import {ILogAutomation, Log} from "../interfaces/ILogAutomation.sol"; -import {IAutomationZKSyncForwarder} from "../interfaces/IAutomationZKSyncForwarder.sol"; +import {IAutomationForwarder} from "../interfaces/IAutomationForwarder.sol"; import {ConfirmedOwner} from "../../shared/access/ConfirmedOwner.sol"; import {AggregatorV3Interface} from "../../shared/interfaces/AggregatorV3Interface.sol"; import {LinkTokenInterface} from "../../shared/interfaces/LinkTokenInterface.sol"; @@ -13,6 +13,13 @@ import {KeeperCompatibleInterface} from "../interfaces/KeeperCompatibleInterface import {UpkeepFormat} from "../interfaces/UpkeepTranscoderInterface.sol"; import {IChainModule} from "../interfaces/IChainModule.sol"; +interface ISystemContext { + function gasPerPubdataByte() external view returns (uint256 gasPerPubdataByte); + function getCurrentPubdataSpent() external view returns (uint256 currentPubdataSpent); +} + +ISystemContext constant SYSTEM_CONTEXT_CONTRACT = ISystemContext(address(0x800b)); + /** * @notice Base Keeper Registry contract, contains shared logic between * AutomationRegistry and AutomationRegistryLogic @@ -243,7 +250,7 @@ abstract contract ZKSyncAutomationRegistryBase2_2 is ConfirmedOwner { bool paused; uint32 performGas; uint32 maxValidBlocknumber; - IAutomationZKSyncForwarder forwarder; + IAutomationForwarder forwarder; // 0 bytes left in 1st EVM word - not written to in transmit uint96 amountSpent; uint96 balance; @@ -401,6 +408,7 @@ abstract contract ZKSyncAutomationRegistryBase2_2 is ConfirmedOwner { event UpkeepTriggerConfigSet(uint256 indexed id, bytes triggerConfig); event UpkeepUnpaused(uint256 indexed id); event Unpaused(address account); + event GasDetails(uint256 indexed pubdataUsed, uint256 indexed gasPerPubdataByte, uint256 indexed executionGasUsed, uint256 p1, uint256 p2, uint256 gasprice); /** * @param link address of the LINK Token @@ -794,12 +802,21 @@ abstract contract ZKSyncAutomationRegistryBase2_2 is ConfirmedOwner { * transmitter and the exact gas required by the Upkeep */ function _performUpkeep( - IAutomationZKSyncForwarder forwarder, + IAutomationForwarder forwarder, uint256 performGas, bytes memory performData ) internal nonReentrant returns (bool success, uint256 gasUsed, uint256 l1GasUsed) { performData = abi.encodeWithSelector(PERFORM_SELECTOR, performData); - return forwarder.forward(performGas, performData); + uint256 p1 = SYSTEM_CONTEXT_CONTRACT.getCurrentPubdataSpent(); + (bool success, uint256 gasUsed) = forwarder.forward(performGas, performData); + uint256 p2 = SYSTEM_CONTEXT_CONTRACT.getCurrentPubdataSpent(); + uint256 pubdataUsed; + if (p2 > p1) { + pubdataUsed = p2 - p1; + } + uint256 gasPerPubdataByte = SYSTEM_CONTEXT_CONTRACT.gasPerPubdataByte(); + emit GasDetails(pubdataUsed, gasPerPubdataByte, gasUsed, p1, p2, tx.gasprice); + return (success, gasUsed, gasPerPubdataByte * pubdataUsed); } /** diff --git a/contracts/src/v0.8/automation/v2_2_zksync/ZKSyncAutomationRegistryLogicA2_2.sol b/contracts/src/v0.8/automation/v2_2_zksync/ZKSyncAutomationRegistryLogicA2_2.sol index 2c3d957fb77..fa605e89256 100644 --- a/contracts/src/v0.8/automation/v2_2_zksync/ZKSyncAutomationRegistryLogicA2_2.sol +++ b/contracts/src/v0.8/automation/v2_2_zksync/ZKSyncAutomationRegistryLogicA2_2.sol @@ -6,8 +6,8 @@ import {Address} from "../../vendor/openzeppelin-solidity/v4.7.3/contracts/utils import {ZKSyncAutomationRegistryBase2_2} from "./ZKSyncAutomationRegistryBase2_2.sol"; import {ZKSyncAutomationRegistryLogicB2_2} from "./ZKSyncAutomationRegistryLogicB2_2.sol"; import {Chainable} from "../Chainable.sol"; -import {AutomationZKSyncForwarder} from "../AutomationZKSyncForwarder.sol"; -import {IAutomationZKSyncForwarder} from "../interfaces/IAutomationZKSyncForwarder.sol"; +import {AutomationForwarder} from "../AutomationForwarder.sol"; +import {IAutomationForwarder} from "../interfaces/IAutomationForwarder.sol"; import {UpkeepTranscoderInterfaceV2} from "../interfaces/UpkeepTranscoderInterfaceV2.sol"; import {MigratableKeeperRegistryInterfaceV2} from "../interfaces/MigratableKeeperRegistryInterfaceV2.sol"; @@ -228,8 +228,8 @@ contract ZKSyncAutomationRegistryLogicA2_2 is ZKSyncAutomationRegistryBase2_2, C if (msg.sender != owner() && !s_registrars.contains(msg.sender)) revert OnlyCallableByOwnerOrRegistrar(); if (!target.isContract()) revert NotAContract(); id = _createID(triggerType); - IAutomationZKSyncForwarder forwarder = IAutomationZKSyncForwarder( - address(new AutomationZKSyncForwarder(target, address(this), i_automationForwarderLogic)) + IAutomationForwarder forwarder = IAutomationForwarder( + address(new AutomationForwarder(target, address(this), i_automationForwarderLogic)) ); _createUpkeep( id, @@ -404,8 +404,8 @@ contract ZKSyncAutomationRegistryLogicA2_2 is ZKSyncAutomationRegistryBase2_2, C ) = abi.decode(encodedUpkeeps, (uint256[], Upkeep[], address[], address[], bytes[], bytes[], bytes[])); for (uint256 idx = 0; idx < ids.length; idx++) { if (address(upkeeps[idx].forwarder) == ZERO_ADDRESS) { - upkeeps[idx].forwarder = IAutomationZKSyncForwarder( - address(new AutomationZKSyncForwarder(targets[idx], address(this), i_automationForwarderLogic)) + upkeeps[idx].forwarder = IAutomationForwarder( + address(new AutomationForwarder(targets[idx], address(this), i_automationForwarderLogic)) ); } _createUpkeep( diff --git a/contracts/src/v0.8/automation/v2_2_zksync/ZKSyncAutomationRegistryLogicB2_2.sol b/contracts/src/v0.8/automation/v2_2_zksync/ZKSyncAutomationRegistryLogicB2_2.sol index cb910c8fcb4..e1620e9182f 100644 --- a/contracts/src/v0.8/automation/v2_2_zksync/ZKSyncAutomationRegistryLogicB2_2.sol +++ b/contracts/src/v0.8/automation/v2_2_zksync/ZKSyncAutomationRegistryLogicB2_2.sol @@ -5,7 +5,7 @@ import {ZKSyncAutomationRegistryBase2_2} from "./ZKSyncAutomationRegistryBase2_2 import {EnumerableSet} from "../../vendor/openzeppelin-solidity/v4.7.3/contracts/utils/structs/EnumerableSet.sol"; import {Address} from "../../vendor/openzeppelin-solidity/v4.7.3/contracts/utils/Address.sol"; import {UpkeepFormat} from "../interfaces/UpkeepTranscoderInterface.sol"; -import {IAutomationZKSyncForwarder} from "../interfaces/IAutomationZKSyncForwarder.sol"; +import {IAutomationForwarder} from "../interfaces/IAutomationForwarder.sol"; import {IChainModule} from "../interfaces/IChainModule.sol"; import {IAutomationV21PlusCommon} from "../interfaces/IAutomationV21PlusCommon.sol"; @@ -527,7 +527,7 @@ contract ZKSyncAutomationRegistryLogicB2_2 is ZKSyncAutomationRegistryBase2_2 { /** * @notice returns the upkeep's forwarder contract */ - function getForwarder(uint256 upkeepID) external view returns (IAutomationZKSyncForwarder) { + function getForwarder(uint256 upkeepID) external view returns (IAutomationForwarder) { return s_upkeep[upkeepID].forwarder; } From f5474475506038b5b0fe217e5ae9da4bef85906f Mon Sep 17 00:00:00 2001 From: FelixFan1992 Date: Mon, 24 Jun 2024 23:23:51 -0400 Subject: [PATCH 14/14] update --- .../automation/testhelpers/ZKSyncStoreTester.sol | 11 +---------- .../v2_2_zksync/ZKSyncAutomationRegistry2_2.sol | 6 +++--- .../v2_2_zksync/ZKSyncAutomationRegistryBase2_2.sol | 13 +++++++++---- 3 files changed, 13 insertions(+), 17 deletions(-) diff --git a/contracts/src/v0.8/automation/testhelpers/ZKSyncStoreTester.sol b/contracts/src/v0.8/automation/testhelpers/ZKSyncStoreTester.sol index 853d1636c4b..8fb43a51849 100644 --- a/contracts/src/v0.8/automation/testhelpers/ZKSyncStoreTester.sol +++ b/contracts/src/v0.8/automation/testhelpers/ZKSyncStoreTester.sol @@ -26,7 +26,7 @@ contract ZKSyncStoreTester { bytes public data3; bytes public data4; bytes public data5; - bytes public full = hex"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"; + bytes public full = hex"ffffffff0f0ff0fffff0fffffff0fffffffffff0fffff0ffffff0ffffffffffff00000fffffffffffff000fffffffffffffffffffffffff0ffffffffff0fffffffffffffffffff00fffffffffff00ffffffffffffffffffffffff000000fffffffffffffffffffffffffffffffffff0ffffffffffffffff0fffff0ffffffff"; bytes public constant empty = hex"00"; constructor() { @@ -41,15 +41,6 @@ contract ZKSyncStoreTester { } function storeData() public { -// data = new bytes[](iterations); -// storedData = new bytes(iterations); -// bytes1 d = 0xff; -// if (reset) { -// d = 0x00; -// } -// for (uint32 i = 0; i < iterations; i++) { -// storedData[i] = d; -// } for (uint32 i = 0; i < iterations; i++) { if (reset) { data0 = empty; diff --git a/contracts/src/v0.8/automation/v2_2_zksync/ZKSyncAutomationRegistry2_2.sol b/contracts/src/v0.8/automation/v2_2_zksync/ZKSyncAutomationRegistry2_2.sol index dc0cece9f06..974b6dd9bd2 100644 --- a/contracts/src/v0.8/automation/v2_2_zksync/ZKSyncAutomationRegistry2_2.sol +++ b/contracts/src/v0.8/automation/v2_2_zksync/ZKSyncAutomationRegistry2_2.sol @@ -136,7 +136,7 @@ contract ZKSyncAutomationRegistry2_2 is ZKSyncAutomationRegistryBase2_2, OCR2Abs } // Actually perform the target upkeep - (upkeepTransmitInfo[i].performSuccess, upkeepTransmitInfo[i].gasUsed, upkeepTransmitInfo[i].l1GasUsed) = _performUpkeep( + (upkeepTransmitInfo[i].performSuccess, upkeepTransmitInfo[i].gasUsed) = _performUpkeep( upkeepTransmitInfo[i].upkeep.forwarder, report.gasLimits[i], report.performDatas[i] @@ -173,7 +173,7 @@ contract ZKSyncAutomationRegistry2_2 is ZKSyncAutomationRegistryBase2_2, OCR2Abs report.fastGasWei, report.linkNative, gasOverhead, - upkeepTransmitInfo[i].l1GasUsed * tx.gasprice + 0 ); transmitVars.totalPremium += premium; transmitVars.totalReimbursement += reimbursement; @@ -216,7 +216,7 @@ contract ZKSyncAutomationRegistry2_2 is ZKSyncAutomationRegistryBase2_2, OCR2Abs if (s_hotVars.paused) revert RegistryPaused(); Upkeep memory upkeep = s_upkeep[id]; - (success, gasUsed,) = _performUpkeep(upkeep.forwarder, upkeep.performGas, performData); + (success, gasUsed) = _performUpkeep(upkeep.forwarder, upkeep.performGas, performData); return (success, gasUsed); } diff --git a/contracts/src/v0.8/automation/v2_2_zksync/ZKSyncAutomationRegistryBase2_2.sol b/contracts/src/v0.8/automation/v2_2_zksync/ZKSyncAutomationRegistryBase2_2.sol index 275a13d1312..4ea19e2c6f6 100644 --- a/contracts/src/v0.8/automation/v2_2_zksync/ZKSyncAutomationRegistryBase2_2.sol +++ b/contracts/src/v0.8/automation/v2_2_zksync/ZKSyncAutomationRegistryBase2_2.sol @@ -163,6 +163,7 @@ abstract contract ZKSyncAutomationRegistryBase2_2 is ConfirmedOwner { error UpkeepNotCanceled(); error UpkeepNotNeeded(); error ValueNotChanged(); + error InsufficientGas(uint256 executionGas, uint256 l1Gas); enum MigrationPermission { NONE, @@ -805,18 +806,22 @@ abstract contract ZKSyncAutomationRegistryBase2_2 is ConfirmedOwner { IAutomationForwarder forwarder, uint256 performGas, bytes memory performData - ) internal nonReentrant returns (bool success, uint256 gasUsed, uint256 l1GasUsed) { + ) internal nonReentrant returns (bool success, uint256 gasUsed) { performData = abi.encodeWithSelector(PERFORM_SELECTOR, performData); uint256 p1 = SYSTEM_CONTEXT_CONTRACT.getCurrentPubdataSpent(); - (bool success, uint256 gasUsed) = forwarder.forward(performGas, performData); + (bool success, uint256 executionGasUsed) = forwarder.forward(performGas, performData); uint256 p2 = SYSTEM_CONTEXT_CONTRACT.getCurrentPubdataSpent(); uint256 pubdataUsed; if (p2 > p1) { pubdataUsed = p2 - p1; } uint256 gasPerPubdataByte = SYSTEM_CONTEXT_CONTRACT.gasPerPubdataByte(); - emit GasDetails(pubdataUsed, gasPerPubdataByte, gasUsed, p1, p2, tx.gasprice); - return (success, gasUsed, gasPerPubdataByte * pubdataUsed); + emit GasDetails(pubdataUsed, gasPerPubdataByte, executionGasUsed, p1, p2, tx.gasprice); + uint256 l1GasUsed = gasPerPubdataByte * pubdataUsed; + if (performGas < l1GasUsed + executionGasUsed) { + revert InsufficientGas(executionGasUsed, l1GasUsed); + } + return (success, executionGasUsed + l1GasUsed); } /**