Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

auction: define main dutch auction messages #4208

Merged
merged 9 commits into from
Apr 15, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
192 changes: 192 additions & 0 deletions crates/core/component/auction/src/auction/dutch.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
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
/// │
erwanor marked this conversation as resolved.
Show resolved Hide resolved
///
#[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> {
let domain_position_id: Option<position::Id> =
if let Some(pb_position_id) = msg.current_position {
Some(pb_position_id.try_into()?)
} else {
None
};
erwanor marked this conversation as resolved.
Show resolved Hide resolved

let domain_type = DutchAuctionState {
sequence: msg.seq,
current_position: domain_position_id,
cratelyn marked this conversation as resolved.
Show resolved Hide resolved
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()?,
};

Ok(domain_type)
}
}
/* ********************************** */
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ pub struct DutchAuctionDescription {
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: ::core::option::Option<super::super::super::asset::v1::AssetId>,
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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -406,7 +406,7 @@ impl serde::Serialize for DutchAuctionDescription {
if self.input.is_some() {
len += 1;
}
if self.output.is_some() {
if self.output_id.is_some() {
len += 1;
}
if self.max_output.is_some() {
Expand All @@ -431,8 +431,8 @@ impl serde::Serialize for DutchAuctionDescription {
if let Some(v) = self.input.as_ref() {
struct_ser.serialize_field("input", v)?;
}
if let Some(v) = self.output.as_ref() {
struct_ser.serialize_field("output", v)?;
if let Some(v) = self.output_id.as_ref() {
struct_ser.serialize_field("outputId", v)?;
}
if let Some(v) = self.max_output.as_ref() {
struct_ser.serialize_field("maxOutput", v)?;
Expand Down Expand Up @@ -467,7 +467,8 @@ impl<'de> serde::Deserialize<'de> for DutchAuctionDescription {
{
const FIELDS: &[&str] = &[
"input",
"output",
"output_id",
"outputId",
"max_output",
"maxOutput",
"min_output",
Expand All @@ -484,7 +485,7 @@ impl<'de> serde::Deserialize<'de> for DutchAuctionDescription {
#[allow(clippy::enum_variant_names)]
enum GeneratedField {
Input,
Output,
OutputId,
MaxOutput,
MinOutput,
StartHeight,
Expand Down Expand Up @@ -514,7 +515,7 @@ impl<'de> serde::Deserialize<'de> for DutchAuctionDescription {
{
match value {
"input" => Ok(GeneratedField::Input),
"output" => Ok(GeneratedField::Output),
"outputId" | "output_id" => Ok(GeneratedField::OutputId),
"maxOutput" | "max_output" => Ok(GeneratedField::MaxOutput),
"minOutput" | "min_output" => Ok(GeneratedField::MinOutput),
"startHeight" | "start_height" => Ok(GeneratedField::StartHeight),
Expand All @@ -541,7 +542,7 @@ impl<'de> serde::Deserialize<'de> for DutchAuctionDescription {
V: serde::de::MapAccess<'de>,
{
let mut input__ = None;
let mut output__ = None;
let mut output_id__ = None;
let mut max_output__ = None;
let mut min_output__ = None;
let mut start_height__ = None;
Expand All @@ -556,11 +557,11 @@ impl<'de> serde::Deserialize<'de> for DutchAuctionDescription {
}
input__ = map_.next_value()?;
}
GeneratedField::Output => {
if output__.is_some() {
return Err(serde::de::Error::duplicate_field("output"));
GeneratedField::OutputId => {
if output_id__.is_some() {
return Err(serde::de::Error::duplicate_field("outputId"));
}
output__ = map_.next_value()?;
output_id__ = map_.next_value()?;
}
GeneratedField::MaxOutput => {
if max_output__.is_some() {
Expand Down Expand Up @@ -613,7 +614,7 @@ impl<'de> serde::Deserialize<'de> for DutchAuctionDescription {
}
Ok(DutchAuctionDescription {
input: input__,
output: output__,
output_id: output_id__,
max_output: max_output__,
min_output: min_output__,
start_height: start_height__.unwrap_or_default(),
Expand Down
Binary file modified crates/proto/src/gen/proto_descriptor.bin.no_lfs
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ message DutchAuctionDescription {
// The value the seller wishes to auction.
asset.v1.Value input = 1;
// The asset ID of the target asset the seller wishes to acquire.
asset.v1.AssetId output = 2;
asset.v1.AssetId output_id = 2;
erwanor marked this conversation as resolved.
Show resolved Hide resolved
// The maximum output the seller can receive.
//
// This implicitly defines the starting price for the auction.
Expand Down
Loading