diff --git a/crates/apps_lib/src/client/rpc.rs b/crates/apps_lib/src/client/rpc.rs index 4f416e60113..efe6279044e 100644 --- a/crates/apps_lib/src/client/rpc.rs +++ b/crates/apps_lib/src/client/rpc.rs @@ -26,7 +26,7 @@ use namada_sdk::governance::pgf::parameters::PgfParameters; use namada_sdk::governance::pgf::storage::steward::StewardDetail; use namada_sdk::governance::storage::keys as governance_storage; use namada_sdk::governance::storage::proposal::{ - StoragePgfFunding, StorageProposal, + ContPgfFundings, StorageProposal, }; use namada_sdk::governance::utils::{ProposalVotes, VotePower}; use namada_sdk::hash::Hash; @@ -705,11 +705,11 @@ pub async fn query_pgf(context: &impl Namada, _args: args::QueryPgf) { true => { display_line!( context.io(), - "Pgf stewards: no stewards are currently set." + "PGF stewards: no stewards are currently set." ) } false => { - display_line!(context.io(), "Pgf stewards:"); + display_line!(context.io(), "PGF stewards:"); for steward in stewards { display_line!(context.io(), "{:4}- {}", "", steward.address); display_line!(context.io(), "{:4} Reward distribution:", ""); @@ -730,19 +730,30 @@ pub async fn query_pgf(context: &impl Namada, _args: args::QueryPgf) { true => { display_line!( context.io(), - "Pgf fundings: no fundings are currently set." + "\nNo continous PGF distributions exist currently." ) } false => { - display_line!(context.io(), "Pgf fundings:"); - for funding in fundings { - display_line!( - context.io(), - "{:4}- {} for {}", - "", - funding.detail.target(), - funding.detail.amount().to_string_native() - ); + display_line!( + context.io(), + "\nContinuous PGF distributions (per epoch):" + ); + for (str_addr, targets) in fundings { + display_line!(context.io(), "{:4}- {}", "", str_addr); + for (proposal_id, c_target) in targets { + display_line!( + context.io(), + "{:6}- Prop {}: {} native tokens, end epoch = {}", + "", + proposal_id, + c_target.amount().to_string_native(), + if let Some(ep) = c_target.end_epoch { + ep.to_string() + } else { + "None".to_string() + } + ); + } } } } @@ -1184,7 +1195,7 @@ pub async fn query_pgf_stewards( pub async fn query_pgf_fundings( client: &C, -) -> Vec { +) -> ContPgfFundings { unwrap_client_response::(RPC.vp().pgf().funding(client).await) } diff --git a/crates/apps_lib/src/wallet/defaults.rs b/crates/apps_lib/src/wallet/defaults.rs index d24832bee92..b4a16297b80 100644 --- a/crates/apps_lib/src/wallet/defaults.rs +++ b/crates/apps_lib/src/wallet/defaults.rs @@ -4,8 +4,8 @@ pub use dev::{ addresses, albert_address, albert_keypair, bertha_address, bertha_keypair, christel_address, christel_keypair, daewon_address, daewon_keypair, - derive_template_dir, ester_address, ester_keypair, frank_keypair, - get_unencrypted_keypair, is_use_device, keys, tokens, + derive_template_dir, ester_address, ester_keypair, frank_address, + frank_keypair, get_unencrypted_keypair, is_use_device, keys, tokens, validator_account_keypair, validator_address, validator_keypair, validator_keys, }; @@ -119,6 +119,11 @@ mod dev { .into_owned() } + /// An established user address for testing & development + pub fn frank_address() -> Address { + (&frank_keypair().ref_to()).into() + } + /// An implicit user address for testing & development pub fn daewon_address() -> Address { (&daewon_keypair().ref_to()).into() diff --git a/crates/governance/src/cli/onchain.rs b/crates/governance/src/cli/onchain.rs index e3e898cad0f..f50ad9ec3b4 100644 --- a/crates/governance/src/cli/onchain.rs +++ b/crates/governance/src/cli/onchain.rs @@ -17,7 +17,7 @@ use super::validation::{ is_valid_proposal_period, is_valid_start_epoch, }; use crate::parameters::GovernanceParameters; -use crate::storage::proposal::PGFTarget; +use crate::storage::proposal::{ContPGFTarget, PGFTarget}; #[derive( Debug, @@ -289,6 +289,15 @@ impl PgfAction { } } +impl Display for PgfAction { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + PgfAction::Add => write!(f, "Add"), + PgfAction::Remove => write!(f, "Remove"), + } + } +} + /// PGF funding #[derive( Debug, @@ -301,7 +310,7 @@ impl PgfAction { )] pub struct PgfFunding { /// PGF continuous funding - pub continuous: Vec, + pub continuous: Vec, /// PGF retro fundings pub retro: Vec, } @@ -336,11 +345,17 @@ impl Display for PgfFunding { )] pub struct PgfContinuous { /// PGF target - pub target: PGFTarget, + pub target: ContPGFTarget, /// PGF action pub action: PgfAction, } +impl Display for PgfContinuous { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "\n{}:\n{}", &self.action, &self.target) + } +} + /// PGF retro funding #[derive( Debug, diff --git a/crates/governance/src/finalize_block.rs b/crates/governance/src/finalize_block.rs index 56ce65b6ff6..b2774df360a 100644 --- a/crates/governance/src/finalize_block.rs +++ b/crates/governance/src/finalize_block.rs @@ -19,9 +19,7 @@ use crate::event::GovernanceEvent; use crate::pgf::storage::keys as pgf_keys; use crate::pgf::storage::steward::StewardDetail; use crate::pgf::{ADDRESS as PGF_ADDRESS, storage as pgf_storage}; -use crate::storage::proposal::{ - AddRemove, PGFAction, PGFTarget, ProposalType, StoragePgfFunding, -}; +use crate::storage::proposal::{AddRemove, PGFAction, PGFTarget, ProposalType}; use crate::storage::{keys, load_proposals}; use crate::utils::{ ProposalVotes, TallyResult, TallyType, VotePower, compute_proposal_result, @@ -449,29 +447,38 @@ where match funding { PGFAction::Continuous(action) => match action { AddRemove::Add(target) => { - pgf_keys::fundings_handle().insert( - storage, - target.target().clone(), - StoragePgfFunding::new(target.clone(), proposal_id), - )?; tracing::info!( - "Added/Updated Continuous PGF from proposal id {}: \ - set {} to {}.", + "Adding Continuous PGF for {} from Proposal {} in the \ + amount of {} per epoch, {}.", + target.target.target(), proposal_id, - target.amount().to_string_native(), - target.target() + target.target.amount().to_string_native(), + if let Some(ep) = target.end_epoch { + format!("until epoch {}", ep) + } else { + "indefinitely".to_string() + } ); + pgf_keys::fundings_handle() + .at(&target.target.target()) + .insert(storage, proposal_id, target.clone())?; } AddRemove::Remove(target) => { - pgf_keys::fundings_handle() - .remove(storage, &target.target())?; tracing::info!( - "Removed Continuous PGF from proposal id {}: set {} \ - to {}.", - proposal_id, - target.amount().to_string_native(), - target.target() + "Removing Continuous PGF for {} from Proposal {} (set \ + to {} native tokens, end epoch: {}).", + target.target.target(), + target.proposal_id, + target.target.amount().to_string_native(), + if let Some(ep) = target.end_epoch { + format!("{}", ep) + } else { + "None".to_string() + } ); + pgf_keys::fundings_handle() + .at(&target.target.target()) + .remove(storage, &target.proposal_id)?; } }, PGFAction::Retro(target) => { @@ -525,16 +532,16 @@ where match result { Ok(()) => { tracing::info!( - "Execute Retroactive PGF from proposal id {}: \ - sent {} to {}.", + "Execute Retroactive PGF from Proposal {}: {} \ + native tokens transferred to {}.", proposal_id, target.amount().to_string_native(), target.target() ); } Err(e) => tracing::warn!( - "Error in Retroactive PGF transfer from proposal id \ - {}, amount {} to {}: {}", + "Error in Retroactive PGF transfer from Proposal {}, \ + attempt to transfer {} native tokens to {}: {}", proposal_id, target.amount().to_string_native(), target.target(), diff --git a/crates/governance/src/pgf/inflation.rs b/crates/governance/src/pgf/inflation.rs index 35f5597096c..9c1963bfe46 100644 --- a/crates/governance/src/pgf/inflation.rs +++ b/crates/governance/src/pgf/inflation.rs @@ -4,12 +4,25 @@ use namada_core::address::Address; use namada_state::{Result, StorageRead, StorageWrite}; use namada_systems::{parameters, trans_token}; +use crate::pgf::storage::keys::fundings_handle; use crate::pgf::storage::{ get_continuous_pgf_payments, get_parameters, get_stewards, }; use crate::storage::proposal::{PGFIbcTarget, PGFTarget}; -/// Apply the PGF inflation. +fn remove_cpgf_target( + storage: &mut S, + id: &u64, + target_address: &String, +) -> Result<()> +where + S: StorageRead + StorageWrite, +{ + fundings_handle().at(target_address).remove(storage, id)?; + Ok(()) +} + +/// Apply the PGF inflation. Also pub fn apply_inflation( storage: &mut S, transfer_over_ibc: F, @@ -26,6 +39,7 @@ where let epochs_per_year = Params::epochs_per_year(storage)?; let total_supply = TransToken::get_effective_total_native_supply(storage)?; + // Mint tokens into the PGF address let pgf_inflation_amount = total_supply .mul_floor(pgf_parameters.pgf_inflation_rate)? .checked_div_u64(epochs_per_year) @@ -39,46 +53,60 @@ where )?; tracing::info!( - "Minting {} tokens for PGF rewards distribution into the PGF account \ - (total supply {}).", + "Minting {} native tokens for PGF rewards distribution into the PGF \ + account (total supply: {}).", pgf_inflation_amount.to_string_native(), total_supply.to_string_native() ); - let mut pgf_fundings = get_continuous_pgf_payments(storage)?; - // prioritize the payments by oldest gov proposal ID - pgf_fundings.sort_by(|a, b| a.id.cmp(&b.id)); + // TODO: make sure this is still sorted prioritizing older proposals + let pgf_fundings = get_continuous_pgf_payments(storage)?; - for funding in pgf_fundings { - let result = match &funding.detail { - PGFTarget::Internal(target) => TransToken::transfer( - storage, - &staking_token, - &super::ADDRESS, - &target.target, - target.amount, - ), - PGFTarget::Ibc(target) => transfer_over_ibc( - storage, - &staking_token, - &super::ADDRESS, - target, - ), - }; - match result { - Ok(()) => { - tracing::info!( - "Paying {} tokens for {} project.", - funding.detail.amount().to_string_native(), - &funding.detail.target(), - ); + let current_epoch = storage.get_block_epoch()?; + + // Act on the continuous PGF fundings in storage: either distribute or + // remove expired ones + for (str_target_address, targets) in pgf_fundings { + for (proposal_id, c_target) in targets { + // Remove expired fundings from storage + if c_target.is_expired(current_epoch) { + remove_cpgf_target(storage, &proposal_id, &str_target_address)?; + continue; } - Err(_) => { - tracing::warn!( - "Failed to pay {} tokens for {} project.", - funding.detail.amount().to_string_native(), - &funding.detail.target(), - ); + + // Transfer PGF payment to target + let result = match &c_target.target { + PGFTarget::Internal(target) => TransToken::transfer( + storage, + &staking_token, + &super::ADDRESS, + &target.target, + target.amount, + ), + PGFTarget::Ibc(target) => transfer_over_ibc( + storage, + &staking_token, + &super::ADDRESS, + target, + ), + }; + match result { + // TODO: not hardcode "NAM" below?? + Ok(()) => { + tracing::info!( + "Successfully transferred CPGF payment of {} NAM to \ + {}.", + c_target.amount().to_string_native(), + &c_target.target(), + ); + } + Err(_) => { + tracing::warn!( + "Failed to transfer CPGF payment of {} NAM to {}.", + c_target.amount().to_string_native(), + &c_target.target(), + ); + } } } } @@ -104,15 +132,16 @@ where .is_ok() { tracing::info!( - "Minting {} tokens for steward {} (total supply {})..", + "Minting {} native tokens for steward {} (total supply: \ + {})..", pgf_steward_reward.to_string_native(), address, total_supply.to_string_native() ); } else { tracing::warn!( - "Failed minting {} tokens for steward {} (total supply \ - {})..", + "Failed minting {} native tokens for steward {} (total \ + supply: {})..", pgf_steward_reward.to_string_native(), address, total_supply.to_string_native() diff --git a/crates/governance/src/pgf/storage/keys.rs b/crates/governance/src/pgf/storage/keys.rs index 20ca69f71fe..da1214ea6cf 100644 --- a/crates/governance/src/pgf/storage/keys.rs +++ b/crates/governance/src/pgf/storage/keys.rs @@ -5,7 +5,7 @@ use namada_state::collections::{LazyCollection, LazyMap, lazy_map}; use crate::pgf::ADDRESS; use crate::pgf::storage::steward::StewardDetail; -use crate::storage::proposal::StoragePgfFunding; +use crate::storage::proposal::ContPGFTarget; /// Storage keys for pgf internal address. #[derive(StorageKeys)] @@ -61,8 +61,10 @@ pub fn fundings_key_prefix() -> Key { } } -/// LazyMap handler for the pgf fundings substorage -pub fn fundings_handle() -> LazyMap { +/// Nested LazyMap handler for the continuous PGF substorage. Structure: Address +/// -> Proposal ID -> Target data +pub fn fundings_handle() +-> lazy_map::NestedMap> { LazyMap::open(fundings_key_prefix()) } diff --git a/crates/governance/src/pgf/storage/mod.rs b/crates/governance/src/pgf/storage/mod.rs index e37a6532c72..b79ff5f755b 100644 --- a/crates/governance/src/pgf/storage/mod.rs +++ b/crates/governance/src/pgf/storage/mod.rs @@ -8,12 +8,13 @@ pub mod steward; use namada_core::address::Address; use namada_core::collections::HashMap; use namada_core::dec::Dec; +use namada_state::collections::lazy_map::Collectable; use namada_state::{Result, StorageRead, StorageWrite}; use crate::pgf::parameters::PgfParameters; use crate::pgf::storage::keys as pgf_keys; use crate::pgf::storage::steward::StewardDetail; -use crate::storage::proposal::StoragePgfFunding; +use crate::storage::proposal::ContPgfFundings; /// Query the current pgf steward set pub fn get_stewards(storage: &S) -> Result> @@ -61,20 +62,11 @@ where } /// Query the current pgf continuous payments -pub fn get_continuous_pgf_payments( - storage: &S, -) -> Result> +pub fn get_continuous_pgf_payments(storage: &S) -> Result where S: StorageRead, { - let fundings = pgf_keys::fundings_handle() - .iter(storage)? - .filter_map(|data| match data { - Ok((_, funding)) => Some(funding), - Err(_) => None, - }) - .collect::>(); - + let fundings = pgf_keys::fundings_handle().collect_map(storage)?; Ok(fundings) } diff --git a/crates/governance/src/storage/mod.rs b/crates/governance/src/storage/mod.rs index 7ecb457d167..ebb8b25866f 100644 --- a/crates/governance/src/storage/mod.rs +++ b/crates/governance/src/storage/mod.rs @@ -17,6 +17,7 @@ use namada_core::hash::Hash; use namada_core::token; use namada_state::{Error, Result, StorageRead, StorageWrite, iter_prefix}; use namada_systems::trans_token; +use proposal::{ContPGFTarget, PGFAction}; use crate::ADDRESS as governance_address; use crate::parameters::GovernanceParameters; @@ -52,7 +53,7 @@ where storage.write(&author_key, data.author.clone())?; let proposal_type_key = governance_keys::get_proposal_type_key(proposal_id); - match data.r#type { + match data.r#type.clone() { ProposalType::DefaultWithWasm(_) => { storage.write(&proposal_type_key, data.r#type.clone())?; let proposal_code_key = @@ -62,6 +63,35 @@ where code.ok_or(Error::new_const("Missing proposal code"))?; storage.write(&proposal_code_key, proposal_code)?; } + ProposalType::PGFPayment(a) => { + let transformed_data = a + .into_iter() + .map(|action| match action { + proposal::PGFAction::Continuous(add_remove) => { + let pgf_target = match add_remove { + proposal::AddRemove::Add(c_tgt) => { + proposal::AddRemove::Add(ContPGFTarget { + target: c_tgt.target, + end_epoch: c_tgt.end_epoch, + proposal_id, + }) + } + proposal::AddRemove::Remove(c) => { + proposal::AddRemove::Remove(c) + } + }; + proposal::PGFAction::Continuous(pgf_target) + } + proposal::PGFAction::Retro(pgftarget) => { + proposal::PGFAction::Retro(pgftarget) + } + }) + .collect::>(); + storage.write( + &proposal_type_key, + ProposalType::PGFPayment(transformed_data), + )?; + } _ => storage.write(&proposal_type_key, data.r#type.clone())?, } diff --git a/crates/governance/src/storage/proposal.rs b/crates/governance/src/storage/proposal.rs index c8cfd4d1464..2ba9f750970 100644 --- a/crates/governance/src/storage/proposal.rs +++ b/crates/governance/src/storage/proposal.rs @@ -139,11 +139,12 @@ impl TryFrom for InitProposalData { .continuous .iter() .cloned() - .map(|target| { - if target.amount().is_zero() { - PGFAction::Continuous(AddRemove::Remove(target)) - } else { - PGFAction::Continuous(AddRemove::Add(target)) + .map(|c_target| match c_target.action { + PgfAction::Add => { + PGFAction::Continuous(AddRemove::Add(c_target.target)) + } + PgfAction::Remove => { + PGFAction::Continuous(AddRemove::Remove(c_target.target)) } }) .collect::>(); @@ -184,18 +185,21 @@ impl TryFrom for InitProposalData { )] pub struct StoragePgfFunding { /// The data about the pgf funding - pub detail: PGFTarget, + pub detail: ContPGFTarget, /// The id of the proposal that added this funding pub id: u64, } impl StoragePgfFunding { /// Init a new pgf funding struct - pub fn new(detail: PGFTarget, id: u64) -> Self { + pub fn new(detail: ContPGFTarget, id: u64) -> Self { Self { detail, id } } } +/// Sorted map of continuous pgf distributions +pub type ContPgfFundings = BTreeMap>; + /// The type of a Proposal #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive( @@ -259,6 +263,86 @@ where } } +/// The actions that yo momma can execute +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +#[derive( + Debug, + Clone, + PartialEq, + BorshSchema, + BorshSerialize, + BorshDeserialize, + BorshDeserializer, + Serialize, + Deserialize, + Eq, + Ord, + PartialOrd, + Hash, +)] +pub struct ContPGFTarget { + /// PGF target + pub target: PGFTarget, + /// The epoch at which the funding ends, if any + pub end_epoch: Option, + /// The proposal ID that added this PGF payment + pub proposal_id: u64, +} + +impl Display for ContPGFTarget { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self.target.clone() { + PGFTarget::Internal(t) => { + write!( + f, + "Internal address={}, amount={}, end epoch = {}", + t.target, + t.amount, + if let Some(epoch) = self.end_epoch { + epoch.0.to_string() + } else { + String::from("None") + } + ) + } + PGFTarget::Ibc(t) => { + write!( + f, + "IBC address={}, amount={}, end epoch = {}", + t.target, + t.amount, + if let Some(epoch) = self.end_epoch { + epoch.0.to_string() + } else { + String::from("None") + } + ) + } + } + } +} + +impl ContPGFTarget { + /// Returns the funding target as String + pub fn target(&self) -> String { + self.target.target() + } + + /// Returns the funding amount + pub fn amount(&self) -> token::Amount { + self.target.amount() + } + + /// Check if the funding is expired + pub fn is_expired(&self, current_epoch: Epoch) -> bool { + if let Some(end_epoch) = self.end_epoch { + current_epoch >= end_epoch + } else { + false + } + } +} + /// The target of a PGF payment #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive( @@ -357,7 +441,7 @@ pub struct PGFInternalTarget { )] pub enum PGFAction { /// A continuous payment - Continuous(AddRemove), + Continuous(AddRemove), /// A retro payment Retro(PGFTarget), } @@ -660,10 +744,28 @@ pub mod testing { ] } + prop_compose! { + /// Generate a proposal initialization + pub fn arb_cpgf_target()( + target in arb_pgf_target(), + end_epoch in arb_epoch_opt(), + proposal_id in 0..u64::MAX, + ) -> ContPGFTarget { + ContPGFTarget { + target, + end_epoch,proposal_id + } + } + } + + fn arb_epoch_opt() -> impl Strategy> { + prop_oneof![Just(None), arb_epoch().prop_map(Some),] + } + /// Generate an arbitrary PGF action pub fn arb_pgf_action() -> impl Strategy { prop_oneof![ - arb_add_remove(arb_pgf_target()).prop_map(PGFAction::Continuous), + arb_add_remove(arb_cpgf_target()).prop_map(PGFAction::Continuous), arb_pgf_target().prop_map(PGFAction::Retro), ] } diff --git a/crates/governance/src/vp/mod.rs b/crates/governance/src/vp/mod.rs index 896edb95584..ad0f6067533 100644 --- a/crates/governance/src/vp/mod.rs +++ b/crates/governance/src/vp/mod.rs @@ -531,18 +531,9 @@ where )); } - // can't remove and add the same target in the same proposal - let are_targets_unique = are_continuous_add_targets_unique - .intersection(&are_continuous_remove_targets_unique) - .count() as u64 - == 0; - - are_targets_unique.ok_or_else(|| { - Error::new_const( - "One or more payment targets were added and removed \ - in the same proposal", - ) - }) + // Should we add a check to the VP for the new data format? + + Ok(()) } // Default proposal condition are checked already for all other // proposals. diff --git a/crates/sdk/src/queries/vp/pgf.rs b/crates/sdk/src/queries/vp/pgf.rs index 1cd1c6d37c3..b920f90de87 100644 --- a/crates/sdk/src/queries/vp/pgf.rs +++ b/crates/sdk/src/queries/vp/pgf.rs @@ -1,7 +1,7 @@ use namada_core::address::Address; use namada_governance::pgf::parameters::PgfParameters; use namada_governance::pgf::storage::steward::StewardDetail; -use namada_governance::storage::proposal::StoragePgfFunding; +use namada_governance::storage::proposal::ContPgfFundings; use namada_state::{DB, DBIter, StorageHasher}; use crate::queries::types::RequestCtx; @@ -10,7 +10,7 @@ use crate::queries::types::RequestCtx; router! {PGF, ( "stewards" / [ address: Address ] ) -> bool = is_steward, ( "stewards" ) -> Vec = stewards, - ( "fundings" ) -> Vec = funding, + ( "fundings" ) -> ContPgfFundings = funding, ( "parameters" ) -> PgfParameters = parameters, } @@ -40,7 +40,7 @@ where /// Query the continuous pgf fundings fn funding( ctx: RequestCtx<'_, D, H, V, T>, -) -> namada_storage::Result> +) -> namada_storage::Result where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, diff --git a/crates/sdk/src/rpc.rs b/crates/sdk/src/rpc.rs index 924eee24d10..e15f9eba9a7 100644 --- a/crates/sdk/src/rpc.rs +++ b/crates/sdk/src/rpc.rs @@ -36,9 +36,7 @@ use namada_gas::event::GasUsed as GasUsedAttr; use namada_governance::parameters::GovernanceParameters; use namada_governance::pgf::parameters::PgfParameters; use namada_governance::pgf::storage::steward::StewardDetail; -use namada_governance::storage::proposal::{ - StoragePgfFunding, StorageProposal, -}; +use namada_governance::storage::proposal::{ContPgfFundings, StorageProposal}; use namada_governance::utils::{ ProposalResult, ProposalVotes, Vote, compute_proposal_result, }; @@ -328,10 +326,8 @@ pub async fn query_pgf_stewards( /// Get the set of pgf fundings pub async fn query_pgf_fundings( client: &C, -) -> Result, error::Error> { - convert_response::>( - RPC.vp().pgf().funding(client).await, - ) +) -> Result { + convert_response::(RPC.vp().pgf().funding(client).await) } /// Query the consensus key by validator address diff --git a/crates/sdk/src/signing.rs b/crates/sdk/src/signing.rs index f8ca8722fb4..f7d9857f782 100644 --- a/crates/sdk/src/signing.rs +++ b/crates/sdk/src/signing.rs @@ -24,7 +24,7 @@ use namada_core::tendermint::Time as TmTime; use namada_core::time::DateTimeUtc; use namada_core::token::{Amount, DenominatedAmount}; use namada_governance::storage::proposal::{ - InitProposalData, ProposalType, VoteProposalData, + ContPGFTarget, InitProposalData, ProposalType, VoteProposalData, }; use namada_governance::storage::vote::ProposalVote; use namada_ibc::core::channel::types::timeout::{ @@ -1144,9 +1144,11 @@ fn proposal_type_to_ledger_vector( output.push("Proposal type : PGF Payment".to_string()); for action in actions { match action { - PGFAction::Continuous(AddRemove::Add( - PGFTarget::Internal(target), - )) => { + PGFAction::Continuous(AddRemove::Add(ContPGFTarget { + target: PGFTarget::Internal(target), + end_epoch, + proposal_id: _, + })) => { output.push( "PGF Action : Add Continuous Payment".to_string(), ); @@ -1157,10 +1159,20 @@ fn proposal_type_to_ledger_vector( &target.amount.to_string_native() ) )); + output.push(format!( + "End Epoch: {}", + if let Some(end_epoch) = end_epoch { + end_epoch.0.to_string() + } else { + "None".to_string() + } + )); } - PGFAction::Continuous(AddRemove::Add(PGFTarget::Ibc( - target, - ))) => { + PGFAction::Continuous(AddRemove::Add(ContPGFTarget { + target: PGFTarget::Ibc(target), + end_epoch, + proposal_id: _, + })) => { output.push( "PGF Action : Add Continuous Payment".to_string(), ); @@ -1171,17 +1183,30 @@ fn proposal_type_to_ledger_vector( &target.amount.to_string_native() ) )); + output.push(format!( + "End Epoch: {}", + if let Some(end_epoch) = end_epoch { + end_epoch.0.to_string() + } else { + "None".to_string() + } + )); output.push(format!("Port ID: {}", target.port_id)); output .push(format!("Channel ID: {}", target.channel_id)); } PGFAction::Continuous(AddRemove::Remove( - PGFTarget::Internal(target), + ContPGFTarget { + target: PGFTarget::Internal(target), + end_epoch, + proposal_id, + }, )) => { output.push( "PGF Action : Remove Continuous Payment" .to_string(), ); + output.push(format!("Proposal ID: {}", proposal_id)); output.push(format!("Target: {}", target.target)); output.push(format!( "Amount: NAM {}", @@ -1189,14 +1214,27 @@ fn proposal_type_to_ledger_vector( &target.amount.to_string_native() ) )); + output.push(format!( + "End Epoch: {}", + if let Some(end_epoch) = end_epoch { + end_epoch.0.to_string() + } else { + "None".to_string() + } + )); } PGFAction::Continuous(AddRemove::Remove( - PGFTarget::Ibc(target), + ContPGFTarget { + target: PGFTarget::Ibc(target), + end_epoch, + proposal_id, + }, )) => { output.push( "PGF Action : Remove Continuous Payment" .to_string(), ); + output.push(format!("Proposal ID: {}", proposal_id)); output.push(format!("Target: {}", target.target)); output.push(format!( "Amount: NAM {}", @@ -1204,6 +1242,14 @@ fn proposal_type_to_ledger_vector( &target.amount.to_string_native() ) )); + output.push(format!( + "End Epoch: {}", + if let Some(end_epoch) = end_epoch { + end_epoch.0.to_string() + } else { + "None".to_string() + } + )); output.push(format!("Port ID: {}", target.port_id)); output .push(format!("Channel ID: {}", target.channel_id)); @@ -2402,6 +2448,7 @@ mod test_signing { use namada_core::token::{Denomination, MaspDigitPos}; use namada_governance::storage::proposal::PGFInternalTarget; use namada_io::client::EncodedResponseQuery; + use namada_state::Epoch; use namada_tx::{Code, Data}; use namada_wallet::test_utils::TestWalletUtils; use tendermint_rpc::SimpleRequest; @@ -3207,10 +3254,14 @@ mod test_signing { // PGF payments proposal_type_to_ledger_vector( &ProposalType::PGFPayment(BTreeSet::from([PGFAction::Continuous( - AddRemove::Add(PGFTarget::Internal(PGFInternalTarget { - target: addr.clone(), - amount: Amount::zero(), - })), + AddRemove::Add(ContPGFTarget { + target: PGFTarget::Internal(PGFInternalTarget { + target: addr.clone(), + amount: Amount::zero(), + }), + end_epoch: Some(Epoch::from(1)), + proposal_id: 0, + }), // TODO: ask Murisi if this is ok )])), &tx, &mut output, @@ -3221,17 +3272,23 @@ mod test_signing { vec![ "Proposal type : PGF Payment".to_string(), "PGF Action : Add Continuous Payment".to_string(), + "Proposal ID: 0".to_string(), format!("Target: {addr}"), "Amount: NAM 0".to_string(), + "End Epoch: 1".to_string(), ], ); output.clear(); proposal_type_to_ledger_vector( &ProposalType::PGFPayment(BTreeSet::from([PGFAction::Continuous( - AddRemove::Remove(PGFTarget::Internal(PGFInternalTarget { - target: addr.clone(), - amount: Amount::zero(), - })), + AddRemove::Remove(ContPGFTarget { + target: PGFTarget::Internal(PGFInternalTarget { + target: addr.clone(), + amount: Amount::zero(), + }), + end_epoch: Some(Epoch::from(1)), + proposal_id: 0, + }), // TODO: ask Murisi if this is ok )])), &tx, &mut output, @@ -3242,8 +3299,10 @@ mod test_signing { vec![ "Proposal type : PGF Payment".to_string(), "PGF Action : Remove Continuous Payment".to_string(), + "Proposal ID: 0".to_string(), format!("Target: {addr}"), "Amount: NAM 0".to_string(), + "End Epoch: 1".to_string(), ], ); output.clear(); @@ -3272,12 +3331,16 @@ mod test_signing { proposal_type_to_ledger_vector( &ProposalType::PGFPayment(BTreeSet::from([PGFAction::Continuous( - AddRemove::Add(PGFTarget::Ibc(PGFIbcTarget { - target: "bloop".to_string(), - amount: Default::default(), - port_id: PortId::transfer(), - channel_id: ChannelId::new(16), - })), + AddRemove::Add(ContPGFTarget { + target: PGFTarget::Ibc(PGFIbcTarget { + target: "bloop".to_string(), + amount: Default::default(), + port_id: PortId::transfer(), + channel_id: ChannelId::new(16), + }), + end_epoch: None, + proposal_id: 0, + }), // TODO: ask Murisi if this is ok )])), &tx, &mut output, @@ -3290,6 +3353,7 @@ mod test_signing { "PGF Action : Add Continuous Payment".to_string(), "Target: bloop".to_string(), "Amount: NAM 0".to_string(), + "End Epoch: None".to_string(), "Port ID: transfer".to_string(), "Channel ID: channel-16".to_string(), ], @@ -3298,12 +3362,16 @@ mod test_signing { proposal_type_to_ledger_vector( &ProposalType::PGFPayment(BTreeSet::from([PGFAction::Continuous( - AddRemove::Remove(PGFTarget::Ibc(PGFIbcTarget { - target: "bloop".to_string(), - amount: Default::default(), - port_id: PortId::transfer(), - channel_id: ChannelId::new(16), - })), + AddRemove::Remove(ContPGFTarget { + target: PGFTarget::Ibc(PGFIbcTarget { + target: "bloop".to_string(), + amount: Default::default(), + port_id: PortId::transfer(), + channel_id: ChannelId::new(16), + }), + end_epoch: None, + proposal_id: 0, + }), // TODO: ask Murisi if this is ok )])), &tx, &mut output, @@ -3314,8 +3382,10 @@ mod test_signing { vec![ "Proposal type : PGF Payment".to_string(), "PGF Action : Remove Continuous Payment".to_string(), + "Proposal ID: 0".to_string(), "Target: bloop".to_string(), "Amount: NAM 0".to_string(), + "End Epoch: None".to_string(), "Port ID: transfer".to_string(), "Channel ID: channel-16".to_string(), ], diff --git a/crates/tests/src/e2e/ibc_tests.rs b/crates/tests/src/e2e/ibc_tests.rs index 2e5873b4dd6..f6d56f21020 100644 --- a/crates/tests/src/e2e/ibc_tests.rs +++ b/crates/tests/src/e2e/ibc_tests.rs @@ -26,7 +26,9 @@ use namada_apps_lib::tendermint_rpc::{Client, HttpClient, Url}; use namada_core::masp::PaymentAddress; use namada_sdk::address::MASP; use namada_sdk::chain::Epoch; -use namada_sdk::governance::cli::onchain::PgfFunding; +use namada_sdk::governance::cli::onchain::{ + PgfAction, PgfContinuous, PgfFunding, +}; use namada_sdk::governance::pgf::ADDRESS as PGF_ADDRESS; use namada_sdk::governance::storage::proposal::{PGFIbcTarget, PGFTarget}; use namada_sdk::ibc::IbcShieldingData; @@ -46,6 +48,7 @@ use namada_sdk::ibc::storage::*; use namada_sdk::ibc::trace::ibc_token; use namada_sdk::token::Amount; use namada_test_utils::TestWasms; +use namada_tx_prelude::gov_storage::proposal::ContPGFTarget; use prost::Message; use serde_json::json; use setup::constants::*; @@ -2947,12 +2950,19 @@ fn propose_funding( src_channel_id: &ChannelId, ) -> Result { let pgf_funding = PgfFunding { - continuous: vec![PGFTarget::Ibc(PGFIbcTarget { - amount: Amount::native_whole(10), - target: continuous_receiver.as_ref().to_string(), - port_id: src_port_id.clone(), - channel_id: src_channel_id.clone(), - })], + continuous: vec![PgfContinuous { + target: ContPGFTarget { + target: PGFTarget::Ibc(PGFIbcTarget { + amount: Amount::native_whole(10), + target: continuous_receiver.as_ref().to_string(), + port_id: src_port_id.clone(), + channel_id: src_channel_id.clone(), + }), + end_epoch: None, + proposal_id: 0, + }, + action: PgfAction::Add, + }], retro: vec![PGFTarget::Ibc(PGFIbcTarget { amount: Amount::native_whole(5), target: retro_receiver.as_ref().to_string(), diff --git a/crates/tests/src/integration/ledger_tests.rs b/crates/tests/src/integration/ledger_tests.rs index ac4dfdc4fed..add6ffa8819 100644 --- a/crates/tests/src/integration/ledger_tests.rs +++ b/crates/tests/src/integration/ledger_tests.rs @@ -23,12 +23,14 @@ use namada_sdk::account::AccountPublicKeysMap; use namada_sdk::borsh::BorshSerializeExt; use namada_sdk::collections::HashMap; use namada_sdk::error::TxSubmitError; +use namada_sdk::governance::cli::onchain::{PgfAction, PgfContinuous}; use namada_sdk::migrations; use namada_sdk::proof_of_stake::parameters::MAX_VALIDATOR_METADATA_LEN; use namada_sdk::queries::RPC; use namada_sdk::token::{self, DenominatedAmount}; use namada_sdk::tx::{self, TX_TRANSFER_WASM, Tx, VP_USER_WASM}; use namada_test_utils::TestWasms; +use namada_tx_prelude::gov_storage::proposal::ContPGFTarget; use test_log::test; use crate::e2e::ledger_tests::prepare_proposal_data; @@ -1166,6 +1168,646 @@ fn inflation() -> Result<()> { Ok(()) } +/// Test adding and removal of continuous PGF proposal payments +/// +/// 1. Submit a first funding proposal where +/// - Add ALB - 10k unam until epoch 52 +/// - Add CHR - 35k unam indefinitely +/// 2. Query the proposal +/// 3. Vote for the accepted proposals and query balances +/// 4. Check the result passed +/// 5. Wait until activation epoch and check balances +/// 6. Submit a new funding proposal where +/// - Remove CHR - prop 0 +/// - Add CHR - 75k unam until epoch 71 +/// - Add EST - 66.6k unam indefinitely +/// - Add ALB - 166 unam until epoch 55 +/// 7. Vote and confirm it passes then wait until activation epoch +/// 8. Query balances +/// 9. Query PGF fundings +/// 10. Advance to epoch 52 and check that the ALB prop 0 cPGF has expired +/// 10. Submit a new funding proposal where +/// - Remove EST - prop 1 +/// 11. Vote on proposal and pass it +/// 12. Advance to epoch 71 and confirm that no cPGF exists anymore +#[test] +fn continuous_pgf_proposals() -> Result<()> { + // This address doesn't matter for tests. But an argument is required. + let validator_one_rpc = "http://127.0.0.1:26567"; + // 1. start the ledger node + let (mut node, _services) = setup::setup()?; + + let tx_args = apply_use_device(vec![ + "bond", + "--validator", + "validator-0-validator", + "--source", + BERTHA, + "--amount", + "100000", + "--ledger-address", + &validator_one_rpc, + ]); + let captured = CapturedOutput::of(|| run(&node, Bin::Client, tx_args)); + assert_matches!(captured.result, Ok(_)); + assert!(captured.contains(TX_APPLIED_SUCCESS)); + + let albert = defaults::albert_address(); + let ester = defaults::ester_address(); + let christel = defaults::christel_address(); + + println!( + "\n\nALBERT: {}\nESTER: {}\nCHRISTEL: {}\n\n", + &albert, &ester, &christel + ); + + // 1. Submit first PGF funding proposal + let pgf_funding = PgfFunding { + continuous: vec![ + PgfContinuous { + target: ContPGFTarget { + target: PGFTarget::Internal(PGFInternalTarget { + amount: token::Amount::from_u64(10_000), + target: albert.clone(), + }), + end_epoch: Some(Epoch(52)), + proposal_id: 999999, // doesn't matter + }, + action: PgfAction::Add, + }, + PgfContinuous { + target: ContPGFTarget { + target: PGFTarget::Internal(PGFInternalTarget { + amount: token::Amount::from_u64(35_000), + target: christel.clone(), + }), + end_epoch: None, + proposal_id: 9999999, // doesn't matter + }, + action: PgfAction::Add, + }, + ], + retro: vec![PGFTarget::Internal(PGFInternalTarget { + amount: token::Amount::from_u64(5), + target: ester.clone(), + })], + }; + + let valid_proposal_json_path = prepare_proposal_data( + node.test_dir.path(), + albert.clone(), + pgf_funding, + 12, + ); + let submit_proposal_args = apply_use_device(vec![ + "init-proposal", + "--pgf-funding", + "--data-path", + valid_proposal_json_path.to_str().unwrap(), + "--ledger-address", + &validator_one_rpc, + ]); + let captured = + CapturedOutput::of(|| run(&node, Bin::Client, submit_proposal_args)); + assert_matches!(captured.result, Ok(_)); + assert!(captured.contains(TX_APPLIED_SUCCESS)); + + // 2. Query the proposal + let proposal_query_args = vec![ + "query-proposal", + "--proposal-id", + "0", + "--ledger-address", + &validator_one_rpc, + ]; + let captured = + CapturedOutput::of(|| run(&node, Bin::Client, proposal_query_args)); + assert_matches!(captured.result, Ok(_)); + assert!(captured.contains("Proposal Id: 0")); + + // Query token balance proposal author (submitted funds) + let query_balance_args = vec![ + "balance", + "--owner", + ALBERT, + "--token", + NAM, + "--ledger-address", + &validator_one_rpc, + ]; + let captured = + CapturedOutput::of(|| run(&node, Bin::Client, query_balance_args)); + assert_matches!(captured.result, Ok(_)); + assert!(captured.contains("nam: 1979500")); + + // Query token balance governance + let query_balance_args = vec![ + "balance", + "--owner", + GOVERNANCE_ADDRESS, + "--token", + NAM, + "--ledger-address", + &validator_one_rpc, + ]; + let captured = + CapturedOutput::of(|| run(&node, Bin::Client, query_balance_args)); + assert_matches!(captured.result, Ok(_)); + assert!(captured.contains("nam: 500")); + + // 3. Send a yay vote from a validator + while node.current_epoch().0 <= 13 { + node.next_epoch(); + } + + let submit_proposal_vote = apply_use_device(vec![ + "vote-proposal", + "--proposal-id", + "0", + "--vote", + "yay", + "--address", + "validator-0-validator", + "--ledger-address", + &validator_one_rpc, + ]); + let captured = + CapturedOutput::of(|| run(&node, Bin::Client, submit_proposal_vote)); + assert_matches!(captured.result, Ok(_)); + assert!(captured.contains(TX_APPLIED_SUCCESS)); + + // Send different yay vote from delegator to check majority on 1/3 + let submit_proposal_vote_delegator = apply_use_device(vec![ + "vote-proposal", + "--proposal-id", + "0", + "--vote", + "yay", + "--address", + BERTHA, + "--ledger-address", + &validator_one_rpc, + ]); + let captured = CapturedOutput::of(|| { + run(&node, Bin::Client, submit_proposal_vote_delegator) + }); + assert_matches!(captured.result, Ok(_)); + assert!(captured.contains(TX_APPLIED_SUCCESS)); + + while node.current_epoch().0 <= 25 { + node.next_epoch(); + } + + let query_proposal = vec![ + "query-proposal-result", + "--proposal-id", + "0", + "--ledger-address", + &validator_one_rpc, + ]; + let captured = + CapturedOutput::of(|| run(&node, Bin::Client, query_proposal)); + assert_matches!(captured.result, Ok(_)); + assert!(captured.contains("Passed")); + + // 5. Wait for proposal activation epoch and check proposal author funds + while node.current_epoch().0 < 30 { + node.next_epoch(); + } + let query_balance_args = vec![ + "balance", + "--owner", + ALBERT, + "--token", + NAM, + "--ledger-address", + &validator_one_rpc, + ]; + let captured = + CapturedOutput::of(|| run(&node, Bin::Client, query_balance_args)); + assert_matches!(captured.result, Ok(_)); + assert!(captured.contains("nam: 1980000.01")); + + let query_balance_args = vec![ + "balance", + "--owner", + CHRISTEL, + "--token", + NAM, + "--ledger-address", + &validator_one_rpc, + ]; + let captured = + CapturedOutput::of(|| run(&node, Bin::Client, query_balance_args)); + assert_matches!(captured.result, Ok(_)); + assert!(captured.contains("nam: 2000000.035")); + + let query_pgf = vec!["query-pgf", "--node", &validator_one_rpc]; + let captured = CapturedOutput::of(|| run(&node, Bin::Client, query_pgf)); + assert_matches!(captured.result, Ok(_)); + assert!(captured.contains("Continuous PGF distributions")); + assert!(captured.contains(&format!("- {}", &christel))); + assert!( + captured.contains("- Prop 0: 0.035000 native tokens, end epoch = None") + ); + assert!(captured.contains(&format!("- {}", &albert))); + assert!( + captured.contains("- Prop 0: 0.010000 native tokens, end epoch = 52") + ); + + // 6. Submit a new cPGF proposal + let pgf_funding = PgfFunding { + continuous: vec![ + PgfContinuous { + target: ContPGFTarget { + target: PGFTarget::Internal(PGFInternalTarget { + amount: token::Amount::zero(), // doesn't matter + target: christel.clone(), + }), + end_epoch: Some(Epoch(999999)), // doesn't matter + proposal_id: 0, + }, + action: PgfAction::Remove, + }, + PgfContinuous { + target: ContPGFTarget { + target: PGFTarget::Internal(PGFInternalTarget { + amount: token::Amount::from_u64(75000), + target: christel.clone(), + }), + end_epoch: Some(Epoch(71)), + proposal_id: 9999999, // doesn't matter + }, + action: PgfAction::Add, + }, + PgfContinuous { + target: ContPGFTarget { + target: PGFTarget::Internal(PGFInternalTarget { + amount: token::Amount::from_u64(66_600), + target: ester.clone(), + }), + end_epoch: None, + proposal_id: 9999999, // doesn't matter + }, + action: PgfAction::Add, + }, + PgfContinuous { + target: ContPGFTarget { + target: PGFTarget::Internal(PGFInternalTarget { + amount: token::Amount::from_u64(166), + target: albert.clone(), + }), + end_epoch: Some(Epoch(55)), + proposal_id: 9999999, // doesn't matter + }, + action: PgfAction::Add, + }, + ], + retro: vec![], + }; + + let valid_proposal_json_path = prepare_proposal_data( + node.test_dir.path(), + albert.clone(), + pgf_funding, + 31, + ); + let submit_proposal_args = apply_use_device(vec![ + "init-proposal", + "--pgf-funding", + "--data-path", + valid_proposal_json_path.to_str().unwrap(), + "--ledger-address", + &validator_one_rpc, + ]); + let captured = + CapturedOutput::of(|| run(&node, Bin::Client, submit_proposal_args)); + assert_matches!(captured.result, Ok(_)); + assert!(captured.contains(TX_APPLIED_SUCCESS)); + + node.next_epoch(); + let submit_proposal_vote = apply_use_device(vec![ + "vote-proposal", + "--proposal-id", + "1", + "--vote", + "yay", + "--address", + "validator-0-validator", + "--ledger-address", + &validator_one_rpc, + ]); + let captured = + CapturedOutput::of(|| run(&node, Bin::Client, submit_proposal_vote)); + assert_matches!(captured.result, Ok(_)); + assert!(captured.contains(TX_APPLIED_SUCCESS)); + + // Send different yay vote from delegator to check majority on 1/3 + let submit_proposal_vote_delegator = apply_use_device(vec![ + "vote-proposal", + "--proposal-id", + "1", + "--vote", + "yay", + "--address", + BERTHA, + "--ledger-address", + &validator_one_rpc, + ]); + let captured = CapturedOutput::of(|| { + run(&node, Bin::Client, submit_proposal_vote_delegator) + }); + assert_matches!(captured.result, Ok(_)); + assert!(captured.contains(TX_APPLIED_SUCCESS)); + + let query_balance_args = vec![ + "balance", + "--owner", + ALBERT, + "--token", + NAM, + "--ledger-address", + &validator_one_rpc, + ]; + let captured = + CapturedOutput::of(|| run(&node, Bin::Client, query_balance_args)); + assert_matches!(captured.result, Ok(_)); + assert!(captured.contains("nam: 1979500.02")); + + let query_balance_args = vec![ + "balance", + "--owner", + CHRISTEL, + "--token", + NAM, + "--ledger-address", + &validator_one_rpc, + ]; + let captured = + CapturedOutput::of(|| run(&node, Bin::Client, query_balance_args)); + assert_matches!(captured.result, Ok(_)); + assert!(captured.contains("nam: 2000000.07")); + + // Wait until activation epoch + while node.current_epoch().0 < 49 { + node.next_epoch(); + } + + // 11. Query pgf fundings + let query_pgf = vec!["query-pgf", "--node", &validator_one_rpc]; + let captured = CapturedOutput::of(|| run(&node, Bin::Client, query_pgf)); + assert_matches!(captured.result, Ok(_)); + + assert!(captured.contains("Continuous PGF distributions")); + assert!(captured.contains(&format!("- {}", &ester))); + assert!( + captured.contains("- Prop 1: 0.066600 native tokens, end epoch = None") + ); + assert!(captured.contains(&format!("- {}", &christel))); + assert!( + captured.contains("- Prop 1: 0.075000 native tokens, end epoch = 71") + ); + assert!(captured.contains(&format!("- {}", &albert))); + assert!( + captured.contains("- Prop 0: 0.010000 native tokens, end epoch = 52") + ); + assert!( + captured.contains("- Prop 1: 0.000166 native tokens, end epoch = 55") + ); + + let query_balance_args = vec![ + "balance", + "--owner", + ALBERT, + "--token", + NAM, + "--ledger-address", + &validator_one_rpc, + ]; + let captured = + CapturedOutput::of(|| run(&node, Bin::Client, query_balance_args)); + assert_matches!(captured.result, Ok(_)); + assert!(captured.contains("nam: 1980000.200166")); + + let query_balance_args = vec![ + "balance", + "--owner", + CHRISTEL, + "--token", + NAM, + "--ledger-address", + &validator_one_rpc, + ]; + let captured = + CapturedOutput::of(|| run(&node, Bin::Client, query_balance_args)); + assert_matches!(captured.result, Ok(_)); + assert!(captured.contains("nam: 2000000.74")); + + let query_balance_args = vec![ + "balance", + "--owner", + ESTER, + "--token", + NAM, + "--ledger-address", + &validator_one_rpc, + ]; + let captured = + CapturedOutput::of(|| run(&node, Bin::Client, query_balance_args)); + assert_matches!(captured.result, Ok(_)); + assert!(captured.contains("nam: 1000000.066605")); + + while node.current_epoch().0 < 52 { + node.next_epoch(); + } + let query_pgf = vec!["query-pgf", "--node", &validator_one_rpc]; + let captured = CapturedOutput::of(|| run(&node, Bin::Client, query_pgf)); + assert_matches!(captured.result, Ok(_)); + + // TODO: How can I check the absence of a string?? + + assert!(captured.contains("Continuous PGF distributions")); + assert!(captured.contains(&format!("- {}", &ester))); + assert!( + captured.contains("- Prop 1: 0.066600 native tokens, end epoch = None") + ); + assert!(captured.contains(&format!("- {}", &christel))); + assert!( + captured.contains("- Prop 1: 0.075000 native tokens, end epoch = 71") + ); + assert!(captured.contains(&format!("- {}", &albert))); + assert!( + captured.contains("- Prop 1: 0.000166 native tokens, end epoch = 55") + ); + + let query_balance_args = vec![ + "balance", + "--owner", + ALBERT, + "--token", + NAM, + "--ledger-address", + &validator_one_rpc, + ]; + let captured = + CapturedOutput::of(|| run(&node, Bin::Client, query_balance_args)); + assert_matches!(captured.result, Ok(_)); + assert!(captured.contains("nam: 1980000.220664")); + + let query_balance_args = vec![ + "balance", + "--owner", + CHRISTEL, + "--token", + NAM, + "--ledger-address", + &validator_one_rpc, + ]; + let captured = + CapturedOutput::of(|| run(&node, Bin::Client, query_balance_args)); + assert_matches!(captured.result, Ok(_)); + assert!(captured.contains("nam: 2000000.965")); + + let query_balance_args = vec![ + "balance", + "--owner", + ESTER, + "--token", + NAM, + "--ledger-address", + &validator_one_rpc, + ]; + let captured = + CapturedOutput::of(|| run(&node, Bin::Client, query_balance_args)); + assert_matches!(captured.result, Ok(_)); + assert!(captured.contains("nam: 1000000.266405")); + + let pgf_funding = PgfFunding { + continuous: vec![PgfContinuous { + target: ContPGFTarget { + target: PGFTarget::Internal(PGFInternalTarget { + amount: token::Amount::zero(), // doesn't matter + target: ester.clone(), + }), + end_epoch: Some(Epoch(0)), // doesn't matter + proposal_id: 1, + }, + action: PgfAction::Remove, + }], + retro: vec![], + }; + + let valid_proposal_json_path = prepare_proposal_data( + node.test_dir.path(), + albert.clone(), + pgf_funding, + 53, + ); + let submit_proposal_args = apply_use_device(vec![ + "init-proposal", + "--pgf-funding", + "--data-path", + valid_proposal_json_path.to_str().unwrap(), + "--ledger-address", + &validator_one_rpc, + ]); + let captured = + CapturedOutput::of(|| run(&node, Bin::Client, submit_proposal_args)); + assert_matches!(captured.result, Ok(_)); + assert!(captured.contains(TX_APPLIED_SUCCESS)); + + node.next_epoch(); + let submit_proposal_vote = apply_use_device(vec![ + "vote-proposal", + "--proposal-id", + "2", + "--vote", + "yay", + "--address", + "validator-0-validator", + "--ledger-address", + &validator_one_rpc, + ]); + let captured = + CapturedOutput::of(|| run(&node, Bin::Client, submit_proposal_vote)); + assert_matches!(captured.result, Ok(_)); + assert!(captured.contains(TX_APPLIED_SUCCESS)); + + // Send different yay vote from delegator to check majority on 1/3 + let submit_proposal_vote_delegator = apply_use_device(vec![ + "vote-proposal", + "--proposal-id", + "2", + "--vote", + "yay", + "--address", + BERTHA, + "--ledger-address", + &validator_one_rpc, + ]); + let captured = CapturedOutput::of(|| { + run(&node, Bin::Client, submit_proposal_vote_delegator) + }); + assert_matches!(captured.result, Ok(_)); + assert!(captured.contains(TX_APPLIED_SUCCESS)); + + // 10. Wait + while node.current_epoch().0 < 71 { + node.next_epoch(); + } + let query_pgf = vec!["query-pgf", "--node", &validator_one_rpc]; + let captured = CapturedOutput::of(|| run(&node, Bin::Client, query_pgf)); + assert_matches!(captured.result, Ok(_)); + + assert!( + captured.contains("No continous PGF distributions exist currently") + ); + + let query_balance_args = vec![ + "balance", + "--owner", + ALBERT, + "--token", + NAM, + "--ledger-address", + &validator_one_rpc, + ]; + let captured = + CapturedOutput::of(|| run(&node, Bin::Client, query_balance_args)); + assert_matches!(captured.result, Ok(_)); + assert!(captured.contains("nam: 1980000.220996")); + + let query_balance_args = vec![ + "balance", + "--owner", + CHRISTEL, + "--token", + NAM, + "--ledger-address", + &validator_one_rpc, + ]; + let captured = + CapturedOutput::of(|| run(&node, Bin::Client, query_balance_args)); + assert_matches!(captured.result, Ok(_)); + assert!(captured.contains("nam: 2000002.315")); + + let query_balance_args = vec![ + "balance", + "--owner", + ESTER, + "--token", + NAM, + "--ledger-address", + &validator_one_rpc, + ]; + let captured = + CapturedOutput::of(|| run(&node, Bin::Client, query_balance_args)); + assert_matches!(captured.result, Ok(_)); + assert!(captured.contains("nam: 1000001.465205")); + + Ok(()) +} + /// Test submission and vote of a PGF proposal /// /// 1. Submit proposal @@ -1361,13 +2003,15 @@ fn pgf_governance_proposal() -> Result<()> { let query_pgf = vec!["query-pgf", "--node", &validator_one_rpc]; let captured = CapturedOutput::of(|| run(&node, Bin::Client, query_pgf)); assert_matches!(captured.result, Ok(_)); - assert!(captured.contains("Pgf stewards:")); + assert!(captured.contains("PGF stewards:")); assert!(captured.contains(&format!("- {}", defaults::albert_address()))); assert!(captured.contains("Reward distribution:")); assert!( captured.contains(&format!("- 1 to {}", defaults::albert_address())) ); - assert!(captured.contains("Pgf fundings: no fundings are currently set.")); + assert!( + captured.contains("No continous PGF distributions exist currently.") + ); // 7.1 Query total NAM supply and PGF balance let query_balance_args = vec![ @@ -1410,19 +2054,44 @@ fn pgf_governance_proposal() -> Result<()> { let albert = defaults::albert_address(); let bertha = defaults::bertha_address(); let christel = defaults::christel_address(); + let cont_end_epoch = Epoch::from(70); let pgf_funding = PgfFunding { - continuous: vec![PGFTarget::Internal(PGFInternalTarget { - amount: token::Amount::from_u64(10), - target: bertha.clone(), - })], + continuous: vec![ + PgfContinuous { + target: ContPGFTarget { + target: PGFTarget::Internal(PGFInternalTarget { + amount: token::Amount::from_u64(10), + target: bertha.clone(), + }), + end_epoch: Some(cont_end_epoch), + proposal_id: 0, + }, + action: PgfAction::Add, + }, + PgfContinuous { + target: ContPGFTarget { + target: PGFTarget::Internal(PGFInternalTarget { + amount: token::Amount::from_u64(35), + target: christel.clone(), + }), + end_epoch: None, + proposal_id: 0, + }, + action: PgfAction::Add, + }, + ], retro: vec![PGFTarget::Internal(PGFInternalTarget { amount: token::Amount::from_u64(5), - target: christel, + target: christel.clone(), })], }; - let valid_proposal_json_path = - prepare_proposal_data(node.test_dir.path(), albert, pgf_funding, 36); + let valid_proposal_json_path = prepare_proposal_data( + node.test_dir.path(), + albert.clone(), + pgf_funding, + 36, + ); let submit_proposal_args = apply_use_device(vec![ "init-proposal", @@ -1459,12 +2128,16 @@ fn pgf_governance_proposal() -> Result<()> { let query_pgf = vec!["query-pgf", "--node", &validator_one_rpc]; let captured = CapturedOutput::of(|| run(&node, Bin::Client, query_pgf)); assert_matches!(captured.result, Ok(_)); - assert!(captured.contains("Pgf fundings")); - assert!(captured.contains(&format!( - "{} for {}", - bertha, - token::Amount::from_u64(10).to_string_native() - ))); + + assert!(captured.contains("Continuous PGF distributions")); + assert!(captured.contains(&format!("- {}", &bertha))); + assert!( + captured.contains("- Prop 1: 0.000010 native tokens, end epoch = 70") + ); + assert!(captured.contains(&format!("- {}", &christel))); + assert!( + captured.contains("- Prop 1: 0.000035 native tokens, end epoch = None") + ); Ok(()) } @@ -1487,13 +2160,15 @@ fn pgf_steward_change_commission() -> Result<()> { let query_pgf = vec!["query-pgf", "--node", &validator_one_rpc]; let captured = CapturedOutput::of(|| run(&node, Bin::Client, query_pgf)); assert_matches!(captured.result, Ok(_)); - assert!(captured.contains("Pgf stewards:")); + assert!(captured.contains("PGF stewards:")); assert!(captured.contains(&format!("- {}", defaults::albert_address()))); assert!(captured.contains("Reward distribution:")); assert!( captured.contains(&format!("- 1 to {}", defaults::albert_address())) ); - assert!(captured.contains("Pgf fundings: no fundings are currently set.")); + assert!( + captured.contains("No continous PGF distributions exist currently.") + ); let commission = Commission { reward_distribution: HashMap::from_iter([ @@ -1524,7 +2199,7 @@ fn pgf_steward_change_commission() -> Result<()> { let query_pgf = vec!["query-pgf", "--node", &validator_one_rpc]; let captured = CapturedOutput::of(|| run(&node, Bin::Client, query_pgf)); assert_matches!(captured.result, Ok(_)); - assert!(captured.contains("Pgf stewards:")); + assert!(captured.contains("PGF stewards:")); assert!(captured.contains(&format!("- {}", defaults::albert_address()))); assert!(captured.contains("Reward distribution:")); assert!( @@ -1537,7 +2212,9 @@ fn pgf_steward_change_commission() -> Result<()> { captured .contains(&format!("- 0.05 to {}", defaults::christel_address())) ); - assert!(captured.contains("Pgf fundings: no fundings are currently set.")); + assert!( + captured.contains("No continous PGF distributions exist currently.") + ); Ok(()) } diff --git a/scripts/funding_proposal_template.json b/scripts/funding_proposal_template.json index 9e8b288e184..c91bf86941b 100644 --- a/scripts/funding_proposal_template.json +++ b/scripts/funding_proposal_template.json @@ -16,7 +16,43 @@ "activation_epoch": 0 }, "data": { - "continuous/retro": [ + "continuous": { + "add": [ + { + "Internal": { + "amount": "0", + "target": "tnamXXX" + } + }, + { + "IBC": { + "amount": "0", + "target": "", + "port_id": "", + "channel_id": "" + } + } + ], + "remove": [ + { + "Internal": { + "amount": "0", + "target": "tnamXXX", + "proposal_id": 0 + } + }, + { + "IBC": { + "amount": "0", + "target": "", + "port_id": "", + "channel_id": "", + "proposal_id": 0 + } + } + ] + }, + "retro": [ { "Internal": { "amount": "0",