Skip to content

Commit d41ac43

Browse files
authored
Merge pull request #11 from init4tech/anna/multiple-ru-passage
feat: improve Zenith Passage
2 parents 0c440b3 + 4b3966d commit d41ac43

File tree

7 files changed

+64
-26
lines changed

7 files changed

+64
-26
lines changed

.gas-snapshot

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
HelpersTest:test_signature() (gas: 6587)
2+
ZenithTest:test_badSequence() (gas: 77336)
3+
ZenithTest:test_badSignature() (gas: 66901)
4+
ZenithTest:test_blockExpired() (gas: 55457)
5+
ZenithTest:test_notSequencer() (gas: 58632)
6+
ZenithTest:test_onePerBlock() (gas: 121189)
7+
ZenithTest:test_submitBlock() (gas: 88201)

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ cache/
33
out/
44

55
# Ignores development broadcast logs
6-
!/broadcast
6+
broadcast/*
77
/broadcast/*/31337/
88
/broadcast/**/dry-run/
99

script/Zenith.s.sol

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ import {Script} from "forge-std/Script.sol";
55
import {Zenith} from "../src/Zenith.sol";
66

77
contract DeployZenith is Script {
8-
// deploy:
8+
// deploy:
99
// forge script DeployZenith --sig "run()" --rpc-url $RPC_URL --etherscan-api-key $ETHERSCAN_API_KEY --private-key $PRIVATE_KEY --broadcast --verify
1010
function run() public {
1111
vm.broadcast();
12-
new Zenith(msg.sender);
12+
new Zenith(block.chainid + 1, msg.sender);
1313
}
1414
}

src/Passage.sol

Lines changed: 48 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,23 @@ import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
77
/// @notice A contract deployed to Host chain that allows tokens to enter the rollup,
88
/// and enables Builders to fulfill requests to exchange tokens on the Rollup for tokens on the Host.
99
contract HostPassage {
10+
/// @notice The chainId of the default rollup chain.
11+
uint256 immutable defaultRollupChainId;
12+
1013
/// @notice Thrown when attempting to fulfill an exit order with a deadline that has passed.
1114
error OrderExpired();
1215

1316
/// @notice Emitted when tokens enter the rollup.
1417
/// @param token - The address of the token entering the rollup.
1518
/// @param rollupRecipient - The recipient of the token on the rollup.
1619
/// @param amount - The amount of the token entering the rollup.
17-
event Enter(address indexed token, address indexed rollupRecipient, uint256 amount);
20+
event Enter(uint256 rollupChainId, address indexed token, address indexed rollupRecipient, uint256 amount);
1821

1922
/// @notice Emitted when an exit order is fulfilled by the Builder.
2023
/// @param token - The address of the token transferred to the recipient.
2124
/// @param hostRecipient - The recipient of the token on host.
2225
/// @param amount - The amount of the token transferred to the recipient.
23-
event ExitFilled(address indexed token, address indexed hostRecipient, uint256 amount);
26+
event ExitFilled(uint256 rollupChainId, address indexed token, address indexed hostRecipient, uint256 amount);
2427

2528
/// @notice Details of an exit order to be fulfilled by the Builder.
2629
/// @param token - The address of the token to be transferred to the recipient.
@@ -30,37 +33,56 @@ contract HostPassage {
3033
/// Corresponds to recipient_H in the RollupPassage contract.
3134
/// @param amount - The amount of the token to be transferred to the recipient.
3235
/// Corresponds to one or more amountOutMinimum_H in the RollupPassage contract.
33-
/// @param deadline - The deadline by which the exit order must be fulfilled.
34-
/// Corresponds to deadline in the RollupPassage contract.
35-
/// If the ExitOrder is a combination of multiple orders, the deadline SHOULD be the latest of all orders.
3636
struct ExitOrder {
37+
uint256 rollupChainId;
3738
address token;
3839
address recipient;
3940
uint256 amount;
40-
uint256 deadline;
41+
}
42+
43+
constructor(uint256 _defaultRollupChainId) {
44+
defaultRollupChainId = _defaultRollupChainId;
4145
}
4246

4347
/// @notice Allows native Ether to enter the rollup by being sent directly to the contract.
4448
fallback() external payable {
45-
enter(msg.sender);
49+
enter(defaultRollupChainId, msg.sender);
4650
}
4751

4852
/// @notice Allows native Ether to enter the rollup by being sent directly to the contract.
4953
receive() external payable {
50-
enter(msg.sender);
54+
enter(defaultRollupChainId, msg.sender);
5155
}
5256

5357
/// @notice Allows native Ether to enter the rollup.
5458
/// @dev Permanently burns the entire msg.value by locking it in this contract.
59+
/// @param rollupChainId - The rollup chain to enter.
60+
/// @param rollupRecipient - The recipient of the Ether on the rollup.
61+
/// @custom:emits Enter indicating the amount of Ether to mint on the rollup & its recipient.
62+
function enter(uint256 rollupChainId, address rollupRecipient) public payable {
63+
emit Enter(rollupChainId, address(0), rollupRecipient, msg.value);
64+
}
65+
66+
/// @notice Allows ERC20s to enter the rollup.
67+
/// @dev Permanently burns the token amount by locking it in this contract.
68+
/// @param rollupChainId - The rollup chain to enter.
5569
/// @param rollupRecipient - The recipient of the Ether on the rollup.
56-
/// @custom:emits Enter indicatig the amount of Ether to mint on the rollup & its recipient.
57-
function enter(address rollupRecipient) public payable {
58-
emit Enter(address(0), rollupRecipient, msg.value);
70+
/// @param token - The address of the ERC20 token on the Host.
71+
/// @param amount - The amount of the ERC20 token to transfer to the rollup.
72+
/// @custom:emits Enter indicating the amount of tokens to mint on the rollup & its recipient.
73+
function enter(uint256 rollupChainId, address rollupRecipient, address token, uint256 amount) public payable {
74+
IERC20(token).transferFrom(msg.sender, address(this), amount);
75+
emit Enter(rollupChainId, token, rollupRecipient, amount);
5976
}
6077

6178
/// @notice Fulfills exit orders by transferring tokenOut to the recipient
6279
/// @param orders The exit orders to fulfill
6380
/// @custom:emits ExitFilled for each exit order fulfilled.
81+
/// @dev Builder SHOULD call `filfillExits` atomically with `submitBlock`.
82+
/// Builder SHOULD set a block expiration time that is AT MOST the minimum of all exit order deadlines;
83+
/// this way, `fulfillExits` + `submitBlock` will revert atomically on mainnet if any exit orders have expired.
84+
/// Otherwise, `filfillExits` may mine on mainnet, while `submitExit` reverts on the rollup,
85+
/// and the Builder can't collect the corresponding value on the rollup.
6486
/// @dev Called by the Builder atomically with a transaction calling `submitBlock`.
6587
/// The user-submitted transactions initiating the ExitOrders on the rollup
6688
/// must be included by the Builder in the rollup block submitted via `submitBlock`.
@@ -72,15 +94,21 @@ contract HostPassage {
7294
/// the Builder may transfer the cumulative tokenOut to the user in a single ExitFilled event.
7395
/// The rollup STF will apply the user's exit transactions on the rollup up to the point that sum(tokenOut) is lte the ExitFilled amount.
7496
/// TODO: add option to fulfill ExitOrders with native ETH? or is it sufficient to only allow users to exit via WETH?
75-
function fulfillExits(ExitOrder[] calldata orders) external {
97+
function fulfillExits(ExitOrder[] calldata orders) external payable {
98+
uint256 ethRemaining = msg.value;
7699
for (uint256 i = 0; i < orders.length; i++) {
77-
ExitOrder memory order = orders[i];
78-
// check that the deadline hasn't passed
79-
if (block.timestamp > order.deadline) revert OrderExpired();
80-
// transfer tokens to the recipient
81-
IERC20(order.token).transferFrom(msg.sender, order.recipient, order.amount);
100+
// transfer value
101+
if (orders[i].token == address(0)) {
102+
// transfer native Ether to the recipient
103+
payable(orders[i].recipient).transfer(orders[i].amount);
104+
// NOTE: this will underflow if sender attempts to transfer more Ether than they sent to the contract
105+
ethRemaining -= orders[i].amount;
106+
} else {
107+
// transfer tokens to the recipient
108+
IERC20(orders[i].token).transferFrom(msg.sender, orders[i].recipient, orders[i].amount);
109+
}
82110
// emit
83-
emit ExitFilled(order.token, order.recipient, order.amount);
111+
emit ExitFilled(orders[i].rollupChainId, orders[i].token, orders[i].recipient, orders[i].amount);
84112
}
85113
}
86114
}
@@ -154,7 +182,7 @@ contract RollupPassage {
154182
/// @param amountOutMinimum_H - The minimum amount of tokenOut_H the user expects to receive on host.
155183
/// @custom:reverts Expired if the deadline has passed.
156184
/// @custom:emits Exit if the exit transaction succeeds.
157-
function submitExit(address tokenOut_H, address recipient_H, uint256 deadline, uint256 amountOutMinimum_H)
185+
function submitEthExit(address tokenOut_H, address recipient_H, uint256 deadline, uint256 amountOutMinimum_H)
158186
external
159187
payable
160188
{
@@ -183,7 +211,7 @@ contract RollupPassage {
183211
/// @dev Called by the Builder within the same block as `submitExit` transactions to claim the amounts of native Ether.
184212
/// @dev Builder MUST ensure that no other account calls `sweepETH` before them.
185213
/// @param recipient - The address to receive the native Ether.
186-
function sweepETH(address payable recipient) public {
214+
function sweepEth(address payable recipient) public {
187215
recipient.transfer(address(this).balance);
188216
emit Sweep(recipient);
189217
}

src/Zenith.sol

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,10 @@ contract Zenith is HostPassage, AccessControlDefaultAdminRules {
6262
/// - Admin role can grant and revoke Sequencer roles.
6363
/// - Admin role can be transferred via two-step process with a 1 day timelock.
6464
/// @param admin - the address that will be the initial admin.
65-
constructor(address admin) AccessControlDefaultAdminRules(1 days, admin) {}
65+
constructor(uint256 defaultRollupChainId, address admin)
66+
HostPassage(defaultRollupChainId)
67+
AccessControlDefaultAdminRules(1 days, admin)
68+
{}
6669

6770
/// @notice Submit a rollup block with block data submitted via calldata.
6871
/// @dev Blocks are submitted by Builders, with an attestation to the block data signed by a Sequencer.

test/Helpers.t.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ contract HelpersTest is Test {
99

1010
function setUp() public {
1111
vm.createSelectFork("https://rpc.holesky.ethpandaops.io");
12-
target = new Zenith(0x0a53e650c6f015eF70a15Da7B18fa95F051465aB);
12+
target = new Zenith(block.chainid + 1, 0x0a53e650c6f015eF70a15Da7B18fa95F051465aB);
1313
}
1414

1515
function test_signature() public {

test/Zenith.t.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ contract ZenithTest is Test {
1919
event BlockSubmitted(address indexed sequencer, Zenith.BlockHeader indexed header, bytes32 blockDataHash);
2020

2121
function setUp() public {
22-
target = new Zenith(address(this));
22+
target = new Zenith(block.chainid + 1, address(this));
2323
target.grantRole(target.SEQUENCER_ROLE(), vm.addr(sequencerKey));
2424

2525
// set default block values

0 commit comments

Comments
 (0)