Skip to content

Commit

Permalink
auto-10161: implement without zksync forwarder interface change (#14037)
Browse files Browse the repository at this point in the history
* add changeset

* add some basic foundry tests

* add zksync module and interfaces

* update

* update

* add tests

* fix lint

* add more tests

* update

* test

* format

* add a zksync interface

* update

* update

* add overhead funcs

* update

* update

* update

* update

* update

* clean up 1

* clean up 2
  • Loading branch information
FelixFan1992 authored Aug 22, 2024
1 parent d50feb0 commit 9c240b6
Show file tree
Hide file tree
Showing 17 changed files with 8,463 additions and 67 deletions.
5 changes: 5 additions & 0 deletions contracts/.changeset/thirty-lamps-reply.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@chainlink/contracts': patch
---

implement an auto registry for zksync with no forwarder interface change
1 change: 0 additions & 1 deletion contracts/.solhintignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
./src/v0.8/automation/libraries/internal/Cron.sol
./src/v0.8/automation/AutomationForwarder.sol
./src/v0.8/automation/AutomationForwarderLogic.sol
./src/v0.8/automation/ZKSyncAutomationForwarder.sol
./src/v0.8/automation/interfaces/v2_2/IAutomationRegistryMaster.sol
./src/v0.8/automation/interfaces/v2_3/IAutomationRegistryMaster2_3.sol

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/**
* @description this script generates a master interface for interacting with the automation registry
* @notice run this script with pnpm ts-node ./scripts/generate-zksync-automation-master-interface-v2_3.ts
*/
import { ZKSyncAutomationRegistry2_3__factory as Registry } from '../typechain/factories/ZKSyncAutomationRegistry2_3__factory'
import { ZKSyncAutomationRegistryLogicA2_3__factory as RegistryLogicA } from '../typechain/factories/ZKSyncAutomationRegistryLogicA2_3__factory'
import { ZKSyncAutomationRegistryLogicB2_3__factory as RegistryLogicB } from '../typechain/factories/ZKSyncAutomationRegistryLogicB2_3__factory'
import { ZKSyncAutomationRegistryLogicC2_3__factory as RegistryLogicC } from '../typechain/factories/ZKSyncAutomationRegistryLogicC2_3__factory'
import { utils } from 'ethers'
import fs from 'fs'
import { exec } from 'child_process'

const dest = 'src/v0.8/automation/interfaces/zksync'
const srcDest = `${dest}/IZKSyncAutomationRegistryMaster2_3.sol`
const tmpDest = `${dest}/tmp.txt`

const combinedABI = []
const abiSet = new Set()
const abis = [
Registry.abi,
RegistryLogicA.abi,
RegistryLogicB.abi,
RegistryLogicC.abi,
]

for (const abi of abis) {
for (const entry of abi) {
const id = utils.id(JSON.stringify(entry))
if (!abiSet.has(id)) {
abiSet.add(id)
if (
entry.type === 'function' &&
(entry.name === 'checkUpkeep' ||
entry.name === 'checkCallback' ||
entry.name === 'simulatePerformUpkeep')
) {
entry.stateMutability = 'view' // override stateMutability for check / callback / simulate functions
}
combinedABI.push(entry)
}
}
}

const checksum = utils.id(abis.join(''))

fs.writeFileSync(`${tmpDest}`, JSON.stringify(combinedABI))

const cmd = `
cat ${tmpDest} | pnpm abi-to-sol --solidity-version ^0.8.4 --license MIT > ${srcDest} IZKSyncAutomationRegistryMaster2_3;
echo "// solhint-disable \n// abi-checksum: ${checksum}" | cat - ${srcDest} > ${tmpDest} && mv ${tmpDest} ${srcDest};
pnpm prettier --write ${srcDest};
`

exec(cmd)

console.log(
'generated new master interface for zksync automation registry v2_3',
)
31 changes: 23 additions & 8 deletions contracts/src/v0.8/automation/ZKSyncAutomationForwarder.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,19 @@
pragma solidity ^0.8.16;

import {IAutomationRegistryConsumer} from "./interfaces/IAutomationRegistryConsumer.sol";
import {GAS_BOUND_CALLER, IGasBoundCaller} from "./interfaces/zksync/IGasBoundCaller.sol";

uint256 constant PERFORM_GAS_CUSHION = 5_000;
uint256 constant PERFORM_GAS_CUSHION = 50_000;

/**
* @title AutomationForwarder is a relayer that sits between the registry and the customer's target contract
* @title ZKSyncAutomationForwarder 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 programmatically interact with the registry (ie top up funds) can do so.
*/
contract ZKSyncAutomationForwarder {
error InvalidCaller(address);

/// @notice the user's target contract address
address private immutable i_target;

Expand All @@ -31,11 +34,14 @@ contract ZKSyncAutomationForwarder {
* @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
* @return gasUsed the total gas used from this forwarding call
*/
function forward(uint256 gasAmount, bytes memory data) external returns (bool success, uint256 gasUsed) {
if (msg.sender != address(s_registry)) revert();
if (msg.sender != address(s_registry)) revert InvalidCaller(msg.sender);

uint256 g1 = gasleft();
address target = i_target;
gasUsed = gasleft();

assembly {
let g := gas()
// Compute g -= PERFORM_GAS_CUSHION and check for underflow
Expand All @@ -52,18 +58,27 @@ contract ZKSyncAutomationForwarder {
if iszero(extcodesize(target)) {
revert(0, 0)
}
// call with exact gas
success := call(gasAmount, target, 0, add(data, 0x20), mload(data), 0, 0)
}
gasUsed = gasUsed - gasleft();

bytes memory returnData;
// solhint-disable-next-line avoid-low-level-calls
(success, returnData) = GAS_BOUND_CALLER.delegatecall{gas: gasAmount}(
abi.encodeWithSelector(IGasBoundCaller.gasBoundCall.selector, target, gasAmount, data)
);
uint256 pubdataGasSpent;
if (success) {
(, pubdataGasSpent) = abi.decode(returnData, (bytes, uint256));
}
gasUsed = g1 - gasleft() + pubdataGasSpent;
return (success, gasUsed);
}

function getTarget() external view returns (address) {
return i_target;
}

fallback() external {
// solhint-disable-next-line no-complex-fallback
fallback() external payable {
// copy to memory for assembly access
address logic = i_logic;
// copied directly from OZ's Proxy contract
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.19;

address constant GAS_BOUND_CALLER = address(0xc706EC7dfA5D4Dc87f29f859094165E8290530f5);

interface IGasBoundCaller {
function gasBoundCall(address _to, uint256 _maxTotalGas, bytes calldata _data) external payable;
}
10 changes: 10 additions & 0 deletions contracts/src/v0.8/automation/interfaces/zksync/ISystemContext.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.19;

ISystemContext constant SYSTEM_CONTEXT_CONTRACT = ISystemContext(address(0x800b));

interface ISystemContext {
function gasPrice() external view returns (uint256);
function gasPerPubdataByte() external view returns (uint256 gasPerPubdataByte);
function getCurrentPubdataSpent() external view returns (uint256 currentPubdataSpent);
}
Loading

0 comments on commit 9c240b6

Please sign in to comment.