diff --git a/Cargo.lock b/Cargo.lock index 9c9b75cda4..d0a0f70c9c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4653,6 +4653,7 @@ dependencies = [ "once_cell", "parking_lot", "penumbra-asset", + "penumbra-auction", "penumbra-community-pool", "penumbra-compact-block", "penumbra-dex", @@ -5469,6 +5470,7 @@ dependencies = [ "decaf377-fmd", "decaf377-ka", "decaf377-rdsa", + "futures", "hex", "ibc-types", "im", diff --git a/crates/bin/pcli/src/command/tx.rs b/crates/bin/pcli/src/command/tx.rs index 2803747423..8d674e79b1 100644 --- a/crates/bin/pcli/src/command/tx.rs +++ b/crates/bin/pcli/src/command/tx.rs @@ -1274,7 +1274,9 @@ impl TxCmd { .await?; app.build_and_submit_transaction(plan).await?; } - TxCmd::Position(PositionCmd::RewardClaim {}) => todo!(), + TxCmd::Position(PositionCmd::RewardClaim {}) => { + unimplemented!("deprecated, remove this") + } TxCmd::Position(PositionCmd::Replicate(replicate_cmd)) => { replicate_cmd.exec(app).await?; } diff --git a/crates/bin/pcli/src/transaction_view_ext.rs b/crates/bin/pcli/src/transaction_view_ext.rs index c2ed4b1c2c..b9d27c356d 100644 --- a/crates/bin/pcli/src/transaction_view_ext.rs +++ b/crates/bin/pcli/src/transaction_view_ext.rs @@ -378,6 +378,13 @@ impl TransactionViewExt for TransactionView { penumbra_transaction::ActionView::Delegate(_) => ["Delegation", ""], penumbra_transaction::ActionView::Undelegate(_) => ["Undelegation", ""], penumbra_transaction::ActionView::UndelegateClaim(_) => ["Undelegation Claim", ""], + penumbra_transaction::ActionView::ActionDutchAuctionSchedule(_) => todo!(), + penumbra_transaction::ActionView::ActionDutchAuctionEnd(_) => { + todo!() + } + penumbra_transaction::ActionView::ActionDutchAuctionWithdraw(_) => { + todo!() + } }; actions_table.add_row(row); diff --git a/crates/cnidarium/src/gen/proto_descriptor.bin.no_lfs b/crates/cnidarium/src/gen/proto_descriptor.bin.no_lfs index 2473a12985..d50e1bd881 100644 Binary files a/crates/cnidarium/src/gen/proto_descriptor.bin.no_lfs and b/crates/cnidarium/src/gen/proto_descriptor.bin.no_lfs differ diff --git a/crates/core/app/Cargo.toml b/crates/core/app/Cargo.toml index 3210cde095..3a3372ac51 100644 --- a/crates/core/app/Cargo.toml +++ b/crates/core/app/Cargo.toml @@ -36,6 +36,7 @@ metrics = { workspace = true } once_cell = { workspace = true } parking_lot = { workspace = true } penumbra-asset = { workspace = true, default-features = true } +penumbra-auction = { workspace = true, default-features = true } penumbra-community-pool = { workspace = true, default-features = true } penumbra-compact-block = { workspace = true, default-features = true } penumbra-dex = { workspace = true, default-features = true } diff --git a/crates/core/app/src/action_handler/actions.rs b/crates/core/app/src/action_handler/actions.rs index 99f80c8005..4d74d185db 100644 --- a/crates/core/app/src/action_handler/actions.rs +++ b/crates/core/app/src/action_handler/actions.rs @@ -48,9 +48,9 @@ impl AppActionHandler for Action { Action::CommunityPoolSpend(action) => action.check_stateless(()).await, Action::CommunityPoolOutput(action) => action.check_stateless(()).await, Action::CommunityPoolDeposit(action) => action.check_stateless(()).await, - Action::ActionDutchAuctionSchedule(_) => todo!(), - Action::ActionDutchAuctionEnd(_) => todo!(), - Action::ActionDutchAuctionWithdraw(_) => todo!(), + Action::ActionDutchAuctionSchedule(action) => action.check_stateless(()).await, + Action::ActionDutchAuctionEnd(action) => action.check_stateless(()).await, + Action::ActionDutchAuctionWithdraw(action) => action.check_stateless(()).await, } } @@ -95,9 +95,9 @@ impl AppActionHandler for Action { Action::CommunityPoolSpend(action) => action.check_historical(state).await, Action::CommunityPoolOutput(action) => action.check_historical(state).await, Action::CommunityPoolDeposit(action) => action.check_historical(state).await, - Action::ActionDutchAuctionSchedule(_) => todo!(), - Action::ActionDutchAuctionEnd(_) => todo!(), - Action::ActionDutchAuctionWithdraw(_) => todo!(), + Action::ActionDutchAuctionSchedule(action) => action.check_historical(state).await, + Action::ActionDutchAuctionEnd(action) => action.check_historical(state).await, + Action::ActionDutchAuctionWithdraw(action) => action.check_historical(state).await, } } @@ -130,9 +130,9 @@ impl AppActionHandler for Action { Action::CommunityPoolSpend(action) => action.check_and_execute(state).await, Action::CommunityPoolOutput(action) => action.check_and_execute(state).await, Action::CommunityPoolDeposit(action) => action.check_and_execute(state).await, - Action::ActionDutchAuctionSchedule(_) => todo!(), - Action::ActionDutchAuctionEnd(_) => todo!(), - Action::ActionDutchAuctionWithdraw(_) => todo!(), + 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, } } } diff --git a/crates/core/app/src/action_handler/actions/submit.rs b/crates/core/app/src/action_handler/actions/submit.rs index 4bfb0282d2..1fddfed9cd 100644 --- a/crates/core/app/src/action_handler/actions/submit.rs +++ b/crates/core/app/src/action_handler/actions/submit.rs @@ -116,10 +116,10 @@ impl AppActionHandler for ProposalSubmit { | CommunityPoolSpend(_) | CommunityPoolOutput(_) | Ics20Withdrawal(_) - | CommunityPoolDeposit(_) => {} - ActionDutchAuctionSchedule(_) => todo!(), - ActionDutchAuctionEnd(_) => todo!(), - ActionDutchAuctionWithdraw(_) => todo!(), + | CommunityPoolDeposit(_) + | ActionDutchAuctionSchedule(_) + | ActionDutchAuctionEnd(_) + | ActionDutchAuctionWithdraw(_) => {} } } } diff --git a/crates/core/app/tests/app_can_deposit_into_community_pool.rs b/crates/core/app/tests/app_can_deposit_into_community_pool.rs new file mode 100644 index 0000000000..ecdee26f92 --- /dev/null +++ b/crates/core/app/tests/app_can_deposit_into_community_pool.rs @@ -0,0 +1,129 @@ +use { + self::common::BuilderExt, + anyhow::anyhow, + cnidarium::TempStorage, + penumbra_app::{genesis::AppState, server::consensus::Consensus}, + penumbra_asset::asset, + penumbra_community_pool::{CommunityPoolDeposit, StateReadExt}, + penumbra_keys::test_keys, + penumbra_mock_client::MockClient, + penumbra_mock_consensus::TestNode, + penumbra_num::Amount, + penumbra_proto::DomainType, + penumbra_shielded_pool::SpendPlan, + penumbra_transaction::{TransactionParameters, TransactionPlan}, + rand_core::OsRng, + std::collections::BTreeMap, + tap::{Tap, TapFallible}, + tracing::info, +}; + +mod common; + +/// Exercises that the app can deposit a note into the community pool. +#[tokio::test] +async fn app_can_deposit_into_community_pool() -> anyhow::Result<()> { + // Install a test logger, and acquire some temporary storage. + let guard = common::set_tracing_subscriber(); + let storage = TempStorage::new().await?; + + // Define our application state, and start the test node. + let mut test_node = { + let app_state = AppState::default(); + let consensus = Consensus::new(storage.as_ref().clone()); + TestNode::builder() + .single_validator() + .with_penumbra_auto_app_state(app_state)? + .init_chain(consensus) + .await + .tap_ok(|e| tracing::info!(hash = %e.last_app_hash_hex(), "finished init chain"))? + }; + + // Sync the mock client, using the test wallet's spend key, to the latest snapshot. + let client = MockClient::new(test_keys::SPEND_KEY.clone()) + .with_sync_to_storage(&storage) + .await? + .tap(|c| info!(client.notes = %c.notes.len(), "mock client synced to test storage")); + + // Take one of the test wallet's notes, and prepare to deposit it in the community pool. + let note = client + .notes + .values() + .cloned() + .next() + .ok_or_else(|| anyhow!("mock client had no note"))?; + + // Create a community pool transaction. + let mut plan = { + let value = note.value(); + let spend = SpendPlan::new( + &mut OsRng, + note.clone(), + client + .position(note.commit()) + .ok_or_else(|| anyhow!("input note commitment was unknown to mock client"))?, + ) + .into(); + let deposit = CommunityPoolDeposit { value }.into(); + TransactionPlan { + actions: vec![spend, deposit], + // Now fill out the remaining parts of the transaction needed for verification: + memo: None, + detection_data: None, // We'll set this automatically below + transaction_parameters: TransactionParameters { + chain_id: TestNode::<()>::CHAIN_ID.to_string(), + ..Default::default() + }, + } + }; + plan.populate_detection_data(OsRng, 0); + let tx = client.witness_auth_build(&plan).await?; + + // Execute the transaction, applying it to the chain state. + let pre_tx_snapshot = storage.latest_snapshot(); + test_node + .block() + .with_data(vec![tx.encode_to_vec()]) + .execute() + .await?; + let post_tx_snapshot = storage.latest_snapshot(); + + // Assert that the community pool balance looks correct for the deposited asset id, and that + // other amounts were not affected by the deposit. + { + type Balance = BTreeMap; + + let id = note.asset_id(); + let pre_tx_balance = pre_tx_snapshot.community_pool_balance().await?; + let post_tx_balance = post_tx_snapshot.community_pool_balance().await?; + + let get_balance_for_id = |balance: &Balance| balance.get(&id).copied().unwrap_or_default(); + let pre_tx_amount = get_balance_for_id(&pre_tx_balance); + let post_tx_amount = get_balance_for_id(&post_tx_balance); + assert_eq!( + pre_tx_amount + note.amount(), + post_tx_amount, + "community pool balance should include the deposited note" + ); + + let count_other_assets_in_pool = |balance: &Balance| { + balance + .into_iter() + // Skip the amount for our note's asset id. + .filter(|(&entry_id, _)| entry_id != id) + .map(|(_, &amount)| amount) + .sum::() + }; + assert_eq!( + count_other_assets_in_pool(&pre_tx_balance), + count_other_assets_in_pool(&post_tx_balance), + "other community pool balance amounts should not have changed" + ); + } + + // Free our temporary storage. + Ok(()) + .tap(|_| drop(test_node)) + .tap(|_| drop(storage)) + .tap(|_| drop(guard)) +} diff --git a/crates/core/app/tests/app_can_disable_community_pool_spends.rs b/crates/core/app/tests/app_can_disable_community_pool_spends.rs new file mode 100644 index 0000000000..bac8e9a281 --- /dev/null +++ b/crates/core/app/tests/app_can_disable_community_pool_spends.rs @@ -0,0 +1,370 @@ +use { + anyhow::anyhow, + cnidarium::TempStorage, + decaf377_rdsa::VerificationKey, + penumbra_app::{ + genesis::{AppState, Content}, + server::consensus::Consensus, + CommunityPoolStateReadExt as _, + }, + penumbra_community_pool::{ + CommunityPoolDeposit, CommunityPoolOutput, CommunityPoolSpend, StateReadExt as _, + }, + penumbra_governance::{ + Proposal, ProposalSubmit, StateReadExt as _, ValidatorVote, ValidatorVoteBody, + ValidatorVoteReason, + }, + penumbra_keys::{ + keys::{SpendKey, SpendKeyBytes}, + test_keys::{self}, + }, + penumbra_mock_client::MockClient, + penumbra_mock_consensus::TestNode, + penumbra_proto::{ + core::keys::v1::{GovernanceKey, IdentityKey}, + penumbra::core::component::stake::v1::Validator as PenumbraValidator, + DomainType, + }, + penumbra_shielded_pool::{genesis::Allocation, OutputPlan, SpendPlan}, + penumbra_stake::{component::validator_handler::ValidatorDataRead, DelegationToken}, + penumbra_transaction::{ + memo::MemoPlaintext, plan::MemoPlan, ActionPlan, TransactionParameters, TransactionPlan, + }, + rand::Rng, + rand_core::OsRng, + std::collections::BTreeMap, + tap::{Tap, TapFallible}, + tracing::{error_span, info, Instrument}, +}; + +mod common; + +const PROPOSAL_VOTING_BLOCKS: u64 = 3; + +/// Exercises that the app can disable proposals to spend community pool funds. +#[tokio::test] +async fn app_can_disable_community_pool_spends() -> anyhow::Result<()> { + // Install a test logger, and acquire some temporary storage. + let guard = common::set_tracing_subscriber(); + let storage = TempStorage::new().await?; + + // Define a helper to get the current community pool balance. + let pool_balance = || async { storage.latest_snapshot().community_pool_balance().await }; + let pending_pool_txs = || async { + storage + .latest_snapshot() + .pending_community_pool_transactions() + .await + }; + + // Generate a set of consensus keys. + let consensus_sk = ed25519_consensus::SigningKey::new(OsRng); + let consensus_vk = consensus_sk.verification_key(); + + // Generate a set of identity keys. + let spend_key: SpendKey = SpendKeyBytes(OsRng.gen()).into(); + let (identity_sk, identity_vk) = { + let sk = spend_key.spend_auth_key(); + let vk = VerificationKey::from(sk); + (sk, vk) + }; + let (governance_sk, governance_vk) = (identity_sk, identity_vk); + + // Define a validator and an associated genesis allocation. + let (validator, allocation) = { + let v = PenumbraValidator { + identity_key: Some(IdentityKey { + ik: identity_vk.to_bytes().to_vec(), + }), + // NB: for now, we will use the same key for governance. See the documentation of + // `GovernanceKey` for more information about cold storage of validator keys. + governance_key: Some(GovernanceKey { + gk: identity_vk.to_bytes().to_vec(), + }), + consensus_key: consensus_vk.as_bytes().to_vec(), + enabled: true, + sequence_number: 0, + name: String::default(), + website: String::default(), + description: String::default(), + funding_streams: Vec::default(), + }; + + let (address, _) = spend_key + .full_viewing_key() + .incoming() + .payment_address(0u32.into()); + + let ik = penumbra_stake::IdentityKey(identity_vk.into()); + let delegation_denom = DelegationToken::from(ik).denom(); + + let allocation = Allocation { + raw_amount: 1000u128.into(), + raw_denom: delegation_denom.to_string(), + address, + }; + + (v, allocation) + }; + + // Define our application state, and start the test node. + let mut test_node = { + let mut content = Content { + governance_content: penumbra_governance::genesis::Content { + governance_params: penumbra_governance::params::GovernanceParameters { + proposal_deposit_amount: 0_u32.into(), + proposal_voting_blocks: PROPOSAL_VOTING_BLOCKS, + ..Default::default() + }, + }, + community_pool_content: penumbra_community_pool::genesis::Content { + community_pool_params: penumbra_community_pool::params::CommunityPoolParameters { + // Disable community spend proposals. + community_pool_spend_proposals_enabled: false, + }, + }, + ..Default::default() + }; + content.stake_content.validators.push(validator); + content.shielded_pool_content.allocations.push(allocation); + let app_state = AppState::Content(content); + let app_state = serde_json::to_vec(&app_state).unwrap(); + let consensus = Consensus::new(storage.as_ref().clone()); + TestNode::builder() + .single_validator() + .app_state(app_state) + .init_chain(consensus) + .await + .tap_ok(|e| tracing::info!(hash = %e.last_app_hash_hex(), "finished init chain"))? + }; + let original_pool_balance = pool_balance().await?; + let [_validator] = storage + .latest_snapshot() + .validator_definitions() + .await? + .try_into() + .map_err(|validator| anyhow::anyhow!("expected one validator, got: {validator:?}"))?; + + // Sync the mock client, using the test wallet's spend key, to the latest snapshot. + let client = MockClient::new(test_keys::SPEND_KEY.clone()) + .with_sync_to_storage(&storage) + .await? + .tap(|c| info!(client.notes = %c.notes.len(), "mock client synced to test storage")); + + // Take one of the test wallet's notes, and prepare to deposit it in the community pool. + let note = client + .notes + .values() + .cloned() + .next() + .ok_or_else(|| anyhow!("mock client had no note"))?; + + // Create a community pool transaction. + let mut plan = { + let value = note.value(); + let spend = SpendPlan::new( + &mut OsRng, + note.clone(), + client + .position(note.commit()) + .ok_or_else(|| anyhow!("input note commitment was unknown to mock client"))?, + ) + .into(); + let deposit = CommunityPoolDeposit { value }.into(); + TransactionPlan { + actions: vec![spend, deposit], + // Now fill out the remaining parts of the transaction needed for verification: + memo: None, + detection_data: None, // We'll set this automatically below + transaction_parameters: TransactionParameters { + chain_id: TestNode::<()>::CHAIN_ID.to_string(), + ..Default::default() + }, + } + }; + plan.populate_detection_data(OsRng, 0); + let tx = client.witness_auth_build(&plan).await?; + + // Execute the transaction, applying it to the chain state. + test_node + .block() + .with_data(vec![tx.encode_to_vec()]) + .execute() + .instrument(error_span!("executing block with community pool deposit")) + .await?; + let post_deposit_pool_balance = pool_balance().await?; + + // Now, make a governance proposal that we should spend community pool funds, to return + // the note back to the test wallet. + let mut plan = { + let value = note.value(); + let proposed_tx_plan = TransactionPlan { + actions: vec![ + CommunityPoolSpend { value }.into(), + CommunityPoolOutput { + value, + address: *test_keys::ADDRESS_0, + } + .into(), + ], + memo: None, + detection_data: None, + transaction_parameters: TransactionParameters { + chain_id: TestNode::<()>::CHAIN_ID.to_string(), + ..Default::default() + }, + }; + let proposal_submit = ProposalSubmit { + proposal: Proposal { + id: 0_u64, + title: "return test deposit".to_owned(), + description: "a proposal to return the community pool deposit".to_owned(), + payload: penumbra_governance::ProposalPayload::CommunityPoolSpend { + transaction_plan: proposed_tx_plan.encode_to_vec(), + // transaction_plan: TransactionPlan::default().encode_to_vec(), + }, + }, + deposit_amount: 0_u32.into(), + }; + let proposal_nft_value = proposal_submit.proposal_nft_value(); + let proposal = ActionPlan::ProposalSubmit(proposal_submit); + TransactionPlan { + actions: vec![ + proposal, + // Next, create a new output of the exact same amount. + OutputPlan::new(&mut OsRng, proposal_nft_value, *test_keys::ADDRESS_0).into(), + ], + // Now fill out the remaining parts of the transaction needed for verification: + memo: Some(MemoPlan::new( + &mut OsRng, + MemoPlaintext::blank_memo(*test_keys::ADDRESS_0), + )?), + detection_data: None, + transaction_parameters: TransactionParameters { + chain_id: TestNode::<()>::CHAIN_ID.to_string(), + ..Default::default() + }, + } + }; + plan.populate_detection_data(OsRng, 0); + let tx = client.witness_auth_build(&plan).await?; + + // Execute the transaction, applying it to the chain state. + test_node + .block() + .with_data(vec![tx.encode_to_vec()]) + .execute() + .instrument(error_span!("executing block with governance proposal")) + .await?; + let post_proposal_pool_balance = pool_balance().await?; + let post_proposal_pending_txs = pending_pool_txs().await?; + let post_proposal_state = storage.latest_snapshot().proposal_state(0).await?; + + // Now make another transaction that will contain a validator vote upon our transaction. + let mut plan = { + let body = ValidatorVoteBody { + proposal: 0_u64, + vote: penumbra_governance::Vote::Yes, + identity_key: penumbra_stake::IdentityKey(identity_vk.to_bytes().into()), + governance_key: penumbra_stake::GovernanceKey(governance_vk), + reason: ValidatorVoteReason("test reason".to_owned()), + }; + let auth_sig = governance_sk.sign(OsRng, body.encode_to_vec().as_slice()); + let vote = ValidatorVote { body, auth_sig }.into(); + TransactionPlan { + actions: vec![vote], + memo: None, + detection_data: None, + transaction_parameters: TransactionParameters { + chain_id: TestNode::<()>::CHAIN_ID.to_string(), + ..Default::default() + }, + } + }; + plan.populate_detection_data(OsRng, 0); + let tx = client.witness_auth_build(&plan).await?; + + // Execute the transaction, applying it to the chain state. + test_node + .block() + .with_data(vec![tx.encode_to_vec()]) + .execute() + .instrument(error_span!("executing block with validator vote")) + .await?; + let post_vote_pool_balance = pool_balance().await?; + let post_vote_pending_txs = pending_pool_txs().await?; + let post_vote_state = storage.latest_snapshot().proposal_state(0).await?; + + test_node.fast_forward(PROPOSAL_VOTING_BLOCKS).await?; + let post_voting_period_pool_balance = pool_balance().await?; + let post_voting_period_pending_txs = pending_pool_txs().await?; + let post_voting_period_state = storage.latest_snapshot().proposal_state(0).await?; + + // At the outset, the pool should be empty. + assert_eq!( + original_pool_balance, + BTreeMap::default(), + "the community pool should be empty at the beginning of the chain" + ); + + // After we deposit a note into the community pool, we should see the original pool contents, + // plus the amount that we deposited. + assert_eq!( + [(note.asset_id(), note.amount())] + .into_iter() + .collect::>(), + post_deposit_pool_balance, + "a community pool deposit should be reflected in the visible balance, even if spends are disabled" + ); + + // A proposal should not itself affect the balance of the community pool. + assert_eq!( + post_deposit_pool_balance, post_proposal_pool_balance, + "the community pool balance should not be affected by a proposal" + ); + assert_eq!(post_proposal_state, None, "the proposal should be rejected"); + assert_eq!( + post_proposal_pending_txs.len(), + 0, + "no transaction(s) should be pending" + ); + + // ...nor should a vote by itself. + assert_eq!( + post_proposal_pool_balance, post_vote_pool_balance, + "the community pool balance should not be affected by a vote, even with quorum" + ); + assert_eq!( + post_vote_state, None, + "a vote for a rejected proposal should not cause it to enter the voting state" + ); + assert_eq!( + post_vote_pending_txs.len(), + 0, + "no transaction(s) should be pending" + ); + + // After any possible voting period, we should see the same pool balance. + assert_eq!( + post_voting_period_pool_balance, + [(note.asset_id(), note.amount())] + .into_iter() + .collect::>(), + "a rejected proposal should not decrease the funds of the community pool" + ); + assert_eq!( + post_voting_period_state, None, + "a proposal should be finished after the voting period completes" + ); + assert_eq!( + post_voting_period_pending_txs.len(), + 0, + "a proposal has been rejected, no transaction(s) are pending" + ); + + // Free our temporary storage. + Ok(()) + .tap(|_| drop(test_node)) + .tap(|_| drop(storage)) + .tap(|_| drop(guard)) +} diff --git a/crates/core/app/tests/app_can_propose_community_pool_spends.rs b/crates/core/app/tests/app_can_propose_community_pool_spends.rs new file mode 100644 index 0000000000..80d43d450e --- /dev/null +++ b/crates/core/app/tests/app_can_propose_community_pool_spends.rs @@ -0,0 +1,378 @@ +use { + anyhow::anyhow, + cnidarium::TempStorage, + decaf377_rdsa::VerificationKey, + penumbra_app::{ + genesis::{AppState, Content}, + server::consensus::Consensus, + CommunityPoolStateReadExt as _, + }, + penumbra_community_pool::{ + CommunityPoolDeposit, CommunityPoolOutput, CommunityPoolSpend, StateReadExt as _, + }, + penumbra_governance::{ + Proposal, ProposalSubmit, StateReadExt as _, ValidatorVote, ValidatorVoteBody, + ValidatorVoteReason, + }, + penumbra_keys::{ + keys::{SpendKey, SpendKeyBytes}, + test_keys::{self}, + }, + penumbra_mock_client::MockClient, + penumbra_mock_consensus::TestNode, + penumbra_proto::{ + core::keys::v1::{GovernanceKey, IdentityKey}, + penumbra::core::component::stake::v1::Validator as PenumbraValidator, + DomainType, + }, + penumbra_shielded_pool::{genesis::Allocation, OutputPlan, SpendPlan}, + penumbra_stake::{component::validator_handler::ValidatorDataRead, DelegationToken}, + penumbra_transaction::{ + memo::MemoPlaintext, plan::MemoPlan, ActionPlan, TransactionParameters, TransactionPlan, + }, + rand::Rng, + rand_core::OsRng, + std::collections::BTreeMap, + tap::{Tap, TapFallible}, + tracing::{error_span, info, Instrument}, +}; + +mod common; + +const PROPOSAL_VOTING_BLOCKS: u64 = 3; + +/// Exercises that the app can make proposals to spend community pool funds. +#[tokio::test] +async fn app_can_propose_community_pool_spends() -> anyhow::Result<()> { + // Install a test logger, and acquire some temporary storage. + let guard = common::set_tracing_subscriber(); + let storage = TempStorage::new().await?; + + // Define a helper to get the current community pool balance. + let pool_balance = || async { storage.latest_snapshot().community_pool_balance().await }; + let pending_pool_txs = || async { + storage + .latest_snapshot() + .pending_community_pool_transactions() + .await + }; + + // Generate a set of consensus keys. + let consensus_sk = ed25519_consensus::SigningKey::new(OsRng); + let consensus_vk = consensus_sk.verification_key(); + + // Generate a set of identity keys. + let spend_key: SpendKey = SpendKeyBytes(OsRng.gen()).into(); + let (identity_sk, identity_vk) = { + let sk = spend_key.spend_auth_key(); + let vk = VerificationKey::from(sk); + (sk, vk) + }; + let (governance_sk, governance_vk) = (identity_sk, identity_vk); + + // Define a validator and an associated genesis allocation. + let (validator, allocation) = { + let v = PenumbraValidator { + identity_key: Some(IdentityKey { + ik: identity_vk.to_bytes().to_vec(), + }), + // NB: for now, we will use the same key for governance. See the documentation of + // `GovernanceKey` for more information about cold storage of validator keys. + governance_key: Some(GovernanceKey { + gk: identity_vk.to_bytes().to_vec(), + }), + consensus_key: consensus_vk.as_bytes().to_vec(), + enabled: true, + sequence_number: 0, + name: String::default(), + website: String::default(), + description: String::default(), + funding_streams: Vec::default(), + }; + + let (address, _) = spend_key + .full_viewing_key() + .incoming() + .payment_address(0u32.into()); + + let ik = penumbra_stake::IdentityKey(identity_vk.into()); + let delegation_denom = DelegationToken::from(ik).denom(); + + let allocation = Allocation { + raw_amount: 1000u128.into(), + raw_denom: delegation_denom.to_string(), + address, + }; + + (v, allocation) + }; + + // Define our application state, and start the test node. + let mut test_node = { + let mut content = Content { + governance_content: penumbra_governance::genesis::Content { + governance_params: penumbra_governance::params::GovernanceParameters { + proposal_deposit_amount: 0_u32.into(), + proposal_voting_blocks: PROPOSAL_VOTING_BLOCKS, + ..Default::default() + }, + }, + ..Default::default() + }; + content.stake_content.validators.push(validator); + content.shielded_pool_content.allocations.push(allocation); + let app_state = AppState::Content(content); + let app_state = serde_json::to_vec(&app_state).unwrap(); + let consensus = Consensus::new(storage.as_ref().clone()); + TestNode::builder() + .single_validator() + .app_state(app_state) + .init_chain(consensus) + .await + .tap_ok(|e| tracing::info!(hash = %e.last_app_hash_hex(), "finished init chain"))? + }; + let original_pool_balance = pool_balance().await?; + let [_validator] = storage + .latest_snapshot() + .validator_definitions() + .await? + .try_into() + .map_err(|validator| anyhow::anyhow!("expected one validator, got: {validator:?}"))?; + + // Sync the mock client, using the test wallet's spend key, to the latest snapshot. + let client = MockClient::new(test_keys::SPEND_KEY.clone()) + .with_sync_to_storage(&storage) + .await? + .tap(|c| info!(client.notes = %c.notes.len(), "mock client synced to test storage")); + + // Take one of the test wallet's notes, and prepare to deposit it in the community pool. + let note = client + .notes + .values() + .cloned() + .next() + .ok_or_else(|| anyhow!("mock client had no note"))?; + + // Create a community pool transaction. + let mut plan = { + let value = note.value(); + let spend = SpendPlan::new( + &mut OsRng, + note.clone(), + client + .position(note.commit()) + .ok_or_else(|| anyhow!("input note commitment was unknown to mock client"))?, + ) + .into(); + let deposit = CommunityPoolDeposit { value }.into(); + TransactionPlan { + actions: vec![spend, deposit], + // Now fill out the remaining parts of the transaction needed for verification: + memo: None, + detection_data: None, // We'll set this automatically below + transaction_parameters: TransactionParameters { + chain_id: TestNode::<()>::CHAIN_ID.to_string(), + ..Default::default() + }, + } + }; + plan.populate_detection_data(OsRng, 0); + let tx = client.witness_auth_build(&plan).await?; + + // Execute the transaction, applying it to the chain state. + test_node + .block() + .with_data(vec![tx.encode_to_vec()]) + .execute() + .instrument(error_span!("executing block with community pool deposit")) + .await?; + let post_deposit_pool_balance = pool_balance().await?; + + // Now, make a governance proposal that we should spend community pool funds, to return + // the note back to the test wallet. + let mut plan = { + let value = note.value(); + let proposed_tx_plan = TransactionPlan { + actions: vec![ + CommunityPoolSpend { value }.into(), + CommunityPoolOutput { + value, + address: *test_keys::ADDRESS_0, + } + .into(), + ], + memo: None, + detection_data: None, + transaction_parameters: TransactionParameters { + chain_id: TestNode::<()>::CHAIN_ID.to_string(), + ..Default::default() + }, + }; + let proposal_submit = ProposalSubmit { + proposal: Proposal { + id: 0_u64, + title: "return test deposit".to_owned(), + description: "a proposal to return the community pool deposit".to_owned(), + payload: penumbra_governance::ProposalPayload::CommunityPoolSpend { + transaction_plan: proposed_tx_plan.encode_to_vec(), + // transaction_plan: TransactionPlan::default().encode_to_vec(), + }, + }, + deposit_amount: 0_u32.into(), + }; + let proposal_nft_value = proposal_submit.proposal_nft_value(); + let proposal = ActionPlan::ProposalSubmit(proposal_submit); + TransactionPlan { + actions: vec![ + proposal, + // Next, create a new output of the exact same amount. + OutputPlan::new(&mut OsRng, proposal_nft_value, *test_keys::ADDRESS_0).into(), + ], + // Now fill out the remaining parts of the transaction needed for verification: + memo: Some(MemoPlan::new( + &mut OsRng, + MemoPlaintext::blank_memo(*test_keys::ADDRESS_0), + )?), + detection_data: None, + transaction_parameters: TransactionParameters { + chain_id: TestNode::<()>::CHAIN_ID.to_string(), + ..Default::default() + }, + } + }; + plan.populate_detection_data(OsRng, 0); + let tx = client.witness_auth_build(&plan).await?; + + // Execute the transaction, applying it to the chain state. + test_node + .block() + .with_data(vec![tx.encode_to_vec()]) + .execute() + .instrument(error_span!("executing block with governance proposal")) + .await?; + let post_proposal_pool_balance = pool_balance().await?; + let post_proposal_pending_txs = pending_pool_txs().await?; + let post_proposal_state = storage.latest_snapshot().proposal_state(0).await?; + + // Now make another transaction that will contain a validator vote upon our transaction. + let mut plan = { + let body = ValidatorVoteBody { + proposal: 0_u64, + vote: penumbra_governance::Vote::Yes, + identity_key: penumbra_stake::IdentityKey(identity_vk.to_bytes().into()), + governance_key: penumbra_stake::GovernanceKey(governance_vk), + reason: ValidatorVoteReason("test reason".to_owned()), + }; + let auth_sig = governance_sk.sign(OsRng, body.encode_to_vec().as_slice()); + let vote = ValidatorVote { body, auth_sig }.into(); + TransactionPlan { + actions: vec![vote], + memo: None, + detection_data: None, + transaction_parameters: TransactionParameters { + chain_id: TestNode::<()>::CHAIN_ID.to_string(), + ..Default::default() + }, + } + }; + plan.populate_detection_data(OsRng, 0); + let tx = client.witness_auth_build(&plan).await?; + + // Execute the transaction, applying it to the chain state. + test_node + .block() + .with_data(vec![tx.encode_to_vec()]) + .execute() + .instrument(error_span!("executing block with validator vote")) + .await?; + let post_vote_pool_balance = pool_balance().await?; + let post_vote_pending_txs = pending_pool_txs().await?; + let post_vote_state = storage.latest_snapshot().proposal_state(0).await?; + + test_node.fast_forward(PROPOSAL_VOTING_BLOCKS).await?; + let post_voting_period_pool_balance = pool_balance().await?; + let post_voting_period_pending_txs = pending_pool_txs().await?; + let post_voting_period_state = storage.latest_snapshot().proposal_state(0).await?; + + // At the outset, the pool should be empty. + assert_eq!( + original_pool_balance, + BTreeMap::default(), + "the community pool should be empty at the beginning of the chain" + ); + + // After we deposit a note into the community pool, we should see the original pool contents, + // plus the amount that we deposited. + assert_eq!( + [(note.asset_id(), note.amount())] + .into_iter() + .collect::>(), + post_deposit_pool_balance, + "a community pool deposit should be reflected in the visible balance" + ); + + // A proposal should not itself affect the balance of the community pool. + assert_eq!( + post_deposit_pool_balance, post_proposal_pool_balance, + "the community pool balance should not be affected by a proposal" + ); + assert_eq!( + post_proposal_state, + Some(penumbra_governance::proposal_state::State::Voting), + "a new proposal should be in the voting phase" + ); + assert_eq!( + post_proposal_pending_txs.len(), + 0, + "a proposal is being voted upon, but its transaction(s) are not pending yet" + ); + + // ...nor should a vote by itself. + assert_eq!( + post_proposal_pool_balance, post_vote_pool_balance, + "the community pool balance should not be affected by a vote, even with quorum" + ); + assert_eq!( + post_vote_state, + Some(penumbra_governance::proposal_state::State::Voting), + "a proposal should remain in the voting phase" + ); + assert_eq!( + post_vote_pending_txs.len(), + 0, + "a proposal is being voted upon, but its transaction(s) are not pending yet" + ); + + // After the proposal passes, we should see the balance decrease by the amount proposed. + assert_eq!( + post_voting_period_pool_balance, + BTreeMap::default(), + "the successful proposal should decrease the funds of the community pool" + ); + assert_eq!( + post_voting_period_state, + Some(penumbra_governance::proposal_state::State::Finished { + outcome: penumbra_governance::proposal_state::Outcome::Passed, + }), + "a proposal should be finished after the voting period completes" + ); + assert_eq!( + post_voting_period_pending_txs.len(), + 1, + "a proposal has finished, its transaction(s) are pending" + ); + + // Move forward one block, and show that the transaction is no longer pending. + test_node.block().execute().await?; + assert_eq!( + pending_pool_txs().await?.len(), + 0, + "the community pool spend is no longer pending" + ); + + // Free our temporary storage. + Ok(()) + .tap(|_| drop(test_node)) + .tap(|_| drop(storage)) + .tap(|_| drop(guard)) +} diff --git a/crates/core/component/auction/src/auction/dutch/actions/end.rs b/crates/core/component/auction/src/auction/dutch/actions/end.rs index e06464dddf..b0ce842fb5 100644 --- a/crates/core/component/auction/src/auction/dutch/actions/end.rs +++ b/crates/core/component/auction/src/auction/dutch/actions/end.rs @@ -1,6 +1,7 @@ use anyhow::anyhow; use penumbra_asset::{Balance, Value}; use penumbra_proto::{core::component::auction::v1alpha1 as pb, DomainType}; +use penumbra_txhash::{EffectHash, EffectingData}; use serde::{Deserialize, Serialize}; use crate::auction::{id::AuctionId, AuctionNft}; @@ -39,6 +40,12 @@ impl ActionDutchAuctionEnd { } } +/* Effect hash */ +impl EffectingData for ActionDutchAuctionEnd { + fn effect_hash(&self) -> EffectHash { + EffectHash::from_proto_effecting_data(&self.to_proto()) + } +} /* Protobuf impls */ impl DomainType for ActionDutchAuctionEnd { type Proto = pb::ActionDutchAuctionEnd; diff --git a/crates/core/component/auction/src/auction/dutch/actions/mod.rs b/crates/core/component/auction/src/auction/dutch/actions/mod.rs index c9451d47c5..90d3c2a813 100644 --- a/crates/core/component/auction/src/auction/dutch/actions/mod.rs +++ b/crates/core/component/auction/src/auction/dutch/actions/mod.rs @@ -6,3 +6,6 @@ pub use end::ActionDutchAuctionEnd; pub mod withdraw; pub use withdraw::ActionDutchAuctionWithdraw; + +pub mod plan; +pub use plan::ActionDutchAuctionWithdrawPlan; diff --git a/crates/core/component/auction/src/auction/dutch/actions/plan.rs b/crates/core/component/auction/src/auction/dutch/actions/plan.rs new file mode 100644 index 0000000000..18ae8e2917 --- /dev/null +++ b/crates/core/component/auction/src/auction/dutch/actions/plan.rs @@ -0,0 +1,100 @@ +use ark_ff::Zero; +use decaf377::Fr; +use penumbra_asset::{balance, Balance, Value}; +use penumbra_proto::{penumbra::core::component::auction::v1alpha1 as pb, DomainType}; +use serde::{Deserialize, Serialize}; + +use crate::auction::{dutch::ActionDutchAuctionWithdraw, AuctionId, AuctionNft}; + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde( + try_from = "pb::ActionDutchAuctionWithdrawPlan", + into = "pb::ActionDutchAuctionWithdrawPlan" +)] +pub struct ActionDutchAuctionWithdrawPlan { + pub auction_id: AuctionId, + pub seq: u64, + pub reserves_input: Value, + pub reserves_output: Value, +} + +impl ActionDutchAuctionWithdrawPlan { + pub fn to_action(&self) -> ActionDutchAuctionWithdraw { + ActionDutchAuctionWithdraw { + auction_id: self.auction_id, + reserves_commitment: self.reserves_commitment(), + seq: self.seq, + } + } + + pub fn reserves_balance(&self) -> Balance { + Balance::from(self.reserves_input) + Balance::from(self.reserves_output) + } + + pub fn reserves_commitment(&self) -> balance::Commitment { + self.reserves_balance().commit(Fr::zero()) + } + + pub fn balance(&self) -> Balance { + let reserves_balance = self.reserves_balance(); + let prev_auction_nft = Balance::from(Value { + amount: 1u128.into(), + asset_id: AuctionNft::new(self.auction_id, self.seq.saturating_sub(1)).asset_id(), + }); + + let next_auction_nft = Balance::from(Value { + amount: 1u128.into(), + asset_id: AuctionNft::new(self.auction_id, self.seq).asset_id(), + }); + + reserves_balance + next_auction_nft - prev_auction_nft + } +} + +impl DomainType for ActionDutchAuctionWithdrawPlan { + type Proto = pb::ActionDutchAuctionWithdrawPlan; +} + +impl From for pb::ActionDutchAuctionWithdrawPlan { + fn from(domain: ActionDutchAuctionWithdrawPlan) -> Self { + Self { + auction_id: Some(domain.auction_id.into()), + seq: domain.seq, + reserves_input: Some(domain.reserves_input.into()), + reserves_output: Some(domain.reserves_output.into()), + } + } +} + +impl TryFrom for ActionDutchAuctionWithdrawPlan { + type Error = anyhow::Error; + fn try_from(msg: pb::ActionDutchAuctionWithdrawPlan) -> Result { + Ok(Self { + auction_id: msg + .auction_id + .ok_or_else(|| { + anyhow::anyhow!( + "ActionDutchAuctionWithdrawPlan message is missing an auction id" + ) + })? + .try_into()?, + seq: msg.seq, + reserves_input: msg + .reserves_input + .ok_or_else(|| { + anyhow::anyhow!( + "ActionDutchAuctionWithdrawPlan message is missing a reserves input" + ) + })? + .try_into()?, + reserves_output: msg + .reserves_output + .ok_or_else(|| { + anyhow::anyhow!( + "ActionDutchAuctionWithdrawPlan message is missing a reserves output" + ) + })? + .try_into()?, + }) + } +} diff --git a/crates/core/component/auction/src/auction/dutch/actions/schedule.rs b/crates/core/component/auction/src/auction/dutch/actions/schedule.rs index 370a6222f6..c97e1cfed4 100644 --- a/crates/core/component/auction/src/auction/dutch/actions/schedule.rs +++ b/crates/core/component/auction/src/auction/dutch/actions/schedule.rs @@ -2,6 +2,7 @@ use crate::auction::{dutch::DutchAuctionDescription, nft::AuctionNft}; use anyhow::anyhow; use penumbra_asset::{Balance, Value}; use penumbra_proto::{core::component::auction::v1alpha1 as pb, DomainType}; +use penumbra_txhash::{EffectHash, EffectingData}; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize)] @@ -37,6 +38,13 @@ impl ActionDutchAuctionSchedule { } } +/* Effect hash */ +impl EffectingData for ActionDutchAuctionSchedule { + fn effect_hash(&self) -> EffectHash { + EffectHash::from_proto_effecting_data(&self.to_proto()) + } +} + /* Protobuf impls */ impl DomainType for ActionDutchAuctionSchedule { type Proto = pb::ActionDutchAuctionSchedule; diff --git a/crates/core/component/auction/src/auction/dutch/actions/withdraw.rs b/crates/core/component/auction/src/auction/dutch/actions/withdraw.rs index d7c4113e46..b40e0ade45 100644 --- a/crates/core/component/auction/src/auction/dutch/actions/withdraw.rs +++ b/crates/core/component/auction/src/auction/dutch/actions/withdraw.rs @@ -4,6 +4,7 @@ use ark_ff::Zero; use decaf377_rdsa::Fr; use penumbra_asset::{balance, Balance, Value}; use penumbra_proto::{core::component::auction::v1alpha1 as pb, DomainType}; +use penumbra_txhash::{EffectHash, EffectingData}; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize)] @@ -55,6 +56,13 @@ impl ActionDutchAuctionWithdraw { } } +/* Effect hash */ +impl EffectingData for ActionDutchAuctionWithdraw { + fn effect_hash(&self) -> EffectHash { + EffectHash::from_proto_effecting_data(&self.to_proto()) + } +} + /* Protobuf impls */ impl DomainType for ActionDutchAuctionWithdraw { type Proto = pb::ActionDutchAuctionWithdraw; diff --git a/crates/core/component/auction/src/component/action_handler/dutch/schedule.rs b/crates/core/component/auction/src/component/action_handler/dutch/schedule.rs index 8ee70b4547..6f2e7f21dc 100644 --- a/crates/core/component/auction/src/component/action_handler/dutch/schedule.rs +++ b/crates/core/component/auction/src/component/action_handler/dutch/schedule.rs @@ -4,7 +4,7 @@ use anyhow::{ensure, Result}; use async_trait::async_trait; use cnidarium::StateWrite; use cnidarium_component::ActionHandler; -use penumbra_sct::component::clock::EpochRead; // AuctionRead? +use penumbra_sct::component::clock::EpochRead; use crate::auction::dutch::ActionDutchAuctionSchedule; use crate::component::DutchAuctionManager; diff --git a/crates/core/component/governance/src/proposal_submit/action.rs b/crates/core/component/governance/src/proposal_submit/action.rs index 6356ac14ee..820a0e6f81 100644 --- a/crates/core/component/governance/src/proposal_submit/action.rs +++ b/crates/core/component/governance/src/proposal_submit/action.rs @@ -30,21 +30,30 @@ impl EffectingData for ProposalSubmit { impl ProposalSubmit { /// Compute a commitment to the value contributed to a transaction by this proposal submission. pub fn balance(&self) -> Balance { - let deposit = Value { - amount: self.deposit_amount, - asset_id: *STAKING_TOKEN_ASSET_ID, - }; - - let proposal_nft = Value { - amount: Amount::from(1u64), - asset_id: ProposalNft::deposit(self.proposal.id).denom().into(), - }; + let deposit = self.deposit_value(); + let proposal_nft = self.proposal_nft_value(); // Proposal submissions *require* the deposit amount in order to be accepted, so they // contribute (-deposit) to the value balance of the transaction, and they contribute a // single proposal NFT to the value balance: Balance::from(proposal_nft) - Balance::from(deposit) } + + /// Returns the [`Value`] of this proposal submission's deposit. + fn deposit_value(&self) -> Value { + Value { + amount: self.deposit_amount, + asset_id: *STAKING_TOKEN_ASSET_ID, + } + } + + /// Returns the [`Value`] of the proposal NFT. + pub fn proposal_nft_value(&self) -> Value { + Value { + amount: Amount::from(1u64), + asset_id: ProposalNft::deposit(self.proposal.id).denom().into(), + } + } } impl From for pb::ProposalSubmit { diff --git a/crates/core/component/governance/src/proposal_withdraw/action.rs b/crates/core/component/governance/src/proposal_withdraw/action.rs index 387868ecd1..2d5b54f0d0 100644 --- a/crates/core/component/governance/src/proposal_withdraw/action.rs +++ b/crates/core/component/governance/src/proposal_withdraw/action.rs @@ -33,19 +33,29 @@ impl From for pb::ProposalWithdraw { } impl ProposalWithdraw { - /// Compute a commitment to the value contributed to a transaction by this proposal submission. + /// Compute a commitment to the value contributed to a transaction by this proposal withdrawal. pub fn balance(&self) -> Balance { - let voting_proposal_nft = Value { + let voting_proposal_nft = self.voting_proposal_nft_value(); + let withdrawn_proposal_nft = self.withdrawn_proposal_nft(); + + // Proposal withdrawals consume the submitted proposal and produce a withdrawn proposal: + Balance::from(withdrawn_proposal_nft) - Balance::from(voting_proposal_nft) + } + + /// Returns the [`Value`] of the proposal NFT. + fn voting_proposal_nft_value(&self) -> Value { + Value { amount: Amount::from(1u64), asset_id: ProposalNft::deposit(self.proposal).denom().into(), - }; - let withdrawn_proposal_nft = Value { + } + } + + /// Returns a withdrawal NFT. + fn withdrawn_proposal_nft(&self) -> Value { + Value { amount: Amount::from(1u64), asset_id: ProposalNft::unbonding_deposit(self.proposal).denom().into(), - }; - - // Proposal withdrawals consume the submitted proposal and produce a withdrawn proposal: - Balance::from(withdrawn_proposal_nft) - Balance::from(voting_proposal_nft) + } } } diff --git a/crates/core/component/shielded-pool/Cargo.toml b/crates/core/component/shielded-pool/Cargo.toml index c399dd96af..bf573f4fb9 100644 --- a/crates/core/component/shielded-pool/Cargo.toml +++ b/crates/core/component/shielded-pool/Cargo.toml @@ -46,6 +46,7 @@ decaf377 = {workspace = true, features = ["r1cs"], default-features = true} decaf377-fmd = {workspace = true} decaf377-ka = {workspace = true} decaf377-rdsa = {workspace = true} +futures = {workspace = true} hex = {workspace = true} ibc-types = {workspace = true, default-features = false} im = {workspace = true} diff --git a/crates/core/component/shielded-pool/src/component/rpc.rs b/crates/core/component/shielded-pool/src/component/rpc.rs index bc27938767..ef77083ca1 100644 --- a/crates/core/component/shielded-pool/src/component/rpc.rs +++ b/crates/core/component/shielded-pool/src/component/rpc.rs @@ -1,7 +1,10 @@ +use std::pin::Pin; + use cnidarium::Storage; use penumbra_asset::asset; use penumbra_proto::core::component::shielded_pool::v1::{ query_service_server::QueryService, AssetMetadataByIdRequest, AssetMetadataByIdResponse, + AssetMetadataByIdsRequest, AssetMetadataByIdsResponse, }; use tonic::Status; @@ -22,6 +25,10 @@ impl Server { #[tonic::async_trait] impl QueryService for Server { + type AssetMetadataByIdsStream = Pin< + Box> + Send>, + >; + #[instrument(skip(self, request))] async fn asset_metadata_by_id( &self, @@ -53,4 +60,11 @@ impl QueryService for Server { Ok(tonic::Response::new(rsp)) } + + async fn asset_metadata_by_ids( + &self, + _request: tonic::Request, + ) -> Result, tonic::Status> { + unimplemented!("asset_metadata_by_ids not yet implemented") + } } diff --git a/crates/core/num/src/amount.rs b/crates/core/num/src/amount.rs index c5e47afe67..2170c8b100 100644 --- a/crates/core/num/src/amount.rs +++ b/crates/core/num/src/amount.rs @@ -394,6 +394,12 @@ impl ops::Sub for Amount { } } +impl ops::SubAssign for Amount { + fn sub_assign(&mut self, rhs: Amount) { + self.inner -= rhs.inner; + } +} + impl ops::Rem for Amount { type Output = Amount; diff --git a/crates/core/transaction/src/action.rs b/crates/core/transaction/src/action.rs index b103c88f9b..8b7d7a4ebd 100644 --- a/crates/core/transaction/src/action.rs +++ b/crates/core/transaction/src/action.rs @@ -71,10 +71,9 @@ impl EffectingData for Action { Action::CommunityPoolSpend(d) => d.effect_hash(), Action::CommunityPoolOutput(d) => d.effect_hash(), Action::CommunityPoolDeposit(d) => d.effect_hash(), - // TODO: fill in skeleton - Action::ActionDutchAuctionSchedule(_) => todo!(), - Action::ActionDutchAuctionEnd(_) => todo!(), - Action::ActionDutchAuctionWithdraw(_) => todo!(), + Action::ActionDutchAuctionSchedule(a) => a.effect_hash(), + Action::ActionDutchAuctionEnd(a) => a.effect_hash(), + Action::ActionDutchAuctionWithdraw(a) => a.effect_hash(), } } } @@ -119,10 +118,13 @@ impl Action { Action::CommunityPoolDeposit(_) => tracing::info_span!("CommunityPoolDeposit", ?idx), Action::CommunityPoolSpend(_) => tracing::info_span!("CommunityPoolSpend", ?idx), Action::CommunityPoolOutput(_) => tracing::info_span!("CommunityPoolOutput", ?idx), - // TODO: fill in skeleton - Action::ActionDutchAuctionSchedule(_) => todo!(), - Action::ActionDutchAuctionEnd(_) => todo!(), - Action::ActionDutchAuctionWithdraw(_) => todo!(), + Action::ActionDutchAuctionSchedule(_) => { + tracing::info_span!("ActionDutchAuctionSchedule", ?idx) + } + Action::ActionDutchAuctionEnd(_) => tracing::info_span!("ActionDutchAuctionEnd", ?idx), + Action::ActionDutchAuctionWithdraw(_) => { + tracing::info_span!("ActionDutchAuctionWithdraw", ?idx) + } } } } @@ -154,9 +156,9 @@ impl IsAction for Action { Action::IbcRelay(x) => x.balance_commitment(), Action::ValidatorDefinition(_) => balance::Commitment::default(), // TODO: fill in skeleton - Action::ActionDutchAuctionSchedule(_) => todo!(), - Action::ActionDutchAuctionEnd(_) => todo!(), - Action::ActionDutchAuctionWithdraw(_) => todo!(), + Action::ActionDutchAuctionSchedule(action) => action.balance_commitment(), + Action::ActionDutchAuctionEnd(action) => action.balance_commitment(), + Action::ActionDutchAuctionWithdraw(action) => action.balance_commitment(), } } @@ -181,10 +183,8 @@ impl IsAction for Action { Action::CommunityPoolSpend(x) => x.view_from_perspective(txp), Action::CommunityPoolOutput(x) => x.view_from_perspective(txp), Action::CommunityPoolDeposit(x) => x.view_from_perspective(txp), - // TODO: figure out where to implement the actual decryption methods for these? where are their action definitions? Action::ValidatorDefinition(x) => ActionView::ValidatorDefinition(x.to_owned()), Action::IbcRelay(x) => ActionView::IbcRelay(x.to_owned()), - // TODO: fill in skeleton Action::ActionDutchAuctionSchedule(_) => todo!(), Action::ActionDutchAuctionEnd(_) => todo!(), Action::ActionDutchAuctionWithdraw(_) => todo!(), @@ -262,10 +262,15 @@ impl From for pb::Action { Action::CommunityPoolDeposit(inner) => pb::Action { action: Some(pb::action::Action::CommunityPoolDeposit(inner.into())), }, - // TODO: fill in skeleton - Action::ActionDutchAuctionSchedule(_) => todo!(), - Action::ActionDutchAuctionEnd(_) => todo!(), - Action::ActionDutchAuctionWithdraw(_) => todo!(), + Action::ActionDutchAuctionSchedule(inner) => pb::Action { + action: Some(pb::action::Action::ActionDutchAuctionSchedule(inner.into())), + }, + Action::ActionDutchAuctionEnd(inner) => pb::Action { + action: Some(pb::action::Action::ActionDutchAuctionEnd(inner.into())), + }, + Action::ActionDutchAuctionWithdraw(inner) => pb::Action { + action: Some(pb::action::Action::ActionDutchAuctionWithdraw(inner.into())), + }, } } } @@ -331,6 +336,15 @@ impl TryFrom for Action { pb::action::Action::CommunityPoolDeposit(inner) => { Ok(Action::CommunityPoolDeposit(inner.try_into()?)) } + pb::action::Action::ActionDutchAuctionSchedule(inner) => { + Ok(Action::ActionDutchAuctionSchedule(inner.try_into()?)) + } + pb::action::Action::ActionDutchAuctionEnd(inner) => { + Ok(Action::ActionDutchAuctionEnd(inner.try_into()?)) + } + pb::action::Action::ActionDutchAuctionWithdraw(inner) => { + Ok(Action::ActionDutchAuctionWithdraw(inner.try_into()?)) + } } } } diff --git a/crates/core/transaction/src/is_action.rs b/crates/core/transaction/src/is_action.rs index f580e902ad..e534daaf22 100644 --- a/crates/core/transaction/src/is_action.rs +++ b/crates/core/transaction/src/is_action.rs @@ -416,7 +416,7 @@ impl IsAction for ActionDutchAuctionSchedule { } fn view_from_perspective(&self, _txp: &TransactionPerspective) -> ActionView { - todo!() + ActionView::ActionDutchAuctionSchedule(self.to_owned()) } } @@ -426,7 +426,7 @@ impl IsAction for ActionDutchAuctionEnd { } fn view_from_perspective(&self, _txp: &TransactionPerspective) -> ActionView { - todo!() + ActionView::ActionDutchAuctionEnd(self.to_owned()) } } @@ -436,6 +436,6 @@ impl IsAction for ActionDutchAuctionWithdraw { } fn view_from_perspective(&self, _txp: &TransactionPerspective) -> ActionView { - todo!() + ActionView::ActionDutchAuctionWithdraw(self.to_owned()) } } diff --git a/crates/core/transaction/src/plan/action.rs b/crates/core/transaction/src/plan/action.rs index f314ea2b1e..69157c91f7 100644 --- a/crates/core/transaction/src/plan/action.rs +++ b/crates/core/transaction/src/plan/action.rs @@ -6,7 +6,7 @@ use decaf377::Fr; use penumbra_asset::Balance; use penumbra_auction::auction::dutch::actions::ActionDutchAuctionEnd; use penumbra_auction::auction::dutch::actions::ActionDutchAuctionSchedule; -use penumbra_auction::auction::dutch::actions::ActionDutchAuctionWithdraw; +use penumbra_auction::auction::dutch::actions::ActionDutchAuctionWithdrawPlan; use penumbra_community_pool::{CommunityPoolDeposit, CommunityPoolOutput, CommunityPoolSpend}; use penumbra_txhash::{EffectHash, EffectingData}; @@ -81,7 +81,7 @@ pub enum ActionPlan { ActionDutchAuctionSchedule(ActionDutchAuctionSchedule), ActionDutchAuctionEnd(ActionDutchAuctionEnd), - ActionDutchAuctionWithdraw(ActionDutchAuctionWithdraw), + ActionDutchAuctionWithdraw(ActionDutchAuctionWithdrawPlan), } impl ActionPlan { @@ -160,10 +160,11 @@ impl ActionPlan { CommunityPoolOutput(plan) => Action::CommunityPoolOutput(plan.clone()), CommunityPoolDeposit(plan) => Action::CommunityPoolDeposit(plan.clone()), Ics20Withdrawal(plan) => Action::Ics20Withdrawal(plan.clone()), - // TODO: fill in skeleton - ActionDutchAuctionSchedule(_) => todo!(), - ActionDutchAuctionEnd(_) => todo!(), - ActionDutchAuctionWithdraw(_) => todo!(), + ActionDutchAuctionSchedule(plan) => Action::ActionDutchAuctionSchedule(plan.clone()), + ActionDutchAuctionEnd(plan) => Action::ActionDutchAuctionEnd(plan.clone()), + ActionDutchAuctionWithdraw(plan) => { + Action::ActionDutchAuctionWithdraw(plan.to_action()) + } }) } @@ -189,12 +190,12 @@ impl ActionPlan { PositionClose(position_close) => position_close.balance(), PositionWithdraw(position_withdraw) => position_withdraw.balance(), Ics20Withdrawal(withdrawal) => withdrawal.balance(), + ActionDutchAuctionSchedule(action) => action.balance(), + ActionDutchAuctionEnd(action) => action.balance(), + ActionDutchAuctionWithdraw(action) => action.balance(), + // None of these contribute to transaction balance: IbcAction(_) | ValidatorDefinition(_) | ValidatorVote(_) => Balance::default(), - // TODO: fill in skeleton - ActionDutchAuctionSchedule(_) => todo!(), - ActionDutchAuctionEnd(_) => todo!(), - ActionDutchAuctionWithdraw(_) => todo!(), } } @@ -223,10 +224,9 @@ impl ActionPlan { CommunityPoolOutput(_) => Fr::zero(), CommunityPoolDeposit(_) => Fr::zero(), Ics20Withdrawal(_) => Fr::zero(), - // TODO: fill in skeleton - ActionDutchAuctionSchedule(_) => todo!(), - ActionDutchAuctionEnd(_) => todo!(), - ActionDutchAuctionWithdraw(_) => todo!(), + ActionDutchAuctionSchedule(_) => Fr::zero(), + ActionDutchAuctionEnd(_) => Fr::zero(), + ActionDutchAuctionWithdraw(_) => Fr::zero(), } } @@ -256,10 +256,9 @@ impl ActionPlan { CommunityPoolOutput(plan) => plan.effect_hash(), CommunityPoolDeposit(plan) => plan.effect_hash(), Ics20Withdrawal(plan) => plan.effect_hash(), - // TODO: fill in skeleton - ActionDutchAuctionSchedule(_) => todo!(), - ActionDutchAuctionEnd(_) => todo!(), - ActionDutchAuctionWithdraw(_) => todo!(), + ActionDutchAuctionSchedule(plan) => plan.effect_hash(), + ActionDutchAuctionEnd(plan) => plan.effect_hash(), + ActionDutchAuctionWithdraw(plan) => plan.to_action().effect_hash(), } } } @@ -356,6 +355,24 @@ impl From for ActionPlan { } } +impl From for ActionPlan { + fn from(inner: CommunityPoolSpend) -> ActionPlan { + ActionPlan::CommunityPoolSpend(inner) + } +} + +impl From for ActionPlan { + fn from(inner: CommunityPoolOutput) -> ActionPlan { + ActionPlan::CommunityPoolOutput(inner) + } +} + +impl From for ActionPlan { + fn from(inner: CommunityPoolDeposit) -> ActionPlan { + ActionPlan::CommunityPoolDeposit(inner) + } +} + impl From for ActionPlan { fn from(inner: Ics20Withdrawal) -> ActionPlan { ActionPlan::Ics20Withdrawal(inner) @@ -440,10 +457,21 @@ impl From for pb_t::ActionPlan { ActionPlan::Ics20Withdrawal(inner) => pb_t::ActionPlan { action: Some(pb_t::action_plan::Action::Ics20Withdrawal(inner.into())), }, - // TODO: fill in skeleton - ActionPlan::ActionDutchAuctionSchedule(_) => todo!(), - ActionPlan::ActionDutchAuctionEnd(_) => todo!(), - ActionPlan::ActionDutchAuctionWithdraw(_) => todo!(), + ActionPlan::ActionDutchAuctionSchedule(inner) => pb_t::ActionPlan { + action: Some(pb_t::action_plan::Action::ActionDutchAuctionSchedule( + inner.into(), + )), + }, + ActionPlan::ActionDutchAuctionEnd(inner) => pb_t::ActionPlan { + action: Some(pb_t::action_plan::Action::ActionDutchAuctionEnd( + inner.into(), + )), + }, + ActionPlan::ActionDutchAuctionWithdraw(inner) => pb_t::ActionPlan { + action: Some(pb_t::action_plan::Action::ActionDutchAuctionWithdraw( + inner.into(), + )), + }, } } } @@ -517,6 +545,15 @@ impl TryFrom for ActionPlan { pb_t::action_plan::Action::CommunityPoolOutput(inner) => { Ok(ActionPlan::CommunityPoolOutput(inner.try_into()?)) } + pb_t::action_plan::Action::ActionDutchAuctionSchedule(inner) => { + Ok(ActionPlan::ActionDutchAuctionSchedule(inner.try_into()?)) + } + pb_t::action_plan::Action::ActionDutchAuctionEnd(inner) => { + Ok(ActionPlan::ActionDutchAuctionEnd(inner.try_into()?)) + } + pb_t::action_plan::Action::ActionDutchAuctionWithdraw(inner) => { + Ok(ActionPlan::ActionDutchAuctionWithdraw(inner.try_into()?)) + } pb_t::action_plan::Action::Ics20Withdrawal(inner) => { Ok(ActionPlan::Ics20Withdrawal(inner.try_into()?)) } diff --git a/crates/core/transaction/src/view/action_view.rs b/crates/core/transaction/src/view/action_view.rs index 84aeb1e0ba..ef00229838 100644 --- a/crates/core/transaction/src/view/action_view.rs +++ b/crates/core/transaction/src/view/action_view.rs @@ -1,3 +1,6 @@ +use penumbra_auction::auction::dutch::{ + ActionDutchAuctionEnd, ActionDutchAuctionSchedule, ActionDutchAuctionWithdraw, +}; use penumbra_community_pool::{CommunityPoolDeposit, CommunityPoolOutput, CommunityPoolSpend}; use penumbra_dex::{ lp::action::{PositionClose, PositionOpen, PositionWithdraw}, @@ -44,6 +47,9 @@ pub enum ActionView { CommunityPoolDeposit(CommunityPoolDeposit), CommunityPoolSpend(CommunityPoolSpend), CommunityPoolOutput(CommunityPoolOutput), + ActionDutchAuctionSchedule(ActionDutchAuctionSchedule), + ActionDutchAuctionEnd(ActionDutchAuctionEnd), + ActionDutchAuctionWithdraw(ActionDutchAuctionWithdraw), } impl DomainType for ActionView { @@ -86,6 +92,13 @@ impl TryFrom for ActionView { AV::CommunityPoolDeposit(x) => ActionView::CommunityPoolDeposit(x.try_into()?), AV::CommunityPoolSpend(x) => ActionView::CommunityPoolSpend(x.try_into()?), AV::CommunityPoolOutput(x) => ActionView::CommunityPoolOutput(x.try_into()?), + AV::ActionDutchAuctionSchedule(x) => { + ActionView::ActionDutchAuctionSchedule(x.try_into()?) + } + AV::ActionDutchAuctionEnd(x) => ActionView::ActionDutchAuctionEnd(x.try_into()?), + AV::ActionDutchAuctionWithdraw(x) => { + ActionView::ActionDutchAuctionWithdraw(x.try_into()?) + } }, ) } @@ -117,6 +130,13 @@ impl From for pbt::ActionView { ActionView::CommunityPoolDeposit(x) => AV::CommunityPoolDeposit(x.into()), ActionView::CommunityPoolSpend(x) => AV::CommunityPoolSpend(x.into()), ActionView::CommunityPoolOutput(x) => AV::CommunityPoolOutput(x.into()), + ActionView::ActionDutchAuctionSchedule(x) => { + AV::ActionDutchAuctionSchedule(x.into()) + } + ActionView::ActionDutchAuctionEnd(x) => AV::ActionDutchAuctionEnd(x.into()), + ActionView::ActionDutchAuctionWithdraw(x) => { + AV::ActionDutchAuctionWithdraw(x.into()) + } }), } } @@ -146,6 +166,9 @@ impl From for Action { ActionView::CommunityPoolDeposit(x) => Action::CommunityPoolDeposit(x), ActionView::CommunityPoolSpend(x) => Action::CommunityPoolSpend(x), ActionView::CommunityPoolOutput(x) => Action::CommunityPoolOutput(x), + ActionView::ActionDutchAuctionSchedule(x) => Action::ActionDutchAuctionSchedule(x), + ActionView::ActionDutchAuctionEnd(x) => Action::ActionDutchAuctionEnd(x), + ActionView::ActionDutchAuctionWithdraw(x) => Action::ActionDutchAuctionWithdraw(x), } } } diff --git a/crates/proto/src/gen/penumbra.core.component.auction.v1alpha1.rs b/crates/proto/src/gen/penumbra.core.component.auction.v1alpha1.rs index 58fd927b32..9cb11332f5 100644 --- a/crates/proto/src/gen/penumbra.core.component.auction.v1alpha1.rs +++ b/crates/proto/src/gen/penumbra.core.component.auction.v1alpha1.rs @@ -351,6 +351,28 @@ impl ::prost::Name for ActionDutchAuctionScheduleView { ) } } +/// A plan to a `ActionDutchAuctionWithdraw` which contains both private and public data. +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ActionDutchAuctionWithdrawPlan { + #[prost(message, optional, tag = "1")] + pub auction_id: ::core::option::Option, + #[prost(uint64, tag = "2")] + pub seq: u64, + #[prost(message, optional, tag = "3")] + pub reserves_input: ::core::option::Option, + #[prost(message, optional, tag = "4")] + pub reserves_output: ::core::option::Option, +} +impl ::prost::Name for ActionDutchAuctionWithdrawPlan { + const NAME: &'static str = "ActionDutchAuctionWithdrawPlan"; + 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 { diff --git a/crates/proto/src/gen/penumbra.core.component.auction.v1alpha1.serde.rs b/crates/proto/src/gen/penumbra.core.component.auction.v1alpha1.serde.rs index e2e4813f22..7f8cd6a68f 100644 --- a/crates/proto/src/gen/penumbra.core.component.auction.v1alpha1.serde.rs +++ b/crates/proto/src/gen/penumbra.core.component.auction.v1alpha1.serde.rs @@ -472,6 +472,158 @@ impl<'de> serde::Deserialize<'de> for ActionDutchAuctionWithdraw { deserializer.deserialize_struct("penumbra.core.component.auction.v1alpha1.ActionDutchAuctionWithdraw", FIELDS, GeneratedVisitor) } } +impl serde::Serialize for ActionDutchAuctionWithdrawPlan { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.auction_id.is_some() { + len += 1; + } + if self.seq != 0 { + len += 1; + } + if self.reserves_input.is_some() { + len += 1; + } + if self.reserves_output.is_some() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("penumbra.core.component.auction.v1alpha1.ActionDutchAuctionWithdrawPlan", len)?; + if let Some(v) = self.auction_id.as_ref() { + struct_ser.serialize_field("auctionId", v)?; + } + if self.seq != 0 { + #[allow(clippy::needless_borrow)] + struct_ser.serialize_field("seq", ToString::to_string(&self.seq).as_str())?; + } + if let Some(v) = self.reserves_input.as_ref() { + struct_ser.serialize_field("reservesInput", v)?; + } + if let Some(v) = self.reserves_output.as_ref() { + struct_ser.serialize_field("reservesOutput", v)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for ActionDutchAuctionWithdrawPlan { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "auction_id", + "auctionId", + "seq", + "reserves_input", + "reservesInput", + "reserves_output", + "reservesOutput", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + AuctionId, + Seq, + ReservesInput, + ReservesOutput, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "auctionId" | "auction_id" => Ok(GeneratedField::AuctionId), + "seq" => Ok(GeneratedField::Seq), + "reservesInput" | "reserves_input" => Ok(GeneratedField::ReservesInput), + "reservesOutput" | "reserves_output" => Ok(GeneratedField::ReservesOutput), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = ActionDutchAuctionWithdrawPlan; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct penumbra.core.component.auction.v1alpha1.ActionDutchAuctionWithdrawPlan") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut auction_id__ = None; + let mut seq__ = None; + let mut reserves_input__ = None; + let mut reserves_output__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::AuctionId => { + if auction_id__.is_some() { + return Err(serde::de::Error::duplicate_field("auctionId")); + } + auction_id__ = map_.next_value()?; + } + GeneratedField::Seq => { + if seq__.is_some() { + return Err(serde::de::Error::duplicate_field("seq")); + } + seq__ = + Some(map_.next_value::<::pbjson::private::NumberDeserialize<_>>()?.0) + ; + } + GeneratedField::ReservesInput => { + if reserves_input__.is_some() { + return Err(serde::de::Error::duplicate_field("reservesInput")); + } + reserves_input__ = map_.next_value()?; + } + GeneratedField::ReservesOutput => { + if reserves_output__.is_some() { + return Err(serde::de::Error::duplicate_field("reservesOutput")); + } + reserves_output__ = map_.next_value()?; + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(ActionDutchAuctionWithdrawPlan { + auction_id: auction_id__, + seq: seq__.unwrap_or_default(), + reserves_input: reserves_input__, + reserves_output: reserves_output__, + }) + } + } + deserializer.deserialize_struct("penumbra.core.component.auction.v1alpha1.ActionDutchAuctionWithdrawPlan", FIELDS, GeneratedVisitor) + } +} impl serde::Serialize for ActionDutchAuctionWithdrawView { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result diff --git a/crates/proto/src/gen/penumbra.core.component.shielded_pool.v1.rs b/crates/proto/src/gen/penumbra.core.component.shielded_pool.v1.rs index b8272ed531..b2c3ff37c5 100644 --- a/crates/proto/src/gen/penumbra.core.component.shielded_pool.v1.rs +++ b/crates/proto/src/gen/penumbra.core.component.shielded_pool.v1.rs @@ -561,6 +561,40 @@ impl ::prost::Name for AssetMetadataByIdResponse { ) } } +/// Requests information on an asset by multiple asset ids +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct AssetMetadataByIdsRequest { + /// The asset IDs to request information on. Note that node is neither required + /// nor expected to stream responses in the same order as this array. + #[prost(message, repeated, tag = "1")] + pub asset_id: ::prost::alloc::vec::Vec, +} +impl ::prost::Name for AssetMetadataByIdsRequest { + const NAME: &'static str = "AssetMetadataByIdsRequest"; + const PACKAGE: &'static str = "penumbra.core.component.shielded_pool.v1"; + fn full_name() -> ::prost::alloc::string::String { + ::prost::alloc::format!( + "penumbra.core.component.shielded_pool.v1.{}", Self::NAME + ) + } +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct AssetMetadataByIdsResponse { + /// A single asset metadata streamed from the node. + #[prost(message, optional, tag = "1")] + pub denom_metadata: ::core::option::Option, +} +impl ::prost::Name for AssetMetadataByIdsResponse { + const NAME: &'static str = "AssetMetadataByIdsResponse"; + const PACKAGE: &'static str = "penumbra.core.component.shielded_pool.v1"; + fn full_name() -> ::prost::alloc::string::String { + ::prost::alloc::format!( + "penumbra.core.component.shielded_pool.v1.{}", Self::NAME + ) + } +} /// Generated client implementations. #[cfg(feature = "rpc")] pub mod query_service_client { @@ -678,6 +712,41 @@ pub mod query_service_client { ); self.inner.unary(req, path, codec).await } + /// Requests a stream of asset metadata, given an array of asset IDs. Responses + /// may be streamed in a different order from that of the asset IDs in the + /// request, and asset IDs unknown to the node will not receive any response + /// objects -- that is, the number of responses may be smaller than the length + /// of the asset IDs array. + pub async fn asset_metadata_by_ids( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response>, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::new( + tonic::Code::Unknown, + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/penumbra.core.component.shielded_pool.v1.QueryService/AssetMetadataByIds", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert( + GrpcMethod::new( + "penumbra.core.component.shielded_pool.v1.QueryService", + "AssetMetadataByIds", + ), + ); + self.inner.server_streaming(req, path, codec).await + } } } /// Generated server implementations. @@ -695,6 +764,27 @@ pub mod query_service_server { tonic::Response, tonic::Status, >; + /// Server streaming response type for the AssetMetadataByIds method. + type AssetMetadataByIdsStream: tonic::codegen::tokio_stream::Stream< + Item = std::result::Result< + super::AssetMetadataByIdsResponse, + tonic::Status, + >, + > + + Send + + 'static; + /// Requests a stream of asset metadata, given an array of asset IDs. Responses + /// may be streamed in a different order from that of the asset IDs in the + /// request, and asset IDs unknown to the node will not receive any response + /// objects -- that is, the number of responses may be smaller than the length + /// of the asset IDs array. + async fn asset_metadata_by_ids( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; } /// Query operations for the shielded pool component. #[derive(Debug)] @@ -823,6 +913,55 @@ pub mod query_service_server { }; Box::pin(fut) } + "/penumbra.core.component.shielded_pool.v1.QueryService/AssetMetadataByIds" => { + #[allow(non_camel_case_types)] + struct AssetMetadataByIdsSvc(pub Arc); + impl< + T: QueryService, + > tonic::server::ServerStreamingService< + super::AssetMetadataByIdsRequest, + > for AssetMetadataByIdsSvc { + type Response = super::AssetMetadataByIdsResponse; + type ResponseStream = T::AssetMetadataByIdsStream; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::asset_metadata_by_ids(&inner, request) + .await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let inner = inner.0; + let method = AssetMetadataByIdsSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.server_streaming(method, req).await; + Ok(res) + }; + Box::pin(fut) + } _ => { Box::pin(async move { Ok( diff --git a/crates/proto/src/gen/penumbra.core.component.shielded_pool.v1.serde.rs b/crates/proto/src/gen/penumbra.core.component.shielded_pool.v1.serde.rs index 04f1e6056c..67ce5950df 100644 --- a/crates/proto/src/gen/penumbra.core.component.shielded_pool.v1.serde.rs +++ b/crates/proto/src/gen/penumbra.core.component.shielded_pool.v1.serde.rs @@ -190,6 +190,198 @@ impl<'de> serde::Deserialize<'de> for AssetMetadataByIdResponse { deserializer.deserialize_struct("penumbra.core.component.shielded_pool.v1.AssetMetadataByIdResponse", FIELDS, GeneratedVisitor) } } +impl serde::Serialize for AssetMetadataByIdsRequest { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if !self.asset_id.is_empty() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("penumbra.core.component.shielded_pool.v1.AssetMetadataByIdsRequest", len)?; + if !self.asset_id.is_empty() { + struct_ser.serialize_field("assetId", &self.asset_id)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for AssetMetadataByIdsRequest { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "asset_id", + "assetId", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + AssetId, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "assetId" | "asset_id" => Ok(GeneratedField::AssetId), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = AssetMetadataByIdsRequest; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct penumbra.core.component.shielded_pool.v1.AssetMetadataByIdsRequest") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut asset_id__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::AssetId => { + if asset_id__.is_some() { + return Err(serde::de::Error::duplicate_field("assetId")); + } + asset_id__ = Some(map_.next_value()?); + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(AssetMetadataByIdsRequest { + asset_id: asset_id__.unwrap_or_default(), + }) + } + } + deserializer.deserialize_struct("penumbra.core.component.shielded_pool.v1.AssetMetadataByIdsRequest", FIELDS, GeneratedVisitor) + } +} +impl serde::Serialize for AssetMetadataByIdsResponse { + #[allow(deprecated)] + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::SerializeStruct; + let mut len = 0; + if self.denom_metadata.is_some() { + len += 1; + } + let mut struct_ser = serializer.serialize_struct("penumbra.core.component.shielded_pool.v1.AssetMetadataByIdsResponse", len)?; + if let Some(v) = self.denom_metadata.as_ref() { + struct_ser.serialize_field("denomMetadata", v)?; + } + struct_ser.end() + } +} +impl<'de> serde::Deserialize<'de> for AssetMetadataByIdsResponse { + #[allow(deprecated)] + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + const FIELDS: &[&str] = &[ + "denom_metadata", + "denomMetadata", + ]; + + #[allow(clippy::enum_variant_names)] + enum GeneratedField { + DenomMetadata, + __SkipField__, + } + impl<'de> serde::Deserialize<'de> for GeneratedField { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + struct GeneratedVisitor; + + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = GeneratedField; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "expected one of: {:?}", &FIELDS) + } + + #[allow(unused_variables)] + fn visit_str(self, value: &str) -> std::result::Result + where + E: serde::de::Error, + { + match value { + "denomMetadata" | "denom_metadata" => Ok(GeneratedField::DenomMetadata), + _ => Ok(GeneratedField::__SkipField__), + } + } + } + deserializer.deserialize_identifier(GeneratedVisitor) + } + } + struct GeneratedVisitor; + impl<'de> serde::de::Visitor<'de> for GeneratedVisitor { + type Value = AssetMetadataByIdsResponse; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("struct penumbra.core.component.shielded_pool.v1.AssetMetadataByIdsResponse") + } + + fn visit_map(self, mut map_: V) -> std::result::Result + where + V: serde::de::MapAccess<'de>, + { + let mut denom_metadata__ = None; + while let Some(k) = map_.next_key()? { + match k { + GeneratedField::DenomMetadata => { + if denom_metadata__.is_some() { + return Err(serde::de::Error::duplicate_field("denomMetadata")); + } + denom_metadata__ = map_.next_value()?; + } + GeneratedField::__SkipField__ => { + let _ = map_.next_value::()?; + } + } + } + Ok(AssetMetadataByIdsResponse { + denom_metadata: denom_metadata__, + }) + } + } + deserializer.deserialize_struct("penumbra.core.component.shielded_pool.v1.AssetMetadataByIdsResponse", FIELDS, GeneratedVisitor) + } +} impl serde::Serialize for EventOutput { #[allow(deprecated)] fn serialize(&self, serializer: S) -> std::result::Result diff --git a/crates/proto/src/gen/penumbra.core.transaction.v1.rs b/crates/proto/src/gen/penumbra.core.transaction.v1.rs index d75557b65e..98b0852670 100644 --- a/crates/proto/src/gen/penumbra.core.transaction.v1.rs +++ b/crates/proto/src/gen/penumbra.core.transaction.v1.rs @@ -94,7 +94,7 @@ impl ::prost::Name for DetectionData { pub struct Action { #[prost( oneof = "action::Action", - tags = "1, 2, 3, 4, 16, 17, 18, 19, 20, 21, 22, 30, 31, 32, 34, 40, 41, 42, 50, 51, 52, 200" + tags = "1, 2, 3, 4, 16, 17, 18, 19, 20, 21, 22, 30, 31, 32, 34, 40, 41, 42, 50, 51, 52, 53, 54, 55, 200" )] pub action: ::core::option::Option, } @@ -164,6 +164,19 @@ pub mod action { CommunityPoolDeposit( super::super::super::component::governance::v1::CommunityPoolDeposit, ), + /// Dutch auctions + #[prost(message, tag = "53")] + ActionDutchAuctionSchedule( + super::super::super::component::auction::v1alpha1::ActionDutchAuctionSchedule, + ), + #[prost(message, tag = "54")] + ActionDutchAuctionEnd( + super::super::super::component::auction::v1alpha1::ActionDutchAuctionEnd, + ), + #[prost(message, tag = "55")] + ActionDutchAuctionWithdraw( + super::super::super::component::auction::v1alpha1::ActionDutchAuctionWithdraw, + ), #[prost(message, tag = "200")] Ics20Withdrawal(super::super::super::component::ibc::v1::Ics20Withdrawal), } @@ -387,7 +400,7 @@ impl ::prost::Name for TransactionBodyView { pub struct ActionView { #[prost( oneof = "action_view::ActionView", - tags = "1, 2, 3, 4, 16, 17, 18, 19, 20, 21, 22, 30, 31, 32, 34, 41, 42, 50, 51, 52, 43, 200" + tags = "1, 2, 3, 4, 16, 17, 18, 19, 20, 21, 22, 30, 31, 32, 34, 41, 42, 50, 51, 52, 53, 54, 55, 43, 200" )] pub action_view: ::core::option::Option, } @@ -454,6 +467,19 @@ pub mod action_view { CommunityPoolDeposit( super::super::super::component::governance::v1::CommunityPoolDeposit, ), + /// Dutch auctions + #[prost(message, tag = "53")] + ActionDutchAuctionSchedule( + super::super::super::component::auction::v1alpha1::ActionDutchAuctionSchedule, + ), + #[prost(message, tag = "54")] + ActionDutchAuctionEnd( + super::super::super::component::auction::v1alpha1::ActionDutchAuctionEnd, + ), + #[prost(message, tag = "55")] + ActionDutchAuctionWithdraw( + super::super::super::component::auction::v1alpha1::ActionDutchAuctionWithdraw, + ), /// TODO: we have no way to recover the opening of the undelegate_claim's /// balance commitment, and can only infer the value from looking at the rest /// of the transaction. is that fine? @@ -568,7 +594,7 @@ impl ::prost::Name for DetectionDataPlan { pub struct ActionPlan { #[prost( oneof = "action_plan::Action", - tags = "1, 2, 3, 4, 16, 17, 18, 19, 20, 21, 22, 200, 30, 31, 32, 34, 40, 41, 42, 50, 51, 52" + tags = "1, 2, 3, 4, 16, 17, 18, 19, 20, 21, 22, 200, 30, 31, 32, 34, 40, 41, 42, 50, 51, 52, 53, 54, 55" )] pub action: ::core::option::Option, } @@ -644,6 +670,19 @@ pub mod action_plan { CommunityPoolDeposit( super::super::super::component::governance::v1::CommunityPoolDeposit, ), + /// Dutch auctions + #[prost(message, tag = "53")] + ActionDutchAuctionSchedule( + super::super::super::component::auction::v1alpha1::ActionDutchAuctionSchedule, + ), + #[prost(message, tag = "54")] + ActionDutchAuctionEnd( + super::super::super::component::auction::v1alpha1::ActionDutchAuctionEnd, + ), + #[prost(message, tag = "55")] + ActionDutchAuctionWithdraw( + super::super::super::component::auction::v1alpha1::ActionDutchAuctionWithdrawPlan, + ), } } impl ::prost::Name for ActionPlan { diff --git a/crates/proto/src/gen/penumbra.core.transaction.v1.serde.rs b/crates/proto/src/gen/penumbra.core.transaction.v1.serde.rs index 82d4f0c729..c754bce6b3 100644 --- a/crates/proto/src/gen/penumbra.core.transaction.v1.serde.rs +++ b/crates/proto/src/gen/penumbra.core.transaction.v1.serde.rs @@ -75,6 +75,15 @@ impl serde::Serialize for Action { action::Action::CommunityPoolDeposit(v) => { struct_ser.serialize_field("communityPoolDeposit", v)?; } + action::Action::ActionDutchAuctionSchedule(v) => { + struct_ser.serialize_field("actionDutchAuctionSchedule", v)?; + } + action::Action::ActionDutchAuctionEnd(v) => { + struct_ser.serialize_field("actionDutchAuctionEnd", v)?; + } + action::Action::ActionDutchAuctionWithdraw(v) => { + struct_ser.serialize_field("actionDutchAuctionWithdraw", v)?; + } action::Action::Ics20Withdrawal(v) => { struct_ser.serialize_field("ics20Withdrawal", v)?; } @@ -127,6 +136,12 @@ impl<'de> serde::Deserialize<'de> for Action { "communityPoolOutput", "community_pool_deposit", "communityPoolDeposit", + "action_dutch_auction_schedule", + "actionDutchAuctionSchedule", + "action_dutch_auction_end", + "actionDutchAuctionEnd", + "action_dutch_auction_withdraw", + "actionDutchAuctionWithdraw", "ics20_withdrawal", "ics20Withdrawal", ]; @@ -154,6 +169,9 @@ impl<'de> serde::Deserialize<'de> for Action { CommunityPoolSpend, CommunityPoolOutput, CommunityPoolDeposit, + ActionDutchAuctionSchedule, + ActionDutchAuctionEnd, + ActionDutchAuctionWithdraw, Ics20Withdrawal, __SkipField__, } @@ -198,6 +216,9 @@ impl<'de> serde::Deserialize<'de> for Action { "communityPoolSpend" | "community_pool_spend" => Ok(GeneratedField::CommunityPoolSpend), "communityPoolOutput" | "community_pool_output" => Ok(GeneratedField::CommunityPoolOutput), "communityPoolDeposit" | "community_pool_deposit" => Ok(GeneratedField::CommunityPoolDeposit), + "actionDutchAuctionSchedule" | "action_dutch_auction_schedule" => Ok(GeneratedField::ActionDutchAuctionSchedule), + "actionDutchAuctionEnd" | "action_dutch_auction_end" => Ok(GeneratedField::ActionDutchAuctionEnd), + "actionDutchAuctionWithdraw" | "action_dutch_auction_withdraw" => Ok(GeneratedField::ActionDutchAuctionWithdraw), "ics20Withdrawal" | "ics20_withdrawal" => Ok(GeneratedField::Ics20Withdrawal), _ => Ok(GeneratedField::__SkipField__), } @@ -366,6 +387,27 @@ impl<'de> serde::Deserialize<'de> for Action { return Err(serde::de::Error::duplicate_field("communityPoolDeposit")); } action__ = map_.next_value::<::std::option::Option<_>>()?.map(action::Action::CommunityPoolDeposit) +; + } + GeneratedField::ActionDutchAuctionSchedule => { + if action__.is_some() { + return Err(serde::de::Error::duplicate_field("actionDutchAuctionSchedule")); + } + action__ = map_.next_value::<::std::option::Option<_>>()?.map(action::Action::ActionDutchAuctionSchedule) +; + } + GeneratedField::ActionDutchAuctionEnd => { + if action__.is_some() { + return Err(serde::de::Error::duplicate_field("actionDutchAuctionEnd")); + } + action__ = map_.next_value::<::std::option::Option<_>>()?.map(action::Action::ActionDutchAuctionEnd) +; + } + GeneratedField::ActionDutchAuctionWithdraw => { + if action__.is_some() { + return Err(serde::de::Error::duplicate_field("actionDutchAuctionWithdraw")); + } + action__ = map_.next_value::<::std::option::Option<_>>()?.map(action::Action::ActionDutchAuctionWithdraw) ; } GeneratedField::Ics20Withdrawal => { @@ -468,6 +510,15 @@ impl serde::Serialize for ActionPlan { action_plan::Action::CommunityPoolDeposit(v) => { struct_ser.serialize_field("communityPoolDeposit", v)?; } + action_plan::Action::ActionDutchAuctionSchedule(v) => { + struct_ser.serialize_field("actionDutchAuctionSchedule", v)?; + } + action_plan::Action::ActionDutchAuctionEnd(v) => { + struct_ser.serialize_field("actionDutchAuctionEnd", v)?; + } + action_plan::Action::ActionDutchAuctionWithdraw(v) => { + struct_ser.serialize_field("actionDutchAuctionWithdraw", v)?; + } } } struct_ser.end() @@ -519,6 +570,12 @@ impl<'de> serde::Deserialize<'de> for ActionPlan { "communityPoolOutput", "community_pool_deposit", "communityPoolDeposit", + "action_dutch_auction_schedule", + "actionDutchAuctionSchedule", + "action_dutch_auction_end", + "actionDutchAuctionEnd", + "action_dutch_auction_withdraw", + "actionDutchAuctionWithdraw", ]; #[allow(clippy::enum_variant_names)] @@ -545,6 +602,9 @@ impl<'de> serde::Deserialize<'de> for ActionPlan { CommunityPoolSpend, CommunityPoolOutput, CommunityPoolDeposit, + ActionDutchAuctionSchedule, + ActionDutchAuctionEnd, + ActionDutchAuctionWithdraw, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -589,6 +649,9 @@ impl<'de> serde::Deserialize<'de> for ActionPlan { "communityPoolSpend" | "community_pool_spend" => Ok(GeneratedField::CommunityPoolSpend), "communityPoolOutput" | "community_pool_output" => Ok(GeneratedField::CommunityPoolOutput), "communityPoolDeposit" | "community_pool_deposit" => Ok(GeneratedField::CommunityPoolDeposit), + "actionDutchAuctionSchedule" | "action_dutch_auction_schedule" => Ok(GeneratedField::ActionDutchAuctionSchedule), + "actionDutchAuctionEnd" | "action_dutch_auction_end" => Ok(GeneratedField::ActionDutchAuctionEnd), + "actionDutchAuctionWithdraw" | "action_dutch_auction_withdraw" => Ok(GeneratedField::ActionDutchAuctionWithdraw), _ => Ok(GeneratedField::__SkipField__), } } @@ -763,6 +826,27 @@ impl<'de> serde::Deserialize<'de> for ActionPlan { return Err(serde::de::Error::duplicate_field("communityPoolDeposit")); } action__ = map_.next_value::<::std::option::Option<_>>()?.map(action_plan::Action::CommunityPoolDeposit) +; + } + GeneratedField::ActionDutchAuctionSchedule => { + if action__.is_some() { + return Err(serde::de::Error::duplicate_field("actionDutchAuctionSchedule")); + } + action__ = map_.next_value::<::std::option::Option<_>>()?.map(action_plan::Action::ActionDutchAuctionSchedule) +; + } + GeneratedField::ActionDutchAuctionEnd => { + if action__.is_some() { + return Err(serde::de::Error::duplicate_field("actionDutchAuctionEnd")); + } + action__ = map_.next_value::<::std::option::Option<_>>()?.map(action_plan::Action::ActionDutchAuctionEnd) +; + } + GeneratedField::ActionDutchAuctionWithdraw => { + if action__.is_some() { + return Err(serde::de::Error::duplicate_field("actionDutchAuctionWithdraw")); + } + action__ = map_.next_value::<::std::option::Option<_>>()?.map(action_plan::Action::ActionDutchAuctionWithdraw) ; } GeneratedField::__SkipField__ => { @@ -852,6 +936,15 @@ impl serde::Serialize for ActionView { action_view::ActionView::CommunityPoolDeposit(v) => { struct_ser.serialize_field("communityPoolDeposit", v)?; } + action_view::ActionView::ActionDutchAuctionSchedule(v) => { + struct_ser.serialize_field("actionDutchAuctionSchedule", v)?; + } + action_view::ActionView::ActionDutchAuctionEnd(v) => { + struct_ser.serialize_field("actionDutchAuctionEnd", v)?; + } + action_view::ActionView::ActionDutchAuctionWithdraw(v) => { + struct_ser.serialize_field("actionDutchAuctionWithdraw", v)?; + } action_view::ActionView::UndelegateClaim(v) => { struct_ser.serialize_field("undelegateClaim", v)?; } @@ -905,6 +998,12 @@ impl<'de> serde::Deserialize<'de> for ActionView { "communityPoolOutput", "community_pool_deposit", "communityPoolDeposit", + "action_dutch_auction_schedule", + "actionDutchAuctionSchedule", + "action_dutch_auction_end", + "actionDutchAuctionEnd", + "action_dutch_auction_withdraw", + "actionDutchAuctionWithdraw", "undelegate_claim", "undelegateClaim", "ics20_withdrawal", @@ -933,6 +1032,9 @@ impl<'de> serde::Deserialize<'de> for ActionView { CommunityPoolSpend, CommunityPoolOutput, CommunityPoolDeposit, + ActionDutchAuctionSchedule, + ActionDutchAuctionEnd, + ActionDutchAuctionWithdraw, UndelegateClaim, Ics20Withdrawal, __SkipField__, @@ -977,6 +1079,9 @@ impl<'de> serde::Deserialize<'de> for ActionView { "communityPoolSpend" | "community_pool_spend" => Ok(GeneratedField::CommunityPoolSpend), "communityPoolOutput" | "community_pool_output" => Ok(GeneratedField::CommunityPoolOutput), "communityPoolDeposit" | "community_pool_deposit" => Ok(GeneratedField::CommunityPoolDeposit), + "actionDutchAuctionSchedule" | "action_dutch_auction_schedule" => Ok(GeneratedField::ActionDutchAuctionSchedule), + "actionDutchAuctionEnd" | "action_dutch_auction_end" => Ok(GeneratedField::ActionDutchAuctionEnd), + "actionDutchAuctionWithdraw" | "action_dutch_auction_withdraw" => Ok(GeneratedField::ActionDutchAuctionWithdraw), "undelegateClaim" | "undelegate_claim" => Ok(GeneratedField::UndelegateClaim), "ics20Withdrawal" | "ics20_withdrawal" => Ok(GeneratedField::Ics20Withdrawal), _ => Ok(GeneratedField::__SkipField__), @@ -1139,6 +1244,27 @@ impl<'de> serde::Deserialize<'de> for ActionView { return Err(serde::de::Error::duplicate_field("communityPoolDeposit")); } action_view__ = map_.next_value::<::std::option::Option<_>>()?.map(action_view::ActionView::CommunityPoolDeposit) +; + } + GeneratedField::ActionDutchAuctionSchedule => { + if action_view__.is_some() { + return Err(serde::de::Error::duplicate_field("actionDutchAuctionSchedule")); + } + action_view__ = map_.next_value::<::std::option::Option<_>>()?.map(action_view::ActionView::ActionDutchAuctionSchedule) +; + } + GeneratedField::ActionDutchAuctionEnd => { + if action_view__.is_some() { + return Err(serde::de::Error::duplicate_field("actionDutchAuctionEnd")); + } + action_view__ = map_.next_value::<::std::option::Option<_>>()?.map(action_view::ActionView::ActionDutchAuctionEnd) +; + } + GeneratedField::ActionDutchAuctionWithdraw => { + if action_view__.is_some() { + return Err(serde::de::Error::duplicate_field("actionDutchAuctionWithdraw")); + } + action_view__ = map_.next_value::<::std::option::Option<_>>()?.map(action_view::ActionView::ActionDutchAuctionWithdraw) ; } GeneratedField::UndelegateClaim => { diff --git a/proto/penumbra/penumbra/core/component/auction/v1alpha1/auction.proto b/proto/penumbra/penumbra/core/component/auction/v1alpha1/auction.proto index 9bdaa46d94..3e900af140 100755 --- a/proto/penumbra/penumbra/core/component/auction/v1alpha1/auction.proto +++ b/proto/penumbra/penumbra/core/component/auction/v1alpha1/auction.proto @@ -138,30 +138,38 @@ message ActionDutchAuctionEnd { // Withdraw funds from the ended auction associated with the specified `auction_id` message ActionDutchAuctionWithdraw { - // The auction to withdraw funds from. - AuctionId auction_id = 1; - // The sequence number of the withdrawal. - uint64 seq = 2; - // A transparent (zero blinding factor) commitment to the - // auction's final reserves. - // - // The chain will check this commitment by recomputing it - // with the on-chain state. - asset.v1.BalanceCommitment reserves_commitment = 3; + // The auction to withdraw funds from. + AuctionId auction_id = 1; + // The sequence number of the withdrawal. + uint64 seq = 2; + // A transparent (zero blinding factor) commitment to the + // auction's final reserves. + // + // The chain will check this commitment by recomputing it + // with the on-chain state. + asset.v1.BalanceCommitment reserves_commitment = 3; } // An `ActionDutchAuctionWithdraw` augmented with additional metadata. message ActionDutchAuctionWithdrawView { - ActionDutchAuctionWithdraw action = 1; - // A sequence of values that sum together to the provided - // reserves commitment. - repeated asset.v1.ValueView reserves = 2; + ActionDutchAuctionWithdraw action = 1; + // A sequence of values that sum together to the provided + // reserves commitment. + repeated asset.v1.ValueView reserves = 2; } // An `ActionDutchAuctionSchedule` augmented with additional metadata. message ActionDutchAuctionScheduleView { - ActionDutchAuctionSchedule action = 1; - AuctionId auction_id = 2; - asset.v1.Metadata input_metadata = 3; - asset.v1.Metadata output_metadata = 4; + ActionDutchAuctionSchedule action = 1; + AuctionId auction_id = 2; + asset.v1.Metadata input_metadata = 3; + asset.v1.Metadata output_metadata = 4; +} + +// A plan to a `ActionDutchAuctionWithdraw` which contains both private and public data. +message ActionDutchAuctionWithdrawPlan { + AuctionId auction_id = 1; + uint64 seq = 2; + asset.v1.Value reserves_input = 3; + asset.v1.Value reserves_output = 4; } diff --git a/proto/penumbra/penumbra/core/component/shielded_pool/v1/shielded_pool.proto b/proto/penumbra/penumbra/core/component/shielded_pool/v1/shielded_pool.proto index 951205572e..979b0c35f7 100644 --- a/proto/penumbra/penumbra/core/component/shielded_pool/v1/shielded_pool.proto +++ b/proto/penumbra/penumbra/core/component/shielded_pool/v1/shielded_pool.proto @@ -194,6 +194,13 @@ message OutputPlan { // Query operations for the shielded pool component. service QueryService { rpc AssetMetadataById(AssetMetadataByIdRequest) returns (AssetMetadataByIdResponse); + + // Requests a stream of asset metadata, given an array of asset IDs. Responses + // may be streamed in a different order from that of the asset IDs in the + // request, and asset IDs unknown to the node will not receive any response + // objects -- that is, the number of responses may be smaller than the length + // of the asset IDs array. + rpc AssetMetadataByIds(AssetMetadataByIdsRequest) returns (stream AssetMetadataByIdsResponse); } // Requests information on an asset by asset id @@ -208,3 +215,15 @@ message AssetMetadataByIdResponse { // If the requested asset was unknown, this field will not be present. core.asset.v1.Metadata denom_metadata = 1; } + +// Requests information on an asset by multiple asset ids +message AssetMetadataByIdsRequest { + // The asset IDs to request information on. Note that node is neither required + // nor expected to stream responses in the same order as this array. + repeated core.asset.v1.AssetId asset_id = 1; +} + +message AssetMetadataByIdsResponse { + // A single asset metadata streamed from the node. + core.asset.v1.Metadata denom_metadata = 1; +} diff --git a/proto/penumbra/penumbra/core/transaction/v1/transaction.proto b/proto/penumbra/penumbra/core/transaction/v1/transaction.proto index c033bf0e67..bc4ee0e20a 100644 --- a/proto/penumbra/penumbra/core/transaction/v1/transaction.proto +++ b/proto/penumbra/penumbra/core/transaction/v1/transaction.proto @@ -3,6 +3,7 @@ package penumbra.core.transaction.v1; import "google/protobuf/any.proto"; import "penumbra/core/asset/v1/asset.proto"; +import "penumbra/core/component/auction/v1alpha1/auction.proto"; import "penumbra/core/component/dex/v1/dex.proto"; import "penumbra/core/component/fee/v1/fee.proto"; import "penumbra/core/component/governance/v1/governance.proto"; @@ -96,6 +97,11 @@ message Action { component.governance.v1.CommunityPoolOutput community_pool_output = 51; component.governance.v1.CommunityPoolDeposit community_pool_deposit = 52; + // Dutch auctions + component.auction.v1alpha1.ActionDutchAuctionSchedule action_dutch_auction_schedule = 53; + component.auction.v1alpha1.ActionDutchAuctionEnd action_dutch_auction_end = 54; + component.auction.v1alpha1.ActionDutchAuctionWithdraw action_dutch_auction_withdraw = 55; + component.ibc.v1.Ics20Withdrawal ics20_withdrawal = 200; } } @@ -216,6 +222,10 @@ message ActionView { component.governance.v1.CommunityPoolSpend community_pool_spend = 50; component.governance.v1.CommunityPoolOutput community_pool_output = 51; component.governance.v1.CommunityPoolDeposit community_pool_deposit = 52; + // Dutch auctions + component.auction.v1alpha1.ActionDutchAuctionSchedule action_dutch_auction_schedule = 53; + component.auction.v1alpha1.ActionDutchAuctionEnd action_dutch_auction_end = 54; + component.auction.v1alpha1.ActionDutchAuctionWithdraw action_dutch_auction_withdraw = 55; // TODO: we have no way to recover the opening of the undelegate_claim's // balance commitment, and can only infer the value from looking at the rest @@ -309,6 +319,11 @@ message ActionPlan { component.governance.v1.CommunityPoolSpend community_pool_spend = 50; component.governance.v1.CommunityPoolOutput community_pool_output = 51; component.governance.v1.CommunityPoolDeposit community_pool_deposit = 52; + + // Dutch auctions + component.auction.v1alpha1.ActionDutchAuctionSchedule action_dutch_auction_schedule = 53; + component.auction.v1alpha1.ActionDutchAuctionEnd action_dutch_auction_end = 54; + component.auction.v1alpha1.ActionDutchAuctionWithdrawPlan action_dutch_auction_withdraw = 55; } }