Skip to content

refactor: split Passage enter/exit from Orders #41

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

Merged
merged 8 commits into from
Jun 10, 2024
Merged
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
12 changes: 6 additions & 6 deletions .gas-snapshot
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
ZenithTest:test_badSequence() (gas: 60188)
ZenithTest:test_badSignature() (gas: 66804)
ZenithTest:test_blockExpired() (gas: 55419)
ZenithTest:test_notSequencer() (gas: 58524)
ZenithTest:test_onePerBlock() (gas: 104196)
ZenithTest:test_submitBlock() (gas: 88425)
ZenithTest:test_badSequence() (gas: 60199)
ZenithTest:test_badSignature() (gas: 66867)
ZenithTest:test_blockExpired() (gas: 55430)
ZenithTest:test_notSequencer() (gas: 58572)
ZenithTest:test_onePerBlock() (gas: 104311)
ZenithTest:test_submitBlock() (gas: 88457)
11 changes: 10 additions & 1 deletion script/Zenith.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,25 @@ pragma solidity ^0.8.24;

import {Script} from "forge-std/Script.sol";
import {Zenith} from "../src/Zenith.sol";
import {HostOrders, RollupOrders} from "../src/Orders.sol";

contract ZenithScript is Script {
// deploy:
// forge script ZenithScript --sig "deploy(uint256,address,address)" --rpc-url $RPC_URL --etherscan-api-key $ETHERSCAN_API_KEY --private-key $PRIVATE_KEY --broadcast --verify $ROLLUP_CHAIN_ID $WITHDRAWAL_ADMIN_ADDRESS $SEQUENCER_ADMIN_ADDRESS
function deploy(uint256 defaultRollupChainId, address withdrawalAdmin, address sequencerAdmin)
public
returns (Zenith z)
returns (Zenith z, HostOrders m)
{
vm.startBroadcast();
z = new Zenith(defaultRollupChainId, withdrawalAdmin, sequencerAdmin);
m = new HostOrders();
}

// deploy:
// forge script ZenithScript --sig "deployL2()" --rpc-url $L2_RPC_URL --private-key $PRIVATE_KEY --broadcast $ZENITH_ADDRESS
function deployL2(address zenith) public returns (RollupOrders m) {
vm.startBroadcast();
m = new RollupOrders();
}

// NOTE: script must be run using SequencerAdmin key
Expand Down
119 changes: 119 additions & 0 deletions src/Orders.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.24;

import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";

/// @notice Contract capable of processing fulfillment of intent-based Orders.
abstract contract OrderDestination {
/// @notice Emitted when an swap order is fulfilled by the Builder.
/// @param originChainId - The chainId on which the swap order was submitted.
/// @param token - The address of the token transferred to the recipient. address(0) corresponds to native Ether.
/// @param recipient - The recipient of the token.
/// @param amount - The amount of the token transferred to the recipient.
event SwapFulfilled(
uint256 indexed originChainId, address indexed token, address indexed recipient, uint256 amount
);

/// @notice Fulfill a rollup Swap order.
/// The user calls `swap` on a rollup; the Builder calls `fulfillSwap` on the target chain.
/// @custom:emits SwapFulfilled
/// @param originChainId - The chainId of the rollup on which `swap` was called.
/// @param token - The address of the token to be transferred to the recipient.
/// address(0) corresponds to native Ether.
/// @param recipient - The recipient of the token.
/// @param amount - The amount of the token to be transferred to the recipient.
function fulfillSwap(uint256 originChainId, address token, address recipient, uint256 amount) external payable {
if (token == address(0)) {
require(amount == msg.value);
payable(recipient).transfer(msg.value);
} else {
IERC20(token).transferFrom(msg.sender, recipient, amount);
}
emit SwapFulfilled(originChainId, token, recipient, amount);
}
}

/// @notice Contract capable of registering initiation of intent-based Orders.
abstract contract OrderOrigin {
/// @notice Thrown when an swap transaction is submitted with a deadline that has passed.
error OrderExpired();

/// @notice Emitted when an swap order is successfully processed, indicating it was also fulfilled on the target chain.
/// @dev See `swap` for parameter docs.
event Swap(
uint256 indexed targetChainId,
address indexed tokenIn,
address indexed tokenOut,
address recipient,
uint256 deadline,
uint256 amountIn,
uint256 amountOut
);

/// @notice Emitted when tokens or native Ether is swept from the contract.
/// @dev Intended to improve visibility for Builders to ensure Sweep isn't called unexpectedly.
/// Intentionally does not bother to emit which token(s) were swept, nor their amounts.
event Sweep(address indexed token, address indexed recipient, uint256 amount);

/// @notice Request to swap ERC20s.
/// @dev tokenIn is provided on the rollup; in exchange,
/// tokenOut is expected to be received on targetChainId.
/// @dev targetChainId may be the current chainId, the Host chainId, or..
/// @dev Fees paid to the Builders for fulfilling the swap orders
/// can be included within the "exchange rate" between tokenIn and tokenOut.
/// @dev The Builder claims the tokenIn from the contract by submitting a transaction to `sweep` the tokens within the same block.
/// @dev The Rollup STF MUST NOT apply `swap` transactions to the rollup state
/// UNLESS a sufficient SwapFulfilled event is emitted on the target chain within the same block.
/// @param targetChainId - The chain on which tokens should be output.
/// @param tokenIn - The address of the token the user supplies as the input on the rollup for the trade.
/// @param tokenOut - The address of the token the user expects to receive on the target chain.
/// @param recipient - The address of the recipient of tokenOut on the target chain.
/// @param deadline - The deadline by which the swap order must be fulfilled.
/// @param amountIn - The amount of tokenIn the user supplies as the input on the rollup for the trade.
/// @param amountOut - The minimum amount of tokenOut the user expects to receive on the target chain.
/// @custom:reverts Expired if the deadline has passed.
/// @custom:emits Swap if the swap transaction succeeds.
function swap(
uint256 targetChainId,
address tokenIn,
address tokenOut,
address recipient,
uint256 deadline,
uint256 amountIn,
uint256 amountOut
) external payable {
// check that the deadline hasn't passed
if (block.timestamp >= deadline) revert OrderExpired();

if (tokenIn == address(0)) {
require(amountIn == msg.value);
} else {
IERC20(tokenIn).transferFrom(msg.sender, address(this), amountIn);
}

// emit the swap event
emit Swap(targetChainId, tokenIn, tokenOut, recipient, deadline, amountIn, amountOut);
}

/// @notice Transfer the entire balance of ERC20 tokens to the recipient.
/// @dev Called by the Builder within the same block as users' `swap` transactions
/// to claim the amounts of `tokenIn`.
/// @dev Builder MUST ensure that no other account calls `sweep` before them.
/// @param token - The token to transfer.
/// @param recipient - The address to receive the tokens.
function sweep(address token, address recipient) public {
uint256 balance;
if (token == address(0)) {
balance = address(this).balance;
payable(recipient).transfer(balance);
} else {
balance = IERC20(token).balanceOf(address(this));
IERC20(token).transfer(recipient, balance);
}
emit Sweep(token, recipient, balance);
}
}

contract HostOrders is OrderDestination {}

contract RollupOrders is OrderOrigin, OrderDestination {}
130 changes: 7 additions & 123 deletions src/Passage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,38 +3,9 @@ pragma solidity ^0.8.24;

import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";

contract BasePassage {
/// @notice Emitted when an swap order is fulfilled by the Builder.
/// @param originChainId - The chainId on which the swap order was submitted.
/// @param token - The address of the token transferred to the recipient. address(0) corresponds to native Ether.
/// @param recipient - The recipient of the token.
/// @param amount - The amount of the token transferred to the recipient.
event SwapFulfilled(
uint256 indexed originChainId, address indexed token, address indexed recipient, uint256 amount
);

/// @notice Fulfill a rollup Swap order.
/// The user calls `swap` on a rollup; the Builder calls `fulfillSwap` on the target chain.
/// @custom:emits SwapFulfilled
/// @param originChainId - The chainId of the rollup on which `swap` was called.
/// @param token - The address of the token to be transferred to the recipient.
/// address(0) corresponds to native Ether.
/// @param recipient - The recipient of the token.
/// @param amount - The amount of the token to be transferred to the recipient.
function fulfillSwap(uint256 originChainId, address token, address recipient, uint256 amount) external payable {
if (token == address(0)) {
require(amount == msg.value);
payable(recipient).transfer(msg.value);
} else {
IERC20(token).transferFrom(msg.sender, recipient, amount);
}
emit SwapFulfilled(originChainId, token, recipient, amount);
}
}

/// @notice A contract deployed to Host chain that allows tokens to enter the rollup,
/// and enables Builders to fulfill requests to exchange tokens on the Rollup for tokens on the Host.
contract Passage is BasePassage {
contract Passage {
/// @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;

Expand Down Expand Up @@ -71,24 +42,18 @@ contract Passage is BasePassage {
}

/// @notice Allows native Ether to enter the rollup.
/// @dev Permanently burns the entire msg.value by locking it in this contract.
/// @param rollupChainId - The rollup chain to enter.
/// @param rollupRecipient - The recipient of the Ether on the rollup.
/// @custom:emits Enter indicating the amount of Ether to mint on the rollup & its recipient.
function enter(uint256 rollupChainId, address rollupRecipient) public payable {
emit Enter(rollupChainId, address(0), rollupRecipient, msg.value);
function enter(address rollupRecipient) public payable {
enter(defaultRollupChainId, rollupRecipient);
}

/// @notice Allows ERC20s to enter the rollup.
/// @dev Permanently burns the token amount by locking it in this contract.
/// @notice Allows native Ether to enter the rollup.
/// @param rollupChainId - The rollup chain to enter.
/// @param rollupRecipient - The recipient of the Ether on the rollup.
/// @param token - The address of the ERC20 token on the Host.
/// @param amount - The amount of the ERC20 token to transfer to the rollup.
/// @custom:emits Enter indicating the amount of tokens to mint on the rollup & its recipient.
function enter(uint256 rollupChainId, address token, address rollupRecipient, uint256 amount) external payable {
IERC20(token).transferFrom(msg.sender, address(this), amount);
emit Enter(rollupChainId, token, rollupRecipient, amount);
/// @custom:emits Enter indicating the amount of Ether to mint on the rollup & its recipient.
function enter(uint256 rollupChainId, address rollupRecipient) public payable {
emit Enter(rollupChainId, address(0), rollupRecipient, msg.value);
}

/// @notice Allows the admin to withdraw ETH or ERC20 tokens from the contract.
Expand All @@ -103,84 +68,3 @@ contract Passage is BasePassage {
emit Withdrawal(token, recipient, amount);
}
}

/// @notice A contract deployed to the Rollup that allows users to atomically exchange tokens on the Rollup for tokens on the Host.
contract RollupPassage is BasePassage {
/// @notice Thrown when an swap transaction is submitted with a deadline that has passed.
error OrderExpired();

/// @notice Emitted when an swap order is successfully processed, indicating it was also fulfilled on the target chain.
/// @dev See `swap` for parameter docs.
event Swap(
uint256 indexed targetChainId,
address indexed tokenIn,
address indexed tokenOut,
address recipient,
uint256 deadline,
uint256 amountIn,
uint256 amountOut
);

/// @notice Emitted when tokens or native Ether is swept from the contract.
/// @dev Intended to improve visibility for Builders to ensure Sweep isn't called unexpectedly.
/// Intentionally does not bother to emit which token(s) were swept, nor their amounts.
event Sweep(address indexed token, address indexed recipient, uint256 amount);

/// @notice Request to swap ERC20s.
/// @dev tokenIn is provided on the rollup; in exchange,
/// tokenOut is expected to be received on targetChainId.
/// @dev targetChainId may be the current chainId, the Host chainId, or..
/// @dev Fees paid to the Builders for fulfilling the swap orders
/// can be included within the "exchange rate" between tokenIn and tokenOut.
/// @dev The Builder claims the tokenIn from the contract by submitting a transaction to `sweep` the tokens within the same block.
/// @dev The Rollup STF MUST NOT apply `swap` transactions to the rollup state
/// UNLESS a sufficient SwapFulfilled event is emitted on the target chain within the same block.
/// @param targetChainId - The chain on which tokens should be output.
/// @param tokenIn - The address of the token the user supplies as the input on the rollup for the trade.
/// @param tokenOut - The address of the token the user expects to receive on the target chain.
/// @param recipient - The address of the recipient of tokenOut on the target chain.
/// @param deadline - The deadline by which the swap order must be fulfilled.
/// @param amountIn - The amount of tokenIn the user supplies as the input on the rollup for the trade.
/// @param amountOut - The minimum amount of tokenOut the user expects to receive on the target chain.
/// @custom:reverts Expired if the deadline has passed.
/// @custom:emits Swap if the swap transaction succeeds.
function swap(
uint256 targetChainId,
address tokenIn,
address tokenOut,
address recipient,
uint256 deadline,
uint256 amountIn,
uint256 amountOut
) external payable {
// check that the deadline hasn't passed
if (block.timestamp >= deadline) revert OrderExpired();

if (tokenIn == address(0)) {
require(amountIn == msg.value);
} else {
IERC20(tokenIn).transferFrom(msg.sender, address(this), amountIn);
}

// emit the swap event
emit Swap(targetChainId, tokenIn, tokenOut, recipient, deadline, amountIn, amountOut);
}

/// @notice Transfer the entire balance of ERC20 tokens to the recipient.
/// @dev Called by the Builder within the same block as users' `swap` transactions
/// to claim the amounts of `tokenIn`.
/// @dev Builder MUST ensure that no other account calls `sweep` before them.
/// @param token - The token to transfer.
/// @param recipient - The address to receive the tokens.
function sweep(address token, address recipient) public {
uint256 balance;
if (token == address(0)) {
balance = address(this).balance;
payable(recipient).transfer(balance);
} else {
balance = IERC20(token).balanceOf(address(this));
IERC20(token).transfer(recipient, balance);
}
emit Sweep(token, recipient, balance);
}
}