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

feat: revise Order event interface #49

Merged
merged 7 commits into from
Jul 1, 2024
Merged
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
149 changes: 83 additions & 66 deletions src/Orders.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,103 +5,120 @@ 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.
/// @notice Emitted when an Order's Output is sent to the recipient.
/// @dev There may be multiple Outputs per Order.
/// @param originChainId - The chainId on which the Order was initiated.
/// @param recipient - The recipient of the token.
/// @param token - The address of the token transferred to the recipient. address(0) corresponds to native Ether.
/// @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.
event OutputFilled(uint256 indexed originChainId, address indexed recipient, address indexed token, uint256 amount);

/// @notice Send the Output(s) of an Order to fulfill it.
/// The user calls `initiate` on a rollup; the Builder calls `fill` on the target chain for each Output.
/// @custom:emits OutputFilled
/// @param originChainId - The chainId on which the Order was initiated.
/// @param recipient - The recipient of the token.
/// @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 {
function fill(uint256 originChainId, address recipient, address token, 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);
emit OutputFilled(originChainId, recipient, token, 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.
/// @notice Tokens sent by the swapper as inputs to the order
/// @dev From ERC-7683
struct Input {
/// @dev The address of the ERC20 token on the origin chain
address token;
/// @dev The amount of the token to be sent
uint256 amount;
}

/// @notice Tokens that must be receive for a valid order fulfillment
/// @dev From ERC-7683
struct Output {
/// @dev The address of the ERC20 token on the destination chain
/// @dev address(0) used as a sentinel for the native token
address token;
/// @dev The amount of the token to be sent
uint256 amount;
/// @dev The address to receive the output tokens
address recipient;
/// @dev The destination chain for this output
uint32 chainId;
}

/// @notice Thrown when an Order 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 Thrown when trying to call `sweep` if not the Builder of the block.
error OnlyBuilder();

/// @notice Emitted when an Order is submitted for fulfillment.
event Order(uint256 deadline, Input[] inputs, Output[] outputs);

/// @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);
event Sweep(address indexed recipient, address indexed token, 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 {
/// @dev inputs are provided on the rollup; in exchange,
/// outputs are expected to be received on the target chain(s).
/// @dev Fees paid to the Builders for fulfilling the Orders
/// can be included within the "exchange rate" between inputs and outputs.
/// @dev The Builder claims the inputs from the contract by submitting `sweep` transactions within the same block.
/// @dev The Rollup STF MUST NOT apply `initiate` transactions to the rollup state
/// UNLESS the outputs are delivered on the target chains within the same block.
/// @param deadline - The deadline by which the Order must be fulfilled.
/// @param inputs - The token amounts offered by the swapper in exchange for the outputs.
/// @param outputs - The token amounts that must be received on their target chain(s) in order for the Order to be executed.
/// @custom:reverts OrderExpired if the deadline has passed.
/// @custom:emits Order if the transaction mines.
function initiate(uint256 deadline, Input[] memory inputs, Output[] memory outputs) 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);
}
// transfer inputs to this contract
_transferInputs(inputs);

// emit
emit Order(deadline, inputs, outputs);
}

// emit the swap event
emit Swap(targetChainId, tokenIn, tokenOut, recipient, deadline, amountIn, amountOut);
/// @notice Transfer the Order inputs to this contract, where they can be collected by the Order filler.
function _transferInputs(Input[] memory inputs) internal {
uint256 value = msg.value;
for (uint256 i; i < inputs.length; i++) {
if (inputs[i].token == address(0)) {
// this line should underflow if there's an attempt to spend more ETH than is attached to the transaction
value -= inputs[i].amount;
} else {
IERC20(inputs[i].token).transferFrom(msg.sender, address(this), inputs[i].amount);
}
}
}

/// @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.
/// @dev Called by the Builder within the same block as users' `initiate` transactions
/// to claim the `inputs`.
/// @dev Builder MUST call `sweep` atomically with `fill` (claim Inputs atomically with sending Outputs).
/// @param recipient - The address to receive the tokens.
function sweep(address token, address recipient) public {
/// @param token - The token to transfer.
/// @custom:emits Sweep
/// @custom:reverts OnlyBuilder if called by non-block builder
function sweep(address recipient, address token) public {
anna-carroll marked this conversation as resolved.
Show resolved Hide resolved
if (msg.sender != block.coinbase) revert OnlyBuilder();
// send ETH or tokens
uint256 balance;
if (token == address(0)) {
balance = address(this).balance;
Expand All @@ -110,7 +127,7 @@ abstract contract OrderOrigin {
balance = IERC20(token).balanceOf(address(this));
IERC20(token).transfer(recipient, balance);
}
emit Sweep(token, recipient, balance);
emit Sweep(recipient, token, balance);
}
}

Expand Down