makerdao / token-auction

Continuous Splitting Token Auction
GNU Affero General Public License v3.0
21 stars 21 forks source link

Stories in Ready warning: this contract is still at an early stage and is provided for reference only. DO NOT use it with real tokens as they may be permanently lost or stolen.

Continuous Splitting Token Auction

This Ethereum contract provides a set of auctions for use with standard tokens.

The auction was designed for the Maker DAO, providing a simple and passive liquidation mechanism to sell off collateral and debt. However, the contract is generic and agnostic about its user provided they adhere to the token standard.

Description

The auction provides three auction types - regular forward and reverse auctions and a composite two-way auction that switches from forward to reverse once a threshold bid is reached.

In a forward auction, bidders compete on the amount they are willing to pay for the lot.

In a reverse auction, bidders compete on the amount of lot they are willing to receive for a given payment.

A two-way auction initially behaves as a forward auction. Once a given quantity of the buy token has been bid the auction reverses and becomes a reverse auction.

The auctions are splitting. Bidders can bid on a fraction of the full lot, provided their bid is an increase in valuation. Doing so splits the auction: the previous bidder has their bid quantity reduced at the same valuation; subsequent bidders can bid on this reduced quantity or on the new split quantity. There is no limit to the number of times an auction can be split.

The splittable unit of an auction is an auctionlet. Each auction initially has a single auctionlet. Auctionlets are the object on which bidders place bids. Splitting an auctionlet produces a new auctionlet of reduced quantity and reduces the available quantity in the original auctionlet.

The auctions are continuous. The auction beneficiaries are continually rewarded as new bids are made: by increasing amounts of the buy token in a forward auction, and by increasing amounts of forgone sell token in a reverse auction. Highest bidders are continually rewarded as well: bids are locked once they have existed for a given time with no higher bids, at which point the highest bidder can claim their dues.

Usage

There are three ways of interacting with the auction contract: management, auction creation, and bidding. Auction users should subscribe to the auction events to be kept aware of auction creation, new bids, splits, and reversals.

Management

Create a new splitting auction manager:

import 'token-auction/manager.sol';
manager = new SplittingAuctionManager();

Create a new non-splitting auction manager:

manager = new AuctionManager();

In a non-splitting auction bidders must always bid on the full lot - they cannot split and bid on a sub-division. The base auctionlet is the only possible auctionlet, and all bids will happen on this.

The creator of the manager has no special permissions - they cannot shutdown or withdraw funds from the manager.

Auction creation

Auction creators connect to an active manager:

import 'token-auction/manager.sol';
manager = SplittingAuctionManager(know_manager_address);

All created auctions return a pair of uint (id , base) that uniquely identify the new auction and its base auctionlet. These identifiers are used when bidding and claiming.

Note: you will not be able to use named arguments on auction creation due to an upstream [solidity issue][name-args-issue].

Create a new forward auction:

var (id, base) = manager.newAuction( beneficiary
                                   , sell_token
                                   , buy_token
                                   , sell_amount
                                   , start_bid
                                   , min_increase
                                   , ttl
                                   )

Create a new reverse auction:

var (id, base) = manager.newReverseAuction( beneficiary
                                          , sell_token
                                          , buy_token
                                          , max_sell_amount
                                          , buy_amount
                                          , min_decrease
                                          , ttl
                                          )

Arguments are as for the forward auction with the following extras:

Create a new two-way auction:

var (id, base) = manager.newTwoWayAuction( beneficiary
                                         , sell_token
                                         , buy_token
                                         , sell_amount
                                         , start_bid
                                         , min_increase
                                         , min_decrease
                                         , ttl
                                         , collection_limit
                                         )

Arguments are as for the forward and reverse auctions with the following extras:

Auctions creators also have access to the bidding functions below.

Bidding

Auction users connect to an active manager either as a creator or as a user:

import 'token-auction/types.sol';
manager = SplittingAuctionFrontendType(known_manager_address);

Users interact with active auctions via bid and claim.

Bid on a non splitting auction:

manager.bid(id, bid_amount)

This will throw if the bid_amount is not greater than the last bid by min_increase. The bid_amount is transferred from the bidder, so they must approve it first. The excess buy_token given by the bid (over the last) is sent directly to the beneficiary.

Bid on a splitting auction:

var (new_id, split_id) = manager.bid(id, bid_amount, split_amount)

Bidding on a splitting auction returns a pair of identifiers:

Claim an elapsed auctionlet:

manager.claim(id)

This will send the sell_token associated with id to the highest bidder. claim will throw if ttl has not elapsed since the last high bid on id.

Events

Creation of a new auction:

LogNewAuction(uint indexed id, uint base_id)

Reversal of a two-way auction:

LogAuctionReversal(uint indexed auction_id)

Successful new bid on an auctionlet:

LogBid(uint indexed auctionlet_id)

Successful split of an auctionlet:

LogSplit(uint base_id, uint new_id, uint split_id)

Note that while new_id == base_id at present, this isn't guaranteed to be the case in the future and you should use new_id as the new identifier.

Advanced Usage

Multiple beneficiaries

Forward and two-way auctions can be configured to have multiple beneficiary addresses, each of which will receive, in turn, a given payout of auction rewards as higher bids come in.

Create a multiple beneficiary auction:

var (id, base) = manager.newAuction( beneficiaries
                                   , payouts
                                   , sell_token
                                   , buy_token
                                   , sell_amount
                                   , start_bid
                                   , min_increase
                                   , ttl);

Note these constraints:

The two-way auction is created similarly, with the sum of the payouts being taken to be the collection_limit:

var (id, base) = manager.newTwoWayAuction( beneficiaries
                                         , payouts
                                         , sell_token
                                         , buy_token
                                         , sell_amount
                                         , start_bid
                                         , min_increase
                                         , min_decrease
                                         , ttl
                                         );

The beneficiaries array is only used in the forward auction or in the forward part of the two-way auction.

Reverse auction refund address

In the reverse auction (or the reverse part of the two-way auction), bidders compete to receive diminishing amounts of the sell_token in return for given buy_token. As bids come in, increasing quantities of the sell_token are forgone by bidders.

The default behaviour is to refund this excess sell_token to the (first) beneficiary. It is possible for the auction creator to set the refund address arbitrarily when creating the auction.

Reverse auction:

var (id, base) = manager.newReverseAuction( beneficiary
                                          , refund
                                          , sell_token
                                          , buy_token
                                          , max_sell_amount
                                          , buy_amount
                                          , min_decrease
                                          , ttl
                                          )

Single beneficiary two-way auction:

var (id, base) = manager.newTwoWayAuction( beneficiary
                                         , refund
                                         , sell_token
                                         , buy_token
                                         , sell_amount
                                         , start_bid
                                         , min_increase
                                         , min_decrease
                                         , ttl
                                         , collection_limit
                                         )

Multi beneficiary two-way auction:

var (id, base) = manager.newTwoWayAuction( beneficiaries
                                         , payouts
                                         , refund
                                         , sell_token
                                         , buy_token
                                         , sell_amount
                                         , start_bid
                                         , min_increase
                                         , min_decrease
                                         , ttl
                                         );

Finite duration auction

The default behaviour is for a new auction to persist indefinitely, until bids have been made for all of the collateral. If there are no bids then the collateral will remain locked forever.

The forward auction can take an extra argument that determines when the auction as a whole will expire. After this time, bids will be rejected and all collateral will be claimable by its last bidder. In the case of unbid collateral the last bidder is taken to be the first beneficiary, who will receive the collateral after a call to claim.

Creating a finite-duration auction:

var (id, base) = manager.newAuction( beneficiary
                                   , sell_token
                                   , buy_token
                                   , sell_amount
                                   , start_bid
                                   , min_increase
                                   , ttl
                                   , expiration
                                   );

Gas costs

Approximate and subject to change.