Skip to content

Commit

Permalink
ibc token query
Browse files Browse the repository at this point in the history
  • Loading branch information
yito88 committed Mar 9, 2024
1 parent 725e7ea commit 6b4f752
Show file tree
Hide file tree
Showing 7 changed files with 198 additions and 50 deletions.
59 changes: 59 additions & 0 deletions crates/apps/src/lib/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,7 @@ pub mod cmds {
.subcommand(QueryMaspRewardTokens::def().display_order(5))
.subcommand(QueryBlock::def().display_order(5))
.subcommand(QueryBalance::def().display_order(5))
.subcommand(QueryIbcToken::def().display_order(5))
.subcommand(QueryBonds::def().display_order(5))
.subcommand(QueryBondedStake::def().display_order(5))
.subcommand(QuerySlashes::def().display_order(5))
Expand Down Expand Up @@ -325,6 +326,7 @@ pub mod cmds {
Self::parse_with_ctx(matches, QueryMaspRewardTokens);
let query_block = Self::parse_with_ctx(matches, QueryBlock);
let query_balance = Self::parse_with_ctx(matches, QueryBalance);
let query_ibc_token = Self::parse_with_ctx(matches, QueryIbcToken);
let query_bonds = Self::parse_with_ctx(matches, QueryBonds);
let query_bonded_stake =
Self::parse_with_ctx(matches, QueryBondedStake);
Expand Down Expand Up @@ -388,6 +390,7 @@ pub mod cmds {
.or(query_masp_reward_tokens)
.or(query_block)
.or(query_balance)
.or(query_ibc_token)
.or(query_bonds)
.or(query_bonded_stake)
.or(query_slashes)
Expand Down Expand Up @@ -479,6 +482,7 @@ pub mod cmds {
QueryMaspRewardTokens(QueryMaspRewardTokens),
QueryBlock(QueryBlock),
QueryBalance(QueryBalance),
QueryIbcToken(QueryIbcToken),
QueryBonds(QueryBonds),
QueryBondedStake(QueryBondedStake),
QueryCommissionRate(QueryCommissionRate),
Expand Down Expand Up @@ -1670,6 +1674,25 @@ pub mod cmds {
}
}

#[derive(Clone, Debug)]
pub struct QueryIbcToken(pub args::QueryIbcToken<args::CliTypes>);

impl SubCmd for QueryIbcToken {
const CMD: &'static str = "ibc-token";

fn parse(matches: &ArgMatches) -> Option<Self> {
matches.subcommand_matches(Self::CMD).map(|matches| {
QueryIbcToken(args::QueryIbcToken::parse(matches))
})
}

fn def() -> App {
App::new(Self::CMD)
.about("Query IBC token(s).")
.add_args::<args::QueryIbcToken<args::CliTypes>>()
}
}

#[derive(Clone, Debug)]
pub struct QueryBonds(pub args::QueryBonds<args::CliTypes>);

Expand Down Expand Up @@ -3182,6 +3205,7 @@ pub mod args {
pub const TIMEOUT_SEC_OFFSET: ArgOpt<u64> = arg_opt("timeout-sec-offset");
pub const TM_ADDRESS: ArgOpt<String> = arg_opt("tm-address");
pub const TOKEN_OPT: ArgOpt<WalletAddress> = TOKEN.opt();
pub const TOKEN_STR_OPT: ArgOpt<String> = TOKEN_STR.opt();
pub const TOKEN: Arg<WalletAddress> = arg("token");
pub const TOKEN_STR: Arg<String> = arg("token");
pub const TRANSFER_SOURCE: Arg<WalletTransferSource> = arg("source");
Expand Down Expand Up @@ -5291,6 +5315,41 @@ pub mod args {
}
}

impl CliToSdk<QueryIbcToken<SdkTypes>> for QueryIbcToken<CliTypes> {
fn to_sdk(self, ctx: &mut Context) -> QueryIbcToken<SdkTypes> {
let query = self.query.to_sdk(ctx);
let chain_ctx = ctx.borrow_mut_chain_or_exit();
QueryIbcToken::<SdkTypes> {
query,
token: self.token,
owner: self.owner.map(|x| chain_ctx.get_cached(&x)),
}
}
}

impl Args for QueryIbcToken<CliTypes> {
fn parse(matches: &ArgMatches) -> Self {
let query = Query::parse(matches);
let token = TOKEN_STR_OPT.parse(matches);
let owner = BALANCE_OWNER.parse(matches);
Self {
query,
owner,
token,
}
}

fn def(app: App) -> App {
app.add_args::<Query<CliTypes>>()
.arg(TOKEN_STR_OPT.def().help("The base token to query."))
.arg(
BALANCE_OWNER
.def()
.help("The account address whose token to query."),
)
}
}

impl CliToSdk<QueryTransfers<SdkTypes>> for QueryTransfers<CliTypes> {
fn to_sdk(self, ctx: &mut Context) -> QueryTransfers<SdkTypes> {
let query = self.query.to_sdk(ctx);
Expand Down
12 changes: 12 additions & 0 deletions crates/apps/src/lib/cli/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -530,6 +530,18 @@ impl CliApi {
let namada = ctx.to_sdk(client, io);
rpc::query_balance(&namada, args).await;
}
Sub::QueryIbcToken(QueryIbcToken(args)) => {
let chain_ctx = ctx.borrow_mut_chain_or_exit();
let ledger_address =
chain_ctx.get(&args.query.ledger_address);
let client = client.unwrap_or_else(|| {
C::from_tendermint_address(&ledger_address)
});
client.wait_until_node_is_synced(&io).await?;
let args = args.to_sdk(&mut ctx);
let namada = ctx.to_sdk(client, io);
rpc::query_ibc_tokens(&namada, args).await;
}
Sub::QueryBonds(QueryBonds(args)) => {
let chain_ctx = ctx.borrow_mut_chain_or_exit();
let ledger_address =
Expand Down
90 changes: 40 additions & 50 deletions crates/apps/src/lib/client/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ use masp_primitives::transaction::components::I128Sum;
use masp_primitives::zip32::ExtendedFullViewingKey;
use namada::core::address::{Address, InternalAddress, MASP};
use namada::core::hash::Hash;
use namada::core::ibc::{is_ibc_denom, IbcTokenHash};
use namada::core::key::*;
use namada::core::masp::{BalanceOwner, ExtendedViewingKey, PaymentAddress};
use namada::core::storage::{
Expand All @@ -39,9 +38,7 @@ use namada::governance::utils::{
};
use namada::io::Io;
use namada::ledger::events::Event;
use namada::ledger::ibc::storage::{
ibc_trace_key, ibc_trace_key_prefix, is_ibc_trace_key,
};
use namada::ledger::ibc::storage::ibc_trace_key;
use namada::ledger::parameters::{storage as param_storage, EpochDuration};
use namada::ledger::pos::types::{CommissionPair, Slash};
use namada::ledger::pos::PosParams;
Expand Down Expand Up @@ -731,7 +728,9 @@ async fn lookup_token_alias(
match query_storage_value::<_, String>(context.client(), &ibc_trace_key)
.await
{
Ok(ibc_trace) => get_ibc_trace_alias(context, ibc_trace).await,
Ok(ibc_trace) => {
context.wallet().await.lookup_ibc_token_alias(ibc_trace)
}
Err(_) => token.to_string(),
}
} else {
Expand Down Expand Up @@ -763,60 +762,51 @@ async fn query_tokens(
if tokens.is_empty() {
base_token = None;
}
let prefixes = match (base_token, owner) {
(Some(base_token), Some(owner)) => vec![
ibc_trace_key_prefix(Some(base_token.to_string())),
ibc_trace_key_prefix(Some(owner.to_string())),
],
(Some(base_token), None) => {
vec![ibc_trace_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_trace_key_prefix(None)]
}
(None, None) => vec![ibc_trace_key_prefix(None)],
};

for prefix in prefixes {
let ibc_denoms = query_storage_prefix::<String>(context, &prefix).await;
if let Some(ibc_denoms) = ibc_denoms {
for (key, ibc_trace) in ibc_denoms {
if let Some((_, hash)) = is_ibc_trace_key(&key) {
let ibc_denom_alias =
get_ibc_trace_alias(context, ibc_trace).await;
let hash: IbcTokenHash = hash.parse().expect(
"Parsing an IBC token hash from storage shouldn't fail",
);
let ibc_token =
Address::Internal(InternalAddress::IbcToken(hash));
tokens.insert(ibc_denom_alias, ibc_token);
}
match rpc::query_ibc_tokens(
context,
base_token.map(|t| t.to_string()),
owner,
)
.await
{
Ok(ibc_tokens) => {
for (trace, addr) in ibc_tokens {
let ibc_trace_alias =
context.wallet().await.lookup_ibc_token_alias(trace);
tokens.insert(ibc_trace_alias, addr);
}
}
Err(e) => {
edisplay_line!(context.io(), "IBC token query failed: {}", e);
}
}
tokens
}

async fn get_ibc_trace_alias(
pub async fn query_ibc_tokens(
context: &impl Namada,
ibc_trace: impl AsRef<str>,
) -> String {
args: args::QueryIbcToken,
) {
let wallet = context.wallet().await;
is_ibc_denom(&ibc_trace)
.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)
let token = args.token.map(|t| {
wallet
.find_address(&t)
.map(|addr| addr.to_string())
.unwrap_or(t)
});
let owner = args.owner.map(|o| o.address().unwrap_or(MASP));
match rpc::query_ibc_tokens(context, token, owner.as_ref()).await {
Ok(ibc_tokens) => {
for (trace, addr) in ibc_tokens {
let alias =
context.wallet().await.lookup_ibc_token_alias(trace);
display_line!(context.io(), "{}: {}", alias, addr);
}
})
.unwrap_or(ibc_trace.as_ref().to_string())
}
Err(e) => {
edisplay_line!(context.io(), "IBC token query failed: {}", e);
}
}
}

/// Query votes for the given proposal
Expand Down
4 changes: 4 additions & 0 deletions crates/ibc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,10 @@ where
&msg.packet.port_id_on_b,
&msg.packet.chan_id_on_b,
)?;
if !ibc_denom.contains('/') {
// Skip to store it because the token has been redeemed
return Ok(());
}
let receiver =
if PaymentAddress::from_str(data.receiver.as_ref()).is_ok() {
MASP.to_string()
Expand Down
11 changes: 11 additions & 0 deletions crates/sdk/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1293,6 +1293,17 @@ pub struct QueryBalance<C: NamadaTypes = SdkTypes> {
pub no_conversions: bool,
}

/// Query IBC token(s)
#[derive(Clone, Debug)]
pub struct QueryIbcToken<C: NamadaTypes = SdkTypes> {
/// Common query args
pub query: Query<C>,
/// The token address which could be a non-namada address
pub token: Option<String>,
/// Address of an owner
pub owner: Option<C::BalanceOwner>,
}

/// Query historical transfer(s)
#[derive(Clone, Debug)]
pub struct QueryTransfers<C: NamadaTypes = SdkTypes> {
Expand Down
43 changes: 43 additions & 0 deletions crates/sdk/src/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use masp_primitives::sapling::Node;
use namada_account::Account;
use namada_core::address::{Address, InternalAddress};
use namada_core::hash::Hash;
use namada_core::ibc::IbcTokenHash;
use namada_core::key::common;
use namada_core::storage::{
BlockHeight, BlockResults, Epoch, Key, PrefixValue,
Expand Down Expand Up @@ -1360,6 +1361,48 @@ pub async fn format_denominated_amount(
.to_string()
}

/// Look up IBC tokens. The given base token can be non-Namada token.
pub async fn query_ibc_tokens<N: Namada>(
context: &N,
base_token: Option<String>,
owner: Option<&Address>,
) -> Result<BTreeMap<String, Address>, Error> {
// Check the base token
let prefixes = match (base_token, owner) {
(Some(base_token), Some(owner)) => vec![
ibc_trace_key_prefix(Some(base_token)),
ibc_trace_key_prefix(Some(owner.to_string())),
],
(Some(base_token), None) => {
vec![ibc_trace_key_prefix(Some(base_token))]
}
_ => {
// Check all IBC denoms because the owner might not know IBC token
// transfers in the same chain
vec![ibc_trace_key_prefix(None)]
}
};

let mut tokens = BTreeMap::new();
for prefix in prefixes {
let ibc_traces =
query_storage_prefix::<_, String>(context, &prefix).await?;
if let Some(ibc_traces) = ibc_traces {
for (key, ibc_trace) in ibc_traces {
if let Some((_, hash)) = is_ibc_trace_key(&key) {
let hash: IbcTokenHash = hash.parse().expect(
"Parsing an IBC token hash from storage shouldn't fail",
);
let ibc_token =
Address::Internal(InternalAddress::IbcToken(hash));
tokens.insert(ibc_trace, ibc_token);
}
}
}
}
Ok(tokens)
}

/// Look up the IBC denomination from a IbcToken.
pub async fn query_ibc_denom<N: Namada>(
context: &N,
Expand Down
29 changes: 29 additions & 0 deletions crates/sdk/src/wallet/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use alias::Alias;
use bip39::{Language, Mnemonic, MnemonicType, Seed};
use borsh::{BorshDeserialize, BorshSerialize};
use namada_core::address::Address;
use namada_core::ibc::is_ibc_denom;
use namada_core::key::*;
use namada_core::masp::{
ExtendedSpendingKey, ExtendedViewingKey, PaymentAddress,
Expand Down Expand Up @@ -379,6 +380,34 @@ impl<U> Wallet<U> {
}
}

/// Try to find an alias of the base token in the given IBC denomination
/// from the wallet. If not found, formats the IBC denomination into a
/// string.
pub fn lookup_ibc_token_alias(&self, ibc_denom: impl AsRef<str>) -> String {
// Convert only an IBC denom or a Namada address since an NFT trace
// doesn't have the alias
is_ibc_denom(&ibc_denom)
.map(|(trace_path, base_token)| {
let base_token_alias = match Address::decode(&base_token) {
Ok(base_token) => self.lookup_alias(&base_token),
Err(_) => base_token,
};
if trace_path.is_empty() {
base_token_alias
} else {
format!("{}/{}", trace_path, base_token_alias)
}
})
.or_else(|| {
// It's not an IBC denom, but could be a raw Namada address
match Address::decode(&ibc_denom) {
Ok(addr) => Some(self.lookup_alias(&addr)),
Err(_) => None,
}
})
.unwrap_or(ibc_denom.as_ref().to_string())
}

/// Find the viewing key with the given alias in the wallet and return it
pub fn find_viewing_key(
&self,
Expand Down

0 comments on commit 6b4f752

Please sign in to comment.