aboutsummaryrefslogtreecommitdiffstats
path: root/contracts/extensions/contracts/src/DutchAuction/DutchAuction.sol
diff options
context:
space:
mode:
Diffstat (limited to 'contracts/extensions/contracts/src/DutchAuction/DutchAuction.sol')
-rw-r--r--contracts/extensions/contracts/src/DutchAuction/DutchAuction.sol205
1 files changed, 205 insertions, 0 deletions
diff --git a/contracts/extensions/contracts/src/DutchAuction/DutchAuction.sol b/contracts/extensions/contracts/src/DutchAuction/DutchAuction.sol
new file mode 100644
index 000000000..7631880c0
--- /dev/null
+++ b/contracts/extensions/contracts/src/DutchAuction/DutchAuction.sol
@@ -0,0 +1,205 @@
+/*
+
+ Copyright 2018 ZeroEx Intl.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+*/
+
+pragma solidity 0.4.24;
+pragma experimental ABIEncoderV2;
+
+import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.sol";
+import "@0x/contracts-exchange-libs/contracts/src/LibOrder.sol";
+import "@0x/contracts-erc20/contracts/src/interfaces/IERC20Token.sol";
+import "@0x/contracts-utils/contracts/src/LibBytes.sol";
+import "@0x/contracts-utils/contracts/src/SafeMath.sol";
+
+
+contract DutchAuction is
+ SafeMath
+{
+ using LibBytes for bytes;
+
+ // solhint-disable var-name-mixedcase
+ IExchange internal EXCHANGE;
+
+ struct AuctionDetails {
+ uint256 beginTimeSeconds; // Auction begin unix timestamp: sellOrder.makerAssetData
+ uint256 endTimeSeconds; // Auction end unix timestamp: sellOrder.expiryTimeSeconds
+ uint256 beginAmount; // Auction begin amount: sellOrder.makerAssetData
+ uint256 endAmount; // Auction end amount: sellOrder.takerAssetAmount
+ uint256 currentAmount; // Calculated amount given block.timestamp
+ uint256 currentTimeSeconds; // block.timestamp
+ }
+
+ constructor (address _exchange)
+ public
+ {
+ EXCHANGE = IExchange(_exchange);
+ }
+
+ /// @dev Matches the buy and sell orders at an amount given the following: the current block time, the auction
+ /// start time and the auction begin amount. The sell order is a an order at the lowest amount
+ /// at the end of the auction. Excess from the match is transferred to the seller.
+ /// Over time the price moves from beginAmount to endAmount given the current block.timestamp.
+ /// sellOrder.expiryTimeSeconds is the end time of the auction.
+ /// sellOrder.takerAssetAmount is the end amount of the auction (lowest possible amount).
+ /// sellOrder.makerAssetData is the ABI encoded Asset Proxy data with the following data appended
+ /// buyOrder.makerAssetData is the buyers bid on the auction, must meet the amount for the current block timestamp
+ /// (uint256 beginTimeSeconds, uint256 beginAmount).
+ /// This function reverts in the following scenarios:
+ /// * Auction has not started (auctionDetails.currentTimeSeconds < auctionDetails.beginTimeSeconds)
+ /// * Auction has expired (auctionDetails.endTimeSeconds < auctionDetails.currentTimeSeconds)
+ /// * Amount is invalid: Buy order amount is too low (buyOrder.makerAssetAmount < auctionDetails.currentAmount)
+ /// * Amount is invalid: Invalid begin amount (auctionDetails.beginAmount > auctionDetails.endAmount)
+ /// * Any failure in the 0x Match Orders
+ /// @param buyOrder The Buyer's order. This order is for the current expected price of the auction.
+ /// @param sellOrder The Seller's order. This order is for the lowest amount (at the end of the auction).
+ /// @param buySignature Proof that order was created by the buyer.
+ /// @param sellSignature Proof that order was created by the seller.
+ /// @return matchedFillResults amounts filled and fees paid by maker and taker of matched orders.
+ function matchOrders(
+ LibOrder.Order memory buyOrder,
+ LibOrder.Order memory sellOrder,
+ bytes memory buySignature,
+ bytes memory sellSignature
+ )
+ public
+ returns (LibFillResults.MatchedFillResults memory matchedFillResults)
+ {
+ AuctionDetails memory auctionDetails = getAuctionDetails(sellOrder);
+ // Ensure the auction has not yet started
+ require(
+ auctionDetails.currentTimeSeconds >= auctionDetails.beginTimeSeconds,
+ "AUCTION_NOT_STARTED"
+ );
+ // Ensure the auction has not expired. This will fail later in 0x but we can save gas by failing early
+ require(
+ sellOrder.expirationTimeSeconds > auctionDetails.currentTimeSeconds,
+ "AUCTION_EXPIRED"
+ );
+ // Validate the buyer amount is greater than the current auction amount
+ require(
+ buyOrder.makerAssetAmount >= auctionDetails.currentAmount,
+ "INVALID_AMOUNT"
+ );
+ // Match orders, maximally filling `buyOrder`
+ matchedFillResults = EXCHANGE.matchOrders(
+ buyOrder,
+ sellOrder,
+ buySignature,
+ sellSignature
+ );
+ // The difference in sellOrder.takerAssetAmount and current amount is given as spread to the matcher
+ // This may include additional spread from the buyOrder.makerAssetAmount and the currentAmount.
+ // e.g currentAmount is 30, sellOrder.takerAssetAmount is 10 and buyOrder.makerAssetamount is 40.
+ // 10 (40-30) is returned to the buyer, 20 (30-10) sent to the seller and 10 has previously
+ // been transferred to the seller during matchOrders
+ uint256 leftMakerAssetSpreadAmount = matchedFillResults.leftMakerAssetSpreadAmount;
+ if (leftMakerAssetSpreadAmount > 0) {
+ // ERC20 Asset data itself is encoded as follows:
+ //
+ // | Area | Offset | Length | Contents |
+ // |----------|--------|---------|-------------------------------------|
+ // | Header | 0 | 4 | function selector |
+ // | Params | | 1 * 32 | function parameters: |
+ // | | 4 | 12 | 1. token address padding |
+ // | | 16 | 20 | 2. token address |
+ bytes memory assetData = sellOrder.takerAssetData;
+ address token = assetData.readAddress(16);
+ // Calculate the excess from the buy order. This can occur if the buyer sends in a higher
+ // amount than the calculated current amount
+ uint256 buyerExcessAmount = safeSub(buyOrder.makerAssetAmount, auctionDetails.currentAmount);
+ uint256 sellerExcessAmount = safeSub(leftMakerAssetSpreadAmount, buyerExcessAmount);
+ // Return the difference between auctionDetails.currentAmount and sellOrder.takerAssetAmount
+ // to the seller
+ if (sellerExcessAmount > 0) {
+ IERC20Token(token).transfer(sellOrder.makerAddress, sellerExcessAmount);
+ }
+ // Return the difference between buyOrder.makerAssetAmount and auctionDetails.currentAmount
+ // to the buyer
+ if (buyerExcessAmount > 0) {
+ IERC20Token(token).transfer(buyOrder.makerAddress, buyerExcessAmount);
+ }
+ }
+ return matchedFillResults;
+ }
+
+ /// @dev Calculates the Auction Details for the given order
+ /// @param order The sell order
+ /// @return AuctionDetails
+ function getAuctionDetails(
+ LibOrder.Order memory order
+ )
+ public
+ returns (AuctionDetails memory auctionDetails)
+ {
+ uint256 makerAssetDataLength = order.makerAssetData.length;
+ // It is unknown the encoded data of makerAssetData, we assume the last 64 bytes
+ // are the Auction Details encoding.
+ // Auction Details is encoded as follows:
+ //
+ // | Area | Offset | Length | Contents |
+ // |----------|--------|---------|-------------------------------------|
+ // | Params | | 2 * 32 | parameters: |
+ // | | -64 | 32 | 1. auction begin unix timestamp |
+ // | | -32 | 32 | 2. auction begin begin amount |
+ // ERC20 asset data length is 4+32, 64 for auction details results in min length 100
+ require(
+ makerAssetDataLength >= 100,
+ "INVALID_ASSET_DATA"
+ );
+ uint256 auctionBeginTimeSeconds = order.makerAssetData.readUint256(makerAssetDataLength - 64);
+ uint256 auctionBeginAmount = order.makerAssetData.readUint256(makerAssetDataLength - 32);
+ // Ensure the auction has a valid begin time
+ require(
+ order.expirationTimeSeconds > auctionBeginTimeSeconds,
+ "INVALID_BEGIN_TIME"
+ );
+ uint256 auctionDurationSeconds = order.expirationTimeSeconds-auctionBeginTimeSeconds;
+ // Ensure the auction goes from high to low
+ uint256 minAmount = order.takerAssetAmount;
+ require(
+ auctionBeginAmount > minAmount,
+ "INVALID_AMOUNT"
+ );
+ uint256 amountDelta = auctionBeginAmount-minAmount;
+ // solhint-disable-next-line not-rely-on-time
+ uint256 timestamp = block.timestamp;
+ auctionDetails.beginTimeSeconds = auctionBeginTimeSeconds;
+ auctionDetails.endTimeSeconds = order.expirationTimeSeconds;
+ auctionDetails.beginAmount = auctionBeginAmount;
+ auctionDetails.endAmount = minAmount;
+ auctionDetails.currentTimeSeconds = timestamp;
+
+ uint256 remainingDurationSeconds = order.expirationTimeSeconds-timestamp;
+ if (timestamp < auctionBeginTimeSeconds) {
+ // If the auction has not yet begun the current amount is the auctionBeginAmount
+ auctionDetails.currentAmount = auctionBeginAmount;
+ } else if (timestamp >= order.expirationTimeSeconds) {
+ // If the auction has ended the current amount is the minAmount.
+ // Auction end time is guaranteed by 0x Exchange due to the order expiration
+ auctionDetails.currentAmount = minAmount;
+ } else {
+ auctionDetails.currentAmount = safeAdd(
+ minAmount,
+ safeDiv(
+ safeMul(remainingDurationSeconds, amountDelta),
+ auctionDurationSeconds
+ )
+ );
+ }
+ return auctionDetails;
+ }
+}