Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

auto-10161: implement without zksync forwarder interface change #14037

Merged
merged 25 commits into from
Aug 22, 2024
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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',
)
49 changes: 25 additions & 24 deletions contracts/src/v0.8/automation/ZKSyncAutomationForwarder.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,20 @@
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;

/**
* @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);
event GasDetails(uint256 executionGas, uint256 gasPrice, uint256 pubdataGasSpent);

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

Expand All @@ -31,39 +35,36 @@ 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();
address target = i_target;
gasUsed = gasleft();
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)
if (msg.sender != address(s_registry)) revert InvalidCaller(msg.sender);

uint256 g1 = gasleft();
infiloop2 marked this conversation as resolved.
Show resolved Hide resolved
bytes memory returnData;
// solhint-disable-next-line avoid-low-level-calls
(success, returnData) = GAS_BOUND_CALLER.delegatecall{gas: gasAmount}(
abi.encodeWithSelector(IGasBoundCaller.gasBoundCall.selector, i_target, gasAmount, data)
);
uint256 pubdataGasSpent;
if (success) {
(, pubdataGasSpent) = abi.decode(returnData, (bytes, uint256));
}
uint256 executionGas = g1 - gasleft();
gasUsed = executionGas + pubdataGasSpent;
emit GasDetails(executionGas, tx.gasprice, pubdataGasSpent);
if (gasUsed > gasAmount) {
return (false, gasUsed);
Copy link
Contributor

@infiloop2 infiloop2 Aug 15, 2024

Choose a reason for hiding this comment

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

Will the pubData gas be still charged by zksync in this case, causing the whole tx to fail by running out of gas

}
gasUsed = gasUsed - gasleft();
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
15 changes: 15 additions & 0 deletions contracts/src/v0.8/automation/chains/ZKSyncModule.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.19;

import {ChainModuleBase} from "./ChainModuleBase.sol";

contract ZKSyncModule is ChainModuleBase {
function getGasOverhead()
external
pure
override
returns (uint256 chainModuleFixedOverhead, uint256 chainModulePerByteOverhead)
{
return (5_000, 0);
}
}
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;
}
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
Loading