From 2d73ca9e187835a6364abd8c0a623002efae4d45 Mon Sep 17 00:00:00 2001
From: Marcin Chrzanowski
+ this post assumes familiarity with the concept of smart contracts and
+ uses Ethereum and Solidity for concrete examples
+
+Multiunit auctions are a generic financial primitive that could be useful in
+various decentralized financial systems. I'd like to present an alternative to
+the Dutch auction that may be more suitable in certain situations.
+
+Let's first define the basic behavior we want from our auction. Then we'll be
+able to see which steps would break if implemented naively, and how to address
+those problems. Let's frame it in the context of a currency auction, where
+currency A is being sold for currency B:
+
+Multiunit auctions
+Designing an auction smart contract
+
+
+
+
+A higher z/y ratio means a higher price (in
+B) is being offered by the user, so step 3 ensures that the
+winners of the auction are the highest bidders, as we'd expect.
+
+To make this all a little more concrete, let's define an interface we might +expect from an auction smart contract: + +
+interface IAuction { + function start(uint pot); + function bid(uint sellAmount, uint buyAmount) returns (uint bidId); + function end(); + function fill(uint bidId); +} ++ + +
+The above is very similar to + + an example from the Solidity tutorial + +showing how to build a single unit auction. The big difference is that when +making a bid, we specify how much we're willing to pay and how many +units of the auctioned asset we're bidding +on.2 +
+ ++The problem, as hinted at earlier, is that we can't just sort the list of bids +and go through it, bid by bid, when implementing step 3 of our protocol. Sorting +is an O(nlogn) operation, and even the linear scan +would greatly limit the allowable number of participants in an auction given +block gas limits. +
+ ++The solution to this problem, as is often the case in computer science, comes in +the shape of a tree. +
+ +
+We'll have bid
store bids in a balanced binary search tree. The
+in-order traversal of the resulting tree gives us a sorted list of all bids.
+We're essentially having each auction participant pay the necessary gas to sort
+just their bid into the data structure.
+
+Here's a scaffold of what bid
could look like:
+
+
+contract Auction { + // Node in a red-black tree + struct Node { + uint sellAmount; + uint buyAmount; + uint left; + uint right; + uint parent; + bool red; + } + + Node[] tree; + uint root; + + function greaterThan(Node n1, Node n2) { + return n1.sellAmount * n2.buyAmount >= n1.buyAmount * n2.sellAmount; + } + + function bid(uint sellAmount, uint buyAmount) payable returns (uint bidId) { + bidId = tree.length; + tree.push(Node(sellAmount, buyAmount, 0, 0, 0, false)); + ... // do RBT insertion with greaterThan as the comparison function + } + + ... +} ++ + +
+When all bids have been submitted and sorted into the tree, we need to be able +to fill the bids of those users that submitted the highest bids. When a user +comes in and says "Hey, look at my bid, I paid z amount of +B so now I deserve y amount of A!", +we need to be able to verify that their bid actually fit into the pot and can be +filled. +
+ +
+At this point, when implementing fill
naively, we would still need
+a linear traversal to determine whether a given bidId
should be
+filled or not. To finish off this auction design, we'll augment the BST such
+that we can implement an O(logn) fill
.
+
+We'll augment each node of the tree to contain the total amount of
+buyToken
in the node's subtree. This will allow us to quickly
+calculate the amount of buyToken
in all nodes with a better price
+than a given bid.
+
+This augmentation is possible because updating this total is efficient when +updating the tree. We only need to modify the insert and rotation operations: + +
+function insert(Bid bid, uint bidId) { + uint current = tree.root; + uint parent = current; + while (current != 0) { + parent = current; + + // Increase totals on the path from root to newly inserted node + tree.[parent].total = tree[parent].total + bid.buyAmount; + + if (greaterThan(bid, tree.bids[current])) { + current = tree.bids[current].left; + } else { + current = tree.bids[current].right; + } + } + + // ... +} + +// Called at the end of each rotation operation +function recalculateTotalsAfterRotation(uint n, uint pivot) { + tree[pivot].total = tree[n].total; + tree[n].total = tree[tree.bids[n].left].total + + tree[tree[n].right].total + + tree[n].buyAmount +} ++ +
+This in our data structure gives us a logarithmic algorithm for checking how +much of the token would be bought by bidders at higher exchange rates. We walk +up from our node to the root. Any time we step up from a right child, we'll add +the total from our sibling's subtree and parent node's value (since, by the BST +property, those are the nodes with greater rates than our starting node). + +
+function totalFromHigherExchangeRates(uint bidId) view returns (uint) { + uint total = tree[tree[bidId].left].total; + uint parent = tree[bidId].parent; + uint currentNode = bidId; + + while (parent != 0) { + if (tree[parent].right == currentNode) { + total += tree[tree[parent].left].total; + total += tree[parent].buyAmount; + } + + currentNode = parent; + parent = tree[parent].parent; + } + + return total; +} ++ + +
+fill
can now use totalFromHigherExchangeRates
to
+determine whether or not a given bid should be filled. The per-bid
+computational complexity (and thus gas cost) is logarithmic with respect to the
+total number of bids.
+
+I originally came up with this auction design while working on +Celo's on-chain stability mechanism. The idea was +to use periodic currency auctions to expand or contract the supply of a token to +match demand, thus stabilizing its price. +
+ ++Later we came up with an even more clever stabilization mechanism, one that can +function continuously. It's based on Uniswap +and you can read more about it in + + this Medium post +. + +
+1. The last successful bid might only be partially filled. +This is a detail that needs to be addressed, but it's easy to do so. For +simplicity of the rest of the text, I'll avoid discussing it. +
+ ++2. For simplicity, we'll talk about a two phase, open bid +auction. As with the single unit auction, this could be modified to a hidden bid +auction by adding a commit step. +
-- cgit v1.2.3