Skip to content

Commit

Permalink
feat: revise Order event interface (#49)
Browse files Browse the repository at this point in the history
* feat: revise Order event interface

* move structs into contract

* rename: OutputFilled

* fix: typo: initiate

* change Input/Output params to memory to make it easier for smart contracts to integrate

* fix: sweep only callable by block builder

* add originChainId back
  • Loading branch information
anna-carroll authored Jul 1, 2024
1 parent f4b5429 commit 5e32da8
Showing 1 changed file with 83 additions and 66 deletions.
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 {
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

0 comments on commit 5e32da8

Please sign in to comment.