Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
635 changes: 635 additions & 0 deletions contracts/ChannelAuction.sol

Large diffs are not rendered by default.

194 changes: 194 additions & 0 deletions contracts/libraries/IterableOrderedOrderList.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
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 linked
/// list with sorted elements. The list starts from QUEUE_START to
/// QUEUE_END, and each node keeps track of its successor.
/// Nodes can be added or removed.
///
/// The list is supposed to be
/// traversed with `next`. If `next` is empty, the node is not part of the
/// list.
struct Data {
mapping(bytes32 => bytes32) nextMap;
}

struct Order {
uint64 owner;
uint96 buyAmount;
uint96 sellAmount;
}

function initializeEmptyList(Data storage self) internal {
self.nextMap[QUEUE_START] = QUEUE_END;
}

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)
);
}
}
74 changes: 74 additions & 0 deletions contracts/test/IterableOrderedOrderListWrapper.sol
Original file line number Diff line number Diff line change
@@ -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);
}
}
35 changes: 35 additions & 0 deletions contracts/wrappers/DepositAndPlaceOrderForChannelAuction.sol
Original file line number Diff line number Diff line change
@@ -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
);
}
}
49 changes: 42 additions & 7 deletions src/priceCalculation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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",
);
});
}
Expand Down Expand Up @@ -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,
Expand All @@ -283,7 +291,7 @@ export async function getAllSellOrders(
}

export async function createTokensAndMintAndApprove(
easyAuction: Contract,
auctionContract: Contract,
users: Wallet[],
hre: HardhatRuntimeEnvironment,
): Promise<{ auctioningToken: Contract; biddingToken: Contract }> {
Expand All @@ -292,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(easyAuction.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(easyAuction.address, BigNumber.from(10).pow(30));
.approve(auctionContract.address, BigNumber.from(10).pow(32));
}
return { auctioningToken: auctioningToken, biddingToken: biddingToken };
}
Expand Down Expand Up @@ -332,3 +340,30 @@ export async function placeOrders(
);
}
}
export async function placeOrdersForChannelAuction(
easyAuction: Contract,
sellOrders: Order[],
auctionId: BigNumber,
hre: HardhatRuntimeEnvironment,
): Promise<Order[]> {
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;
}
Loading