From 1783a94459b0c9d7ad1654e11a7cafe5280d121d Mon Sep 17 00:00:00 2001 From: yito88 Date: Wed, 27 Sep 2023 14:49:52 +0200 Subject: [PATCH 1/4] transfer and query with trace-path --- apps/src/lib/cli.rs | 14 ++++ apps/src/lib/client/rpc.rs | 91 ++++++++++++++++++++++++-- benches/lib.rs | 1 + core/src/ledger/ibc/context/common.rs | 5 +- core/src/ledger/ibc/mod.rs | 57 +++++++++++----- core/src/ledger/ibc/storage.rs | 35 +++++++--- core/src/types/ibc.rs | 5 ++ core/src/types/storage.rs | 2 +- shared/src/ledger/native_vp/ibc/mod.rs | 4 +- shared/src/sdk/args.rs | 9 ++- shared/src/sdk/signing.rs | 1 + shared/src/sdk/tx.rs | 29 ++++---- tests/src/e2e/ibc_tests.rs | 88 +++++++++++++++++-------- tests/src/vm_host_env/mod.rs | 4 -- 14 files changed, 264 insertions(+), 81 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 135ff1e3c5..13ed0d2e84 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -2516,6 +2516,7 @@ pub mod args { use std::path::PathBuf; use std::str::FromStr; + use namada::ibc::applications::transfer::TracePath; use namada::ibc::core::ics24_host::identifier::{ChannelId, PortId}; pub use namada::sdk::args::*; use namada::types::address::Address; @@ -2725,6 +2726,7 @@ pub mod args { pub const TM_ADDRESS: Arg = arg("tm-address"); pub const TOKEN_OPT: ArgOpt = TOKEN.opt(); pub const TOKEN: Arg = arg("token"); + pub const TRACE_PATH: ArgOpt = arg_opt("trace-path"); pub const TRANSFER_SOURCE: Arg = arg("source"); pub const TRANSFER_TARGET: Arg = arg("target"); pub const TX_HASH: Arg = arg("tx-hash"); @@ -3504,6 +3506,7 @@ pub mod args { source: ctx.get_cached(&self.source), target: ctx.get(&self.target), token: ctx.get(&self.token), + trace_path: self.trace_path, amount: self.amount, native_token: ctx.native_token.clone(), tx_code_path: self.tx_code_path.to_path_buf(), @@ -3517,6 +3520,7 @@ pub mod args { let source = TRANSFER_SOURCE.parse(matches); let target = TRANSFER_TARGET.parse(matches); let token = TOKEN.parse(matches); + let trace_path = TRACE_PATH.parse(matches); let amount = InputAmount::Unvalidated(AMOUNT.parse(matches)); let tx_code_path = PathBuf::from(TX_TRANSFER_WASM); Self { @@ -3524,6 +3528,7 @@ pub mod args { source, target, token, + trace_path, amount, native_token: (), tx_code_path, @@ -3541,6 +3546,7 @@ pub mod args { to produce the signature.", )) .arg(TOKEN.def().help("The transfer token.")) + .arg(TRACE_PATH.def().help("The transfer token's trace path.")) .arg(AMOUNT.def().help("The amount to transfer in decimal.")) } } @@ -3552,6 +3558,7 @@ pub mod args { source: ctx.get(&self.source), receiver: self.receiver, token: ctx.get(&self.token), + trace_path: self.trace_path, amount: self.amount, port_id: self.port_id, channel_id: self.channel_id, @@ -3569,6 +3576,7 @@ pub mod args { let source = SOURCE.parse(matches); let receiver = RECEIVER.parse(matches); let token = TOKEN.parse(matches); + let trace_path = TRACE_PATH.parse(matches); let amount = InputAmount::Unvalidated(AMOUNT.parse(matches)); let port_id = PORT_ID.parse(matches); let channel_id = CHANNEL_ID.parse(matches); @@ -3581,6 +3589,7 @@ pub mod args { source, receiver, token, + trace_path, amount, port_id, channel_id, @@ -3601,6 +3610,7 @@ pub mod args { "The receiver address on the destination chain as string.", )) .arg(TOKEN.def().help("The transfer token.")) + .arg(TRACE_PATH.def().help("The transfer token's trace path.")) .arg(AMOUNT.def().help("The amount to transfer in decimal.")) .arg(PORT_ID.def().help("The port ID.")) .arg(CHANNEL_ID.def().help("The channel ID.")) @@ -4448,6 +4458,7 @@ pub mod args { query: self.query.to_sdk(ctx), owner: self.owner.map(|x| ctx.get_cached(&x)), token: self.token.map(|x| ctx.get(&x)), + trace_path: self.trace_path, no_conversions: self.no_conversions, } } @@ -4458,11 +4469,13 @@ pub mod args { let query = Query::parse(matches); let owner = BALANCE_OWNER.parse(matches); let token = TOKEN_OPT.parse(matches); + let trace_path = TRACE_PATH.parse(matches); let no_conversions = NO_CONVERSIONS.parse(matches); Self { query, owner, token, + trace_path, no_conversions, } } @@ -4479,6 +4492,7 @@ pub mod args { .def() .help("The token's address whose balance to query."), ) + .arg(TRACE_PATH.def().help("The transfer token's trace path.")) .arg( NO_CONVERSIONS.def().help( "Whether not to automatically perform conversions.", diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index d750ccc759..0606efd06e 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -29,6 +29,9 @@ use namada::core::ledger::governance::utils::{ use namada::core::ledger::pgf::parameters::PgfParameters; use namada::core::ledger::pgf::storage::steward::StewardDetail; use namada::ledger::events::Event; +use namada::ledger::ibc::storage::{ + ibc_denom_key, ibc_denom_key_prefix, ibc_token, is_ibc_denom_key, +}; use namada::ledger::parameters::{storage as param_storage, EpochDuration}; use namada::ledger::pos::{CommissionPair, PosParams, Slash}; use namada::ledger::queries::RPC; @@ -44,7 +47,7 @@ use namada::sdk::rpc::{ TxResponse, }; use namada::sdk::wallet::{AddressVpType, Wallet}; -use namada::types::address::{masp, Address}; +use namada::types::address::{masp, Address, InternalAddress}; use namada::types::control_flow::ProceedOrElse; use namada::types::hash::Hash; use namada::types::io::Io; @@ -348,12 +351,24 @@ pub async fn query_transparent_balance< Address::Internal(namada::types::address::InternalAddress::Multitoken) .to_db_key(), ); - let tokens = wallet.tokens_with_aliases(); - match (args.token, args.owner) { + let token = args.token.as_ref().map(|token| { + if let Some(trace_path) = &args.trace_path { + ibc_token(format!("{}/{}", trace_path, token)) + } else { + token.clone() + } + }); + match (token, args.owner) { (Some(token), Some(owner)) => { let balance_key = token::balance_key(&token, &owner.address().unwrap()); - let token_alias = wallet.lookup_alias(&token); + let base_token_alias = + wallet.lookup_alias(&args.token.expect("No token")); + let token_alias = if let Some(trace_path) = args.trace_path { + format!("{}/{}", trace_path, base_token_alias) + } else { + base_token_alias + }; match query_storage_value::(client, &balance_key) .await { @@ -377,6 +392,9 @@ pub async fn query_transparent_balance< } (None, Some(owner)) => { let owner = owner.address().unwrap(); + let tokens = + query_tokens::<_, IO>(client, &wallet, Some(&owner)).await; + println!("DEBUG: tokens {:?}", tokens); for (token_alias, token) in tokens { let balance = get_token_balance(client, &token, &owner).await; if !balance.is_zero() { @@ -416,6 +434,66 @@ pub async fn query_transparent_balance< } } +async fn get_token_alias( + client: &C, + wallet: &Wallet, + token: &Address, + owner: &Address, +) -> String { + if let Address::Internal(InternalAddress::IbcToken(trace_hash)) = token { + let ibc_denom_key = ibc_denom_key(owner, trace_hash); + match query_storage_value::(client, &ibc_denom_key).await { + Ok(ibc_denom) => get_ibc_denom_alias(wallet, ibc_denom), + Err(_) => token.to_string(), + } + } else { + wallet.lookup_alias(token) + } +} + +async fn query_tokens( + client: &C, + wallet: &Wallet, + owner: Option<&Address>, +) -> BTreeMap { + // Base tokens + let mut tokens = wallet.tokens_with_aliases(); + + let prefix = ibc_denom_key_prefix(owner); + let ibc_denoms = + query_storage_prefix::(client, &prefix).await; + if let Some(ibc_denoms) = ibc_denoms { + for (key, ibc_denom) in ibc_denoms { + if let Some((_, hash)) = is_ibc_denom_key(&key) { + let ibc_denom_alias = get_ibc_denom_alias(wallet, ibc_denom); + let ibc_token = + Address::Internal(InternalAddress::IbcToken(hash)); + tokens.insert(ibc_denom_alias, ibc_token); + } + } + } + tokens +} + +fn get_ibc_denom_alias( + wallet: &Wallet, + ibc_denom: impl AsRef, +) -> String { + let (trace_path, base_denom) = ibc_denom + .as_ref() + .rsplit_once('/') + .unwrap_or(("", ibc_denom.as_ref())); + let token_alias = match Address::decode(&base_denom) { + Ok(token) => wallet.lookup_alias(&token), + Err(_) => base_denom.to_string(), + }; + if trace_path.is_empty() { + token_alias + } else { + format!("{}/{}", trace_path, token_alias) + } +} + /// Query the token pinned balance(s) pub async fn query_pinned_balance< C: namada::ledger::queries::Client + Sync, @@ -606,6 +684,7 @@ async fn print_balances( ), None => continue, }; + let token_alias = get_token_alias(client, wallet, &t, &o).await; // Get the token and the balance let (t, s) = match (token, target) { // the given token and the given target are the same as the @@ -628,7 +707,6 @@ async fn print_balances( // the token has been already printed } _ => { - let token_alias = wallet.lookup_alias(&t); display_line!(IO, &mut w; "Token {}", token_alias).unwrap(); print_token = Some(t); } @@ -768,7 +846,8 @@ pub async fn query_shielded_balance< .expect("context should contain viewing key") }; - let token_alias = wallet.lookup_alias(&token); + let token_alias = + get_token_alias(client, wallet, &token, &masp()).await; let total_balance = balance .get(&(epoch, token.clone())) diff --git a/benches/lib.rs b/benches/lib.rs index 47645abdf4..30765d8beb 100644 --- a/benches/lib.rs +++ b/benches/lib.rs @@ -783,6 +783,7 @@ impl BenchShieldedCtx { source: source.clone(), target: target.clone(), token: address::nam(), + trace_path: None, amount: InputAmount::Validated(DenominatedAmount { amount, denom: 0.into(), diff --git a/core/src/ledger/ibc/context/common.rs b/core/src/ledger/ibc/context/common.rs index 5e963e7a5f..b5f0326767 100644 --- a/core/src/ledger/ibc/context/common.rs +++ b/core/src/ledger/ibc/context/common.rs @@ -361,15 +361,16 @@ pub trait IbcCommonContext: IbcStorageContext { /// Write the IBC denom fn store_ibc_denom( &mut self, + receiver: &Address, trace_hash: impl AsRef, denom: impl AsRef, ) -> Result<(), ContextError> { - let key = storage::ibc_denom_key(trace_hash.as_ref()); + let key = storage::ibc_denom_key(receiver, trace_hash.as_ref()); let has_key = self.has_key(&key).map_err(|_| { ContextError::ChannelError(ChannelError::Other { description: format!( "Reading the IBC denom failed: Key {}", - key + key, ), }) })?; diff --git a/core/src/ledger/ibc/mod.rs b/core/src/ledger/ibc/mod.rs index fcabcee745..4cc86277f8 100644 --- a/core/src/ledger/ibc/mod.rs +++ b/core/src/ledger/ibc/mod.rs @@ -26,7 +26,9 @@ use crate::ibc::core::ics24_host::identifier::{ChainId as IbcChainId, PortId}; use crate::ibc::core::router::{Module, ModuleId, Router}; use crate::ibc::core::{execute, validate, MsgEnvelope, RouterError}; use crate::ibc_proto::google::protobuf::Any; +use crate::types::address::Address; use crate::types::chain::ChainId; +use crate::types::ibc::{EVENT_TYPE_DENOM_TRACE, EVENT_TYPE_PACKET}; #[allow(missing_docs)] #[derive(Error, Debug)] @@ -135,20 +137,8 @@ where fn store_denom(&mut self, envelope: MsgEnvelope) -> Result<(), Error> { match envelope { MsgEnvelope::Packet(PacketMsg::Recv(_)) => { - let result = self - .ctx - .borrow() - .get_ibc_event("denomination_trace") - .map_err(|_| { - Error::Denom("Reading the IBC event failed".to_string()) - })?; - if let Some((trace_hash, ibc_denom)) = - result.as_ref().and_then(|event| { - event - .attributes - .get("trace_hash") - .zip(event.attributes.get("denom")) - }) + if let Some((trace_hash, ibc_denom, receiver)) = + self.get_minted_token_info()? { // If the denomination trace event has the trace hash and // the IBC denom, a token has been minted. The raw IBC denom @@ -157,7 +147,7 @@ where // denomination is also set for the minting. self.ctx .borrow_mut() - .store_ibc_denom(trace_hash, ibc_denom) + .store_ibc_denom(&receiver, trace_hash, &ibc_denom) .map_err(|e| { Error::Denom(format!( "Writing the IBC denom failed: {}", @@ -182,6 +172,43 @@ where } } + /// Get the minted IBC denom, the trace hash, and the receiver from IBC + /// events + fn get_minted_token_info( + &self, + ) -> Result, Error> { + let receive_event = + self.ctx.borrow().get_ibc_event(EVENT_TYPE_PACKET).map_err( + |_| Error::Denom("Reading the IBC event failed".to_string()), + )?; + let receiver = match receive_event + .as_ref() + .and_then(|event| event.attributes.get("receiver")) + { + Some(receiver) => { + Some(Address::decode(receiver).map_err(|_| { + Error::Denom(format!( + "Decoding the receiver address failed: {:?}", + receive_event + )) + })?) + } + None => None, + }; + let denom_event = self + .ctx + .borrow() + .get_ibc_event(EVENT_TYPE_DENOM_TRACE) + .map_err(|_| { + Error::Denom("Reading the IBC event failed".to_string()) + })?; + Ok(denom_event.as_ref().and_then(|event| { + let trace_hash = event.attributes.get("trace_hash").cloned()?; + let denom = event.attributes.get("denom").cloned()?; + Some((trace_hash, denom, receiver?)) + })) + } + /// Validate according to the message in IBC VP pub fn validate(&self, tx_data: &[u8]) -> Result<(), Error> { let any_msg = Any::decode(tx_data).map_err(Error::DecodingData)?; diff --git a/core/src/ledger/ibc/storage.rs b/core/src/ledger/ibc/storage.rs index 317aa108bf..349c4b6ef3 100644 --- a/core/src/ledger/ibc/storage.rs +++ b/core/src/ledger/ibc/storage.rs @@ -367,10 +367,26 @@ pub fn port_id(key: &Key) -> Result { } } -/// The storage key to get the denom name from the hashed token -pub fn ibc_denom_key(token_hash: impl AsRef) -> Key { - let path = format!("{}/{}", DENOM, token_hash.as_ref()); - ibc_key(path).expect("Creating a key for the denom key shouldn't fail") +/// The storage key prefix to get the denom name with the hashed IBC denom +pub fn ibc_denom_key_prefix(owner: Option<&Address>) -> Key { + let prefix = Key::from(Address::Internal(InternalAddress::Ibc).to_db_key()) + .push(&DENOM.to_string().to_db_key()) + .expect("Cannot obtain a storage key"); + + if let Some(owner) = owner { + prefix + .push(&owner.to_db_key()) + .expect("Cannot obtain a storage key") + } else { + prefix + } +} + +/// The storage key to get the denom name with the hashed IBC denom +pub fn ibc_denom_key(owner: &Address, token_hash: impl AsRef) -> Key { + ibc_denom_key_prefix(Some(owner)) + .push(&token_hash.as_ref().to_string().to_db_key()) + .expect("Cannot obtain a storage key") } /// Hash the denom @@ -392,20 +408,19 @@ pub fn is_ibc_key(key: &Key) -> bool { DbKeySeg::AddressSeg(addr) if *addr == Address::Internal(InternalAddress::Ibc)) } -/// Returns the token hash if the given key is the denom key -pub fn is_ibc_denom_key(key: &Key) -> Option { +/// Returns the owner and the token hash if the given key is the denom key +pub fn is_ibc_denom_key(key: &Key) -> Option<(Address, String)> { match &key.segments[..] { [ DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(prefix), - DbKeySeg::AddressSeg(Address::Internal(InternalAddress::IbcToken( - hash, - ))), + DbKeySeg::AddressSeg(owner), + DbKeySeg::StringSeg(hash), ] => { if addr == &Address::Internal(InternalAddress::Ibc) && prefix == DENOM { - Some(hash.clone()) + Some((owner.clone(), hash.clone())) } else { None } diff --git a/core/src/types/ibc.rs b/core/src/types/ibc.rs index 7a412ecb05..32a93218eb 100644 --- a/core/src/types/ibc.rs +++ b/core/src/types/ibc.rs @@ -5,6 +5,11 @@ use std::collections::HashMap; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +/// The event type defined in ibc-rs for receiving a token +pub const EVENT_TYPE_PACKET: &str = "fungible_token_packet"; +/// The event type defined in ibc-rs for IBC denom +pub const EVENT_TYPE_DENOM_TRACE: &str = "denomination_trace"; + /// Wrapped IbcEvent #[derive( Debug, Clone, BorshSerialize, BorshDeserialize, BorshSchema, PartialEq, Eq, diff --git a/core/src/types/storage.rs b/core/src/types/storage.rs index ad0c14f499..7dcfd14726 100644 --- a/core/src/types/storage.rs +++ b/core/src/types/storage.rs @@ -26,7 +26,7 @@ use crate::types::keccak::{KeccakHash, TryFromError}; use crate::types::time::DateTimeUtc; /// The maximum size of an IBC key (in bytes) allowed in merkle-ized storage -pub const IBC_KEY_LIMIT: usize = 120; +pub const IBC_KEY_LIMIT: usize = 240; #[allow(missing_docs)] #[derive(Error, Debug, Clone)] diff --git a/shared/src/ledger/native_vp/ibc/mod.rs b/shared/src/ledger/native_vp/ibc/mod.rs index 3b6521905b..c50ee89600 100644 --- a/shared/src/ledger/native_vp/ibc/mod.rs +++ b/shared/src/ledger/native_vp/ibc/mod.rs @@ -169,7 +169,7 @@ where fn validate_denom(&self, keys_changed: &BTreeSet) -> VpResult<()> { for key in keys_changed { - if let Some(hash) = is_ibc_denom_key(key) { + if let Some((_, hash)) = is_ibc_denom_key(key) { match self.ctx.read_post::(key).map_err(|e| { Error::Denom(format!( "Getting the denom failed: Key {}, Error {}", @@ -2191,7 +2191,7 @@ mod tests { packet.chan_id_on_b.clone(), )); let trace_hash = calc_hash(coin.denom.to_string()); - let denom_key = ibc_denom_key(&trace_hash); + let denom_key = ibc_denom_key(&receiver, &trace_hash); let bytes = coin.denom.to_string().try_to_vec().unwrap(); wl_storage .write_log diff --git a/shared/src/sdk/args.rs b/shared/src/sdk/args.rs index b765dece5a..3c31b17ba7 100644 --- a/shared/src/sdk/args.rs +++ b/shared/src/sdk/args.rs @@ -11,6 +11,7 @@ use namada_core::types::time::DateTimeUtc; use serde::{Deserialize, Serialize}; use zeroize::Zeroizing; +use crate::ibc::applications::transfer::TracePath; use crate::ibc::core::ics24_host::identifier::{ChannelId, PortId}; use crate::types::address::Address; use crate::types::keccak::KeccakHash; @@ -135,6 +136,8 @@ pub struct TxTransfer { pub target: C::TransferTarget, /// Transferred token address pub token: C::Address, + /// Transferred token's trace path + pub trace_path: Option, /// Transferred token amount pub amount: InputAmount, /// Native token address @@ -163,8 +166,10 @@ pub struct TxIbcTransfer { pub source: C::Address, /// Transfer target address pub receiver: String, - /// Transferred token addres s + /// Transferred token address pub token: C::Address, + /// Transferred token's trace path + pub trace_path: Option, /// Transferred token amount pub amount: InputAmount, /// Port ID @@ -391,6 +396,8 @@ pub struct QueryBalance { pub owner: Option, /// Address of a token pub token: Option, + /// Transferred token's trace path + pub trace_path: Option, /// Whether not to convert balances pub no_conversions: bool, } diff --git a/shared/src/sdk/signing.rs b/shared/src/sdk/signing.rs index 042be03a63..9f1d541faf 100644 --- a/shared/src/sdk/signing.rs +++ b/shared/src/sdk/signing.rs @@ -426,6 +426,7 @@ pub async fn wrap_tx< fee_payer_address.clone(), ), token: args.fee_token.clone(), + trace_path: None, amount: args::InputAmount::Validated(DenominatedAmount { // NOTE: must unshield the total fee amount, not the // diff, because the ledger evaluates the transaction in diff --git a/shared/src/sdk/tx.rs b/shared/src/sdk/tx.rs index 9d7fe0cfe4..5b477de47e 100644 --- a/shared/src/sdk/tx.rs +++ b/shared/src/sdk/tx.rs @@ -23,7 +23,7 @@ use namada_core::ledger::governance::cli::onchain::{ use namada_core::ledger::governance::storage::proposal::ProposalType; use namada_core::ledger::governance::storage::vote::StorageProposalVote; use namada_core::ledger::pgf::cli::steward::Commission; -use namada_core::types::address::{masp, Address, InternalAddress}; +use namada_core::types::address::{masp, Address}; use namada_core::types::dec::Dec; use namada_core::types::hash::Hash; use namada_core::types::token::MaspDenom; @@ -36,12 +36,12 @@ use namada_proof_of_stake::types::{CommissionPair, ValidatorState}; use crate::ibc::applications::transfer::msgs::transfer::MsgTransfer; use crate::ibc::applications::transfer::packet::PacketData; -use crate::ibc::applications::transfer::PrefixedCoin; +use crate::ibc::applications::transfer::{PrefixedCoin, PrefixedDenom}; use crate::ibc::core::ics04_channel::timeout::TimeoutHeight; use crate::ibc::core::timestamp::Timestamp as IbcTimestamp; use crate::ibc::core::Msg; use crate::ibc::Height as IbcHeight; -use crate::ledger::ibc::storage::ibc_denom_key; +use crate::ledger::ibc::storage::ibc_token; use crate::proto::{MaspBuilder, Tx}; use crate::sdk::args::{self, InputAmount}; use crate::sdk::error::{EncodingError, Error, QueryError, Result, TxError}; @@ -1414,17 +1414,16 @@ pub async fn build_ibc_transfer< .await .map_err(|e| Error::from(QueryError::Wasm(e.to_string())))?; - let ibc_denom = match &args.token { - Address::Internal(InternalAddress::IbcToken(hash)) => { - let ibc_denom_key = ibc_denom_key(hash); - rpc::query_storage_value::(client, &ibc_denom_key) - .await - .map_err(|_e| TxError::TokenDoesNotExist(args.token.clone()))? - } - _ => args.token.to_string(), + let ibc_denom = PrefixedDenom { + trace_path: args.trace_path.unwrap_or_default(), + base_denom: args + .token + .to_string() + .parse() + .expect("Conversion from the token shouldn't fail"), }; let token = PrefixedCoin { - denom: ibc_denom.parse().expect("Invalid IBC denom"), + denom: ibc_denom, // Set the IBC amount as an integer amount: validated_amount.into(), }; @@ -1667,7 +1666,11 @@ pub async fn build_transfer< ) -> Result<(Tx, Option)> { let source = args.source.effective_address(); let target = args.target.effective_address(); - let token = args.token.clone(); + let token = if let Some(trace_path) = &args.trace_path { + ibc_token(format!("{}/{}", trace_path.clone(), args.token)) + } else { + args.token.clone() + }; // Check that the source address exists on chain source_exists_or_err::<_, IO>(source.clone(), args.tx.force, client) diff --git a/tests/src/e2e/ibc_tests.rs b/tests/src/e2e/ibc_tests.rs index c294367f1c..a539efaae8 100644 --- a/tests/src/e2e/ibc_tests.rs +++ b/tests/src/e2e/ibc_tests.rs @@ -637,6 +637,7 @@ fn transfer_token( ALBERT, &receiver, NAM, + None, "100000", ALBERT_KEY, port_id_a, @@ -706,6 +707,7 @@ fn try_invalid_transfers( ALBERT, &receiver, NAM, + None, "10.1", ALBERT_KEY, port_id_a, @@ -721,6 +723,7 @@ fn try_invalid_transfers( ALBERT, &receiver, NAM, + None, "10", ALBERT_KEY, &"port".parse().unwrap(), @@ -736,6 +739,7 @@ fn try_invalid_transfers( ALBERT, &receiver, NAM, + None, "10", ALBERT_KEY, port_id_a, @@ -753,10 +757,8 @@ fn transfer_received_token( channel_id: &ChannelId, test: &Test, ) -> Result<()> { - let nam = find_address(test, NAM)?; // token received via the port and channel - let denom = format!("{port_id}/{channel_id}/{nam}"); - let ibc_token = ibc_token(denom).to_string(); + let trace_path = format!("{port_id}/{channel_id}"); let rpc = get_actor_rpc(test, &Who::Validator(0)); let amount = Amount::native_whole(50000).to_string_native(); @@ -767,7 +769,9 @@ fn transfer_received_token( "--target", ALBERT, "--token", - &ibc_token, + NAM, + "--trace-path", + &trace_path, "--amount", &amount, "--gas-token", @@ -791,18 +795,17 @@ fn transfer_back( port_id_b: &PortId, channel_id_b: &ChannelId, ) -> Result<()> { - let token = find_address(test_b, NAM)?.to_string(); let receiver = find_address(test_a, ALBERT)?; // Chain A was the source for the sent token - let denom_raw = format!("{}/{}/{}", port_id_b, channel_id_b, token); - let ibc_token = ibc_token(denom_raw).to_string(); + let trace_path = format!("{}/{}", port_id_b, channel_id_b); // Send a token from Chain B let height = transfer( test_b, BERTHA, &receiver, - ibc_token, + NAM, + Some(&trace_path), "50000", BERTHA_KEY, port_id_b, @@ -866,6 +869,7 @@ fn transfer_timeout( ALBERT, &receiver, NAM, + None, "100000", ALBERT_KEY, port_id_a, @@ -995,6 +999,7 @@ fn transfer( sender: impl AsRef, receiver: &Address, token: impl AsRef, + trace_path: Option<&str>, amount: impl AsRef, signer: impl AsRef, port_id: &PortId, @@ -1028,6 +1033,11 @@ fn transfer( &rpc, ]; + if let Some(trace_path) = trace_path { + tx_args.push("--trace-path"); + tx_args.push(&trace_path.clone()); + } + let timeout = timeout_sec.unwrap_or_default().as_secs().to_string(); if timeout_sec.is_some() { tx_args.push("--timeout-sec-offset"); @@ -1211,8 +1221,6 @@ fn check_balances( test_a: &Test, test_b: &Test, ) -> Result<()> { - let token = find_address(test_a, NAM)?; - // Check the balances on Chain A let rpc_a = get_actor_rpc(test_a, &Who::Validator(0)); let query_args = vec!["balance", "--token", NAM, "--node", &rpc_a]; @@ -1229,13 +1237,20 @@ fn check_balances( client.assert_success(); // Check the balance on Chain B - let denom = format!("{}/{}/{}", &dest_port_id, &dest_channel_id, &token,); - let ibc_token = ibc_token(denom).to_string(); + let trace_path = format!("{}/{}", &dest_port_id, &dest_channel_id); let rpc_b = get_actor_rpc(test_b, &Who::Validator(0)); let query_args = vec![ - "balance", "--owner", BERTHA, "--token", &ibc_token, "--node", &rpc_b, + "balance", + "--owner", + BERTHA, + "--token", + NAM, + "--trace-path", + &trace_path, + "--node", + &rpc_b, ]; - let expected = format!("{}: 100000", ibc_token); + let expected = format!("{}: 100000", format!("{}/nam", trace_path)); let mut client = run!(test_b, Bin::Client, query_args, Some(40))?; client.exp_string(&expected)?; client.assert_success(); @@ -1249,25 +1264,39 @@ fn check_balances_after_non_ibc( test: &Test, ) -> Result<()> { // Check the balance on Chain B - let token = find_address(test, NAM)?; - let denom = format!("{}/{}/{}", port_id, channel_id, token); - let ibc_token = ibc_token(denom).to_string(); + let trace_path = format!("{}/{}", port_id, channel_id); // Check the source let rpc = get_actor_rpc(test, &Who::Validator(0)); let query_args = vec![ - "balance", "--owner", BERTHA, "--token", &ibc_token, "--node", &rpc, + "balance", + "--owner", + BERTHA, + "--token", + NAM, + "--trace-path", + &trace_path, + "--node", + &rpc, ]; - let expected = format!("{}: 50000", ibc_token); + let expected = format!("{}: 50000", format!("{}/nam", trace_path)); let mut client = run!(test, Bin::Client, query_args, Some(40))?; client.exp_string(&expected)?; client.assert_success(); // Check the traget let query_args = vec![ - "balance", "--owner", ALBERT, "--token", &ibc_token, "--node", &rpc, + "balance", + "--owner", + ALBERT, + "--token", + NAM, + "--trace-path", + &trace_path, + "--node", + &rpc, ]; - let expected = format!("{}: 50000", ibc_token); + let expected = format!("{}: 50000", format!("{}/nam", trace_path)); let mut client = run!(test, Bin::Client, query_args, Some(40))?; client.exp_string(&expected)?; client.assert_success(); @@ -1282,8 +1311,6 @@ fn check_balances_after_back( test_a: &Test, test_b: &Test, ) -> Result<()> { - let token = find_address(test_b, NAM)?; - // Check the balances on Chain A let rpc_a = get_actor_rpc(test_a, &Who::Validator(0)); let query_args = vec!["balance", "--token", NAM, "--node", &rpc_a]; @@ -1300,13 +1327,20 @@ fn check_balances_after_back( client.assert_success(); // Check the balance on Chain B - let denom = format!("{}/{}/{}", dest_port_id, dest_channel_id, &token,); - let ibc_token = ibc_token(denom).to_string(); + let trace_path = format!("{}/{}", dest_port_id, dest_channel_id); let rpc_b = get_actor_rpc(test_b, &Who::Validator(0)); let query_args = vec![ - "balance", "--owner", BERTHA, "--token", &ibc_token, "--node", &rpc_b, + "balance", + "--owner", + BERTHA, + "--token", + NAM, + "--trace-path", + &trace_path, + "--node", + &rpc_b, ]; - let expected = format!("{}: 0", ibc_token); + let expected = format!("{}: 0", format!("{}/nam", trace_path)); let mut client = run!(test_b, Bin::Client, query_args, Some(40))?; client.exp_string(&expected)?; client.assert_success(); diff --git a/tests/src/vm_host_env/mod.rs b/tests/src/vm_host_env/mod.rs index 68ebd76dff..b22e9ff6b2 100644 --- a/tests/src/vm_host_env/mod.rs +++ b/tests/src/vm_host_env/mod.rs @@ -1260,10 +1260,6 @@ mod tests { .try_to_vec() .unwrap(), ); - // original denom - let hash = ibc_storage::calc_hash(&denom); - let denom_key = ibc_storage::ibc_denom_key(hash); - writes.insert(denom_key, denom.try_to_vec().unwrap()); writes.into_iter().for_each(|(key, val)| { tx_host_env::with(|env| { env.wl_storage From 8c7bdf05f45981fb6fa39791d437e05fd36e04f3 Mon Sep 17 00:00:00 2001 From: yito88 Date: Thu, 28 Sep 2023 00:12:56 +0200 Subject: [PATCH 2/4] query balances for IbcToken --- apps/src/lib/cli.rs | 4 - apps/src/lib/client/rpc.rs | 487 ++++++++++++++----------- core/src/ledger/ibc/context/common.rs | 6 +- core/src/ledger/ibc/mod.rs | 21 +- core/src/ledger/ibc/storage.rs | 24 +- core/src/types/ibc.rs | 15 + shared/src/ledger/native_vp/ibc/mod.rs | 9 +- shared/src/sdk/args.rs | 2 - tests/src/e2e/ibc_tests.rs | 48 +-- 9 files changed, 334 insertions(+), 282 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 13ed0d2e84..79db0b3824 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -4458,7 +4458,6 @@ pub mod args { query: self.query.to_sdk(ctx), owner: self.owner.map(|x| ctx.get_cached(&x)), token: self.token.map(|x| ctx.get(&x)), - trace_path: self.trace_path, no_conversions: self.no_conversions, } } @@ -4469,13 +4468,11 @@ pub mod args { let query = Query::parse(matches); let owner = BALANCE_OWNER.parse(matches); let token = TOKEN_OPT.parse(matches); - let trace_path = TRACE_PATH.parse(matches); let no_conversions = NO_CONVERSIONS.parse(matches); Self { query, owner, token, - trace_path, no_conversions, } } @@ -4492,7 +4489,6 @@ pub mod args { .def() .help("The token's address whose balance to query."), ) - .arg(TRACE_PATH.def().help("The transfer token's trace path.")) .arg( NO_CONVERSIONS.def().help( "Whether not to automatically perform conversions.", diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 0606efd06e..27073fe2d1 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -30,7 +30,7 @@ use namada::core::ledger::pgf::parameters::PgfParameters; use namada::core::ledger::pgf::storage::steward::StewardDetail; use namada::ledger::events::Event; use namada::ledger::ibc::storage::{ - ibc_denom_key, ibc_denom_key_prefix, ibc_token, is_ibc_denom_key, + ibc_denom_key, ibc_denom_key_prefix, is_ibc_denom_key, }; use namada::ledger::parameters::{storage as param_storage, EpochDuration}; use namada::ledger::pos::{CommissionPair, PosParams, Slash}; @@ -50,6 +50,7 @@ use namada::sdk::wallet::{AddressVpType, Wallet}; use namada::types::address::{masp, Address, InternalAddress}; use namada::types::control_flow::ProceedOrElse; use namada::types::hash::Hash; +use namada::types::ibc::split_ibc_denom; use namada::types::io::Io; use namada::types::key::*; use namada::types::masp::{BalanceOwner, ExtendedViewingKey, PaymentAddress}; @@ -217,7 +218,8 @@ pub async fn query_transfers< for (account, MaspChange { ref asset, change }) in tfer_delta { if account != masp() { display!(IO, " {}:", account); - let token_alias = wallet.lookup_alias(asset); + let token_alias = + lookup_token_alias(client, wallet, asset, &account).await; let sign = match change.cmp(&Change::zero()) { Ordering::Greater => "+", Ordering::Less => "-", @@ -245,7 +247,13 @@ pub async fn query_transfers< if fvk_map.contains_key(&account) { display!(IO, " {}:", fvk_map[&account]); for (token_addr, val) in masp_change { - let token_alias = wallet.lookup_alias(&token_addr); + let token_alias = lookup_token_alias( + client, + wallet, + &token_addr, + &masp(), + ) + .await; let sign = match val.cmp(&Change::zero()) { Ordering::Greater => "+", Ordering::Less => "-", @@ -351,50 +359,47 @@ pub async fn query_transparent_balance< Address::Internal(namada::types::address::InternalAddress::Multitoken) .to_db_key(), ); - let token = args.token.as_ref().map(|token| { - if let Some(trace_path) = &args.trace_path { - ibc_token(format!("{}/{}", trace_path, token)) - } else { - token.clone() - } - }); - match (token, args.owner) { - (Some(token), Some(owner)) => { - let balance_key = - token::balance_key(&token, &owner.address().unwrap()); - let base_token_alias = - wallet.lookup_alias(&args.token.expect("No token")); - let token_alias = if let Some(trace_path) = args.trace_path { - format!("{}/{}", trace_path, base_token_alias) - } else { - base_token_alias - }; - match query_storage_value::(client, &balance_key) + match (args.token, args.owner) { + (Some(base_token), Some(owner)) => { + let owner = owner.address().unwrap(); + let tokens = query_tokens::<_, IO>( + client, + wallet, + Some(&base_token), + Some(&owner), + ) + .await; + for (token_alias, token) in tokens { + let balance_key = token::balance_key(&token, &owner); + match query_storage_value::( + client, + &balance_key, + ) .await - { - Ok(balance) => { - let balance = format_denominated_amount::<_, IO>( - client, &token, balance, - ) - .await; - display_line!(IO, "{}: {}", token_alias, balance); - } - Err(e) => { - display_line!(IO, "Eror in querying: {e}"); - display_line!( - IO, - "No {} balance found for {}", - token_alias, - owner - ) + { + Ok(balance) => { + let balance = format_denominated_amount::<_, IO>( + client, &token, balance, + ) + .await; + display_line!(IO, "{}: {}", token_alias, balance); + } + Err(e) => { + display_line!(IO, "Eror in querying: {e}"); + display_line!( + IO, + "No {} balance found for {}", + token_alias, + owner + ) + } } } } (None, Some(owner)) => { let owner = owner.address().unwrap(); let tokens = - query_tokens::<_, IO>(client, &wallet, Some(&owner)).await; - println!("DEBUG: tokens {:?}", tokens); + query_tokens::<_, IO>(client, wallet, None, Some(&owner)).await; for (token_alias, token) in tokens { let balance = get_token_balance(client, &token, &owner).await; if !balance.is_zero() { @@ -406,20 +411,26 @@ pub async fn query_transparent_balance< } } } - (Some(token), None) => { - let prefix = token::balance_prefix(&token); - let balances = - query_storage_prefix::(client, &prefix) + (Some(base_token), None) => { + let tokens = + query_tokens::<_, IO>(client, wallet, Some(&base_token), None) .await; - if let Some(balances) = balances { - print_balances::<_, IO>( - client, - wallet, - balances, - Some(&token), - None, + for (_, token) in tokens { + let prefix = token::balance_prefix(&token); + let balances = query_storage_prefix::( + client, &prefix, ) .await; + if let Some(balances) = balances { + print_balances::<_, IO>( + client, + wallet, + balances, + Some(&token), + None, + ) + .await; + } } } (None, None) => { @@ -434,14 +445,14 @@ pub async fn query_transparent_balance< } } -async fn get_token_alias( +async fn lookup_token_alias( client: &C, wallet: &Wallet, token: &Address, owner: &Address, ) -> String { if let Address::Internal(InternalAddress::IbcToken(trace_hash)) = token { - let ibc_denom_key = ibc_denom_key(owner, trace_hash); + let ibc_denom_key = ibc_denom_key(owner.to_string(), trace_hash); match query_storage_value::(client, &ibc_denom_key).await { Ok(ibc_denom) => get_ibc_denom_alias(wallet, ibc_denom), Err(_) => token.to_string(), @@ -451,24 +462,51 @@ async fn get_token_alias( } } +/// Returns pairs of token alias and token address async fn query_tokens( client: &C, wallet: &Wallet, + base_token: Option<&Address>, owner: Option<&Address>, ) -> BTreeMap { // Base tokens - let mut tokens = wallet.tokens_with_aliases(); - - let prefix = ibc_denom_key_prefix(owner); - let ibc_denoms = - query_storage_prefix::(client, &prefix).await; - if let Some(ibc_denoms) = ibc_denoms { - for (key, ibc_denom) in ibc_denoms { - if let Some((_, hash)) = is_ibc_denom_key(&key) { - let ibc_denom_alias = get_ibc_denom_alias(wallet, ibc_denom); - let ibc_token = - Address::Internal(InternalAddress::IbcToken(hash)); - tokens.insert(ibc_denom_alias, ibc_token); + let mut tokens = match base_token { + Some(base_token) => { + let mut map = BTreeMap::new(); + map.insert(wallet.lookup_alias(base_token), base_token.clone()); + map + } + None => wallet.tokens_with_aliases(), + }; + + let prefixes = match (base_token, owner) { + (Some(base_token), Some(owner)) => vec![ + ibc_denom_key_prefix(Some(base_token.to_string())), + ibc_denom_key_prefix(Some(owner.to_string())), + ], + (Some(base_token), None) => { + vec![ibc_denom_key_prefix(Some(base_token.to_string()))] + } + (None, Some(_)) => { + // Check all IBC denoms because the owner might not know IBC token + // transfers in the same chain + vec![ibc_denom_key_prefix(None)] + } + (None, None) => vec![ibc_denom_key_prefix(None)], + }; + + for prefix in prefixes { + let ibc_denoms = + query_storage_prefix::(client, &prefix).await; + if let Some(ibc_denoms) = ibc_denoms { + for (key, ibc_denom) in ibc_denoms { + if let Some((_, hash)) = is_ibc_denom_key(&key) { + let ibc_denom_alias = + get_ibc_denom_alias(wallet, ibc_denom); + let ibc_token = + Address::Internal(InternalAddress::IbcToken(hash)); + tokens.insert(ibc_denom_alias, ibc_token); + } } } } @@ -479,19 +517,19 @@ fn get_ibc_denom_alias( wallet: &Wallet, ibc_denom: impl AsRef, ) -> String { - let (trace_path, base_denom) = ibc_denom - .as_ref() - .rsplit_once('/') - .unwrap_or(("", ibc_denom.as_ref())); - let token_alias = match Address::decode(&base_denom) { - Ok(token) => wallet.lookup_alias(&token), - Err(_) => base_denom.to_string(), - }; - if trace_path.is_empty() { - token_alias - } else { - format!("{}/{}", trace_path, token_alias) - } + split_ibc_denom(&ibc_denom) + .map(|(trace_path, base_token)| { + let base_token_alias = match Address::decode(&base_token) { + Ok(base_token) => wallet.lookup_alias(&base_token), + Err(_) => base_token, + }; + if trace_path.is_empty() { + base_token_alias + } else { + format!("{}/{}", trace_path, base_token_alias) + } + }) + .unwrap_or(ibc_denom.as_ref().to_string()) } /// Query the token pinned balance(s) @@ -505,8 +543,6 @@ pub async fn query_pinned_balance< shielded: &mut ShieldedContext, args: args::QueryBalance, ) { - // Map addresses to token names - let tokens = wallet.get_addresses_with_vp_type(AddressVpType::Token); let owners = if let Some(pa) = args.owner.and_then(|x| x.payment_address()) { vec![pa] @@ -578,39 +614,46 @@ pub async fn query_pinned_balance< (Err(other), _) => { display_line!(IO, "Error in Querying Pinned balance {}", other) } - (Ok((balance, epoch)), Some(token)) => { - let token_alias = wallet.lookup_alias(token); - - let total_balance = balance - .get(&(epoch, token.clone())) - .cloned() - .unwrap_or_default(); + (Ok((balance, epoch)), Some(base_token)) => { + let tokens = query_tokens::<_, IO>( + client, + wallet, + Some(base_token), + None, + ) + .await; + for (token_alias, token) in &tokens { + let total_balance = balance + .get(&(epoch, token.clone())) + .cloned() + .unwrap_or_default(); - if total_balance.is_zero() { - display_line!( - IO, - "Payment address {} was consumed during epoch {}. \ - Received no shielded {}", - owner, - epoch, - token_alias - ); - } else { - let formatted = format_denominated_amount::<_, IO>( - client, - token, - total_balance.into(), - ) - .await; - display_line!( - IO, - "Payment address {} was consumed during epoch {}. \ - Received {} {}", - owner, - epoch, - formatted, - token_alias, - ); + if total_balance.is_zero() { + display_line!( + IO, + "Payment address {} was consumed during epoch {}. \ + Received no shielded {}", + owner, + epoch, + token_alias + ); + } else { + let formatted = format_denominated_amount::<_, IO>( + client, + token, + total_balance.into(), + ) + .await; + display_line!( + IO, + "Payment address {} was consumed during epoch {}. \ + Received {} {}", + owner, + epoch, + formatted, + token_alias, + ); + } } } (Ok((balance, epoch)), None) => { @@ -636,10 +679,9 @@ pub async fn query_pinned_balance< (*value).into(), ) .await; - let token_alias = tokens - .get(token_addr) - .map(|a| a.to_string()) - .unwrap_or_else(|| token_addr.to_string()); + let token_alias = + lookup_token_alias(client, wallet, token_addr, &masp()) + .await; display_line!(IO, " {}: {}", token_alias, formatted,); } if !found_any { @@ -684,7 +726,7 @@ async fn print_balances( ), None => continue, }; - let token_alias = get_token_alias(client, wallet, &t, &o).await; + let token_alias = lookup_token_alias(client, wallet, &t, &o).await; // Get the token and the balance let (t, s) = match (token, target) { // the given token and the given target are the same as the @@ -821,56 +863,61 @@ pub async fn query_shielded_balance< // The epoch is required to identify timestamped tokens let epoch = query_and_print_epoch::<_, IO>(client).await; // Map addresses to token names - let tokens = wallet.get_addresses_with_vp_type(AddressVpType::Token); match (args.token, owner.is_some()) { // Here the user wants to know the balance for a specific token - (Some(token), true) => { - // Query the multi-asset balance at the given spending key - let viewing_key = - ExtendedFullViewingKey::from(viewing_keys[0]).fvk.vk; - let balance: MaspAmount = if no_conversions { - shielded - .compute_shielded_balance(client, &viewing_key) - .await - .unwrap() - .expect("context should contain viewing key") - } else { - shielded - .compute_exchanged_balance::<_, IO>( - client, - &viewing_key, - epoch, - ) - .await - .unwrap() - .expect("context should contain viewing key") - }; - - let token_alias = - get_token_alias(client, wallet, &token, &masp()).await; + (Some(base_token), true) => { + let tokens = query_tokens::<_, IO>( + client, + wallet, + Some(&base_token), + Some(&masp()), + ) + .await; + for (token_alias, token) in tokens { + // Query the multi-asset balance at the given spending key + let viewing_key = + ExtendedFullViewingKey::from(viewing_keys[0]).fvk.vk; + let balance: MaspAmount = if no_conversions { + shielded + .compute_shielded_balance(client, &viewing_key) + .await + .unwrap() + .expect("context should contain viewing key") + } else { + shielded + .compute_exchanged_balance::<_, IO>( + client, + &viewing_key, + epoch, + ) + .await + .unwrap() + .expect("context should contain viewing key") + }; - let total_balance = balance - .get(&(epoch, token.clone())) - .cloned() - .unwrap_or_default(); - if total_balance.is_zero() { - display_line!( - IO, - "No shielded {} balance found for given key", - token_alias - ); - } else { - display_line!( - IO, - "{}: {}", - token_alias, - format_denominated_amount::<_, IO>( - client, - &token, - token::Amount::from(total_balance) - ) - .await - ); + let total_balance = balance + .get(&(epoch, token.clone())) + .cloned() + .unwrap_or_default(); + if total_balance.is_zero() { + display_line!( + IO, + "No shielded {} balance found for given key", + token_alias + ); + } else { + display_line!( + IO, + "{}: {}", + token_alias, + format_denominated_amount::<_, IO>( + client, + &token, + token::Amount::from(total_balance) + ) + .await + ); + } } } // Here the user wants to know the balance of all tokens across users @@ -927,10 +974,8 @@ pub async fn query_shielded_balance< } for ((fvk, token), token_balance) in balance_map { // Only assets with the current timestamp count - let alias = tokens - .get(&token) - .map(|a| a.to_string()) - .unwrap_or_else(|| token.to_string()); + let alias = + lookup_token_alias(client, wallet, &token, &masp()).await; display_line!(IO, "Shielded Token {}:", alias); let formatted = format_denominated_amount::<_, IO>( client, @@ -943,61 +988,63 @@ pub async fn query_shielded_balance< } // Here the user wants to know the balance for a specific token across // users - (Some(token), false) => { - // Compute the unique asset identifier from the token address - let token = token; - let _asset_type = AssetType::new( - (token.clone(), epoch.0) - .try_to_vec() - .expect("token addresses should serialize") - .as_ref(), - ) - .unwrap(); - let token_alias = wallet.lookup_alias(&token); - display_line!(IO, "Shielded Token {}:", token_alias); - let mut found_any = false; - let token_alias = wallet.lookup_alias(&token); - display_line!(IO, "Shielded Token {}:", token_alias,); - for fvk in viewing_keys { - // Query the multi-asset balance at the given spending key - let viewing_key = ExtendedFullViewingKey::from(fvk).fvk.vk; - let balance = if no_conversions { - shielded - .compute_shielded_balance(client, &viewing_key) - .await - .unwrap() - .expect("context should contain viewing key") - } else { - shielded - .compute_exchanged_balance::<_, IO>( + (Some(base_token), false) => { + let tokens = + query_tokens::<_, IO>(client, wallet, Some(&base_token), None) + .await; + for (token_alias, token) in tokens { + // Compute the unique asset identifier from the token address + let token = token; + let _asset_type = AssetType::new( + (token.clone(), epoch.0) + .try_to_vec() + .expect("token addresses should serialize") + .as_ref(), + ) + .unwrap(); + let mut found_any = false; + display_line!(IO, "Shielded Token {}:", token_alias); + for fvk in &viewing_keys { + // Query the multi-asset balance at the given spending key + let viewing_key = ExtendedFullViewingKey::from(*fvk).fvk.vk; + let balance = if no_conversions { + shielded + .compute_shielded_balance(client, &viewing_key) + .await + .unwrap() + .expect("context should contain viewing key") + } else { + shielded + .compute_exchanged_balance::<_, IO>( + client, + &viewing_key, + epoch, + ) + .await + .unwrap() + .expect("context should contain viewing key") + }; + + for ((_, address), val) in balance.iter() { + if !val.is_zero() { + found_any = true; + } + let formatted = format_denominated_amount::<_, IO>( client, - &viewing_key, - epoch, + address, + (*val).into(), ) - .await - .unwrap() - .expect("context should contain viewing key") - }; - - for ((_, address), val) in balance.iter() { - if !val.is_zero() { - found_any = true; + .await; + display_line!(IO, " {}, owned by {}", formatted, fvk); } - let formatted = format_denominated_amount::<_, IO>( - client, - address, - (*val).into(), - ) - .await; - display_line!(IO, " {}, owned by {}", formatted, fvk); } - } - if !found_any { - display_line!( - IO, - "No shielded {} balance found for any wallet key", - token_alias, - ); + if !found_any { + display_line!( + IO, + "No shielded {} balance found for any wallet key", + token_alias, + ); + } } } // Here the user wants to know all possible token balances for a key @@ -1053,7 +1100,7 @@ pub async fn print_decoded_balance< display_line!( IO, "{} : {}", - wallet.lookup_alias(token_addr), + lookup_token_alias(client, wallet, token_addr, &masp()).await, format_denominated_amount::<_, IO>( client, token_addr, diff --git a/core/src/ledger/ibc/context/common.rs b/core/src/ledger/ibc/context/common.rs index b5f0326767..c47b78a38c 100644 --- a/core/src/ledger/ibc/context/common.rs +++ b/core/src/ledger/ibc/context/common.rs @@ -358,14 +358,14 @@ pub trait IbcCommonContext: IbcStorageContext { }) } - /// Write the IBC denom + /// Write the IBC denom. The given address could be a non-Namada token. fn store_ibc_denom( &mut self, - receiver: &Address, + addr: impl AsRef, trace_hash: impl AsRef, denom: impl AsRef, ) -> Result<(), ContextError> { - let key = storage::ibc_denom_key(receiver, trace_hash.as_ref()); + let key = storage::ibc_denom_key(addr, trace_hash.as_ref()); let has_key = self.has_key(&key).map_err(|_| { ContextError::ChannelError(ChannelError::Other { description: format!( diff --git a/core/src/ledger/ibc/mod.rs b/core/src/ledger/ibc/mod.rs index 4cc86277f8..4900312a7f 100644 --- a/core/src/ledger/ibc/mod.rs +++ b/core/src/ledger/ibc/mod.rs @@ -28,7 +28,9 @@ use crate::ibc::core::{execute, validate, MsgEnvelope, RouterError}; use crate::ibc_proto::google::protobuf::Any; use crate::types::address::Address; use crate::types::chain::ChainId; -use crate::types::ibc::{EVENT_TYPE_DENOM_TRACE, EVENT_TYPE_PACKET}; +use crate::types::ibc::{ + split_ibc_denom, EVENT_TYPE_DENOM_TRACE, EVENT_TYPE_PACKET, +}; #[allow(missing_docs)] #[derive(Error, Debug)] @@ -147,13 +149,28 @@ where // denomination is also set for the minting. self.ctx .borrow_mut() - .store_ibc_denom(&receiver, trace_hash, &ibc_denom) + .store_ibc_denom( + &receiver.to_string(), + &trace_hash, + &ibc_denom, + ) .map_err(|e| { Error::Denom(format!( "Writing the IBC denom failed: {}", e )) })?; + if let Some((_, base_token)) = split_ibc_denom(&ibc_denom) { + self.ctx + .borrow_mut() + .store_ibc_denom(base_token, trace_hash, &ibc_denom) + .map_err(|e| { + Error::Denom(format!( + "Writing the IBC denom failed: {}", + e + )) + })?; + } let token = storage::ibc_token(ibc_denom); self.ctx.borrow_mut().store_token_denom(&token).map_err( |e| { diff --git a/core/src/ledger/ibc/storage.rs b/core/src/ledger/ibc/storage.rs index 349c4b6ef3..717991b9f1 100644 --- a/core/src/ledger/ibc/storage.rs +++ b/core/src/ledger/ibc/storage.rs @@ -367,24 +367,30 @@ pub fn port_id(key: &Key) -> Result { } } -/// The storage key prefix to get the denom name with the hashed IBC denom -pub fn ibc_denom_key_prefix(owner: Option<&Address>) -> Key { +/// The storage key prefix to get the denom name with the hashed IBC denom. The +/// address is given as string because the given address could be non-Namada +/// token. +pub fn ibc_denom_key_prefix(addr: Option) -> Key { let prefix = Key::from(Address::Internal(InternalAddress::Ibc).to_db_key()) .push(&DENOM.to_string().to_db_key()) .expect("Cannot obtain a storage key"); - if let Some(owner) = owner { + if let Some(addr) = addr { prefix - .push(&owner.to_db_key()) + .push(&addr.to_db_key()) .expect("Cannot obtain a storage key") } else { prefix } } -/// The storage key to get the denom name with the hashed IBC denom -pub fn ibc_denom_key(owner: &Address, token_hash: impl AsRef) -> Key { - ibc_denom_key_prefix(Some(owner)) +/// The storage key to get the denom name with the hashed IBC denom. The address +/// is given as string because the given address could be non-Namada token. +pub fn ibc_denom_key( + addr: impl AsRef, + token_hash: impl AsRef, +) -> Key { + ibc_denom_key_prefix(Some(addr.as_ref().to_string())) .push(&token_hash.as_ref().to_string().to_db_key()) .expect("Cannot obtain a storage key") } @@ -409,12 +415,12 @@ pub fn is_ibc_key(key: &Key) -> bool { } /// Returns the owner and the token hash if the given key is the denom key -pub fn is_ibc_denom_key(key: &Key) -> Option<(Address, String)> { +pub fn is_ibc_denom_key(key: &Key) -> Option<(String, String)> { match &key.segments[..] { [ DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(prefix), - DbKeySeg::AddressSeg(owner), + DbKeySeg::StringSeg(owner), DbKeySeg::StringSeg(hash), ] => { if addr == &Address::Internal(InternalAddress::Ibc) diff --git a/core/src/types/ibc.rs b/core/src/types/ibc.rs index 32a93218eb..4ee504fb4a 100644 --- a/core/src/types/ibc.rs +++ b/core/src/types/ibc.rs @@ -53,10 +53,12 @@ impl std::fmt::Display for IbcEvent { #[cfg(any(feature = "abciplus", feature = "abcipp"))] mod ibc_rs_conversion { use std::collections::HashMap; + use std::str::FromStr; use thiserror::Error; use super::IbcEvent; + use crate::ibc::applications::transfer::{PrefixedDenom, TracePath}; use crate::ibc::core::events::{ Error as IbcEventError, IbcEvent as RawIbcEvent, }; @@ -89,6 +91,19 @@ mod ibc_rs_conversion { }) } } + + /// Returns the trace path and the token string if the denom is an IBC + /// denom. + pub fn split_ibc_denom( + denom: impl AsRef, + ) -> Option<(TracePath, String)> { + let prefixed_denom = PrefixedDenom::from_str(denom.as_ref()).ok()?; + // The base token isn't decoded because it could be non Namada token + Some(( + prefixed_denom.trace_path, + prefixed_denom.base_denom.to_string(), + )) + } } #[cfg(any(feature = "abciplus", feature = "abcipp"))] diff --git a/shared/src/ledger/native_vp/ibc/mod.rs b/shared/src/ledger/native_vp/ibc/mod.rs index c50ee89600..5e48a8613f 100644 --- a/shared/src/ledger/native_vp/ibc/mod.rs +++ b/shared/src/ledger/native_vp/ibc/mod.rs @@ -2191,7 +2191,14 @@ mod tests { packet.chan_id_on_b.clone(), )); let trace_hash = calc_hash(coin.denom.to_string()); - let denom_key = ibc_denom_key(&receiver, &trace_hash); + let denom_key = ibc_denom_key(receiver.to_string(), &trace_hash); + let bytes = coin.denom.to_string().try_to_vec().unwrap(); + wl_storage + .write_log + .write(&denom_key, bytes) + .expect("write failed"); + keys_changed.insert(denom_key); + let denom_key = ibc_denom_key(nam().to_string(), &trace_hash); let bytes = coin.denom.to_string().try_to_vec().unwrap(); wl_storage .write_log diff --git a/shared/src/sdk/args.rs b/shared/src/sdk/args.rs index 3c31b17ba7..af65abcad2 100644 --- a/shared/src/sdk/args.rs +++ b/shared/src/sdk/args.rs @@ -396,8 +396,6 @@ pub struct QueryBalance { pub owner: Option, /// Address of a token pub token: Option, - /// Transferred token's trace path - pub trace_path: Option, /// Whether not to convert balances pub no_conversions: bool, } diff --git a/tests/src/e2e/ibc_tests.rs b/tests/src/e2e/ibc_tests.rs index a539efaae8..7e717b4a54 100644 --- a/tests/src/e2e/ibc_tests.rs +++ b/tests/src/e2e/ibc_tests.rs @@ -1035,7 +1035,7 @@ fn transfer( if let Some(trace_path) = trace_path { tx_args.push("--trace-path"); - tx_args.push(&trace_path.clone()); + tx_args.push(trace_path.clone()); } let timeout = timeout_sec.unwrap_or_default().as_secs().to_string(); @@ -1240,15 +1240,7 @@ fn check_balances( let trace_path = format!("{}/{}", &dest_port_id, &dest_channel_id); let rpc_b = get_actor_rpc(test_b, &Who::Validator(0)); let query_args = vec![ - "balance", - "--owner", - BERTHA, - "--token", - NAM, - "--trace-path", - &trace_path, - "--node", - &rpc_b, + "balance", "--owner", BERTHA, "--token", NAM, "--node", &rpc_b, ]; let expected = format!("{}: 100000", format!("{}/nam", trace_path)); let mut client = run!(test_b, Bin::Client, query_args, Some(40))?; @@ -1268,34 +1260,16 @@ fn check_balances_after_non_ibc( // Check the source let rpc = get_actor_rpc(test, &Who::Validator(0)); - let query_args = vec![ - "balance", - "--owner", - BERTHA, - "--token", - NAM, - "--trace-path", - &trace_path, - "--node", - &rpc, - ]; + let query_args = + vec!["balance", "--owner", BERTHA, "--token", NAM, "--node", &rpc]; let expected = format!("{}: 50000", format!("{}/nam", trace_path)); let mut client = run!(test, Bin::Client, query_args, Some(40))?; client.exp_string(&expected)?; client.assert_success(); // Check the traget - let query_args = vec![ - "balance", - "--owner", - ALBERT, - "--token", - NAM, - "--trace-path", - &trace_path, - "--node", - &rpc, - ]; + let query_args = + vec!["balance", "--owner", ALBERT, "--token", NAM, "--node", &rpc]; let expected = format!("{}: 50000", format!("{}/nam", trace_path)); let mut client = run!(test, Bin::Client, query_args, Some(40))?; client.exp_string(&expected)?; @@ -1330,15 +1304,7 @@ fn check_balances_after_back( let trace_path = format!("{}/{}", dest_port_id, dest_channel_id); let rpc_b = get_actor_rpc(test_b, &Who::Validator(0)); let query_args = vec![ - "balance", - "--owner", - BERTHA, - "--token", - NAM, - "--trace-path", - &trace_path, - "--node", - &rpc_b, + "balance", "--owner", BERTHA, "--token", NAM, "--node", &rpc_b, ]; let expected = format!("{}: 0", format!("{}/nam", trace_path)); let mut client = run!(test_b, Bin::Client, query_args, Some(40))?; From 1b79599ea4f281f22899cbcca7fcc30693ebca77 Mon Sep 17 00:00:00 2001 From: yito88 Date: Thu, 28 Sep 2023 11:02:41 +0200 Subject: [PATCH 3/4] add changelog --- .../improvements/1946-ibc-balance-query.md | 2 + apps/src/lib/client/rpc.rs | 175 +++++++++--------- tests/src/e2e/ibc_tests.rs | 8 +- 3 files changed, 94 insertions(+), 91 deletions(-) create mode 100644 .changelog/unreleased/improvements/1946-ibc-balance-query.md diff --git a/.changelog/unreleased/improvements/1946-ibc-balance-query.md b/.changelog/unreleased/improvements/1946-ibc-balance-query.md new file mode 100644 index 0000000000..1f1093caa0 --- /dev/null +++ b/.changelog/unreleased/improvements/1946-ibc-balance-query.md @@ -0,0 +1,2 @@ +- Query also IBC token balances + ([\#1946](https://github.com/anoma/namada/issues/1946)) \ No newline at end of file diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 27073fe2d1..d3363d17a7 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -445,93 +445,6 @@ pub async fn query_transparent_balance< } } -async fn lookup_token_alias( - client: &C, - wallet: &Wallet, - token: &Address, - owner: &Address, -) -> String { - if let Address::Internal(InternalAddress::IbcToken(trace_hash)) = token { - let ibc_denom_key = ibc_denom_key(owner.to_string(), trace_hash); - match query_storage_value::(client, &ibc_denom_key).await { - Ok(ibc_denom) => get_ibc_denom_alias(wallet, ibc_denom), - Err(_) => token.to_string(), - } - } else { - wallet.lookup_alias(token) - } -} - -/// Returns pairs of token alias and token address -async fn query_tokens( - client: &C, - wallet: &Wallet, - base_token: Option<&Address>, - owner: Option<&Address>, -) -> BTreeMap { - // Base tokens - let mut tokens = match base_token { - Some(base_token) => { - let mut map = BTreeMap::new(); - map.insert(wallet.lookup_alias(base_token), base_token.clone()); - map - } - None => wallet.tokens_with_aliases(), - }; - - let prefixes = match (base_token, owner) { - (Some(base_token), Some(owner)) => vec![ - ibc_denom_key_prefix(Some(base_token.to_string())), - ibc_denom_key_prefix(Some(owner.to_string())), - ], - (Some(base_token), None) => { - vec![ibc_denom_key_prefix(Some(base_token.to_string()))] - } - (None, Some(_)) => { - // Check all IBC denoms because the owner might not know IBC token - // transfers in the same chain - vec![ibc_denom_key_prefix(None)] - } - (None, None) => vec![ibc_denom_key_prefix(None)], - }; - - for prefix in prefixes { - let ibc_denoms = - query_storage_prefix::(client, &prefix).await; - if let Some(ibc_denoms) = ibc_denoms { - for (key, ibc_denom) in ibc_denoms { - if let Some((_, hash)) = is_ibc_denom_key(&key) { - let ibc_denom_alias = - get_ibc_denom_alias(wallet, ibc_denom); - let ibc_token = - Address::Internal(InternalAddress::IbcToken(hash)); - tokens.insert(ibc_denom_alias, ibc_token); - } - } - } - } - tokens -} - -fn get_ibc_denom_alias( - wallet: &Wallet, - ibc_denom: impl AsRef, -) -> String { - split_ibc_denom(&ibc_denom) - .map(|(trace_path, base_token)| { - let base_token_alias = match Address::decode(&base_token) { - Ok(base_token) => wallet.lookup_alias(&base_token), - Err(_) => base_token, - }; - if trace_path.is_empty() { - base_token_alias - } else { - format!("{}/{}", trace_path, base_token_alias) - } - }) - .unwrap_or(ibc_denom.as_ref().to_string()) -} - /// Query the token pinned balance(s) pub async fn query_pinned_balance< C: namada::ledger::queries::Client + Sync, @@ -776,6 +689,94 @@ async fn print_balances( } } +async fn lookup_token_alias( + client: &C, + wallet: &Wallet, + token: &Address, + owner: &Address, +) -> String { + if let Address::Internal(InternalAddress::IbcToken(trace_hash)) = token { + let ibc_denom_key = ibc_denom_key(owner.to_string(), trace_hash); + match query_storage_value::(client, &ibc_denom_key).await { + Ok(ibc_denom) => get_ibc_denom_alias(wallet, ibc_denom), + Err(_) => token.to_string(), + } + } else { + wallet.lookup_alias(token) + } +} + +/// Returns pairs of token alias and token address +async fn query_tokens( + client: &C, + wallet: &Wallet, + base_token: Option<&Address>, + owner: Option<&Address>, +) -> BTreeMap { + // Base tokens + let mut tokens = match base_token { + Some(base_token) => { + let mut map = BTreeMap::new(); + map.insert(wallet.lookup_alias(base_token), base_token.clone()); + map + } + None => wallet.tokens_with_aliases(), + }; + + let prefixes = match (base_token, owner) { + (Some(base_token), Some(owner)) => vec![ + ibc_denom_key_prefix(Some(base_token.to_string())), + ibc_denom_key_prefix(Some(owner.to_string())), + ], + (Some(base_token), None) => { + vec![ibc_denom_key_prefix(Some(base_token.to_string()))] + } + (None, Some(_)) => { + // Check all IBC denoms because the owner might not know IBC token + // transfers in the same chain + vec![ibc_denom_key_prefix(None)] + } + (None, None) => vec![ibc_denom_key_prefix(None)], + }; + + for prefix in prefixes { + let ibc_denoms = + query_storage_prefix::(client, &prefix).await; + if let Some(ibc_denoms) = ibc_denoms { + for (key, ibc_denom) in ibc_denoms { + if let Some((_, hash)) = is_ibc_denom_key(&key) { + let ibc_denom_alias = + get_ibc_denom_alias(wallet, ibc_denom); + let ibc_token = + Address::Internal(InternalAddress::IbcToken(hash)); + tokens.insert(ibc_denom_alias, ibc_token); + } + } + } + } + tokens +} + +fn get_ibc_denom_alias( + wallet: &Wallet, + ibc_denom: impl AsRef, +) -> String { + split_ibc_denom(&ibc_denom) + .map(|(trace_path, base_token)| { + let base_token_alias = match Address::decode(&base_token) { + Ok(base_token) => wallet.lookup_alias(&base_token), + Err(_) => base_token, + }; + if trace_path.is_empty() { + base_token_alias + } else { + format!("{}/{}", trace_path, base_token_alias) + } + }) + .unwrap_or(ibc_denom.as_ref().to_string()) +} + + /// Query Proposals pub async fn query_proposal< C: namada::ledger::queries::Client + Sync, diff --git a/tests/src/e2e/ibc_tests.rs b/tests/src/e2e/ibc_tests.rs index 7e717b4a54..27261a233d 100644 --- a/tests/src/e2e/ibc_tests.rs +++ b/tests/src/e2e/ibc_tests.rs @@ -1242,7 +1242,7 @@ fn check_balances( let query_args = vec![ "balance", "--owner", BERTHA, "--token", NAM, "--node", &rpc_b, ]; - let expected = format!("{}: 100000", format!("{}/nam", trace_path)); + let expected = format!("{}/nam: 100000", trace_path); let mut client = run!(test_b, Bin::Client, query_args, Some(40))?; client.exp_string(&expected)?; client.assert_success(); @@ -1262,7 +1262,7 @@ fn check_balances_after_non_ibc( let rpc = get_actor_rpc(test, &Who::Validator(0)); let query_args = vec!["balance", "--owner", BERTHA, "--token", NAM, "--node", &rpc]; - let expected = format!("{}: 50000", format!("{}/nam", trace_path)); + let expected = format!("{}/nam: 50000", trace_path); let mut client = run!(test, Bin::Client, query_args, Some(40))?; client.exp_string(&expected)?; client.assert_success(); @@ -1270,7 +1270,7 @@ fn check_balances_after_non_ibc( // Check the traget let query_args = vec!["balance", "--owner", ALBERT, "--token", NAM, "--node", &rpc]; - let expected = format!("{}: 50000", format!("{}/nam", trace_path)); + let expected = format!("{}/nam: 50000", trace_path); let mut client = run!(test, Bin::Client, query_args, Some(40))?; client.exp_string(&expected)?; client.assert_success(); @@ -1306,7 +1306,7 @@ fn check_balances_after_back( let query_args = vec![ "balance", "--owner", BERTHA, "--token", NAM, "--node", &rpc_b, ]; - let expected = format!("{}: 0", format!("{}/nam", trace_path)); + let expected = format!("{}/nam: 0", trace_path); let mut client = run!(test_b, Bin::Client, query_args, Some(40))?; client.exp_string(&expected)?; client.assert_success(); From b874ffaf17be6e61a4af43769ee1733ada3fbce1 Mon Sep 17 00:00:00 2001 From: yito88 Date: Mon, 2 Oct 2023 17:23:42 +0200 Subject: [PATCH 4/4] remove trace-path --- apps/src/lib/cli.rs | 10 --------- apps/src/lib/cli/context.rs | 16 +++++++++++++ apps/src/lib/client/rpc.rs | 7 +++--- benches/lib.rs | 1 - core/src/ledger/ibc/mod.rs | 4 ++-- core/src/types/ibc.rs | 7 +++--- shared/src/sdk/args.rs | 5 ----- shared/src/sdk/rpc.rs | 45 ++++++++++++++++++++++++++++++++++++- shared/src/sdk/signing.rs | 1 - shared/src/sdk/tx.rs | 39 +++++++++++++------------------- tests/src/e2e/ibc_tests.rs | 24 ++++---------------- 11 files changed, 89 insertions(+), 70 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 79db0b3824..135ff1e3c5 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -2516,7 +2516,6 @@ pub mod args { use std::path::PathBuf; use std::str::FromStr; - use namada::ibc::applications::transfer::TracePath; use namada::ibc::core::ics24_host::identifier::{ChannelId, PortId}; pub use namada::sdk::args::*; use namada::types::address::Address; @@ -2726,7 +2725,6 @@ pub mod args { pub const TM_ADDRESS: Arg = arg("tm-address"); pub const TOKEN_OPT: ArgOpt = TOKEN.opt(); pub const TOKEN: Arg = arg("token"); - pub const TRACE_PATH: ArgOpt = arg_opt("trace-path"); pub const TRANSFER_SOURCE: Arg = arg("source"); pub const TRANSFER_TARGET: Arg = arg("target"); pub const TX_HASH: Arg = arg("tx-hash"); @@ -3506,7 +3504,6 @@ pub mod args { source: ctx.get_cached(&self.source), target: ctx.get(&self.target), token: ctx.get(&self.token), - trace_path: self.trace_path, amount: self.amount, native_token: ctx.native_token.clone(), tx_code_path: self.tx_code_path.to_path_buf(), @@ -3520,7 +3517,6 @@ pub mod args { let source = TRANSFER_SOURCE.parse(matches); let target = TRANSFER_TARGET.parse(matches); let token = TOKEN.parse(matches); - let trace_path = TRACE_PATH.parse(matches); let amount = InputAmount::Unvalidated(AMOUNT.parse(matches)); let tx_code_path = PathBuf::from(TX_TRANSFER_WASM); Self { @@ -3528,7 +3524,6 @@ pub mod args { source, target, token, - trace_path, amount, native_token: (), tx_code_path, @@ -3546,7 +3541,6 @@ pub mod args { to produce the signature.", )) .arg(TOKEN.def().help("The transfer token.")) - .arg(TRACE_PATH.def().help("The transfer token's trace path.")) .arg(AMOUNT.def().help("The amount to transfer in decimal.")) } } @@ -3558,7 +3552,6 @@ pub mod args { source: ctx.get(&self.source), receiver: self.receiver, token: ctx.get(&self.token), - trace_path: self.trace_path, amount: self.amount, port_id: self.port_id, channel_id: self.channel_id, @@ -3576,7 +3569,6 @@ pub mod args { let source = SOURCE.parse(matches); let receiver = RECEIVER.parse(matches); let token = TOKEN.parse(matches); - let trace_path = TRACE_PATH.parse(matches); let amount = InputAmount::Unvalidated(AMOUNT.parse(matches)); let port_id = PORT_ID.parse(matches); let channel_id = CHANNEL_ID.parse(matches); @@ -3589,7 +3581,6 @@ pub mod args { source, receiver, token, - trace_path, amount, port_id, channel_id, @@ -3610,7 +3601,6 @@ pub mod args { "The receiver address on the destination chain as string.", )) .arg(TOKEN.def().help("The transfer token.")) - .arg(TRACE_PATH.def().help("The transfer token's trace path.")) .arg(AMOUNT.def().help("The amount to transfer in decimal.")) .arg(PORT_ID.def().help("The port ID.")) .arg(CHANNEL_ID.def().help("The channel ID.")) diff --git a/apps/src/lib/cli/context.rs b/apps/src/lib/cli/context.rs index 4aac8b1026..520e999189 100644 --- a/apps/src/lib/cli/context.rs +++ b/apps/src/lib/cli/context.rs @@ -6,11 +6,13 @@ use std::path::{Path, PathBuf}; use std::str::FromStr; use color_eyre::eyre::Result; +use namada::ledger::ibc::storage::ibc_token; use namada::sdk::masp::ShieldedContext; use namada::sdk::wallet::Wallet; use namada::types::address::{Address, InternalAddress}; use namada::types::chain::ChainId; use namada::types::ethereum_events::EthAddress; +use namada::types::ibc::is_ibc_denom; use namada::types::io::Io; use namada::types::key::*; use namada::types::masp::*; @@ -367,6 +369,20 @@ impl ArgFromContext for Address { }) .unwrap_or(Err(Skip)) }) + // An IBC token + .or_else(|_| { + is_ibc_denom(raw) + .map(|(trace_path, base_denom)| { + let base_token = ctx + .wallet + .find_address(&base_denom) + .map(|addr| addr.to_string()) + .unwrap_or(base_denom); + let ibc_denom = format!("{trace_path}/{base_token}"); + ibc_token(ibc_denom) + }) + .ok_or(Skip) + }) // Or it can be an alias that may be found in the wallet .or_else(|_| ctx.wallet.find_address(raw).cloned().ok_or(Skip)) .map_err(|_| format!("Unknown address {raw}")) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index d3363d17a7..cfd13b42e2 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -50,7 +50,7 @@ use namada::sdk::wallet::{AddressVpType, Wallet}; use namada::types::address::{masp, Address, InternalAddress}; use namada::types::control_flow::ProceedOrElse; use namada::types::hash::Hash; -use namada::types::ibc::split_ibc_denom; +use namada::types::ibc::is_ibc_denom; use namada::types::io::Io; use namada::types::key::*; use namada::types::masp::{BalanceOwner, ExtendedViewingKey, PaymentAddress}; @@ -385,7 +385,7 @@ pub async fn query_transparent_balance< display_line!(IO, "{}: {}", token_alias, balance); } Err(e) => { - display_line!(IO, "Eror in querying: {e}"); + display_line!(IO, "Querying error: {e}"); display_line!( IO, "No {} balance found for {}", @@ -761,7 +761,7 @@ fn get_ibc_denom_alias( wallet: &Wallet, ibc_denom: impl AsRef, ) -> String { - split_ibc_denom(&ibc_denom) + is_ibc_denom(&ibc_denom) .map(|(trace_path, base_token)| { let base_token_alias = match Address::decode(&base_token) { Ok(base_token) => wallet.lookup_alias(&base_token), @@ -776,7 +776,6 @@ fn get_ibc_denom_alias( .unwrap_or(ibc_denom.as_ref().to_string()) } - /// Query Proposals pub async fn query_proposal< C: namada::ledger::queries::Client + Sync, diff --git a/benches/lib.rs b/benches/lib.rs index 30765d8beb..47645abdf4 100644 --- a/benches/lib.rs +++ b/benches/lib.rs @@ -783,7 +783,6 @@ impl BenchShieldedCtx { source: source.clone(), target: target.clone(), token: address::nam(), - trace_path: None, amount: InputAmount::Validated(DenominatedAmount { amount, denom: 0.into(), diff --git a/core/src/ledger/ibc/mod.rs b/core/src/ledger/ibc/mod.rs index 4900312a7f..40382282cd 100644 --- a/core/src/ledger/ibc/mod.rs +++ b/core/src/ledger/ibc/mod.rs @@ -29,7 +29,7 @@ use crate::ibc_proto::google::protobuf::Any; use crate::types::address::Address; use crate::types::chain::ChainId; use crate::types::ibc::{ - split_ibc_denom, EVENT_TYPE_DENOM_TRACE, EVENT_TYPE_PACKET, + is_ibc_denom, EVENT_TYPE_DENOM_TRACE, EVENT_TYPE_PACKET, }; #[allow(missing_docs)] @@ -160,7 +160,7 @@ where e )) })?; - if let Some((_, base_token)) = split_ibc_denom(&ibc_denom) { + if let Some((_, base_token)) = is_ibc_denom(&ibc_denom) { self.ctx .borrow_mut() .store_ibc_denom(base_token, trace_hash, &ibc_denom) diff --git a/core/src/types/ibc.rs b/core/src/types/ibc.rs index 4ee504fb4a..e7cb5f745f 100644 --- a/core/src/types/ibc.rs +++ b/core/src/types/ibc.rs @@ -94,10 +94,11 @@ mod ibc_rs_conversion { /// Returns the trace path and the token string if the denom is an IBC /// denom. - pub fn split_ibc_denom( - denom: impl AsRef, - ) -> Option<(TracePath, String)> { + pub fn is_ibc_denom(denom: impl AsRef) -> Option<(TracePath, String)> { let prefixed_denom = PrefixedDenom::from_str(denom.as_ref()).ok()?; + if prefixed_denom.trace_path.is_empty() { + return None; + } // The base token isn't decoded because it could be non Namada token Some(( prefixed_denom.trace_path, diff --git a/shared/src/sdk/args.rs b/shared/src/sdk/args.rs index af65abcad2..0b87317530 100644 --- a/shared/src/sdk/args.rs +++ b/shared/src/sdk/args.rs @@ -11,7 +11,6 @@ use namada_core::types::time::DateTimeUtc; use serde::{Deserialize, Serialize}; use zeroize::Zeroizing; -use crate::ibc::applications::transfer::TracePath; use crate::ibc::core::ics24_host::identifier::{ChannelId, PortId}; use crate::types::address::Address; use crate::types::keccak::KeccakHash; @@ -136,8 +135,6 @@ pub struct TxTransfer { pub target: C::TransferTarget, /// Transferred token address pub token: C::Address, - /// Transferred token's trace path - pub trace_path: Option, /// Transferred token amount pub amount: InputAmount, /// Native token address @@ -168,8 +165,6 @@ pub struct TxIbcTransfer { pub receiver: String, /// Transferred token address pub token: C::Address, - /// Transferred token's trace path - pub trace_path: Option, /// Transferred token amount pub amount: InputAmount, /// Port ID diff --git a/shared/src/sdk/rpc.rs b/shared/src/sdk/rpc.rs index 58609bed42..d98e0b61ec 100644 --- a/shared/src/sdk/rpc.rs +++ b/shared/src/sdk/rpc.rs @@ -13,7 +13,7 @@ use namada_core::ledger::governance::storage::proposal::StorageProposal; use namada_core::ledger::governance::utils::Vote; use namada_core::ledger::storage::LastBlock; use namada_core::types::account::Account; -use namada_core::types::address::Address; +use namada_core::types::address::{Address, InternalAddress}; use namada_core::types::storage::Key; use namada_core::types::token::{ Amount, DenominatedAmount, Denomination, MaspDenom, @@ -25,6 +25,9 @@ use namada_proof_of_stake::types::{ use serde::Serialize; use crate::ledger::events::Event; +use crate::ledger::ibc::storage::{ + ibc_denom_key, ibc_denom_key_prefix, is_ibc_denom_key, +}; use crate::ledger::queries::vp::pos::EnrichedBondsAndUnbondsDetails; use crate::ledger::queries::RPC; use crate::proto::Tx; @@ -1088,3 +1091,43 @@ pub async fn format_denominated_amount< }); DenominatedAmount { amount, denom }.to_string() } + +/// Look up the IBC denomination from a IbcToken. +pub async fn query_ibc_denom< + C: crate::ledger::queries::Client + Sync, + IO: Io, +>( + client: &C, + token: &Address, + owner: Option<&Address>, +) -> String { + let hash = match token { + Address::Internal(InternalAddress::IbcToken(hash)) => hash, + _ => return token.to_string(), + }; + + if let Some(owner) = owner { + let ibc_denom_key = ibc_denom_key(owner.to_string(), hash); + if let Ok(ibc_denom) = + query_storage_value::(client, &ibc_denom_key).await + { + return ibc_denom; + } + } + + // No owner is specified or the owner doesn't have the token + let ibc_denom_prefix = ibc_denom_key_prefix(None); + if let Ok(Some(ibc_denoms)) = + query_storage_prefix::(client, &ibc_denom_prefix).await + { + for (key, ibc_denom) in ibc_denoms { + if let Some((_, token_hash)) = is_ibc_denom_key(&key) { + if token_hash == *hash { + return ibc_denom; + } + } + } + } + + token.to_string() +} diff --git a/shared/src/sdk/signing.rs b/shared/src/sdk/signing.rs index 9f1d541faf..042be03a63 100644 --- a/shared/src/sdk/signing.rs +++ b/shared/src/sdk/signing.rs @@ -426,7 +426,6 @@ pub async fn wrap_tx< fee_payer_address.clone(), ), token: args.fee_token.clone(), - trace_path: None, amount: args::InputAmount::Validated(DenominatedAmount { // NOTE: must unshield the total fee amount, not the // diff, because the ledger evaluates the transaction in diff --git a/shared/src/sdk/tx.rs b/shared/src/sdk/tx.rs index 5b477de47e..e95ab58fe1 100644 --- a/shared/src/sdk/tx.rs +++ b/shared/src/sdk/tx.rs @@ -36,12 +36,11 @@ use namada_proof_of_stake::types::{CommissionPair, ValidatorState}; use crate::ibc::applications::transfer::msgs::transfer::MsgTransfer; use crate::ibc::applications::transfer::packet::PacketData; -use crate::ibc::applications::transfer::{PrefixedCoin, PrefixedDenom}; +use crate::ibc::applications::transfer::PrefixedCoin; use crate::ibc::core::ics04_channel::timeout::TimeoutHeight; use crate::ibc::core::timestamp::Timestamp as IbcTimestamp; use crate::ibc::core::Msg; use crate::ibc::Height as IbcHeight; -use crate::ledger::ibc::storage::ibc_token; use crate::proto::{MaspBuilder, Tx}; use crate::sdk::args::{self, InputAmount}; use crate::sdk::error::{EncodingError, Error, QueryError, Result, TxError}; @@ -1414,16 +1413,10 @@ pub async fn build_ibc_transfer< .await .map_err(|e| Error::from(QueryError::Wasm(e.to_string())))?; - let ibc_denom = PrefixedDenom { - trace_path: args.trace_path.unwrap_or_default(), - base_denom: args - .token - .to_string() - .parse() - .expect("Conversion from the token shouldn't fail"), - }; + let ibc_denom = + rpc::query_ibc_denom::<_, IO>(client, &args.token, Some(&source)).await; let token = PrefixedCoin { - denom: ibc_denom, + denom: ibc_denom.parse().expect("Invalid IBC denom"), // Set the IBC amount as an integer amount: validated_amount.into(), }; @@ -1666,11 +1659,6 @@ pub async fn build_transfer< ) -> Result<(Tx, Option)> { let source = args.source.effective_address(); let target = args.target.effective_address(); - let token = if let Some(trace_path) = &args.trace_path { - ibc_token(format!("{}/{}", trace_path.clone(), args.token)) - } else { - args.token.clone() - }; // Check that the source address exists on chain source_exists_or_err::<_, IO>(source.clone(), args.tx.force, client) @@ -1679,16 +1667,20 @@ pub async fn build_transfer< target_exists_or_err::<_, IO>(target.clone(), args.tx.force, client) .await?; // Check source balance - let balance_key = token::balance_key(&token, &source); + let balance_key = token::balance_key(&args.token, &source); // validate the amount given - let validated_amount = - validate_amount::<_, IO>(client, args.amount, &token, args.tx.force) - .await?; + let validated_amount = validate_amount::<_, IO>( + client, + args.amount, + &args.token, + args.tx.force, + ) + .await?; args.amount = InputAmount::Validated(validated_amount); let post_balance = check_balance_too_low_err::( - &token, + &args.token, &source, validated_amount.amount, balance_key, @@ -1699,7 +1691,7 @@ pub async fn build_transfer< let tx_source_balance = Some(TxSourcePostBalance { post_balance, source: source.clone(), - token: token.clone(), + token: args.token.clone(), }); let masp_addr = masp(); @@ -1712,7 +1704,7 @@ pub async fn build_transfer< // TODO Refactor me, we shouldn't rely on any specific token here. (token::Amount::default(), args.native_token.clone()) } else { - (validated_amount.amount, token) + (validated_amount.amount, args.token.clone()) }; // Determine whether to pin this transaction to a storage key let key = match &args.target { @@ -2215,6 +2207,7 @@ fn validate_untrusted_code_err( Ok(()) } } + async fn query_wasm_code_hash_buf< C: crate::ledger::queries::Client + Sync, IO: Io, diff --git a/tests/src/e2e/ibc_tests.rs b/tests/src/e2e/ibc_tests.rs index 27261a233d..3cd2ba48a3 100644 --- a/tests/src/e2e/ibc_tests.rs +++ b/tests/src/e2e/ibc_tests.rs @@ -637,7 +637,6 @@ fn transfer_token( ALBERT, &receiver, NAM, - None, "100000", ALBERT_KEY, port_id_a, @@ -707,7 +706,6 @@ fn try_invalid_transfers( ALBERT, &receiver, NAM, - None, "10.1", ALBERT_KEY, port_id_a, @@ -723,7 +721,6 @@ fn try_invalid_transfers( ALBERT, &receiver, NAM, - None, "10", ALBERT_KEY, &"port".parse().unwrap(), @@ -739,7 +736,6 @@ fn try_invalid_transfers( ALBERT, &receiver, NAM, - None, "10", ALBERT_KEY, port_id_a, @@ -757,10 +753,8 @@ fn transfer_received_token( channel_id: &ChannelId, test: &Test, ) -> Result<()> { - // token received via the port and channel - let trace_path = format!("{port_id}/{channel_id}"); - let rpc = get_actor_rpc(test, &Who::Validator(0)); + let ibc_denom = format!("{port_id}/{channel_id}/nam"); let amount = Amount::native_whole(50000).to_string_native(); let tx_args = [ "transfer", @@ -769,9 +763,7 @@ fn transfer_received_token( "--target", ALBERT, "--token", - NAM, - "--trace-path", - &trace_path, + &ibc_denom, "--amount", &amount, "--gas-token", @@ -798,14 +790,13 @@ fn transfer_back( let receiver = find_address(test_a, ALBERT)?; // Chain A was the source for the sent token - let trace_path = format!("{}/{}", port_id_b, channel_id_b); + let ibc_denom = format!("{port_id_b}/{channel_id_b}/nam"); // Send a token from Chain B let height = transfer( test_b, BERTHA, &receiver, - NAM, - Some(&trace_path), + ibc_denom, "50000", BERTHA_KEY, port_id_b, @@ -869,7 +860,6 @@ fn transfer_timeout( ALBERT, &receiver, NAM, - None, "100000", ALBERT_KEY, port_id_a, @@ -999,7 +989,6 @@ fn transfer( sender: impl AsRef, receiver: &Address, token: impl AsRef, - trace_path: Option<&str>, amount: impl AsRef, signer: impl AsRef, port_id: &PortId, @@ -1033,11 +1022,6 @@ fn transfer( &rpc, ]; - if let Some(trace_path) = trace_path { - tx_args.push("--trace-path"); - tx_args.push(trace_path.clone()); - } - let timeout = timeout_sec.unwrap_or_default().as_secs().to_string(); if timeout_sec.is_some() { tx_args.push("--timeout-sec-offset");