Add comments

This commit is contained in:
Michael Zhu
2021-12-01 22:25:22 -08:00
parent 29892db0fd
commit 35f375a525
2 changed files with 189 additions and 13 deletions

View File

@@ -132,6 +132,7 @@ contract ERC721OrdersFeature is
payable
{
uint256 ethSpent = _buyERC721(order, signature, msg.value);
// Cannot spent more than `msg.value`
rrequire(
ethSpent <= msg.value,
LibERC721OrdersRichErrors.OverspentEthError(
@@ -139,6 +140,7 @@ contract ERC721OrdersFeature is
msg.value
)
);
// Refund
if (ethSpent < msg.value) {
_transferEth(msg.sender, msg.value - ethSpent);
}
@@ -159,6 +161,7 @@ contract ERC721OrdersFeature is
order.maker
)
);
// Mark order as cancelled
_setOrderStatusBit(order);
emit ERC721OrderCancelled(
@@ -194,6 +197,10 @@ contract ERC721OrdersFeature is
uint256 ethSpent = 0;
for (uint256 i = 0; i < orders.length; i++) {
bytes memory returnData;
// Delegatecall `_buyERC721` to track ETH consumption while
// preserving execution context.
// Note that `_buyERC721` is a public function but should _not_
// be registered in the Exchange Proxy.
(successes[i], returnData) = _implementation.delegatecall(
abi.encodeWithSelector(
this._buyERC721.selector,
@@ -206,8 +213,10 @@ contract ERC721OrdersFeature is
(uint256 _ethSpent) = abi.decode(returnData, (uint256));
ethSpent = ethSpent.safeAdd(_ethSpent);
} else if (revertIfIncomplete) {
// Bubble up revert
returnData.rrevert();
}
rrequire(
ethSpent <= msg.value,
LibERC721OrdersRichErrors.OverspentEthError(
@@ -216,6 +225,7 @@ contract ERC721OrdersFeature is
)
);
}
// Refund
if (ethSpent < msg.value) {
_transferEth(msg.sender, msg.value - ethSpent);
}
@@ -250,6 +260,7 @@ contract ERC721OrdersFeature is
sellOrder.erc721TokenId
);
// The ERC721 tokens must match
rrequire(
sellOrder.erc721Token == buyOrder.erc721Token,
LibERC721OrdersRichErrors.ERC721TokenMismatchError(
@@ -257,6 +268,9 @@ contract ERC721OrdersFeature is
address(buyOrder.erc721Token)
)
);
// The ERC20 tokens must match. Okay if the sell order specifies ETH
// and the buy order specifies WETH; we will unwrap the WETH before
// sending it to `sellOrder.maker`.
rrequire(
sellOrder.erc20Token == buyOrder.erc20Token ||
(
@@ -268,6 +282,8 @@ contract ERC721OrdersFeature is
address(buyOrder.erc20Token)
)
);
// The buyer must be willing to pay at least the amount that the
// seller is asking.
rrequire(
buyOrder.erc20TokenAmount >= sellOrder.erc20TokenAmount,
LibERC721OrdersRichErrors.NegativeSpreadError(
@@ -276,11 +292,14 @@ contract ERC721OrdersFeature is
)
);
// The difference in ERC20 token amounts is the spread.
uint256 spread = buyOrder.erc20TokenAmount - sellOrder.erc20TokenAmount;
// Mark both orders as filled.
_setOrderStatusBit(sellOrder);
_setOrderStatusBit(buyOrder);
// Transfer the ERC721 asset from seller to buyer.
_transferERC721AssetFrom(
sellOrder.erc721Token,
sellOrder.maker,
@@ -288,32 +307,74 @@ contract ERC721OrdersFeature is
sellOrder.erc721TokenId
);
// Handle the ERC20 side of the order:
if (
address(sellOrder.erc20Token) == NATIVE_TOKEN_ADDRESS &&
buyOrder.erc20Token == WETH
) {
// The sell order specifies ETH, while the buy order specifies WETH.
// The orders are still compatible with one another, but we'll have
// to unwrap the WETH on behalf of the buyer.
// Step 1: Transfer WETH from the buyer to the EP.
// Note that we transfer `buyOrder.erc20TokenAmount`, which
// is the amount the buyer signaled they are willing to pay
// for the ERC721 asset, which may be more than the seller's
// ask.
_transferERC20TokensFrom(
WETH,
buyOrder.maker,
address(this),
sellOrder.erc20TokenAmount
buyOrder.erc20TokenAmount
);
// Step 2: Unwrap the WETH into ETH. We unwrap the entire
// `buyOrder.erc20TokenAmount`.
// The ETH will be used for three purposes:
// - To pay the seller
// - To pay fees for the sell order
// - Any remaining ETH will be sent to
// `msg.sender` as profit.
WETH.withdraw(buyOrder.erc20TokenAmount);
// Step 3: Pay the seller (in ETH).
_transferEth(payable(sellOrder.maker), sellOrder.erc20TokenAmount);
// Step 4: Pay fees for the buy order. Note that these are paid
// in _WETH_ by the _buyer_. By signing the buy order, the
// buyer signals that they are willing to spend a total
// of `erc20TokenAmount` _plus_ fees, all denominated in
// the `erc20Token`, which in this case is WETH.
_payFees(buyOrder, buyOrder.maker, false);
// Step 5: Pay fees for the sell order. The `erc20Token` of the
// sell order is ETH, so the fees are paid out in ETH.
// There should be `spread` wei of ETH remaining in the
// EP at this point, which we will use ETH to pay the
// sell order fees.
uint256 sellOrderFees = _payFees(sellOrder, address(this), true);
// Step 6: The spread must be enough to cover the sell order fees.
// If not, either `_payFees` will have reverted, or we
// have spent ETH that was in the EP before this
// `matchERC721Orders` call, which we disallow.
rrequire(
spread >= sellOrderFees,
LibERC721OrdersRichErrors.SellOrderFeesExceedSpreadError(
sellOrderFees,
spread
)
);
);
// Step 7: The spread less the sell order fees is the amount of ETH
// remaining in the EP that can be sent to `msg.sender` as
// the profit from matching these two orders.
profit = spread - sellOrderFees;
if (profit > 0) {
_transferEth(msg.sender, profit);
}
} else {
// Step 1: Transfer the ERC20 token from the buyer to the seller.
// Note that we transfer `sellOrder.erc20TokenAmount`, which
// is at most `buyOrder.erc20TokenAmount`.
_transferERC20TokensFrom(
buyOrder.erc20Token,
buyOrder.maker,
@@ -321,15 +382,36 @@ contract ERC721OrdersFeature is
sellOrder.erc20TokenAmount
);
// Step 2: Pay fees for the buy order. Note that these are paid
// by the buyer. By signing the buy order, the buyer signals
// that they are willing to spend a total of
// `buyOrder.erc20TokenAmount` _plus_ `buyOrder.fees`.
_payFees(buyOrder, buyOrder.maker, false);
// Step 3: Pay fees for the sell order. These are paid by the buyer
// as well. After paying these fees, we may have taken more
// from the buyer than they agreed to in the buy order. If
// so, we revert in the following step.
uint256 sellOrderFees = _payFees(sellOrder, buyOrder.maker, false);
// Step 4: The spread must be enough to cover the sell order fees.
// If not, `_payFees` will have taken more tokens from the
// buyer than they had agreed to in the buy order, in which
// case we revert here.
rrequire(
spread >= sellOrderFees,
LibERC721OrdersRichErrors.SellOrderFeesExceedSpreadError(
sellOrderFees,
spread
)
);
);
// Step 7: We calculate the profit as:
// profit = buyOrder.erc20TokenAmount - sellOrder.erc20TokenAmount - sellOrderFees
// = spread - sellOrderFees
// I.e. the buyer would've been willing to pay up to `profit`
// more to buy the asset, so instead that amount is sent to
// `msg.sender` as the profit from matching these two orders.
profit = spread - sellOrderFees;
if (profit > 0) {
_transferERC20TokensFrom(
@@ -390,6 +472,8 @@ contract ERC721OrdersFeature is
for (uint256 i = 0; i < sellOrders.length; i++) {
bytes memory returnData;
// Delegatecall `matchERC721Orders` to catch reverts while
// preserving execution context.
(successes[i], returnData) = _implementation.delegatecall(
abi.encodeWithSelector(
this.matchERC721Orders.selector,
@@ -400,6 +484,7 @@ contract ERC721OrdersFeature is
)
);
if (successes[i]) {
// If the matching succeeded, record the profit.
(uint256 profit) = abi.decode(returnData, (uint256));
profits[i] = profit;
}
@@ -430,6 +515,12 @@ contract ERC721OrdersFeature is
override
returns (bytes4 success)
{
// TODO: Throw helpful reverts for malformed `data` before
// attempting to decode?
// Decode the order, signature, and `unwrapNativeToken` from
// `data`. If `data` does not encode such parameters, this
// will throw.
(
LibERC721Order.ERC721Order memory order,
LibSignature.Signature memory signature,
@@ -439,6 +530,8 @@ contract ERC721OrdersFeature is
(LibERC721Order.ERC721Order, LibSignature.Signature, bool)
);
// `onERC721Received` is called by the ERC721 token contract.
// Check that it matches the ERC721 token in the order.
rrequire(
msg.sender == address(order.erc721Token),
LibERC721OrdersRichErrors.ERC721TokenMismatchError(
@@ -461,6 +554,8 @@ contract ERC721OrdersFeature is
return ERC721_RECEIVED_MAGIC_BYTES;
}
// Core settlment logic for selling an ERC721 asset.
// Used by `sellERC721` and `onERC721Received`.
function _sellERC721(
LibERC721Order.ERC721Order memory order,
LibSignature.Signature memory signature,
@@ -471,6 +566,7 @@ contract ERC721OrdersFeature is
)
private
{
// Check that the order can be filled.
_validateBuyOrder(
order,
signature,
@@ -478,8 +574,14 @@ contract ERC721OrdersFeature is
erc721TokenId
);
// Mark the order as filled.
_setOrderStatusBit(order);
// Transfer the ERC721 asset to the buyer.
// If this function is called from the
// `onERC721Received` callback the Exchange Proxy
// holds the asset. Otherwise, transfer it from
// the seller.
_transferERC721AssetFrom(
order.erc721Token,
isCallback ? address(this) : taker,
@@ -488,6 +590,7 @@ contract ERC721OrdersFeature is
);
if (unwrapNativeToken) {
// The ERC20 token must be WETH for it to be unwrapped.
rrequire(
order.erc20Token == WETH,
LibERC721OrdersRichErrors.ERC20TokenMismatchError(
@@ -495,6 +598,8 @@ contract ERC721OrdersFeature is
address(WETH)
)
);
// Transfer the WETH from the maker to the Exchange Proxy
// so we can unwrap it before sending it to the seller.
// TODO: Probably safe to just use WETH.transferFrom for some
// small gas savings
_transferERC20TokensFrom(
@@ -503,9 +608,12 @@ contract ERC721OrdersFeature is
address(this),
order.erc20TokenAmount
);
// Unwrap WETH into ETH.
WETH.withdraw(order.erc20TokenAmount);
// Send ETH to the seller.
_transferEth(payable(taker), order.erc20TokenAmount);
} else {
// Transfer the ERC20 token from the buyer to the seller.
_transferERC20TokensFrom(
order.erc20Token,
order.maker,
@@ -514,6 +622,7 @@ contract ERC721OrdersFeature is
);
}
// The buyer pays the order fees.
_payFees(order, order.maker, false);
emit ERC721OrderFilled(
@@ -528,6 +637,8 @@ contract ERC721OrdersFeature is
);
}
// Core settlement logic for buying an ERC721 asset.
// Used by `buyERC721` and `batchBuyERC721s`.
function _buyERC721(
LibERC721Order.ERC721Order memory order,
LibSignature.Signature memory signature,
@@ -537,10 +648,13 @@ contract ERC721OrdersFeature is
payable
returns (uint256 ethSpent)
{
// Check that the order can be filled.
_validateSellOrder(order, signature);
// Mark the order as filled.
_setOrderStatusBit(order);
// Transfer the ERC721 asset to the buyer (`msg.sender`).
_transferERC721AssetFrom(
order.erc721Token,
order.maker,
@@ -549,6 +663,7 @@ contract ERC721OrdersFeature is
);
if (address(order.erc20Token) == NATIVE_TOKEN_ADDRESS) {
// Check that we have enough ETH.
rrequire(
ethAvailable >= order.erc20TokenAmount,
LibERC721OrdersRichErrors.InsufficientEthError(
@@ -556,38 +671,51 @@ contract ERC721OrdersFeature is
order.erc20TokenAmount
)
);
// Transfer ETH to the seller.
_transferEth(payable(order.maker), order.erc20TokenAmount);
// Fees are paid from the EP's current balance of ETH.
uint256 ethFees = _payFees(order, address(this), true);
// Sum the amount of ETH spent.
ethSpent = order.erc20TokenAmount.safeAdd(ethFees);
} else if (order.erc20Token == WETH) {
// If there is enough ETH available, fill the WETH order
// (including fees) using that ETH.
// Otherwise, transfer WETH from the taker.
if (ethAvailable >= order.erc20TokenAmount) {
// Wrap native token
// Wrap ETH.
WETH.deposit{value: order.erc20TokenAmount}();
// TODO: Probably safe to just use WETH.transfer for some
// small gas savings
// Transfer WETH to the seller.
_transferERC20Tokens(
WETH,
order.maker,
order.erc20TokenAmount
);
// Pay fees using ETH.
uint256 ethFees = _payFees(order, address(this), true);
// Sum the amount of ETH spent.
ethSpent = order.erc20TokenAmount.safeAdd(ethFees);
} else {
// Transfer WETH from the buyer to the seller.
_transferERC20TokensFrom(
order.erc20Token,
msg.sender,
order.maker,
order.erc20TokenAmount
);
// The buyer pays fees using WETH.
_payFees(order, msg.sender, false);
}
} else {
// Transfer ERC20 token from the buyer to the seller.
_transferERC20TokensFrom(
order.erc20Token,
msg.sender,
order.maker,
order.erc20TokenAmount
);
// The buyer pays fees.
_payFees(order, msg.sender, false);
}
@@ -630,16 +758,19 @@ contract ERC721OrdersFeature is
private
view
{
// Order must be selling the ERC721 asset.
require(
order.direction == LibERC721Order.TradeDirection.SELL_721,
"ERC721OrdersFeature::_validateSellOrder/WRONG_TRADE_DIRECTION"
);
// The taker must either be unspecified in the order, or it must
// be equal to `msg.sender`.
rrequire(
order.taker == address(0) || order.taker == msg.sender,
LibERC721OrdersRichErrors.OnlyTakerError(msg.sender, order.taker)
);
// Check that the order is valid and has not expired, been cancelled,
// or been filled.
LibERC721Order.OrderStatus status = getERC721OrderStatus(order);
rrequire(
getERC721OrderStatus(order) == LibERC721Order.OrderStatus.FILLABLE,
@@ -649,7 +780,7 @@ contract ERC721OrdersFeature is
uint8(status)
)
);
// Check the signature.
// TODO: Signer registry
bytes32 orderHash = getERC721OrderHash(order);
address signer = LibSignature.getSignerOfHash(orderHash, signature);
@@ -668,16 +799,19 @@ contract ERC721OrdersFeature is
private
view
{
// Order must be buying the ERC721 asset.
require(
order.direction == LibERC721Order.TradeDirection.BUY_721,
"ERC721OrdersFeature::_validateBuyOrder/WRONG_TRADE_DIRECTION"
);
// The taker must either be unspecified in the order, or it must
// be equal to `msg.sender`.
rrequire(
order.taker == address(0) || order.taker == taker,
LibERC721OrdersRichErrors.OnlyTakerError(taker, order.taker)
);
// Check that the order is valid and has not expired, been cancelled,
// or been filled.
LibERC721Order.OrderStatus status = getERC721OrderStatus(order);
rrequire(
getERC721OrderStatus(order) == LibERC721Order.OrderStatus.FILLABLE,
@@ -687,9 +821,10 @@ contract ERC721OrdersFeature is
uint8(status)
)
);
// Check that the asset with the given token ID satisfies the properties
// specified by the order.
validateERC721OrderProperties(order, erc721TokenId);
// Check the signature.
// TODO: Signer registry
bytes32 orderHash = getERC721OrderHash(order);
address signer = LibSignature.getSignerOfHash(orderHash, signature);
@@ -710,8 +845,10 @@ contract ERC721OrdersFeature is
for (uint256 i = 0; i < order.fees.length; i++) {
LibERC721Order.Fee memory fee = order.fees[i];
if (useNativeToken) {
// Transfer ETH to the fee recipient.
_transferEth(payable(fee.recipient), fee.amount);
} else {
// Transfer ERC20 token from payer to recipient.
_transferERC20TokensFrom(
order.erc20Token,
payer,
@@ -719,17 +856,24 @@ contract ERC721OrdersFeature is
fee.amount
);
}
// Note that the fee callback is _not_ called if zero
// `feeData` is provided. If `feeData` is provided, we assume
// the fee recipient is a contract that implements the
// `IFeeRecipient` interface.
if (fee.feeData.length > 0) {
// Invoke the callback
bytes4 callbackResult = IFeeRecipient(fee.recipient).receiveFeeCallback(
useNativeToken ? NATIVE_TOKEN_ADDRESS : address(order.erc20Token),
fee.amount,
fee.feeData
);
// Check for the magic success bytes
require(
callbackResult == FEE_CALLBACK_MAGIC_BYTES,
"Fee callback failed"
"ERC721OrdersFeature::_payFees/CALLBACK_FAILED"
);
}
// Sum the fees paid
totalFeesPaid = totalFeesPaid.safeAdd(fee.amount);
}
}
@@ -737,8 +881,10 @@ contract ERC721OrdersFeature is
function _setOrderStatusBit(LibERC721Order.ERC721Order memory order)
private
{
// The bitvector is indexed by the lower 8 bits of the nonce.
uint256 flag = 1 << (order.nonce % 256);
// Update order status bit vector
// Update order status bit vector to indicate that the given order
// has been cancelled/filled by setting the designated bit to 1.
LibERC721OrdersStorage.getStorage().orderStatusByMaker
[order.maker][uint248(order.nonce >> 8)] |= flag;
}
@@ -791,11 +937,15 @@ contract ERC721OrdersFeature is
override
view
{
// Order must be selling an buying an ERC721 asset to
// have properties.
require(
order.direction == LibERC721Order.TradeDirection.BUY_721,
"ERC721OrdersFeature::validateERC721OrderProperties/WRONG_TRADE_DIRECTION"
);
// If no properties are specified, check that the given
// `erc721TokenId` matches the one specified in the order.
if (order.erc721TokenProperties.length == 0) {
rrequire(
erc721TokenId == order.erc721TokenId,
@@ -806,12 +956,17 @@ contract ERC721OrdersFeature is
);
}
// Validate each property
for (uint256 i = 0; i < order.erc721TokenProperties.length; i++) {
LibERC721Order.Property memory property = order.erc721TokenProperties[i];
// `address(0)` is interpreted as a no-op. Any token ID
// will satisfy a property with `propertyValidator == address(0)`.
if (address(property.propertyValidator) == address(0)) {
continue;
}
// Call the property validator and throw a descriptive error
// if the call reverts.
try property.propertyValidator.validateProperty(
address(order.erc721Token),
erc721TokenId,
@@ -837,6 +992,8 @@ contract ERC721OrdersFeature is
view
returns (LibERC721Order.OrderStatus status)
{
// Only sell orders with `erc721TokenId` == 0 can be property
// orders.
if (order.erc721TokenProperties.length > 0 &&
(order.direction == LibERC721Order.TradeDirection.SELL_721 ||
order.erc721TokenId != 0))
@@ -844,25 +1001,35 @@ contract ERC721OrdersFeature is
return LibERC721Order.OrderStatus.INVALID;
}
// Buy orders cannot use ETH as the ERC20 token, since ETH cannot be
// transferred from the buyer by a contract.
if (order.direction == LibERC721Order.TradeDirection.BUY_721 &&
address(order.erc20Token) == NATIVE_TOKEN_ADDRESS)
{
return LibERC721Order.OrderStatus.INVALID;
}
// Check for expiry.
if (order.expiry <= block.timestamp) {
return LibERC721Order.OrderStatus.EXPIRED;
}
// Check `orderStatusByMaker` state variable to see if the order
// has been cancelled or previously filled.
LibERC721OrdersStorage.Storage storage stor =
LibERC721OrdersStorage.getStorage();
// `orderStatusByMaker` is indexed by maker and nonce.
uint256 orderStatusBitVector =
stor.orderStatusByMaker[order.maker][uint248(order.nonce >> 8)];
// The bitvector is indexed by the lower 8 bits of the nonce.
uint256 flag = 1 << (order.nonce % 256);
// If the designated bit is set, the order has been cancelled or
// previously filled, so it is now unfillable.
if (orderStatusBitVector & flag != 0) {
return LibERC721Order.OrderStatus.UNFILLABLE;
}
// Otherwise, the order is fillable.
return LibERC721Order.OrderStatus.FILLABLE;
}

View File

@@ -132,10 +132,16 @@ library LibERC721Order {
// TODO: Verify EIP-712 behavior for array of structs
// TODO: Rewrite in assembly to hash in place
// We give `order.erc721TokenProperties.length == 0` and
// `order.erc721TokenProperties.length == 1` special treatment
// because we expect these to be the most common.
bytes32 propertiesHash;
if (order.erc721TokenProperties.length == 0) {
propertiesHash = _NULL_KECCAK256;
} else if (order.erc721TokenProperties.length == 1) {
// TODO: Maybe introduce yet another case here for if
// propertyValidator == 0 and propertyData == 0. Should be
// a particularly common use case that we can optimize for.
propertiesHash = keccak256(abi.encodePacked(keccak256(abi.encode(
_PROPERTY_TYPEHASH,
order.erc721TokenProperties[0].propertyValidator,
@@ -155,6 +161,9 @@ library LibERC721Order {
propertiesHash = keccak256(abi.encodePacked(propertyStructHashArray));
}
// We give `order.fees.length == 0` and
// `order.fees.length == 1` special treatment
// because we expect these to be the most common.
bytes32 feesHash;
if (order.fees.length == 0) {
feesHash = _NULL_KECCAK256;