From c4ba90597e0985fe39458b2f9dc009c950d2d50d Mon Sep 17 00:00:00 2001 From: josojo Date: Mon, 3 May 2021 14:28:59 +0200 Subject: [PATCH 1/4] work in progress, tests still to be fixed --- contracts/ChannelAuction.sol | 600 +++ .../libraries/IterableOrderedOrderList.sol | 199 + .../test/IterableOrderedOrderListWrapper.sol | 74 + src/priceCalculation.ts | 6 +- src/ts/types.ts | 11 + test/contract/Channelauction.spec.ts | 3316 +++++++++++++++++ test/contract/IterableOrderList.spec.ts | 295 ++ test/contract/defaultContractInteractions.ts | 42 +- 8 files changed, 4539 insertions(+), 4 deletions(-) create mode 100644 contracts/ChannelAuction.sol create mode 100644 contracts/libraries/IterableOrderedOrderList.sol create mode 100644 contracts/test/IterableOrderedOrderListWrapper.sol create mode 100644 test/contract/Channelauction.spec.ts create mode 100644 test/contract/IterableOrderList.spec.ts diff --git a/contracts/ChannelAuction.sol b/contracts/ChannelAuction.sol new file mode 100644 index 0000000..f957797 --- /dev/null +++ b/contracts/ChannelAuction.sol @@ -0,0 +1,600 @@ +pragma solidity >=0.6.8; +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; +import "./libraries/IterableOrderedOrderList.sol"; +import "@openzeppelin/contracts/math/Math.sol"; +import "@openzeppelin/contracts/math/SafeMath.sol"; +import "./libraries/IdToAddressBiMap.sol"; +import "./libraries/SafeCast.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; + +contract ChannelAuction is Ownable { + using SafeERC20 for IERC20; + using SafeMath for uint64; + using SafeMath for uint96; + using SafeMath for uint256; + using SafeCast for uint256; + using IterableOrderedOrderList for IterableOrderedOrderList.Data; + using IterableOrderedOrderList for bytes32; + using IdToAddressBiMap for IdToAddressBiMap.Data; + + modifier atStageOrderPlacement(uint256 auctionId) { + require( + block.timestamp > auctionData[auctionId].auctionStartDate, + "not yet in order placement phase" + ); + require( + bytes32(0) == auctionData[auctionId].clearingPriceOrder, + "no longer in order placement phase" + ); + _; + } + + modifier atStageFinished(uint256 auctionId) { + require( + auctionData[auctionId].clearingPriceOrder != bytes32(0), + "Auction not yet finished" + ); + _; + } + + event NewSellOrder( + uint256 indexed auctionId, + uint64 indexed userId, + uint96 buyAmount, + uint96 sellAmount + ); + event ClaimedFromOrder( + uint256 indexed auctionId, + uint64 indexed userId, + uint96 buyAmount, + uint96 sellAmount + ); + event NewUser(uint64 indexed userId, address indexed userAddress); + event NewAuction( + uint256 indexed auctionId, + IERC20 indexed _auctioningToken, + IERC20 indexed _biddingToken, + uint64 userId, + uint96 auctionStartDate, + uint96 _auctionedSellAmount, + uint96 _auctioneerBuyAmountMinimum, + uint96 _auctioneerBuyAmountMaximum, + uint96 minimumBiddingAmountPerOrder, + uint96 maxDuration + ); + event AuctionCleared( + uint256 indexed auctionId, + uint96 soldAuctioningTokens, + uint96 soldBiddingTokens, + bytes32 clearingPriceOrder + ); + event UserRegistration(address indexed user, uint64 userId); + + struct AuctionData { + IERC20 auctioningToken; + IERC20 biddingToken; + uint96 auctionStartDate; + bytes32 initialAuctionOrder; + uint256 minimumBiddingAmountPerOrder; + bytes32 clearingPriceOrder; + uint96 volumeClearingPriceOrder; + uint96 maxDuration; + uint96 auctioneerBuyAmountMinimum; + } + mapping(uint256 => IterableOrderedOrderList.Data) internal sellOrders; + mapping(uint256 => AuctionData) public auctionData; + + IdToAddressBiMap.Data private registeredUsers; + uint64 public numUsers; + uint256 public auctionCounter; + + constructor() public Ownable() {} + + uint256 public feeNumerator = 0; + uint256 public constant FEE_DENOMINATOR = 1000; + uint64 public feeReceiverUserId = 1; + + function setFeeParameters( + uint256 newFeeNumerator, + address newfeeReceiverAddress + ) public onlyOwner() { + require( + newFeeNumerator <= 15, + "Fee is not allowed to be set higher than 1.5%" + ); + // caution: for currently running auctions, the feeReceiverUserId is changing as well. + feeReceiverUserId = getUserId(newfeeReceiverAddress); + feeNumerator = newFeeNumerator; + } + + // @dev: function to intiate a new auction + // Warning: In case the auction is expected to raise more than + // 2^96 units of the biddingToken, don't start the auction, as + // it will not be settlable. This corresponds to about 79 + // billion DAI. + // + // Prices between biddingToken and auctioningToken are expressed by a + // fraction whose components are stored as uint96. + function initiateAuction( + IERC20 _auctioningToken, + IERC20 _biddingToken, + uint96 _auctionedSellAmount, + uint96 _auctioneerBuyAmountMinimum, + uint96 _auctioneerBuyAmountMaximum, + uint96 _auctionStartDate, + uint96 _minimumBiddingAmountPerOrder, + uint96 _maxDuration + ) public returns (uint256) { + // withdraws sellAmount + fees + _auctioningToken.safeTransferFrom( + msg.sender, + address(this), + _auctionedSellAmount //[0] + ); + if (feeNumerator > 0) { + _auctioningToken.safeTransferFrom( + msg.sender, + address(this), + _auctionedSellAmount.mul(feeNumerator).div(FEE_DENOMINATOR) //[1] + ); + } + require(_auctionedSellAmount > 0, "cannot auction zero tokens"); + require( + _auctioneerBuyAmountMinimum > 0, + "_auctioneerBuyAmountMinimum must be positive" + ); + require( + _auctioneerBuyAmountMaximum > _auctioneerBuyAmountMinimum, + "_auctioneerBuyAmountMaximum must be higher than _auctioneerBuyAmountMinimum" + ); + require( + _minimumBiddingAmountPerOrder > 0, + "minimumBiddingAmountPerOrder is not allowed to be zero" + ); + require( + _auctionStartDate + _maxDuration >= now, + "time periods are not configured correctly" + ); + auctionCounter = auctionCounter.add(1); + sellOrders[auctionCounter].initializeEmptyList(); + uint64 userId = getUserId(msg.sender); + auctionData[auctionCounter] = AuctionData( + _auctioningToken, + _biddingToken, + _auctionStartDate, + IterableOrderedOrderList.encodeOrder( + userId, + _auctioneerBuyAmountMaximum, + _auctionedSellAmount + ), + _minimumBiddingAmountPerOrder, + bytes32(0), + 0, + _maxDuration, + _auctioneerBuyAmountMinimum + ); + emit NewAuction( + auctionCounter, + _auctioningToken, + _biddingToken, + userId, + _auctionStartDate, + _auctionedSellAmount, + _auctioneerBuyAmountMinimum, + _auctioneerBuyAmountMaximum, + _minimumBiddingAmountPerOrder, + _maxDuration + ); + return auctionCounter; + } + + function placeSellOrders( + uint256 auctionId, + uint96[] memory _minBuyAmounts, + uint96[] memory _sellAmounts, + bytes32[] memory _prevSellOrders + ) external atStageOrderPlacement(auctionId) returns (uint64 userId) { + return + _placeSellOrders( + auctionId, + _minBuyAmounts, + _sellAmounts, + _prevSellOrders, + msg.sender + ); + } + + function placeSellOrdersOnBehalf( + uint256 auctionId, + uint96[] memory _minBuyAmounts, + uint96[] memory _sellAmounts, + bytes32[] memory _prevSellOrders, + address orderSubmitter + ) external atStageOrderPlacement(auctionId) returns (uint64 userId) { + return + _placeSellOrders( + auctionId, + _minBuyAmounts, + _sellAmounts, + _prevSellOrders, + orderSubmitter + ); + } + + function _placeSellOrders( + uint256 auctionId, + uint96[] memory _minBuyAmounts, + uint96[] memory _sellAmounts, + bytes32[] memory _prevSellOrders, + address orderSubmitter + ) internal returns (uint64 userId) { + { + ( + , + uint96 buyAmountOfInitialAuctionOrder, + uint96 sellAmountOfInitialAuctionOrder + ) = auctionData[auctionId].initialAuctionOrder.decodeOrder(); + for (uint256 i = 0; i < _minBuyAmounts.length; i++) { + require( + _minBuyAmounts[i].mul(buyAmountOfInitialAuctionOrder) < + sellAmountOfInitialAuctionOrder.mul(_sellAmounts[i]), + "limit price not better than mimimal offer" + ); + } + } + uint256 sumOfSellAmounts = 0; + userId = getUserId(orderSubmitter); + uint256 minimumBiddingAmountPerOrder = + auctionData[auctionId].minimumBiddingAmountPerOrder; + for (uint256 i = 0; i < _minBuyAmounts.length; i++) { + require( + _minBuyAmounts[i] > 0, + "_minBuyAmounts must be greater than 0" + ); + // orders should have a minimum bid size in order to limit the gas + // required to compute the final price of the auction. + require( + _sellAmounts[i] > minimumBiddingAmountPerOrder, + "order too small" + ); + if ( + sellOrders[auctionId].insert( + IterableOrderedOrderList.encodeOrder( + userId, + _minBuyAmounts[i], + _sellAmounts[i] + ), + _prevSellOrders[i] + ) + ) { + sumOfSellAmounts = sumOfSellAmounts.add(_sellAmounts[i]); + emit NewSellOrder( + auctionId, + userId, + _minBuyAmounts[i], + _sellAmounts[i] + ); + } + } + auctionData[auctionId].biddingToken.safeTransferFrom( + msg.sender, + address(this), + sumOfSellAmounts + ); //[1] + } + + function settleAuctionWithAdditionalOrder( + uint256 auctionId, + uint96[] memory _minBuyAmount, + uint96[] memory _sellAmount, + bytes32[] memory _prevSellOrder + ) public atStageOrderPlacement(auctionId) { + require( + _minBuyAmount.length == 1 && _sellAmount.length == 1, + "Only one order can be placed atomically" + ); + _placeSellOrders( + auctionId, + _minBuyAmount, + _sellAmount, + _prevSellOrder, + msg.sender + ); + settleAuction(auctionId); + bytes32[] memory claimOrder = new bytes32[](1); + claimOrder[0] = IterableOrderedOrderList.encodeOrder( + getUserId(msg.sender), + _minBuyAmount[0], + _sellAmount[0] + ); + claimFromParticipantOrder(auctionId, claimOrder); + } + + function getCurrentMinBuyAmount( + uint96 buyAmountMinimum, + uint96 buyAmountMaximum, + uint96 passedTime, + uint96 maxDuration + ) public pure returns (uint96) { + if (passedTime > maxDuration) { + return buyAmountMinimum; + } else { + return + buyAmountMaximum + .sub( + (buyAmountMaximum.sub(buyAmountMinimum)) + .mul(passedTime) + .div(maxDuration) + ) + .toUint96(); + } + } + + // @dev function settling the auction and calculating the price + function settleAuction(uint256 auctionId) + public + atStageOrderPlacement(auctionId) + returns (bytes32 clearingOrder) + { + (uint64 auctioneerId, , uint96 fullAuctionedAmount) = + auctionData[auctionId].initialAuctionOrder.decodeOrder(); + + uint96 minAuctionedBuyAmount = 0; + { + (, uint96 auctioneerBuyAmountMaximum, ) = + auctionData[auctionId].initialAuctionOrder.decodeOrder(); + minAuctionedBuyAmount = getCurrentMinBuyAmount( + auctioneerBuyAmountMaximum, + auctionData[auctionId].auctioneerBuyAmountMinimum, + block + .timestamp + .sub(auctionData[auctionId].auctionStartDate) + .toUint96(), + auctionData[auctionId].maxDuration + ); + } + uint256 currentBidSum = 0; + bytes32 currentOrder = IterableOrderedOrderList.QUEUE_START; + uint256 buyAmountOfIter; + uint256 sellAmountOfIter; + uint96 fillVolumeOfAuctioneerOrder = fullAuctionedAmount; + // Sum order up, until fullAuctionedAmount is fully bought or queue end is reached + do { + bytes32 nextOrder = sellOrders[auctionId].next(currentOrder); + if (nextOrder == IterableOrderedOrderList.QUEUE_END) { + if ( + now > + auctionData[auctionId].auctionStartDate.add( + auctionData[auctionId].maxDuration + ) + ) break; + // fractional fillements are accepted, as price won't decrease more + else { + // fractional fillments are not accepted, as prices will continue to decrease + revert("Auction can not yet be settled"); + } + } + currentOrder = nextOrder; + (, buyAmountOfIter, sellAmountOfIter) = currentOrder.decodeOrder(); + currentBidSum = currentBidSum.add(sellAmountOfIter); + } while ( + currentBidSum.mul(buyAmountOfIter) < + fullAuctionedAmount.mul(sellAmountOfIter) + ); + + if ( + currentBidSum > 0 && + currentBidSum.mul(buyAmountOfIter) >= + fullAuctionedAmount.mul(sellAmountOfIter) + ) { + // All considered/summed orders are sufficient to close the auction fully + // at price between current and previous orders. + uint256 uncoveredBids = + currentBidSum.sub( + fullAuctionedAmount.mul(sellAmountOfIter).div( + buyAmountOfIter + ) + ); + + if (sellAmountOfIter >= uncoveredBids) { + //[13] + // Auction fully filled via partial match of currentOrder + uint256 sellAmountClearingOrder = + sellAmountOfIter.sub(uncoveredBids); + auctionData[auctionId] + .volumeClearingPriceOrder = sellAmountClearingOrder + .toUint96(); + currentBidSum = currentBidSum.sub(uncoveredBids); + clearingOrder = currentOrder; + } else { + //[14] + // Auction fully filled via price strictly between currentOrder and the order + // immediately before. For a proof, see the security-considerations.md + currentBidSum = currentBidSum.sub(sellAmountOfIter); + clearingOrder = IterableOrderedOrderList.encodeOrder( + 0, + fullAuctionedAmount, + currentBidSum.toUint96() + ); + } + } else { + // All considered/summed orders are not sufficient to close the auction fully at price of last order //[18] + // Either a higher price must be used or auction is only partially filled + + if (currentBidSum > minAuctionedBuyAmount) { + //[15] + // Price higher than last order would fill the auction + clearingOrder = IterableOrderedOrderList.encodeOrder( + 0, + fullAuctionedAmount, + currentBidSum.toUint96() + ); + } else { + //[16] + // Even at the initial auction price, the auction is partially filled + clearingOrder = IterableOrderedOrderList.encodeOrder( + 0, + fullAuctionedAmount, + minAuctionedBuyAmount + ); + fillVolumeOfAuctioneerOrder = currentBidSum + .mul(fullAuctionedAmount) + .div(minAuctionedBuyAmount) + .toUint96(); + } + } + auctionData[auctionId].clearingPriceOrder = clearingOrder; + processAuctioneerFunds( + auctionId, + fillVolumeOfAuctioneerOrder, + auctioneerId, + fullAuctionedAmount + ); + emit AuctionCleared( + auctionId, + fillVolumeOfAuctioneerOrder, + uint96(currentBidSum), + clearingOrder + ); + // Gas refunds + auctionData[auctionId].initialAuctionOrder = bytes32(0); + auctionData[auctionId].minimumBiddingAmountPerOrder = uint256(0); + } + + function claimFromParticipantOrder( + uint256 auctionId, + bytes32[] memory orders + ) + public + atStageFinished(auctionId) + returns ( + uint256 sumAuctioningTokenAmount, + uint256 sumBiddingTokenAmount + ) + { + for (uint256 i = 0; i < orders.length; i++) { + // Note: we don't need to keep any information about the node since + // no new elements need to be inserted. + require( + sellOrders[auctionId].remove(orders[i]), + "order is no longer claimable" + ); + } + AuctionData memory auction = auctionData[auctionId]; + (, uint96 priceNumerator, uint96 priceDenominator) = + auction.clearingPriceOrder.decodeOrder(); + (uint64 userId, , ) = orders[0].decodeOrder(); + for (uint256 i = 0; i < orders.length; i++) { + (uint64 userIdOrder, uint96 buyAmount, uint96 sellAmount) = + orders[i].decodeOrder(); + require( + userIdOrder == userId, + "only allowed to claim for same user" + ); + //[23] + if (orders[i] == auction.clearingPriceOrder) { + //[25] + sumAuctioningTokenAmount = sumAuctioningTokenAmount.add( + auction.volumeClearingPriceOrder.mul(priceNumerator).div( + priceDenominator + ) + ); + sumBiddingTokenAmount = sumBiddingTokenAmount.add( + sellAmount.sub(auction.volumeClearingPriceOrder) + ); + } else { + if (orders[i].smallerThan(auction.clearingPriceOrder)) { + //[17] + sumAuctioningTokenAmount = sumAuctioningTokenAmount.add( + sellAmount.mul(priceNumerator).div(priceDenominator) + ); + } else { + //[24] + sumBiddingTokenAmount = sumBiddingTokenAmount.add( + sellAmount + ); + } + } + emit ClaimedFromOrder(auctionId, userId, buyAmount, sellAmount); + } + sendOutTokens( + auctionId, + sumAuctioningTokenAmount, + sumBiddingTokenAmount, + userId + ); //[3] + } + + function processAuctioneerFunds( + uint256 auctionId, + uint256 fillVolumeOfAuctioneerOrder, + uint64 auctioneerId, + uint96 fullAuctionedAmount + ) internal { + //[11] + (, uint96 priceNumerator, uint96 priceDenominator) = + auctionData[auctionId].clearingPriceOrder.decodeOrder(); + uint256 unsettledAuctionTokens = + fullAuctionedAmount.sub(fillVolumeOfAuctioneerOrder); + uint256 auctioningTokenAmount = unsettledAuctionTokens; + uint256 biddingTokenAmount = + fillVolumeOfAuctioneerOrder.mul(priceDenominator).div( + priceNumerator + ); + sendOutTokens( + auctionId, + auctioningTokenAmount, + biddingTokenAmount, + auctioneerId + ); //[5] + } + + function sendOutTokens( + uint256 auctionId, + uint256 auctioningTokenAmount, + uint256 biddingTokenAmount, + uint64 userId + ) internal { + address userAddress = registeredUsers.getAddressAt(userId); + if (auctioningTokenAmount > 0) { + auctionData[auctionId].auctioningToken.safeTransfer( + userAddress, + auctioningTokenAmount + ); + } + if (biddingTokenAmount > 0) { + auctionData[auctionId].biddingToken.safeTransfer( + userAddress, + biddingTokenAmount + ); + } + } + + function registerUser(address user) public returns (uint64 userId) { + numUsers = numUsers.add(1).toUint64(); + require( + registeredUsers.insert(numUsers, user), + "User already registered" + ); + userId = numUsers; + emit UserRegistration(user, userId); + } + + function getUserId(address user) public returns (uint64 userId) { + if (registeredUsers.hasAddress(user)) { + userId = registeredUsers.getId(user); + } else { + userId = registerUser(user); + emit NewUser(userId, user); + } + } + + function containsOrder(uint256 auctionId, bytes32 order) + public + view + returns (bool) + { + return sellOrders[auctionId].contains(order); + } +} diff --git a/contracts/libraries/IterableOrderedOrderList.sol b/contracts/libraries/IterableOrderedOrderList.sol new file mode 100644 index 0000000..6ab296a --- /dev/null +++ b/contracts/libraries/IterableOrderedOrderList.sol @@ -0,0 +1,199 @@ +pragma solidity >=0.6.8; + +import "@openzeppelin/contracts/math/SafeMath.sol"; + +library IterableOrderedOrderList { + using SafeMath for uint96; + using IterableOrderedOrderList for bytes32; + + // represents smallest possible value for an order under comparison of fn smallerThan() + bytes32 internal constant QUEUE_START = + 0x0000000000000000000000000000000000000000000000000000000000000001; + // represents highest possible value for an order under comparison of fn smallerThan() + bytes32 internal constant QUEUE_END = + 0xffffffffffffffffffffffffffffffffffffffff000000000000000000000001; + + /// The struct is used to implement a modified version of a doubly linked + /// list with sorted elements. The list starts from QUEUE_START to + /// QUEUE_END, and each node keeps track of its predecessor and successor. + /// Nodes can be added or removed. + /// + /// `next` and `prev` have a different role. The list is supposed to be + /// traversed with `next`. If `next` is empty, the node is not part of the + /// list. However, `prev` might be set for elements that are not in the + /// list, which is why it should not be used for traversing. Having a `prev` + /// set for elements not in the list is used to keep track of the history of + /// the position in the list of a removed element. + struct Data { + mapping(bytes32 => bytes32) nextMap; + mapping(bytes32 => bytes32) prevMap; + } + + struct Order { + uint64 owner; + uint96 buyAmount; + uint96 sellAmount; + } + + function initializeEmptyList(Data storage self) internal { + self.nextMap[QUEUE_START] = QUEUE_END; + self.prevMap[QUEUE_END] = QUEUE_START; + } + + function isEmpty(Data storage self) internal view returns (bool) { + return self.nextMap[QUEUE_START] == QUEUE_END; + } + + function insert( + Data storage self, + bytes32 elementToInsert, + bytes32 elementBeforeNewOne + ) internal returns (bool) { + (, , uint96 denominator) = decodeOrder(elementToInsert); + require(denominator != uint96(0), "Inserting zero is not supported"); + require( + elementToInsert != QUEUE_START && elementToInsert != QUEUE_END, + "Inserting element is not valid" + ); + if (contains(self, elementToInsert)) { + return false; + } + if ( + elementBeforeNewOne != QUEUE_START && + !contains(self, elementBeforeNewOne) + ) { + return false; + } + if (!elementBeforeNewOne.smallerThan(elementToInsert)) { + return false; + } + + // `elementBeforeNewOne` belongs now to the linked list. We search the + // largest entry that is smaller than the element to insert. + bytes32 previous; + bytes32 current = elementBeforeNewOne; + do { + previous = current; + current = self.nextMap[current]; + } while (current.smallerThan(elementToInsert)); + // Note: previous < elementToInsert < current + self.nextMap[previous] = elementToInsert; + self.nextMap[elementToInsert] = current; + + return true; + } + + /// Remove an element from the chain, clearing all related storage. + function remove(Data storage self, bytes32 elementToRemove) + internal + returns (bool) + { + if (!contains(self, elementToRemove)) { + return false; + } + self.nextMap[elementToRemove] = bytes32(0); + return true; + } + + function contains(Data storage self, bytes32 value) + internal + view + returns (bool) + { + if (value == QUEUE_START) { + return false; + } + // Note: QUEUE_END is not contained in the list since it has no + // successor. + return self.nextMap[value] != bytes32(0); + } + + // @dev orders are ordered by + // 1. their price - buyAmount/sellAmount + // 2. by the sellAmount + // 3. their userId, + function smallerThan(bytes32 orderLeft, bytes32 orderRight) + internal + pure + returns (bool) + { + ( + uint64 userIdLeft, + uint96 priceNumeratorLeft, + uint96 priceDenominatorLeft + ) = decodeOrder(orderLeft); + ( + uint64 userIdRight, + uint96 priceNumeratorRight, + uint96 priceDenominatorRight + ) = decodeOrder(orderRight); + + if ( + priceNumeratorLeft.mul(priceDenominatorRight) < + priceNumeratorRight.mul(priceDenominatorLeft) + ) return true; + if ( + priceNumeratorLeft.mul(priceDenominatorRight) > + priceNumeratorRight.mul(priceDenominatorLeft) + ) return false; + + if (priceNumeratorLeft < priceNumeratorRight) return true; + if (priceNumeratorLeft > priceNumeratorRight) return false; + require( + userIdLeft != userIdRight, + "user is not allowed to place same order twice" + ); + if (userIdLeft < userIdRight) { + return true; + } + return false; + } + + function first(Data storage self) internal view returns (bytes32) { + require(!isEmpty(self), "Trying to get first from empty set"); + return self.nextMap[QUEUE_START]; + } + + function next(Data storage self, bytes32 value) + internal + view + returns (bytes32) + { + require(value != QUEUE_END, "Trying to get next of last element"); + bytes32 nextElement = self.nextMap[value]; + require( + nextElement != bytes32(0), + "Trying to get next of non-existent element" + ); + return nextElement; + } + + function decodeOrder(bytes32 _orderData) + internal + pure + returns ( + uint64 userId, + uint96 buyAmount, + uint96 sellAmount + ) + { + // Note: converting to uint discards the binary digits that do not fit + // the type. + userId = uint64(uint256(_orderData) >> 192); + buyAmount = uint96(uint256(_orderData) >> 96); + sellAmount = uint96(uint256(_orderData)); + } + + function encodeOrder( + uint64 userId, + uint96 buyAmount, + uint96 sellAmount + ) internal pure returns (bytes32) { + return + bytes32( + (uint256(userId) << 192) + + (uint256(buyAmount) << 96) + + uint256(sellAmount) + ); + } +} diff --git a/contracts/test/IterableOrderedOrderListWrapper.sol b/contracts/test/IterableOrderedOrderListWrapper.sol new file mode 100644 index 0000000..0a905b2 --- /dev/null +++ b/contracts/test/IterableOrderedOrderListWrapper.sol @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: LGPL-3.0-or-newer +pragma solidity ^0.6.0; +import "../libraries/IterableOrderedOrderList.sol"; + +contract IterableOrderedOrderListWrapper { + using IterableOrderedOrderList for IterableOrderedOrderList.Data; + + IterableOrderedOrderList.Data internal data; + + function initializeEmptyList() public { + data.initializeEmptyList(); + } + + function insert(bytes32 value) public returns (bool) { + return data.insert(value, IterableOrderedOrderList.QUEUE_START); + } + + function insertAt(bytes32 value, bytes32 at) public returns (bool) { + return data.insert(value, at); + } + + function remove(bytes32 value) public returns (bool) { + return data.remove(value); + } + + function contains(bytes32 value) public view returns (bool) { + return data.contains(value); + } + + function isEmpty() public view returns (bool) { + return data.isEmpty(); + } + + function first() public view returns (bytes32) { + return data.first(); + } + + function next(bytes32 value) public view returns (bytes32) { + return data.next(value); + } + + function nextMap(bytes32 value) public view returns (bytes32) { + return data.nextMap[value]; + } + + function decodeOrder(bytes32 value) + public + pure + returns ( + uint64, + uint96, + uint96 + ) + { + return IterableOrderedOrderList.decodeOrder(value); + } + + function encodeOrder( + uint64 userId, + uint96 sellAmount, + uint96 buyAmount + ) public pure returns (bytes32) { + return + IterableOrderedOrderList.encodeOrder(userId, sellAmount, buyAmount); + } + + function smallerThan(bytes32 orderLeft, bytes32 orderRight) + public + pure + returns (bool) + { + return IterableOrderedOrderList.smallerThan(orderLeft, orderRight); + } +} diff --git a/src/priceCalculation.ts b/src/priceCalculation.ts index bd2291b..2ea0dd2 100644 --- a/src/priceCalculation.ts +++ b/src/priceCalculation.ts @@ -283,7 +283,7 @@ export async function getAllSellOrders( } export async function createTokensAndMintAndApprove( - easyAuction: Contract, + auctionContract: Contract, users: Wallet[], hre: HardhatRuntimeEnvironment, ): Promise<{ auctioningToken: Contract; biddingToken: Contract }> { @@ -295,12 +295,12 @@ export async function createTokensAndMintAndApprove( await biddingToken.mint(user.address, BigNumber.from(10).pow(30)); await biddingToken .connect(user) - .approve(easyAuction.address, BigNumber.from(10).pow(30)); + .approve(auctionContract.address, BigNumber.from(10).pow(30)); await auctioningToken.mint(user.address, BigNumber.from(10).pow(30)); await auctioningToken .connect(user) - .approve(easyAuction.address, BigNumber.from(10).pow(30)); + .approve(auctionContract.address, BigNumber.from(10).pow(30)); } return { auctioningToken: auctioningToken, biddingToken: biddingToken }; } diff --git a/src/ts/types.ts b/src/ts/types.ts index 87dc7f3..0b9873a 100644 --- a/src/ts/types.ts +++ b/src/ts/types.ts @@ -13,3 +13,14 @@ export interface InitiateAuctionInput { allowListManager: BytesLike; allowListData: BytesLike; } + +export interface InitiateChannelAuctionInput { + auctioningToken: Contract; + biddingToken: Contract; + _auctionedSellAmount: BigNumberish; + _auctioneerBuyAmountMinimum: BigNumberish; + _auctioneerBuyAmountMaximum: BigNumberish; + _auctionStartDate: BigNumberish; + _minimumBiddingAmountPerOrder: BigNumberish; + _maxDuration: BigNumberish; +} diff --git a/test/contract/Channelauction.spec.ts b/test/contract/Channelauction.spec.ts new file mode 100644 index 0000000..a7e83b2 --- /dev/null +++ b/test/contract/Channelauction.spec.ts @@ -0,0 +1,3316 @@ +import { deployMockContract } from "@ethereum-waffle/mock-contract"; +import { expect } from "chai"; +import { Contract, BigNumber } from "ethers"; +import hre, { artifacts, ethers, waffle } from "hardhat"; +import "@nomiclabs/hardhat-ethers"; + +import { + toReceivedFunds, + encodeOrder, + queueStartElement, + createTokensAndMintAndApprove, + placeOrders, + calculateClearingPrice, + getAllSellOrders, + getClearingPriceFromInitialOrder, +} from "../../src/priceCalculation"; + +import { + createAuctionWithDefaults, + createAuctionWithDefaultsAndReturnId, + createChannelAuctionWithDefaults, +} from "./defaultContractInteractions"; +import { + sendTxAndGetReturnValue, + closeAuction, + increaseTime, + claimFromAllOrders, + MAGIC_VALUE_FROM_ALLOW_LIST_VERIFIER_INTERFACE, +} from "./utilities"; + +///////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Some tests use different test cases 1,..,10. These test cases are illustrated in the following jam board: +// https://jamboard.google.com/d/1DMgMYCQQzsSLKPq_hlK3l32JNBbRdIhsOrLB1oHaEYY/edit?usp=sharing +///////////////////////////////////////////////////////////////////////////////////////////////////////////// + +describe("ChannelAuction", async () => { + const [user_1, user_2, user_3] = waffle.provider.getWallets(); + let channelAuction: Contract; + beforeEach(async () => { + const ChannelAuction = await ethers.getContractFactory("ChannelAuction"); + channelAuction = await ChannelAuction.deploy(); + }); + describe("initiate Auction", async () => { + it("throws if minimumBiddingAmountPerOrder is zero", async () => { + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2], + hre, + ); + + await expect( + createChannelAuctionWithDefaults(channelAuction, { + auctioningToken, + biddingToken, + _minimumBiddingAmountPerOrder: 0, + }), + ).to.be.revertedWith( + "minimumBiddingAmountPerOrder is not allowed to be zero", + ); + }); + it.only("throws if auctioned amount is zero", async () => { + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2], + hre, + ); + + await expect( + createChannelAuctionWithDefaults(channelAuction, { + auctioningToken, + biddingToken, + _auctionedSellAmount: 0, + }), + ).to.be.revertedWith("cannot auction zero tokens"); + }); + it.only("throws if auction is a giveaway", async () => { + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2], + hre, + ); + + await expect( + createChannelAuctionWithDefaults(channelAuction, { + auctioningToken, + biddingToken, + _auctioneerBuyAmountMinimum: 0, + }), + ).to.be.revertedWith("_auctioneerBuyAmountMinimum must be positive"); + }); + it.only("throws if auction end is not in the future", async () => { + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2], + hre, + ); + + const now = (await ethers.provider.getBlock("latest")).timestamp; + await expect( + createChannelAuctionWithDefaults(channelAuction, { + auctioningToken, + biddingToken, + _auctionStartDate: now - 60 * 60, + _maxDuration: 60, + }), + ).to.be.revertedWith("time periods are not configured correctly"); + }); + it.only("initiateAuction stores the parameters correctly", async () => { + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2], + hre, + ); + + const now = (await ethers.provider.getBlock("latest")).timestamp; + const auctionStartDate = now + 1337; + const _maxDuration = 2000; + + const initialAuctionOrder = { + sellAmount: ethers.utils.parseEther("1"), + buyAmount: ethers.utils.parseEther("1"), + userId: BigNumber.from(1), + }; + const auctionId: BigNumber = await createChannelAuctionWithDefaultsAndReturnId( + channelAuction, + { + auctioningToken, + biddingToken, + auctionedSellAmount: initialAuctionOrder.sellAmount, + _buyAmountMaximum: initialAuctionOrder.buyAmount, + auctionStartDate, + _maxDuration, + }, + ); + const auctionData = await channelAuction.auctionData(auctionId); + expect(auctionData.auctioningToken).to.equal(auctioningToken.address); + expect(auctionData.biddingToken).to.equal(biddingToken.address); + expect(auctionData.initialAuctionOrder).to.equal( + encodeOrder(initialAuctionOrder), + ); + expect(auctionData.auctionEndDate).to.be.equal(auctionEndDate); + expect(auctionData.orderCancellationEndDate).to.be.equal( + orderCancellationEndDate, + ); + await expect(auctionData.clearingPriceOrder).to.equal( + encodeOrder({ + userId: BigNumber.from(0), + sellAmount: ethers.utils.parseEther("0"), + buyAmount: ethers.utils.parseEther("0"), + }), + ); + expect(auctionData.volumeClearingPriceOrder).to.be.equal(0); + + expect(await auctioningToken.balanceOf(channelAuction.address)).to.equal( + ethers.utils.parseEther("1"), + ); + }); + }); + + describe("getUserId", async () => { + it("creates new userIds", async () => { + expect( + await sendTxAndGetReturnValue( + channelAuction, + "getUserId(address)", + user_1.address, + ), + ).to.equal(1); + expect( + await sendTxAndGetReturnValue( + channelAuction, + "getUserId(address)", + user_2.address, + ), + ).to.equal(2); + expect( + await sendTxAndGetReturnValue( + channelAuction, + "getUserId(address)", + user_1.address, + ), + ).to.equal(1); + }); + }); + describe("placeOrdersOnBehalf", async () => { + it("places a new order and checks that tokens were transferred", async () => { + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2], + hre, + ); + const now = (await ethers.provider.getBlock("latest")).timestamp; + const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + channelAuction, + { + auctioningToken, + biddingToken, + orderCancellationEndDate: now + 3600, + auctionEndDate: now + 3600, + }, + ); + + const balanceBeforeOrderPlacement = await biddingToken.balanceOf( + user_1.address, + ); + const balanceBeforeOrderPlacementOfUser2 = await biddingToken.balanceOf( + user_2.address, + ); + const sellAmount = ethers.utils.parseEther("1").add(1); + const buyAmount = ethers.utils.parseEther("1"); + + await channelAuction + .connect(user_1) + .placeSellOrdersOnBehalf( + auctionId, + [buyAmount], + [sellAmount], + [queueStartElement], + "0x", + user_2.address, + ); + + expect(await biddingToken.balanceOf(channelAuction.address)).to.equal( + sellAmount, + ); + expect(await biddingToken.balanceOf(user_1.address)).to.equal( + balanceBeforeOrderPlacement.sub(sellAmount), + ); + expect(await biddingToken.balanceOf(user_2.address)).to.equal( + balanceBeforeOrderPlacementOfUser2, + ); + const userId = BigNumber.from( + await channelAuction.callStatic.getUserId(user_2.address), + ); + await channelAuction + .connect(user_2) + .cancelSellOrders(auctionId, [ + encodeOrder({ sellAmount, buyAmount, userId }), + ]); + expect(await biddingToken.balanceOf(channelAuction.address)).to.equal( + "0", + ); + expect(await biddingToken.balanceOf(user_2.address)).to.equal( + balanceBeforeOrderPlacementOfUser2.add(sellAmount), + ); + }); + }); + + describe("placeOrders", async () => { + it("one can not place orders, if auction is not yet initiated", async () => { + await expect( + channelAuction.placeSellOrders( + 0, + [ethers.utils.parseEther("1")], + [ethers.utils.parseEther("1").add(1)], + [queueStartElement], + "0x", + ), + ).to.be.revertedWith("no longer in order placement phase"); + }); + it("one can not place orders, if auction is over", async () => { + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2], + hre, + ); + const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + channelAuction, + { + auctioningToken, + biddingToken, + }, + ); + await closeAuction(channelAuction, auctionId); + await expect( + channelAuction.placeSellOrders( + 0, + [ethers.utils.parseEther("1")], + [ethers.utils.parseEther("1").add(1)], + [queueStartElement], + "0x", + ), + ).to.be.revertedWith("no longer in order placement phase"); + }); + it("one can not place orders, with a worser or same rate", async () => { + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2], + hre, + ); + const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + channelAuction, + { + auctioningToken, + biddingToken, + }, + ); + await expect( + channelAuction.placeSellOrders( + auctionId, + [ethers.utils.parseEther("1").add(1)], + [ethers.utils.parseEther("1")], + [queueStartElement], + "0x", + ), + ).to.be.revertedWith("limit price not better than mimimal offer"); + await expect( + channelAuction.placeSellOrders( + auctionId, + [ethers.utils.parseEther("1")], + [ethers.utils.parseEther("1")], + [queueStartElement], + "0x", + ), + ).to.be.revertedWith("limit price not better than mimimal offer"); + }); + it("one can not place orders with buyAmount == 0", async () => { + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2], + hre, + ); + const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + channelAuction, + { + auctioningToken, + biddingToken, + }, + ); + await expect( + channelAuction.placeSellOrders( + auctionId, + [ethers.utils.parseEther("0")], + [ethers.utils.parseEther("1")], + [queueStartElement], + "0x", + ), + ).to.be.revertedWith("_minBuyAmounts must be greater than 0"); + }); + it("does not withdraw funds, if orders are placed twice", async () => { + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2], + hre, + ); + const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + channelAuction, + { + auctioningToken, + biddingToken, + }, + ); + await expect(() => + channelAuction.placeSellOrders( + auctionId, + [ethers.utils.parseEther("1").sub(1)], + [ethers.utils.parseEther("1")], + [queueStartElement], + "0x", + ), + ).to.changeTokenBalances( + biddingToken, + [user_1], + [ethers.utils.parseEther("-1")], + ); + await expect(() => + channelAuction.placeSellOrders( + auctionId, + [ethers.utils.parseEther("1").sub(1)], + [ethers.utils.parseEther("1")], + [queueStartElement], + "0x", + ), + ).to.changeTokenBalances(biddingToken, [user_1], [BigNumber.from(0)]); + }); + it("places a new order and checks that tokens were transferred", async () => { + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2], + hre, + ); + const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + channelAuction, + { + auctioningToken, + biddingToken, + }, + ); + + const balanceBeforeOrderPlacement = await biddingToken.balanceOf( + user_1.address, + ); + const sellAmount = ethers.utils.parseEther("1").add(1); + const buyAmount = ethers.utils.parseEther("1"); + + await channelAuction.placeSellOrders( + auctionId, + [buyAmount, buyAmount], + [sellAmount, sellAmount.add(1)], + [queueStartElement, queueStartElement], + "0x", + ); + const transferredbiddingTokenAmount = sellAmount.add(sellAmount.add(1)); + + expect(await biddingToken.balanceOf(channelAuction.address)).to.equal( + transferredbiddingTokenAmount, + ); + expect(await biddingToken.balanceOf(user_1.address)).to.equal( + balanceBeforeOrderPlacement.sub(transferredbiddingTokenAmount), + ); + }); + it("order placement reverts, if order placer is not allowed", async () => { + const verifier = await artifacts.readArtifact("AllowListVerifier"); + const verifierMocked = await deployMockContract(user_3, verifier.abi); + await verifierMocked.mock.isAllowed.returns("0x00000000"); + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2], + hre, + ); + + const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + channelAuction, + { + auctioningToken, + biddingToken, + allowListManager: verifierMocked.address, + }, + ); + + const sellAmount = ethers.utils.parseEther("1").add(1); + const buyAmount = ethers.utils.parseEther("1"); + await expect( + channelAuction.placeSellOrders( + auctionId, + [buyAmount], + [sellAmount], + [queueStartElement], + "0x", + ), + ).to.be.revertedWith("user not allowed to place order"); + }); + it("order placement reverts, if allow manager is an EOA", async () => { + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2], + hre, + ); + + const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + channelAuction, + { + auctioningToken, + biddingToken, + allowListManager: user_3.address, + }, + ); + + const sellAmount = ethers.utils.parseEther("1").add(1); + const buyAmount = ethers.utils.parseEther("1"); + await expect( + channelAuction.placeSellOrders( + auctionId, + [buyAmount], + [sellAmount], + [queueStartElement], + "0x", + ), + ).to.be.revertedWith("function call to a non-contract account"); + }); + it("allow manager can not mutate state", async () => { + const StateChangingAllowListManager = await ethers.getContractFactory( + "StateChangingAllowListVerifier", + ); + + const stateChangingAllowListManager = await StateChangingAllowListManager.deploy(); + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2], + hre, + ); + + const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + channelAuction, + { + auctioningToken, + biddingToken, + allowListManager: stateChangingAllowListManager.address, + }, + ); + + const sellAmount = ethers.utils.parseEther("1").add(1); + const buyAmount = ethers.utils.parseEther("1"); + expect( + await stateChangingAllowListManager.callStatic.isAllowed( + user_1.address, + auctionId, + "0x", + ), + ).to.be.equal(MAGIC_VALUE_FROM_ALLOW_LIST_VERIFIER_INTERFACE); + await expect( + channelAuction.placeSellOrders( + auctionId, + [buyAmount], + [sellAmount], + [queueStartElement], + "0x", + ), + ).to.be.revertedWith( + "Transaction reverted and Hardhat couldn't infer the reason. Please report this to help us improve Hardhat", + ); + }); + + it("order placement works, if order placer is allowed", async () => { + const verifier = await artifacts.readArtifact("AllowListVerifier"); + const verifierMocked = await deployMockContract(user_3, verifier.abi); + await verifierMocked.mock.isAllowed.returns( + MAGIC_VALUE_FROM_ALLOW_LIST_VERIFIER_INTERFACE, + ); + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2], + hre, + ); + + const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + channelAuction, + { + auctioningToken, + biddingToken, + allowListManager: verifierMocked.address, + }, + ); + + const sellAmount = ethers.utils.parseEther("1").add(1); + const buyAmount = ethers.utils.parseEther("1"); + await expect( + channelAuction.placeSellOrders( + auctionId, + [buyAmount], + [sellAmount], + [queueStartElement], + "0x", + ), + ).to.emit(channelAuction, "NewSellOrder"); + }); + it("an order is only placed once", async () => { + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2], + hre, + ); + const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + channelAuction, + { + auctioningToken, + biddingToken, + }, + ); + + const sellAmount = ethers.utils.parseEther("1").add(1); + const buyAmount = ethers.utils.parseEther("1"); + + await channelAuction.placeSellOrders( + auctionId, + [buyAmount], + [sellAmount], + [queueStartElement], + "0x", + ); + const allPlacedOrders = await getAllSellOrders(channelAuction, auctionId); + expect(allPlacedOrders.length).to.be.equal(1); + }); + it("throws, if DDOS attack with small order amounts is started", async () => { + const initialAuctionOrder = { + sellAmount: ethers.utils.parseEther("1"), + buyAmount: ethers.utils.parseEther("1"), + userId: BigNumber.from(1), + }; + const sellOrders = [ + { + sellAmount: ethers.utils.parseEther("1").div(5000), + buyAmount: ethers.utils.parseEther("1").div(10000), + userId: BigNumber.from(1), + }, + ]; + + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2], + hre, + ); + + const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + channelAuction, + { + auctioningToken, + biddingToken, + auctionedSellAmount: initialAuctionOrder.sellAmount, + minBuyAmount: initialAuctionOrder.buyAmount, + minimumBiddingAmountPerOrder: ethers.utils.parseEther("1").div(100), + }, + ); + await expect( + channelAuction.placeSellOrders( + auctionId, + sellOrders.map((buyOrder) => buyOrder.buyAmount), + sellOrders.map((buyOrder) => buyOrder.sellAmount), + Array(sellOrders.length).fill(queueStartElement), + "0x", + ), + ).to.be.revertedWith("order too small"); + }); + it("fails, if transfers are failing", async () => { + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2], + hre, + ); + const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + channelAuction, + { + auctioningToken, + biddingToken, + }, + ); + const sellAmount = ethers.utils.parseEther("1").add(1); + const buyAmount = ethers.utils.parseEther("1"); + await biddingToken.approve( + channelAuction.address, + ethers.utils.parseEther("0"), + ); + + await expect( + channelAuction.placeSellOrders( + auctionId, + [buyAmount, buyAmount], + [sellAmount, sellAmount.add(1)], + [queueStartElement, queueStartElement], + "0x", + ), + ).to.be.revertedWith("ERC20: transfer amount exceeds allowance"); + }); + }); + describe("precalculateSellAmountSum", async () => { + it("fails if too many orders are considered", async () => { + const initialAuctionOrder = { + sellAmount: ethers.utils.parseEther("1"), + buyAmount: ethers.utils.parseEther("1"), + userId: BigNumber.from(1), + }; + const sellOrders = [ + { + sellAmount: ethers.utils.parseEther("1").mul(2), + buyAmount: ethers.utils.parseEther("1").div(5), + userId: BigNumber.from(1), + }, + { + sellAmount: ethers.utils.parseEther("1").mul(2), + buyAmount: ethers.utils.parseEther("1"), + userId: BigNumber.from(1), + }, + + { + sellAmount: ethers.utils.parseEther("1").mul(2).add(1), + buyAmount: ethers.utils.parseEther("1").mul(2), + userId: BigNumber.from(1), + }, + ]; + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2], + hre, + ); + + const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + channelAuction, + { + auctioningToken, + biddingToken, + auctionedSellAmount: initialAuctionOrder.sellAmount, + minBuyAmount: initialAuctionOrder.buyAmount, + }, + ); + await placeOrders(channelAuction, sellOrders, auctionId, hre); + + await closeAuction(channelAuction, auctionId); + await expect( + channelAuction.precalculateSellAmountSum(auctionId, 3), + ).to.be.revertedWith("too many orders summed up"); + }); + it("fails if queue end is reached", async () => { + const initialAuctionOrder = { + sellAmount: ethers.utils.parseEther("1"), + buyAmount: ethers.utils.parseEther("1"), + userId: BigNumber.from(1), + }; + const sellOrders = [ + { + sellAmount: ethers.utils.parseEther("1").mul(2), + buyAmount: ethers.utils.parseEther("1").div(5), + userId: BigNumber.from(1), + }, + ]; + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2], + hre, + ); + + const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + channelAuction, + { + auctioningToken, + biddingToken, + auctionedSellAmount: initialAuctionOrder.sellAmount, + minBuyAmount: initialAuctionOrder.buyAmount, + }, + ); + await placeOrders(channelAuction, sellOrders, auctionId, hre); + + await closeAuction(channelAuction, auctionId); + await expect( + channelAuction.precalculateSellAmountSum(auctionId, 2), + ).to.be.revertedWith("reached end of order list"); + }); + it("verifies that interimSumBidAmount and iterOrder is set correctly", async () => { + const initialAuctionOrder = { + sellAmount: ethers.utils.parseEther("1"), + buyAmount: ethers.utils.parseEther("1"), + userId: BigNumber.from(1), + }; + const sellOrders = [ + { + sellAmount: ethers.utils.parseEther("1").mul(2), + buyAmount: ethers.utils.parseEther("1").div(5), + userId: BigNumber.from(1), + }, + { + sellAmount: ethers.utils.parseEther("1").mul(2), + buyAmount: ethers.utils.parseEther("1"), + userId: BigNumber.from(1), + }, + + { + sellAmount: ethers.utils.parseEther("1").mul(2).add(1), + buyAmount: ethers.utils.parseEther("1").mul(2), + userId: BigNumber.from(1), + }, + ]; + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2], + hre, + ); + + const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + channelAuction, + { + auctioningToken, + biddingToken, + auctionedSellAmount: initialAuctionOrder.sellAmount, + minBuyAmount: initialAuctionOrder.buyAmount, + }, + ); + await placeOrders(channelAuction, sellOrders, auctionId, hre); + + await closeAuction(channelAuction, auctionId); + + await channelAuction.precalculateSellAmountSum(auctionId, 1); + const auctionData = await channelAuction.auctionData(auctionId); + expect(auctionData.interimSumBidAmount).to.equal( + sellOrders[0].sellAmount, + ); + + expect(auctionData.interimOrder).to.equal(encodeOrder(sellOrders[0])); + }); + it("verifies that interimSumBidAmount and iterOrder takes correct starting values by applying twice", async () => { + const initialAuctionOrder = { + sellAmount: ethers.utils.parseEther("1"), + buyAmount: ethers.utils.parseEther("1"), + userId: BigNumber.from(1), + }; + const sellOrders = [ + { + sellAmount: ethers.utils.parseEther("1"), + buyAmount: ethers.utils.parseEther("1").div(10), + userId: BigNumber.from(1), + }, + { + sellAmount: ethers.utils.parseEther("1"), + buyAmount: ethers.utils.parseEther("1").div(10), + userId: BigNumber.from(2), + }, + { + sellAmount: ethers.utils.parseEther("1").mul(2), + buyAmount: ethers.utils.parseEther("1"), + userId: BigNumber.from(1), + }, + + { + sellAmount: ethers.utils.parseEther("1").mul(2).add(1), + buyAmount: ethers.utils.parseEther("1").mul(2), + userId: BigNumber.from(1), + }, + ]; + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2], + hre, + ); + + const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + channelAuction, + { + auctioningToken, + biddingToken, + auctionedSellAmount: initialAuctionOrder.sellAmount, + minBuyAmount: initialAuctionOrder.buyAmount, + }, + ); + await placeOrders(channelAuction, sellOrders, auctionId, hre); + + await closeAuction(channelAuction, auctionId); + + await channelAuction.precalculateSellAmountSum(auctionId, 1); + await channelAuction.precalculateSellAmountSum(auctionId, 1); + const auctionData = await channelAuction.auctionData(auctionId); + expect(auctionData.interimSumBidAmount).to.equal( + sellOrders[0].sellAmount.add(sellOrders[0].sellAmount), + ); + + expect(auctionData.interimOrder).to.equal(encodeOrder(sellOrders[1])); + }); + }); + describe("settleAuction", async () => { + it("checks case 4, it verifies the price in case of clearing order == initialAuctionOrder", async () => { + const initialAuctionOrder = { + sellAmount: ethers.utils.parseEther("1"), + buyAmount: ethers.utils.parseEther("1"), + userId: BigNumber.from(1), + }; + const sellOrders = [ + { + sellAmount: ethers.utils.parseEther("1").div(10), + buyAmount: ethers.utils.parseEther("1").div(20), + userId: BigNumber.from(1), + }, + ]; + + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2], + hre, + ); + + await createAuctionWithDefaults(channelAuction, { + auctioningToken, + biddingToken, + auctionedSellAmount: initialAuctionOrder.sellAmount, + minBuyAmount: initialAuctionOrder.buyAmount, + }); + + const auctionId = BigNumber.from(1); + await placeOrders(channelAuction, sellOrders, auctionId, hre); + + await closeAuction(channelAuction, auctionId); + + const { clearingOrder: price } = await calculateClearingPrice( + channelAuction, + auctionId, + ); + await expect(channelAuction.settleAuction(auctionId)) + .to.emit(channelAuction, "AuctionCleared") + .withArgs( + auctionId, + sellOrders[0].sellAmount.mul(price.buyAmount).div(price.sellAmount), + sellOrders[0].sellAmount, + encodeOrder(getClearingPriceFromInitialOrder(initialAuctionOrder)), + ); + const auctionData = await channelAuction.auctionData(auctionId); + expect(auctionData.clearingPriceOrder).to.equal(encodeOrder(price)); + }); + it("checks case 4, it verifies the price in case of clearingOrder == initialAuctionOrder", async () => { + const initialAuctionOrder = { + sellAmount: ethers.utils.parseEther("5"), + buyAmount: ethers.utils.parseEther("1"), + userId: BigNumber.from(1), + }; + const sellOrders = [ + { + sellAmount: ethers.utils.parseEther("0.1"), + buyAmount: ethers.utils.parseEther("0.1"), + userId: BigNumber.from(1), + }, + ]; + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2], + hre, + ); + + await createAuctionWithDefaults(channelAuction, { + auctioningToken, + biddingToken, + auctionedSellAmount: initialAuctionOrder.sellAmount, + minBuyAmount: initialAuctionOrder.buyAmount, + }); + const auctionId = BigNumber.from(1); + await placeOrders(channelAuction, sellOrders, auctionId, hre); + + await closeAuction(channelAuction, auctionId); + + const { clearingOrder: price } = await calculateClearingPrice( + channelAuction, + auctionId, + ); + await expect(channelAuction.settleAuction(auctionId)) + .to.emit(channelAuction, "AuctionCleared") + .withArgs( + auctionId, + sellOrders[0].sellAmount.mul(price.buyAmount).div(price.sellAmount), + sellOrders[0].sellAmount, + encodeOrder(getClearingPriceFromInitialOrder(initialAuctionOrder)), + ); + const auctionData = await channelAuction.auctionData(auctionId); + expect(auctionData.clearingPriceOrder).to.equal(encodeOrder(price)); + await claimFromAllOrders(channelAuction, auctionId, sellOrders); + }); + it("checks case 4, it verifies the price in case of clearingOrder == initialAuctionOrder with 3 orders", async () => { + const initialAuctionOrder = { + sellAmount: ethers.utils.parseEther("2"), + buyAmount: ethers.utils.parseEther("1"), + userId: BigNumber.from(1), + }; + const sellOrders = [ + { + sellAmount: ethers.utils.parseEther("0.1"), + buyAmount: ethers.utils.parseEther("0.1"), + userId: BigNumber.from(1), + }, + { + sellAmount: ethers.utils.parseEther("0.1"), + buyAmount: ethers.utils.parseEther("0.1"), + userId: BigNumber.from(2), + }, + { + sellAmount: ethers.utils.parseEther("0.1"), + buyAmount: ethers.utils.parseEther("0.1"), + userId: BigNumber.from(3), + }, + ]; + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2, user_3], + hre, + ); + + await createAuctionWithDefaults(channelAuction, { + auctioningToken, + biddingToken, + auctionedSellAmount: initialAuctionOrder.sellAmount, + minBuyAmount: initialAuctionOrder.buyAmount, + }); + const auctionId = BigNumber.from(1); + await placeOrders(channelAuction, sellOrders, auctionId, hre); + + await closeAuction(channelAuction, auctionId); + const { clearingOrder: price } = await calculateClearingPrice( + channelAuction, + auctionId, + ); + await expect(channelAuction.settleAuction(auctionId)) + .to.emit(channelAuction, "AuctionCleared") + .withArgs( + auctionId, + sellOrders[0].sellAmount + .mul(3) + .mul(price.buyAmount) + .div(price.sellAmount), + sellOrders[0].sellAmount.mul(3), + encodeOrder(getClearingPriceFromInitialOrder(initialAuctionOrder)), + ); + const auctionData = await channelAuction.auctionData(auctionId); + expect(auctionData.clearingPriceOrder).to.equal( + encodeOrder(getClearingPriceFromInitialOrder(initialAuctionOrder)), + ); + await claimFromAllOrders(channelAuction, auctionId, sellOrders); + }); + it("checks case 6, it verifies the price in case of clearingOrder == initialOrder, although last iterOrder would also be possible", async () => { + // This test demonstrates the case 6, + // where price could be either the auctioningOrder or sellOrder + const initialAuctionOrder = { + sellAmount: ethers.utils.parseEther("500"), + buyAmount: ethers.utils.parseEther("1"), + userId: BigNumber.from(1), + }; + const sellOrders = [ + { + sellAmount: ethers.utils.parseEther("1"), + buyAmount: ethers.utils.parseEther("1"), + userId: BigNumber.from(1), + }, + ]; + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2], + hre, + ); + + await createAuctionWithDefaults(channelAuction, { + auctioningToken, + biddingToken, + auctionedSellAmount: initialAuctionOrder.sellAmount, + minBuyAmount: initialAuctionOrder.buyAmount, + }); + const auctionId = BigNumber.from(1); + await placeOrders(channelAuction, sellOrders, auctionId, hre); + + await closeAuction(channelAuction, auctionId); + + await expect(channelAuction.settleAuction(auctionId)) + .to.emit(channelAuction, "AuctionCleared") + .withArgs( + auctionId, + initialAuctionOrder.sellAmount, + sellOrders[0].sellAmount, + encodeOrder(getClearingPriceFromInitialOrder(initialAuctionOrder)), + ); + const auctionData = await channelAuction.auctionData(auctionId); + expect(auctionData.clearingPriceOrder).to.equal( + encodeOrder(getClearingPriceFromInitialOrder(initialAuctionOrder)), + ); + await channelAuction.claimFromParticipantOrder( + auctionId, + sellOrders.map((order) => encodeOrder(order)), + ); + }); + it("checks case 12, it verifies that price can not be the initial auction price (Adam's case)", async () => { + const initialAuctionOrder = { + sellAmount: ethers.utils.parseEther("1").add(1), + buyAmount: ethers.utils.parseEther("0.1"), + userId: BigNumber.from(1), + }; + const sellOrders = [ + { + sellAmount: ethers.utils.parseEther("0.5"), + buyAmount: ethers.utils.parseEther("1"), + userId: BigNumber.from(1), + }, + { + sellAmount: BigNumber.from(2), + buyAmount: BigNumber.from(4), + userId: BigNumber.from(2), + }, + ]; + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2], + hre, + ); + + await createAuctionWithDefaults(channelAuction, { + auctioningToken, + biddingToken, + auctionedSellAmount: initialAuctionOrder.sellAmount, + minBuyAmount: initialAuctionOrder.buyAmount, + }); + const auctionId = BigNumber.from(1); + await placeOrders(channelAuction, sellOrders, auctionId, hre); + + await closeAuction(channelAuction, auctionId); + + await channelAuction.settleAuction(auctionId); + await channelAuction.auctionData(auctionId); + await claimFromAllOrders(channelAuction, auctionId, sellOrders); + }); + it("checks case 3, it verifies the price in case of clearingOrder != placed order with 3x participation", async () => { + const initialAuctionOrder = { + sellAmount: ethers.utils.parseEther("500"), + buyAmount: ethers.utils.parseEther("1"), + userId: BigNumber.from(1), + }; + const sellOrders = [ + { + sellAmount: ethers.utils.parseEther("1"), + buyAmount: ethers.utils.parseEther("1"), + userId: BigNumber.from(1), + }, + { + sellAmount: ethers.utils.parseEther("1"), + buyAmount: ethers.utils.parseEther("1"), + userId: BigNumber.from(3), + }, + { + sellAmount: ethers.utils.parseEther("1"), + buyAmount: ethers.utils.parseEther("1"), + userId: BigNumber.from(2), + }, + ]; + + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2, user_3], + hre, + ); + + await createAuctionWithDefaults(channelAuction, { + auctioningToken, + biddingToken, + auctionedSellAmount: initialAuctionOrder.sellAmount, + minBuyAmount: initialAuctionOrder.buyAmount, + }); + const auctionId = BigNumber.from(1); + await placeOrders(channelAuction, sellOrders, auctionId, hre); + + await closeAuction(channelAuction, auctionId); + + await channelAuction.settleAuction(auctionId); + const auctionData = await channelAuction.auctionData(auctionId); + expect(auctionData.clearingPriceOrder).to.equal( + encodeOrder({ + sellAmount: ethers.utils.parseEther("3"), + buyAmount: initialAuctionOrder.sellAmount, + userId: BigNumber.from(0), + }), + ); + expect(auctionData.volumeClearingPriceOrder).to.equal(0); + await claimFromAllOrders(channelAuction, auctionId, sellOrders); + }); + it("checks case 8, it verifies the price in case of no participation of the auction", async () => { + const initialAuctionOrder = { + sellAmount: BigNumber.from(1000), + buyAmount: BigNumber.from(1000), + userId: BigNumber.from(1), + }; + + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2], + hre, + ); + + await createAuctionWithDefaults(channelAuction, { + auctioningToken, + biddingToken, + auctionedSellAmount: initialAuctionOrder.sellAmount, + minBuyAmount: initialAuctionOrder.buyAmount, + }); + const auctionId = BigNumber.from(1); + + await closeAuction(channelAuction, auctionId); + + await channelAuction.settleAuction(auctionId); + const auctionData = await channelAuction.auctionData(auctionId); + expect(auctionData.clearingPriceOrder).to.equal( + encodeOrder(getClearingPriceFromInitialOrder(initialAuctionOrder)), + ); + expect(auctionData.volumeClearingPriceOrder).to.equal(BigNumber.from(0)); + }); + it("checks case 2, it verifies the price in case without a partially filled order", async () => { + const initialAuctionOrder = { + sellAmount: ethers.utils.parseEther("1"), + buyAmount: ethers.utils.parseEther("1"), + userId: BigNumber.from(1), + }; + const sellOrders = [ + { + sellAmount: ethers.utils.parseEther("1").mul(2), + buyAmount: ethers.utils.parseEther("1").div(2), + userId: BigNumber.from(1), + }, + { + sellAmount: ethers.utils.parseEther("1").add(1), + buyAmount: ethers.utils.parseEther("1"), + userId: BigNumber.from(1), + }, + ]; + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2], + hre, + ); + + const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + channelAuction, + { + auctioningToken, + biddingToken, + auctionedSellAmount: initialAuctionOrder.sellAmount, + minBuyAmount: initialAuctionOrder.buyAmount, + }, + ); + await placeOrders(channelAuction, sellOrders, auctionId, hre); + + await closeAuction(channelAuction, auctionId); + await channelAuction.settleAuction(auctionId); + const auctionData = await channelAuction.auctionData(auctionId); + expect(auctionData.clearingPriceOrder).to.equal( + encodeOrder({ + sellAmount: sellOrders[0].sellAmount, + buyAmount: initialAuctionOrder.sellAmount, + userId: BigNumber.from(0), + }), + ); + expect(auctionData.volumeClearingPriceOrder).to.equal(0); + await claimFromAllOrders(channelAuction, auctionId, sellOrders); + }); + it("checks case 10, verifies the price in case one sellOrder is eating initialAuctionOrder completely", async () => { + const initialAuctionOrder = { + sellAmount: ethers.utils.parseEther("1"), + buyAmount: ethers.utils.parseEther("1"), + userId: BigNumber.from(1), + }; + const sellOrders = [ + { + sellAmount: ethers.utils.parseEther("1").mul(20), + buyAmount: ethers.utils.parseEther("1").mul(10), + userId: BigNumber.from(1), + }, + ]; + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2], + hre, + ); + + const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + channelAuction, + { + auctioningToken, + biddingToken, + auctionedSellAmount: initialAuctionOrder.sellAmount, + minBuyAmount: initialAuctionOrder.buyAmount, + }, + ); + await placeOrders(channelAuction, sellOrders, auctionId, hre); + + await closeAuction(channelAuction, auctionId); + await channelAuction.settleAuction(auctionId); + const auctionData = await channelAuction.auctionData(auctionId); + expect(auctionData.clearingPriceOrder).to.equal( + encodeOrder(sellOrders[0]), + ); + expect(auctionData.volumeClearingPriceOrder).to.equal( + initialAuctionOrder.sellAmount + .mul(sellOrders[0].sellAmount) + .div(sellOrders[0].buyAmount), + ); + await claimFromAllOrders(channelAuction, auctionId, sellOrders); + }); + it("checks case 5, bidding amount matches min buyAmount of initialOrder perfectly", async () => { + const initialAuctionOrder = { + sellAmount: ethers.utils.parseEther("1"), + buyAmount: ethers.utils.parseEther("0.5"), + userId: BigNumber.from(1), + }; + const sellOrders = [ + { + sellAmount: ethers.utils.parseEther("0.5"), + buyAmount: ethers.utils.parseEther("0.5"), + userId: BigNumber.from(2), + }, + { + sellAmount: ethers.utils.parseEther("0.5"), + buyAmount: ethers.utils.parseEther("0.5"), + userId: BigNumber.from(3), + }, + ]; + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2, user_3], + hre, + ); + + const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + channelAuction, + { + auctioningToken, + biddingToken, + auctionedSellAmount: initialAuctionOrder.sellAmount, + minBuyAmount: initialAuctionOrder.buyAmount, + }, + ); + await placeOrders(channelAuction, sellOrders, auctionId, hre); + + await closeAuction(channelAuction, auctionId); + await channelAuction.settleAuction(auctionId); + const auctionData = await channelAuction.auctionData(auctionId); + expect(auctionData.clearingPriceOrder).to.eql(encodeOrder(sellOrders[1])); + expect(auctionData.volumeClearingPriceOrder).to.equal( + sellOrders[1].sellAmount, + ); + await expect(() => + channelAuction.claimFromParticipantOrder(auctionId, [ + encodeOrder(sellOrders[0]), + ]), + ).to.changeTokenBalances( + auctioningToken, + [user_2], + [sellOrders[0].sellAmount], + ); + await expect(() => + channelAuction.claimFromParticipantOrder(auctionId, [ + encodeOrder(sellOrders[1]), + ]), + ).to.changeTokenBalances( + auctioningToken, + [user_3], + [sellOrders[1].sellAmount], + ); + }); + it("checks case 7, bidding amount matches min buyAmount of initialOrder perfectly with additional order", async () => { + const initialAuctionOrder = { + sellAmount: ethers.utils.parseEther("1"), + buyAmount: ethers.utils.parseEther("0.5"), + userId: BigNumber.from(1), + }; + const sellOrders = [ + { + sellAmount: ethers.utils.parseEther("0.5"), + buyAmount: ethers.utils.parseEther("0.5"), + userId: BigNumber.from(2), + }, + { + sellAmount: ethers.utils.parseEther("0.5"), + buyAmount: ethers.utils.parseEther("0.5"), + userId: BigNumber.from(3), + }, + { + sellAmount: ethers.utils.parseEther("0.5"), + buyAmount: ethers.utils.parseEther("0.6"), + userId: BigNumber.from(3), + }, + ]; + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2, user_3], + hre, + ); + + const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + channelAuction, + { + auctioningToken, + biddingToken, + auctionedSellAmount: initialAuctionOrder.sellAmount, + minBuyAmount: initialAuctionOrder.buyAmount, + }, + ); + await placeOrders(channelAuction, sellOrders, auctionId, hre); + + await closeAuction(channelAuction, auctionId); + await channelAuction.settleAuction(auctionId); + const auctionData = await channelAuction.auctionData(auctionId); + expect(auctionData.clearingPriceOrder).to.eql(encodeOrder(sellOrders[1])); + expect(auctionData.volumeClearingPriceOrder).to.equal( + sellOrders[1].sellAmount, + ); + await expect(() => + channelAuction.claimFromParticipantOrder(auctionId, [ + encodeOrder(sellOrders[0]), + ]), + ).to.changeTokenBalances( + auctioningToken, + [user_2], + [sellOrders[0].sellAmount], + ); + await expect(() => + channelAuction.claimFromParticipantOrder(auctionId, [ + encodeOrder(sellOrders[1]), + ]), + ).to.changeTokenBalances( + auctioningToken, + [user_3], + [sellOrders[1].sellAmount], + ); + await expect(() => + channelAuction.claimFromParticipantOrder(auctionId, [ + encodeOrder(sellOrders[2]), + ]), + ).to.changeTokenBalances( + biddingToken, + [user_3], + [sellOrders[2].sellAmount], + ); + }); + it("checks case 10: it shows an example why userId should always be given: 2 orders with the same price", async () => { + const initialAuctionOrder = { + sellAmount: ethers.utils.parseEther("1"), + buyAmount: ethers.utils.parseEther("0.5"), + userId: BigNumber.from(1), + }; + const sellOrders = [ + { + sellAmount: ethers.utils.parseEther("0.5"), + buyAmount: ethers.utils.parseEther("0.5"), + userId: BigNumber.from(2), + }, + { + sellAmount: ethers.utils.parseEther("0.5"), + buyAmount: ethers.utils.parseEther("0.5"), + userId: BigNumber.from(3), + }, + { + sellAmount: ethers.utils.parseEther("0.5"), + buyAmount: ethers.utils.parseEther("0.4"), + userId: BigNumber.from(3), + }, + ]; + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2, user_3], + hre, + ); + + const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + channelAuction, + { + auctioningToken, + biddingToken, + auctionedSellAmount: initialAuctionOrder.sellAmount, + minBuyAmount: initialAuctionOrder.buyAmount, + }, + ); + await placeOrders(channelAuction, sellOrders, auctionId, hre); + + await closeAuction(channelAuction, auctionId); + await channelAuction.settleAuction(auctionId); + const auctionData = await channelAuction.auctionData(auctionId); + expect(auctionData.clearingPriceOrder).to.equal( + encodeOrder(sellOrders[0]), + ); + expect(auctionData.volumeClearingPriceOrder).to.equal( + sellOrders[1].sellAmount, + ); + await expect(() => + channelAuction.claimFromParticipantOrder(auctionId, [ + encodeOrder(sellOrders[0]), + ]), + ).to.changeTokenBalances( + auctioningToken, + [user_2], + [sellOrders[0].sellAmount], + ); + await expect(() => + channelAuction.claimFromParticipantOrder(auctionId, [ + encodeOrder(sellOrders[1]), + ]), + ).to.changeTokenBalances( + biddingToken, + [user_3], + [sellOrders[1].sellAmount], + ); + await expect(() => + channelAuction.claimFromParticipantOrder(auctionId, [ + encodeOrder(sellOrders[2]), + ]), + ).to.changeTokenBalances( + auctioningToken, + [user_3], + [sellOrders[2].sellAmount], + ); + }); + it("checks case 1, it verifies the price in case of 2 of 3 sellOrders eating initialAuctionOrder completely", async () => { + const initialAuctionOrder = { + sellAmount: ethers.utils.parseEther("1"), + buyAmount: ethers.utils.parseEther("1"), + userId: BigNumber.from(1), + }; + const sellOrders = [ + { + sellAmount: ethers.utils.parseEther("1").mul(2), + buyAmount: ethers.utils.parseEther("1").div(5), + userId: BigNumber.from(1), + }, + { + sellAmount: ethers.utils.parseEther("1").mul(2), + buyAmount: ethers.utils.parseEther("1"), + userId: BigNumber.from(1), + }, + + { + sellAmount: ethers.utils.parseEther("1").mul(2).add(1), + buyAmount: ethers.utils.parseEther("1").mul(2), + userId: BigNumber.from(1), + }, + ]; + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2], + hre, + ); + + const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + channelAuction, + { + auctioningToken, + biddingToken, + auctionedSellAmount: initialAuctionOrder.sellAmount, + minBuyAmount: initialAuctionOrder.buyAmount, + }, + ); + await placeOrders(channelAuction, sellOrders, auctionId, hre); + + await closeAuction(channelAuction, auctionId); + await channelAuction.settleAuction(auctionId); + const auctionData = await channelAuction.auctionData(auctionId); + expect(auctionData.clearingPriceOrder).to.eql(encodeOrder(sellOrders[1])); + expect(auctionData.volumeClearingPriceOrder).to.equal(0); + await claimFromAllOrders(channelAuction, auctionId, sellOrders); + }); + it("verifies the price in case of 2 of 3 sellOrders eating initialAuctionOrder completely - with precalculateSellAmountSum step", async () => { + const initialAuctionOrder = { + sellAmount: ethers.utils.parseEther("1"), + buyAmount: ethers.utils.parseEther("1"), + userId: BigNumber.from(1), + }; + const sellOrders = [ + { + sellAmount: ethers.utils.parseEther("1").mul(2), + buyAmount: ethers.utils.parseEther("1").div(5), + userId: BigNumber.from(1), + }, + { + sellAmount: ethers.utils.parseEther("1").mul(2), + buyAmount: ethers.utils.parseEther("1"), + userId: BigNumber.from(1), + }, + + { + sellAmount: ethers.utils.parseEther("1").mul(2).add(1), + buyAmount: ethers.utils.parseEther("1").mul(2), + userId: BigNumber.from(1), + }, + ]; + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2], + hre, + ); + + const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + channelAuction, + { + auctioningToken, + biddingToken, + auctionedSellAmount: initialAuctionOrder.sellAmount, + minBuyAmount: initialAuctionOrder.buyAmount, + }, + ); + await placeOrders(channelAuction, sellOrders, auctionId, hre); + + await closeAuction(channelAuction, auctionId); + // this is the additional step + await channelAuction.precalculateSellAmountSum(auctionId, 1); + + await channelAuction.settleAuction(auctionId); + const auctionData = await channelAuction.auctionData(auctionId); + expect(auctionData.clearingPriceOrder).to.eql(encodeOrder(sellOrders[1])); + expect(auctionData.volumeClearingPriceOrder).to.equal(0); + }); + it("verifies the price in case of 2 of 4 sellOrders eating initialAuctionOrder completely - with precalculateSellAmountSum step and one more step within settleAuction", async () => { + const initialAuctionOrder = { + sellAmount: ethers.utils.parseEther("1"), + buyAmount: ethers.utils.parseEther("1"), + userId: BigNumber.from(1), + }; + const sellOrders = [ + { + sellAmount: ethers.utils.parseEther("1"), + buyAmount: ethers.utils.parseEther("1").div(5), + userId: BigNumber.from(1), + }, + { + sellAmount: ethers.utils.parseEther("1"), + buyAmount: ethers.utils.parseEther("1").div(5), + userId: BigNumber.from(2), + }, + { + sellAmount: ethers.utils.parseEther("1").mul(2), + buyAmount: ethers.utils.parseEther("1"), + userId: BigNumber.from(1), + }, + + { + sellAmount: ethers.utils.parseEther("1").mul(2).add(1), + buyAmount: ethers.utils.parseEther("1").mul(2), + userId: BigNumber.from(1), + }, + ]; + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2], + hre, + ); + + const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + channelAuction, + { + auctioningToken, + biddingToken, + auctionedSellAmount: initialAuctionOrder.sellAmount, + minBuyAmount: initialAuctionOrder.buyAmount, + }, + ); + await placeOrders(channelAuction, sellOrders, auctionId, hre); + + await closeAuction(channelAuction, auctionId); + // this is the additional step + await channelAuction.precalculateSellAmountSum(auctionId, 1); + + const auctionData = await channelAuction.auctionData(auctionId); + expect(auctionData.interimSumBidAmount).to.equal( + sellOrders[0].sellAmount, + ); + expect(auctionData.interimOrder).to.equal(encodeOrder(sellOrders[0])); + await channelAuction.settleAuction(auctionId); + const auctionData2 = await channelAuction.auctionData(auctionId); + expect(auctionData2.clearingPriceOrder).to.eql( + encodeOrder(sellOrders[2]), + ); + expect(auctionData2.volumeClearingPriceOrder).to.equal(0); + await claimFromAllOrders(channelAuction, auctionId, sellOrders); + }); + it("verifies the price in case of clearing order is decided by userId", async () => { + const initialAuctionOrder = { + sellAmount: ethers.utils.parseEther("1"), + buyAmount: ethers.utils.parseEther("1"), + userId: BigNumber.from(1), + }; + const sellOrders = [ + { + sellAmount: ethers.utils.parseEther("1").mul(2), + buyAmount: ethers.utils.parseEther("1").div(5), + userId: BigNumber.from(1), + }, + { + sellAmount: ethers.utils.parseEther("1").mul(2), + buyAmount: ethers.utils.parseEther("1"), + userId: BigNumber.from(1), + }, + + { + sellAmount: ethers.utils.parseEther("1").mul(2), + buyAmount: ethers.utils.parseEther("1"), + userId: BigNumber.from(2), + }, + ]; + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2], + hre, + ); + + const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + channelAuction, + { + auctioningToken, + biddingToken, + auctionedSellAmount: initialAuctionOrder.sellAmount, + minBuyAmount: initialAuctionOrder.buyAmount, + }, + ); + await placeOrders(channelAuction, sellOrders, auctionId, hre); + + await closeAuction(channelAuction, auctionId); + await channelAuction.settleAuction(auctionId); + const auctionData = await channelAuction.auctionData(auctionId); + expect(auctionData.clearingPriceOrder).to.be.equal( + encodeOrder(sellOrders[1]), + ); + expect(auctionData.volumeClearingPriceOrder).to.equal(0); + await claimFromAllOrders(channelAuction, auctionId, sellOrders); + }); + it("simple version of e2e gas test", async () => { + const initialAuctionOrder = { + sellAmount: ethers.utils.parseEther("1"), + buyAmount: ethers.utils.parseEther("1"), + userId: BigNumber.from(1), + }; + const sellOrders = [ + { + sellAmount: ethers.utils.parseEther("1").div(4), + buyAmount: ethers.utils.parseEther("1").div(8), + userId: BigNumber.from(1), + }, + { + sellAmount: ethers.utils.parseEther("1").div(4), + buyAmount: ethers.utils.parseEther("1").div(12), + userId: BigNumber.from(1), + }, + { + sellAmount: ethers.utils.parseEther("1").div(4), + buyAmount: ethers.utils.parseEther("1").div(16), + userId: BigNumber.from(1), + }, + { + sellAmount: ethers.utils.parseEther("1").div(4), + buyAmount: ethers.utils.parseEther("1").div(20), + userId: BigNumber.from(1), + }, + ]; + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2], + hre, + ); + + const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + channelAuction, + { + auctioningToken, + biddingToken, + auctionedSellAmount: initialAuctionOrder.sellAmount, + minBuyAmount: initialAuctionOrder.buyAmount, + }, + ); + await placeOrders(channelAuction, sellOrders, auctionId, hre); + + await closeAuction(channelAuction, auctionId); + const { clearingOrder: price } = await calculateClearingPrice( + channelAuction, + auctionId, + ); + + await channelAuction.settleAuction(auctionId); + expect(price.toString()).to.eql( + getClearingPriceFromInitialOrder(initialAuctionOrder).toString(), + ); + const auctionData = await channelAuction.auctionData(auctionId); + expect(auctionData.clearingPriceOrder).to.equal( + encodeOrder(getClearingPriceFromInitialOrder(initialAuctionOrder)), + ); + await claimFromAllOrders(channelAuction, auctionId, sellOrders); + }); + it("checks whether the minimalFundingThreshold is not met", async () => { + const initialAuctionOrder = { + sellAmount: ethers.utils.parseEther("10"), + buyAmount: ethers.utils.parseEther("10"), + userId: BigNumber.from(1), + }; + const sellOrders = [ + { + sellAmount: ethers.utils.parseEther("1"), + buyAmount: ethers.utils.parseEther("1").div(2), + userId: BigNumber.from(1), + }, + { + sellAmount: ethers.utils.parseEther("1"), + buyAmount: ethers.utils.parseEther("1").div(4), + userId: BigNumber.from(1), + }, + ]; + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2], + hre, + ); + + const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + channelAuction, + { + auctioningToken, + biddingToken, + auctionedSellAmount: initialAuctionOrder.sellAmount, + minBuyAmount: initialAuctionOrder.buyAmount, + minFundingThreshold: ethers.utils.parseEther("5"), + }, + ); + await placeOrders(channelAuction, sellOrders, auctionId, hre); + + await closeAuction(channelAuction, auctionId); + const { clearingOrder: price } = await calculateClearingPrice( + channelAuction, + auctionId, + ); + + await channelAuction.settleAuction(auctionId); + expect(price.toString()).to.eql( + getClearingPriceFromInitialOrder(initialAuctionOrder).toString(), + ); + const auctionData = await channelAuction.auctionData(auctionId); + expect(auctionData.minFundingThresholdNotReached).to.equal(true); + }); + }); + describe("claimFromAuctioneerOrder", async () => { + it("checks that auctioneer receives all their auctioningTokens back if minFundingThreshold was not met", async () => { + const initialAuctionOrder = { + sellAmount: ethers.utils.parseEther("10"), + buyAmount: ethers.utils.parseEther("10"), + userId: BigNumber.from(1), + }; + const sellOrders = [ + { + sellAmount: ethers.utils.parseEther("1"), + buyAmount: ethers.utils.parseEther("1").div(2), + userId: BigNumber.from(2), + }, + { + sellAmount: ethers.utils.parseEther("1"), + buyAmount: ethers.utils.parseEther("1").div(4), + userId: BigNumber.from(3), + }, + ]; + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2, user_3], + hre, + ); + const auctioningTokenBalanceBeforeAuction = await auctioningToken.balanceOf( + user_1.address, + ); + const feeReceiver = user_3; + const feeNumerator = 10; + await channelAuction + .connect(user_1) + .setFeeParameters(feeNumerator, feeReceiver.address); + const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + channelAuction, + { + auctioningToken, + biddingToken, + auctionedSellAmount: initialAuctionOrder.sellAmount, + minBuyAmount: initialAuctionOrder.buyAmount, + minFundingThreshold: ethers.utils.parseEther("5"), + }, + ); + await placeOrders(channelAuction, sellOrders, auctionId, hre); + await closeAuction(channelAuction, auctionId); + await channelAuction.settleAuction(auctionId); + const auctionData = await channelAuction.auctionData(auctionId); + expect(auctionData.minFundingThresholdNotReached).to.equal(true); + expect(await auctioningToken.balanceOf(user_1.address)).to.be.equal( + auctioningTokenBalanceBeforeAuction, + ); + }); + it("checks the claimed amounts for a fully matched initialAuctionOrder and buyOrder", async () => { + const initialAuctionOrder = { + sellAmount: ethers.utils.parseEther("1"), + buyAmount: ethers.utils.parseEther("1"), + userId: BigNumber.from(1), + }; + const sellOrders = [ + { + sellAmount: ethers.utils.parseEther("1").add(1), + buyAmount: ethers.utils.parseEther("1"), + userId: BigNumber.from(1), + }, + ]; + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2], + hre, + ); + + const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + channelAuction, + { + auctioningToken, + biddingToken, + auctionedSellAmount: initialAuctionOrder.sellAmount, + minBuyAmount: initialAuctionOrder.buyAmount, + }, + ); + await placeOrders(channelAuction, sellOrders, auctionId, hre); + + await closeAuction(channelAuction, auctionId); + const { clearingOrder: price } = await calculateClearingPrice( + channelAuction, + auctionId, + ); + const callPromise = channelAuction.settleAuction(auctionId); + // auctioneer reward check: + await expect(() => callPromise).to.changeTokenBalances( + auctioningToken, + [user_1], + [0], + ); + await expect(callPromise) + .to.emit(biddingToken, "Transfer") + .withArgs(channelAuction.address, user_1.address, price.sellAmount); + }); + it("checks the claimed amounts for a partially matched initialAuctionOrder and buyOrder", async () => { + const initialAuctionOrder = { + sellAmount: ethers.utils.parseEther("1"), + buyAmount: ethers.utils.parseEther("1"), + userId: BigNumber.from(1), + }; + const sellOrders = [ + { + sellAmount: ethers.utils.parseEther("1").div(2).add(1), + buyAmount: ethers.utils.parseEther("1").div(2), + userId: BigNumber.from(1), + }, + ]; + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2], + hre, + ); + + const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + channelAuction, + { + auctioningToken, + biddingToken, + auctionedSellAmount: initialAuctionOrder.sellAmount, + minBuyAmount: initialAuctionOrder.buyAmount, + }, + ); + await placeOrders(channelAuction, sellOrders, auctionId, hre); + + await closeAuction(channelAuction, auctionId); + const callPromise = channelAuction.settleAuction(auctionId); + // auctioneer reward check: + await expect(callPromise) + .to.emit(auctioningToken, "Transfer") + .withArgs( + channelAuction.address, + user_1.address, + initialAuctionOrder.sellAmount.sub(sellOrders[0].sellAmount), + ); + await expect(callPromise) + .to.emit(biddingToken, "Transfer") + .withArgs( + channelAuction.address, + user_1.address, + sellOrders[0].sellAmount, + ); + }); + }); + describe("claimFromParticipantOrder", async () => { + it("checks that participant receives all their biddingTokens back if minFundingThreshold was not met", async () => { + const initialAuctionOrder = { + sellAmount: ethers.utils.parseEther("10"), + buyAmount: ethers.utils.parseEther("10"), + userId: BigNumber.from(1), + }; + const sellOrders = [ + { + sellAmount: ethers.utils.parseEther("1"), + buyAmount: ethers.utils.parseEther("1").div(2), + userId: BigNumber.from(2), + }, + { + sellAmount: ethers.utils.parseEther("1"), + buyAmount: ethers.utils.parseEther("1").div(4), + userId: BigNumber.from(2), + }, + ]; + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2], + hre, + ); + + const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + channelAuction, + { + auctioningToken, + biddingToken, + auctionedSellAmount: initialAuctionOrder.sellAmount, + minBuyAmount: initialAuctionOrder.buyAmount, + minFundingThreshold: ethers.utils.parseEther("5"), + }, + ); + await placeOrders(channelAuction, sellOrders, auctionId, hre); + + await closeAuction(channelAuction, auctionId); + await channelAuction.settleAuction(auctionId); + await expect(() => + channelAuction.claimFromParticipantOrder( + auctionId, + sellOrders.map((order) => encodeOrder(order)), + ), + ).to.changeTokenBalances( + biddingToken, + [user_2], + [sellOrders[0].sellAmount.add(sellOrders[1].sellAmount)], + ); + }); + it("checks that claiming only works after the finishing of the auction", async () => { + const initialAuctionOrder = { + sellAmount: ethers.utils.parseEther("1"), + buyAmount: ethers.utils.parseEther("1"), + userId: BigNumber.from(1), + }; + const sellOrders = [ + { + sellAmount: ethers.utils.parseEther("1").add(1), + buyAmount: ethers.utils.parseEther("1"), + userId: BigNumber.from(1), + }, + ]; + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2], + hre, + ); + + const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + channelAuction, + { + auctioningToken, + biddingToken, + auctionedSellAmount: initialAuctionOrder.sellAmount, + minBuyAmount: initialAuctionOrder.buyAmount, + }, + ); + await placeOrders(channelAuction, sellOrders, auctionId, hre); + + await expect( + channelAuction.claimFromParticipantOrder( + auctionId, + sellOrders.map((order) => encodeOrder(order)), + ), + ).to.be.revertedWith("Auction not yet finished"); + await closeAuction(channelAuction, auctionId); + await expect( + channelAuction.claimFromParticipantOrder( + auctionId, + sellOrders.map((order) => encodeOrder(order)), + ), + ).to.be.revertedWith("Auction not yet finished"); + }); + it("checks the claimed amounts for a partially matched buyOrder", async () => { + const initialAuctionOrder = { + sellAmount: ethers.utils.parseEther("1"), + buyAmount: ethers.utils.parseEther("1"), + userId: BigNumber.from(1), + }; + const sellOrders = [ + { + sellAmount: ethers.utils.parseEther("1").div(2).add(1), + buyAmount: ethers.utils.parseEther("1").div(2), + userId: BigNumber.from(1), + }, + { + sellAmount: ethers.utils.parseEther("1").mul(2).div(3).add(1), + buyAmount: ethers.utils.parseEther("1").mul(2).div(3), + userId: BigNumber.from(1), + }, + ]; + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2], + hre, + ); + + const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + channelAuction, + { + auctioningToken, + biddingToken, + auctionedSellAmount: initialAuctionOrder.sellAmount, + minBuyAmount: initialAuctionOrder.buyAmount, + }, + ); + await placeOrders(channelAuction, sellOrders, auctionId, hre); + + await closeAuction(channelAuction, auctionId); + const { clearingOrder: price } = await calculateClearingPrice( + channelAuction, + auctionId, + ); + await channelAuction.settleAuction(auctionId); + + const receivedAmounts = toReceivedFunds( + await channelAuction.callStatic.claimFromParticipantOrder(auctionId, [ + encodeOrder(sellOrders[1]), + ]), + ); + const settledBuyAmount = sellOrders[1].sellAmount + .mul(price.buyAmount) + .div(price.sellAmount) + .sub( + sellOrders[0].sellAmount + .add(sellOrders[1].sellAmount) + .mul(price.buyAmount) + .div(price.sellAmount) + .sub(initialAuctionOrder.sellAmount), + ) + .sub(1); + expect(receivedAmounts.auctioningTokenAmount).to.equal(settledBuyAmount); + expect(receivedAmounts.biddingTokenAmount).to.equal( + sellOrders[1].sellAmount + .sub(settledBuyAmount.mul(price.sellAmount).div(price.buyAmount)) + .sub(1), + ); + }); + it("checks the claimed amounts for a fully not-matched buyOrder", async () => { + const initialAuctionOrder = { + sellAmount: ethers.utils.parseEther("1"), + buyAmount: ethers.utils.parseEther("1"), + userId: BigNumber.from(1), + }; + const sellOrders = [ + { + sellAmount: ethers.utils.parseEther("1").div(2).add(1), + buyAmount: ethers.utils.parseEther("1").div(2), + userId: BigNumber.from(1), + }, + { + sellAmount: ethers.utils.parseEther("1").mul(2).div(3).add(1), + buyAmount: ethers.utils.parseEther("1").mul(2).div(3), + userId: BigNumber.from(1), + }, + { + sellAmount: ethers.utils.parseEther("1").mul(2).div(3).add(1), + buyAmount: ethers.utils.parseEther("1").mul(2).div(3), + userId: BigNumber.from(2), + }, + ]; + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2], + hre, + ); + + const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + channelAuction, + { + auctioningToken, + biddingToken, + auctionedSellAmount: initialAuctionOrder.sellAmount, + minBuyAmount: initialAuctionOrder.buyAmount, + }, + ); + await placeOrders(channelAuction, sellOrders, auctionId, hre); + await closeAuction(channelAuction, auctionId); + await channelAuction.settleAuction(auctionId); + const receivedAmounts = toReceivedFunds( + await channelAuction.callStatic.claimFromParticipantOrder(auctionId, [ + encodeOrder(sellOrders[2]), + ]), + ); + expect(receivedAmounts.biddingTokenAmount).to.equal( + sellOrders[2].sellAmount, + ); + expect(receivedAmounts.auctioningTokenAmount).to.equal("0"); + }); + it("checks the claimed amounts for a fully matched buyOrder", async () => { + const initialAuctionOrder = { + sellAmount: ethers.utils.parseEther("1"), + buyAmount: ethers.utils.parseEther("1"), + userId: BigNumber.from(1), + }; + const sellOrders = [ + { + sellAmount: ethers.utils.parseEther("1").div(2).add(1), + buyAmount: ethers.utils.parseEther("1").div(2), + userId: BigNumber.from(1), + }, + { + sellAmount: ethers.utils.parseEther("1").mul(2).div(3).add(1), + buyAmount: ethers.utils.parseEther("1").mul(2).div(3), + userId: BigNumber.from(1), + }, + ]; + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2], + hre, + ); + + const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + channelAuction, + { + auctioningToken, + biddingToken, + auctionedSellAmount: initialAuctionOrder.sellAmount, + minBuyAmount: initialAuctionOrder.buyAmount, + }, + ); + await placeOrders(channelAuction, sellOrders, auctionId, hre); + + await closeAuction(channelAuction, auctionId); + const { clearingOrder: price } = await calculateClearingPrice( + channelAuction, + auctionId, + ); + await channelAuction.settleAuction(auctionId); + + const receivedAmounts = toReceivedFunds( + await channelAuction.callStatic.claimFromParticipantOrder(auctionId, [ + encodeOrder(sellOrders[0]), + ]), + ); + expect(receivedAmounts.biddingTokenAmount).to.equal("0"); + expect(receivedAmounts.auctioningTokenAmount).to.equal( + sellOrders[0].sellAmount.mul(price.buyAmount).div(price.sellAmount), + ); + }); + it("checks that an order can not be used for claiming twice", async () => { + const initialAuctionOrder = { + sellAmount: ethers.utils.parseEther("1"), + buyAmount: ethers.utils.parseEther("1"), + userId: BigNumber.from(1), + }; + const sellOrders = [ + { + sellAmount: ethers.utils.parseEther("1").div(2).add(1), + buyAmount: ethers.utils.parseEther("1").div(2), + userId: BigNumber.from(1), + }, + { + sellAmount: ethers.utils.parseEther("1").mul(2).div(3).add(1), + buyAmount: ethers.utils.parseEther("1").mul(2).div(3), + userId: BigNumber.from(1), + }, + ]; + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2], + hre, + ); + + const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + channelAuction, + { + auctioningToken, + biddingToken, + auctionedSellAmount: initialAuctionOrder.sellAmount, + minBuyAmount: initialAuctionOrder.buyAmount, + }, + ); + await placeOrders(channelAuction, sellOrders, auctionId, hre); + + await closeAuction(channelAuction, auctionId); + await channelAuction.settleAuction(auctionId); + await channelAuction.claimFromParticipantOrder(auctionId, [ + encodeOrder(sellOrders[0]), + ]), + await expect( + channelAuction.claimFromParticipantOrder(auctionId, [ + encodeOrder(sellOrders[0]), + ]), + ).to.be.revertedWith("order is no longer claimable"); + }); + }); + it("checks that orders from different users can not be claimed at once", async () => { + const initialAuctionOrder = { + sellAmount: ethers.utils.parseEther("1"), + buyAmount: ethers.utils.parseEther("1"), + userId: BigNumber.from(1), + }; + const sellOrders = [ + { + sellAmount: ethers.utils.parseEther("1").div(2).add(1), + buyAmount: ethers.utils.parseEther("1").div(2), + userId: BigNumber.from(1), + }, + { + sellAmount: ethers.utils.parseEther("1").mul(2).div(3).add(1), + buyAmount: ethers.utils.parseEther("1").mul(2).div(3), + userId: BigNumber.from(2), + }, + ]; + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2], + hre, + ); + + const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + channelAuction, + { + auctioningToken, + biddingToken, + auctionedSellAmount: initialAuctionOrder.sellAmount, + minBuyAmount: initialAuctionOrder.buyAmount, + }, + ); + await placeOrders(channelAuction, sellOrders, auctionId, hre); + + await closeAuction(channelAuction, auctionId); + await channelAuction.settleAuction(auctionId); + await expect( + channelAuction.claimFromParticipantOrder(auctionId, [ + encodeOrder(sellOrders[0]), + encodeOrder(sellOrders[1]), + ]), + ).to.be.revertedWith("only allowed to claim for same user"); + }); + it("checks the claimed amounts are summed up correctly for two orders", async () => { + const initialAuctionOrder = { + sellAmount: ethers.utils.parseEther("1"), + buyAmount: ethers.utils.parseEther("1"), + userId: BigNumber.from(1), + }; + const sellOrders = [ + { + sellAmount: ethers.utils.parseEther("1").div(2).add(1), + buyAmount: ethers.utils.parseEther("1").div(2), + userId: BigNumber.from(1), + }, + { + sellAmount: ethers.utils.parseEther("1").mul(2).div(3).add(1), + buyAmount: ethers.utils.parseEther("1").mul(2).div(3), + userId: BigNumber.from(1), + }, + ]; + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2], + hre, + ); + + const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + channelAuction, + { + auctioningToken, + biddingToken, + auctionedSellAmount: initialAuctionOrder.sellAmount, + minBuyAmount: initialAuctionOrder.buyAmount, + }, + ); + await placeOrders(channelAuction, sellOrders, auctionId, hre); + + await closeAuction(channelAuction, auctionId); + const { clearingOrder: price } = await calculateClearingPrice( + channelAuction, + auctionId, + ); + await channelAuction.settleAuction(auctionId); + + const receivedAmounts = toReceivedFunds( + await channelAuction.callStatic.claimFromParticipantOrder(auctionId, [ + encodeOrder(sellOrders[0]), + encodeOrder(sellOrders[1]), + ]), + ); + expect(receivedAmounts.biddingTokenAmount).to.equal( + sellOrders[0].sellAmount + .add(sellOrders[1].sellAmount) + .sub( + initialAuctionOrder.sellAmount + .mul(price.sellAmount) + .div(price.buyAmount), + ), + ); + expect(receivedAmounts.auctioningTokenAmount).to.equal( + initialAuctionOrder.sellAmount.sub(1), + ); + }); + describe("settleAuctionAtomically", async () => { + it("can not settle atomically, if it is not allowed", async () => { + const initialAuctionOrder = { + sellAmount: ethers.utils.parseEther("1"), + buyAmount: ethers.utils.parseEther("0.5"), + userId: BigNumber.from(1), + }; + const sellOrders = [ + { + sellAmount: ethers.utils.parseEther("0.5"), + buyAmount: ethers.utils.parseEther("0.5"), + userId: BigNumber.from(1), + }, + ]; + const atomicSellOrders = [ + { + sellAmount: ethers.utils.parseEther("0.499"), + buyAmount: ethers.utils.parseEther("0.4999"), + userId: BigNumber.from(2), + }, + ]; + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2], + hre, + ); + + const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + channelAuction, + { + auctioningToken, + biddingToken, + auctionedSellAmount: initialAuctionOrder.sellAmount, + minBuyAmount: initialAuctionOrder.buyAmount, + isAtomicClosureAllowed: false, + }, + ); + await placeOrders(channelAuction, sellOrders, auctionId, hre); + + await closeAuction(channelAuction, auctionId); + await expect( + channelAuction.settleAuctionAtomically( + auctionId, + [atomicSellOrders[0].sellAmount], + [atomicSellOrders[0].buyAmount], + [queueStartElement], + "0x", + ), + ).to.be.revertedWith("not allowed to settle auction atomically"); + }); + it("reverts, if more than one order is intended to be settled atomically", async () => { + const initialAuctionOrder = { + sellAmount: ethers.utils.parseEther("1"), + buyAmount: ethers.utils.parseEther("0.5"), + userId: BigNumber.from(1), + }; + const sellOrders = [ + { + sellAmount: ethers.utils.parseEther("0.5"), + buyAmount: ethers.utils.parseEther("0.5"), + userId: BigNumber.from(1), + }, + ]; + const atomicSellOrders = [ + { + sellAmount: ethers.utils.parseEther("0.49"), + buyAmount: ethers.utils.parseEther("0.49"), + userId: BigNumber.from(2), + }, + { + sellAmount: ethers.utils.parseEther("0.4"), + buyAmount: ethers.utils.parseEther("0.4"), + userId: BigNumber.from(2), + }, + ]; + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2], + hre, + ); + + const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + channelAuction, + { + auctioningToken, + biddingToken, + auctionedSellAmount: initialAuctionOrder.sellAmount, + minBuyAmount: initialAuctionOrder.buyAmount, + isAtomicClosureAllowed: true, + }, + ); + await placeOrders(channelAuction, sellOrders, auctionId, hre); + + await closeAuction(channelAuction, auctionId); + await expect( + channelAuction.settleAuctionAtomically( + auctionId, + [atomicSellOrders[0].sellAmount, atomicSellOrders[1].sellAmount], + [atomicSellOrders[0].buyAmount, atomicSellOrders[1].buyAmount], + [queueStartElement, queueStartElement], + "0x", + ), + ).to.be.revertedWith("Only one order can be placed atomically"); + }); + it("can not settle atomically, if precalculateSellAmountSum was used", async () => { + const initialAuctionOrder = { + sellAmount: ethers.utils.parseEther("1"), + buyAmount: ethers.utils.parseEther("0.5"), + userId: BigNumber.from(1), + }; + const sellOrders = [ + { + sellAmount: ethers.utils.parseEther("0.5"), + buyAmount: ethers.utils.parseEther("0.5"), + userId: BigNumber.from(1), + }, + ]; + const atomicSellOrders = [ + { + sellAmount: ethers.utils.parseEther("0.499"), + buyAmount: ethers.utils.parseEther("0.4999"), + userId: BigNumber.from(2), + }, + ]; + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2], + hre, + ); + + const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + channelAuction, + { + auctioningToken, + biddingToken, + auctionedSellAmount: initialAuctionOrder.sellAmount, + minBuyAmount: initialAuctionOrder.buyAmount, + isAtomicClosureAllowed: true, + }, + ); + await placeOrders(channelAuction, sellOrders, auctionId, hre); + + await closeAuction(channelAuction, auctionId); + await channelAuction.precalculateSellAmountSum(auctionId, 1); + + await expect( + channelAuction.settleAuctionAtomically( + auctionId, + [atomicSellOrders[0].sellAmount], + [atomicSellOrders[0].buyAmount], + [queueStartElement], + "0x", + ), + ).to.be.revertedWith("precalculateSellAmountSum is already too advanced"); + }); + it("allows an atomic settlement, if the precalculation are not yet beyond the price of the inserted order", async () => { + const initialAuctionOrder = { + sellAmount: ethers.utils.parseEther("1"), + buyAmount: ethers.utils.parseEther("0.5"), + userId: BigNumber.from(1), + }; + const sellOrders = [ + { + sellAmount: ethers.utils.parseEther("0.5"), + buyAmount: ethers.utils.parseEther("0.5"), + userId: BigNumber.from(1), + }, + ]; + const atomicSellOrders = [ + { + sellAmount: ethers.utils.parseEther("0.5"), + buyAmount: ethers.utils.parseEther("0.55"), + userId: BigNumber.from(1), + }, + ]; + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2], + hre, + ); + + const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + channelAuction, + { + auctioningToken, + biddingToken, + auctionedSellAmount: initialAuctionOrder.sellAmount, + minBuyAmount: initialAuctionOrder.buyAmount, + isAtomicClosureAllowed: true, + }, + ); + await placeOrders(channelAuction, sellOrders, auctionId, hre); + + await closeAuction(channelAuction, auctionId); + await channelAuction.precalculateSellAmountSum(auctionId, 1); + + await channelAuction.settleAuctionAtomically( + auctionId, + [atomicSellOrders[0].buyAmount], + [atomicSellOrders[0].sellAmount], + [queueStartElement], + "0x", + ); + await claimFromAllOrders(channelAuction, auctionId, sellOrders); + await claimFromAllOrders(channelAuction, auctionId, atomicSellOrders); + }); + it("can settle atomically, if it is allowed", async () => { + const initialAuctionOrder = { + sellAmount: ethers.utils.parseEther("1"), + buyAmount: ethers.utils.parseEther("0.5"), + userId: BigNumber.from(1), + }; + const sellOrders = [ + { + sellAmount: ethers.utils.parseEther("0.5"), + buyAmount: ethers.utils.parseEther("0.5"), + userId: BigNumber.from(1), + }, + ]; + const atomicSellOrders = [ + { + sellAmount: ethers.utils.parseEther("0.4999"), + buyAmount: ethers.utils.parseEther("0.4999"), + userId: BigNumber.from(2), + }, + ]; + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2], + hre, + ); + + const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + channelAuction, + { + auctioningToken, + biddingToken, + auctionedSellAmount: initialAuctionOrder.sellAmount, + minBuyAmount: initialAuctionOrder.buyAmount, + isAtomicClosureAllowed: true, + }, + ); + await placeOrders(channelAuction, sellOrders, auctionId, hre); + + await closeAuction(channelAuction, auctionId); + await channelAuction + .connect(user_2) + .settleAuctionAtomically( + auctionId, + [atomicSellOrders[0].sellAmount], + [atomicSellOrders[0].buyAmount], + [queueStartElement], + "0x", + ); + const auctionData = await channelAuction.auctionData(auctionId); + expect(auctionData.clearingPriceOrder).to.equal( + encodeOrder({ + sellAmount: sellOrders[0].sellAmount.add( + atomicSellOrders[0].sellAmount, + ), + buyAmount: initialAuctionOrder.sellAmount, + userId: BigNumber.from(0), + }), + ); + }); + it("can not settle auctions atomically, before auction finished", async () => { + const initialAuctionOrder = { + sellAmount: ethers.utils.parseEther("1"), + buyAmount: ethers.utils.parseEther("0.5"), + userId: BigNumber.from(1), + }; + const sellOrders = [ + { + sellAmount: ethers.utils.parseEther("0.5"), + buyAmount: ethers.utils.parseEther("0.5"), + userId: BigNumber.from(1), + }, + ]; + const atomicSellOrders = [ + { + sellAmount: ethers.utils.parseEther("0.4999"), + buyAmount: ethers.utils.parseEther("0.4999"), + userId: BigNumber.from(2), + }, + ]; + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2], + hre, + ); + + const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + channelAuction, + { + auctioningToken, + biddingToken, + auctionedSellAmount: initialAuctionOrder.sellAmount, + minBuyAmount: initialAuctionOrder.buyAmount, + isAtomicClosureAllowed: true, + }, + ); + await placeOrders(channelAuction, sellOrders, auctionId, hre); + + await expect( + channelAuction + .connect(user_2) + .settleAuctionAtomically( + auctionId, + [atomicSellOrders[0].sellAmount], + [atomicSellOrders[0].buyAmount], + [queueStartElement], + "0x", + ), + ).to.be.revertedWith("Auction not in solution submission phase"); + }); + }); + describe("registerUser", async () => { + it("registers a user only once", async () => { + await channelAuction.registerUser(user_1.address); + await expect( + channelAuction.registerUser(user_1.address), + ).to.be.revertedWith("User already registered"); + }); + }); + describe("cancelOrder", async () => { + it("cancels an order", async () => { + const initialAuctionOrder = { + sellAmount: ethers.utils.parseEther("1"), + buyAmount: ethers.utils.parseEther("1"), + userId: BigNumber.from(1), + }; + const sellOrders = [ + { + sellAmount: ethers.utils.parseEther("1").div(2).add(1), + buyAmount: ethers.utils.parseEther("1").div(2), + userId: BigNumber.from(1), + }, + ]; + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2], + hre, + ); + + const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + channelAuction, + { + auctioningToken, + biddingToken, + auctionedSellAmount: initialAuctionOrder.sellAmount, + minBuyAmount: initialAuctionOrder.buyAmount, + }, + ); + await placeOrders(channelAuction, sellOrders, auctionId, hre); + + await expect( + channelAuction.cancelSellOrders(auctionId, [ + encodeOrder(sellOrders[0]), + ]), + ) + .to.emit(biddingToken, "Transfer") + .withArgs( + channelAuction.address, + user_1.address, + sellOrders[0].sellAmount, + ); + }); + it("does not allow to cancel a order, if it is too late", async () => { + const initialAuctionOrder = { + sellAmount: ethers.utils.parseEther("1"), + buyAmount: ethers.utils.parseEther("1"), + userId: BigNumber.from(1), + }; + const sellOrders = [ + { + sellAmount: ethers.utils.parseEther("1").div(2).add(1), + buyAmount: ethers.utils.parseEther("1").div(2), + userId: BigNumber.from(1), + }, + ]; + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2], + hre, + ); + + const now = (await ethers.provider.getBlock("latest")).timestamp; + const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + channelAuction, + { + auctioningToken, + biddingToken, + auctionedSellAmount: initialAuctionOrder.sellAmount, + minBuyAmount: initialAuctionOrder.buyAmount, + orderCancellationEndDate: now + 60 * 60, + auctionEndDate: now + 60 * 60 * 60, + }, + ); + await placeOrders(channelAuction, sellOrders, auctionId, hre); + + await increaseTime(3601); + await expect( + channelAuction.cancelSellOrders(auctionId, [ + encodeOrder(sellOrders[0]), + ]), + ).to.be.revertedWith( + "revert no longer in order placement and cancelation phase", + ); + await closeAuction(channelAuction, auctionId); + await expect( + channelAuction.cancelSellOrders(auctionId, [ + encodeOrder(sellOrders[0]), + ]), + ).to.be.revertedWith( + "revert no longer in order placement and cancelation phase", + ); + }); + it("can't cancel orders twice", async () => { + const initialAuctionOrder = { + sellAmount: ethers.utils.parseEther("1"), + buyAmount: ethers.utils.parseEther("1"), + userId: BigNumber.from(1), + }; + const sellOrders = [ + { + sellAmount: ethers.utils.parseEther("1").div(2).add(1), + buyAmount: ethers.utils.parseEther("1").div(2), + userId: BigNumber.from(1), + }, + ]; + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2], + hre, + ); + + const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + channelAuction, + { + auctioningToken, + biddingToken, + auctionedSellAmount: initialAuctionOrder.sellAmount, + minBuyAmount: initialAuctionOrder.buyAmount, + }, + ); + await placeOrders(channelAuction, sellOrders, auctionId, hre); + + // removes the order + channelAuction.cancelSellOrders(auctionId, [encodeOrder(sellOrders[0])]); + // claims 0 sellAmount tokens + await expect( + channelAuction.cancelSellOrders(auctionId, [ + encodeOrder(sellOrders[0]), + ]), + ) + .to.emit(biddingToken, "Transfer") + .withArgs(channelAuction.address, user_1.address, 0); + }); + it("prevents an order from canceling, if tx is not from owner", async () => { + const initialAuctionOrder = { + sellAmount: ethers.utils.parseEther("1"), + buyAmount: ethers.utils.parseEther("1"), + userId: BigNumber.from(1), + }; + const sellOrders = [ + { + sellAmount: ethers.utils.parseEther("1").div(2).add(1), + buyAmount: ethers.utils.parseEther("1").div(2), + userId: BigNumber.from(2), + }, + ]; + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2], + hre, + ); + + const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + channelAuction, + { + auctioningToken, + biddingToken, + auctionedSellAmount: initialAuctionOrder.sellAmount, + minBuyAmount: initialAuctionOrder.buyAmount, + }, + ); + await placeOrders(channelAuction, sellOrders, auctionId, hre); + + await expect( + channelAuction.cancelSellOrders(auctionId, [ + encodeOrder(sellOrders[0]), + ]), + ).to.be.revertedWith("Only the user can cancel his orders"); + }); + }); + + describe("containsOrder", async () => { + it("returns true, if it contains order", async () => { + const initialAuctionOrder = { + sellAmount: ethers.utils.parseEther("1"), + buyAmount: ethers.utils.parseEther("1"), + userId: BigNumber.from(1), + }; + const sellOrders = [ + { + sellAmount: ethers.utils.parseEther("1").div(2).add(1), + buyAmount: ethers.utils.parseEther("1").div(2), + userId: BigNumber.from(1), + }, + ]; + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2], + hre, + ); + + const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + channelAuction, + { + auctioningToken, + biddingToken, + auctionedSellAmount: initialAuctionOrder.sellAmount, + minBuyAmount: initialAuctionOrder.buyAmount, + }, + ); + await placeOrders(channelAuction, sellOrders, auctionId, hre); + + await closeAuction(channelAuction, auctionId); + expect( + await channelAuction.callStatic.containsOrder( + auctionId, + encodeOrder(sellOrders[0]), + ), + ).to.be.equal(true); + }); + }); + describe("getSecondsRemainingInBatch", async () => { + it("checks that claiming only works after the finishing of the auction", async () => { + const initialAuctionOrder = { + sellAmount: ethers.utils.parseEther("1"), + buyAmount: ethers.utils.parseEther("1"), + userId: BigNumber.from(1), + }; + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2], + hre, + ); + + const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + channelAuction, + { + auctioningToken, + biddingToken, + auctionedSellAmount: initialAuctionOrder.sellAmount, + minBuyAmount: initialAuctionOrder.buyAmount, + }, + ); + await closeAuction(channelAuction, auctionId); + expect( + await channelAuction.callStatic.getSecondsRemainingInBatch(auctionId), + ).to.be.equal("0"); + }); + }); + describe("claimsFee", async () => { + it("claims fees fully for a non-partially filled initialAuctionOrder", async () => { + const initialAuctionOrder = { + sellAmount: ethers.utils.parseEther("1"), + buyAmount: ethers.utils.parseEther("1"), + userId: BigNumber.from(1), + }; + let sellOrders = [ + { + sellAmount: ethers.utils.parseEther("1").div(2).add(1), + buyAmount: ethers.utils.parseEther("1").div(2), + userId: BigNumber.from(1), + }, + { + sellAmount: ethers.utils.parseEther("1").mul(2).div(3).add(1), + buyAmount: ethers.utils.parseEther("1").mul(2).div(3), + userId: BigNumber.from(1), + }, + ]; + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2, user_3], + hre, + ); + + const feeReceiver = user_3; + const feeNumerator = 10; + await channelAuction + .connect(user_1) + .setFeeParameters(feeNumerator, feeReceiver.address); + + const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + channelAuction, + { + auctioningToken, + biddingToken, + auctionedSellAmount: initialAuctionOrder.sellAmount, + minBuyAmount: initialAuctionOrder.buyAmount, + }, + ); + await placeOrders(channelAuction, sellOrders, auctionId, hre); + // resets the userId, as they are only given during function call. + sellOrders = await getAllSellOrders(channelAuction, auctionId); + + await closeAuction(channelAuction, auctionId); + await expect(() => + channelAuction.settleAuction(auctionId), + ).to.changeTokenBalances( + auctioningToken, + [feeReceiver], + [initialAuctionOrder.sellAmount.mul(feeNumerator).div("1000")], + ); + + // contract still holds sufficient funds to pay the participants fully + await channelAuction.callStatic.claimFromParticipantOrder( + auctionId, + sellOrders.map((order) => encodeOrder(order)), + ); + }); + it("claims also fee amount of zero, even when it is changed later", async () => { + const initialAuctionOrder = { + sellAmount: ethers.utils.parseEther("1"), + buyAmount: ethers.utils.parseEther("1"), + userId: BigNumber.from(1), + }; + let sellOrders = [ + { + sellAmount: ethers.utils.parseEther("1").div(2).add(1), + buyAmount: ethers.utils.parseEther("1").div(2), + userId: BigNumber.from(1), + }, + { + sellAmount: ethers.utils.parseEther("1").mul(2).div(3).add(1), + buyAmount: ethers.utils.parseEther("1").mul(2).div(3), + userId: BigNumber.from(1), + }, + ]; + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2, user_3], + hre, + ); + + const feeReceiver = user_3; + const feeNumerator = 0; + await channelAuction + .connect(user_1) + .setFeeParameters(feeNumerator, feeReceiver.address); + + const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + channelAuction, + { + auctioningToken, + biddingToken, + auctionedSellAmount: initialAuctionOrder.sellAmount, + minBuyAmount: initialAuctionOrder.buyAmount, + }, + ); + await placeOrders(channelAuction, sellOrders, auctionId, hre); + // resets the userId, as they are only given during function call. + sellOrders = await getAllSellOrders(channelAuction, auctionId); + await channelAuction + .connect(user_1) + .setFeeParameters(10, feeReceiver.address); + + await closeAuction(channelAuction, auctionId); + await expect(() => + channelAuction.settleAuction(auctionId), + ).to.changeTokenBalances( + auctioningToken, + [feeReceiver], + [BigNumber.from(0)], + ); + + // contract still holds sufficient funds to pay the participants fully + await channelAuction.callStatic.claimFromParticipantOrder( + auctionId, + sellOrders.map((order) => encodeOrder(order)), + ); + }); + it("claims fees fully for a partially filled initialAuctionOrder", async () => { + const initialAuctionOrder = { + sellAmount: ethers.utils.parseEther("1"), + buyAmount: ethers.utils.parseEther("1"), + userId: BigNumber.from(1), + }; + let sellOrders = [ + { + sellAmount: ethers.utils.parseEther("1").div(4), + buyAmount: ethers.utils.parseEther("1").div(4).sub(1), + userId: BigNumber.from(3), + }, + ]; + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2, user_3], + hre, + ); + + const feeReceiver = user_3; + const feeNumerator = 10; + await channelAuction + .connect(user_1) + .setFeeParameters(feeNumerator, feeReceiver.address); + + const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + channelAuction, + { + auctioningToken, + biddingToken, + auctionedSellAmount: initialAuctionOrder.sellAmount, + minBuyAmount: initialAuctionOrder.buyAmount, + }, + ); + await placeOrders(channelAuction, sellOrders, auctionId, hre); + // resets the userId, as they are only given during function call. + sellOrders = await getAllSellOrders(channelAuction, auctionId); + + await closeAuction(channelAuction, auctionId); + await expect(() => + channelAuction.settleAuction(auctionId), + ).to.changeTokenBalances( + auctioningToken, + [user_1, feeReceiver], + [ + // since only 1/4th of the tokens were sold, the auctioneer + // is getting 3/4th of the tokens plus 3/4th of the fee back + initialAuctionOrder.sellAmount + .mul(3) + .div(4) + .add( + initialAuctionOrder.sellAmount + .mul(feeNumerator) + .div("1000") + .mul(3) + .div(4), + ), + initialAuctionOrder.sellAmount.mul(feeNumerator).div("1000").div(4), + ], + ); + // contract still holds sufficient funds to pay the participants fully + await channelAuction.callStatic.claimFromParticipantOrder( + auctionId, + sellOrders.map((order) => encodeOrder(order)), + ); + }); + }); + describe("setFeeParameters", async () => { + it("can only be called by owner", async () => { + const feeReceiver = user_3; + const feeNumerator = 10; + await expect( + channelAuction + .connect(user_2) + .setFeeParameters(feeNumerator, feeReceiver.address), + ).to.be.revertedWith("Ownable: caller is not the owner"); + }); + it("does not allow fees higher than 1.5%", async () => { + const feeReceiver = user_3; + const feeNumerator = 16; + await expect( + channelAuction + .connect(user_1) + .setFeeParameters(feeNumerator, feeReceiver.address), + ).to.be.revertedWith("Fee is not allowed to be set higher than 1.5%"); + }); + }); +}); diff --git a/test/contract/IterableOrderList.spec.ts b/test/contract/IterableOrderList.spec.ts new file mode 100644 index 0000000..ffb247f --- /dev/null +++ b/test/contract/IterableOrderList.spec.ts @@ -0,0 +1,295 @@ +import { expect } from "chai"; +import { Contract, BigNumber } from "ethers"; +import { ethers } from "hardhat"; + +import { + queueLastElement, + queueStartElement, + encodeOrder, +} from "../../src/priceCalculation"; + +const QUEUE_END = + "0xffffffffffffffffffffffffffffffffffffffff000000000000000000000001"; +const BYTES32_ZERO = encodeOrder({ + userId: BigNumber.from(1), + sellAmount: BigNumber.from(0), + buyAmount: BigNumber.from(0), +}); +const BYTES32_ONE = encodeOrder({ + userId: BigNumber.from(2), + sellAmount: BigNumber.from(2), + buyAmount: BigNumber.from(2), +}); +const BYTES32_ONE_DIFFERENT = encodeOrder({ + userId: BigNumber.from(2), + sellAmount: BigNumber.from(3), + buyAmount: BigNumber.from(3), +}); +const BYTES32_ONE_BEST_USER = encodeOrder({ + userId: BigNumber.from(1), + sellAmount: BigNumber.from(2), + buyAmount: BigNumber.from(2), +}); +const BYTES32_ONE_BEST_AMOUNT = encodeOrder({ + userId: BigNumber.from(2), + sellAmount: BigNumber.from(1), + buyAmount: BigNumber.from(1), +}); +const BYTES32_TWO = encodeOrder({ + userId: BigNumber.from(2), + buyAmount: BigNumber.from(8), + sellAmount: BigNumber.from(4), +}); +const BYTES32_THREE = encodeOrder({ + userId: BigNumber.from(2), + buyAmount: BigNumber.from(6), + sellAmount: BigNumber.from(2), +}); +const BYTES32_FOUR = encodeOrder({ + userId: BigNumber.from(2), + buyAmount: BigNumber.from(8), + sellAmount: BigNumber.from(2), +}); +const BYTES32_FIVE = encodeOrder({ + userId: BigNumber.from(2), + buyAmount: BigNumber.from(10), + sellAmount: BigNumber.from(2), +}); + +async function getSetContent(set: Contract) { + const result = []; + if (!(await set.isEmpty())) { + const last_element = queueLastElement; + let current = await set.first(); + while (current != last_element) { + result.push(current); + current = await set.next(current); + } + } + return result; +} + +describe("IterableOrderedOrderList", function () { + let set: Contract; + beforeEach(async () => { + const IterableOrderedOrderListWrapper = await ethers.getContractFactory( + "IterableOrderedOrderListWrapper", + ); + + set = await IterableOrderedOrderListWrapper.deploy(); + await set.initializeEmptyList(); + }); + + it("should contain the added values", async () => { + expect(await getSetContent(set)).to.be.empty; + expect(await set.contains(BYTES32_ONE)).to.equal(false); + expect(await set.callStatic.insert(BYTES32_ONE)).to.equal(true); + await set.insert(BYTES32_ONE); + expect(await set.contains(BYTES32_ONE)).to.equal(true); + + expect(await getSetContent(set)).to.eql([BYTES32_ONE]); + }); + + it("should insert the same value only once", async () => { + expect(await set.callStatic.insert(BYTES32_ONE)).to.equal(true); + await set.insert(BYTES32_ONE); + expect(await getSetContent(set)).to.eql([BYTES32_ONE]); + + expect(await set.callStatic.insert(BYTES32_ONE)).to.equal(false); + await set.insert(BYTES32_ONE); + expect(await getSetContent(set)).to.eql([BYTES32_ONE]); + }); + + it("should return first", async () => { + await set.insert(BYTES32_ONE); + expect(await set.first()).to.equal(BYTES32_ONE); + }); + + it("should allow to iterate over content and check order - part 1", async () => { + await set.insert(BYTES32_ONE); + await set.insert(BYTES32_TWO); + await set.insert(BYTES32_THREE); + await set.insert(BYTES32_ONE_BEST_USER); + await set.insert(BYTES32_ONE_BEST_AMOUNT); + + const first = await set.first(); + const second = await set.next(first); + const third = await set.next(second); + const fourth = await set.next(third); + const fifth = await set.next(fourth); + + expect(first).to.equal(BYTES32_ONE_BEST_AMOUNT); + expect(second).to.equal(BYTES32_ONE_BEST_USER); + expect(third).to.equal(BYTES32_ONE); + expect(fourth).to.equal(BYTES32_TWO); + expect(fifth).to.equal(BYTES32_THREE); + }); + it("should allow to iterate over content and check order - part 2", async () => { + await set.insert(BYTES32_ONE); + await set.insert(BYTES32_ONE_BEST_AMOUNT); + await set.insert(BYTES32_ONE_BEST_USER); + await set.insert(BYTES32_TWO); + await set.insert(BYTES32_THREE); + + const first = await set.first(); + const second = await set.next(first); + const third = await set.next(second); + const fourth = await set.next(third); + const fifth = await set.next(fourth); + + expect(first).to.equal(BYTES32_ONE_BEST_AMOUNT); + expect(second).to.equal(BYTES32_ONE_BEST_USER); + expect(third).to.equal(BYTES32_ONE); + expect(fourth).to.equal(BYTES32_TWO); + expect(fifth).to.equal(BYTES32_THREE); + }); + it("should allow to insert same limit price with different amount with same user", async () => { + await set.insert(BYTES32_ONE); + expect(await set.callStatic.insert(BYTES32_ONE_DIFFERENT)).to.equal(true); + await set.insert(BYTES32_ONE_DIFFERENT); + expect(await set.callStatic.insert(BYTES32_ONE_DIFFERENT)).to.equal(false); + }); + it("should throw if the same orders are compared with smallerThan", async () => { + await expect(set.smallerThan(BYTES32_ONE, BYTES32_ONE)).to.be.revertedWith( + "user is not allowed to place same order twice", + ); + }); + + it("should allow to insert element at certain element", async () => { + await set.insert(BYTES32_ONE); + await set.insert(BYTES32_THREE); + + expect(await set.callStatic.insertAt(BYTES32_TWO, BYTES32_ONE)).to.equal( + true, + ); + expect(await set.callStatic.insertAt(BYTES32_TWO, BYTES32_THREE)).to.equal( + false, + ); + }); + + it("should not allow to insert element with non-containing element-Before-New-One", async () => { + await set.insert(BYTES32_THREE); + + expect(await set.callStatic.insertAt(BYTES32_TWO, BYTES32_ONE)).to.equal( + false, + ); + }); + + it("should not allow to insert element with element not in front of other element", async () => { + expect(await set.callStatic.insertAt(BYTES32_TWO, BYTES32_THREE)).to.equal( + false, + ); + }); + + it("should not allow to insert queue start element", async () => { + await expect( + set.callStatic.insertAt(queueStartElement, queueStartElement), + ).to.be.revertedWith("Inserting element is not valid"); + }); + it("should not allow to insert queue end element", async () => { + await expect( + set.callStatic.insertAt(queueLastElement, queueStartElement), + ).to.be.revertedWith("Inserting element is not valid"); + }); + + it("should insert element according to rate", async () => { + await set.insert(BYTES32_THREE); + await set.insert(BYTES32_ONE); + await set.insert(BYTES32_TWO); + + const first = await set.first(); + const second = await set.next(first); + const third = await set.next(second); + + expect(first).to.equal(BYTES32_ONE); + expect(second).to.equal(BYTES32_TWO); + expect(third).to.equal(BYTES32_THREE); + }); + + it("does not allow to get next of the queue end element", async () => { + await set.insert(BYTES32_THREE); + + const first = await set.first(); + const second = await set.next(first); + await expect(set.next(second)).to.be.revertedWith( + "Trying to get next of last element", + ); + }); + + it("doesn't allow to insert a number with denominator == 0", async () => { + await expect(set.insert(BYTES32_ZERO)).to.be.revertedWith( + "Inserting zero is not supported", + ); + }); + + it("cannot get first of empty list", async () => { + await expect(set.first()).to.be.revertedWith( + "Trying to get first from empty set", + ); + }); + + it("cannot get next of non-existent element", async () => { + await set.insert(BYTES32_ONE); + await expect(set.next(BYTES32_TWO)).to.be.reverted; + }); + + it("should remove element", async () => { + await set.insert(BYTES32_THREE); + await set.insert(BYTES32_ONE); + await set.insert(BYTES32_TWO); + + await set.remove(BYTES32_TWO); + + const first = await set.first(); + const second = await set.next(first); + const next_of_removed = await set.nextMap(BYTES32_TWO); + + expect(first).to.equal(BYTES32_ONE); + expect(second).to.equal(BYTES32_TWO); + expect(next_of_removed).to.equal(ethers.constants.Zero); + }); + + it("should allow to remove element twice", async () => { + await set.insert(BYTES32_ONE); + await set.insert(BYTES32_TWO); + await set.insert(BYTES32_THREE); + + expect(await set.callStatic.remove(BYTES32_TWO)).to.equal(true); + await set.remove(BYTES32_TWO); + expect(await set.callStatic.remove(BYTES32_TWO)).to.equal(false); + }); + + it("should recognize empty sets", async () => { + expect(await set.callStatic.isEmpty()).to.equal(true); + await set.insert(BYTES32_ONE); + expect(await set.callStatic.isEmpty()).to.equal(false); + }); + + it("cannot contain queue start element or queue end element", async () => { + expect(await set.callStatic.contains(queueLastElement)).to.equal(false); + expect(await set.callStatic.contains(queueStartElement)).to.equal(false); + }); + + it("cannot contain queue start element or queue end element", async () => { + expect(await set.callStatic.contains(queueLastElement)).to.equal(false); + expect(await set.callStatic.contains(queueStartElement)).to.equal(false); + }); + + it("does not allow to remove element not contained", async () => { + await set.insert(BYTES32_ONE); + await set.insert(BYTES32_TWO); + + expect(await set.callStatic.remove(BYTES32_THREE)).to.equal(false); + }); + + it("can add element BYTES32_ONE => rate of startingElement ==0", async () => { + expect(await set.callStatic.insert(BYTES32_ONE)).to.equal(true); + }); + + it("encodeOrder reverses decodeOrder", async () => { + const ans = await set.decodeOrder(BYTES32_THREE); + expect(await set.callStatic.encodeOrder(ans[0], ans[1], ans[2])).to.equal( + BYTES32_THREE, + ); + }); +}); diff --git a/test/contract/defaultContractInteractions.ts b/test/contract/defaultContractInteractions.ts index 19cbd78..85c1bed 100644 --- a/test/contract/defaultContractInteractions.ts +++ b/test/contract/defaultContractInteractions.ts @@ -1,13 +1,19 @@ import { Contract, BigNumber } from "ethers"; import { ethers } from "hardhat"; -import { InitiateAuctionInput } from "../../src/ts/types"; +import { + InitiateAuctionInput, + InitiateChannelAuctionInput, +} from "../../src/ts/types"; import { sendTxAndGetReturnValue } from "./utilities"; type PartialAuctionInput = Partial & Pick; +type PartialChannelAuctionInput = Partial & + Pick; + async function createAuctionInputWithDefaults( parameters: PartialAuctionInput, ): Promise { @@ -26,6 +32,21 @@ async function createAuctionInputWithDefaults( parameters.allowListData ?? "0x", ]; } +async function createChannelAuctionInputWithDefaults( + parameters: PartialChannelAuctionInput, +): Promise { + const now = (await ethers.provider.getBlock("latest")).timestamp; + return [ + parameters.auctioningToken.address, + parameters.biddingToken.address, + parameters._auctionedSellAmount ?? ethers.utils.parseEther("1"), + parameters._auctioneerBuyAmountMinimum ?? ethers.utils.parseEther("1"), + parameters._auctioneerBuyAmountMaximum ?? ethers.utils.parseEther("2"), + parameters._auctionStartDate ?? now + 3600, + parameters._minimumBiddingAmountPerOrder ?? 1, + parameters._maxDuration ?? 3600, + ]; +} export async function createAuctionWithDefaults( easyAuction: Contract, @@ -35,6 +56,25 @@ export async function createAuctionWithDefaults( ...(await createAuctionInputWithDefaults(parameters)), ); } +export async function createChannelAuctionWithDefaults( + channelAuction: Contract, + parameters: PartialChannelAuctionInput, +): Promise { + return channelAuction.initiateAuction( + ...(await createChannelAuctionInputWithDefaults(parameters)), + ); +} + +export async function createChannelAuctionWithDefaultsAndReturnId( + channelAuction: Contract, + parameters: PartialAuctionInput, +): Promise { + return sendTxAndGetReturnValue( + channelAuction, + "initiateAuction(address,address,uint256,uint256,uint96,uint96,uint256,uint256,bool,address,bytes)", + ...(await createAuctionInputWithDefaults(parameters)), + ); +} export async function createAuctionWithDefaultsAndReturnId( easyAuction: Contract, From b932ddfc58240afe292903f7ea541caa7449fcfc Mon Sep 17 00:00:00 2001 From: josojo Date: Sun, 9 May 2021 15:26:42 +0200 Subject: [PATCH 2/4] fixing some more tests --- contracts/ChannelAuction.sol | 235 +- .../libraries/IterableOrderedOrderList.sol | 13 +- .../DepositAndPlaceOrderForChannelAuction.sol | 35 + src/priceCalculation.ts | 39 +- test/contract/Channelauction.spec.ts | 3782 ++++++++--------- test/contract/defaultContractInteractions.ts | 6 +- test/contract/utilities.ts | 20 + 7 files changed, 1948 insertions(+), 2182 deletions(-) create mode 100644 contracts/wrappers/DepositAndPlaceOrderForChannelAuction.sol diff --git a/contracts/ChannelAuction.sol b/contracts/ChannelAuction.sol index f957797..c7c3d2c 100644 --- a/contracts/ChannelAuction.sol +++ b/contracts/ChannelAuction.sol @@ -19,13 +19,29 @@ contract ChannelAuction is Ownable { using IdToAddressBiMap for IdToAddressBiMap.Data; modifier atStageOrderPlacement(uint256 auctionId) { + { + uint256 auctionStartDate = auctionData[auctionId].auctionStartDate; + require( + block.timestamp > auctionStartDate, + "not yet in order placement phase" + ); + require( + block.timestamp < + auctionStartDate.add(auctionData[auctionId].maxDuration), + "auction finished or not yet started" + ); + require( + bytes32(0) == auctionData[auctionId].clearingPriceOrder, + "no longer in order placement phase" + ); + } + _; + } + + modifier atStagePriceCalculation(uint256 auctionId) { require( - block.timestamp > auctionData[auctionId].auctionStartDate, - "not yet in order placement phase" - ); - require( - bytes32(0) == auctionData[auctionId].clearingPriceOrder, - "no longer in order placement phase" + auctionData[auctionId].clearingPriceOrder == bytes32(0), + "Auction already finished" ); _; } @@ -76,11 +92,13 @@ contract ChannelAuction is Ownable { IERC20 biddingToken; uint96 auctionStartDate; bytes32 initialAuctionOrder; - uint256 minimumBiddingAmountPerOrder; + uint96 minimumBiddingAmountPerOrder; bytes32 clearingPriceOrder; uint96 volumeClearingPriceOrder; uint96 maxDuration; - uint96 auctioneerBuyAmountMinimum; + uint96 auctioneerBuyAmountMaximum; + bytes32 interimOrder; + uint96 volumeInterimOrder; } mapping(uint256 => IterableOrderedOrderList.Data) internal sellOrders; mapping(uint256 => AuctionData) public auctionData; @@ -100,8 +118,8 @@ contract ChannelAuction is Ownable { address newfeeReceiverAddress ) public onlyOwner() { require( - newFeeNumerator <= 15, - "Fee is not allowed to be set higher than 1.5%" + newFeeNumerator <= 5, + "Fee is not allowed to be set higher than 0.5%" ); // caution: for currently running auctions, the feeReceiverUserId is changing as well. feeReceiverUserId = getUserId(newfeeReceiverAddress); @@ -165,14 +183,16 @@ contract ChannelAuction is Ownable { _auctionStartDate, IterableOrderedOrderList.encodeOrder( userId, - _auctioneerBuyAmountMaximum, + _auctioneerBuyAmountMinimum, _auctionedSellAmount ), _minimumBiddingAmountPerOrder, bytes32(0), 0, _maxDuration, - _auctioneerBuyAmountMinimum + _auctioneerBuyAmountMaximum, + bytes32(0), + 0 ); emit NewAuction( auctionCounter, @@ -191,109 +211,112 @@ contract ChannelAuction is Ownable { function placeSellOrders( uint256 auctionId, - uint96[] memory _minBuyAmounts, - uint96[] memory _sellAmounts, - bytes32[] memory _prevSellOrders - ) external atStageOrderPlacement(auctionId) returns (uint64 userId) { - return - _placeSellOrders( - auctionId, - _minBuyAmounts, - _sellAmounts, - _prevSellOrders, - msg.sender - ); + uint96 _minBuyAmounts, + uint96 _sellAmounts, + bytes32 _prevSellOrders + ) external atStageOrderPlacement(auctionId) { + _placeSellOrders( + auctionId, + _minBuyAmounts, + _sellAmounts, + _prevSellOrders, + msg.sender + ); } function placeSellOrdersOnBehalf( uint256 auctionId, - uint96[] memory _minBuyAmounts, - uint96[] memory _sellAmounts, - bytes32[] memory _prevSellOrders, + uint96 _minBuyAmounts, + uint96 _sellAmounts, + bytes32 _prevSellOrders, address orderSubmitter - ) external atStageOrderPlacement(auctionId) returns (uint64 userId) { - return - _placeSellOrders( - auctionId, - _minBuyAmounts, - _sellAmounts, - _prevSellOrders, - orderSubmitter - ); + ) external atStageOrderPlacement(auctionId) { + _placeSellOrders( + auctionId, + _minBuyAmounts, + _sellAmounts, + _prevSellOrders, + orderSubmitter + ); } function _placeSellOrders( uint256 auctionId, - uint96[] memory _minBuyAmounts, - uint96[] memory _sellAmounts, - bytes32[] memory _prevSellOrders, + uint96 _minBuyAmount, + uint96 _sellAmount, + bytes32 _prevSellOrder, address orderSubmitter - ) internal returns (uint64 userId) { + ) internal { + require(_minBuyAmount > 0, "_minBuyAmounts must be greater than 0"); + uint256 currentMinBuyAmountFromAuctioneer = 0; + ( + , + uint96 buyAmountOfInitialAuctionOrder, + uint96 sellAmountOfInitialAuctionOrder + ) = auctionData[auctionId].initialAuctionOrder.decodeOrder(); { - ( - , - uint96 buyAmountOfInitialAuctionOrder, - uint96 sellAmountOfInitialAuctionOrder - ) = auctionData[auctionId].initialAuctionOrder.decodeOrder(); - for (uint256 i = 0; i < _minBuyAmounts.length; i++) { - require( - _minBuyAmounts[i].mul(buyAmountOfInitialAuctionOrder) < - sellAmountOfInitialAuctionOrder.mul(_sellAmounts[i]), - "limit price not better than mimimal offer" - ); - } - } - uint256 sumOfSellAmounts = 0; - userId = getUserId(orderSubmitter); - uint256 minimumBiddingAmountPerOrder = - auctionData[auctionId].minimumBiddingAmountPerOrder; - for (uint256 i = 0; i < _minBuyAmounts.length; i++) { - require( - _minBuyAmounts[i] > 0, - "_minBuyAmounts must be greater than 0" + uint96 auctioneerBuyAmountMaximum = + auctionData[auctionId].auctioneerBuyAmountMaximum; + currentMinBuyAmountFromAuctioneer = getCurrentMinBuyAmountFromAuctioneer( + buyAmountOfInitialAuctionOrder, + auctioneerBuyAmountMaximum, + block + .timestamp + .sub(auctionData[auctionId].auctionStartDate) + .toUint96(), + auctionData[auctionId].maxDuration ); - // orders should have a minimum bid size in order to limit the gas - // required to compute the final price of the auction. + } + uint96 minBuyAmount = _minBuyAmount; + { require( - _sellAmounts[i] > minimumBiddingAmountPerOrder, - "order too small" + _minBuyAmount.mul(buyAmountOfInitialAuctionOrder) < + sellAmountOfInitialAuctionOrder.mul(_sellAmount), + "limit price not better than mimimal offer" ); if ( - sellOrders[auctionId].insert( - IterableOrderedOrderList.encodeOrder( - userId, - _minBuyAmounts[i], - _sellAmounts[i] - ), - _prevSellOrders[i] - ) + _minBuyAmount.mul(currentMinBuyAmountFromAuctioneer) < + sellAmountOfInitialAuctionOrder.mul(_sellAmount) ) { - sumOfSellAmounts = sumOfSellAmounts.add(_sellAmounts[i]); - emit NewSellOrder( - auctionId, - userId, - _minBuyAmounts[i], - _sellAmounts[i] - ); + minBuyAmount = _sellAmount + .mul(currentMinBuyAmountFromAuctioneer) + .div(sellAmountOfInitialAuctionOrder) + .toUint96(); } } + uint64 userId = getUserId(orderSubmitter); + // orders should have a minimum bid size in order to limit the gas + // required to compute the final price of the auction. + require( + _sellAmount > auctionData[auctionId].minimumBiddingAmountPerOrder, + "order too small" + ); + require( + sellOrders[auctionId].insert( + IterableOrderedOrderList.encodeOrder( + userId, + minBuyAmount, + _sellAmount + ), + _prevSellOrder + ), + "could not insert order" + ); auctionData[auctionId].biddingToken.safeTransferFrom( msg.sender, address(this), - sumOfSellAmounts + _sellAmount ); //[1] + emit NewSellOrder(auctionId, userId, minBuyAmount, _sellAmount); } function settleAuctionWithAdditionalOrder( uint256 auctionId, - uint96[] memory _minBuyAmount, - uint96[] memory _sellAmount, - bytes32[] memory _prevSellOrder - ) public atStageOrderPlacement(auctionId) { - require( - _minBuyAmount.length == 1 && _sellAmount.length == 1, - "Only one order can be placed atomically" - ); + uint96 _minBuyAmount, + uint96 _sellAmount, + bytes32 _prevSellOrder + ) public atStagePriceCalculation(auctionId) { + //claculate the maximal outstanding volume and adjust sell amount _placeSellOrders( auctionId, _minBuyAmount, @@ -305,13 +328,13 @@ contract ChannelAuction is Ownable { bytes32[] memory claimOrder = new bytes32[](1); claimOrder[0] = IterableOrderedOrderList.encodeOrder( getUserId(msg.sender), - _minBuyAmount[0], - _sellAmount[0] + _minBuyAmount, + _sellAmount ); claimFromParticipantOrder(auctionId, claimOrder); } - function getCurrentMinBuyAmount( + function getCurrentMinBuyAmountFromAuctioneer( uint96 buyAmountMinimum, uint96 buyAmountMaximum, uint96 passedTime, @@ -334,7 +357,7 @@ contract ChannelAuction is Ownable { // @dev function settling the auction and calculating the price function settleAuction(uint256 auctionId) public - atStageOrderPlacement(auctionId) + atStagePriceCalculation(auctionId) returns (bytes32 clearingOrder) { (uint64 auctioneerId, , uint96 fullAuctionedAmount) = @@ -342,11 +365,11 @@ contract ChannelAuction is Ownable { uint96 minAuctionedBuyAmount = 0; { - (, uint96 auctioneerBuyAmountMaximum, ) = + (, uint96 auctioneerBuyAmountMinimum, ) = auctionData[auctionId].initialAuctionOrder.decodeOrder(); - minAuctionedBuyAmount = getCurrentMinBuyAmount( - auctioneerBuyAmountMaximum, - auctionData[auctionId].auctioneerBuyAmountMinimum, + minAuctionedBuyAmount = getCurrentMinBuyAmountFromAuctioneer( + auctioneerBuyAmountMinimum, + auctionData[auctionId].auctioneerBuyAmountMaximum, block .timestamp .sub(auctionData[auctionId].auctionStartDate) @@ -457,9 +480,6 @@ contract ChannelAuction is Ownable { uint96(currentBidSum), clearingOrder ); - // Gas refunds - auctionData[auctionId].initialAuctionOrder = bytes32(0); - auctionData[auctionId].minimumBiddingAmountPerOrder = uint256(0); } function claimFromParticipantOrder( @@ -590,6 +610,21 @@ contract ChannelAuction is Ownable { } } + function getSecondsRemainingUntilLastPossibleClose(uint256 auctionId) + public + view + returns (uint256) + { + uint256 auctionEndDate = + auctionData[auctionId].auctionStartDate.add( + auctionData[auctionId].maxDuration + ); + if (auctionEndDate < block.timestamp) { + return 0; + } + return auctionEndDate.sub(block.timestamp); + } + function containsOrder(uint256 auctionId, bytes32 order) public view diff --git a/contracts/libraries/IterableOrderedOrderList.sol b/contracts/libraries/IterableOrderedOrderList.sol index 6ab296a..e0dc46e 100644 --- a/contracts/libraries/IterableOrderedOrderList.sol +++ b/contracts/libraries/IterableOrderedOrderList.sol @@ -13,20 +13,16 @@ library IterableOrderedOrderList { bytes32 internal constant QUEUE_END = 0xffffffffffffffffffffffffffffffffffffffff000000000000000000000001; - /// The struct is used to implement a modified version of a doubly linked + /// The struct is used to implement a modified version of a linked /// list with sorted elements. The list starts from QUEUE_START to - /// QUEUE_END, and each node keeps track of its predecessor and successor. + /// QUEUE_END, and each node keeps track of its successor. /// Nodes can be added or removed. /// - /// `next` and `prev` have a different role. The list is supposed to be + /// The list is supposed to be /// traversed with `next`. If `next` is empty, the node is not part of the - /// list. However, `prev` might be set for elements that are not in the - /// list, which is why it should not be used for traversing. Having a `prev` - /// set for elements not in the list is used to keep track of the history of - /// the position in the list of a removed element. + /// list. struct Data { mapping(bytes32 => bytes32) nextMap; - mapping(bytes32 => bytes32) prevMap; } struct Order { @@ -37,7 +33,6 @@ library IterableOrderedOrderList { function initializeEmptyList(Data storage self) internal { self.nextMap[QUEUE_START] = QUEUE_END; - self.prevMap[QUEUE_END] = QUEUE_START; } function isEmpty(Data storage self) internal view returns (bool) { diff --git a/contracts/wrappers/DepositAndPlaceOrderForChannelAuction.sol b/contracts/wrappers/DepositAndPlaceOrderForChannelAuction.sol new file mode 100644 index 0000000..a8a32a6 --- /dev/null +++ b/contracts/wrappers/DepositAndPlaceOrderForChannelAuction.sol @@ -0,0 +1,35 @@ +pragma solidity >=0.6.8; +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "../ChannelAuction.sol"; +import "../interfaces/IWETH.sol"; + +contract DepositAndPlaceOrderForChannelAuction { + ChannelAuction public immutable channelAuction; + IWETH public immutable nativeTokenWrapper; + + constructor(address channelAuctionAddress, address _nativeTokenWrapper) + public + { + nativeTokenWrapper = IWETH(_nativeTokenWrapper); + channelAuction = ChannelAuction(channelAuctionAddress); + IERC20(_nativeTokenWrapper).approve(channelAuctionAddress, uint256(-1)); + } + + function depositAndPlaceOrder( + uint256 auctionId, + uint96 _minBuyAmount, + bytes32 _prevSellOrder + ) external payable { + require(msg.value < 2**96, "too much value sent"); + nativeTokenWrapper.deposit{value: msg.value}(); + uint96 sellAmount = uint96(msg.value); + + channelAuction.placeSellOrdersOnBehalf( + auctionId, + _minBuyAmount, + sellAmount, + _prevSellOrder, + msg.sender + ); + } +} diff --git a/src/priceCalculation.ts b/src/priceCalculation.ts index 2ea0dd2..e9a8a54 100644 --- a/src/priceCalculation.ts +++ b/src/priceCalculation.ts @@ -163,7 +163,9 @@ function printOrders(orders: Order[], isInitialOrder: boolean, debug = false) { " for ", order.buyAmount.toString(), " at price of", - order.sellAmount.div(order.buyAmount).toString(), + order.buyAmount.gt(BigNumber.from("0")) + ? order.sellAmount.div(order.buyAmount).toString() + : "infinity", ); }); } else { @@ -175,7 +177,9 @@ function printOrders(orders: Order[], isInitialOrder: boolean, debug = false) { " for ", order.buyAmount.toString(), " at price of", - order.buyAmount.div(order.sellAmount).toString(), + order.buyAmount.gt(BigNumber.from("0")) + ? order.sellAmount.div(order.buyAmount).toString() + : "infinity", ); }); } @@ -260,6 +264,10 @@ export async function getAllSellOrders( }); const filterOrderCancellations = easyAuction.filters.CancellationSellOrder; + if (filterOrderCancellations == undefined) { + // For ChannelAuctions, there are no cancellations and hence, we return all sell orders + return sellOrders; + } const logsForCancellations = await easyAuction.queryFilter( filterOrderCancellations(), 0, @@ -332,3 +340,30 @@ export async function placeOrders( ); } } +export async function placeOrdersForChannelAuction( + easyAuction: Contract, + sellOrders: Order[], + auctionId: BigNumber, + hre: HardhatRuntimeEnvironment, +): Promise { + const newSellOrders = []; + for (const sellOrder of sellOrders) { + const tx = await easyAuction + .connect( + await hre.waffle.provider.getWallets()[sellOrder.userId.toNumber() - 1], + ) + .placeSellOrders( + auctionId, + sellOrder.buyAmount, + sellOrder.sellAmount, + queueStartElement, + ); + const newSellOrderEventData = (await tx.wait()).events.pop(); + newSellOrders.push({ + sellAmount: newSellOrderEventData.args.sellAmount, + buyAmount: newSellOrderEventData.args.buyAmount, + userId: newSellOrderEventData.args.userId, + }); + } + return newSellOrders; +} diff --git a/test/contract/Channelauction.spec.ts b/test/contract/Channelauction.spec.ts index a7e83b2..8bdf23c 100644 --- a/test/contract/Channelauction.spec.ts +++ b/test/contract/Channelauction.spec.ts @@ -9,7 +9,7 @@ import { encodeOrder, queueStartElement, createTokensAndMintAndApprove, - placeOrders, + placeOrdersForChannelAuction, calculateClearingPrice, getAllSellOrders, getClearingPriceFromInitialOrder, @@ -17,15 +17,16 @@ import { import { createAuctionWithDefaults, - createAuctionWithDefaultsAndReturnId, createChannelAuctionWithDefaults, + createChannelAuctionWithDefaultsAndReturnId, } from "./defaultContractInteractions"; import { sendTxAndGetReturnValue, - closeAuction, + closeChannelAuction, increaseTime, claimFromAllOrders, MAGIC_VALUE_FROM_ALLOW_LIST_VERIFIER_INTERFACE, + startChannelAuction, } from "./utilities"; ///////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -34,7 +35,7 @@ import { ///////////////////////////////////////////////////////////////////////////////////////////////////////////// describe("ChannelAuction", async () => { - const [user_1, user_2, user_3] = waffle.provider.getWallets(); + const [user_1, user_2, user_3, user_4] = await waffle.provider.getWallets(); let channelAuction: Contract; beforeEach(async () => { const ChannelAuction = await ethers.getContractFactory("ChannelAuction"); @@ -61,7 +62,7 @@ describe("ChannelAuction", async () => { "minimumBiddingAmountPerOrder is not allowed to be zero", ); }); - it.only("throws if auctioned amount is zero", async () => { + it("throws if auctioned amount is zero", async () => { const { auctioningToken, biddingToken, @@ -79,7 +80,7 @@ describe("ChannelAuction", async () => { }), ).to.be.revertedWith("cannot auction zero tokens"); }); - it.only("throws if auction is a giveaway", async () => { + it("throws if auction is a giveaway", async () => { const { auctioningToken, biddingToken, @@ -97,7 +98,7 @@ describe("ChannelAuction", async () => { }), ).to.be.revertedWith("_auctioneerBuyAmountMinimum must be positive"); }); - it.only("throws if auction end is not in the future", async () => { + it("throws if auction end is not in the future", async () => { const { auctioningToken, biddingToken, @@ -117,7 +118,7 @@ describe("ChannelAuction", async () => { }), ).to.be.revertedWith("time periods are not configured correctly"); }); - it.only("initiateAuction stores the parameters correctly", async () => { + it("initiateAuction stores the parameters correctly", async () => { const { auctioningToken, biddingToken, @@ -141,9 +142,10 @@ describe("ChannelAuction", async () => { { auctioningToken, biddingToken, - auctionedSellAmount: initialAuctionOrder.sellAmount, - _buyAmountMaximum: initialAuctionOrder.buyAmount, - auctionStartDate, + _auctionedSellAmount: initialAuctionOrder.sellAmount, + _auctioneerBuyAmountMaximum: initialAuctionOrder.buyAmount, + _auctioneerBuyAmountMinimum: initialAuctionOrder.buyAmount.div(2), + _auctionStartDate: auctionStartDate, _maxDuration, }, ); @@ -153,10 +155,8 @@ describe("ChannelAuction", async () => { expect(auctionData.initialAuctionOrder).to.equal( encodeOrder(initialAuctionOrder), ); - expect(auctionData.auctionEndDate).to.be.equal(auctionEndDate); - expect(auctionData.orderCancellationEndDate).to.be.equal( - orderCancellationEndDate, - ); + expect(auctionData.auctionStartDate).to.be.equal(auctionStartDate); + expect(auctionData.maxDuration).to.be.equal(_maxDuration); await expect(auctionData.clearingPriceOrder).to.equal( encodeOrder({ userId: BigNumber.from(0), @@ -197,8 +197,71 @@ describe("ChannelAuction", async () => { ).to.equal(1); }); }); - describe("placeOrdersOnBehalf", async () => { - it("places a new order and checks that tokens were transferred", async () => { + // Maybe we have to disable that functionality + // describe("placeOrdersOnBehalf", async () => { + // it("places a new order and checks that tokens were transferred", async () => { + // const { + // auctioningToken, + // biddingToken, + // } = await createTokensAndMintAndApprove( + // channelAuction, + // [user_1, user_2], + // hre, + // ); + // const now = (await ethers.provider.getBlock("latest")).timestamp; + // const auctionId: BigNumber = await createChannelAuctionWithDefaultsAndReturnId( + // channelAuction, + // { + // auctioningToken, + // biddingToken, + // _auctionStartDate: now + 3600, + // }, + // ); + + // const balanceBeforeOrderPlacement = await biddingToken.balanceOf( + // user_1.address, + // ); + // const balanceBeforeOrderPlacementOfUser2 = await biddingToken.balanceOf( + // user_2.address, + // ); + // const sellAmount = ethers.utils.parseEther("2").add(1); + // const buyAmount = ethers.utils.parseEther("1"); + // await startChannelAuction(channelAuction, auctionId); + + // await channelAuction + // .connect(user_1) + // .placeSellOrdersOnBehalf( + // auctionId, + // [buyAmount], + // [sellAmount], + // [queueStartElement], + // user_2.address, + // ); + + // expect(await biddingToken.balanceOf(channelAuction.address)).to.equal( + // sellAmount, + // ); + // expect(await biddingToken.balanceOf(user_1.address)).to.equal( + // balanceBeforeOrderPlacement.sub(sellAmount), + // ); + // expect(await biddingToken.balanceOf(user_2.address)).to.equal( + // balanceBeforeOrderPlacementOfUser2, + // ); + // }); + // }); + + describe("placeOrders", async () => { + it("one can not place orders, if auction is not yet initiated", async () => { + await expect( + channelAuction.placeSellOrders( + 0, + ethers.utils.parseEther("1"), + ethers.utils.parseEther("1").add(1), + queueStartElement, + ), + ).to.be.revertedWith("auction finished or not yet started"); + }); + it("one can not place orders, if auction is not yet started", async () => { const { auctioningToken, biddingToken, @@ -208,73 +271,23 @@ describe("ChannelAuction", async () => { hre, ); const now = (await ethers.provider.getBlock("latest")).timestamp; - const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + const auctionId: BigNumber = await createChannelAuctionWithDefaultsAndReturnId( channelAuction, { auctioningToken, biddingToken, - orderCancellationEndDate: now + 3600, - auctionEndDate: now + 3600, + _auctionStartDate: now + 360000, }, ); - const balanceBeforeOrderPlacement = await biddingToken.balanceOf( - user_1.address, - ); - const balanceBeforeOrderPlacementOfUser2 = await biddingToken.balanceOf( - user_2.address, - ); - const sellAmount = ethers.utils.parseEther("1").add(1); - const buyAmount = ethers.utils.parseEther("1"); - - await channelAuction - .connect(user_1) - .placeSellOrdersOnBehalf( - auctionId, - [buyAmount], - [sellAmount], - [queueStartElement], - "0x", - user_2.address, - ); - - expect(await biddingToken.balanceOf(channelAuction.address)).to.equal( - sellAmount, - ); - expect(await biddingToken.balanceOf(user_1.address)).to.equal( - balanceBeforeOrderPlacement.sub(sellAmount), - ); - expect(await biddingToken.balanceOf(user_2.address)).to.equal( - balanceBeforeOrderPlacementOfUser2, - ); - const userId = BigNumber.from( - await channelAuction.callStatic.getUserId(user_2.address), - ); - await channelAuction - .connect(user_2) - .cancelSellOrders(auctionId, [ - encodeOrder({ sellAmount, buyAmount, userId }), - ]); - expect(await biddingToken.balanceOf(channelAuction.address)).to.equal( - "0", - ); - expect(await biddingToken.balanceOf(user_2.address)).to.equal( - balanceBeforeOrderPlacementOfUser2.add(sellAmount), - ); - }); - }); - - describe("placeOrders", async () => { - it("one can not place orders, if auction is not yet initiated", async () => { await expect( channelAuction.placeSellOrders( - 0, - [ethers.utils.parseEther("1")], - [ethers.utils.parseEther("1").add(1)], - [queueStartElement], - "0x", + auctionId, + ethers.utils.parseEther("4").add(1), + ethers.utils.parseEther("1"), + queueStartElement, ), - ).to.be.revertedWith("no longer in order placement phase"); + ).to.be.revertedWith("not yet in order placement phase"); }); it("one can not place orders, if auction is over", async () => { const { @@ -285,23 +298,22 @@ describe("ChannelAuction", async () => { [user_1, user_2], hre, ); - const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + const auctionId: BigNumber = await createChannelAuctionWithDefaultsAndReturnId( channelAuction, { auctioningToken, biddingToken, }, ); - await closeAuction(channelAuction, auctionId); + await closeChannelAuction(channelAuction, auctionId); await expect( channelAuction.placeSellOrders( - 0, - [ethers.utils.parseEther("1")], - [ethers.utils.parseEther("1").add(1)], - [queueStartElement], - "0x", + auctionId, + ethers.utils.parseEther("1"), + ethers.utils.parseEther("1").add(1), + queueStartElement, ), - ).to.be.revertedWith("no longer in order placement phase"); + ).to.be.revertedWith("auction finished or not yet started"); }); it("one can not place orders, with a worser or same rate", async () => { const { @@ -312,29 +324,35 @@ describe("ChannelAuction", async () => { [user_1, user_2], hre, ); - const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + const _auctioneerBuyAmountMaximum = ethers.utils.parseEther("2"); + const _auctioneerBuyAmountMinimum = ethers.utils.parseEther("1"); + const _auctionedSellAmount = ethers.utils.parseEther("1"); + const auctionId: BigNumber = await createChannelAuctionWithDefaultsAndReturnId( channelAuction, { auctioningToken, biddingToken, + _auctionedSellAmount, + _auctioneerBuyAmountMaximum, + _auctioneerBuyAmountMinimum, }, ); + await startChannelAuction(channelAuction, auctionId); + // todo: maybe test is not accurate await expect( channelAuction.placeSellOrders( auctionId, - [ethers.utils.parseEther("1").add(1)], - [ethers.utils.parseEther("1")], - [queueStartElement], - "0x", + _auctioneerBuyAmountMinimum, + _auctionedSellAmount, + queueStartElement, ), ).to.be.revertedWith("limit price not better than mimimal offer"); await expect( channelAuction.placeSellOrders( auctionId, - [ethers.utils.parseEther("1")], - [ethers.utils.parseEther("1")], - [queueStartElement], - "0x", + _auctioneerBuyAmountMaximum, + _auctionedSellAmount, + queueStartElement, ), ).to.be.revertedWith("limit price not better than mimimal offer"); }); @@ -347,24 +365,25 @@ describe("ChannelAuction", async () => { [user_1, user_2], hre, ); - const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + const auctionId: BigNumber = await createChannelAuctionWithDefaultsAndReturnId( channelAuction, { auctioningToken, biddingToken, }, ); + await startChannelAuction(channelAuction, auctionId); + await expect( channelAuction.placeSellOrders( auctionId, - [ethers.utils.parseEther("0")], - [ethers.utils.parseEther("1")], - [queueStartElement], - "0x", + ethers.utils.parseEther("0"), + ethers.utils.parseEther("1"), + queueStartElement, ), ).to.be.revertedWith("_minBuyAmounts must be greater than 0"); }); - it("does not withdraw funds, if orders are placed twice", async () => { + it("can't place an order twice", async () => { const { auctioningToken, biddingToken, @@ -373,35 +392,35 @@ describe("ChannelAuction", async () => { [user_1, user_2], hre, ); - const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + const auctionId: BigNumber = await createChannelAuctionWithDefaultsAndReturnId( channelAuction, { auctioningToken, biddingToken, }, ); + await startChannelAuction(channelAuction, auctionId); + await expect(() => channelAuction.placeSellOrders( auctionId, - [ethers.utils.parseEther("1").sub(1)], - [ethers.utils.parseEther("1")], - [queueStartElement], - "0x", + ethers.utils.parseEther("1").sub(1), + ethers.utils.parseEther("1"), + queueStartElement, ), ).to.changeTokenBalances( biddingToken, [user_1], [ethers.utils.parseEther("-1")], ); - await expect(() => + await expect( channelAuction.placeSellOrders( auctionId, - [ethers.utils.parseEther("1").sub(1)], - [ethers.utils.parseEther("1")], - [queueStartElement], - "0x", + ethers.utils.parseEther("1").sub(1), + ethers.utils.parseEther("1"), + queueStartElement, ), - ).to.changeTokenBalances(biddingToken, [user_1], [BigNumber.from(0)]); + ).to.be.revertedWith("could not insert order"); }); it("places a new order and checks that tokens were transferred", async () => { const { @@ -412,7 +431,7 @@ describe("ChannelAuction", async () => { [user_1, user_2], hre, ); - const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + const auctionId: BigNumber = await createChannelAuctionWithDefaultsAndReturnId( channelAuction, { auctioningToken, @@ -425,15 +444,15 @@ describe("ChannelAuction", async () => { ); const sellAmount = ethers.utils.parseEther("1").add(1); const buyAmount = ethers.utils.parseEther("1"); + await startChannelAuction(channelAuction, auctionId); await channelAuction.placeSellOrders( auctionId, - [buyAmount, buyAmount], - [sellAmount, sellAmount.add(1)], - [queueStartElement, queueStartElement], - "0x", + buyAmount, + sellAmount, + queueStartElement, ); - const transferredbiddingTokenAmount = sellAmount.add(sellAmount.add(1)); + const transferredbiddingTokenAmount = sellAmount; expect(await biddingToken.balanceOf(channelAuction.address)).to.equal( transferredbiddingTokenAmount, @@ -442,10 +461,20 @@ describe("ChannelAuction", async () => { balanceBeforeOrderPlacement.sub(transferredbiddingTokenAmount), ); }); - it("order placement reverts, if order placer is not allowed", async () => { - const verifier = await artifacts.readArtifact("AllowListVerifier"); - const verifierMocked = await deployMockContract(user_3, verifier.abi); - await verifierMocked.mock.isAllowed.returns("0x00000000"); + it("throws, if DDOS attack with small order amounts is started", async () => { + const initialAuctionOrder = { + sellAmount: ethers.utils.parseEther("1"), + buyAmount: ethers.utils.parseEther("1"), + userId: BigNumber.from(1), + }; + const sellOrders = [ + { + sellAmount: ethers.utils.parseEther("1").div(5000), + buyAmount: ethers.utils.parseEther("1").div(10000), + userId: BigNumber.from(1), + }, + ]; + const { auctioningToken, biddingToken, @@ -455,28 +484,27 @@ describe("ChannelAuction", async () => { hre, ); - const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + const auctionId: BigNumber = await createChannelAuctionWithDefaultsAndReturnId( channelAuction, { auctioningToken, biddingToken, - allowListManager: verifierMocked.address, + _auctionedSellAmount: initialAuctionOrder.sellAmount, + _auctioneerBuyAmountMinimum: initialAuctionOrder.buyAmount, + _minimumBiddingAmountPerOrder: ethers.utils.parseEther("1").div(100), }, ); - - const sellAmount = ethers.utils.parseEther("1").add(1); - const buyAmount = ethers.utils.parseEther("1"); + await startChannelAuction(channelAuction, auctionId); await expect( channelAuction.placeSellOrders( auctionId, - [buyAmount], - [sellAmount], - [queueStartElement], - "0x", + sellOrders[0].buyAmount, + sellOrders[0].sellAmount, + queueStartElement, ), - ).to.be.revertedWith("user not allowed to place order"); + ).to.be.revertedWith("order too small"); }); - it("order placement reverts, if allow manager is an EOA", async () => { + it("fails, if transfers are failing", async () => { const { auctioningToken, biddingToken, @@ -485,34 +513,257 @@ describe("ChannelAuction", async () => { [user_1, user_2], hre, ); - - const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + const auctionId: BigNumber = await createChannelAuctionWithDefaultsAndReturnId( channelAuction, { auctioningToken, biddingToken, - allowListManager: user_3.address, }, ); - const sellAmount = ethers.utils.parseEther("1").add(1); const buyAmount = ethers.utils.parseEther("1"); + await biddingToken.approve( + channelAuction.address, + ethers.utils.parseEther("0"), + ); + await startChannelAuction(channelAuction, auctionId); await expect( channelAuction.placeSellOrders( auctionId, - [buyAmount], - [sellAmount], - [queueStartElement], - "0x", + buyAmount, + sellAmount, + queueStartElement, ), - ).to.be.revertedWith("function call to a non-contract account"); + ).to.be.revertedWith("ERC20: transfer amount exceeds allowance"); }); - it("allow manager can not mutate state", async () => { - const StateChangingAllowListManager = await ethers.getContractFactory( - "StateChangingAllowListVerifier", - ); - - const stateChangingAllowListManager = await StateChangingAllowListManager.deploy(); + }); + // describe("precalculateSellAmountSum", async () => { + // it("fails if too many orders are considered", async () => { + // const initialAuctionOrder = { + // sellAmount: ethers.utils.parseEther("1"), + // buyAmount: ethers.utils.parseEther("1"), + // userId: BigNumber.from(1), + // }; + // const sellOrders = [ + // { + // sellAmount: ethers.utils.parseEther("1").mul(2), + // buyAmount: ethers.utils.parseEther("1").div(5), + // userId: BigNumber.from(1), + // }, + // { + // sellAmount: ethers.utils.parseEther("1").mul(2), + // buyAmount: ethers.utils.parseEther("1"), + // userId: BigNumber.from(1), + // }, + + // { + // sellAmount: ethers.utils.parseEther("1").mul(2).add(1), + // buyAmount: ethers.utils.parseEther("1").mul(2), + // userId: BigNumber.from(1), + // }, + // ]; + // const { + // auctioningToken, + // biddingToken, + // } = await createTokensAndMintAndApprove( + // channelAuction, + // [user_1, user_2], + // hre, + // ); + + // const auctionId: BigNumber = await createChannelAuctionWithDefaultsAndReturnId( + // channelAuction, + // { + // auctioningToken, + // biddingToken, + // _auctionedSellAmount: initialAuctionOrder.sellAmount, + // _auctioneerBuyAmountMinimum: initialAuctionOrder.buyAmount, + // }, + // ); + // await placeOrdersForChannelAuction( + // channelAuction, + // sellOrders, + // auctionId, + // hre, + // ); + + // await closeChannelAuction(channelAuction, auctionId); + // await expect( + // channelAuction.precalculateSellAmountSum(auctionId, 3), + // ).to.be.revertedWith("too many orders summed up"); + // }); + // it("fails if queue end is reached", async () => { + // const initialAuctionOrder = { + // sellAmount: ethers.utils.parseEther("1"), + // buyAmount: ethers.utils.parseEther("1"), + // userId: BigNumber.from(1), + // }; + // const sellOrders = [ + // { + // sellAmount: ethers.utils.parseEther("1").mul(2), + // buyAmount: ethers.utils.parseEther("1").div(5), + // userId: BigNumber.from(1), + // }, + // ]; + // const { + // auctioningToken, + // biddingToken, + // } = await createTokensAndMintAndApprove( + // channelAuction, + // [user_1, user_2], + // hre, + // ); + + // const auctionId: BigNumber = await createChannelAuctionWithDefaultsAndReturnId( + // channelAuction, + // { + // auctioningToken, + // biddingToken, + // _auctionedSellAmount: initialAuctionOrder.sellAmount, + // _auctioneerBuyAmountMinimum: initialAuctionOrder.buyAmount, + // }, + // ); + // await placeOrdersForChannelAuction( + // channelAuction, + // sellOrders, + // auctionId, + // hre, + // ); + + // await closeChannelAuction(channelAuction, auctionId); + // await expect( + // channelAuction.precalculateSellAmountSum(auctionId, 2), + // ).to.be.revertedWith("reached end of order list"); + // }); + // it("verifies that interimSumBidAmount and iterOrder is set correctly", async () => { + // const initialAuctionOrder = { + // sellAmount: ethers.utils.parseEther("1"), + // buyAmount: ethers.utils.parseEther("1"), + // userId: BigNumber.from(1), + // }; + // const sellOrders = [ + // { + // sellAmount: ethers.utils.parseEther("1").mul(2), + // buyAmount: ethers.utils.parseEther("1").div(5), + // userId: BigNumber.from(1), + // }, + // { + // sellAmount: ethers.utils.parseEther("1").mul(2), + // buyAmount: ethers.utils.parseEther("1"), + // userId: BigNumber.from(1), + // }, + + // { + // sellAmount: ethers.utils.parseEther("1").mul(2).add(1), + // buyAmount: ethers.utils.parseEther("1").mul(2), + // userId: BigNumber.from(1), + // }, + // ]; + // const { + // auctioningToken, + // biddingToken, + // } = await createTokensAndMintAndApprove( + // channelAuction, + // [user_1, user_2], + // hre, + // ); + + // const auctionId: BigNumber = await createChannelAuctionWithDefaultsAndReturnId( + // channelAuction, + // { + // auctioningToken, + // biddingToken, + // _auctionedSellAmount: initialAuctionOrder.sellAmount, + // _auctioneerBuyAmountMinimum: initialAuctionOrder.buyAmount, + // }, + // ); + // await placeOrdersForChannelAuction( + // channelAuction, + // sellOrders, + // auctionId, + // hre, + // ); + + // await closeChannelAuction(channelAuction, auctionId); + + // await channelAuction.precalculateSellAmountSum(auctionId, 1); + // const auctionData = await channelAuction.auctionData(auctionId); + // expect(auctionData.interimSumBidAmount).to.equal( + // sellOrders[0].sellAmount, + // ); + + // expect(auctionData.interimOrder).to.equal(encodeOrder(sellOrders[0])); + // }); + // it("verifies that interimSumBidAmount and iterOrder takes correct starting values by applying twice", async () => { + // const initialAuctionOrder = { + // sellAmount: ethers.utils.parseEther("1"), + // buyAmount: ethers.utils.parseEther("1"), + // userId: BigNumber.from(1), + // }; + // const sellOrders = [ + // { + // sellAmount: ethers.utils.parseEther("1"), + // buyAmount: ethers.utils.parseEther("1").div(10), + // userId: BigNumber.from(1), + // }, + // { + // sellAmount: ethers.utils.parseEther("1"), + // buyAmount: ethers.utils.parseEther("1").div(10), + // userId: BigNumber.from(2), + // }, + // { + // sellAmount: ethers.utils.parseEther("1").mul(2), + // buyAmount: ethers.utils.parseEther("1"), + // userId: BigNumber.from(1), + // }, + + // { + // sellAmount: ethers.utils.parseEther("1").mul(2).add(1), + // buyAmount: ethers.utils.parseEther("1").mul(2), + // userId: BigNumber.from(1), + // }, + // ]; + // const { + // auctioningToken, + // biddingToken, + // } = await createTokensAndMintAndApprove( + // channelAuction, + // [user_1, user_2], + // hre, + // ); + + // const auctionId: BigNumber = await createChannelAuctionWithDefaultsAndReturnId( + // channelAuction, + // { + // auctioningToken, + // biddingToken, + // _auctionedSellAmount: initialAuctionOrder.sellAmount, + // _auctioneerBuyAmountMinimum: initialAuctionOrder.buyAmount, + // }, + // ); + // await startChannelAuction(channelAuction, auctionId); + + // await placeOrdersForChannelAuction( + // channelAuction, + // sellOrders, + // auctionId, + // hre, + // ); + + // await closeChannelAuction(channelAuction, auctionId); + + // await channelAuction.precalculateSellAmountSum(auctionId, 1); + // await channelAuction.precalculateSellAmountSum(auctionId, 1); + // const auctionData = await channelAuction.auctionData(auctionId); + // expect(auctionData.interimSumBidAmount).to.equal( + // sellOrders[0].sellAmount.add(sellOrders[0].sellAmount), + // ); + + // expect(auctionData.interimOrder).to.equal(encodeOrder(sellOrders[1])); + // }); + // }); + describe("settleAuction", async () => { + it("checks case 4, it verifies the price in case of clearing order == initialAuctionOrder", async () => { const { auctioningToken, biddingToken, @@ -521,118 +772,138 @@ describe("ChannelAuction", async () => { [user_1, user_2], hre, ); - - const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( - channelAuction, + const auctionInitParameters = { + auctioningToken, + biddingToken, + _auctionedSellAmount: ethers.utils.parseEther("1"), + _auctioneerBuyAmountMinimum: ethers.utils.parseEther("1"), + _auctioneerBuyAmountMaximum: ethers.utils.parseEther("2"), + _auctioneerUserId: BigNumber.from(0), + }; + const sellOrders = [ { - auctioningToken, - biddingToken, - allowListManager: stateChangingAllowListManager.address, + sellAmount: ethers.utils.parseEther("1").div(10), + buyAmount: ethers.utils.parseEther("1").div(20), + userId: BigNumber.from(1), }, + ]; + const auctionId: BigNumber = await createChannelAuctionWithDefaultsAndReturnId( + channelAuction, + auctionInitParameters, ); - - const sellAmount = ethers.utils.parseEther("1").add(1); - const buyAmount = ethers.utils.parseEther("1"); - expect( - await stateChangingAllowListManager.callStatic.isAllowed( - user_1.address, - auctionId, - "0x", - ), - ).to.be.equal(MAGIC_VALUE_FROM_ALLOW_LIST_VERIFIER_INTERFACE); - await expect( - channelAuction.placeSellOrders( - auctionId, - [buyAmount], - [sellAmount], - [queueStartElement], - "0x", - ), - ).to.be.revertedWith( - "Transaction reverted and Hardhat couldn't infer the reason. Please report this to help us improve Hardhat", - ); - }); - - it("order placement works, if order placer is allowed", async () => { - const verifier = await artifacts.readArtifact("AllowListVerifier"); - const verifierMocked = await deployMockContract(user_3, verifier.abi); - await verifierMocked.mock.isAllowed.returns( - MAGIC_VALUE_FROM_ALLOW_LIST_VERIFIER_INTERFACE, - ); - const { - auctioningToken, - biddingToken, - } = await createTokensAndMintAndApprove( + await startChannelAuction(channelAuction, auctionId); + await placeOrdersForChannelAuction( channelAuction, - [user_1, user_2], + sellOrders, + auctionId, hre, ); - - const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + await closeChannelAuction(channelAuction, auctionId); + const { clearingOrder: price } = await calculateClearingPrice( channelAuction, - { - auctioningToken, - biddingToken, - allowListManager: verifierMocked.address, - }, + auctionId, ); - - const sellAmount = ethers.utils.parseEther("1").add(1); - const buyAmount = ethers.utils.parseEther("1"); - await expect( - channelAuction.placeSellOrders( + await expect(channelAuction.settleAuction(auctionId)) + .to.emit(channelAuction, "AuctionCleared") + .withArgs( auctionId, - [buyAmount], - [sellAmount], - [queueStartElement], - "0x", - ), - ).to.emit(channelAuction, "NewSellOrder"); + sellOrders[0].sellAmount.mul(price.buyAmount).div(price.sellAmount), + sellOrders[0].sellAmount, + encodeOrder({ + sellAmount: auctionInitParameters._auctioneerBuyAmountMinimum, + buyAmount: auctionInitParameters._auctionedSellAmount, + userId: auctionInitParameters._auctioneerUserId, + }), + ); + const auctionData = await channelAuction.auctionData(auctionId); + expect(auctionData.clearingPriceOrder).to.equal(encodeOrder(price)); }); - it("an order is only placed once", async () => { + it("checks case 4, it verifies the price in case of clearingOrder == initialAuctionOrder with 3 orders", async () => { const { auctioningToken, biddingToken, } = await createTokensAndMintAndApprove( channelAuction, - [user_1, user_2], + [user_1, user_2, user_3, user_4], hre, ); - const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( - channelAuction, + const auctionInitParameters = { + auctioningToken, + biddingToken, + _auctionedSellAmount: ethers.utils.parseEther("2"), + _auctioneerBuyAmountMinimum: ethers.utils.parseEther("0.5"), + _auctioneerBuyAmountMaximum: ethers.utils.parseEther("1"), + _auctioneerUserId: BigNumber.from(0), + }; + let sellOrders = [ { - auctioningToken, - biddingToken, + sellAmount: ethers.utils.parseEther("0.1"), + buyAmount: ethers.utils.parseEther("0.1"), + userId: BigNumber.from(1), }, - ); + { + sellAmount: ethers.utils.parseEther("0.1"), + buyAmount: ethers.utils.parseEther("0.099"), + userId: BigNumber.from(2), + }, + { + sellAmount: ethers.utils.parseEther("0.1"), + buyAmount: ethers.utils.parseEther("0.1"), + userId: BigNumber.from(3), + }, + ]; - const sellAmount = ethers.utils.parseEther("1").add(1); - const buyAmount = ethers.utils.parseEther("1"); + const auctionId: BigNumber = await createChannelAuctionWithDefaultsAndReturnId( + channelAuction, + auctionInitParameters, + ); + await startChannelAuction(channelAuction, auctionId); + sellOrders = await placeOrdersForChannelAuction( + channelAuction, + sellOrders, + auctionId, + hre, + ); - await channelAuction.placeSellOrders( + await closeChannelAuction(channelAuction, auctionId); + const { clearingOrder: price } = await calculateClearingPrice( + channelAuction, auctionId, - [buyAmount], - [sellAmount], - [queueStartElement], - "0x", ); - const allPlacedOrders = await getAllSellOrders(channelAuction, auctionId); - expect(allPlacedOrders.length).to.be.equal(1); + await expect(channelAuction.settleAuction(auctionId)) + .to.emit(channelAuction, "AuctionCleared") + .withArgs( + auctionId, + sellOrders[0].sellAmount + .mul(3) + .mul(price.buyAmount) + .div(price.sellAmount), + sellOrders[0].sellAmount.mul(3), + encodeOrder({ + sellAmount: auctionInitParameters._auctioneerBuyAmountMinimum, + buyAmount: auctionInitParameters._auctionedSellAmount, + userId: auctionInitParameters._auctioneerUserId, + }), + ); + const auctionData = await channelAuction.auctionData(auctionId); + expect(auctionData.clearingPriceOrder).to.equal(encodeOrder(price)); + await claimFromAllOrders(channelAuction, auctionId, sellOrders); }); - it("throws, if DDOS attack with small order amounts is started", async () => { + it("checks case 6, it verifies the price in case of clearingOrder == initialOrder, although last iterOrder would also be possible", async () => { + // This test demonstrates the case 6, + // where price could be either the auctioningOrder or sellOrder const initialAuctionOrder = { - sellAmount: ethers.utils.parseEther("1"), + sellAmount: ethers.utils.parseEther("500"), buyAmount: ethers.utils.parseEther("1"), userId: BigNumber.from(1), }; const sellOrders = [ { - sellAmount: ethers.utils.parseEther("1").div(5000), - buyAmount: ethers.utils.parseEther("1").div(10000), + sellAmount: ethers.utils.parseEther("1"), + buyAmount: ethers.utils.parseEther("1"), userId: BigNumber.from(1), }, ]; - const { auctioningToken, biddingToken, @@ -642,27 +913,57 @@ describe("ChannelAuction", async () => { hre, ); - const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + await createChannelAuctionWithDefaults(channelAuction, { + auctioningToken, + biddingToken, + _auctionedSellAmount: initialAuctionOrder.sellAmount, + _auctioneerBuyAmountMinimum: initialAuctionOrder.buyAmount, + }); + const auctionId = BigNumber.from(1); + await placeOrdersForChannelAuction( channelAuction, - { - auctioningToken, - biddingToken, - auctionedSellAmount: initialAuctionOrder.sellAmount, - minBuyAmount: initialAuctionOrder.buyAmount, - minimumBiddingAmountPerOrder: ethers.utils.parseEther("1").div(100), - }, + sellOrders, + auctionId, + hre, ); - await expect( - channelAuction.placeSellOrders( + + await closeChannelAuction(channelAuction, auctionId); + + await expect(channelAuction.settleAuction(auctionId)) + .to.emit(channelAuction, "AuctionCleared") + .withArgs( auctionId, - sellOrders.map((buyOrder) => buyOrder.buyAmount), - sellOrders.map((buyOrder) => buyOrder.sellAmount), - Array(sellOrders.length).fill(queueStartElement), - "0x", - ), - ).to.be.revertedWith("order too small"); + initialAuctionOrder.sellAmount, + sellOrders[0].sellAmount, + encodeOrder(getClearingPriceFromInitialOrder(initialAuctionOrder)), + ); + const auctionData = await channelAuction.auctionData(auctionId); + expect(auctionData.clearingPriceOrder).to.equal( + encodeOrder(getClearingPriceFromInitialOrder(initialAuctionOrder)), + ); + await channelAuction.claimFromParticipantOrder( + auctionId, + sellOrders.map((order) => encodeOrder(order)), + ); }); - it("fails, if transfers are failing", async () => { + it("checks case 12, it verifies that price can not be the initial auction price (Adam's case)", async () => { + const initialAuctionOrder = { + sellAmount: ethers.utils.parseEther("1").add(1), + buyAmount: ethers.utils.parseEther("0.1"), + userId: BigNumber.from(1), + }; + const sellOrders = [ + { + sellAmount: ethers.utils.parseEther("0.5"), + buyAmount: ethers.utils.parseEther("1"), + userId: BigNumber.from(1), + }, + { + sellAmount: BigNumber.from(2), + buyAmount: BigNumber.from(4), + userId: BigNumber.from(2), + }, + ]; const { auctioningToken, biddingToken, @@ -671,94 +972,95 @@ describe("ChannelAuction", async () => { [user_1, user_2], hre, ); - const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( - channelAuction, - { - auctioningToken, - biddingToken, - }, - ); - const sellAmount = ethers.utils.parseEther("1").add(1); - const buyAmount = ethers.utils.parseEther("1"); - await biddingToken.approve( - channelAuction.address, - ethers.utils.parseEther("0"), + + await createChannelAuctionWithDefaults(channelAuction, { + auctioningToken, + biddingToken, + _auctionedSellAmount: initialAuctionOrder.sellAmount, + _auctioneerBuyAmountMinimum: initialAuctionOrder.buyAmount, + }); + const auctionId = BigNumber.from(1); + await placeOrdersForChannelAuction( + channelAuction, + sellOrders, + auctionId, + hre, ); - await expect( - channelAuction.placeSellOrders( - auctionId, - [buyAmount, buyAmount], - [sellAmount, sellAmount.add(1)], - [queueStartElement, queueStartElement], - "0x", - ), - ).to.be.revertedWith("ERC20: transfer amount exceeds allowance"); + await closeChannelAuction(channelAuction, auctionId); + + await channelAuction.settleAuction(auctionId); + await channelAuction.auctionData(auctionId); + await claimFromAllOrders(channelAuction, auctionId, sellOrders); }); - }); - describe("precalculateSellAmountSum", async () => { - it("fails if too many orders are considered", async () => { + it("checks case 3, it verifies the price in case of clearingOrder != placed order with 3x participation", async () => { const initialAuctionOrder = { - sellAmount: ethers.utils.parseEther("1"), + sellAmount: ethers.utils.parseEther("500"), buyAmount: ethers.utils.parseEther("1"), userId: BigNumber.from(1), }; const sellOrders = [ { - sellAmount: ethers.utils.parseEther("1").mul(2), - buyAmount: ethers.utils.parseEther("1").div(5), + sellAmount: ethers.utils.parseEther("1"), + buyAmount: ethers.utils.parseEther("1"), userId: BigNumber.from(1), }, { - sellAmount: ethers.utils.parseEther("1").mul(2), + sellAmount: ethers.utils.parseEther("1"), buyAmount: ethers.utils.parseEther("1"), - userId: BigNumber.from(1), + userId: BigNumber.from(3), }, - { - sellAmount: ethers.utils.parseEther("1").mul(2).add(1), - buyAmount: ethers.utils.parseEther("1").mul(2), - userId: BigNumber.from(1), + sellAmount: ethers.utils.parseEther("1"), + buyAmount: ethers.utils.parseEther("1"), + userId: BigNumber.from(2), }, ]; + const { auctioningToken, biddingToken, } = await createTokensAndMintAndApprove( channelAuction, - [user_1, user_2], + [user_1, user_2, user_3], hre, ); - const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + await createChannelAuctionWithDefaults(channelAuction, { + auctioningToken, + biddingToken, + _auctionedSellAmount: initialAuctionOrder.sellAmount, + _auctioneerBuyAmountMinimum: initialAuctionOrder.buyAmount, + }); + const auctionId = BigNumber.from(1); + await placeOrdersForChannelAuction( channelAuction, - { - auctioningToken, - biddingToken, - auctionedSellAmount: initialAuctionOrder.sellAmount, - minBuyAmount: initialAuctionOrder.buyAmount, - }, + sellOrders, + auctionId, + hre, ); - await placeOrders(channelAuction, sellOrders, auctionId, hre); - await closeAuction(channelAuction, auctionId); - await expect( - channelAuction.precalculateSellAmountSum(auctionId, 3), - ).to.be.revertedWith("too many orders summed up"); + await closeChannelAuction(channelAuction, auctionId); + + await channelAuction.settleAuction(auctionId); + const auctionData = await channelAuction.auctionData(auctionId); + expect(auctionData.clearingPriceOrder).to.equal( + encodeOrder({ + sellAmount: ethers.utils.parseEther("3"), + buyAmount: initialAuctionOrder.sellAmount, + userId: BigNumber.from(0), + }), + ); + expect(auctionData.volumeClearingPriceOrder).to.equal(0); + await claimFromAllOrders(channelAuction, auctionId, sellOrders); }); - it("fails if queue end is reached", async () => { + it("checks case 8, it verifies the price in case of no participation of the auction", async () => { const initialAuctionOrder = { - sellAmount: ethers.utils.parseEther("1"), - buyAmount: ethers.utils.parseEther("1"), + sellAmount: BigNumber.from(1000), + buyAmount: BigNumber.from(1000), userId: BigNumber.from(1), }; - const sellOrders = [ - { - sellAmount: ethers.utils.parseEther("1").mul(2), - buyAmount: ethers.utils.parseEther("1").div(5), - userId: BigNumber.from(1), - }, - ]; + const { auctioningToken, biddingToken, @@ -768,23 +1070,24 @@ describe("ChannelAuction", async () => { hre, ); - const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( - channelAuction, - { - auctioningToken, - biddingToken, - auctionedSellAmount: initialAuctionOrder.sellAmount, - minBuyAmount: initialAuctionOrder.buyAmount, - }, - ); - await placeOrders(channelAuction, sellOrders, auctionId, hre); + await createChannelAuctionWithDefaults(channelAuction, { + auctioningToken, + biddingToken, + _auctionedSellAmount: initialAuctionOrder.sellAmount, + _auctioneerBuyAmountMinimum: initialAuctionOrder.buyAmount, + }); + const auctionId = BigNumber.from(1); - await closeAuction(channelAuction, auctionId); - await expect( - channelAuction.precalculateSellAmountSum(auctionId, 2), - ).to.be.revertedWith("reached end of order list"); + await closeChannelAuction(channelAuction, auctionId); + + await channelAuction.settleAuction(auctionId); + const auctionData = await channelAuction.auctionData(auctionId); + expect(auctionData.clearingPriceOrder).to.equal( + encodeOrder(getClearingPriceFromInitialOrder(initialAuctionOrder)), + ); + expect(auctionData.volumeClearingPriceOrder).to.equal(BigNumber.from(0)); }); - it("verifies that interimSumBidAmount and iterOrder is set correctly", async () => { + it("checks case 2, it verifies the price in case without a partially filled order", async () => { const initialAuctionOrder = { sellAmount: ethers.utils.parseEther("1"), buyAmount: ethers.utils.parseEther("1"), @@ -793,20 +1096,14 @@ describe("ChannelAuction", async () => { const sellOrders = [ { sellAmount: ethers.utils.parseEther("1").mul(2), - buyAmount: ethers.utils.parseEther("1").div(5), + buyAmount: ethers.utils.parseEther("1").div(2), userId: BigNumber.from(1), }, { - sellAmount: ethers.utils.parseEther("1").mul(2), + sellAmount: ethers.utils.parseEther("1").add(1), buyAmount: ethers.utils.parseEther("1"), userId: BigNumber.from(1), }, - - { - sellAmount: ethers.utils.parseEther("1").mul(2).add(1), - buyAmount: ethers.utils.parseEther("1").mul(2), - userId: BigNumber.from(1), - }, ]; const { auctioningToken, @@ -817,28 +1114,36 @@ describe("ChannelAuction", async () => { hre, ); - const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + const auctionId: BigNumber = await createChannelAuctionWithDefaultsAndReturnId( channelAuction, { auctioningToken, biddingToken, - auctionedSellAmount: initialAuctionOrder.sellAmount, - minBuyAmount: initialAuctionOrder.buyAmount, + _auctionedSellAmount: initialAuctionOrder.sellAmount, + _auctioneerBuyAmountMinimum: initialAuctionOrder.buyAmount, }, ); - await placeOrders(channelAuction, sellOrders, auctionId, hre); - - await closeAuction(channelAuction, auctionId); + await placeOrdersForChannelAuction( + channelAuction, + sellOrders, + auctionId, + hre, + ); - await channelAuction.precalculateSellAmountSum(auctionId, 1); + await closeChannelAuction(channelAuction, auctionId); + await channelAuction.settleAuction(auctionId); const auctionData = await channelAuction.auctionData(auctionId); - expect(auctionData.interimSumBidAmount).to.equal( - sellOrders[0].sellAmount, + expect(auctionData.clearingPriceOrder).to.equal( + encodeOrder({ + sellAmount: sellOrders[0].sellAmount, + buyAmount: initialAuctionOrder.sellAmount, + userId: BigNumber.from(0), + }), ); - - expect(auctionData.interimOrder).to.equal(encodeOrder(sellOrders[0])); + expect(auctionData.volumeClearingPriceOrder).to.equal(0); + await claimFromAllOrders(channelAuction, auctionId, sellOrders); }); - it("verifies that interimSumBidAmount and iterOrder takes correct starting values by applying twice", async () => { + it("checks case 10, verifies the price in case one sellOrder is eating initialAuctionOrder completely", async () => { const initialAuctionOrder = { sellAmount: ethers.utils.parseEther("1"), buyAmount: ethers.utils.parseEther("1"), @@ -846,24 +1151,8 @@ describe("ChannelAuction", async () => { }; const sellOrders = [ { - sellAmount: ethers.utils.parseEther("1"), - buyAmount: ethers.utils.parseEther("1").div(10), - userId: BigNumber.from(1), - }, - { - sellAmount: ethers.utils.parseEther("1"), - buyAmount: ethers.utils.parseEther("1").div(10), - userId: BigNumber.from(2), - }, - { - sellAmount: ethers.utils.parseEther("1").mul(2), - buyAmount: ethers.utils.parseEther("1"), - userId: BigNumber.from(1), - }, - - { - sellAmount: ethers.utils.parseEther("1").mul(2).add(1), - buyAmount: ethers.utils.parseEther("1").mul(2), + sellAmount: ethers.utils.parseEther("1").mul(20), + buyAmount: ethers.utils.parseEther("1").mul(10), userId: BigNumber.from(1), }, ]; @@ -876,1050 +1165,124 @@ describe("ChannelAuction", async () => { hre, ); - const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + const auctionId: BigNumber = await createChannelAuctionWithDefaultsAndReturnId( channelAuction, { auctioningToken, biddingToken, - auctionedSellAmount: initialAuctionOrder.sellAmount, - minBuyAmount: initialAuctionOrder.buyAmount, + _auctionedSellAmount: initialAuctionOrder.sellAmount, + _auctioneerBuyAmountMinimum: initialAuctionOrder.buyAmount, }, ); - await placeOrders(channelAuction, sellOrders, auctionId, hre); - - await closeAuction(channelAuction, auctionId); + await placeOrdersForChannelAuction( + channelAuction, + sellOrders, + auctionId, + hre, + ); - await channelAuction.precalculateSellAmountSum(auctionId, 1); - await channelAuction.precalculateSellAmountSum(auctionId, 1); + await closeChannelAuction(channelAuction, auctionId); + await channelAuction.settleAuction(auctionId); const auctionData = await channelAuction.auctionData(auctionId); - expect(auctionData.interimSumBidAmount).to.equal( - sellOrders[0].sellAmount.add(sellOrders[0].sellAmount), + expect(auctionData.clearingPriceOrder).to.equal( + encodeOrder(sellOrders[0]), ); - - expect(auctionData.interimOrder).to.equal(encodeOrder(sellOrders[1])); + expect(auctionData.volumeClearingPriceOrder).to.equal( + initialAuctionOrder.sellAmount + .mul(sellOrders[0].sellAmount) + .div(sellOrders[0].buyAmount), + ); + await claimFromAllOrders(channelAuction, auctionId, sellOrders); }); - }); - describe("settleAuction", async () => { - it("checks case 4, it verifies the price in case of clearing order == initialAuctionOrder", async () => { + it("checks case 5, bidding amount matches min buyAmount of initialOrder perfectly", async () => { const initialAuctionOrder = { sellAmount: ethers.utils.parseEther("1"), - buyAmount: ethers.utils.parseEther("1"), + buyAmount: ethers.utils.parseEther("0.5"), userId: BigNumber.from(1), }; const sellOrders = [ { - sellAmount: ethers.utils.parseEther("1").div(10), - buyAmount: ethers.utils.parseEther("1").div(20), - userId: BigNumber.from(1), + sellAmount: ethers.utils.parseEther("0.5"), + buyAmount: ethers.utils.parseEther("0.5"), + userId: BigNumber.from(2), + }, + { + sellAmount: ethers.utils.parseEther("0.5"), + buyAmount: ethers.utils.parseEther("0.5"), + userId: BigNumber.from(3), }, ]; - const { auctioningToken, biddingToken, } = await createTokensAndMintAndApprove( channelAuction, - [user_1, user_2], + [user_1, user_2, user_3], hre, ); - await createAuctionWithDefaults(channelAuction, { - auctioningToken, - biddingToken, - auctionedSellAmount: initialAuctionOrder.sellAmount, - minBuyAmount: initialAuctionOrder.buyAmount, - }); - - const auctionId = BigNumber.from(1); - await placeOrders(channelAuction, sellOrders, auctionId, hre); - - await closeAuction(channelAuction, auctionId); - - const { clearingOrder: price } = await calculateClearingPrice( + const auctionId: BigNumber = await createChannelAuctionWithDefaultsAndReturnId( channelAuction, + { + auctioningToken, + biddingToken, + _auctionedSellAmount: initialAuctionOrder.sellAmount, + _auctioneerBuyAmountMinimum: initialAuctionOrder.buyAmount, + }, + ); + await placeOrdersForChannelAuction( + channelAuction, + sellOrders, auctionId, + hre, ); - await expect(channelAuction.settleAuction(auctionId)) - .to.emit(channelAuction, "AuctionCleared") - .withArgs( - auctionId, - sellOrders[0].sellAmount.mul(price.buyAmount).div(price.sellAmount), - sellOrders[0].sellAmount, - encodeOrder(getClearingPriceFromInitialOrder(initialAuctionOrder)), - ); + + await closeChannelAuction(channelAuction, auctionId); + await channelAuction.settleAuction(auctionId); const auctionData = await channelAuction.auctionData(auctionId); - expect(auctionData.clearingPriceOrder).to.equal(encodeOrder(price)); + expect(auctionData.clearingPriceOrder).to.eql(encodeOrder(sellOrders[1])); + expect(auctionData.volumeClearingPriceOrder).to.equal( + sellOrders[1].sellAmount, + ); + await expect(() => + channelAuction.claimFromParticipantOrder(auctionId, [ + encodeOrder(sellOrders[0]), + ]), + ).to.changeTokenBalances( + auctioningToken, + [user_2], + [sellOrders[0].sellAmount], + ); + await expect(() => + channelAuction.claimFromParticipantOrder(auctionId, [ + encodeOrder(sellOrders[1]), + ]), + ).to.changeTokenBalances( + auctioningToken, + [user_3], + [sellOrders[1].sellAmount], + ); }); - it("checks case 4, it verifies the price in case of clearingOrder == initialAuctionOrder", async () => { + it("checks case 7, bidding amount matches min buyAmount of initialOrder perfectly with additional order", async () => { const initialAuctionOrder = { - sellAmount: ethers.utils.parseEther("5"), - buyAmount: ethers.utils.parseEther("1"), + sellAmount: ethers.utils.parseEther("1"), + buyAmount: ethers.utils.parseEther("0.5"), userId: BigNumber.from(1), }; const sellOrders = [ { - sellAmount: ethers.utils.parseEther("0.1"), - buyAmount: ethers.utils.parseEther("0.1"), - userId: BigNumber.from(1), + sellAmount: ethers.utils.parseEther("0.5"), + buyAmount: ethers.utils.parseEther("0.5"), + userId: BigNumber.from(2), }, - ]; - const { - auctioningToken, - biddingToken, - } = await createTokensAndMintAndApprove( - channelAuction, - [user_1, user_2], - hre, - ); - - await createAuctionWithDefaults(channelAuction, { - auctioningToken, - biddingToken, - auctionedSellAmount: initialAuctionOrder.sellAmount, - minBuyAmount: initialAuctionOrder.buyAmount, - }); - const auctionId = BigNumber.from(1); - await placeOrders(channelAuction, sellOrders, auctionId, hre); - - await closeAuction(channelAuction, auctionId); - - const { clearingOrder: price } = await calculateClearingPrice( - channelAuction, - auctionId, - ); - await expect(channelAuction.settleAuction(auctionId)) - .to.emit(channelAuction, "AuctionCleared") - .withArgs( - auctionId, - sellOrders[0].sellAmount.mul(price.buyAmount).div(price.sellAmount), - sellOrders[0].sellAmount, - encodeOrder(getClearingPriceFromInitialOrder(initialAuctionOrder)), - ); - const auctionData = await channelAuction.auctionData(auctionId); - expect(auctionData.clearingPriceOrder).to.equal(encodeOrder(price)); - await claimFromAllOrders(channelAuction, auctionId, sellOrders); - }); - it("checks case 4, it verifies the price in case of clearingOrder == initialAuctionOrder with 3 orders", async () => { - const initialAuctionOrder = { - sellAmount: ethers.utils.parseEther("2"), - buyAmount: ethers.utils.parseEther("1"), - userId: BigNumber.from(1), - }; - const sellOrders = [ - { - sellAmount: ethers.utils.parseEther("0.1"), - buyAmount: ethers.utils.parseEther("0.1"), - userId: BigNumber.from(1), - }, - { - sellAmount: ethers.utils.parseEther("0.1"), - buyAmount: ethers.utils.parseEther("0.1"), - userId: BigNumber.from(2), - }, - { - sellAmount: ethers.utils.parseEther("0.1"), - buyAmount: ethers.utils.parseEther("0.1"), - userId: BigNumber.from(3), - }, - ]; - const { - auctioningToken, - biddingToken, - } = await createTokensAndMintAndApprove( - channelAuction, - [user_1, user_2, user_3], - hre, - ); - - await createAuctionWithDefaults(channelAuction, { - auctioningToken, - biddingToken, - auctionedSellAmount: initialAuctionOrder.sellAmount, - minBuyAmount: initialAuctionOrder.buyAmount, - }); - const auctionId = BigNumber.from(1); - await placeOrders(channelAuction, sellOrders, auctionId, hre); - - await closeAuction(channelAuction, auctionId); - const { clearingOrder: price } = await calculateClearingPrice( - channelAuction, - auctionId, - ); - await expect(channelAuction.settleAuction(auctionId)) - .to.emit(channelAuction, "AuctionCleared") - .withArgs( - auctionId, - sellOrders[0].sellAmount - .mul(3) - .mul(price.buyAmount) - .div(price.sellAmount), - sellOrders[0].sellAmount.mul(3), - encodeOrder(getClearingPriceFromInitialOrder(initialAuctionOrder)), - ); - const auctionData = await channelAuction.auctionData(auctionId); - expect(auctionData.clearingPriceOrder).to.equal( - encodeOrder(getClearingPriceFromInitialOrder(initialAuctionOrder)), - ); - await claimFromAllOrders(channelAuction, auctionId, sellOrders); - }); - it("checks case 6, it verifies the price in case of clearingOrder == initialOrder, although last iterOrder would also be possible", async () => { - // This test demonstrates the case 6, - // where price could be either the auctioningOrder or sellOrder - const initialAuctionOrder = { - sellAmount: ethers.utils.parseEther("500"), - buyAmount: ethers.utils.parseEther("1"), - userId: BigNumber.from(1), - }; - const sellOrders = [ - { - sellAmount: ethers.utils.parseEther("1"), - buyAmount: ethers.utils.parseEther("1"), - userId: BigNumber.from(1), - }, - ]; - const { - auctioningToken, - biddingToken, - } = await createTokensAndMintAndApprove( - channelAuction, - [user_1, user_2], - hre, - ); - - await createAuctionWithDefaults(channelAuction, { - auctioningToken, - biddingToken, - auctionedSellAmount: initialAuctionOrder.sellAmount, - minBuyAmount: initialAuctionOrder.buyAmount, - }); - const auctionId = BigNumber.from(1); - await placeOrders(channelAuction, sellOrders, auctionId, hre); - - await closeAuction(channelAuction, auctionId); - - await expect(channelAuction.settleAuction(auctionId)) - .to.emit(channelAuction, "AuctionCleared") - .withArgs( - auctionId, - initialAuctionOrder.sellAmount, - sellOrders[0].sellAmount, - encodeOrder(getClearingPriceFromInitialOrder(initialAuctionOrder)), - ); - const auctionData = await channelAuction.auctionData(auctionId); - expect(auctionData.clearingPriceOrder).to.equal( - encodeOrder(getClearingPriceFromInitialOrder(initialAuctionOrder)), - ); - await channelAuction.claimFromParticipantOrder( - auctionId, - sellOrders.map((order) => encodeOrder(order)), - ); - }); - it("checks case 12, it verifies that price can not be the initial auction price (Adam's case)", async () => { - const initialAuctionOrder = { - sellAmount: ethers.utils.parseEther("1").add(1), - buyAmount: ethers.utils.parseEther("0.1"), - userId: BigNumber.from(1), - }; - const sellOrders = [ - { - sellAmount: ethers.utils.parseEther("0.5"), - buyAmount: ethers.utils.parseEther("1"), - userId: BigNumber.from(1), - }, - { - sellAmount: BigNumber.from(2), - buyAmount: BigNumber.from(4), - userId: BigNumber.from(2), - }, - ]; - const { - auctioningToken, - biddingToken, - } = await createTokensAndMintAndApprove( - channelAuction, - [user_1, user_2], - hre, - ); - - await createAuctionWithDefaults(channelAuction, { - auctioningToken, - biddingToken, - auctionedSellAmount: initialAuctionOrder.sellAmount, - minBuyAmount: initialAuctionOrder.buyAmount, - }); - const auctionId = BigNumber.from(1); - await placeOrders(channelAuction, sellOrders, auctionId, hre); - - await closeAuction(channelAuction, auctionId); - - await channelAuction.settleAuction(auctionId); - await channelAuction.auctionData(auctionId); - await claimFromAllOrders(channelAuction, auctionId, sellOrders); - }); - it("checks case 3, it verifies the price in case of clearingOrder != placed order with 3x participation", async () => { - const initialAuctionOrder = { - sellAmount: ethers.utils.parseEther("500"), - buyAmount: ethers.utils.parseEther("1"), - userId: BigNumber.from(1), - }; - const sellOrders = [ - { - sellAmount: ethers.utils.parseEther("1"), - buyAmount: ethers.utils.parseEther("1"), - userId: BigNumber.from(1), - }, - { - sellAmount: ethers.utils.parseEther("1"), - buyAmount: ethers.utils.parseEther("1"), - userId: BigNumber.from(3), - }, - { - sellAmount: ethers.utils.parseEther("1"), - buyAmount: ethers.utils.parseEther("1"), - userId: BigNumber.from(2), - }, - ]; - - const { - auctioningToken, - biddingToken, - } = await createTokensAndMintAndApprove( - channelAuction, - [user_1, user_2, user_3], - hre, - ); - - await createAuctionWithDefaults(channelAuction, { - auctioningToken, - biddingToken, - auctionedSellAmount: initialAuctionOrder.sellAmount, - minBuyAmount: initialAuctionOrder.buyAmount, - }); - const auctionId = BigNumber.from(1); - await placeOrders(channelAuction, sellOrders, auctionId, hre); - - await closeAuction(channelAuction, auctionId); - - await channelAuction.settleAuction(auctionId); - const auctionData = await channelAuction.auctionData(auctionId); - expect(auctionData.clearingPriceOrder).to.equal( - encodeOrder({ - sellAmount: ethers.utils.parseEther("3"), - buyAmount: initialAuctionOrder.sellAmount, - userId: BigNumber.from(0), - }), - ); - expect(auctionData.volumeClearingPriceOrder).to.equal(0); - await claimFromAllOrders(channelAuction, auctionId, sellOrders); - }); - it("checks case 8, it verifies the price in case of no participation of the auction", async () => { - const initialAuctionOrder = { - sellAmount: BigNumber.from(1000), - buyAmount: BigNumber.from(1000), - userId: BigNumber.from(1), - }; - - const { - auctioningToken, - biddingToken, - } = await createTokensAndMintAndApprove( - channelAuction, - [user_1, user_2], - hre, - ); - - await createAuctionWithDefaults(channelAuction, { - auctioningToken, - biddingToken, - auctionedSellAmount: initialAuctionOrder.sellAmount, - minBuyAmount: initialAuctionOrder.buyAmount, - }); - const auctionId = BigNumber.from(1); - - await closeAuction(channelAuction, auctionId); - - await channelAuction.settleAuction(auctionId); - const auctionData = await channelAuction.auctionData(auctionId); - expect(auctionData.clearingPriceOrder).to.equal( - encodeOrder(getClearingPriceFromInitialOrder(initialAuctionOrder)), - ); - expect(auctionData.volumeClearingPriceOrder).to.equal(BigNumber.from(0)); - }); - it("checks case 2, it verifies the price in case without a partially filled order", async () => { - const initialAuctionOrder = { - sellAmount: ethers.utils.parseEther("1"), - buyAmount: ethers.utils.parseEther("1"), - userId: BigNumber.from(1), - }; - const sellOrders = [ - { - sellAmount: ethers.utils.parseEther("1").mul(2), - buyAmount: ethers.utils.parseEther("1").div(2), - userId: BigNumber.from(1), - }, - { - sellAmount: ethers.utils.parseEther("1").add(1), - buyAmount: ethers.utils.parseEther("1"), - userId: BigNumber.from(1), - }, - ]; - const { - auctioningToken, - biddingToken, - } = await createTokensAndMintAndApprove( - channelAuction, - [user_1, user_2], - hre, - ); - - const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( - channelAuction, - { - auctioningToken, - biddingToken, - auctionedSellAmount: initialAuctionOrder.sellAmount, - minBuyAmount: initialAuctionOrder.buyAmount, - }, - ); - await placeOrders(channelAuction, sellOrders, auctionId, hre); - - await closeAuction(channelAuction, auctionId); - await channelAuction.settleAuction(auctionId); - const auctionData = await channelAuction.auctionData(auctionId); - expect(auctionData.clearingPriceOrder).to.equal( - encodeOrder({ - sellAmount: sellOrders[0].sellAmount, - buyAmount: initialAuctionOrder.sellAmount, - userId: BigNumber.from(0), - }), - ); - expect(auctionData.volumeClearingPriceOrder).to.equal(0); - await claimFromAllOrders(channelAuction, auctionId, sellOrders); - }); - it("checks case 10, verifies the price in case one sellOrder is eating initialAuctionOrder completely", async () => { - const initialAuctionOrder = { - sellAmount: ethers.utils.parseEther("1"), - buyAmount: ethers.utils.parseEther("1"), - userId: BigNumber.from(1), - }; - const sellOrders = [ - { - sellAmount: ethers.utils.parseEther("1").mul(20), - buyAmount: ethers.utils.parseEther("1").mul(10), - userId: BigNumber.from(1), - }, - ]; - const { - auctioningToken, - biddingToken, - } = await createTokensAndMintAndApprove( - channelAuction, - [user_1, user_2], - hre, - ); - - const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( - channelAuction, - { - auctioningToken, - biddingToken, - auctionedSellAmount: initialAuctionOrder.sellAmount, - minBuyAmount: initialAuctionOrder.buyAmount, - }, - ); - await placeOrders(channelAuction, sellOrders, auctionId, hre); - - await closeAuction(channelAuction, auctionId); - await channelAuction.settleAuction(auctionId); - const auctionData = await channelAuction.auctionData(auctionId); - expect(auctionData.clearingPriceOrder).to.equal( - encodeOrder(sellOrders[0]), - ); - expect(auctionData.volumeClearingPriceOrder).to.equal( - initialAuctionOrder.sellAmount - .mul(sellOrders[0].sellAmount) - .div(sellOrders[0].buyAmount), - ); - await claimFromAllOrders(channelAuction, auctionId, sellOrders); - }); - it("checks case 5, bidding amount matches min buyAmount of initialOrder perfectly", async () => { - const initialAuctionOrder = { - sellAmount: ethers.utils.parseEther("1"), - buyAmount: ethers.utils.parseEther("0.5"), - userId: BigNumber.from(1), - }; - const sellOrders = [ - { - sellAmount: ethers.utils.parseEther("0.5"), - buyAmount: ethers.utils.parseEther("0.5"), - userId: BigNumber.from(2), - }, - { - sellAmount: ethers.utils.parseEther("0.5"), - buyAmount: ethers.utils.parseEther("0.5"), - userId: BigNumber.from(3), - }, - ]; - const { - auctioningToken, - biddingToken, - } = await createTokensAndMintAndApprove( - channelAuction, - [user_1, user_2, user_3], - hre, - ); - - const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( - channelAuction, - { - auctioningToken, - biddingToken, - auctionedSellAmount: initialAuctionOrder.sellAmount, - minBuyAmount: initialAuctionOrder.buyAmount, - }, - ); - await placeOrders(channelAuction, sellOrders, auctionId, hre); - - await closeAuction(channelAuction, auctionId); - await channelAuction.settleAuction(auctionId); - const auctionData = await channelAuction.auctionData(auctionId); - expect(auctionData.clearingPriceOrder).to.eql(encodeOrder(sellOrders[1])); - expect(auctionData.volumeClearingPriceOrder).to.equal( - sellOrders[1].sellAmount, - ); - await expect(() => - channelAuction.claimFromParticipantOrder(auctionId, [ - encodeOrder(sellOrders[0]), - ]), - ).to.changeTokenBalances( - auctioningToken, - [user_2], - [sellOrders[0].sellAmount], - ); - await expect(() => - channelAuction.claimFromParticipantOrder(auctionId, [ - encodeOrder(sellOrders[1]), - ]), - ).to.changeTokenBalances( - auctioningToken, - [user_3], - [sellOrders[1].sellAmount], - ); - }); - it("checks case 7, bidding amount matches min buyAmount of initialOrder perfectly with additional order", async () => { - const initialAuctionOrder = { - sellAmount: ethers.utils.parseEther("1"), - buyAmount: ethers.utils.parseEther("0.5"), - userId: BigNumber.from(1), - }; - const sellOrders = [ - { - sellAmount: ethers.utils.parseEther("0.5"), - buyAmount: ethers.utils.parseEther("0.5"), - userId: BigNumber.from(2), - }, - { - sellAmount: ethers.utils.parseEther("0.5"), - buyAmount: ethers.utils.parseEther("0.5"), - userId: BigNumber.from(3), - }, - { - sellAmount: ethers.utils.parseEther("0.5"), - buyAmount: ethers.utils.parseEther("0.6"), - userId: BigNumber.from(3), - }, - ]; - const { - auctioningToken, - biddingToken, - } = await createTokensAndMintAndApprove( - channelAuction, - [user_1, user_2, user_3], - hre, - ); - - const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( - channelAuction, - { - auctioningToken, - biddingToken, - auctionedSellAmount: initialAuctionOrder.sellAmount, - minBuyAmount: initialAuctionOrder.buyAmount, - }, - ); - await placeOrders(channelAuction, sellOrders, auctionId, hre); - - await closeAuction(channelAuction, auctionId); - await channelAuction.settleAuction(auctionId); - const auctionData = await channelAuction.auctionData(auctionId); - expect(auctionData.clearingPriceOrder).to.eql(encodeOrder(sellOrders[1])); - expect(auctionData.volumeClearingPriceOrder).to.equal( - sellOrders[1].sellAmount, - ); - await expect(() => - channelAuction.claimFromParticipantOrder(auctionId, [ - encodeOrder(sellOrders[0]), - ]), - ).to.changeTokenBalances( - auctioningToken, - [user_2], - [sellOrders[0].sellAmount], - ); - await expect(() => - channelAuction.claimFromParticipantOrder(auctionId, [ - encodeOrder(sellOrders[1]), - ]), - ).to.changeTokenBalances( - auctioningToken, - [user_3], - [sellOrders[1].sellAmount], - ); - await expect(() => - channelAuction.claimFromParticipantOrder(auctionId, [ - encodeOrder(sellOrders[2]), - ]), - ).to.changeTokenBalances( - biddingToken, - [user_3], - [sellOrders[2].sellAmount], - ); - }); - it("checks case 10: it shows an example why userId should always be given: 2 orders with the same price", async () => { - const initialAuctionOrder = { - sellAmount: ethers.utils.parseEther("1"), - buyAmount: ethers.utils.parseEther("0.5"), - userId: BigNumber.from(1), - }; - const sellOrders = [ - { - sellAmount: ethers.utils.parseEther("0.5"), - buyAmount: ethers.utils.parseEther("0.5"), - userId: BigNumber.from(2), - }, - { - sellAmount: ethers.utils.parseEther("0.5"), - buyAmount: ethers.utils.parseEther("0.5"), - userId: BigNumber.from(3), - }, - { - sellAmount: ethers.utils.parseEther("0.5"), - buyAmount: ethers.utils.parseEther("0.4"), - userId: BigNumber.from(3), - }, - ]; - const { - auctioningToken, - biddingToken, - } = await createTokensAndMintAndApprove( - channelAuction, - [user_1, user_2, user_3], - hre, - ); - - const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( - channelAuction, - { - auctioningToken, - biddingToken, - auctionedSellAmount: initialAuctionOrder.sellAmount, - minBuyAmount: initialAuctionOrder.buyAmount, - }, - ); - await placeOrders(channelAuction, sellOrders, auctionId, hre); - - await closeAuction(channelAuction, auctionId); - await channelAuction.settleAuction(auctionId); - const auctionData = await channelAuction.auctionData(auctionId); - expect(auctionData.clearingPriceOrder).to.equal( - encodeOrder(sellOrders[0]), - ); - expect(auctionData.volumeClearingPriceOrder).to.equal( - sellOrders[1].sellAmount, - ); - await expect(() => - channelAuction.claimFromParticipantOrder(auctionId, [ - encodeOrder(sellOrders[0]), - ]), - ).to.changeTokenBalances( - auctioningToken, - [user_2], - [sellOrders[0].sellAmount], - ); - await expect(() => - channelAuction.claimFromParticipantOrder(auctionId, [ - encodeOrder(sellOrders[1]), - ]), - ).to.changeTokenBalances( - biddingToken, - [user_3], - [sellOrders[1].sellAmount], - ); - await expect(() => - channelAuction.claimFromParticipantOrder(auctionId, [ - encodeOrder(sellOrders[2]), - ]), - ).to.changeTokenBalances( - auctioningToken, - [user_3], - [sellOrders[2].sellAmount], - ); - }); - it("checks case 1, it verifies the price in case of 2 of 3 sellOrders eating initialAuctionOrder completely", async () => { - const initialAuctionOrder = { - sellAmount: ethers.utils.parseEther("1"), - buyAmount: ethers.utils.parseEther("1"), - userId: BigNumber.from(1), - }; - const sellOrders = [ - { - sellAmount: ethers.utils.parseEther("1").mul(2), - buyAmount: ethers.utils.parseEther("1").div(5), - userId: BigNumber.from(1), - }, - { - sellAmount: ethers.utils.parseEther("1").mul(2), - buyAmount: ethers.utils.parseEther("1"), - userId: BigNumber.from(1), - }, - - { - sellAmount: ethers.utils.parseEther("1").mul(2).add(1), - buyAmount: ethers.utils.parseEther("1").mul(2), - userId: BigNumber.from(1), - }, - ]; - const { - auctioningToken, - biddingToken, - } = await createTokensAndMintAndApprove( - channelAuction, - [user_1, user_2], - hre, - ); - - const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( - channelAuction, - { - auctioningToken, - biddingToken, - auctionedSellAmount: initialAuctionOrder.sellAmount, - minBuyAmount: initialAuctionOrder.buyAmount, - }, - ); - await placeOrders(channelAuction, sellOrders, auctionId, hre); - - await closeAuction(channelAuction, auctionId); - await channelAuction.settleAuction(auctionId); - const auctionData = await channelAuction.auctionData(auctionId); - expect(auctionData.clearingPriceOrder).to.eql(encodeOrder(sellOrders[1])); - expect(auctionData.volumeClearingPriceOrder).to.equal(0); - await claimFromAllOrders(channelAuction, auctionId, sellOrders); - }); - it("verifies the price in case of 2 of 3 sellOrders eating initialAuctionOrder completely - with precalculateSellAmountSum step", async () => { - const initialAuctionOrder = { - sellAmount: ethers.utils.parseEther("1"), - buyAmount: ethers.utils.parseEther("1"), - userId: BigNumber.from(1), - }; - const sellOrders = [ - { - sellAmount: ethers.utils.parseEther("1").mul(2), - buyAmount: ethers.utils.parseEther("1").div(5), - userId: BigNumber.from(1), - }, - { - sellAmount: ethers.utils.parseEther("1").mul(2), - buyAmount: ethers.utils.parseEther("1"), - userId: BigNumber.from(1), - }, - - { - sellAmount: ethers.utils.parseEther("1").mul(2).add(1), - buyAmount: ethers.utils.parseEther("1").mul(2), - userId: BigNumber.from(1), - }, - ]; - const { - auctioningToken, - biddingToken, - } = await createTokensAndMintAndApprove( - channelAuction, - [user_1, user_2], - hre, - ); - - const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( - channelAuction, - { - auctioningToken, - biddingToken, - auctionedSellAmount: initialAuctionOrder.sellAmount, - minBuyAmount: initialAuctionOrder.buyAmount, - }, - ); - await placeOrders(channelAuction, sellOrders, auctionId, hre); - - await closeAuction(channelAuction, auctionId); - // this is the additional step - await channelAuction.precalculateSellAmountSum(auctionId, 1); - - await channelAuction.settleAuction(auctionId); - const auctionData = await channelAuction.auctionData(auctionId); - expect(auctionData.clearingPriceOrder).to.eql(encodeOrder(sellOrders[1])); - expect(auctionData.volumeClearingPriceOrder).to.equal(0); - }); - it("verifies the price in case of 2 of 4 sellOrders eating initialAuctionOrder completely - with precalculateSellAmountSum step and one more step within settleAuction", async () => { - const initialAuctionOrder = { - sellAmount: ethers.utils.parseEther("1"), - buyAmount: ethers.utils.parseEther("1"), - userId: BigNumber.from(1), - }; - const sellOrders = [ - { - sellAmount: ethers.utils.parseEther("1"), - buyAmount: ethers.utils.parseEther("1").div(5), - userId: BigNumber.from(1), - }, - { - sellAmount: ethers.utils.parseEther("1"), - buyAmount: ethers.utils.parseEther("1").div(5), - userId: BigNumber.from(2), - }, - { - sellAmount: ethers.utils.parseEther("1").mul(2), - buyAmount: ethers.utils.parseEther("1"), - userId: BigNumber.from(1), - }, - - { - sellAmount: ethers.utils.parseEther("1").mul(2).add(1), - buyAmount: ethers.utils.parseEther("1").mul(2), - userId: BigNumber.from(1), - }, - ]; - const { - auctioningToken, - biddingToken, - } = await createTokensAndMintAndApprove( - channelAuction, - [user_1, user_2], - hre, - ); - - const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( - channelAuction, - { - auctioningToken, - biddingToken, - auctionedSellAmount: initialAuctionOrder.sellAmount, - minBuyAmount: initialAuctionOrder.buyAmount, - }, - ); - await placeOrders(channelAuction, sellOrders, auctionId, hre); - - await closeAuction(channelAuction, auctionId); - // this is the additional step - await channelAuction.precalculateSellAmountSum(auctionId, 1); - - const auctionData = await channelAuction.auctionData(auctionId); - expect(auctionData.interimSumBidAmount).to.equal( - sellOrders[0].sellAmount, - ); - expect(auctionData.interimOrder).to.equal(encodeOrder(sellOrders[0])); - await channelAuction.settleAuction(auctionId); - const auctionData2 = await channelAuction.auctionData(auctionId); - expect(auctionData2.clearingPriceOrder).to.eql( - encodeOrder(sellOrders[2]), - ); - expect(auctionData2.volumeClearingPriceOrder).to.equal(0); - await claimFromAllOrders(channelAuction, auctionId, sellOrders); - }); - it("verifies the price in case of clearing order is decided by userId", async () => { - const initialAuctionOrder = { - sellAmount: ethers.utils.parseEther("1"), - buyAmount: ethers.utils.parseEther("1"), - userId: BigNumber.from(1), - }; - const sellOrders = [ - { - sellAmount: ethers.utils.parseEther("1").mul(2), - buyAmount: ethers.utils.parseEther("1").div(5), - userId: BigNumber.from(1), - }, - { - sellAmount: ethers.utils.parseEther("1").mul(2), - buyAmount: ethers.utils.parseEther("1"), - userId: BigNumber.from(1), - }, - - { - sellAmount: ethers.utils.parseEther("1").mul(2), - buyAmount: ethers.utils.parseEther("1"), - userId: BigNumber.from(2), - }, - ]; - const { - auctioningToken, - biddingToken, - } = await createTokensAndMintAndApprove( - channelAuction, - [user_1, user_2], - hre, - ); - - const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( - channelAuction, - { - auctioningToken, - biddingToken, - auctionedSellAmount: initialAuctionOrder.sellAmount, - minBuyAmount: initialAuctionOrder.buyAmount, - }, - ); - await placeOrders(channelAuction, sellOrders, auctionId, hre); - - await closeAuction(channelAuction, auctionId); - await channelAuction.settleAuction(auctionId); - const auctionData = await channelAuction.auctionData(auctionId); - expect(auctionData.clearingPriceOrder).to.be.equal( - encodeOrder(sellOrders[1]), - ); - expect(auctionData.volumeClearingPriceOrder).to.equal(0); - await claimFromAllOrders(channelAuction, auctionId, sellOrders); - }); - it("simple version of e2e gas test", async () => { - const initialAuctionOrder = { - sellAmount: ethers.utils.parseEther("1"), - buyAmount: ethers.utils.parseEther("1"), - userId: BigNumber.from(1), - }; - const sellOrders = [ - { - sellAmount: ethers.utils.parseEther("1").div(4), - buyAmount: ethers.utils.parseEther("1").div(8), - userId: BigNumber.from(1), - }, - { - sellAmount: ethers.utils.parseEther("1").div(4), - buyAmount: ethers.utils.parseEther("1").div(12), - userId: BigNumber.from(1), - }, - { - sellAmount: ethers.utils.parseEther("1").div(4), - buyAmount: ethers.utils.parseEther("1").div(16), - userId: BigNumber.from(1), - }, - { - sellAmount: ethers.utils.parseEther("1").div(4), - buyAmount: ethers.utils.parseEther("1").div(20), - userId: BigNumber.from(1), - }, - ]; - const { - auctioningToken, - biddingToken, - } = await createTokensAndMintAndApprove( - channelAuction, - [user_1, user_2], - hre, - ); - - const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( - channelAuction, - { - auctioningToken, - biddingToken, - auctionedSellAmount: initialAuctionOrder.sellAmount, - minBuyAmount: initialAuctionOrder.buyAmount, - }, - ); - await placeOrders(channelAuction, sellOrders, auctionId, hre); - - await closeAuction(channelAuction, auctionId); - const { clearingOrder: price } = await calculateClearingPrice( - channelAuction, - auctionId, - ); - - await channelAuction.settleAuction(auctionId); - expect(price.toString()).to.eql( - getClearingPriceFromInitialOrder(initialAuctionOrder).toString(), - ); - const auctionData = await channelAuction.auctionData(auctionId); - expect(auctionData.clearingPriceOrder).to.equal( - encodeOrder(getClearingPriceFromInitialOrder(initialAuctionOrder)), - ); - await claimFromAllOrders(channelAuction, auctionId, sellOrders); - }); - it("checks whether the minimalFundingThreshold is not met", async () => { - const initialAuctionOrder = { - sellAmount: ethers.utils.parseEther("10"), - buyAmount: ethers.utils.parseEther("10"), - userId: BigNumber.from(1), - }; - const sellOrders = [ - { - sellAmount: ethers.utils.parseEther("1"), - buyAmount: ethers.utils.parseEther("1").div(2), - userId: BigNumber.from(1), - }, - { - sellAmount: ethers.utils.parseEther("1"), - buyAmount: ethers.utils.parseEther("1").div(4), - userId: BigNumber.from(1), - }, - ]; - const { - auctioningToken, - biddingToken, - } = await createTokensAndMintAndApprove( - channelAuction, - [user_1, user_2], - hre, - ); - - const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( - channelAuction, - { - auctioningToken, - biddingToken, - auctionedSellAmount: initialAuctionOrder.sellAmount, - minBuyAmount: initialAuctionOrder.buyAmount, - minFundingThreshold: ethers.utils.parseEther("5"), - }, - ); - await placeOrders(channelAuction, sellOrders, auctionId, hre); - - await closeAuction(channelAuction, auctionId); - const { clearingOrder: price } = await calculateClearingPrice( - channelAuction, - auctionId, - ); - - await channelAuction.settleAuction(auctionId); - expect(price.toString()).to.eql( - getClearingPriceFromInitialOrder(initialAuctionOrder).toString(), - ); - const auctionData = await channelAuction.auctionData(auctionId); - expect(auctionData.minFundingThresholdNotReached).to.equal(true); - }); - }); - describe("claimFromAuctioneerOrder", async () => { - it("checks that auctioneer receives all their auctioningTokens back if minFundingThreshold was not met", async () => { - const initialAuctionOrder = { - sellAmount: ethers.utils.parseEther("10"), - buyAmount: ethers.utils.parseEther("10"), - userId: BigNumber.from(1), - }; - const sellOrders = [ { - sellAmount: ethers.utils.parseEther("1"), - buyAmount: ethers.utils.parseEther("1").div(2), - userId: BigNumber.from(2), + sellAmount: ethers.utils.parseEther("0.5"), + buyAmount: ethers.utils.parseEther("0.5"), + userId: BigNumber.from(3), }, { - sellAmount: ethers.utils.parseEther("1"), - buyAmount: ethers.utils.parseEther("1").div(4), + sellAmount: ethers.utils.parseEther("0.5"), + buyAmount: ethers.utils.parseEther("0.6"), userId: BigNumber.from(3), }, ]; @@ -1931,323 +1294,79 @@ describe("ChannelAuction", async () => { [user_1, user_2, user_3], hre, ); - const auctioningTokenBalanceBeforeAuction = await auctioningToken.balanceOf( - user_1.address, - ); - const feeReceiver = user_3; - const feeNumerator = 10; - await channelAuction - .connect(user_1) - .setFeeParameters(feeNumerator, feeReceiver.address); - const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( - channelAuction, - { - auctioningToken, - biddingToken, - auctionedSellAmount: initialAuctionOrder.sellAmount, - minBuyAmount: initialAuctionOrder.buyAmount, - minFundingThreshold: ethers.utils.parseEther("5"), - }, - ); - await placeOrders(channelAuction, sellOrders, auctionId, hre); - await closeAuction(channelAuction, auctionId); - await channelAuction.settleAuction(auctionId); - const auctionData = await channelAuction.auctionData(auctionId); - expect(auctionData.minFundingThresholdNotReached).to.equal(true); - expect(await auctioningToken.balanceOf(user_1.address)).to.be.equal( - auctioningTokenBalanceBeforeAuction, - ); - }); - it("checks the claimed amounts for a fully matched initialAuctionOrder and buyOrder", async () => { - const initialAuctionOrder = { - sellAmount: ethers.utils.parseEther("1"), - buyAmount: ethers.utils.parseEther("1"), - userId: BigNumber.from(1), - }; - const sellOrders = [ - { - sellAmount: ethers.utils.parseEther("1").add(1), - buyAmount: ethers.utils.parseEther("1"), - userId: BigNumber.from(1), - }, - ]; - const { - auctioningToken, - biddingToken, - } = await createTokensAndMintAndApprove( - channelAuction, - [user_1, user_2], - hre, - ); - const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + const auctionId: BigNumber = await createChannelAuctionWithDefaultsAndReturnId( channelAuction, { auctioningToken, biddingToken, - auctionedSellAmount: initialAuctionOrder.sellAmount, - minBuyAmount: initialAuctionOrder.buyAmount, + _auctionedSellAmount: initialAuctionOrder.sellAmount, + _auctioneerBuyAmountMinimum: initialAuctionOrder.buyAmount, }, ); - await placeOrders(channelAuction, sellOrders, auctionId, hre); - - await closeAuction(channelAuction, auctionId); - const { clearingOrder: price } = await calculateClearingPrice( + await placeOrdersForChannelAuction( channelAuction, + sellOrders, auctionId, - ); - const callPromise = channelAuction.settleAuction(auctionId); - // auctioneer reward check: - await expect(() => callPromise).to.changeTokenBalances( - auctioningToken, - [user_1], - [0], - ); - await expect(callPromise) - .to.emit(biddingToken, "Transfer") - .withArgs(channelAuction.address, user_1.address, price.sellAmount); - }); - it("checks the claimed amounts for a partially matched initialAuctionOrder and buyOrder", async () => { - const initialAuctionOrder = { - sellAmount: ethers.utils.parseEther("1"), - buyAmount: ethers.utils.parseEther("1"), - userId: BigNumber.from(1), - }; - const sellOrders = [ - { - sellAmount: ethers.utils.parseEther("1").div(2).add(1), - buyAmount: ethers.utils.parseEther("1").div(2), - userId: BigNumber.from(1), - }, - ]; - const { - auctioningToken, - biddingToken, - } = await createTokensAndMintAndApprove( - channelAuction, - [user_1, user_2], - hre, - ); - - const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( - channelAuction, - { - auctioningToken, - biddingToken, - auctionedSellAmount: initialAuctionOrder.sellAmount, - minBuyAmount: initialAuctionOrder.buyAmount, - }, - ); - await placeOrders(channelAuction, sellOrders, auctionId, hre); - - await closeAuction(channelAuction, auctionId); - const callPromise = channelAuction.settleAuction(auctionId); - // auctioneer reward check: - await expect(callPromise) - .to.emit(auctioningToken, "Transfer") - .withArgs( - channelAuction.address, - user_1.address, - initialAuctionOrder.sellAmount.sub(sellOrders[0].sellAmount), - ); - await expect(callPromise) - .to.emit(biddingToken, "Transfer") - .withArgs( - channelAuction.address, - user_1.address, - sellOrders[0].sellAmount, - ); - }); - }); - describe("claimFromParticipantOrder", async () => { - it("checks that participant receives all their biddingTokens back if minFundingThreshold was not met", async () => { - const initialAuctionOrder = { - sellAmount: ethers.utils.parseEther("10"), - buyAmount: ethers.utils.parseEther("10"), - userId: BigNumber.from(1), - }; - const sellOrders = [ - { - sellAmount: ethers.utils.parseEther("1"), - buyAmount: ethers.utils.parseEther("1").div(2), - userId: BigNumber.from(2), - }, - { - sellAmount: ethers.utils.parseEther("1"), - buyAmount: ethers.utils.parseEther("1").div(4), - userId: BigNumber.from(2), - }, - ]; - const { - auctioningToken, - biddingToken, - } = await createTokensAndMintAndApprove( - channelAuction, - [user_1, user_2], hre, ); - const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( - channelAuction, - { - auctioningToken, - biddingToken, - auctionedSellAmount: initialAuctionOrder.sellAmount, - minBuyAmount: initialAuctionOrder.buyAmount, - minFundingThreshold: ethers.utils.parseEther("5"), - }, - ); - await placeOrders(channelAuction, sellOrders, auctionId, hre); - - await closeAuction(channelAuction, auctionId); + await closeChannelAuction(channelAuction, auctionId); await channelAuction.settleAuction(auctionId); + const auctionData = await channelAuction.auctionData(auctionId); + expect(auctionData.clearingPriceOrder).to.eql(encodeOrder(sellOrders[1])); + expect(auctionData.volumeClearingPriceOrder).to.equal( + sellOrders[1].sellAmount, + ); await expect(() => - channelAuction.claimFromParticipantOrder( - auctionId, - sellOrders.map((order) => encodeOrder(order)), - ), + channelAuction.claimFromParticipantOrder(auctionId, [ + encodeOrder(sellOrders[0]), + ]), ).to.changeTokenBalances( - biddingToken, - [user_2], - [sellOrders[0].sellAmount.add(sellOrders[1].sellAmount)], - ); - }); - it("checks that claiming only works after the finishing of the auction", async () => { - const initialAuctionOrder = { - sellAmount: ethers.utils.parseEther("1"), - buyAmount: ethers.utils.parseEther("1"), - userId: BigNumber.from(1), - }; - const sellOrders = [ - { - sellAmount: ethers.utils.parseEther("1").add(1), - buyAmount: ethers.utils.parseEther("1"), - userId: BigNumber.from(1), - }, - ]; - const { - auctioningToken, - biddingToken, - } = await createTokensAndMintAndApprove( - channelAuction, - [user_1, user_2], - hre, - ); - - const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( - channelAuction, - { - auctioningToken, - biddingToken, - auctionedSellAmount: initialAuctionOrder.sellAmount, - minBuyAmount: initialAuctionOrder.buyAmount, - }, - ); - await placeOrders(channelAuction, sellOrders, auctionId, hre); - - await expect( - channelAuction.claimFromParticipantOrder( - auctionId, - sellOrders.map((order) => encodeOrder(order)), - ), - ).to.be.revertedWith("Auction not yet finished"); - await closeAuction(channelAuction, auctionId); - await expect( - channelAuction.claimFromParticipantOrder( - auctionId, - sellOrders.map((order) => encodeOrder(order)), - ), - ).to.be.revertedWith("Auction not yet finished"); - }); - it("checks the claimed amounts for a partially matched buyOrder", async () => { - const initialAuctionOrder = { - sellAmount: ethers.utils.parseEther("1"), - buyAmount: ethers.utils.parseEther("1"), - userId: BigNumber.from(1), - }; - const sellOrders = [ - { - sellAmount: ethers.utils.parseEther("1").div(2).add(1), - buyAmount: ethers.utils.parseEther("1").div(2), - userId: BigNumber.from(1), - }, - { - sellAmount: ethers.utils.parseEther("1").mul(2).div(3).add(1), - buyAmount: ethers.utils.parseEther("1").mul(2).div(3), - userId: BigNumber.from(1), - }, - ]; - const { auctioningToken, - biddingToken, - } = await createTokensAndMintAndApprove( - channelAuction, - [user_1, user_2], - hre, - ); - - const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( - channelAuction, - { - auctioningToken, - biddingToken, - auctionedSellAmount: initialAuctionOrder.sellAmount, - minBuyAmount: initialAuctionOrder.buyAmount, - }, - ); - await placeOrders(channelAuction, sellOrders, auctionId, hre); - - await closeAuction(channelAuction, auctionId); - const { clearingOrder: price } = await calculateClearingPrice( - channelAuction, - auctionId, - ); - await channelAuction.settleAuction(auctionId); - - const receivedAmounts = toReceivedFunds( - await channelAuction.callStatic.claimFromParticipantOrder(auctionId, [ - encodeOrder(sellOrders[1]), - ]), - ); - const settledBuyAmount = sellOrders[1].sellAmount - .mul(price.buyAmount) - .div(price.sellAmount) - .sub( - sellOrders[0].sellAmount - .add(sellOrders[1].sellAmount) - .mul(price.buyAmount) - .div(price.sellAmount) - .sub(initialAuctionOrder.sellAmount), - ) - .sub(1); - expect(receivedAmounts.auctioningTokenAmount).to.equal(settledBuyAmount); - expect(receivedAmounts.biddingTokenAmount).to.equal( - sellOrders[1].sellAmount - .sub(settledBuyAmount.mul(price.sellAmount).div(price.buyAmount)) - .sub(1), + [user_2], + [sellOrders[0].sellAmount], + ); + await expect(() => + channelAuction.claimFromParticipantOrder(auctionId, [ + encodeOrder(sellOrders[1]), + ]), + ).to.changeTokenBalances( + auctioningToken, + [user_3], + [sellOrders[1].sellAmount], + ); + await expect(() => + channelAuction.claimFromParticipantOrder(auctionId, [ + encodeOrder(sellOrders[2]), + ]), + ).to.changeTokenBalances( + biddingToken, + [user_3], + [sellOrders[2].sellAmount], ); }); - it("checks the claimed amounts for a fully not-matched buyOrder", async () => { + it("checks case 10: it shows an example why userId should always be given: 2 orders with the same price", async () => { const initialAuctionOrder = { sellAmount: ethers.utils.parseEther("1"), - buyAmount: ethers.utils.parseEther("1"), + buyAmount: ethers.utils.parseEther("0.5"), userId: BigNumber.from(1), }; const sellOrders = [ { - sellAmount: ethers.utils.parseEther("1").div(2).add(1), - buyAmount: ethers.utils.parseEther("1").div(2), - userId: BigNumber.from(1), + sellAmount: ethers.utils.parseEther("0.5"), + buyAmount: ethers.utils.parseEther("0.5"), + userId: BigNumber.from(2), }, { - sellAmount: ethers.utils.parseEther("1").mul(2).div(3).add(1), - buyAmount: ethers.utils.parseEther("1").mul(2).div(3), - userId: BigNumber.from(1), + sellAmount: ethers.utils.parseEther("0.5"), + buyAmount: ethers.utils.parseEther("0.5"), + userId: BigNumber.from(3), }, { - sellAmount: ethers.utils.parseEther("1").mul(2).div(3).add(1), - buyAmount: ethers.utils.parseEther("1").mul(2).div(3), - userId: BigNumber.from(2), + sellAmount: ethers.utils.parseEther("0.5"), + buyAmount: ethers.utils.parseEther("0.4"), + userId: BigNumber.from(3), }, ]; const { @@ -2255,33 +1374,64 @@ describe("ChannelAuction", async () => { biddingToken, } = await createTokensAndMintAndApprove( channelAuction, - [user_1, user_2], + [user_1, user_2, user_3], hre, ); - const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + const auctionId: BigNumber = await createChannelAuctionWithDefaultsAndReturnId( channelAuction, { auctioningToken, biddingToken, - auctionedSellAmount: initialAuctionOrder.sellAmount, - minBuyAmount: initialAuctionOrder.buyAmount, + _auctionedSellAmount: initialAuctionOrder.sellAmount, + _auctioneerBuyAmountMinimum: initialAuctionOrder.buyAmount, }, ); - await placeOrders(channelAuction, sellOrders, auctionId, hre); - await closeAuction(channelAuction, auctionId); + await placeOrdersForChannelAuction( + channelAuction, + sellOrders, + auctionId, + hre, + ); + + await closeChannelAuction(channelAuction, auctionId); await channelAuction.settleAuction(auctionId); - const receivedAmounts = toReceivedFunds( - await channelAuction.callStatic.claimFromParticipantOrder(auctionId, [ - encodeOrder(sellOrders[2]), + const auctionData = await channelAuction.auctionData(auctionId); + expect(auctionData.clearingPriceOrder).to.equal( + encodeOrder(sellOrders[0]), + ); + expect(auctionData.volumeClearingPriceOrder).to.equal( + sellOrders[1].sellAmount, + ); + await expect(() => + channelAuction.claimFromParticipantOrder(auctionId, [ + encodeOrder(sellOrders[0]), + ]), + ).to.changeTokenBalances( + auctioningToken, + [user_2], + [sellOrders[0].sellAmount], + ); + await expect(() => + channelAuction.claimFromParticipantOrder(auctionId, [ + encodeOrder(sellOrders[1]), ]), + ).to.changeTokenBalances( + biddingToken, + [user_3], + [sellOrders[1].sellAmount], ); - expect(receivedAmounts.biddingTokenAmount).to.equal( - sellOrders[2].sellAmount, + await expect(() => + channelAuction.claimFromParticipantOrder(auctionId, [ + encodeOrder(sellOrders[2]), + ]), + ).to.changeTokenBalances( + auctioningToken, + [user_3], + [sellOrders[2].sellAmount], ); - expect(receivedAmounts.auctioningTokenAmount).to.equal("0"); }); - it("checks the claimed amounts for a fully matched buyOrder", async () => { + it("checks case 1, it verifies the price in case of 2 of 3 sellOrders eating initialAuctionOrder completely", async () => { const initialAuctionOrder = { sellAmount: ethers.utils.parseEther("1"), buyAmount: ethers.utils.parseEther("1"), @@ -2289,13 +1439,19 @@ describe("ChannelAuction", async () => { }; const sellOrders = [ { - sellAmount: ethers.utils.parseEther("1").div(2).add(1), - buyAmount: ethers.utils.parseEther("1").div(2), + sellAmount: ethers.utils.parseEther("1").mul(2), + buyAmount: ethers.utils.parseEther("1").div(5), userId: BigNumber.from(1), }, { - sellAmount: ethers.utils.parseEther("1").mul(2).div(3).add(1), - buyAmount: ethers.utils.parseEther("1").mul(2).div(3), + sellAmount: ethers.utils.parseEther("1").mul(2), + buyAmount: ethers.utils.parseEther("1"), + userId: BigNumber.from(1), + }, + + { + sellAmount: ethers.utils.parseEther("1").mul(2).add(1), + buyAmount: ethers.utils.parseEther("1").mul(2), userId: BigNumber.from(1), }, ]; @@ -2308,35 +1464,30 @@ describe("ChannelAuction", async () => { hre, ); - const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + const auctionId: BigNumber = await createChannelAuctionWithDefaultsAndReturnId( channelAuction, { auctioningToken, biddingToken, - auctionedSellAmount: initialAuctionOrder.sellAmount, - minBuyAmount: initialAuctionOrder.buyAmount, + _auctionedSellAmount: initialAuctionOrder.sellAmount, + _auctioneerBuyAmountMinimum: initialAuctionOrder.buyAmount, }, ); - await placeOrders(channelAuction, sellOrders, auctionId, hre); - - await closeAuction(channelAuction, auctionId); - const { clearingOrder: price } = await calculateClearingPrice( + await placeOrdersForChannelAuction( channelAuction, + sellOrders, auctionId, + hre, ); - await channelAuction.settleAuction(auctionId); - const receivedAmounts = toReceivedFunds( - await channelAuction.callStatic.claimFromParticipantOrder(auctionId, [ - encodeOrder(sellOrders[0]), - ]), - ); - expect(receivedAmounts.biddingTokenAmount).to.equal("0"); - expect(receivedAmounts.auctioningTokenAmount).to.equal( - sellOrders[0].sellAmount.mul(price.buyAmount).div(price.sellAmount), - ); + await closeChannelAuction(channelAuction, auctionId); + await channelAuction.settleAuction(auctionId); + const auctionData = await channelAuction.auctionData(auctionId); + expect(auctionData.clearingPriceOrder).to.eql(encodeOrder(sellOrders[1])); + expect(auctionData.volumeClearingPriceOrder).to.equal(0); + await claimFromAllOrders(channelAuction, auctionId, sellOrders); }); - it("checks that an order can not be used for claiming twice", async () => { + it("verifies the price in case of 2 of 3 sellOrders eating initialAuctionOrder completely - with precalculateSellAmountSum step", async () => { const initialAuctionOrder = { sellAmount: ethers.utils.parseEther("1"), buyAmount: ethers.utils.parseEther("1"), @@ -2344,13 +1495,19 @@ describe("ChannelAuction", async () => { }; const sellOrders = [ { - sellAmount: ethers.utils.parseEther("1").div(2).add(1), - buyAmount: ethers.utils.parseEther("1").div(2), + sellAmount: ethers.utils.parseEther("1").mul(2), + buyAmount: ethers.utils.parseEther("1").div(5), userId: BigNumber.from(1), }, { - sellAmount: ethers.utils.parseEther("1").mul(2).div(3).add(1), - buyAmount: ethers.utils.parseEther("1").mul(2).div(3), + sellAmount: ethers.utils.parseEther("1").mul(2), + buyAmount: ethers.utils.parseEther("1"), + userId: BigNumber.from(1), + }, + + { + sellAmount: ethers.utils.parseEther("1").mul(2).add(1), + buyAmount: ethers.utils.parseEther("1").mul(2), userId: BigNumber.from(1), }, ]; @@ -2363,160 +1520,59 @@ describe("ChannelAuction", async () => { hre, ); - const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + const auctionId: BigNumber = await createChannelAuctionWithDefaultsAndReturnId( channelAuction, { auctioningToken, biddingToken, - auctionedSellAmount: initialAuctionOrder.sellAmount, - minBuyAmount: initialAuctionOrder.buyAmount, + _auctionedSellAmount: initialAuctionOrder.sellAmount, + _auctioneerBuyAmountMinimum: initialAuctionOrder.buyAmount, }, ); - await placeOrders(channelAuction, sellOrders, auctionId, hre); - - await closeAuction(channelAuction, auctionId); - await channelAuction.settleAuction(auctionId); - await channelAuction.claimFromParticipantOrder(auctionId, [ - encodeOrder(sellOrders[0]), - ]), - await expect( - channelAuction.claimFromParticipantOrder(auctionId, [ - encodeOrder(sellOrders[0]), - ]), - ).to.be.revertedWith("order is no longer claimable"); - }); - }); - it("checks that orders from different users can not be claimed at once", async () => { - const initialAuctionOrder = { - sellAmount: ethers.utils.parseEther("1"), - buyAmount: ethers.utils.parseEther("1"), - userId: BigNumber.from(1), - }; - const sellOrders = [ - { - sellAmount: ethers.utils.parseEther("1").div(2).add(1), - buyAmount: ethers.utils.parseEther("1").div(2), - userId: BigNumber.from(1), - }, - { - sellAmount: ethers.utils.parseEther("1").mul(2).div(3).add(1), - buyAmount: ethers.utils.parseEther("1").mul(2).div(3), - userId: BigNumber.from(2), - }, - ]; - const { - auctioningToken, - biddingToken, - } = await createTokensAndMintAndApprove( - channelAuction, - [user_1, user_2], - hre, - ); - - const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( - channelAuction, - { - auctioningToken, - biddingToken, - auctionedSellAmount: initialAuctionOrder.sellAmount, - minBuyAmount: initialAuctionOrder.buyAmount, - }, - ); - await placeOrders(channelAuction, sellOrders, auctionId, hre); - - await closeAuction(channelAuction, auctionId); - await channelAuction.settleAuction(auctionId); - await expect( - channelAuction.claimFromParticipantOrder(auctionId, [ - encodeOrder(sellOrders[0]), - encodeOrder(sellOrders[1]), - ]), - ).to.be.revertedWith("only allowed to claim for same user"); - }); - it("checks the claimed amounts are summed up correctly for two orders", async () => { - const initialAuctionOrder = { - sellAmount: ethers.utils.parseEther("1"), - buyAmount: ethers.utils.parseEther("1"), - userId: BigNumber.from(1), - }; - const sellOrders = [ - { - sellAmount: ethers.utils.parseEther("1").div(2).add(1), - buyAmount: ethers.utils.parseEther("1").div(2), - userId: BigNumber.from(1), - }, - { - sellAmount: ethers.utils.parseEther("1").mul(2).div(3).add(1), - buyAmount: ethers.utils.parseEther("1").mul(2).div(3), - userId: BigNumber.from(1), - }, - ]; - const { - auctioningToken, - biddingToken, - } = await createTokensAndMintAndApprove( - channelAuction, - [user_1, user_2], - hre, - ); - - const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( - channelAuction, - { - auctioningToken, - biddingToken, - auctionedSellAmount: initialAuctionOrder.sellAmount, - minBuyAmount: initialAuctionOrder.buyAmount, - }, - ); - await placeOrders(channelAuction, sellOrders, auctionId, hre); - - await closeAuction(channelAuction, auctionId); - const { clearingOrder: price } = await calculateClearingPrice( - channelAuction, - auctionId, - ); - await channelAuction.settleAuction(auctionId); + await placeOrdersForChannelAuction( + channelAuction, + sellOrders, + auctionId, + hre, + ); - const receivedAmounts = toReceivedFunds( - await channelAuction.callStatic.claimFromParticipantOrder(auctionId, [ - encodeOrder(sellOrders[0]), - encodeOrder(sellOrders[1]), - ]), - ); - expect(receivedAmounts.biddingTokenAmount).to.equal( - sellOrders[0].sellAmount - .add(sellOrders[1].sellAmount) - .sub( - initialAuctionOrder.sellAmount - .mul(price.sellAmount) - .div(price.buyAmount), - ), - ); - expect(receivedAmounts.auctioningTokenAmount).to.equal( - initialAuctionOrder.sellAmount.sub(1), - ); - }); - describe("settleAuctionAtomically", async () => { - it("can not settle atomically, if it is not allowed", async () => { + await closeChannelAuction(channelAuction, auctionId); + // this is the additional step + await channelAuction.precalculateSellAmountSum(auctionId, 1); + + await channelAuction.settleAuction(auctionId); + const auctionData = await channelAuction.auctionData(auctionId); + expect(auctionData.clearingPriceOrder).to.eql(encodeOrder(sellOrders[1])); + expect(auctionData.volumeClearingPriceOrder).to.equal(0); + }); + it("verifies the price in case of 2 of 4 sellOrders eating initialAuctionOrder completely - with precalculateSellAmountSum step and one more step within settleAuction", async () => { const initialAuctionOrder = { sellAmount: ethers.utils.parseEther("1"), - buyAmount: ethers.utils.parseEther("0.5"), + buyAmount: ethers.utils.parseEther("1"), userId: BigNumber.from(1), }; const sellOrders = [ { - sellAmount: ethers.utils.parseEther("0.5"), - buyAmount: ethers.utils.parseEther("0.5"), + sellAmount: ethers.utils.parseEther("1"), + buyAmount: ethers.utils.parseEther("1").div(5), userId: BigNumber.from(1), }, - ]; - const atomicSellOrders = [ { - sellAmount: ethers.utils.parseEther("0.499"), - buyAmount: ethers.utils.parseEther("0.4999"), + sellAmount: ethers.utils.parseEther("1"), + buyAmount: ethers.utils.parseEther("1").div(5), userId: BigNumber.from(2), }, + { + sellAmount: ethers.utils.parseEther("1").mul(2), + buyAmount: ethers.utils.parseEther("1"), + userId: BigNumber.from(1), + }, + + { + sellAmount: ethers.utils.parseEther("1").mul(2).add(1), + buyAmount: ethers.utils.parseEther("1").mul(2), + userId: BigNumber.from(1), + }, ]; const { auctioningToken, @@ -2527,51 +1583,60 @@ describe("ChannelAuction", async () => { hre, ); - const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + const auctionId: BigNumber = await createChannelAuctionWithDefaultsAndReturnId( channelAuction, { auctioningToken, biddingToken, - auctionedSellAmount: initialAuctionOrder.sellAmount, - minBuyAmount: initialAuctionOrder.buyAmount, - isAtomicClosureAllowed: false, + _auctionedSellAmount: initialAuctionOrder.sellAmount, + _auctioneerBuyAmountMinimum: initialAuctionOrder.buyAmount, }, ); - await placeOrders(channelAuction, sellOrders, auctionId, hre); + await placeOrdersForChannelAuction( + channelAuction, + sellOrders, + auctionId, + hre, + ); - await closeAuction(channelAuction, auctionId); - await expect( - channelAuction.settleAuctionAtomically( - auctionId, - [atomicSellOrders[0].sellAmount], - [atomicSellOrders[0].buyAmount], - [queueStartElement], - "0x", - ), - ).to.be.revertedWith("not allowed to settle auction atomically"); + await closeChannelAuction(channelAuction, auctionId); + // this is the additional step + await channelAuction.precalculateSellAmountSum(auctionId, 1); + + const auctionData = await channelAuction.auctionData(auctionId); + expect(auctionData.interimSumBidAmount).to.equal( + sellOrders[0].sellAmount, + ); + expect(auctionData.interimOrder).to.equal(encodeOrder(sellOrders[0])); + await channelAuction.settleAuction(auctionId); + const auctionData2 = await channelAuction.auctionData(auctionId); + expect(auctionData2.clearingPriceOrder).to.eql( + encodeOrder(sellOrders[2]), + ); + expect(auctionData2.volumeClearingPriceOrder).to.equal(0); + await claimFromAllOrders(channelAuction, auctionId, sellOrders); }); - it("reverts, if more than one order is intended to be settled atomically", async () => { + it("verifies the price in case of clearing order is decided by userId", async () => { const initialAuctionOrder = { sellAmount: ethers.utils.parseEther("1"), - buyAmount: ethers.utils.parseEther("0.5"), + buyAmount: ethers.utils.parseEther("1"), userId: BigNumber.from(1), }; const sellOrders = [ { - sellAmount: ethers.utils.parseEther("0.5"), - buyAmount: ethers.utils.parseEther("0.5"), + sellAmount: ethers.utils.parseEther("1").mul(2), + buyAmount: ethers.utils.parseEther("1").div(5), userId: BigNumber.from(1), }, - ]; - const atomicSellOrders = [ { - sellAmount: ethers.utils.parseEther("0.49"), - buyAmount: ethers.utils.parseEther("0.49"), - userId: BigNumber.from(2), + sellAmount: ethers.utils.parseEther("1").mul(2), + buyAmount: ethers.utils.parseEther("1"), + userId: BigNumber.from(1), }, + { - sellAmount: ethers.utils.parseEther("0.4"), - buyAmount: ethers.utils.parseEther("0.4"), + sellAmount: ethers.utils.parseEther("1").mul(2), + buyAmount: ethers.utils.parseEther("1"), userId: BigNumber.from(2), }, ]; @@ -2584,47 +1649,57 @@ describe("ChannelAuction", async () => { hre, ); - const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + const auctionId: BigNumber = await createChannelAuctionWithDefaultsAndReturnId( channelAuction, { auctioningToken, biddingToken, - auctionedSellAmount: initialAuctionOrder.sellAmount, - minBuyAmount: initialAuctionOrder.buyAmount, - isAtomicClosureAllowed: true, + _auctionedSellAmount: initialAuctionOrder.sellAmount, + _auctioneerBuyAmountMinimum: initialAuctionOrder.buyAmount, }, ); - await placeOrders(channelAuction, sellOrders, auctionId, hre); + await placeOrdersForChannelAuction( + channelAuction, + sellOrders, + auctionId, + hre, + ); - await closeAuction(channelAuction, auctionId); - await expect( - channelAuction.settleAuctionAtomically( - auctionId, - [atomicSellOrders[0].sellAmount, atomicSellOrders[1].sellAmount], - [atomicSellOrders[0].buyAmount, atomicSellOrders[1].buyAmount], - [queueStartElement, queueStartElement], - "0x", - ), - ).to.be.revertedWith("Only one order can be placed atomically"); + await closeChannelAuction(channelAuction, auctionId); + await channelAuction.settleAuction(auctionId); + const auctionData = await channelAuction.auctionData(auctionId); + expect(auctionData.clearingPriceOrder).to.be.equal( + encodeOrder(sellOrders[1]), + ); + expect(auctionData.volumeClearingPriceOrder).to.equal(0); + await claimFromAllOrders(channelAuction, auctionId, sellOrders); }); - it("can not settle atomically, if precalculateSellAmountSum was used", async () => { + it("simple version of e2e gas test", async () => { const initialAuctionOrder = { sellAmount: ethers.utils.parseEther("1"), - buyAmount: ethers.utils.parseEther("0.5"), + buyAmount: ethers.utils.parseEther("1"), userId: BigNumber.from(1), }; const sellOrders = [ { - sellAmount: ethers.utils.parseEther("0.5"), - buyAmount: ethers.utils.parseEther("0.5"), + sellAmount: ethers.utils.parseEther("1").div(4), + buyAmount: ethers.utils.parseEther("1").div(8), userId: BigNumber.from(1), }, - ]; - const atomicSellOrders = [ { - sellAmount: ethers.utils.parseEther("0.499"), - buyAmount: ethers.utils.parseEther("0.4999"), - userId: BigNumber.from(2), + sellAmount: ethers.utils.parseEther("1").div(4), + buyAmount: ethers.utils.parseEther("1").div(12), + userId: BigNumber.from(1), + }, + { + sellAmount: ethers.utils.parseEther("1").div(4), + buyAmount: ethers.utils.parseEther("1").div(16), + userId: BigNumber.from(1), + }, + { + sellAmount: ethers.utils.parseEther("1").div(4), + buyAmount: ethers.utils.parseEther("1").div(20), + userId: BigNumber.from(1), }, ]; const { @@ -2636,48 +1711,50 @@ describe("ChannelAuction", async () => { hre, ); - const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + const auctionId: BigNumber = await createChannelAuctionWithDefaultsAndReturnId( channelAuction, { auctioningToken, biddingToken, - auctionedSellAmount: initialAuctionOrder.sellAmount, - minBuyAmount: initialAuctionOrder.buyAmount, - isAtomicClosureAllowed: true, + _auctionedSellAmount: initialAuctionOrder.sellAmount, + _auctioneerBuyAmountMinimum: initialAuctionOrder.buyAmount, }, ); - await placeOrders(channelAuction, sellOrders, auctionId, hre); + await placeOrdersForChannelAuction( + channelAuction, + sellOrders, + auctionId, + hre, + ); - await closeAuction(channelAuction, auctionId); - await channelAuction.precalculateSellAmountSum(auctionId, 1); + await closeChannelAuction(channelAuction, auctionId); + const { clearingOrder: price } = await calculateClearingPrice( + channelAuction, + auctionId, + ); - await expect( - channelAuction.settleAuctionAtomically( - auctionId, - [atomicSellOrders[0].sellAmount], - [atomicSellOrders[0].buyAmount], - [queueStartElement], - "0x", - ), - ).to.be.revertedWith("precalculateSellAmountSum is already too advanced"); + await channelAuction.settleAuction(auctionId); + expect(price.toString()).to.eql( + getClearingPriceFromInitialOrder(initialAuctionOrder).toString(), + ); + const auctionData = await channelAuction.auctionData(auctionId); + expect(auctionData.clearingPriceOrder).to.equal( + encodeOrder(getClearingPriceFromInitialOrder(initialAuctionOrder)), + ); + await claimFromAllOrders(channelAuction, auctionId, sellOrders); }); - it("allows an atomic settlement, if the precalculation are not yet beyond the price of the inserted order", async () => { + }); + describe("claimFromAuctioneerOrder", async () => { + it("checks the claimed amounts for a fully matched initialAuctionOrder and buyOrder", async () => { const initialAuctionOrder = { sellAmount: ethers.utils.parseEther("1"), - buyAmount: ethers.utils.parseEther("0.5"), + buyAmount: ethers.utils.parseEther("1"), userId: BigNumber.from(1), }; const sellOrders = [ { - sellAmount: ethers.utils.parseEther("0.5"), - buyAmount: ethers.utils.parseEther("0.5"), - userId: BigNumber.from(1), - }, - ]; - const atomicSellOrders = [ - { - sellAmount: ethers.utils.parseEther("0.5"), - buyAmount: ethers.utils.parseEther("0.55"), + sellAmount: ethers.utils.parseEther("1").add(1), + buyAmount: ethers.utils.parseEther("1"), userId: BigNumber.from(1), }, ]; @@ -2690,51 +1767,51 @@ describe("ChannelAuction", async () => { hre, ); - const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + const auctionId: BigNumber = await createChannelAuctionWithDefaultsAndReturnId( channelAuction, { auctioningToken, biddingToken, - auctionedSellAmount: initialAuctionOrder.sellAmount, - minBuyAmount: initialAuctionOrder.buyAmount, - isAtomicClosureAllowed: true, + _auctionedSellAmount: initialAuctionOrder.sellAmount, + _auctioneerBuyAmountMinimum: initialAuctionOrder.buyAmount, }, ); - await placeOrders(channelAuction, sellOrders, auctionId, hre); - - await closeAuction(channelAuction, auctionId); - await channelAuction.precalculateSellAmountSum(auctionId, 1); + await placeOrdersForChannelAuction( + channelAuction, + sellOrders, + auctionId, + hre, + ); - await channelAuction.settleAuctionAtomically( + await closeChannelAuction(channelAuction, auctionId); + const { clearingOrder: price } = await calculateClearingPrice( + channelAuction, auctionId, - [atomicSellOrders[0].buyAmount], - [atomicSellOrders[0].sellAmount], - [queueStartElement], - "0x", ); - await claimFromAllOrders(channelAuction, auctionId, sellOrders); - await claimFromAllOrders(channelAuction, auctionId, atomicSellOrders); + const callPromise = channelAuction.settleAuction(auctionId); + // auctioneer reward check: + await expect(() => callPromise).to.changeTokenBalances( + auctioningToken, + [user_1], + [0], + ); + await expect(callPromise) + .to.emit(biddingToken, "Transfer") + .withArgs(channelAuction.address, user_1.address, price.sellAmount); }); - it("can settle atomically, if it is allowed", async () => { + it("checks the claimed amounts for a partially matched initialAuctionOrder and buyOrder", async () => { const initialAuctionOrder = { sellAmount: ethers.utils.parseEther("1"), - buyAmount: ethers.utils.parseEther("0.5"), + buyAmount: ethers.utils.parseEther("1"), userId: BigNumber.from(1), }; const sellOrders = [ { - sellAmount: ethers.utils.parseEther("0.5"), - buyAmount: ethers.utils.parseEther("0.5"), + sellAmount: ethers.utils.parseEther("1").div(2).add(1), + buyAmount: ethers.utils.parseEther("1").div(2), userId: BigNumber.from(1), }, ]; - const atomicSellOrders = [ - { - sellAmount: ethers.utils.parseEther("0.4999"), - buyAmount: ethers.utils.parseEther("0.4999"), - userId: BigNumber.from(2), - }, - ]; const { auctioningToken, biddingToken, @@ -2744,59 +1821,55 @@ describe("ChannelAuction", async () => { hre, ); - const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + const auctionId: BigNumber = await createChannelAuctionWithDefaultsAndReturnId( channelAuction, { auctioningToken, biddingToken, - auctionedSellAmount: initialAuctionOrder.sellAmount, - minBuyAmount: initialAuctionOrder.buyAmount, - isAtomicClosureAllowed: true, + _auctionedSellAmount: initialAuctionOrder.sellAmount, + _auctioneerBuyAmountMinimum: initialAuctionOrder.buyAmount, }, ); - await placeOrders(channelAuction, sellOrders, auctionId, hre); + await placeOrdersForChannelAuction( + channelAuction, + sellOrders, + auctionId, + hre, + ); - await closeAuction(channelAuction, auctionId); - await channelAuction - .connect(user_2) - .settleAuctionAtomically( - auctionId, - [atomicSellOrders[0].sellAmount], - [atomicSellOrders[0].buyAmount], - [queueStartElement], - "0x", + await closeChannelAuction(channelAuction, auctionId); + const callPromise = channelAuction.settleAuction(auctionId); + // auctioneer reward check: + await expect(callPromise) + .to.emit(auctioningToken, "Transfer") + .withArgs( + channelAuction.address, + user_1.address, + initialAuctionOrder.sellAmount.sub(sellOrders[0].sellAmount), + ); + await expect(callPromise) + .to.emit(biddingToken, "Transfer") + .withArgs( + channelAuction.address, + user_1.address, + sellOrders[0].sellAmount, ); - const auctionData = await channelAuction.auctionData(auctionId); - expect(auctionData.clearingPriceOrder).to.equal( - encodeOrder({ - sellAmount: sellOrders[0].sellAmount.add( - atomicSellOrders[0].sellAmount, - ), - buyAmount: initialAuctionOrder.sellAmount, - userId: BigNumber.from(0), - }), - ); }); - it("can not settle auctions atomically, before auction finished", async () => { + }); + describe("claimFromParticipantOrder", async () => { + it("checks that claiming only works after the finishing of the auction", async () => { const initialAuctionOrder = { sellAmount: ethers.utils.parseEther("1"), - buyAmount: ethers.utils.parseEther("0.5"), + buyAmount: ethers.utils.parseEther("1"), userId: BigNumber.from(1), }; const sellOrders = [ { - sellAmount: ethers.utils.parseEther("0.5"), - buyAmount: ethers.utils.parseEther("0.5"), + sellAmount: ethers.utils.parseEther("1").add(1), + buyAmount: ethers.utils.parseEther("1"), userId: BigNumber.from(1), }, ]; - const atomicSellOrders = [ - { - sellAmount: ethers.utils.parseEther("0.4999"), - buyAmount: ethers.utils.parseEther("0.4999"), - userId: BigNumber.from(2), - }, - ]; const { auctioningToken, biddingToken, @@ -2806,41 +1879,37 @@ describe("ChannelAuction", async () => { hre, ); - const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + const auctionId: BigNumber = await createChannelAuctionWithDefaultsAndReturnId( channelAuction, { auctioningToken, biddingToken, - auctionedSellAmount: initialAuctionOrder.sellAmount, - minBuyAmount: initialAuctionOrder.buyAmount, - isAtomicClosureAllowed: true, + _auctionedSellAmount: initialAuctionOrder.sellAmount, + _auctioneerBuyAmountMinimum: initialAuctionOrder.buyAmount, }, ); - await placeOrders(channelAuction, sellOrders, auctionId, hre); + await placeOrdersForChannelAuction( + channelAuction, + sellOrders, + auctionId, + hre, + ); await expect( - channelAuction - .connect(user_2) - .settleAuctionAtomically( - auctionId, - [atomicSellOrders[0].sellAmount], - [atomicSellOrders[0].buyAmount], - [queueStartElement], - "0x", - ), - ).to.be.revertedWith("Auction not in solution submission phase"); - }); - }); - describe("registerUser", async () => { - it("registers a user only once", async () => { - await channelAuction.registerUser(user_1.address); + channelAuction.claimFromParticipantOrder( + auctionId, + sellOrders.map((order) => encodeOrder(order)), + ), + ).to.be.revertedWith("Auction not yet finished"); + await closeChannelAuction(channelAuction, auctionId); await expect( - channelAuction.registerUser(user_1.address), - ).to.be.revertedWith("User already registered"); + channelAuction.claimFromParticipantOrder( + auctionId, + sellOrders.map((order) => encodeOrder(order)), + ), + ).to.be.revertedWith("Auction not yet finished"); }); - }); - describe("cancelOrder", async () => { - it("cancels an order", async () => { + it("checks the claimed amounts for a partially matched buyOrder", async () => { const initialAuctionOrder = { sellAmount: ethers.utils.parseEther("1"), buyAmount: ethers.utils.parseEther("1"), @@ -2852,6 +1921,11 @@ describe("ChannelAuction", async () => { buyAmount: ethers.utils.parseEther("1").div(2), userId: BigNumber.from(1), }, + { + sellAmount: ethers.utils.parseEther("1").mul(2).div(3).add(1), + buyAmount: ethers.utils.parseEther("1").mul(2).div(3), + userId: BigNumber.from(1), + }, ]; const { auctioningToken, @@ -2862,30 +1936,53 @@ describe("ChannelAuction", async () => { hre, ); - const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + const auctionId: BigNumber = await createChannelAuctionWithDefaultsAndReturnId( + channelAuction, + { + auctioningToken, + biddingToken, + _auctionedSellAmount: initialAuctionOrder.sellAmount, + _auctioneerBuyAmountMinimum: initialAuctionOrder.buyAmount, + }, + ); + await placeOrdersForChannelAuction( + channelAuction, + sellOrders, + auctionId, + hre, + ); + + await closeChannelAuction(channelAuction, auctionId); + const { clearingOrder: price } = await calculateClearingPrice( channelAuction, - { - auctioningToken, - biddingToken, - auctionedSellAmount: initialAuctionOrder.sellAmount, - minBuyAmount: initialAuctionOrder.buyAmount, - }, + auctionId, ); - await placeOrders(channelAuction, sellOrders, auctionId, hre); + await channelAuction.settleAuction(auctionId); - await expect( - channelAuction.cancelSellOrders(auctionId, [ - encodeOrder(sellOrders[0]), + const receivedAmounts = toReceivedFunds( + await channelAuction.callStatic.claimFromParticipantOrder(auctionId, [ + encodeOrder(sellOrders[1]), ]), - ) - .to.emit(biddingToken, "Transfer") - .withArgs( - channelAuction.address, - user_1.address, - sellOrders[0].sellAmount, - ); + ); + const settledBuyAmount = sellOrders[1].sellAmount + .mul(price.buyAmount) + .div(price.sellAmount) + .sub( + sellOrders[0].sellAmount + .add(sellOrders[1].sellAmount) + .mul(price.buyAmount) + .div(price.sellAmount) + .sub(initialAuctionOrder.sellAmount), + ) + .sub(1); + expect(receivedAmounts.auctioningTokenAmount).to.equal(settledBuyAmount); + expect(receivedAmounts.biddingTokenAmount).to.equal( + sellOrders[1].sellAmount + .sub(settledBuyAmount.mul(price.sellAmount).div(price.buyAmount)) + .sub(1), + ); }); - it("does not allow to cancel a order, if it is too late", async () => { + it("checks the claimed amounts for a fully not-matched buyOrder", async () => { const initialAuctionOrder = { sellAmount: ethers.utils.parseEther("1"), buyAmount: ethers.utils.parseEther("1"), @@ -2897,6 +1994,16 @@ describe("ChannelAuction", async () => { buyAmount: ethers.utils.parseEther("1").div(2), userId: BigNumber.from(1), }, + { + sellAmount: ethers.utils.parseEther("1").mul(2).div(3).add(1), + buyAmount: ethers.utils.parseEther("1").mul(2).div(3), + userId: BigNumber.from(1), + }, + { + sellAmount: ethers.utils.parseEther("1").mul(2).div(3).add(1), + buyAmount: ethers.utils.parseEther("1").mul(2).div(3), + userId: BigNumber.from(2), + }, ]; const { auctioningToken, @@ -2907,38 +2014,34 @@ describe("ChannelAuction", async () => { hre, ); - const now = (await ethers.provider.getBlock("latest")).timestamp; - const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + const auctionId: BigNumber = await createChannelAuctionWithDefaultsAndReturnId( channelAuction, { auctioningToken, biddingToken, - auctionedSellAmount: initialAuctionOrder.sellAmount, - minBuyAmount: initialAuctionOrder.buyAmount, - orderCancellationEndDate: now + 60 * 60, - auctionEndDate: now + 60 * 60 * 60, + _auctionedSellAmount: initialAuctionOrder.sellAmount, + _auctioneerBuyAmountMinimum: initialAuctionOrder.buyAmount, }, ); - await placeOrders(channelAuction, sellOrders, auctionId, hre); - - await increaseTime(3601); - await expect( - channelAuction.cancelSellOrders(auctionId, [ - encodeOrder(sellOrders[0]), - ]), - ).to.be.revertedWith( - "revert no longer in order placement and cancelation phase", + await placeOrdersForChannelAuction( + channelAuction, + sellOrders, + auctionId, + hre, ); - await closeAuction(channelAuction, auctionId); - await expect( - channelAuction.cancelSellOrders(auctionId, [ - encodeOrder(sellOrders[0]), + await closeChannelAuction(channelAuction, auctionId); + await channelAuction.settleAuction(auctionId); + const receivedAmounts = toReceivedFunds( + await channelAuction.callStatic.claimFromParticipantOrder(auctionId, [ + encodeOrder(sellOrders[2]), ]), - ).to.be.revertedWith( - "revert no longer in order placement and cancelation phase", ); + expect(receivedAmounts.biddingTokenAmount).to.equal( + sellOrders[2].sellAmount, + ); + expect(receivedAmounts.auctioningTokenAmount).to.equal("0"); }); - it("can't cancel orders twice", async () => { + it("checks the claimed amounts for a fully matched buyOrder", async () => { const initialAuctionOrder = { sellAmount: ethers.utils.parseEther("1"), buyAmount: ethers.utils.parseEther("1"), @@ -2950,6 +2053,11 @@ describe("ChannelAuction", async () => { buyAmount: ethers.utils.parseEther("1").div(2), userId: BigNumber.from(1), }, + { + sellAmount: ethers.utils.parseEther("1").mul(2).div(3).add(1), + buyAmount: ethers.utils.parseEther("1").mul(2).div(3), + userId: BigNumber.from(1), + }, ]; const { auctioningToken, @@ -2960,29 +2068,40 @@ describe("ChannelAuction", async () => { hre, ); - const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + const auctionId: BigNumber = await createChannelAuctionWithDefaultsAndReturnId( channelAuction, { auctioningToken, biddingToken, - auctionedSellAmount: initialAuctionOrder.sellAmount, - minBuyAmount: initialAuctionOrder.buyAmount, + _auctionedSellAmount: initialAuctionOrder.sellAmount, + _auctioneerBuyAmountMinimum: initialAuctionOrder.buyAmount, }, ); - await placeOrders(channelAuction, sellOrders, auctionId, hre); + await placeOrdersForChannelAuction( + channelAuction, + sellOrders, + auctionId, + hre, + ); - // removes the order - channelAuction.cancelSellOrders(auctionId, [encodeOrder(sellOrders[0])]); - // claims 0 sellAmount tokens - await expect( - channelAuction.cancelSellOrders(auctionId, [ + await closeChannelAuction(channelAuction, auctionId); + const { clearingOrder: price } = await calculateClearingPrice( + channelAuction, + auctionId, + ); + await channelAuction.settleAuction(auctionId); + + const receivedAmounts = toReceivedFunds( + await channelAuction.callStatic.claimFromParticipantOrder(auctionId, [ encodeOrder(sellOrders[0]), ]), - ) - .to.emit(biddingToken, "Transfer") - .withArgs(channelAuction.address, user_1.address, 0); + ); + expect(receivedAmounts.biddingTokenAmount).to.equal("0"); + expect(receivedAmounts.auctioningTokenAmount).to.equal( + sellOrders[0].sellAmount.mul(price.buyAmount).div(price.sellAmount), + ); }); - it("prevents an order from canceling, if tx is not from owner", async () => { + it("checks that an order can not be used for claiming twice", async () => { const initialAuctionOrder = { sellAmount: ethers.utils.parseEther("1"), buyAmount: ethers.utils.parseEther("1"), @@ -2992,7 +2111,12 @@ describe("ChannelAuction", async () => { { sellAmount: ethers.utils.parseEther("1").div(2).add(1), buyAmount: ethers.utils.parseEther("1").div(2), - userId: BigNumber.from(2), + userId: BigNumber.from(1), + }, + { + sellAmount: ethers.utils.parseEther("1").mul(2).div(3).add(1), + buyAmount: ethers.utils.parseEther("1").mul(2).div(3), + userId: BigNumber.from(1), }, ]; const { @@ -3004,25 +2128,527 @@ describe("ChannelAuction", async () => { hre, ); - const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + const auctionId: BigNumber = await createChannelAuctionWithDefaultsAndReturnId( channelAuction, { auctioningToken, biddingToken, - auctionedSellAmount: initialAuctionOrder.sellAmount, - minBuyAmount: initialAuctionOrder.buyAmount, + _auctionedSellAmount: initialAuctionOrder.sellAmount, + _auctioneerBuyAmountMinimum: initialAuctionOrder.buyAmount, }, ); - await placeOrders(channelAuction, sellOrders, auctionId, hre); + await placeOrdersForChannelAuction( + channelAuction, + sellOrders, + auctionId, + hre, + ); - await expect( - channelAuction.cancelSellOrders(auctionId, [ - encodeOrder(sellOrders[0]), - ]), - ).to.be.revertedWith("Only the user can cancel his orders"); + await closeChannelAuction(channelAuction, auctionId); + await channelAuction.settleAuction(auctionId); + await channelAuction.claimFromParticipantOrder(auctionId, [ + encodeOrder(sellOrders[0]), + ]), + await expect( + channelAuction.claimFromParticipantOrder(auctionId, [ + encodeOrder(sellOrders[0]), + ]), + ).to.be.revertedWith("order is no longer claimable"); }); }); + it("checks that orders from different users can not be claimed at once", async () => { + const initialAuctionOrder = { + sellAmount: ethers.utils.parseEther("1"), + buyAmount: ethers.utils.parseEther("1"), + userId: BigNumber.from(1), + }; + const sellOrders = [ + { + sellAmount: ethers.utils.parseEther("1").div(2).add(1), + buyAmount: ethers.utils.parseEther("1").div(2), + userId: BigNumber.from(1), + }, + { + sellAmount: ethers.utils.parseEther("1").mul(2).div(3).add(1), + buyAmount: ethers.utils.parseEther("1").mul(2).div(3), + userId: BigNumber.from(2), + }, + ]; + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2], + hre, + ); + + const auctionId: BigNumber = await createChannelAuctionWithDefaultsAndReturnId( + channelAuction, + { + auctioningToken, + biddingToken, + _auctionedSellAmount: initialAuctionOrder.sellAmount, + _auctioneerBuyAmountMinimum: initialAuctionOrder.buyAmount, + }, + ); + await placeOrdersForChannelAuction( + channelAuction, + sellOrders, + auctionId, + hre, + ); + + await closeChannelAuction(channelAuction, auctionId); + await channelAuction.settleAuction(auctionId); + await expect( + channelAuction.claimFromParticipantOrder(auctionId, [ + encodeOrder(sellOrders[0]), + encodeOrder(sellOrders[1]), + ]), + ).to.be.revertedWith("only allowed to claim for same user"); + }); + it("checks the claimed amounts are summed up correctly for two orders", async () => { + const initialAuctionOrder = { + sellAmount: ethers.utils.parseEther("1"), + buyAmount: ethers.utils.parseEther("1"), + userId: BigNumber.from(1), + }; + const sellOrders = [ + { + sellAmount: ethers.utils.parseEther("1").div(2).add(1), + buyAmount: ethers.utils.parseEther("1").div(2), + userId: BigNumber.from(1), + }, + { + sellAmount: ethers.utils.parseEther("1").mul(2).div(3).add(1), + buyAmount: ethers.utils.parseEther("1").mul(2).div(3), + userId: BigNumber.from(1), + }, + ]; + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2], + hre, + ); + + const auctionId: BigNumber = await createChannelAuctionWithDefaultsAndReturnId( + channelAuction, + { + auctioningToken, + biddingToken, + _auctionedSellAmount: initialAuctionOrder.sellAmount, + _auctioneerBuyAmountMinimum: initialAuctionOrder.buyAmount, + }, + ); + await placeOrdersForChannelAuction( + channelAuction, + sellOrders, + auctionId, + hre, + ); + + await closeChannelAuction(channelAuction, auctionId); + const { clearingOrder: price } = await calculateClearingPrice( + channelAuction, + auctionId, + ); + await channelAuction.settleAuction(auctionId); + const receivedAmounts = toReceivedFunds( + await channelAuction.callStatic.claimFromParticipantOrder(auctionId, [ + encodeOrder(sellOrders[0]), + encodeOrder(sellOrders[1]), + ]), + ); + expect(receivedAmounts.biddingTokenAmount).to.equal( + sellOrders[0].sellAmount + .add(sellOrders[1].sellAmount) + .sub( + initialAuctionOrder.sellAmount + .mul(price.sellAmount) + .div(price.buyAmount), + ), + ); + expect(receivedAmounts.auctioningTokenAmount).to.equal( + initialAuctionOrder.sellAmount.sub(1), + ); + }); + // describe("settleAuctionAtomically", async () => { + // it("can not settle atomically, if it is not allowed", async () => { + // const initialAuctionOrder = { + // sellAmount: ethers.utils.parseEther("1"), + // buyAmount: ethers.utils.parseEther("0.5"), + // userId: BigNumber.from(1), + // }; + // const sellOrders = [ + // { + // sellAmount: ethers.utils.parseEther("0.5"), + // buyAmount: ethers.utils.parseEther("0.5"), + // userId: BigNumber.from(1), + // }, + // ]; + // const atomicSellOrders = [ + // { + // sellAmount: ethers.utils.parseEther("0.499"), + // buyAmount: ethers.utils.parseEther("0.4999"), + // userId: BigNumber.from(2), + // }, + // ]; + // const { + // auctioningToken, + // biddingToken, + // } = await createTokensAndMintAndApprove( + // channelAuction, + // [user_1, user_2], + // hre, + // ); + + // const auctionId: BigNumber = await createChannelAuctionWithDefaultsAndReturnId( + // channelAuction, + // { + // auctioningToken, + // biddingToken, + // _auctionedSellAmount: initialAuctionOrder.sellAmount, + // _auctioneerBuyAmountMinimum: initialAuctionOrder.buyAmount, + // isAtomicClosureAllowed: false, + // }, + // ); + // await placeOrdersForChannelAuction( + // channelAuction, + // sellOrders, + // auctionId, + // hre, + // ); + + // await closeChannelAuction(channelAuction, auctionId); + // await expect( + // channelAuction.settleAuctionAtomically( + // auctionId, + // [atomicSellOrders[0].sellAmount], + // [atomicSellOrders[0].buyAmount], + // [queueStartElement], + // "0x", + // ), + // ).to.be.revertedWith("not allowed to settle auction atomically"); + // }); + // it("reverts, if more than one order is intended to be settled atomically", async () => { + // const initialAuctionOrder = { + // sellAmount: ethers.utils.parseEther("1"), + // buyAmount: ethers.utils.parseEther("0.5"), + // userId: BigNumber.from(1), + // }; + // const sellOrders = [ + // { + // sellAmount: ethers.utils.parseEther("0.5"), + // buyAmount: ethers.utils.parseEther("0.5"), + // userId: BigNumber.from(1), + // }, + // ]; + // const atomicSellOrders = [ + // { + // sellAmount: ethers.utils.parseEther("0.49"), + // buyAmount: ethers.utils.parseEther("0.49"), + // userId: BigNumber.from(2), + // }, + // { + // sellAmount: ethers.utils.parseEther("0.4"), + // buyAmount: ethers.utils.parseEther("0.4"), + // userId: BigNumber.from(2), + // }, + // ]; + // const { + // auctioningToken, + // biddingToken, + // } = await createTokensAndMintAndApprove( + // channelAuction, + // [user_1, user_2], + // hre, + // ); + + // const auctionId: BigNumber = await createChannelAuctionWithDefaultsAndReturnId( + // channelAuction, + // { + // auctioningToken, + // biddingToken, + // _auctionedSellAmount: initialAuctionOrder.sellAmount, + // _auctioneerBuyAmountMinimum: initialAuctionOrder.buyAmount, + // isAtomicClosureAllowed: true, + // }, + // ); + // await placeOrdersForChannelAuction( + // channelAuction, + // sellOrders, + // auctionId, + // hre, + // ); + + // await closeChannelAuction(channelAuction, auctionId); + // await expect( + // channelAuction.settleAuctionAtomically( + // auctionId, + // [atomicSellOrders[0].sellAmount, atomicSellOrders[1].sellAmount], + // [atomicSellOrders[0].buyAmount, atomicSellOrders[1].buyAmount], + // [queueStartElement, queueStartElement], + // "0x", + // ), + // ).to.be.revertedWith("Only one order can be placed atomically"); + // }); + // it("can not settle atomically, if precalculateSellAmountSum was used", async () => { + // const initialAuctionOrder = { + // sellAmount: ethers.utils.parseEther("1"), + // buyAmount: ethers.utils.parseEther("0.5"), + // userId: BigNumber.from(1), + // }; + // const sellOrders = [ + // { + // sellAmount: ethers.utils.parseEther("0.5"), + // buyAmount: ethers.utils.parseEther("0.5"), + // userId: BigNumber.from(1), + // }, + // ]; + // const atomicSellOrders = [ + // { + // sellAmount: ethers.utils.parseEther("0.499"), + // buyAmount: ethers.utils.parseEther("0.4999"), + // userId: BigNumber.from(2), + // }, + // ]; + // const { + // auctioningToken, + // biddingToken, + // } = await createTokensAndMintAndApprove( + // channelAuction, + // [user_1, user_2], + // hre, + // ); + + // const auctionId: BigNumber = await createChannelAuctionWithDefaultsAndReturnId( + // channelAuction, + // { + // auctioningToken, + // biddingToken, + // _auctionedSellAmount: initialAuctionOrder.sellAmount, + // _auctioneerBuyAmountMinimum: initialAuctionOrder.buyAmount, + // isAtomicClosureAllowed: true, + // }, + // ); + // await placeOrdersForChannelAuction( + // channelAuction, + // sellOrders, + // auctionId, + // hre, + // ); + + // await closeChannelAuction(channelAuction, auctionId); + // await channelAuction.precalculateSellAmountSum(auctionId, 1); + + // await expect( + // channelAuction.settleAuctionAtomically( + // auctionId, + // [atomicSellOrders[0].sellAmount], + // [atomicSellOrders[0].buyAmount], + // [queueStartElement], + // "0x", + // ), + // ).to.be.revertedWith("precalculateSellAmountSum is already too advanced"); + // }); + // it("allows an atomic settlement, if the precalculation are not yet beyond the price of the inserted order", async () => { + // const initialAuctionOrder = { + // sellAmount: ethers.utils.parseEther("1"), + // buyAmount: ethers.utils.parseEther("0.5"), + // userId: BigNumber.from(1), + // }; + // const sellOrders = [ + // { + // sellAmount: ethers.utils.parseEther("0.5"), + // buyAmount: ethers.utils.parseEther("0.5"), + // userId: BigNumber.from(1), + // }, + // ]; + // const atomicSellOrders = [ + // { + // sellAmount: ethers.utils.parseEther("0.5"), + // buyAmount: ethers.utils.parseEther("0.55"), + // userId: BigNumber.from(1), + // }, + // ]; + // const { + // auctioningToken, + // biddingToken, + // } = await createTokensAndMintAndApprove( + // channelAuction, + // [user_1, user_2], + // hre, + // ); + + // const auctionId: BigNumber = await createChannelAuctionWithDefaultsAndReturnId( + // channelAuction, + // { + // auctioningToken, + // biddingToken, + // _auctionedSellAmount: initialAuctionOrder.sellAmount, + // _auctioneerBuyAmountMinimum: initialAuctionOrder.buyAmount, + // isAtomicClosureAllowed: true, + // }, + // ); + // await placeOrdersForChannelAuction( + // channelAuction, + // sellOrders, + // auctionId, + // hre, + // ); + + // await closeChannelAuction(channelAuction, auctionId); + // await channelAuction.precalculateSellAmountSum(auctionId, 1); + + // await channelAuction.settleAuctionAtomically( + // auctionId, + // [atomicSellOrders[0].buyAmount], + // [atomicSellOrders[0].sellAmount], + // [queueStartElement], + // "0x", + // ); + // await claimFromAllOrders(channelAuction, auctionId, sellOrders); + // await claimFromAllOrders(channelAuction, auctionId, atomicSellOrders); + // }); + // it("can settle atomically, if it is allowed", async () => { + // const initialAuctionOrder = { + // sellAmount: ethers.utils.parseEther("1"), + // buyAmount: ethers.utils.parseEther("0.5"), + // userId: BigNumber.from(1), + // }; + // const sellOrders = [ + // { + // sellAmount: ethers.utils.parseEther("0.5"), + // buyAmount: ethers.utils.parseEther("0.5"), + // userId: BigNumber.from(1), + // }, + // ]; + // const atomicSellOrders = [ + // { + // sellAmount: ethers.utils.parseEther("0.4999"), + // buyAmount: ethers.utils.parseEther("0.4999"), + // userId: BigNumber.from(2), + // }, + // ]; + // const { + // auctioningToken, + // biddingToken, + // } = await createTokensAndMintAndApprove( + // channelAuction, + // [user_1, user_2], + // hre, + // ); + + // const auctionId: BigNumber = await createChannelAuctionWithDefaultsAndReturnId( + // channelAuction, + // { + // auctioningToken, + // biddingToken, + // _auctionedSellAmount: initialAuctionOrder.sellAmount, + // _auctioneerBuyAmountMinimum: initialAuctionOrder.buyAmount, + // isAtomicClosureAllowed: true, + // }, + // ); + // await placeOrdersForChannelAuction( + // channelAuction, + // sellOrders, + // auctionId, + // hre, + // ); + + // await closeChannelAuction(channelAuction, auctionId); + // await channelAuction + // .connect(user_2) + // .settleAuctionAtomically( + // auctionId, + // [atomicSellOrders[0].sellAmount], + // [atomicSellOrders[0].buyAmount], + // [queueStartElement], + // "0x", + // ); + // const auctionData = await channelAuction.auctionData(auctionId); + // expect(auctionData.clearingPriceOrder).to.equal( + // encodeOrder({ + // sellAmount: sellOrders[0].sellAmount.add( + // atomicSellOrders[0].sellAmount, + // ), + // buyAmount: initialAuctionOrder.sellAmount, + // userId: BigNumber.from(0), + // }), + // ); + // }); + // it("can not settle auctions atomically, before auction finished", async () => { + // const initialAuctionOrder = { + // sellAmount: ethers.utils.parseEther("1"), + // buyAmount: ethers.utils.parseEther("0.5"), + // userId: BigNumber.from(1), + // }; + // const sellOrders = [ + // { + // sellAmount: ethers.utils.parseEther("0.5"), + // buyAmount: ethers.utils.parseEther("0.5"), + // userId: BigNumber.from(1), + // }, + // ]; + // const atomicSellOrders = [ + // { + // sellAmount: ethers.utils.parseEther("0.4999"), + // buyAmount: ethers.utils.parseEther("0.4999"), + // userId: BigNumber.from(2), + // }, + // ]; + // const { + // auctioningToken, + // biddingToken, + // } = await createTokensAndMintAndApprove( + // channelAuction, + // [user_1, user_2], + // hre, + // ); + + // const auctionId: BigNumber = await createChannelAuctionWithDefaultsAndReturnId( + // channelAuction, + // { + // auctioningToken, + // biddingToken, + // _auctionedSellAmount: initialAuctionOrder.sellAmount, + // _auctioneerBuyAmountMinimum: initialAuctionOrder.buyAmount, + // isAtomicClosureAllowed: true, + // }, + // ); + // await placeOrdersForChannelAuction( + // channelAuction, + // sellOrders, + // auctionId, + // hre, + // ); + + // await expect( + // channelAuction + // .connect(user_2) + // .settleAuctionAtomically( + // auctionId, + // [atomicSellOrders[0].sellAmount], + // [atomicSellOrders[0].buyAmount], + // [queueStartElement], + // "0x", + // ), + // ).to.be.revertedWith("Auction not in solution submission phase"); + // }); + // }); + describe("registerUser", async () => { + it("registers a user only once", async () => { + await channelAuction.registerUser(user_1.address); + await expect( + channelAuction.registerUser(user_1.address), + ).to.be.revertedWith("User already registered"); + }); + }); describe("containsOrder", async () => { it("returns true, if it contains order", async () => { const initialAuctionOrder = { @@ -3046,18 +2672,23 @@ describe("ChannelAuction", async () => { hre, ); - const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + const auctionId: BigNumber = await createChannelAuctionWithDefaultsAndReturnId( channelAuction, { auctioningToken, biddingToken, - auctionedSellAmount: initialAuctionOrder.sellAmount, - minBuyAmount: initialAuctionOrder.buyAmount, + _auctionedSellAmount: initialAuctionOrder.sellAmount, + _auctioneerBuyAmountMinimum: initialAuctionOrder.buyAmount, }, ); - await placeOrders(channelAuction, sellOrders, auctionId, hre); + await placeOrdersForChannelAuction( + channelAuction, + sellOrders, + auctionId, + hre, + ); - await closeAuction(channelAuction, auctionId); + await closeChannelAuction(channelAuction, auctionId); expect( await channelAuction.callStatic.containsOrder( auctionId, @@ -3082,16 +2713,16 @@ describe("ChannelAuction", async () => { hre, ); - const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + const auctionId: BigNumber = await createChannelAuctionWithDefaultsAndReturnId( channelAuction, { auctioningToken, biddingToken, - auctionedSellAmount: initialAuctionOrder.sellAmount, - minBuyAmount: initialAuctionOrder.buyAmount, + _auctionedSellAmount: initialAuctionOrder.sellAmount, + _auctioneerBuyAmountMinimum: initialAuctionOrder.buyAmount, }, ); - await closeAuction(channelAuction, auctionId); + await closeChannelAuction(channelAuction, auctionId); expect( await channelAuction.callStatic.getSecondsRemainingInBatch(auctionId), ).to.be.equal("0"); @@ -3131,20 +2762,25 @@ describe("ChannelAuction", async () => { .connect(user_1) .setFeeParameters(feeNumerator, feeReceiver.address); - const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + const auctionId: BigNumber = await createChannelAuctionWithDefaultsAndReturnId( channelAuction, { auctioningToken, biddingToken, - auctionedSellAmount: initialAuctionOrder.sellAmount, - minBuyAmount: initialAuctionOrder.buyAmount, + _auctionedSellAmount: initialAuctionOrder.sellAmount, + _auctioneerBuyAmountMinimum: initialAuctionOrder.buyAmount, }, ); - await placeOrders(channelAuction, sellOrders, auctionId, hre); + await placeOrdersForChannelAuction( + channelAuction, + sellOrders, + auctionId, + hre, + ); // resets the userId, as they are only given during function call. sellOrders = await getAllSellOrders(channelAuction, auctionId); - await closeAuction(channelAuction, auctionId); + await closeChannelAuction(channelAuction, auctionId); await expect(() => channelAuction.settleAuction(auctionId), ).to.changeTokenBalances( @@ -3192,23 +2828,28 @@ describe("ChannelAuction", async () => { .connect(user_1) .setFeeParameters(feeNumerator, feeReceiver.address); - const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + const auctionId: BigNumber = await createChannelAuctionWithDefaultsAndReturnId( channelAuction, { auctioningToken, biddingToken, - auctionedSellAmount: initialAuctionOrder.sellAmount, - minBuyAmount: initialAuctionOrder.buyAmount, + _auctionedSellAmount: initialAuctionOrder.sellAmount, + _auctioneerBuyAmountMinimum: initialAuctionOrder.buyAmount, }, ); - await placeOrders(channelAuction, sellOrders, auctionId, hre); + await placeOrdersForChannelAuction( + channelAuction, + sellOrders, + auctionId, + hre, + ); // resets the userId, as they are only given during function call. sellOrders = await getAllSellOrders(channelAuction, auctionId); await channelAuction .connect(user_1) .setFeeParameters(10, feeReceiver.address); - await closeAuction(channelAuction, auctionId); + await closeChannelAuction(channelAuction, auctionId); await expect(() => channelAuction.settleAuction(auctionId), ).to.changeTokenBalances( @@ -3251,20 +2892,25 @@ describe("ChannelAuction", async () => { .connect(user_1) .setFeeParameters(feeNumerator, feeReceiver.address); - const auctionId: BigNumber = await createAuctionWithDefaultsAndReturnId( + const auctionId: BigNumber = await createChannelAuctionWithDefaultsAndReturnId( channelAuction, { auctioningToken, biddingToken, - auctionedSellAmount: initialAuctionOrder.sellAmount, - minBuyAmount: initialAuctionOrder.buyAmount, + _auctionedSellAmount: initialAuctionOrder.sellAmount, + _auctioneerBuyAmountMinimum: initialAuctionOrder.buyAmount, }, ); - await placeOrders(channelAuction, sellOrders, auctionId, hre); + await placeOrdersForChannelAuction( + channelAuction, + sellOrders, + auctionId, + hre, + ); // resets the userId, as they are only given during function call. sellOrders = await getAllSellOrders(channelAuction, auctionId); - await closeAuction(channelAuction, auctionId); + await closeChannelAuction(channelAuction, auctionId); await expect(() => channelAuction.settleAuction(auctionId), ).to.changeTokenBalances( diff --git a/test/contract/defaultContractInteractions.ts b/test/contract/defaultContractInteractions.ts index 85c1bed..7eb1877 100644 --- a/test/contract/defaultContractInteractions.ts +++ b/test/contract/defaultContractInteractions.ts @@ -67,12 +67,12 @@ export async function createChannelAuctionWithDefaults( export async function createChannelAuctionWithDefaultsAndReturnId( channelAuction: Contract, - parameters: PartialAuctionInput, + parameters: PartialChannelAuctionInput, ): Promise { return sendTxAndGetReturnValue( channelAuction, - "initiateAuction(address,address,uint256,uint256,uint96,uint96,uint256,uint256,bool,address,bytes)", - ...(await createAuctionInputWithDefaults(parameters)), + "initiateAuction(address,address,uint96,uint96,uint96,uint96,uint96,uint96)", + ...(await createChannelAuctionInputWithDefaults(parameters)), ); } diff --git a/test/contract/utilities.ts b/test/contract/utilities.ts index ff51432..add9389 100644 --- a/test/contract/utilities.ts +++ b/test/contract/utilities.ts @@ -14,6 +14,26 @@ export async function closeAuction( ).toNumber(); await increaseTime(time_remaining + 1); } +export async function closeChannelAuction( + instance: Contract, + auctionId: BigNumber, +): Promise { + const time_remaining = ( + await instance.callStatic.getSecondsRemainingUntilLastPossibleClose( + auctionId, + ) + ).toNumber(); + await increaseTime(time_remaining + 1); +} + +export async function startChannelAuction( + instance: Contract, + auctionId: BigNumber, +): Promise { + const now = (await ethers.provider.getBlock("latest")).timestamp; + const startTime = (await instance.auctionData(auctionId)).auctionStartDate; + await increaseTime(startTime - now); +} export async function claimFromAllOrders( easyAuction: Contract, From 4aa2dac1b59d56bcf88bf779ef4316c135460443 Mon Sep 17 00:00:00 2001 From: josojo Date: Sun, 9 May 2021 22:23:01 +0200 Subject: [PATCH 3/4] adapting all necessary tests --- contracts/ChannelAuction.sol | 2 +- src/priceCalculation.ts | 8 +- test/contract/Channelauction.spec.ts | 1300 +++++++++++--------------- 3 files changed, 539 insertions(+), 771 deletions(-) diff --git a/contracts/ChannelAuction.sol b/contracts/ChannelAuction.sol index c7c3d2c..32ea86a 100644 --- a/contracts/ChannelAuction.sol +++ b/contracts/ChannelAuction.sol @@ -153,7 +153,7 @@ contract ChannelAuction is Ownable { if (feeNumerator > 0) { _auctioningToken.safeTransferFrom( msg.sender, - address(this), + registeredUsers.getAddressAt(feeReceiverUserId), _auctionedSellAmount.mul(feeNumerator).div(FEE_DENOMINATOR) //[1] ); } diff --git a/src/priceCalculation.ts b/src/priceCalculation.ts index e9a8a54..067e3fd 100644 --- a/src/priceCalculation.ts +++ b/src/priceCalculation.ts @@ -300,15 +300,15 @@ export async function createTokensAndMintAndApprove( const auctioningToken = await ERC20.deploy("BT", "BT"); for (const user of users) { - await biddingToken.mint(user.address, BigNumber.from(10).pow(30)); + await biddingToken.mint(user.address, BigNumber.from(10).pow(32)); await biddingToken .connect(user) - .approve(auctionContract.address, BigNumber.from(10).pow(30)); + .approve(auctionContract.address, BigNumber.from(10).pow(32)); - await auctioningToken.mint(user.address, BigNumber.from(10).pow(30)); + await auctioningToken.mint(user.address, BigNumber.from(10).pow(32)); await auctioningToken .connect(user) - .approve(auctionContract.address, BigNumber.from(10).pow(30)); + .approve(auctionContract.address, BigNumber.from(10).pow(32)); } return { auctioningToken: auctioningToken, biddingToken: biddingToken }; } diff --git a/test/contract/Channelauction.spec.ts b/test/contract/Channelauction.spec.ts index 8bdf23c..fc06436 100644 --- a/test/contract/Channelauction.spec.ts +++ b/test/contract/Channelauction.spec.ts @@ -1,7 +1,6 @@ -import { deployMockContract } from "@ethereum-waffle/mock-contract"; import { expect } from "chai"; import { Contract, BigNumber } from "ethers"; -import hre, { artifacts, ethers, waffle } from "hardhat"; +import hre, { ethers, waffle } from "hardhat"; import "@nomiclabs/hardhat-ethers"; import { @@ -11,21 +10,16 @@ import { createTokensAndMintAndApprove, placeOrdersForChannelAuction, calculateClearingPrice, - getAllSellOrders, - getClearingPriceFromInitialOrder, } from "../../src/priceCalculation"; import { - createAuctionWithDefaults, createChannelAuctionWithDefaults, createChannelAuctionWithDefaultsAndReturnId, } from "./defaultContractInteractions"; import { sendTxAndGetReturnValue, closeChannelAuction, - increaseTime, claimFromAllOrders, - MAGIC_VALUE_FROM_ALLOW_LIST_VERIFIER_INTERFACE, startChannelAuction, } from "./utilities"; @@ -130,7 +124,7 @@ describe("ChannelAuction", async () => { const now = (await ethers.provider.getBlock("latest")).timestamp; const auctionStartDate = now + 1337; - const _maxDuration = 2000; + const _maxDuration = BigNumber.from(2000); const initialAuctionOrder = { sellAmount: ethers.utils.parseEther("1"), @@ -143,8 +137,8 @@ describe("ChannelAuction", async () => { auctioningToken, biddingToken, _auctionedSellAmount: initialAuctionOrder.sellAmount, - _auctioneerBuyAmountMaximum: initialAuctionOrder.buyAmount, - _auctioneerBuyAmountMinimum: initialAuctionOrder.buyAmount.div(2), + _auctioneerBuyAmountMaximum: initialAuctionOrder.buyAmount.mul(2), + _auctioneerBuyAmountMinimum: initialAuctionOrder.buyAmount, _auctionStartDate: auctionStartDate, _maxDuration, }, @@ -157,6 +151,9 @@ describe("ChannelAuction", async () => { ); expect(auctionData.auctionStartDate).to.be.equal(auctionStartDate); expect(auctionData.maxDuration).to.be.equal(_maxDuration); + expect(auctionData.auctioneerBuyAmountMaximum).to.be.equal( + initialAuctionOrder.buyAmount.mul(2), + ); await expect(auctionData.clearingPriceOrder).to.equal( encodeOrder({ userId: BigNumber.from(0), @@ -166,9 +163,9 @@ describe("ChannelAuction", async () => { ); expect(auctionData.volumeClearingPriceOrder).to.be.equal(0); - expect(await auctioningToken.balanceOf(channelAuction.address)).to.equal( - ethers.utils.parseEther("1"), - ); + // expect(await auctioningToken.balanceOf(channelAuction.address)).to.equal( + // ethers.utils.parseEther("1"), + // ); }); }); @@ -892,18 +889,6 @@ describe("ChannelAuction", async () => { it("checks case 6, it verifies the price in case of clearingOrder == initialOrder, although last iterOrder would also be possible", async () => { // This test demonstrates the case 6, // where price could be either the auctioningOrder or sellOrder - const initialAuctionOrder = { - sellAmount: ethers.utils.parseEther("500"), - buyAmount: ethers.utils.parseEther("1"), - userId: BigNumber.from(1), - }; - const sellOrders = [ - { - sellAmount: ethers.utils.parseEther("1"), - buyAmount: ethers.utils.parseEther("1"), - userId: BigNumber.from(1), - }, - ]; const { auctioningToken, biddingToken, @@ -912,14 +897,27 @@ describe("ChannelAuction", async () => { [user_1, user_2], hre, ); - - await createChannelAuctionWithDefaults(channelAuction, { + const auctionInitParameters = { auctioningToken, biddingToken, - _auctionedSellAmount: initialAuctionOrder.sellAmount, - _auctioneerBuyAmountMinimum: initialAuctionOrder.buyAmount, - }); - const auctionId = BigNumber.from(1); + _auctionedSellAmount: ethers.utils.parseEther("500"), + _auctioneerBuyAmountMinimum: ethers.utils.parseEther("1"), + _auctioneerBuyAmountMaximum: ethers.utils.parseEther("2"), + _auctioneerUserId: BigNumber.from(0), + }; + const sellOrders = [ + { + sellAmount: ethers.utils.parseEther("1"), + buyAmount: ethers.utils.parseEther("260"), + userId: BigNumber.from(1), + }, + ]; + + const auctionId: BigNumber = await createChannelAuctionWithDefaultsAndReturnId( + channelAuction, + auctionInitParameters, + ); + await startChannelAuction(channelAuction, auctionId); await placeOrdersForChannelAuction( channelAuction, sellOrders, @@ -933,24 +931,40 @@ describe("ChannelAuction", async () => { .to.emit(channelAuction, "AuctionCleared") .withArgs( auctionId, - initialAuctionOrder.sellAmount, + auctionInitParameters._auctionedSellAmount, sellOrders[0].sellAmount, - encodeOrder(getClearingPriceFromInitialOrder(initialAuctionOrder)), + encodeOrder({ + sellAmount: auctionInitParameters._auctioneerBuyAmountMinimum, + buyAmount: auctionInitParameters._auctionedSellAmount, + userId: auctionInitParameters._auctioneerUserId, + }), ); const auctionData = await channelAuction.auctionData(auctionId); expect(auctionData.clearingPriceOrder).to.equal( - encodeOrder(getClearingPriceFromInitialOrder(initialAuctionOrder)), - ); - await channelAuction.claimFromParticipantOrder( - auctionId, - sellOrders.map((order) => encodeOrder(order)), + encodeOrder({ + sellAmount: auctionInitParameters._auctioneerBuyAmountMinimum, + buyAmount: auctionInitParameters._auctionedSellAmount, + userId: auctionInitParameters._auctioneerUserId, + }), ); + await claimFromAllOrders(channelAuction, auctionId, sellOrders); }); it("checks case 12, it verifies that price can not be the initial auction price (Adam's case)", async () => { - const initialAuctionOrder = { - sellAmount: ethers.utils.parseEther("1").add(1), - buyAmount: ethers.utils.parseEther("0.1"), - userId: BigNumber.from(1), + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2], + hre, + ); + const auctionInitParameters = { + auctioningToken, + biddingToken, + _auctionedSellAmount: ethers.utils.parseEther("1"), + _auctioneerBuyAmountMinimum: ethers.utils.parseEther("0.1"), + _auctioneerBuyAmountMaximum: ethers.utils.parseEther("0.8"), + _auctioneerUserId: BigNumber.from(0), }; const sellOrders = [ { @@ -964,22 +978,13 @@ describe("ChannelAuction", async () => { userId: BigNumber.from(2), }, ]; - const { - auctioningToken, - biddingToken, - } = await createTokensAndMintAndApprove( + + const auctionId: BigNumber = await createChannelAuctionWithDefaultsAndReturnId( channelAuction, - [user_1, user_2], - hre, + auctionInitParameters, ); + await startChannelAuction(channelAuction, auctionId); - await createChannelAuctionWithDefaults(channelAuction, { - auctioningToken, - biddingToken, - _auctionedSellAmount: initialAuctionOrder.sellAmount, - _auctioneerBuyAmountMinimum: initialAuctionOrder.buyAmount, - }); - const auctionId = BigNumber.from(1); await placeOrdersForChannelAuction( channelAuction, sellOrders, @@ -994,10 +999,21 @@ describe("ChannelAuction", async () => { await claimFromAllOrders(channelAuction, auctionId, sellOrders); }); it("checks case 3, it verifies the price in case of clearingOrder != placed order with 3x participation", async () => { - const initialAuctionOrder = { - sellAmount: ethers.utils.parseEther("500"), - buyAmount: ethers.utils.parseEther("1"), - userId: BigNumber.from(1), + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2, user_3], + hre, + ); + const auctionInitParameters = { + auctioningToken, + biddingToken, + _auctionedSellAmount: ethers.utils.parseEther("500"), + _auctioneerBuyAmountMinimum: ethers.utils.parseEther("1"), + _auctioneerBuyAmountMaximum: ethers.utils.parseEther("1000"), + _auctioneerUserId: BigNumber.from(0), }; const sellOrders = [ { @@ -1017,22 +1033,12 @@ describe("ChannelAuction", async () => { }, ]; - const { - auctioningToken, - biddingToken, - } = await createTokensAndMintAndApprove( + const auctionId: BigNumber = await createChannelAuctionWithDefaultsAndReturnId( channelAuction, - [user_1, user_2, user_3], - hre, + auctionInitParameters, ); + await startChannelAuction(channelAuction, auctionId); - await createChannelAuctionWithDefaults(channelAuction, { - auctioningToken, - biddingToken, - _auctionedSellAmount: initialAuctionOrder.sellAmount, - _auctioneerBuyAmountMinimum: initialAuctionOrder.buyAmount, - }); - const auctionId = BigNumber.from(1); await placeOrdersForChannelAuction( channelAuction, sellOrders, @@ -1047,7 +1053,7 @@ describe("ChannelAuction", async () => { expect(auctionData.clearingPriceOrder).to.equal( encodeOrder({ sellAmount: ethers.utils.parseEther("3"), - buyAmount: initialAuctionOrder.sellAmount, + buyAmount: auctionInitParameters._auctionedSellAmount, userId: BigNumber.from(0), }), ); @@ -1055,44 +1061,60 @@ describe("ChannelAuction", async () => { await claimFromAllOrders(channelAuction, auctionId, sellOrders); }); it("checks case 8, it verifies the price in case of no participation of the auction", async () => { - const initialAuctionOrder = { - sellAmount: BigNumber.from(1000), - buyAmount: BigNumber.from(1000), - userId: BigNumber.from(1), - }; - const { auctioningToken, biddingToken, } = await createTokensAndMintAndApprove( channelAuction, - [user_1, user_2], + [user_1, user_2, user_3], hre, ); - - await createChannelAuctionWithDefaults(channelAuction, { + const auctionInitParameters = { auctioningToken, biddingToken, - _auctionedSellAmount: initialAuctionOrder.sellAmount, - _auctioneerBuyAmountMinimum: initialAuctionOrder.buyAmount, - }); - const auctionId = BigNumber.from(1); + _auctionedSellAmount: ethers.utils.parseEther("500"), + _auctioneerBuyAmountMinimum: ethers.utils.parseEther("1"), + _auctioneerBuyAmountMaximum: ethers.utils.parseEther("1000"), + _auctioneerUserId: BigNumber.from(0), + }; + + const auctionId: BigNumber = await createChannelAuctionWithDefaultsAndReturnId( + channelAuction, + auctionInitParameters, + ); await closeChannelAuction(channelAuction, auctionId); await channelAuction.settleAuction(auctionId); const auctionData = await channelAuction.auctionData(auctionId); expect(auctionData.clearingPriceOrder).to.equal( - encodeOrder(getClearingPriceFromInitialOrder(initialAuctionOrder)), + encodeOrder({ + sellAmount: auctionInitParameters._auctioneerBuyAmountMinimum, + buyAmount: auctionInitParameters._auctionedSellAmount, + userId: auctionInitParameters._auctioneerUserId, + }), ); expect(auctionData.volumeClearingPriceOrder).to.equal(BigNumber.from(0)); }); it("checks case 2, it verifies the price in case without a partially filled order", async () => { - const initialAuctionOrder = { - sellAmount: ethers.utils.parseEther("1"), - buyAmount: ethers.utils.parseEther("1"), - userId: BigNumber.from(1), + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2, user_3], + hre, + ); + + const auctionInitParameters = { + auctioningToken, + biddingToken, + _auctionedSellAmount: ethers.utils.parseEther("1"), + _auctioneerBuyAmountMinimum: ethers.utils.parseEther("1"), + _auctioneerBuyAmountMaximum: ethers.utils.parseEther("5"), + _auctioneerUserId: BigNumber.from(1), }; + const sellOrders = [ { sellAmount: ethers.utils.parseEther("1").mul(2), @@ -1105,24 +1127,12 @@ describe("ChannelAuction", async () => { userId: BigNumber.from(1), }, ]; - const { - auctioningToken, - biddingToken, - } = await createTokensAndMintAndApprove( - channelAuction, - [user_1, user_2], - hre, - ); const auctionId: BigNumber = await createChannelAuctionWithDefaultsAndReturnId( channelAuction, - { - auctioningToken, - biddingToken, - _auctionedSellAmount: initialAuctionOrder.sellAmount, - _auctioneerBuyAmountMinimum: initialAuctionOrder.buyAmount, - }, + auctionInitParameters, ); + await startChannelAuction(channelAuction, auctionId); await placeOrdersForChannelAuction( channelAuction, sellOrders, @@ -1136,7 +1146,7 @@ describe("ChannelAuction", async () => { expect(auctionData.clearingPriceOrder).to.equal( encodeOrder({ sellAmount: sellOrders[0].sellAmount, - buyAmount: initialAuctionOrder.sellAmount, + buyAmount: auctionInitParameters._auctionedSellAmount, userId: BigNumber.from(0), }), ); @@ -1144,10 +1154,21 @@ describe("ChannelAuction", async () => { await claimFromAllOrders(channelAuction, auctionId, sellOrders); }); it("checks case 10, verifies the price in case one sellOrder is eating initialAuctionOrder completely", async () => { - const initialAuctionOrder = { - sellAmount: ethers.utils.parseEther("1"), - buyAmount: ethers.utils.parseEther("1"), - userId: BigNumber.from(1), + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2, user_3], + hre, + ); + const auctionInitParameters = { + auctioningToken, + biddingToken, + _auctionedSellAmount: ethers.utils.parseEther("1"), + _auctioneerBuyAmountMinimum: ethers.utils.parseEther("1"), + _auctioneerBuyAmountMaximum: ethers.utils.parseEther("5"), + _auctioneerUserId: BigNumber.from(1), }; const sellOrders = [ { @@ -1156,24 +1177,13 @@ describe("ChannelAuction", async () => { userId: BigNumber.from(1), }, ]; - const { - auctioningToken, - biddingToken, - } = await createTokensAndMintAndApprove( - channelAuction, - [user_1, user_2], - hre, - ); const auctionId: BigNumber = await createChannelAuctionWithDefaultsAndReturnId( channelAuction, - { - auctioningToken, - biddingToken, - _auctionedSellAmount: initialAuctionOrder.sellAmount, - _auctioneerBuyAmountMinimum: initialAuctionOrder.buyAmount, - }, + auctionInitParameters, ); + await startChannelAuction(channelAuction, auctionId); + await placeOrdersForChannelAuction( channelAuction, sellOrders, @@ -1188,17 +1198,28 @@ describe("ChannelAuction", async () => { encodeOrder(sellOrders[0]), ); expect(auctionData.volumeClearingPriceOrder).to.equal( - initialAuctionOrder.sellAmount + auctionInitParameters._auctionedSellAmount .mul(sellOrders[0].sellAmount) .div(sellOrders[0].buyAmount), ); await claimFromAllOrders(channelAuction, auctionId, sellOrders); }); it("checks case 5, bidding amount matches min buyAmount of initialOrder perfectly", async () => { - const initialAuctionOrder = { - sellAmount: ethers.utils.parseEther("1"), - buyAmount: ethers.utils.parseEther("0.5"), - userId: BigNumber.from(1), + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2, user_3], + hre, + ); + const auctionInitParameters = { + auctioningToken, + biddingToken, + _auctionedSellAmount: ethers.utils.parseEther("1"), + _auctioneerBuyAmountMinimum: ethers.utils.parseEther("0.5"), + _auctioneerBuyAmountMaximum: ethers.utils.parseEther("5"), + _auctioneerUserId: BigNumber.from(1), }; const sellOrders = [ { @@ -1212,24 +1233,12 @@ describe("ChannelAuction", async () => { userId: BigNumber.from(3), }, ]; - const { - auctioningToken, - biddingToken, - } = await createTokensAndMintAndApprove( - channelAuction, - [user_1, user_2, user_3], - hre, - ); const auctionId: BigNumber = await createChannelAuctionWithDefaultsAndReturnId( channelAuction, - { - auctioningToken, - biddingToken, - _auctionedSellAmount: initialAuctionOrder.sellAmount, - _auctioneerBuyAmountMinimum: initialAuctionOrder.buyAmount, - }, + auctionInitParameters, ); + await startChannelAuction(channelAuction, auctionId); await placeOrdersForChannelAuction( channelAuction, sellOrders, @@ -1264,10 +1273,21 @@ describe("ChannelAuction", async () => { ); }); it("checks case 7, bidding amount matches min buyAmount of initialOrder perfectly with additional order", async () => { - const initialAuctionOrder = { - sellAmount: ethers.utils.parseEther("1"), - buyAmount: ethers.utils.parseEther("0.5"), - userId: BigNumber.from(1), + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2, user_3], + hre, + ); + const auctionInitParameters = { + auctioningToken, + biddingToken, + _auctionedSellAmount: ethers.utils.parseEther("1"), + _auctioneerBuyAmountMinimum: ethers.utils.parseEther("0.5"), + _auctioneerBuyAmountMaximum: ethers.utils.parseEther("5"), + _auctioneerUserId: BigNumber.from(1), }; const sellOrders = [ { @@ -1286,31 +1306,17 @@ describe("ChannelAuction", async () => { userId: BigNumber.from(3), }, ]; - const { - auctioningToken, - biddingToken, - } = await createTokensAndMintAndApprove( - channelAuction, - [user_1, user_2, user_3], - hre, - ); - const auctionId: BigNumber = await createChannelAuctionWithDefaultsAndReturnId( channelAuction, - { - auctioningToken, - biddingToken, - _auctionedSellAmount: initialAuctionOrder.sellAmount, - _auctioneerBuyAmountMinimum: initialAuctionOrder.buyAmount, - }, + auctionInitParameters, ); + await startChannelAuction(channelAuction, auctionId); await placeOrdersForChannelAuction( channelAuction, sellOrders, auctionId, hre, ); - await closeChannelAuction(channelAuction, auctionId); await channelAuction.settleAuction(auctionId); const auctionData = await channelAuction.auctionData(auctionId); @@ -1347,10 +1353,21 @@ describe("ChannelAuction", async () => { ); }); it("checks case 10: it shows an example why userId should always be given: 2 orders with the same price", async () => { - const initialAuctionOrder = { - sellAmount: ethers.utils.parseEther("1"), - buyAmount: ethers.utils.parseEther("0.5"), - userId: BigNumber.from(1), + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2, user_3], + hre, + ); + const auctionInitParameters = { + auctioningToken, + biddingToken, + _auctionedSellAmount: ethers.utils.parseEther("1"), + _auctioneerBuyAmountMinimum: ethers.utils.parseEther("0.5"), + _auctioneerBuyAmountMaximum: ethers.utils.parseEther("5"), + _auctioneerUserId: BigNumber.from(1), }; const sellOrders = [ { @@ -1369,24 +1386,11 @@ describe("ChannelAuction", async () => { userId: BigNumber.from(3), }, ]; - const { - auctioningToken, - biddingToken, - } = await createTokensAndMintAndApprove( - channelAuction, - [user_1, user_2, user_3], - hre, - ); - const auctionId: BigNumber = await createChannelAuctionWithDefaultsAndReturnId( channelAuction, - { - auctioningToken, - biddingToken, - _auctionedSellAmount: initialAuctionOrder.sellAmount, - _auctioneerBuyAmountMinimum: initialAuctionOrder.buyAmount, - }, + auctionInitParameters, ); + await startChannelAuction(channelAuction, auctionId); await placeOrdersForChannelAuction( channelAuction, sellOrders, @@ -1432,66 +1436,21 @@ describe("ChannelAuction", async () => { ); }); it("checks case 1, it verifies the price in case of 2 of 3 sellOrders eating initialAuctionOrder completely", async () => { - const initialAuctionOrder = { - sellAmount: ethers.utils.parseEther("1"), - buyAmount: ethers.utils.parseEther("1"), - userId: BigNumber.from(1), - }; - const sellOrders = [ - { - sellAmount: ethers.utils.parseEther("1").mul(2), - buyAmount: ethers.utils.parseEther("1").div(5), - userId: BigNumber.from(1), - }, - { - sellAmount: ethers.utils.parseEther("1").mul(2), - buyAmount: ethers.utils.parseEther("1"), - userId: BigNumber.from(1), - }, - - { - sellAmount: ethers.utils.parseEther("1").mul(2).add(1), - buyAmount: ethers.utils.parseEther("1").mul(2), - userId: BigNumber.from(1), - }, - ]; const { auctioningToken, biddingToken, } = await createTokensAndMintAndApprove( channelAuction, - [user_1, user_2], - hre, - ); - - const auctionId: BigNumber = await createChannelAuctionWithDefaultsAndReturnId( - channelAuction, - { - auctioningToken, - biddingToken, - _auctionedSellAmount: initialAuctionOrder.sellAmount, - _auctioneerBuyAmountMinimum: initialAuctionOrder.buyAmount, - }, - ); - await placeOrdersForChannelAuction( - channelAuction, - sellOrders, - auctionId, + [user_1, user_2, user_3], hre, ); - - await closeChannelAuction(channelAuction, auctionId); - await channelAuction.settleAuction(auctionId); - const auctionData = await channelAuction.auctionData(auctionId); - expect(auctionData.clearingPriceOrder).to.eql(encodeOrder(sellOrders[1])); - expect(auctionData.volumeClearingPriceOrder).to.equal(0); - await claimFromAllOrders(channelAuction, auctionId, sellOrders); - }); - it("verifies the price in case of 2 of 3 sellOrders eating initialAuctionOrder completely - with precalculateSellAmountSum step", async () => { - const initialAuctionOrder = { - sellAmount: ethers.utils.parseEther("1"), - buyAmount: ethers.utils.parseEther("1"), - userId: BigNumber.from(1), + const auctionInitParameters = { + auctioningToken, + biddingToken, + _auctionedSellAmount: ethers.utils.parseEther("1"), + _auctioneerBuyAmountMinimum: ethers.utils.parseEther("1"), + _auctioneerBuyAmountMaximum: ethers.utils.parseEther("20"), + _auctioneerUserId: BigNumber.from(1), }; const sellOrders = [ { @@ -1511,24 +1470,11 @@ describe("ChannelAuction", async () => { userId: BigNumber.from(1), }, ]; - const { - auctioningToken, - biddingToken, - } = await createTokensAndMintAndApprove( - channelAuction, - [user_1, user_2], - hre, - ); - const auctionId: BigNumber = await createChannelAuctionWithDefaultsAndReturnId( channelAuction, - { - auctioningToken, - biddingToken, - _auctionedSellAmount: initialAuctionOrder.sellAmount, - _auctioneerBuyAmountMinimum: initialAuctionOrder.buyAmount, - }, + auctionInitParameters, ); + await startChannelAuction(channelAuction, auctionId); await placeOrdersForChannelAuction( channelAuction, sellOrders, @@ -1537,90 +1483,157 @@ describe("ChannelAuction", async () => { ); await closeChannelAuction(channelAuction, auctionId); - // this is the additional step - await channelAuction.precalculateSellAmountSum(auctionId, 1); - await channelAuction.settleAuction(auctionId); const auctionData = await channelAuction.auctionData(auctionId); expect(auctionData.clearingPriceOrder).to.eql(encodeOrder(sellOrders[1])); expect(auctionData.volumeClearingPriceOrder).to.equal(0); + await claimFromAllOrders(channelAuction, auctionId, sellOrders); }); - it("verifies the price in case of 2 of 4 sellOrders eating initialAuctionOrder completely - with precalculateSellAmountSum step and one more step within settleAuction", async () => { - const initialAuctionOrder = { - sellAmount: ethers.utils.parseEther("1"), - buyAmount: ethers.utils.parseEther("1"), - userId: BigNumber.from(1), - }; - const sellOrders = [ - { - sellAmount: ethers.utils.parseEther("1"), - buyAmount: ethers.utils.parseEther("1").div(5), - userId: BigNumber.from(1), - }, - { - sellAmount: ethers.utils.parseEther("1"), - buyAmount: ethers.utils.parseEther("1").div(5), - userId: BigNumber.from(2), - }, - { - sellAmount: ethers.utils.parseEther("1").mul(2), - buyAmount: ethers.utils.parseEther("1"), - userId: BigNumber.from(1), - }, - - { - sellAmount: ethers.utils.parseEther("1").mul(2).add(1), - buyAmount: ethers.utils.parseEther("1").mul(2), - userId: BigNumber.from(1), - }, - ]; + // it("verifies the price in case of 2 of 3 sellOrders eating initialAuctionOrder completely - with precalculateSellAmountSum step", async () => { + // const { + // auctioningToken, + // biddingToken, + // } = await createTokensAndMintAndApprove( + // channelAuction, + // [user_1, user_2, user_3], + // hre, + // ); + // const auctionInitParameters = { + // auctioningToken, + // biddingToken, + // _auctionedSellAmount: ethers.utils.parseEther("1"), + // _auctioneerBuyAmountMinimum: ethers.utils.parseEther("1"), + // _auctioneerBuyAmountMaximum: ethers.utils.parseEther("5"), + // _auctioneerUserId: BigNumber.from(1), + // }; + // const sellOrders = [ + // { + // sellAmount: ethers.utils.parseEther("1").mul(2), + // buyAmount: ethers.utils.parseEther("1").div(5), + // userId: BigNumber.from(1), + // }, + // { + // sellAmount: ethers.utils.parseEther("1").mul(2), + // buyAmount: ethers.utils.parseEther("1"), + // userId: BigNumber.from(1), + // }, + + // { + // sellAmount: ethers.utils.parseEther("1").mul(2).add(1), + // buyAmount: ethers.utils.parseEther("1").mul(2), + // userId: BigNumber.from(1), + // }, + // ]; + + // const auctionId: BigNumber = await createChannelAuctionWithDefaultsAndReturnId( + // channelAuction, + // auctionInitParameters, + // ); + // await startChannelAuction(channelAuction, auctionId); + + // await placeOrdersForChannelAuction( + // channelAuction, + // sellOrders, + // auctionId, + // hre, + // ); + + // await closeChannelAuction(channelAuction, auctionId); + // // this is the additional step + // await channelAuction.precalculateSellAmountSum(auctionId, 1); + + // await channelAuction.settleAuction(auctionId); + // const auctionData = await channelAuction.auctionData(auctionId); + // expect(auctionData.clearingPriceOrder).to.eql(encodeOrder(sellOrders[1])); + // expect(auctionData.volumeClearingPriceOrder).to.equal(0); + // }); + // it("verifies the price in case of 2 of 4 sellOrders eating initialAuctionOrder completely - with precalculateSellAmountSum step and one more step within settleAuction", async () => { + // const initialAuctionOrder = { + // sellAmount: ethers.utils.parseEther("1"), + // buyAmount: ethers.utils.parseEther("1"), + // userId: BigNumber.from(1), + // }; + // const sellOrders = [ + // { + // sellAmount: ethers.utils.parseEther("1"), + // buyAmount: ethers.utils.parseEther("1").div(5), + // userId: BigNumber.from(1), + // }, + // { + // sellAmount: ethers.utils.parseEther("1"), + // buyAmount: ethers.utils.parseEther("1").div(5), + // userId: BigNumber.from(2), + // }, + // { + // sellAmount: ethers.utils.parseEther("1").mul(2), + // buyAmount: ethers.utils.parseEther("1"), + // userId: BigNumber.from(1), + // }, + + // { + // sellAmount: ethers.utils.parseEther("1").mul(2).add(1), + // buyAmount: ethers.utils.parseEther("1").mul(2), + // userId: BigNumber.from(1), + // }, + // ]; + // const { + // auctioningToken, + // biddingToken, + // } = await createTokensAndMintAndApprove( + // channelAuction, + // [user_1, user_2], + // hre, + // ); + + // const auctionId: BigNumber = await createChannelAuctionWithDefaultsAndReturnId( + // channelAuction, + // { + // auctioningToken, + // biddingToken, + // _auctionedSellAmount: initialAuctionOrder.sellAmount, + // _auctioneerBuyAmountMinimum: initialAuctionOrder.buyAmount, + // }, + // ); + // await placeOrdersForChannelAuction( + // channelAuction, + // sellOrders, + // auctionId, + // hre, + // ); + + // await closeChannelAuction(channelAuction, auctionId); + // // this is the additional step + // await channelAuction.precalculateSellAmountSum(auctionId, 1); + + // const auctionData = await channelAuction.auctionData(auctionId); + // expect(auctionData.interimSumBidAmount).to.equal( + // sellOrders[0].sellAmount, + // ); + // expect(auctionData.interimOrder).to.equal(encodeOrder(sellOrders[0])); + // await channelAuction.settleAuction(auctionId); + // const auctionData2 = await channelAuction.auctionData(auctionId); + // expect(auctionData2.clearingPriceOrder).to.eql( + // encodeOrder(sellOrders[2]), + // ); + // expect(auctionData2.volumeClearingPriceOrder).to.equal(0); + // await claimFromAllOrders(channelAuction, auctionId, sellOrders); + // }); + it("verifies the price in case of clearing order is decided by userId", async () => { const { auctioningToken, biddingToken, } = await createTokensAndMintAndApprove( channelAuction, - [user_1, user_2], - hre, - ); - - const auctionId: BigNumber = await createChannelAuctionWithDefaultsAndReturnId( - channelAuction, - { - auctioningToken, - biddingToken, - _auctionedSellAmount: initialAuctionOrder.sellAmount, - _auctioneerBuyAmountMinimum: initialAuctionOrder.buyAmount, - }, - ); - await placeOrdersForChannelAuction( - channelAuction, - sellOrders, - auctionId, + [user_1, user_2, user_3], hre, ); - - await closeChannelAuction(channelAuction, auctionId); - // this is the additional step - await channelAuction.precalculateSellAmountSum(auctionId, 1); - - const auctionData = await channelAuction.auctionData(auctionId); - expect(auctionData.interimSumBidAmount).to.equal( - sellOrders[0].sellAmount, - ); - expect(auctionData.interimOrder).to.equal(encodeOrder(sellOrders[0])); - await channelAuction.settleAuction(auctionId); - const auctionData2 = await channelAuction.auctionData(auctionId); - expect(auctionData2.clearingPriceOrder).to.eql( - encodeOrder(sellOrders[2]), - ); - expect(auctionData2.volumeClearingPriceOrder).to.equal(0); - await claimFromAllOrders(channelAuction, auctionId, sellOrders); - }); - it("verifies the price in case of clearing order is decided by userId", async () => { - const initialAuctionOrder = { - sellAmount: ethers.utils.parseEther("1"), - buyAmount: ethers.utils.parseEther("1"), - userId: BigNumber.from(1), + const auctionInitParameters = { + auctioningToken, + biddingToken, + _auctionedSellAmount: ethers.utils.parseEther("1"), + _auctioneerBuyAmountMinimum: ethers.utils.parseEther("1"), + _auctioneerBuyAmountMaximum: ethers.utils.parseEther("20"), + _auctioneerUserId: BigNumber.from(1), }; const sellOrders = [ { @@ -1640,24 +1653,12 @@ describe("ChannelAuction", async () => { userId: BigNumber.from(2), }, ]; - const { - auctioningToken, - biddingToken, - } = await createTokensAndMintAndApprove( - channelAuction, - [user_1, user_2], - hre, - ); const auctionId: BigNumber = await createChannelAuctionWithDefaultsAndReturnId( channelAuction, - { - auctioningToken, - biddingToken, - _auctionedSellAmount: initialAuctionOrder.sellAmount, - _auctioneerBuyAmountMinimum: initialAuctionOrder.buyAmount, - }, + auctionInitParameters, ); + await startChannelAuction(channelAuction, auctionId); await placeOrdersForChannelAuction( channelAuction, sellOrders, @@ -1674,82 +1675,24 @@ describe("ChannelAuction", async () => { expect(auctionData.volumeClearingPriceOrder).to.equal(0); await claimFromAllOrders(channelAuction, auctionId, sellOrders); }); - it("simple version of e2e gas test", async () => { - const initialAuctionOrder = { - sellAmount: ethers.utils.parseEther("1"), - buyAmount: ethers.utils.parseEther("1"), - userId: BigNumber.from(1), - }; - const sellOrders = [ - { - sellAmount: ethers.utils.parseEther("1").div(4), - buyAmount: ethers.utils.parseEther("1").div(8), - userId: BigNumber.from(1), - }, - { - sellAmount: ethers.utils.parseEther("1").div(4), - buyAmount: ethers.utils.parseEther("1").div(12), - userId: BigNumber.from(1), - }, - { - sellAmount: ethers.utils.parseEther("1").div(4), - buyAmount: ethers.utils.parseEther("1").div(16), - userId: BigNumber.from(1), - }, - { - sellAmount: ethers.utils.parseEther("1").div(4), - buyAmount: ethers.utils.parseEther("1").div(20), - userId: BigNumber.from(1), - }, - ]; + }); + describe("claimFromAuctioneerOrder", async () => { + it("checks the claimed amounts for a fully matched initialAuctionOrder and buyOrder", async () => { const { auctioningToken, biddingToken, } = await createTokensAndMintAndApprove( channelAuction, - [user_1, user_2], - hre, - ); - - const auctionId: BigNumber = await createChannelAuctionWithDefaultsAndReturnId( - channelAuction, - { - auctioningToken, - biddingToken, - _auctionedSellAmount: initialAuctionOrder.sellAmount, - _auctioneerBuyAmountMinimum: initialAuctionOrder.buyAmount, - }, - ); - await placeOrdersForChannelAuction( - channelAuction, - sellOrders, - auctionId, + [user_1, user_2, user_3], hre, ); - - await closeChannelAuction(channelAuction, auctionId); - const { clearingOrder: price } = await calculateClearingPrice( - channelAuction, - auctionId, - ); - - await channelAuction.settleAuction(auctionId); - expect(price.toString()).to.eql( - getClearingPriceFromInitialOrder(initialAuctionOrder).toString(), - ); - const auctionData = await channelAuction.auctionData(auctionId); - expect(auctionData.clearingPriceOrder).to.equal( - encodeOrder(getClearingPriceFromInitialOrder(initialAuctionOrder)), - ); - await claimFromAllOrders(channelAuction, auctionId, sellOrders); - }); - }); - describe("claimFromAuctioneerOrder", async () => { - it("checks the claimed amounts for a fully matched initialAuctionOrder and buyOrder", async () => { - const initialAuctionOrder = { - sellAmount: ethers.utils.parseEther("1"), - buyAmount: ethers.utils.parseEther("1"), - userId: BigNumber.from(1), + const auctionInitParameters = { + auctioningToken, + biddingToken, + _auctionedSellAmount: ethers.utils.parseEther("1"), + _auctioneerBuyAmountMinimum: ethers.utils.parseEther("1"), + _auctioneerBuyAmountMaximum: ethers.utils.parseEther("20"), + _auctioneerUserId: BigNumber.from(1), }; const sellOrders = [ { @@ -1758,31 +1701,18 @@ describe("ChannelAuction", async () => { userId: BigNumber.from(1), }, ]; - const { - auctioningToken, - biddingToken, - } = await createTokensAndMintAndApprove( - channelAuction, - [user_1, user_2], - hre, - ); const auctionId: BigNumber = await createChannelAuctionWithDefaultsAndReturnId( channelAuction, - { - auctioningToken, - biddingToken, - _auctionedSellAmount: initialAuctionOrder.sellAmount, - _auctioneerBuyAmountMinimum: initialAuctionOrder.buyAmount, - }, + auctionInitParameters, ); + await startChannelAuction(channelAuction, auctionId); await placeOrdersForChannelAuction( channelAuction, sellOrders, auctionId, hre, ); - await closeChannelAuction(channelAuction, auctionId); const { clearingOrder: price } = await calculateClearingPrice( channelAuction, @@ -1800,10 +1730,21 @@ describe("ChannelAuction", async () => { .withArgs(channelAuction.address, user_1.address, price.sellAmount); }); it("checks the claimed amounts for a partially matched initialAuctionOrder and buyOrder", async () => { - const initialAuctionOrder = { - sellAmount: ethers.utils.parseEther("1"), - buyAmount: ethers.utils.parseEther("1"), - userId: BigNumber.from(1), + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2, user_3], + hre, + ); + const auctionInitParameters = { + auctioningToken, + biddingToken, + _auctionedSellAmount: ethers.utils.parseEther("1"), + _auctioneerBuyAmountMinimum: ethers.utils.parseEther("1"), + _auctioneerBuyAmountMaximum: ethers.utils.parseEther("20"), + _auctioneerUserId: BigNumber.from(1), }; const sellOrders = [ { @@ -1812,31 +1753,18 @@ describe("ChannelAuction", async () => { userId: BigNumber.from(1), }, ]; - const { - auctioningToken, - biddingToken, - } = await createTokensAndMintAndApprove( - channelAuction, - [user_1, user_2], - hre, - ); const auctionId: BigNumber = await createChannelAuctionWithDefaultsAndReturnId( channelAuction, - { - auctioningToken, - biddingToken, - _auctionedSellAmount: initialAuctionOrder.sellAmount, - _auctioneerBuyAmountMinimum: initialAuctionOrder.buyAmount, - }, + auctionInitParameters, ); + await startChannelAuction(channelAuction, auctionId); await placeOrdersForChannelAuction( channelAuction, sellOrders, auctionId, hre, ); - await closeChannelAuction(channelAuction, auctionId); const callPromise = channelAuction.settleAuction(auctionId); // auctioneer reward check: @@ -1845,7 +1773,9 @@ describe("ChannelAuction", async () => { .withArgs( channelAuction.address, user_1.address, - initialAuctionOrder.sellAmount.sub(sellOrders[0].sellAmount), + auctionInitParameters._auctionedSellAmount.sub( + sellOrders[0].sellAmount, + ), ); await expect(callPromise) .to.emit(biddingToken, "Transfer") @@ -1858,10 +1788,21 @@ describe("ChannelAuction", async () => { }); describe("claimFromParticipantOrder", async () => { it("checks that claiming only works after the finishing of the auction", async () => { - const initialAuctionOrder = { - sellAmount: ethers.utils.parseEther("1"), - buyAmount: ethers.utils.parseEther("1"), - userId: BigNumber.from(1), + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2, user_3], + hre, + ); + const auctionInitParameters = { + auctioningToken, + biddingToken, + _auctionedSellAmount: ethers.utils.parseEther("1"), + _auctioneerBuyAmountMinimum: ethers.utils.parseEther("1"), + _auctioneerBuyAmountMaximum: ethers.utils.parseEther("20"), + _auctioneerUserId: BigNumber.from(1), }; const sellOrders = [ { @@ -1870,24 +1811,12 @@ describe("ChannelAuction", async () => { userId: BigNumber.from(1), }, ]; - const { - auctioningToken, - biddingToken, - } = await createTokensAndMintAndApprove( - channelAuction, - [user_1, user_2], - hre, - ); const auctionId: BigNumber = await createChannelAuctionWithDefaultsAndReturnId( channelAuction, - { - auctioningToken, - biddingToken, - _auctionedSellAmount: initialAuctionOrder.sellAmount, - _auctioneerBuyAmountMinimum: initialAuctionOrder.buyAmount, - }, + auctionInitParameters, ); + await startChannelAuction(channelAuction, auctionId); await placeOrdersForChannelAuction( channelAuction, sellOrders, @@ -1910,10 +1839,21 @@ describe("ChannelAuction", async () => { ).to.be.revertedWith("Auction not yet finished"); }); it("checks the claimed amounts for a partially matched buyOrder", async () => { - const initialAuctionOrder = { - sellAmount: ethers.utils.parseEther("1"), - buyAmount: ethers.utils.parseEther("1"), - userId: BigNumber.from(1), + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2, user_3], + hre, + ); + const auctionInitParameters = { + auctioningToken, + biddingToken, + _auctionedSellAmount: ethers.utils.parseEther("1"), + _auctioneerBuyAmountMinimum: ethers.utils.parseEther("1"), + _auctioneerBuyAmountMaximum: ethers.utils.parseEther("20"), + _auctioneerUserId: BigNumber.from(1), }; const sellOrders = [ { @@ -1927,24 +1867,12 @@ describe("ChannelAuction", async () => { userId: BigNumber.from(1), }, ]; - const { - auctioningToken, - biddingToken, - } = await createTokensAndMintAndApprove( - channelAuction, - [user_1, user_2], - hre, - ); const auctionId: BigNumber = await createChannelAuctionWithDefaultsAndReturnId( channelAuction, - { - auctioningToken, - biddingToken, - _auctionedSellAmount: initialAuctionOrder.sellAmount, - _auctioneerBuyAmountMinimum: initialAuctionOrder.buyAmount, - }, + auctionInitParameters, ); + await startChannelAuction(channelAuction, auctionId); await placeOrdersForChannelAuction( channelAuction, sellOrders, @@ -1972,7 +1900,7 @@ describe("ChannelAuction", async () => { .add(sellOrders[1].sellAmount) .mul(price.buyAmount) .div(price.sellAmount) - .sub(initialAuctionOrder.sellAmount), + .sub(auctionInitParameters._auctionedSellAmount), ) .sub(1); expect(receivedAmounts.auctioningTokenAmount).to.equal(settledBuyAmount); @@ -1983,10 +1911,21 @@ describe("ChannelAuction", async () => { ); }); it("checks the claimed amounts for a fully not-matched buyOrder", async () => { - const initialAuctionOrder = { - sellAmount: ethers.utils.parseEther("1"), - buyAmount: ethers.utils.parseEther("1"), - userId: BigNumber.from(1), + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2, user_3], + hre, + ); + const auctionInitParameters = { + auctioningToken, + biddingToken, + _auctionedSellAmount: ethers.utils.parseEther("1"), + _auctioneerBuyAmountMinimum: ethers.utils.parseEther("1"), + _auctioneerBuyAmountMaximum: ethers.utils.parseEther("20"), + _auctioneerUserId: BigNumber.from(1), }; const sellOrders = [ { @@ -2005,24 +1944,11 @@ describe("ChannelAuction", async () => { userId: BigNumber.from(2), }, ]; - const { - auctioningToken, - biddingToken, - } = await createTokensAndMintAndApprove( - channelAuction, - [user_1, user_2], - hre, - ); - const auctionId: BigNumber = await createChannelAuctionWithDefaultsAndReturnId( channelAuction, - { - auctioningToken, - biddingToken, - _auctionedSellAmount: initialAuctionOrder.sellAmount, - _auctioneerBuyAmountMinimum: initialAuctionOrder.buyAmount, - }, + auctionInitParameters, ); + await startChannelAuction(channelAuction, auctionId); await placeOrdersForChannelAuction( channelAuction, sellOrders, @@ -2042,10 +1968,21 @@ describe("ChannelAuction", async () => { expect(receivedAmounts.auctioningTokenAmount).to.equal("0"); }); it("checks the claimed amounts for a fully matched buyOrder", async () => { - const initialAuctionOrder = { - sellAmount: ethers.utils.parseEther("1"), - buyAmount: ethers.utils.parseEther("1"), - userId: BigNumber.from(1), + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2, user_3], + hre, + ); + const auctionInitParameters = { + auctioningToken, + biddingToken, + _auctionedSellAmount: ethers.utils.parseEther("1"), + _auctioneerBuyAmountMinimum: ethers.utils.parseEther("1"), + _auctioneerBuyAmountMaximum: ethers.utils.parseEther("20"), + _auctioneerUserId: BigNumber.from(1), }; const sellOrders = [ { @@ -2058,25 +1995,13 @@ describe("ChannelAuction", async () => { buyAmount: ethers.utils.parseEther("1").mul(2).div(3), userId: BigNumber.from(1), }, - ]; - const { - auctioningToken, - biddingToken, - } = await createTokensAndMintAndApprove( - channelAuction, - [user_1, user_2], - hre, - ); + ]; const auctionId: BigNumber = await createChannelAuctionWithDefaultsAndReturnId( channelAuction, - { - auctioningToken, - biddingToken, - _auctionedSellAmount: initialAuctionOrder.sellAmount, - _auctioneerBuyAmountMinimum: initialAuctionOrder.buyAmount, - }, + auctionInitParameters, ); + await startChannelAuction(channelAuction, auctionId); await placeOrdersForChannelAuction( channelAuction, sellOrders, @@ -2102,10 +2027,21 @@ describe("ChannelAuction", async () => { ); }); it("checks that an order can not be used for claiming twice", async () => { - const initialAuctionOrder = { - sellAmount: ethers.utils.parseEther("1"), - buyAmount: ethers.utils.parseEther("1"), - userId: BigNumber.from(1), + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2, user_3], + hre, + ); + const auctionInitParameters = { + auctioningToken, + biddingToken, + _auctionedSellAmount: ethers.utils.parseEther("1"), + _auctioneerBuyAmountMinimum: ethers.utils.parseEther("1"), + _auctioneerBuyAmountMaximum: ethers.utils.parseEther("20"), + _auctioneerUserId: BigNumber.from(1), }; const sellOrders = [ { @@ -2119,24 +2055,12 @@ describe("ChannelAuction", async () => { userId: BigNumber.from(1), }, ]; - const { - auctioningToken, - biddingToken, - } = await createTokensAndMintAndApprove( - channelAuction, - [user_1, user_2], - hre, - ); const auctionId: BigNumber = await createChannelAuctionWithDefaultsAndReturnId( channelAuction, - { - auctioningToken, - biddingToken, - _auctionedSellAmount: initialAuctionOrder.sellAmount, - _auctioneerBuyAmountMinimum: initialAuctionOrder.buyAmount, - }, + auctionInitParameters, ); + await startChannelAuction(channelAuction, auctionId); await placeOrdersForChannelAuction( channelAuction, sellOrders, @@ -2157,10 +2081,21 @@ describe("ChannelAuction", async () => { }); }); it("checks that orders from different users can not be claimed at once", async () => { - const initialAuctionOrder = { - sellAmount: ethers.utils.parseEther("1"), - buyAmount: ethers.utils.parseEther("1"), - userId: BigNumber.from(1), + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2, user_3], + hre, + ); + const auctionInitParameters = { + auctioningToken, + biddingToken, + _auctionedSellAmount: ethers.utils.parseEther("1"), + _auctioneerBuyAmountMinimum: ethers.utils.parseEther("1"), + _auctioneerBuyAmountMaximum: ethers.utils.parseEther("20"), + _auctioneerUserId: BigNumber.from(1), }; const sellOrders = [ { @@ -2174,24 +2109,12 @@ describe("ChannelAuction", async () => { userId: BigNumber.from(2), }, ]; - const { - auctioningToken, - biddingToken, - } = await createTokensAndMintAndApprove( - channelAuction, - [user_1, user_2], - hre, - ); const auctionId: BigNumber = await createChannelAuctionWithDefaultsAndReturnId( channelAuction, - { - auctioningToken, - biddingToken, - _auctionedSellAmount: initialAuctionOrder.sellAmount, - _auctioneerBuyAmountMinimum: initialAuctionOrder.buyAmount, - }, + auctionInitParameters, ); + await startChannelAuction(channelAuction, auctionId); await placeOrdersForChannelAuction( channelAuction, sellOrders, @@ -2209,10 +2132,21 @@ describe("ChannelAuction", async () => { ).to.be.revertedWith("only allowed to claim for same user"); }); it("checks the claimed amounts are summed up correctly for two orders", async () => { - const initialAuctionOrder = { - sellAmount: ethers.utils.parseEther("1"), - buyAmount: ethers.utils.parseEther("1"), - userId: BigNumber.from(1), + const { + auctioningToken, + biddingToken, + } = await createTokensAndMintAndApprove( + channelAuction, + [user_1, user_2, user_3], + hre, + ); + const auctionInitParameters = { + auctioningToken, + biddingToken, + _auctionedSellAmount: ethers.utils.parseEther("1"), + _auctioneerBuyAmountMinimum: ethers.utils.parseEther("1"), + _auctioneerBuyAmountMaximum: ethers.utils.parseEther("20"), + _auctioneerUserId: BigNumber.from(1), }; const sellOrders = [ { @@ -2226,24 +2160,13 @@ describe("ChannelAuction", async () => { userId: BigNumber.from(1), }, ]; - const { - auctioningToken, - biddingToken, - } = await createTokensAndMintAndApprove( - channelAuction, - [user_1, user_2], - hre, - ); const auctionId: BigNumber = await createChannelAuctionWithDefaultsAndReturnId( channelAuction, - { - auctioningToken, - biddingToken, - _auctionedSellAmount: initialAuctionOrder.sellAmount, - _auctioneerBuyAmountMinimum: initialAuctionOrder.buyAmount, - }, + auctionInitParameters, ); + await startChannelAuction(channelAuction, auctionId); + await placeOrdersForChannelAuction( channelAuction, sellOrders, @@ -2268,13 +2191,13 @@ describe("ChannelAuction", async () => { sellOrders[0].sellAmount .add(sellOrders[1].sellAmount) .sub( - initialAuctionOrder.sellAmount + auctionInitParameters._auctionedSellAmount .mul(price.sellAmount) .div(price.buyAmount), ), ); expect(receivedAmounts.auctioningTokenAmount).to.equal( - initialAuctionOrder.sellAmount.sub(1), + auctionInitParameters._auctionedSellAmount.sub(1), ); }); // describe("settleAuctionAtomically", async () => { @@ -2681,6 +2604,7 @@ describe("ChannelAuction", async () => { _auctioneerBuyAmountMinimum: initialAuctionOrder.buyAmount, }, ); + await startChannelAuction(channelAuction, auctionId); await placeOrdersForChannelAuction( channelAuction, sellOrders, @@ -2697,43 +2621,24 @@ describe("ChannelAuction", async () => { ).to.be.equal(true); }); }); - describe("getSecondsRemainingInBatch", async () => { - it("checks that claiming only works after the finishing of the auction", async () => { - const initialAuctionOrder = { - sellAmount: ethers.utils.parseEther("1"), - buyAmount: ethers.utils.parseEther("1"), - userId: BigNumber.from(1), - }; + describe("transfers fees", async () => { + it("transfers fees to feeReceiver", async () => { const { auctioningToken, biddingToken, } = await createTokensAndMintAndApprove( channelAuction, - [user_1, user_2], + [user_1, user_2, user_3], hre, ); - - const auctionId: BigNumber = await createChannelAuctionWithDefaultsAndReturnId( - channelAuction, - { - auctioningToken, - biddingToken, - _auctionedSellAmount: initialAuctionOrder.sellAmount, - _auctioneerBuyAmountMinimum: initialAuctionOrder.buyAmount, - }, - ); - await closeChannelAuction(channelAuction, auctionId); - expect( - await channelAuction.callStatic.getSecondsRemainingInBatch(auctionId), - ).to.be.equal("0"); - }); - }); - describe("claimsFee", async () => { - it("claims fees fully for a non-partially filled initialAuctionOrder", async () => { - const initialAuctionOrder = { - sellAmount: ethers.utils.parseEther("1"), - buyAmount: ethers.utils.parseEther("1"), - userId: BigNumber.from(1), + const auctionInitParameters = { + auctioningToken, + biddingToken, + _auctionedSellAmount: ethers.utils.parseEther("1"), + _auctioneerBuyAmountMinimum: ethers.utils.parseEther("1"), + _auctioneerBuyAmountMaximum: ethers.utils.parseEther("20"), + _auctioneerUserId: BigNumber.from(1), + _minimumBiddingAmount: ethers.utils.parseEther("0.01"), }; let sellOrders = [ { @@ -2747,202 +2652,65 @@ describe("ChannelAuction", async () => { userId: BigNumber.from(1), }, ]; - const { - auctioningToken, - biddingToken, - } = await createTokensAndMintAndApprove( - channelAuction, - [user_1, user_2, user_3], - hre, - ); const feeReceiver = user_3; - const feeNumerator = 10; + const feeNumerator = 4; await channelAuction .connect(user_1) .setFeeParameters(feeNumerator, feeReceiver.address); - const auctionId: BigNumber = await createChannelAuctionWithDefaultsAndReturnId( - channelAuction, - { - auctioningToken, - biddingToken, - _auctionedSellAmount: initialAuctionOrder.sellAmount, - _auctioneerBuyAmountMinimum: initialAuctionOrder.buyAmount, - }, - ); - await placeOrdersForChannelAuction( - channelAuction, - sellOrders, - auctionId, - hre, - ); - // resets the userId, as they are only given during function call. - sellOrders = await getAllSellOrders(channelAuction, auctionId); + const now = (await ethers.provider.getBlock("latest")).timestamp; - await closeChannelAuction(channelAuction, auctionId); await expect(() => - channelAuction.settleAuction(auctionId), + channelAuction.initiateAuction( + auctionInitParameters.auctioningToken.address, + auctionInitParameters.biddingToken.address, + auctionInitParameters._auctionedSellAmount, + auctionInitParameters._auctioneerBuyAmountMinimum, + auctionInitParameters._auctioneerBuyAmountMaximum, + now + 3600, + auctionInitParameters._minimumBiddingAmount, + 3600, + ), ).to.changeTokenBalances( auctioningToken, [feeReceiver], - [initialAuctionOrder.sellAmount.mul(feeNumerator).div("1000")], - ); - - // contract still holds sufficient funds to pay the participants fully - await channelAuction.callStatic.claimFromParticipantOrder( - auctionId, - sellOrders.map((order) => encodeOrder(order)), - ); - }); - it("claims also fee amount of zero, even when it is changed later", async () => { - const initialAuctionOrder = { - sellAmount: ethers.utils.parseEther("1"), - buyAmount: ethers.utils.parseEther("1"), - userId: BigNumber.from(1), - }; - let sellOrders = [ - { - sellAmount: ethers.utils.parseEther("1").div(2).add(1), - buyAmount: ethers.utils.parseEther("1").div(2), - userId: BigNumber.from(1), - }, - { - sellAmount: ethers.utils.parseEther("1").mul(2).div(3).add(1), - buyAmount: ethers.utils.parseEther("1").mul(2).div(3), - userId: BigNumber.from(1), - }, - ]; - const { - auctioningToken, - biddingToken, - } = await createTokensAndMintAndApprove( - channelAuction, - [user_1, user_2, user_3], - hre, - ); - - const feeReceiver = user_3; - const feeNumerator = 0; - await channelAuction - .connect(user_1) - .setFeeParameters(feeNumerator, feeReceiver.address); - - const auctionId: BigNumber = await createChannelAuctionWithDefaultsAndReturnId( - channelAuction, - { - auctioningToken, - biddingToken, - _auctionedSellAmount: initialAuctionOrder.sellAmount, - _auctioneerBuyAmountMinimum: initialAuctionOrder.buyAmount, - }, + [ + auctionInitParameters._auctionedSellAmount + .mul(feeNumerator) + .div("1000"), + ], ); - await placeOrdersForChannelAuction( + const auctionId = BigNumber.from(1); + await startChannelAuction(channelAuction, auctionId); + sellOrders = await placeOrdersForChannelAuction( channelAuction, sellOrders, auctionId, hre, ); - // resets the userId, as they are only given during function call. - sellOrders = await getAllSellOrders(channelAuction, auctionId); - await channelAuction - .connect(user_1) - .setFeeParameters(10, feeReceiver.address); await closeChannelAuction(channelAuction, auctionId); - await expect(() => - channelAuction.settleAuction(auctionId), - ).to.changeTokenBalances( - auctioningToken, - [feeReceiver], - [BigNumber.from(0)], - ); - + await channelAuction.settleAuction(auctionId); // contract still holds sufficient funds to pay the participants fully await channelAuction.callStatic.claimFromParticipantOrder( auctionId, sellOrders.map((order) => encodeOrder(order)), ); }); - it("claims fees fully for a partially filled initialAuctionOrder", async () => { - const initialAuctionOrder = { - sellAmount: ethers.utils.parseEther("1"), - buyAmount: ethers.utils.parseEther("1"), - userId: BigNumber.from(1), - }; - let sellOrders = [ - { - sellAmount: ethers.utils.parseEther("1").div(4), - buyAmount: ethers.utils.parseEther("1").div(4).sub(1), - userId: BigNumber.from(3), - }, - ]; - const { - auctioningToken, - biddingToken, - } = await createTokensAndMintAndApprove( - channelAuction, - [user_1, user_2, user_3], - hre, - ); - + }); + describe("setFeeParameters", async () => { + it("changing the paramter works", async () => { const feeReceiver = user_3; - const feeNumerator = 10; + const feeNumerator = 4; await channelAuction .connect(user_1) .setFeeParameters(feeNumerator, feeReceiver.address); - - const auctionId: BigNumber = await createChannelAuctionWithDefaultsAndReturnId( - channelAuction, - { - auctioningToken, - biddingToken, - _auctionedSellAmount: initialAuctionOrder.sellAmount, - _auctioneerBuyAmountMinimum: initialAuctionOrder.buyAmount, - }, - ); - await placeOrdersForChannelAuction( - channelAuction, - sellOrders, - auctionId, - hre, - ); - // resets the userId, as they are only given during function call. - sellOrders = await getAllSellOrders(channelAuction, auctionId); - - await closeChannelAuction(channelAuction, auctionId); - await expect(() => - channelAuction.settleAuction(auctionId), - ).to.changeTokenBalances( - auctioningToken, - [user_1, feeReceiver], - [ - // since only 1/4th of the tokens were sold, the auctioneer - // is getting 3/4th of the tokens plus 3/4th of the fee back - initialAuctionOrder.sellAmount - .mul(3) - .div(4) - .add( - initialAuctionOrder.sellAmount - .mul(feeNumerator) - .div("1000") - .mul(3) - .div(4), - ), - initialAuctionOrder.sellAmount.mul(feeNumerator).div("1000").div(4), - ], - ); - // contract still holds sufficient funds to pay the participants fully - await channelAuction.callStatic.claimFromParticipantOrder( - auctionId, - sellOrders.map((order) => encodeOrder(order)), - ); + expect(await channelAuction.callStatic.feeNumerator()).to.be.equal(4); }); - }); - describe("setFeeParameters", async () => { it("can only be called by owner", async () => { const feeReceiver = user_3; - const feeNumerator = 10; + const feeNumerator = 4; await expect( channelAuction .connect(user_2) @@ -2956,7 +2724,7 @@ describe("ChannelAuction", async () => { channelAuction .connect(user_1) .setFeeParameters(feeNumerator, feeReceiver.address), - ).to.be.revertedWith("Fee is not allowed to be set higher than 1.5%"); + ).to.be.revertedWith("Fee is not allowed to be set higher than 0.5%"); }); }); }); From 6e0f471718ffc07dfd0b898459f43542b1f4f7bf Mon Sep 17 00:00:00 2001 From: josojo Date: Sun, 9 May 2021 22:51:15 +0200 Subject: [PATCH 4/4] some linting imporvements --- test/contract/IterableOrderList.spec.ts | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/test/contract/IterableOrderList.spec.ts b/test/contract/IterableOrderList.spec.ts index ffb247f..16364b6 100644 --- a/test/contract/IterableOrderList.spec.ts +++ b/test/contract/IterableOrderList.spec.ts @@ -8,8 +8,6 @@ import { encodeOrder, } from "../../src/priceCalculation"; -const QUEUE_END = - "0xffffffffffffffffffffffffffffffffffffffff000000000000000000000001"; const BYTES32_ZERO = encodeOrder({ userId: BigNumber.from(1), sellAmount: BigNumber.from(0), @@ -45,16 +43,6 @@ const BYTES32_THREE = encodeOrder({ buyAmount: BigNumber.from(6), sellAmount: BigNumber.from(2), }); -const BYTES32_FOUR = encodeOrder({ - userId: BigNumber.from(2), - buyAmount: BigNumber.from(8), - sellAmount: BigNumber.from(2), -}); -const BYTES32_FIVE = encodeOrder({ - userId: BigNumber.from(2), - buyAmount: BigNumber.from(10), - sellAmount: BigNumber.from(2), -}); async function getSetContent(set: Contract) { const result = [];