From 75d9afbf7bb93c816aa6ef2a3ac4c0c45568a9fe Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 22 Apr 2024 11:18:19 +0100 Subject: [PATCH 01/30] Implement FromStr on I256 --- crates/core/src/uint.rs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/crates/core/src/uint.rs b/crates/core/src/uint.rs index 60cfd14952..40c730d651 100644 --- a/crates/core/src/uint.rs +++ b/crates/core/src/uint.rs @@ -4,6 +4,7 @@ use std::cmp::Ordering; use std::fmt; use std::ops::{Add, AddAssign, BitAnd, Div, Mul, Neg, Rem, Sub, SubAssign}; +use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use impl_num_traits::impl_uint_num_traits; @@ -507,6 +508,20 @@ impl fmt::Display for I256 { } } +impl FromStr for I256 { + type Err = Box; + + fn from_str(num: &str) -> Result { + if let Some(("", neg_num)) = num.split_once('-') { + let uint = neg_num.parse::()?.negate(); + Ok(I256(uint)) + } else { + let uint = num.parse::()?; + Ok(I256(uint)) + } + } +} + impl I256 { /// Check if the amount is not negative (greater /// than or equal to zero) @@ -1064,4 +1079,14 @@ mod test_uint { assert_eq!(e.checked_mul_div(c, b), Some((Uint::zero(), c))); assert_eq!(d.checked_mul_div(a, e), None); } + + #[test] + fn test_i256_str_roundtrip() { + let minus_one = I256(I256::one().0.negate()); + let minus_one_str = minus_one.to_string(); + assert_eq!(minus_one_str, "-1"); + + let parsed: I256 = minus_one_str.parse().unwrap(); + assert_eq!(minus_one, parsed); + } } From b1f963f30286a5544e153512ac39933fcf5aad62 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 22 Apr 2024 13:47:04 +0100 Subject: [PATCH 02/30] Convert from Uint to Amount with 0 denom --- crates/core/src/token.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/crates/core/src/token.rs b/crates/core/src/token.rs index 9765a017bc..7eaca47a4e 100644 --- a/crates/core/src/token.rs +++ b/crates/core/src/token.rs @@ -211,7 +211,7 @@ impl Amount { let denom = denom.into(); let uint = uint.into(); if denom == 0 { - return Ok(Self { raw: uint }); + return Ok(uint.into()); } match Uint::from(10) .checked_pow(Uint::from(denom)) @@ -907,6 +907,12 @@ impl From for Uint { } } +impl From for Amount { + fn from(raw: Uint) -> Self { + Self { raw } + } +} + /// The four possible u64 words in a [`Uint`]. /// Used for converting to MASP amounts. #[derive( From 06cd002478bf73c51d0b85f3f49aefe3287340dc Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 22 Apr 2024 14:20:51 +0100 Subject: [PATCH 03/30] Negate I256 numbers --- crates/core/src/uint.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/crates/core/src/uint.rs b/crates/core/src/uint.rs index 40c730d651..284018296e 100644 --- a/crates/core/src/uint.rs +++ b/crates/core/src/uint.rs @@ -523,6 +523,11 @@ impl FromStr for I256 { } impl I256 { + /// Compute the two's complement of a number. + pub fn negate(&self) -> Self { + Self(self.0.negate()) + } + /// Check if the amount is not negative (greater /// than or equal to zero) pub fn non_negative(&self) -> bool { @@ -1082,7 +1087,7 @@ mod test_uint { #[test] fn test_i256_str_roundtrip() { - let minus_one = I256(I256::one().0.negate()); + let minus_one = I256::one().negate(); let minus_one_str = minus_one.to_string(); assert_eq!(minus_one_str, "-1"); From 42a47848152928d18c759d1f81db97ac6fb2c341 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 22 Apr 2024 13:39:52 +0100 Subject: [PATCH 04/30] Add token balance change events --- Cargo.lock | 1 + crates/token/Cargo.toml | 2 + crates/token/src/event.rs | 144 ++++++++++++++++++++++++++++++++++++++ crates/token/src/lib.rs | 1 + wasm/Cargo.lock | 1 + wasm_for_tests/Cargo.lock | 1 + 6 files changed, 150 insertions(+) create mode 100644 crates/token/src/event.rs diff --git a/Cargo.lock b/Cargo.lock index 95b5898964..36eb0f8f50 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5056,6 +5056,7 @@ dependencies = [ name = "namada_token" version = "0.34.0" dependencies = [ + "konst", "namada_core", "namada_events", "namada_shielded_token", diff --git a/crates/token/Cargo.toml b/crates/token/Cargo.toml index 1f0849ebe5..7f9e54c61a 100644 --- a/crates/token/Cargo.toml +++ b/crates/token/Cargo.toml @@ -22,3 +22,5 @@ namada_events = { path = "../events", default-features = false } namada_shielded_token = { path = "../shielded_token" } namada_storage = { path = "../storage" } namada_trans_token = { path = "../trans_token" } + +konst.workspace = true diff --git a/crates/token/src/event.rs b/crates/token/src/event.rs new file mode 100644 index 0000000000..835a32a49a --- /dev/null +++ b/crates/token/src/event.rs @@ -0,0 +1,144 @@ +//! Token transaction events. + +use std::borrow::Cow; + +use namada_core::address::Address; +use namada_core::uint::{Uint, I256}; +use namada_events::extend::EventAttributeEntry; +use namada_events::{Event, EventLevel, EventToEmit}; + +pub mod types { + //! Token event types. + + use namada_events::{event_type, EventType}; + + use super::TokenEvent; + + /// Balance change event. + pub const BALANCE_CHANGE: EventType = + event_type!(TokenEvent, "balance-change"); +} + +/// Namada token event. +pub enum TokenEvent { + /// Balance change event. + BalanceChange { + /// Describes the reason of the balance change. + descriptor: Cow<'static, str>, + /// The address of the token whose balance was updated. + token: Address, + /// Account whose balance has changed. + /// + /// If the account is `None`, then the balance + /// change refers to the minted supply of tokens. + account: Option
, + /// The diff between the pre and post balance + /// (`pre_balance` + `diff` = `post_balance`). + diff: I256, + /// The balance that `account` ended up with. + post_balance: Uint, + }, +} + +impl EventToEmit for TokenEvent { + const DOMAIN: &'static str = "token"; +} + +impl From for Event { + fn from(token_event: TokenEvent) -> Self { + match token_event { + TokenEvent::BalanceChange { + descriptor, + token, + account, + diff, + post_balance, + } => { + let mut event = + Self::new(types::BALANCE_CHANGE, EventLevel::Tx); + + event + .extend(BalanceChangeKind(&descriptor)) + .extend(TokenAddress(token)) + .extend(BalanceDiff(&diff)) + .extend(PostBalance(&post_balance)); + + if let Some(account) = account { + event.extend(AccountAddress(account)); + } + + event + } + } + } +} + +/// Extend an [`Event`] with balance change kind data. +pub struct BalanceChangeKind<'k>(pub &'k str); + +impl<'k> EventAttributeEntry<'k> for BalanceChangeKind<'k> { + type Value = &'k str; + type ValueOwned = String; + + const KEY: &'static str = "balance-change-kind"; + + fn into_value(self) -> Self::Value { + self.0 + } +} + +/// Extend an [`Event`] with token address data. +pub struct TokenAddress(pub Address); + +impl EventAttributeEntry<'static> for TokenAddress { + type Value = Address; + type ValueOwned = Self::Value; + + const KEY: &'static str = "token-address"; + + fn into_value(self) -> Self::Value { + self.0 + } +} + +/// Extend an [`Event`] with account address data. +pub struct AccountAddress(pub Address); + +impl EventAttributeEntry<'static> for AccountAddress { + type Value = Address; + type ValueOwned = Self::Value; + + const KEY: &'static str = "account-address"; + + fn into_value(self) -> Self::Value { + self.0 + } +} + +/// Extend an [`Event`] with balance change diff data. +pub struct BalanceDiff<'bal>(pub &'bal I256); + +impl<'bal> EventAttributeEntry<'bal> for BalanceDiff<'bal> { + type Value = &'bal I256; + type ValueOwned = I256; + + const KEY: &'static str = "diff"; + + fn into_value(self) -> Self::Value { + self.0 + } +} + +/// Extend an [`Event`] with post balance data. +pub struct PostBalance<'bal>(pub &'bal Uint); + +impl<'bal> EventAttributeEntry<'bal> for PostBalance<'bal> { + type Value = &'bal Uint; + type ValueOwned = Uint; + + const KEY: &'static str = "post-balance"; + + fn into_value(self) -> Self::Value { + self.0 + } +} diff --git a/crates/token/src/lib.rs b/crates/token/src/lib.rs index d525c68acc..2dbc4df27a 100644 --- a/crates/token/src/lib.rs +++ b/crates/token/src/lib.rs @@ -4,6 +4,7 @@ pub use namada_shielded_token::*; pub use namada_trans_token::*; +pub mod event; pub mod storage_key { pub use namada_shielded_token::storage_key::*; pub use namada_trans_token::storage_key::*; diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index f743950cc6..082d31cb0f 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -4040,6 +4040,7 @@ dependencies = [ name = "namada_token" version = "0.34.0" dependencies = [ + "konst", "namada_core", "namada_events", "namada_shielded_token", diff --git a/wasm_for_tests/Cargo.lock b/wasm_for_tests/Cargo.lock index b6bf44cb88..efc27a8ccd 100644 --- a/wasm_for_tests/Cargo.lock +++ b/wasm_for_tests/Cargo.lock @@ -3986,6 +3986,7 @@ dependencies = [ name = "namada_token" version = "0.34.0" dependencies = [ + "konst", "namada_core", "namada_events", "namada_shielded_token", From f79757b45d0b18e261fc155a73ac8fabdede3fa9 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 22 Apr 2024 14:22:45 +0100 Subject: [PATCH 05/30] Emit fee payment balance change events --- crates/namada/src/ledger/protocol/mod.rs | 26 ++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/crates/namada/src/ledger/protocol/mod.rs b/crates/namada/src/ledger/protocol/mod.rs index e4dd86a46f..60d1b5e005 100644 --- a/crates/namada/src/ledger/protocol/mod.rs +++ b/crates/namada/src/ledger/protocol/mod.rs @@ -12,6 +12,7 @@ use namada_core::storage::Key; use namada_gas::TxGasMeter; use namada_sdk::tx::TX_TRANSFER_WASM; use namada_state::StorageWrite; +use namada_token::event::TokenEvent; use namada_tx::data::protocol::ProtocolTxType; use namada_tx::data::{ GasLimit, TxResult, TxType, VpStatusFlags, VpsResult, WrapperTx, @@ -493,12 +494,15 @@ where ) .unwrap(); + const FEE_PAYMENT_DESCRIPTOR: std::borrow::Cow<'static, str> = + std::borrow::Cow::Borrowed("fee-payment"); + match wrapper.get_tx_fee() { Ok(fees) => { let fees = crate::token::denom_to_amount(fees, &wrapper.fee.token, state) .map_err(|e| Error::FeeError(e.to_string()))?; - if balance.checked_sub(fees).is_some() { + if let Some(post_bal) = balance.checked_sub(fees) { token_transfer( state, &wrapper.fee.token, @@ -506,7 +510,17 @@ where block_proposer, fees, ) - .map_err(|e| Error::FeeError(e.to_string())) + .map_err(|e| Error::FeeError(e.to_string()))?; + + state.write_log_mut().emit_event(TokenEvent::BalanceChange { + descriptor: FEE_PAYMENT_DESCRIPTOR, + token: wrapper.fee.token.clone(), + account: Some(wrapper.fee_payer()), + post_balance: post_bal.into(), + diff: fees.change().negate(), + }); + + Ok(()) } else { // Balance was insufficient for fee payment, move all the // available funds in the transparent balance of @@ -527,6 +541,14 @@ where ) .map_err(|e| Error::FeeError(e.to_string()))?; + state.write_log_mut().emit_event(TokenEvent::BalanceChange { + descriptor: FEE_PAYMENT_DESCRIPTOR, + token: wrapper.fee.token.clone(), + account: Some(wrapper.fee_payer()), + post_balance: namada_core::uint::ZERO, + diff: balance.change().negate(), + }); + Err(Error::FeeError( "Transparent balance of wrapper's signer was insufficient \ to pay fee. All the available transparent funds have \ From b6a9b4390517c5dce5e652d61a5e52ff46b399d5 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 22 Apr 2024 14:32:27 +0100 Subject: [PATCH 06/30] Include current height in wrapper fee payment event --- crates/namada/src/ledger/protocol/mod.rs | 41 +++++++++++++++--------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/crates/namada/src/ledger/protocol/mod.rs b/crates/namada/src/ledger/protocol/mod.rs index 60d1b5e005..d6997347e3 100644 --- a/crates/namada/src/ledger/protocol/mod.rs +++ b/crates/namada/src/ledger/protocol/mod.rs @@ -9,6 +9,7 @@ use masp_primitives::transaction::Transaction; use namada_core::booleans::BoolResultUnitExt; use namada_core::hash::Hash; use namada_core::storage::Key; +use namada_events::extend::{ComposeEvent, Height as HeightAttr}; use namada_gas::TxGasMeter; use namada_sdk::tx::TX_TRANSFER_WASM; use namada_state::StorageWrite; @@ -495,13 +496,17 @@ where .unwrap(); const FEE_PAYMENT_DESCRIPTOR: std::borrow::Cow<'static, str> = - std::borrow::Cow::Borrowed("fee-payment"); + std::borrow::Cow::Borrowed("wrapper-fee-payment"); match wrapper.get_tx_fee() { Ok(fees) => { let fees = crate::token::denom_to_amount(fees, &wrapper.fee.token, state) .map_err(|e| Error::FeeError(e.to_string()))?; + + let current_block_height = + state.in_mem().get_last_block_height() + 1; + if let Some(post_bal) = balance.checked_sub(fees) { token_transfer( state, @@ -512,13 +517,16 @@ where ) .map_err(|e| Error::FeeError(e.to_string()))?; - state.write_log_mut().emit_event(TokenEvent::BalanceChange { - descriptor: FEE_PAYMENT_DESCRIPTOR, - token: wrapper.fee.token.clone(), - account: Some(wrapper.fee_payer()), - post_balance: post_bal.into(), - diff: fees.change().negate(), - }); + state.write_log_mut().emit_event( + TokenEvent::BalanceChange { + descriptor: FEE_PAYMENT_DESCRIPTOR, + token: wrapper.fee.token.clone(), + account: Some(wrapper.fee_payer()), + post_balance: post_bal.into(), + diff: fees.change().negate(), + } + .with(HeightAttr(current_block_height)), + ); Ok(()) } else { @@ -541,13 +549,16 @@ where ) .map_err(|e| Error::FeeError(e.to_string()))?; - state.write_log_mut().emit_event(TokenEvent::BalanceChange { - descriptor: FEE_PAYMENT_DESCRIPTOR, - token: wrapper.fee.token.clone(), - account: Some(wrapper.fee_payer()), - post_balance: namada_core::uint::ZERO, - diff: balance.change().negate(), - }); + state.write_log_mut().emit_event( + TokenEvent::BalanceChange { + descriptor: FEE_PAYMENT_DESCRIPTOR, + token: wrapper.fee.token.clone(), + account: Some(wrapper.fee_payer()), + post_balance: namada_core::uint::ZERO, + diff: balance.change().negate(), + } + .with(HeightAttr(current_block_height)), + ); Err(Error::FeeError( "Transparent balance of wrapper's signer was insufficient \ From 4e26c858372f93d5a73335ea6cfe4556842a0914 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 23 Apr 2024 11:04:39 +0100 Subject: [PATCH 07/30] Include wrapper tx hash in balance change event --- .../lib/node/ledger/shell/prepare_proposal.rs | 13 ++++++++-- .../lib/node/ledger/shell/process_proposal.rs | 12 ++++++++-- crates/namada/src/ledger/protocol/mod.rs | 24 +++++++++++++++---- 3 files changed, 40 insertions(+), 9 deletions(-) diff --git a/crates/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/crates/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 75e0dfafe2..fa98273fcd 100644 --- a/crates/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/crates/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -6,6 +6,7 @@ use masp_primitives::transaction::Transaction; use namada::core::address::Address; use namada::core::key::tm_raw_hash_to_string; use namada::gas::TxGasMeter; +use namada::hash::Hash; use namada::ledger::protocol::{self, ShellParams}; use namada::proof_of_stake::storage::find_validator_by_raw_hash; use namada::state::{DBIter, StorageHasher, TempWlState, DB}; @@ -295,6 +296,7 @@ where // Check fees and extract the gas limit of this transaction match prepare_proposal_fee_check( &wrapper, + tx.header_hash(), protocol::get_fee_unshielding_transaction(&tx, &wrapper), block_proposer, proposer_local_config, @@ -313,8 +315,10 @@ where } } +#[allow(clippy::too_many_arguments)] fn prepare_proposal_fee_check( wrapper: &WrapperTx, + wrapper_tx_hash: Hash, masp_transaction: Option, proposer: &Address, proposer_local_config: Option<&ValidatorLocalConfig>, @@ -357,8 +361,13 @@ where shell_params, )?; - protocol::transfer_fee(shell_params.state, proposer, wrapper) - .map_err(Error::TxApply) + protocol::transfer_fee( + shell_params.state, + proposer, + wrapper, + wrapper_tx_hash, + ) + .map_err(Error::TxApply) } #[cfg(test)] diff --git a/crates/apps/src/lib/node/ledger/shell/process_proposal.rs b/crates/apps/src/lib/node/ledger/shell/process_proposal.rs index 4d3ed0b063..f73375a8d5 100644 --- a/crates/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/crates/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -2,6 +2,7 @@ //! and [`RevertProposal`] ABCI++ methods for the Shell use data_encoding::HEXUPPER; +use namada::hash::Hash; use namada::ledger::pos::PosQueries; use namada::proof_of_stake::storage::find_validator_by_raw_hash; use namada::tx::data::protocol::ProtocolTxType; @@ -465,6 +466,7 @@ where // Check that the fee payer has sufficient balance. match process_proposal_fee_check( &wrapper, + tx.header_hash(), get_fee_unshielding_transaction(&tx, &wrapper), block_proposer, &mut ShellParams::new( @@ -498,6 +500,7 @@ where fn process_proposal_fee_check( wrapper: &WrapperTx, + wrapper_tx_hash: Hash, masp_transaction: Option, proposer: &Address, shell_params: &mut ShellParams<'_, TempWlState, D, H, CA>, @@ -524,8 +527,13 @@ where shell_params, )?; - protocol::transfer_fee(shell_params.state, proposer, wrapper) - .map_err(Error::TxApply) + protocol::transfer_fee( + shell_params.state, + proposer, + wrapper, + wrapper_tx_hash, + ) + .map_err(Error::TxApply) } /// We test the failure cases of [`process_proposal`]. The happy flows diff --git a/crates/namada/src/ledger/protocol/mod.rs b/crates/namada/src/ledger/protocol/mod.rs index d6997347e3..e26935c441 100644 --- a/crates/namada/src/ledger/protocol/mod.rs +++ b/crates/namada/src/ledger/protocol/mod.rs @@ -9,7 +9,9 @@ use masp_primitives::transaction::Transaction; use namada_core::booleans::BoolResultUnitExt; use namada_core::hash::Hash; use namada_core::storage::Key; -use namada_events::extend::{ComposeEvent, Height as HeightAttr}; +use namada_events::extend::{ + ComposeEvent, Height as HeightAttr, TxHash as TxHashAttr, +}; use namada_gas::TxGasMeter; use namada_sdk::tx::TX_TRANSFER_WASM; use namada_state::StorageWrite; @@ -274,16 +276,19 @@ where { let mut changed_keys = BTreeSet::default(); + let wrapper_tx_hash = tx.header_hash(); + // Write wrapper tx hash to storage shell_params .state .write_log_mut() - .write_tx_hash(tx.header_hash()) + .write_tx_hash(wrapper_tx_hash) .expect("Error while writing tx hash to storage"); // Charge fee before performing any fallible operations charge_fee( wrapper, + wrapper_tx_hash, fee_unshield_transaction, &mut shell_params, &mut changed_keys, @@ -327,6 +332,7 @@ pub fn get_fee_unshielding_transaction( /// - The accumulated fee amount to be credited to the block proposer overflows fn charge_fee( wrapper: &WrapperTx, + wrapper_tx_hash: Hash, masp_transaction: Option, shell_params: &mut ShellParams<'_, S, D, H, CA>, changed_keys: &mut BTreeSet, @@ -353,7 +359,12 @@ where Some(WrapperArgs { block_proposer, is_committed_fee_unshield: _, - }) => transfer_fee(shell_params.state, block_proposer, wrapper)?, + }) => transfer_fee( + shell_params.state, + block_proposer, + wrapper, + wrapper_tx_hash, + )?, None => check_fees(shell_params.state, wrapper)?, } @@ -484,6 +495,7 @@ pub fn transfer_fee( state: &mut S, block_proposer: &Address, wrapper: &WrapperTx, + wrapper_tx_hash: Hash, ) -> Result<()> where S: State + StorageRead + StorageWrite, @@ -525,7 +537,8 @@ where post_balance: post_bal.into(), diff: fees.change().negate(), } - .with(HeightAttr(current_block_height)), + .with(HeightAttr(current_block_height)) + .with(TxHashAttr(wrapper_tx_hash)), ); Ok(()) @@ -557,7 +570,8 @@ where post_balance: namada_core::uint::ZERO, diff: balance.change().negate(), } - .with(HeightAttr(current_block_height)), + .with(HeightAttr(current_block_height)) + .with(TxHashAttr(wrapper_tx_hash)), ); Err(Error::FeeError( From 4e11bc5dae8497fdc42547c9e41918e94f268186 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 23 Apr 2024 15:10:42 +0100 Subject: [PATCH 08/30] Add more balance change targets --- crates/namada/src/ledger/protocol/mod.rs | 10 +- crates/token/src/event.rs | 118 +++++++++++++++++------ 2 files changed, 97 insertions(+), 31 deletions(-) diff --git a/crates/namada/src/ledger/protocol/mod.rs b/crates/namada/src/ledger/protocol/mod.rs index e26935c441..742e2f5acd 100644 --- a/crates/namada/src/ledger/protocol/mod.rs +++ b/crates/namada/src/ledger/protocol/mod.rs @@ -15,7 +15,7 @@ use namada_events::extend::{ use namada_gas::TxGasMeter; use namada_sdk::tx::TX_TRANSFER_WASM; use namada_state::StorageWrite; -use namada_token::event::TokenEvent; +use namada_token::event::{BalanceChangeTarget, TokenEvent}; use namada_tx::data::protocol::ProtocolTxType; use namada_tx::data::{ GasLimit, TxResult, TxType, VpStatusFlags, VpsResult, WrapperTx, @@ -533,7 +533,9 @@ where TokenEvent::BalanceChange { descriptor: FEE_PAYMENT_DESCRIPTOR, token: wrapper.fee.token.clone(), - account: Some(wrapper.fee_payer()), + target: BalanceChangeTarget::Internal( + wrapper.fee_payer(), + ), post_balance: post_bal.into(), diff: fees.change().negate(), } @@ -566,7 +568,9 @@ where TokenEvent::BalanceChange { descriptor: FEE_PAYMENT_DESCRIPTOR, token: wrapper.fee.token.clone(), - account: Some(wrapper.fee_payer()), + target: BalanceChangeTarget::Internal( + wrapper.fee_payer(), + ), post_balance: namada_core::uint::ZERO, diff: balance.change().negate(), } diff --git a/crates/token/src/event.rs b/crates/token/src/event.rs index 835a32a49a..8517e2d164 100644 --- a/crates/token/src/event.rs +++ b/crates/token/src/event.rs @@ -1,10 +1,12 @@ //! Token transaction events. use std::borrow::Cow; +use std::fmt; +use std::str::FromStr; use namada_core::address::Address; use namada_core::uint::{Uint, I256}; -use namada_events::extend::EventAttributeEntry; +use namada_events::extend::{ComposeEvent, EventAttributeEntry}; use namada_events::{Event, EventLevel, EventToEmit}; pub mod types { @@ -19,7 +21,51 @@ pub mod types { event_type!(TokenEvent, "balance-change"); } +/// The target of a balance change. +#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] +pub enum BalanceChangeTarget { + /// The minted supply of tokens. + MintedSupply, + /// Internal chain address in Namada. + Internal(Address), + /// External chain address. + External(String), +} + +impl fmt::Display for BalanceChangeTarget { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::MintedSupply => write!(f, "minted-supply"), + Self::Internal(addr) => write!(f, "internal-address/{addr}"), + Self::External(addr) => write!(f, "external-address/{addr}"), + } + } +} + +impl FromStr for BalanceChangeTarget { + type Err = String; + + fn from_str(s: &str) -> Result { + match s.split_once('/') { + None if s == "minted-supply" => Ok(Self::MintedSupply), + Some(("internal-address", addr)) => { + Ok(Self::Internal(Address::decode(addr).map_err(|err| { + format!( + "Unknown internal address balance change target \ + {s:?}: {err}" + ) + })?)) + } + Some(("external-address", addr)) => { + Ok(Self::External(addr.to_owned())) + } + _ => Err(format!("Unknown balance change target {s:?}")), + } + } +} + /// Namada token event. +#[derive(Debug)] pub enum TokenEvent { /// Balance change event. BalanceChange { @@ -27,11 +73,8 @@ pub enum TokenEvent { descriptor: Cow<'static, str>, /// The address of the token whose balance was updated. token: Address, - /// Account whose balance has changed. - /// - /// If the account is `None`, then the balance - /// change refers to the minted supply of tokens. - account: Option
, + /// The target whose balance was changed. + target: BalanceChangeTarget, /// The diff between the pre and post balance /// (`pre_balance` + `diff` = `post_balance`). diff: I256, @@ -50,25 +93,16 @@ impl From for Event { TokenEvent::BalanceChange { descriptor, token, - account, + target, diff, post_balance, - } => { - let mut event = - Self::new(types::BALANCE_CHANGE, EventLevel::Tx); - - event - .extend(BalanceChangeKind(&descriptor)) - .extend(TokenAddress(token)) - .extend(BalanceDiff(&diff)) - .extend(PostBalance(&post_balance)); - - if let Some(account) = account { - event.extend(AccountAddress(account)); - } - - event - } + } => Self::new(types::BALANCE_CHANGE, EventLevel::Tx) + .with(TargetAccount(target)) + .with(BalanceChangeKind(&descriptor)) + .with(TokenAddress(token)) + .with(BalanceDiff(&diff)) + .with(PostBalance(&post_balance)) + .into(), } } } @@ -101,14 +135,14 @@ impl EventAttributeEntry<'static> for TokenAddress { } } -/// Extend an [`Event`] with account address data. -pub struct AccountAddress(pub Address); +/// Extend an [`Event`] with target account data. +pub struct TargetAccount(pub BalanceChangeTarget); -impl EventAttributeEntry<'static> for AccountAddress { - type Value = Address; +impl EventAttributeEntry<'static> for TargetAccount { + type Value = BalanceChangeTarget; type ValueOwned = Self::Value; - const KEY: &'static str = "account-address"; + const KEY: &'static str = "target-account"; fn into_value(self) -> Self::Value { self.0 @@ -142,3 +176,31 @@ impl<'bal> EventAttributeEntry<'bal> for PostBalance<'bal> { self.0 } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn balance_change_target_str_roundtrip() { + let targets = [ + BalanceChangeTarget::MintedSupply, + BalanceChangeTarget::External( + "cosmos1hkgjfuznl4af5ayzn6gzl6kwwkcu28urxmqejg".to_owned(), + ), + BalanceChangeTarget::Internal( + Address::decode( + "tnam1q82t25z5f9gmnv5sztyr8ht9tqhrw4u875qjhy56", + ) + .unwrap(), + ), + ]; + + for target in targets { + let as_str = target.to_string(); + let decoded: BalanceChangeTarget = as_str.parse().unwrap(); + + assert_eq!(decoded, target); + } + } +} From e8a0b248990fb989f82533bf4a236b3c07a9a38d Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 23 Apr 2024 15:12:04 +0100 Subject: [PATCH 09/30] Refactor governace event emission --- .../apps/src/lib/node/ledger/shell/governance.rs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/crates/apps/src/lib/node/ledger/shell/governance.rs b/crates/apps/src/lib/node/ledger/shell/governance.rs index edc20a1ca5..753724919a 100644 --- a/crates/apps/src/lib/node/ledger/shell/governance.rs +++ b/crates/apps/src/lib/node/ledger/shell/governance.rs @@ -181,11 +181,17 @@ where // Take events that could have been emitted by PGF // over IBC, governance proposal execution, etc - for event in shell.state.write_log_mut().take_events() { - events.emit(event.with(Height( - shell.state.in_mem().get_last_block_height() + 1, - ))); - } + let current_height = + shell.state.in_mem().get_last_block_height() + 1; + + events.emit_many( + shell + .state + .write_log_mut() + .take_events() + .into_iter() + .map(|event| event.with(Height(current_height))), + ); gov_api::get_proposal_author(&shell.state, id)? } From 2dab64ea74ab45785c82661d85c03f7d55258d19 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 23 Apr 2024 15:36:26 +0100 Subject: [PATCH 10/30] Extend event with a closure --- crates/events/src/extend.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/crates/events/src/extend.rs b/crates/events/src/extend.rs index 313086102f..61c6dd0004 100644 --- a/crates/events/src/extend.rs +++ b/crates/events/src/extend.rs @@ -616,6 +616,20 @@ where } } +/// Extend an [`Event`] with the given closure. +pub struct Closure(pub F); + +impl ExtendEvent for Closure +where + F: FnOnce(&mut Event), +{ + #[inline] + fn extend_event(self, event: &mut Event) { + let Self(closure) = self; + closure(event); + } +} + #[cfg(test)] mod event_composition_tests { use super::*; From 517837646a98632f559164b6f0d17100e97c71e5 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 23 Apr 2024 15:37:14 +0100 Subject: [PATCH 11/30] Make the token event's post balance optional --- crates/namada/src/ledger/protocol/mod.rs | 4 ++-- crates/token/src/event.rs | 13 +++++++++---- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/crates/namada/src/ledger/protocol/mod.rs b/crates/namada/src/ledger/protocol/mod.rs index 742e2f5acd..d4ebaadd57 100644 --- a/crates/namada/src/ledger/protocol/mod.rs +++ b/crates/namada/src/ledger/protocol/mod.rs @@ -536,7 +536,7 @@ where target: BalanceChangeTarget::Internal( wrapper.fee_payer(), ), - post_balance: post_bal.into(), + post_balance: Some(post_bal.into()), diff: fees.change().negate(), } .with(HeightAttr(current_block_height)) @@ -571,7 +571,7 @@ where target: BalanceChangeTarget::Internal( wrapper.fee_payer(), ), - post_balance: namada_core::uint::ZERO, + post_balance: Some(namada_core::uint::ZERO), diff: balance.change().negate(), } .with(HeightAttr(current_block_height)) diff --git a/crates/token/src/event.rs b/crates/token/src/event.rs index 8517e2d164..ab69192c37 100644 --- a/crates/token/src/event.rs +++ b/crates/token/src/event.rs @@ -6,7 +6,7 @@ use std::str::FromStr; use namada_core::address::Address; use namada_core::uint::{Uint, I256}; -use namada_events::extend::{ComposeEvent, EventAttributeEntry}; +use namada_events::extend::{Closure, ComposeEvent, EventAttributeEntry}; use namada_events::{Event, EventLevel, EventToEmit}; pub mod types { @@ -78,8 +78,9 @@ pub enum TokenEvent { /// The diff between the pre and post balance /// (`pre_balance` + `diff` = `post_balance`). diff: I256, - /// The balance that `account` ended up with. - post_balance: Uint, + /// The balance that `account` ended up with, + /// if it is known. + post_balance: Option, }, } @@ -101,7 +102,11 @@ impl From for Event { .with(BalanceChangeKind(&descriptor)) .with(TokenAddress(token)) .with(BalanceDiff(&diff)) - .with(PostBalance(&post_balance)) + .with(Closure(|event: &mut Event| { + if let Some(post_balance) = post_balance { + event.extend(PostBalance(&post_balance)); + } + })) .into(), } } From 430e9b00933daa38bee83037166c1fdb06e6ab16 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 23 Apr 2024 15:46:29 +0100 Subject: [PATCH 12/30] Add event level to balance change events --- crates/namada/src/ledger/protocol/mod.rs | 3 +++ crates/token/src/event.rs | 5 ++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/crates/namada/src/ledger/protocol/mod.rs b/crates/namada/src/ledger/protocol/mod.rs index d4ebaadd57..4c81369022 100644 --- a/crates/namada/src/ledger/protocol/mod.rs +++ b/crates/namada/src/ledger/protocol/mod.rs @@ -12,6 +12,7 @@ use namada_core::storage::Key; use namada_events::extend::{ ComposeEvent, Height as HeightAttr, TxHash as TxHashAttr, }; +use namada_events::EventLevel; use namada_gas::TxGasMeter; use namada_sdk::tx::TX_TRANSFER_WASM; use namada_state::StorageWrite; @@ -531,6 +532,7 @@ where state.write_log_mut().emit_event( TokenEvent::BalanceChange { + level: EventLevel::Tx, descriptor: FEE_PAYMENT_DESCRIPTOR, token: wrapper.fee.token.clone(), target: BalanceChangeTarget::Internal( @@ -566,6 +568,7 @@ where state.write_log_mut().emit_event( TokenEvent::BalanceChange { + level: EventLevel::Tx, descriptor: FEE_PAYMENT_DESCRIPTOR, token: wrapper.fee.token.clone(), target: BalanceChangeTarget::Internal( diff --git a/crates/token/src/event.rs b/crates/token/src/event.rs index ab69192c37..af790b6dc2 100644 --- a/crates/token/src/event.rs +++ b/crates/token/src/event.rs @@ -69,6 +69,8 @@ impl FromStr for BalanceChangeTarget { pub enum TokenEvent { /// Balance change event. BalanceChange { + /// Whether the balance change occurred at the block or tx level. + level: EventLevel, /// Describes the reason of the balance change. descriptor: Cow<'static, str>, /// The address of the token whose balance was updated. @@ -92,12 +94,13 @@ impl From for Event { fn from(token_event: TokenEvent) -> Self { match token_event { TokenEvent::BalanceChange { + level, descriptor, token, target, diff, post_balance, - } => Self::new(types::BALANCE_CHANGE, EventLevel::Tx) + } => Self::new(types::BALANCE_CHANGE, level) .with(TargetAccount(target)) .with(BalanceChangeKind(&descriptor)) .with(TokenAddress(token)) From e8527e5c04a53bbfe7f2dcecbedbda94c870753d Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 23 Apr 2024 15:46:48 +0100 Subject: [PATCH 13/30] Emit PGF payment events --- .../src/lib/node/ledger/shell/governance.rs | 70 ++++++++++++++----- 1 file changed, 53 insertions(+), 17 deletions(-) diff --git a/crates/apps/src/lib/node/ledger/shell/governance.rs b/crates/apps/src/lib/node/ledger/shell/governance.rs index 753724919a..be067f7eb1 100644 --- a/crates/apps/src/lib/node/ledger/shell/governance.rs +++ b/crates/apps/src/lib/node/ledger/shell/governance.rs @@ -23,8 +23,10 @@ use namada::proof_of_stake::storage::{ read_total_active_stake, validator_state_handle, }; use namada::proof_of_stake::types::{BondId, ValidatorState}; -use namada::sdk::events::EmitEvents; +use namada::sdk::events::{EmitEvents, EventLevel}; use namada::state::StorageWrite; +use namada::token::event::{BalanceChangeTarget, TokenEvent}; +use namada::token::read_balance; use namada::tx::{Code, Data}; use namada_sdk::proof_of_stake::storage::read_validator_stake; @@ -163,6 +165,7 @@ where let native_token = &shell.state.get_native_token()?; let result = execute_pgf_funding_proposal( &mut shell.state, + events, native_token, payments, id, @@ -399,6 +402,7 @@ where fn execute_pgf_funding_proposal( state: &mut WlState, + events: &mut impl EmitEvents, token: &Address, fundings: BTreeSet, proposal_id: u64, @@ -437,25 +441,57 @@ where } }, PGFAction::Retro(target) => { - let result = match &target { - PGFTarget::Internal(target) => token::transfer( - state, - token, - &ADDRESS, - &target.target, - target.amount, + let (result, event) = match &target { + PGFTarget::Internal(target) => ( + token::transfer( + state, + token, + &ADDRESS, + &target.target, + target.amount, + ), + TokenEvent::BalanceChange { + level: EventLevel::Block, + descriptor: "pgf-payments".into(), + token: token.clone(), + target: BalanceChangeTarget::Internal( + target.target.clone(), + ), + post_balance: read_balance( + state, + token, + &target.target, + ) + .ok() + .map(|balance| balance.into()), + diff: target.amount.change().negate(), + }, + ), + PGFTarget::Ibc(target) => ( + ibc::transfer_over_ibc(state, token, &ADDRESS, target), + TokenEvent::BalanceChange { + level: EventLevel::Block, + descriptor: "pgf-payments-over-ibc".into(), + token: token.clone(), + target: BalanceChangeTarget::External( + target.target.clone(), + ), + post_balance: None, + diff: target.amount.change().negate(), + }, ), - PGFTarget::Ibc(target) => { - ibc::transfer_over_ibc(state, token, &ADDRESS, target) - } }; match result { - Ok(()) => tracing::info!( - "Execute RetroPgf from proposal id {}: sent {} to {}.", - proposal_id, - target.amount().to_string_native(), - target.target() - ), + Ok(()) => { + tracing::info!( + "Execute RetroPgf from proposal id {}: sent {} to \ + {}.", + proposal_id, + target.amount().to_string_native(), + target.target() + ); + events.emit(event); + } Err(e) => tracing::warn!( "Error in RetroPgf transfer from proposal id {}, \ amount {} to {}: {}", From 555ace47e7094cd21d4e2768eaf3751c86048e06 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 24 Apr 2024 14:17:19 +0100 Subject: [PATCH 14/30] Add token minting function --- crates/trans_token/src/storage.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/crates/trans_token/src/storage.rs b/crates/trans_token/src/storage.rs index 4324f0be29..f554f1fc84 100644 --- a/crates/trans_token/src/storage.rs +++ b/crates/trans_token/src/storage.rs @@ -152,6 +152,21 @@ where } } +/// Mint `amount` of `token` as `minter` to `dest`. +pub fn mint_tokens( + storage: &mut S, + minter: &Address, + token: &Address, + dest: &Address, + amount: token::Amount, +) -> storage::Result<()> +where + S: StorageRead + StorageWrite, +{ + credit_tokens(storage, token, dest, amount)?; + storage.write(&minter_key(token), minter.clone()) +} + /// Credit tokens to an account, to be used only by protocol. In transactions, /// this would get rejected by the default `vp_token`. pub fn credit_tokens( From 49a59e448b19a40db78dd89238f87e89b6d003b3 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 24 Apr 2024 15:44:03 +0100 Subject: [PATCH 15/30] Implement EmitEvents on state impls --- crates/state/src/host_env.rs | 25 ++++++++++++++++++ crates/state/src/wl_state.rs | 49 ++++++++++++++++++++++++++++++++++++ crates/tx_prelude/src/lib.rs | 22 +++++++++++++++- 3 files changed, 95 insertions(+), 1 deletion(-) diff --git a/crates/state/src/host_env.rs b/crates/state/src/host_env.rs index 5a6804b5ba..4dff83f296 100644 --- a/crates/state/src/host_env.rs +++ b/crates/state/src/host_env.rs @@ -1,6 +1,7 @@ use std::cell::RefCell; use namada_gas::{GasMetering, TxGasMeter, VpGasMeter}; +use namada_events::{EmitEvents, EventToEmit}; use namada_tx::data::TxSentinel; use crate::in_memory::InMemory; @@ -91,6 +92,30 @@ where } } +impl EmitEvents for TxHostEnvState<'_, D, H> +where + D: 'static + DB + for<'iter> DBIter<'iter>, + H: 'static + StorageHasher, +{ + #[inline] + fn emit(&mut self, event: E) + where + E: EventToEmit, + { + self.write_log_mut().emit_event(event); + } + + fn emit_many(&mut self, event_batch: B) + where + B: IntoIterator, + E: EventToEmit, + { + for event in event_batch { + self.emit(event.into()); + } + } +} + impl StateRead for VpHostEnvState<'_, D, H> where D: 'static + DB + for<'iter> DBIter<'iter>, diff --git a/crates/state/src/wl_state.rs b/crates/state/src/wl_state.rs index d40481ffcf..05af157f38 100644 --- a/crates/state/src/wl_state.rs +++ b/crates/state/src/wl_state.rs @@ -6,6 +6,7 @@ use namada_core::borsh::BorshSerializeExt; use namada_core::chain::ChainId; use namada_core::storage; use namada_core::time::DateTimeUtc; +use namada_events::{EmitEvents, EventToEmit}; use namada_parameters::EpochDuration; use namada_replay_protection as replay_protection; use namada_storage::conversion_state::{ConversionState, WithConversionState}; @@ -1087,6 +1088,30 @@ where } } +impl EmitEvents for FullAccessState +where + D: 'static + DB + for<'iter> DBIter<'iter>, + H: 'static + StorageHasher, +{ + #[inline] + fn emit(&mut self, event: E) + where + E: EventToEmit, + { + self.write_log_mut().emit_event(event); + } + + fn emit_many(&mut self, event_batch: B) + where + B: IntoIterator, + E: EventToEmit, + { + for event in event_batch { + self.emit(event.into()); + } + } +} + impl WithConversionState for FullAccessState where D: 'static + DB + for<'iter> DBIter<'iter>, @@ -1142,6 +1167,30 @@ where } } +impl EmitEvents for WlState +where + D: 'static + DB + for<'iter> DBIter<'iter>, + H: 'static + StorageHasher, +{ + #[inline] + fn emit(&mut self, event: E) + where + E: EventToEmit, + { + self.write_log_mut().emit_event(event); + } + + fn emit_many(&mut self, event_batch: B) + where + B: IntoIterator, + E: EventToEmit, + { + for event in event_batch { + self.emit(event.into()); + } + } +} + impl StateRead for TempWlState<'_, D, H> where D: 'static + DB + for<'iter> DBIter<'iter>, diff --git a/crates/tx_prelude/src/lib.rs b/crates/tx_prelude/src/lib.rs index bba65283b1..3948342af8 100644 --- a/crates/tx_prelude/src/lib.rs +++ b/crates/tx_prelude/src/lib.rs @@ -31,7 +31,7 @@ pub use namada_core::storage::{ self, BlockHash, BlockHeight, Epoch, Header, BLOCK_HASH_LENGTH, }; pub use namada_core::{encode, eth_bridge_pool, *}; -use namada_events::{Event, EventToEmit, EventType}; +use namada_events::{EmitEvents, Event, EventToEmit, EventType}; pub use namada_governance::storage as gov_storage; pub use namada_macros::transaction; pub use namada_parameters::storage as parameters_storage; @@ -249,6 +249,26 @@ impl StorageWrite for Ctx { } } +impl EmitEvents for Ctx { + #[inline] + fn emit(&mut self, event: E) + where + E: EventToEmit, + { + _ = self.emit_event(event); + } + + fn emit_many(&mut self, event_batch: B) + where + B: IntoIterator, + E: EventToEmit, + { + for event in event_batch { + self.emit(event.into()); + } + } +} + impl TxEnv for Ctx { fn read_bytes_temp( &self, From 4179ce31af3630f7d44d1192f8bfacf2ccefd7f9 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 24 Apr 2024 15:55:03 +0100 Subject: [PATCH 16/30] Remove unused file --- crates/state/src/wl_storage.rs | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 crates/state/src/wl_storage.rs diff --git a/crates/state/src/wl_storage.rs b/crates/state/src/wl_storage.rs deleted file mode 100644 index e69de29bb2..0000000000 From d6085fef3f7be8bb25d80a1e4f4648918944c643 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 24 Apr 2024 14:17:36 +0100 Subject: [PATCH 17/30] Mint IBC tokens and emit events --- crates/ibc/src/actions.rs | 29 ++++++++++++-------------- crates/ibc/src/storage.rs | 37 +++++++++++++++++++++++++++++++++- crates/state/src/host_env.rs | 2 +- crates/tx_prelude/src/ibc.rs | 13 ++++-------- crates/tx_prelude/src/token.rs | 10 --------- 5 files changed, 54 insertions(+), 37 deletions(-) diff --git a/crates/ibc/src/actions.rs b/crates/ibc/src/actions.rs index 32514ab4ef..93ca5f305e 100644 --- a/crates/ibc/src/actions.rs +++ b/crates/ibc/src/actions.rs @@ -4,7 +4,7 @@ use std::cell::RefCell; use std::collections::BTreeSet; use std::rc::Rc; -use namada_core::address::{Address, InternalAddress}; +use namada_core::address::Address; use namada_core::borsh::BorshSerializeExt; use namada_core::ibc::apps::transfer::types::msgs::transfer::MsgTransfer as IbcMsgTransfer; use namada_core::ibc::apps::transfer::types::packet::PacketData; @@ -13,7 +13,7 @@ use namada_core::ibc::core::channel::types::timeout::TimeoutHeight; use namada_core::ibc::MsgTransfer; use namada_core::tendermint::Time as TmTime; use namada_core::token::Amount; -use namada_events::EventTypeBuilder; +use namada_events::{EmitEvents, EventTypeBuilder}; use namada_governance::storage::proposal::PGFIbcTarget; use namada_parameters::read_epoch_duration_parameter; use namada_state::{ @@ -24,14 +24,13 @@ use namada_token as token; use token::DenominatedAmount; use crate::event::IbcEvent; -use crate::{IbcActions, IbcCommonContext, IbcStorageContext}; +use crate::{ + storage as ibc_storage, IbcActions, IbcCommonContext, IbcStorageContext, +}; /// IBC protocol context #[derive(Debug)] -pub struct IbcProtocolContext<'a, S> -where - S: State, -{ +pub struct IbcProtocolContext<'a, S> { state: &'a mut S, } @@ -166,9 +165,7 @@ where token: &Address, amount: Amount, ) -> Result<(), StorageError> { - token::credit_tokens(self, token, target, amount)?; - let minter_key = token::storage_key::minter_key(token); - self.write(&minter_key, Address::Internal(InternalAddress::Ibc)) + ibc_storage::mint_tokens(self, target, token, amount) } fn burn_token( @@ -194,7 +191,7 @@ where impl IbcStorageContext for IbcProtocolContext<'_, S> where - S: State, + S: State + EmitEvents, { fn emit_ibc_event(&mut self, event: IbcEvent) -> Result<(), StorageError> { self.state.write_log_mut().emit_event(event); @@ -245,10 +242,7 @@ where token: &Address, amount: Amount, ) -> Result<(), StorageError> { - token::credit_tokens(self.state, token, target, amount)?; - let minter_key = token::storage_key::minter_key(token); - self.state - .write(&minter_key, Address::Internal(InternalAddress::Ibc)) + ibc_storage::mint_tokens(self.state, target, token, amount) } /// Burn token @@ -266,7 +260,10 @@ where } } -impl IbcCommonContext for IbcProtocolContext<'_, S> where S: State {} +impl IbcCommonContext for IbcProtocolContext<'_, S> where + S: State + EmitEvents +{ +} /// Transfer tokens over IBC pub fn transfer_over_ibc( diff --git a/crates/ibc/src/storage.rs b/crates/ibc/src/storage.rs index e506cfbb0e..1626be7eea 100644 --- a/crates/ibc/src/storage.rs +++ b/crates/ibc/src/storage.rs @@ -16,7 +16,10 @@ use namada_core::ibc::core::host::types::path::{ use namada_core::ibc::IbcTokenHash; use namada_core::storage::{DbKeySeg, Key, KeySeg}; use namada_core::token::Amount; -use namada_state::{StorageRead, StorageResult}; +use namada_events::{EmitEvents, EventLevel}; +use namada_state::{StorageRead, StorageResult, StorageWrite}; +use namada_token as token; +use namada_token::event::{BalanceChangeTarget, TokenEvent}; use sha2::{Digest, Sha256}; use thiserror::Error; @@ -50,6 +53,38 @@ pub enum Error { /// IBC storage functions result pub type Result = std::result::Result; +/// Mint tokens, and emit an IBC token mint event. +pub fn mint_tokens( + state: &mut S, + target: &Address, + token: &Address, + amount: Amount, +) -> StorageResult<()> +where + S: StorageRead + StorageWrite + EmitEvents, +{ + token::mint_tokens( + state, + &Address::Internal(InternalAddress::Ibc), + token, + target, + amount, + )?; + + let post_balance = token::read_balance(state, token, target)?; + + state.emit(TokenEvent::BalanceChange { + level: EventLevel::Tx, + descriptor: "mint-ibc-tokens".into(), + token: token.clone(), + target: BalanceChangeTarget::Internal(target.clone()), + post_balance: Some(post_balance.into()), + diff: amount.change(), + }); + + Ok(()) +} + /// Returns a key of the IBC-related data pub fn ibc_key(path: impl AsRef) -> Result { let path = Key::parse(path).map_err(Error::StorageKey)?; diff --git a/crates/state/src/host_env.rs b/crates/state/src/host_env.rs index 4dff83f296..d270c11d19 100644 --- a/crates/state/src/host_env.rs +++ b/crates/state/src/host_env.rs @@ -1,7 +1,7 @@ use std::cell::RefCell; -use namada_gas::{GasMetering, TxGasMeter, VpGasMeter}; use namada_events::{EmitEvents, EventToEmit}; +use namada_gas::{GasMetering, TxGasMeter, VpGasMeter}; use namada_tx::data::TxSentinel; use crate::in_memory::InMemory; diff --git a/crates/tx_prelude/src/ibc.rs b/crates/tx_prelude/src/ibc.rs index c18ebe395a..42ce392824 100644 --- a/crates/tx_prelude/src/ibc.rs +++ b/crates/tx_prelude/src/ibc.rs @@ -4,20 +4,18 @@ use std::cell::RefCell; use std::collections::BTreeSet; use std::rc::Rc; -use namada_core::address::{Address, InternalAddress}; +use namada_core::address::Address; use namada_core::token::Amount; use namada_events::EventTypeBuilder; pub use namada_ibc::event::{IbcEvent, IbcEventType}; -pub use namada_ibc::storage::{ibc_token, is_ibc_key}; +pub use namada_ibc::storage::{ibc_token, is_ibc_key, mint_tokens}; pub use namada_ibc::{ IbcActions, IbcCommonContext, IbcStorageContext, NftTransferModule, ProofSpec, TransferModule, }; -use namada_storage::StorageWrite; -use namada_token::storage_key::minter_key; use namada_tx_env::TxEnv; -use crate::token::{burn, mint, transfer}; +use crate::token::{burn, transfer}; use crate::{Ctx, Error}; /// IBC actions to handle an IBC message. The `verifiers` inserted into the set @@ -83,10 +81,7 @@ impl IbcStorageContext for Ctx { token: &Address, amount: Amount, ) -> Result<(), Error> { - mint(self, target, token, amount)?; - - let minter_key = minter_key(token); - self.write(&minter_key, &Address::Internal(InternalAddress::Ibc)) + mint_tokens(self, target, token, amount) } fn burn_token( diff --git a/crates/tx_prelude/src/token.rs b/crates/tx_prelude/src/token.rs index f62ec8d5a5..080b1a9c48 100644 --- a/crates/tx_prelude/src/token.rs +++ b/crates/tx_prelude/src/token.rs @@ -48,16 +48,6 @@ pub fn transfer( Ok(()) } -/// Mint that can be used in a transaction. -pub fn mint( - ctx: &mut Ctx, - target: &Address, - token: &Address, - amount: Amount, -) -> TxResult { - credit_tokens(ctx, token, target, amount) -} - /// Burn that can be used in a transaction. pub fn burn( ctx: &mut Ctx, From 98361864aa030ce404ea748a3ca79b3ee7432d44 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 24 Apr 2024 16:00:18 +0100 Subject: [PATCH 18/30] Burn IBC tokens and emit events --- crates/ibc/src/actions.rs | 8 ++++---- crates/ibc/src/storage.rs | 26 ++++++++++++++++++++++++++ crates/tx_prelude/src/ibc.rs | 8 +++++--- crates/tx_prelude/src/token.rs | 10 ---------- 4 files changed, 35 insertions(+), 17 deletions(-) diff --git a/crates/ibc/src/actions.rs b/crates/ibc/src/actions.rs index 93ca5f305e..eb71c9e940 100644 --- a/crates/ibc/src/actions.rs +++ b/crates/ibc/src/actions.rs @@ -155,8 +155,8 @@ where shielded: &masp_primitives::transaction::Transaction, pin_key: Option<&str>, ) -> Result<(), StorageError> { - namada_token::utils::handle_masp_tx(self, shielded, pin_key)?; - namada_token::utils::update_note_commitment_tree(self, shielded) + token::utils::handle_masp_tx(self, shielded, pin_key)?; + token::utils::update_note_commitment_tree(self, shielded) } fn mint_token( @@ -174,7 +174,7 @@ where token: &Address, amount: Amount, ) -> Result<(), StorageError> { - token::burn_tokens(self, token, target, amount) + ibc_storage::burn_tokens(self, target, token, amount) } fn log_string(&self, message: String) { @@ -252,7 +252,7 @@ where token: &Address, amount: Amount, ) -> Result<(), StorageError> { - token::burn_tokens(self.state, token, target, amount) + ibc_storage::burn_tokens(self.state, target, token, amount) } fn log_string(&self, message: String) { diff --git a/crates/ibc/src/storage.rs b/crates/ibc/src/storage.rs index 1626be7eea..24f1d125fb 100644 --- a/crates/ibc/src/storage.rs +++ b/crates/ibc/src/storage.rs @@ -85,6 +85,32 @@ where Ok(()) } +/// Burn tokens, and emit an IBC token burn event. +pub fn burn_tokens( + state: &mut S, + target: &Address, + token: &Address, + amount: Amount, +) -> StorageResult<()> +where + S: StorageRead + StorageWrite + EmitEvents, +{ + token::burn_tokens(state, token, target, amount)?; + + let post_balance = token::read_balance(state, token, target)?; + + state.emit(TokenEvent::BalanceChange { + level: EventLevel::Tx, + descriptor: "burn-ibc-tokens".into(), + token: token.clone(), + target: BalanceChangeTarget::Internal(target.clone()), + post_balance: Some(post_balance.into()), + diff: amount.change().negate(), + }); + + Ok(()) +} + /// Returns a key of the IBC-related data pub fn ibc_key(path: impl AsRef) -> Result { let path = Key::parse(path).map_err(Error::StorageKey)?; diff --git a/crates/tx_prelude/src/ibc.rs b/crates/tx_prelude/src/ibc.rs index 42ce392824..00b10c9bcd 100644 --- a/crates/tx_prelude/src/ibc.rs +++ b/crates/tx_prelude/src/ibc.rs @@ -8,14 +8,16 @@ use namada_core::address::Address; use namada_core::token::Amount; use namada_events::EventTypeBuilder; pub use namada_ibc::event::{IbcEvent, IbcEventType}; -pub use namada_ibc::storage::{ibc_token, is_ibc_key, mint_tokens}; +pub use namada_ibc::storage::{ + burn_tokens, ibc_token, is_ibc_key, mint_tokens, +}; pub use namada_ibc::{ IbcActions, IbcCommonContext, IbcStorageContext, NftTransferModule, ProofSpec, TransferModule, }; use namada_tx_env::TxEnv; -use crate::token::{burn, transfer}; +use crate::token::transfer; use crate::{Ctx, Error}; /// IBC actions to handle an IBC message. The `verifiers` inserted into the set @@ -90,7 +92,7 @@ impl IbcStorageContext for Ctx { token: &Address, amount: Amount, ) -> Result<(), Error> { - burn(self, target, token, amount) + burn_tokens(self, target, token, amount) } fn log_string(&self, message: String) { diff --git a/crates/tx_prelude/src/token.rs b/crates/tx_prelude/src/token.rs index 080b1a9c48..bcc376296c 100644 --- a/crates/tx_prelude/src/token.rs +++ b/crates/tx_prelude/src/token.rs @@ -47,13 +47,3 @@ pub fn transfer( Ok(()) } - -/// Burn that can be used in a transaction. -pub fn burn( - ctx: &mut Ctx, - target: &Address, - token: &Address, - amount: Amount, -) -> TxResult { - burn_tokens(ctx, token, target, amount) -} From 1f92db0dd2cfcb839484cb579397afbfad9143f0 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 26 Apr 2024 13:20:59 +0100 Subject: [PATCH 19/30] Governance refund balance change events --- .../src/lib/node/ledger/shell/governance.rs | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/crates/apps/src/lib/node/ledger/shell/governance.rs b/crates/apps/src/lib/node/ledger/shell/governance.rs index be067f7eb1..6e76a7d7d1 100644 --- a/crates/apps/src/lib/node/ledger/shell/governance.rs +++ b/crates/apps/src/lib/node/ledger/shell/governance.rs @@ -238,6 +238,34 @@ where &address, funds, )?; + + const DESCRIPTOR: &str = "governance-locked-funds-refund"; + + let final_gov_balance = + read_balance(&shell.state, &native_token, &gov_address) + .ok() + .map(|balance| balance.into()); + let final_target_balance = + read_balance(&shell.state, &native_token, &address) + .ok() + .map(|balance| balance.into()); + + events.emit(TokenEvent::BalanceChange { + level: EventLevel::Block, + descriptor: DESCRIPTOR.into(), + token: native_token.clone(), + target: BalanceChangeTarget::Internal(gov_address), + post_balance: final_gov_balance, + diff: funds.change().negate(), + }); + events.emit(TokenEvent::BalanceChange { + level: EventLevel::Block, + descriptor: DESCRIPTOR.into(), + token: native_token.clone(), + target: BalanceChangeTarget::Internal(address), + post_balance: final_target_balance, + diff: funds.change(), + }); } else { token::burn_tokens( &mut shell.state, @@ -245,6 +273,22 @@ where &gov_address, funds, )?; + + const DESCRIPTOR: &str = "governance-locked-funds-burn"; + + let final_gov_balance = + read_balance(&shell.state, &native_token, &gov_address) + .ok() + .map(|balance| balance.into()); + + events.emit(TokenEvent::BalanceChange { + level: EventLevel::Block, + descriptor: DESCRIPTOR.into(), + token: native_token.clone(), + target: BalanceChangeTarget::Internal(gov_address), + post_balance: final_gov_balance, + diff: funds.change().negate(), + }); } } From 0d466aeec7882f71039b838977a084c42fe24b4a Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 26 Apr 2024 13:24:58 +0100 Subject: [PATCH 20/30] Remove minted supply target --- crates/token/src/event.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/crates/token/src/event.rs b/crates/token/src/event.rs index af790b6dc2..f1ee0c8ba0 100644 --- a/crates/token/src/event.rs +++ b/crates/token/src/event.rs @@ -24,8 +24,6 @@ pub mod types { /// The target of a balance change. #[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] pub enum BalanceChangeTarget { - /// The minted supply of tokens. - MintedSupply, /// Internal chain address in Namada. Internal(Address), /// External chain address. @@ -35,7 +33,6 @@ pub enum BalanceChangeTarget { impl fmt::Display for BalanceChangeTarget { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::MintedSupply => write!(f, "minted-supply"), Self::Internal(addr) => write!(f, "internal-address/{addr}"), Self::External(addr) => write!(f, "external-address/{addr}"), } @@ -47,7 +44,6 @@ impl FromStr for BalanceChangeTarget { fn from_str(s: &str) -> Result { match s.split_once('/') { - None if s == "minted-supply" => Ok(Self::MintedSupply), Some(("internal-address", addr)) => { Ok(Self::Internal(Address::decode(addr).map_err(|err| { format!( @@ -192,7 +188,6 @@ mod tests { #[test] fn balance_change_target_str_roundtrip() { let targets = [ - BalanceChangeTarget::MintedSupply, BalanceChangeTarget::External( "cosmos1hkgjfuznl4af5ayzn6gzl6kwwkcu28urxmqejg".to_owned(), ), From 584ce8d65eb506daf4679ed3dce747ed714b446a Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 26 Apr 2024 14:36:33 +0100 Subject: [PATCH 21/30] Refactor token events --- .../src/lib/node/ledger/shell/governance.rs | 97 ++++---- crates/ibc/src/event.rs | 3 + crates/ibc/src/storage.rs | 31 +-- crates/namada/src/ledger/protocol/mod.rs | 58 +++-- crates/token/src/event.rs | 233 +++++++++++++----- 5 files changed, 287 insertions(+), 135 deletions(-) diff --git a/crates/apps/src/lib/node/ledger/shell/governance.rs b/crates/apps/src/lib/node/ledger/shell/governance.rs index 6e76a7d7d1..594b2b346e 100644 --- a/crates/apps/src/lib/node/ledger/shell/governance.rs +++ b/crates/apps/src/lib/node/ledger/shell/governance.rs @@ -25,7 +25,7 @@ use namada::proof_of_stake::storage::{ use namada::proof_of_stake::types::{BondId, ValidatorState}; use namada::sdk::events::{EmitEvents, EventLevel}; use namada::state::StorageWrite; -use namada::token::event::{BalanceChangeTarget, TokenEvent}; +use namada::token::event::{TokenEvent, TokenOperation, UserAccount}; use namada::token::read_balance; use namada::tx::{Code, Data}; use namada_sdk::proof_of_stake::storage::read_validator_stake; @@ -242,29 +242,21 @@ where const DESCRIPTOR: &str = "governance-locked-funds-refund"; let final_gov_balance = - read_balance(&shell.state, &native_token, &gov_address) - .ok() - .map(|balance| balance.into()); + read_balance(&shell.state, &native_token, &gov_address)?.into(); let final_target_balance = - read_balance(&shell.state, &native_token, &address) - .ok() - .map(|balance| balance.into()); + read_balance(&shell.state, &native_token, &address)?.into(); - events.emit(TokenEvent::BalanceChange { - level: EventLevel::Block, + events.emit(TokenEvent { descriptor: DESCRIPTOR.into(), - token: native_token.clone(), - target: BalanceChangeTarget::Internal(gov_address), - post_balance: final_gov_balance, - diff: funds.change().negate(), - }); - events.emit(TokenEvent::BalanceChange { level: EventLevel::Block, - descriptor: DESCRIPTOR.into(), token: native_token.clone(), - target: BalanceChangeTarget::Internal(address), - post_balance: final_target_balance, - diff: funds.change(), + operation: TokenOperation::Transfer { + amount: funds.into(), + source: UserAccount::Internal(gov_address), + target: UserAccount::Internal(address), + source_post_balance: final_gov_balance, + target_post_balance: Some(final_target_balance), + }, }); } else { token::burn_tokens( @@ -277,17 +269,17 @@ where const DESCRIPTOR: &str = "governance-locked-funds-burn"; let final_gov_balance = - read_balance(&shell.state, &native_token, &gov_address) - .ok() - .map(|balance| balance.into()); + read_balance(&shell.state, &native_token, &gov_address)?.into(); - events.emit(TokenEvent::BalanceChange { - level: EventLevel::Block, + events.emit(TokenEvent { descriptor: DESCRIPTOR.into(), + level: EventLevel::Block, token: native_token.clone(), - target: BalanceChangeTarget::Internal(gov_address), - post_balance: final_gov_balance, - diff: funds.change().negate(), + operation: TokenOperation::Burn { + amount: funds.into(), + target_account: UserAccount::Internal(gov_address), + post_balance: final_gov_balance, + }, }); } } @@ -494,34 +486,45 @@ where &target.target, target.amount, ), - TokenEvent::BalanceChange { - level: EventLevel::Block, + TokenEvent { descriptor: "pgf-payments".into(), + level: EventLevel::Block, token: token.clone(), - target: BalanceChangeTarget::Internal( - target.target.clone(), - ), - post_balance: read_balance( - state, - token, - &target.target, - ) - .ok() - .map(|balance| balance.into()), - diff: target.amount.change().negate(), + operation: TokenOperation::Transfer { + amount: target.amount.into(), + source: UserAccount::Internal(ADDRESS), + target: UserAccount::Internal( + target.target.clone(), + ), + source_post_balance: read_balance( + state, token, &ADDRESS, + )? + .into(), + target_post_balance: Some( + read_balance(state, token, &target.target)? + .into(), + ), + }, }, ), PGFTarget::Ibc(target) => ( ibc::transfer_over_ibc(state, token, &ADDRESS, target), - TokenEvent::BalanceChange { - level: EventLevel::Block, + TokenEvent { descriptor: "pgf-payments-over-ibc".into(), + level: EventLevel::Block, token: token.clone(), - target: BalanceChangeTarget::External( - target.target.clone(), - ), - post_balance: None, - diff: target.amount.change().negate(), + operation: TokenOperation::Transfer { + amount: target.amount.into(), + source: UserAccount::Internal(ADDRESS), + target: UserAccount::External( + target.target.clone(), + ), + source_post_balance: read_balance( + state, token, &ADDRESS, + )? + .into(), + target_post_balance: None, + }, }, ), }; diff --git a/crates/ibc/src/event.rs b/crates/ibc/src/event.rs index aec5b1961a..98e514de6a 100644 --- a/crates/ibc/src/event.rs +++ b/crates/ibc/src/event.rs @@ -30,6 +30,9 @@ use namada_macros::BorshDeserializer; use namada_migrations::*; use serde::{Deserialize, Serialize}; +/// Describes a token event within IBC. +pub const TOKEN_EVENT_DESCRIPTOR: &str = IbcEvent::DOMAIN; + pub mod types { //! IBC event types. diff --git a/crates/ibc/src/storage.rs b/crates/ibc/src/storage.rs index 24f1d125fb..c9fa19839a 100644 --- a/crates/ibc/src/storage.rs +++ b/crates/ibc/src/storage.rs @@ -19,10 +19,11 @@ use namada_core::token::Amount; use namada_events::{EmitEvents, EventLevel}; use namada_state::{StorageRead, StorageResult, StorageWrite}; use namada_token as token; -use namada_token::event::{BalanceChangeTarget, TokenEvent}; +use namada_token::event::{TokenEvent, TokenOperation, UserAccount}; use sha2::{Digest, Sha256}; use thiserror::Error; +use crate::event::TOKEN_EVENT_DESCRIPTOR; use crate::parameters::IbcParameters; const CLIENTS_COUNTER_PREFIX: &str = "clients"; @@ -71,15 +72,15 @@ where amount, )?; - let post_balance = token::read_balance(state, token, target)?; - - state.emit(TokenEvent::BalanceChange { + state.emit(TokenEvent { + descriptor: TOKEN_EVENT_DESCRIPTOR.into(), level: EventLevel::Tx, - descriptor: "mint-ibc-tokens".into(), token: token.clone(), - target: BalanceChangeTarget::Internal(target.clone()), - post_balance: Some(post_balance.into()), - diff: amount.change(), + operation: TokenOperation::Mint { + amount: amount.into(), + post_balance: token::read_balance(state, token, target)?.into(), + target_account: UserAccount::Internal(target.clone()), + }, }); Ok(()) @@ -97,15 +98,15 @@ where { token::burn_tokens(state, token, target, amount)?; - let post_balance = token::read_balance(state, token, target)?; - - state.emit(TokenEvent::BalanceChange { + state.emit(TokenEvent { + descriptor: TOKEN_EVENT_DESCRIPTOR.into(), level: EventLevel::Tx, - descriptor: "burn-ibc-tokens".into(), token: token.clone(), - target: BalanceChangeTarget::Internal(target.clone()), - post_balance: Some(post_balance.into()), - diff: amount.change().negate(), + operation: TokenOperation::Burn { + amount: amount.into(), + post_balance: token::read_balance(state, token, target)?.into(), + target_account: UserAccount::Internal(target.clone()), + }, }); Ok(()) diff --git a/crates/namada/src/ledger/protocol/mod.rs b/crates/namada/src/ledger/protocol/mod.rs index 4c81369022..25a4b30d99 100644 --- a/crates/namada/src/ledger/protocol/mod.rs +++ b/crates/namada/src/ledger/protocol/mod.rs @@ -16,7 +16,7 @@ use namada_events::EventLevel; use namada_gas::TxGasMeter; use namada_sdk::tx::TX_TRANSFER_WASM; use namada_state::StorageWrite; -use namada_token::event::{BalanceChangeTarget, TokenEvent}; +use namada_token::event::{TokenEvent, TokenOperation, UserAccount}; use namada_tx::data::protocol::ProtocolTxType; use namada_tx::data::{ GasLimit, TxResult, TxType, VpStatusFlags, VpsResult, WrapperTx, @@ -530,16 +530,30 @@ where ) .map_err(|e| Error::FeeError(e.to_string()))?; + let target_post_balance = Some( + namada_token::read_balance( + state, + &wrapper.fee.token, + block_proposer, + ) + .map_err(Error::StorageError)? + .into(), + ); + state.write_log_mut().emit_event( - TokenEvent::BalanceChange { - level: EventLevel::Tx, + TokenEvent { descriptor: FEE_PAYMENT_DESCRIPTOR, + level: EventLevel::Tx, token: wrapper.fee.token.clone(), - target: BalanceChangeTarget::Internal( - wrapper.fee_payer(), - ), - post_balance: Some(post_bal.into()), - diff: fees.change().negate(), + operation: TokenOperation::Transfer { + amount: fees.into(), + source: UserAccount::Internal(wrapper.fee_payer()), + target: UserAccount::Internal( + block_proposer.clone(), + ), + source_post_balance: post_bal.into(), + target_post_balance, + }, } .with(HeightAttr(current_block_height)) .with(TxHashAttr(wrapper_tx_hash)), @@ -566,16 +580,30 @@ where ) .map_err(|e| Error::FeeError(e.to_string()))?; + let target_post_balance = Some( + namada_token::read_balance( + state, + &wrapper.fee.token, + block_proposer, + ) + .map_err(Error::StorageError)? + .into(), + ); + state.write_log_mut().emit_event( - TokenEvent::BalanceChange { - level: EventLevel::Tx, + TokenEvent { descriptor: FEE_PAYMENT_DESCRIPTOR, + level: EventLevel::Tx, token: wrapper.fee.token.clone(), - target: BalanceChangeTarget::Internal( - wrapper.fee_payer(), - ), - post_balance: Some(namada_core::uint::ZERO), - diff: balance.change().negate(), + operation: TokenOperation::Transfer { + amount: balance.into(), + source: UserAccount::Internal(wrapper.fee_payer()), + target: UserAccount::Internal( + block_proposer.clone(), + ), + source_post_balance: namada_core::uint::ZERO, + target_post_balance, + }, } .with(HeightAttr(current_block_height)) .with(TxHashAttr(wrapper_tx_hash)), diff --git a/crates/token/src/event.rs b/crates/token/src/event.rs index f1ee0c8ba0..2f4435d8f8 100644 --- a/crates/token/src/event.rs +++ b/crates/token/src/event.rs @@ -5,9 +5,9 @@ use std::fmt; use std::str::FromStr; use namada_core::address::Address; -use namada_core::uint::{Uint, I256}; +use namada_core::uint::Uint; use namada_events::extend::{Closure, ComposeEvent, EventAttributeEntry}; -use namada_events::{Event, EventLevel, EventToEmit}; +use namada_events::{Event, EventLevel, EventToEmit, EventType}; pub mod types { //! Token event types. @@ -16,21 +16,26 @@ pub mod types { use super::TokenEvent; - /// Balance change event. - pub const BALANCE_CHANGE: EventType = - event_type!(TokenEvent, "balance-change"); + /// Mint token event. + pub const MINT: EventType = event_type!(TokenEvent, "mint"); + + /// Burn token event. + pub const BURN: EventType = event_type!(TokenEvent, "burn"); + + /// Transfer token event. + pub const TRANSFER: EventType = event_type!(TokenEvent, "transfer"); } -/// The target of a balance change. +/// A user account. #[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] -pub enum BalanceChangeTarget { +pub enum UserAccount { /// Internal chain address in Namada. Internal(Address), /// External chain address. External(String), } -impl fmt::Display for BalanceChangeTarget { +impl fmt::Display for UserAccount { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Internal(addr) => write!(f, "internal-address/{addr}"), @@ -39,7 +44,7 @@ impl fmt::Display for BalanceChangeTarget { } } -impl FromStr for BalanceChangeTarget { +impl FromStr for UserAccount { type Err = String; fn from_str(s: &str) -> Result { @@ -60,50 +65,134 @@ impl FromStr for BalanceChangeTarget { } } +/// Token event kind. +#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] +pub enum TokenEventKind { + /// Token mint operation. + Mint, + /// Token burn operation. + Burn, + /// Token transfer operation. + Transfer, +} + +impl From<&TokenEventKind> for EventType { + fn from(token_event_kind: &TokenEventKind) -> Self { + match token_event_kind { + TokenEventKind::Mint => types::MINT, + TokenEventKind::Burn => types::BURN, + TokenEventKind::Transfer => types::TRANSFER, + } + } +} + +impl From for EventType { + fn from(token_event_kind: TokenEventKind) -> Self { + (&token_event_kind).into() + } +} + /// Namada token event. #[derive(Debug)] -pub enum TokenEvent { - /// Balance change event. - BalanceChange { - /// Whether the balance change occurred at the block or tx level. - level: EventLevel, - /// Describes the reason of the balance change. - descriptor: Cow<'static, str>, - /// The address of the token whose balance was updated. - token: Address, - /// The target whose balance was changed. - target: BalanceChangeTarget, - /// The diff between the pre and post balance - /// (`pre_balance` + `diff` = `post_balance`). - diff: I256, - /// The balance that `account` ended up with, +pub struct TokenEvent { + /// The event level. + pub level: EventLevel, + /// The affected token address. + pub token: Address, + /// The operation that took place. + pub operation: TokenOperation, + /// Additional description of the token event. + pub descriptor: Cow<'static, str>, +} + +/// Namada token operation. +#[derive(Debug)] +pub enum TokenOperation { + /// Token mint event. + Mint { + /// The target account whose balance was changed. + target_account: UserAccount, + /// The amount of minted tokens. + amount: Uint, + /// The balance that `target_account` ended up with. + post_balance: Uint, + }, + /// Token burn event. + Burn { + /// The target account whose balance was changed. + target_account: UserAccount, + /// The amount of minted tokens. + amount: Uint, + /// The balance that `target_account` ended up with. + post_balance: Uint, + }, + /// Token transfer event. + Transfer { + /// The source of the token transfer. + source: UserAccount, + /// The target of the token transfer. + target: UserAccount, + /// The transferred amount. + amount: Uint, + /// The balance that `source` ended up with. + source_post_balance: Uint, + /// The balance that `target` ended up with, /// if it is known. - post_balance: Option, + target_post_balance: Option, }, } +impl TokenOperation { + /// The token event kind associated with this operation. + pub fn kind(&self) -> TokenEventKind { + match self { + Self::Mint { .. } => TokenEventKind::Mint, + Self::Burn { .. } => TokenEventKind::Burn, + Self::Transfer { .. } => TokenEventKind::Transfer, + } + } +} + impl EventToEmit for TokenEvent { const DOMAIN: &'static str = "token"; } impl From for Event { fn from(token_event: TokenEvent) -> Self { - match token_event { - TokenEvent::BalanceChange { - level, - descriptor, - token, - target, - diff, + let event = + Self::new(token_event.operation.kind().into(), token_event.level) + .with(TokenAddress(token_event.token)) + .with(Descriptor(&token_event.descriptor)); + + match token_event.operation { + TokenOperation::Mint { + target_account, + amount, post_balance, - } => Self::new(types::BALANCE_CHANGE, level) + } + | TokenOperation::Burn { + target_account, + amount, + post_balance, + } => event + .with(TargetAccount(target_account)) + .with(Amount(&amount)) + .with(TargetPostBalance(&post_balance)) + .into(), + TokenOperation::Transfer { + source, + target, + amount, + source_post_balance, + target_post_balance, + } => event + .with(SourceAccount(source)) .with(TargetAccount(target)) - .with(BalanceChangeKind(&descriptor)) - .with(TokenAddress(token)) - .with(BalanceDiff(&diff)) + .with(Amount(&amount)) + .with(SourcePostBalance(&source_post_balance)) .with(Closure(|event: &mut Event| { - if let Some(post_balance) = post_balance { - event.extend(PostBalance(&post_balance)); + if let Some(post_balance) = target_post_balance { + event.extend(TargetPostBalance(&post_balance)); } })) .into(), @@ -111,14 +200,14 @@ impl From for Event { } } -/// Extend an [`Event`] with balance change kind data. -pub struct BalanceChangeKind<'k>(pub &'k str); +/// Extend an [`Event`] with token event descriptor data. +pub struct Descriptor<'k>(pub &'k str); -impl<'k> EventAttributeEntry<'k> for BalanceChangeKind<'k> { +impl<'k> EventAttributeEntry<'k> for Descriptor<'k> { type Value = &'k str; type ValueOwned = String; - const KEY: &'static str = "balance-change-kind"; + const KEY: &'static str = "token-event-descriptor"; fn into_value(self) -> Self::Value { self.0 @@ -139,11 +228,25 @@ impl EventAttributeEntry<'static> for TokenAddress { } } +/// Extend an [`Event`] with source account data. +pub struct SourceAccount(pub UserAccount); + +impl EventAttributeEntry<'static> for SourceAccount { + type Value = UserAccount; + type ValueOwned = Self::Value; + + const KEY: &'static str = "source-account"; + + fn into_value(self) -> Self::Value { + self.0 + } +} + /// Extend an [`Event`] with target account data. -pub struct TargetAccount(pub BalanceChangeTarget); +pub struct TargetAccount(pub UserAccount); impl EventAttributeEntry<'static> for TargetAccount { - type Value = BalanceChangeTarget; + type Value = UserAccount; type ValueOwned = Self::Value; const KEY: &'static str = "target-account"; @@ -153,28 +256,42 @@ impl EventAttributeEntry<'static> for TargetAccount { } } -/// Extend an [`Event`] with balance change diff data. -pub struct BalanceDiff<'bal>(pub &'bal I256); +/// Extend an [`Event`] with amount data. +pub struct Amount<'amt>(pub &'amt Uint); -impl<'bal> EventAttributeEntry<'bal> for BalanceDiff<'bal> { - type Value = &'bal I256; - type ValueOwned = I256; +impl<'amt> EventAttributeEntry<'amt> for Amount<'amt> { + type Value = &'amt Uint; + type ValueOwned = Uint; + + const KEY: &'static str = "amount"; + + fn into_value(self) -> Self::Value { + self.0 + } +} + +/// Extend an [`Event`] with source post balance data. +pub struct SourcePostBalance<'bal>(pub &'bal Uint); + +impl<'bal> EventAttributeEntry<'bal> for SourcePostBalance<'bal> { + type Value = &'bal Uint; + type ValueOwned = Uint; - const KEY: &'static str = "diff"; + const KEY: &'static str = "source-post-balance"; fn into_value(self) -> Self::Value { self.0 } } -/// Extend an [`Event`] with post balance data. -pub struct PostBalance<'bal>(pub &'bal Uint); +/// Extend an [`Event`] with target post balance data. +pub struct TargetPostBalance<'bal>(pub &'bal Uint); -impl<'bal> EventAttributeEntry<'bal> for PostBalance<'bal> { +impl<'bal> EventAttributeEntry<'bal> for TargetPostBalance<'bal> { type Value = &'bal Uint; type ValueOwned = Uint; - const KEY: &'static str = "post-balance"; + const KEY: &'static str = "target-post-balance"; fn into_value(self) -> Self::Value { self.0 @@ -186,12 +303,12 @@ mod tests { use super::*; #[test] - fn balance_change_target_str_roundtrip() { + fn user_account_str_roundtrip() { let targets = [ - BalanceChangeTarget::External( + UserAccount::External( "cosmos1hkgjfuznl4af5ayzn6gzl6kwwkcu28urxmqejg".to_owned(), ), - BalanceChangeTarget::Internal( + UserAccount::Internal( Address::decode( "tnam1q82t25z5f9gmnv5sztyr8ht9tqhrw4u875qjhy56", ) @@ -201,7 +318,7 @@ mod tests { for target in targets { let as_str = target.to_string(); - let decoded: BalanceChangeTarget = as_str.parse().unwrap(); + let decoded: UserAccount = as_str.parse().unwrap(); assert_eq!(decoded, target); } From 4fb2b3b23540d078aa26ba8d26998ba50372a473 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 26 Apr 2024 15:28:22 +0100 Subject: [PATCH 22/30] Move `token` events to `trans_token` --- Cargo.lock | 3 ++- crates/token/Cargo.toml | 2 -- crates/token/src/lib.rs | 5 ++++- crates/trans_token/Cargo.toml | 2 ++ crates/{token => trans_token}/src/event.rs | 0 crates/trans_token/src/lib.rs | 1 + wasm/Cargo.lock | 3 ++- wasm_for_tests/Cargo.lock | 3 ++- 8 files changed, 13 insertions(+), 6 deletions(-) rename crates/{token => trans_token}/src/event.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index 36eb0f8f50..031713a568 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5056,7 +5056,6 @@ dependencies = [ name = "namada_token" version = "0.34.0" dependencies = [ - "konst", "namada_core", "namada_events", "namada_shielded_token", @@ -5068,8 +5067,10 @@ dependencies = [ name = "namada_trans_token" version = "0.34.0" dependencies = [ + "konst", "linkme", "namada_core", + "namada_events", "namada_storage", ] diff --git a/crates/token/Cargo.toml b/crates/token/Cargo.toml index 7f9e54c61a..1f0849ebe5 100644 --- a/crates/token/Cargo.toml +++ b/crates/token/Cargo.toml @@ -22,5 +22,3 @@ namada_events = { path = "../events", default-features = false } namada_shielded_token = { path = "../shielded_token" } namada_storage = { path = "../storage" } namada_trans_token = { path = "../trans_token" } - -konst.workspace = true diff --git a/crates/token/src/lib.rs b/crates/token/src/lib.rs index 2dbc4df27a..f613b47106 100644 --- a/crates/token/src/lib.rs +++ b/crates/token/src/lib.rs @@ -4,12 +4,15 @@ pub use namada_shielded_token::*; pub use namada_trans_token::*; -pub mod event; pub mod storage_key { pub use namada_shielded_token::storage_key::*; pub use namada_trans_token::storage_key::*; } +pub mod event { + pub use namada_trans_token::event::*; +} + use namada_core::address::Address; use namada_events::EmitEvents; use namada_storage::{Result, StorageRead, StorageWrite}; diff --git a/crates/trans_token/Cargo.toml b/crates/trans_token/Cargo.toml index a3302c4de8..a2a6f38a98 100644 --- a/crates/trans_token/Cargo.toml +++ b/crates/trans_token/Cargo.toml @@ -20,8 +20,10 @@ migrations = [ [dependencies] namada_core = { path = "../core" } +namada_events = { path = "../events", default-features = false } namada_storage = { path = "../storage" } +konst.workspace = true linkme = {workspace = true, optional = true} [dev-dependencies] diff --git a/crates/token/src/event.rs b/crates/trans_token/src/event.rs similarity index 100% rename from crates/token/src/event.rs rename to crates/trans_token/src/event.rs diff --git a/crates/trans_token/src/lib.rs b/crates/trans_token/src/lib.rs index 6644f73d7a..04b80b152c 100644 --- a/crates/trans_token/src/lib.rs +++ b/crates/trans_token/src/lib.rs @@ -1,5 +1,6 @@ //! Transparent token types, storage functions, and validation. +pub mod event; mod storage; pub mod storage_key; diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index 082d31cb0f..604550eba2 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -4040,7 +4040,6 @@ dependencies = [ name = "namada_token" version = "0.34.0" dependencies = [ - "konst", "namada_core", "namada_events", "namada_shielded_token", @@ -4052,7 +4051,9 @@ dependencies = [ name = "namada_trans_token" version = "0.34.0" dependencies = [ + "konst", "namada_core", + "namada_events", "namada_storage", ] diff --git a/wasm_for_tests/Cargo.lock b/wasm_for_tests/Cargo.lock index efc27a8ccd..15bc35636e 100644 --- a/wasm_for_tests/Cargo.lock +++ b/wasm_for_tests/Cargo.lock @@ -3986,7 +3986,6 @@ dependencies = [ name = "namada_token" version = "0.34.0" dependencies = [ - "konst", "namada_core", "namada_events", "namada_shielded_token", @@ -3998,7 +3997,9 @@ dependencies = [ name = "namada_trans_token" version = "0.34.0" dependencies = [ + "konst", "namada_core", + "namada_events", "namada_storage", ] From 360ccec97ddf8de8c1790811b153f53e49500200 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 26 Apr 2024 16:10:08 +0100 Subject: [PATCH 23/30] Proof of stake events --- Cargo.lock | 1 + crates/proof_of_stake/Cargo.toml | 1 + crates/proof_of_stake/src/event.rs | 75 ++++++++++++++++++++++++++++++ crates/proof_of_stake/src/lib.rs | 1 + wasm/Cargo.lock | 1 + wasm_for_tests/Cargo.lock | 1 + 6 files changed, 80 insertions(+) create mode 100644 crates/proof_of_stake/src/event.rs diff --git a/Cargo.lock b/Cargo.lock index 031713a568..11de4da55b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4816,6 +4816,7 @@ dependencies = [ "data-encoding", "derivative", "itertools 0.10.5", + "konst", "linkme", "namada_account", "namada_controller", diff --git a/crates/proof_of_stake/Cargo.toml b/crates/proof_of_stake/Cargo.toml index 20ab734cd6..8ab23f0f6a 100644 --- a/crates/proof_of_stake/Cargo.toml +++ b/crates/proof_of_stake/Cargo.toml @@ -36,6 +36,7 @@ namada_trans_token = { path = "../trans_token" } borsh.workspace = true data-encoding.workspace = true derivative.workspace = true +konst.workspace = true linkme = {workspace = true, optional = true} num-traits.workspace = true once_cell.workspace = true diff --git a/crates/proof_of_stake/src/event.rs b/crates/proof_of_stake/src/event.rs new file mode 100644 index 0000000000..2b9407f2dc --- /dev/null +++ b/crates/proof_of_stake/src/event.rs @@ -0,0 +1,75 @@ +//! Proof of Stake events. + +use namada_core::address::Address; +use namada_core::token; +use namada_core::uint::Uint; +use namada_events::extend::{ComposeEvent, EventAttributeEntry}; +use namada_events::{Event, EventLevel, EventToEmit}; + +pub mod types { + //! Proof of Stake event types. + + use namada_events::{event_type, EventType}; + + use super::PosEvent; + + /// Slash event. + pub const SLASH: EventType = event_type!(PosEvent, "slash"); +} + +/// Proof of Stake event. +#[derive(Debug)] +pub enum PosEvent { + /// Slashing event. + Slash { + /// The address of the slashed validator. + validator: Address, + /// Amount of tokens that have been slashed. + amount: token::Amount, + }, +} + +impl EventToEmit for PosEvent { + const DOMAIN: &'static str = "proof-of-stake"; +} + +impl From for Event { + fn from(pos_event: PosEvent) -> Self { + match pos_event { + PosEvent::Slash { validator, amount } => { + Event::new(types::SLASH, EventLevel::Block) + .with(SlashedValidator(validator)) + .with(SlashedAmount(&amount.into())) + .into() + } + } + } +} + +/// Extend an [`Event`] with slashed validator data. +pub struct SlashedValidator(pub Address); + +impl EventAttributeEntry<'static> for SlashedValidator { + type Value = Address; + type ValueOwned = Self::Value; + + const KEY: &'static str = "slashed-validator"; + + fn into_value(self) -> Self::Value { + self.0 + } +} + +/// Extend an [`Event`] with slashed amount data. +pub struct SlashedAmount<'amt>(pub &'amt Uint); + +impl<'amt> EventAttributeEntry<'amt> for SlashedAmount<'amt> { + type Value = &'amt Uint; + type ValueOwned = Uint; + + const KEY: &'static str = "slashed-amount"; + + fn into_value(self) -> Self::Value { + self.0 + } +} diff --git a/crates/proof_of_stake/src/lib.rs b/crates/proof_of_stake/src/lib.rs index b364d09d89..6a5dec6878 100644 --- a/crates/proof_of_stake/src/lib.rs +++ b/crates/proof_of_stake/src/lib.rs @@ -7,6 +7,7 @@ #![deny(rustdoc::private_intra_doc_links)] pub mod epoched; +pub mod event; pub mod parameters; pub mod pos_queries; pub mod queries; diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index 604550eba2..a4e2db7947 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -3836,6 +3836,7 @@ dependencies = [ "borsh 1.4.0", "data-encoding", "derivative", + "konst", "linkme", "namada_account", "namada_controller", diff --git a/wasm_for_tests/Cargo.lock b/wasm_for_tests/Cargo.lock index 15bc35636e..f1f43a5e29 100644 --- a/wasm_for_tests/Cargo.lock +++ b/wasm_for_tests/Cargo.lock @@ -3790,6 +3790,7 @@ dependencies = [ "borsh 1.2.1", "data-encoding", "derivative", + "konst", "namada_account", "namada_controller", "namada_core", From c6a08edf57f2956754e25c01b4e452187d6a5b63 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 26 Apr 2024 16:09:02 +0100 Subject: [PATCH 24/30] Add void event sink for testing --- crates/events/Cargo.toml | 1 + crates/events/src/lib.rs | 2 ++ crates/events/src/testing.rs | 21 +++++++++++++++++++++ 3 files changed, 24 insertions(+) create mode 100644 crates/events/src/testing.rs diff --git a/crates/events/Cargo.toml b/crates/events/Cargo.toml index d427cf3c03..0e6972db4e 100644 --- a/crates/events/Cargo.toml +++ b/crates/events/Cargo.toml @@ -19,6 +19,7 @@ migrations = [ "namada_migrations", "linkme", ] +testing = [] [dependencies] namada_core = {path = "../core"} diff --git a/crates/events/src/lib.rs b/crates/events/src/lib.rs index 83de56705e..f0f5ce819e 100644 --- a/crates/events/src/lib.rs +++ b/crates/events/src/lib.rs @@ -1,6 +1,8 @@ //! Events emitted by the Namada ledger. pub mod extend; +#[cfg(any(test, feature = "testing"))] +pub mod testing; use std::borrow::Cow; use std::collections::BTreeMap; diff --git a/crates/events/src/testing.rs b/crates/events/src/testing.rs new file mode 100644 index 0000000000..e02a566b18 --- /dev/null +++ b/crates/events/src/testing.rs @@ -0,0 +1,21 @@ +//! Events testing utilities. + +use super::{EmitEvents, Event}; + +/// Event sink that drops any emitted events. +pub struct VoidEventSink; + +impl EmitEvents for VoidEventSink { + fn emit(&mut self, _: E) + where + E: Into, + { + } + + fn emit_many(&mut self, _: B) + where + B: IntoIterator, + E: Into, + { + } +} From 5dc7798c7af20350cf9344ec9cbd80fdd7c83b72 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 26 Apr 2024 16:12:22 +0100 Subject: [PATCH 25/30] Emit PoS slashing events --- crates/proof_of_stake/Cargo.toml | 1 + crates/proof_of_stake/src/lib.rs | 6 +- crates/proof_of_stake/src/slashing.rs | 8 + .../proof_of_stake/src/tests/state_machine.rs | 8 +- .../src/tests/state_machine_v2.rs | 8 +- crates/proof_of_stake/src/tests/test_pos.rs | 49 +++- .../src/tests/test_slash_and_redel.rs | 217 +++++++++++++++--- 7 files changed, 253 insertions(+), 44 deletions(-) diff --git a/crates/proof_of_stake/Cargo.toml b/crates/proof_of_stake/Cargo.toml index 8ab23f0f6a..e8ac302a28 100644 --- a/crates/proof_of_stake/Cargo.toml +++ b/crates/proof_of_stake/Cargo.toml @@ -48,6 +48,7 @@ tracing.workspace = true [dev-dependencies] namada_core = { path = "../core", features = ["testing"] } +namada_events = { path = "../events", features = ["testing"] } namada_state = { path = "../state", features = ["testing"] } assert_matches.workspace = true diff --git a/crates/proof_of_stake/src/lib.rs b/crates/proof_of_stake/src/lib.rs index 6a5dec6878..26cd0aad97 100644 --- a/crates/proof_of_stake/src/lib.rs +++ b/crates/proof_of_stake/src/lib.rs @@ -2870,7 +2870,7 @@ where /// Apply PoS updates for a block pub fn finalize_block( storage: &mut S, - _events: &mut impl EmitEvents, + events: &mut impl EmitEvents, is_new_epoch: bool, validator_set_update_epoch: Epoch, votes: Vec, @@ -2929,7 +2929,9 @@ where // Process and apply slashes that have already been recorded for the // current epoch - if let Err(err) = slashing::process_slashes(storage, current_epoch) { + if let Err(err) = + slashing::process_slashes(storage, events, current_epoch) + { tracing::error!( "Error while processing slashes queued for epoch {}: {}", current_epoch, diff --git a/crates/proof_of_stake/src/slashing.rs b/crates/proof_of_stake/src/slashing.rs index 3965ff6014..6b2a662bb1 100644 --- a/crates/proof_of_stake/src/slashing.rs +++ b/crates/proof_of_stake/src/slashing.rs @@ -11,12 +11,14 @@ use namada_core::key::tm_raw_hash_to_string; use namada_core::storage::{BlockHeight, Epoch}; use namada_core::tendermint::abci::types::{Misbehavior, MisbehaviorKind}; use namada_core::token; +use namada_events::EmitEvents; use namada_storage::collections::lazy_map::{ Collectable, NestedMap, NestedSubKey, SubKey, }; use namada_storage::collections::LazyMap; use namada_storage::{StorageRead, StorageWrite}; +use crate::event::PosEvent; use crate::storage::{ enqueued_slashes_handle, read_pos_params, read_validator_last_slash_epoch, read_validator_stake, total_bonded_handle, total_unbonded_handle, @@ -201,6 +203,7 @@ where /// validators. pub fn process_slashes( storage: &mut S, + events: &mut impl EmitEvents, current_epoch: Epoch, ) -> namada_storage::Result<()> where @@ -318,6 +321,11 @@ where epoch, Some(0), )?; + + events.emit(PosEvent::Slash { + validator: validator.clone(), + amount: slash_amount, + }); } } // Then update validator and total deltas diff --git a/crates/proof_of_stake/src/tests/state_machine.rs b/crates/proof_of_stake/src/tests/state_machine.rs index 0ad85e9448..3529323149 100644 --- a/crates/proof_of_stake/src/tests/state_machine.rs +++ b/crates/proof_of_stake/src/tests/state_machine.rs @@ -268,8 +268,12 @@ impl StateMachineTest for ConcretePosState { // Need to apply some slashing let current_epoch = state.s.in_mem().block.epoch; - crate::slashing::process_slashes(&mut state.s, current_epoch) - .unwrap(); + crate::slashing::process_slashes( + &mut state.s, + &mut namada_events::testing::VoidEventSink, + current_epoch, + ) + .unwrap(); let params = read_pos_params(&state.s).unwrap(); state.check_next_epoch_post_conditions(¶ms); diff --git a/crates/proof_of_stake/src/tests/state_machine_v2.rs b/crates/proof_of_stake/src/tests/state_machine_v2.rs index 5c89bc6d98..1b8dbbd642 100644 --- a/crates/proof_of_stake/src/tests/state_machine_v2.rs +++ b/crates/proof_of_stake/src/tests/state_machine_v2.rs @@ -1986,8 +1986,12 @@ impl StateMachineTest for ConcretePosState { // Need to apply some slashing let current_epoch = state.s.in_mem().block.epoch; - crate::slashing::process_slashes(&mut state.s, current_epoch) - .unwrap(); + crate::slashing::process_slashes( + &mut state.s, + &mut namada_events::testing::VoidEventSink, + current_epoch, + ) + .unwrap(); let params = read_pos_params(&state.s).unwrap(); state.check_next_epoch_post_conditions(¶ms); diff --git a/crates/proof_of_stake/src/tests/test_pos.rs b/crates/proof_of_stake/src/tests/test_pos.rs index 0150cba4d5..a26764788b 100644 --- a/crates/proof_of_stake/src/tests/test_pos.rs +++ b/crates/proof_of_stake/src/tests/test_pos.rs @@ -854,7 +854,12 @@ fn test_unjail_validator_aux( s.commit_block().unwrap(); current_epoch = advance_epoch(&mut s, ¶ms); - process_slashes(&mut s, current_epoch).unwrap(); + process_slashes( + &mut s, + &mut namada_events::testing::VoidEventSink, + current_epoch, + ) + .unwrap(); // Discover first slash let slash_0_evidence_epoch = current_epoch; @@ -914,7 +919,12 @@ fn test_unjail_validator_aux( slash_0_evidence_epoch + params.slash_processing_epoch_offset(); while current_epoch < unfreeze_epoch + 4u64 { current_epoch = advance_epoch(&mut s, ¶ms); - process_slashes(&mut s, current_epoch).unwrap(); + process_slashes( + &mut s, + &mut namada_events::testing::VoidEventSink, + current_epoch, + ) + .unwrap(); } // Unjail the validator @@ -960,7 +970,12 @@ fn test_unjail_validator_aux( // Advance another epoch current_epoch = advance_epoch(&mut s, ¶ms); - process_slashes(&mut s, current_epoch).unwrap(); + process_slashes( + &mut s, + &mut namada_events::testing::VoidEventSink, + current_epoch, + ) + .unwrap(); let second_att = unjail_validator(&mut s, val_addr, current_epoch); assert!(second_att.is_err()); @@ -1040,7 +1055,12 @@ fn test_unslashed_bond_amount_aux(validators: Vec) { // Advance an epoch current_epoch = advance_epoch(&mut storage, ¶ms); - process_slashes(&mut storage, current_epoch).unwrap(); + process_slashes( + &mut storage, + &mut namada_events::testing::VoidEventSink, + current_epoch, + ) + .unwrap(); // Bond to validator 1 bond_tokens( @@ -1088,7 +1108,12 @@ fn test_unslashed_bond_amount_aux(validators: Vec) { // Advance an epoch current_epoch = advance_epoch(&mut storage, ¶ms); - process_slashes(&mut storage, current_epoch).unwrap(); + process_slashes( + &mut storage, + &mut namada_events::testing::VoidEventSink, + current_epoch, + ) + .unwrap(); // Bond to validator 1 bond_tokens( @@ -1630,7 +1655,12 @@ fn test_is_delegator_aux(mut validators: Vec) { // Advance to epoch 1 current_epoch = advance_epoch(&mut storage, ¶ms); - process_slashes(&mut storage, current_epoch).unwrap(); + process_slashes( + &mut storage, + &mut namada_events::testing::VoidEventSink, + current_epoch, + ) + .unwrap(); // Delegate in epoch 1 to validator1 let del1_epoch = current_epoch; @@ -1646,7 +1676,12 @@ fn test_is_delegator_aux(mut validators: Vec) { // Advance to epoch 2 current_epoch = advance_epoch(&mut storage, ¶ms); - process_slashes(&mut storage, current_epoch).unwrap(); + process_slashes( + &mut storage, + &mut namada_events::testing::VoidEventSink, + current_epoch, + ) + .unwrap(); // Delegate in epoch 2 to validator2 let del2_epoch = current_epoch; diff --git a/crates/proof_of_stake/src/tests/test_slash_and_redel.rs b/crates/proof_of_stake/src/tests/test_slash_and_redel.rs index 9df89eeef4..d866220cc0 100644 --- a/crates/proof_of_stake/src/tests/test_slash_and_redel.rs +++ b/crates/proof_of_stake/src/tests/test_slash_and_redel.rs @@ -117,7 +117,12 @@ fn test_simple_redelegation_aux( for _ in 0..5 { current_epoch = advance_epoch(&mut storage, ¶ms); - process_slashes(&mut storage, current_epoch).unwrap(); + process_slashes( + &mut storage, + &mut namada_events::testing::VoidEventSink, + current_epoch, + ) + .unwrap(); } let init_epoch = current_epoch; @@ -135,11 +140,26 @@ fn test_simple_redelegation_aux( // Advance three epochs current_epoch = advance_epoch(&mut storage, ¶ms); - process_slashes(&mut storage, current_epoch).unwrap(); + process_slashes( + &mut storage, + &mut namada_events::testing::VoidEventSink, + current_epoch, + ) + .unwrap(); current_epoch = advance_epoch(&mut storage, ¶ms); - process_slashes(&mut storage, current_epoch).unwrap(); + process_slashes( + &mut storage, + &mut namada_events::testing::VoidEventSink, + current_epoch, + ) + .unwrap(); current_epoch = advance_epoch(&mut storage, ¶ms); - process_slashes(&mut storage, current_epoch).unwrap(); + process_slashes( + &mut storage, + &mut namada_events::testing::VoidEventSink, + current_epoch, + ) + .unwrap(); // Redelegate in epoch 3 redelegate_tokens( @@ -183,11 +203,26 @@ fn test_simple_redelegation_aux( // Advance three epochs current_epoch = advance_epoch(&mut storage, ¶ms); - process_slashes(&mut storage, current_epoch).unwrap(); + process_slashes( + &mut storage, + &mut namada_events::testing::VoidEventSink, + current_epoch, + ) + .unwrap(); current_epoch = advance_epoch(&mut storage, ¶ms); - process_slashes(&mut storage, current_epoch).unwrap(); + process_slashes( + &mut storage, + &mut namada_events::testing::VoidEventSink, + current_epoch, + ) + .unwrap(); current_epoch = advance_epoch(&mut storage, ¶ms); - process_slashes(&mut storage, current_epoch).unwrap(); + process_slashes( + &mut storage, + &mut namada_events::testing::VoidEventSink, + current_epoch, + ) + .unwrap(); // Unbond in epoch 5 from dest_validator let _ = unbond_tokens( @@ -239,7 +274,12 @@ fn test_simple_redelegation_aux( // Advance to withdrawal epoch loop { current_epoch = advance_epoch(&mut storage, ¶ms); - process_slashes(&mut storage, current_epoch).unwrap(); + process_slashes( + &mut storage, + &mut namada_events::testing::VoidEventSink, + current_epoch, + ) + .unwrap(); if current_epoch == unbond_end { break; } @@ -324,7 +364,12 @@ fn test_slashes_with_unbonding_aux( s.commit_block().unwrap(); current_epoch = advance_epoch(&mut s, ¶ms); - process_slashes(&mut s, current_epoch).unwrap(); + process_slashes( + &mut s, + &mut namada_events::testing::VoidEventSink, + current_epoch, + ) + .unwrap(); // Discover first slash let slash_0_evidence_epoch = current_epoch; @@ -349,7 +394,12 @@ fn test_slashes_with_unbonding_aux( slash_0_evidence_epoch + params.slash_processing_epoch_offset(); while current_epoch < unfreeze_epoch { current_epoch = advance_epoch(&mut s, ¶ms); - process_slashes(&mut s, current_epoch).unwrap(); + process_slashes( + &mut s, + &mut namada_events::testing::VoidEventSink, + current_epoch, + ) + .unwrap(); } // Advance more epochs randomly from the generated delay @@ -386,7 +436,12 @@ fn test_slashes_with_unbonding_aux( let withdraw_epoch = unbond_epoch + params.withdrawable_epoch_offset(); while current_epoch < withdraw_epoch { current_epoch = advance_epoch(&mut s, ¶ms); - process_slashes(&mut s, current_epoch).unwrap(); + process_slashes( + &mut s, + &mut namada_events::testing::VoidEventSink, + current_epoch, + ) + .unwrap(); } let token = staking_token_address(&s); let val_balance_pre = read_balance(&s, &token, val_addr).unwrap(); @@ -496,7 +551,12 @@ fn test_redelegation_with_slashing_aux( for _ in 0..5 { current_epoch = advance_epoch(&mut storage, ¶ms); - process_slashes(&mut storage, current_epoch).unwrap(); + process_slashes( + &mut storage, + &mut namada_events::testing::VoidEventSink, + current_epoch, + ) + .unwrap(); } let init_epoch = current_epoch; @@ -514,11 +574,26 @@ fn test_redelegation_with_slashing_aux( // Advance three epochs current_epoch = advance_epoch(&mut storage, ¶ms); - process_slashes(&mut storage, current_epoch).unwrap(); + process_slashes( + &mut storage, + &mut namada_events::testing::VoidEventSink, + current_epoch, + ) + .unwrap(); current_epoch = advance_epoch(&mut storage, ¶ms); - process_slashes(&mut storage, current_epoch).unwrap(); + process_slashes( + &mut storage, + &mut namada_events::testing::VoidEventSink, + current_epoch, + ) + .unwrap(); current_epoch = advance_epoch(&mut storage, ¶ms); - process_slashes(&mut storage, current_epoch).unwrap(); + process_slashes( + &mut storage, + &mut namada_events::testing::VoidEventSink, + current_epoch, + ) + .unwrap(); // Redelegate in epoch 8 redelegate_tokens( @@ -558,11 +633,26 @@ fn test_redelegation_with_slashing_aux( // Advance three epochs current_epoch = advance_epoch(&mut storage, ¶ms); - process_slashes(&mut storage, current_epoch).unwrap(); + process_slashes( + &mut storage, + &mut namada_events::testing::VoidEventSink, + current_epoch, + ) + .unwrap(); current_epoch = advance_epoch(&mut storage, ¶ms); - process_slashes(&mut storage, current_epoch).unwrap(); + process_slashes( + &mut storage, + &mut namada_events::testing::VoidEventSink, + current_epoch, + ) + .unwrap(); current_epoch = advance_epoch(&mut storage, ¶ms); - process_slashes(&mut storage, current_epoch).unwrap(); + process_slashes( + &mut storage, + &mut namada_events::testing::VoidEventSink, + current_epoch, + ) + .unwrap(); // Unbond in epoch 11 from dest_validator let _ = unbond_tokens( @@ -577,7 +667,12 @@ fn test_redelegation_with_slashing_aux( // Advance one epoch current_epoch = advance_epoch(&mut storage, ¶ms); - process_slashes(&mut storage, current_epoch).unwrap(); + process_slashes( + &mut storage, + &mut namada_events::testing::VoidEventSink, + current_epoch, + ) + .unwrap(); // Discover evidence slash( @@ -631,7 +726,12 @@ fn test_redelegation_with_slashing_aux( // Advance to withdrawal epoch loop { current_epoch = advance_epoch(&mut storage, ¶ms); - process_slashes(&mut storage, current_epoch).unwrap(); + process_slashes( + &mut storage, + &mut namada_events::testing::VoidEventSink, + current_epoch, + ) + .unwrap(); if current_epoch == unbond_end { break; } @@ -729,7 +829,12 @@ fn test_chain_redelegations_aux(mut validators: Vec) { // Advance one epoch current_epoch = advance_epoch(&mut storage, ¶ms); - process_slashes(&mut storage, current_epoch).unwrap(); + process_slashes( + &mut storage, + &mut namada_events::testing::VoidEventSink, + current_epoch, + ) + .unwrap(); // Redelegate in epoch 1 to dest_validator let redel_amount_1: token::Amount = 58.into(); @@ -842,9 +947,19 @@ fn test_chain_redelegations_aux(mut validators: Vec) { // Attempt to redelegate in epoch 3 to dest_validator current_epoch = advance_epoch(&mut storage, ¶ms); - process_slashes(&mut storage, current_epoch).unwrap(); + process_slashes( + &mut storage, + &mut namada_events::testing::VoidEventSink, + current_epoch, + ) + .unwrap(); current_epoch = advance_epoch(&mut storage, ¶ms); - process_slashes(&mut storage, current_epoch).unwrap(); + process_slashes( + &mut storage, + &mut namada_events::testing::VoidEventSink, + current_epoch, + ) + .unwrap(); let redel_amount_2: token::Amount = 23.into(); let redel_att = redelegate_tokens( @@ -863,7 +978,12 @@ fn test_chain_redelegations_aux(mut validators: Vec) { redel_end.prev() + params.slash_processing_epoch_offset(); loop { current_epoch = advance_epoch(&mut storage, ¶ms); - process_slashes(&mut storage, current_epoch).unwrap(); + process_slashes( + &mut storage, + &mut namada_events::testing::VoidEventSink, + current_epoch, + ) + .unwrap(); if current_epoch == epoch_can_redel.prev() { break; } @@ -882,7 +1002,12 @@ fn test_chain_redelegations_aux(mut validators: Vec) { // Advance one more epoch current_epoch = advance_epoch(&mut storage, ¶ms); - process_slashes(&mut storage, current_epoch).unwrap(); + process_slashes( + &mut storage, + &mut namada_events::testing::VoidEventSink, + current_epoch, + ) + .unwrap(); // Redelegate from dest_validator to dest_validator_2 now redelegate_tokens( @@ -1154,7 +1279,12 @@ fn test_overslashing_aux(mut validators: Vec) { // Advance to processing epoch 1 loop { current_epoch = advance_epoch(&mut storage, ¶ms); - process_slashes(&mut storage, current_epoch).unwrap(); + process_slashes( + &mut storage, + &mut namada_events::testing::VoidEventSink, + current_epoch, + ) + .unwrap(); if current_epoch == processing_epoch_1 { break; } @@ -1190,7 +1320,12 @@ fn test_overslashing_aux(mut validators: Vec) { // Advance to processing epoch 2 loop { current_epoch = advance_epoch(&mut storage, ¶ms); - process_slashes(&mut storage, current_epoch).unwrap(); + process_slashes( + &mut storage, + &mut namada_events::testing::VoidEventSink, + current_epoch, + ) + .unwrap(); if current_epoch == processing_epoch_2 { break; } @@ -1333,7 +1468,12 @@ fn test_slashed_bond_amount_aux(validators: Vec) { // Advance an epoch to 1 current_epoch = advance_epoch(&mut storage, ¶ms); - process_slashes(&mut storage, current_epoch).unwrap(); + process_slashes( + &mut storage, + &mut namada_events::testing::VoidEventSink, + current_epoch, + ) + .unwrap(); // Bond to validator 1 bond_tokens( @@ -1381,7 +1521,12 @@ fn test_slashed_bond_amount_aux(validators: Vec) { // Advance an epoch to ep 2 current_epoch = advance_epoch(&mut storage, ¶ms); - process_slashes(&mut storage, current_epoch).unwrap(); + process_slashes( + &mut storage, + &mut namada_events::testing::VoidEventSink, + current_epoch, + ) + .unwrap(); // Bond to validator 1 bond_tokens( @@ -1419,7 +1564,12 @@ fn test_slashed_bond_amount_aux(validators: Vec) { // Advance two epochs to ep 4 for _ in 0..2 { current_epoch = advance_epoch(&mut storage, ¶ms); - process_slashes(&mut storage, current_epoch).unwrap(); + process_slashes( + &mut storage, + &mut namada_events::testing::VoidEventSink, + current_epoch, + ) + .unwrap(); } // Find some slashes committed in various epochs @@ -1471,7 +1621,12 @@ fn test_slashed_bond_amount_aux(validators: Vec) { // Advance such that these slashes are all processed for _ in 0..params.slash_processing_epoch_offset() { current_epoch = advance_epoch(&mut storage, ¶ms); - process_slashes(&mut storage, current_epoch).unwrap(); + process_slashes( + &mut storage, + &mut namada_events::testing::VoidEventSink, + current_epoch, + ) + .unwrap(); } let pipeline_epoch = current_epoch + params.pipeline_len; From a63a2870936aa38e2a753fc7faea4137791d4613 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 29 Apr 2024 14:04:25 +0100 Subject: [PATCH 26/30] Emit token transfer event from wasm --- crates/trans_token/src/storage.rs | 10 ++++--- crates/tx_prelude/src/token.rs | 47 +++++++++++++++---------------- 2 files changed, 29 insertions(+), 28 deletions(-) diff --git a/crates/trans_token/src/storage.rs b/crates/trans_token/src/storage.rs index f554f1fc84..962b60c3c2 100644 --- a/crates/trans_token/src/storage.rs +++ b/crates/trans_token/src/storage.rs @@ -143,12 +143,14 @@ where storage.write(&src_key, new_src_balance)?; storage.write(&dest_key, new_dest_balance) } - None => Err(storage::Error::new_const( - "The transfer would overflow destination balance", - )), + None => Err(storage::Error::new_alloc(format!( + "The transfer would overflow balance of {dest}" + ))), } } - None => Err(storage::Error::new_const("Insufficient source balance")), + None => Err(storage::Error::new_alloc(format!( + "{src} has insufficient balance" + ))), } } diff --git a/crates/tx_prelude/src/token.rs b/crates/tx_prelude/src/token.rs index bcc376296c..7eaa1129a7 100644 --- a/crates/tx_prelude/src/token.rs +++ b/crates/tx_prelude/src/token.rs @@ -1,10 +1,11 @@ use namada_core::address::Address; -use namada_proof_of_stake::token::storage_key::balance_key; -use namada_storage::{Error as StorageError, ResultExt}; -pub use namada_token::*; +use namada_events::{EmitEvents, EventLevel}; +pub use namada_token::{ + storage_key, utils, Amount, DenominatedAmount, Transfer, +}; use namada_tx_env::TxEnv; -use crate::{Ctx, StorageRead, StorageWrite, TxResult}; +use crate::{Ctx, TxResult}; /// A token transfer that can be used in a transaction. pub fn transfer( @@ -14,6 +15,8 @@ pub fn transfer( token: &Address, amount: Amount, ) -> TxResult { + use namada_token::event::{TokenEvent, TokenOperation, UserAccount}; + // The tx must be authorized by the source address ctx.insert_verifier(src)?; if token.is_internal() { @@ -23,27 +26,23 @@ pub fn transfer( ctx.insert_verifier(token)?; } - if amount == Amount::zero() { - return Ok(()); - } - - let src_key = balance_key(token, src); - let dest_key = balance_key(token, dest); - let src_bal: Option = ctx.read(&src_key)?; - let mut src_bal = src_bal - .ok_or_else(|| StorageError::new_const("the source has no balance"))?; - - if !src_bal.can_spend(&amount) { - return Err(StorageError::new_const( - "the source has no enough balance", - )); - } + namada_token::transfer(ctx, token, src, dest, amount)?; - src_bal.spend(&amount).into_storage_result()?; - let mut dest_bal: Amount = ctx.read(&dest_key)?.unwrap_or_default(); - dest_bal.receive(&amount).into_storage_result()?; - ctx.write(&src_key, src_bal)?; - ctx.write(&dest_key, dest_bal)?; + ctx.emit(TokenEvent { + descriptor: "transfer-from-wasm".into(), + level: EventLevel::Tx, + token: token.clone(), + operation: TokenOperation::Transfer { + amount: amount.into(), + source: UserAccount::Internal(src.clone()), + target: UserAccount::Internal(dest.clone()), + source_post_balance: namada_token::read_balance(ctx, token, src)? + .into(), + target_post_balance: Some( + namada_token::read_balance(ctx, token, dest)?.into(), + ), + }, + }); Ok(()) } From b36959add74f4298821b3a79ba83fb320eb80d40 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 30 Apr 2024 12:40:07 +0100 Subject: [PATCH 27/30] Fix unit tests --- crates/apps/src/lib/node/ledger/shell/init_chain.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/apps/src/lib/node/ledger/shell/init_chain.rs b/crates/apps/src/lib/node/ledger/shell/init_chain.rs index c75e72615f..0042ef924c 100644 --- a/crates/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/crates/apps/src/lib/node/ledger/shell/init_chain.rs @@ -1202,7 +1202,7 @@ mod test { token::Amount::from_uint(1, 6).unwrap(), 6.into(), ), - "Insufficient source balance".to_string(), + format!("{albert_address_str} has insufficient balance"), )]; assert_eq!(expected, initializer.warnings); initializer.warnings.clear(); From dcac2b6f97b939b4c69732f58b20a1248c634cb1 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 30 Apr 2024 12:52:17 +0100 Subject: [PATCH 28/30] Log which token failed to be minted on IBC native VP --- crates/namada/src/ledger/native_vp/multitoken.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/namada/src/ledger/native_vp/multitoken.rs b/crates/namada/src/ledger/native_vp/multitoken.rs index 08676ca949..bd454a1c4b 100644 --- a/crates/namada/src/ledger/native_vp/multitoken.rs +++ b/crates/namada/src/ledger/native_vp/multitoken.rs @@ -272,9 +272,9 @@ where .into()), } } - _ => Err(native_vp::Error::new_const( - "Only IBC tokens can be minted by a user transaction", - ) + _ => Err(native_vp::Error::new_alloc(format!( + "Attempted to mint non-IBC token {token}" + )) .into()), } } From ff62f8efec41d1ec0c14d75e5c1c69d1c0abf47e Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 30 Apr 2024 13:03:16 +0100 Subject: [PATCH 29/30] Fix Multitoken native VP err msg --- crates/namada/src/ledger/protocol/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/namada/src/ledger/protocol/mod.rs b/crates/namada/src/ledger/protocol/mod.rs index 25a4b30d99..97ad0cc7e5 100644 --- a/crates/namada/src/ledger/protocol/mod.rs +++ b/crates/namada/src/ledger/protocol/mod.rs @@ -87,7 +87,7 @@ pub enum Error { PosNativeVpRuntime, #[error("Parameters native VP: {0}")] ParametersNativeVpError(parameters::Error), - #[error("IBC Token native VP: {0}")] + #[error("Multitoken native VP: {0}")] MultitokenNativeVpError(crate::ledger::native_vp::multitoken::Error), #[error("Governance native VP error: {0}")] GovernanceNativeVpError(crate::ledger::governance::Error), From 406f715b416b069b310410fec20f0ba0cb7c4a06 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 29 Apr 2024 14:10:28 +0100 Subject: [PATCH 30/30] Changelog for #3141 --- .../unreleased/improvements/3141-balance-change-events.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/improvements/3141-balance-change-events.md diff --git a/.changelog/unreleased/improvements/3141-balance-change-events.md b/.changelog/unreleased/improvements/3141-balance-change-events.md new file mode 100644 index 0000000000..0fc29ce8ab --- /dev/null +++ b/.changelog/unreleased/improvements/3141-balance-change-events.md @@ -0,0 +1,2 @@ +- Emit balance change events for various protocol actions. + ([\#3141](https://github.com/anoma/namada/pull/3141)) \ No newline at end of file