From e42c0107318d03803f06061dfacb2bfdf3f75b6d Mon Sep 17 00:00:00 2001 From: Lucas Meier Date: Wed, 29 Jan 2025 15:33:20 -0800 Subject: [PATCH] Implement rust equivalent of LQT actions --- Cargo.lock | 2 + crates/core/app/src/action_handler/actions.rs | 3 + crates/core/component/funding/Cargo.toml | 1 + .../src/liquidity_tournament/action/mod.rs | 147 ++++++++++++++++++ .../funding/src/liquidity_tournament/mod.rs | 8 + .../src/liquidity_tournament/view/mod.rs | 91 +++++++++++ crates/core/transaction/Cargo.toml | 2 + crates/core/transaction/src/action.rs | 18 +++ crates/core/transaction/src/gas.rs | 41 +++++ crates/core/transaction/src/is_action.rs | 23 +++ crates/core/transaction/src/transaction.rs | 9 +- .../core/transaction/src/view/action_view.rs | 11 ++ 12 files changed, 352 insertions(+), 4 deletions(-) create mode 100644 crates/core/component/funding/src/liquidity_tournament/action/mod.rs create mode 100644 crates/core/component/funding/src/liquidity_tournament/view/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 620f5e38f2..cad652cb6f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5229,6 +5229,7 @@ dependencies = [ "penumbra-sdk-shielded-pool", "penumbra-sdk-stake", "penumbra-sdk-tct", + "penumbra-sdk-txhash", "serde", "tendermint 0.40.1", "tracing", @@ -5899,6 +5900,7 @@ dependencies = [ "penumbra-sdk-community-pool", "penumbra-sdk-dex", "penumbra-sdk-fee", + "penumbra-sdk-funding", "penumbra-sdk-governance", "penumbra-sdk-ibc", "penumbra-sdk-keys", diff --git a/crates/core/app/src/action_handler/actions.rs b/crates/core/app/src/action_handler/actions.rs index 402932b12f..93f33355e0 100644 --- a/crates/core/app/src/action_handler/actions.rs +++ b/crates/core/app/src/action_handler/actions.rs @@ -56,6 +56,7 @@ impl AppActionHandler for Action { Action::ActionDutchAuctionSchedule(action) => action.check_stateless(()).await, Action::ActionDutchAuctionEnd(action) => action.check_stateless(()).await, Action::ActionDutchAuctionWithdraw(action) => action.check_stateless(()).await, + Action::ActionLiquidityTournamentVote(_action) => todo!(), } } @@ -97,6 +98,7 @@ impl AppActionHandler for Action { Action::ActionDutchAuctionSchedule(action) => action.check_historical(state).await, Action::ActionDutchAuctionEnd(action) => action.check_historical(state).await, Action::ActionDutchAuctionWithdraw(action) => action.check_historical(state).await, + Action::ActionLiquidityTournamentVote(_action) => todo!(), } } @@ -138,6 +140,7 @@ impl AppActionHandler for Action { Action::ActionDutchAuctionSchedule(action) => action.check_and_execute(state).await, Action::ActionDutchAuctionEnd(action) => action.check_and_execute(state).await, Action::ActionDutchAuctionWithdraw(action) => action.check_and_execute(state).await, + Action::ActionLiquidityTournamentVote(_action) => todo!(), } } } diff --git a/crates/core/component/funding/Cargo.toml b/crates/core/component/funding/Cargo.toml index 932e1f6b61..fec6e3d9d7 100644 --- a/crates/core/component/funding/Cargo.toml +++ b/crates/core/component/funding/Cargo.toml @@ -54,6 +54,7 @@ penumbra-sdk-sct = {workspace = true, default-features = false} penumbra-sdk-shielded-pool = {workspace = true, default-features = false} penumbra-sdk-stake = {workspace = true, default-features = false} penumbra-sdk-tct = {workspace = true, default-features = false} +penumbra-sdk-txhash = {workspace = true, default-features = false} serde = {workspace = true, features = ["derive"]} tendermint = {workspace = true} tracing = {workspace = true} diff --git a/crates/core/component/funding/src/liquidity_tournament/action/mod.rs b/crates/core/component/funding/src/liquidity_tournament/action/mod.rs new file mode 100644 index 0000000000..c460be5086 --- /dev/null +++ b/crates/core/component/funding/src/liquidity_tournament/action/mod.rs @@ -0,0 +1,147 @@ +use anyhow::{anyhow, Context}; +use decaf377_rdsa::{Signature, SpendAuth, VerificationKey}; +use penumbra_sdk_asset::{asset::Denom, balance, Value}; +use penumbra_sdk_keys::Address; +use penumbra_sdk_proto::{core::component::funding::v1 as pb, DomainType}; +use penumbra_sdk_sct::Nullifier; +use penumbra_sdk_tct::Position; +use penumbra_sdk_txhash::{EffectHash, EffectingData}; + +use super::proof::LiquidityTournamentVoteProof; + +/// The internal body of an LQT vote, containing the intended vote and other validity information. +/// +/// c.f. [`penumbra_sdk_governance::delegator_vote::action::DelegatorVoteBody`], which is similar. +#[derive(Clone, Debug)] +pub struct LiquidityTournamentVoteBody { + /// Which asset is being incentivized. + /// + /// We use the base denom to allow filtering particular asset sources (i.e. IBC transfers)a. + pub incentivized: Denom, + /// The address that will receive any rewards for voting. + pub rewards_recipient: Address, + /// The start position of the tournament. + /// + /// This is included to allow stateless verification, but should match the epoch of the LQT. + pub start_position: Position, + /// The value being used to vote with. + /// + /// This should be the delegation tokens for a specific validator. + pub value: Value, + /// The nullifier of the note being spent. + pub nullifier: Nullifier, + /// The key that must be used to vote. + pub rk: VerificationKey, +} + +impl DomainType for LiquidityTournamentVoteBody { + type Proto = pb::LiquidityTournamentVoteBody; +} + +impl TryFrom for LiquidityTournamentVoteBody { + type Error = anyhow::Error; + + fn try_from(proto: pb::LiquidityTournamentVoteBody) -> Result { + Result::<_, Self::Error>::Ok(Self { + incentivized: proto + .incentivized + .ok_or_else(|| anyhow!("missing `incentivized`"))? + .try_into()?, + rewards_recipient: proto + .rewards_recipient + .ok_or_else(|| anyhow!("missing `rewards_recipient`"))? + .try_into()?, + start_position: proto.start_position.into(), + value: proto + .value + .ok_or_else(|| anyhow!("missing `value`"))? + .try_into()?, + nullifier: proto + .nullifier + .ok_or_else(|| anyhow!("missing `nullifier`"))? + .try_into()?, + rk: proto + .rk + .ok_or_else(|| anyhow!("missing `rk`"))? + .try_into()?, + }) + .with_context(|| format!("while parsing {}", std::any::type_name::())) + } +} + +impl From for pb::LiquidityTournamentVoteBody { + fn from(value: LiquidityTournamentVoteBody) -> Self { + Self { + incentivized: Some(value.incentivized.into()), + rewards_recipient: Some(value.rewards_recipient.into()), + start_position: value.start_position.into(), + value: Some(value.value.into()), + nullifier: Some(value.nullifier.into()), + rk: Some(value.rk.into()), + } + } +} + +/// The action used to vote in the liquidity tournament. +/// +/// This vote is towards a particular asset whose liquidity should be incentivized, +/// and is weighted by the amount of delegation tokens being expended. +#[derive(Clone, Debug)] +pub struct ActionLiquidityTournamentVote { + /// The actual body, containing the vote and other validity information. + pub body: LiquidityTournamentVoteBody, + /// An authorization over the body. + pub auth_sig: Signature, + /// A ZK proof tying in the private information for this action. + pub proof: LiquidityTournamentVoteProof, +} + +impl DomainType for ActionLiquidityTournamentVote { + type Proto = pb::ActionLiquidityTournamentVote; +} + +impl TryFrom for ActionLiquidityTournamentVote { + type Error = anyhow::Error; + + fn try_from(value: pb::ActionLiquidityTournamentVote) -> Result { + Result::<_, Self::Error>::Ok(Self { + body: value + .body + .ok_or_else(|| anyhow!("missing `body`"))? + .try_into()?, + auth_sig: value + .auth_sig + .ok_or_else(|| anyhow!("missing `auth_sig`"))? + .try_into()?, + proof: value + .proof + .ok_or_else(|| anyhow!("missing `proof`"))? + .try_into()?, + }) + .with_context(|| format!("while parsing {}", std::any::type_name::())) + } +} + +impl From for pb::ActionLiquidityTournamentVote { + fn from(value: ActionLiquidityTournamentVote) -> Self { + Self { + body: Some(value.body.into()), + auth_sig: Some(value.auth_sig.into()), + proof: Some(value.proof.into()), + } + } +} + +impl EffectingData for ActionLiquidityTournamentVote { + fn effect_hash(&self) -> EffectHash { + EffectHash::from_proto_effecting_data(&self.to_proto()) + } +} + +impl ActionLiquidityTournamentVote { + /// This action doesn't actually produce or consume value. + pub fn balance_commmitment(&self) -> balance::Commitment { + // This will be the commitment to zero. + balance::Commitment::default() + } +} diff --git a/crates/core/component/funding/src/liquidity_tournament/mod.rs b/crates/core/component/funding/src/liquidity_tournament/mod.rs index 1e185861fc..5a660f69a1 100644 --- a/crates/core/component/funding/src/liquidity_tournament/mod.rs +++ b/crates/core/component/funding/src/liquidity_tournament/mod.rs @@ -1 +1,9 @@ +mod action; +mod view; + pub mod proof; +pub use action::{ActionLiquidityTournamentVote, LiquidityTournamentVoteBody}; +pub use view::ActionLiquidityTournamentVoteView; + +/// The maximum number of allowable bytes in the denom string. +pub const LIQUIDITY_TOURNAMENT_VOTE_DENOM_MAX_BYTES: usize = 256; diff --git a/crates/core/component/funding/src/liquidity_tournament/view/mod.rs b/crates/core/component/funding/src/liquidity_tournament/view/mod.rs new file mode 100644 index 0000000000..0389535fc7 --- /dev/null +++ b/crates/core/component/funding/src/liquidity_tournament/view/mod.rs @@ -0,0 +1,91 @@ +use anyhow::{anyhow, Context}; +use penumbra_sdk_proto::{core::component::funding::v1 as pb, DomainType}; +use penumbra_sdk_shielded_pool::NoteView; +use serde::{Deserialize, Serialize}; + +use super::ActionLiquidityTournamentVote; + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde( + try_from = "pb::ActionLiquidityTournamentVoteView", + into = "pb::ActionLiquidityTournamentVoteView" +)] +#[allow(clippy::large_enum_variant)] +pub enum ActionLiquidityTournamentVoteView { + Visible { + vote: ActionLiquidityTournamentVote, + note: NoteView, + }, + Opaque { + vote: ActionLiquidityTournamentVote, + }, +} + +impl DomainType for ActionLiquidityTournamentVoteView { + type Proto = pb::ActionLiquidityTournamentVoteView; +} + +impl TryFrom for ActionLiquidityTournamentVoteView { + type Error = anyhow::Error; + + fn try_from(value: pb::ActionLiquidityTournamentVoteView) -> Result { + let out: Result = match value + .liquidity_tournament_vote + .ok_or_else(|| anyhow::anyhow!("missing `liquidity_tournament_vote`"))? + { + pb::action_liquidity_tournament_vote_view::LiquidityTournamentVote::Visible( + visible, + ) => Ok(Self::Visible { + vote: visible + .vote + .ok_or_else(|| anyhow!("missing `visible.vote`"))? + .try_into()?, + note: visible + .note + .ok_or_else(|| anyhow!("missing `visible.note`"))? + .try_into()?, + }), + pb::action_liquidity_tournament_vote_view::LiquidityTournamentVote::Opaque(opaque) => { + Ok(Self::Opaque { + vote: opaque + .vote + .ok_or_else(|| anyhow!("missing `opaque.vote`"))? + .try_into()?, + }) + } + }; + out.with_context(|| format!("while parsing `{}`", std::any::type_name::())) + } +} + +impl From for pb::ActionLiquidityTournamentVoteView { + fn from(value: ActionLiquidityTournamentVoteView) -> Self { + use pb::action_liquidity_tournament_vote_view as pblqtvv; + match value { + ActionLiquidityTournamentVoteView::Visible { vote, note } => Self { + liquidity_tournament_vote: Some(pblqtvv::LiquidityTournamentVote::Visible( + pblqtvv::Visible { + vote: Some(vote.into()), + note: Some(note.into()), + }, + )), + }, + ActionLiquidityTournamentVoteView::Opaque { vote } => Self { + liquidity_tournament_vote: Some(pblqtvv::LiquidityTournamentVote::Opaque( + pblqtvv::Opaque { + vote: Some(vote.into()), + }, + )), + }, + } + } +} + +impl From for ActionLiquidityTournamentVote { + fn from(value: ActionLiquidityTournamentVoteView) -> Self { + match value { + ActionLiquidityTournamentVoteView::Visible { vote, .. } => vote, + ActionLiquidityTournamentVoteView::Opaque { vote } => vote, + } + } +} diff --git a/crates/core/transaction/Cargo.toml b/crates/core/transaction/Cargo.toml index 2b43b075f5..5a24e0fb09 100644 --- a/crates/core/transaction/Cargo.toml +++ b/crates/core/transaction/Cargo.toml @@ -15,6 +15,7 @@ parallel = [ "penumbra-sdk-shielded-pool/parallel", "penumbra-sdk-auction/parallel", "penumbra-sdk-dex/parallel", + "penumbra-sdk-funding/parallel", "penumbra-sdk-governance/parallel", "penumbra-sdk-stake/parallel", ] @@ -45,6 +46,7 @@ penumbra-sdk-community-pool = {workspace = true, default-features = false} penumbra-sdk-auction = {workspace = true, default-features = false} penumbra-sdk-dex = {workspace = true, default-features = false} penumbra-sdk-fee = {workspace = true, default-features = false} +penumbra-sdk-funding = {workspace = true, default-features = false} penumbra-sdk-governance = {workspace = true, default-features = false} penumbra-sdk-ibc = {workspace = true, default-features = false} penumbra-sdk-keys = {workspace = true, default-features = false} diff --git a/crates/core/transaction/src/action.rs b/crates/core/transaction/src/action.rs index 68a5705e8f..defede771d 100644 --- a/crates/core/transaction/src/action.rs +++ b/crates/core/transaction/src/action.rs @@ -45,6 +45,9 @@ pub enum Action { ActionDutchAuctionSchedule(ActionDutchAuctionSchedule), ActionDutchAuctionEnd(ActionDutchAuctionEnd), ActionDutchAuctionWithdraw(ActionDutchAuctionWithdraw), + ActionLiquidityTournamentVote( + penumbra_sdk_funding::liquidity_tournament::ActionLiquidityTournamentVote, + ), } impl EffectingData for Action { @@ -74,6 +77,7 @@ impl EffectingData for Action { Action::ActionDutchAuctionSchedule(a) => a.effect_hash(), Action::ActionDutchAuctionEnd(a) => a.effect_hash(), Action::ActionDutchAuctionWithdraw(a) => a.effect_hash(), + Action::ActionLiquidityTournamentVote(a) => a.effect_hash(), } } } @@ -125,6 +129,9 @@ impl Action { Action::ActionDutchAuctionWithdraw(_) => { tracing::info_span!("ActionDutchAuctionWithdraw", ?idx) } + Action::ActionLiquidityTournamentVote(_) => { + tracing::info_span!("ActionLiquidityTournamentVote", ?idx) + } } } @@ -155,6 +162,7 @@ impl Action { Action::ActionDutchAuctionSchedule(_) => 53, Action::ActionDutchAuctionEnd(_) => 54, Action::ActionDutchAuctionWithdraw(_) => 55, + Action::ActionLiquidityTournamentVote(_) => 70, } } } @@ -188,6 +196,7 @@ impl IsAction for Action { Action::ActionDutchAuctionSchedule(action) => action.balance_commitment(), Action::ActionDutchAuctionEnd(action) => action.balance_commitment(), Action::ActionDutchAuctionWithdraw(action) => action.balance_commitment(), + Action::ActionLiquidityTournamentVote(action) => action.balance_commmitment(), } } @@ -217,6 +226,7 @@ impl IsAction for Action { Action::ActionDutchAuctionSchedule(x) => x.view_from_perspective(txp), Action::ActionDutchAuctionEnd(x) => x.view_from_perspective(txp), Action::ActionDutchAuctionWithdraw(x) => x.view_from_perspective(txp), + Action::ActionLiquidityTournamentVote(x) => x.view_from_perspective(txp), } } } @@ -300,6 +310,11 @@ impl From for pb::Action { Action::ActionDutchAuctionWithdraw(inner) => pb::Action { action: Some(pb::action::Action::ActionDutchAuctionWithdraw(inner.into())), }, + Action::ActionLiquidityTournamentVote(inner) => pb::Action { + action: Some(pb::action::Action::ActionLiquidityTournamentVote( + inner.into(), + )), + }, } } } @@ -374,6 +389,9 @@ impl TryFrom for Action { pb::action::Action::ActionDutchAuctionWithdraw(inner) => { Ok(Action::ActionDutchAuctionWithdraw(inner.try_into()?)) } + pb::action::Action::ActionLiquidityTournamentVote(inner) => { + Ok(Action::ActionLiquidityTournamentVote(inner.try_into()?)) + } } } } diff --git a/crates/core/transaction/src/gas.rs b/crates/core/transaction/src/gas.rs index a2559ae5e1..a4f3afd192 100644 --- a/crates/core/transaction/src/gas.rs +++ b/crates/core/transaction/src/gas.rs @@ -4,6 +4,9 @@ use penumbra_sdk_auction::auction::dutch::actions::{ use penumbra_sdk_community_pool::{CommunityPoolDeposit, CommunityPoolOutput, CommunityPoolSpend}; use penumbra_sdk_dex::{PositionClose, PositionOpen, PositionWithdraw, Swap, SwapClaim}; use penumbra_sdk_fee::Gas; +use penumbra_sdk_funding::liquidity_tournament::{ + ActionLiquidityTournamentVote, LIQUIDITY_TOURNAMENT_VOTE_DENOM_MAX_BYTES, +}; use penumbra_sdk_ibc::IbcRelay; use penumbra_sdk_shielded_pool::{Ics20Withdrawal, Output, Spend}; use penumbra_sdk_stake::{ @@ -288,6 +291,35 @@ fn dutch_auction_withdraw_gas_cost() -> Gas { } } +fn liquidity_tournament_vote_gas_cost() -> Gas { + Gas { + block_space: + // LiquidityTournamentVoteBody body = 1; + ( + // asset.v1.Denom incentivized = 1; (restricted to MAX bytes) + LIQUIDITY_TOURNAMENT_VOTE_DENOM_MAX_BYTES as u64 + // keys.v1.Address rewards_recipient = 2; (the larger of the two exclusive fields) + + 223 + // uint64 start_position = 3; + + 8 + // asset.v1.Value value = 4; + + 48 + // sct.v1.Nullifier nullifier = 5; + + 32 + // crypto.decaf377_rdsa.v1.SpendVerificationKey rk = 6; + + 32 + ) + // crypto.decaf377_rdsa.v1.SpendAuthSignature auth_sig = 2; + + 64 + // ZKLiquidityTournamentVoteProof proof = 3; + + ZKPROOF_SIZE, + // For the rest, c.f. `delegator_vote_gas_cost` + compact_block_space: 0, + verification: 1000, + execution: 10, + } +} + impl GasCost for Transaction { fn gas_cost(&self) -> Gas { self.actions().map(GasCost::gas_cost).sum() @@ -376,6 +408,9 @@ impl GasCost for Action { Action::ActionDutchAuctionWithdraw(action_dutch_auction_withdraw) => { action_dutch_auction_withdraw.gas_cost() } + Action::ActionLiquidityTournamentVote(action_liquidity_tournament_vote) => { + action_liquidity_tournament_vote.gas_cost() + } } } } @@ -651,3 +686,9 @@ impl GasCost for ActionDutchAuctionWithdraw { dutch_auction_withdraw_gas_cost() } } + +impl GasCost for ActionLiquidityTournamentVote { + fn gas_cost(&self) -> Gas { + liquidity_tournament_vote_gas_cost() + } +} diff --git a/crates/core/transaction/src/is_action.rs b/crates/core/transaction/src/is_action.rs index b985f56e0a..50eb8aa54f 100644 --- a/crates/core/transaction/src/is_action.rs +++ b/crates/core/transaction/src/is_action.rs @@ -14,6 +14,9 @@ use penumbra_sdk_dex::{ swap::{Swap, SwapCiphertext, SwapView}, swap_claim::{SwapClaim, SwapClaimView}, }; +use penumbra_sdk_funding::liquidity_tournament::{ + ActionLiquidityTournamentVote, ActionLiquidityTournamentVoteView, +}; use penumbra_sdk_governance::{ DelegatorVote, DelegatorVoteView, ProposalDepositClaim, ProposalSubmit, ProposalWithdraw, ValidatorVote, VotingReceiptToken, @@ -498,3 +501,23 @@ impl IsAction for ActionDutchAuctionWithdraw { ActionView::ActionDutchAuctionWithdraw(view) } } + +impl IsAction for ActionLiquidityTournamentVote { + fn balance_commitment(&self) -> balance::Commitment { + self.balance_commmitment() + } + + fn view_from_perspective(&self, txp: &TransactionPerspective) -> ActionView { + let lqt_vote_view = match txp.spend_nullifiers.get(&self.body.nullifier) { + Some(note) => ActionLiquidityTournamentVoteView::Visible { + vote: self.to_owned(), + note: txp.view_note(note.to_owned()), + }, + None => ActionLiquidityTournamentVoteView::Opaque { + vote: self.to_owned(), + }, + }; + + ActionView::ActionLiquidityTournamentVote(lqt_vote_view) + } +} diff --git a/crates/core/transaction/src/transaction.rs b/crates/core/transaction/src/transaction.rs index 1307a1a428..d7154e9c72 100644 --- a/crates/core/transaction/src/transaction.rs +++ b/crates/core/transaction/src/transaction.rs @@ -272,10 +272,11 @@ impl Transaction { | Action::Ics20Withdrawal(_) | Action::CommunityPoolSpend(_) | Action::CommunityPoolOutput(_) - | Action::CommunityPoolDeposit(_) => {} - Action::ActionDutchAuctionSchedule(_) => {} - Action::ActionDutchAuctionEnd(_) => {} - Action::ActionDutchAuctionWithdraw(_) => {} + | Action::CommunityPoolDeposit(_) + | Action::ActionDutchAuctionSchedule(_) + | Action::ActionDutchAuctionEnd(_) + | Action::ActionDutchAuctionWithdraw(_) + | Action::ActionLiquidityTournamentVote(_) => {} } } diff --git a/crates/core/transaction/src/view/action_view.rs b/crates/core/transaction/src/view/action_view.rs index d06f98d2ed..17a3f45bc0 100644 --- a/crates/core/transaction/src/view/action_view.rs +++ b/crates/core/transaction/src/view/action_view.rs @@ -8,6 +8,7 @@ use penumbra_sdk_dex::{ swap::SwapView, swap_claim::SwapClaimView, }; +use penumbra_sdk_funding::liquidity_tournament::ActionLiquidityTournamentVoteView; use penumbra_sdk_governance::{ ProposalDepositClaim, ProposalSubmit, ProposalWithdraw, ValidatorVote, }; @@ -53,6 +54,7 @@ pub enum ActionView { ActionDutchAuctionSchedule(ActionDutchAuctionScheduleView), ActionDutchAuctionEnd(ActionDutchAuctionEnd), ActionDutchAuctionWithdraw(ActionDutchAuctionWithdrawView), + ActionLiquidityTournamentVote(ActionLiquidityTournamentVoteView), } impl DomainType for ActionView { @@ -102,6 +104,9 @@ impl TryFrom for ActionView { AV::ActionDutchAuctionWithdraw(x) => { ActionView::ActionDutchAuctionWithdraw(x.try_into()?) } + AV::ActionLiquidityTournamentVote(x) => { + ActionView::ActionLiquidityTournamentVote(x.try_into()?) + } }, ) } @@ -140,6 +145,9 @@ impl From for pbt::ActionView { ActionView::ActionDutchAuctionWithdraw(x) => { AV::ActionDutchAuctionWithdraw(x.into()) } + ActionView::ActionLiquidityTournamentVote(x) => { + AV::ActionLiquidityTournamentVote(x.into()) + } }), } } @@ -176,6 +184,9 @@ impl From for Action { ActionView::ActionDutchAuctionWithdraw(x) => { Action::ActionDutchAuctionWithdraw(x.into()) } + ActionView::ActionLiquidityTournamentVote(x) => { + Action::ActionLiquidityTournamentVote(x.into()) + } } } }