Skip to content

Commit

Permalink
auction: define and record events (#4353)
Browse files Browse the repository at this point in the history
## Describe your changes

This PR adds three messages to the `v1alpha1` auction interface:
- `EventDutchAuctionScheduled`
- `EventDutchAuctionUpdated`
- `EventDutchAuctionEnded`

## Issue ticket number and link

Fixes #4303.

## 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:

> this only makes additive changes, recording events in the auction
component.
  • Loading branch information
erwanor authored May 8, 2024
1 parent a1b234d commit 099e9ea
Show file tree
Hide file tree
Showing 9 changed files with 662 additions and 6 deletions.
Binary file modified crates/cnidarium/src/gen/proto_descriptor.bin.no_lfs
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ use anyhow::{ensure, Result};
use async_trait::async_trait;
use cnidarium::StateWrite;
use cnidarium_component::ActionHandler;
use penumbra_proto::StateWriteProto;

use crate::auction::dutch::ActionDutchAuctionEnd;
use crate::component::AuctionStoreRead;
use crate::component::DutchAuctionManager;
use crate::event;

use anyhow::{bail, Context};

Expand Down Expand Up @@ -35,7 +37,17 @@ impl ActionHandler for ActionDutchAuctionEnd {
auction.state.sequence
);

// Keep a copy of the auction state for the event.
let auction_state = auction.state.clone();

// Terminate the auction
state.close_auction(auction).await?;
// Emit an event, tracing the reason for the auction ending.
state.record_proto(event::dutch_auction_closed_by_user(
auction_id,
auction_state,
));

Ok(())
}
}
29 changes: 26 additions & 3 deletions crates/core/component/auction/src/component/dutch_auction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::auction::dutch::{DutchAuction, DutchAuctionDescription, DutchAuctionS
use crate::auction::AuctionId;
use crate::component::trigger_data::TriggerData;
use crate::component::AuctionStoreRead;
use crate::state_key;
use crate::{event, state_key};
use anyhow::Result;
use async_trait::async_trait;
use cnidarium::{StateRead, StateWrite};
Expand Down Expand Up @@ -61,12 +61,17 @@ pub(crate) trait DutchAuctionManager: StateWrite {
output_reserves: Amount::zero(),
};

let dutch_auction = DutchAuction { description, state };
let dutch_auction = DutchAuction {
description: description.clone(),
state,
};

// Set the triggger
self.set_trigger_for_dutch_id(auction_id, next_trigger);
// Write position to state
self.write_dutch_auction_state(dutch_auction);
// Emit an event
self.record_proto(event::dutch_auction_schedule_event(auction_id, description));
}

/// Execute the [`DutchAuction`] associated with [`AuctionId`], ticking its
Expand Down Expand Up @@ -171,10 +176,15 @@ pub(crate) trait DutchAuctionManager: StateWrite {
.compute_step_index(trigger_height)
.expect("trigger data is validated");

// We want to track the reason for the auction ending, so that we can emit
// an event with the appropriate context.
let is_auction_expired = step_index >= step_count;
let is_auction_filled = new_dutch_auction.state.input_reserves == Amount::zero();

// Termination conditions:
// 1. We have reached the `step_count` (= `end_height`)
// 2. There are no more input reserves.
if step_index >= step_count || new_dutch_auction.state.input_reserves == Amount::zero() {
if is_auction_expired || is_auction_filled {
// If the termination condition has been reached, we set the auction
// sequence to 1 (Closed).
new_dutch_auction.state.sequence = 1;
Expand All @@ -198,7 +208,20 @@ pub(crate) trait DutchAuctionManager: StateWrite {
self.set_trigger_for_dutch_id(auction_id, next_trigger);
};

// Keep a copy of the auction state for the event.
let auction_state = new_dutch_auction.state.clone();

// Write back the new auction state.
self.write_dutch_auction_state(new_dutch_auction);

// Emit an execution/termination event with the relevant context.
if is_auction_expired {
self.record_proto(event::dutch_auction_expired(auction_id, auction_state));
} else if is_auction_filled {
self.record_proto(event::dutch_auction_exhausted(auction_id, auction_state))
} else {
self.record_proto(event::dutch_auction_updated(auction_id, auction_state));
}
Ok(())
}

Expand Down
62 changes: 59 additions & 3 deletions crates/core/component/auction/src/event.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,61 @@
use tendermint::abci::{Event, EventAttributeIndexExt};
use crate::auction::dutch::{DutchAuctionDescription, DutchAuctionState};
use crate::auction::AuctionId;
use penumbra_proto::penumbra::core::component::auction::v1alpha1 as pb;

pub fn stub_event(delegate: &Delegate) -> Event {
Event::new("stub_event", [])
/// Event for a Dutch auction that has been scheduled.
pub fn dutch_auction_schedule_event(
id: AuctionId,
description: DutchAuctionDescription,
) -> pb::EventDutchAuctionScheduled {
pb::EventDutchAuctionScheduled {
auction_id: Some(id.into()),
description: Some(description.into()),
}
}

/// Event for an execution round of a Dutch auction.
pub fn dutch_auction_updated(
id: AuctionId,
state: DutchAuctionState,
) -> pb::EventDutchAuctionUpdated {
pb::EventDutchAuctionUpdated {
auction_id: Some(id.into()),
state: Some(state.into()),
}
}

/// Event for a Dutch auction that is ending because it has been closed by its owner.
pub fn dutch_auction_closed_by_user(
id: AuctionId,
state: DutchAuctionState,
) -> pb::EventDutchAuctionEnded {
pb::EventDutchAuctionEnded {
auction_id: Some(id.into()),
state: Some(state.into()),
reason: pb::event_dutch_auction_ended::Reason::ClosedByOwner as i32,
}
}

/// Event for a Dutch auction that is ending because it has expired.
pub fn dutch_auction_expired(
id: AuctionId,
state: DutchAuctionState,
) -> pb::EventDutchAuctionEnded {
pb::EventDutchAuctionEnded {
auction_id: Some(id.into()),
state: Some(state.into()),
reason: pb::event_dutch_auction_ended::Reason::Expired as i32,
}
}

/// Event for a Dutch auction that is ending because it has been completely filled.
pub fn dutch_auction_exhausted(
id: AuctionId,
state: DutchAuctionState,
) -> pb::EventDutchAuctionEnded {
pb::EventDutchAuctionEnded {
auction_id: Some(id.into()),
state: Some(state.into()),
reason: pb::event_dutch_auction_ended::Reason::Filled as i32,
}
}
1 change: 1 addition & 0 deletions crates/core/component/auction/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#![deny(clippy::unwrap_used)]

pub mod auction;
pub mod event;
pub mod genesis;
pub mod params;
pub mod state_key;
Expand Down
102 changes: 102 additions & 0 deletions crates/proto/src/gen/penumbra.core.component.auction.v1alpha1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,108 @@ impl ::prost::Name for ActionDutchAuctionWithdrawView {
)
}
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct EventDutchAuctionScheduled {
#[prost(message, optional, tag = "1")]
pub auction_id: ::core::option::Option<AuctionId>,
#[prost(message, optional, tag = "2")]
pub description: ::core::option::Option<DutchAuctionDescription>,
}
impl ::prost::Name for EventDutchAuctionScheduled {
const NAME: &'static str = "EventDutchAuctionScheduled";
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 EventDutchAuctionUpdated {
#[prost(message, optional, tag = "1")]
pub auction_id: ::core::option::Option<AuctionId>,
#[prost(message, optional, tag = "2")]
pub state: ::core::option::Option<DutchAuctionState>,
}
impl ::prost::Name for EventDutchAuctionUpdated {
const NAME: &'static str = "EventDutchAuctionUpdated";
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 EventDutchAuctionEnded {
#[prost(message, optional, tag = "1")]
pub auction_id: ::core::option::Option<AuctionId>,
#[prost(message, optional, tag = "2")]
pub state: ::core::option::Option<DutchAuctionState>,
#[prost(enumeration = "event_dutch_auction_ended::Reason", tag = "3")]
pub reason: i32,
}
/// Nested message and enum types in `EventDutchAuctionEnded`.
pub mod event_dutch_auction_ended {
/// The reason the auction ended.
#[derive(
Clone,
Copy,
Debug,
PartialEq,
Eq,
Hash,
PartialOrd,
Ord,
::prost::Enumeration
)]
#[repr(i32)]
pub enum Reason {
Unspecified = 0,
/// The auction ended due to reaching its terminal height.
Expired = 1,
/// The auction ran out of reserves.
Filled = 2,
/// The auction ended was terminated by the initiator.
ClosedByOwner = 3,
}
impl Reason {
/// String value of the enum field names used in the ProtoBuf definition.
///
/// The values are not transformed in any way and thus are considered stable
/// (if the ProtoBuf definition does not change) and safe for programmatic use.
pub fn as_str_name(&self) -> &'static str {
match self {
Reason::Unspecified => "REASON_UNSPECIFIED",
Reason::Expired => "REASON_EXPIRED",
Reason::Filled => "REASON_FILLED",
Reason::ClosedByOwner => "REASON_CLOSED_BY_OWNER",
}
}
/// Creates an enum from field names used in the ProtoBuf definition.
pub fn from_str_name(value: &str) -> ::core::option::Option<Self> {
match value {
"REASON_UNSPECIFIED" => Some(Self::Unspecified),
"REASON_EXPIRED" => Some(Self::Expired),
"REASON_FILLED" => Some(Self::Filled),
"REASON_CLOSED_BY_OWNER" => Some(Self::ClosedByOwner),
_ => None,
}
}
}
}
impl ::prost::Name for EventDutchAuctionEnded {
const NAME: &'static str = "EventDutchAuctionEnded";
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
)
}
}
/// Generated client implementations.
#[cfg(feature = "rpc")]
pub mod query_service_client {
Expand Down
Loading

0 comments on commit 099e9ea

Please sign in to comment.