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-11081: zksync automation test #13609

Closed
wants to merge 17 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 118 additions & 0 deletions contracts/src/v0.8/automation/AutomationZKSyncForwarder.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
// 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 gasprice);

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) {
pubdataUsed = p2 - p1;
}

uint256 gasPerPubdataByte = SYSTEM_CONTEXT_CONTRACT.gasPerPubdataByte();
uint256 g2 = gasleft();
gasUsed = g1 - g2;
emit GasDetails(pubdataUsed, gasPerPubdataByte, gasUsed, p1, p2, tx.gasprice);
return (success, gasUsed, pubdataUsed * gasPerPubdataByte);
}

/*
0x000000000000000000000000000000000000000000000000000000000000000a
0x0000000000000000000000000000000000000000000000000000000000000000
0x0000000000000000000000000000000000000000000000000000000000d89056
0x0000000000000000000000000000000000000000000000000000000000d093ec
*/
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())
}
}
}
}
27 changes: 27 additions & 0 deletions contracts/src/v0.8/automation/chains/ZKSyncModule.sol
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
16 changes: 16 additions & 0 deletions contracts/src/v0.8/automation/testhelpers/MockFeed.sol
Original file line number Diff line number Diff line change
@@ -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;
}
}
81 changes: 81 additions & 0 deletions contracts/src/v0.8/automation/testhelpers/ZKSyncHashTester.sol
Original file line number Diff line number Diff line change
@@ -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;
}
}
101 changes: 101 additions & 0 deletions contracts/src/v0.8/automation/testhelpers/ZKSyncStoreTester.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// 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 data0;
bytes public data1;
bytes public data2;
bytes public data3;
bytes public data4;
bytes public data5;
bytes public full = hex"ffffffff0f0ff0fffff0fffffff0fffffffffff0fffff0ffffff0ffffffffffff00000fffffffffffff000fffffffffffffffffffffffff0ffffffffff0fffffffffffffffffff00fffffffffff00ffffffffffffffffffffffff000000fffffffffffffffffffffffffffffffffff0ffffffffffffffff0fffff0ffffffff";
bytes public constant empty = hex"00";

constructor() {
testRange = 10000;
interval = 200;
previousPerformBlock = 0;
lastTimestamp = block.timestamp;
initialTimestamp = 0;
counter = 0;

iterations = 1;
}

function storeData() public {
for (uint32 i = 0; i < iterations; i++) {
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;
}

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;
}
}
Loading
Loading