metaplex-foundation / metaplex-program-library

Smart contracts maintained by the Metaplex team
Other
577 stars 511 forks source link

Listing Config, New Auction Instructions. #275

Closed samuelvanderwaal closed 2 years ago

samuelvanderwaal commented 2 years ago

Original design

[ausbot wrote]: Today Listings have only a trade state and an optional receipt account for tracking.

But like a phoenix from the ashes, a feature need has arisen that just could save the universe.

Tune in this summer for "Sale Configuration".

To accomplish sale configuration correctly we must make some new instructions

ListForSale, Bid, CancelListing, CancelBid, ExecuteAuctionSale,

We also must add more configuration to the AuctionHouse struct

AuctionConfig: {
track_higest_bid: bool,
restrict_high_bidder_cancelation: bool,
can_cancel_timed_auction: bool,
}

pda [AUTH, sale_authority, auction_house]

SaleAuthority: {
}

In this new nomenclature "Sell" and "Buy" instructions remain but we indicate those IXes are used for the auction house "instant_sale" semantics.

List For Sale

A new instruction to allow an item to be listed for sale on an auction, not an instant sale "ListForSale" should support the following additional Sell args:

min_price: u64 ends_at: u128(I think) if 0 then not set sale_authority_must_sign: bool

These args must last in an account holding the "SaleConfig" struct

zero_copy
pub struct SaleConfig{
min_price: u64
ends_at: u128(I think) if 0 then not set
sale_authority_must_sign: bool,
highest_bid: Bid{
amount: u64,
ts: PubKey
}
}

If the sale price is 0 then allow the auction house or sale authority to execute trade states for a sale as long as

The authority can execute sale <--- sale authority The price is above the min price <--- min price The timer is up <--- times auction The bid being executed is the highest bid <--- track_highest_bids

Bid

Allows a bidder to use the same buyer escrow to "bid" on an auction item. This bid if the first or higher that the current highest bid will be saved in the highest bid field on the ListingConfig.

Combining this with the Receipts and we can have a UI showing all the bids and which one is the highest

CancelListing

Cancels the Listing if the auction_house configuration allows and returns rent lamports to the feepayer or seller

CancelBid

Cancels the bit on an auction if allowed but auction_house configuration

ExecuteAuctionSale

This IX is almost the same as ExecuteSale except it enforces the Auction semantics and configuration fields specified in AuctionHouse Config and SaleConfig.

We need a new instruction here to ensure that saleconfig is passed in without having to deal with remaining accounts

dborstelmann commented 2 years ago

This looks awesome. The design above looks to be exactly what we need. If we get this soonish we will use on our platform.

We will use all new instructions.

Couple notes:

Please loop me in if you have changes or need any more info.

kespinola commented 2 years ago

What does it look like if the different sales mechanisms are different programs and auction house manages the escrow accounts, transfer of nfts to winner, and payout to creators/ah?

Not looking to create program dependency nightmare but have 2 program layers. 1 for managing price discovery (ie auctions) and another for completing sales (auction house).

samuelvanderwaal commented 2 years ago

What does it look like if the different sales mechanisms are different programs and auction house manages the escrow accounts, transfer of nfts to winner, and payout to creators/ah?

Not looking to create program dependency nightmare but have 2 program layers. 1 for managing price discovery (ie auctions) and another for completing sales (auction house).

As I understand Austin's design here, you could still have an external program manage advanced auction settings by putting its signing address in the approved_sale_authorites vec and setting all SalesConfig options to 0 except for sale_authority_must_sign which would be true.

kespinola commented 2 years ago

Can the change to ah be adding approved_sale_authorties (maybe as as its own pda so no limit on the number of sales authorities associated to auction house). And then building out the different sales engines as independent programs.

enum AuthorityScope {
  Sale,
  ExecuteSale,
  PublicBid,
  Buy,
}

SaleAuthority {
  authority: Pubkey,
  auction_house: Pubkey,
  scopes: Vec<AuctionHouseScope>
}

Items like the auction config and sales config are managed by the sales engine programs. The program can track tradestates as it sees fit but has the permission the auction house to call ah instructions as if it was the authority with can_change_sale_price.

Core interactions to the ah can potentially be bundled into rust package for unify the experience of placing bids, putting item for listing, and executing sales.

kespinola commented 2 years ago

When an NFT is put up for sale by a user its delegate is set to the sale authority program this ensures it maintains authority of the NFT during the time of the auction.

Once its time to execute the sale the delegate is transferred to ah program and then makes cpi call to execute_sale on ah.

We want to do dutch style auctions. I'm going to write out complete lifecycle of dutch-auction-program that uses ah as the escrow, payment engine, and bid tracking. Lets see how it goes :)

austbot commented 2 years ago

This looks awesome. The design above looks to be exactly what we need. If we get this soonish we will use on our platform.

We will use all new instructions.

Couple notes:

* Perfect: `track_higest_bid` `restrict_high_bidder_cancelation`

* I'm guessing we still need to manually cancel lower bids with off chain processes that return funds

* The timer is great

Please loop me in if you have changes or need any more info.

yes there is a race condition in any of these things where a high volume auction will see highest bidder rapidly changing and since we need to pass in all accounts we could be passing a 2 bids old bidders bid to cancel

austbot commented 2 years ago

When an NFT is put up for sale by a user its delegate is set to the sale authority program this ensures it maintains authority of the NFT during the time of the auction.

Once its time to execute the sale the delegate is transferred to ah program and then makes cpi call to execute_sale on ah.

We want to do dutch style auctions. I'm going to write out complete lifecycle of dutch-auction-program that uses ah as the escrow, payment engine, and bid tracking. Lets see how it goes :)

This will not work, the delegate needs to remain with Auction House especially for cross AH liquidity and freezing. The Sale Authority hoeverwould be the only signer that can ExecuteAuctionSale

austbot commented 2 years ago

Can the change to ah be adding approved_sale_authorties (maybe as as its own pda so no limit on the number of sales authorities associated to auction house). And then building out the different sales engines as independent programs.

enum AuthorityScope {
  Sale,
  ExecuteSale,
  PublicBid,
  Buy,
}

SaleAuthority {
  authority: Pubkey,
  auction_house: Pubkey,
  scopes: Vec<AuctionHouseScope>
}

Items like the auction config and sales config are managed by the sales engine programs. The program can track tradestates as it sees fit but has the permission the auction house to call ah instructions as if it was the authority with can_change_sale_price.

Core interactions to the ah can potentially be bundled into rust package for unify the experience of placing bids, putting item for listing, and executing sales.

I think we can do sale authorities as pdas yes similar to the "delegated authority" pattern we have used elsewhere but I don't see what is different in your proposal. The only thing I think you are adding is that 100% of listing config is managed by another program. This is doable but we wanted to provide the 90% of use cases the most simple model

kespinola commented 2 years ago

Background

The auction house program is currently capable of matching sellers with buyers and facilitating the transfer of funds for an NFT. However, the auction house program does not provide any price discovery mechanisms, such as an auction. It does allow the the authority of the auction house to “pick” a price for a seller via can_change_sale_price and free trade state constructs. The free trade state represents an unspecified listing by a seller. This combined with the can_change_sale_price setting of the auction house allows price discovery to be done off-chain.

Goals

Allow sellers of NFTs to run a timed auction on-chain.

Auction house allows for the registration of sales authorities which give “authority” like permissions to to third party programs. The auction house program continues to govern escrow funds, payout sales, and send NFTs to buyers.

Requirements

Sales Authority

// seed: ["sale_authority", {auction_house_address}, {authority_progam}]
pub struct SaleAuthority {
  authority_program: Pubkey,
    auction_house: Pubkey,
}

The authority of auction house can bestow to another program the ability to execute instructions on behalf of the authority.

Auction Program

This is a simple auction example where a seller can put a single NFT up for sale at some start time and a desired ending time with min price. At the end of the auction a permission-less crank will facilitate the sending of funds and NFT through the auction house program.

// seed: ["auction", {seller}, {metadata}, {auction_house}, {start_at}]
pub struct Auction {
  auction_house: Pubkey,
  metadata: Pubkey,
  seller: Pubkey,
  start_at: i64,
    end_at: i64,
  min_price: u64,
  winner: Option<Pubkey>,
  current_bid: Option<u64>,
  bid_at: i64,
}

// seed: ["bid_history", {bidder}, {auction}, {amount}]
pub struct BidHistory {
  bidder: Pubkey,
  auction: Pubkey,
  amount: u64,
  bid_at: i64,
}

Instructions

Questions

Benefits

Drawbacks

austbot commented 2 years ago
How should the NFT be locked so it can’t be sent to another wallet or listed for sale with another program?

We will implement the freeze authority system soon

dborstelmann commented 2 years ago

Background

The auction house program is currently capable of matching sellers with buyers and facilitating the transfer of funds for an NFT. However, the auction house program does not provide any price discovery mechanisms, such as an auction. It does allow the the authority of the auction house to “pick” a price for a seller via can_change_sale_price and free trade state constructs. The free trade state represents an unspecified listing by a seller. This combined with the can_change_sale_price setting of the auction house allows price discovery to be done off-chain.

Goals

Allow sellers of NFTs to run a timed auction on-chain.

Auction house allows for the registration of sales authorities which give “authority” like permissions to to third party programs. The auction house program continues to govern escrow funds, payout sales, and send NFTs to buyers.

Requirements

  • The auction house authority can create a sales authority PDA to assign to the auction house which is scoped to execute instructions on behalf of the authority.
  • A program for managing auction mechanics and interacting with auction house for escrow, funds payout, and NFT transferring.

Sales Authority

// seed: ["sale_authority", {auction_house_address}, {authority_progam}]
pub struct SaleAuthority {
  authority_program: Pubkey,
  auction_house: Pubkey,
}

The authority of auction house can bestow to another program the ability to execute instructions on behalf of the authority.

Auction Program

This is a simple auction example where a seller can put a single NFT up for sale at some start time and a desired ending time with min price. At the end of the auction a permission-less crank will facilitate the sending of funds and NFT through the auction house program.

// seed: ["auction", {seller}, {metadata}, {auction_house}, {start_at}]
pub struct Auction {
  auction_house: Pubkey,
  metadata: Pubkey,
  seller: Pubkey,
  start_at: i64,
  end_at: i64,
  min_price: u64,
  winner: Option<Pubkey>,
  current_bid: Option<u64>,
  bid_at: i64,
}

// seed: ["bid_history", {bidder}, {auction}, {amount}]
pub struct BidHistory {
  bidder: Pubkey,
  auction: Pubkey,
  amount: u64,
  bid_at: i64,
}

Instructions

  • Create Auction

    • User submits auction parameters (eg min_price, start_at, end_at, metedata, etc) which are saved to auction PDA.
    • CPI to sell the NFT with a free trade state. This sets the delegate of the NFT to auction house and allows the auction program to determine the sell price for the NFT.
  • Place Bid

    • Reject when bid is out of time bounds of the auction or the bid is lower than the min price (and or current highest bid).
    • Transfer funds from bidder to buyer escrow account of the auction on auction house. This is a “deposit” instruction on auction house. Instead managing an escrow account of each bidder the auction will have its escrow account hold any funds from bids.
    • Document the bid on the auction writing the current bidder as the winner, the bid time, and amount.
    • If there was a previous high bid empty the escrow account of the auction, send the previous bidder back the funds, and write a bid history to record the the participation in the auction.
  • Settle Auction

    • Permission-less action that requires the auction that action has ended.
    • CPI to buy and execute sale on auction house using the free trade state representing the listing within auction house.
    • The NFT is sent to the auction PDA as the proxy buyer of the NFT but within the same instruction call is transferred to the the winner of the auction.

Questions

  • How should the NFT be locked so it can’t be sent to another wallet or listed for sale with another program?

Benefits

  • The auction program holds a scoped single focus of managing escrow accounts, paying out funds, and transferring NFTs. It does not become bloated with miscellaneous auction mechanics.
  • Auction programs the proxy auction house can be tailored and swapped out by the authority of the auction house.

Drawbacks

  • Running an auction requires 2 programs. The auction and the auction house. This can be a development burden when updates require changes to both programs. However, with auction house maintaining a limited scope of capabilities it should not need to be altered when developing auction programs on top of it.

This looks absolutely perfect. Highest bid prevention (I'm guessing there is no way to cancel because it's in auction escrow) and auto refunding of lower bids. Looking forward to using it.

dborstelmann commented 2 years ago
How should the NFT be locked so it can’t be sent to another wallet or listed for sale with another program?

We will implement the freeze authority system soon

This will allow us to freeze whitelist tokens before mint date as well hopefully?

austbot commented 2 years ago

PROPOSED UPDATE TO DESIGN

After talking with internal team members and getting more time to deliver these features the metplex team is exploring a new design which was an originally proposed composition design.

The new design can be accomplished with minimal changes to the Auction House Contract:

New Design Summary

@samuelvanderwaal and @blockiosaurus will provide a more detailed design.

Metaplex will implement the scoped authority design where an auction house authority can create a delegate authority PDA with specific scopes that correspond to the instructions that will only be allowed by the authority signature.

Example: Authority X and the Main Auction house authority can take actions on the auction house. The User can take specific actions on the auction house if Authority X also signs when performing an Action with a configured scope for authority X.

Example: Highest bidder cancellation restriction is accomplished by an auction house being created with a delegated authority that must sign for the "cancel" instruction.

Then along with this feature Metaplex must release modules and reference contracts that allow these features to be composed over auction house.

We will provide in this design the following modules:

We will then create another contract that includes these module functions, then the auction house implementer will call the auction house program THROUGH this new Auction Overlay or Auctioneer program.

This design and modules will allow the community to create extremely specific and complex auction functionality while settling the auction with the core auction house protocol. This also allows metaplex to provide examples of how to compose and build new auctioneer contracts that the community can use.

this helps prevent forking, and keeps the core security of the assets being exchanged in the core auction house contract.

Examples of other auctioneer module features that could be added:

Restrict all interactions to users who hold specific nfts or tokens Include captcha gateways to prevent bid spam. Auto canceling of some bids if the number of bids is small enough. Settle multiple sales in one instruction (not a great idea, use multiple Instructions)

samuelvanderwaal commented 2 years ago

@kespinola @austbot @dborstelmann @blockiosaurus

All,

I apologize for the delay on this. I had a few unexpected things hose my week, but ultimately it's still my responsibility to keep this design moving forward and I haven't. I'm going to be working on fleshing out the design elements above from Austin and Kyle into a Notion doc this weekend and starting to write the code as well. I will present it on Monday and we can start assigning out individual pieces to whoever is able to help write the various modules.

One concern I have about the Scoped Authority design is that if the authoriity_program_id is one of the seeds for the SaleAuthority PDA then that account always has to be passed in to the auction house handlers so they can check if the SaleAuthority PDA exists to check the scope limitations. This has a security issue because someone can just pass in an invalid authority and no PDA will be found and the handlers will assume there are no scope limitations.

This can be solved by adding the delegated authority as a field to the AuctionHouse struct when the auction house instance is first created, or by removing the authority_program_id seed from the PDA derivation. Both of these have the effect of tying a single Auction House instance to one specific external authority and therefore it can only do one kind of auction at a time. I'm not sure if this is a design or usability issue in practice, or not.

Let me know if I'm missing something there.

dborstelmann commented 2 years ago

what happened here?

blockiosaurus commented 2 years ago

We are employing a composable approach using the new Auctioneer contract, as seen in this PR https://github.com/metaplex-foundation/metaplex-program-library/pull/427