Skip to content

Commit

Permalink
auction: define main dutch auction messages (#4208)
Browse files Browse the repository at this point in the history
## Describe your changes

This PR adds the main domain types and protobuf definitions for dutch
auction support.

## Issue ticket number and link

Part of #4206 

## Checklist before requesting a review

- [x] If this code contains consensus-breaking changes, I have added the
"consensus-breaking" label. Otherwise, I declare my belief that there
are not consensus-breaking changes, for the following reason:

> Component is not hooked to the app yet and this just adds
messages/types.

---------

Signed-off-by: Erwan Or <[email protected]>
Co-authored-by: Henry de Valence <[email protected]>
Co-authored-by: katelyn martin <[email protected]>
  • Loading branch information
3 people authored Apr 15, 2024
1 parent 2d9c784 commit 2aa7094
Show file tree
Hide file tree
Showing 6 changed files with 890 additions and 18 deletions.
1 change: 1 addition & 0 deletions crates/core/component/auction/src/auction.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pub mod dutch;
pub mod id;
pub mod nft;
183 changes: 183 additions & 0 deletions crates/core/component/auction/src/auction/dutch.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
use anyhow::anyhow;
use penumbra_asset::{asset, Value};
use penumbra_dex::lp::position::{self};
use penumbra_num::Amount;
use penumbra_proto::{core::component::auction::v1alpha1 as pb, DomainType};
use serde::{Deserialize, Serialize};

/// A deployed Dutch Auction, containing an immutable description
/// and stateful data about its current state.
#[derive(Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(try_from = "pb::DutchAuction", into = "pb::DutchAuction")]
pub struct DutchAuction {
pub description: DutchAuctionDescription,
pub state: DutchAuctionState,
}

/* Protobuf impls for `DutchAuction` */
impl DomainType for DutchAuction {
type Proto = pb::DutchAuction;
}

impl From<DutchAuction> for pb::DutchAuction {
fn from(domain: DutchAuction) -> Self {
pb::DutchAuction {
description: Some(domain.description.into()),
state: Some(domain.state.into()),
}
}
}

impl TryFrom<pb::DutchAuction> for DutchAuction {
type Error = anyhow::Error;

fn try_from(msg: pb::DutchAuction) -> Result<Self, Self::Error> {
Ok(DutchAuction {
description: msg
.description
.ok_or_else(|| anyhow!("DutchAuction is missing description"))?
.try_into()?,
state: msg
.state
.ok_or_else(|| anyhow!("DutchAuction is missing a state field"))?
.try_into()?,
})
}
}
/* ********************************** */

/// A description of the immutable parts of a dutch auction.
#[derive(Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(
try_from = "pb::DutchAuctionDescription",
into = "pb::DutchAuctionDescription"
)]
pub struct DutchAuctionDescription {
pub input: Value,
pub output_id: asset::Id,
pub max_output: Amount,
pub min_output: Amount,
pub start_height: u64,
pub end_height: u64,
pub step_count: u64,
pub nonce: [u8; 32],
}

/* Protobuf impls */
impl DomainType for DutchAuctionDescription {
type Proto = pb::DutchAuctionDescription;
}

impl From<DutchAuctionDescription> for pb::DutchAuctionDescription {
fn from(domain: DutchAuctionDescription) -> Self {
Self {
input: Some(domain.input.into()),
output_id: Some(domain.output_id.into()),
max_output: Some(domain.max_output.into()),
min_output: Some(domain.min_output.into()),
start_height: domain.start_height,
end_height: domain.end_height,
step_count: domain.step_count,
nonce: domain.nonce.as_slice().to_vec(),
}
}
}

impl TryFrom<pb::DutchAuctionDescription> for DutchAuctionDescription {
type Error = anyhow::Error;

fn try_from(msg: pb::DutchAuctionDescription) -> Result<Self, Self::Error> {
let d = DutchAuctionDescription {
input: msg
.input
.ok_or_else(|| anyhow!("DutchAuctionDescription message is missing input"))?
.try_into()?,
output_id: msg
.output_id
.ok_or_else(|| {
anyhow!("DutchAuctionDescription message is missing an output identifier")
})?
.try_into()?,
max_output: msg
.max_output
.ok_or_else(|| anyhow!("DutchAuctionDescription message is missing max output"))?
.try_into()?,
min_output: msg
.min_output
.ok_or_else(|| anyhow!("DutchAuctionDescription message is missing min output"))?
.try_into()?,
start_height: msg.start_height,
end_height: msg.end_height,
step_count: msg.step_count,
nonce: msg.nonce.as_slice().try_into()?,
};
Ok(d)
}
}
/* ********************************** */

/// A stateful description of a dutch auction, recording its state (via a sequence number),
/// the current position id associated to it (if any), and its amount IO.
/// # State
/// We record the state of the dutch auction via an untyped `u64` instead of an enum.
/// This futureproof support for auction types that have a richer state machine e.g. allows
/// claiming a withdrawn auction multiple times, burning and minting a new withdrawn auction
/// with an incremented sequence number.
///
/// For Dutch auctions:
///
/// ┌───┐ ┌───┐ ┌───┐
/// │ 0 │───Closed──▶│ 1 │──Withdrawn─▶│ 2 │
/// └───┘ └───┘ └───┘
/// ▲
/// │
/// Opened
/// │
///
#[derive(Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(try_from = "pb::DutchAuctionState", into = "pb::DutchAuctionState")]
pub struct DutchAuctionState {
pub sequence: u64,
pub current_position: Option<position::Id>,
pub next_trigger: u64,
pub input_reserves: Amount,
pub output_reserves: Amount,
}

/* Protobuf impls for `DutchAuctionState` */
impl DomainType for DutchAuctionState {
type Proto = pb::DutchAuctionState;
}

impl From<DutchAuctionState> for pb::DutchAuctionState {
fn from(domain: DutchAuctionState) -> Self {
Self {
seq: domain.sequence,
current_position: domain.current_position.map(Into::into),
next_trigger: domain.next_trigger,
input_reserves: Some(domain.input_reserves.into()),
output_reserves: Some(domain.output_reserves.into()),
}
}
}

impl TryFrom<pb::DutchAuctionState> for DutchAuctionState {
type Error = anyhow::Error;

fn try_from(msg: pb::DutchAuctionState) -> Result<Self, Self::Error> {
Ok(DutchAuctionState {
sequence: msg.seq,
current_position: msg.current_position.map(TryInto::try_into).transpose()?,
next_trigger: msg.next_trigger,
input_reserves: msg
.input_reserves
.ok_or_else(|| anyhow!("DutchAuctionState message is missing input reserves"))?
.try_into()?,
output_reserves: msg
.output_reserves
.ok_or_else(|| anyhow!("DutchAuctionState message is missing output reserves"))?
.try_into()?,
})
}
}
/* ********************************** */
126 changes: 116 additions & 10 deletions crates/proto/src/gen/penumbra.core.component.auction.v1alpha1.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,33 @@
/// The configuration parameters for the auction component.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct AuctionParameters {}
impl ::prost::Name for AuctionParameters {
const NAME: &'static str = "AuctionParameters";
const PACKAGE: &'static str = "penumbra.core.component.auction.v1alpha1";
fn full_name() -> ::prost::alloc::string::String {
::prost::alloc::format!(
"penumbra.core.component.auction.v1alpha1.{}", Self::NAME
)
}
}
/// Genesis data for the auction component.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct GenesisContent {
/// The configuration parameters for the auction component at genesis.
#[prost(message, optional, tag = "1")]
pub params: ::core::option::Option<AuctionParameters>,
}
impl ::prost::Name for GenesisContent {
const NAME: &'static str = "GenesisContent";
const PACKAGE: &'static str = "penumbra.core.component.auction.v1alpha1";
fn full_name() -> ::prost::alloc::string::String {
::prost::alloc::format!(
"penumbra.core.component.auction.v1alpha1.{}", Self::NAME
)
}
}
/// A unique identifier for an auction, obtained from hashing a domain separator
/// along with the immutable part of an auction description.
#[allow(clippy::derive_partial_eq_without_eq)]
Expand Down Expand Up @@ -33,29 +63,105 @@ impl ::prost::Name for AuctionNft {
)
}
}
/// The configuration parameters for the auction component.
/// Describes a Dutch auction using programmatic liquidity on the DEX.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct AuctionParameters {}
impl ::prost::Name for AuctionParameters {
const NAME: &'static str = "AuctionParameters";
pub struct DutchAuctionDescription {
/// The value the seller wishes to auction.
#[prost(message, optional, tag = "1")]
pub input: ::core::option::Option<super::super::super::asset::v1::Value>,
/// The asset ID of the target asset the seller wishes to acquire.
#[prost(message, optional, tag = "2")]
pub output_id: ::core::option::Option<super::super::super::asset::v1::AssetId>,
/// The maximum output the seller can receive.
///
/// This implicitly defines the starting price for the auction.
#[prost(message, optional, tag = "3")]
pub max_output: ::core::option::Option<super::super::super::num::v1::Amount>,
/// The minimum output the seller is willing to receive.
///
/// This implicitly defines the ending price for the auction.
#[prost(message, optional, tag = "4")]
pub min_output: ::core::option::Option<super::super::super::num::v1::Amount>,
/// The block height at which the auction begins.
///
/// This allows the seller to schedule an auction at a future time.
#[prost(uint64, tag = "5")]
pub start_height: u64,
/// The block height at which the auction ends.
///
/// Together with `start_height`, `max_output`, and `min_output`,
/// this implicitly defines the speed of the auction.
#[prost(uint64, tag = "6")]
pub end_height: u64,
/// The number of discrete price steps to use for the auction.
///
/// `end_height - start_height` must be a multiple of `step_count`.
#[prost(uint64, tag = "7")]
pub step_count: u64,
/// A random nonce used to allow identical auctions to have
/// distinct auction IDs.
#[prost(bytes = "vec", tag = "8")]
pub nonce: ::prost::alloc::vec::Vec<u8>,
}
impl ::prost::Name for DutchAuctionDescription {
const NAME: &'static str = "DutchAuctionDescription";
const PACKAGE: &'static str = "penumbra.core.component.auction.v1alpha1";
fn full_name() -> ::prost::alloc::string::String {
::prost::alloc::format!(
"penumbra.core.component.auction.v1alpha1.{}", Self::NAME
)
}
}
/// Genesis data for the auction component.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct GenesisContent {
/// The configuration parameters for the auction component at genesis.
pub struct DutchAuctionState {
/// The sequence number of the auction state.
///
/// Dutch auctions move from:
/// 0 (opened) => 1 (closed) => n (withdrawn)
#[prost(uint64, tag = "1")]
pub seq: u64,
/// If present, the current position controlled by this auction.
#[prost(message, optional, tag = "2")]
pub current_position: ::core::option::Option<super::super::dex::v1::PositionId>,
/// If present, the next trigger height to step down the price.
#[prost(uint64, tag = "3")]
pub next_trigger: u64,
/// The amount of the input asset directly owned by the auction.
///
/// The auction may also own the input asset indirectly,
/// via the reserves of `current_position` if it exists.
#[prost(message, optional, tag = "4")]
pub input_reserves: ::core::option::Option<super::super::super::num::v1::Amount>,
/// The amount of the output asset directly owned by the auction.
///
/// The auction may also own the output asset indirectly,
/// via the reserves of `current_position` if it exists.
#[prost(message, optional, tag = "5")]
pub output_reserves: ::core::option::Option<super::super::super::num::v1::Amount>,
}
impl ::prost::Name for DutchAuctionState {
const NAME: &'static str = "DutchAuctionState";
const PACKAGE: &'static str = "penumbra.core.component.auction.v1alpha1";
fn full_name() -> ::prost::alloc::string::String {
::prost::alloc::format!(
"penumbra.core.component.auction.v1alpha1.{}", Self::NAME
)
}
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct DutchAuction {
/// The immutable data describing the auction and its auction ID.
#[prost(message, optional, tag = "1")]
pub params: ::core::option::Option<AuctionParameters>,
pub description: ::core::option::Option<DutchAuctionDescription>,
/// The mutable data describing the auction's execution.
#[prost(message, optional, tag = "2")]
pub state: ::core::option::Option<DutchAuctionState>,
}
impl ::prost::Name for GenesisContent {
const NAME: &'static str = "GenesisContent";
impl ::prost::Name for DutchAuction {
const NAME: &'static str = "DutchAuction";
const PACKAGE: &'static str = "penumbra.core.component.auction.v1alpha1";
fn full_name() -> ::prost::alloc::string::String {
::prost::alloc::format!(
Expand Down
Loading

0 comments on commit 2aa7094

Please sign in to comment.