Dutch Auction

Introduction

A Dutch auction is one of several types of auctions for buying or selling goods. Most commonly, it refers to an auction in which the auctioneer begins with a high asking price in the case of selling and gradually lowers it until some participant accepts the price or it reaches a predetermined reserve price. A Dutch auction has also been referred to as a clock auction or open-outcry descending-price auction. This type of auction demonstrates the advantage of speed since a sale never requires more than one bid.

The auction uses Vara non-fungible tokens (VFT) as tradable goods.

This article explains the programming interface, data structure, basic functions, and their respective purposes. The program can be used as-is or modified to suit specific scenarios. Anyone can easily create an application and run it on the Gear Network. The source code is available on GitHub.

Program Description

Actions

dutch-auction/io/src/auction.rs
pub enum Action {
    /// Creates auction
    Create(CreateConfig),
    /// Buy current NFT
    Buy,
    /// Stop Auction
    ForceStop,
    /// Reward gas to NFT seller
    Reward,
}
  • Buy is an action to buy a GNFT token at the current price.
  • Create(CreateConfig) is an action to create a new auction if the previous one is over or if it's the first auction in this program.
  • ForceStop is an action to forcefully stop an auction if the program owner prefers to finish it ahead of time.

Note how Dutch Auction is composed; it allows users to reuse its functionality repeatedly.

Structures in Actions:

dutch-auction/io/src/auction.rs
pub struct CreateConfig {
    pub nft_contract_actor_id: ActorId,
    pub token_id: U256,
    pub starting_price: u128,
    pub discount_rate: u128,
    pub duration: Duration,
}

To create a new auction, these fields are required:

  • nft_contract_actor_id is the program (smart contract) address where the auctioneer's NFT has been minted.
  • token_id is the ID of the NFT within its contract.
  • starting_price is the initial price at which the auction begins and subsequently decreases.
  • discount_rate is the rate at which the price decreases per millisecond over time.
  • duration sets the duration of the auction.
dutch-auction/io/src/auction.rs
pub struct Duration {
    pub hours: u64,
    pub minutes: u64,
    pub seconds: u64,
}
  • hours is the number of hours in the period.
  • minutes is the number of minutes in the period.
  • seconds is the number of seconds in the period.

Events

dutch-auction/io/src/auction.rs
pub enum Event {
    AuctionStarted {
        token_owner: ActorId,
        price: u128,
        token_id: U256,
    },
    Bought {
        price: u128,
    },
    AuctionStopped {
        token_owner: ActorId,
        token_id: U256,
    },
    Rewarded {
        price: u128,
    },
}
  • AuctionStarted is an event that occurs when the Create(CreateConfig) action is successfully executed.
  • AuctionStopped is an event that occurs when the program owner forcibly ends the auction.

Consistency of Program States

The Dutch auction program interacts with the non-fungible token contract. Each transaction that modifies the states of the Dutch Auction and the non-fungible token is stored in the state until it is completed. A user can finalize a pending transaction by sending a message that matches the previous one while specifying the transaction ID. The idempotency of the non-fungible token contract allows for restarting a transaction without causing duplicate changes, ensuring the state consistency of these two programs.

Program Metadata and State

Metadata interface description:

dutch-auction/io/src/io.rs
pub struct AuctionMetadata;

impl Metadata for AuctionMetadata {
    type Init = ();
    type Handle = InOut<Action, Result<Event, Error>>;
    type Others = ();
    type Reply = ();
    type Signal = ();
    type State = Out<AuctionInfo>;
}

To display the full program state information, the state() function is used:

dutch-auction/src/lib.rs
#[no_mangle]
extern fn state() {
    let contract = unsafe { AUCTION.take().expect("Unexpected error in taking state") };
    msg::reply::<AuctionInfo>(contract.into(), 0)
        .expect("Failed to encode or reply with `AuctionInfo` from `state()`");
}

To display only specific values from the state, write a separate crate. In this crate, specify functions that will return the desired values from the AuctionInfo state. For example, see dutch-auction/state:

dutch-auction/state/src/lib.rs
#[gmeta::metawasm]
pub mod metafns {
    pub type State = AuctionInfo;

    pub fn info(mut state: State) -> AuctionInfo {
        if matches!(state.status, Status::IsRunning) && exec::block_timestamp() >= state.expires_at {
            state.status = Status::Expired;
        }
        state
    }
}

Source Code

The source code for this example of a Dutch Auction program and an implementation of its testing is available on GitHub.

See also an example of the program testing implementation based on gtest: gear-foundation/dapps/dutch-auction/tests.

For more details about testing programs written on Vara, refer to the Program Testing article.

On this page