forked from MystenLabs/sui
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Move] Added example of an auction using shared objects (MystenLabs#856)
* Added implementation of an auction using shared objects * [Move] Added example of an auction using shared objects and refactored existing code to share common parts with the other implementation * Modified shared auction implementation to conform to shared object semantics plus added (forgotten) shared auction tests * Renamed shared auction implementation * Renamed shared TicTacToe implementation * Removed development-time comments * Updated readme * Another round of changes based on review feedback * Added missing renames
- Loading branch information
Showing
9 changed files
with
359 additions
and
96 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
# DeFi | ||
|
||
* FlashLoan: a flash loan is a loan that must be initiated and repaid during the same transaction. This implementation works for any currency type, and is a good illustration of the power of Move [abilities](https://diem.github.io/move/abilities.html) and the "hot potato" design pattern. | ||
* Auction: example implementation of the [English auction](https://en.wikipedia.org/wiki/English_auction). | ||
* Auction: example implementation of the [English auction](https://en.wikipedia.org/wiki/English_auction) using single-owner objects only | ||
* SharedAuction: example implementation of the [English auction](https://en.wikipedia.org/wiki/English_auction) using shared objects | ||
* Escrow: an atomic swap leveraging an escrow agent that is trusted for liveness, but not safety (i.e., the agent cannot steal the goods being swapped). | ||
* Uniswap 1.0-style DEX (coming soon). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
130 changes: 130 additions & 0 deletions
130
sui_programmability/examples/defi/sources/AuctionLib.move
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
/// This is a helper module for implementing two versions of an | ||
/// English auction (https://en.wikipedia.org/wiki/English_auction), | ||
/// one using single-owner objects only and the other using shared | ||
/// objects. | ||
module DeFi::AuctionLib { | ||
use Std::Option::{Self, Option}; | ||
|
||
use Sui::Coin::{Self, Coin}; | ||
use Sui::GAS::GAS; | ||
use Sui::ID::{Self, ID, VersionedID}; | ||
use Sui::Transfer; | ||
use Sui::TxContext::{Self,TxContext}; | ||
|
||
friend DeFi::Auction; | ||
friend DeFi::SharedAuction; | ||
|
||
/// Stores information about an auction bid. | ||
struct BidData has store { | ||
/// Coin representing the current (highest) bid. | ||
funds: Coin<GAS>, | ||
/// Address of the highest bidder. | ||
highest_bidder: address, | ||
} | ||
|
||
/// Maintains the state of the auction owned by a trusted | ||
/// auctioneer. | ||
struct Auction<T: key + store> has key { | ||
id: VersionedID, | ||
/// Item to be sold. It only really needs to be wrapped in | ||
/// Option if Auction represents a shared object but we do it | ||
/// for single-owner Auctions for better code re-use. | ||
to_sell: Option<T>, | ||
/// Owner of the time to be sold. | ||
owner: address, | ||
/// Data representing the highest bid (starts with no bid) | ||
bid_data: Option<BidData>, | ||
} | ||
|
||
public(friend) fun auction_id<T: key + store>(auction: &Auction<T>): &ID { | ||
ID::inner(&auction.id) | ||
} | ||
|
||
public(friend) fun auction_owner<T: key + store>(auction: &Auction<T>): address { | ||
auction.owner | ||
} | ||
|
||
/// Creates an auction. This is executed by the owner of the asset to be | ||
/// auctioned. | ||
public(friend) fun create_auction<T: key + store>(id: VersionedID, to_sell: T, ctx: &mut TxContext): Auction<T> { | ||
// A question one might asked is how do we know that to_sell | ||
// is owned by the caller of this entry function and the | ||
// answer is that it's checked by the runtime. | ||
Auction<T> { | ||
id, | ||
to_sell: Option::some(to_sell), | ||
owner: TxContext::sender(ctx), | ||
bid_data: Option::none(), | ||
} | ||
} | ||
|
||
/// Updates the auction based on the information in the bid | ||
/// (update auction if higher bid received and send coin back for | ||
/// bids that are too low). | ||
public fun update_auction<T: key + store>(auction: &mut Auction<T>, bidder: address, coin: Coin<GAS>) { | ||
if (Option::is_none(&auction.bid_data)) { | ||
// first bid | ||
let bid_data = BidData { | ||
funds: coin, | ||
highest_bidder: bidder, | ||
}; | ||
Option::fill(&mut auction.bid_data, bid_data); | ||
} else { | ||
let prev_bid_data = Option::borrow(&auction.bid_data); | ||
if (Coin::value(&coin) > Coin::value(&prev_bid_data.funds)) { | ||
// a bid higher than currently highest bid received | ||
let new_bid_data = BidData { | ||
funds: coin, | ||
highest_bidder: bidder | ||
}; | ||
// update auction to reflect highest bid | ||
let BidData { funds, highest_bidder } = Option::swap(&mut auction.bid_data, new_bid_data); | ||
// transfer previously highest bid to its bidder | ||
Coin::transfer(funds, highest_bidder); | ||
} else { | ||
// a bid is too low - return funds to the bidder | ||
Coin::transfer(coin, bidder); | ||
} | ||
} | ||
} | ||
|
||
|
||
/// Ends the auction - transfers item to the currently highest | ||
/// bidder or to the original owner if no bids have been placed. | ||
fun end_auction<T: key + store>(to_sell: &mut Option<T>, owner: address, bid_data: &mut Option<BidData>) { | ||
let item = Option::extract(to_sell); | ||
if (Option::is_some<BidData>(bid_data)) { | ||
// bids have been placed - send funds to the original item | ||
// owner and the item to the highest bidder | ||
let BidData { funds, highest_bidder } = Option::extract(bid_data); | ||
Transfer::transfer(funds, owner); | ||
Transfer::transfer(item, highest_bidder); | ||
} else { | ||
// no bids placed - send the item back to the original owner | ||
Transfer::transfer(item, owner); | ||
}; | ||
} | ||
|
||
/// Ends auction and destroys auction object (can only be used if | ||
/// Auction is single-owner object) - transfers item to the | ||
/// currently highest bidder or to the original owner if no bids | ||
/// have been placed. | ||
public fun end_and_destroy_auction<T: key + store>(auction: Auction<T>) { | ||
let Auction { id, to_sell, owner, bid_data } = auction; | ||
ID::delete(id); | ||
|
||
end_auction(&mut to_sell, owner, &mut bid_data); | ||
|
||
Option::destroy_none(bid_data); | ||
Option::destroy_none(to_sell); | ||
} | ||
|
||
/// Ends auction (should only be used if Auction is a shared | ||
/// object) - transfers item to the currently highest bidder or to | ||
/// the original owner if no bids have been placed. | ||
public fun end_shared_auction<T: key + store>(auction: &mut Auction<T>) { | ||
end_auction(&mut auction.to_sell, auction.owner, &mut auction.bid_data); | ||
} | ||
|
||
|
||
} |
66 changes: 66 additions & 0 deletions
66
sui_programmability/examples/defi/sources/SharedAuction.move
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
/// This is an implementation of an English auction | ||
/// (https://en.wikipedia.org/wiki/English_auction) using shared | ||
/// objects. There are types of participants: | ||
/// - owner - this is the original owner of an item that is sold at an | ||
/// auction; the owner creates an auction and ends it the time of her | ||
/// choice | ||
/// - bidders - these are parties interested in purchasing items sold | ||
/// at an auction; similarly to the owner they have access to the | ||
/// auction object and can submit bids to change its state | ||
/// A typical lifetime of an auction looks as follows: | ||
/// - auction is created by the owner and shared with the bidders | ||
/// - bidders submit bids to try out-biding one another | ||
/// - if a submitted bid is higher than the current bid (initially | ||
/// there is no bid), the auction is updated with the current bid | ||
/// and funds representing previous highest bid are sent to the | ||
/// original owner | ||
/// - otherwise (bid is too low) the bidder's funds are sent back to | ||
/// the bidder and the auction remains unchanged | ||
/// - the owner eventually ends the auction | ||
/// - if no bids were received, the item goes back to the owner | ||
/// - otherwise the funds accumulated in the auction go to the owner | ||
/// and the item goes to the bidder that won the auction | ||
module DeFi::SharedAuction { | ||
use Sui::Coin::Coin; | ||
use Sui::GAS::GAS; | ||
use Sui::Transfer; | ||
use Sui::TxContext::{Self,TxContext}; | ||
|
||
use DeFi::AuctionLib::{Self, Auction}; | ||
|
||
// Error codes. | ||
|
||
/// An attempt to end auction by a different user than the owner | ||
const EWRONG_OWNER: u64 = 1; | ||
|
||
// Entry functions. | ||
|
||
/// Creates an auction. This is executed by the owner of the asset | ||
/// to be auctioned. | ||
public fun create_auction<T: key + store >(to_sell: T, ctx: &mut TxContext) { | ||
let auction = AuctionLib::create_auction(TxContext::new_id(ctx), to_sell, ctx); | ||
Transfer::share_object(auction); | ||
} | ||
|
||
/// Sends a bid to the auction. The result is either successful | ||
/// change of the auction state (if bid was high enough) or return | ||
/// of the funds (if the bid was too low). This is executed by a | ||
/// bidder. | ||
public fun bid<T: key + store>(coin: Coin<GAS>, auction: &mut Auction<T>, ctx: &mut TxContext) { | ||
let bidder = TxContext::sender(ctx); | ||
AuctionLib::update_auction(auction, bidder, coin); | ||
} | ||
|
||
/// Ends the auction - transfers item to the currently highest | ||
/// bidder or back to the original owner if no bids have been | ||
/// placed. This is executed by the owner of the asset to be | ||
/// auctioned. | ||
public fun end_auction<T: key + store>(auction: &mut Auction<T>, ctx: &mut TxContext) { | ||
let owner = AuctionLib::auction_owner(auction); | ||
assert!(TxContext::sender(ctx) == owner, EWRONG_OWNER); | ||
AuctionLib::end_shared_auction(auction); | ||
} | ||
|
||
} |
Oops, something went wrong.