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

Add order deletion #7

Merged
merged 6 commits into from
Aug 31, 2022
Merged
Show file tree
Hide file tree
Changes from 3 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
60 changes: 56 additions & 4 deletions src/CoWSwapEthFlow.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,20 @@
pragma solidity ^0.8;

import "./libraries/EthFlowOrder.sol";
import "./interfaces/ICoWSwapSettlement.sol";
import "./interfaces/ICoWSwapEthFlow.sol";
import "./mixins/CoWSwapOnchainOrders.sol";

/// @title CoW Swap ETH Flow
/// @author CoW Swap Developers
contract CoWSwapEthFlow is CoWSwapOnchainOrders, ICoWSwapEthFlow {
using EthFlowOrder for EthFlowOrder.Data;
using GPv2Order for GPv2Order.Data;
using GPv2Order for bytes;

/// @dev The address of the CoW Swap settlement contract that will be used to settle orders created by this
/// contract.
ICoWSwapSettlement public immutable cowSwapSettlement;

/// @dev The address of the contract representing the default native token in the current chain (e.g., WETH for
/// Ethereum mainnet).
Expand All @@ -21,11 +28,13 @@ contract CoWSwapEthFlow is CoWSwapOnchainOrders, ICoWSwapEthFlow {
/// onchain data.
mapping(bytes32 => EthFlowOrder.OnchainData) public orders;

/// @param _cowSwapSettlementContract The address of the CoW Swap settlement contract.
fedgiac marked this conversation as resolved.
Show resolved Hide resolved
/// @param _cowSwapSettlement The address of the CoW Swap settlement contract.
/// @param _wrappedNativeToken The address of the default native token in the current chain (e.g., WETH on mainnet).
constructor(address _cowSwapSettlementContract, IERC20 _wrappedNativeToken)
CoWSwapOnchainOrders(_cowSwapSettlementContract)
{
constructor(
ICoWSwapSettlement _cowSwapSettlement,
IERC20 _wrappedNativeToken
) CoWSwapOnchainOrders(address(_cowSwapSettlement)) {
cowSwapSettlement = _cowSwapSettlement;
wrappedNativeToken = _wrappedNativeToken;
}

Expand Down Expand Up @@ -68,4 +77,47 @@ contract CoWSwapEthFlow is CoWSwapOnchainOrders, ICoWSwapEthFlow {

orders[orderHash] = onchainData;
}

/// @inheritdoc ICoWSwapEthFlow
function deleteOrder(EthFlowOrder.Data calldata order) external {
GPv2Order.Data memory cowSwapOrder = order.toCoWSwapOrder(
wrappedNativeToken
);
bytes32 orderHash = cowSwapOrder.hash(cowSwapDomainSeparator);

EthFlowOrder.OnchainData memory orderData = orders[orderHash];

if (
orderData.owner == EthFlowOrder.INVALIDATED_OWNER ||
orderData.owner == EthFlowOrder.NO_OWNER ||
(// solhint-disable-next-line not-rely-on-time
orderData.validTo >= block.timestamp &&
orderData.owner != msg.sender)
) {
revert NotAllowedToDeleteOrder(orderHash);
}

orders[orderHash].owner = EthFlowOrder.INVALIDATED_OWNER;

bytes memory orderUid = new bytes(GPv2Order.UID_LENGTH);
orderUid.packOrderUidParams(
orderHash,
address(this),
cowSwapOrder.validTo
);
uint256 freedAmount = cowSwapOrder.sellAmount -
cowSwapSettlement.filledAmount(orderUid);

// Using low level calls to perform the transfer avoids setting arbitrary limits to the amount of gas used in a
// call. The drawback is allowing reentrancy from this point in the contract. Note that the current order was
// already invalidated and cannot be claimed again. TODO: ensure that this is robust under order updates when
// implemented.
// solhint-disable-next-line avoid-low-level-calls
(bool success, ) = payable(orderData.owner).call{value: freedAmount}(
josojo marked this conversation as resolved.
Show resolved Hide resolved
nlordell marked this conversation as resolved.
Show resolved Hide resolved
""
);
if (!success) {
revert EthTransferFailed();
}
}
}
12 changes: 12 additions & 0 deletions src/interfaces/ICoWSwapEthFlow.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ interface ICoWSwapEthFlow {
/// @dev Error thrown when trying to create an order without sending the expected amount of ETH to this contract.
error IncorrectEthAmount();

/// @dev Error thrown if trying to delete an order while not allowed.
error NotAllowedToDeleteOrder(bytes32 orderHash);

/// @dev Error thrown when unsuccessfully sending ETH to an address.
error EthTransferFailed();

/// @dev Function that creates and broadcasts an ETH flow order that sells native ETH. The order is paid for when
/// the caller sends out the transaction. The caller takes ownership of the new order.
///
Expand All @@ -23,4 +29,10 @@ interface ICoWSwapEthFlow {
external
payable
returns (bytes32 orderHash);

/// @dev Marks an existing ETH flow order as invalid and refunds the trader of all ETH that hasn't been traded yet.
/// Note that some parameters of the order are ignored, as for example the order expiration date and the quote id.
///
/// @param order The order to be deleted.
function deleteOrder(EthFlowOrder.Data calldata order) external;
}
16 changes: 16 additions & 0 deletions src/interfaces/ICoWSwapSettlement.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
pragma solidity ^0.8;

/// @title CoW Swap Settlement Contract Interface
/// @author CoW Swap Developers
/// @dev This interface collects the functions of the CoW Swap settlement contract that are used by the ETH flow
/// contract.
interface ICoWSwapSettlement {
/// @dev Map each user order by UID to the amount that has been filled so
/// far. If this amount is larger than or equal to the amount traded in the
/// order (amount sold for sell orders, amount bought for buy orders) then
/// the order cannot be traded anymore. If the order is fill or kill, then
/// this value is only used to determine whether the order has already been
/// executed.
function filledAmount(bytes memory orderUid) external returns (uint256);
}
4 changes: 4 additions & 0 deletions src/libraries/EthFlowOrder.sol
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ library EthFlowOrder {
/// @dev An order that is owned by this address is an order that has not yet been assigned.
address public constant NO_OWNER = address(0);

/// @dev An order that is owned by this address is an order that has been invalidated. Note that this address cannot
/// be directly used to create orders.
address public constant INVALIDATED_OWNER = address(type(uint160).max);
Copy link
Contributor

Choose a reason for hiding this comment

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

Why don't you set it to NO_OWNER?

Is it because the order is still existing in the settlement contract and hence a new order placement would fail? Probably deleting the old order is too expensive?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The worse that could happen is that the order was partially filled, the user cancels it, and then another completely different user creates an order with the same parameters. Then they would "inherit" the state of filledAmount, which would mean that (part of) their ETH is lost forever. (The same would happen for a fill-or-kill order that was cancelled, which is currently possible and returns 0 ETH.)
It's not possible to just clear the state of filledAmount, so I don't see a way around this. The most similar function is invalidateOrder in the settlement contract, but this just sets filledAmount to maxUint256.


/// @dev Error returned if the receiver of the ETH flow order is unspecified (`GPv2Order.RECEIVER_SAME_AS_OWNER`).
error ReceiverMustBeSet();

Expand Down
Loading