Skip to content

feat: transact gas limits #56

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

Closed
wants to merge 2 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
45 changes: 23 additions & 22 deletions .gas-snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -14,25 +14,26 @@ OrdersTest:test_sweepERC20() (gas: 60402)
OrdersTest:test_sweepETH() (gas: 81940)
OrdersTest:test_underflowETH() (gas: 63528)
PassageTest:test_configureEnter() (gas: 82311)
PassageTest:test_disallowedEnter() (gas: 17916)
PassageTest:test_enter() (gas: 25563)
PassageTest:test_enterToken() (gas: 64332)
PassageTest:test_enterToken_defaultChain() (gas: 62915)
PassageTest:test_enterTransact() (gas: 60890)
PassageTest:test_enter_defaultChain() (gas: 24033)
PassageTest:test_fallback() (gas: 21534)
PassageTest:test_onlyTokenAdmin() (gas: 16926)
PassageTest:test_receive() (gas: 21384)
PassageTest:test_setUp() (gas: 16968)
PassageTest:test_transact() (gas: 58562)
PassageTest:test_transact_defaultChain() (gas: 57475)
PassageTest:test_withdraw() (gas: 59033)
ZenithTest:test_addSequencer() (gas: 88121)
ZenithTest:test_badSignature() (gas: 37241)
ZenithTest:test_incorrectHostBlock() (gas: 35086)
ZenithTest:test_notSequencer() (gas: 34076)
ZenithTest:test_notSequencerAdmin() (gas: 10125)
ZenithTest:test_onePerBlock() (gas: 68193)
ZenithTest:test_removeSequencer() (gas: 39665)
ZenithTest:test_setUp() (gas: 8366)
ZenithTest:test_submitBlock() (gas: 63333)
PassageTest:test_disallowedEnter() (gas: 17938)
PassageTest:test_enter() (gas: 25507)
PassageTest:test_enterToken() (gas: 64354)
PassageTest:test_enterToken_defaultChain() (gas: 62870)
PassageTest:test_enter_defaultChain() (gas: 24011)
PassageTest:test_fallback() (gas: 21445)
PassageTest:test_onlyTokenAdmin() (gas: 16881)
PassageTest:test_receive() (gas: 21339)
PassageTest:test_setUp() (gas: 16901)
PassageTest:test_withdraw() (gas: 59055)
ZenithTest:test_addSequencer() (gas: 110340)
ZenithTest:test_badSignature() (gas: 37389)
ZenithTest:test_incorrectHostBlock() (gas: 35233)
ZenithTest:test_notSequencer() (gas: 34157)
ZenithTest:test_notSequencerAdmin() (gas: 10149)
ZenithTest:test_onePerBlock() (gas: 90513)
ZenithTest:test_removeSequencer() (gas: 39747)
ZenithTest:test_setUp() (gas: 8478)
ZenithTest:test_submitBlock() (gas: 85551)
ZenithTest:test_transact() (gas: 135881)
ZenithTest:test_transact_defaultChain() (gas: 126171)
ZenithTest:test_transact_globalGasLimit() (gas: 152687)
ZenithTest:test_transact_perTransactGasLimit() (gas: 106413)
2 changes: 1 addition & 1 deletion script/Zenith.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ contract ZenithScript is Script {
address sequencerAdmin
) public returns (Zenith z, Passage p, HostOrders m) {
vm.startBroadcast();
z = new Zenith(sequencerAdmin);
z = new Zenith(defaultRollupChainId, sequencerAdmin);
p = new Passage(defaultRollupChainId, withdrawalAdmin, initialEnterTokens);
m = new HostOrders();
}
Expand Down
59 changes: 0 additions & 59 deletions src/Passage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -36,17 +36,6 @@ contract Passage {
uint256 indexed rollupChainId, address indexed rollupRecipient, address indexed token, uint256 amount
);

/// @notice Emitted to send a special transaction to the rollup.
event Transact(
uint256 indexed rollupChainId,
address indexed sender,
address indexed to,
bytes data,
uint256 value,
uint256 gas,
uint256 maxFeePerGas
);

/// @notice Emitted when the admin withdraws tokens from the contract.
event Withdrawal(address indexed token, address indexed recipient, uint256 amount);

Expand Down Expand Up @@ -106,54 +95,6 @@ contract Passage {
enterToken(defaultRollupChainId, rollupRecipient, token, amount);
}

/// @notice Allows a special transaction to be sent to the rollup with sender == L1 msg.sender.
/// @dev Transaction is processed after normal rollup block execution.
/// @dev See `enterTransact` for docs.
function transact(
uint256 rollupChainId,
address to,
bytes calldata data,
uint256 value,
uint256 gas,
uint256 maxFeePerGas
) public payable {
enterTransact(rollupChainId, msg.sender, to, data, value, gas, maxFeePerGas);
}

/// @dev See `transact` for docs.
function transact(address to, bytes calldata data, uint256 value, uint256 gas, uint256 maxFeePerGas)
external
payable
{
enterTransact(defaultRollupChainId, msg.sender, to, data, value, gas, maxFeePerGas);
}

/// @notice Send Ether on the rollup, send a special transaction to be sent to the rollup with sender == L1 msg.sender.
/// @dev Enter and Transact are processed after normal rollup block execution.
/// @dev See `enter` for Enter docs.
/// @param rollupChainId - The rollup chain to send the transaction to.
/// @param etherRecipient - The recipient of the ether.
/// @param to - The address to call on the rollup.
/// @param data - The data to send to the rollup.
/// @param value - The amount of Ether to send on the rollup.
/// @param gas - The gas limit for the transaction.
/// @param maxFeePerGas - The maximum fee per gas for the transaction (per EIP-1559).
/// @custom:emits Transact indicating the transaction to mine on the rollup.
function enterTransact(
Copy link
Contributor Author

@anna-carroll anna-carroll Jul 2, 2024

Choose a reason for hiding this comment

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

note: moving transact to Zenith && splitting Passage from Zenith means that EOAs can no longer atomically enter + transact

Copy link
Member

Choose a reason for hiding this comment

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

this seems bad

Copy link
Contributor Author

@anna-carroll anna-carroll Jul 2, 2024

Choose a reason for hiding this comment

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

the only ways around this would be

  1. keep Passage & Zenith deployed at the same address (as we discussed, makes potential migration path worse)
  2. call Passage.enter as an external contract (i find this to be undesirable but it could be done)

Copy link
Member

Choose a reason for hiding this comment

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

call Passage.enter as an external contract (i find this to be undesirable but it could be done)

seems fine

uint256 rollupChainId,
address etherRecipient,
address to,
bytes calldata data,
uint256 value,
uint256 gas,
uint256 maxFeePerGas
) public payable {
// if msg.value is attached, Enter
enter(rollupChainId, etherRecipient);
// emit Transact event
emit Transact(rollupChainId, msg.sender, to, data, value, gas, maxFeePerGas);
}

/// @notice Alow/Disallow a given ERC20 token to enter the rollup.
function configureEnter(address token, bool _canEnter) external {
if (msg.sender != tokenAdmin) revert OnlyTokenAdmin();
Expand Down
74 changes: 72 additions & 2 deletions src/Zenith.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ pragma solidity ^0.8.24;
import {Passage} from "./Passage.sol";

contract Zenith {
/// @notice Each `transact` call cannot use more than 1/5 of the global `transact` gasLimit for the block.
uint256 public constant PER_TRANSACT_GAS_LIMIT = 5;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

still need to align on parameterization of this constant

Copy link
Member

Choose a reason for hiding this comment

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

MAX_TRANSACTS_PER_BLOCK

Copy link
Contributor Author

@anna-carroll anna-carroll Jul 2, 2024

Choose a reason for hiding this comment

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

well, it wouldn't be max transacts per block right

if this is set at 5, you could still have 10 transacts in the block if they use less than the limit (which i think we expect)

but yeah.. we should rename this var

Copy link
Contributor Author

@anna-carroll anna-carroll Jul 2, 2024

Choose a reason for hiding this comment

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

when I said "need to align on parameterization" above I meant what should we set this as - 5? 3? 2? i think not more than 5.


/// @notice The chainId of rollup that Ether will be sent to by default when entering the rollup via fallback() or receive().
uint256 public immutable defaultRollupChainId;

/// @notice The address that is allowed to set/remove sequencers.
address public immutable sequencerAdmin;

Expand All @@ -27,9 +33,18 @@ contract Zenith {
}

/// @notice The host block number that a block was last submitted at for a given rollup chainId.
/// rollupChainId => host blockNumber that block was last submitted at
/// rollupChainId => host blockNumber the last block was submitted at.
mapping(uint256 => uint256) public lastSubmittedAtBlock;

/// @notice The gasLimit of the last submitted block for a given rollup chainId.
/// @dev NOTE that this can change mid-block, causing some `transact` to have a different limit than others.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

starting at this WIP code to figure the best way to fix this (or if it's necessary)

Copy link
Contributor Author

@anna-carroll anna-carroll Jul 2, 2024

Choose a reason for hiding this comment

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

consider the following set of host chain transactions within a single block:

[transact, transact, submitBlock, transact]

The first two transacts will have the previous RU block's gasLimit; the last transact will have the current RU block's gasLimit

i wonder if this could cause any weird or unexpected behavior?

Copy link
Member

Choose a reason for hiding this comment

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

that's not necessarily the case, you'd just have to store the last block's limit the first time a transact occurs. bears more discussion tho

/// rollupChainId => gasLimit of the previous block.
mapping(uint256 => uint256) public currentGasLimit;

/// @notice The total gasLimit used by `transact` so far in this block.
/// rollupChainId => block number => `transasct` gasLimit used so far.
mapping(uint256 => mapping(uint256 => uint256)) public transactGasUsed;

/// @notice Registry of permissioned sequencers.
/// address => TRUE if it's a permissioned sequencer
mapping(address => bool) public isSequencer;
Expand All @@ -45,6 +60,12 @@ contract Zenith {
/// @notice Thrown when attempting to submit more than one rollup block per host block
error OneRollupBlockPerHostBlock();

/// @notice Thrown when attempting to use more then the current global `transact` gasLimit for the block.
error GlobalTransactGasLimitReached(uint256 globalTransactLimit);

/// @notice Thrown when attempting to use too much gas per single `transact` call.
error PerTransactGasLimitReached(uint256 perTransactLimit);

/// @notice Thrown when attempting to modify sequencer roles if not sequencerAdmin.
error OnlySequencerAdmin();

Expand All @@ -63,10 +84,22 @@ contract Zenith {
bytes32 blockDataHash
);

/// @notice Emitted to send a special transaction to the rollup.
event Transact(
uint256 indexed rollupChainId,
address indexed sender,
address indexed to,
bytes data,
uint256 value,
uint256 gas,
uint256 maxFeePerGas
);

/// @notice Emitted when a sequencer is added or removed.
event SequencerSet(address indexed sequencer, bool indexed permissioned);

constructor(address _sequencerAdmin) {
constructor(uint256 _defaultRollupChainId, address _sequencerAdmin) {
defaultRollupChainId = _defaultRollupChainId;
sequencerAdmin = _sequencerAdmin;
deployBlockNumber = block.number;
}
Expand Down Expand Up @@ -118,13 +151,50 @@ contract Zenith {
// assert this is the first rollup block submitted for this host block
if (lastSubmittedAtBlock[header.rollupChainId] == block.number) revert OneRollupBlockPerHostBlock();
lastSubmittedAtBlock[header.rollupChainId] = block.number;
currentGasLimit[header.rollupChainId] = header.gasLimit;

// emit event
emit BlockSubmitted(
sequencer, header.rollupChainId, header.gasLimit, header.rewardAddress, header.blockDataHash
);
}

/// @dev See `transact` for docs.
function transact(address to, bytes calldata data, uint256 value, uint256 gas, uint256 maxFeePerGas) external {
transact(defaultRollupChainId, to, data, value, gas, maxFeePerGas);
}

/// @notice Send a special transaction to be sent to the rollup with sender == L1 msg.sender.
/// @dev Transact is processed after normal rollup block execution.
/// @param rollupChainId - The rollup chain to send the transaction to.
/// @param to - The address to call on the rollup.
/// @param data - The data to send to the rollup.
/// @param value - The amount of Ether to send on the rollup.
/// @param gas - The gas limit for the transaction.
/// @param maxFeePerGas - The maximum fee per gas for the transaction (per EIP-1559).
/// @custom:emits Transact indicating the transaction to mine on the rollup.
function transact(
uint256 rollupChainId,
address to,
bytes calldata data,
uint256 value,
uint256 gas,
uint256 maxFeePerGas
) public {
// ensure per-transact gas limit is respected
uint256 globalLimit = currentGasLimit[rollupChainId];
uint256 perTransactLimit = globalLimit / PER_TRANSACT_GAS_LIMIT;
if (gas > perTransactLimit) revert PerTransactGasLimitReached(perTransactLimit);

// ensure global transact gas limit is respected
uint256 gasUsed = transactGasUsed[rollupChainId][block.number];
if (gasUsed + gas > globalLimit) revert GlobalTransactGasLimitReached(globalLimit);
transactGasUsed[rollupChainId][block.number] = gasUsed + gas;

// emit Transact event
emit Transact(rollupChainId, msg.sender, to, data, value, gas, maxFeePerGas);
}

/// @notice Construct hash of block details that the sequencer signs.
/// @param header - the header information for the rollup block.
/// @return commit - the hash of the encoded block details.
Expand Down
2 changes: 1 addition & 1 deletion test/Helpers.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ contract HelpersTest is Test {

function setUp() public {
vm.createSelectFork("https://rpc.holesky.ethpandaops.io");
target = new Zenith(0x29403F107781ea45Bf93710abf8df13F67f2008f);
target = new Zenith(block.number + 1, 0x29403F107781ea45Bf93710abf8df13F67f2008f);
}

function check_signature() public {
Expand Down
46 changes: 0 additions & 46 deletions test/Passage.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,6 @@ contract PassageTest is Test {
address recipient = address(0x123);
uint256 amount = 200;

address to = address(0x01);
bytes data = abi.encodeWithSelector(ERC20.transfer.selector, recipient, amount);
uint256 value = 100;
uint256 gas = 10_000_000;
uint256 maxFeePerGas = 50;

uint256 tokenAdminKey = 123;

event Enter(uint256 indexed rollupChainId, address indexed rollupRecipient, uint256 amount);
Expand All @@ -28,16 +22,6 @@ contract PassageTest is Test {
uint256 indexed rollupChainId, address indexed rollupRecipient, address indexed token, uint256 amount
);

event Transact(
uint256 indexed rollupChainId,
address indexed sender,
address indexed to,
bytes data,
uint256 value,
uint256 gas,
uint256 maxFeePerGas
);

event Withdrawal(address indexed token, address indexed recipient, uint256 amount);

event EnterConfigured(address indexed token, bool indexed canEnter);
Expand Down Expand Up @@ -150,36 +134,6 @@ contract PassageTest is Test {
target.enterToken(recipient, token, amount);
}

function test_transact() public {
vm.expectEmit();
emit Transact(chainId, address(this), to, data, value, gas, maxFeePerGas);
target.transact(chainId, to, data, value, gas, maxFeePerGas);

vm.expectEmit();
emit Enter(chainId, address(this), amount);
target.transact{value: amount}(chainId, to, data, value, gas, maxFeePerGas);
}

function test_transact_defaultChain() public {
vm.expectEmit();
emit Transact(target.defaultRollupChainId(), address(this), to, data, value, gas, maxFeePerGas);
target.transact(to, data, value, gas, maxFeePerGas);

vm.expectEmit();
emit Enter(target.defaultRollupChainId(), address(this), amount);
target.transact{value: amount}(to, data, value, gas, maxFeePerGas);
}

function test_enterTransact() public {
vm.expectEmit();
emit Transact(chainId, address(this), to, data, value, gas, maxFeePerGas);
target.enterTransact(chainId, recipient, to, data, value, gas, maxFeePerGas);

vm.expectEmit();
emit Enter(chainId, recipient, amount);
target.enterTransact{value: amount}(chainId, recipient, to, data, value, gas, maxFeePerGas);
}

function test_withdraw() public {
TestERC20(token).mint(address(target), amount);

Expand Down
Loading