From d58b327c7fb13605ec08e9ea491b8a1527ee4603 Mon Sep 17 00:00:00 2001 From: Erwan Or Date: Tue, 25 Jun 2024 19:31:04 -0400 Subject: [PATCH] community-pool: add `initial_balance` to content message (#4666) This adds an `initial_balance` value to the genesis content message for the community-pool component. --- .../app_can_disable_community_pool_spends.rs | 28 +++++++++++++----- .../app_can_propose_community_pool_spends.rs | 28 +++++++++++++----- .../component/community-pool/src/component.rs | 1 + .../action_handler/community_pool_deposit.rs | 2 +- .../community-pool/src/component/view.rs | 9 ++++-- .../component/community-pool/src/genesis.rs | 26 ++++++++++++++-- .../core/component/funding/src/component.rs | 2 +- ...numbra.core.component.community_pool.v1.rs | 3 ++ ....core.component.community_pool.v1.serde.rs | 18 +++++++++++ .../proto/src/gen/proto_descriptor.bin.no_lfs | Bin 526509 -> 526682 bytes .../community_pool/v1/community_pool.proto | 2 ++ 11 files changed, 97 insertions(+), 22 deletions(-) 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 index e3c64a07b0..4cfb3c6b3a 100644 --- a/crates/core/app/tests/app_can_disable_community_pool_spends.rs +++ b/crates/core/app/tests/app_can_disable_community_pool_spends.rs @@ -8,6 +8,7 @@ use { server::consensus::Consensus, CommunityPoolStateReadExt as _, }, + penumbra_asset::STAKING_TOKEN_ASSET_ID, penumbra_community_pool::{ CommunityPoolDeposit, CommunityPoolOutput, CommunityPoolSpend, StateReadExt as _, }, @@ -17,10 +18,11 @@ use { }, penumbra_keys::{ keys::{SpendKey, SpendKeyBytes}, - test_keys::{self}, + test_keys, }, penumbra_mock_client::MockClient, penumbra_mock_consensus::TestNode, + penumbra_num::Amount, penumbra_proto::{ core::keys::v1::{GovernanceKey, IdentityKey}, penumbra::core::component::stake::v1::Validator as PenumbraValidator, @@ -124,6 +126,7 @@ async fn app_can_disable_community_pool_spends() -> anyhow::Result<()> { // Disable community spend proposals. community_pool_spend_proposals_enabled: false, }, + ..Default::default() }, ..Default::default() }; @@ -309,15 +312,23 @@ async fn app_can_disable_community_pool_spends() -> anyhow::Result<()> { // At the outset, the pool should be empty. assert_eq!( - original_pool_balance, - BTreeMap::default(), + original_pool_balance.len(), + 1, + "fresh community pool only track the staking token" + ); + assert_eq!( + *original_pool_balance + .get(&STAKING_TOKEN_ASSET_ID) + .expect("CP tracks staking token, even with no balance"), + Amount::zero(), "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())] + [(note.asset_id(), note.amount()), + (*STAKING_TOKEN_ASSET_ID, Amount::zero())] .into_iter() .collect::>(), post_deposit_pool_balance, @@ -354,9 +365,12 @@ async fn app_can_disable_community_pool_spends() -> anyhow::Result<()> { // 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::>(), + [ + (note.asset_id(), note.amount()), + (*STAKING_TOKEN_ASSET_ID, Amount::zero()) + ] + .into_iter() + .collect::>(), "a rejected proposal should not decrease the funds of the community pool" ); assert_eq!( 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 index 0f77a8a757..2b7d31be0f 100644 --- a/crates/core/app/tests/app_can_propose_community_pool_spends.rs +++ b/crates/core/app/tests/app_can_propose_community_pool_spends.rs @@ -8,6 +8,7 @@ use { server::consensus::Consensus, CommunityPoolStateReadExt as _, }, + penumbra_asset::STAKING_TOKEN_ASSET_ID, penumbra_community_pool::{ CommunityPoolDeposit, CommunityPoolOutput, CommunityPoolSpend, StateReadExt as _, }, @@ -17,10 +18,11 @@ use { }, penumbra_keys::{ keys::{SpendKey, SpendKeyBytes}, - test_keys::{self}, + test_keys, }, penumbra_mock_client::MockClient, penumbra_mock_consensus::TestNode, + penumbra_num::Amount, penumbra_proto::{ core::keys::v1::{GovernanceKey, IdentityKey}, penumbra::core::component::stake::v1::Validator as PenumbraValidator, @@ -303,17 +305,27 @@ async fn app_can_propose_community_pool_spends() -> anyhow::Result<()> { // At the outset, the pool should be empty. assert_eq!( - original_pool_balance, - BTreeMap::default(), + original_pool_balance.len(), + 1, + "fresh community pool only track the staking token" + ); + assert_eq!( + *original_pool_balance + .get(&STAKING_TOKEN_ASSET_ID) + .expect("CP tracks the staking token"), + Amount::zero(), "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::>(), + [ + (note.asset_id(), note.amount()), + (*STAKING_TOKEN_ASSET_ID, Amount::zero()) + ] + .into_iter() + .collect::>(), post_deposit_pool_balance, "a community pool deposit should be reflected in the visible balance" ); @@ -353,7 +365,9 @@ async fn app_can_propose_community_pool_spends() -> anyhow::Result<()> { // After the proposal passes, we should see the balance decrease by the amount proposed. assert_eq!( post_voting_period_pool_balance, - BTreeMap::default(), + [(*STAKING_TOKEN_ASSET_ID, Amount::zero())] + .into_iter() + .collect::>(), "the successful proposal should decrease the funds of the community pool" ); assert_eq!( diff --git a/crates/core/component/community-pool/src/component.rs b/crates/core/component/community-pool/src/component.rs index 55756cb3dd..06238b4c9b 100644 --- a/crates/core/component/community-pool/src/component.rs +++ b/crates/core/component/community-pool/src/component.rs @@ -28,6 +28,7 @@ impl Component for CommunityPool { match app_state { Some(genesis) => { state.put_community_pool_params(genesis.community_pool_params.clone()); + state.community_pool_deposit(genesis.initial_balance).await; } None => {} } diff --git a/crates/core/component/community-pool/src/component/action_handler/community_pool_deposit.rs b/crates/core/component/community-pool/src/component/action_handler/community_pool_deposit.rs index bfc80b1a2c..f816979bb9 100644 --- a/crates/core/component/community-pool/src/component/action_handler/community_pool_deposit.rs +++ b/crates/core/component/community-pool/src/component/action_handler/community_pool_deposit.rs @@ -14,6 +14,6 @@ impl ActionHandler for CommunityPoolDeposit { } async fn check_and_execute(&self, mut state: S) -> Result<()> { - state.community_pool_deposit(self.value).await + Ok(state.community_pool_deposit(self.value).await) } } diff --git a/crates/core/component/community-pool/src/component/view.rs b/crates/core/component/community-pool/src/component/view.rs index 665cce4674..c36cb3d928 100644 --- a/crates/core/component/community-pool/src/component/view.rs +++ b/crates/core/component/community-pool/src/component/view.rs @@ -52,11 +52,14 @@ pub trait StateWriteExt: StateWrite { self.put(state_key::community_pool_params().into(), params) } - async fn community_pool_deposit(&mut self, value: Value) -> Result<()> { + async fn community_pool_deposit(&mut self, value: Value) { let key = state_key::balance_for_asset(value.asset_id); - let current = self.get(&key).await?.unwrap_or_else(|| Amount::from(0u64)); + let current = self + .get(&key) + .await + .expect("no deserialization errors") + .unwrap_or_else(|| Amount::from(0u64)); self.put(key, current + value.amount); - Ok(()) } async fn community_pool_withdraw(&mut self, value: Value) -> Result<()> { diff --git a/crates/core/component/community-pool/src/genesis.rs b/crates/core/component/community-pool/src/genesis.rs index 1897efec4e..1b5f5b159d 100644 --- a/crates/core/component/community-pool/src/genesis.rs +++ b/crates/core/component/community-pool/src/genesis.rs @@ -1,20 +1,24 @@ use anyhow::Context; +use penumbra_asset::{Value, STAKING_TOKEN_ASSET_ID}; use penumbra_proto::{penumbra::core::component::community_pool::v1 as pb, DomainType}; use serde::{Deserialize, Serialize}; use crate::params::CommunityPoolParameters; -#[derive(Deserialize, Serialize, Debug, Clone, Default)] +#[derive(Deserialize, Serialize, Debug, Clone)] #[serde(try_from = "pb::GenesisContent", into = "pb::GenesisContent")] pub struct Content { /// The initial configuration parameters for the Community Pool component. pub community_pool_params: CommunityPoolParameters, + /// The initial balance of the Community Pool. + pub initial_balance: Value, } impl From for pb::GenesisContent { - fn from(value: Content) -> Self { + fn from(genesis: Content) -> Self { pb::GenesisContent { - community_pool_params: Some(value.community_pool_params.into()), + community_pool_params: Some(genesis.community_pool_params.into()), + initial_balance: Some(genesis.initial_balance.into()), } } } @@ -24,6 +28,10 @@ impl TryFrom for Content { fn try_from(msg: pb::GenesisContent) -> Result { Ok(Content { + initial_balance: msg + .initial_balance + .context("Initial balance not present in protobuf message")? + .try_into()?, community_pool_params: msg .community_pool_params .context("Community Pool params not present in protobuf message")? @@ -35,3 +43,15 @@ impl TryFrom for Content { impl DomainType for Content { type Proto = pb::GenesisContent; } + +impl Default for Content { + fn default() -> Self { + Content { + community_pool_params: CommunityPoolParameters::default(), + initial_balance: Value { + amount: 0u128.into(), + asset_id: *STAKING_TOKEN_ASSET_ID, + }, + } + } +} diff --git a/crates/core/component/funding/src/component.rs b/crates/core/component/funding/src/component.rs index 5e03cbffef..b459eac239 100644 --- a/crates/core/component/funding/src/component.rs +++ b/crates/core/component/funding/src/component.rs @@ -145,7 +145,7 @@ impl Component for Funding { amount: reward_amount_for_stream.into(), asset_id: *STAKING_TOKEN_ASSET_ID, }) - .await?; + .await; } } } diff --git a/crates/proto/src/gen/penumbra.core.component.community_pool.v1.rs b/crates/proto/src/gen/penumbra.core.component.community_pool.v1.rs index ff601550e8..98e2df1748 100644 --- a/crates/proto/src/gen/penumbra.core.component.community_pool.v1.rs +++ b/crates/proto/src/gen/penumbra.core.component.community_pool.v1.rs @@ -22,6 +22,9 @@ pub struct GenesisContent { /// CommunityPool parameters. #[prost(message, optional, tag = "1")] pub community_pool_params: ::core::option::Option, + /// The initial balance of the Community Pool. + #[prost(message, optional, tag = "2")] + pub initial_balance: ::core::option::Option, } impl ::prost::Name for GenesisContent { const NAME: &'static str = "GenesisContent"; diff --git a/crates/proto/src/gen/penumbra.core.component.community_pool.v1.serde.rs b/crates/proto/src/gen/penumbra.core.component.community_pool.v1.serde.rs index e412c7f5b2..23c50bba2f 100644 --- a/crates/proto/src/gen/penumbra.core.component.community_pool.v1.serde.rs +++ b/crates/proto/src/gen/penumbra.core.component.community_pool.v1.serde.rs @@ -296,10 +296,16 @@ impl serde::Serialize for GenesisContent { if self.community_pool_params.is_some() { len += 1; } + if self.initial_balance.is_some() { + len += 1; + } let mut struct_ser = serializer.serialize_struct("penumbra.core.component.community_pool.v1.GenesisContent", len)?; if let Some(v) = self.community_pool_params.as_ref() { struct_ser.serialize_field("communityPoolParams", v)?; } + if let Some(v) = self.initial_balance.as_ref() { + struct_ser.serialize_field("initialBalance", v)?; + } struct_ser.end() } } @@ -312,11 +318,14 @@ impl<'de> serde::Deserialize<'de> for GenesisContent { const FIELDS: &[&str] = &[ "community_pool_params", "communityPoolParams", + "initial_balance", + "initialBalance", ]; #[allow(clippy::enum_variant_names)] enum GeneratedField { CommunityPoolParams, + InitialBalance, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -340,6 +349,7 @@ impl<'de> serde::Deserialize<'de> for GenesisContent { { match value { "communityPoolParams" | "community_pool_params" => Ok(GeneratedField::CommunityPoolParams), + "initialBalance" | "initial_balance" => Ok(GeneratedField::InitialBalance), _ => Ok(GeneratedField::__SkipField__), } } @@ -360,6 +370,7 @@ impl<'de> serde::Deserialize<'de> for GenesisContent { V: serde::de::MapAccess<'de>, { let mut community_pool_params__ = None; + let mut initial_balance__ = None; while let Some(k) = map_.next_key()? { match k { GeneratedField::CommunityPoolParams => { @@ -368,6 +379,12 @@ impl<'de> serde::Deserialize<'de> for GenesisContent { } community_pool_params__ = map_.next_value()?; } + GeneratedField::InitialBalance => { + if initial_balance__.is_some() { + return Err(serde::de::Error::duplicate_field("initialBalance")); + } + initial_balance__ = map_.next_value()?; + } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } @@ -375,6 +392,7 @@ impl<'de> serde::Deserialize<'de> for GenesisContent { } Ok(GenesisContent { community_pool_params: community_pool_params__, + initial_balance: initial_balance__, }) } } diff --git a/crates/proto/src/gen/proto_descriptor.bin.no_lfs b/crates/proto/src/gen/proto_descriptor.bin.no_lfs index da6e9112163e8dd5cc38597223c5e97d5dea53c7..ff73c23a12060ebed4f31723386b6ad5f0bf7e8b 100644 GIT binary patch delta 464 zcmXw#K}!Nb6vy|?&aOI&*|NK&tGHVjDN-GT3Nq9~(4k9uu}fH331S^0x&+ZBI;Mv? zbPYU2x4={M6(YmjK7z;S8uTXJ!~gJm^WN{x%+sCy)wZvm;rpGA!fc~cT%X~EEjrJ0 zeRzd8{?muskhaOD8m-1zqt@Kxj}J;VvJ;+gsn)Eu4(h$j4`@T+*T+Kn zNV5VXs-#g}G9i}hB!@BH`GmQ$?$=q6QB@+6CL82JObI}Tq1ql1B&XyNZZo#a9--f; zoUYP;^Q0R85%9TaUPAb zMw9qOQ!eDgNq>edqEQw`hwr+uoNy+u#u_>tGx2XkLWgtHbpb;#GKOLljN0qEK6U92 Dimq8s delta 333 zcmXw!F)svB5P;vjo!eb=ZY9qpXP3?G5{XlA5ryJTj{YjLp`#F$L?uem93_g;_z604 z4V7QOI_|dDC`B!1i!Hw5n`FMpWY^LCRdn_MKX3S4q4k@M))adwe8C0(rf`X~`xNut z00RCSfYbkeha(ufb;vfTRK$B9@Cp;W@(Jga4ryB%G^z^0D#tq*aAyc}iE*n`CbX4S z#+uZpG)|>lK+KK`(_-;vVYXOzt1wG!`-FB$qc&Jm0R&xczE}aLLuh$5w?PMlsPh9p zha)&#^EKt$`Ak}VdAcyU>d(v18U diff --git a/proto/penumbra/penumbra/core/component/community_pool/v1/community_pool.proto b/proto/penumbra/penumbra/core/component/community_pool/v1/community_pool.proto index f9655edbe8..6f9b20c55c 100644 --- a/proto/penumbra/penumbra/core/component/community_pool/v1/community_pool.proto +++ b/proto/penumbra/penumbra/core/component/community_pool/v1/community_pool.proto @@ -13,6 +13,8 @@ message CommunityPoolParameters { message GenesisContent { // CommunityPool parameters. CommunityPoolParameters community_pool_params = 1; + // The initial balance of the Community Pool. + core.asset.v1.Value initial_balance = 2; } // Query operations for the community_pool component.