From cfdf73a43d31ac5d7ee1f5216a6fe106d6c548b1 Mon Sep 17 00:00:00 2001 From: yito88 Date: Wed, 31 May 2023 19:07:08 +0900 Subject: [PATCH 01/25] clean denom validation --- core/src/ledger/ibc/context/common.rs | 10 +++- core/src/ledger/ibc/mod.rs | 3 +- core/src/ledger/ibc/storage.rs | 22 +++++-- shared/src/ledger/ibc/vp/denom.rs | 85 --------------------------- shared/src/ledger/ibc/vp/mod.rs | 42 ++++++++++--- shared/src/ledger/ibc/vp/token.rs | 2 +- tests/src/vm_host_env/mod.rs | 2 +- 7 files changed, 64 insertions(+), 102 deletions(-) delete mode 100644 shared/src/ledger/ibc/vp/denom.rs diff --git a/core/src/ledger/ibc/context/common.rs b/core/src/ledger/ibc/context/common.rs index 1d03ffbeec..4c96c034b5 100644 --- a/core/src/ledger/ibc/context/common.rs +++ b/core/src/ledger/ibc/context/common.rs @@ -1,5 +1,6 @@ //! IbcCommonContext implementation for IBC +use borsh::BorshSerialize; use prost::Message; use sha2::Digest; @@ -363,7 +364,14 @@ pub trait IbcCommonContext: IbcStorageContext { denom: PrefixedDenom, ) -> Result<(), ContextError> { let key = storage::ibc_denom_key(trace_hash); - let bytes = denom.to_string().as_bytes().to_vec(); + let bytes = denom.to_string().try_to_vec().map_err(|e| { + ContextError::ChannelError(ChannelError::Other { + description: format!( + "Encoding the denom failed: Denom {}, error {}", + denom, e + ), + }) + })?; self.write(&key, bytes).map_err(|_| { ContextError::ChannelError(ChannelError::Other { description: format!("Writing the denom failed: Key {}", key), diff --git a/core/src/ledger/ibc/mod.rs b/core/src/ledger/ibc/mod.rs index 9a184ee51e..ef9ddc7735 100644 --- a/core/src/ledger/ibc/mod.rs +++ b/core/src/ledger/ibc/mod.rs @@ -9,6 +9,7 @@ use std::fmt::Debug; use std::rc::Rc; use std::time::Duration; +use borsh::BorshDeserialize; pub use context::common::IbcCommonContext; pub use context::storage::{IbcStorageContext, ProofSpec}; pub use context::transfer_mod::{ModuleWrapper, TransferModule}; @@ -151,7 +152,7 @@ where { let denom_key = storage::ibc_denom_key(token_hash); let denom = match self.ctx.borrow().read(&denom_key) { - Ok(Some(v)) => String::from_utf8(v).map_err(|e| { + Ok(Some(v)) => String::try_from_slice(&v[..]).map_err(|e| { Error::Denom(format!( "Decoding the denom string failed: {}", e diff --git a/core/src/ledger/ibc/storage.rs b/core/src/ledger/ibc/storage.rs index 3f0eb94d2a..1e84cb24ef 100644 --- a/core/src/ledger/ibc/storage.rs +++ b/core/src/ledger/ibc/storage.rs @@ -528,12 +528,24 @@ pub fn is_ibc_sub_prefix(sub_prefix: &Key) -> bool { DbKeySeg::StringSeg(s) if s == MULTITOKEN_STORAGE_KEY) } -/// Returns true if the given key is the denom key -pub fn is_ibc_denom_key(key: &Key) -> bool { +/// Returns the token hash if the given key is the denom key +pub fn is_ibc_denom_key(key: &Key) -> Option { match &key.segments[..] { - [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(prefix), ..] => { - addr == &Address::Internal(InternalAddress::Ibc) && prefix == DENOM + [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(prefix), + DbKeySeg::AddressSeg(Address::Internal(InternalAddress::IbcToken( + hash, + ))), + ] => { + if addr == &Address::Internal(InternalAddress::Ibc) + && prefix == DENOM + { + Some(hash.clone()) + } else { + None + } } - _ => false, + _ => None, } } diff --git a/shared/src/ledger/ibc/vp/denom.rs b/shared/src/ledger/ibc/vp/denom.rs deleted file mode 100644 index d58edfdc33..0000000000 --- a/shared/src/ledger/ibc/vp/denom.rs +++ /dev/null @@ -1,85 +0,0 @@ -//! IBC validity predicate for denom - -use prost::Message; -use thiserror::Error; - -use super::Ibc; -use crate::ibc::applications::transfer::packet::PacketData; -use crate::ibc::core::ics04_channel::msgs::PacketMsg; -use crate::ibc::core::ics26_routing::msgs::MsgEnvelope; -use crate::ibc_proto::google::protobuf::Any; -use crate::ledger::ibc::storage; -use crate::ledger::native_vp::VpEnv; -use crate::ledger::storage::{self as ledger_storage, StorageHasher}; -use crate::types::storage::KeySeg; -use crate::vm::WasmCacheAccess; - -#[allow(missing_docs)] -#[derive(Error, Debug)] -pub enum Error { - #[error("Decoding IBC data error: {0}")] - DecodingData(prost::DecodeError), - #[error("Invalid message: {0}")] - IbcMessage(String), - #[error("Decoding PacketData error: {0}")] - DecodingPacketData(serde_json::Error), - #[error("Denom error: {0}")] - Denom(String), -} - -/// IBC channel functions result -pub type Result = std::result::Result; - -impl<'a, DB, H, CA> Ibc<'a, DB, H, CA> -where - DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: 'static + StorageHasher, - CA: 'static + WasmCacheAccess, -{ - pub(super) fn validate_denom(&self, tx_data: &[u8]) -> Result<()> { - let ibc_msg = Any::decode(tx_data).map_err(Error::DecodingData)?; - let envelope: MsgEnvelope = ibc_msg.try_into().map_err(|e| { - Error::IbcMessage(format!( - "Decoding a MsgRecvPacket failed: Error {}", - e - )) - })?; - // A transaction only with MsgRecvPacket can update the denom store - let msg = match envelope { - MsgEnvelope::Packet(PacketMsg::Recv(msg)) => msg, - _ => { - return Err(Error::IbcMessage( - "Non-MsgRecvPacket message updated the denom store" - .to_string(), - )); - } - }; - let data = serde_json::from_slice::(&msg.packet.data) - .map_err(Error::DecodingPacketData)?; - let denom = format!( - "{}/{}/{}", - &msg.packet.port_id_on_b, - &msg.packet.chan_id_on_b, - &data.token.denom, - ); - let token_hash = storage::calc_hash(&denom); - let denom_key = storage::ibc_denom_key(token_hash.raw()); - match self.ctx.read_bytes_post(&denom_key) { - Ok(Some(v)) => match std::str::from_utf8(&v) { - Ok(d) if d == denom => Ok(()), - Ok(d) => Err(Error::Denom(format!( - "Mismatch the denom: original {}, denom {}", - denom, d - ))), - Err(e) => Err(Error::Denom(format!( - "Decoding the denom failed: key {}, error {}", - denom_key, e - ))), - }, - _ => Err(Error::Denom(format!( - "Looking up the denom failed: Key {}", - denom_key - ))), - } - } -} diff --git a/shared/src/ledger/ibc/vp/mod.rs b/shared/src/ledger/ibc/vp/mod.rs index 5986a01771..f5625112ff 100644 --- a/shared/src/ledger/ibc/vp/mod.rs +++ b/shared/src/ledger/ibc/vp/mod.rs @@ -1,7 +1,6 @@ //! IBC integration as a native validity predicate mod context; -mod denom; mod token; use std::cell::RefCell; @@ -10,7 +9,6 @@ use std::rc::Rc; use std::time::Duration; use context::{PseudoExecutionContext, VpValidationContext}; -use namada_core::ledger::ibc::storage::{is_ibc_denom_key, is_ibc_key}; use namada_core::ledger::ibc::{ Error as ActionError, IbcActions, TransferModule, ValidationParams, }; @@ -23,6 +21,7 @@ use namada_proof_of_stake::read_pos_params; use thiserror::Error; pub use token::{Error as IbcTokenError, IbcToken}; +use crate::ledger::ibc::storage::{calc_hash, is_ibc_denom_key, is_ibc_key}; use crate::ledger::native_vp::{self, Ctx, NativeVp, VpEnv}; use crate::ledger::parameters::read_epoch_duration_parameter; use crate::vm::WasmCacheAccess; @@ -40,8 +39,8 @@ pub enum Error { IbcAction(ActionError), #[error("State change error: {0}")] StateChange(String), - #[error("Denom store error: {0}")] - Denom(denom::Error), + #[error("Denom error: {0}")] + Denom(String), #[error("IBC event error: {0}")] IbcEvent(String), } @@ -86,9 +85,7 @@ where self.validate_with_msg(&tx_data)?; // Validate the denom store if a denom key has been changed - if keys_changed.iter().any(is_ibc_denom_key) { - self.validate_denom(&tx_data).map_err(Error::Denom)?; - } + self.validate_denom(keys_changed)?; Ok(true) } @@ -173,6 +170,35 @@ where upgrade_path: Vec::new(), }) } + + fn validate_denom(&self, keys_changed: &BTreeSet) -> VpResult<()> { + for key in keys_changed { + 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 {}", + key, e + )) + })? { + Some(denom) => { + if calc_hash(&denom) != hash { + return Err(Error::Denom(format!( + "The denom is invalid: Key {}, Denom {}", + key, denom + ))); + } + } + None => { + return Err(Error::Denom(format!( + "The corresponding denom wasn't stored: Key {}", + key + ))); + } + } + } + } + Ok(()) + } } fn match_value( @@ -2187,7 +2213,7 @@ mod tests { )); let trace_hash = calc_hash(coin.denom.to_string()); let denom_key = ibc_denom_key(&trace_hash); - let bytes = coin.denom.to_string().as_bytes().to_vec(); + let bytes = coin.denom.to_string().try_to_vec().unwrap(); wl_storage .write_log .write(&denom_key, bytes) diff --git a/shared/src/ledger/ibc/vp/token.rs b/shared/src/ledger/ibc/vp/token.rs index 18234abd35..204fa21ea0 100644 --- a/shared/src/ledger/ibc/vp/token.rs +++ b/shared/src/ledger/ibc/vp/token.rs @@ -192,7 +192,7 @@ where { let denom_key = ibc_storage::ibc_denom_key(token_hash); coin.denom = match self.ctx.read_bytes_pre(&denom_key) { - Ok(Some(v)) => String::from_utf8(v).map_err(|e| { + Ok(Some(v)) => String::try_from_slice(&v[..]).map_err(|e| { Error::Denom(format!( "Decoding the denom string failed: {}", e diff --git a/tests/src/vm_host_env/mod.rs b/tests/src/vm_host_env/mod.rs index 1766d7bad3..81192796a2 100644 --- a/tests/src/vm_host_env/mod.rs +++ b/tests/src/vm_host_env/mod.rs @@ -1195,7 +1195,7 @@ mod tests { // original denom let hash = ibc_storage::calc_hash(&denom); let denom_key = ibc_storage::ibc_denom_key(&hash); - writes.insert(denom_key, denom.as_bytes().to_vec()); + 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 0e5d22b9216ca6981bf19e7a9d65b1bc5084c259 Mon Sep 17 00:00:00 2001 From: yito88 Date: Fri, 2 Jun 2023 23:41:41 +0900 Subject: [PATCH 02/25] change multitoken --- apps/src/lib/client/rpc.rs | 79 ++++------ core/src/ledger/ibc/context/storage.rs | 23 ++- core/src/ledger/ibc/context/transfer_mod.rs | 84 +++-------- core/src/ledger/ibc/mod.rs | 2 +- core/src/ledger/ibc/storage.rs | 116 +++------------ core/src/types/address.rs | 87 ++++------- core/src/types/token.rs | 94 +++++------- shared/src/ledger/ibc/vp/context.rs | 152 ++++++++++++++----- shared/src/ledger/ibc/vp/mod.rs | 6 +- shared/src/ledger/native_vp/mod.rs | 1 + shared/src/ledger/protocol/mod.rs | 21 ++- shared/src/ledger/tx.rs | 36 +++-- tests/src/e2e/ibc_tests.rs | 49 +++--- tests/src/vm_host_env/ibc.rs | 40 ++--- tests/src/vm_host_env/mod.rs | 92 +++++++----- tests/src/vm_host_env/tx.rs | 8 +- tx_prelude/src/ibc.rs | 35 ++++- tx_prelude/src/token.rs | 156 +++++++------------- wasm/wasm_source/src/vp_implicit.rs | 7 +- wasm/wasm_source/src/vp_token.rs | 38 +---- wasm/wasm_source/src/vp_user.rs | 7 +- wasm/wasm_source/src/vp_validator.rs | 7 +- 22 files changed, 497 insertions(+), 643 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 44955967d0..da2078139d 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -305,17 +305,13 @@ pub async fn query_transparent_balance< let tokens = wallet.get_addresses_with_vp_type(AddressVpType::Token); match (args.token, args.owner) { (Some(token), Some(owner)) => { - let (balance_key, sub_prefix) = match &args.sub_prefix { - Some(sub_prefix) => { - let sub_prefix = Key::parse(sub_prefix).unwrap(); - let prefix = - token::multitoken_balance_prefix(&token, &sub_prefix); - ( - token::multitoken_balance_key( - &prefix, - &owner.address().unwrap(), - ), - Some(sub_prefix), + let key = match &args.sub_prefix { + Some(sp) => { + let sub_prefix = + Address::decode(sp).expect("Invalid sub_prefix"); + token::multitoken_balance_key( + &sub_prefix, + &owner.address().unwrap(), ) } None => ( @@ -559,30 +555,20 @@ async fn print_balances( let token_alias = lookup_alias(wallet, token); writeln!(w, "Token {}", token_alias).unwrap(); - let mut print_num = 0; - for (key, balance) in balances { - let (o, s) = match token::is_any_multitoken_balance_key(&key) { - Some((sub_prefix, [tok, owner])) => ( - owner.clone(), - format!( - "with {}: {}, owned by {}", - sub_prefix.clone(), - format_denominated_amount( - client, - &TokenAddress { - address: tok.clone(), - sub_prefix: Some(sub_prefix) - }, - balance - ) - .await, - lookup_alias(wallet, owner) - ), - ), - None => { - if let Some([tok, owner]) = - token::is_any_token_balance_key(&key) - { + + let print_num = balances + .filter_map(|(key, balance)| { + match token::is_multitoken_balance_key(&key) { + Some((sub_prefix, owner)) => Some(( + owner.clone(), + format!( + "with {}: {}, owned by {}", + sub_prefix, + balance, + lookup_alias(wallet, owner) + ), + )), + None => token::is_any_token_balance_key(&key).map(|owner| { ( owner.clone(), format!( @@ -599,19 +585,18 @@ async fn print_balances( lookup_alias(wallet, owner) ), ) - } else { - continue; - } + }), } - }; - let s = match target { - Some(t) if o == *t => s, - Some(_) => continue, - None => s, - }; - writeln!(w, "{}", s).unwrap(); - print_num += 1; - } + }) + .filter_map(|(o, s)| match target { + Some(t) if o == *t => Some(s), + Some(_) => None, + None => Some(s), + }) + .map(|s| { + writeln!(w, "{}", s).unwrap(); + }) + .count(); if print_num == 0 { match target { diff --git a/core/src/ledger/ibc/context/storage.rs b/core/src/ledger/ibc/context/storage.rs index a99a659187..e047395a68 100644 --- a/core/src/ledger/ibc/context/storage.rs +++ b/core/src/ledger/ibc/context/storage.rs @@ -6,6 +6,7 @@ pub use ics23::ProofSpec; use super::super::Error; use crate::ledger::storage_api; +use crate::types::address::Address; use crate::types::ibc::IbcEvent; use crate::types::storage::{BlockHeight, Header, Key}; use crate::types::token::Amount; @@ -54,8 +55,26 @@ pub trait IbcStorageContext { /// Transfer token fn transfer_token( &mut self, - src: &Key, - dest: &Key, + src: &Address, + dest: &Address, + token: &Address, + sub_prefix: Option
, + amount: Amount, + ) -> Result<(), Self::Error>; + + /// Mint token + fn mint_token( + &mut self, + target: &Address, + sub_prefix: &Address, + amount: Amount, + ) -> Result<(), Self::Error>; + + /// Burn token + fn burn_token( + &mut self, + target: &Address, + sub_prefix: &Address, amount: Amount, ) -> Result<(), Self::Error>; diff --git a/core/src/ledger/ibc/context/transfer_mod.rs b/core/src/ledger/ibc/context/transfer_mod.rs index ad0aa75800..9dfce35e6c 100644 --- a/core/src/ledger/ibc/context/transfer_mod.rs +++ b/core/src/ledger/ibc/context/transfer_mod.rs @@ -388,7 +388,7 @@ where _port_id: &PortId, _channel_id: &ChannelId, ) -> Result { - Ok(Address::Internal(InternalAddress::IbcEscrow)) + Ok(Address::Internal(InternalAddress::Ibc)) } fn can_send_coins(&self) -> Result<(), TokenTransferError> { @@ -446,42 +446,22 @@ where // has no prefix let (token, amount) = get_token_amount(coin)?; - let src = if coin.denom.trace_path.is_empty() - || *from == Address::Internal(InternalAddress::IbcEscrow) - || *from == Address::Internal(InternalAddress::IbcMint) - { - token::balance_key(&token, from) + let ibc_token = if coin.denom.trace_path.is_empty() { + None } else { - let prefix = storage::ibc_token_prefix(coin.denom.to_string()) - .map_err(|_| TokenTransferError::InvalidCoin { - coin: coin.to_string(), - })?; - token::multitoken_balance_key(&prefix, from) - }; - - let dest = if coin.denom.trace_path.is_empty() - || *to == Address::Internal(InternalAddress::IbcEscrow) - || *to == Address::Internal(InternalAddress::IbcBurn) - { - token::balance_key(&token, to) - } else { - let prefix = storage::ibc_token_prefix(coin.denom.to_string()) - .map_err(|_| TokenTransferError::InvalidCoin { - coin: coin.to_string(), - })?; - token::multitoken_balance_key(&prefix, to) + Some(storage::ibc_token(coin.denom.to_string())) }; self.ctx .borrow_mut() - .transfer_token(&src, &dest, amount) + .transfer_token(from, to, &token, ibc_token, amount) .map_err(|_| { TokenTransferError::ContextError(ContextError::ChannelError( ChannelError::Other { description: format!( "Sending a coin failed: from {}, to {}, amount {}", - src, - dest, + from, + to, amount.to_string_native() ), }, @@ -494,33 +474,20 @@ where account: &Self::AccountId, coin: &PrefixedCoin, ) -> Result<(), TokenTransferError> { - let (token, amount) = get_token_amount(coin)?; + let (_, amount) = get_token_amount(coin)?; - let src = token::balance_key( - &token, - &Address::Internal(InternalAddress::IbcMint), - ); - - let dest = if coin.denom.trace_path.is_empty() { - token::balance_key(&token, account) - } else { - let prefix = storage::ibc_token_prefix(coin.denom.to_string()) - .map_err(|_| TokenTransferError::InvalidCoin { - coin: coin.to_string(), - })?; - token::multitoken_balance_key(&prefix, account) - }; + // The trace path of the denom is already updated if receiving the token + let ibc_token = storage::ibc_token(coin.denom.to_string()); self.ctx .borrow_mut() - .transfer_token(&src, &dest, amount) + .mint_token(account, &ibc_token, amount) .map_err(|_| { TokenTransferError::ContextError(ContextError::ChannelError( ChannelError::Other { description: format!( - "Sending a coin failed: from {}, to {}, amount {}", - src, - dest, + "Minting a coin failed: account {}, amount {}", + account, amount.to_string_native() ), }, @@ -533,33 +500,20 @@ where account: &Self::AccountId, coin: &PrefixedCoin, ) -> Result<(), TokenTransferError> { - let (token, amount) = get_token_amount(coin)?; + let (_, amount) = get_token_amount(coin)?; - let src = if coin.denom.trace_path.is_empty() { - token::balance_key(&token, account) - } else { - let prefix = storage::ibc_token_prefix(coin.denom.to_string()) - .map_err(|_| TokenTransferError::InvalidCoin { - coin: coin.to_string(), - })?; - token::multitoken_balance_key(&prefix, account) - }; - - let dest = token::balance_key( - &token, - &Address::Internal(InternalAddress::IbcBurn), - ); + // The burn is unminting of the minted + let ibc_token = storage::ibc_token(coin.denom.to_string()); self.ctx .borrow_mut() - .transfer_token(&src, &dest, amount) + .burn_token(account, &ibc_token, amount) .map_err(|_| { TokenTransferError::ContextError(ContextError::ChannelError( ChannelError::Other { description: format!( - "Sending a coin failed: from {}, to {}, amount {}", - src, - dest, + "Burning a coin failed: account {}, amount {}", + account, amount.to_string_native() ), }, diff --git a/core/src/ledger/ibc/mod.rs b/core/src/ledger/ibc/mod.rs index ef9ddc7735..b56e8ce54a 100644 --- a/core/src/ledger/ibc/mod.rs +++ b/core/src/ledger/ibc/mod.rs @@ -141,7 +141,7 @@ where } } - /// Restore the denom when it is hashed, i.e. the denom is `ibc/{hash}`. + /// Restore the denom when it is hashed fn restore_denom(&self, msg: MsgTransfer) -> Result { let mut msg = msg; // lookup the original denom with the IBC token hash diff --git a/core/src/ledger/ibc/storage.rs b/core/src/ledger/ibc/storage.rs index 1e84cb24ef..1a47680f00 100644 --- a/core/src/ledger/ibc/storage.rs +++ b/core/src/ledger/ibc/storage.rs @@ -23,8 +23,6 @@ const CLIENTS_COUNTER: &str = "clients/counter"; const CONNECTIONS_COUNTER: &str = "connections/counter"; const CHANNELS_COUNTER: &str = "channelEnds/counter"; const DENOM: &str = "ibc_denom"; -/// Key segment for a multitoken related to IBC -pub const MULTITOKEN_STORAGE_KEY: &str = "ibc"; #[allow(missing_docs)] #[derive(Error, Debug)] @@ -44,67 +42,6 @@ pub enum Error { /// IBC storage functions result pub type Result = std::result::Result; -/// IBC prefix -#[allow(missing_docs)] -pub enum IbcPrefix { - Client, - Connection, - Channel, - Port, - Capability, - SeqSend, - SeqRecv, - SeqAck, - Commitment, - Receipt, - Ack, - Event, - Denom, - Unknown, -} - -/// Returns the prefix from the given key -pub fn ibc_prefix(key: &Key) -> Option { - match &key.segments[..] { - [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(prefix), ..] - if addr == &Address::Internal(InternalAddress::Ibc) => - { - Some(match &*prefix.raw() { - "clients" => IbcPrefix::Client, - "connections" => IbcPrefix::Connection, - "channelEnds" => IbcPrefix::Channel, - "ports" => IbcPrefix::Port, - "capabilities" => IbcPrefix::Capability, - "nextSequenceSend" => IbcPrefix::SeqSend, - "nextSequenceRecv" => IbcPrefix::SeqRecv, - "nextSequenceAck" => IbcPrefix::SeqAck, - "commitments" => IbcPrefix::Commitment, - "receipts" => IbcPrefix::Receipt, - "acks" => IbcPrefix::Ack, - "event" => IbcPrefix::Event, - "ibc_denom" => IbcPrefix::Denom, - _ => IbcPrefix::Unknown, - }) - } - _ => None, - } -} - -/// Check if the given key is a key of the client counter -pub fn is_client_counter_key(key: &Key) -> bool { - *key == client_counter_key() -} - -/// Check if the given key is a key of the connection counter -pub fn is_connection_counter_key(key: &Key) -> bool { - *key == connection_counter_key() -} - -/// Check if the given key is a key of the channel counter -pub fn is_channel_counter_key(key: &Key) -> bool { - *key == channel_counter_key() -} - /// 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)?; @@ -473,26 +410,20 @@ pub fn token(denom: impl AsRef) -> Result
{ /// Get the hash of IBC token address from the denom string pub fn token_hash_from_denom(denom: impl AsRef) -> Result> { - match denom - .as_ref() - .strip_prefix(&format!("{}/", MULTITOKEN_STORAGE_KEY)) - { - Some(addr_str) => { - let addr = Address::decode(addr_str).map_err(|e| { - Error::Denom(format!( - "Decoding the denom failed: ibc_token {}, error {}", - addr_str, e - )) - })?; - match addr { - Address::Internal(InternalAddress::IbcToken(h)) => Ok(Some(h)), - _ => Err(Error::Denom(format!( - "Unexpected address was given: {}", - addr - ))), - } - } - None => Ok(None), + let addr = Address::decode(denom.as_ref()).map_err(|e| { + Error::Denom(format!( + "Decoding the denom failed: denom {}, error {}", + denom.as_ref(), + e + )) + })?; + match addr { + Address::Established(_) => Ok(None), + Address::Internal(InternalAddress::IbcToken(h)) => Ok(Some(h)), + _ => Err(Error::Denom(format!( + "Unexpected address was given: {}", + addr + ))), } } @@ -503,17 +434,10 @@ pub fn calc_hash(denom: impl AsRef) -> String { format!("{:.width$x}", hasher.finalize(), width = HASH_HEX_LEN) } -/// Key's prefix of the received token over IBC -pub fn ibc_token_prefix(denom: impl AsRef) -> Result { - let token = token(&denom)?; +/// Obtain the IbcToken with the hash from the given denom +pub fn ibc_token(denom: impl AsRef) -> Address { let hash = calc_hash(&denom); - let ibc_token = Address::Internal(InternalAddress::IbcToken(hash)); - let prefix = Key::from(token.to_db_key()) - .push(&MULTITOKEN_STORAGE_KEY.to_owned()) - .expect("Cannot obtain a storage key") - .push(&ibc_token.to_db_key()) - .expect("Cannot obtain a storage key"); - Ok(prefix) + Address::Internal(InternalAddress::IbcToken(hash)) } /// Returns true if the given key is for IBC @@ -522,12 +446,6 @@ pub fn is_ibc_key(key: &Key) -> bool { DbKeySeg::AddressSeg(addr) if *addr == Address::Internal(InternalAddress::Ibc)) } -/// Returns true if the sub prefix is for IBC -pub fn is_ibc_sub_prefix(sub_prefix: &Key) -> bool { - matches!(&sub_prefix.segments[0], - DbKeySeg::StringSeg(s) if s == MULTITOKEN_STORAGE_KEY) -} - /// Returns the token hash if the given key is the denom key pub fn is_ibc_denom_key(key: &Key) -> Option { match &key.segments[..] { diff --git a/core/src/types/address.rs b/core/src/types/address.rs index eca6c87251..c647b85238 100644 --- a/core/src/types/address.rs +++ b/core/src/types/address.rs @@ -54,10 +54,8 @@ pub const FIXED_LEN_STRING_BYTES: usize = 45; /// Internal IBC address pub const IBC: Address = Address::Internal(InternalAddress::Ibc); -/// Internal IBC token burn address -pub const IBC_BURN: Address = Address::Internal(InternalAddress::IbcBurn); -/// Internal IBC token mint address -pub const IBC_MINT: Address = Address::Internal(InternalAddress::IbcMint); +/// Internal multitoken mint address +pub const MINT: Address = Address::Internal(InternalAddress::Mint); /// Internal ledger parameters address pub const PARAMETERS: Address = Address::Internal(InternalAddress::Parameters); /// Internal PoS address @@ -84,18 +82,16 @@ mod internal { "ano::Slash Fund "; pub const IBC: &str = "ibc::Inter-Blockchain Communication "; - pub const IBC_ESCROW: &str = - "ibc::IBC Escrow Address "; - pub const IBC_BURN: &str = - "ibc::IBC Burn Address "; - pub const IBC_MINT: &str = - "ibc::IBC Mint Address "; pub const ETH_BRIDGE: &str = "ano::ETH Bridge Address "; pub const ETH_BRIDGE_POOL: &str = "ano::ETH Bridge Pool Address "; pub const REPLAY_PROTECTION: &str = "ano::Replay Protection "; + pub const MULTITOKEN: &str = + "ano::Multitoken "; + pub const MINT: &str = + "ano::Multitoken Mint Address "; } /// Fixed-length address strings prefix for established addresses. @@ -233,11 +229,6 @@ impl Address { InternalAddress::IbcToken(hash) => { format!("{}::{}", PREFIX_IBC, hash) } - InternalAddress::IbcEscrow => { - internal::IBC_ESCROW.to_string() - } - InternalAddress::IbcBurn => internal::IBC_BURN.to_string(), - InternalAddress::IbcMint => internal::IBC_MINT.to_string(), InternalAddress::EthBridge => { internal::ETH_BRIDGE.to_string() } @@ -247,6 +238,10 @@ impl Address { InternalAddress::ReplayProtection => { internal::REPLAY_PROTECTION.to_string() } + InternalAddress::Multitoken => { + internal::MULTITOKEN.to_string() + } + InternalAddress::Mint => internal::MINT.to_string(), }; debug_assert_eq!(string.len(), FIXED_LEN_STRING_BYTES); string @@ -320,6 +315,10 @@ impl Address { internal::REPLAY_PROTECTION => { Ok(Address::Internal(InternalAddress::ReplayProtection)) } + internal::MULTITOKEN => { + Ok(Address::Internal(InternalAddress::Multitoken)) + } + internal::MINT => Ok(Address::Internal(InternalAddress::Mint)), _ => Err(Error::new( ErrorKind::InvalidData, "Invalid internal address", @@ -327,15 +326,6 @@ impl Address { }, Some((PREFIX_IBC, raw)) => match string { internal::IBC => Ok(Address::Internal(InternalAddress::Ibc)), - internal::IBC_ESCROW => { - Ok(Address::Internal(InternalAddress::IbcEscrow)) - } - internal::IBC_BURN => { - Ok(Address::Internal(InternalAddress::IbcBurn)) - } - internal::IBC_MINT => { - Ok(Address::Internal(InternalAddress::IbcMint)) - } _ if raw.len() == HASH_HEX_LEN => Ok(Address::Internal( InternalAddress::IbcToken(raw.to_string()), )), @@ -532,12 +522,6 @@ pub enum InternalAddress { Ibc, /// IBC-related token IbcToken(String), - /// Escrow for IBC token transfer - IbcEscrow, - /// Burn tokens with IBC token transfer - IbcBurn, - /// Mint tokens from this address with IBC token transfer - IbcMint, /// Governance address Governance, /// SlashFund address for governance @@ -548,22 +532,10 @@ pub enum InternalAddress { EthBridgePool, /// Replay protection contains transactions' hash ReplayProtection, -} - -impl InternalAddress { - /// Get an IBC token address from the port ID and channel ID - pub fn ibc_token_address( - port_id: String, - channel_id: String, - token: &Address, - ) -> Self { - let mut hasher = Sha256::new(); - let s = format!("{}/{}/{}", port_id, channel_id, token); - hasher.update(&s); - let hash = - format!("{:.width$x}", hasher.finalize(), width = HASH_HEX_LEN); - InternalAddress::IbcToken(hash) - } + /// Multitoken + Multitoken, + /// Minted multitoken address + Mint, } impl Display for InternalAddress { @@ -579,12 +551,11 @@ impl Display for InternalAddress { Self::SlashFund => "SlashFund".to_string(), Self::Ibc => "IBC".to_string(), Self::IbcToken(hash) => format!("IbcToken: {}", hash), - Self::IbcEscrow => "IbcEscrow".to_string(), - Self::IbcBurn => "IbcBurn".to_string(), - Self::IbcMint => "IbcMint".to_string(), Self::EthBridge => "EthBridge".to_string(), Self::EthBridgePool => "EthBridgePool".to_string(), Self::ReplayProtection => "ReplayProtection".to_string(), + Self::Multitoken => "Multitoken".to_string(), + Self::Mint => "Mint".to_string(), } ) } @@ -875,30 +846,26 @@ pub mod testing { InternalAddress::Parameters => {} InternalAddress::Ibc => {} InternalAddress::IbcToken(_) => {} - InternalAddress::IbcEscrow => {} - InternalAddress::IbcBurn => {} - InternalAddress::IbcMint => {} InternalAddress::EthBridge => {} InternalAddress::EthBridgePool => {} - InternalAddress::ReplayProtection => {} /* Add new addresses in - * the - * `prop_oneof` below. */ + InternalAddress::ReplayProtection => {} + InternalAddress::Multitoken => {} + InternalAddress::Mint => {} /* Add new addresses in the + * `prop_oneof` below. */ }; prop_oneof![ Just(InternalAddress::PoS), Just(InternalAddress::PosSlashPool), Just(InternalAddress::Ibc), Just(InternalAddress::Parameters), - Just(InternalAddress::Ibc), arb_ibc_token(), - Just(InternalAddress::IbcEscrow), - Just(InternalAddress::IbcBurn), - Just(InternalAddress::IbcMint), Just(InternalAddress::Governance), Just(InternalAddress::SlashFund), Just(InternalAddress::EthBridge), Just(InternalAddress::EthBridgePool), - Just(InternalAddress::ReplayProtection) + Just(InternalAddress::ReplayProtection), + Just(InternalAddress::Multitoken), + Just(InternalAddress::Mint) ] } diff --git a/core/src/types/token.rs b/core/src/types/token.rs index b7c731e0e1..2e12e06220 100644 --- a/core/src/types/token.rs +++ b/core/src/types/token.rs @@ -15,7 +15,9 @@ use super::dec::POS_DECIMAL_PRECISION; use crate::ibc::applications::transfer::Amount as IbcAmount; use crate::ledger::storage_api::token::read_denom; use crate::ledger::storage_api::{self, StorageRead}; -use crate::types::address::{masp, Address, DecodeError as AddressError}; +use crate::types::address::{ + masp, Address, DecodeError as AddressError, InternalAddress, +}; use crate::types::dec::Dec; use crate::types::hash::Hash; use crate::types::storage; @@ -767,6 +769,8 @@ impl TryFrom for Amount { pub const BALANCE_STORAGE_KEY: &str = "balance"; /// Key segment for a denomination key pub const DENOM_STORAGE_KEY: &str = "denomination"; +/// Key segment for multitoken minter +pub const MINTER_STORAGE_KEY: &str = "minter"; /// Key segment for head shielded transaction pointer keys pub const HEAD_TX_KEY: &str = "head-tx"; /// Key segment prefix for shielded transaction key @@ -841,23 +845,37 @@ pub fn balance_prefix(token_addr: &Address) -> Key { .expect("Cannot obtain a storage key") } -/// Obtain a storage key prefix for multitoken balances. -pub fn multitoken_balance_prefix( - token_addr: &Address, - sub_prefix: &Key, -) -> Key { - Key::from(token_addr.to_db_key()).join(sub_prefix) -} - /// Obtain a storage key for user's multitoken balance. -pub fn multitoken_balance_key(prefix: &Key, owner: &Address) -> Key { - prefix +pub fn multitoken_balance_key(prefix: &Address, owner: &Address) -> Key { + Key::from(Address::Internal(InternalAddress::Multitoken).to_db_key()) + .push(prefix) + .expect("Cannot obtain a storage key") .push(&BALANCE_STORAGE_KEY.to_owned()) .expect("Cannot obtain a storage key") .push(&owner.to_db_key()) .expect("Cannot obtain a storage key") } +/// Obtain a storage key for the multitoken minter. +pub fn multitoken_minter_key(prefix: &Address) -> Key { + Key::from(Address::Internal(InternalAddress::Multitoken).to_db_key()) + .push(&MINTER_STORAGE_KEY.to_owned()) + .expect("Cannot obtain a storage key") + .push(prefix) + .expect("Cannot obtain a storage key") +} + +/// Obtain a storage key for the minted multitoken balance. +pub fn multitoken_minted_key(prefix: &Address) -> Key { + Key::from(Address::Internal(InternalAddress::Multitoken).to_db_key()) + .push(prefix) + .expect("Cannot obtain a storage key") + .push(&BALANCE_STORAGE_KEY.to_owned()) + .expect("Cannot obtain a storage key") + .push(&Address::Internal(InternalAddress::Mint).to_db_key()) + .expect("Cannot obtain a storage key") +} + /// Check if the given storage key is balance key for the given token. If it is, /// returns the owner. pub fn is_balance_key<'a>( @@ -934,56 +952,16 @@ pub fn is_total_supply_key(key: &Key, token_address: &Address) -> bool { /// Check if the given storage key is multitoken balance key for the given /// token. If it is, returns the sub prefix and the owner. -pub fn is_multitoken_balance_key<'a>( - token_addr: &Address, - key: &'a Key, -) -> Option<(Key, &'a Address)> { - match key.segments.first() { - Some(DbKeySeg::AddressSeg(addr)) if addr == token_addr => { - multitoken_balance_owner(key) - } - _ => None, - } -} - -/// Check if the given storage key is multitoken balance key for unspecified -/// token. If it is, returns the sub prefix and the token and owner addresses. -pub fn is_any_multitoken_balance_key( - key: &Key, -) -> Option<(Key, [&Address; 2])> { - match key.segments.first() { - Some(DbKeySeg::AddressSeg(token)) => multitoken_balance_owner(key) - .map(|(sub, owner)| (sub, [token, owner])), - _ => None, - } -} - -/// Check if the given storage key is token or multitoken balance key for -/// unspecified token. If it is, returns the token and owner addresses. -pub fn is_any_token_or_multitoken_balance_key( - key: &Key, -) -> Option<[&Address; 2]> { - is_any_multitoken_balance_key(key) - .map(|a| a.1) - .or_else(|| is_any_token_balance_key(key)) -} - -fn multitoken_balance_owner(key: &Key) -> Option<(Key, &Address)> { - let len = key.segments.len(); - if len < 4 { - // the key of a multitoken should have 1 or more segments other than - // token, balance, owner - return None; - } +pub fn is_multitoken_balance_key(key: &Key) -> Option<(&Address, &Address)> { match &key.segments[..] { [ - .., + DbKeySeg::AddressSeg(addr), + DbKeySeg::AddressSeg(sub_prefix), DbKeySeg::StringSeg(balance), DbKeySeg::AddressSeg(owner), - ] if balance == BALANCE_STORAGE_KEY => { - let sub_prefix = Key { - segments: key.segments[1..(len - 2)].to_vec(), - }; + ] if *addr == Address::Internal(InternalAddress::Multitoken) + && balance == BALANCE_STORAGE_KEY => + { Some((sub_prefix, owner)) } _ => None, @@ -1012,7 +990,7 @@ pub struct Transfer { /// Token's address pub token: Address, /// Source token's sub prefix - pub sub_prefix: Option, + pub sub_prefix: Option
, /// The amount of tokens pub amount: DenominatedAmount, /// The unused storage location at which to place TxId diff --git a/shared/src/ledger/ibc/vp/context.rs b/shared/src/ledger/ibc/vp/context.rs index 0da78bba53..cee112854e 100644 --- a/shared/src/ledger/ibc/vp/context.rs +++ b/shared/src/ledger/ibc/vp/context.rs @@ -2,7 +2,7 @@ use std::collections::{BTreeSet, HashMap, HashSet}; -use borsh::{BorshDeserialize, BorshSerialize}; +use borsh::BorshSerialize; use namada_core::ledger::ibc::storage::is_ibc_key; use namada_core::ledger::ibc::{IbcCommonContext, IbcStorageContext}; use namada_core::ledger::storage::write_log::StorageModification; @@ -12,7 +12,7 @@ use namada_core::types::address::{Address, InternalAddress}; use namada_core::types::ibc::IbcEvent; use namada_core::types::storage::{BlockHeight, Header, Key}; use namada_core::types::token::{ - is_any_token_balance_key, is_any_token_or_multitoken_balance_key, Amount, + self, is_any_token_balance_key, is_any_token_or_multitoken_balance_key, Amount, }; use super::Error; @@ -117,50 +117,111 @@ where fn transfer_token( &mut self, - src: &Key, - dest: &Key, + src: &Address, + dest: &Address, + token: &Address, + sub_prefix: Option
, amount: Amount, ) -> Result<(), Self::Error> { - let src_owner = is_any_token_or_multitoken_balance_key(src); - let mut src_bal = match src_owner { - Some([_, Address::Internal(InternalAddress::IbcMint)]) => { - Amount::max() - } - Some([_, Address::Internal(InternalAddress::IbcBurn)]) => { - unreachable!("Invalid transfer from IBC burn address") - } - _ => match self.read(src)? { - Some(v) => { - Amount::try_from_slice(&v[..]).map_err(Error::Decoding)? - } - None => unreachable!("The source has no balance"), - }, + let src_key = match &sub_prefix { + Some(sub_prefix) => token::multitoken_balance_key(sub_prefix, src), + None => token::balance_key(token, src), }; - src_bal.spend(&amount); - let dest_owner = is_any_token_balance_key(dest); - let mut dest_bal = match dest_owner { - Some([_, Address::Internal(InternalAddress::IbcMint)]) => { - unreachable!("Invalid transfer to IBC mint address") - } - _ => match self.read(dest)? { - Some(v) => { - Amount::try_from_slice(&v[..]).map_err(Error::Decoding)? - } - None => Amount::default(), - }, + let dest_key = match &sub_prefix { + Some(sub_prefix) => token::multitoken_balance_key(sub_prefix, dest), + None => token::balance_key(token, dest), }; + let src_bal: Option = + self.ctx.read(&src_key).map_err(Error::NativeVpError)?; + let mut src_bal = src_bal.expect("The source has no balance"); + src_bal.spend(&amount); + let mut dest_bal: Amount = self + .ctx + .read(&dest_key) + .map_err(Error::NativeVpError)? + .unwrap_or_default(); dest_bal.receive(&amount); self.write( - src, + &src_key, src_bal.try_to_vec().expect("encoding shouldn't failed"), )?; self.write( - dest, + &dest_key, dest_bal.try_to_vec().expect("encoding shouldn't failed"), + ) + } + + fn mint_token( + &mut self, + target: &Address, + sub_prefix: &Address, + amount: Amount, + ) -> Result<(), Self::Error> { + let target_key = token::multitoken_balance_key(sub_prefix, target); + let mut target_bal: Amount = self + .ctx + .read(&target_key) + .map_err(Error::NativeVpError)? + .unwrap_or_default(); + target_bal.receive(&amount); + + let minted_key = token::multitoken_minted_key(sub_prefix); + let mut minted_bal: Amount = self + .ctx + .read(&minted_key) + .map_err(Error::NativeVpError)? + .unwrap_or_default(); + minted_bal.receive(&amount); + + self.write( + &target_key, + target_bal.try_to_vec().expect("encoding shouldn't failed"), + )?; + self.write( + &minted_key, + minted_bal.try_to_vec().expect("encoding shouldn't failed"), )?; - Ok(()) + let minter_key = token::multitoken_minter_key(sub_prefix); + self.write( + &minter_key, + Address::Internal(InternalAddress::Ibc) + .try_to_vec() + .expect("encoding shouldn't failed"), + ) + } + + fn burn_token( + &mut self, + target: &Address, + sub_prefix: &Address, + amount: Amount, + ) -> Result<(), Self::Error> { + let target_key = token::multitoken_balance_key(sub_prefix, target); + let mut target_bal: Amount = self + .ctx + .read(&target_key) + .map_err(Error::NativeVpError)? + .unwrap_or_default(); + target_bal.spend(&amount); + + let minted_key = token::multitoken_minted_key(sub_prefix); + let mut minted_bal: Amount = self + .ctx + .read(&minted_key) + .map_err(Error::NativeVpError)? + .unwrap_or_default(); + minted_bal.spend(&amount); + + self.write( + &target_key, + target_bal.try_to_vec().expect("encoding shouldn't failed"), + )?; + self.write( + &minted_key, + minted_bal.try_to_vec().expect("encoding shouldn't failed"), + ) } /// Get the current height of this chain @@ -254,16 +315,35 @@ where unimplemented!("Validation doesn't emit an event") } - /// Transfer token fn transfer_token( &mut self, - _src: &Key, - _dest: &Key, + _src: &Address, + _dest: &Address, + _token: &Address, + _sub_prefix: Option
, _amount: Amount, ) -> Result<(), Self::Error> { unimplemented!("Validation doesn't transfer") } + fn mint_token( + &mut self, + _target: &Address, + _sub_prefix: &Address, + _amount: Amount, + ) -> Result<(), Self::Error> { + unimplemented!("Validation doesn't mint") + } + + fn burn_token( + &mut self, + _target: &Address, + _sub_prefix: &Address, + _amount: Amount, + ) -> Result<(), Self::Error> { + unimplemented!("Validation doesn't burn") + } + fn get_height(&self) -> Result { self.ctx.get_block_height().map_err(Error::NativeVpError) } diff --git a/shared/src/ledger/ibc/vp/mod.rs b/shared/src/ledger/ibc/vp/mod.rs index f5625112ff..619e484ba2 100644 --- a/shared/src/ledger/ibc/vp/mod.rs +++ b/shared/src/ledger/ibc/vp/mod.rs @@ -1,7 +1,6 @@ //! IBC integration as a native validity predicate mod context; -mod token; use std::cell::RefCell; use std::collections::{BTreeSet, HashSet}; @@ -19,7 +18,6 @@ use namada_core::types::address::{Address, InternalAddress}; use namada_core::types::storage::Key; use namada_proof_of_stake::read_pos_params; use thiserror::Error; -pub use token::{Error as IbcTokenError, IbcToken}; use crate::ledger::ibc::storage::{calc_hash, is_ibc_denom_key, is_ibc_key}; use crate::ledger::native_vp::{self, Ctx, NativeVp, VpEnv}; @@ -2458,7 +2456,7 @@ mod tests { .expect("write failed"); // init the escrow balance let balance_key = - balance_key(&nam(), &Address::Internal(InternalAddress::IbcEscrow)); + balance_key(&nam(), &Address::Internal(InternalAddress::Ibc)); let amount = Amount::native_whole(100); wl_storage .write_log @@ -2607,7 +2605,7 @@ mod tests { .expect("write failed"); // init the escrow balance let balance_key = - balance_key(&nam(), &Address::Internal(InternalAddress::IbcEscrow)); + balance_key(&nam(), &Address::Internal(InternalAddress::Ibc)); let amount = Amount::native_whole(100); wl_storage .write_log diff --git a/shared/src/ledger/native_vp/mod.rs b/shared/src/ledger/native_vp/mod.rs index dcc0432ea7..c7d58f0563 100644 --- a/shared/src/ledger/native_vp/mod.rs +++ b/shared/src/ledger/native_vp/mod.rs @@ -3,6 +3,7 @@ pub mod ethereum_bridge; pub mod governance; +pub mod multitoken; pub mod parameters; pub mod replay_protection; pub mod slash_fund; diff --git a/shared/src/ledger/protocol/mod.rs b/shared/src/ledger/protocol/mod.rs index 8d360a3bd5..a71c2d0cae 100644 --- a/shared/src/ledger/protocol/mod.rs +++ b/shared/src/ledger/protocol/mod.rs @@ -8,10 +8,11 @@ use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; use thiserror::Error; use crate::ledger::gas::{self, BlockGasMeter, VpGasMeter}; -use crate::ledger::ibc::vp::{Ibc, IbcToken}; +use crate::ledger::ibc::vp::Ibc; use crate::ledger::native_vp::ethereum_bridge::bridge_pool_vp::BridgePoolVp; use crate::ledger::native_vp::ethereum_bridge::vp::EthBridge; use crate::ledger::native_vp::governance::GovernanceVp; +use crate::ledger::native_vp::multitoken::MultitokenVp; use crate::ledger::native_vp::parameters::{self, ParametersVp}; use crate::ledger::native_vp::replay_protection::ReplayProtectionVp; use crate::ledger::native_vp::slash_fund::SlashFundVp; @@ -58,7 +59,7 @@ pub enum Error { #[error("Parameters native VP: {0}")] ParametersNativeVpError(parameters::Error), #[error("IBC Token native VP: {0}")] - IbcTokenNativeVpError(crate::ledger::ibc::vp::IbcTokenError), + MultitokenNativeVpError(crate::ledger::native_vp::multitoken::Error), #[error("Governance native VP error: {0}")] GovernanceNativeVpError(crate::ledger::native_vp::governance::Error), #[error("SlashFund native VP error: {0}")] @@ -550,16 +551,14 @@ where gas_meter = slash_fund.ctx.gas_meter.into_inner(); result } - InternalAddress::IbcToken(_) - | InternalAddress::IbcEscrow - | InternalAddress::IbcBurn - | InternalAddress::IbcMint => { - // validate the transfer - let ibc_token = IbcToken { ctx }; - let result = ibc_token + InternalAddress::Multitoken + | InternalAddress::IbcToken(_) + | InternalAddress::Mint => { + let multitoken = MultitokenVp { ctx }; + let result = multitoken .validate_tx(tx, &keys_changed, &verifiers) - .map_err(Error::IbcTokenNativeVpError); - gas_meter = ibc_token.ctx.gas_meter.into_inner(); + .map_err(Error::MultitokenNativeVpError); + gas_meter = multitoken.ctx.gas_meter.into_inner(); result } InternalAddress::EthBridge => { diff --git a/shared/src/ledger/tx.rs b/shared/src/ledger/tx.rs index 36644a3e14..6b11cac281 100644 --- a/shared/src/ledger/tx.rs +++ b/shared/src/ledger/tx.rs @@ -1145,13 +1145,17 @@ pub async fn build_ibc_transfer< // Check source balance let (sub_prefix, balance_key) = match args.sub_prefix { - Some(sub_prefix) => { - let sub_prefix = storage::Key::parse(sub_prefix).unwrap(); - let prefix = token::multitoken_balance_prefix(&token, &sub_prefix); - ( - Some(sub_prefix), - token::multitoken_balance_key(&prefix, &source), - ) + Some(sp) => { + let sub_prefix = Address::decode(&sp).map_err(|e| { + Error::Other(format!( + "The sub_prefix was not an Address: sub_prefix {}, error \ + {}", + sp, e + )) + })?; + let balance_key = + token::multitoken_balance_key(&sub_prefix, &source); + (Some(sub_prefix), balance_key) } None => (None, token::balance_key(&token, &source)), }; @@ -1334,13 +1338,17 @@ pub async fn build_transfer< token_exists_or_err(token.clone(), args.tx.force, client).await?; // Check source balance let (sub_prefix, balance_key) = match &args.sub_prefix { - Some(ref sub_prefix) => { - let sub_prefix = storage::Key::parse(sub_prefix).unwrap(); - let prefix = token::multitoken_balance_prefix(&token, &sub_prefix); - ( - Some(sub_prefix), - token::multitoken_balance_key(&prefix, &source), - ) + Some(sp) => { + let sub_prefix = Address::decode(sp).map_err(|e| { + Error::Other(format!( + "The sub_prefix was not an Address: sub_prefix {}, error \ + {}", + sp, e + )) + })?; + let balance_key = + token::multitoken_balance_key(&sub_prefix, &source); + (Some(sub_prefix), balance_key) } None => (None, token::balance_key(&token, &source)), }; diff --git a/tests/src/e2e/ibc_tests.rs b/tests/src/e2e/ibc_tests.rs index e071c2bd13..15356d3f71 100644 --- a/tests/src/e2e/ibc_tests.rs +++ b/tests/src/e2e/ibc_tests.rs @@ -72,7 +72,7 @@ use namada::ledger::storage::ics23_specs::ibc_proof_specs; use namada::ledger::storage::traits::Sha256Hasher; use namada::types::address::{Address, InternalAddress}; use namada::types::key::PublicKey; -use namada::types::storage::{BlockHeight, Key, RESERVED_ADDRESS_PREFIX}; +use namada::types::storage::{BlockHeight, Key}; use namada::types::token::Amount; use namada_apps::client::rpc::{ query_storage_value, query_storage_value_bytes, @@ -776,11 +776,7 @@ fn transfer_received_token( "{}/{}/{}", port_channel_id.port_id, port_channel_id.channel_id, xan ); - let sub_prefix = ibc_token_prefix(denom) - .unwrap() - .sub_key() - .unwrap() - .to_string(); + let ibc_token = ibc_token(denom).to_string(); let rpc = get_actor_rpc(test, &Who::Validator(0)); let amount = Amount::native_whole(50000).to_string_native(); @@ -793,7 +789,7 @@ fn transfer_received_token( "--token", NAM, "--sub-prefix", - &sub_prefix, + &ibc_token, "--amount", &amount, "--gas-amount", @@ -828,13 +824,7 @@ fn transfer_back( "{}/{}/{}", port_channel_id_b.port_id, port_channel_id_b.channel_id, xan ); - let hash = calc_hash(denom_raw); - let ibc_token = Address::Internal(InternalAddress::IbcToken(hash)); - // Need the address prefix for ibc-transfer command - let sub_prefix = format!( - "{}/{}{}", - MULTITOKEN_STORAGE_KEY, RESERVED_ADDRESS_PREFIX, ibc_token - ); + let ibc_token = ibc_token(denom_raw).to_string(); // Send a token from Chain B let height = transfer( test_b, @@ -843,7 +833,7 @@ fn transfer_back( NAM, &Amount::native_whole(50000), port_channel_id_b, - Some(sub_prefix), + Some(ibc_token), None, )?; let packet = match get_event(test_b, height)? { @@ -1272,7 +1262,7 @@ fn check_balances( // Check the escrowed balance let expected = format!( ": 100000, owned by {}", - Address::Internal(InternalAddress::IbcEscrow) + Address::Internal(InternalAddress::Ibc) ); client.exp_string(&expected)?; // Check the source balance @@ -1285,8 +1275,7 @@ fn check_balances( "{}/{}/{}", &dest_port_channel_id.port_id, &dest_port_channel_id.channel_id, &token, ); - let key_prefix = ibc_token_prefix(denom)?; - let sub_prefix = key_prefix.sub_key().unwrap().to_string(); + let ibc_token = ibc_token(denom).to_string(); let rpc_b = get_actor_rpc(test_b, &Who::Validator(0)); let query_args = vec![ "balance", @@ -1295,11 +1284,11 @@ fn check_balances( "--token", NAM, "--sub-prefix", - &sub_prefix, + &ibc_token, "--node", &rpc_b, ]; - let expected = format!("nam with {}: 100000", sub_prefix); + let expected = format!("nam with {}: 100000", ibc_token); let mut client = run!(test_b, Bin::Client, query_args, Some(40))?; client.exp_string(&expected)?; client.assert_success(); @@ -1317,8 +1306,7 @@ fn check_balances_after_non_ibc( "{}/{}/{}", port_channel_id.port_id, port_channel_id.channel_id, token ); - let key_prefix = ibc_token_prefix(denom)?; - let sub_prefix = key_prefix.sub_key().unwrap().to_string(); + let ibc_token = ibc_token(denom).to_string(); // Check the source let rpc = get_actor_rpc(test, &Who::Validator(0)); @@ -1329,11 +1317,11 @@ fn check_balances_after_non_ibc( "--token", NAM, "--sub-prefix", - &sub_prefix, + &ibc_token, "--node", &rpc, ]; - let expected = format!("nam with {}: 50000", sub_prefix); + let expected = format!("nam with {}: 50000", ibc_token); let mut client = run!(test, Bin::Client, query_args, Some(40))?; client.exp_string(&expected)?; client.assert_success(); @@ -1346,11 +1334,11 @@ fn check_balances_after_non_ibc( "--token", NAM, "--sub-prefix", - &sub_prefix, + &ibc_token, "--node", &rpc, ]; - let expected = format!("nam with {}: 50000", sub_prefix); + let expected = format!("nam with {}: 50000", ibc_token); let mut client = run!(test, Bin::Client, query_args, Some(40))?; client.exp_string(&expected)?; client.assert_success(); @@ -1373,7 +1361,7 @@ fn check_balances_after_back( // Check the escrowed balance let expected = format!( ": 50000, owned by {}", - Address::Internal(InternalAddress::IbcEscrow) + Address::Internal(InternalAddress::Ibc) ); client.exp_string(&expected)?; // Check the source balance @@ -1386,8 +1374,7 @@ fn check_balances_after_back( "{}/{}/{}", &dest_port_channel_id.port_id, &dest_port_channel_id.channel_id, &token, ); - let key_prefix = ibc_token_prefix(denom)?; - let sub_prefix = key_prefix.sub_key().unwrap().to_string(); + let ibc_token = ibc_token(denom).to_string(); let rpc_b = get_actor_rpc(test_b, &Who::Validator(0)); let query_args = vec![ "balance", @@ -1396,11 +1383,11 @@ fn check_balances_after_back( "--token", NAM, "--sub-prefix", - &sub_prefix, + &ibc_token, "--node", &rpc_b, ]; - let expected = format!("nam with {}: 0", sub_prefix); + let expected = format!("nam with {}: 0", ibc_token); 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/ibc.rs b/tests/src/vm_host_env/ibc.rs index b73fef0a3d..e3cbce8f30 100644 --- a/tests/src/vm_host_env/ibc.rs +++ b/tests/src/vm_host_env/ibc.rs @@ -60,13 +60,14 @@ pub use namada::ledger::ibc::storage::{ ack_key, channel_counter_key, channel_key, client_counter_key, client_state_key, client_type_key, client_update_height_key, client_update_timestamp_key, commitment_key, connection_counter_key, - connection_key, consensus_state_key, ibc_token_prefix, - next_sequence_ack_key, next_sequence_recv_key, next_sequence_send_key, - port_key, receipt_key, + connection_key, consensus_state_key, ibc_token, next_sequence_ack_key, + next_sequence_recv_key, next_sequence_send_key, port_key, receipt_key, }; use namada::ledger::ibc::vp::{ get_dummy_genesis_validator, get_dummy_header as tm_dummy_header, Ibc, - IbcToken, +}; +use namada::ledger::native_vp::multitoken::{ + Error as MultitokenVpError, MultitokenVp, }; use namada::ledger::native_vp::{Ctx, NativeVp}; use namada::ledger::parameters::storage::{ @@ -115,19 +116,20 @@ impl<'a> TestIbcVp<'a> { } } -pub struct TestIbcTokenVp<'a> { - pub token: IbcToken<'a, MockDB, Sha256Hasher, WasmCacheRwAccess>, +pub struct TestMultitokenVp<'a> { + pub multitoken_vp: + MultitokenVp<'a, MockDB, Sha256Hasher, WasmCacheRwAccess>, } -impl<'a> TestIbcTokenVp<'a> { +impl<'a> TestMultitokenVp<'a> { pub fn validate( &self, - tx_data: &Tx, - ) -> std::result::Result { - self.token.validate_tx( - tx_data, - self.token.ctx.keys_changed, - self.token.ctx.verifiers, + tx: &Tx, + ) -> std::result::Result { + self.multitoken_vp.validate_tx( + tx, + self.multitoken_vp.ctx.keys_changed, + self.multitoken_vp.ctx.verifiers, ) } } @@ -168,11 +170,11 @@ pub fn validate_ibc_vp_from_tx<'a>( } /// Validate the native token VP for the given address -pub fn validate_token_vp_from_tx<'a>( +pub fn validate_multitoken_vp_from_tx<'a>( tx_env: &'a TestTxEnv, tx: &'a Tx, target: &Key, -) -> std::result::Result { +) -> std::result::Result { let (verifiers, keys_changed) = tx_env .wl_storage .write_log @@ -198,9 +200,9 @@ pub fn validate_token_vp_from_tx<'a>( &verifiers, vp_wasm_cache, ); - let token = IbcToken { ctx }; + let multitoken_vp = MultitokenVp { ctx }; - TestIbcTokenVp { token }.validate(tx) + TestMultitokenVp { multitoken_vp }.validate(tx) } /// Initialize the test storage. Requires initialized [`tx_host_env::ENV`]. @@ -762,6 +764,6 @@ pub fn packet_from_message( } pub fn balance_key_with_ibc_prefix(denom: String, owner: &Address) -> Key { - let prefix = ibc_token_prefix(denom).expect("invalid denom"); - token::multitoken_balance_key(&prefix, owner) + let ibc_token = ibc_token(denom); + token::multitoken_balance_key(&ibc_token, owner) } diff --git a/tests/src/vm_host_env/mod.rs b/tests/src/vm_host_env/mod.rs index 81192796a2..13d371ce0c 100644 --- a/tests/src/vm_host_env/mod.rs +++ b/tests/src/vm_host_env/mod.rs @@ -28,6 +28,8 @@ mod tests { }; use namada::ledger::tx_env::TxEnv; use namada::proto::{Code, Data, Section, Signature, Tx}; + use namada::types::address::{Address, InternalAddress}; + use namada::types::chain::ChainId; use namada::types::hash::Hash; use namada::types::key::*; use namada::types::storage::{self, BlockHash, BlockHeight, Key, KeySeg}; @@ -1110,10 +1112,10 @@ mod tests { // Check if the token was escrowed let escrow = token::balance_key( &token, - &address::Address::Internal(address::InternalAddress::IbcEscrow), + &address::Address::Internal(address::InternalAddress::Ibc), ); let token_vp_result = - ibc::validate_token_vp_from_tx(&env, &tx, &escrow); + ibc::validate_multitoken_vp_from_tx(&env, &tx, &escrow); assert!(token_vp_result.expect("token validation failed unexpectedly")); // Commit @@ -1165,7 +1167,7 @@ mod tests { assert_eq!(balance, Some(Amount::native_whole(0))); let escrow_key = token::balance_key( &token, - &address::Address::Internal(address::InternalAddress::IbcEscrow), + &address::Address::Internal(address::InternalAddress::Ibc), ); let escrow: Option = tx_host_env::with(|env| { env.wl_storage.read(&escrow_key).expect("read error") @@ -1188,13 +1190,25 @@ mod tests { writes.extend(channel_writes); // the origin-specific token let denom = format!("{}/{}/{}", port_id, channel_id, token); - let key_prefix = ibc_storage::ibc_token_prefix(&denom).unwrap(); - let balance_key = token::multitoken_balance_key(&key_prefix, &sender); + let ibc_token = ibc_storage::ibc_token(&denom); + let balance_key = token::multitoken_balance_key(&ibc_token, &sender); let init_bal = Amount::native_whole(100); writes.insert(balance_key.clone(), init_bal.try_to_vec().unwrap()); + let minted_key = token::multitoken_balance_key( + &ibc_token, + &Address::Internal(InternalAddress::Mint), + ); + writes.insert(minted_key.clone(), init_bal.try_to_vec().unwrap()); + let minter_key = token::multitoken_minter_key(&ibc_token); + writes.insert( + minter_key, + Address::Internal(InternalAddress::Ibc) + .try_to_vec() + .unwrap(), + ); // original denom let hash = ibc_storage::calc_hash(&denom); - let denom_key = ibc_storage::ibc_denom_key(&hash); + 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| { @@ -1207,11 +1221,7 @@ mod tests { // Start a transaction to send a packet // Set this chain is the sink zone - let ibc_token = address::Address::Internal( - address::InternalAddress::IbcToken(hash), - ); - let hashed_denom = - format!("{}/{}", ibc_storage::MULTITOKEN_STORAGE_KEY, ibc_token); + let hashed_denom = ibc_token.to_string(); let msg = ibc::msg_transfer(port_id, channel_id, hashed_denom, &sender); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); @@ -1233,11 +1243,8 @@ mod tests { let result = ibc::validate_ibc_vp_from_tx(&env, &tx); assert!(result.expect("validation failed unexpectedly")); // Check if the token was burned - let burn = token::balance_key( - &token, - &address::Address::Internal(address::InternalAddress::IbcBurn), - ); - let result = ibc::validate_token_vp_from_tx(&env, &tx, &burn); + let result = + ibc::validate_multitoken_vp_from_tx(&env, &tx, &minted_key); assert!(result.expect("token validation failed unexpectedly")); // Check the balance tx_host_env::set(env); @@ -1245,14 +1252,10 @@ mod tests { env.wl_storage.read(&balance_key).expect("read error") }); assert_eq!(balance, Some(Amount::native_whole(0))); - let burn_key = token::balance_key( - &token, - &address::Address::Internal(address::InternalAddress::IbcBurn), - ); - let burn: Option = tx_host_env::with(|env| { - env.wl_storage.read(&burn_key).expect("read error") + let minted: Option = tx_host_env::with(|env| { + env.wl_storage.read(&minted_key).expect("read error") }); - assert_eq!(burn, Some(Amount::native_whole(100))); + assert_eq!(minted, Some(Amount::native_whole(0))); } #[test] @@ -1309,20 +1312,23 @@ mod tests { let result = ibc::validate_ibc_vp_from_tx(&env, &tx); assert!(result.expect("validation failed unexpectedly")); // Check if the token was minted - let mint = token::balance_key( - &token, - &address::Address::Internal(address::InternalAddress::IbcMint), - ); - let result = ibc::validate_token_vp_from_tx(&env, &tx, &mint); + let denom = format!("{}/{}/{}", port_id, channel_id, token); + let ibc_token = ibc::ibc_token(&denom); + let minted_key = token::multitoken_minted_key(&ibc_token); + let result = + ibc::validate_multitoken_vp_from_tx(&env, &tx, &minted_key); assert!(result.expect("token validation failed unexpectedly")); // Check the balance tx_host_env::set(env); - let denom = format!("{}/{}/{}", port_id, channel_id, token); let key = ibc::balance_key_with_ibc_prefix(denom, &receiver); let balance: Option = tx_host_env::with(|env| { env.wl_storage.read(&key).expect("read error") }); assert_eq!(balance, Some(Amount::native_whole(100))); + let minted: Option = tx_host_env::with(|env| { + env.wl_storage.read(&minted_key).expect("read error") + }); + assert_eq!(minted, Some(Amount::native_whole(100))); } #[test] @@ -1349,7 +1355,7 @@ mod tests { // escrow in advance let escrow_key = token::balance_key( &token, - &address::Address::Internal(address::InternalAddress::IbcEscrow), + &address::Address::Internal(address::InternalAddress::Ibc), ); let val = Amount::native_whole(100).try_to_vec().unwrap(); tx_host_env::with(|env| { @@ -1397,7 +1403,8 @@ mod tests { let result = ibc::validate_ibc_vp_from_tx(&env, &tx); assert!(result.expect("validation failed unexpectedly")); // Check if the token was unescrowed - let result = ibc::validate_token_vp_from_tx(&env, &tx, &escrow_key); + let result = + ibc::validate_multitoken_vp_from_tx(&env, &tx, &escrow_key); assert!(result.expect("token validation failed unexpectedly")); // Check the balance tx_host_env::set(env); @@ -1434,9 +1441,13 @@ mod tests { }); }); // escrow in advance - let escrow_key = token::balance_key( - &token, - &address::Address::Internal(address::InternalAddress::IbcEscrow), + let dummy_src_port = "dummy_transfer"; + let dummy_src_channel = "channel_42"; + let denom = + format!("{}/{}/{}", dummy_src_port, dummy_src_channel, token); + let escrow_key = ibc::balance_key_with_ibc_prefix( + denom, + &address::Address::Internal(address::InternalAddress::Ibc), ); let val = Amount::native_whole(100).try_to_vec().unwrap(); tx_host_env::with(|env| { @@ -1448,8 +1459,6 @@ mod tests { // Set this chain as the source zone let counterparty = ibc::dummy_channel_counterparty(); - let dummy_src_port = "dummy_transfer"; - let dummy_src_channel = "channel_42"; let denom = format!( "{}/{}/{}/{}/{}", counterparty.port_id().clone(), @@ -1489,7 +1498,8 @@ mod tests { let result = ibc::validate_ibc_vp_from_tx(&env, &tx); assert!(result.expect("validation failed unexpectedly")); // Check if the token was unescrowed - let result = ibc::validate_token_vp_from_tx(&env, &tx, &escrow_key); + let result = + ibc::validate_multitoken_vp_from_tx(&env, &tx, &escrow_key); assert!(result.expect("token validation failed unexpectedly")); // Check the balance tx_host_env::set(env); @@ -1587,9 +1597,9 @@ mod tests { // Check if the token was refunded let escrow = token::balance_key( &token, - &address::Address::Internal(address::InternalAddress::IbcEscrow), + &address::Address::Internal(address::InternalAddress::Ibc), ); - let result = ibc::validate_token_vp_from_tx(&env, &tx, &escrow); + let result = ibc::validate_multitoken_vp_from_tx(&env, &tx, &escrow); assert!(result.expect("token validation failed unexpectedly")); } @@ -1672,9 +1682,9 @@ mod tests { // Check if the token was refunded let escrow = token::balance_key( &token, - &address::Address::Internal(address::InternalAddress::IbcEscrow), + &address::Address::Internal(address::InternalAddress::Ibc), ); - let result = ibc::validate_token_vp_from_tx(&env, &tx, &escrow); + let result = ibc::validate_multitoken_vp_from_tx(&env, &tx, &escrow); assert!(result.expect("token validation failed unexpectedly")); } } diff --git a/tests/src/vm_host_env/tx.rs b/tests/src/vm_host_env/tx.rs index c9ab18a08a..bb66ee71b7 100644 --- a/tests/src/vm_host_env/tx.rs +++ b/tests/src/vm_host_env/tx.rs @@ -177,15 +177,11 @@ impl TestTxEnv { &mut self, target: &Address, token: &Address, - sub_prefix: Option, + sub_prefix: Option
, amount: token::Amount, ) { let storage_key = match &sub_prefix { - Some(sub_prefix) => { - let prefix = - token::multitoken_balance_prefix(token, sub_prefix); - token::multitoken_balance_key(&prefix, target) - } + Some(sp) => token::multitoken_balance_key(sp, target), None => token::balance_key(token, target), }; self.wl_storage diff --git a/tx_prelude/src/ibc.rs b/tx_prelude/src/ibc.rs index 12654df964..12747e6a15 100644 --- a/tx_prelude/src/ibc.rs +++ b/tx_prelude/src/ibc.rs @@ -9,11 +9,12 @@ pub use namada_core::ledger::ibc::{ }; use namada_core::ledger::storage_api::{StorageRead, StorageWrite}; use namada_core::ledger::tx_env::TxEnv; +use namada_core::types::address::{Address, InternalAddress}; pub use namada_core::types::ibc::IbcEvent; use namada_core::types::storage::{BlockHeight, Header, Key}; use namada_core::types::token::Amount; -use crate::token::transfer_with_keys; +use crate::token::{burn, mint, transfer}; use crate::{Ctx, KeyValIterator}; /// IBC actions to handle an IBC message @@ -72,11 +73,37 @@ impl IbcStorageContext for Ctx { fn transfer_token( &mut self, - src: &Key, - dest: &Key, + src: &Address, + dest: &Address, + token: &Address, + sub_prefix: Option
, amount: Amount, ) -> std::result::Result<(), Self::Error> { - transfer_with_keys(self, src, dest, amount) + transfer(self, src, dest, token, sub_prefix, amount, &None, &None) + } + + fn mint_token( + &mut self, + target: &Address, + sub_prefix: &Address, + amount: Amount, + ) -> Result<(), Self::Error> { + mint( + self, + &Address::Internal(InternalAddress::Ibc), + target, + sub_prefix, + amount, + ) + } + + fn burn_token( + &mut self, + target: &Address, + sub_prefix: &Address, + amount: Amount, + ) -> Result<(), Self::Error> { + burn(self, target, sub_prefix, amount) } fn get_height(&self) -> std::result::Result { diff --git a/tx_prelude/src/token.rs b/tx_prelude/src/token.rs index 685a2e51a6..e85edf6b7a 100644 --- a/tx_prelude/src/token.rs +++ b/tx_prelude/src/token.rs @@ -1,5 +1,5 @@ use masp_primitives::transaction::Transaction; -use namada_core::types::address::{Address, InternalAddress}; +use namada_core::types::address::Address; use namada_core::types::hash::Hash; use namada_core::types::storage::KeySeg; use namada_core::types::token; @@ -14,7 +14,7 @@ pub fn transfer( src: &Address, dest: &Address, token: &Address, - sub_prefix: Option, + sub_prefix: Option
, amount: DenominatedAmount, key: &Option, shielded_hash: &Option, @@ -22,70 +22,24 @@ pub fn transfer( ) -> TxResult { if amount.amount != Amount::default() { let src_key = match &sub_prefix { - Some(sub_prefix) => { - let prefix = - token::multitoken_balance_prefix(token, sub_prefix); - token::multitoken_balance_key(&prefix, src) - } + Some(sub_prefix) => token::multitoken_balance_key(sub_prefix, src), None => token::balance_key(token, src), }; let dest_key = match &sub_prefix { - Some(sub_prefix) => { - let prefix = - token::multitoken_balance_prefix(token, sub_prefix); - token::multitoken_balance_key(&prefix, dest) - } + Some(sub_prefix) => token::multitoken_balance_key(sub_prefix, dest), None => token::balance_key(token, dest), }; + let src_bal: Option = ctx.read(&src_key)?; + let mut src_bal = src_bal.unwrap_or_else(|| { + log_string(format!("src {} has no balance", src_key)); + unreachable!() + }); + src_bal.spend(&amount.amount); + let mut dest_bal: Amount = ctx.read(&dest_key)?.unwrap_or_default(); + dest_bal.receive(&amount.amount); if src != dest { - let src_bal: Option = match src { - Address::Internal(InternalAddress::IbcMint) => { - Some(Amount::max_signed()) - } - Address::Internal(InternalAddress::IbcBurn) => { - log_string("invalid transfer from the burn address"); - unreachable!() - } - _ => ctx.read(&src_key)?, - }; - let mut src_bal = src_bal.unwrap_or_else(|| { - log_string(format!("src {} has no balance", src_key)); - unreachable!() - }); - src_bal.spend(&amount.amount); - let mut dest_bal: Amount = match dest { - Address::Internal(InternalAddress::IbcMint) => { - log_string("invalid transfer to the mint address"); - unreachable!() - } - _ => ctx.read(&dest_key)?.unwrap_or_default(), - }; - dest_bal.receive(&amount.amount); - - match src { - Address::Internal(InternalAddress::IbcMint) => { - ctx.write_temp(&src_key, src_bal)?; - } - Address::Internal(InternalAddress::IbcBurn) => { - log_string("invalid transfer from the burn address"); - unreachable!() - } - _ => { - ctx.write(&src_key, src_bal)?; - } - } - match dest { - Address::Internal(InternalAddress::IbcMint) => { - log_string("invalid transfer to the mint address"); - unreachable!() - } - Address::Internal(InternalAddress::IbcBurn) => { - ctx.write_temp(&dest_key, dest_bal)?; - } - _ => { - ctx.write(&dest_key, dest_bal)?; - } - } + ctx.write(&src_key, src_bal)?; + ctx.write(&dest_key, dest_bal)?; } } @@ -135,49 +89,49 @@ pub fn transfer( Ok(()) } -/// A token transfer with storage keys that can be used in a transaction. -pub fn transfer_with_keys( +/// Mint that can be used in a transaction. +pub fn mint( ctx: &mut Ctx, - src_key: &storage::Key, - dest_key: &storage::Key, + minter: &Address, + target: &Address, + sub_prefix: &Address, amount: Amount, ) -> TxResult { - let src_owner = is_any_token_or_multitoken_balance_key(src_key); - let src_bal: Option = match src_owner { - Some([_, Address::Internal(InternalAddress::IbcMint)]) => { - Some(Amount::max_signed()) - } - Some([_, Address::Internal(InternalAddress::IbcBurn)]) => { - log_string("invalid transfer from the burn address"); - unreachable!() - } - _ => ctx.read(src_key)?, - }; - let mut src_bal = src_bal.unwrap_or_else(|| { - log_string(format!("src {} has no balance", src_key)); - unreachable!() - }); - src_bal.spend(&amount); - let dest_owner = is_any_token_balance_key(dest_key); - let mut dest_bal: Amount = match dest_owner { - Some([_, Address::Internal(InternalAddress::IbcMint)]) => { - log_string("invalid transfer to the mint address"); - unreachable!() - } - _ => ctx.read(dest_key)?.unwrap_or_default(), - }; - dest_bal.receive(&amount); - match src_owner { - Some([_, Address::Internal(InternalAddress::IbcMint)]) => { - ctx.write_temp(src_key, src_bal)?; - } - _ => ctx.write(src_key, src_bal)?, - } - match dest_owner { - Some([_, Address::Internal(InternalAddress::IbcBurn)]) => { - ctx.write_temp(dest_key, dest_bal)?; - } - _ => ctx.write(dest_key, dest_bal)?, - } + let target_key = token::multitoken_balance_key(sub_prefix, target); + let mut target_bal: Amount = ctx.read(&target_key)?.unwrap_or_default(); + target_bal.receive(&amount); + + let minted_key = token::multitoken_minted_key(sub_prefix); + let mut minted_bal: Amount = ctx.read(&minted_key)?.unwrap_or_default(); + minted_bal.receive(&amount); + + ctx.write(&target_key, target_bal)?; + ctx.write(&minted_key, minted_bal)?; + + let minter_key = token::multitoken_minter_key(sub_prefix); + ctx.write(&minter_key, minter)?; + + Ok(()) +} + +/// Burn that can be used in a transaction. +pub fn burn( + ctx: &mut Ctx, + target: &Address, + sub_prefix: &Address, + amount: Amount, +) -> TxResult { + let target_key = token::multitoken_balance_key(sub_prefix, target); + let mut target_bal: Amount = ctx.read(&target_key)?.unwrap_or_default(); + target_bal.spend(&amount); + + // burn the minted amount + let minted_key = token::multitoken_minted_key(sub_prefix); + let mut minted_bal: Amount = ctx.read(&minted_key)?.unwrap_or_default(); + minted_bal.spend(&amount); + + ctx.write(&target_key, target_bal)?; + ctx.write(&minted_key, minted_bal)?; + Ok(()) } diff --git a/wasm/wasm_source/src/vp_implicit.rs b/wasm/wasm_source/src/vp_implicit.rs index 729ddb6fbe..70195a9c69 100644 --- a/wasm/wasm_source/src/vp_implicit.rs +++ b/wasm/wasm_source/src/vp_implicit.rs @@ -30,10 +30,9 @@ impl<'a> From<&'a storage::Key> for KeyType<'a> { fn from(key: &'a storage::Key) -> KeyType<'a> { if let Some(address) = key::is_pk_key(key) { Self::Pk(address) - } else if let Some([_, owner]) = token::is_any_token_balance_key(key) { - Self::Token { owner } - } else if let Some((_, [_, owner])) = - token::is_any_multitoken_balance_key(key) + } else if let Some(address) = token::is_any_token_balance_key(key) { + Self::Token(address) + } else if let Some((_, address)) = token::is_multitoken_balance_key(key) { Self::Token { owner } } else if proof_of_stake::is_pos_key(key) { diff --git a/wasm/wasm_source/src/vp_token.rs b/wasm/wasm_source/src/vp_token.rs index 29b639bd56..e59b0864b7 100644 --- a/wasm/wasm_source/src/vp_token.rs +++ b/wasm/wasm_source/src/vp_token.rs @@ -3,7 +3,7 @@ use std::collections::BTreeSet; -use namada_vp_prelude::address::{self, Address, InternalAddress}; +use namada_vp_prelude::address::{self, Address}; use namada_vp_prelude::storage::KeySeg; use namada_vp_prelude::{storage, token, *}; @@ -53,10 +53,7 @@ fn token_checks( ) -> VpResult { let mut change = token::Change::default(); for key in keys_touched.iter() { - let owner: Option<&Address> = token::is_balance_key(token, key) - .or_else(|| { - token::is_multitoken_balance_key(token, key).map(|a| a.1) - }); + let owner: Option<&Address> = token::is_balance_key(token, key); match owner { None => { @@ -77,33 +74,10 @@ fn token_checks( } Some(owner) => { // accumulate the change - let pre: token::Change = match owner { - Address::Internal(InternalAddress::IbcMint) => { - token::Change::maximum() - } - Address::Internal(InternalAddress::IbcBurn) => { - token::Change::default() - } - _ => ctx - .read_pre::(key)? - .unwrap_or_default() - .change(), - }; - let post: token::Change = match owner { - Address::Internal(InternalAddress::IbcMint) => ctx - .read_temp::(key)? - .map(|x| x.change()) - .unwrap_or_else(token::Change::maximum), - Address::Internal(InternalAddress::IbcBurn) => ctx - .read_temp::(key)? - .unwrap_or_default() - .change(), - _ => ctx - .read_post::(key)? - .unwrap_or_default() - .change(), - }; - let this_change = post - pre; + let pre: token::Amount = ctx.read_pre(key)?.unwrap_or_default(); + let post: token::Amount = + ctx.read_post(key)?.unwrap_or_default(); + let this_change = post.change() - pre.change(); change += this_change; // make sure that the spender approved the transaction if !(this_change.non_negative() diff --git a/wasm/wasm_source/src/vp_user.rs b/wasm/wasm_source/src/vp_user.rs index b32d801ef2..dd86f09cbd 100644 --- a/wasm/wasm_source/src/vp_user.rs +++ b/wasm/wasm_source/src/vp_user.rs @@ -24,10 +24,9 @@ enum KeyType<'a> { impl<'a> From<&'a storage::Key> for KeyType<'a> { fn from(key: &'a storage::Key) -> KeyType<'a> { - if let Some([_, owner]) = token::is_any_token_balance_key(key) { - Self::Token { owner } - } else if let Some((_, [_, owner])) = - token::is_any_multitoken_balance_key(key) + if let Some(address) = token::is_any_token_balance_key(key) { + Self::Token(address) + } else if let Some((_, address)) = token::is_multitoken_balance_key(key) { Self::Token { owner } } else if proof_of_stake::is_pos_key(key) { diff --git a/wasm/wasm_source/src/vp_validator.rs b/wasm/wasm_source/src/vp_validator.rs index 9b10999d89..e79f51b629 100644 --- a/wasm/wasm_source/src/vp_validator.rs +++ b/wasm/wasm_source/src/vp_validator.rs @@ -26,10 +26,9 @@ enum KeyType<'a> { impl<'a> From<&'a storage::Key> for KeyType<'a> { fn from(key: &'a storage::Key) -> KeyType<'a> { - if let Some([_, owner]) = token::is_any_token_balance_key(key) { - Self::Token { owner } - } else if let Some((_, [_, owner])) = - token::is_any_multitoken_balance_key(key) + if let Some(address) = token::is_any_token_balance_key(key) { + Self::Token(address) + } else if let Some((_, address)) = token::is_multitoken_balance_key(key) { Self::Token { owner } } else if proof_of_stake::is_pos_key(key) { From 8f62edd92f2a8a40d040d9ce0d98e054351bb4ca Mon Sep 17 00:00:00 2001 From: yito88 Date: Thu, 8 Jun 2023 22:53:47 +0900 Subject: [PATCH 03/25] change to multitokens --- apps/src/lib/cli.rs | 25 -- apps/src/lib/client/rpc.rs | 69 +--- .../lib/node/ledger/shell/finalize_block.rs | 4 +- core/src/ledger/ibc/context/storage.rs | 5 +- core/src/ledger/ibc/context/transfer_mod.rs | 6 +- core/src/ledger/storage_api/token.rs | 19 +- core/src/types/token.rs | 96 ++--- shared/src/ledger/args.rs | 6 - shared/src/ledger/ibc/vp/context.rs | 30 +- shared/src/ledger/ibc/vp/token.rs | 377 ------------------ shared/src/ledger/protocol/mod.rs | 11 +- shared/src/ledger/queries/vp/token.rs | 22 +- shared/src/ledger/rpc.rs | 4 +- shared/src/ledger/tx.rs | 45 +-- test_utils/src/tx_data.rs | 26 ++ tests/src/e2e.rs | 1 - tests/src/e2e/ibc_tests.rs | 62 +-- tests/src/e2e/ledger_tests.rs | 1 - tests/src/e2e/multitoken_tests.rs | 372 ----------------- tests/src/e2e/multitoken_tests/helpers.rs | 189 --------- tests/src/vm_host_env/ibc.rs | 2 +- tests/src/vm_host_env/mod.rs | 12 +- tests/src/vm_host_env/tx.rs | 6 +- tx_prelude/src/ibc.rs | 11 +- tx_prelude/src/token.rs | 27 +- wasm/wasm_source/src/tx_bond.rs | 2 +- wasm/wasm_source/src/tx_transfer.rs | 2 - wasm/wasm_source/src/tx_unbond.rs | 7 +- wasm/wasm_source/src/tx_withdraw.rs | 7 +- wasm/wasm_source/src/vp_implicit.rs | 26 +- wasm/wasm_source/src/vp_testnet_faucet.rs | 13 +- wasm/wasm_source/src/vp_token.rs | 47 +-- wasm/wasm_source/src/vp_user.rs | 23 +- wasm/wasm_source/src/vp_validator.rs | 26 +- wasm_for_tests/wasm_source/src/lib.rs | 47 ++- 35 files changed, 222 insertions(+), 1406 deletions(-) delete mode 100644 shared/src/ledger/ibc/vp/token.rs delete mode 100644 tests/src/e2e/multitoken_tests.rs delete mode 100644 tests/src/e2e/multitoken_tests/helpers.rs diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 06d6f3f406..241524d881 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -2382,7 +2382,6 @@ pub mod args { pub const SOURCE: Arg = arg("source"); pub const SOURCE_OPT: ArgOpt = SOURCE.opt(); pub const STORAGE_KEY: Arg = arg("storage-key"); - pub const SUB_PREFIX: ArgOpt = arg_opt("sub-prefix"); pub const SUSPEND_ACTION: ArgFlag = flag("suspend"); pub const TIMEOUT_HEIGHT: ArgOpt = arg_opt("timeout-height"); pub const TIMEOUT_SEC_OFFSET: ArgOpt = arg_opt("timeout-sec-offset"); @@ -3070,7 +3069,6 @@ pub mod args { source: ctx.get_cached(&self.source), target: ctx.get(&self.target), token: ctx.get(&self.token), - sub_prefix: self.sub_prefix, amount: self.amount, native_token: ctx.native_token.clone(), tx_code_path: self.tx_code_path.to_path_buf(), @@ -3084,7 +3082,6 @@ pub mod args { let source = TRANSFER_SOURCE.parse(matches); let target = TRANSFER_TARGET.parse(matches); let token = TOKEN.parse(matches); - let sub_prefix = SUB_PREFIX.parse(matches); let amount = InputAmount::Unvalidated(AMOUNT.parse(matches)); let tx_code_path = PathBuf::from(TX_TRANSFER_WASM); Self { @@ -3092,7 +3089,6 @@ pub mod args { source, target, token, - sub_prefix, amount, native_token: (), tx_code_path, @@ -3110,7 +3106,6 @@ pub mod args { to produce the signature.", )) .arg(TOKEN.def().help("The transfer token.")) - .arg(SUB_PREFIX.def().help("The token's sub prefix.")) .arg(AMOUNT.def().help("The amount to transfer in decimal.")) } } @@ -3122,7 +3117,6 @@ pub mod args { source: ctx.get(&self.source), receiver: self.receiver, token: ctx.get(&self.token), - sub_prefix: self.sub_prefix, amount: self.amount, port_id: self.port_id, channel_id: self.channel_id, @@ -3139,7 +3133,6 @@ pub mod args { let source = SOURCE.parse(matches); let receiver = RECEIVER.parse(matches); let token = TOKEN.parse(matches); - let sub_prefix = SUB_PREFIX.parse(matches); let amount = AMOUNT.parse(matches); let port_id = PORT_ID.parse(matches); let channel_id = CHANNEL_ID.parse(matches); @@ -3151,7 +3144,6 @@ pub mod args { source, receiver, token, - sub_prefix, amount: amount.amount, port_id, channel_id, @@ -3171,7 +3163,6 @@ pub mod args { "The receiver address on the destination chain as string.", )) .arg(TOKEN.def().help("The transfer token.")) - .arg(SUB_PREFIX.def().help("The token's sub prefix.")) .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.")) @@ -3864,7 +3855,6 @@ pub mod args { owner: self.owner.map(|x| ctx.get_cached(&x)), token: self.token.map(|x| ctx.get(&x)), no_conversions: self.no_conversions, - sub_prefix: self.sub_prefix, } } } @@ -3875,13 +3865,11 @@ pub mod args { let owner = BALANCE_OWNER.parse(matches); let token = TOKEN_OPT.parse(matches); let no_conversions = NO_CONVERSIONS.parse(matches); - let sub_prefix = SUB_PREFIX.parse(matches); Self { query, owner, token, no_conversions, - sub_prefix, } } @@ -3902,11 +3890,6 @@ pub mod args { "Whether not to automatically perform conversions.", ), ) - .arg( - SUB_PREFIX - .def() - .help("The token's sub prefix whose balance to query."), - ) } } @@ -3916,7 +3899,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)), - sub_prefix: self.sub_prefix, } } } @@ -3926,12 +3908,10 @@ pub mod args { let query = Query::parse(matches); let owner = BALANCE_OWNER.parse(matches); let token = TOKEN_OPT.parse(matches); - let sub_prefix = SUB_PREFIX.parse(matches); Self { query, owner, token, - sub_prefix, } } @@ -3943,11 +3923,6 @@ pub mod args { .arg(TOKEN_OPT.def().help( "The token address that queried transfers must involve.", )) - .arg( - SUB_PREFIX - .def() - .help("The token's sub prefix whose balance to query."), - ) } } diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index da2078139d..b136ce17e0 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -305,43 +305,17 @@ pub async fn query_transparent_balance< let tokens = wallet.get_addresses_with_vp_type(AddressVpType::Token); match (args.token, args.owner) { (Some(token), Some(owner)) => { - let key = match &args.sub_prefix { - Some(sp) => { - let sub_prefix = - Address::decode(sp).expect("Invalid sub_prefix"); - token::multitoken_balance_key( - &sub_prefix, - &owner.address().unwrap(), - ) - } - None => ( - token::balance_key(&token, &owner.address().unwrap()), - None, - ), - }; + let balance_key = + token::balance_key(&token, &owner.address().unwrap()); let token_alias = lookup_alias(wallet, &token); match query_storage_value::(client, &balance_key) .await { Some(balance) => { - let balance = format_denominated_amount( - client, - &TokenAddress { - address: token, - sub_prefix, - }, - balance, - ) - .await; - match &args.sub_prefix { - Some(sub_prefix) => { - println!( - "{} with {}: {}", - token_alias, sub_prefix, balance - ); - } - None => println!("{}: {}", token_alias, balance), - } + let balance = + format_denominated_amount(client, &token, balance) + .await; + println!("{}: {}", token_alias, balance); } None => { println!("No {} balance found for {}", token_alias, owner) @@ -558,35 +532,16 @@ async fn print_balances( let print_num = balances .filter_map(|(key, balance)| { - match token::is_multitoken_balance_key(&key) { - Some((sub_prefix, owner)) => Some(( + token::is_balance_key(token, &key).map(|owner| { + ( owner.clone(), format!( - "with {}: {}, owned by {}", - sub_prefix, - balance, + ": {}, owned by {}", + format_denominated_amount(client, token, balance).await, lookup_alias(wallet, owner) ), - )), - None => token::is_any_token_balance_key(&key).map(|owner| { - ( - owner.clone(), - format!( - ": {}, owned by {}", - format_denominated_amount( - client, - &TokenAddress { - address: tok.clone(), - sub_prefix: None - }, - balance - ) - .await, - lookup_alias(wallet, owner) - ), - ) - }), - } + ) + }) }) .filter_map(|(o, s)| match target { Some(t) if o == *t => Some(s), diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 3af8ab9e3f..50e470c731 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -21,7 +21,7 @@ use namada::types::address::Address; use namada::types::dec::Dec; use namada::types::key::tm_raw_hash_to_string; use namada::types::storage::{BlockHash, BlockResults, Epoch, Header}; -use namada::types::token::{total_supply_key, Amount}; +use namada::types::token::{minted_balance_key, Amount}; use namada::types::transaction::protocol::{ ethereum_tx_data_variants, ProtocolTxType, }; @@ -707,7 +707,7 @@ where .expect("PoS inflation amount should exist in storage"); // Read from PoS storage let total_tokens = self - .read_storage_key(&total_supply_key(&staking_token_address( + .read_storage_key(&minted_balance_key(&staking_token_address( &self.wl_storage, ))) .expect("Total NAM balance should exist in storage"); diff --git a/core/src/ledger/ibc/context/storage.rs b/core/src/ledger/ibc/context/storage.rs index e047395a68..87555990a4 100644 --- a/core/src/ledger/ibc/context/storage.rs +++ b/core/src/ledger/ibc/context/storage.rs @@ -58,7 +58,6 @@ pub trait IbcStorageContext { src: &Address, dest: &Address, token: &Address, - sub_prefix: Option
, amount: Amount, ) -> Result<(), Self::Error>; @@ -66,7 +65,7 @@ pub trait IbcStorageContext { fn mint_token( &mut self, target: &Address, - sub_prefix: &Address, + token: &Address, amount: Amount, ) -> Result<(), Self::Error>; @@ -74,7 +73,7 @@ pub trait IbcStorageContext { fn burn_token( &mut self, target: &Address, - sub_prefix: &Address, + token: &Address, amount: Amount, ) -> Result<(), Self::Error>; diff --git a/core/src/ledger/ibc/context/transfer_mod.rs b/core/src/ledger/ibc/context/transfer_mod.rs index 9dfce35e6c..51ac801aec 100644 --- a/core/src/ledger/ibc/context/transfer_mod.rs +++ b/core/src/ledger/ibc/context/transfer_mod.rs @@ -447,14 +447,14 @@ where let (token, amount) = get_token_amount(coin)?; let ibc_token = if coin.denom.trace_path.is_empty() { - None + token } else { - Some(storage::ibc_token(coin.denom.to_string())) + storage::ibc_token(coin.denom.to_string()) }; self.ctx .borrow_mut() - .transfer_token(from, to, &token, ibc_token, amount) + .transfer_token(from, to, &ibc_token, amount) .map_err(|_| { TokenTransferError::ContextError(ContextError::ChannelError( ChannelError::Other { diff --git a/core/src/ledger/storage_api/token.rs b/core/src/ledger/storage_api/token.rs index 880d748274..383911d09a 100644 --- a/core/src/ledger/storage_api/token.rs +++ b/core/src/ledger/storage_api/token.rs @@ -7,8 +7,8 @@ use crate::types::storage::DbKeySeg::StringSeg; use crate::types::storage::Key; use crate::types::token; pub use crate::types::token::{ - balance_key, is_balance_key, is_total_supply_key, total_supply_key, Amount, - Change, + balance_key, is_balance_key, is_minted_balance_key, minted_balance_key, + minter_key, Amount, Change, }; /// Read the balance of a given token and owner. @@ -33,7 +33,7 @@ pub fn read_total_supply( where S: StorageRead, { - let key = token::total_supply_key(token); + let key = token::minted_balance_key(token); let balance = storage.read::(&key)?.unwrap_or_default(); Ok(balance) } @@ -44,17 +44,11 @@ where pub fn read_denom( storage: &S, token: &Address, - sub_prefix: Option<&Key>, ) -> storage_api::Result> where S: StorageRead, { - if let Some(sub_prefix) = sub_prefix { - if sub_prefix.segments.contains(&StringSeg("ibc".to_string())) { - return Ok(Some(token::NATIVE_MAX_DECIMAL_PLACES.into())); - } - } - let key = token::denom_key(token, sub_prefix); + let key = token::denom_key(token); storage.read(&key).map(|opt_denom| { Some( opt_denom @@ -67,13 +61,12 @@ where pub fn write_denom( storage: &mut S, token: &Address, - sub_prefix: Option<&Key>, denom: token::Denomination, ) -> storage_api::Result<()> where S: StorageRead + StorageWrite, { - let key = token::denom_key(token, sub_prefix); + let key = token::denom_key(token); storage.write(&key, denom) } @@ -132,7 +125,7 @@ where storage_api::Error::new_const("Token balance overflow") })?; - let total_supply_key = token::total_supply_key(token); + let total_supply_key = token::minted_balance_key(token); let cur_supply = storage .read::(&total_supply_key)? .unwrap_or_default(); diff --git a/core/src/types/token.rs b/core/src/types/token.rs index 2e12e06220..df50a5b1ba 100644 --- a/core/src/types/token.rs +++ b/core/src/types/token.rs @@ -779,7 +779,6 @@ pub const TX_KEY_PREFIX: &str = "tx-"; pub const CONVERSION_KEY_PREFIX: &str = "conv"; /// Key segment prefix for pinned shielded transactions pub const PIN_KEY_PREFIX: &str = "pin-"; -const TOTAL_SUPPLY_STORAGE_KEY: &str = "total_supply"; /// A fully qualified (multi-) token address. #[derive( @@ -831,47 +830,32 @@ impl Display for TokenAddress { /// Obtain a storage key for user's balance. pub fn balance_key(token_addr: &Address, owner: &Address) -> Key { - Key::from(token_addr.to_db_key()) - .push(&BALANCE_STORAGE_KEY.to_owned()) - .expect("Cannot obtain a storage key") + balance_prefix(token_addr) .push(&owner.to_db_key()) .expect("Cannot obtain a storage key") } /// Obtain a storage key prefix for all users' balances. pub fn balance_prefix(token_addr: &Address) -> Key { - Key::from(token_addr.to_db_key()) - .push(&BALANCE_STORAGE_KEY.to_owned()) - .expect("Cannot obtain a storage key") -} - -/// Obtain a storage key for user's multitoken balance. -pub fn multitoken_balance_key(prefix: &Address, owner: &Address) -> Key { Key::from(Address::Internal(InternalAddress::Multitoken).to_db_key()) - .push(prefix) + .push(&token_addr.to_db_key()) .expect("Cannot obtain a storage key") .push(&BALANCE_STORAGE_KEY.to_owned()) .expect("Cannot obtain a storage key") - .push(&owner.to_db_key()) - .expect("Cannot obtain a storage key") } /// Obtain a storage key for the multitoken minter. -pub fn multitoken_minter_key(prefix: &Address) -> Key { +pub fn minter_key(token_addr: &Address) -> Key { Key::from(Address::Internal(InternalAddress::Multitoken).to_db_key()) - .push(&MINTER_STORAGE_KEY.to_owned()) + .push(&token_addr.to_db_key()) .expect("Cannot obtain a storage key") - .push(prefix) + .push(&MINTER_STORAGE_KEY.to_owned()) .expect("Cannot obtain a storage key") } /// Obtain a storage key for the minted multitoken balance. -pub fn multitoken_minted_key(prefix: &Address) -> Key { - Key::from(Address::Internal(InternalAddress::Multitoken).to_db_key()) - .push(prefix) - .expect("Cannot obtain a storage key") - .push(&BALANCE_STORAGE_KEY.to_owned()) - .expect("Cannot obtain a storage key") +pub fn minted_balance_key(token_addr: &Address) -> Key { + balance_prefix(token_addr) .push(&Address::Internal(InternalAddress::Mint).to_db_key()) .expect("Cannot obtain a storage key") } @@ -885,9 +869,15 @@ pub fn is_balance_key<'a>( match &key.segments[..] { [ DbKeySeg::AddressSeg(addr), - DbKeySeg::StringSeg(key), + DbKeySeg::AddressSeg(token), + DbKeySeg::StringSeg(balance), DbKeySeg::AddressSeg(owner), - ] if key == BALANCE_STORAGE_KEY && addr == token_addr => Some(owner), + ] if *addr == Address::Internal(InternalAddress::Multitoken) + && token == token_addr + && balance == BALANCE_STORAGE_KEY => + { + Some(owner) + } _ => None, } } @@ -897,25 +887,24 @@ pub fn is_balance_key<'a>( pub fn is_any_token_balance_key(key: &Key) -> Option<[&Address; 2]> { match &key.segments[..] { [ + DbKeySeg::AddressSeg(addr), DbKeySeg::AddressSeg(token), - DbKeySeg::StringSeg(key), + DbKeySeg::StringSeg(balance), DbKeySeg::AddressSeg(owner), - ] if key == BALANCE_STORAGE_KEY => Some([token, owner]), + ] if *addr == Address::Internal(InternalAddress::Multitoken) + && balance == BALANCE_STORAGE_KEY => + { + Some([token, owner]) + } _ => None, } } /// Obtain a storage key denomination of a token. -pub fn denom_key(token_addr: &Address, sub_prefix: Option<&Key>) -> Key { - match sub_prefix { - Some(sub) => Key::from(token_addr.to_db_key()) - .join(sub) - .push(&DENOM_STORAGE_KEY.to_owned()) - .expect("Cannot obtain a storage key"), - None => Key::from(token_addr.to_db_key()) - .push(&DENOM_STORAGE_KEY.to_owned()) - .expect("Cannot obtain a storage key"), - } +pub fn denom_key(token_addr: &Address) -> Key { + Key::from(token_addr.to_db_key()) + .push(&DENOM_STORAGE_KEY.to_owned()) + .expect("Cannot obtain a storage key") } /// Check if the given storage key is a denomination key for the given token. @@ -938,33 +927,12 @@ pub fn is_masp_key(key: &Key) -> bool { || key.starts_with(PIN_KEY_PREFIX))) } -/// Storage key for total supply of a token -pub fn total_supply_key(token_address: &Address) -> Key { - Key::from(token_address.to_db_key()) - .push(&TOTAL_SUPPLY_STORAGE_KEY.to_owned()) - .expect("Cannot obtain a storage key") -} - /// Is storage key for total supply of a specific token? -pub fn is_total_supply_key(key: &Key, token_address: &Address) -> bool { - matches!(&key.segments[..], [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(key)] if addr == token_address && key == TOTAL_SUPPLY_STORAGE_KEY) -} - -/// Check if the given storage key is multitoken balance key for the given -/// token. If it is, returns the sub prefix and the owner. -pub fn is_multitoken_balance_key(key: &Key) -> Option<(&Address, &Address)> { - match &key.segments[..] { - [ - DbKeySeg::AddressSeg(addr), - DbKeySeg::AddressSeg(sub_prefix), - DbKeySeg::StringSeg(balance), - DbKeySeg::AddressSeg(owner), - ] if *addr == Address::Internal(InternalAddress::Multitoken) - && balance == BALANCE_STORAGE_KEY => - { - Some((sub_prefix, owner)) - } - _ => None, +pub fn is_minted_balance_key(token_addr: &Address, key: &Key) -> bool { + if let Some(owner) = is_balance_key(token_addr, key) { + *owner == Address::Internal(InternalAddress::Mint) + } else { + false } } @@ -989,8 +957,6 @@ pub struct Transfer { pub target: Address, /// Token's address pub token: Address, - /// Source token's sub prefix - pub sub_prefix: Option
, /// The amount of tokens pub amount: DenominatedAmount, /// The unused storage location at which to place TxId diff --git a/shared/src/ledger/args.rs b/shared/src/ledger/args.rs index 35081d3283..ba1f071fb8 100644 --- a/shared/src/ledger/args.rs +++ b/shared/src/ledger/args.rs @@ -115,8 +115,6 @@ pub struct TxTransfer { pub target: C::TransferTarget, /// Transferred token address pub token: C::Address, - /// Transferred token address - pub sub_prefix: Option, /// Transferred token amount pub amount: InputAmount, /// Native token address @@ -147,8 +145,6 @@ pub struct TxIbcTransfer { pub receiver: String, /// Transferred token addres s pub token: C::Address, - /// Transferred token address - pub sub_prefix: Option, /// Transferred token amount pub amount: token::Amount, /// Port ID @@ -317,8 +313,6 @@ pub struct QueryBalance { pub token: Option, /// Whether not to convert balances pub no_conversions: bool, - /// Sub prefix of an account - pub sub_prefix: Option, } /// Query historical transfer(s) diff --git a/shared/src/ledger/ibc/vp/context.rs b/shared/src/ledger/ibc/vp/context.rs index cee112854e..17697f181c 100644 --- a/shared/src/ledger/ibc/vp/context.rs +++ b/shared/src/ledger/ibc/vp/context.rs @@ -120,17 +120,10 @@ where src: &Address, dest: &Address, token: &Address, - sub_prefix: Option
, amount: Amount, ) -> Result<(), Self::Error> { - let src_key = match &sub_prefix { - Some(sub_prefix) => token::multitoken_balance_key(sub_prefix, src), - None => token::balance_key(token, src), - }; - let dest_key = match &sub_prefix { - Some(sub_prefix) => token::multitoken_balance_key(sub_prefix, dest), - None => token::balance_key(token, dest), - }; + let src_key = token::balance_key(token, src); + let dest_key = token::balance_key(token, dest); let src_bal: Option = self.ctx.read(&src_key).map_err(Error::NativeVpError)?; let mut src_bal = src_bal.expect("The source has no balance"); @@ -155,10 +148,10 @@ where fn mint_token( &mut self, target: &Address, - sub_prefix: &Address, + token: &Address, amount: Amount, ) -> Result<(), Self::Error> { - let target_key = token::multitoken_balance_key(sub_prefix, target); + let target_key = token::balance_key(token, target); let mut target_bal: Amount = self .ctx .read(&target_key) @@ -166,7 +159,7 @@ where .unwrap_or_default(); target_bal.receive(&amount); - let minted_key = token::multitoken_minted_key(sub_prefix); + let minted_key = token::minted_balance_key(token); let mut minted_bal: Amount = self .ctx .read(&minted_key) @@ -183,7 +176,7 @@ where minted_bal.try_to_vec().expect("encoding shouldn't failed"), )?; - let minter_key = token::multitoken_minter_key(sub_prefix); + let minter_key = token::minter_key(token); self.write( &minter_key, Address::Internal(InternalAddress::Ibc) @@ -195,10 +188,10 @@ where fn burn_token( &mut self, target: &Address, - sub_prefix: &Address, + token: &Address, amount: Amount, ) -> Result<(), Self::Error> { - let target_key = token::multitoken_balance_key(sub_prefix, target); + let target_key = token::balance_key(token, target); let mut target_bal: Amount = self .ctx .read(&target_key) @@ -206,7 +199,7 @@ where .unwrap_or_default(); target_bal.spend(&amount); - let minted_key = token::multitoken_minted_key(sub_prefix); + let minted_key = token::minted_balance_key(token); let mut minted_bal: Amount = self .ctx .read(&minted_key) @@ -320,7 +313,6 @@ where _src: &Address, _dest: &Address, _token: &Address, - _sub_prefix: Option
, _amount: Amount, ) -> Result<(), Self::Error> { unimplemented!("Validation doesn't transfer") @@ -329,7 +321,7 @@ where fn mint_token( &mut self, _target: &Address, - _sub_prefix: &Address, + _token: &Address, _amount: Amount, ) -> Result<(), Self::Error> { unimplemented!("Validation doesn't mint") @@ -338,7 +330,7 @@ where fn burn_token( &mut self, _target: &Address, - _sub_prefix: &Address, + _token: &Address, _amount: Amount, ) -> Result<(), Self::Error> { unimplemented!("Validation doesn't burn") diff --git a/shared/src/ledger/ibc/vp/token.rs b/shared/src/ledger/ibc/vp/token.rs deleted file mode 100644 index 204fa21ea0..0000000000 --- a/shared/src/ledger/ibc/vp/token.rs +++ /dev/null @@ -1,377 +0,0 @@ -//! IBC token transfer validation as a native validity predicate - -use std::collections::{BTreeSet, HashMap, HashSet}; - -use borsh::BorshDeserialize; -use prost::Message; -use thiserror::Error; - -use crate::ibc::applications::transfer::coin::PrefixedCoin; -use crate::ibc::applications::transfer::error::TokenTransferError; -use crate::ibc::applications::transfer::msgs::transfer::{ - MsgTransfer, TYPE_URL as MSG_TRANSFER_TYPE_URL, -}; -use crate::ibc::applications::transfer::packet::PacketData; -use crate::ibc::applications::transfer::{ - is_receiver_chain_source, is_sender_chain_source, -}; -use crate::ibc::core::ics04_channel::msgs::PacketMsg; -use crate::ibc::core::ics04_channel::packet::Packet; -use crate::ibc::core::ics26_routing::error::RouterError; -use crate::ibc::core::ics26_routing::msgs::MsgEnvelope; -use crate::ibc_proto::google::protobuf::Any; -use crate::ledger::ibc::storage as ibc_storage; -use crate::ledger::native_vp::{self, Ctx, NativeVp, VpEnv}; -use crate::ledger::storage::{self as ledger_storage, StorageHasher}; -use crate::proto::Tx; -use crate::types::address::{Address, InternalAddress}; -use crate::types::storage::Key; -use crate::types::token::{self, Amount, AmountParseError}; -use crate::vm::WasmCacheAccess; - -#[allow(missing_docs)] -#[derive(Error, Debug)] -pub enum Error { - #[error("Native VP error: {0}")] - NativeVpError(native_vp::Error), - #[error("IBC message error: {0}")] - IbcMessage(RouterError), - #[error("Invalid message")] - InvalidMessage, - #[error("Parsing amount error: {0}")] - Amount(AmountParseError), - #[error("Decoding error: {0}")] - Decoding(std::io::Error), - #[error("Decoding IBC data error: {0}")] - DecodingIbcData(prost::DecodeError), - #[error("Decoding PacketData error: {0}")] - DecodingPacketData(serde_json::Error), - #[error("IBC message is required as transaction data")] - NoTxData, - #[error("Invalid denom: {0}")] - Denom(String), - #[error("Invalid MsgTransfer: {0}")] - MsgTransfer(TokenTransferError), - #[error("Invalid token transfer: {0}")] - TokenTransfer(String), -} - -/// Result for IBC token VP -pub type Result = std::result::Result; - -/// IBC token VP to validate the transfer for an IBC-specific account. The -/// account is a sub-prefixed account with an IBC token hash, or a normal -/// account for `IbcEscrow`, `IbcBurn`, or `IbcMint`. -pub struct IbcToken<'a, DB, H, CA> -where - DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: StorageHasher, - CA: 'static + WasmCacheAccess, -{ - /// Context to interact with the host structures. - pub ctx: Ctx<'a, DB, H, CA>, -} - -impl<'a, DB, H, CA> NativeVp for IbcToken<'a, DB, H, CA> -where - DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: 'static + StorageHasher, - CA: 'static + WasmCacheAccess, -{ - type Error = Error; - - const ADDR: InternalAddress = InternalAddress::IbcBurn; - - fn validate_tx( - &self, - tx_data: &Tx, - keys_changed: &BTreeSet, - _verifiers: &BTreeSet
, - ) -> Result { - let signed = tx_data; - let tx_data = signed.data().ok_or(Error::NoTxData)?; - - // Check the non-onwer balance updates - let ibc_keys_changed: HashSet = keys_changed - .iter() - .filter(|k| { - matches!( - token::is_any_token_balance_key(k), - Some([ - _, - Address::Internal( - InternalAddress::IbcEscrow - | InternalAddress::IbcBurn - | InternalAddress::IbcMint - ) - ]) - ) - }) - .cloned() - .collect(); - if ibc_keys_changed.is_empty() { - // some multitoken balances are changed - let mut changes = HashMap::new(); - for key in keys_changed { - if let Some((sub_prefix, _)) = - token::is_any_multitoken_balance_key(key) - { - if !ibc_storage::is_ibc_sub_prefix(&sub_prefix) { - continue; - } - let pre: token::Amount = - self.ctx.read_pre(key)?.unwrap_or_default(); - let post: token::Amount = - self.ctx.read_post(key)?.unwrap_or_default(); - let this_change = post.change() - pre.change(); - let change: token::Change = - changes.get(&sub_prefix).cloned().unwrap_or_default(); - changes.insert(sub_prefix, change + this_change); - } - } - if changes.iter().all(|(_, c)| c.is_zero()) { - return Ok(true); - } else { - return Err(Error::TokenTransfer( - "Invalid transfer between different origin accounts" - .to_owned(), - )); - } - } else if ibc_keys_changed.len() > 1 { - // a transaction can update at most 1 special IBC account for now - return Err(Error::TokenTransfer( - "Invalid transfer for multiple non-owner balances".to_owned(), - )); - } - - // Check the message - let ibc_msg = - Any::decode(&tx_data[..]).map_err(Error::DecodingIbcData)?; - match ibc_msg.type_url.as_str() { - MSG_TRANSFER_TYPE_URL => { - let msg = MsgTransfer::try_from(ibc_msg) - .map_err(Error::MsgTransfer)?; - self.validate_sending_token(&msg) - } - _ => { - let envelope: MsgEnvelope = - ibc_msg.try_into().map_err(Error::IbcMessage)?; - match envelope { - MsgEnvelope::Packet(PacketMsg::Recv(msg)) => { - self.validate_receiving_token(&msg.packet) - } - MsgEnvelope::Packet(PacketMsg::Ack(msg)) => { - self.validate_refunding_token(&msg.packet) - } - MsgEnvelope::Packet(PacketMsg::Timeout(msg)) => { - self.validate_refunding_token(&msg.packet) - } - MsgEnvelope::Packet(PacketMsg::TimeoutOnClose(msg)) => { - self.validate_refunding_token(&msg.packet) - } - _ => Err(Error::InvalidMessage), - } - } - } - } -} - -impl<'a, DB, H, CA> IbcToken<'a, DB, H, CA> -where - DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: 'static + StorageHasher, - CA: 'static + WasmCacheAccess, -{ - fn validate_sending_token(&self, msg: &MsgTransfer) -> Result { - let mut coin = msg.token.clone(); - // lookup the original denom with the IBC token hash - if let Some(token_hash) = - ibc_storage::token_hash_from_denom(&coin.denom).map_err(|e| { - Error::Denom(format!("Invalid denom: error {}", e)) - })? - { - let denom_key = ibc_storage::ibc_denom_key(token_hash); - coin.denom = match self.ctx.read_bytes_pre(&denom_key) { - Ok(Some(v)) => String::try_from_slice(&v[..]).map_err(|e| { - Error::Denom(format!( - "Decoding the denom string failed: {}", - e - )) - })?, - _ => { - return Err(Error::Denom(format!( - "No original denom: denom_key {}", - denom_key - ))); - } - }; - } - let coin = PrefixedCoin::try_from(coin).map_err(Error::MsgTransfer)?; - let token = ibc_storage::token(coin.denom.to_string()) - .map_err(|e| Error::Denom(e.to_string()))?; - let amount = Amount::try_from(coin.amount).map_err(Error::Amount)?; - - // check the denomination field - let change = if is_sender_chain_source( - msg.port_id_on_a.clone(), - msg.chan_id_on_a.clone(), - &coin.denom, - ) { - // source zone - // check the amount of the token has been escrowed - let target_key = token::balance_key( - &token, - &Address::Internal(InternalAddress::IbcEscrow), - ); - let pre = - try_decode_token_amount(self.ctx.read_bytes_pre(&target_key)?)? - .unwrap_or_default(); - let post = try_decode_token_amount( - self.ctx.read_bytes_post(&target_key)?, - )? - .unwrap_or_default(); - post.change() - pre.change() - } else { - // sink zone - // check the amount of the token has been burned - let target_key = token::balance_key( - &token, - &Address::Internal(InternalAddress::IbcBurn), - ); - let post = try_decode_token_amount( - self.ctx.read_bytes_temp(&target_key)?, - )? - .unwrap_or_default(); - // the previous balance of the burn address should be zero - post.change() - }; - - if change == amount.change() { - Ok(true) - } else { - Err(Error::TokenTransfer(format!( - "Sending the token is invalid: coin {}", - coin, - ))) - } - } - - fn validate_receiving_token(&self, packet: &Packet) -> Result { - let data = serde_json::from_slice::(&packet.data) - .map_err(Error::DecodingPacketData)?; - let token = ibc_storage::token(data.token.denom.to_string()) - .map_err(|e| Error::Denom(e.to_string()))?; - let amount = - Amount::try_from(data.token.amount).map_err(Error::Amount)?; - - let change = if is_receiver_chain_source( - packet.port_id_on_a.clone(), - packet.chan_id_on_a.clone(), - &data.token.denom, - ) { - // this chain is the source - // check the amount of the token has been unescrowed - let source_key = token::balance_key( - &token, - &Address::Internal(InternalAddress::IbcEscrow), - ); - let pre = - try_decode_token_amount(self.ctx.read_bytes_pre(&source_key)?)? - .unwrap_or_default(); - let post = try_decode_token_amount( - self.ctx.read_bytes_post(&source_key)?, - )? - .unwrap_or_default(); - pre.change() - post.change() - } else { - // the sender is the source - // check the amount of the token has been minted - let source_key = token::balance_key( - &token, - &Address::Internal(InternalAddress::IbcMint), - ); - let post = try_decode_token_amount( - self.ctx.read_bytes_temp(&source_key)?, - )? - .unwrap_or_default(); - // the previous balance of the mint address should be the maximum - Amount::max_signed().change() - post.change() - }; - - if change == amount.change() { - Ok(true) - } else { - Err(Error::TokenTransfer(format!( - "Receivinging the token is invalid: coin {}", - data.token - ))) - } - } - - fn validate_refunding_token(&self, packet: &Packet) -> Result { - let data = serde_json::from_slice::(&packet.data) - .map_err(Error::DecodingPacketData)?; - let token = ibc_storage::token(data.token.denom.to_string()) - .map_err(|e| Error::Denom(e.to_string()))?; - let amount = - Amount::try_from(data.token.amount).map_err(Error::Amount)?; - - // check the denom field - let change = if is_sender_chain_source( - packet.port_id_on_a.clone(), - packet.chan_id_on_a.clone(), - &data.token.denom, - ) { - // source zone: unescrow the token for the refund - let source_key = token::balance_key( - &token, - &Address::Internal(InternalAddress::IbcEscrow), - ); - let pre = - try_decode_token_amount(self.ctx.read_bytes_pre(&source_key)?)? - .unwrap_or_default(); - let post = try_decode_token_amount( - self.ctx.read_bytes_post(&source_key)?, - )? - .unwrap_or_default(); - pre.change() - post.change() - } else { - // sink zone: mint the token for the refund - let source_key = token::balance_key( - &token, - &Address::Internal(InternalAddress::IbcMint), - ); - let post = try_decode_token_amount( - self.ctx.read_bytes_temp(&source_key)?, - )? - .unwrap_or_default(); - // the previous balance of the mint address should be the maximum - Amount::max_signed().change() - post.change() - }; - - if change == amount.change() { - Ok(true) - } else { - Err(Error::TokenTransfer(format!( - "Refunding the token is invalid: coin {}", - data.token, - ))) - } - } -} - -impl From for Error { - fn from(err: native_vp::Error) -> Self { - Self::NativeVpError(err) - } -} - -fn try_decode_token_amount( - bytes: Option>, -) -> Result> { - if let Some(bytes) = bytes { - let tokens = Amount::try_from_slice(&bytes).map_err(Error::Decoding)?; - return Ok(Some(tokens)); - } - Ok(None) -} diff --git a/shared/src/ledger/protocol/mod.rs b/shared/src/ledger/protocol/mod.rs index a71c2d0cae..c1a8484e45 100644 --- a/shared/src/ledger/protocol/mod.rs +++ b/shared/src/ledger/protocol/mod.rs @@ -551,9 +551,7 @@ where gas_meter = slash_fund.ctx.gas_meter.into_inner(); result } - InternalAddress::Multitoken - | InternalAddress::IbcToken(_) - | InternalAddress::Mint => { + InternalAddress::Multitoken => { let multitoken = MultitokenVp { ctx }; let result = multitoken .validate_tx(tx, &keys_changed, &verifiers) @@ -587,6 +585,13 @@ where replay_protection_vp.ctx.gas_meter.into_inner(); result } + InternalAddress::IbcToken(_) + | InternalAddress::Mint => { + // These addresses should be a part of a multitoken + // key + gas_meter = ctx.gas_meter.into_inner(); + Ok(true) + } }; accepted diff --git a/shared/src/ledger/queries/vp/token.rs b/shared/src/ledger/queries/vp/token.rs index cbad27005f..030bdddd53 100644 --- a/shared/src/ledger/queries/vp/token.rs +++ b/shared/src/ledger/queries/vp/token.rs @@ -8,8 +8,7 @@ use namada_core::types::token; use crate::ledger::queries::RequestCtx; router! {TOKEN, - ( "denomination" / [addr: Address] / [sub_prefix: opt Key] ) -> Option = denomination, - ( "denomination" / [addr: Address] / "ibc" / [_ibc_junk: String] ) -> Option = denomination_ibc, + ( "denomination" / [addr: Address] ) -> Option = denomination, } /// Get the number of decimal places (in base 10) for a @@ -17,27 +16,10 @@ router! {TOKEN, fn denomination( ctx: RequestCtx<'_, D, H>, addr: Address, - sub_prefix: Option, ) -> storage_api::Result> where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, { - read_denom(ctx.wl_storage, &addr, sub_prefix.as_ref()) -} - -// TODO Please fix this - -/// Get the number of decimal places (in base 10) for a -/// token specified by `addr`. -fn denomination_ibc( - ctx: RequestCtx<'_, D, H>, - addr: Address, - _ibc_junk: String, -) -> storage_api::Result> -where - D: 'static + DB + for<'iter> DBIter<'iter> + Sync, - H: 'static + StorageHasher + Sync, -{ - read_denom(ctx.wl_storage, &addr, None) + read_denom(ctx.wl_storage, &addr) } diff --git a/shared/src/ledger/rpc.rs b/shared/src/ledger/rpc.rs index 90d05ebd13..28431a7ffb 100644 --- a/shared/src/ledger/rpc.rs +++ b/shared/src/ledger/rpc.rs @@ -1061,13 +1061,13 @@ pub async fn format_denominated_amount< C: crate::ledger::queries::Client + Sync, >( client: &C, - token: &TokenAddress, + token: &Address, amount: token::Amount, ) -> String { let denom = unwrap_client_response::>( RPC.vp() .token() - .denomination(client, &token.address, &token.sub_prefix) + .denomination(client, &token.address) .await, ) .unwrap_or_else(|| { diff --git a/shared/src/ledger/tx.rs b/shared/src/ledger/tx.rs index 6b11cac281..6b676eb020 100644 --- a/shared/src/ledger/tx.rs +++ b/shared/src/ledger/tx.rs @@ -44,7 +44,7 @@ use crate::tendermint_rpc::error::Error as RpcError; use crate::types::control_flow::{time, ProceedOrElse}; use crate::types::key::*; use crate::types::masp::TransferTarget; -use crate::types::storage::{Epoch, RESERVED_ADDRESS_PREFIX}; +use crate::types::storage::Epoch; use crate::types::time::DateTimeUtc; use crate::types::transaction::{pos, InitAccount, TxType, UpdateVp}; use crate::types::{storage, token}; @@ -1144,21 +1144,7 @@ pub async fn build_ibc_transfer< let token = token_exists_or_err(args.token, args.tx.force, client).await?; // Check source balance - let (sub_prefix, balance_key) = match args.sub_prefix { - Some(sp) => { - let sub_prefix = Address::decode(&sp).map_err(|e| { - Error::Other(format!( - "The sub_prefix was not an Address: sub_prefix {}, error \ - {}", - sp, e - )) - })?; - let balance_key = - token::multitoken_balance_key(&sub_prefix, &source); - (Some(sub_prefix), balance_key) - } - None => (None, token::balance_key(&token, &source)), - }; + let balance_key = token::balance_key(&token, &source); check_balance_too_low_err( &token, @@ -1175,11 +1161,6 @@ pub async fn build_ibc_transfer< .await .unwrap(); - let denom = match sub_prefix { - // To parse IbcToken address, remove the address prefix - Some(sp) => sp.to_string().replace(RESERVED_ADDRESS_PREFIX, ""), - None => token.to_string(), - }; let amount = args .amount .to_string_native() @@ -1187,7 +1168,10 @@ pub async fn build_ibc_transfer< .next() .expect("invalid amount") .to_string(); - let token = Coin { denom, amount }; + let token = Coin { + denom: token.to_string(), + amount, + }; // this height should be that of the destination chain, not this chain let timeout_height = match args.timeout_height { @@ -1337,21 +1321,7 @@ pub async fn build_transfer< // Check that the token address exists on chain token_exists_or_err(token.clone(), args.tx.force, client).await?; // Check source balance - let (sub_prefix, balance_key) = match &args.sub_prefix { - Some(sp) => { - let sub_prefix = Address::decode(sp).map_err(|e| { - Error::Other(format!( - "The sub_prefix was not an Address: sub_prefix {}, error \ - {}", - sp, e - )) - })?; - let balance_key = - token::multitoken_balance_key(&sub_prefix, &source); - (Some(sub_prefix), balance_key) - } - None => (None, token::balance_key(&token, &source)), - }; + let balance_key = token::balance_key(&token, &source); // validate the amount given let validated_amount = validate_amount( @@ -1476,7 +1446,6 @@ pub async fn build_transfer< source: source.clone(), target: target.clone(), token: token.clone(), - sub_prefix: sub_prefix.clone(), amount: validated_amount, key: key.clone(), // Link the Transfer to the MASP Transaction by hash code diff --git a/test_utils/src/tx_data.rs b/test_utils/src/tx_data.rs index 878217bc2c..a985479237 100644 --- a/test_utils/src/tx_data.rs +++ b/test_utils/src/tx_data.rs @@ -2,7 +2,9 @@ //! Namada transaction. use borsh::{BorshDeserialize, BorshSerialize}; +use namada_core::types::address::Address; use namada_core::types::storage; +use namada_core::types::token::Amount; /// Represents an arbitrary write to storage at the specified key. This should /// be used alongside the test `tx_write.wasm`. @@ -23,3 +25,27 @@ pub struct TxWriteData { /// The bytes to be written. pub value: Vec, } + +/// Represents minting of the specified token. This should +/// be used alongside the test `tx_mint_tokens.wasm`. +#[derive( + Clone, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + BorshSerialize, + BorshDeserialize, +)] +pub struct TxMintData { + /// The minter to mint the token + pub minter: Address, + /// The minted target + pub target: Address, + /// The minted token + pub token: Address, + /// The minted amount + pub amount: Amount, +} diff --git a/tests/src/e2e.rs b/tests/src/e2e.rs index ffe2ec0d86..10a6f69d6e 100644 --- a/tests/src/e2e.rs +++ b/tests/src/e2e.rs @@ -16,6 +16,5 @@ pub mod eth_bridge_tests; pub mod helpers; pub mod ibc_tests; pub mod ledger_tests; -pub mod multitoken_tests; pub mod setup; pub mod wallet_tests; diff --git a/tests/src/e2e/ibc_tests.rs b/tests/src/e2e/ibc_tests.rs index 15356d3f71..a7f89d9a9f 100644 --- a/tests/src/e2e/ibc_tests.rs +++ b/tests/src/e2e/ibc_tests.rs @@ -718,7 +718,6 @@ fn transfer_token( &Amount::native_whole(100000), port_channel_id_a, None, - None, )?; let packet = match get_event(test_a, height)? { Some(IbcEvent::SendPacket(event)) => event.packet, @@ -787,8 +786,6 @@ fn transfer_received_token( "--target", ALBERT, "--token", - NAM, - "--sub-prefix", &ibc_token, "--amount", &amount, @@ -830,10 +827,9 @@ fn transfer_back( test_b, BERTHA, &receiver, - NAM, + ibc_token, &Amount::native_whole(50000), port_channel_id_b, - Some(ibc_token), None, )?; let packet = match get_event(test_b, height)? { @@ -892,7 +888,6 @@ fn transfer_timeout( NAM, &Amount::native_whole(100000), port_channel_id_a, - None, Some(Duration::new(5, 0)), )?; let packet = match get_event(test_a, height)? { @@ -1026,7 +1021,6 @@ fn transfer( token: impl AsRef, amount: &Amount, port_channel_id: &PortChannelId, - sub_prefix: Option, timeout_sec: Option, ) -> Result { let rpc = get_actor_rpc(test, &Who::Validator(0)); @@ -1054,11 +1048,7 @@ fn transfer( "--node", &rpc, ]; - let sp = sub_prefix.clone().unwrap_or_default(); - if sub_prefix.is_some() { - tx_args.push("--sub-prefix"); - tx_args.push(&sp); - } + let timeout = timeout_sec.unwrap_or_default().as_secs().to_string(); if timeout_sec.is_some() { tx_args.push("--timeout-sec-offset"); @@ -1278,17 +1268,9 @@ fn check_balances( let ibc_token = ibc_token(denom).to_string(); let rpc_b = get_actor_rpc(test_b, &Who::Validator(0)); let query_args = vec![ - "balance", - "--owner", - BERTHA, - "--token", - NAM, - "--sub-prefix", - &ibc_token, - "--node", - &rpc_b, + "balance", "--owner", BERTHA, "--token", &ibc_token, "--node", &rpc_b, ]; - let expected = format!("nam with {}: 100000", ibc_token); + let expected = format!("{}: 100000", ibc_token); let mut client = run!(test_b, Bin::Client, query_args, Some(40))?; client.exp_string(&expected)?; client.assert_success(); @@ -1311,34 +1293,18 @@ 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, - "--sub-prefix", - &ibc_token, - "--node", - &rpc, + "balance", "--owner", BERTHA, "--token", &ibc_token, "--node", &rpc, ]; - let expected = format!("nam with {}: 50000", ibc_token); + let expected = format!("{}: 50000", ibc_token); 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, - "--sub-prefix", - &ibc_token, - "--node", - &rpc, + "balance", "--owner", ALBERT, "--token", &ibc_token, "--node", &rpc, ]; - let expected = format!("nam with {}: 50000", ibc_token); + let expected = format!("{}: 50000", ibc_token); let mut client = run!(test, Bin::Client, query_args, Some(40))?; client.exp_string(&expected)?; client.assert_success(); @@ -1377,17 +1343,9 @@ fn check_balances_after_back( let ibc_token = ibc_token(denom).to_string(); let rpc_b = get_actor_rpc(test_b, &Who::Validator(0)); let query_args = vec![ - "balance", - "--owner", - BERTHA, - "--token", - NAM, - "--sub-prefix", - &ibc_token, - "--node", - &rpc_b, + "balance", "--owner", BERTHA, "--token", &ibc_token, "--node", &rpc_b, ]; - let expected = format!("nam with {}: 0", ibc_token); + let expected = format!("{}: 0", ibc_token); let mut client = run!(test_b, Bin::Client, query_args, Some(40))?; client.exp_string(&expected)?; client.assert_success(); diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index ca7bd974c7..9797dadf58 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -422,7 +422,6 @@ fn ledger_txs_and_queries() -> Result<()> { source: find_address(&test, BERTHA).unwrap(), target: find_address(&test, ALBERT).unwrap(), token: find_address(&test, NAM).unwrap(), - sub_prefix: None, amount: token::DenominatedAmount { amount: token::Amount::native_whole(10), denom: token::NATIVE_MAX_DECIMAL_PLACES.into(), diff --git a/tests/src/e2e/multitoken_tests.rs b/tests/src/e2e/multitoken_tests.rs deleted file mode 100644 index 0f2b15d877..0000000000 --- a/tests/src/e2e/multitoken_tests.rs +++ /dev/null @@ -1,372 +0,0 @@ -//! Tests for multitoken functionality -use color_eyre::eyre::Result; -use namada_core::types::token; - -use super::helpers::get_actor_rpc; -use super::setup::constants::{ALBERT, BERTHA, CHRISTEL}; -use super::setup::{self, Who}; -use crate::e2e; -use crate::e2e::setup::constants::{ALBERT_KEY, BERTHA_KEY}; - -mod helpers; - -#[test] -fn test_multitoken_transfer_implicit_to_implicit() -> Result<()> { - let (test, _ledger) = e2e::helpers::setup_single_node_test()?; - - let rpc_addr = get_actor_rpc(&test, &Who::Validator(0)); - let multitoken_alias = helpers::init_multitoken_vp(&test, &rpc_addr)?; - - // establish a multitoken VP with the following balances - // - #atest5blah/tokens/red/balance/$albert_established = 100 - // - #atest5blah/tokens/red/balance/$bertha = 0 - - let multitoken_vp_addr = - e2e::helpers::find_address(&test, &multitoken_alias)?; - println!("Fake multitoken VP established at {}", multitoken_vp_addr); - - let albert_addr = e2e::helpers::find_address(&test, ALBERT)?; - let albert_starting_red_balance = token::Amount::native_whole(100_000_000); - helpers::mint_red_tokens( - &test, - &rpc_addr, - &multitoken_vp_addr, - &albert_addr, - &albert_starting_red_balance, - )?; - - let transfer_amount = token::Amount::native_whole(10_000_000); - - // make a transfer from Albert to Bertha, signed by Christel - this should - // be rejected - let mut unauthorized_transfer = helpers::attempt_red_tokens_transfer( - &test, - &rpc_addr, - &multitoken_alias, - ALBERT, - BERTHA, - CHRISTEL, - &transfer_amount, - )?; - unauthorized_transfer.exp_string("Transaction applied with result")?; - unauthorized_transfer.exp_string("Transaction is invalid")?; - unauthorized_transfer.exp_string(&format!("Rejected: {albert_addr}"))?; - unauthorized_transfer.assert_success(); - - let albert_balance = helpers::fetch_red_token_balance( - &test, - &rpc_addr, - &multitoken_alias, - ALBERT, - )?; - assert_eq!(albert_balance, albert_starting_red_balance); - - // make a transfer from Albert to Bertha, signed by Albert - this should - // be accepted - let mut authorized_transfer = helpers::attempt_red_tokens_transfer( - &test, - &rpc_addr, - &multitoken_alias, - ALBERT, - BERTHA, - ALBERT, - &token::Amount::native_whole(10_000_000), - )?; - authorized_transfer.exp_string("Transaction applied with result")?; - authorized_transfer.exp_string("Transaction is valid")?; - authorized_transfer.assert_success(); - - let albert_balance = helpers::fetch_red_token_balance( - &test, - &rpc_addr, - &multitoken_alias, - ALBERT, - )?; - assert_eq!( - albert_balance, - albert_starting_red_balance - transfer_amount - ); - Ok(()) -} - -#[test] -fn test_multitoken_transfer_established_to_implicit() -> Result<()> { - let (test, _ledger) = e2e::helpers::setup_single_node_test()?; - - let rpc_addr = get_actor_rpc(&test, &Who::Validator(0)); - let multitoken_alias = helpers::init_multitoken_vp(&test, &rpc_addr)?; - - let multitoken_vp_addr = - e2e::helpers::find_address(&test, &multitoken_alias)?; - println!("Fake multitoken VP established at {}", multitoken_vp_addr); - - // create an established account that Albert controls - let established_alias = "established"; - e2e::helpers::init_established_account( - &test, - &rpc_addr, - ALBERT, - ALBERT_KEY, - established_alias, - )?; - - let established_starting_red_balance = - token::Amount::native_whole(100_000_000); - // mint some red tokens for the established account - let established_addr = - e2e::helpers::find_address(&test, established_alias)?; - helpers::mint_red_tokens( - &test, - &rpc_addr, - &multitoken_vp_addr, - &established_addr, - &established_starting_red_balance, - )?; - - let transfer_amount = token::Amount::native_whole(10_000_000); - // attempt an unauthorized transfer to Albert from the established account - let mut unauthorized_transfer = helpers::attempt_red_tokens_transfer( - &test, - &rpc_addr, - &multitoken_alias, - established_alias, - BERTHA, - CHRISTEL, - &transfer_amount, - )?; - unauthorized_transfer.exp_string("Transaction applied with result")?; - unauthorized_transfer.exp_string("Transaction is invalid")?; - unauthorized_transfer - .exp_string(&format!("Rejected: {established_addr}"))?; - unauthorized_transfer.assert_success(); - - let established_balance = helpers::fetch_red_token_balance( - &test, - &rpc_addr, - &multitoken_alias, - established_alias, - )?; - assert_eq!(established_balance, established_starting_red_balance); - - // attempt an authorized transfer to Albert from the established account - let mut authorized_transfer = helpers::attempt_red_tokens_transfer( - &test, - &rpc_addr, - &multitoken_alias, - established_alias, - BERTHA, - ALBERT, - &transfer_amount, - )?; - authorized_transfer.exp_string("Transaction applied with result")?; - authorized_transfer.exp_string("Transaction is valid")?; - authorized_transfer.assert_success(); - - let established_balance = helpers::fetch_red_token_balance( - &test, - &rpc_addr, - &multitoken_alias, - established_alias, - )?; - assert_eq!( - established_balance, - established_starting_red_balance - transfer_amount - ); - - Ok(()) -} - -#[test] -fn test_multitoken_transfer_implicit_to_established() -> Result<()> { - let (test, _ledger) = e2e::helpers::setup_single_node_test()?; - - let rpc_addr = get_actor_rpc(&test, &Who::Validator(0)); - let multitoken_alias = helpers::init_multitoken_vp(&test, &rpc_addr)?; - - let multitoken_vp_addr = - e2e::helpers::find_address(&test, &multitoken_alias)?; - println!("Fake multitoken VP established at {}", multitoken_vp_addr); - - // create an established account controlled by Bertha - let established_alias = "established"; - e2e::helpers::init_established_account( - &test, - &rpc_addr, - BERTHA, - BERTHA_KEY, - established_alias, - )?; - - let albert_addr = e2e::helpers::find_address(&test, ALBERT)?; - let albert_starting_red_balance = token::Amount::native_whole(100_000_000); - helpers::mint_red_tokens( - &test, - &rpc_addr, - &multitoken_vp_addr, - &albert_addr, - &albert_starting_red_balance, - )?; - - let transfer_amount = token::Amount::native_whole(10_000_000); - - // attempt an unauthorized transfer from Albert to the established account - let mut unauthorized_transfer = helpers::attempt_red_tokens_transfer( - &test, - &rpc_addr, - &multitoken_alias, - ALBERT, - established_alias, - CHRISTEL, - &transfer_amount, - )?; - unauthorized_transfer.exp_string("Transaction applied with result")?; - unauthorized_transfer.exp_string("Transaction is invalid")?; - unauthorized_transfer.exp_string(&format!("Rejected: {albert_addr}"))?; - unauthorized_transfer.assert_success(); - - let albert_balance = helpers::fetch_red_token_balance( - &test, - &rpc_addr, - &multitoken_alias, - ALBERT, - )?; - assert_eq!(albert_balance, albert_starting_red_balance); - - // attempt an authorized transfer to Albert from the established account - let mut authorized_transfer = helpers::attempt_red_tokens_transfer( - &test, - &rpc_addr, - &multitoken_alias, - ALBERT, - established_alias, - ALBERT, - &transfer_amount, - )?; - authorized_transfer.exp_string("Transaction applied with result")?; - authorized_transfer.exp_string("Transaction is valid")?; - authorized_transfer.assert_success(); - - let albert_balance = helpers::fetch_red_token_balance( - &test, - &rpc_addr, - &multitoken_alias, - ALBERT, - )?; - assert_eq!( - albert_balance, - albert_starting_red_balance - transfer_amount - ); - - Ok(()) -} - -#[test] -fn test_multitoken_transfer_established_to_established() -> Result<()> { - let (test, _ledger) = e2e::helpers::setup_single_node_test()?; - - let rpc_addr = get_actor_rpc(&test, &Who::Validator(0)); - let multitoken_alias = helpers::init_multitoken_vp(&test, &rpc_addr)?; - - let multitoken_vp_addr = - e2e::helpers::find_address(&test, &multitoken_alias)?; - println!("Fake multitoken VP established at {}", multitoken_vp_addr); - - // create an established account that Albert controls - let established_alias = "established"; - e2e::helpers::init_established_account( - &test, - &rpc_addr, - ALBERT, - ALBERT_KEY, - established_alias, - )?; - - let established_starting_red_balance = - token::Amount::native_whole(100_000_000); - // mint some red tokens for the established account - let established_addr = - e2e::helpers::find_address(&test, established_alias)?; - helpers::mint_red_tokens( - &test, - &rpc_addr, - &multitoken_vp_addr, - &established_addr, - &established_starting_red_balance, - )?; - - // create another established account to receive transfers - let receiver_alias = "receiver"; - e2e::helpers::init_established_account( - &test, - &rpc_addr, - BERTHA, - BERTHA_KEY, - receiver_alias, - )?; - - let established_starting_red_balance = - token::Amount::native_whole(100_000_000); - // mint some red tokens for the established account - let established_addr = - e2e::helpers::find_address(&test, established_alias)?; - helpers::mint_red_tokens( - &test, - &rpc_addr, - &multitoken_vp_addr, - &established_addr, - &established_starting_red_balance, - )?; - - let transfer_amount = token::Amount::native_whole(10_000_000); - - // attempt an unauthorized transfer - let mut unauthorized_transfer = helpers::attempt_red_tokens_transfer( - &test, - &rpc_addr, - &multitoken_alias, - established_alias, - receiver_alias, - CHRISTEL, - &transfer_amount, - )?; - unauthorized_transfer.exp_string("Transaction applied with result")?; - unauthorized_transfer.exp_string("Transaction is invalid")?; - unauthorized_transfer - .exp_string(&format!("Rejected: {established_addr}"))?; - unauthorized_transfer.assert_success(); - - let established_balance = helpers::fetch_red_token_balance( - &test, - &rpc_addr, - &multitoken_alias, - established_alias, - )?; - assert_eq!(established_balance, established_starting_red_balance); - - // attempt an authorized transfer which should succeed - let mut authorized_transfer = helpers::attempt_red_tokens_transfer( - &test, - &rpc_addr, - &multitoken_alias, - established_alias, - receiver_alias, - ALBERT, - &transfer_amount, - )?; - authorized_transfer.exp_string("Transaction applied with result")?; - authorized_transfer.exp_string("Transaction is valid")?; - authorized_transfer.assert_success(); - - let established_balance = helpers::fetch_red_token_balance( - &test, - &rpc_addr, - &multitoken_alias, - established_alias, - )?; - assert_eq!( - established_balance, - established_starting_red_balance - transfer_amount - ); - - Ok(()) -} diff --git a/tests/src/e2e/multitoken_tests/helpers.rs b/tests/src/e2e/multitoken_tests/helpers.rs deleted file mode 100644 index 27228cf266..0000000000 --- a/tests/src/e2e/multitoken_tests/helpers.rs +++ /dev/null @@ -1,189 +0,0 @@ -//! Helpers for use in multitoken tests. -use std::path::PathBuf; - -use borsh::BorshSerialize; -use color_eyre::eyre::Result; -use eyre::Context; -use namada_core::types::address::Address; -use namada_core::types::token::NATIVE_MAX_DECIMAL_PLACES; -use namada_core::types::{storage, token}; -use namada_test_utils::tx_data::TxWriteData; -use namada_test_utils::TestWasms; -use namada_tx_prelude::storage::KeySeg; -use rand::Rng; -use regex::Regex; - -use super::setup::constants::NAM; -use super::setup::{Bin, NamadaCmd, Test}; -use crate::e2e::setup::constants::ALBERT; -use crate::run; - -const MULTITOKEN_KEY_SEGMENT: &str = "tokens"; -const BALANCE_KEY_SEGMENT: &str = "balance"; -const RED_TOKEN_KEY_SEGMENT: &str = "red"; -const MULTITOKEN_RED_TOKEN_SUB_PREFIX: &str = "tokens/red"; - -const ARBITRARY_SIGNER: &str = ALBERT; - -/// Initializes a VP to represent a multitoken account. -pub fn init_multitoken_vp(test: &Test, rpc_addr: &str) -> Result { - // we use a VP that always returns true for the multitoken VP here, as we - // are testing out the VPs of the sender and receiver of multitoken - // transactions here - not any multitoken VP itself - let multitoken_vp_wasm_path = - TestWasms::VpAlwaysTrue.path().to_string_lossy().to_string(); - let multitoken_alias = "multitoken"; - - let init_account_args = vec![ - "init-account", - "--source", - ARBITRARY_SIGNER, - "--public-key", - // Value obtained from - // `namada::types::key::ed25519::tests::gen_keypair` - "001be519a321e29020fa3cbfbfd01bd5e92db134305609270b71dace25b5a21168", - "--code-path", - &multitoken_vp_wasm_path, - "--alias", - multitoken_alias, - "--gas-amount", - "0", - "--gas-limit", - "0", - "--gas-token", - NAM, - "--ledger-address", - rpc_addr, - ]; - let mut client_init_account = - run!(test, Bin::Client, init_account_args, Some(40))?; - client_init_account.exp_string("Transaction is valid.")?; - client_init_account.exp_string("Transaction applied")?; - client_init_account.assert_success(); - Ok(multitoken_alias.to_string()) -} - -/// Generates a random path within the `test` directory. -fn generate_random_test_dir_path(test: &Test) -> PathBuf { - let rng = rand::thread_rng(); - let random_string: String = rng - .sample_iter(&rand::distributions::Alphanumeric) - .take(24) - .map(char::from) - .collect(); - test.test_dir.path().join(random_string) -} - -/// Writes `contents` to a random path within the `test` directory, and return -/// the path. -pub fn write_test_file( - test: &Test, - contents: impl AsRef<[u8]>, -) -> Result { - let path = generate_random_test_dir_path(test); - std::fs::write(&path, contents)?; - Ok(path) -} - -/// Mint red tokens to the given address. -pub fn mint_red_tokens( - test: &Test, - rpc_addr: &str, - multitoken: &Address, - owner: &Address, - amount: &token::Amount, -) -> Result<()> { - let red_balance_key = storage::Key::from(multitoken.to_db_key()) - .push(&MULTITOKEN_KEY_SEGMENT.to_owned())? - .push(&RED_TOKEN_KEY_SEGMENT.to_owned())? - .push(&BALANCE_KEY_SEGMENT.to_owned())? - .push(owner)?; - - let tx_code_path = TestWasms::TxWriteStorageKey.path(); - let tx_data_path = write_test_file( - test, - TxWriteData { - key: red_balance_key, - value: amount.try_to_vec()?, - } - .try_to_vec()?, - )?; - - let tx_data_path = tx_data_path.to_string_lossy().to_string(); - let tx_code_path = tx_code_path.to_string_lossy().to_string(); - let tx_args = vec![ - "tx", - "--signer", - ARBITRARY_SIGNER, - "--code-path", - &tx_code_path, - "--data-path", - &tx_data_path, - "--ledger-address", - rpc_addr, - ]; - let mut client_tx = run!(test, Bin::Client, tx_args, Some(40))?; - client_tx.exp_string("Transaction is valid.")?; - client_tx.exp_string("Transaction applied")?; - client_tx.assert_success(); - Ok(()) -} - -pub fn attempt_red_tokens_transfer( - test: &Test, - rpc_addr: &str, - multitoken: &str, - from: &str, - to: &str, - signer: &str, - amount: &token::Amount, -) -> Result { - let amount = amount.to_string_native(); - let transfer_args = vec![ - "transfer", - "--token", - multitoken, - "--sub-prefix", - MULTITOKEN_RED_TOKEN_SUB_PREFIX, - "--source", - from, - "--target", - to, - "--signer", - signer, - "--amount", - &amount, - "--ledger-address", - rpc_addr, - ]; - run!(test, Bin::Client, transfer_args, Some(40)) -} - -pub fn fetch_red_token_balance( - test: &Test, - rpc_addr: &str, - multitoken_alias: &str, - owner_alias: &str, -) -> Result { - let balance_args = vec![ - "balance", - "--owner", - owner_alias, - "--token", - multitoken_alias, - "--sub-prefix", - MULTITOKEN_RED_TOKEN_SUB_PREFIX, - "--ledger-address", - rpc_addr, - ]; - let mut client_balance = run!(test, Bin::Client, balance_args, Some(40))?; - let (_, matched) = client_balance.exp_regex(&format!( - r"{MULTITOKEN_RED_TOKEN_SUB_PREFIX}: (\d*\.?\d+)" - ))?; - let decimal_regex = Regex::new(r"(\d*\.?\d+)").unwrap(); - println!("Got balance for {}: {}", owner_alias, matched); - let decimal = decimal_regex.find(&matched).unwrap().as_str(); - client_balance.assert_success(); - token::Amount::from_str(decimal, NATIVE_MAX_DECIMAL_PLACES) - .wrap_err(format!("Failed to parse {}", matched)) -} diff --git a/tests/src/vm_host_env/ibc.rs b/tests/src/vm_host_env/ibc.rs index e3cbce8f30..9baafd6fc2 100644 --- a/tests/src/vm_host_env/ibc.rs +++ b/tests/src/vm_host_env/ibc.rs @@ -765,5 +765,5 @@ pub fn packet_from_message( pub fn balance_key_with_ibc_prefix(denom: String, owner: &Address) -> Key { let ibc_token = ibc_token(denom); - token::multitoken_balance_key(&ibc_token, owner) + token::balance_key(&ibc_token, owner) } diff --git a/tests/src/vm_host_env/mod.rs b/tests/src/vm_host_env/mod.rs index 13d371ce0c..a8c72cf9b7 100644 --- a/tests/src/vm_host_env/mod.rs +++ b/tests/src/vm_host_env/mod.rs @@ -29,7 +29,6 @@ mod tests { use namada::ledger::tx_env::TxEnv; use namada::proto::{Code, Data, Section, Signature, Tx}; use namada::types::address::{Address, InternalAddress}; - use namada::types::chain::ChainId; use namada::types::hash::Hash; use namada::types::key::*; use namada::types::storage::{self, BlockHash, BlockHeight, Key, KeySeg}; @@ -1191,15 +1190,12 @@ mod tests { // the origin-specific token let denom = format!("{}/{}/{}", port_id, channel_id, token); let ibc_token = ibc_storage::ibc_token(&denom); - let balance_key = token::multitoken_balance_key(&ibc_token, &sender); + let balance_key = token::balance_key(&ibc_token, &sender); let init_bal = Amount::native_whole(100); writes.insert(balance_key.clone(), init_bal.try_to_vec().unwrap()); - let minted_key = token::multitoken_balance_key( - &ibc_token, - &Address::Internal(InternalAddress::Mint), - ); + let minted_key = token::minted_balance_key(&ibc_token); writes.insert(minted_key.clone(), init_bal.try_to_vec().unwrap()); - let minter_key = token::multitoken_minter_key(&ibc_token); + let minter_key = token::minter_key(&ibc_token); writes.insert( minter_key, Address::Internal(InternalAddress::Ibc) @@ -1314,7 +1310,7 @@ mod tests { // Check if the token was minted let denom = format!("{}/{}/{}", port_id, channel_id, token); let ibc_token = ibc::ibc_token(&denom); - let minted_key = token::multitoken_minted_key(&ibc_token); + let minted_key = token::minted_balance_key(&ibc_token); let result = ibc::validate_multitoken_vp_from_tx(&env, &tx, &minted_key); assert!(result.expect("token validation failed unexpectedly")); diff --git a/tests/src/vm_host_env/tx.rs b/tests/src/vm_host_env/tx.rs index bb66ee71b7..d3da806ca5 100644 --- a/tests/src/vm_host_env/tx.rs +++ b/tests/src/vm_host_env/tx.rs @@ -177,13 +177,9 @@ impl TestTxEnv { &mut self, target: &Address, token: &Address, - sub_prefix: Option
, amount: token::Amount, ) { - let storage_key = match &sub_prefix { - Some(sp) => token::multitoken_balance_key(sp, target), - None => token::balance_key(token, target), - }; + let storage_key = token::balance_key(token, target); self.wl_storage .storage .write(&storage_key, amount.try_to_vec().unwrap()) diff --git a/tx_prelude/src/ibc.rs b/tx_prelude/src/ibc.rs index 12747e6a15..28694aa7f7 100644 --- a/tx_prelude/src/ibc.rs +++ b/tx_prelude/src/ibc.rs @@ -76,23 +76,22 @@ impl IbcStorageContext for Ctx { src: &Address, dest: &Address, token: &Address, - sub_prefix: Option
, amount: Amount, ) -> std::result::Result<(), Self::Error> { - transfer(self, src, dest, token, sub_prefix, amount, &None, &None) + transfer(self, src, dest, token, amount, &None, &None, &None) } fn mint_token( &mut self, target: &Address, - sub_prefix: &Address, + token: &Address, amount: Amount, ) -> Result<(), Self::Error> { mint( self, &Address::Internal(InternalAddress::Ibc), target, - sub_prefix, + token, amount, ) } @@ -100,10 +99,10 @@ impl IbcStorageContext for Ctx { fn burn_token( &mut self, target: &Address, - sub_prefix: &Address, + token: &Address, amount: Amount, ) -> Result<(), Self::Error> { - burn(self, target, sub_prefix, amount) + burn(self, target, token, amount) } fn get_height(&self) -> std::result::Result { diff --git a/tx_prelude/src/token.rs b/tx_prelude/src/token.rs index e85edf6b7a..3cf1d8ead3 100644 --- a/tx_prelude/src/token.rs +++ b/tx_prelude/src/token.rs @@ -14,21 +14,14 @@ pub fn transfer( src: &Address, dest: &Address, token: &Address, - sub_prefix: Option
, amount: DenominatedAmount, key: &Option, shielded_hash: &Option, shielded: &Option, ) -> TxResult { if amount.amount != Amount::default() { - let src_key = match &sub_prefix { - Some(sub_prefix) => token::multitoken_balance_key(sub_prefix, src), - None => token::balance_key(token, src), - }; - let dest_key = match &sub_prefix { - Some(sub_prefix) => token::multitoken_balance_key(sub_prefix, dest), - None => token::balance_key(token, dest), - }; + let src_key = token::balance_key(token, src); + let dest_key = token::balance_key(token, dest); let src_bal: Option = ctx.read(&src_key)?; let mut src_bal = src_bal.unwrap_or_else(|| { log_string(format!("src {} has no balance", src_key)); @@ -63,8 +56,6 @@ pub fn transfer( source: src.clone(), target: dest.clone(), token: token.clone(), - // todo: build asset types for multitokens - sub_prefix: None, amount, key: key.clone(), shielded: *shielded_hash, @@ -94,21 +85,21 @@ pub fn mint( ctx: &mut Ctx, minter: &Address, target: &Address, - sub_prefix: &Address, + token: &Address, amount: Amount, ) -> TxResult { - let target_key = token::multitoken_balance_key(sub_prefix, target); + let target_key = token::balance_key(token, target); let mut target_bal: Amount = ctx.read(&target_key)?.unwrap_or_default(); target_bal.receive(&amount); - let minted_key = token::multitoken_minted_key(sub_prefix); + let minted_key = token::minted_balance_key(token); let mut minted_bal: Amount = ctx.read(&minted_key)?.unwrap_or_default(); minted_bal.receive(&amount); ctx.write(&target_key, target_bal)?; ctx.write(&minted_key, minted_bal)?; - let minter_key = token::multitoken_minter_key(sub_prefix); + let minter_key = token::minter_key(token); ctx.write(&minter_key, minter)?; Ok(()) @@ -118,15 +109,15 @@ pub fn mint( pub fn burn( ctx: &mut Ctx, target: &Address, - sub_prefix: &Address, + token: &Address, amount: Amount, ) -> TxResult { - let target_key = token::multitoken_balance_key(sub_prefix, target); + let target_key = token::balance_key(token, target); let mut target_bal: Amount = ctx.read(&target_key)?.unwrap_or_default(); target_bal.spend(&amount); // burn the minted amount - let minted_key = token::multitoken_minted_key(sub_prefix); + let minted_key = token::minted_balance_key(token); let mut minted_bal: Amount = ctx.read(&minted_key)?.unwrap_or_default(); minted_bal.spend(&amount); diff --git a/wasm/wasm_source/src/tx_bond.rs b/wasm/wasm_source/src/tx_bond.rs index df55270ceb..a32455412e 100644 --- a/wasm/wasm_source/src/tx_bond.rs +++ b/wasm/wasm_source/src/tx_bond.rs @@ -102,7 +102,7 @@ mod tests { // Ensure that the bond's source has enough tokens for the bond let target = bond.source.as_ref().unwrap_or(&bond.validator); let native_token = tx_env.wl_storage.storage.native_token.clone(); - tx_env.credit_tokens(target, &native_token, None, bond.amount); + tx_env.credit_tokens(target, &native_token, bond.amount); native_token }); diff --git a/wasm/wasm_source/src/tx_transfer.rs b/wasm/wasm_source/src/tx_transfer.rs index d2e3dc314f..33899640c4 100644 --- a/wasm/wasm_source/src/tx_transfer.rs +++ b/wasm/wasm_source/src/tx_transfer.rs @@ -15,7 +15,6 @@ fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { source, target, token, - sub_prefix, amount, key, shielded: shielded_hash, @@ -34,7 +33,6 @@ fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { &source, &target, &token, - sub_prefix, amount, &key, &shielded_hash, diff --git a/wasm/wasm_source/src/tx_unbond.rs b/wasm/wasm_source/src/tx_unbond.rs index 7fb1de8f4f..be5dac3ea1 100644 --- a/wasm/wasm_source/src/tx_unbond.rs +++ b/wasm/wasm_source/src/tx_unbond.rs @@ -109,12 +109,7 @@ mod tests { // bond first. // First, credit the bond's source with the initial stake, // before we initialize the bond below - tx_env.credit_tokens( - source, - &native_token, - None, - initial_stake, - ); + tx_env.credit_tokens(source, &native_token, initial_stake); } native_token }); diff --git a/wasm/wasm_source/src/tx_withdraw.rs b/wasm/wasm_source/src/tx_withdraw.rs index 85ab2b3af4..20b202bdb6 100644 --- a/wasm/wasm_source/src/tx_withdraw.rs +++ b/wasm/wasm_source/src/tx_withdraw.rs @@ -113,12 +113,7 @@ mod tests { // bond first. // First, credit the bond's source with the initial stake, // before we initialize the bond below - tx_env.credit_tokens( - source, - &native_token, - None, - initial_stake, - ); + tx_env.credit_tokens(source, &native_token, initial_stake); } native_token }); diff --git a/wasm/wasm_source/src/vp_implicit.rs b/wasm/wasm_source/src/vp_implicit.rs index 70195a9c69..022a22700a 100644 --- a/wasm/wasm_source/src/vp_implicit.rs +++ b/wasm/wasm_source/src/vp_implicit.rs @@ -30,9 +30,7 @@ impl<'a> From<&'a storage::Key> for KeyType<'a> { fn from(key: &'a storage::Key) -> KeyType<'a> { if let Some(address) = key::is_pk_key(key) { Self::Pk(address) - } else if let Some(address) = token::is_any_token_balance_key(key) { - Self::Token(address) - } else if let Some((_, address)) = token::is_multitoken_balance_key(key) + } else if let Some((_, address)) = token::is_any_token_balance_key(key) { Self::Token { owner } } else if proof_of_stake::is_pos_key(key) { @@ -354,7 +352,7 @@ mod tests { // Credit the tokens to the source before running the transaction to be // able to transfer from it - tx_env.credit_tokens(&source, &token, None, amount); + tx_env.credit_tokens(&source, &token, amount); let amount = token::DenominatedAmount { amount, @@ -368,7 +366,6 @@ mod tests { &source, address, &token, - None, amount, &None, &None, @@ -433,15 +430,15 @@ mod tests { // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it - tx_env.credit_tokens(&vp_owner, &token, None, amount); + tx_env.credit_tokens(&vp_owner, &token, amount); // write the denomination of NAM into storage storage_api::token::write_denom( &mut tx_env.wl_storage, &token, - None, token::NATIVE_MAX_DECIMAL_PLACES.into(), ) .unwrap(); + // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |_address| { // Bond the tokens, then unbond some of them @@ -513,12 +510,11 @@ mod tests { // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it - tx_env.credit_tokens(&vp_owner, &token, None, amount); + tx_env.credit_tokens(&vp_owner, &token, amount); // write the denomination of NAM into storage storage_api::token::write_denom( &mut tx_env.wl_storage, &token, - None, token::NATIVE_MAX_DECIMAL_PLACES.into(), ) .unwrap(); @@ -574,12 +570,11 @@ mod tests { // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it - tx_env.credit_tokens(&vp_owner, &token, None, amount); + tx_env.credit_tokens(&vp_owner, &token, amount); // write the denomination of NAM into storage storage_api::token::write_denom( &mut tx_env.wl_storage, &token, - None, token::NATIVE_MAX_DECIMAL_PLACES.into(), ) .unwrap(); @@ -597,7 +592,6 @@ mod tests { address, &target, &token, - None, amount, &None, &None, @@ -637,12 +631,11 @@ mod tests { // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it - tx_env.credit_tokens(&vp_owner, &token, None, amount); + tx_env.credit_tokens(&vp_owner, &token, amount); // write the denomination of NAM into storage storage_api::token::write_denom( &mut tx_env.wl_storage, &token, - None, token::NATIVE_MAX_DECIMAL_PLACES.into(), ) .unwrap(); @@ -661,7 +654,6 @@ mod tests { address, &target, &token, - None, amount, &None, &None, @@ -709,12 +701,11 @@ mod tests { // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it - tx_env.credit_tokens(&source, &token, None, amount); + tx_env.credit_tokens(&source, &token, amount); // write the denomination of NAM into storage storage_api::token::write_denom( &mut tx_env.wl_storage, &token, - None, token::NATIVE_MAX_DECIMAL_PLACES.into(), ) .unwrap(); @@ -733,7 +724,6 @@ mod tests { &source, &target, &token, - None, amount, &None, &None, diff --git a/wasm/wasm_source/src/vp_testnet_faucet.rs b/wasm/wasm_source/src/vp_testnet_faucet.rs index 8e002663ee..7d5a4d6a0b 100644 --- a/wasm/wasm_source/src/vp_testnet_faucet.rs +++ b/wasm/wasm_source/src/vp_testnet_faucet.rs @@ -155,7 +155,7 @@ mod tests { // Credit the tokens to the source before running the transaction to be // able to transfer from it - tx_env.credit_tokens(&source, &token, None, amount); + tx_env.credit_tokens(&source, &token, amount); let amount = token::DenominatedAmount { amount, @@ -170,7 +170,6 @@ mod tests { &source, address, &token, - None, amount, &None, &None, @@ -315,7 +314,7 @@ mod tests { // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it - tx_env.credit_tokens(&vp_owner, &token, None, amount); + tx_env.credit_tokens(&vp_owner, &token, amount); tx_env.commit_genesis(); let amount = token::DenominatedAmount { amount, @@ -325,7 +324,7 @@ mod tests { // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Apply transfer in a transaction - tx_host_env::token::transfer(tx::ctx(), address, &target, &token, None, amount, &None, &None, &None).unwrap(); + tx_host_env::token::transfer(tx::ctx(), address, &target, &token, amount, &None, &None, &None).unwrap(); }); let vp_env = vp_host_env::take(); @@ -361,9 +360,9 @@ mod tests { // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it - tx_env.credit_tokens(&vp_owner, &token, None, amount); + tx_env.credit_tokens(&vp_owner, &token, amount); // write the denomination of NAM into storage - storage_api::token::write_denom(&mut tx_env.wl_storage, &token, None, token::NATIVE_MAX_DECIMAL_PLACES.into()).unwrap(); + storage_api::token::write_denom(&mut tx_env.wl_storage, &token, token::NATIVE_MAX_DECIMAL_PLACES.into()).unwrap(); tx_env.commit_genesis(); // Construct a PoW solution like a client would @@ -383,7 +382,7 @@ mod tests { let valid = solution.validate(tx::ctx(), address, target.clone()).unwrap(); assert!(valid); // Apply transfer in a transaction - tx_host_env::token::transfer(tx::ctx(), address, &target, &token, None, amount, &None, &None, &None).unwrap(); + tx_host_env::token::transfer(tx::ctx(), address, &target, &token, amount, &None, &None, &None).unwrap(); }); let mut vp_env = vp_host_env::take(); diff --git a/wasm/wasm_source/src/vp_token.rs b/wasm/wasm_source/src/vp_token.rs index e59b0864b7..98ad53d8d2 100644 --- a/wasm/wasm_source/src/vp_token.rs +++ b/wasm/wasm_source/src/vp_token.rs @@ -51,13 +51,19 @@ fn token_checks( keys_touched: &BTreeSet, verifiers: &BTreeSet
, ) -> VpResult { - let mut change = token::Change::default(); for key in keys_touched.iter() { let owner: Option<&Address> = token::is_balance_key(token, key); match owner { None => { - if token::is_total_supply_key(key, token) { + if key.segments.get(0) == Some(&token.to_db_key()) { + // Unknown changes to this address space are disallowed, but + // unknown changes anywhere else are permitted + return reject(); + } + } + Some(owner) => { + if token::is_minted_balance_key(token, key) { // check if total supply is changed, which it should never // be from a tx let total_pre: token::Amount = ctx.read_pre(key)?.unwrap(); @@ -66,30 +72,25 @@ fn token_checks( if total_pre != total_post { return reject(); } - } else if key.segments.get(0) == Some(&token.to_db_key()) { - // Unknown changes to this address space are disallowed, but - // unknown changes anywhere else are permitted - return reject(); - } - } - Some(owner) => { - // accumulate the change - let pre: token::Amount = ctx.read_pre(key)?.unwrap_or_default(); - let post: token::Amount = - ctx.read_post(key)?.unwrap_or_default(); - let this_change = post.change() - pre.change(); - change += this_change; - // make sure that the spender approved the transaction - if !(this_change.non_negative() - || verifiers.contains(owner) - || *owner == address::masp()) - { - return reject(); + } else { + // accumulate the change + let pre: token::Amount = + ctx.read_pre(key)?.unwrap_or_default(); + let post: token::Amount = + ctx.read_post(key)?.unwrap_or_default(); + // make sure that the spender approved the transaction + if post < pre + && !(verifiers.contains(owner) + || *owner == address::masp()) + { + return reject(); + } } } } } - Ok(change.is_zero()) + // The total change should be validated by multitoken VP + Ok(true) } #[cfg(test)] @@ -228,7 +229,7 @@ mod tests { // Commit the initial state tx_env.commit_tx_and_block(); - let total_supply_key = token::total_supply_key(&token); + let total_supply_key = token::minted_balance_key(&token); // Initialize VP environment from a transaction vp_host_env::init_from_tx(token.clone(), tx_env, |_address| { diff --git a/wasm/wasm_source/src/vp_user.rs b/wasm/wasm_source/src/vp_user.rs index dd86f09cbd..e605ce28d8 100644 --- a/wasm/wasm_source/src/vp_user.rs +++ b/wasm/wasm_source/src/vp_user.rs @@ -24,10 +24,7 @@ enum KeyType<'a> { impl<'a> From<&'a storage::Key> for KeyType<'a> { fn from(key: &'a storage::Key) -> KeyType<'a> { - if let Some(address) = token::is_any_token_balance_key(key) { - Self::Token(address) - } else if let Some((_, address)) = token::is_multitoken_balance_key(key) - { + if let Some([_, address]) = token::is_any_token_balance_key(key) { Self::Token { owner } } else if proof_of_stake::is_pos_key(key) { Self::PoS @@ -238,12 +235,11 @@ mod tests { // Credit the tokens to the source before running the transaction to be // able to transfer from it - tx_env.credit_tokens(&source, &token, None, amount); + tx_env.credit_tokens(&source, &token, amount); // write the denomination of NAM into storage storage_api::token::write_denom( &mut tx_env.wl_storage, &token, - None, token::NATIVE_MAX_DECIMAL_PLACES.into(), ) .unwrap(); @@ -260,7 +256,6 @@ mod tests { &source, address, &token, - None, amount, &None, &None, @@ -306,7 +301,7 @@ mod tests { // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it - tx_env.credit_tokens(&vp_owner, &token, None, amount); + tx_env.credit_tokens(&vp_owner, &token, amount); let amount = token::DenominatedAmount { amount, @@ -320,7 +315,6 @@ mod tests { address, &target, &token, - None, amount, &None, &None, @@ -360,12 +354,11 @@ mod tests { // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it - tx_env.credit_tokens(&vp_owner, &token, None, amount); + tx_env.credit_tokens(&vp_owner, &token, amount); // write the denomination of NAM into storage storage_api::token::write_denom( &mut tx_env.wl_storage, &token, - None, token::NATIVE_MAX_DECIMAL_PLACES.into(), ) .unwrap(); @@ -385,7 +378,6 @@ mod tests { address, &target, &token, - None, amount, &None, &None, @@ -465,7 +457,7 @@ mod tests { // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it - tx_env.credit_tokens(&vp_owner, &token, None, amount); + tx_env.credit_tokens(&vp_owner, &token, amount); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |_address| { @@ -546,7 +538,7 @@ mod tests { // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it - tx_env.credit_tokens(&vp_owner, &token, None, amount); + tx_env.credit_tokens(&vp_owner, &token, amount); tx_env.write_public_key(&vp_owner, &public_key); @@ -598,7 +590,7 @@ mod tests { // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it - tx_env.credit_tokens(&source, &token, None, amount); + tx_env.credit_tokens(&source, &token, amount); let amount = token::DenominatedAmount { amount, @@ -614,7 +606,6 @@ mod tests { &source, &target, &token, - None, amount, &None, &None, diff --git a/wasm/wasm_source/src/vp_validator.rs b/wasm/wasm_source/src/vp_validator.rs index e79f51b629..768f025d78 100644 --- a/wasm/wasm_source/src/vp_validator.rs +++ b/wasm/wasm_source/src/vp_validator.rs @@ -26,11 +26,8 @@ enum KeyType<'a> { impl<'a> From<&'a storage::Key> for KeyType<'a> { fn from(key: &'a storage::Key) -> KeyType<'a> { - if let Some(address) = token::is_any_token_balance_key(key) { + if let Some([_, address]) = token::is_any_token_balance_key(key) { Self::Token(address) - } else if let Some((_, address)) = token::is_multitoken_balance_key(key) - { - Self::Token { owner } } else if proof_of_stake::is_pos_key(key) { Self::PoS } else if gov_storage::is_vote_key(key) { @@ -245,12 +242,11 @@ mod tests { // Credit the tokens to the source before running the transaction to be // able to transfer from it - tx_env.credit_tokens(&source, &token, None, amount); + tx_env.credit_tokens(&source, &token, amount); // write the denomination of NAM into storage storage_api::token::write_denom( &mut tx_env.wl_storage, &token, - None, token::NATIVE_MAX_DECIMAL_PLACES.into(), ) .unwrap(); @@ -267,7 +263,6 @@ mod tests { &source, address, &token, - None, amount, &None, &None, @@ -305,7 +300,7 @@ mod tests { // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it - tx_env.credit_tokens(&vp_owner, &token, None, amount); + tx_env.credit_tokens(&vp_owner, &token, amount); let amount = token::DenominatedAmount { amount, denom: token::NATIVE_MAX_DECIMAL_PLACES.into(), @@ -314,7 +309,6 @@ mod tests { storage_api::token::write_denom( &mut tx_env.wl_storage, &token, - None, token::NATIVE_MAX_DECIMAL_PLACES.into(), ) .unwrap(); @@ -327,7 +321,6 @@ mod tests { address, &target, &token, - None, amount, &None, &None, @@ -367,12 +360,11 @@ mod tests { // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it - tx_env.credit_tokens(&vp_owner, &token, None, amount); + tx_env.credit_tokens(&vp_owner, &token, amount); // write the denomination of NAM into storage storage_api::token::write_denom( &mut tx_env.wl_storage, &token, - None, token::NATIVE_MAX_DECIMAL_PLACES.into(), ) .unwrap(); @@ -391,7 +383,6 @@ mod tests { address, &target, &token, - None, amount, &None, &None, @@ -463,12 +454,11 @@ mod tests { // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it - tx_env.credit_tokens(&vp_owner, &token, None, amount); + tx_env.credit_tokens(&vp_owner, &token, amount); // write the denomination of NAM into storage storage_api::token::write_denom( &mut tx_env.wl_storage, &token, - None, token::NATIVE_MAX_DECIMAL_PLACES.into(), ) .unwrap(); @@ -550,12 +540,11 @@ mod tests { // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it - tx_env.credit_tokens(&vp_owner, &token, None, amount); + tx_env.credit_tokens(&vp_owner, &token, amount); // write the denomination of NAM into storage storage_api::token::write_denom( &mut tx_env.wl_storage, &token, - None, token::NATIVE_MAX_DECIMAL_PLACES.into(), ) .unwrap(); @@ -616,7 +605,7 @@ mod tests { // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it - tx_env.credit_tokens(&source, &token, None, amount); + tx_env.credit_tokens(&source, &token, amount); let amount = token::DenominatedAmount { amount, denom: token::NATIVE_MAX_DECIMAL_PLACES.into(), @@ -631,7 +620,6 @@ mod tests { &source, &target, &token, - None, amount, &None, &None, diff --git a/wasm_for_tests/wasm_source/src/lib.rs b/wasm_for_tests/wasm_source/src/lib.rs index 3822a8a01f..561d43fc2e 100644 --- a/wasm_for_tests/wasm_source/src/lib.rs +++ b/wasm_for_tests/wasm_source/src/lib.rs @@ -16,7 +16,8 @@ pub mod main { #[transaction] fn apply_tx(_ctx: &mut Ctx, tx_data: Tx) -> TxResult { - let len = usize::try_from_slice(&tx_data.data().as_ref().unwrap()[..]).unwrap(); + let len = usize::try_from_slice(&tx_data.data().as_ref().unwrap()[..]) + .unwrap(); log_string(format!("allocate len {}", len)); let bytes: Vec = vec![6_u8; len]; // use the variable to prevent it from compiler optimizing it away @@ -51,7 +52,9 @@ pub mod main { #[transaction] fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { // Allocates a memory of size given from the `tx_data (usize)` - let key = storage::Key::try_from_slice(&tx_data.data().as_ref().unwrap()[..]).unwrap(); + let key = + storage::Key::try_from_slice(&tx_data.data().as_ref().unwrap()[..]) + .unwrap(); log_string(format!("key {}", key)); let _result: Vec = ctx.read(&key)?.unwrap(); Ok(()) @@ -64,7 +67,7 @@ pub mod main { use borsh::BorshDeserialize; use namada_test_utils::tx_data::TxWriteData; use namada_tx_prelude::{ - log_string, transaction, Ctx, StorageRead, StorageWrite, TxResult, Tx, + log_string, transaction, Ctx, StorageRead, StorageWrite, Tx, TxResult, }; const TX_NAME: &str = "tx_write"; @@ -129,29 +132,22 @@ pub mod main { /// token's VP. #[cfg(feature = "tx_mint_tokens")] pub mod main { + use namada_test_utils::tx_data::TxMintData; use namada_tx_prelude::*; #[transaction] fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { let signed = tx_data; - let transfer = - token::Transfer::try_from_slice(&signed.data().unwrap()[..]).unwrap(); - log_string(format!("apply_tx called to mint tokens: {:#?}", transfer)); - let token::Transfer { - source: _, + let mint_data = + TxMintData::try_from_slice(&signed.data().unwrap()[..]).unwrap(); + log_string(format!("apply_tx called to mint tokens: {:#?}", mint_data)); + let TxMintData { + minter, target, token, - sub_prefix: _, amount, - key: _, - shielded: _, - } = transfer; - let target_key = token::balance_key(&token, &target); - let mut target_bal: token::Amount = - ctx.read(&target_key)?.unwrap_or_default(); - target_bal.receive(&amount.amount); - ctx.write(&target_key, target_bal)?; - Ok(()) + } = mint_data; + token::mint(ctx, &minter, &target, &token, amount) } } @@ -204,8 +200,12 @@ pub mod main { _verifiers: BTreeSet
, ) -> VpResult { use validity_predicate::EvalVp; - let EvalVp { vp_code_hash, input }: EvalVp = - EvalVp::try_from_slice(&tx_data.data().as_ref().unwrap()[..]).unwrap(); + let EvalVp { + vp_code_hash, + input, + }: EvalVp = + EvalVp::try_from_slice(&tx_data.data().as_ref().unwrap()[..]) + .unwrap(); ctx.eval(vp_code_hash, input) } } @@ -224,7 +224,8 @@ pub mod main { _keys_changed: BTreeSet, _verifiers: BTreeSet
, ) -> VpResult { - let len = usize::try_from_slice(&tx_data.data().as_ref().unwrap()[..]).unwrap(); + let len = usize::try_from_slice(&tx_data.data().as_ref().unwrap()[..]) + .unwrap(); log_string(format!("allocate len {}", len)); let bytes: Vec = vec![6_u8; len]; // use the variable to prevent it from compiler optimizing it away @@ -248,7 +249,9 @@ pub mod main { _verifiers: BTreeSet
, ) -> VpResult { // Allocates a memory of size given from the `tx_data (usize)` - let key = storage::Key::try_from_slice(&tx_data.data().as_ref().unwrap()[..]).unwrap(); + let key = + storage::Key::try_from_slice(&tx_data.data().as_ref().unwrap()[..]) + .unwrap(); log_string(format!("key {}", key)); let _result: Vec = ctx.read_pre(&key)?.unwrap(); accept() From fc5eab4ad68ed95d95eb3a9afcdb2a7aa46001f5 Mon Sep 17 00:00:00 2001 From: yito88 Date: Fri, 9 Jun 2023 00:01:29 +0900 Subject: [PATCH 04/25] add multitoken VP file --- shared/src/ledger/native_vp/multitoken.rs | 95 +++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 shared/src/ledger/native_vp/multitoken.rs diff --git a/shared/src/ledger/native_vp/multitoken.rs b/shared/src/ledger/native_vp/multitoken.rs new file mode 100644 index 0000000000..c80e982afb --- /dev/null +++ b/shared/src/ledger/native_vp/multitoken.rs @@ -0,0 +1,95 @@ +//! Native VP for multitokens + +use std::collections::{BTreeSet, HashMap}; + +use thiserror::Error; + +use crate::ledger::native_vp::{self, Ctx, NativeVp}; +use crate::ledger::storage; +use crate::ledger::vp_env::VpEnv; +use crate::proto::Tx; +use crate::types::address::{Address, InternalAddress}; +use crate::types::storage::Key; +use crate::types::token::{ + is_any_token_balance_key, is_minted_balance_key, minter_key, Amount, +}; +use crate::vm::WasmCacheAccess; + +#[allow(missing_docs)] +#[derive(Error, Debug)] +pub enum Error { + #[error("Native VP error: {0}")] + NativeVpError(#[from] native_vp::Error), +} + +/// Multitoken functions result +pub type Result = std::result::Result; + +/// Multitoken VP +pub struct MultitokenVp<'a, DB, H, CA> +where + DB: storage::DB + for<'iter> storage::DBIter<'iter>, + H: storage::StorageHasher, + CA: WasmCacheAccess, +{ + /// Context to interact with the host structures. + pub ctx: Ctx<'a, DB, H, CA>, +} + +impl<'a, DB, H, CA> NativeVp for MultitokenVp<'a, DB, H, CA> +where + DB: 'static + storage::DB + for<'iter> storage::DBIter<'iter>, + H: 'static + storage::StorageHasher, + CA: 'static + WasmCacheAccess, +{ + type Error = Error; + + const ADDR: InternalAddress = InternalAddress::Multitoken; + + fn validate_tx( + &self, + _tx: &Tx, + keys_changed: &BTreeSet, + verifiers: &BTreeSet
, + ) -> Result { + let mut changes = HashMap::new(); + let mut mints = HashMap::new(); + for key in keys_changed { + if let Some((token, _)) = is_any_token_balance_key(key) { + let pre: Amount = self.ctx.read_pre(key)?.unwrap_or_default(); + let post: Amount = self.ctx.read_post(key)?.unwrap_or_default(); + let diff = post.change() - pre.change(); + + if is_minted_balance_key(token, key) { + match mints.get_mut(token) { + Some(mint) => *mint += diff, + None => _ = mints.insert(token, diff), + } + + // Check if the minter VP is called + let minter_key = minter_key(token); + let minter = match self.ctx.read_post(&minter_key)? { + Some(m) => m, + None => return Ok(false), + }; + if !verifiers.contains(&minter) { + return Ok(false); + } + } else { + match changes.get_mut(token) { + Some(change) => *change += diff, + None => _ = changes.insert(token, diff), + } + } + } + } + + Ok(changes.iter().all(|(token, change)| { + let mint = match mints.get(token) { + Some(mint) => *mint, + None => 0, + }; + *change == mint + })) + } +} From 9995a20a7e04f65cce7fa9c575a54af4dd8544ac Mon Sep 17 00:00:00 2001 From: yito88 Date: Tue, 13 Jun 2023 10:23:02 +0900 Subject: [PATCH 05/25] fix balance print --- apps/src/lib/client/rpc.rs | 95 ++++++++++--------- core/src/ledger/storage/masp_conversions.rs | 46 +++------ core/src/ledger/storage_api/token.rs | 2 - core/src/types/address.rs | 17 ++-- core/src/types/token.rs | 62 ++---------- .../transactions/ethereum_events/events.rs | 4 +- shared/src/ledger/ibc/vp/context.rs | 3 +- shared/src/ledger/rpc.rs | 5 +- tx_prelude/src/ibc.rs | 3 +- 9 files changed, 85 insertions(+), 152 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index b136ce17e0..a3863a9d82 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -302,7 +302,10 @@ pub async fn query_transparent_balance< wallet: &mut Wallet, args: args::QueryBalance, ) { - let tokens = wallet.get_addresses_with_vp_type(AddressVpType::Token); + let prefix = Key::from( + Address::Internal(namada::types::address::InternalAddress::Multitoken) + .to_db_key(), + ); match (args.token, args.owner) { (Some(token), Some(owner)) => { let balance_key = @@ -323,22 +326,16 @@ pub async fn query_transparent_balance< } } (None, Some(owner)) => { - for token in tokens { - let prefix = - token::balance_key(&token, &owner.address().unwrap()); - let balances = - query_storage_prefix::(client, &prefix) - .await; - if let Some(balances) = balances { - print_balances( - client, - wallet, - balances, - &token, - owner.address().as_ref(), - ) - .await; - } + let balances = + query_storage_prefix::(client, &prefix).await; + if let Some(balances) = balances { + print_balances( + client, + wallet, + balances, + None, + owner.address().as_ref(), + ); } } (Some(token), None) => { @@ -346,19 +343,14 @@ pub async fn query_transparent_balance< let balances = query_storage_prefix::(client, &prefix).await; if let Some(balances) = balances { - print_balances(client, wallet, balances, &token, None).await; + print_balances(client, wallet, balances, Some(&token), None); } } (None, None) => { - for token in tokens { - let key = token::balance_prefix(&token); - let balances = - query_storage_prefix::(client, &key) - .await; - if let Some(balances) = balances { - print_balances(client, wallet, balances, &token, None) - .await; - } + let balances = + query_storage_prefix::(client, &prefix).await; + if let Some(balances) = balances { + print_balances(client, wallet, balances, None, None); } } } @@ -521,19 +513,18 @@ async fn print_balances( client: &C, wallet: &Wallet, balances: impl Iterator, - token: &Address, + token: Option<&Address>, target: Option<&Address>, ) { let stdout = io::stdout(); let mut w = stdout.lock(); - let token_alias = lookup_alias(wallet, token); - writeln!(w, "Token {}", token_alias).unwrap(); - + let mut print_token = None; let print_num = balances .filter_map(|(key, balance)| { - token::is_balance_key(token, &key).map(|owner| { + token::is_any_token_balance_key(&key).map(|(token, owner)| { ( + token.clone(), owner.clone(), format!( ": {}, owned by {}", @@ -543,25 +534,43 @@ async fn print_balances( ) }) }) - .filter_map(|(o, s)| match target { - Some(t) if o == *t => Some(s), - Some(_) => None, - None => Some(s), + .filter_map(|(t, o, s)| match (token, target) { + (Some(token), Some(target)) if t == *token && o == *target => { + Some((t, s)) + } + (Some(token), None) if t == *token => Some((t, s)), + (None, Some(target)) if o == *target => Some((t, s)), + (None, None) => Some((t, s)), + _ => None, }) - .map(|s| { + .map(|(t, s)| { + match &print_token { + Some(token) if *token == t => { + // the token was already printed + } + Some(_) | None => { + let token_alias = lookup_alias(wallet, &t); + writeln!(w, "Token {}", token_alias).unwrap(); + print_token = Some(t); + } + } writeln!(w, "{}", s).unwrap(); }) .count(); if print_num == 0 { - match target { - Some(t) => { - writeln!(w, "No balances owned by {}", lookup_alias(wallet, t)) - .unwrap() - } - None => { + match (token, target) { + (Some(_), Some(target)) | (None, Some(target)) => writeln!( + w, + "No balances owned by {}", + lookup_alias(wallet, target) + ) + .unwrap(), + (Some(token), None) => { + let token_alias = lookup_alias(wallet, token); writeln!(w, "No balances for token {}", token_alias).unwrap() } + (None, None) => writeln!(w, "No balances").unwrap(), } } } diff --git a/core/src/ledger/storage/masp_conversions.rs b/core/src/ledger/storage/masp_conversions.rs index 4dec02d4f5..eefc6d7342 100644 --- a/core/src/ledger/storage/masp_conversions.rs +++ b/core/src/ledger/storage/masp_conversions.rs @@ -9,7 +9,7 @@ use masp_primitives::merkle_tree::FrozenCommitmentTree; use masp_primitives::sapling::Node; use crate::types::address::Address; -use crate::types::storage::{Epoch, Key}; +use crate::types::storage::Epoch; use crate::types::token::MaspDenom; /// A representation of the conversion state @@ -24,7 +24,7 @@ pub struct ConversionState { pub assets: BTreeMap< AssetType, ( - (Address, Option, MaspDenom), + (Address, MaspDenom), Epoch, AllowedConversion, usize, @@ -66,24 +66,16 @@ where // have to use. This trick works under the assumption that reward tokens // from different epochs are exactly equivalent. let reward_asset = - encode_asset_type(address::nam(), &None, MaspDenom::Zero, Epoch(0)); + encode_asset_type(address::nam(), MaspDenom::Zero, Epoch(0)); // Conversions from the previous to current asset for each address let mut current_convs = - BTreeMap::<(Address, Option, MaspDenom), AllowedConversion>::new(); + BTreeMap::<(Address, MaspDenom), AllowedConversion>::new(); // Reward all tokens according to above reward rates - for ((addr, sub_prefix), reward) in &masp_rewards { + for (addr, reward) in &masp_rewards { // Dispense a transparent reward in parallel to the shielded rewards - let addr_bal: token::Amount = match sub_prefix { - None => wl_storage - .read(&token::balance_key(addr, &masp_addr))? - .unwrap_or_default(), - Some(sub) => wl_storage - .read(&token::multitoken_balance_key( - &token::multitoken_balance_prefix(addr, sub), - &masp_addr, - ))? - .unwrap_or_default(), - }; + let addr_bal: token::Amount = wl_storage + .read(&token::balance_key(addr, &masp_addr))? + .unwrap_or_default(); // The reward for each reward.1 units of the current asset is // reward.0 units of the reward token // Since floor(a) + floor(b) <= floor(a+b), there will always be @@ -95,18 +87,16 @@ where // cancelled out/replaced with the new asset let old_asset = encode_asset_type( addr.clone(), - sub_prefix, denom, wl_storage.storage.last_epoch, ); let new_asset = encode_asset_type( addr.clone(), - sub_prefix, denom, wl_storage.storage.block.epoch, ); current_convs.insert( - (addr.clone(), sub_prefix.clone(), denom), + (addr.clone(), denom), (MaspAmount::from_pair(old_asset, -(reward.1 as i64)).unwrap() + MaspAmount::from_pair(new_asset, reward.1).unwrap() + MaspAmount::from_pair(reward_asset, reward.0).unwrap()) @@ -116,7 +106,7 @@ where wl_storage.storage.conversion_state.assets.insert( old_asset, ( - (addr.clone(), sub_prefix.clone(), denom), + (addr.clone(), denom), wl_storage.storage.last_epoch, MaspAmount::zero().into(), 0, @@ -188,20 +178,19 @@ where // Add purely decoding entries to the assets map. These will be // overwritten before the creation of the next commitment tree - for (addr, sub_prefix) in masp_rewards.keys() { + for addr in masp_rewards.keys() { for denom in token::MaspDenom::iter() { // Add the decoding entry for the new asset type. An uncommited // node position is used since this is not a conversion. let new_asset = encode_asset_type( addr.clone(), - sub_prefix, denom, wl_storage.storage.block.epoch, ); wl_storage.storage.conversion_state.assets.insert( new_asset, ( - (addr.clone(), sub_prefix.clone(), denom), + (addr.clone(), denom), wl_storage.storage.block.epoch, MaspAmount::zero().into(), wl_storage.storage.conversion_state.tree.size(), @@ -229,19 +218,10 @@ where /// Construct MASP asset type with given epoch for given token pub fn encode_asset_type( addr: Address, - sub_prefix: &Option, denom: MaspDenom, epoch: Epoch, ) -> AssetType { - let new_asset_bytes = ( - addr, - sub_prefix - .as_ref() - .map(|k| k.to_string()) - .unwrap_or_default(), - denom, - epoch.0, - ) + let new_asset_bytes = (addr, denom, epoch.0) .try_to_vec() .expect("unable to serialize address and epoch"); AssetType::new(new_asset_bytes.as_ref()) diff --git a/core/src/ledger/storage_api/token.rs b/core/src/ledger/storage_api/token.rs index 383911d09a..4609ab03df 100644 --- a/core/src/ledger/storage_api/token.rs +++ b/core/src/ledger/storage_api/token.rs @@ -3,8 +3,6 @@ use super::{StorageRead, StorageWrite}; use crate::ledger::storage_api; use crate::types::address::Address; -use crate::types::storage::DbKeySeg::StringSeg; -use crate::types::storage::Key; use crate::types::token; pub use crate::types::token::{ balance_key, is_balance_key, is_minted_balance_key, minted_balance_key, diff --git a/core/src/types/address.rs b/core/src/types/address.rs index c647b85238..d802b4b6ab 100644 --- a/core/src/types/address.rs +++ b/core/src/types/address.rs @@ -17,7 +17,6 @@ use crate::ibc::signer::Signer; use crate::types::ethereum_events::EthAddress; use crate::types::key; use crate::types::key::PublicKeyHash; -use crate::types::storage::Key; use crate::types::token::Denomination; /// The length of an established [`Address`] encoded with Borsh. @@ -641,15 +640,15 @@ pub fn tokens() -> HashMap { /// Temporary helper for testing, a hash map of tokens addresses with their /// MASP XAN incentive schedules. If the reward is (a, b) then a rewarded tokens /// are dispensed for every b possessed tokens. -pub fn masp_rewards() -> HashMap<(Address, Option), (u64, u64)> { +pub fn masp_rewards() -> HashMap { vec![ - ((nam(), None), (0, 100)), - ((btc(), None), (1, 100)), - ((eth(), None), (2, 100)), - ((dot(), None), (3, 100)), - ((schnitzel(), None), (4, 100)), - ((apfel(), None), (5, 100)), - ((kartoffel(), None), (6, 100)), + (nam(), (0, 100)), + (btc(), (1, 100)), + (eth(), (2, 100)), + (dot(), (3, 100)), + (schnitzel(), (4, 100)), + (apfel(), (5, 100)), + (kartoffel(), (6, 100)), ] .into_iter() .collect() diff --git a/core/src/types/token.rs b/core/src/types/token.rs index df50a5b1ba..97eb83b8e3 100644 --- a/core/src/types/token.rs +++ b/core/src/types/token.rs @@ -1,6 +1,6 @@ //! A basic fungible token -use std::fmt::{Display, Formatter}; +use std::fmt::Display; use std::iter::Sum; use std::ops::{Add, AddAssign, Div, Mul, Sub, SubAssign}; use std::str::FromStr; @@ -201,15 +201,13 @@ impl Amount { pub fn denominated( &self, token: &Address, - sub_prefix: Option<&Key>, storage: &impl StorageRead, ) -> storage_api::Result { - let denom = - read_denom(storage, token, sub_prefix)?.ok_or_else(|| { - storage_api::Error::SimpleMessage( - "No denomination found in storage for the given token", - ) - })?; + let denom = read_denom(storage, token)?.ok_or_else(|| { + storage_api::Error::SimpleMessage( + "No denomination found in storage for the given token", + ) + })?; Ok(DenominatedAmount { amount: *self, denom, @@ -780,54 +778,6 @@ pub const CONVERSION_KEY_PREFIX: &str = "conv"; /// Key segment prefix for pinned shielded transactions pub const PIN_KEY_PREFIX: &str = "pin-"; -/// A fully qualified (multi-) token address. -#[derive( - Clone, - PartialEq, - Eq, - PartialOrd, - Ord, - Debug, - Hash, - BorshSerialize, - BorshDeserialize, -)] -pub struct TokenAddress { - /// The address of the (multi-) token - pub address: Address, - /// If it is a mutli-token, this indicates the sub-token. - pub sub_prefix: Option, -} - -impl TokenAddress { - /// A function for displaying a [`TokenAddress`]. Takes a - /// human readable name of the token as input. - pub fn format_with_alias(&self, alias: &str) -> String { - format!( - "{}{}", - alias, - self.sub_prefix - .as_ref() - .map(|k| format!("/{}", k)) - .unwrap_or_default() - ) - } -} - -impl Display for TokenAddress { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - let formatted = format!( - "{}{}", - self.address, - self.sub_prefix - .as_ref() - .map(|k| format!("/{}", k)) - .unwrap_or_default() - ); - f.write_str(&formatted) - } -} - /// Obtain a storage key for user's balance. pub fn balance_key(token_addr: &Address, owner: &Address) -> Key { balance_prefix(token_addr) diff --git a/ethereum_bridge/src/protocol/transactions/ethereum_events/events.rs b/ethereum_bridge/src/protocol/transactions/ethereum_events/events.rs index d4cd0370aa..1da9c5381c 100644 --- a/ethereum_bridge/src/protocol/transactions/ethereum_events/events.rs +++ b/ethereum_bridge/src/protocol/transactions/ethereum_events/events.rs @@ -26,9 +26,7 @@ use namada_core::types::ethereum_events::{ }; use namada_core::types::storage::{BlockHeight, Key, KeySeg}; use namada_core::types::token; -use namada_core::types::token::{ - balance_key, multitoken_balance_key, multitoken_balance_prefix, -}; +use namada_core::types::token::balance_key; use crate::parameters::read_native_erc20_address; use crate::protocol::transactions::update; diff --git a/shared/src/ledger/ibc/vp/context.rs b/shared/src/ledger/ibc/vp/context.rs index 17697f181c..d05eaebcd1 100644 --- a/shared/src/ledger/ibc/vp/context.rs +++ b/shared/src/ledger/ibc/vp/context.rs @@ -12,7 +12,8 @@ use namada_core::types::address::{Address, InternalAddress}; use namada_core::types::ibc::IbcEvent; use namada_core::types::storage::{BlockHeight, Header, Key}; use namada_core::types::token::{ - self, is_any_token_balance_key, is_any_token_or_multitoken_balance_key, Amount, + self, is_any_token_balance_key, is_any_token_or_multitoken_balance_key, + Amount, }; use super::Error; diff --git a/shared/src/ledger/rpc.rs b/shared/src/ledger/rpc.rs index 28431a7ffb..08111692e3 100644 --- a/shared/src/ledger/rpc.rs +++ b/shared/src/ledger/rpc.rs @@ -1065,10 +1065,7 @@ pub async fn format_denominated_amount< amount: token::Amount, ) -> String { let denom = unwrap_client_response::>( - RPC.vp() - .token() - .denomination(client, &token.address) - .await, + RPC.vp().token().denomination(client, &token.address).await, ) .unwrap_or_else(|| { println!( diff --git a/tx_prelude/src/ibc.rs b/tx_prelude/src/ibc.rs index 28694aa7f7..2edb13652b 100644 --- a/tx_prelude/src/ibc.rs +++ b/tx_prelude/src/ibc.rs @@ -12,7 +12,7 @@ use namada_core::ledger::tx_env::TxEnv; use namada_core::types::address::{Address, InternalAddress}; pub use namada_core::types::ibc::IbcEvent; use namada_core::types::storage::{BlockHeight, Header, Key}; -use namada_core::types::token::Amount; +use namada_core::types::token::{DenominatedAmount, Amount}; use crate::token::{burn, mint, transfer}; use crate::{Ctx, KeyValIterator}; @@ -78,6 +78,7 @@ impl IbcStorageContext for Ctx { token: &Address, amount: Amount, ) -> std::result::Result<(), Self::Error> { + let amount = DenominatedAmount::native(amount); transfer(self, src, dest, token, amount, &None, &None, &None) } From db117cfd141e419bf47c7f3cc0e061609a102cdc Mon Sep 17 00:00:00 2001 From: yito88 Date: Thu, 15 Jun 2023 16:15:53 +0900 Subject: [PATCH 06/25] fix token decoding --- core/src/ledger/ibc/context/transfer_mod.rs | 31 +++++++-------------- 1 file changed, 10 insertions(+), 21 deletions(-) diff --git a/core/src/ledger/ibc/context/transfer_mod.rs b/core/src/ledger/ibc/context/transfer_mod.rs index 51ac801aec..8280f7c36b 100644 --- a/core/src/ledger/ibc/context/transfer_mod.rs +++ b/core/src/ledger/ibc/context/transfer_mod.rs @@ -444,13 +444,7 @@ where ) -> Result<(), TokenTransferError> { // Assumes that the coin denom is prefixed with "port-id/channel-id" or // has no prefix - let (token, amount) = get_token_amount(coin)?; - - let ibc_token = if coin.denom.trace_path.is_empty() { - token - } else { - storage::ibc_token(coin.denom.to_string()) - }; + let (ibc_token, amount) = get_token_amount(coin)?; self.ctx .borrow_mut() @@ -474,10 +468,8 @@ where account: &Self::AccountId, coin: &PrefixedCoin, ) -> Result<(), TokenTransferError> { - let (_, amount) = get_token_amount(coin)?; - // The trace path of the denom is already updated if receiving the token - let ibc_token = storage::ibc_token(coin.denom.to_string()); + let (ibc_token, amount) = get_token_amount(coin)?; self.ctx .borrow_mut() @@ -500,11 +492,9 @@ where account: &Self::AccountId, coin: &PrefixedCoin, ) -> Result<(), TokenTransferError> { - let (_, amount) = get_token_amount(coin)?; - - // The burn is unminting of the minted - let ibc_token = storage::ibc_token(coin.denom.to_string()); + let (ibc_token, amount) = get_token_amount(coin)?; + // The burn is "unminting" from the minted balance self.ctx .borrow_mut() .burn_token(account, &ibc_token, amount) @@ -559,16 +549,15 @@ where } } -/// Get the token address and the amount from PrefixedCoin +/// Get the token address and the amount from PrefixedCoin. If the base denom is +/// not an address, it returns `IbcToken` fn get_token_amount( coin: &PrefixedCoin, ) -> Result<(Address, token::Amount), TokenTransferError> { - let token = - Address::decode(coin.denom.base_denom.as_str()).map_err(|_| { - TokenTransferError::InvalidCoin { - coin: coin.denom.base_denom.to_string(), - } - })?; + let token = match Address::decode(coin.denom.base_denom.as_str()) { + Ok(token_addr) if coin.denom.trace_path.is_empty() => token_addr, + _ => storage::ibc_token(coin.denom.to_string()), + }; let amount = coin.amount.try_into().map_err(|_| { TokenTransferError::InvalidCoin { From 818cceda19974a4d2a9fd9b06d265e7af390c669 Mon Sep 17 00:00:00 2001 From: yito88 Date: Thu, 15 Jun 2023 16:44:46 +0900 Subject: [PATCH 07/25] rename --- core/src/types/address.rs | 20 +++++++++++--------- core/src/types/token.rs | 4 ++-- shared/src/ledger/protocol/mod.rs | 2 +- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/core/src/types/address.rs b/core/src/types/address.rs index d802b4b6ab..6d58cf049a 100644 --- a/core/src/types/address.rs +++ b/core/src/types/address.rs @@ -54,7 +54,7 @@ pub const FIXED_LEN_STRING_BYTES: usize = 45; /// Internal IBC address pub const IBC: Address = Address::Internal(InternalAddress::Ibc); /// Internal multitoken mint address -pub const MINT: Address = Address::Internal(InternalAddress::Mint); +pub const MINTED: Address = Address::Internal(InternalAddress::Minted); /// Internal ledger parameters address pub const PARAMETERS: Address = Address::Internal(InternalAddress::Parameters); /// Internal PoS address @@ -89,7 +89,7 @@ mod internal { "ano::Replay Protection "; pub const MULTITOKEN: &str = "ano::Multitoken "; - pub const MINT: &str = + pub const MINTED: &str = "ano::Multitoken Mint Address "; } @@ -240,7 +240,7 @@ impl Address { InternalAddress::Multitoken => { internal::MULTITOKEN.to_string() } - InternalAddress::Mint => internal::MINT.to_string(), + InternalAddress::Minted => internal::MINTED.to_string(), }; debug_assert_eq!(string.len(), FIXED_LEN_STRING_BYTES); string @@ -317,7 +317,9 @@ impl Address { internal::MULTITOKEN => { Ok(Address::Internal(InternalAddress::Multitoken)) } - internal::MINT => Ok(Address::Internal(InternalAddress::Mint)), + internal::MINTED => { + Ok(Address::Internal(InternalAddress::Minted)) + } _ => Err(Error::new( ErrorKind::InvalidData, "Invalid internal address", @@ -534,7 +536,7 @@ pub enum InternalAddress { /// Multitoken Multitoken, /// Minted multitoken address - Mint, + Minted, } impl Display for InternalAddress { @@ -554,7 +556,7 @@ impl Display for InternalAddress { Self::EthBridgePool => "EthBridgePool".to_string(), Self::ReplayProtection => "ReplayProtection".to_string(), Self::Multitoken => "Multitoken".to_string(), - Self::Mint => "Mint".to_string(), + Self::Minted => "Minted".to_string(), } ) } @@ -849,8 +851,8 @@ pub mod testing { InternalAddress::EthBridgePool => {} InternalAddress::ReplayProtection => {} InternalAddress::Multitoken => {} - InternalAddress::Mint => {} /* Add new addresses in the - * `prop_oneof` below. */ + InternalAddress::Minted => {} /* Add new addresses in the + * `prop_oneof` below. */ }; prop_oneof![ Just(InternalAddress::PoS), @@ -864,7 +866,7 @@ pub mod testing { Just(InternalAddress::EthBridgePool), Just(InternalAddress::ReplayProtection), Just(InternalAddress::Multitoken), - Just(InternalAddress::Mint) + Just(InternalAddress::Minted) ] } diff --git a/core/src/types/token.rs b/core/src/types/token.rs index 97eb83b8e3..f0ac2379a6 100644 --- a/core/src/types/token.rs +++ b/core/src/types/token.rs @@ -806,7 +806,7 @@ pub fn minter_key(token_addr: &Address) -> Key { /// Obtain a storage key for the minted multitoken balance. pub fn minted_balance_key(token_addr: &Address) -> Key { balance_prefix(token_addr) - .push(&Address::Internal(InternalAddress::Mint).to_db_key()) + .push(&Address::Internal(InternalAddress::Minted).to_db_key()) .expect("Cannot obtain a storage key") } @@ -880,7 +880,7 @@ pub fn is_masp_key(key: &Key) -> bool { /// Is storage key for total supply of a specific token? pub fn is_minted_balance_key(token_addr: &Address, key: &Key) -> bool { if let Some(owner) = is_balance_key(token_addr, key) { - *owner == Address::Internal(InternalAddress::Mint) + *owner == Address::Internal(InternalAddress::Minted) } else { false } diff --git a/shared/src/ledger/protocol/mod.rs b/shared/src/ledger/protocol/mod.rs index c1a8484e45..fbdc6b9bdd 100644 --- a/shared/src/ledger/protocol/mod.rs +++ b/shared/src/ledger/protocol/mod.rs @@ -586,7 +586,7 @@ where result } InternalAddress::IbcToken(_) - | InternalAddress::Mint => { + | InternalAddress::Minted => { // These addresses should be a part of a multitoken // key gas_meter = ctx.gas_meter.into_inner(); From 9759185361ebdc50319648b58a03e43e1a119b1e Mon Sep 17 00:00:00 2001 From: yito88 Date: Mon, 19 Jun 2023 22:26:14 +0900 Subject: [PATCH 08/25] remove InternalAddress::Minted --- core/src/ledger/storage_api/token.rs | 2 +- core/src/types/address.rs | 17 ++------- core/src/types/token.rs | 28 ++++++++++----- shared/src/ledger/native_vp/multitoken.rs | 42 +++++++++++----------- shared/src/ledger/protocol/mod.rs | 10 +++--- tests/src/e2e/helpers.rs | 3 +- wasm/wasm_source/src/vp_token.rs | 44 +++++++++++------------ 7 files changed, 71 insertions(+), 75 deletions(-) diff --git a/core/src/ledger/storage_api/token.rs b/core/src/ledger/storage_api/token.rs index 4609ab03df..1985d8325c 100644 --- a/core/src/ledger/storage_api/token.rs +++ b/core/src/ledger/storage_api/token.rs @@ -5,7 +5,7 @@ use crate::ledger::storage_api; use crate::types::address::Address; use crate::types::token; pub use crate::types::token::{ - balance_key, is_balance_key, is_minted_balance_key, minted_balance_key, + balance_key, is_any_minted_balance_key, is_balance_key, minted_balance_key, minter_key, Amount, Change, }; diff --git a/core/src/types/address.rs b/core/src/types/address.rs index 6d58cf049a..cb36593edc 100644 --- a/core/src/types/address.rs +++ b/core/src/types/address.rs @@ -53,8 +53,6 @@ pub const FIXED_LEN_STRING_BYTES: usize = 45; /// Internal IBC address pub const IBC: Address = Address::Internal(InternalAddress::Ibc); -/// Internal multitoken mint address -pub const MINTED: Address = Address::Internal(InternalAddress::Minted); /// Internal ledger parameters address pub const PARAMETERS: Address = Address::Internal(InternalAddress::Parameters); /// Internal PoS address @@ -89,8 +87,6 @@ mod internal { "ano::Replay Protection "; pub const MULTITOKEN: &str = "ano::Multitoken "; - pub const MINTED: &str = - "ano::Multitoken Mint Address "; } /// Fixed-length address strings prefix for established addresses. @@ -240,7 +236,6 @@ impl Address { InternalAddress::Multitoken => { internal::MULTITOKEN.to_string() } - InternalAddress::Minted => internal::MINTED.to_string(), }; debug_assert_eq!(string.len(), FIXED_LEN_STRING_BYTES); string @@ -317,9 +312,6 @@ impl Address { internal::MULTITOKEN => { Ok(Address::Internal(InternalAddress::Multitoken)) } - internal::MINTED => { - Ok(Address::Internal(InternalAddress::Minted)) - } _ => Err(Error::new( ErrorKind::InvalidData, "Invalid internal address", @@ -535,8 +527,6 @@ pub enum InternalAddress { ReplayProtection, /// Multitoken Multitoken, - /// Minted multitoken address - Minted, } impl Display for InternalAddress { @@ -556,7 +546,6 @@ impl Display for InternalAddress { Self::EthBridgePool => "EthBridgePool".to_string(), Self::ReplayProtection => "ReplayProtection".to_string(), Self::Multitoken => "Multitoken".to_string(), - Self::Minted => "Minted".to_string(), } ) } @@ -850,9 +839,8 @@ pub mod testing { InternalAddress::EthBridge => {} InternalAddress::EthBridgePool => {} InternalAddress::ReplayProtection => {} - InternalAddress::Multitoken => {} - InternalAddress::Minted => {} /* Add new addresses in the - * `prop_oneof` below. */ + InternalAddress::Multitoken => {} /* Add new addresses in the + * `prop_oneof` below. */ }; prop_oneof![ Just(InternalAddress::PoS), @@ -866,7 +854,6 @@ pub mod testing { Just(InternalAddress::EthBridgePool), Just(InternalAddress::ReplayProtection), Just(InternalAddress::Multitoken), - Just(InternalAddress::Minted) ] } diff --git a/core/src/types/token.rs b/core/src/types/token.rs index f0ac2379a6..16dcb6d424 100644 --- a/core/src/types/token.rs +++ b/core/src/types/token.rs @@ -769,6 +769,8 @@ pub const BALANCE_STORAGE_KEY: &str = "balance"; pub const DENOM_STORAGE_KEY: &str = "denomination"; /// Key segment for multitoken minter pub const MINTER_STORAGE_KEY: &str = "minter"; +/// Key segment for minted balance +pub const MINTED_STORAGE_KEY: &str = "minted"; /// Key segment for head shielded transaction pointer keys pub const HEAD_TX_KEY: &str = "head-tx"; /// Key segment prefix for shielded transaction key @@ -806,12 +808,12 @@ pub fn minter_key(token_addr: &Address) -> Key { /// Obtain a storage key for the minted multitoken balance. pub fn minted_balance_key(token_addr: &Address) -> Key { balance_prefix(token_addr) - .push(&Address::Internal(InternalAddress::Minted).to_db_key()) + .push(&MINTED_STORAGE_KEY.to_owned()) .expect("Cannot obtain a storage key") } /// Check if the given storage key is balance key for the given token. If it is, -/// returns the owner. +/// returns the owner. For minted balances, use [`is_any_minted_balance_key()`]. pub fn is_balance_key<'a>( token_addr: &Address, key: &'a Key, @@ -877,12 +879,22 @@ pub fn is_masp_key(key: &Key) -> bool { || key.starts_with(PIN_KEY_PREFIX))) } -/// Is storage key for total supply of a specific token? -pub fn is_minted_balance_key(token_addr: &Address, key: &Key) -> bool { - if let Some(owner) = is_balance_key(token_addr, key) { - *owner == Address::Internal(InternalAddress::Minted) - } else { - false +/// Check if the given storage key is for total supply of a unspecified token. +/// If it is, returns the token. +pub fn is_any_minted_balance_key(key: &Key) -> Option<&Address> { + match &key.segments[..] { + [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::AddressSeg(token), + DbKeySeg::StringSeg(balance), + DbKeySeg::StringSeg(owner), + ] if *addr == Address::Internal(InternalAddress::Multitoken) + && balance == BALANCE_STORAGE_KEY + && owner == MINTED_STORAGE_KEY => + { + Some(token) + } + _ => None, } } diff --git a/shared/src/ledger/native_vp/multitoken.rs b/shared/src/ledger/native_vp/multitoken.rs index c80e982afb..08e9437495 100644 --- a/shared/src/ledger/native_vp/multitoken.rs +++ b/shared/src/ledger/native_vp/multitoken.rs @@ -11,7 +11,7 @@ use crate::proto::Tx; use crate::types::address::{Address, InternalAddress}; use crate::types::storage::Key; use crate::types::token::{ - is_any_token_balance_key, is_minted_balance_key, minter_key, Amount, + is_any_minted_balance_key, is_any_token_balance_key, minter_key, Amount, }; use crate::vm::WasmCacheAccess; @@ -59,27 +59,27 @@ where let pre: Amount = self.ctx.read_pre(key)?.unwrap_or_default(); let post: Amount = self.ctx.read_post(key)?.unwrap_or_default(); let diff = post.change() - pre.change(); + match changes.get_mut(token) { + Some(change) => *change += diff, + None => _ = changes.insert(token, diff), + } + } else if let Some(token) = is_any_minted_balance_key(key) { + let pre: Amount = self.ctx.read_pre(key)?.unwrap_or_default(); + let post: Amount = self.ctx.read_post(key)?.unwrap_or_default(); + let diff = post.change() - pre.change(); + match mints.get_mut(token) { + Some(mint) => *mint += diff, + None => _ = mints.insert(token, diff), + } - if is_minted_balance_key(token, key) { - match mints.get_mut(token) { - Some(mint) => *mint += diff, - None => _ = mints.insert(token, diff), - } - - // Check if the minter VP is called - let minter_key = minter_key(token); - let minter = match self.ctx.read_post(&minter_key)? { - Some(m) => m, - None => return Ok(false), - }; - if !verifiers.contains(&minter) { - return Ok(false); - } - } else { - match changes.get_mut(token) { - Some(change) => *change += diff, - None => _ = changes.insert(token, diff), - } + // Check if the minter VP is called + let minter_key = minter_key(token); + let minter = match self.ctx.read_post(&minter_key)? { + Some(m) => m, + None => return Ok(false), + }; + if !verifiers.contains(&minter) { + return Ok(false); } } } diff --git a/shared/src/ledger/protocol/mod.rs b/shared/src/ledger/protocol/mod.rs index fbdc6b9bdd..b6920fb4f9 100644 --- a/shared/src/ledger/protocol/mod.rs +++ b/shared/src/ledger/protocol/mod.rs @@ -585,12 +585,12 @@ where replay_protection_vp.ctx.gas_meter.into_inner(); result } - InternalAddress::IbcToken(_) - | InternalAddress::Minted => { - // These addresses should be a part of a multitoken - // key + InternalAddress::IbcToken(_) => { + // The address should be a part of a multitoken key gas_meter = ctx.gas_meter.into_inner(); - Ok(true) + Ok(verifiers.contains(&Address::Internal( + InternalAddress::Multitoken, + ))) } }; diff --git a/tests/src/e2e/helpers.rs b/tests/src/e2e/helpers.rs index 7c79fdfdb6..c9042802d1 100644 --- a/tests/src/e2e/helpers.rs +++ b/tests/src/e2e/helpers.rs @@ -25,8 +25,7 @@ use namada_apps::config::{Config, TendermintMode}; use namada_core::types::token::NATIVE_MAX_DECIMAL_PLACES; use super::setup::{ - self, sleep, NamadaBgCmd, NamadaCmd, Test, ENV_VAR_DEBUG, - ENV_VAR_USE_PREBUILT_BINARIES, + sleep, NamadaCmd, Test, ENV_VAR_DEBUG, ENV_VAR_USE_PREBUILT_BINARIES, }; use crate::e2e::setup::{Bin, Who, APPS_PACKAGE}; use crate::{run, run_as}; diff --git a/wasm/wasm_source/src/vp_token.rs b/wasm/wasm_source/src/vp_token.rs index 98ad53d8d2..002553dd75 100644 --- a/wasm/wasm_source/src/vp_token.rs +++ b/wasm/wasm_source/src/vp_token.rs @@ -56,35 +56,33 @@ fn token_checks( match owner { None => { - if key.segments.get(0) == Some(&token.to_db_key()) { + if let Some(t) = token::is_any_minted_balance_key(key) { + if t == token { + // check if total supply is changed, which it should + // never be from a tx + let total_pre: token::Amount = + ctx.read_pre(key)?.unwrap(); + let total_post: token::Amount = + ctx.read_post(key)?.unwrap(); + if total_pre != total_post { + return reject(); + } + } + } else if key.segments.get(0) == Some(&token.to_db_key()) { // Unknown changes to this address space are disallowed, but // unknown changes anywhere else are permitted return reject(); } } Some(owner) => { - if token::is_minted_balance_key(token, key) { - // check if total supply is changed, which it should never - // be from a tx - let total_pre: token::Amount = ctx.read_pre(key)?.unwrap(); - let total_post: token::Amount = - ctx.read_post(key)?.unwrap(); - if total_pre != total_post { - return reject(); - } - } else { - // accumulate the change - let pre: token::Amount = - ctx.read_pre(key)?.unwrap_or_default(); - let post: token::Amount = - ctx.read_post(key)?.unwrap_or_default(); - // make sure that the spender approved the transaction - if post < pre - && !(verifiers.contains(owner) - || *owner == address::masp()) - { - return reject(); - } + let pre: token::Amount = ctx.read_pre(key)?.unwrap_or_default(); + let post: token::Amount = + ctx.read_post(key)?.unwrap_or_default(); + // make sure that the spender approved the transaction + if post < pre + && !(verifiers.contains(owner) || *owner == address::masp()) + { + return reject(); } } } From aa0cbbade92a730677baa44f27dbc9a303bcfbf4 Mon Sep 17 00:00:00 2001 From: yito88 Date: Thu, 29 Jun 2023 11:14:54 +0200 Subject: [PATCH 09/25] add unit tests --- shared/src/ledger/native_vp/multitoken.rs | 370 ++++++++++++++++++++++ 1 file changed, 370 insertions(+) diff --git a/shared/src/ledger/native_vp/multitoken.rs b/shared/src/ledger/native_vp/multitoken.rs index 08e9437495..9c985b9dab 100644 --- a/shared/src/ledger/native_vp/multitoken.rs +++ b/shared/src/ledger/native_vp/multitoken.rs @@ -93,3 +93,373 @@ where })) } } + +#[cfg(test)] +mod tests { + use std::collections::BTreeSet; + + use borsh::BorshSerialize; + + use super::*; + use crate::core::ledger::storage::testing::TestWlStorage; + use crate::core::types::address::nam; + use crate::core::types::address::testing::{ + established_address_1, established_address_2, + }; + use crate::ledger::gas::VpGasMeter; + use crate::proto::{Code, Data, Section, Signature, Tx}; + use crate::types::address::{Address, InternalAddress}; + use crate::types::key::testing::keypair_1; + use crate::types::storage::TxIndex; + use crate::types::token::{ + balance_key, minted_balance_key, minter_key, Amount, + }; + use crate::types::transaction::TxType; + use crate::vm::wasm::compilation_cache::common::testing::cache as wasm_cache; + + const ADDRESS: Address = Address::Internal(InternalAddress::Multitoken); + + fn dummy_tx(wl_storage: &TestWlStorage) -> Tx { + let tx_code = vec![]; + let tx_data = vec![]; + let mut tx = Tx::new(TxType::Raw); + tx.header.chain_id = wl_storage.storage.chain_id.clone(); + tx.set_code(Code::new(tx_code)); + tx.set_data(Data::new(tx_data)); + tx.add_section(Section::Signature(Signature::new( + tx.code_sechash(), + &keypair_1(), + ))); + tx.add_section(Section::Signature(Signature::new( + tx.data_sechash(), + &keypair_1(), + ))); + tx + } + + #[test] + fn test_valid_transfer() { + let mut wl_storage = TestWlStorage::default(); + let mut keys_changed = BTreeSet::new(); + + let sender = established_address_1(); + let sender_key = balance_key(&nam(), &sender); + let amount = Amount::whole(100); + wl_storage + .storage + .write(&sender_key, amount.try_to_vec().unwrap()) + .expect("write failed"); + + // transfer 10 + let amount = Amount::whole(90); + wl_storage + .write_log + .write(&sender_key, amount.try_to_vec().unwrap()) + .expect("write failed"); + keys_changed.insert(sender_key); + let receiver = established_address_2(); + let receiver_key = balance_key(&nam(), &receiver); + let amount = Amount::whole(10); + wl_storage + .write_log + .write(&receiver_key, amount.try_to_vec().unwrap()) + .expect("write failed"); + keys_changed.insert(receiver_key); + + let tx_index = TxIndex::default(); + let tx = dummy_tx(&wl_storage); + let gas_meter = VpGasMeter::new(0); + let (vp_wasm_cache, _vp_cache_dir) = wasm_cache(); + let verifiers = BTreeSet::new(); + let ctx = Ctx::new( + &ADDRESS, + &wl_storage.storage, + &wl_storage.write_log, + &tx, + &tx_index, + gas_meter, + &keys_changed, + &verifiers, + vp_wasm_cache, + ); + + let vp = MultitokenVp { ctx }; + assert!( + vp.validate_tx(&tx, &keys_changed, &verifiers) + .expect("validation failed") + ); + } + + #[test] + fn test_invalid_transfer() { + let mut wl_storage = TestWlStorage::default(); + let mut keys_changed = BTreeSet::new(); + + let sender = established_address_1(); + let sender_key = balance_key(&nam(), &sender); + let amount = Amount::whole(100); + wl_storage + .storage + .write(&sender_key, amount.try_to_vec().unwrap()) + .expect("write failed"); + + // transfer 10 + let amount = Amount::whole(90); + wl_storage + .write_log + .write(&sender_key, amount.try_to_vec().unwrap()) + .expect("write failed"); + keys_changed.insert(sender_key); + let receiver = established_address_2(); + let receiver_key = balance_key(&nam(), &receiver); + // receive more than 10 + let amount = Amount::whole(100); + wl_storage + .write_log + .write(&receiver_key, amount.try_to_vec().unwrap()) + .expect("write failed"); + keys_changed.insert(receiver_key); + + let tx_index = TxIndex::default(); + let tx = dummy_tx(&wl_storage); + let gas_meter = VpGasMeter::new(0); + let (vp_wasm_cache, _vp_cache_dir) = wasm_cache(); + let verifiers = BTreeSet::new(); + let ctx = Ctx::new( + &ADDRESS, + &wl_storage.storage, + &wl_storage.write_log, + &tx, + &tx_index, + gas_meter, + &keys_changed, + &verifiers, + vp_wasm_cache, + ); + + let vp = MultitokenVp { ctx }; + assert!( + !vp.validate_tx(&tx, &keys_changed, &verifiers) + .expect("validation failed") + ); + } + + #[test] + fn test_valid_mint() { + let mut wl_storage = TestWlStorage::default(); + let mut keys_changed = BTreeSet::new(); + + // mint 100 + let target = established_address_1(); + let target_key = balance_key(&nam(), &target); + let amount = Amount::whole(100); + wl_storage + .write_log + .write(&target_key, amount.try_to_vec().unwrap()) + .expect("write failed"); + keys_changed.insert(target_key); + let minted_key = minted_balance_key(&nam()); + let amount = Amount::whole(100); + wl_storage + .write_log + .write(&minted_key, amount.try_to_vec().unwrap()) + .expect("write failed"); + keys_changed.insert(minted_key); + + // minter + let minter = Address::Internal(InternalAddress::Ibc); + let minter_key = minter_key(&nam()); + wl_storage + .write_log + .write(&minter_key, minter.try_to_vec().unwrap()) + .expect("write failed"); + keys_changed.insert(minter_key); + + let tx_index = TxIndex::default(); + let tx = dummy_tx(&wl_storage); + let gas_meter = VpGasMeter::new(0); + let (vp_wasm_cache, _vp_cache_dir) = wasm_cache(); + let mut verifiers = BTreeSet::new(); + // for the minter + verifiers.insert(minter); + let ctx = Ctx::new( + &ADDRESS, + &wl_storage.storage, + &wl_storage.write_log, + &tx, + &tx_index, + gas_meter, + &keys_changed, + &verifiers, + vp_wasm_cache, + ); + + let vp = MultitokenVp { ctx }; + assert!( + vp.validate_tx(&tx, &keys_changed, &verifiers) + .expect("validation failed") + ); + } + + #[test] + fn test_invalid_mint() { + let mut wl_storage = TestWlStorage::default(); + let mut keys_changed = BTreeSet::new(); + + // mint 100 + let target = established_address_1(); + let target_key = balance_key(&nam(), &target); + // mint more than 100 + let amount = Amount::whole(1000); + wl_storage + .write_log + .write(&target_key, amount.try_to_vec().unwrap()) + .expect("write failed"); + keys_changed.insert(target_key); + let minted_key = minted_balance_key(&nam()); + let amount = Amount::whole(100); + wl_storage + .write_log + .write(&minted_key, amount.try_to_vec().unwrap()) + .expect("write failed"); + keys_changed.insert(minted_key); + + // minter + let minter = Address::Internal(InternalAddress::Ibc); + let minter_key = minter_key(&nam()); + wl_storage + .write_log + .write(&minter_key, minter.try_to_vec().unwrap()) + .expect("write failed"); + keys_changed.insert(minter_key); + + let tx_index = TxIndex::default(); + let tx = dummy_tx(&wl_storage); + let gas_meter = VpGasMeter::new(0); + let (vp_wasm_cache, _vp_cache_dir) = wasm_cache(); + let mut verifiers = BTreeSet::new(); + // for the minter + verifiers.insert(minter); + let ctx = Ctx::new( + &ADDRESS, + &wl_storage.storage, + &wl_storage.write_log, + &tx, + &tx_index, + gas_meter, + &keys_changed, + &verifiers, + vp_wasm_cache, + ); + + let vp = MultitokenVp { ctx }; + assert!( + !vp.validate_tx(&tx, &keys_changed, &verifiers) + .expect("validation failed") + ); + } + + #[test] + fn test_no_minter() { + let mut wl_storage = TestWlStorage::default(); + let mut keys_changed = BTreeSet::new(); + + // mint 100 + let target = established_address_1(); + let target_key = balance_key(&nam(), &target); + let amount = Amount::whole(100); + wl_storage + .write_log + .write(&target_key, amount.try_to_vec().unwrap()) + .expect("write failed"); + keys_changed.insert(target_key); + let minted_key = minted_balance_key(&nam()); + let amount = Amount::whole(100); + wl_storage + .write_log + .write(&minted_key, amount.try_to_vec().unwrap()) + .expect("write failed"); + keys_changed.insert(minted_key); + + // no minter is set + + let tx_index = TxIndex::default(); + let tx = dummy_tx(&wl_storage); + let gas_meter = VpGasMeter::new(0); + let (vp_wasm_cache, _vp_cache_dir) = wasm_cache(); + let verifiers = BTreeSet::new(); + let ctx = Ctx::new( + &ADDRESS, + &wl_storage.storage, + &wl_storage.write_log, + &tx, + &tx_index, + gas_meter, + &keys_changed, + &verifiers, + vp_wasm_cache, + ); + + let vp = MultitokenVp { ctx }; + assert!( + !vp.validate_tx(&tx, &keys_changed, &verifiers) + .expect("validation failed") + ); + } + + #[test] + fn test_no_minter_vp() { + let mut wl_storage = TestWlStorage::default(); + let mut keys_changed = BTreeSet::new(); + + // mint 100 + let target = established_address_1(); + let target_key = balance_key(&nam(), &target); + let amount = Amount::whole(100); + wl_storage + .write_log + .write(&target_key, amount.try_to_vec().unwrap()) + .expect("write failed"); + keys_changed.insert(target_key); + let minted_key = minted_balance_key(&nam()); + let amount = Amount::whole(100); + wl_storage + .write_log + .write(&minted_key, amount.try_to_vec().unwrap()) + .expect("write failed"); + keys_changed.insert(minted_key); + + // minter + let minter = Address::Internal(InternalAddress::Ibc); + let minter_key = minter_key(&nam()); + wl_storage + .write_log + .write(&minter_key, minter.try_to_vec().unwrap()) + .expect("write failed"); + keys_changed.insert(minter_key); + + let tx_index = TxIndex::default(); + let tx = dummy_tx(&wl_storage); + let gas_meter = VpGasMeter::new(0); + let (vp_wasm_cache, _vp_cache_dir) = wasm_cache(); + let verifiers = BTreeSet::new(); + // the minter isn't included in the verifiers + let ctx = Ctx::new( + &ADDRESS, + &wl_storage.storage, + &wl_storage.write_log, + &tx, + &tx_index, + gas_meter, + &keys_changed, + &verifiers, + vp_wasm_cache, + ); + + let vp = MultitokenVp { ctx }; + assert!( + !vp.validate_tx(&tx, &keys_changed, &verifiers) + .expect("validation failed") + ); + } +} From eeb09af2929e734ff3a2fe85a43762f55737700d Mon Sep 17 00:00:00 2001 From: yito88 Date: Sun, 2 Jul 2023 15:32:40 +0200 Subject: [PATCH 10/25] change eth_bridge balance keys to multitoken keys --- apps/src/lib/client/rpc.rs | 115 ++----- .../lib/node/ledger/shell/finalize_block.rs | 18 +- core/src/ledger/eth_bridge/storage/mod.rs | 20 ++ .../eth_bridge/storage/wrapped_erc20s.rs | 291 +++++++----------- core/src/ledger/storage/masp_conversions.rs | 7 +- core/src/proto/types.rs | 4 +- core/src/types/address.rs | 35 ++- .../transactions/ethereum_events/events.rs | 106 +++---- .../transactions/ethereum_events/mod.rs | 24 +- shared/src/ledger/eth_bridge/bridge_pool.rs | 2 +- shared/src/ledger/ibc/vp/context.rs | 5 +- shared/src/ledger/masp.rs | 147 +++------ .../ethereum_bridge/bridge_pool_vp.rs | 22 +- .../ledger/native_vp/ethereum_bridge/vp.rs | 35 ++- shared/src/ledger/native_vp/multitoken.rs | 5 +- shared/src/ledger/protocol/mod.rs | 3 +- shared/src/ledger/queries/shell.rs | 6 +- shared/src/ledger/queries/shell/eth_bridge.rs | 10 +- shared/src/ledger/queries/vp/token.rs | 1 - shared/src/ledger/rpc.rs | 11 +- shared/src/ledger/signing.rs | 67 +--- shared/src/ledger/tx.rs | 26 +- tests/src/e2e/eth_bridge_tests.rs | 1 - tests/src/e2e/eth_bridge_tests/helpers.rs | 14 +- tests/src/e2e/helpers.rs | 3 +- tests/src/native_vp/eth_bridge_pool.rs | 24 +- tx_prelude/src/ibc.rs | 4 +- wasm/wasm_source/src/tx_bridge_pool.rs | 13 +- 28 files changed, 387 insertions(+), 632 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index a3863a9d82..95d0cb592d 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -46,7 +46,7 @@ use namada::types::hash::Hash; use namada::types::key::*; use namada::types::masp::{BalanceOwner, ExtendedViewingKey, PaymentAddress}; use namada::types::storage::{BlockHeight, BlockResults, Epoch, Key, KeySeg}; -use namada::types::token::{Change, Denomination, MaspDenom, TokenAddress}; +use namada::types::token::{Change, Denomination, MaspDenom}; use namada::types::{storage, token}; use tokio::time::Instant; @@ -119,7 +119,6 @@ pub async fn query_transfers< args: args::QueryTransfers, ) { let query_token = args.token; - let sub_prefix = args.sub_prefix.map(|s| Key::parse(s).unwrap()); let query_owner = args.owner.map_or_else( || Either::Right(wallet.get_addresses().into_values().collect()), Either::Left, @@ -173,10 +172,8 @@ pub async fn query_transfers< // Check if this transfer pertains to the supplied token relevant &= match &query_token { Some(token) => { - let check = |(tok, chg): (&TokenAddress, &Change)| { - tok.sub_prefix == sub_prefix - && &tok.address == token - && !chg.is_zero() + let check = |(tok, chg): (&Address, &Change)| { + &tok.address == token && !chg.is_zero() }; tfer_delta.values().cloned().any( |MaspChange { ref asset, change }| check((asset, &change)), @@ -431,13 +428,8 @@ pub async fn query_pinned_balance< (Ok((balance, epoch)), Some(token), sub_prefix) => { let token_alias = lookup_alias(wallet, token); - let token_address = TokenAddress { - address: token.clone(), - sub_prefix: sub_prefix - .map(|string| Key::parse(string).unwrap()), - }; let total_balance = balance - .get(&(epoch, token_address.clone())) + .get(&(epoch, token.clone())) .cloned() .unwrap_or_default(); @@ -447,12 +439,12 @@ pub async fn query_pinned_balance< Received no shielded {}", owner, epoch, - token_address.format_with_alias(&token_alias) + token.format_with_alias(&token_alias) ); } else { let formatted = format_denominated_amount( client, - &token_address, + &token, total_balance.into(), ) .await; @@ -462,7 +454,7 @@ pub async fn query_pinned_balance< owner, epoch, formatted, - token_address.format_with_alias(&token_alias), + token.format_with_alias(&token_alias), ); } } @@ -760,27 +752,22 @@ pub async fn query_shielded_balance< let token_alias = lookup_alias(wallet, &token); - let token_address = TokenAddress { - address: token, - sub_prefix: args.sub_prefix.map(|k| Key::parse(k).unwrap()), - }; - let total_balance = balance - .get(&(epoch, token_address.clone())) + .get(&(epoch, token.clone())) .cloned() .unwrap_or_default(); if total_balance.is_zero() { println!( "No shielded {} balance found for given key", - token_address.format_with_alias(&token_alias) + token.format_with_alias(&token_alias) ); } else { println!( "{}: {}", - token_address.format_with_alias(&token_alias), + token.format_with_alias(&token_alias), format_denominated_amount( client, - &token_address, + &token, token::Amount::from(total_balance) ) .await @@ -813,9 +800,6 @@ pub async fn query_shielded_balance< } } - // These are the asset types for which we have human-readable names - let mut read_tokens: HashMap>> = - HashMap::new(); // Print non-zero balances whose asset types can be decoded // TODO Implement a function for this @@ -835,72 +819,21 @@ pub async fn query_shielded_balance< } } } - for ( - ( - fvk, - TokenAddress { - address: addr, - sub_prefix, - }, - ), - token_balance, - ) in balance_map - { - read_tokens - .entry(addr.clone()) - .and_modify(|addr_vec| addr_vec.push(sub_prefix.clone())) - .or_insert_with(|| vec![sub_prefix.clone()]); - let token_address = TokenAddress { - address: addr, - sub_prefix, - }; + for ((fvk, token), token_balance) in balance_map { // Only assets with the current timestamp count let alias = tokens - .get(&token_address.address) + .get(&token) .map(|a| a.to_string()) - .unwrap_or_else(|| token_address.address.to_string()); - println!( - "Shielded Token {}:", - token_address.format_with_alias(&alias), - ); + .unwrap_or_else(|| token.to_string()); + println!("Shielded Token {}:", alias); let formatted = format_denominated_amount( client, - &token_address, + &token, token_balance.into(), ) .await; println!(" {}, owned by {}", formatted, fvk); } - // Print zero balances for remaining assets - for token in tokens { - if let Some(sub_addrs) = read_tokens.get(&token) { - let token_alias = lookup_alias(wallet, &token); - for sub_addr in sub_addrs { - match sub_addr { - // abstract out these prints - Some(sub_addr) => { - println!( - "Shielded Token {}/{}:", - token_alias, sub_addr - ); - println!( - "No shielded {}/{} balance found for any \ - wallet key", - token_alias, sub_addr - ); - } - None => { - println!("Shielded Token {}:", token_alias,); - println!( - "No shielded {} balance found for any \ - wallet key", - token_alias - ); - } - } - } - } - } } // Here the user wants to know the balance for a specific token across // users @@ -918,16 +851,9 @@ pub async fn query_shielded_balance< println!("Shielded Token {}:", token_alias); let mut found_any = false; let token_alias = lookup_alias(wallet, &token); - let token_address = TokenAddress { - address: token.clone(), - sub_prefix: args - .sub_prefix - .as_ref() - .map(|k| Key::parse(k).unwrap()), - }; println!( "Shielded Token {}:", - token_address.format_with_alias(&token_alias), + token.format_with_alias(&token_alias), ); for fvk in viewing_keys { // Query the multi-asset balance at the given spending key @@ -960,7 +886,7 @@ pub async fn query_shielded_balance< if !found_any { println!( "No shielded {} balance found for any wallet key", - token_address.format_with_alias(&token_alias), + token.format_with_alias(&token_alias), ); } } @@ -2270,10 +2196,7 @@ pub async fn validate_amount( InputAmount::Validated(amt) => return amt, }; let denom = unwrap_client_response::>( - RPC.vp() - .token() - .denomination(client, token, sub_prefix) - .await, + RPC.vp().token().denomination(client, token).await, ) .unwrap_or_else(|| { if force { diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 50e470c731..5c65930167 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -21,7 +21,7 @@ use namada::types::address::Address; use namada::types::dec::Dec; use namada::types::key::tm_raw_hash_to_string; use namada::types::storage::{BlockHash, BlockResults, Epoch, Header}; -use namada::types::token::{minted_balance_key, Amount}; +use namada::types::token::Amount; use namada::types::transaction::protocol::{ ethereum_tx_data_variants, ProtocolTxType, }; @@ -707,9 +707,9 @@ where .expect("PoS inflation amount should exist in storage"); // Read from PoS storage let total_tokens = self - .read_storage_key(&minted_balance_key(&staking_token_address( - &self.wl_storage, - ))) + .read_storage_key(&token::minted_balance_key( + &staking_token_address(&self.wl_storage), + )) .expect("Total NAM balance should exist in storage"); let pos_locked_supply = read_total_stake(&self.wl_storage, ¶ms, last_epoch)?; @@ -1628,10 +1628,12 @@ mod test_finalize_block { let bertha = crate::wallet::defaults::bertha_address(); // add bertha's escrowed `asset` to the pool { - let asset_key = wrapped_erc20s::Keys::from(&asset); - let owner_key = - asset_key.balance(&bridge_pool::BRIDGE_POOL_ADDRESS); - let supply_key = asset_key.supply(); + let token = wrapped_erc20s::token(&asset); + let owner_key = token::balance_key( + &token, + &bridge_pool::BRIDGE_POOL_ADDRESS, + ); + let supply_key = token::minted_balance_key(&token); let amt: Amount = 999_999_u64.into(); shell .wl_storage diff --git a/core/src/ledger/eth_bridge/storage/mod.rs b/core/src/ledger/eth_bridge/storage/mod.rs index 958b002af0..0906be3d5d 100644 --- a/core/src/ledger/eth_bridge/storage/mod.rs +++ b/core/src/ledger/eth_bridge/storage/mod.rs @@ -29,6 +29,7 @@ pub fn escrow_key(nam_addr: &Address) -> Key { pub fn is_eth_bridge_key(nam_addr: &Address, key: &Key) -> bool { key == &escrow_key(nam_addr) || matches!(key.segments.get(0), Some(first_segment) if first_segment == &ADDRESS.to_db_key()) + || wrapped_erc20s::has_erc20_segment(key) } /// A key for storing the active / inactive status @@ -62,6 +63,7 @@ mod test { use super::*; use crate::types::address; use crate::types::address::nam; + use crate::types::ethereum_events::testing::arbitrary_eth_address; #[test] fn test_is_eth_bridge_key_returns_true_for_eth_bridge_address() { @@ -77,6 +79,17 @@ mod test { assert!(is_eth_bridge_key(&nam(), &key)); } + #[test] + fn test_is_eth_bridge_key_returns_true_for_eth_bridge_balance_key() { + let eth_addr = arbitrary_eth_address(); + let token = address::Address::Internal( + address::InternalAddress::Erc20(eth_addr), + ); + let key = + balance_key(&token, &address::testing::established_address_1()); + assert!(is_eth_bridge_key(&nam(), &key)); + } + #[test] fn test_is_eth_bridge_key_returns_false_for_different_address() { let key = @@ -92,4 +105,11 @@ mod test { .expect("Could not set up test"); assert!(!is_eth_bridge_key(&nam(), &key)); } + + #[test] + fn test_is_eth_bridge_key_returns_false_for_non_eth_bridge_balance_key() { + let key = + balance_key(&nam(), &address::testing::established_address_1()); + assert!(!is_eth_bridge_key(&nam(), &key)); + } } diff --git a/core/src/ledger/eth_bridge/storage/wrapped_erc20s.rs b/core/src/ledger/eth_bridge/storage/wrapped_erc20s.rs index 6d2f6de4da..beb4a61723 100644 --- a/core/src/ledger/eth_bridge/storage/wrapped_erc20s.rs +++ b/core/src/ledger/eth_bridge/storage/wrapped_erc20s.rs @@ -1,68 +1,17 @@ //! Functionality for accessing the multitoken subspace -use std::str::FromStr; use eyre::eyre; -use crate::types::address::Address; +use crate::types::address::{Address, InternalAddress}; use crate::types::ethereum_events::EthAddress; -use crate::types::storage::{self, DbKeySeg, KeySeg}; - -#[allow(missing_docs)] -pub const MULTITOKEN_KEY_SEGMENT: &str = "ERC20"; - -/// Get the key prefix corresponding to the storage subspace that holds wrapped -/// ERC20 tokens -pub fn prefix() -> storage::Key { - super::prefix() - .push(&MULTITOKEN_KEY_SEGMENT.to_owned()) - .expect("should always be able to construct this key") -} - -const BALANCE_KEY_SEGMENT: &str = "balance"; -const SUPPLY_KEY_SEGMENT: &str = "supply"; - -/// Generator for the keys under which details of an ERC20 token are stored -pub struct Keys { - /// The prefix of keys under which the details for a specific ERC20 token - /// are stored - prefix: storage::Key, -} - -impl Keys { - /// Get the `balance` key for a specific owner - there should be a - /// [`crate::types::token::Amount`] stored here - pub fn balance(&self, owner: &Address) -> storage::Key { - self.prefix - .push(&BALANCE_KEY_SEGMENT.to_owned()) - .expect("should always be able to construct this key") - .push(&owner.to_db_key()) - .expect("should always be able to construct this key") - } - - /// Get the `supply` key - there should be a - /// [`crate::types::token::Amount`] stored here - pub fn supply(&self) -> storage::Key { - self.prefix - .push(&SUPPLY_KEY_SEGMENT.to_owned()) - .expect("should always be able to construct this key") - } -} - -impl From<&EthAddress> for Keys { - fn from(address: &EthAddress) -> Self { - Keys { - prefix: prefix() - .push(&address.to_canonical()) - .expect("should always be able to construct this key"), - } - } -} - -/// Construct a sub-prefix from an ERC20 address. -pub fn sub_prefix(address: &EthAddress) -> storage::Key { - storage::Key::from(MULTITOKEN_KEY_SEGMENT.to_owned().to_db_key()) - .push(&address.to_db_key()) - .expect("should always be able to construct this key") +use crate::types::storage::{self, DbKeySeg}; +use crate::types::token::{ + balance_key, minted_balance_key, MINTED_STORAGE_KEY, +}; + +/// Construct a token address from an ERC20 address. +pub fn token(address: &EthAddress) -> Address { + Address::Internal(InternalAddress::Erc20(address.clone())) } /// Represents the type of a key relating to a wrapped ERC20 @@ -88,19 +37,22 @@ pub struct Key { impl From<&Key> for storage::Key { fn from(mt_key: &Key) -> Self { - let keys = Keys::from(&mt_key.asset); + let token = token(&mt_key.asset); match &mt_key.suffix { - KeyType::Balance { owner } => keys.balance(owner), - KeyType::Supply => keys.supply(), + KeyType::Balance { owner } => balance_key(&token, owner), + KeyType::Supply => minted_balance_key(&token), } } } -fn has_erc20_segment(key: &storage::Key) -> bool { - matches!( - key.segments.get(1), - Some(segment) if segment == &DbKeySeg::StringSeg(MULTITOKEN_KEY_SEGMENT.to_owned()), - ) +/// Returns true if the given key has an ERC20 token +pub fn has_erc20_segment(key: &storage::Key) -> bool { + match key.segments.get(1) { + Some(DbKeySeg::AddressSeg(Address::Internal( + InternalAddress::Erc20(_addr), + ))) => true, + _ => false, + } } impl TryFrom<(&Address, &storage::Key)> for Key { @@ -116,52 +68,41 @@ impl TryFrom<(&Address, &storage::Key)> for Key { return Err(eyre!("key does not have ERC20 segment")); } - let asset = - if let Some(DbKeySeg::StringSeg(segment)) = key.segments.get(2) { - EthAddress::from_str(segment)? - } else { - return Err(eyre!( - "key has an incorrect segment at index #2, expected an \ - Ethereum address" - )); - }; - - let segment_3 = - if let Some(DbKeySeg::StringSeg(segment)) = key.segments.get(3) { - segment.to_owned() - } else { - return Err(eyre!( - "key has an incorrect segment at index #3, expected a \ - string segment" - )); - }; - - match segment_3.as_str() { - SUPPLY_KEY_SEGMENT => { - let supply_key = Key { + let asset = if let Some(DbKeySeg::AddressSeg(Address::Internal( + InternalAddress::Erc20(addr), + ))) = key.segments.get(1) + { + addr.clone() + } else { + return Err(eyre!( + "key has an incorrect segment at index #2, expected an \ + Ethereum address" + )); + }; + + match key.segments.get(3) { + Some(DbKeySeg::AddressSeg(owner)) => { + let balance_key = Key { asset, - suffix: KeyType::Supply, + suffix: KeyType::Balance { + owner: owner.clone(), + }, }; - Ok(supply_key) + Ok(balance_key) } - BALANCE_KEY_SEGMENT => { - let owner = if let Some(DbKeySeg::AddressSeg(address)) = - key.segments.get(4) - { - address.to_owned() - } else { - return Err(eyre!( - "key has an incorrect segment at index #4, expected \ - an address segment" - )); - }; - let balance_key = Key { + Some(DbKeySeg::StringSeg(segment)) + if segment == MINTED_STORAGE_KEY => + { + let supply_key = Key { asset, - suffix: KeyType::Balance { owner }, + suffix: KeyType::Supply, }; - Ok(balance_key) + Ok(supply_key) } - _ => Err(eyre!("key has unrecognized string segment at index #3")), + _ => Err(eyre!( + "key has an incorrect segment at index #3, expected a string \ + segment" + )), } } } @@ -174,97 +115,77 @@ mod test { use super::*; use crate::ledger::eth_bridge::ADDRESS; use crate::types::address::{nam, Address}; - use crate::types::ethereum_events::testing::{ - DAI_ERC20_ETH_ADDRESS, DAI_ERC20_ETH_ADDRESS_CHECKSUMMED, - }; + use crate::types::ethereum_events::testing::DAI_ERC20_ETH_ADDRESS; use crate::types::storage::DbKeySeg; + use crate::types::token::BALANCE_STORAGE_KEY; + const MULTITOKEN_ADDRESS: Address = + Address::Internal(InternalAddress::Multitoken); const ARBITRARY_OWNER_ADDRESS: &str = "atest1d9khqw36x9zyxwfhgfpygv2pgc65gse4gy6rjs34gfzr2v69gy6y23zpggurjv2yx5m52sesu6r4y4"; - #[test] - fn test_prefix() { - assert_matches!( - &prefix().segments[..], - [ - DbKeySeg::AddressSeg(multitoken_addr), - DbKeySeg::StringSeg(multitoken_path), - ] if multitoken_addr == &ADDRESS && - multitoken_path == MULTITOKEN_KEY_SEGMENT - ) - } - - #[test] - fn test_keys_from_eth_address() { - let keys: Keys = (&DAI_ERC20_ETH_ADDRESS).into(); - assert_matches!( - &keys.prefix.segments[..], - [ - DbKeySeg::AddressSeg(multitoken_addr), - DbKeySeg::StringSeg(multitoken_path), - DbKeySeg::StringSeg(token_id), - ] if multitoken_addr == &ADDRESS && - multitoken_path == MULTITOKEN_KEY_SEGMENT && - token_id == &DAI_ERC20_ETH_ADDRESS_CHECKSUMMED.to_ascii_lowercase() - ) + fn dai_erc20_token() -> Address { + Address::Internal(InternalAddress::Erc20(DAI_ERC20_ETH_ADDRESS)) } #[test] fn test_keys_balance() { - let keys: Keys = (&DAI_ERC20_ETH_ADDRESS).into(); - let key = - keys.balance(&Address::from_str(ARBITRARY_OWNER_ADDRESS).unwrap()); + let token = token(&DAI_ERC20_ETH_ADDRESS); + let key = balance_key( + &token, + &Address::from_str(ARBITRARY_OWNER_ADDRESS).unwrap(), + ); assert_matches!( &key.segments[..], [ DbKeySeg::AddressSeg(multitoken_addr), - DbKeySeg::StringSeg(multitoken_path), - DbKeySeg::StringSeg(token_id), + DbKeySeg::AddressSeg(token_addr), DbKeySeg::StringSeg(balance_key_seg), DbKeySeg::AddressSeg(owner_addr), - ] if multitoken_addr == &ADDRESS && - multitoken_path == MULTITOKEN_KEY_SEGMENT && - token_id == &DAI_ERC20_ETH_ADDRESS_CHECKSUMMED.to_ascii_lowercase() && - balance_key_seg == BALANCE_KEY_SEGMENT && + ] if multitoken_addr == &MULTITOKEN_ADDRESS && + token_addr == &dai_erc20_token() && + balance_key_seg == BALANCE_STORAGE_KEY && owner_addr == &Address::decode(ARBITRARY_OWNER_ADDRESS).unwrap() ) } #[test] fn test_keys_balance_to_string() { - let keys: Keys = (&DAI_ERC20_ETH_ADDRESS).into(); - let key = - keys.balance(&Address::from_str(ARBITRARY_OWNER_ADDRESS).unwrap()); + let token = token(&DAI_ERC20_ETH_ADDRESS); + let key = balance_key( + &token, + &Address::from_str(ARBITRARY_OWNER_ADDRESS).unwrap(), + ); assert_eq!( - "#atest1v9hx7w36g42ysgzzwf5kgem9ypqkgerjv4ehxgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpq8f99ew/ERC20/0x6b175474e89094c44da98b954eedeac495271d0f/balance/#atest1d9khqw36x9zyxwfhgfpygv2pgc65gse4gy6rjs34gfzr2v69gy6y23zpggurjv2yx5m52sesu6r4y4", + "#atest1v9hx7w36f46kcarfw3hkketwyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpq4w0mck/#atest1v46xsw36xe3rzde4xsmngefc8ycrjdrrxs6xgcfe8p3rjdf5v4jkgetpvv6rjdfjxuckgvrxqhdj5x/balance/#atest1d9khqw36x9zyxwfhgfpygv2pgc65gse4gy6rjs34gfzr2v69gy6y23zpggurjv2yx5m52sesu6r4y4", key.to_string() ) } #[test] fn test_keys_supply() { - let keys: Keys = (&DAI_ERC20_ETH_ADDRESS).into(); - let key = keys.supply(); + let token = token(&DAI_ERC20_ETH_ADDRESS); + let key = minted_balance_key(&token); assert_matches!( &key.segments[..], [ DbKeySeg::AddressSeg(multitoken_addr), - DbKeySeg::StringSeg(multitoken_path), - DbKeySeg::StringSeg(token_id), + DbKeySeg::AddressSeg(token_addr), + DbKeySeg::StringSeg(balance_key_seg), DbKeySeg::StringSeg(supply_key_seg), - ] if multitoken_addr == &ADDRESS && - multitoken_path == MULTITOKEN_KEY_SEGMENT && - token_id == &DAI_ERC20_ETH_ADDRESS_CHECKSUMMED.to_ascii_lowercase() && - supply_key_seg == SUPPLY_KEY_SEGMENT + ] if multitoken_addr == &MULTITOKEN_ADDRESS && + token_addr == &dai_erc20_token() && + balance_key_seg == BALANCE_STORAGE_KEY && + supply_key_seg == MINTED_STORAGE_KEY ) } #[test] fn test_keys_supply_to_string() { - let keys: Keys = (&DAI_ERC20_ETH_ADDRESS).into(); - let key = keys.supply(); + let token = token(&DAI_ERC20_ETH_ADDRESS); + let key = minted_balance_key(&token); assert_eq!( - "#atest1v9hx7w36g42ysgzzwf5kgem9ypqkgerjv4ehxgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpq8f99ew/ERC20/0x6b175474e89094c44da98b954eedeac495271d0f/supply", + "#atest1v9hx7w36f46kcarfw3hkketwyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpq4w0mck/#atest1v46xsw36xe3rzde4xsmngefc8ycrjdrrxs6xgcfe8p3rjdf5v4jkgetpvv6rjdfjxuckgvrxqhdj5x/balance/minted", key.to_string(), ) } @@ -281,13 +202,13 @@ mod test { &key.segments[..], [ DbKeySeg::AddressSeg(multitoken_addr), - DbKeySeg::StringSeg(multitoken_path), - DbKeySeg::StringSeg(token_id), + DbKeySeg::AddressSeg(token_addr), + DbKeySeg::StringSeg(balance_key_seg), DbKeySeg::StringSeg(supply_key_seg), - ] if multitoken_addr == &ADDRESS && - multitoken_path == MULTITOKEN_KEY_SEGMENT && - token_id == &DAI_ERC20_ETH_ADDRESS_CHECKSUMMED.to_ascii_lowercase() && - supply_key_seg == SUPPLY_KEY_SEGMENT + ] if multitoken_addr == &MULTITOKEN_ADDRESS && + token_addr == &dai_erc20_token() && + balance_key_seg == &BALANCE_STORAGE_KEY && + supply_key_seg == MINTED_STORAGE_KEY ); // balance key @@ -302,14 +223,12 @@ mod test { &key.segments[..], [ DbKeySeg::AddressSeg(multitoken_addr), - DbKeySeg::StringSeg(multitoken_path), - DbKeySeg::StringSeg(token_id), + DbKeySeg::AddressSeg(token_addr), DbKeySeg::StringSeg(balance_key_seg), DbKeySeg::AddressSeg(owner_addr), - ] if multitoken_addr == &ADDRESS && - multitoken_path == MULTITOKEN_KEY_SEGMENT && - token_id == &DAI_ERC20_ETH_ADDRESS_CHECKSUMMED.to_ascii_lowercase() && - balance_key_seg == BALANCE_KEY_SEGMENT && + ] if multitoken_addr == &MULTITOKEN_ADDRESS && + token_addr == &dai_erc20_token() && + balance_key_seg == BALANCE_STORAGE_KEY && owner_addr == &Address::decode(ARBITRARY_OWNER_ADDRESS).unwrap() ); } @@ -318,9 +237,10 @@ mod test { fn test_try_from_key_for_multitoken_key_supply() { // supply key let key = storage::Key::from_str(&format!( - "#{}/ERC20/{}/supply", - ADDRESS, - DAI_ERC20_ETH_ADDRESS_CHECKSUMMED.to_ascii_lowercase(), + "#{}/#{}/balance/{}", + MULTITOKEN_ADDRESS, + dai_erc20_token(), + MINTED_STORAGE_KEY, )) .expect("Should be able to construct key for test"); @@ -344,9 +264,9 @@ mod test { fn test_try_from_key_for_multitoken_key_balance() { // supply key let key = storage::Key::from_str(&format!( - "#{}/ERC20/{}/balance/#{}", + "#{}/#{}/balance/#{}", ADDRESS, - DAI_ERC20_ETH_ADDRESS_CHECKSUMMED.to_ascii_lowercase(), + dai_erc20_token(), ARBITRARY_OWNER_ADDRESS )) .expect("Should be able to construct key for test"); @@ -375,9 +295,9 @@ mod test { #[test] fn test_has_erc20_segment() { let key = storage::Key::from_str(&format!( - "#{}/ERC20/{}/balance/#{}", + "#{}/#{}/balance/#{}", ADDRESS, - DAI_ERC20_ETH_ADDRESS_CHECKSUMMED.to_ascii_lowercase(), + dai_erc20_token(), ARBITRARY_OWNER_ADDRESS )) .expect("Should be able to construct key for test"); @@ -385,16 +305,21 @@ mod test { assert!(has_erc20_segment(&key)); let key = storage::Key::from_str(&format!( - "#{}/ERC20/{}/supply", + "#{}/#{}/balance/{}", ADDRESS, - DAI_ERC20_ETH_ADDRESS_CHECKSUMMED.to_ascii_lowercase(), + dai_erc20_token(), + MINTED_STORAGE_KEY, )) .expect("Should be able to construct key for test"); assert!(has_erc20_segment(&key)); - let key = storage::Key::from_str(&format!("#{}/ERC20", ADDRESS)) - .expect("Should be able to construct key for test"); + let key = storage::Key::from_str(&format!( + "#{}/#{}", + MULTITOKEN_ADDRESS, + dai_erc20_token() + )) + .expect("Should be able to construct key for test"); assert!(has_erc20_segment(&key)); } diff --git a/core/src/ledger/storage/masp_conversions.rs b/core/src/ledger/storage/masp_conversions.rs index eefc6d7342..bbb61ff50d 100644 --- a/core/src/ledger/storage/masp_conversions.rs +++ b/core/src/ledger/storage/masp_conversions.rs @@ -23,12 +23,7 @@ pub struct ConversionState { #[allow(clippy::type_complexity)] pub assets: BTreeMap< AssetType, - ( - (Address, MaspDenom), - Epoch, - AllowedConversion, - usize, - ), + ((Address, MaspDenom), Epoch, AllowedConversion, usize), >, } diff --git a/core/src/proto/types.rs b/core/src/proto/types.rs index 69da19f58d..610d453654 100644 --- a/core/src/proto/types.rs +++ b/core/src/proto/types.rs @@ -28,7 +28,7 @@ use crate::types::address::Address; use crate::types::chain::ChainId; use crate::types::keccak::{keccak_hash, KeccakHash}; use crate::types::key::{self, *}; -use crate::types::storage::{Epoch, Key}; +use crate::types::storage::Epoch; use crate::types::time::DateTimeUtc; use crate::types::token::MaspDenom; #[cfg(feature = "ferveo-tpke")] @@ -671,7 +671,7 @@ pub struct MaspBuilder { pub target: crate::types::hash::Hash, /// The decoded set of asset types used by the transaction. Useful for /// offline wallets trying to display AssetTypes. - pub asset_types: HashSet<(Address, Option, MaspDenom, Epoch)>, + pub asset_types: HashSet<(Address, MaspDenom, Epoch)>, /// Track how Info objects map to descriptors and outputs #[serde( serialize_with = "borsh_serde::", diff --git a/core/src/types/address.rs b/core/src/types/address.rs index cb36593edc..1a6611a2f5 100644 --- a/core/src/types/address.rs +++ b/core/src/types/address.rs @@ -97,6 +97,8 @@ const PREFIX_IMPLICIT: &str = "imp"; const PREFIX_INTERNAL: &str = "ano"; /// Fixed-length address strings prefix for IBC addresses. const PREFIX_IBC: &str = "ibc"; +/// Fixed-length address strings prefix for Ethereum addresses. +const PREFIX_ETH: &str = "eth"; #[allow(missing_docs)] #[derive(Error, Debug)] @@ -230,6 +232,11 @@ impl Address { InternalAddress::EthBridgePool => { internal::ETH_BRIDGE_POOL.to_string() } + InternalAddress::Erc20(eth_addr) => { + let eth_addr = + eth_addr.to_canonical().replace("0x", ""); + format!("{}::{}", PREFIX_ETH, eth_addr) + } InternalAddress::ReplayProtection => { internal::REPLAY_PROTECTION.to_string() } @@ -327,6 +334,23 @@ impl Address { "Invalid IBC internal address", )), }, + Some((PREFIX_ETH, raw)) => match string { + _ if raw.len() == HASH_HEX_LEN => { + match EthAddress::from_str(&format!("0x{}", raw)) { + Ok(eth_addr) => Ok(Address::Internal( + InternalAddress::Erc20(eth_addr), + )), + Err(e) => Err(Error::new( + ErrorKind::InvalidData, + e.to_string(), + )), + } + } + _ => Err(Error::new( + ErrorKind::InvalidData, + "Invalid ERC20 internal address", + )), + }, _ => Err(Error::new( ErrorKind::InvalidData, "Invalid address prefix", @@ -523,6 +547,8 @@ pub enum InternalAddress { EthBridge, /// The pool of transactions to be relayed to Ethereum EthBridgePool, + /// ERC20 token for Ethereum bridge + Erc20(EthAddress), /// Replay protection contains transactions' hash ReplayProtection, /// Multitoken @@ -544,6 +570,7 @@ impl Display for InternalAddress { Self::IbcToken(hash) => format!("IbcToken: {}", hash), Self::EthBridge => "EthBridge".to_string(), Self::EthBridgePool => "EthBridgePool".to_string(), + Self::Erc20(eth_addr) => format!("Erc20: {}", eth_addr), Self::ReplayProtection => "ReplayProtection".to_string(), Self::Multitoken => "Multitoken".to_string(), } @@ -838,6 +865,7 @@ pub mod testing { InternalAddress::IbcToken(_) => {} InternalAddress::EthBridge => {} InternalAddress::EthBridgePool => {} + InternalAddress::Erc20(_) => {} InternalAddress::ReplayProtection => {} InternalAddress::Multitoken => {} /* Add new addresses in the * `prop_oneof` below. */ @@ -852,13 +880,13 @@ pub mod testing { Just(InternalAddress::SlashFund), Just(InternalAddress::EthBridge), Just(InternalAddress::EthBridgePool), + Just(arb_erc20()), Just(InternalAddress::ReplayProtection), Just(InternalAddress::Multitoken), ] } fn arb_ibc_token() -> impl Strategy { - // use sha2::{Digest, Sha256}; ("[a-zA-Z0-9_]{2,128}", any::()).prop_map(|(id, counter)| { let mut hasher = sha2::Sha256::new(); let s = format!( @@ -873,4 +901,9 @@ pub mod testing { InternalAddress::IbcToken(hash) }) } + + fn arb_erc20() -> InternalAddress { + use crate::types::ethereum_events::testing::arbitrary_eth_address; + InternalAddress::Erc20(arbitrary_eth_address()) + } } diff --git a/ethereum_bridge/src/protocol/transactions/ethereum_events/events.rs b/ethereum_bridge/src/protocol/transactions/ethereum_events/events.rs index 1da9c5381c..8ef07a64a5 100644 --- a/ethereum_bridge/src/protocol/transactions/ethereum_events/events.rs +++ b/ethereum_bridge/src/protocol/transactions/ethereum_events/events.rs @@ -3,7 +3,7 @@ use std::collections::{BTreeSet, HashSet}; use std::str::FromStr; -use borsh::BorshDeserialize; +use borsh::{BorshDeserialize, BorshSerialize}; use eyre::{Result, WrapErr}; use namada_core::hints; use namada_core::ledger::eth_bridge::storage::bridge_pool::{ @@ -26,7 +26,7 @@ use namada_core::types::ethereum_events::{ }; use namada_core::types::storage::{BlockHeight, Key, KeySeg}; use namada_core::types::token; -use namada_core::types::token::balance_key; +use namada_core::types::token::{balance_key, minted_balance_key, minter_key}; use crate::parameters::read_native_erc20_address; use crate::protocol::transactions::update; @@ -232,8 +232,8 @@ where H: 'static + StorageHasher + Sync, { let mut changed_keys = BTreeSet::default(); - let keys: wrapped_erc20s::Keys = asset.into(); - let balance_key = keys.balance(receiver); + let token = wrapped_erc20s::token(asset); + let balance_key = balance_key(&token, receiver); update::amount(wl_storage, &balance_key, |balance| { tracing::debug!( %balance_key, @@ -249,7 +249,7 @@ where })?; _ = changed_keys.insert(balance_key); - let supply_key = keys.supply(); + let supply_key = minted_balance_key(&token); update::amount(wl_storage, &supply_key, |supply| { tracing::debug!( %supply_key, @@ -264,6 +264,11 @@ where ); })?; _ = changed_keys.insert(supply_key); + + let minter_key = minter_key(&token); + wl_storage.write_bytes(&minter_key, BRIDGE_POOL_ADDRESS.try_to_vec()?)?; + _ = changed_keys.insert(minter_key); + Ok(changed_keys) } @@ -475,12 +480,9 @@ where ); (escrow_balance_key, sender_balance_key) } else { - let sub_prefix = wrapped_erc20s::sub_prefix(&transfer.transfer.asset); - let prefix = multitoken_balance_prefix(&BRIDGE_ADDRESS, &sub_prefix); - let escrow_balance_key = - multitoken_balance_key(&prefix, &BRIDGE_POOL_ADDRESS); - let sender_balance_key = - multitoken_balance_key(&prefix, &transfer.transfer.sender); + let token = wrapped_erc20s::token(&transfer.transfer.asset); + let escrow_balance_key = balance_key(&token, &BRIDGE_POOL_ADDRESS); + let sender_balance_key = balance_key(&token, &transfer.transfer.sender); (escrow_balance_key, sender_balance_key) }; update::amount(wl_storage, &source, |balance| { @@ -516,15 +518,15 @@ where return Ok(changed_keys); } - let keys: wrapped_erc20s::Keys = (&transfer.transfer.asset).into(); + let token = wrapped_erc20s::token(&transfer.transfer.asset); - let escrow_balance_key = keys.balance(&BRIDGE_POOL_ADDRESS); + let escrow_balance_key = balance_key(&token, &BRIDGE_POOL_ADDRESS); update::amount(wl_storage, &escrow_balance_key, |balance| { balance.spend(&transfer.transfer.amount); })?; _ = changed_keys.insert(escrow_balance_key); - let supply_key = keys.supply(); + let supply_key = minted_balance_key(&token); update::amount(wl_storage, &supply_key, |supply| { supply.spend(&transfer.transfer.amount); })?; @@ -657,12 +659,8 @@ mod tests { ) .expect("Test failed"); } else { - let sub_prefix = - wrapped_erc20s::sub_prefix(&transfer.transfer.asset); - let prefix = - multitoken_balance_prefix(&BRIDGE_ADDRESS, &sub_prefix); - let sender_key = - multitoken_balance_key(&prefix, &transfer.transfer.sender); + let token = wrapped_erc20s::token(&transfer.transfer.asset); + let sender_key = balance_key(&token, &transfer.transfer.sender); let sender_balance = Amount::from(0); wl_storage .write_bytes( @@ -670,8 +668,7 @@ mod tests { sender_balance.try_to_vec().expect("Test failed"), ) .expect("Test failed"); - let escrow_key = - multitoken_balance_key(&prefix, &BRIDGE_POOL_ADDRESS); + let escrow_key = balance_key(&token, &BRIDGE_POOL_ADDRESS); let escrow_balance = Amount::from(10); wl_storage .write_bytes( @@ -679,11 +676,13 @@ mod tests { escrow_balance.try_to_vec().expect("Test failed"), ) .expect("Test failed"); - let asset_keys: wrapped_erc20s::Keys = - (&transfer.transfer.asset).into(); - update::amount(wl_storage, &asset_keys.supply(), |supply| { - supply.receive(&transfer.transfer.amount); - }) + update::amount( + wl_storage, + &minted_balance_key(&token), + |supply| { + supply.receive(&transfer.transfer.amount); + }, + ) .expect("Test failed"); }; let gas_fee = Amount::from(1); @@ -758,7 +757,7 @@ mod tests { assert_eq!( stored_keys_count(&wl_storage), - initial_stored_keys_count + 2 + initial_stored_keys_count + 3 ); } @@ -784,13 +783,13 @@ mod tests { ) .unwrap(); - let wdai: wrapped_erc20s::Keys = (&DAI_ERC20_ETH_ADDRESS).into(); - let receiver_balance_key = wdai.balance(&receiver); - let wdai_supply_key = wdai.supply(); + let wdai = wrapped_erc20s::token(&DAI_ERC20_ETH_ADDRESS); + let receiver_balance_key = balance_key(&wdai, &receiver); + let wdai_supply_key = minted_balance_key(&wdai); assert_eq!( stored_keys_count(&wl_storage), - initial_stored_keys_count + 2 + initial_stored_keys_count + 3 ); let expected_amount = amount.try_to_vec().unwrap(); @@ -812,7 +811,7 @@ mod tests { let native_erc20 = read_native_erc20_address(&wl_storage).expect("Test failed"); let random_erc20 = EthAddress([0xff; 20]); - let random_erc20_keys: wrapped_erc20s::Keys = (&random_erc20).into(); + let random_erc20_token = wrapped_erc20s::token(&random_erc20); let pending_transfers = init_bridge_pool_transfers( &mut wl_storage, [native_erc20, random_erc20], @@ -851,10 +850,12 @@ mod tests { let mut changed_keys = act_on(&mut wl_storage, event).unwrap(); assert!( - changed_keys - .remove(&random_erc20_keys.balance(&BRIDGE_POOL_ADDRESS)) + changed_keys.remove(&balance_key( + &random_erc20_token, + &BRIDGE_POOL_ADDRESS + )) ); - assert!(changed_keys.remove(&random_erc20_keys.supply())); + assert!(changed_keys.remove(&minted_balance_key(&random_erc20_token))); assert!(changed_keys.remove(&payer_balance_key)); assert!(changed_keys.remove(&pool_balance_key)); assert!(changed_keys.remove(&get_nonce_key())); @@ -985,20 +986,15 @@ mod tests { .expect("Test failed"); assert_eq!(escrow_balance, Amount::from(0)); } else { - let sub_prefix = - wrapped_erc20s::sub_prefix(&transfer.transfer.asset); - let prefix = - multitoken_balance_prefix(&BRIDGE_ADDRESS, &sub_prefix); - let sender_key = - multitoken_balance_key(&prefix, &transfer.transfer.sender); + let token = wrapped_erc20s::token(&transfer.transfer.asset); + let sender_key = balance_key(&token, &transfer.transfer.sender); let value = wl_storage.read_bytes(&sender_key).expect("Test failed"); let sender_balance = Amount::try_from_slice(&value.expect("Test failed")) .expect("Test failed"); assert_eq!(sender_balance, transfer.transfer.amount); - let escrow_key = - multitoken_balance_key(&prefix, &BRIDGE_POOL_ADDRESS); + let escrow_key = balance_key(&token, &BRIDGE_POOL_ADDRESS); let value = wl_storage.read_bytes(&escrow_key).expect("Test failed"); let escrow_balance = @@ -1127,12 +1123,12 @@ mod tests { if asset == &native_erc20 { return None; } - let asset_keys: wrapped_erc20s::Keys = asset.into(); + let erc20_token = wrapped_erc20s::token(asset); let prev_balance = wl_storage - .read(&asset_keys.balance(&BRIDGE_POOL_ADDRESS)) + .read(&balance_key(&erc20_token, &BRIDGE_POOL_ADDRESS)) .expect("Test failed"); let prev_supply = wl_storage - .read(&asset_keys.supply()) + .read(&minted_balance_key(&erc20_token)) .expect("Test failed"); Some(Delta { asset: *asset, @@ -1161,14 +1157,14 @@ mod tests { .checked_sub(sent_amount) .expect("Test failed"); - let asset_keys: wrapped_erc20s::Keys = asset.into(); + let erc20_token = wrapped_erc20s::token(asset); let balance: token::Amount = wl_storage - .read(&asset_keys.balance(&BRIDGE_POOL_ADDRESS)) + .read(&balance_key(&erc20_token, &BRIDGE_POOL_ADDRESS)) .expect("Read must succeed") .expect("Balance must exist"); let supply: token::Amount = wl_storage - .read(&asset_keys.supply()) + .read(&minted_balance_key(&erc20_token)) .expect("Read must succeed") .expect("Balance must exist"); @@ -1187,19 +1183,19 @@ mod tests { test_wrapped_erc20s_aux(|wl_storage, event| { let native_erc20 = read_native_erc20_address(wl_storage).expect("Test failed"); - let wnam_keys: wrapped_erc20s::Keys = (&native_erc20).into(); + let wnam = wrapped_erc20s::token(&native_erc20); let escrow_balance_key = balance_key(&nam(), &BRIDGE_ADDRESS); // check pre supply assert!( wl_storage - .read_bytes(&wnam_keys.balance(&BRIDGE_POOL_ADDRESS)) + .read_bytes(&balance_key(&wnam, &BRIDGE_POOL_ADDRESS)) .expect("Test failed") .is_none() ); assert!( wl_storage - .read_bytes(&wnam_keys.supply()) + .read_bytes(&minted_balance_key(&wnam)) .expect("Test failed") .is_none() ); @@ -1215,13 +1211,13 @@ mod tests { // check post supply assert!( wl_storage - .read_bytes(&wnam_keys.balance(&BRIDGE_POOL_ADDRESS)) + .read_bytes(&balance_key(&wnam, &BRIDGE_POOL_ADDRESS)) .expect("Test failed") .is_none() ); assert!( wl_storage - .read_bytes(&wnam_keys.supply()) + .read_bytes(&minted_balance_key(&wnam)) .expect("Test failed") .is_none() ); diff --git a/ethereum_bridge/src/protocol/transactions/ethereum_events/mod.rs b/ethereum_bridge/src/protocol/transactions/ethereum_events/mod.rs index bb7b614187..29fd07a8e7 100644 --- a/ethereum_bridge/src/protocol/transactions/ethereum_events/mod.rs +++ b/ethereum_bridge/src/protocol/transactions/ethereum_events/mod.rs @@ -284,7 +284,9 @@ mod tests { use namada_core::types::ethereum_events::{ EthereumEvent, TransferToNamada, }; - use namada_core::types::token::Amount; + use namada_core::types::token::{ + balance_key, minted_balance_key, minter_key, Amount, + }; use super::*; use crate::protocol::transactions::utils::GetVoters; @@ -335,7 +337,7 @@ mod tests { apply_updates(&mut wl_storage, updates, voting_powers)?; let eth_msg_keys: vote_tallies::Keys = (&body).into(); - let wrapped_erc20_keys: wrapped_erc20s::Keys = (&asset).into(); + let wrapped_erc20_token = wrapped_erc20s::token(&asset); assert_eq!( BTreeSet::from_iter(vec![ eth_msg_keys.body(), @@ -343,8 +345,9 @@ mod tests { eth_msg_keys.seen_by(), eth_msg_keys.voting_power(), eth_msg_keys.voting_started_epoch(), - wrapped_erc20_keys.balance(&receiver), - wrapped_erc20_keys.supply(), + balance_key(&wrapped_erc20_token, &receiver), + minted_balance_key(&wrapped_erc20_token), + minter_key(&wrapped_erc20_token), ]), changed_keys ); @@ -375,8 +378,8 @@ mod tests { let epoch_bytes = epoch_bytes.unwrap(); assert_eq!(Epoch::try_from_slice(&epoch_bytes)?, Epoch(0)); - let wrapped_erc20_balance_bytes = - wl_storage.read_bytes(&wrapped_erc20_keys.balance(&receiver))?; + let wrapped_erc20_balance_bytes = wl_storage + .read_bytes(&balance_key(&wrapped_erc20_token, &receiver))?; let wrapped_erc20_balance_bytes = wrapped_erc20_balance_bytes.unwrap(); assert_eq!( Amount::try_from_slice(&wrapped_erc20_balance_bytes)?, @@ -384,7 +387,7 @@ mod tests { ); let wrapped_erc20_supply_bytes = - wl_storage.read_bytes(&wrapped_erc20_keys.supply())?; + wl_storage.read_bytes(&minted_balance_key(&wrapped_erc20_token))?; let wrapped_erc20_supply_bytes = wrapped_erc20_supply_bytes.unwrap(); assert_eq!( Amount::try_from_slice(&wrapped_erc20_supply_bytes)?, @@ -435,7 +438,7 @@ mod tests { "No gas should be used for a derived transaction" ); let eth_msg_keys = vote_tallies::Keys::from(&event); - let dai_keys = wrapped_erc20s::Keys::from(&DAI_ERC20_ETH_ADDRESS); + let dai_token = wrapped_erc20s::token(&DAI_ERC20_ETH_ADDRESS); assert_eq!( tx_result.changed_keys, BTreeSet::from_iter(vec![ @@ -444,8 +447,9 @@ mod tests { eth_msg_keys.seen_by(), eth_msg_keys.voting_power(), eth_msg_keys.voting_started_epoch(), - dai_keys.balance(&receiver), - dai_keys.supply(), + balance_key(&dai_token, &receiver), + minted_balance_key(&dai_token), + minter_key(&dai_token), ]) ); assert!(tx_result.vps_result.accepted_vps.is_empty()); diff --git a/shared/src/ledger/eth_bridge/bridge_pool.rs b/shared/src/ledger/eth_bridge/bridge_pool.rs index a95d56d475..e06c141614 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool.rs @@ -60,7 +60,7 @@ pub async fn build_bridge_pool_tx< let sub_prefix = Some(wrapped_erc20s::sub_prefix(&asset)); let DenominatedAmount { amount, .. } = - validate_amount(client, amount, &BRIDGE_ADDRESS, &sub_prefix, tx.force) + validate_amount(client, amount, &BRIDGE_ADDRESS, tx.force) .await .expect("Failed to validate amount"); let transfer = PendingTransfer { diff --git a/shared/src/ledger/ibc/vp/context.rs b/shared/src/ledger/ibc/vp/context.rs index d05eaebcd1..daf2246cbe 100644 --- a/shared/src/ledger/ibc/vp/context.rs +++ b/shared/src/ledger/ibc/vp/context.rs @@ -11,10 +11,7 @@ use namada_core::ledger::storage_api::StorageRead; use namada_core::types::address::{Address, InternalAddress}; use namada_core::types::ibc::IbcEvent; use namada_core::types::storage::{BlockHeight, Header, Key}; -use namada_core::types::token::{ - self, is_any_token_balance_key, is_any_token_or_multitoken_balance_key, - Amount, -}; +use namada_core::types::token::{self, Amount}; use super::Error; use crate::ledger::native_vp::CtxPreStorageRead; diff --git a/shared/src/ledger/masp.rs b/shared/src/ledger/masp.rs index b6ccd8672b..6fe972d4ac 100644 --- a/shared/src/ledger/masp.rs +++ b/shared/src/ledger/masp.rs @@ -50,7 +50,7 @@ use masp_proofs::bellman::groth16::PreparedVerifyingKey; use masp_proofs::bls12_381::Bls12; use masp_proofs::prover::LocalTxProver; use masp_proofs::sapling::SaplingVerificationContext; -use namada_core::types::token::{Change, MaspDenom, TokenAddress}; +use namada_core::types::token::{Change, MaspDenom}; use namada_core::types::transaction::AffineCurve; #[cfg(feature = "masp-tx-gen")] use rand_core::{CryptoRng, OsRng, RngCore}; @@ -407,7 +407,7 @@ pub enum PinnedBalanceError { // #[derive(BorshSerialize, BorshDeserialize, Debug, Clone)] // pub struct MaspAmount { -// pub asset: TokenAddress, +// pub asset: Address, // pub amount: token::Amount, // } @@ -415,7 +415,7 @@ pub enum PinnedBalanceError { #[derive(BorshSerialize, BorshDeserialize, Debug, Clone)] pub struct MaspChange { /// the token address - pub asset: TokenAddress, + pub asset: Address, /// the change in the token pub change: token::Change, } @@ -424,10 +424,10 @@ pub struct MaspChange { #[derive( BorshSerialize, BorshDeserialize, Debug, Clone, Default, PartialEq, Eq, )] -pub struct MaspAmount(HashMap<(Epoch, TokenAddress), token::Change>); +pub struct MaspAmount(HashMap<(Epoch, Address), token::Change>); impl std::ops::Deref for MaspAmount { - type Target = HashMap<(Epoch, TokenAddress), token::Change>; + type Target = HashMap<(Epoch, Address), token::Change>; fn deref(&self) -> &Self::Target { &self.0 @@ -495,14 +495,9 @@ impl std::ops::Mul for MaspAmount { impl<'a> From<&'a MaspAmount> for Amount { fn from(masp_amount: &'a MaspAmount) -> Amount { let mut res = Amount::zero(); - for ((epoch, key), val) in masp_amount.iter() { + for ((epoch, token), val) in masp_amount.iter() { for denom in MaspDenom::iter() { - let asset = make_asset_type( - Some(*epoch), - &key.address, - &key.sub_prefix, - denom, - ); + let asset = make_asset_type(Some(*epoch), token, denom); res += Amount::from_pair(asset, denom.denominate_i128(val)) .unwrap(); } @@ -558,8 +553,7 @@ pub struct ShieldedContext { /// The set of note positions that have been spent pub spents: HashSet, /// Maps asset types to their decodings - pub asset_types: - HashMap, MaspDenom, Epoch)>, + pub asset_types: HashMap, /// Maps note positions to their corresponding viewing keys pub vk_map: HashMap, } @@ -850,10 +844,7 @@ impl ShieldedContext { } // Record the changes to the transparent accounts let mut transfer_delta = TransferDelta::new(); - let token_addr = TokenAddress { - address: tx.token.clone(), - sub_prefix: tx.sub_prefix.clone(), - }; + let token_addr = tx.token.clone(); transfer_delta.insert( tx.source.clone(), MaspChange { @@ -917,23 +908,22 @@ impl ShieldedContext { &mut self, client: &U::C, asset_type: AssetType, - ) -> Option<(Address, Option, MaspDenom, Epoch)> { + ) -> Option<(Address, MaspDenom, Epoch)> { // Try to find the decoding in the cache if let decoded @ Some(_) = self.asset_types.get(&asset_type) { return decoded.cloned(); } // Query for the ID of the last accepted transaction - let (addr, sub_prefix, denom, ep, _conv, _path): ( + let (addr, denom, ep, _conv, _path): ( Address, - Option, MaspDenom, _, Amount, MerklePath, ) = rpc::query_conversion(client, asset_type).await?; self.asset_types - .insert(asset_type, (addr.clone(), sub_prefix.clone(), denom, ep)); - Some((addr, sub_prefix, denom, ep)) + .insert(asset_type, (addr.clone(), denom, ep)); + Some((addr, denom, ep)) } /// Query the ledger for the conversion that is allowed for the given asset @@ -946,11 +936,10 @@ impl ShieldedContext { ) { if let Entry::Vacant(conv_entry) = conversions.entry(asset_type) { // Query for the ID of the last accepted transaction - if let Some((addr, sub_prefix, denom, ep, conv, path)) = + if let Some((addr, denom, ep, conv, path)) = query_conversion(client, asset_type).await { - self.asset_types - .insert(asset_type, (addr, sub_prefix, denom, ep)); + self.asset_types.insert(asset_type, (addr, denom, ep)); // If the conversion is 0, then we just have a pure decoding if conv != Amount::zero() { conv_entry.insert((conv.into(), path, 0)); @@ -997,7 +986,7 @@ impl ShieldedContext { &mut self, client: &U::C, conv: AllowedConversion, - asset_type: (Epoch, TokenAddress, MaspDenom), + asset_type: (Epoch, Address, MaspDenom), value: i128, usage: &mut i128, input: &mut MaspAmount, @@ -1010,12 +999,8 @@ impl ShieldedContext { // If conversion if possible, accumulate the exchanged amount let conv: Amount = conv.into(); // The amount required of current asset to qualify for conversion - let masp_asset = make_asset_type( - Some(asset_type.0), - &asset_type.1.address, - &asset_type.1.sub_prefix, - asset_type.2, - ); + let masp_asset = + make_asset_type(Some(asset_type.0), &asset_type.1, asset_type.2); let threshold = -conv[&masp_asset]; if threshold == 0 { eprintln!( @@ -1063,18 +1048,10 @@ impl ShieldedContext { let asset_epoch = *asset_epoch; let token_addr = token_addr.clone(); for denom in MaspDenom::iter() { - let target_asset_type = make_asset_type( - Some(target_epoch), - &token_addr.address, - &token_addr.sub_prefix, - denom, - ); - let asset_type = make_asset_type( - Some(asset_epoch), - &token_addr.address, - &token_addr.sub_prefix, - denom, - ); + let target_asset_type = + make_asset_type(Some(target_epoch), &token_addr, denom); + let asset_type = + make_asset_type(Some(asset_epoch), &token_addr, denom); let at_target_asset_type = target_epoch == asset_epoch; let denom_value = denom.denominate_i128(&value); @@ -1139,9 +1116,9 @@ impl ShieldedContext { (asset_epoch, token_addr.clone()), denom_value.into(), ); - for ((e, key), val) in input.iter() { - if *key == token_addr && *e == asset_epoch { - comp.insert((*e, key.clone()), *val); + for ((e, token), val) in input.iter() { + if *token == token_addr && *e == asset_epoch { + comp.insert((*e, token.clone()), *val); } } output += comp.clone(); @@ -1327,24 +1304,19 @@ impl ShieldedContext { client: &U::C, amt: Amount, target_epoch: Epoch, - ) -> HashMap { + ) -> HashMap { let mut res = HashMap::new(); for (asset_type, val) in amt.components() { // Decode the asset type let decoded = self.decode_asset_type(client, *asset_type).await; // Only assets with the target timestamp count match decoded { - Some(asset_type @ (_, _, _, epoch)) - if epoch == target_epoch => - { + Some(asset_type @ (_, _, epoch)) if epoch == target_epoch => { decode_component( asset_type, *val, &mut res, - |address, sub_prefix, _| TokenAddress { - address, - sub_prefix, - }, + |address, _| address, ); } _ => {} @@ -1360,27 +1332,15 @@ impl ShieldedContext { client: &U::C, amt: Amount, ) -> MaspAmount { - let mut res: HashMap<(Epoch, TokenAddress), Change> = - HashMap::default(); + let mut res: HashMap<(Epoch, Address), Change> = HashMap::default(); for (asset_type, val) in amt.components() { // Decode the asset type if let Some(decoded) = self.decode_asset_type(client, *asset_type).await { - decode_component( - decoded, - *val, - &mut res, - |address, sub_prefix, epoch| { - ( - epoch, - TokenAddress { - address, - sub_prefix, - }, - ) - }, - ) + decode_component(decoded, *val, &mut res, |address, epoch| { + (epoch, address) + }) } } MaspAmount(res) @@ -1440,12 +1400,8 @@ impl ShieldedContext { unreachable!("The function `gen_shielded_transfer` is only called by `submit_tx` which validates amounts.") }; // Convert transaction amount into MASP types - let (asset_types, amount) = convert_amount( - epoch, - &args.token, - &args.sub_prefix.as_ref(), - amt.amount, - ); + let (asset_types, amount) = + convert_amount(epoch, &args.token, amt.amount); let tx_fee = // If there are shielded inputs @@ -1456,7 +1412,7 @@ impl ShieldedContext { // Transaction fees need to match the amount in the wrapper Transfer // when MASP source is used let (_, shielded_fee) = - convert_amount(epoch, &args.tx.fee_token, &None, fee.amount); + convert_amount(epoch, &args.tx.fee_token, fee.amount); let required_amt = if shielded_gas { amount + shielded_fee.clone() } else { @@ -1701,10 +1657,7 @@ impl ShieldedContext { let delta = TransferDelta::from([( transfer.source.clone(), MaspChange { - asset: TokenAddress { - address: transfer.token.clone(), - sub_prefix: transfer.sub_prefix.clone(), - }, + asset: transfer.token.clone(), change: -transfer.amount.amount.change(), }, )]); @@ -1744,33 +1697,15 @@ fn extract_payload( } /// Make asset type corresponding to given address and epoch -pub fn make_asset_type( +pub fn make_asset_type( epoch: Option, token: &Address, - sub_prefix: &Option, denom: MaspDenom, ) -> AssetType { // Typestamp the chosen token with the current epoch let token_bytes = match epoch { - None => ( - token, - sub_prefix - .as_ref() - .map(|k| k.to_string()) - .unwrap_or_default(), - denom, - ) - .try_to_vec() - .expect("token should serialize"), - Some(epoch) => ( - token, - sub_prefix - .as_ref() - .map(|k| k.to_string()) - .unwrap_or_default(), - denom, - epoch.0, - ) + None => (token, denom).try_to_vec().expect("token should serialize"), + Some(epoch) => (token, denom, epoch.0) .try_to_vec() .expect("token should serialize"), }; @@ -1782,14 +1717,12 @@ pub fn make_asset_type( fn convert_amount( epoch: Epoch, token: &Address, - sub_prefix: &Option<&String>, val: token::Amount, ) -> ([AssetType; 4], Amount) { let mut amount = Amount::zero(); let asset_types: [AssetType; 4] = MaspDenom::iter() .map(|denom| { - let asset_type = - make_asset_type(Some(epoch), token, sub_prefix, denom); + let asset_type = make_asset_type(Some(epoch), token, denom); // Combine the value and unit into one amount amount += Amount::from_nonnegative(asset_type, denom.denominate(&val)) diff --git a/shared/src/ledger/native_vp/ethereum_bridge/bridge_pool_vp.rs b/shared/src/ledger/native_vp/ethereum_bridge/bridge_pool_vp.rs index 283cf52c58..63caf82a57 100644 --- a/shared/src/ledger/native_vp/ethereum_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/native_vp/ethereum_bridge/bridge_pool_vp.rs @@ -92,9 +92,9 @@ where transfer: &PendingTransfer, ) -> Result { // check that the assets to be transferred were escrowed - let asset_key = wrapped_erc20s::Keys::from(&transfer.transfer.asset); - let owner_key = asset_key.balance(&transfer.transfer.sender); - let escrow_key = asset_key.balance(&BRIDGE_POOL_ADDRESS); + let token = wrapped_erc20s::token(&transfer.transfer.asset); + let owner_key = balance_key(&token, &transfer.transfer.sender); + let escrow_key = balance_key(&token, &BRIDGE_POOL_ADDRESS); if keys_changed.contains(&owner_key) && keys_changed.contains(&escrow_key) { @@ -486,7 +486,7 @@ mod test_bridge_pool_vp { ) -> BTreeSet { // get the balance keys let token_key = - wrapped_erc20s::Keys::from(&ASSET).balance(&balance.owner); + balance_key(&wrapped_erc20s::token(&ASSET), &balance.owner); let account_key = balance_key(&nam(), &balance.owner); // update the balance of nam @@ -1028,12 +1028,14 @@ mod test_bridge_pool_vp { BTreeSet::from([get_pending_key(&transfer)]) }; // We escrow 0 tokens - keys_changed.insert( - wrapped_erc20s::Keys::from(&ASSET).balance(&bertha_address()), - ); - keys_changed.insert( - wrapped_erc20s::Keys::from(&ASSET).balance(&BRIDGE_POOL_ADDRESS), - ); + keys_changed.insert(balance_key( + &wrapped_erc20s::token(&ASSET), + &bertha_address(), + )); + keys_changed.insert(balance_key( + &wrapped_erc20s::token(&ASSET), + &BRIDGE_POOL_ADDRESS, + )); let verifiers = BTreeSet::default(); // create the data to be given to the vp diff --git a/shared/src/ledger/native_vp/ethereum_bridge/vp.rs b/shared/src/ledger/native_vp/ethereum_bridge/vp.rs index 47eed29f3c..de068f5e9e 100644 --- a/shared/src/ledger/native_vp/ethereum_bridge/vp.rs +++ b/shared/src/ledger/native_vp/ethereum_bridge/vp.rs @@ -437,6 +437,7 @@ mod tests { use crate::types::ethereum_events; use crate::types::ethereum_events::EthAddress; use crate::types::storage::TxIndex; + use crate::types::token::minted_balance_key; use crate::types::transaction::TxType; use crate::vm::wasm::VpCache; use crate::vm::WasmCacheRwAccess; @@ -563,10 +564,9 @@ mod tests { { let keys_changed = BTreeSet::from_iter(vec![ arbitrary_key(), - wrapped_erc20s::Keys::from( + minted_balance_key(&wrapped_erc20s::token( ðereum_events::testing::DAI_ERC20_ETH_ADDRESS, - ) - .supply(), + )), ]); let result = determine_check_type(&nam(), &keys_changed); @@ -577,10 +577,10 @@ mod tests { { let keys_changed = BTreeSet::from_iter(vec![ arbitrary_key(), - wrapped_erc20s::Keys::from( - ðereum_events::testing::DAI_ERC20_ETH_ADDRESS, - ) - .balance( + balance_key( + &wrapped_erc20s::token( + ðereum_events::testing::DAI_ERC20_ETH_ADDRESS, + ), &Address::decode(ARBITRARY_OWNER_A_ADDRESS) .expect("Couldn't set up test"), ), @@ -596,17 +596,17 @@ mod tests { fn test_rejects_if_multitoken_keys_for_different_assets() { { let keys_changed = BTreeSet::from_iter(vec![ - wrapped_erc20s::Keys::from( - ðereum_events::testing::DAI_ERC20_ETH_ADDRESS, - ) - .balance( + balance_key( + &wrapped_erc20s::token( + ðereum_events::testing::DAI_ERC20_ETH_ADDRESS, + ), &Address::decode(ARBITRARY_OWNER_A_ADDRESS) .expect("Couldn't set up test"), ), - wrapped_erc20s::Keys::from( - ðereum_events::testing::USDC_ERC20_ETH_ADDRESS, - ) - .balance( + balance_key( + &wrapped_erc20s::token( + ðereum_events::testing::USDC_ERC20_ETH_ADDRESS, + ), &Address::decode(ARBITRARY_OWNER_B_ADDRESS) .expect("Couldn't set up test"), ), @@ -623,8 +623,9 @@ mod tests { let asset = ðereum_events::testing::DAI_ERC20_ETH_ADDRESS; { let keys_changed = BTreeSet::from_iter(vec![ - wrapped_erc20s::Keys::from(asset).supply(), - wrapped_erc20s::Keys::from(asset).balance( + minted_balance_key(&wrapped_erc20s::token(asset)), + balance_key( + &wrapped_erc20s::token(asset), &Address::decode(ARBITRARY_OWNER_B_ADDRESS) .expect("Couldn't set up test"), ), diff --git a/shared/src/ledger/native_vp/multitoken.rs b/shared/src/ledger/native_vp/multitoken.rs index 9c985b9dab..22556b75a1 100644 --- a/shared/src/ledger/native_vp/multitoken.rs +++ b/shared/src/ledger/native_vp/multitoken.rs @@ -12,6 +12,7 @@ use crate::types::address::{Address, InternalAddress}; use crate::types::storage::Key; use crate::types::token::{ is_any_minted_balance_key, is_any_token_balance_key, minter_key, Amount, + Change, }; use crate::vm::WasmCacheAccess; @@ -55,7 +56,7 @@ where let mut changes = HashMap::new(); let mut mints = HashMap::new(); for key in keys_changed { - if let Some((token, _)) = is_any_token_balance_key(key) { + if let Some([token, _]) = is_any_token_balance_key(key) { let pre: Amount = self.ctx.read_pre(key)?.unwrap_or_default(); let post: Amount = self.ctx.read_post(key)?.unwrap_or_default(); let diff = post.change() - pre.change(); @@ -87,7 +88,7 @@ where Ok(changes.iter().all(|(token, change)| { let mint = match mints.get(token) { Some(mint) => *mint, - None => 0, + None => Change::zero(), }; *change == mint })) diff --git a/shared/src/ledger/protocol/mod.rs b/shared/src/ledger/protocol/mod.rs index b6920fb4f9..6cab156d7a 100644 --- a/shared/src/ledger/protocol/mod.rs +++ b/shared/src/ledger/protocol/mod.rs @@ -585,7 +585,8 @@ where replay_protection_vp.ctx.gas_meter.into_inner(); result } - InternalAddress::IbcToken(_) => { + InternalAddress::IbcToken(_) + | InternalAddress::Erc20(_) => { // The address should be a part of a multitoken key gas_meter = ctx.gas_meter.into_inner(); Ok(verifiers.contains(&Address::Internal( diff --git a/shared/src/ledger/queries/shell.rs b/shared/src/ledger/queries/shell.rs index 7583f8bcf2..3eb95c8e8c 100644 --- a/shared/src/ledger/queries/shell.rs +++ b/shared/src/ledger/queries/shell.rs @@ -7,7 +7,7 @@ use masp_primitives::sapling::Node; use namada_core::ledger::storage::LastBlock; use namada_core::types::address::Address; use namada_core::types::hash::Hash; -use namada_core::types::storage::{BlockHeight, BlockResults, Key, KeySeg}; +use namada_core::types::storage::{BlockHeight, BlockResults, KeySeg}; use namada_core::types::token::MaspDenom; use self::eth_bridge::{EthBridge, ETH_BRIDGE}; @@ -27,7 +27,6 @@ use crate::types::transaction::TxResult; type Conversion = ( Address, - Option, MaspDenom, Epoch, masp_primitives::transaction::components::Amount, @@ -163,7 +162,7 @@ where H: 'static + StorageHasher + Sync, { // Conversion values are constructed on request - if let Some(((addr, sub_prefix, denom), epoch, conv, pos)) = ctx + if let Some(((addr, denom), epoch, conv, pos)) = ctx .wl_storage .storage .conversion_state @@ -172,7 +171,6 @@ where { Ok(( addr.clone(), - sub_prefix.clone(), *denom, *epoch, Into::::into( diff --git a/shared/src/ledger/queries/shell/eth_bridge.rs b/shared/src/ledger/queries/shell/eth_bridge.rs index 42d0207d5b..55e5255b62 100644 --- a/shared/src/ledger/queries/shell/eth_bridge.rs +++ b/shared/src/ledger/queries/shell/eth_bridge.rs @@ -17,7 +17,7 @@ use namada_core::types::ethereum_events::{ }; use namada_core::types::ethereum_structs::RelayProof; use namada_core::types::storage::{BlockHeight, DbKeySeg, Key}; -use namada_core::types::token::Amount; +use namada_core::types::token::{minted_balance_key, Amount}; use namada_core::types::vote_extensions::validator_set_update::{ ValidatorSetArgs, VotingPowersMap, }; @@ -128,8 +128,8 @@ where "Wrapped NAM's supply is not kept track of", )); } - let keys: wrapped_erc20s::Keys = (&asset).into(); - ctx.wl_storage.read(&keys.supply()) + let token = wrapped_erc20s::token(&asset); + ctx.wl_storage.read(&minted_balance_key(&token)) } /// Helper function to read a smart contract from storage. @@ -1420,10 +1420,10 @@ mod test_ethbridge_router { // write tokens to storage let amount = Amount::native_whole(12345); - let keys: wrapped_erc20s::Keys = (&ERC20_TOKEN).into(); + let token = wrapped_erc20s::token(&ERC20_TOKEN); client .wl_storage - .write(&keys.supply(), amount) + .write(&minted_balance_key(&token), amount) .expect("Test failed"); // check that the supply was updated diff --git a/shared/src/ledger/queries/vp/token.rs b/shared/src/ledger/queries/vp/token.rs index 030bdddd53..4e6cdc6374 100644 --- a/shared/src/ledger/queries/vp/token.rs +++ b/shared/src/ledger/queries/vp/token.rs @@ -2,7 +2,6 @@ use namada_core::ledger::storage::{DBIter, StorageHasher, DB}; use namada_core::ledger::storage_api; use namada_core::ledger::storage_api::token::read_denom; use namada_core::types::address::Address; -use namada_core::types::storage::Key; use namada_core::types::token; use crate::ledger::queries::RequestCtx; diff --git a/shared/src/ledger/rpc.rs b/shared/src/ledger/rpc.rs index 08111692e3..56cc388f5a 100644 --- a/shared/src/ledger/rpc.rs +++ b/shared/src/ledger/rpc.rs @@ -13,7 +13,7 @@ use namada_core::ledger::testnet_pow; use namada_core::types::address::Address; use namada_core::types::storage::Key; use namada_core::types::token::{ - Amount, DenominatedAmount, Denomination, MaspDenom, TokenAddress, + Amount, DenominatedAmount, Denomination, MaspDenom, }; use namada_proof_of_stake::types::{BondsAndUnbondsDetails, CommissionPair}; use serde::Serialize; @@ -241,7 +241,6 @@ pub async fn query_conversion( asset_type: AssetType, ) -> Option<( Address, - Option, MaspDenom, Epoch, masp_primitives::transaction::components::Amount, @@ -954,7 +953,6 @@ pub async fn validate_amount( client: &C, amount: InputAmount, token: &Address, - sub_prefix: &Option, force: bool, ) -> Option { let input_amount = match amount { @@ -962,10 +960,7 @@ pub async fn validate_amount( InputAmount::Validated(amt) => return Some(amt), }; let denom = unwrap_client_response::>( - RPC.vp() - .token() - .denomination(client, token, sub_prefix) - .await, + RPC.vp().token().denomination(client, token).await, ) .or_else(|| { if force { @@ -1065,7 +1060,7 @@ pub async fn format_denominated_amount< amount: token::Amount, ) -> String { let denom = unwrap_client_response::>( - RPC.vp().token().denomination(client, &token.address).await, + RPC.vp().token().denomination(client, token).await, ) .unwrap_or_else(|| { println!( diff --git a/shared/src/ledger/signing.rs b/shared/src/ledger/signing.rs index b6b06f0dae..2f0fd0d56d 100644 --- a/shared/src/ledger/signing.rs +++ b/shared/src/ledger/signing.rs @@ -19,9 +19,7 @@ use namada_core::types::address::{ masp, masp_tx_key, Address, ImplicitAddress, }; use namada_core::types::storage::Key; -use namada_core::types::token::{ - self, Amount, DenominatedAmount, MaspDenom, TokenAddress, -}; +use namada_core::types::token::{self, Amount, DenominatedAmount, MaspDenom}; use namada_core::types::transaction::{pos, MIN_FEE}; use prost::Message; use serde::{Deserialize, Serialize}; @@ -252,10 +250,7 @@ pub async fn solve_pow_challenge( .unwrap_or_default(); let is_bal_sufficient = fee_amount <= balance; if !is_bal_sufficient { - let token_addr = TokenAddress { - address: args.fee_token.clone(), - sub_prefix: None, - }; + let token_addr = args.fee_token.clone(); let err_msg = format!( "The wrapper transaction source doesn't have enough balance to \ pay fee {}, got {}.", @@ -407,10 +402,7 @@ pub async fn sign_wrapper< .unwrap_or_default(); let is_bal_sufficient = fee_amount <= balance; if !is_bal_sufficient { - let token_addr = TokenAddress { - address: args.fee_token.clone(), - sub_prefix: None, - }; + let token_addr = args.fee_token.clone(); let err_msg = format!( "The wrapper transaction source doesn't have enough balance to \ pay fee {}, got {}.", @@ -537,23 +529,13 @@ fn make_ledger_amount_addr( output: &mut Vec, amount: DenominatedAmount, token: &Address, - sub_prefix: &Option, prefix: &str, ) { - let token_address = TokenAddress { - address: token.clone(), - sub_prefix: sub_prefix.clone(), - }; if let Some(token) = tokens.get(token) { - output.push(format!( - "{}Amount {}: {}", - prefix, - token_address.format_with_alias(token), - amount - )); + output.push(format!("{}Amount {}: {}", prefix, token, amount)); } else { output.extend(vec![ - format!("{}Token: {}", prefix, token_address), + format!("{}Token: {}", prefix, token), format!("{}Amount: {}", prefix, amount), ]); } @@ -567,27 +549,21 @@ async fn make_ledger_amount_asset( output: &mut Vec, amount: u64, token: &AssetType, - assets: &HashMap, MaspDenom, Epoch)>, + assets: &HashMap, prefix: &str, ) { - if let Some((token, sub_prefix, _, _epoch)) = assets.get(token) { + if let Some((token, _, _epoch)) = assets.get(token) { // If the AssetType can be decoded, then at least display Addressees - let token_addr = TokenAddress { - address: token.clone(), - sub_prefix: sub_prefix.clone(), - }; let formatted_amt = - format_denominated_amount(client, &token_addr, amount.into()).await; + format_denominated_amount(client, &token, amount.into()).await; if let Some(token) = tokens.get(token) { - output.push(format!( - "{}Amount: {} {}", - prefix, - token_addr.format_with_alias(token), - formatted_amt, - )); + output + .push( + format!("{}Amount: {} {}", prefix, token, formatted_amt,), + ); } else { output.extend(vec![ - format!("{}Token: {}", prefix, token_addr), + format!("{}Token: {}", prefix, token), format!("{}Amount: {}", prefix, formatted_amt), ]); } @@ -1022,16 +998,10 @@ pub async fn to_ledger_vector< Section::MaspBuilder(builder) if builder.target == shielded_hash => { - for (addr, sub_prefix, denom, epoch) in &builder.asset_types - { + for (addr, denom, epoch) in &builder.asset_types { asset_types.insert( - make_asset_type( - Some(*epoch), - addr, - sub_prefix, - *denom, - ), - (addr.clone(), sub_prefix.clone(), *denom, *epoch), + make_asset_type(Some(*epoch), addr, *denom), + (addr.clone(), *denom, *epoch), ); } Some(builder) @@ -1216,10 +1186,7 @@ pub async fn to_ledger_vector< } if let Some(wrapper) = tx.header.wrapper() { - let gas_token = TokenAddress { - address: wrapper.fee.token.clone(), - sub_prefix: None, - }; + let gas_token = wrapper.fee.token.clone(); let gas_limit = format_denominated_amount( client, &gas_token, diff --git a/shared/src/ledger/tx.rs b/shared/src/ledger/tx.rs index 6b676eb020..0a02f8dcf0 100644 --- a/shared/src/ledger/tx.rs +++ b/shared/src/ledger/tx.rs @@ -17,7 +17,6 @@ use masp_primitives::transaction::components::transparent::fees::{ use masp_primitives::transaction::components::Amount; use namada_core::types::address::{masp, masp_tx_key, Address}; use namada_core::types::dec::Dec; -use namada_core::types::storage::Key; use namada_core::types::token::MaspDenom; use namada_proof_of_stake::parameters::PosParams; use namada_proof_of_stake::types::CommissionPair; @@ -548,18 +547,18 @@ where /// decode components of a masp note pub fn decode_component( - (addr, sub, denom, epoch): (Address, Option, MaspDenom, Epoch), + (addr, denom, epoch): (Address, MaspDenom, Epoch), val: i128, res: &mut HashMap, mk_key: F, ) where - F: FnOnce(Address, Option, Epoch) -> K, + F: FnOnce(Address, Epoch) -> K, K: Eq + std::hash::Hash, { let decoded_change = token::Change::from_masp_denominated(val, denom) .expect("expected this to fit"); - res.entry(mk_key(addr, sub, epoch)) + res.entry(mk_key(addr, epoch)) .and_modify(|val| *val += decoded_change) .or_insert(decoded_change); } @@ -1231,7 +1230,7 @@ async fn add_asset_type< C: crate::ledger::queries::Client + Sync, U: ShieldedUtils, >( - asset_types: &mut HashSet<(Address, Option, MaspDenom, Epoch)>, + asset_types: &mut HashSet<(Address, MaspDenom, Epoch)>, shielded: &mut ShieldedContext, client: &C, asset_type: AssetType, @@ -1259,7 +1258,7 @@ async fn used_asset_types< shielded: &mut ShieldedContext, client: &C, builder: &Builder, -) -> Result, MaspDenom, Epoch)>, RpcError> { +) -> Result, RpcError> { let mut asset_types = HashSet::new(); // Collect all the asset types used in the Sapling inputs for input in builder.sapling_inputs() { @@ -1324,21 +1323,14 @@ pub async fn build_transfer< let balance_key = token::balance_key(&token, &source); // validate the amount given - let validated_amount = validate_amount( - client, - args.amount, - &token, - &sub_prefix, - args.tx.force, - ) - .await - .expect("expected to validate amount"); + let validated_amount = + validate_amount(client, args.amount, &token, args.tx.force) + .await + .expect("expected to validate amount"); let validate_fee = validate_amount( client, args.tx.fee_amount, &args.tx.fee_token, - // TODO: Currently multi-tokens cannot be used to pay fees - &None, args.tx.force, ) .await diff --git a/tests/src/e2e/eth_bridge_tests.rs b/tests/src/e2e/eth_bridge_tests.rs index 1d15c5323a..76756fea32 100644 --- a/tests/src/e2e/eth_bridge_tests.rs +++ b/tests/src/e2e/eth_bridge_tests.rs @@ -6,7 +6,6 @@ use std::str::FromStr; use borsh::{BorshDeserialize, BorshSerialize}; use color_eyre::eyre::{eyre, Result}; -use expectrl::ControlCode; use namada::eth_bridge::oracle; use namada::eth_bridge::storage::vote_tallies; use namada::ledger::eth_bridge::{ diff --git a/tests/src/e2e/eth_bridge_tests/helpers.rs b/tests/src/e2e/eth_bridge_tests/helpers.rs index 9fc7acd781..09df129e2b 100644 --- a/tests/src/e2e/eth_bridge_tests/helpers.rs +++ b/tests/src/e2e/eth_bridge_tests/helpers.rs @@ -14,7 +14,6 @@ use namada::ledger::eth_bridge::{ use namada::types::address::{wnam, Address}; use namada::types::ethereum_events::{EthAddress, Uint}; use namada_apps::config::ethereum_bridge; -use namada_core::ledger::eth_bridge; use namada_core::types::ethereum_events::{EthereumEvent, TransferToNamada}; use namada_core::types::token; @@ -173,16 +172,13 @@ pub fn attempt_wrapped_erc20_transfer( ) -> Result { let ledger_address = get_actor_rpc(test, node); - let eth_bridge_addr = eth_bridge::ADDRESS.to_string(); - let sub_prefix = wrapped_erc20s::sub_prefix(asset).to_string(); + let token = wrapped_erc20s::token(asset).to_string(); let amount = amount.to_string(); let transfer_args = vec![ "transfer", "--token", - ð_bridge_addr, - "--sub-prefix", - &sub_prefix, + &token, "--source", from, "--target", @@ -208,10 +204,8 @@ pub fn find_wrapped_erc20_balance( ) -> Result { let ledger_address = get_actor_rpc(test, node); - let sub_prefix = wrapped_erc20s::sub_prefix(asset); - let prefix = - token::multitoken_balance_prefix(ð_bridge::ADDRESS, &sub_prefix); - let balance_key = token::multitoken_balance_key(&prefix, owner); + let token = wrapped_erc20s::token(asset); + let balance_key = token::balance_key(&token, owner); let mut bytes = run!( test, Bin::Client, diff --git a/tests/src/e2e/helpers.rs b/tests/src/e2e/helpers.rs index c9042802d1..7c79fdfdb6 100644 --- a/tests/src/e2e/helpers.rs +++ b/tests/src/e2e/helpers.rs @@ -25,7 +25,8 @@ use namada_apps::config::{Config, TendermintMode}; use namada_core::types::token::NATIVE_MAX_DECIMAL_PLACES; use super::setup::{ - sleep, NamadaCmd, Test, ENV_VAR_DEBUG, ENV_VAR_USE_PREBUILT_BINARIES, + self, sleep, NamadaBgCmd, NamadaCmd, Test, ENV_VAR_DEBUG, + ENV_VAR_USE_PREBUILT_BINARIES, }; use crate::e2e::setup::{Bin, Who, APPS_PACKAGE}; use crate::{run, run_as}; diff --git a/tests/src/native_vp/eth_bridge_pool.rs b/tests/src/native_vp/eth_bridge_pool.rs index 2829029abf..46478acf48 100644 --- a/tests/src/native_vp/eth_bridge_pool.rs +++ b/tests/src/native_vp/eth_bridge_pool.rs @@ -6,7 +6,6 @@ mod test_bridge_pool_vp { use namada::core::ledger::eth_bridge::storage::bridge_pool::BRIDGE_POOL_ADDRESS; use namada::ledger::eth_bridge::{ wrapped_erc20s, Contracts, EthereumBridgeConfig, UpgradeableContract, - ADDRESS, }; use namada::ledger::native_vp::ethereum_bridge::bridge_pool_vp::BridgePoolVp; use namada::proto::{Code, Data, Section, Signature, Tx}; @@ -84,27 +83,12 @@ mod test_bridge_pool_vp { // initialize Bertha's account env.spawn_accounts([&albert_address(), &bertha_address(), &nam()]); // enrich Albert - env.credit_tokens( - &albert_address(), - &nam(), - None, - BERTHA_WEALTH.into(), - ); + env.credit_tokens(&albert_address(), &nam(), BERTHA_WEALTH.into()); // enrich Bertha - env.credit_tokens( - &bertha_address(), - &nam(), - None, - BERTHA_WEALTH.into(), - ); + env.credit_tokens(&bertha_address(), &nam(), BERTHA_WEALTH.into()); // Bertha has ERC20 tokens too. - let sub_prefix = wrapped_erc20s::sub_prefix(&ASSET); - env.credit_tokens( - &bertha_address(), - &ADDRESS, - Some(sub_prefix), - BERTHA_TOKENS.into(), - ); + let token = wrapped_erc20s::token(&ASSET); + env.credit_tokens(&bertha_address(), &token, BERTHA_TOKENS.into()); env } diff --git a/tx_prelude/src/ibc.rs b/tx_prelude/src/ibc.rs index 2edb13652b..d15a60a5af 100644 --- a/tx_prelude/src/ibc.rs +++ b/tx_prelude/src/ibc.rs @@ -12,7 +12,7 @@ use namada_core::ledger::tx_env::TxEnv; use namada_core::types::address::{Address, InternalAddress}; pub use namada_core::types::ibc::IbcEvent; use namada_core::types::storage::{BlockHeight, Header, Key}; -use namada_core::types::token::{DenominatedAmount, Amount}; +use namada_core::types::token::Amount; use crate::token::{burn, mint, transfer}; use crate::{Ctx, KeyValIterator}; @@ -78,7 +78,7 @@ impl IbcStorageContext for Ctx { token: &Address, amount: Amount, ) -> std::result::Result<(), Self::Error> { - let amount = DenominatedAmount::native(amount); + let amount = amount.denominated(token, self)?; transfer(self, src, dest, token, amount, &None, &None, &None) } diff --git a/wasm/wasm_source/src/tx_bridge_pool.rs b/wasm/wasm_source/src/tx_bridge_pool.rs index 001fdcffcf..1dbb86adb7 100644 --- a/wasm/wasm_source/src/tx_bridge_pool.rs +++ b/wasm/wasm_source/src/tx_bridge_pool.rs @@ -19,7 +19,6 @@ fn apply_tx(ctx: &mut Ctx, signed: Tx) -> TxResult { payer, &bridge_pool::BRIDGE_POOL_ADDRESS, &nam_addr, - None, amount.native_denominated(), &None, &None, @@ -39,7 +38,6 @@ fn apply_tx(ctx: &mut Ctx, signed: Tx) -> TxResult { sender, ð_bridge::ADDRESS, &nam_addr, - None, amount.native_denominated(), &None, &None, @@ -47,18 +45,13 @@ fn apply_tx(ctx: &mut Ctx, signed: Tx) -> TxResult { )?; } else { // Otherwise we escrow ERC20 tokens. - let sub_prefix = Some(wrapped_erc20s::sub_prefix(&asset)); - let amount = amount.denominated( - ð_bridge::ADDRESS, - sub_prefix.as_ref(), - ctx, - )?; + let token = wrapped_erc20s::token(&asset); + let amount = amount.denominated(ð_bridge::ADDRESS, ctx)?; token::transfer( ctx, sender, &bridge_pool::BRIDGE_POOL_ADDRESS, - ð_bridge::ADDRESS, - sub_prefix, + &token, amount, &None, &None, From ba65482b9a457f44172454f1a7098756f5f39509 Mon Sep 17 00:00:00 2001 From: yito88 Date: Sun, 2 Jul 2023 19:07:01 +0200 Subject: [PATCH 11/25] revert unexpected changes --- tests/src/e2e/helpers.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/src/e2e/helpers.rs b/tests/src/e2e/helpers.rs index 7c79fdfdb6..d1cb9b9f95 100644 --- a/tests/src/e2e/helpers.rs +++ b/tests/src/e2e/helpers.rs @@ -53,6 +53,7 @@ where /// and returns the [`Test`] handle and [`NamadaBgCmd`] for the validator node. /// It blocks until the node is ready to receive RPC requests from /// `namadac`. +#[allow(dead_code)] pub fn setup_single_node_test() -> Result<(Test, NamadaBgCmd)> { let test = setup::single_node_net()?; run_single_node_test_from(test) From 381dc7613350577fe7fbf794df92aa90c137077b Mon Sep 17 00:00:00 2001 From: yito88 Date: Tue, 4 Jul 2023 14:34:21 +0200 Subject: [PATCH 12/25] modify EthBridge VP and EthBridgePool VP for multitoken --- apps/src/lib/client/rpc.rs | 195 ++++++----------- apps/src/lib/node/ledger/shell/init_chain.rs | 9 +- shared/src/ledger/args.rs | 2 - shared/src/ledger/eth_bridge/bridge_pool.rs | 3 - .../native_vp/ethereum_bridge/authorize.rs | 56 ----- .../ethereum_bridge/bridge_pool_vp.rs | 37 +--- .../ledger/native_vp/ethereum_bridge/mod.rs | 1 - .../ledger/native_vp/ethereum_bridge/vp.rs | 204 ++++++------------ wasm/wasm_source/src/vp_implicit.rs | 3 +- wasm/wasm_source/src/vp_masp.rs | 21 +- wasm/wasm_source/src/vp_user.rs | 2 +- wasm/wasm_source/src/vp_validator.rs | 4 +- 12 files changed, 140 insertions(+), 397 deletions(-) delete mode 100644 shared/src/ledger/native_vp/ethereum_bridge/authorize.rs diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 95d0cb592d..62242d4584 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -16,7 +16,6 @@ use masp_primitives::merkle_tree::MerklePath; use masp_primitives::sapling::{Node, ViewingKey}; use masp_primitives::zip32::ExtendedFullViewingKey; use namada::core::types::transaction::governance::ProposalType; -use namada::ledger::args::InputAmount; use namada::ledger::events::Event; use namada::ledger::governance::parameters::GovParams; use namada::ledger::governance::storage as gov_storage; @@ -46,7 +45,7 @@ use namada::types::hash::Hash; use namada::types::key::*; use namada::types::masp::{BalanceOwner, ExtendedViewingKey, PaymentAddress}; use namada::types::storage::{BlockHeight, BlockResults, Epoch, Key, KeySeg}; -use namada::types::token::{Change, Denomination, MaspDenom}; +use namada::types::token::{Change, MaspDenom}; use namada::types::{storage, token}; use tokio::time::Instant; @@ -173,7 +172,7 @@ pub async fn query_transfers< relevant &= match &query_token { Some(token) => { let check = |(tok, chg): (&Address, &Change)| { - &tok.address == token && !chg.is_zero() + tok == token && !chg.is_zero() }; tfer_delta.values().cloned().any( |MaspChange { ref asset, change }| check((asset, &change)), @@ -193,7 +192,7 @@ pub async fn query_transfers< for (account, MaspChange { ref asset, change }) in tfer_delta { if account != masp() { print!(" {}:", account); - let token_alias = lookup_alias(wallet, &asset.address); + let token_alias = lookup_alias(wallet, &asset); let sign = match change.cmp(&Change::zero()) { Ordering::Greater => "+", Ordering::Less => "-", @@ -204,7 +203,7 @@ pub async fn query_transfers< sign, format_denominated_amount(client, asset, change.into(),) .await, - asset.format_with_alias(&token_alias) + token_alias ); } println!(); @@ -216,7 +215,7 @@ pub async fn query_transfers< if fvk_map.contains_key(&account) { print!(" {}:", fvk_map[&account]); for (token_addr, val) in masp_change { - let token_alias = lookup_alias(wallet, &token_addr.address); + let token_alias = lookup_alias(wallet, &token_addr); let sign = match val.cmp(&Change::zero()) { Ordering::Greater => "+", Ordering::Less => "-", @@ -231,7 +230,7 @@ pub async fn query_transfers< val.into(), ) .await, - token_addr.format_with_alias(&token_alias), + token_alias, ); } println!(); @@ -332,7 +331,8 @@ pub async fn query_transparent_balance< balances, None, owner.address().as_ref(), - ); + ) + .await; } } (Some(token), None) => { @@ -340,14 +340,15 @@ pub async fn query_transparent_balance< let balances = query_storage_prefix::(client, &prefix).await; if let Some(balances) = balances { - print_balances(client, wallet, balances, Some(&token), None); + print_balances(client, wallet, balances, Some(&token), None) + .await; } } (None, None) => { let balances = query_storage_prefix::(client, &prefix).await; if let Some(balances) = balances { - print_balances(client, wallet, balances, None, None); + print_balances(client, wallet, balances, None, None).await; } } } @@ -417,15 +418,15 @@ pub async fn query_pinned_balance< } // Now print out the received quantities according to CLI arguments - match (balance, args.token.as_ref(), args.sub_prefix.as_ref()) { - (Err(PinnedBalanceError::InvalidViewingKey), _, _) => println!( + match (balance, args.token.as_ref()) { + (Err(PinnedBalanceError::InvalidViewingKey), _) => println!( "Supplied viewing key cannot decode transactions to given \ payment address." ), - (Err(PinnedBalanceError::NoTransactionPinned), _, _) => { + (Err(PinnedBalanceError::NoTransactionPinned), _) => { println!("Payment address {} has not yet been consumed.", owner) } - (Ok((balance, epoch)), Some(token), sub_prefix) => { + (Ok((balance, epoch)), Some(token)) => { let token_alias = lookup_alias(wallet, token); let total_balance = balance @@ -437,9 +438,7 @@ pub async fn query_pinned_balance< println!( "Payment address {} was consumed during epoch {}. \ Received no shielded {}", - owner, - epoch, - token.format_with_alias(&token_alias) + owner, epoch, token_alias ); } else { let formatted = format_denominated_amount( @@ -451,14 +450,11 @@ pub async fn query_pinned_balance< println!( "Payment address {} was consumed during epoch {}. \ Received {} {}", - owner, - epoch, - formatted, - token.format_with_alias(&token_alias), + owner, epoch, formatted, token_alias, ); } } - (Ok((balance, epoch)), None, _) => { + (Ok((balance, epoch)), None) => { let mut found_any = false; for ((_, token_addr), value) in balance @@ -480,14 +476,10 @@ pub async fn query_pinned_balance< ) .await; let token_alias = tokens - .get(&token_addr.address) + .get(&token_addr) .map(|a| a.to_string()) - .unwrap_or_else(|| token_addr.address.to_string()); - println!( - " {}: {}", - token_addr.format_with_alias(&token_alias), - formatted, - ); + .unwrap_or_else(|| token_addr.to_string()); + println!(" {}: {}", token_alias, formatted,); } if !found_any { println!( @@ -511,44 +503,44 @@ async fn print_balances( let stdout = io::stdout(); let mut w = stdout.lock(); + let mut print_num = 0; let mut print_token = None; - let print_num = balances - .filter_map(|(key, balance)| { - token::is_any_token_balance_key(&key).map(|(token, owner)| { - ( - token.clone(), - owner.clone(), - format!( - ": {}, owned by {}", - format_denominated_amount(client, token, balance).await, - lookup_alias(wallet, owner) - ), - ) - }) - }) - .filter_map(|(t, o, s)| match (token, target) { + for (key, balance) in balances { + let (t, o, s) = match token::is_any_token_balance_key(&key) { + Some([tok, owner]) => ( + tok.clone(), + owner.clone(), + format!( + ": {}, owned by {}", + format_denominated_amount(client, tok, balance).await, + lookup_alias(wallet, owner) + ), + ), + None => continue, + }; + + let (t, s) = match (token, target) { (Some(token), Some(target)) if t == *token && o == *target => { - Some((t, s)) + (t, s) } - (Some(token), None) if t == *token => Some((t, s)), - (None, Some(target)) if o == *target => Some((t, s)), - (None, None) => Some((t, s)), - _ => None, - }) - .map(|(t, s)| { - match &print_token { - Some(token) if *token == t => { - // the token was already printed - } - Some(_) | None => { - let token_alias = lookup_alias(wallet, &t); - writeln!(w, "Token {}", token_alias).unwrap(); - print_token = Some(t); - } + (Some(token), None) if t == *token => (t, s), + (None, Some(target)) if o == *target => (t, s), + (None, None) => (t, s), + _ => continue, + }; + match &print_token { + Some(token) if *token == t => { + // the token was already printed } - writeln!(w, "{}", s).unwrap(); - }) - .count(); + _ => { + let token_alias = lookup_alias(wallet, &t); + writeln!(w, "Token {}", token_alias).unwrap(); + print_token = Some(t); + } + } + writeln!(w, "{}", s).unwrap(); + print_num += 1; + } if print_num == 0 { match (token, target) { @@ -759,12 +751,12 @@ pub async fn query_shielded_balance< if total_balance.is_zero() { println!( "No shielded {} balance found for given key", - token.format_with_alias(&token_alias) + token_alias ); } else { println!( "{}: {}", - token.format_with_alias(&token_alias), + token_alias, format_denominated_amount( client, &token, @@ -851,10 +843,7 @@ pub async fn query_shielded_balance< println!("Shielded Token {}:", token_alias); let mut found_any = false; let token_alias = lookup_alias(wallet, &token); - println!( - "Shielded Token {}:", - token.format_with_alias(&token_alias), - ); + println!("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; @@ -886,7 +875,7 @@ pub async fn query_shielded_balance< if !found_any { println!( "No shielded {} balance found for any wallet key", - token.format_with_alias(&token_alias), + token_alias, ); } } @@ -931,10 +920,7 @@ pub async fn print_decoded_balance< { println!( "{} : {}", - token_addr.format_with_alias(&lookup_alias( - wallet, - &token_addr.address - )), + lookup_alias(wallet, &token_addr), format_denominated_amount(client, token_addr, (*amount).into()) .await, ); @@ -956,12 +942,12 @@ pub async fn print_decoded_balance_with_epoch< for ((epoch, token_addr), value) in decoded_balance.iter() { let asset_value = (*value).into(); let alias = tokens - .get(&token_addr.address) + .get(&token_addr) .map(|a| a.to_string()) .unwrap_or_else(|| token_addr.to_string()); println!( "{} | {} : {}", - token_addr.format_with_alias(&alias), + alias, epoch, format_denominated_amount(client, token_addr, asset_value).await, ); @@ -1725,7 +1711,7 @@ pub async fn query_conversions( .expect("Conversions should be defined"); // Track whether any non-sentinel conversions are found let mut conversions_found = false; - for ((addr, sub, _), epoch, conv, _) in conv_state.assets.values() { + for ((addr, _), epoch, conv, _) in conv_state.assets.values() { let amt: masp_primitives::transaction::components::Amount = conv.clone().into(); // If the user has specified any targets, then meet them @@ -1739,9 +1725,8 @@ pub async fn query_conversions( conversions_found = true; // Print the asset to which the conversion applies print!( - "{}{}[{}]: ", + "{}[{}]: ", tokens.get(addr).cloned().unwrap_or_else(|| addr.clone()), - sub.as_ref().map(|k| format!("/{}", k)).unwrap_or_default(), epoch, ); // Now print out the components of the allowed conversion @@ -1749,14 +1734,13 @@ pub async fn query_conversions( for (asset_type, val) in amt.components() { // Look up the address and epoch of asset to facilitate pretty // printing - let ((addr, sub, _), epoch, _, _) = &conv_state.assets[asset_type]; + let ((addr, _), epoch, _, _) = &conv_state.assets[asset_type]; // Now print out this component of the conversion print!( - "{}{} {}{}[{}]", + "{}{} {}[{}]", prefix, val, tokens.get(addr).cloned().unwrap_or_else(|| addr.clone()), - sub.as_ref().map(|k| format!("/{}", k)).unwrap_or_default(), epoch ); // Future iterations need to be prefixed with + @@ -1776,7 +1760,6 @@ pub async fn query_conversion( asset_type: AssetType, ) -> Option<( Address, - Option, MaspDenom, Epoch, masp_primitives::transaction::components::Amount, @@ -2182,49 +2165,3 @@ fn unwrap_client_response( cli::safe_exit(1) }) } - -/// Get the correct representation of the amount given the token type. -pub async fn validate_amount( - client: &C, - amount: InputAmount, - token: &Address, - sub_prefix: &Option, - force: bool, -) -> token::DenominatedAmount { - let input_amount = match amount { - InputAmount::Unvalidated(amt) => amt.canonical(), - InputAmount::Validated(amt) => return amt, - }; - let denom = unwrap_client_response::>( - RPC.vp().token().denomination(client, token).await, - ) - .unwrap_or_else(|| { - if force { - println!( - "No denomination found for token: {token}, but --force was \ - passed. Defaulting to the provided denomination." - ); - input_amount.denom - } else { - println!( - "No denomination found for token: {token}, the input \ - arguments could not be parsed." - ); - cli::safe_exit(1); - } - }); - if denom < input_amount.denom && !force { - println!( - "The input amount contained a higher precision than allowed by \ - {token}." - ); - cli::safe_exit(1); - } else { - input_amount.increase_precision(denom).unwrap_or_else(|_| { - println!( - "The amount provided requires more the 256 bits to represent." - ); - cli::safe_exit(1); - }) - } -} diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index 91f48e3e05..4fa8889753 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -354,14 +354,7 @@ where } in accounts { // associate a token with its denomination. - write_denom( - &mut self.wl_storage, - &address, - // TODO: Should we support multi-tokens at genesis? - None, - denom, - ) - .unwrap(); + write_denom(&mut self.wl_storage, &address, denom).unwrap(); let vp_code_hash = read_wasm_hash(&self.wl_storage, vp_code_path.clone()) .unwrap() diff --git a/shared/src/ledger/args.rs b/shared/src/ledger/args.rs index ba1f071fb8..cb71e65b9c 100644 --- a/shared/src/ledger/args.rs +++ b/shared/src/ledger/args.rs @@ -324,8 +324,6 @@ pub struct QueryTransfers { pub owner: Option, /// Address of a token pub token: Option, - /// sub-prefix if querying a multi-token - pub sub_prefix: Option, } /// Query PoS bond(s) diff --git a/shared/src/ledger/eth_bridge/bridge_pool.rs b/shared/src/ledger/eth_bridge/bridge_pool.rs index e06c141614..30b1f4e171 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool.rs @@ -8,7 +8,6 @@ use std::sync::Arc; use borsh::BorshSerialize; use ethbridge_bridge_contract::Bridge; use ethers::providers::Middleware; -use namada_core::ledger::eth_bridge::storage::wrapped_erc20s; use namada_core::ledger::eth_bridge::ADDRESS as BRIDGE_ADDRESS; use namada_core::types::key::common; use owo_colors::OwoColorize; @@ -57,8 +56,6 @@ pub async fn build_bridge_pool_tx< gas_payer, code_path: wasm_code, } = args; - - let sub_prefix = Some(wrapped_erc20s::sub_prefix(&asset)); let DenominatedAmount { amount, .. } = validate_amount(client, amount, &BRIDGE_ADDRESS, tx.force) .await diff --git a/shared/src/ledger/native_vp/ethereum_bridge/authorize.rs b/shared/src/ledger/native_vp/ethereum_bridge/authorize.rs deleted file mode 100644 index 8c998ad50b..0000000000 --- a/shared/src/ledger/native_vp/ethereum_bridge/authorize.rs +++ /dev/null @@ -1,56 +0,0 @@ -//! Functionality to do with checking whether a transaction is authorized by the -//! "owner" of some key under this account -use std::collections::BTreeSet; - -use namada_core::types::address::Address; - -/// For wrapped ERC20 transfers, checks that `verifiers` contains the `sender`'s -/// address - we delegate to the sender's VP to authorize the transfer (for -/// regular Namada accounts, this will be `vp_implicit` or `vp_user`). -pub(super) fn is_authorized( - verifiers: &BTreeSet
, - sender: &Address, - receiver: &Address, -) -> bool { - verifiers.contains(sender) && verifiers.contains(receiver) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::types::address; - - #[test] - fn test_is_authorized_passes() { - let sender = address::testing::established_address_1(); - let receiver = address::testing::established_address_2(); - let verifiers = BTreeSet::from([sender.clone(), receiver.clone()]); - - let authorized = is_authorized(&verifiers, &sender, &receiver); - - assert!(authorized); - } - - #[test] - fn test_is_authorized_fails() { - let sender = address::testing::established_address_1(); - let receiver = address::testing::established_address_2(); - let verifiers = BTreeSet::default(); - - let authorized = is_authorized(&verifiers, &sender, &receiver); - - assert!(!authorized); - - let verifiers = BTreeSet::from([sender.clone()]); - - let authorized = is_authorized(&verifiers, &sender, &receiver); - - assert!(!authorized); - - let verifiers = BTreeSet::from([receiver.clone()]); - - let authorized = is_authorized(&verifiers, &sender, &receiver); - - assert!(!authorized); - } -} diff --git a/shared/src/ledger/native_vp/ethereum_bridge/bridge_pool_vp.rs b/shared/src/ledger/native_vp/ethereum_bridge/bridge_pool_vp.rs index 63caf82a57..e3cb7e24e8 100644 --- a/shared/src/ledger/native_vp/ethereum_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/native_vp/ethereum_bridge/bridge_pool_vp.rs @@ -98,24 +98,14 @@ where if keys_changed.contains(&owner_key) && keys_changed.contains(&escrow_key) { - match check_balance_changes( - &self.ctx, - (&self.ctx.storage.native_token, &escrow_key) - .try_into() - .expect("This should not fail"), - (&self.ctx.storage.native_token, &owner_key) - .try_into() - .expect("This should not fail"), - ) { - Ok(Some((sender, _, amount))) - if check_delta(&sender, &amount, transfer) => {} - other => { + match check_balance_changes(&self.ctx, &owner_key, &escrow_key)? { + Some(amount) if amount == transfer.transfer.amount => Ok(true), + _ => { tracing::debug!( "The assets of the transfer were not properly \ - escrowed into the Ethereum bridge pool: {:?}", - other + escrowed into the Ethereum bridge pool" ); - return Ok(false); + Ok(false) } } } else { @@ -123,14 +113,8 @@ where "The assets of the transfer were not properly escrowed into \ the Ethereum bridge pool." ); - return Ok(false); + Ok(false) } - - tracing::info!( - "The Ethereum bridge pool VP accepted the transfer {:?}.", - transfer - ); - Ok(true) } /// Check that the correct amount of Nam was sent @@ -235,15 +219,6 @@ where } } -/// Check if a delta matches the delta given by a transfer -fn check_delta( - sender: &Address, - amount: &Amount, - transfer: &PendingTransfer, -) -> bool { - *sender == transfer.transfer.sender && *amount == transfer.transfer.amount -} - /// Helper struct for handling the different escrow /// checking scenarios. struct EscrowDelta<'a> { diff --git a/shared/src/ledger/native_vp/ethereum_bridge/mod.rs b/shared/src/ledger/native_vp/ethereum_bridge/mod.rs index 7e5062a251..85df785e79 100644 --- a/shared/src/ledger/native_vp/ethereum_bridge/mod.rs +++ b/shared/src/ledger/native_vp/ethereum_bridge/mod.rs @@ -2,6 +2,5 @@ //! This includes both the bridge vp and the vp for the bridge //! pool. -mod authorize; pub mod bridge_pool_vp; pub mod vp; diff --git a/shared/src/ledger/native_vp/ethereum_bridge/vp.rs b/shared/src/ledger/native_vp/ethereum_bridge/vp.rs index de068f5e9e..01a9f7625d 100644 --- a/shared/src/ledger/native_vp/ethereum_bridge/vp.rs +++ b/shared/src/ledger/native_vp/ethereum_bridge/vp.rs @@ -13,7 +13,6 @@ use namada_core::types::address::{Address, InternalAddress}; use namada_core::types::storage::Key; use namada_core::types::token::{balance_key, Amount, Change}; -use crate::ledger::native_vp::ethereum_bridge::authorize; use crate::ledger::native_vp::{Ctx, NativeVp, StorageReader, VpEnv}; use crate::proto::Tx; use crate::vm::WasmCacheAccess; @@ -94,7 +93,7 @@ where #[derive(Debug)] enum CheckType { Escrow, - Erc20Transfer(wrapped_erc20s::Key, wrapped_erc20s::Key), + Erc20Transfer, } #[derive(thiserror::Error, Debug)] @@ -138,35 +137,14 @@ where "Ethereum Bridge VP triggered", ); - let (key_a, key_b) = match determine_check_type( + match determine_check_type( &self.ctx.storage.native_token, keys_changed, )? { - Some(CheckType::Erc20Transfer(key_a, key_b)) => (key_a, key_b), + // Multitoken VP checks the balance changes for the ERC20 transfer + Some(CheckType::Erc20Transfer) => Ok(true), Some(CheckType::Escrow) => return self.check_escrow(verifiers), - None => return Ok(false), - }; - let (sender, receiver, _) = - match check_balance_changes(&self.ctx, key_a, key_b)? { - Some(sender) => sender, - None => return Ok(false), - }; - if authorize::is_authorized(verifiers, &sender, &receiver) { - tracing::info!( - ?verifiers, - ?sender, - ?receiver, - "Ethereum Bridge VP authorized transfer" - ); - Ok(true) - } else { - tracing::info!( - ?verifiers, - ?sender, - ?receiver, - "Ethereum Bridge VP rejected unauthorized transfer" - ); - Ok(false) + None => Ok(false), } } } @@ -245,155 +223,95 @@ fn determine_check_type( ); return Ok(None); } - Ok(Some(CheckType::Erc20Transfer(key_a, key_b))) + Ok(Some(CheckType::Erc20Transfer)) } -/// Checks that the balances at both `key_a` and `key_b` have changed by some -/// amount, and that the changes balance each other out. If the balance changes -/// are invalid, the reason is logged and a `None` is returned. Otherwise, -/// return: -/// - the `Address` of the sender i.e. the owner of the balance which is -/// decreasing -/// - the `Address` of the receiver i.e. the owner of the balance which is -/// increasing -/// - the `Amount` of the transfer i.e. by how much the sender's balance -/// decreased, or equivalently by how much the receiver's balance increased +/// Checks that the balances at both `sender` and `receiver` have changed by +/// some amount, and that the changes balance each other out. If the balance +/// changes are invalid, the reason is logged and a `None` is returned. +/// Otherwise, return the `Amount` of the transfer i.e. by how much the sender's +/// balance decreased, or equivalently by how much the receiver's balance +/// increased pub(super) fn check_balance_changes( reader: impl StorageReader, - key_a: wrapped_erc20s::Key, - key_b: wrapped_erc20s::Key, -) -> Result> { - let (balance_a, balance_b) = - match (key_a.suffix.clone(), key_b.suffix.clone()) { - ( - wrapped_erc20s::KeyType::Balance { .. }, - wrapped_erc20s::KeyType::Balance { .. }, - ) => (Key::from(&key_a), Key::from(&key_b)), - ( - wrapped_erc20s::KeyType::Balance { .. }, - wrapped_erc20s::KeyType::Supply, - ) - | ( - wrapped_erc20s::KeyType::Supply, - wrapped_erc20s::KeyType::Balance { .. }, - ) => { - tracing::debug!( - ?key_a, - ?key_b, - "Rejecting transaction that is attempting to change a \ - supply key" - ); - return Ok(None); - } - ( - wrapped_erc20s::KeyType::Supply, - wrapped_erc20s::KeyType::Supply, - ) => { - // in theory, this should be unreachable!() as we would have - // already rejected if both supply keys were for - // the same asset - tracing::debug!( - ?key_a, - ?key_b, - "Rejecting transaction that is attempting to change two \ - supply keys" - ); - return Ok(None); - } - }; - let balance_a_pre = reader - .read_pre_value::(&balance_a)? + sender: &Key, + receiver: &Key, +) -> Result> { + let sender_balance_pre = reader + .read_pre_value::(&sender)? .unwrap_or_default() .change(); - let balance_a_post = match reader.read_post_value::(&balance_a)? { - Some(value) => value, - None => { - tracing::debug!( - ?balance_a, - "Rejecting transaction as could not read_post balance key" - ); - return Ok(None); + let sender_balance_post = + match reader.read_post_value::(&sender)? { + Some(value) => value, + None => { + return Err(eyre!( + "Rejecting transaction as could not read_post balance key \ + {}", + sender, + )); + } } - } - .change(); - let balance_b_pre = reader - .read_pre_value::(&balance_b)? + .change(); + let receiver_balance_pre = reader + .read_pre_value::(&receiver)? .unwrap_or_default() .change(); - let balance_b_post = match reader.read_post_value::(&balance_b)? { + let receiver_balance_post = match reader + .read_post_value::(&receiver)? + { Some(value) => value, None => { - tracing::debug!( - ?balance_b, - "Rejecting transaction as could not read_post balance key" - ); - return Ok(None); + return Err(eyre!( + "Rejecting transaction as could not read_post balance key {}", + receiver, + )); } } .change(); - let balance_a_delta = calculate_delta(balance_a_pre, balance_a_post)?; - let balance_b_delta = calculate_delta(balance_b_pre, balance_b_post)?; - if balance_a_delta != -balance_b_delta { + let sender_balance_delta = + calculate_delta(sender_balance_pre, sender_balance_post)?; + let receiver_balance_delta = + calculate_delta(receiver_balance_pre, receiver_balance_post)?; + if receiver_balance_delta != -sender_balance_delta { tracing::debug!( - ?balance_a_pre, - ?balance_b_pre, - ?balance_a_post, - ?balance_b_post, - ?balance_a_delta, - ?balance_b_delta, + ?sender_balance_pre, + ?receiver_balance_pre, + ?sender_balance_post, + ?receiver_balance_post, + ?sender_balance_delta, + ?receiver_balance_delta, "Rejecting transaction as balance changes do not match" ); return Ok(None); } - if balance_a_delta.is_zero() { - assert_eq!(balance_b_delta, Change::zero()); - tracing::debug!("Rejecting transaction as no balance change"); + if sender_balance_delta.is_zero() || sender_balance_delta > Change::zero() { + assert!( + receiver_balance_delta.is_zero() + || receiver_balance_delta < Change::zero() + ); + tracing::debug!( + "Rejecting transaction as no balance change or invalid change" + ); return Ok(None); } - if balance_a_post < Change::zero() { + if sender_balance_post < Change::zero() { tracing::debug!( - ?balance_a_post, + ?sender_balance_post, "Rejecting transaction as balance is negative" ); return Ok(None); } - if balance_b_post < Change::zero() { + if receiver_balance_post < Change::zero() { tracing::debug!( - ?balance_b_post, + ?receiver_balance_post, "Rejecting transaction as balance is negative" ); return Ok(None); } - if balance_a_delta < Change::zero() { - if let wrapped_erc20s::KeyType::Balance { owner: sender } = key_a.suffix - { - let wrapped_erc20s::KeyType::Balance { owner: receiver } = - key_b.suffix else { unreachable!() }; - Ok(Some(( - sender, - receiver, - Amount::from_change(balance_b_delta), - ))) - } else { - unreachable!() - } - } else { - assert!(balance_b_delta < Change::zero()); - if let wrapped_erc20s::KeyType::Balance { owner: sender } = key_b.suffix - { - let wrapped_erc20s::KeyType::Balance { owner: receiver } = - key_a.suffix else { unreachable!() }; - Ok(Some(( - sender, - receiver, - Amount::from_change(balance_a_delta), - ))) - } else { - unreachable!() - } - } + Ok(Some(Amount::from_change(receiver_balance_delta))) } /// Return the delta between `balance_pre` and `balance_post`, erroring if there diff --git a/wasm/wasm_source/src/vp_implicit.rs b/wasm/wasm_source/src/vp_implicit.rs index 022a22700a..1e4ff59852 100644 --- a/wasm/wasm_source/src/vp_implicit.rs +++ b/wasm/wasm_source/src/vp_implicit.rs @@ -30,8 +30,7 @@ impl<'a> From<&'a storage::Key> for KeyType<'a> { fn from(key: &'a storage::Key) -> KeyType<'a> { if let Some(address) = key::is_pk_key(key) { Self::Pk(address) - } else if let Some((_, address)) = token::is_any_token_balance_key(key) - { + } else if let Some([_, owner]) = token::is_any_token_balance_key(key) { Self::Token { owner } } else if proof_of_stake::is_pos_key(key) { Self::PoS diff --git a/wasm/wasm_source/src/vp_masp.rs b/wasm/wasm_source/src/vp_masp.rs index 39952339ea..1e43d93a25 100644 --- a/wasm/wasm_source/src/vp_masp.rs +++ b/wasm/wasm_source/src/vp_masp.rs @@ -13,11 +13,10 @@ use ripemd::{Digest, Ripemd160}; fn asset_type_from_epoched_address( epoch: Epoch, token: &Address, - sub_prefix: String, denom: token::MaspDenom, ) -> AssetType { // Timestamp the chosen token with the current epoch - let token_bytes = (token, sub_prefix, denom, epoch.0) + let token_bytes = (token, denom, epoch.0) .try_to_vec() .expect("token should serialize"); // Generate the unique asset identifier from the unique token address @@ -63,19 +62,10 @@ fn valid_transfer_amount( fn convert_amount( epoch: Epoch, token: &Address, - sub_prefix: &Option, val: token::Amount, denom: token::MaspDenom, ) -> (AssetType, Amount) { - let asset_type = asset_type_from_epoched_address( - epoch, - token, - sub_prefix - .as_ref() - .map(|k| k.to_string()) - .unwrap_or_default(), - denom, - ); + let asset_type = asset_type_from_epoched_address(epoch, token, denom); // Combine the value and unit into one amount let amount = Amount::from_nonnegative(asset_type, denom.denominate(&val)) .expect("invalid value or asset type for amount"); @@ -127,7 +117,6 @@ fn validate_tx( let (_transp_asset, transp_amt) = convert_amount( ctx.get_block_epoch().unwrap(), &transfer.token, - &transfer.sub_prefix, transfer.amount.into(), denom, ); @@ -191,11 +180,6 @@ fn validate_tx( asset_type_from_epoched_address( ctx.get_block_epoch().unwrap(), &transfer.token, - transfer - .sub_prefix - .as_ref() - .map(|k| k.to_string()) - .unwrap_or_default(), denom, ); @@ -215,7 +199,6 @@ fn validate_tx( let (_transp_asset, transp_amt) = convert_amount( ctx.get_block_epoch().unwrap(), &transfer.token, - &transfer.sub_prefix, transfer.amount.amount, denom, ); diff --git a/wasm/wasm_source/src/vp_user.rs b/wasm/wasm_source/src/vp_user.rs index e605ce28d8..e3fc1e1098 100644 --- a/wasm/wasm_source/src/vp_user.rs +++ b/wasm/wasm_source/src/vp_user.rs @@ -24,7 +24,7 @@ enum KeyType<'a> { impl<'a> From<&'a storage::Key> for KeyType<'a> { fn from(key: &'a storage::Key) -> KeyType<'a> { - if let Some([_, address]) = token::is_any_token_balance_key(key) { + if let Some([_, owner]) = token::is_any_token_balance_key(key) { Self::Token { owner } } else if proof_of_stake::is_pos_key(key) { Self::PoS diff --git a/wasm/wasm_source/src/vp_validator.rs b/wasm/wasm_source/src/vp_validator.rs index 768f025d78..eb80929626 100644 --- a/wasm/wasm_source/src/vp_validator.rs +++ b/wasm/wasm_source/src/vp_validator.rs @@ -26,8 +26,8 @@ enum KeyType<'a> { impl<'a> From<&'a storage::Key> for KeyType<'a> { fn from(key: &'a storage::Key) -> KeyType<'a> { - if let Some([_, address]) = token::is_any_token_balance_key(key) { - Self::Token(address) + if let Some([_, owner]) = token::is_any_token_balance_key(key) { + Self::Token { owner } } else if proof_of_stake::is_pos_key(key) { Self::PoS } else if gov_storage::is_vote_key(key) { From c441ad52a26d40d479a494d1694e28d84fb66c63 Mon Sep 17 00:00:00 2001 From: yito88 Date: Wed, 5 Jul 2023 11:02:15 +0200 Subject: [PATCH 13/25] for clippy --- .../eth_bridge/storage/wrapped_erc20s.rs | 14 +++++----- .../ledger/native_vp/ethereum_bridge/vp.rs | 28 +++++++++---------- tests/src/e2e/eth_bridge_tests.rs | 1 + 3 files changed, 21 insertions(+), 22 deletions(-) diff --git a/core/src/ledger/eth_bridge/storage/wrapped_erc20s.rs b/core/src/ledger/eth_bridge/storage/wrapped_erc20s.rs index beb4a61723..0062dd50c9 100644 --- a/core/src/ledger/eth_bridge/storage/wrapped_erc20s.rs +++ b/core/src/ledger/eth_bridge/storage/wrapped_erc20s.rs @@ -11,7 +11,7 @@ use crate::types::token::{ /// Construct a token address from an ERC20 address. pub fn token(address: &EthAddress) -> Address { - Address::Internal(InternalAddress::Erc20(address.clone())) + Address::Internal(InternalAddress::Erc20(*address)) } /// Represents the type of a key relating to a wrapped ERC20 @@ -47,12 +47,12 @@ impl From<&Key> for storage::Key { /// Returns true if the given key has an ERC20 token pub fn has_erc20_segment(key: &storage::Key) -> bool { - match key.segments.get(1) { + matches!( + key.segments.get(1), Some(DbKeySeg::AddressSeg(Address::Internal( InternalAddress::Erc20(_addr), - ))) => true, - _ => false, - } + ))) + ) } impl TryFrom<(&Address, &storage::Key)> for Key { @@ -72,7 +72,7 @@ impl TryFrom<(&Address, &storage::Key)> for Key { InternalAddress::Erc20(addr), ))) = key.segments.get(1) { - addr.clone() + *addr } else { return Err(eyre!( "key has an incorrect segment at index #2, expected an \ @@ -207,7 +207,7 @@ mod test { DbKeySeg::StringSeg(supply_key_seg), ] if multitoken_addr == &MULTITOKEN_ADDRESS && token_addr == &dai_erc20_token() && - balance_key_seg == &BALANCE_STORAGE_KEY && + balance_key_seg == BALANCE_STORAGE_KEY && supply_key_seg == MINTED_STORAGE_KEY ); diff --git a/shared/src/ledger/native_vp/ethereum_bridge/vp.rs b/shared/src/ledger/native_vp/ethereum_bridge/vp.rs index 01a9f7625d..f2b0f5245e 100644 --- a/shared/src/ledger/native_vp/ethereum_bridge/vp.rs +++ b/shared/src/ledger/native_vp/ethereum_bridge/vp.rs @@ -143,7 +143,7 @@ where )? { // Multitoken VP checks the balance changes for the ERC20 transfer Some(CheckType::Erc20Transfer) => Ok(true), - Some(CheckType::Escrow) => return self.check_escrow(verifiers), + Some(CheckType::Escrow) => self.check_escrow(verifiers), None => Ok(false), } } @@ -238,27 +238,25 @@ pub(super) fn check_balance_changes( receiver: &Key, ) -> Result> { let sender_balance_pre = reader - .read_pre_value::(&sender)? + .read_pre_value::(sender)? .unwrap_or_default() .change(); - let sender_balance_post = - match reader.read_post_value::(&sender)? { - Some(value) => value, - None => { - return Err(eyre!( - "Rejecting transaction as could not read_post balance key \ - {}", - sender, - )); - } + let sender_balance_post = match reader.read_post_value::(sender)? { + Some(value) => value, + None => { + return Err(eyre!( + "Rejecting transaction as could not read_post balance key {}", + sender, + )); } - .change(); + } + .change(); let receiver_balance_pre = reader - .read_pre_value::(&receiver)? + .read_pre_value::(receiver)? .unwrap_or_default() .change(); let receiver_balance_post = match reader - .read_post_value::(&receiver)? + .read_post_value::(receiver)? { Some(value) => value, None => { diff --git a/tests/src/e2e/eth_bridge_tests.rs b/tests/src/e2e/eth_bridge_tests.rs index 76756fea32..1d15c5323a 100644 --- a/tests/src/e2e/eth_bridge_tests.rs +++ b/tests/src/e2e/eth_bridge_tests.rs @@ -6,6 +6,7 @@ use std::str::FromStr; use borsh::{BorshDeserialize, BorshSerialize}; use color_eyre::eyre::{eyre, Result}; +use expectrl::ControlCode; use namada::eth_bridge::oracle; use namada::eth_bridge::storage::vote_tallies; use namada::ledger::eth_bridge::{ From d46513f79eb41e6bd22aa7adab83f90d09f62f62 Mon Sep 17 00:00:00 2001 From: yito88 Date: Wed, 5 Jul 2023 15:12:06 +0200 Subject: [PATCH 14/25] fix multitoken vp to check the minter --- apps/src/lib/client/tx.rs | 18 +- core/src/types/token.rs | 17 ++ shared/src/ledger/native_vp/multitoken.rs | 217 +++++++++++++++++++--- tests/src/e2e/helpers.rs | 1 + tests/src/e2e/ledger_tests.rs | 36 ++-- 5 files changed, 228 insertions(+), 61 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index d70f8c7e6f..f361406643 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -1275,7 +1275,6 @@ mod test_tx { use masp_primitives::transaction::components::Amount; use namada::ledger::masp::{make_asset_type, MaspAmount}; use namada::types::address::testing::gen_established_address; - use namada::types::storage::DbKeySeg; use namada::types::token::MaspDenom; use super::*; @@ -1283,25 +1282,14 @@ mod test_tx { #[test] fn test_masp_add_amount() { let address_1 = gen_established_address(); - let prefix_1: Key = - DbKeySeg::StringSeg("eth_seg".parse().unwrap()).into(); - let prefix_2: Key = - DbKeySeg::StringSeg("crypto_kitty".parse().unwrap()).into(); let denom_1 = MaspDenom::One; let denom_2 = MaspDenom::Three; let epoch = Epoch::default(); let _masp_amount = MaspAmount::default(); - let asset_base = make_asset_type( - Some(epoch), - &address_1, - &Some(prefix_1.clone()), - denom_1, - ); - let _asset_denom = - make_asset_type(Some(epoch), &address_1, &Some(prefix_1), denom_2); - let _asset_prefix = - make_asset_type(Some(epoch), &address_1, &Some(prefix_2), denom_1); + let asset_base = make_asset_type(Some(epoch), &address_1, denom_1); + let _asset_denom = make_asset_type(Some(epoch), &address_1, denom_2); + let _asset_prefix = make_asset_type(Some(epoch), &address_1, denom_1); let _amount_base = Amount::from_pair(asset_base, 16).expect("Test failed"); diff --git a/core/src/types/token.rs b/core/src/types/token.rs index 16dcb6d424..bf4e23cbe3 100644 --- a/core/src/types/token.rs +++ b/core/src/types/token.rs @@ -879,6 +879,23 @@ pub fn is_masp_key(key: &Key) -> bool { || key.starts_with(PIN_KEY_PREFIX))) } +/// Check if the given storage key is for a minter of a unspecified token. +/// If it is, returns the token. +pub fn is_any_minter_key(key: &Key) -> Option<&Address> { + match &key.segments[..] { + [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::AddressSeg(token), + DbKeySeg::StringSeg(minter), + ] if *addr == Address::Internal(InternalAddress::Multitoken) + && minter == MINTER_STORAGE_KEY => + { + Some(token) + } + _ => None, + } +} + /// Check if the given storage key is for total supply of a unspecified token. /// If it is, returns the token. pub fn is_any_minted_balance_key(key: &Key) -> Option<&Address> { diff --git a/shared/src/ledger/native_vp/multitoken.rs b/shared/src/ledger/native_vp/multitoken.rs index 22556b75a1..ffb8731dbb 100644 --- a/shared/src/ledger/native_vp/multitoken.rs +++ b/shared/src/ledger/native_vp/multitoken.rs @@ -9,10 +9,10 @@ use crate::ledger::storage; use crate::ledger::vp_env::VpEnv; use crate::proto::Tx; use crate::types::address::{Address, InternalAddress}; -use crate::types::storage::Key; +use crate::types::storage::{Key, KeySeg}; use crate::types::token::{ - is_any_minted_balance_key, is_any_token_balance_key, minter_key, Amount, - Change, + is_any_minted_balance_key, is_any_minter_key, is_any_token_balance_key, + minter_key, Amount, Change, }; use crate::vm::WasmCacheAccess; @@ -73,13 +73,25 @@ where None => _ = mints.insert(token, diff), } - // Check if the minter VP is called - let minter_key = minter_key(token); - let minter = match self.ctx.read_post(&minter_key)? { - Some(m) => m, + // Check if the minter is set + match self.check_minter(token)? { + Some(minter) if verifiers.contains(&minter) => {} + _ => return Ok(false), + } + } else if let Some(token) = is_any_minter_key(key) { + match self.check_minter(token)? { + Some(_) => {} None => return Ok(false), - }; - if !verifiers.contains(&minter) { + } + } else { + if key.segments.get(0) + == Some( + &Address::Internal(InternalAddress::Multitoken) + .to_db_key(), + ) + { + // Reject when trying to update an unexpected key under + // `#Multitoken/...` return Ok(false); } } @@ -95,6 +107,43 @@ where } } +impl<'a, DB, H, CA> MultitokenVp<'a, DB, H, CA> +where + DB: 'static + storage::DB + for<'iter> storage::DBIter<'iter>, + H: 'static + storage::StorageHasher, + CA: 'static + WasmCacheAccess, +{ + /// Return the minter if the minter is valid and the minter VP exists + pub fn check_minter(&self, token: &Address) -> Result> { + // Check if the minter is set + let minter_key = minter_key(token); + let minter = match self.ctx.read_post(&minter_key)? { + Some(m) => m, + None => return Ok(None), + }; + match token { + Address::Internal(InternalAddress::Erc20(_)) => { + if minter == Address::Internal(InternalAddress::EthBridge) { + return Ok(Some(minter)); + } + } + Address::Internal(InternalAddress::IbcToken(_)) => { + if minter == Address::Internal(InternalAddress::Ibc) { + return Ok(Some(minter)); + } + } + _ => { + // Check the minter VP exists + let vp_key = Key::validity_predicate(&minter); + if self.ctx.has_key_post(&vp_key)? { + return Ok(Some(minter)); + } + } + } + Ok(None) + } +} + #[cfg(test)] mod tests { use std::collections::BTreeSet; @@ -107,9 +156,11 @@ mod tests { use crate::core::types::address::testing::{ established_address_1, established_address_2, }; + use crate::eth_bridge::storage::wrapped_erc20s; use crate::ledger::gas::VpGasMeter; use crate::proto::{Code, Data, Section, Signature, Tx}; use crate::types::address::{Address, InternalAddress}; + use crate::types::ethereum_events::testing::arbitrary_eth_address; use crate::types::key::testing::keypair_1; use crate::types::storage::TxIndex; use crate::types::token::{ @@ -145,14 +196,14 @@ mod tests { let sender = established_address_1(); let sender_key = balance_key(&nam(), &sender); - let amount = Amount::whole(100); + let amount = Amount::native_whole(100); wl_storage .storage .write(&sender_key, amount.try_to_vec().unwrap()) .expect("write failed"); // transfer 10 - let amount = Amount::whole(90); + let amount = Amount::native_whole(90); wl_storage .write_log .write(&sender_key, amount.try_to_vec().unwrap()) @@ -160,7 +211,7 @@ mod tests { keys_changed.insert(sender_key); let receiver = established_address_2(); let receiver_key = balance_key(&nam(), &receiver); - let amount = Amount::whole(10); + let amount = Amount::native_whole(10); wl_storage .write_log .write(&receiver_key, amount.try_to_vec().unwrap()) @@ -198,14 +249,14 @@ mod tests { let sender = established_address_1(); let sender_key = balance_key(&nam(), &sender); - let amount = Amount::whole(100); + let amount = Amount::native_whole(100); wl_storage .storage .write(&sender_key, amount.try_to_vec().unwrap()) .expect("write failed"); // transfer 10 - let amount = Amount::whole(90); + let amount = Amount::native_whole(90); wl_storage .write_log .write(&sender_key, amount.try_to_vec().unwrap()) @@ -214,7 +265,7 @@ mod tests { let receiver = established_address_2(); let receiver_key = balance_key(&nam(), &receiver); // receive more than 10 - let amount = Amount::whole(100); + let amount = Amount::native_whole(100); wl_storage .write_log .write(&receiver_key, amount.try_to_vec().unwrap()) @@ -250,17 +301,20 @@ mod tests { let mut wl_storage = TestWlStorage::default(); let mut keys_changed = BTreeSet::new(); + // ERC20 token + let token = wrapped_erc20s::token(&arbitrary_eth_address()); + // mint 100 let target = established_address_1(); - let target_key = balance_key(&nam(), &target); - let amount = Amount::whole(100); + let target_key = balance_key(&token, &target); + let amount = Amount::native_whole(100); wl_storage .write_log .write(&target_key, amount.try_to_vec().unwrap()) .expect("write failed"); keys_changed.insert(target_key); - let minted_key = minted_balance_key(&nam()); - let amount = Amount::whole(100); + let minted_key = minted_balance_key(&token); + let amount = Amount::native_whole(100); wl_storage .write_log .write(&minted_key, amount.try_to_vec().unwrap()) @@ -268,8 +322,8 @@ mod tests { keys_changed.insert(minted_key); // minter - let minter = Address::Internal(InternalAddress::Ibc); - let minter_key = minter_key(&nam()); + let minter = Address::Internal(InternalAddress::EthBridge); + let minter_key = minter_key(&token); wl_storage .write_log .write(&minter_key, minter.try_to_vec().unwrap()) @@ -307,18 +361,25 @@ mod tests { let mut wl_storage = TestWlStorage::default(); let mut keys_changed = BTreeSet::new(); + // set the dummy nam vp + let vp_key = Key::validity_predicate(&nam()); + wl_storage + .storage + .write(&vp_key, vec![]) + .expect("write failed"); + // mint 100 let target = established_address_1(); let target_key = balance_key(&nam(), &target); // mint more than 100 - let amount = Amount::whole(1000); + let amount = Amount::native_whole(1000); wl_storage .write_log .write(&target_key, amount.try_to_vec().unwrap()) .expect("write failed"); keys_changed.insert(target_key); let minted_key = minted_balance_key(&nam()); - let amount = Amount::whole(100); + let amount = Amount::native_whole(100); wl_storage .write_log .write(&minted_key, amount.try_to_vec().unwrap()) @@ -326,7 +387,7 @@ mod tests { keys_changed.insert(minted_key); // minter - let minter = Address::Internal(InternalAddress::Ibc); + let minter = nam(); let minter_key = minter_key(&nam()); wl_storage .write_log @@ -368,14 +429,14 @@ mod tests { // mint 100 let target = established_address_1(); let target_key = balance_key(&nam(), &target); - let amount = Amount::whole(100); + let amount = Amount::native_whole(100); wl_storage .write_log .write(&target_key, amount.try_to_vec().unwrap()) .expect("write failed"); keys_changed.insert(target_key); let minted_key = minted_balance_key(&nam()); - let amount = Amount::whole(100); + let amount = Amount::native_whole(100); wl_storage .write_log .write(&minted_key, amount.try_to_vec().unwrap()) @@ -416,14 +477,14 @@ mod tests { // mint 100 let target = established_address_1(); let target_key = balance_key(&nam(), &target); - let amount = Amount::whole(100); + let amount = Amount::native_whole(100); wl_storage .write_log .write(&target_key, amount.try_to_vec().unwrap()) .expect("write failed"); keys_changed.insert(target_key); let minted_key = minted_balance_key(&nam()); - let amount = Amount::whole(100); + let amount = Amount::native_whole(100); wl_storage .write_log .write(&minted_key, amount.try_to_vec().unwrap()) @@ -463,4 +524,104 @@ mod tests { .expect("validation failed") ); } + + #[test] + fn test_invalid_minter() { + let mut wl_storage = TestWlStorage::default(); + let mut keys_changed = BTreeSet::new(); + + // ERC20 token + let token = wrapped_erc20s::token(&arbitrary_eth_address()); + + // mint 100 + let target = established_address_1(); + let target_key = balance_key(&token, &target); + let amount = Amount::native_whole(100); + wl_storage + .write_log + .write(&target_key, amount.try_to_vec().unwrap()) + .expect("write failed"); + keys_changed.insert(target_key); + let minted_key = minted_balance_key(&token); + let amount = Amount::native_whole(100); + wl_storage + .write_log + .write(&minted_key, amount.try_to_vec().unwrap()) + .expect("write failed"); + keys_changed.insert(minted_key); + + // invalid minter + let minter = established_address_1(); + let minter_key = minter_key(&token); + wl_storage + .write_log + .write(&minter_key, minter.try_to_vec().unwrap()) + .expect("write failed"); + keys_changed.insert(minter_key); + + let tx_index = TxIndex::default(); + let tx = dummy_tx(&wl_storage); + let gas_meter = VpGasMeter::new(0); + let (vp_wasm_cache, _vp_cache_dir) = wasm_cache(); + let mut verifiers = BTreeSet::new(); + // for the minter + verifiers.insert(minter); + let ctx = Ctx::new( + &ADDRESS, + &wl_storage.storage, + &wl_storage.write_log, + &tx, + &tx_index, + gas_meter, + &keys_changed, + &verifiers, + vp_wasm_cache, + ); + + let vp = MultitokenVp { ctx }; + assert!( + !vp.validate_tx(&tx, &keys_changed, &verifiers) + .expect("validation failed") + ); + } + + #[test] + fn test_invalid_minter_update() { + let mut wl_storage = TestWlStorage::default(); + let mut keys_changed = BTreeSet::new(); + + let minter_key = minter_key(&nam()); + let minter = established_address_1(); + wl_storage + .write_log + .write(&minter_key, minter.try_to_vec().unwrap()) + .expect("write failed"); + + keys_changed.insert(minter_key); + + let tx_index = TxIndex::default(); + let tx = dummy_tx(&wl_storage); + let gas_meter = VpGasMeter::new(0); + let (vp_wasm_cache, _vp_cache_dir) = wasm_cache(); + let mut verifiers = BTreeSet::new(); + // for the minter + verifiers.insert(minter); + let ctx = Ctx::new( + &ADDRESS, + &wl_storage.storage, + &wl_storage.write_log, + &tx, + &tx_index, + gas_meter, + &keys_changed, + &verifiers, + vp_wasm_cache, + ); + + let vp = MultitokenVp { ctx }; + assert!( + !vp.validate_tx(&tx, &keys_changed, &verifiers) + .expect("validation failed") + ); + } } diff --git a/tests/src/e2e/helpers.rs b/tests/src/e2e/helpers.rs index d1cb9b9f95..8d9d23f626 100644 --- a/tests/src/e2e/helpers.rs +++ b/tests/src/e2e/helpers.rs @@ -72,6 +72,7 @@ pub fn run_single_node_test_from(test: Test) -> Result<(Test, NamadaBgCmd)> { } /// Initialize an established account. +#[allow(dead_code)] pub fn init_established_account( test: &Test, rpc_addr: &str, diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 9797dadf58..3889a1ae2c 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -1248,7 +1248,7 @@ fn masp_incentives() -> Result<()> { ], Some(60) )?; - let amt = (amt20 * masp_rewards[&(btc(), None)]).0 * (ep1.0 - ep0.0); + let amt = (amt20 * masp_rewards[&btc()]).0 * (ep1.0 - ep0.0); let denominated = DenominatedAmount { amount: amt, denom: NATIVE_MAX_DECIMAL_PLACES.into(), @@ -1271,7 +1271,7 @@ fn masp_incentives() -> Result<()> { ], Some(60) )?; - let amt = (amt20 * masp_rewards[&(btc(), None)]).0 * (ep1.0 - ep0.0); + let amt = (amt20 * masp_rewards[&btc()]).0 * (ep1.0 - ep0.0); let denominated = DenominatedAmount { amount: amt, denom: NATIVE_MAX_DECIMAL_PLACES.into(), @@ -1315,7 +1315,7 @@ fn masp_incentives() -> Result<()> { ], Some(60) )?; - let amt = (amt20 * masp_rewards[&(btc(), None)]).0 * (ep2.0 - ep0.0); + let amt = (amt20 * masp_rewards[&btc()]).0 * (ep2.0 - ep0.0); let denominated = DenominatedAmount { amount: amt, denom: NATIVE_MAX_DECIMAL_PLACES.into(), @@ -1338,7 +1338,7 @@ fn masp_incentives() -> Result<()> { ], Some(60) )?; - let amt = (amt20 * masp_rewards[&(btc(), None)]).0 * (ep2.0 - ep0.0); + let amt = (amt20 * masp_rewards[&btc()]).0 * (ep2.0 - ep0.0); let denominated = DenominatedAmount { amount: amt, denom: NATIVE_MAX_DECIMAL_PLACES.into(), @@ -1445,7 +1445,7 @@ fn masp_incentives() -> Result<()> { ], Some(60) )?; - let amt = (amt10 * masp_rewards[&(eth(), None)]).0 * (ep4.0 - ep3.0); + let amt = (amt10 * masp_rewards[ð()]).0 * (ep4.0 - ep3.0); let denominated = DenominatedAmount { amount: amt, denom: NATIVE_MAX_DECIMAL_PLACES.into(), @@ -1469,8 +1469,8 @@ fn masp_incentives() -> Result<()> { ], Some(60) )?; - let amt = ((amt20 * masp_rewards[&(btc(), None)]).0 * (ep4.0 - ep0.0)) - + ((amt10 * masp_rewards[&(eth(), None)]).0 * (ep4.0 - ep3.0)); + let amt = ((amt20 * masp_rewards[&btc()]).0 * (ep4.0 - ep0.0)) + + ((amt10 * masp_rewards[ð()]).0 * (ep4.0 - ep3.0)); let denominated = DenominatedAmount { amount: amt, denom: NATIVE_MAX_DECIMAL_PLACES.into(), @@ -1542,7 +1542,7 @@ fn masp_incentives() -> Result<()> { ], Some(60) )?; - let amt = (amt10 * masp_rewards[&(eth(), None)]).0 * (ep5.0 - ep3.0); + let amt = (amt10 * masp_rewards[ð()]).0 * (ep5.0 - ep3.0); let denominated = DenominatedAmount { amount: amt, denom: NATIVE_MAX_DECIMAL_PLACES.into(), @@ -1567,7 +1567,7 @@ fn masp_incentives() -> Result<()> { ], Some(60) )?; - let amt = ((amt20 * masp_rewards[&(btc(), None)]).0 * (ep5.0 - ep0.0)) + let amt = ((amt20 * masp_rewards[&btc()]).0 * (ep5.0 - ep0.0)) + ((amt10 * masp_rewards[&(eth(), None)]).0 * (ep5.0 - ep3.0)); let denominated = DenominatedAmount { amount: amt, @@ -1638,7 +1638,7 @@ fn masp_incentives() -> Result<()> { ], Some(60) )?; - let amt = (amt20 * masp_rewards[&(btc(), None)]).0 * (ep6.0 - ep0.0); + let amt = (amt20 * masp_rewards[&btc()]).0 * (ep6.0 - ep0.0); let denominated = DenominatedAmount { amount: amt, denom: NATIVE_MAX_DECIMAL_PLACES.into(), @@ -1662,8 +1662,8 @@ fn masp_incentives() -> Result<()> { ], Some(60) )?; - let amt = ((amt20 * masp_rewards[&(btc(), None)]).0 * (ep6.0 - ep0.0)) - + ((amt10 * masp_rewards[&(eth(), None)]).0 * (ep5.0 - ep3.0)); + let amt = ((amt20 * masp_rewards[&btc()]).0 * (ep6.0 - ep0.0)) + + ((amt10 * masp_rewards[ð()]).0 * (ep5.0 - ep3.0)); let denominated = DenominatedAmount { amount: amt, denom: NATIVE_MAX_DECIMAL_PLACES.into(), @@ -1689,7 +1689,7 @@ fn masp_incentives() -> Result<()> { ], Some(60) )?; - let amt = (amt20 * masp_rewards[&(btc(), None)]).0 * (ep6.0 - ep0.0); + let amt = (amt20 * masp_rewards[&btc()]).0 * (ep6.0 - ep0.0); let denominated = DenominatedAmount { amount: amt, denom: NATIVE_MAX_DECIMAL_PLACES.into(), @@ -1712,7 +1712,7 @@ fn masp_incentives() -> Result<()> { ], Some(60) )?; - let amt = (amt10 * masp_rewards[&(eth(), None)]).0 * (ep5.0 - ep3.0); + let amt = (amt10 * masp_rewards[ð()]).0 * (ep5.0 - ep3.0); let denominated = DenominatedAmount { amount: amt, denom: NATIVE_MAX_DECIMAL_PLACES.into(), @@ -1736,8 +1736,8 @@ fn masp_incentives() -> Result<()> { ], Some(60) )?; - let amt = ((amt20 * masp_rewards[&(btc(), None)]).0 * (ep6.0 - ep0.0)) - + ((amt10 * masp_rewards[&(eth(), None)]).0 * (ep5.0 - ep3.0)); + let amt = ((amt20 * masp_rewards[&btc()]).0 * (ep6.0 - ep0.0)) + + ((amt10 * masp_rewards[ð()]).0 * (ep5.0 - ep3.0)); let denominated = DenominatedAmount { amount: amt, denom: NATIVE_MAX_DECIMAL_PLACES.into(), @@ -1762,7 +1762,7 @@ fn masp_incentives() -> Result<()> { "--token", NAM, "--amount", - &((amt10 * masp_rewards[&(eth(), None)]).0 * (ep5.0 - ep3.0)) + &((amt10 * masp_rewards[ð()]).0 * (ep5.0 - ep3.0)) .to_string_native(), "--signer", BERTHA, @@ -1792,7 +1792,7 @@ fn masp_incentives() -> Result<()> { "--token", NAM, "--amount", - &((amt20 * masp_rewards[&(btc(), None)]).0 * (ep6.0 - ep0.0)) + &((amt20 * masp_rewards[&btc()]).0 * (ep6.0 - ep0.0)) .to_string_native(), "--signer", ALBERT, From 13f66dc2f43fe1ad0f5cc75ebf3de0c393c0c572 Mon Sep 17 00:00:00 2001 From: yito88 Date: Thu, 6 Jul 2023 17:19:47 +0200 Subject: [PATCH 15/25] for clippy --- apps/src/lib/client/rpc.rs | 10 +++++----- shared/src/ledger/native_vp/multitoken.rs | 19 ++++++++----------- shared/src/ledger/signing.rs | 2 +- wasm/wasm_source/src/vp_implicit.rs | 1 - wasm/wasm_source/src/vp_user.rs | 3 --- 5 files changed, 14 insertions(+), 21 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 62242d4584..f3b3ab5e44 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -192,7 +192,7 @@ pub async fn query_transfers< for (account, MaspChange { ref asset, change }) in tfer_delta { if account != masp() { print!(" {}:", account); - let token_alias = lookup_alias(wallet, &asset); + let token_alias = lookup_alias(wallet, asset); let sign = match change.cmp(&Change::zero()) { Ordering::Greater => "+", Ordering::Less => "-", @@ -443,7 +443,7 @@ pub async fn query_pinned_balance< } else { let formatted = format_denominated_amount( client, - &token, + token, total_balance.into(), ) .await; @@ -476,7 +476,7 @@ pub async fn query_pinned_balance< ) .await; let token_alias = tokens - .get(&token_addr) + .get(token_addr) .map(|a| a.to_string()) .unwrap_or_else(|| token_addr.to_string()); println!(" {}: {}", token_alias, formatted,); @@ -920,7 +920,7 @@ pub async fn print_decoded_balance< { println!( "{} : {}", - lookup_alias(wallet, &token_addr), + lookup_alias(wallet, token_addr), format_denominated_amount(client, token_addr, (*amount).into()) .await, ); @@ -942,7 +942,7 @@ pub async fn print_decoded_balance_with_epoch< for ((epoch, token_addr), value) in decoded_balance.iter() { let asset_value = (*value).into(); let alias = tokens - .get(&token_addr) + .get(token_addr) .map(|a| a.to_string()) .unwrap_or_else(|| token_addr.to_string()); println!( diff --git a/shared/src/ledger/native_vp/multitoken.rs b/shared/src/ledger/native_vp/multitoken.rs index ffb8731dbb..9e407e8602 100644 --- a/shared/src/ledger/native_vp/multitoken.rs +++ b/shared/src/ledger/native_vp/multitoken.rs @@ -83,17 +83,14 @@ where Some(_) => {} None => return Ok(false), } - } else { - if key.segments.get(0) - == Some( - &Address::Internal(InternalAddress::Multitoken) - .to_db_key(), - ) - { - // Reject when trying to update an unexpected key under - // `#Multitoken/...` - return Ok(false); - } + } else if key.segments.get(0) + == Some( + &Address::Internal(InternalAddress::Multitoken).to_db_key(), + ) + { + // Reject when trying to update an unexpected key under + // `#Multitoken/...` + return Ok(false); } } diff --git a/shared/src/ledger/signing.rs b/shared/src/ledger/signing.rs index 2f0fd0d56d..8a8e35a11c 100644 --- a/shared/src/ledger/signing.rs +++ b/shared/src/ledger/signing.rs @@ -555,7 +555,7 @@ async fn make_ledger_amount_asset( if let Some((token, _, _epoch)) = assets.get(token) { // If the AssetType can be decoded, then at least display Addressees let formatted_amt = - format_denominated_amount(client, &token, amount.into()).await; + format_denominated_amount(client, token, amount.into()).await; if let Some(token) = tokens.get(token) { output .push( diff --git a/wasm/wasm_source/src/vp_implicit.rs b/wasm/wasm_source/src/vp_implicit.rs index 1e4ff59852..f82c573bc1 100644 --- a/wasm/wasm_source/src/vp_implicit.rs +++ b/wasm/wasm_source/src/vp_implicit.rs @@ -344,7 +344,6 @@ mod tests { storage_api::token::write_denom( &mut tx_env.wl_storage, &token, - None, token::NATIVE_MAX_DECIMAL_PLACES.into(), ) .unwrap(); diff --git a/wasm/wasm_source/src/vp_user.rs b/wasm/wasm_source/src/vp_user.rs index e3fc1e1098..6d6977997e 100644 --- a/wasm/wasm_source/src/vp_user.rs +++ b/wasm/wasm_source/src/vp_user.rs @@ -294,7 +294,6 @@ mod tests { storage_api::token::write_denom( &mut tx_env.wl_storage, &token, - None, token::NATIVE_MAX_DECIMAL_PLACES.into(), ) .unwrap(); @@ -450,7 +449,6 @@ mod tests { storage_api::token::write_denom( &mut tx_env.wl_storage, &token, - None, token::NATIVE_MAX_DECIMAL_PLACES.into(), ) .unwrap(); @@ -531,7 +529,6 @@ mod tests { storage_api::token::write_denom( &mut tx_env.wl_storage, &token, - None, token::NATIVE_MAX_DECIMAL_PLACES.into(), ) .unwrap(); From 74eee2dc18ea41cfd28ad441da4db0188cca1caf Mon Sep 17 00:00:00 2001 From: yito88 Date: Mon, 10 Jul 2023 11:06:39 +0200 Subject: [PATCH 16/25] fix for tx signing --- apps/src/lib/client/rpc.rs | 7 ++++++- shared/src/ledger/native_vp/multitoken.rs | 6 +----- shared/src/ledger/signing.rs | 6 +----- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index f3b3ab5e44..eb03de78d9 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -506,6 +506,8 @@ async fn print_balances( let mut print_num = 0; let mut print_token = None; for (key, balance) in balances { + // Get the token, the owner, and the balance with the token and the + // owner let (t, o, s) = match token::is_any_token_balance_key(&key) { Some([tok, owner]) => ( tok.clone(), @@ -519,6 +521,7 @@ async fn print_balances( None => continue, }; + // Get the token and the balance let (t, s) = match (token, target) { (Some(token), Some(target)) if t == *token && o == *target => { (t, s) @@ -528,9 +531,10 @@ async fn print_balances( (None, None) => (t, s), _ => continue, }; + // Print the token if it isn't printed yet match &print_token { Some(token) if *token == t => { - // the token was already printed + // the token has been already printed } _ => { let token_alias = lookup_alias(wallet, &t); @@ -538,6 +542,7 @@ async fn print_balances( print_token = Some(t); } } + // Print the balance writeln!(w, "{}", s).unwrap(); print_num += 1; } diff --git a/shared/src/ledger/native_vp/multitoken.rs b/shared/src/ledger/native_vp/multitoken.rs index 9e407e8602..3e2a0b84df 100644 --- a/shared/src/ledger/native_vp/multitoken.rs +++ b/shared/src/ledger/native_vp/multitoken.rs @@ -176,11 +176,7 @@ mod tests { tx.set_code(Code::new(tx_code)); tx.set_data(Data::new(tx_data)); tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), - &keypair_1(), - ))); - tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + tx.sechashes(), &keypair_1(), ))); tx diff --git a/shared/src/ledger/signing.rs b/shared/src/ledger/signing.rs index 8a8e35a11c..4fdd426172 100644 --- a/shared/src/ledger/signing.rs +++ b/shared/src/ledger/signing.rs @@ -18,7 +18,6 @@ use masp_primitives::transaction::components::sapling::fees::{ use namada_core::types::address::{ masp, masp_tx_key, Address, ImplicitAddress, }; -use namada_core::types::storage::Key; use namada_core::types::token::{self, Amount, DenominatedAmount, MaspDenom}; use namada_core::types::transaction::{pos, MIN_FEE}; use prost::Message; @@ -647,7 +646,7 @@ pub async fn make_ledger_masp_endpoints< output: &mut Vec, transfer: &Transfer, builder: Option<&MaspBuilder>, - assets: &HashMap, MaspDenom, Epoch)>, + assets: &HashMap, ) { if transfer.source != masp() { output.push(format!("Sender : {}", transfer.source)); @@ -657,7 +656,6 @@ pub async fn make_ledger_masp_endpoints< output, transfer.amount, &transfer.token, - &transfer.sub_prefix, "Sending ", ); } @@ -685,7 +683,6 @@ pub async fn make_ledger_masp_endpoints< output, transfer.amount, &transfer.token, - &transfer.sub_prefix, "Receiving ", ); } @@ -711,7 +708,6 @@ pub async fn make_ledger_masp_endpoints< output, transfer.amount, &transfer.token, - &transfer.sub_prefix, "", ); } From 713dee1de6d9c9412d5dade6012853a7d6b702b3 Mon Sep 17 00:00:00 2001 From: yito88 Date: Mon, 10 Jul 2023 11:17:14 +0200 Subject: [PATCH 17/25] fix a test --- tests/src/e2e/ledger_tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 3889a1ae2c..3038371cba 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -1568,7 +1568,7 @@ fn masp_incentives() -> Result<()> { Some(60) )?; let amt = ((amt20 * masp_rewards[&btc()]).0 * (ep5.0 - ep0.0)) - + ((amt10 * masp_rewards[&(eth(), None)]).0 * (ep5.0 - ep3.0)); + + ((amt10 * masp_rewards[ð()]).0 * (ep5.0 - ep3.0)); let denominated = DenominatedAmount { amount: amt, denom: NATIVE_MAX_DECIMAL_PLACES.into(), From 2a908a00a4dd5cd781fca6212c98903b4e19852c Mon Sep 17 00:00:00 2001 From: yito88 Date: Mon, 10 Jul 2023 21:42:51 +0200 Subject: [PATCH 18/25] fix according to feedback --- core/src/ledger/ibc/actions.rs | 1605 ----------------- .../transactions/ethereum_events/events.rs | 13 +- .../transactions/ethereum_events/mod.rs | 6 +- shared/src/ledger/native_vp/multitoken.rs | 12 +- shared/src/ledger/queries/shell/eth_bridge.rs | 37 +- wasm/wasm_source/src/tx_bridge_pool.rs | 3 +- 6 files changed, 33 insertions(+), 1643 deletions(-) delete mode 100644 core/src/ledger/ibc/actions.rs diff --git a/core/src/ledger/ibc/actions.rs b/core/src/ledger/ibc/actions.rs deleted file mode 100644 index e925a98d92..0000000000 --- a/core/src/ledger/ibc/actions.rs +++ /dev/null @@ -1,1605 +0,0 @@ -//! Functions to handle IBC modules - -use std::str::FromStr; - -use sha2::Digest; -use thiserror::Error; - -use crate::ibc::applications::ics20_fungible_token_transfer::msgs::transfer::MsgTransfer; -use crate::ibc::clients::ics07_tendermint::consensus_state::ConsensusState as TmConsensusState; -use crate::ibc::core::ics02_client::client_consensus::{ - AnyConsensusState, ConsensusState, -}; -use crate::ibc::core::ics02_client::client_state::{ - AnyClientState, ClientState, -}; -use crate::ibc::core::ics02_client::client_type::ClientType; -use crate::ibc::core::ics02_client::events::{ - Attributes as ClientAttributes, CreateClient, UpdateClient, UpgradeClient, -}; -use crate::ibc::core::ics02_client::header::{AnyHeader, Header}; -use crate::ibc::core::ics02_client::height::Height; -use crate::ibc::core::ics02_client::msgs::create_client::MsgCreateAnyClient; -use crate::ibc::core::ics02_client::msgs::update_client::MsgUpdateAnyClient; -use crate::ibc::core::ics02_client::msgs::upgrade_client::MsgUpgradeAnyClient; -use crate::ibc::core::ics02_client::msgs::ClientMsg; -use crate::ibc::core::ics03_connection::connection::{ - ConnectionEnd, Counterparty as ConnCounterparty, State as ConnState, -}; -use crate::ibc::core::ics03_connection::events::{ - Attributes as ConnectionAttributes, OpenAck as ConnOpenAck, - OpenConfirm as ConnOpenConfirm, OpenInit as ConnOpenInit, - OpenTry as ConnOpenTry, -}; -use crate::ibc::core::ics03_connection::msgs::conn_open_ack::MsgConnectionOpenAck; -use crate::ibc::core::ics03_connection::msgs::conn_open_confirm::MsgConnectionOpenConfirm; -use crate::ibc::core::ics03_connection::msgs::conn_open_init::MsgConnectionOpenInit; -use crate::ibc::core::ics03_connection::msgs::conn_open_try::MsgConnectionOpenTry; -use crate::ibc::core::ics03_connection::msgs::ConnectionMsg; -use crate::ibc::core::ics04_channel::channel::{ - ChannelEnd, Counterparty as ChanCounterparty, Order, State as ChanState, -}; -use crate::ibc::core::ics04_channel::commitment::PacketCommitment; -use crate::ibc::core::ics04_channel::events::{ - AcknowledgePacket, CloseConfirm as ChanCloseConfirm, - CloseInit as ChanCloseInit, OpenAck as ChanOpenAck, - OpenConfirm as ChanOpenConfirm, OpenInit as ChanOpenInit, - OpenTry as ChanOpenTry, SendPacket, TimeoutPacket, WriteAcknowledgement, -}; -use crate::ibc::core::ics04_channel::msgs::acknowledgement::MsgAcknowledgement; -use crate::ibc::core::ics04_channel::msgs::chan_close_confirm::MsgChannelCloseConfirm; -use crate::ibc::core::ics04_channel::msgs::chan_close_init::MsgChannelCloseInit; -use crate::ibc::core::ics04_channel::msgs::chan_open_ack::MsgChannelOpenAck; -use crate::ibc::core::ics04_channel::msgs::chan_open_confirm::MsgChannelOpenConfirm; -use crate::ibc::core::ics04_channel::msgs::chan_open_init::MsgChannelOpenInit; -use crate::ibc::core::ics04_channel::msgs::chan_open_try::MsgChannelOpenTry; -use crate::ibc::core::ics04_channel::msgs::recv_packet::MsgRecvPacket; -use crate::ibc::core::ics04_channel::msgs::timeout::MsgTimeout; -use crate::ibc::core::ics04_channel::msgs::timeout_on_close::MsgTimeoutOnClose; -use crate::ibc::core::ics04_channel::msgs::{ChannelMsg, PacketMsg}; -use crate::ibc::core::ics04_channel::packet::{Packet, Sequence}; -use crate::ibc::core::ics23_commitment::commitment::CommitmentPrefix; -use crate::ibc::core::ics24_host::error::ValidationError as Ics24Error; -use crate::ibc::core::ics24_host::identifier::{ - ChannelId, ClientId, ConnectionId, PortChannelId, PortId, -}; -use crate::ibc::core::ics26_routing::msgs::Ics26Envelope; -use crate::ibc::events::IbcEvent; -#[cfg(any(feature = "ibc-mocks-abcipp", feature = "ibc-mocks"))] -use crate::ibc::mock::client_state::{MockClientState, MockConsensusState}; -use crate::ibc::timestamp::Timestamp; -use crate::ledger::ibc::data::{ - Error as IbcDataError, FungibleTokenPacketData, IbcMessage, PacketAck, - PacketReceipt, -}; -use crate::ledger::ibc::storage; -use crate::ledger::storage_api; -use crate::tendermint::Time; -use crate::tendermint_proto::{Error as ProtoError, Protobuf}; -use crate::types::address::{Address, InternalAddress}; -use crate::types::ibc::IbcEvent as NamadaIbcEvent; -use crate::types::storage::{BlockHeight, Key}; -use crate::types::time::Rfc3339String; -use crate::types::token::{self, Amount}; - -const COMMITMENT_PREFIX: &[u8] = b"ibc"; - -#[allow(missing_docs)] -#[derive(Error, Debug)] -pub enum Error { - #[error("Invalid client error: {0}")] - ClientId(Ics24Error), - #[error("Invalid port error: {0}")] - PortId(Ics24Error), - #[error("Updating a client error: {0}")] - ClientUpdate(String), - #[error("IBC data error: {0}")] - IbcData(IbcDataError), - #[error("Decoding IBC data error: {0}")] - Decoding(ProtoError), - #[error("Client error: {0}")] - Client(String), - #[error("Connection error: {0}")] - Connection(String), - #[error("Channel error: {0}")] - Channel(String), - #[error("Counter error: {0}")] - Counter(String), - #[error("Sequence error: {0}")] - Sequence(String), - #[error("Time error: {0}")] - Time(String), - #[error("Invalid transfer message: {0}")] - TransferMessage(token::TransferError), - #[error("Sending a token error: {0}")] - SendingToken(String), - #[error("Receiving a token error: {0}")] - ReceivingToken(String), - #[error("IBC storage error: {0}")] - IbcStorage(storage::Error), -} - -// This is needed to use `ibc::Handler::Error` with `IbcActions` in -// `tx_prelude/src/ibc.rs` -impl From for storage_api::Error { - fn from(err: Error) -> Self { - storage_api::Error::new(err) - } -} - -/// for handling IBC modules -pub type Result = std::result::Result; - -/// IBC trait to be implemented in integration that can read and write -pub trait IbcActions { - /// IBC action error - type Error: From; - - /// Read IBC-related data - fn read_ibc_data( - &self, - key: &Key, - ) -> std::result::Result>, Self::Error>; - - /// Write IBC-related data - fn write_ibc_data( - &mut self, - key: &Key, - data: impl AsRef<[u8]>, - ) -> std::result::Result<(), Self::Error>; - - /// Delete IBC-related data - fn delete_ibc_data( - &mut self, - key: &Key, - ) -> std::result::Result<(), Self::Error>; - - /// Emit an IBC event - fn emit_ibc_event( - &mut self, - event: NamadaIbcEvent, - ) -> std::result::Result<(), Self::Error>; - - /// Transfer token - fn transfer_token( - &mut self, - src: &Key, - dest: &Key, - amount: Amount, - ) -> std::result::Result<(), Self::Error>; - - /// Get the current height of this chain - fn get_height(&self) -> std::result::Result; - - /// Get the current time of the tendermint header of this chain - fn get_header_time( - &self, - ) -> std::result::Result; - - /// dispatch according to ICS26 routing - fn dispatch_ibc_action( - &mut self, - tx_data: &[u8], - ) -> std::result::Result<(), Self::Error> { - let ibc_msg = IbcMessage::decode(tx_data).map_err(Error::IbcData)?; - match &ibc_msg.0 { - Ics26Envelope::Ics2Msg(ics02_msg) => match ics02_msg { - ClientMsg::CreateClient(msg) => self.create_client(msg), - ClientMsg::UpdateClient(msg) => self.update_client(msg), - ClientMsg::Misbehaviour(_msg) => todo!(), - ClientMsg::UpgradeClient(msg) => self.upgrade_client(msg), - }, - Ics26Envelope::Ics3Msg(ics03_msg) => match ics03_msg { - ConnectionMsg::ConnectionOpenInit(msg) => { - self.init_connection(msg) - } - ConnectionMsg::ConnectionOpenTry(msg) => { - self.try_connection(msg) - } - ConnectionMsg::ConnectionOpenAck(msg) => { - self.ack_connection(msg) - } - ConnectionMsg::ConnectionOpenConfirm(msg) => { - self.confirm_connection(msg) - } - }, - Ics26Envelope::Ics4ChannelMsg(ics04_msg) => match ics04_msg { - ChannelMsg::ChannelOpenInit(msg) => self.init_channel(msg), - ChannelMsg::ChannelOpenTry(msg) => self.try_channel(msg), - ChannelMsg::ChannelOpenAck(msg) => self.ack_channel(msg), - ChannelMsg::ChannelOpenConfirm(msg) => { - self.confirm_channel(msg) - } - ChannelMsg::ChannelCloseInit(msg) => { - self.close_init_channel(msg) - } - ChannelMsg::ChannelCloseConfirm(msg) => { - self.close_confirm_channel(msg) - } - }, - Ics26Envelope::Ics4PacketMsg(ics04_msg) => match ics04_msg { - PacketMsg::AckPacket(msg) => self.acknowledge_packet(msg), - PacketMsg::RecvPacket(msg) => self.receive_packet(msg), - PacketMsg::ToPacket(msg) => self.timeout_packet(msg), - PacketMsg::ToClosePacket(msg) => { - self.timeout_on_close_packet(msg) - } - }, - Ics26Envelope::Ics20Msg(msg) => self.send_token(msg), - } - } - - /// Create a new client - fn create_client( - &mut self, - msg: &MsgCreateAnyClient, - ) -> std::result::Result<(), Self::Error> { - let counter_key = storage::client_counter_key(); - let counter = self.get_and_inc_counter(&counter_key)?; - let client_type = msg.client_state.client_type(); - let client_id = client_id(client_type, counter)?; - // client type - let client_type_key = storage::client_type_key(&client_id); - self.write_ibc_data(&client_type_key, client_type.as_str().as_bytes())?; - // client state - let client_state_key = storage::client_state_key(&client_id); - self.write_ibc_data( - &client_state_key, - msg.client_state - .encode_vec() - .expect("encoding shouldn't fail"), - )?; - // consensus state - let height = msg.client_state.latest_height(); - let consensus_state_key = - storage::consensus_state_key(&client_id, height); - self.write_ibc_data( - &consensus_state_key, - msg.consensus_state - .encode_vec() - .expect("encoding shouldn't fail"), - )?; - - self.set_client_update_time(&client_id)?; - - let event = make_create_client_event(&client_id, msg) - .try_into() - .unwrap(); - self.emit_ibc_event(event)?; - - Ok(()) - } - - /// Update a client - fn update_client( - &mut self, - msg: &MsgUpdateAnyClient, - ) -> std::result::Result<(), Self::Error> { - // get and update the client - let client_id = msg.client_id.clone(); - let client_state_key = storage::client_state_key(&client_id); - let value = - self.read_ibc_data(&client_state_key)?.ok_or_else(|| { - Error::Client(format!( - "The client to be updated doesn't exist: ID {}", - client_id - )) - })?; - let client_state = - AnyClientState::decode_vec(&value).map_err(Error::Decoding)?; - let (new_client_state, new_consensus_state) = - update_client(client_state, msg.header.clone())?; - - let height = new_client_state.latest_height(); - self.write_ibc_data( - &client_state_key, - new_client_state - .encode_vec() - .expect("encoding shouldn't fail"), - )?; - let consensus_state_key = - storage::consensus_state_key(&client_id, height); - self.write_ibc_data( - &consensus_state_key, - new_consensus_state - .encode_vec() - .expect("encoding shouldn't fail"), - )?; - - self.set_client_update_time(&client_id)?; - - let event = make_update_client_event(&client_id, msg) - .try_into() - .unwrap(); - self.emit_ibc_event(event)?; - - Ok(()) - } - - /// Upgrade a client - fn upgrade_client( - &mut self, - msg: &MsgUpgradeAnyClient, - ) -> std::result::Result<(), Self::Error> { - let client_state_key = storage::client_state_key(&msg.client_id); - let height = msg.client_state.latest_height(); - let consensus_state_key = - storage::consensus_state_key(&msg.client_id, height); - self.write_ibc_data( - &client_state_key, - msg.client_state - .encode_vec() - .expect("encoding shouldn't fail"), - )?; - self.write_ibc_data( - &consensus_state_key, - msg.consensus_state - .encode_vec() - .expect("encoding shouldn't fail"), - )?; - - self.set_client_update_time(&msg.client_id)?; - - let event = make_upgrade_client_event(&msg.client_id, msg) - .try_into() - .unwrap(); - self.emit_ibc_event(event)?; - - Ok(()) - } - - /// Initialize a connection for ConnectionOpenInit - fn init_connection( - &mut self, - msg: &MsgConnectionOpenInit, - ) -> std::result::Result<(), Self::Error> { - let counter_key = storage::connection_counter_key(); - let counter = self.get_and_inc_counter(&counter_key)?; - // new connection - let conn_id = connection_id(counter); - let conn_key = storage::connection_key(&conn_id); - let connection = init_connection(msg); - self.write_ibc_data( - &conn_key, - connection.encode_vec().expect("encoding shouldn't fail"), - )?; - - let event = make_open_init_connection_event(&conn_id, msg) - .try_into() - .unwrap(); - self.emit_ibc_event(event)?; - - Ok(()) - } - - /// Initialize a connection for ConnectionOpenTry - fn try_connection( - &mut self, - msg: &MsgConnectionOpenTry, - ) -> std::result::Result<(), Self::Error> { - let counter_key = storage::connection_counter_key(); - let counter = self.get_and_inc_counter(&counter_key)?; - // new connection - let conn_id = connection_id(counter); - let conn_key = storage::connection_key(&conn_id); - let connection = try_connection(msg); - self.write_ibc_data( - &conn_key, - connection.encode_vec().expect("encoding shouldn't fail"), - )?; - - let event = make_open_try_connection_event(&conn_id, msg) - .try_into() - .unwrap(); - self.emit_ibc_event(event)?; - - Ok(()) - } - - /// Open the connection for ConnectionOpenAck - fn ack_connection( - &mut self, - msg: &MsgConnectionOpenAck, - ) -> std::result::Result<(), Self::Error> { - let conn_key = storage::connection_key(&msg.connection_id); - let value = self.read_ibc_data(&conn_key)?.ok_or_else(|| { - Error::Connection(format!( - "The connection to be opened doesn't exist: ID {}", - msg.connection_id - )) - })?; - let mut connection = - ConnectionEnd::decode_vec(&value).map_err(Error::Decoding)?; - open_connection(&mut connection); - let mut counterparty = connection.counterparty().clone(); - counterparty.connection_id = - Some(msg.counterparty_connection_id.clone()); - connection.set_counterparty(counterparty); - self.write_ibc_data( - &conn_key, - connection.encode_vec().expect("encoding shouldn't fail"), - )?; - - let event = make_open_ack_connection_event(msg).try_into().unwrap(); - self.emit_ibc_event(event)?; - - Ok(()) - } - - /// Open the connection for ConnectionOpenConfirm - fn confirm_connection( - &mut self, - msg: &MsgConnectionOpenConfirm, - ) -> std::result::Result<(), Self::Error> { - let conn_key = storage::connection_key(&msg.connection_id); - let value = self.read_ibc_data(&conn_key)?.ok_or_else(|| { - Error::Connection(format!( - "The connection to be opend doesn't exist: ID {}", - msg.connection_id - )) - })?; - let mut connection = - ConnectionEnd::decode_vec(&value).map_err(Error::Decoding)?; - open_connection(&mut connection); - self.write_ibc_data( - &conn_key, - connection.encode_vec().expect("encoding shouldn't fail"), - )?; - - let event = make_open_confirm_connection_event(msg).try_into().unwrap(); - self.emit_ibc_event(event)?; - - Ok(()) - } - - /// Initialize a channel for ChannelOpenInit - fn init_channel( - &mut self, - msg: &MsgChannelOpenInit, - ) -> std::result::Result<(), Self::Error> { - self.bind_port(&msg.port_id)?; - let counter_key = storage::channel_counter_key(); - let counter = self.get_and_inc_counter(&counter_key)?; - let channel_id = channel_id(counter); - let port_channel_id = port_channel_id(msg.port_id.clone(), channel_id); - let channel_key = storage::channel_key(&port_channel_id); - self.write_ibc_data( - &channel_key, - msg.channel.encode_vec().expect("encoding shouldn't fail"), - )?; - - let event = make_open_init_channel_event(&channel_id, msg) - .try_into() - .unwrap(); - self.emit_ibc_event(event)?; - - Ok(()) - } - - /// Initialize a channel for ChannelOpenTry - fn try_channel( - &mut self, - msg: &MsgChannelOpenTry, - ) -> std::result::Result<(), Self::Error> { - self.bind_port(&msg.port_id)?; - let counter_key = storage::channel_counter_key(); - let counter = self.get_and_inc_counter(&counter_key)?; - let channel_id = channel_id(counter); - let port_channel_id = port_channel_id(msg.port_id.clone(), channel_id); - let channel_key = storage::channel_key(&port_channel_id); - self.write_ibc_data( - &channel_key, - msg.channel.encode_vec().expect("encoding shouldn't fail"), - )?; - - let event = make_open_try_channel_event(&channel_id, msg) - .try_into() - .unwrap(); - self.emit_ibc_event(event)?; - - Ok(()) - } - - /// Open the channel for ChannelOpenAck - fn ack_channel( - &mut self, - msg: &MsgChannelOpenAck, - ) -> std::result::Result<(), Self::Error> { - let port_channel_id = - port_channel_id(msg.port_id.clone(), msg.channel_id); - let channel_key = storage::channel_key(&port_channel_id); - let value = self.read_ibc_data(&channel_key)?.ok_or_else(|| { - Error::Channel(format!( - "The channel to be opened doesn't exist: Port/Channel {}", - port_channel_id - )) - })?; - let mut channel = - ChannelEnd::decode_vec(&value).map_err(Error::Decoding)?; - channel.set_counterparty_channel_id(msg.counterparty_channel_id); - open_channel(&mut channel); - self.write_ibc_data( - &channel_key, - channel.encode_vec().expect("encoding shouldn't fail"), - )?; - - let event = make_open_ack_channel_event(msg, &channel)? - .try_into() - .unwrap(); - self.emit_ibc_event(event)?; - - Ok(()) - } - - /// Open the channel for ChannelOpenConfirm - fn confirm_channel( - &mut self, - msg: &MsgChannelOpenConfirm, - ) -> std::result::Result<(), Self::Error> { - let port_channel_id = - port_channel_id(msg.port_id.clone(), msg.channel_id); - let channel_key = storage::channel_key(&port_channel_id); - let value = self.read_ibc_data(&channel_key)?.ok_or_else(|| { - Error::Channel(format!( - "The channel to be opened doesn't exist: Port/Channel {}", - port_channel_id - )) - })?; - let mut channel = - ChannelEnd::decode_vec(&value).map_err(Error::Decoding)?; - open_channel(&mut channel); - self.write_ibc_data( - &channel_key, - channel.encode_vec().expect("encoding shouldn't fail"), - )?; - - let event = make_open_confirm_channel_event(msg, &channel)? - .try_into() - .unwrap(); - self.emit_ibc_event(event)?; - - Ok(()) - } - - /// Close the channel for ChannelCloseInit - fn close_init_channel( - &mut self, - msg: &MsgChannelCloseInit, - ) -> std::result::Result<(), Self::Error> { - let port_channel_id = - port_channel_id(msg.port_id.clone(), msg.channel_id); - let channel_key = storage::channel_key(&port_channel_id); - let value = self.read_ibc_data(&channel_key)?.ok_or_else(|| { - Error::Channel(format!( - "The channel to be closed doesn't exist: Port/Channel {}", - port_channel_id - )) - })?; - let mut channel = - ChannelEnd::decode_vec(&value).map_err(Error::Decoding)?; - close_channel(&mut channel); - self.write_ibc_data( - &channel_key, - channel.encode_vec().expect("encoding shouldn't fail"), - )?; - - let event = make_close_init_channel_event(msg, &channel)? - .try_into() - .unwrap(); - self.emit_ibc_event(event)?; - - Ok(()) - } - - /// Close the channel for ChannelCloseConfirm - fn close_confirm_channel( - &mut self, - msg: &MsgChannelCloseConfirm, - ) -> std::result::Result<(), Self::Error> { - let port_channel_id = - port_channel_id(msg.port_id.clone(), msg.channel_id); - let channel_key = storage::channel_key(&port_channel_id); - let value = self.read_ibc_data(&channel_key)?.ok_or_else(|| { - Error::Channel(format!( - "The channel to be closed doesn't exist: Port/Channel {}", - port_channel_id - )) - })?; - let mut channel = - ChannelEnd::decode_vec(&value).map_err(Error::Decoding)?; - close_channel(&mut channel); - self.write_ibc_data( - &channel_key, - channel.encode_vec().expect("encoding shouldn't fail"), - )?; - - let event = make_close_confirm_channel_event(msg, &channel)? - .try_into() - .unwrap(); - self.emit_ibc_event(event)?; - - Ok(()) - } - - /// Send a packet - fn send_packet( - &mut self, - port_channel_id: PortChannelId, - data: Vec, - timeout_height: Height, - timeout_timestamp: Timestamp, - ) -> std::result::Result<(), Self::Error> { - // get and increment the next sequence send - let seq_key = storage::next_sequence_send_key(&port_channel_id); - let sequence = self.get_and_inc_sequence(&seq_key)?; - - // get the channel for the destination info. - let channel_key = storage::channel_key(&port_channel_id); - let channel = self - .read_ibc_data(&channel_key)? - .expect("cannot get the channel to be closed"); - let channel = - ChannelEnd::decode_vec(&channel).expect("cannot get the channel"); - let counterparty = channel.counterparty(); - - // make a packet - let packet = Packet { - sequence, - source_port: port_channel_id.port_id.clone(), - source_channel: port_channel_id.channel_id, - destination_port: counterparty.port_id.clone(), - destination_channel: *counterparty - .channel_id() - .expect("the counterparty channel should exist"), - data, - timeout_height, - timeout_timestamp, - }; - // store the commitment of the packet - let commitment_key = storage::commitment_key( - &port_channel_id.port_id, - &port_channel_id.channel_id, - packet.sequence, - ); - let commitment = commitment(&packet); - self.write_ibc_data(&commitment_key, commitment.into_vec())?; - - let event = make_send_packet_event(packet).try_into().unwrap(); - self.emit_ibc_event(event)?; - - Ok(()) - } - - /// Receive a packet - fn receive_packet( - &mut self, - msg: &MsgRecvPacket, - ) -> std::result::Result<(), Self::Error> { - // check the packet data - let packet_ack = - if let Ok(data) = serde_json::from_slice(&msg.packet.data) { - match self.receive_token(&msg.packet, &data) { - Ok(_) => PacketAck::result_success(), - Err(_) => PacketAck::result_error( - "receiving a token failed".to_string(), - ), - } - } else { - PacketAck::result_error("unknown packet data".to_string()) - }; - - // store the receipt - let receipt_key = storage::receipt_key( - &msg.packet.destination_port, - &msg.packet.destination_channel, - msg.packet.sequence, - ); - self.write_ibc_data(&receipt_key, PacketReceipt::default().as_bytes())?; - - // store the ack - let ack_key = storage::ack_key( - &msg.packet.destination_port, - &msg.packet.destination_channel, - msg.packet.sequence, - ); - let ack = packet_ack.encode_to_vec(); - let ack_commitment = sha2::Sha256::digest(&ack).to_vec(); - self.write_ibc_data(&ack_key, ack_commitment)?; - - // increment the next sequence receive - let port_channel_id = port_channel_id( - msg.packet.destination_port.clone(), - msg.packet.destination_channel, - ); - let seq_key = storage::next_sequence_recv_key(&port_channel_id); - self.get_and_inc_sequence(&seq_key)?; - - let event = make_write_ack_event(msg.packet.clone(), ack) - .try_into() - .unwrap(); - self.emit_ibc_event(event)?; - - Ok(()) - } - - /// Receive a acknowledgement - fn acknowledge_packet( - &mut self, - msg: &MsgAcknowledgement, - ) -> std::result::Result<(), Self::Error> { - let ack = PacketAck::try_from(msg.acknowledgement.clone()) - .map_err(Error::IbcData)?; - if !ack.is_success() { - if let Ok(data) = serde_json::from_slice(&msg.packet.data) { - self.refund_token(&msg.packet, &data)?; - } - } - - let commitment_key = storage::commitment_key( - &msg.packet.source_port, - &msg.packet.source_channel, - msg.packet.sequence, - ); - self.delete_ibc_data(&commitment_key)?; - - // get and increment the next sequence ack - let port_channel_id = port_channel_id( - msg.packet.source_port.clone(), - msg.packet.source_channel, - ); - let seq_key = storage::next_sequence_ack_key(&port_channel_id); - self.get_and_inc_sequence(&seq_key)?; - - let event = make_ack_event(msg.packet.clone()).try_into().unwrap(); - self.emit_ibc_event(event)?; - - Ok(()) - } - - /// Receive a timeout - fn timeout_packet( - &mut self, - msg: &MsgTimeout, - ) -> std::result::Result<(), Self::Error> { - // check the packet data - if let Ok(data) = serde_json::from_slice(&msg.packet.data) { - self.refund_token(&msg.packet, &data)?; - } - - // delete the commitment of the packet - let commitment_key = storage::commitment_key( - &msg.packet.source_port, - &msg.packet.source_channel, - msg.packet.sequence, - ); - self.delete_ibc_data(&commitment_key)?; - - // close the channel - let port_channel_id = port_channel_id( - msg.packet.source_port.clone(), - msg.packet.source_channel, - ); - let channel_key = storage::channel_key(&port_channel_id); - let value = self.read_ibc_data(&channel_key)?.ok_or_else(|| { - Error::Channel(format!( - "The channel to be closed doesn't exist: Port/Channel {}", - port_channel_id - )) - })?; - let mut channel = - ChannelEnd::decode_vec(&value).map_err(Error::Decoding)?; - if channel.order_matches(&Order::Ordered) { - close_channel(&mut channel); - self.write_ibc_data( - &channel_key, - channel.encode_vec().expect("encoding shouldn't fail"), - )?; - } - - let event = make_timeout_event(msg.packet.clone()).try_into().unwrap(); - self.emit_ibc_event(event)?; - - Ok(()) - } - - /// Receive a timeout for TimeoutOnClose - fn timeout_on_close_packet( - &mut self, - msg: &MsgTimeoutOnClose, - ) -> std::result::Result<(), Self::Error> { - // check the packet data - if let Ok(data) = serde_json::from_slice(&msg.packet.data) { - self.refund_token(&msg.packet, &data)?; - } - - // delete the commitment of the packet - let commitment_key = storage::commitment_key( - &msg.packet.source_port, - &msg.packet.source_channel, - msg.packet.sequence, - ); - self.delete_ibc_data(&commitment_key)?; - - // close the channel - let port_channel_id = port_channel_id( - msg.packet.source_port.clone(), - msg.packet.source_channel, - ); - let channel_key = storage::channel_key(&port_channel_id); - let value = self.read_ibc_data(&channel_key)?.ok_or_else(|| { - Error::Channel(format!( - "The channel to be closed doesn't exist: Port/Channel {}", - port_channel_id - )) - })?; - let mut channel = - ChannelEnd::decode_vec(&value).map_err(Error::Decoding)?; - if channel.order_matches(&Order::Ordered) { - close_channel(&mut channel); - self.write_ibc_data( - &channel_key, - channel.encode_vec().expect("encoding shouldn't fail"), - )?; - } - - Ok(()) - } - - /// Set the timestamp and the height for the client update - fn set_client_update_time( - &mut self, - client_id: &ClientId, - ) -> std::result::Result<(), Self::Error> { - let time = Time::parse_from_rfc3339(&self.get_header_time()?.0) - .map_err(|e| { - Error::Time(format!("The time of the header is invalid: {}", e)) - })?; - let key = storage::client_update_timestamp_key(client_id); - self.write_ibc_data( - &key, - time.encode_vec().expect("encoding shouldn't fail"), - )?; - - // the revision number is always 0 - let height = Height::new(0, self.get_height()?.0); - let height_key = storage::client_update_height_key(client_id); - // write the current height as u64 - self.write_ibc_data( - &height_key, - height.encode_vec().expect("Encoding shouldn't fail"), - )?; - - Ok(()) - } - - /// Get and increment the counter - fn get_and_inc_counter( - &mut self, - key: &Key, - ) -> std::result::Result { - let value = self.read_ibc_data(key)?.ok_or_else(|| { - Error::Counter(format!("The counter doesn't exist: {}", key)) - })?; - let value: [u8; 8] = value.try_into().map_err(|_| { - Error::Counter(format!("The counter value wasn't u64: Key {}", key)) - })?; - let counter = u64::from_be_bytes(value); - self.write_ibc_data(key, (counter + 1).to_be_bytes())?; - Ok(counter) - } - - /// Get and increment the sequence - fn get_and_inc_sequence( - &mut self, - key: &Key, - ) -> std::result::Result { - let index = match self.read_ibc_data(key)? { - Some(v) => { - let index: [u8; 8] = v.try_into().map_err(|_| { - Error::Sequence(format!( - "The sequence index wasn't u64: Key {}", - key - )) - })?; - u64::from_be_bytes(index) - } - // when the sequence has never been used, returns the initial value - None => 1, - }; - self.write_ibc_data(key, (index + 1).to_be_bytes())?; - Ok(index.into()) - } - - /// Bind a new port - fn bind_port( - &mut self, - port_id: &PortId, - ) -> std::result::Result<(), Self::Error> { - let port_key = storage::port_key(port_id); - match self.read_ibc_data(&port_key)? { - Some(_) => {} - None => { - // create a new capability and claim it - let index_key = storage::capability_index_key(); - let cap_index = self.get_and_inc_counter(&index_key)?; - self.write_ibc_data(&port_key, cap_index.to_be_bytes())?; - let cap_key = storage::capability_key(cap_index); - self.write_ibc_data(&cap_key, port_id.as_bytes())?; - } - } - Ok(()) - } - - /// Send the specified token by escrowing or burning - fn send_token( - &mut self, - msg: &MsgTransfer, - ) -> std::result::Result<(), Self::Error> { - let mut data = FungibleTokenPacketData::from(msg.clone()); - if let Some(hash) = storage::token_hash_from_denom(&data.denom) - .map_err(Error::IbcStorage)? - { - let denom_key = storage::ibc_denom_key(hash); - let denom_bytes = - self.read_ibc_data(&denom_key)?.ok_or_else(|| { - Error::SendingToken(format!( - "No original denom: denom_key {}", - denom_key - )) - })?; - let denom = std::str::from_utf8(&denom_bytes).map_err(|e| { - Error::SendingToken(format!( - "Decoding the denom failed: denom_key {}, error {}", - denom_key, e - )) - })?; - data.denom = denom.to_string(); - } - let token = storage::token(&data.denom).map_err(Error::IbcStorage)?; - let amount = Amount::from_str(&data.amount, 0).map_err(|e| { - Error::SendingToken(format!( - "Invalid amount: amount {}, error {}", - data.amount, e - )) - })?; - - let source_addr = Address::decode(&data.sender).map_err(|e| { - Error::SendingToken(format!( - "Invalid sender address: sender {}, error {}", - data.sender, e - )) - })?; - - // check the denomination field - let prefix = format!( - "{}/{}/", - msg.source_port.clone(), - msg.source_channel.clone() - ); - let (source, target) = if data.denom.starts_with(&prefix) { - // the receiver's chain was the source - // transfer from the origin-specific account of the token - let key_prefix = storage::ibc_token_prefix(&data.denom) - .map_err(Error::IbcStorage)?; - let src = token::multitoken_balance_key(&key_prefix, &source_addr); - - let key_prefix = storage::ibc_account_prefix( - &msg.source_port, - &msg.source_channel, - &token, - ); - let burn = token::multitoken_balance_key( - &key_prefix, - &Address::Internal(InternalAddress::IbcBurn), - ); - (src, burn) - } else { - // this chain is the source - // escrow the amount of the token - let src = if data.denom == token.to_string() { - token::balance_key(&token, &source_addr) - } else { - let key_prefix = storage::ibc_token_prefix(&data.denom) - .map_err(Error::IbcStorage)?; - token::multitoken_balance_key(&key_prefix, &source_addr) - }; - - let key_prefix = storage::ibc_account_prefix( - &msg.source_port, - &msg.source_channel, - &token, - ); - let escrow = token::multitoken_balance_key( - &key_prefix, - &Address::Internal(InternalAddress::IbcEscrow), - ); - (src, escrow) - }; - self.transfer_token(&source, &target, amount)?; - - // send a packet - let port_channel_id = - port_channel_id(msg.source_port.clone(), msg.source_channel); - let packet_data = serde_json::to_vec(&data) - .expect("encoding the packet data shouldn't fail"); - self.send_packet( - port_channel_id, - packet_data, - msg.timeout_height, - msg.timeout_timestamp, - ) - } - - /// Receive the specified token by unescrowing or minting - fn receive_token( - &mut self, - packet: &Packet, - data: &FungibleTokenPacketData, - ) -> std::result::Result<(), Self::Error> { - let token = storage::token(&data.denom).map_err(Error::IbcStorage)?; - let amount = Amount::from_str(&data.amount, 0).map_err(|e| { - Error::ReceivingToken(format!( - "Invalid amount: amount {}, error {}", - data.amount, e - )) - })?; - // The receiver should be an address because the origin-specific account - // key should be assigned internally - let dest_addr = Address::decode(&data.receiver).map_err(|e| { - Error::ReceivingToken(format!( - "Invalid receiver address: receiver {}, error {}", - data.receiver, e - )) - })?; - - let prefix = format!( - "{}/{}/", - packet.source_port.clone(), - packet.source_channel.clone() - ); - let (source, target) = match data.denom.strip_prefix(&prefix) { - Some(denom) => { - // unescrow the token because this chain was the source - let escrow_prefix = storage::ibc_account_prefix( - &packet.destination_port, - &packet.destination_channel, - &token, - ); - let escrow = token::multitoken_balance_key( - &escrow_prefix, - &Address::Internal(InternalAddress::IbcEscrow), - ); - let dest = if denom == token.to_string() { - token::balance_key(&token, &dest_addr) - } else { - let key_prefix = storage::ibc_token_prefix(denom) - .map_err(Error::IbcStorage)?; - token::multitoken_balance_key(&key_prefix, &dest_addr) - }; - (escrow, dest) - } - None => { - // mint the token because the sender chain is the source - let key_prefix = storage::ibc_account_prefix( - &packet.destination_port, - &packet.destination_channel, - &token, - ); - let mint = token::multitoken_balance_key( - &key_prefix, - &Address::Internal(InternalAddress::IbcMint), - ); - - // prefix the denom with the this chain port and channel - let denom = format!( - "{}/{}/{}", - &packet.destination_port, - &packet.destination_channel, - &data.denom - ); - let key_prefix = storage::ibc_token_prefix(&denom) - .map_err(Error::IbcStorage)?; - let dest = - token::multitoken_balance_key(&key_prefix, &dest_addr); - - // store the prefixed denom - let token_hash = storage::calc_hash(&denom); - let denom_key = storage::ibc_denom_key(token_hash); - self.write_ibc_data(&denom_key, denom.as_bytes())?; - - (mint, dest) - } - }; - self.transfer_token(&source, &target, amount)?; - - Ok(()) - } - - /// Refund the specified token by unescrowing or minting - fn refund_token( - &mut self, - packet: &Packet, - data: &FungibleTokenPacketData, - ) -> std::result::Result<(), Self::Error> { - let token = storage::token(&data.denom).map_err(Error::IbcStorage)?; - let amount = Amount::from_str(&data.amount, 0).map_err(|e| { - Error::ReceivingToken(format!( - "Invalid amount: amount {}, error {}", - data.amount, e - )) - })?; - - let dest_addr = Address::decode(&data.sender).map_err(|e| { - Error::SendingToken(format!( - "Invalid sender address: sender {}, error {}", - data.sender, e - )) - })?; - - let prefix = format!( - "{}/{}/", - packet.source_port.clone(), - packet.source_channel.clone() - ); - let (source, target) = if data.denom.starts_with(&prefix) { - // mint the token because the amount was burned - let key_prefix = storage::ibc_account_prefix( - &packet.source_port, - &packet.source_channel, - &token, - ); - let mint = token::multitoken_balance_key( - &key_prefix, - &Address::Internal(InternalAddress::IbcMint), - ); - let key_prefix = storage::ibc_token_prefix(&data.denom) - .map_err(Error::IbcStorage)?; - let dest = token::multitoken_balance_key(&key_prefix, &dest_addr); - (mint, dest) - } else { - // unescrow the token because the acount was escrowed - let dest = if data.denom == token.to_string() { - token::balance_key(&token, &dest_addr) - } else { - let key_prefix = storage::ibc_token_prefix(&data.denom) - .map_err(Error::IbcStorage)?; - token::multitoken_balance_key(&key_prefix, &dest_addr) - }; - - let key_prefix = storage::ibc_account_prefix( - &packet.source_port, - &packet.source_channel, - &token, - ); - let escrow = token::multitoken_balance_key( - &key_prefix, - &Address::Internal(InternalAddress::IbcEscrow), - ); - (escrow, dest) - }; - self.transfer_token(&source, &target, amount)?; - - Ok(()) - } -} - -/// Update a client with the given state and headers -pub fn update_client( - client_state: AnyClientState, - header: AnyHeader, -) -> Result<(AnyClientState, AnyConsensusState)> { - match client_state { - AnyClientState::Tendermint(cs) => match header { - AnyHeader::Tendermint(h) => { - let new_client_state = cs.with_header(h.clone()).wrap_any(); - let new_consensus_state = TmConsensusState::from(h).wrap_any(); - Ok((new_client_state, new_consensus_state)) - } - #[cfg(any(feature = "ibc-mocks-abcipp", feature = "ibc-mocks"))] - _ => Err(Error::ClientUpdate( - "The header type is mismatched".to_owned(), - )), - }, - #[cfg(any(feature = "ibc-mocks-abcipp", feature = "ibc-mocks"))] - AnyClientState::Mock(_) => match header { - AnyHeader::Mock(h) => Ok(( - MockClientState::new(h).wrap_any(), - MockConsensusState::new(h).wrap_any(), - )), - _ => Err(Error::ClientUpdate( - "The header type is mismatched".to_owned(), - )), - }, - } -} - -/// Returns a new client ID -pub fn client_id(client_type: ClientType, counter: u64) -> Result { - ClientId::new(client_type, counter).map_err(Error::ClientId) -} - -/// Returns a new connection ID -pub fn connection_id(counter: u64) -> ConnectionId { - ConnectionId::new(counter) -} - -/// Make a connection end from the init message -pub fn init_connection(msg: &MsgConnectionOpenInit) -> ConnectionEnd { - ConnectionEnd::new( - ConnState::Init, - msg.client_id.clone(), - msg.counterparty.clone(), - vec![msg.version.clone().unwrap_or_default()], - msg.delay_period, - ) -} - -/// Make a connection end from the try message -pub fn try_connection(msg: &MsgConnectionOpenTry) -> ConnectionEnd { - ConnectionEnd::new( - ConnState::TryOpen, - msg.client_id.clone(), - msg.counterparty.clone(), - msg.counterparty_versions.clone(), - msg.delay_period, - ) -} - -/// Open the connection -pub fn open_connection(conn: &mut ConnectionEnd) { - conn.set_state(ConnState::Open); -} - -/// Returns a new channel ID -pub fn channel_id(counter: u64) -> ChannelId { - ChannelId::new(counter) -} - -/// Open the channel -pub fn open_channel(channel: &mut ChannelEnd) { - channel.set_state(ChanState::Open); -} - -/// Close the channel -pub fn close_channel(channel: &mut ChannelEnd) { - channel.set_state(ChanState::Closed); -} - -/// Returns a port ID -pub fn port_id(id: &str) -> Result { - PortId::from_str(id).map_err(Error::PortId) -} - -/// Returns a pair of port ID and channel ID -pub fn port_channel_id( - port_id: PortId, - channel_id: ChannelId, -) -> PortChannelId { - PortChannelId { - port_id, - channel_id, - } -} - -/// Returns a sequence -pub fn sequence(index: u64) -> Sequence { - Sequence::from(index) -} - -/// Make a packet from MsgTransfer -pub fn packet_from_message( - msg: &MsgTransfer, - sequence: Sequence, - counterparty: &ChanCounterparty, -) -> Packet { - Packet { - sequence, - source_port: msg.source_port.clone(), - source_channel: msg.source_channel, - destination_port: counterparty.port_id.clone(), - destination_channel: *counterparty - .channel_id() - .expect("the counterparty channel should exist"), - data: serde_json::to_vec(&FungibleTokenPacketData::from(msg.clone())) - .expect("encoding the packet data shouldn't fail"), - timeout_height: msg.timeout_height, - timeout_timestamp: msg.timeout_timestamp, - } -} - -/// Returns a commitment from the given packet -pub fn commitment(packet: &Packet) -> PacketCommitment { - let timeout = packet.timeout_timestamp.nanoseconds().to_be_bytes(); - let revision_number = packet.timeout_height.revision_number.to_be_bytes(); - let revision_height = packet.timeout_height.revision_height.to_be_bytes(); - let data = sha2::Sha256::digest(&packet.data); - let input = [ - &timeout, - &revision_number, - &revision_height, - data.as_slice(), - ] - .concat(); - sha2::Sha256::digest(&input).to_vec().into() -} - -/// Returns a counterparty of a connection -pub fn connection_counterparty( - client_id: ClientId, - conn_id: ConnectionId, -) -> ConnCounterparty { - ConnCounterparty::new(client_id, Some(conn_id), commitment_prefix()) -} - -/// Returns a counterparty of a channel -pub fn channel_counterparty( - port_id: PortId, - channel_id: ChannelId, -) -> ChanCounterparty { - ChanCounterparty::new(port_id, Some(channel_id)) -} - -/// Returns Namada commitment prefix -pub fn commitment_prefix() -> CommitmentPrefix { - CommitmentPrefix::try_from(COMMITMENT_PREFIX.to_vec()) - .expect("the conversion shouldn't fail") -} - -/// Makes CreateClient event -pub fn make_create_client_event( - client_id: &ClientId, - msg: &MsgCreateAnyClient, -) -> IbcEvent { - let attributes = ClientAttributes { - client_id: client_id.clone(), - client_type: msg.client_state.client_type(), - consensus_height: msg.client_state.latest_height(), - ..Default::default() - }; - IbcEvent::CreateClient(CreateClient::from(attributes)) -} - -/// Makes UpdateClient event -pub fn make_update_client_event( - client_id: &ClientId, - msg: &MsgUpdateAnyClient, -) -> IbcEvent { - let attributes = ClientAttributes { - client_id: client_id.clone(), - client_type: msg.header.client_type(), - consensus_height: msg.header.height(), - ..Default::default() - }; - IbcEvent::UpdateClient(UpdateClient::from(attributes)) -} - -/// Makes UpgradeClient event -pub fn make_upgrade_client_event( - client_id: &ClientId, - msg: &MsgUpgradeAnyClient, -) -> IbcEvent { - let attributes = ClientAttributes { - client_id: client_id.clone(), - client_type: msg.client_state.client_type(), - consensus_height: msg.client_state.latest_height(), - ..Default::default() - }; - IbcEvent::UpgradeClient(UpgradeClient::from(attributes)) -} - -/// Makes OpenInitConnection event -pub fn make_open_init_connection_event( - conn_id: &ConnectionId, - msg: &MsgConnectionOpenInit, -) -> IbcEvent { - let attributes = ConnectionAttributes { - connection_id: Some(conn_id.clone()), - client_id: msg.client_id.clone(), - counterparty_connection_id: msg.counterparty.connection_id().cloned(), - counterparty_client_id: msg.counterparty.client_id().clone(), - ..Default::default() - }; - ConnOpenInit::from(attributes).into() -} - -/// Makes OpenTryConnection event -pub fn make_open_try_connection_event( - conn_id: &ConnectionId, - msg: &MsgConnectionOpenTry, -) -> IbcEvent { - let attributes = ConnectionAttributes { - connection_id: Some(conn_id.clone()), - client_id: msg.client_id.clone(), - counterparty_connection_id: msg.counterparty.connection_id().cloned(), - counterparty_client_id: msg.counterparty.client_id().clone(), - ..Default::default() - }; - ConnOpenTry::from(attributes).into() -} - -/// Makes OpenAckConnection event -pub fn make_open_ack_connection_event(msg: &MsgConnectionOpenAck) -> IbcEvent { - let attributes = ConnectionAttributes { - connection_id: Some(msg.connection_id.clone()), - counterparty_connection_id: Some( - msg.counterparty_connection_id.clone(), - ), - ..Default::default() - }; - ConnOpenAck::from(attributes).into() -} - -/// Makes OpenConfirmConnection event -pub fn make_open_confirm_connection_event( - msg: &MsgConnectionOpenConfirm, -) -> IbcEvent { - let attributes = ConnectionAttributes { - connection_id: Some(msg.connection_id.clone()), - ..Default::default() - }; - ConnOpenConfirm::from(attributes).into() -} - -/// Makes OpenInitChannel event -pub fn make_open_init_channel_event( - channel_id: &ChannelId, - msg: &MsgChannelOpenInit, -) -> IbcEvent { - let connection_id = match msg.channel.connection_hops().get(0) { - Some(c) => c.clone(), - None => ConnectionId::default(), - }; - let attributes = ChanOpenInit { - height: Height::default(), - port_id: msg.port_id.clone(), - channel_id: Some(*channel_id), - connection_id, - counterparty_port_id: msg.channel.counterparty().port_id().clone(), - counterparty_channel_id: msg - .channel - .counterparty() - .channel_id() - .cloned(), - }; - attributes.into() -} - -/// Makes OpenTryChannel event -pub fn make_open_try_channel_event( - channel_id: &ChannelId, - msg: &MsgChannelOpenTry, -) -> IbcEvent { - let connection_id = match msg.channel.connection_hops().get(0) { - Some(c) => c.clone(), - None => ConnectionId::default(), - }; - let attributes = ChanOpenTry { - height: Height::default(), - port_id: msg.port_id.clone(), - channel_id: Some(*channel_id), - connection_id, - counterparty_port_id: msg.channel.counterparty().port_id().clone(), - counterparty_channel_id: msg - .channel - .counterparty() - .channel_id() - .cloned(), - }; - attributes.into() -} - -/// Makes OpenAckChannel event -pub fn make_open_ack_channel_event( - msg: &MsgChannelOpenAck, - channel: &ChannelEnd, -) -> Result { - let conn_id = get_connection_id_from_channel(channel)?; - let counterparty = channel.counterparty(); - let attributes = ChanOpenAck { - height: Height::default(), - port_id: msg.port_id.clone(), - channel_id: Some(msg.channel_id), - counterparty_channel_id: Some(msg.counterparty_channel_id), - connection_id: conn_id.clone(), - counterparty_port_id: counterparty.port_id().clone(), - }; - Ok(attributes.into()) -} - -/// Makes OpenConfirmChannel event -pub fn make_open_confirm_channel_event( - msg: &MsgChannelOpenConfirm, - channel: &ChannelEnd, -) -> Result { - let conn_id = get_connection_id_from_channel(channel)?; - let counterparty = channel.counterparty(); - let attributes = ChanOpenConfirm { - height: Height::default(), - port_id: msg.port_id.clone(), - channel_id: Some(msg.channel_id), - connection_id: conn_id.clone(), - counterparty_port_id: counterparty.port_id().clone(), - counterparty_channel_id: counterparty.channel_id().cloned(), - }; - Ok(attributes.into()) -} - -/// Makes CloseInitChannel event -pub fn make_close_init_channel_event( - msg: &MsgChannelCloseInit, - channel: &ChannelEnd, -) -> Result { - let conn_id = get_connection_id_from_channel(channel)?; - let counterparty = channel.counterparty(); - let attributes = ChanCloseInit { - height: Height::default(), - port_id: msg.port_id.clone(), - channel_id: msg.channel_id, - connection_id: conn_id.clone(), - counterparty_port_id: counterparty.port_id().clone(), - counterparty_channel_id: counterparty.channel_id().cloned(), - }; - Ok(attributes.into()) -} - -/// Makes CloseConfirmChannel event -pub fn make_close_confirm_channel_event( - msg: &MsgChannelCloseConfirm, - channel: &ChannelEnd, -) -> Result { - let conn_id = get_connection_id_from_channel(channel)?; - let counterparty = channel.counterparty(); - let attributes = ChanCloseConfirm { - height: Height::default(), - port_id: msg.port_id.clone(), - channel_id: Some(msg.channel_id), - connection_id: conn_id.clone(), - counterparty_port_id: counterparty.port_id.clone(), - counterparty_channel_id: counterparty.channel_id().cloned(), - }; - Ok(attributes.into()) -} - -fn get_connection_id_from_channel( - channel: &ChannelEnd, -) -> Result<&ConnectionId> { - channel.connection_hops().get(0).ok_or_else(|| { - Error::Channel("No connection for the channel".to_owned()) - }) -} - -/// Makes SendPacket event -pub fn make_send_packet_event(packet: Packet) -> IbcEvent { - IbcEvent::SendPacket(SendPacket { - height: packet.timeout_height, - packet, - }) -} - -/// Makes WriteAcknowledgement event -pub fn make_write_ack_event(packet: Packet, ack: Vec) -> IbcEvent { - IbcEvent::WriteAcknowledgement(WriteAcknowledgement { - // this height is not used - height: Height::default(), - packet, - ack, - }) -} - -/// Makes AcknowledgePacket event -pub fn make_ack_event(packet: Packet) -> IbcEvent { - IbcEvent::AcknowledgePacket(AcknowledgePacket { - // this height is not used - height: Height::default(), - packet, - }) -} - -/// Makes TimeoutPacket event -pub fn make_timeout_event(packet: Packet) -> IbcEvent { - IbcEvent::TimeoutPacket(TimeoutPacket { - // this height is not used - height: Height::default(), - packet, - }) -} diff --git a/ethereum_bridge/src/protocol/transactions/ethereum_events/events.rs b/ethereum_bridge/src/protocol/transactions/ethereum_events/events.rs index 8ef07a64a5..0052fb01b1 100644 --- a/ethereum_bridge/src/protocol/transactions/ethereum_events/events.rs +++ b/ethereum_bridge/src/protocol/transactions/ethereum_events/events.rs @@ -3,7 +3,7 @@ use std::collections::{BTreeSet, HashSet}; use std::str::FromStr; -use borsh::{BorshDeserialize, BorshSerialize}; +use borsh::BorshDeserialize; use eyre::{Result, WrapErr}; use namada_core::hints; use namada_core::ledger::eth_bridge::storage::bridge_pool::{ @@ -26,7 +26,7 @@ use namada_core::types::ethereum_events::{ }; use namada_core::types::storage::{BlockHeight, Key, KeySeg}; use namada_core::types::token; -use namada_core::types::token::{balance_key, minted_balance_key, minter_key}; +use namada_core::types::token::{balance_key, minted_balance_key}; use crate::parameters::read_native_erc20_address; use crate::protocol::transactions::update; @@ -265,9 +265,8 @@ where })?; _ = changed_keys.insert(supply_key); - let minter_key = minter_key(&token); - wl_storage.write_bytes(&minter_key, BRIDGE_POOL_ADDRESS.try_to_vec()?)?; - _ = changed_keys.insert(minter_key); + // mint the token without a minter because a protocol tx doesn't need to + // trigger a VP Ok(changed_keys) } @@ -757,7 +756,7 @@ mod tests { assert_eq!( stored_keys_count(&wl_storage), - initial_stored_keys_count + 3 + initial_stored_keys_count + 2 ); } @@ -789,7 +788,7 @@ mod tests { assert_eq!( stored_keys_count(&wl_storage), - initial_stored_keys_count + 3 + initial_stored_keys_count + 2 ); let expected_amount = amount.try_to_vec().unwrap(); diff --git a/ethereum_bridge/src/protocol/transactions/ethereum_events/mod.rs b/ethereum_bridge/src/protocol/transactions/ethereum_events/mod.rs index 29fd07a8e7..d3cd32972d 100644 --- a/ethereum_bridge/src/protocol/transactions/ethereum_events/mod.rs +++ b/ethereum_bridge/src/protocol/transactions/ethereum_events/mod.rs @@ -284,9 +284,7 @@ mod tests { use namada_core::types::ethereum_events::{ EthereumEvent, TransferToNamada, }; - use namada_core::types::token::{ - balance_key, minted_balance_key, minter_key, Amount, - }; + use namada_core::types::token::{balance_key, minted_balance_key, Amount}; use super::*; use crate::protocol::transactions::utils::GetVoters; @@ -347,7 +345,6 @@ mod tests { eth_msg_keys.voting_started_epoch(), balance_key(&wrapped_erc20_token, &receiver), minted_balance_key(&wrapped_erc20_token), - minter_key(&wrapped_erc20_token), ]), changed_keys ); @@ -449,7 +446,6 @@ mod tests { eth_msg_keys.voting_started_epoch(), balance_key(&dai_token, &receiver), minted_balance_key(&dai_token), - minter_key(&dai_token), ]) ); assert!(tx_result.vps_result.accepted_vps.is_empty()); diff --git a/shared/src/ledger/native_vp/multitoken.rs b/shared/src/ledger/native_vp/multitoken.rs index 3e2a0b84df..b3d93e5170 100644 --- a/shared/src/ledger/native_vp/multitoken.rs +++ b/shared/src/ledger/native_vp/multitoken.rs @@ -120,9 +120,8 @@ where }; match token { Address::Internal(InternalAddress::Erc20(_)) => { - if minter == Address::Internal(InternalAddress::EthBridge) { - return Ok(Some(minter)); - } + // ERC20 token should not be minted by a wasm transaction + return Ok(None); } Address::Internal(InternalAddress::IbcToken(_)) => { if minter == Address::Internal(InternalAddress::Ibc) { @@ -155,6 +154,7 @@ mod tests { }; use crate::eth_bridge::storage::wrapped_erc20s; use crate::ledger::gas::VpGasMeter; + use crate::ledger::ibc::storage::ibc_token; use crate::proto::{Code, Data, Section, Signature, Tx}; use crate::types::address::{Address, InternalAddress}; use crate::types::ethereum_events::testing::arbitrary_eth_address; @@ -294,8 +294,8 @@ mod tests { let mut wl_storage = TestWlStorage::default(); let mut keys_changed = BTreeSet::new(); - // ERC20 token - let token = wrapped_erc20s::token(&arbitrary_eth_address()); + // IBC token + let token = ibc_token("/port-42/channel-42/denom"); // mint 100 let target = established_address_1(); @@ -315,7 +315,7 @@ mod tests { keys_changed.insert(minted_key); // minter - let minter = Address::Internal(InternalAddress::EthBridge); + let minter = Address::Internal(InternalAddress::Ibc); let minter_key = minter_key(&token); wl_storage .write_log diff --git a/shared/src/ledger/queries/shell/eth_bridge.rs b/shared/src/ledger/queries/shell/eth_bridge.rs index 55e5255b62..d7d0ed249e 100644 --- a/shared/src/ledger/queries/shell/eth_bridge.rs +++ b/shared/src/ledger/queries/shell/eth_bridge.rs @@ -123,12 +123,11 @@ where "The Ethereum bridge storage is not initialized", )); }; - if asset == native_erc20 { - return Err(storage_api::Error::SimpleMessage( - "Wrapped NAM's supply is not kept track of", - )); - } - let token = wrapped_erc20s::token(&asset); + let token = if asset == native_erc20 { + ctx.wl_storage.storage.native_token.clone() + } else { + wrapped_erc20s::token(&asset) + }; ctx.wl_storage.read(&minted_balance_key(&token)) } @@ -1365,15 +1364,26 @@ mod test_ethbridge_router { assert!(resp.is_err()); } - /// Test that reading the wrapped NAM supply fails. + /// Test reading the wrapped NAM supply #[tokio::test] - async fn test_read_wnam_supply_fails() { + async fn test_read_wnam_supply() { let mut client = TestClient::new(RPC); assert_eq!(client.wl_storage.storage.last_epoch.0, 0); // initialize storage test_utils::init_default_storage(&mut client.wl_storage); + let native_erc20 = + read_native_erc20_address(&client.wl_storage).expect("Test failed"); + + // write tokens to storage + let amount = Amount::native_whole(12345); + let token = &client.wl_storage.storage.native_token; + client + .wl_storage + .write(&minted_balance_key(token), amount) + .expect("Test failed"); + // commit the changes client .wl_storage @@ -1382,21 +1392,12 @@ mod test_ethbridge_router { .expect("Test failed"); // check that reading wrapped NAM fails - let native_erc20 = - read_native_erc20_address(&client.wl_storage).expect("Test failed"); let result = RPC .shell() .eth_bridge() .read_erc20_supply(&client, &native_erc20) .await; - let Err(err) = result else { - panic!("Test failed"); - }; - - assert_eq!( - err.to_string(), - "Wrapped NAM's supply is not kept track of" - ); + assert_matches!(result, Ok(Some(a)) if a == amount); } /// Test reading the supply of an ERC20 token. diff --git a/wasm/wasm_source/src/tx_bridge_pool.rs b/wasm/wasm_source/src/tx_bridge_pool.rs index 1dbb86adb7..bf73e83f7d 100644 --- a/wasm/wasm_source/src/tx_bridge_pool.rs +++ b/wasm/wasm_source/src/tx_bridge_pool.rs @@ -46,13 +46,12 @@ fn apply_tx(ctx: &mut Ctx, signed: Tx) -> TxResult { } else { // Otherwise we escrow ERC20 tokens. let token = wrapped_erc20s::token(&asset); - let amount = amount.denominated(ð_bridge::ADDRESS, ctx)?; token::transfer( ctx, sender, &bridge_pool::BRIDGE_POOL_ADDRESS, &token, - amount, + amount.native_denominated(), &None, &None, &None, From 93b6679c5f74d1ce361f86b2619a0244742d83f0 Mon Sep 17 00:00:00 2001 From: yito88 Date: Mon, 10 Jul 2023 22:29:21 +0200 Subject: [PATCH 19/25] add comments --- apps/src/lib/client/rpc.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index eb03de78d9..dd18aa8520 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -520,15 +520,20 @@ async fn print_balances( ), None => continue, }; - // Get the token and the balance let (t, s) = match (token, target) { + // the given token and the given target are the same as the + // retrieved ones (Some(token), Some(target)) if t == *token && o == *target => { (t, s) } + // the given token is the same as the retrieved one (Some(token), None) if t == *token => (t, s), + // the given target is the same as the retrieved one (None, Some(target)) if o == *target => (t, s), + // no specified token or target (None, None) => (t, s), + // otherwise, this balance will not be printed _ => continue, }; // Print the token if it isn't printed yet From 079d5c133bd18067ec28ec0df88e1492aefd587b Mon Sep 17 00:00:00 2001 From: yito88 Date: Tue, 11 Jul 2023 20:53:29 +0200 Subject: [PATCH 20/25] fix client_connections encoding --- core/src/ledger/ibc/context/execution.rs | 6 ++++-- shared/src/ledger/ibc/vp/mod.rs | 6 +++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/core/src/ledger/ibc/context/execution.rs b/core/src/ledger/ibc/context/execution.rs index ddf289ce08..ae84afa614 100644 --- a/core/src/ledger/ibc/context/execution.rs +++ b/core/src/ledger/ibc/context/execution.rs @@ -1,5 +1,7 @@ //! ExecutionContext implementation for IBC +use borsh::{BorshDeserialize, BorshSerialize}; + use super::super::{IbcActions, IbcCommonContext}; use crate::ibc::core::ics02_client::client_state::ClientState; use crate::ibc::core::ics02_client::client_type::ClientType; @@ -180,7 +182,7 @@ where .expect("Creating a key for the client state shouldn't fail"); let list = match self.ctx.borrow().read(&key) { Ok(Some(value)) => { - let list = String::from_utf8(value).map_err(|e| { + let list = String::try_from_slice(&value).map_err(|e| { ContextError::ConnectionError(ConnectionError::Other { description: format!( "Decoding the connection list failed: Key {}, \ @@ -201,7 +203,7 @@ where }))? } }; - let bytes = list.as_bytes().to_vec(); + let bytes = list.try_to_vec().expect("encoding shouldn't fail"); self.ctx.borrow_mut().write(&key, bytes).map_err(|_| { ContextError::ConnectionError(ConnectionError::Other { description: format!( diff --git a/shared/src/ledger/ibc/vp/mod.rs b/shared/src/ledger/ibc/vp/mod.rs index 619e484ba2..04c980a21b 100644 --- a/shared/src/ledger/ibc/vp/mod.rs +++ b/shared/src/ledger/ibc/vp/mod.rs @@ -1047,7 +1047,7 @@ mod tests { // client connection list let client_conn_key = client_connections_key(&msg.client_id_on_a); let conn_list = conn_id.to_string(); - let bytes = conn_list.as_bytes().to_vec(); + let bytes = conn_list.try_to_vec().expect("encoding failed"); wl_storage .write_log .write(&client_conn_key, bytes) @@ -1154,7 +1154,7 @@ mod tests { // client connection list let client_conn_key = client_connections_key(&msg.client_id_on_a); let conn_list = conn_id.to_string(); - let bytes = conn_list.as_bytes().to_vec(); + let bytes = conn_list.try_to_vec().expect("encoding failed"); wl_storage .write_log .write(&client_conn_key, bytes) @@ -1270,7 +1270,7 @@ mod tests { // client connection list let client_conn_key = client_connections_key(&msg.client_id_on_b); let conn_list = conn_id.to_string(); - let bytes = conn_list.as_bytes().to_vec(); + let bytes = conn_list.try_to_vec().expect("encoding failed"); wl_storage .write_log .write(&client_conn_key, bytes) From e65ffebac5741ba2fafa8e6c09ced807f5e9d61e Mon Sep 17 00:00:00 2001 From: yito88 Date: Wed, 12 Jul 2023 18:41:46 +0200 Subject: [PATCH 21/25] remove vp_token --- apps/src/lib/config/genesis.rs | 22 -- apps/src/lib/node/ledger/shell/init_chain.rs | 34 +-- core/src/ledger/storage/write_log.rs | 45 ++-- genesis/dev.toml | 12 - genesis/e2e-tests-single-node.toml | 11 - shared/src/ledger/native_vp/multitoken.rs | 192 ++++++++------ shared/src/ledger/tx.rs | 24 +- shared/src/vm/host_env.rs | 14 + tests/src/vm_host_env/ibc.rs | 22 +- wasm/wasm_source/Makefile | 1 - wasm/wasm_source/src/lib.rs | 2 - wasm/wasm_source/src/vp_token.rs | 263 ------------------- 12 files changed, 179 insertions(+), 463 deletions(-) delete mode 100644 wasm/wasm_source/src/vp_token.rs diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index fcd57be745..0fd5a319ec 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -392,27 +392,13 @@ pub mod genesis_config { fn load_token( config: &TokenAccountConfig, - wasm: &HashMap, validators: &HashMap, established_accounts: &HashMap, implicit_accounts: &HashMap, ) -> TokenAccount { - let token_vp_name = config.vp.as_ref().unwrap(); - let token_vp_config = wasm.get(token_vp_name).unwrap(); - TokenAccount { address: Address::decode(config.address.as_ref().unwrap()).unwrap(), denom: config.denom, - vp_code_path: token_vp_config.filename.to_owned(), - vp_sha256: token_vp_config - .sha256 - .clone() - .unwrap_or_else(|| { - eprintln!("Unknown token VP WASM sha256"); - cli::safe_exit(1); - }) - .to_sha256_bytes() - .unwrap(), balances: config .balances .as_ref() @@ -579,7 +565,6 @@ pub mod genesis_config { .map(|(_name, cfg)| { load_token( cfg, - &wasm, &validators, &established_accounts, &implicit_accounts, @@ -814,10 +799,6 @@ pub struct TokenAccount { pub address: Address, /// The number of decimal places amounts of this token has pub denom: Denomination, - /// Validity predicate code WASM - pub vp_code_path: String, - /// Expected SHA-256 hash of the validity predicate wasm - pub vp_sha256: [u8; 32], /// Accounts' balances of this token #[derivative(PartialOrd = "ignore", Ord = "ignore")] pub balances: HashMap, @@ -903,7 +884,6 @@ pub fn genesis(num_validators: u64) -> Genesis { use crate::wallet; let vp_implicit_path = "vp_implicit.wasm"; - let vp_token_path = "vp_token.wasm"; let vp_user_path = "vp_user.wasm"; // NOTE When the validator's key changes, tendermint must be reset with @@ -1081,8 +1061,6 @@ pub fn genesis(num_validators: u64) -> Genesis { .map(|(address, (_, denom))| TokenAccount { address, denom, - vp_code_path: vp_token_path.into(), - vp_sha256: Default::default(), balances: balances.clone(), }) .collect(); diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index 4fa8889753..cf029b5a84 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -236,10 +236,7 @@ where self.initialize_implicit_accounts(genesis.implicit_accounts); // Initialize genesis token accounts - self.initialize_token_accounts( - genesis.token_accounts, - &implicit_vp_code_path, - ); + self.initialize_token_accounts(genesis.token_accounts); // Initialize genesis validator accounts let staking_token = staking_token_address(&self.wl_storage); @@ -342,45 +339,16 @@ where fn initialize_token_accounts( &mut self, accounts: Vec, - implicit_vp_code_path: &str, ) { // Initialize genesis token accounts for genesis::TokenAccount { address, denom, - vp_code_path, - vp_sha256, balances, } in accounts { // associate a token with its denomination. write_denom(&mut self.wl_storage, &address, denom).unwrap(); - let vp_code_hash = - read_wasm_hash(&self.wl_storage, vp_code_path.clone()) - .unwrap() - .ok_or(Error::LoadingWasm(format!( - "Unknown vp code path: {}", - implicit_vp_code_path - ))) - .expect("Reading wasms should succeed"); - - // In dev, we don't check the hash - #[cfg(feature = "dev")] - let _ = vp_sha256; - #[cfg(not(feature = "dev"))] - { - assert_eq!( - vp_code_hash.0.as_slice(), - &vp_sha256, - "Invalid token account's VP sha256 hash for {}", - vp_code_path - ); - } - - self.wl_storage - .write_bytes(&Key::validity_predicate(&address), vp_code_hash) - .unwrap(); - for (owner, amount) in balances { credit_tokens(&mut self.wl_storage, &address, &owner, amount) .unwrap(); diff --git a/core/src/ledger/storage/write_log.rs b/core/src/ledger/storage/write_log.rs index 4b4c055c50..641fa7fc19 100644 --- a/core/src/ledger/storage/write_log.rs +++ b/core/src/ledger/storage/write_log.rs @@ -9,10 +9,13 @@ use thiserror::Error; use crate::ledger; use crate::ledger::storage::traits::StorageHasher; use crate::ledger::storage::Storage; -use crate::types::address::{Address, EstablishedAddressGen}; +use crate::types::address::{Address, EstablishedAddressGen, InternalAddress}; use crate::types::hash::Hash; use crate::types::ibc::IbcEvent; use crate::types::storage; +use crate::types::token::{ + is_any_minted_balance_key, is_any_minter_key, is_any_token_balance_key, +}; #[allow(missing_docs)] #[derive(Error, Debug)] @@ -470,21 +473,33 @@ impl WriteLog { // get changed keys grouped by the address for key in changed_keys.iter() { - for addr in &key.find_addresses() { - if verifiers_from_tx.contains(addr) - || initialized_accounts.contains(addr) - { - // We can skip this when the address has been added from the - // Tx above. - // Also skip if it's an address of a newly initialized - // account, because anything can be written into an - // account's storage in the same tx in which it's - // initialized (there is no VP in the state prior to tx - // execution). - continue; + // for token keys, trigger Multitoken VP and the owner's VP + if let Some([_, owner]) = is_any_token_balance_key(key) { + verifiers + .insert(Address::Internal(InternalAddress::Multitoken)); + verifiers.insert(owner.clone()); + } else if is_any_minted_balance_key(key).is_some() + || is_any_minter_key(key).is_some() + { + verifiers + .insert(Address::Internal(InternalAddress::Multitoken)); + } else { + for addr in &key.find_addresses() { + if verifiers_from_tx.contains(addr) + || initialized_accounts.contains(addr) + { + // We can skip this when the address has been added from + // the Tx above. + // Also skip if it's an address of a newly initialized + // account, because anything can be written into an + // account's storage in the same tx in which it's + // initialized (there is no VP in the state prior to tx + // execution). + continue; + } + // Add the address as a verifier + verifiers.insert(addr.clone()); } - // Add the address as a verifier - verifiers.insert(addr.clone()); } } (verifiers, changed_keys) diff --git a/genesis/dev.toml b/genesis/dev.toml index b6eb070b42..19985a3b9e 100644 --- a/genesis/dev.toml +++ b/genesis/dev.toml @@ -28,7 +28,6 @@ net_address = "127.0.0.1:26656" [token.NAM] address = "atest1v4ehgw36x3prswzxggunzv6pxqmnvdj9xvcyzvpsggeyvs3cg9qnywf589qnwvfsg5erg3fkl09rg5" denom = 8 -vp = "vp_token" [token.NAM.balances] # In token balances, we can use: # 1. An address any account @@ -45,7 +44,6 @@ Bertha = "1000000" [token.BTC] address = "atest1v4ehgw36xdzryve5gsc52veeg5cnsv2yx5eygvp38qcrvd29xy6rys6p8yc5xvp4xfpy2v694wgwcp" denom = 8 -vp = "vp_token" [token.BTC.balances] atest1v4ehgw368ycryv2z8qcnxv3cxgmrgvjpxs6yg333gym5vv2zxepnj334g4rryvj9xucrgve4x3xvr4 = 1000000 atest1v4ehgw36x3qng3jzggu5yvpsxgcngv2xgguy2dpkgvu5x33kx3pr2w2zgep5xwfkxscrxs2pj8075p = 1000000 @@ -55,7 +53,6 @@ a1qyqzsqqqqqcyvvf5xcu5vd6rg4z5233hg9pn23pjgdryzdjy8pz52wzxxscnvvjxx3rryvzz8y5p6m [token.ETH] address = "atest1v4ehgw36xqmr2d3nx3ryvd2xxgmrq33j8qcns33sxezrgv6zxdzrydjrxveygd2yxumrsdpsf9jc2p" denom = 18 -vp = "vp_token" [token.ETH.balances] atest1v4ehgw368ycryv2z8qcnxv3cxgmrgvjpxs6yg333gym5vv2zxepnj334g4rryvj9xucrgve4x3xvr4 = 1000000 atest1v4ehgw36x3qng3jzggu5yvpsxgcngv2xgguy2dpkgvu5x33kx3pr2w2zgep5xwfkxscrxs2pj8075p = 1000000 @@ -65,7 +62,6 @@ a1qyqzsqqqqqcyvvf5xcu5vd6rg4z5233hg9pn23pjgdryzdjy8pz52wzxxscnvvjxx3rryvzz8y5p6m [token.DOT] address = "atest1v4ehgw36gg6nvs2zgfpyxsfjgc65yv6pxy6nwwfsxgungdzrggeyzv35gveyxsjyxymyz335hur2jn" denom = 10 -vp = "vp_token" [token.DOT.balances] atest1v4ehgw368ycryv2z8qcnxv3cxgmrgvjpxs6yg333gym5vv2zxepnj334g4rryvj9xucrgve4x3xvr4 = 1000000 atest1v4ehgw36x3qng3jzggu5yvpsxgcngv2xgguy2dpkgvu5x33kx3pr2w2zgep5xwfkxscrxs2pj8075p = 1000000 @@ -75,7 +71,6 @@ a1qyqzsqqqqqcyvvf5xcu5vd6rg4z5233hg9pn23pjgdryzdjy8pz52wzxxscnvvjxx3rryvzz8y5p6m [token.schnitzel] address = "atest1v4ehgw36xue5xvf5xvuyzvpjx5un2v3k8qeyvd3cxdqns32p89rrxd6xx9zngvpegccnzs699rdnnt" denom = 6 -vp = "vp_token" [token.schnitzel.balances] atest1v4ehgw368ycryv2z8qcnxv3cxgmrgvjpxs6yg333gym5vv2zxepnj334g4rryvj9xucrgve4x3xvr4 = 1000000 atest1v4ehgw36x3qng3jzggu5yvpsxgcngv2xgguy2dpkgvu5x33kx3pr2w2zgep5xwfkxscrxs2pj8075p = 1000000 @@ -85,7 +80,6 @@ a1qyqzsqqqqqcyvvf5xcu5vd6rg4z5233hg9pn23pjgdryzdjy8pz52wzxxscnvvjxx3rryvzz8y5p6m [token.apfel] address = "atest1v4ehgw36gfryydj9g3p5zv3kg9znyd358ycnzsfcggc5gvecgc6ygs2rxv6ry3zpg4zrwdfeumqcz9" denom = 6 -vp = "vp_token" [token.apfel.balances] atest1v4ehgw368ycryv2z8qcnxv3cxgmrgvjpxs6yg333gym5vv2zxepnj334g4rryvj9xucrgve4x3xvr4 = 1000000 atest1v4ehgw36x3qng3jzggu5yvpsxgcngv2xgguy2dpkgvu5x33kx3pr2w2zgep5xwfkxscrxs2pj8075p = 1000000 @@ -96,7 +90,6 @@ a1qyqzsqqqqqcyvvf5xcu5vd6rg4z5233hg9pn23pjgdryzdjy8pz52wzxxscnvvjxx3rryvzz8y5p6m address = "atest1v4ehgw36gep5ysecxq6nyv3jg3zygv3e89qn2vp48pryxsf4xpznvve5gvmy23fs89pryvf5a6ht90" denom = 6 public_key = "" -vp = "vp_token" [token.kartoffel.balances] atest1v4ehgw368ycryv2z8qcnxv3cxgmrgvjpxs6yg333gym5vv2zxepnj334g4rryvj9xucrgve4x3xvr4 = 1000000 atest1v4ehgw36x3qng3jzggu5yvpsxgcngv2xgguy2dpkgvu5x33kx3pr2w2zgep5xwfkxscrxs2pj8075p = 1000000 @@ -140,11 +133,6 @@ sha256 = "dc7b97f0448f2369bd2401c3c1d8898f53cac8c464a8c1b1f7f81415a658625d" # filename (relative to wasm path used by the node) filename = "vp_validator.wasm" -# Token VP -[wasm.vp_token] -filename = "vp_token.wasm" -sha256 = "e428a11f570d21dd3c871f5d35de6fe18098eb8ee0456b3e11a72ccdd8685cd0" - # General protocol parameters. [parameters] # Minimum number of blocks in an epoch. diff --git a/genesis/e2e-tests-single-node.toml b/genesis/e2e-tests-single-node.toml index beab8edc2f..e3d309f6d1 100644 --- a/genesis/e2e-tests-single-node.toml +++ b/genesis/e2e-tests-single-node.toml @@ -28,7 +28,6 @@ net_address = "127.0.0.1:27656" [token.NAM] address = "atest1v4ehgw36x3prswzxggunzv6pxqmnvdj9xvcyzvpsggeyvs3cg9qnywf589qnwvfsg5erg3fkl09rg5" denom = 6 -vp = "vp_token" [token.NAM.balances] Albert = "1000000" "Albert.public_key" = "100" @@ -45,7 +44,6 @@ faucet = "9223372036854" [token.BTC] address = "atest1v4ehgw36xdzryve5gsc52veeg5cnsv2yx5eygvp38qcrvd29xy6rys6p8yc5xvp4xfpy2v694wgwcp" denom = 8 -vp = "vp_token" [token.BTC.balances] Albert = "1000000" Bertha = "1000000" @@ -57,7 +55,6 @@ faucet = "9223372036854" [token.ETH] address = "atest1v4ehgw36xqmr2d3nx3ryvd2xxgmrq33j8qcns33sxezrgv6zxdzrydjrxveygd2yxumrsdpsf9jc2p" denom = 18 -vp = "vp_token" [token.ETH.balances] Albert = "1000000" Bertha = "1000000" @@ -69,7 +66,6 @@ faucet = "9223372036854" [token.DOT] address = "atest1v4ehgw36gg6nvs2zgfpyxsfjgc65yv6pxy6nwwfsxgungdzrggeyzv35gveyxsjyxymyz335hur2jn" denom = 10 -vp = "vp_token" [token.DOT.balances] Albert = "1000000" Bertha = "1000000" @@ -81,7 +77,6 @@ faucet = "9223372036854" [token.Schnitzel] address = "atest1v4ehgw36xue5xvf5xvuyzvpjx5un2v3k8qeyvd3cxdqns32p89rrxd6xx9zngvpegccnzs699rdnnt" denom = 6 -vp = "vp_token" [token.Schnitzel.balances] Albert = "1000000" Bertha = "1000000" @@ -93,7 +88,6 @@ faucet = "9223372036854" [token.Apfel] address = "atest1v4ehgw36gfryydj9g3p5zv3kg9znyd358ycnzsfcggc5gvecgc6ygs2rxv6ry3zpg4zrwdfeumqcz9" denom = 6 -vp = "vp_token" [token.Apfel.balances] Albert = "1000000" Bertha = "1000000" @@ -106,7 +100,6 @@ faucet = "9223372036854" address = "atest1v4ehgw36gep5ysecxq6nyv3jg3zygv3e89qn2vp48pryxsf4xpznvve5gvmy23fs89pryvf5a6ht90" public_key = "" denom = 6 -vp = "vp_token" [token.Kartoffel.balances] Albert = "1000000" Bertha = "1000000" @@ -151,10 +144,6 @@ filename = "vp_user.wasm" # filename (relative to wasm path used by the node) filename = "vp_validator.wasm" -# Token VP -[wasm.vp_token] -filename = "vp_token.wasm" - # Faucet VP [wasm.vp_testnet_faucet] filename = "vp_testnet_faucet.wasm" diff --git a/shared/src/ledger/native_vp/multitoken.rs b/shared/src/ledger/native_vp/multitoken.rs index b3d93e5170..038e40349e 100644 --- a/shared/src/ledger/native_vp/multitoken.rs +++ b/shared/src/ledger/native_vp/multitoken.rs @@ -8,7 +8,7 @@ use crate::ledger::native_vp::{self, Ctx, NativeVp}; use crate::ledger::storage; use crate::ledger::vp_env::VpEnv; use crate::proto::Tx; -use crate::types::address::{Address, InternalAddress}; +use crate::types::address::{self, Address, InternalAddress}; use crate::types::storage::{Key, KeySeg}; use crate::types::token::{ is_any_minted_balance_key, is_any_minter_key, is_any_token_balance_key, @@ -56,10 +56,15 @@ where let mut changes = HashMap::new(); let mut mints = HashMap::new(); for key in keys_changed { - if let Some([token, _]) = is_any_token_balance_key(key) { + if let Some([token, owner]) = is_any_token_balance_key(key) { let pre: Amount = self.ctx.read_pre(key)?.unwrap_or_default(); let post: Amount = self.ctx.read_post(key)?.unwrap_or_default(); let diff = post.change() - pre.change(); + if diff.is_negative() + && !(verifiers.contains(owner) || *owner == address::masp()) + { + return Ok(false); + } match changes.get_mut(token) { Some(change) => *change += diff, None => _ = changes.insert(token, diff), @@ -74,14 +79,12 @@ where } // Check if the minter is set - match self.check_minter(token)? { - Some(minter) if verifiers.contains(&minter) => {} - _ => return Ok(false), + if !self.is_valid_minter(token, verifiers)? { + return Ok(false); } } else if let Some(token) = is_any_minter_key(key) { - match self.check_minter(token)? { - Some(_) => {} - None => return Ok(false), + if !self.is_valid_minter(token, verifiers)? { + return Ok(false); } } else if key.segments.get(0) == Some( @@ -111,32 +114,31 @@ where CA: 'static + WasmCacheAccess, { /// Return the minter if the minter is valid and the minter VP exists - pub fn check_minter(&self, token: &Address) -> Result> { - // Check if the minter is set - let minter_key = minter_key(token); - let minter = match self.ctx.read_post(&minter_key)? { - Some(m) => m, - None => return Ok(None), - }; + pub fn is_valid_minter( + &self, + token: &Address, + verifiers: &BTreeSet
, + ) -> Result { match token { - Address::Internal(InternalAddress::Erc20(_)) => { - // ERC20 token should not be minted by a wasm transaction - return Ok(None); - } Address::Internal(InternalAddress::IbcToken(_)) => { - if minter == Address::Internal(InternalAddress::Ibc) { - return Ok(Some(minter)); + // Check if the minter is set + let minter_key = minter_key(token); + match self.ctx.read_post::
(&minter_key)? { + Some(minter) + if minter + == Address::Internal(InternalAddress::Ibc) => + { + Ok(verifiers.contains(&minter)) + } + _ => Ok(false), } } _ => { - // Check the minter VP exists - let vp_key = Key::validity_predicate(&minter); - if self.ctx.has_key_post(&vp_key)? { - return Ok(Some(minter)); - } + // ERC20 and other tokens should not be minted by a wasm + // transaction + Ok(false) } } - Ok(None) } } @@ -152,12 +154,10 @@ mod tests { use crate::core::types::address::testing::{ established_address_1, established_address_2, }; - use crate::eth_bridge::storage::wrapped_erc20s; use crate::ledger::gas::VpGasMeter; use crate::ledger::ibc::storage::ibc_token; use crate::proto::{Code, Data, Section, Signature, Tx}; use crate::types::address::{Address, InternalAddress}; - use crate::types::ethereum_events::testing::arbitrary_eth_address; use crate::types::key::testing::keypair_1; use crate::types::storage::TxIndex; use crate::types::token::{ @@ -215,7 +215,8 @@ mod tests { let tx = dummy_tx(&wl_storage); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm_cache(); - let verifiers = BTreeSet::new(); + let mut verifiers = BTreeSet::new(); + verifiers.insert(sender); let ctx = Ctx::new( &ADDRESS, &wl_storage.storage, @@ -289,6 +290,60 @@ mod tests { ); } + #[test] + fn test_invalid_sender() { + let mut wl_storage = TestWlStorage::default(); + let mut keys_changed = BTreeSet::new(); + + let sender = established_address_1(); + let sender_key = balance_key(&nam(), &sender); + let amount = Amount::native_whole(100); + wl_storage + .storage + .write(&sender_key, amount.try_to_vec().unwrap()) + .expect("write failed"); + + // transfer 10 + let amount = Amount::native_whole(90); + wl_storage + .write_log + .write(&sender_key, amount.try_to_vec().unwrap()) + .expect("write failed"); + keys_changed.insert(sender_key); + let receiver = established_address_2(); + let receiver_key = balance_key(&nam(), &receiver); + let amount = Amount::native_whole(10); + wl_storage + .write_log + .write(&receiver_key, amount.try_to_vec().unwrap()) + .expect("write failed"); + keys_changed.insert(receiver_key); + + let tx_index = TxIndex::default(); + let tx = dummy_tx(&wl_storage); + let gas_meter = VpGasMeter::new(0); + let (vp_wasm_cache, _vp_cache_dir) = wasm_cache(); + let verifiers = BTreeSet::new(); + // The sender is not set + let ctx = Ctx::new( + &ADDRESS, + &wl_storage.storage, + &wl_storage.write_log, + &tx, + &tx_index, + gas_meter, + &keys_changed, + &verifiers, + vp_wasm_cache, + ); + + let vp = MultitokenVp { ctx }; + assert!( + !vp.validate_tx(&tx, &keys_changed, &verifiers) + .expect("validation failed") + ); + } + #[test] fn test_valid_mint() { let mut wl_storage = TestWlStorage::default(); @@ -354,13 +409,6 @@ mod tests { let mut wl_storage = TestWlStorage::default(); let mut keys_changed = BTreeSet::new(); - // set the dummy nam vp - let vp_key = Key::validity_predicate(&nam()); - wl_storage - .storage - .write(&vp_key, vec![]) - .expect("write failed"); - // mint 100 let target = established_address_1(); let target_key = balance_key(&nam(), &target); @@ -419,16 +467,19 @@ mod tests { let mut wl_storage = TestWlStorage::default(); let mut keys_changed = BTreeSet::new(); + // IBC token + let token = ibc_token("/port-42/channel-42/denom"); + // mint 100 let target = established_address_1(); - let target_key = balance_key(&nam(), &target); + let target_key = balance_key(&token, &target); let amount = Amount::native_whole(100); wl_storage .write_log .write(&target_key, amount.try_to_vec().unwrap()) .expect("write failed"); keys_changed.insert(target_key); - let minted_key = minted_balance_key(&nam()); + let minted_key = minted_balance_key(&token); let amount = Amount::native_whole(100); wl_storage .write_log @@ -463,20 +514,23 @@ mod tests { } #[test] - fn test_no_minter_vp() { + fn test_invalid_minter() { let mut wl_storage = TestWlStorage::default(); let mut keys_changed = BTreeSet::new(); + // IBC token + let token = ibc_token("/port-42/channel-42/denom"); + // mint 100 let target = established_address_1(); - let target_key = balance_key(&nam(), &target); + let target_key = balance_key(&token, &target); let amount = Amount::native_whole(100); wl_storage .write_log .write(&target_key, amount.try_to_vec().unwrap()) .expect("write failed"); keys_changed.insert(target_key); - let minted_key = minted_balance_key(&nam()); + let minted_key = minted_balance_key(&token); let amount = Amount::native_whole(100); wl_storage .write_log @@ -484,9 +538,9 @@ mod tests { .expect("write failed"); keys_changed.insert(minted_key); - // minter - let minter = Address::Internal(InternalAddress::Ibc); - let minter_key = minter_key(&nam()); + // invalid minter + let minter = established_address_1(); + let minter_key = minter_key(&token); wl_storage .write_log .write(&minter_key, minter.try_to_vec().unwrap()) @@ -497,8 +551,9 @@ mod tests { let tx = dummy_tx(&wl_storage); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm_cache(); - let verifiers = BTreeSet::new(); - // the minter isn't included in the verifiers + let mut verifiers = BTreeSet::new(); + // for the minter + verifiers.insert(minter); let ctx = Ctx::new( &ADDRESS, &wl_storage.storage, @@ -519,37 +574,17 @@ mod tests { } #[test] - fn test_invalid_minter() { + fn test_invalid_minter_update() { let mut wl_storage = TestWlStorage::default(); let mut keys_changed = BTreeSet::new(); - // ERC20 token - let token = wrapped_erc20s::token(&arbitrary_eth_address()); - - // mint 100 - let target = established_address_1(); - let target_key = balance_key(&token, &target); - let amount = Amount::native_whole(100); - wl_storage - .write_log - .write(&target_key, amount.try_to_vec().unwrap()) - .expect("write failed"); - keys_changed.insert(target_key); - let minted_key = minted_balance_key(&token); - let amount = Amount::native_whole(100); - wl_storage - .write_log - .write(&minted_key, amount.try_to_vec().unwrap()) - .expect("write failed"); - keys_changed.insert(minted_key); - - // invalid minter + let minter_key = minter_key(&nam()); let minter = established_address_1(); - let minter_key = minter_key(&token); wl_storage .write_log .write(&minter_key, minter.try_to_vec().unwrap()) .expect("write failed"); + keys_changed.insert(minter_key); let tx_index = TxIndex::default(); @@ -579,26 +614,27 @@ mod tests { } #[test] - fn test_invalid_minter_update() { + fn test_invalid_key_update() { let mut wl_storage = TestWlStorage::default(); let mut keys_changed = BTreeSet::new(); - let minter_key = minter_key(&nam()); - let minter = established_address_1(); + let key = Key::from( + Address::Internal(InternalAddress::Multitoken).to_db_key(), + ) + .push(&"invalid_segment".to_string()) + .unwrap(); wl_storage .write_log - .write(&minter_key, minter.try_to_vec().unwrap()) + .write(&key, 0.try_to_vec().unwrap()) .expect("write failed"); - keys_changed.insert(minter_key); + keys_changed.insert(key); let tx_index = TxIndex::default(); let tx = dummy_tx(&wl_storage); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm_cache(); - let mut verifiers = BTreeSet::new(); - // for the minter - verifiers.insert(minter); + let verifiers = BTreeSet::new(); let ctx = Ctx::new( &ADDRESS, &wl_storage.storage, diff --git a/shared/src/ledger/tx.rs b/shared/src/ledger/tx.rs index 0a02f8dcf0..f90c7d4795 100644 --- a/shared/src/ledger/tx.rs +++ b/shared/src/ledger/tx.rs @@ -1140,7 +1140,7 @@ pub async fn build_ibc_transfer< .await?; // We cannot check the receiver - let token = token_exists_or_err(args.token, args.tx.force, client).await?; + let token = args.token; // Check source balance let balance_key = token::balance_key(&token, &source); @@ -1317,8 +1317,6 @@ pub async fn build_transfer< source_exists_or_err(source.clone(), args.tx.force, client).await?; // Check that the target address exists on chain target_exists_or_err(target.clone(), args.tx.force, client).await?; - // Check that the token address exists on chain - token_exists_or_err(token.clone(), args.tx.force, client).await?; // Check source balance let balance_key = token::balance_key(&token, &source); @@ -1704,26 +1702,6 @@ where } } -/// Returns the given token if the given address exists on chain -/// otherwise returns an error, force forces the address through even -/// if it isn't on chain -pub async fn token_exists_or_err( - token: Address, - force: bool, - client: &C, -) -> Result { - let message = - format!("The token address {} doesn't exist on chain.", token); - address_exists_or_err( - token, - force, - client, - message, - Error::TokenDoesNotExist, - ) - .await -} - /// Returns the given source address if the given address exists on chain /// otherwise returns an error, force forces the address through even /// if it isn't on chain diff --git a/shared/src/vm/host_env.rs b/shared/src/vm/host_env.rs index 56cbc7d680..f290e484ff 100644 --- a/shared/src/vm/host_env.rs +++ b/shared/src/vm/host_env.rs @@ -23,6 +23,9 @@ use crate::types::hash::Hash; use crate::types::ibc::IbcEvent; use crate::types::internal::HostEnvResult; use crate::types::storage::{BlockHeight, Key, TxIndex}; +use crate::types::token::{ + is_any_minted_balance_key, is_any_minter_key, is_any_token_balance_key, +}; use crate::vm::memory::VmMemory; use crate::vm::prefix_iter::{PrefixIteratorId, PrefixIterators}; use crate::vm::{HostRef, MutHostRef}; @@ -902,9 +905,20 @@ where H: StorageHasher, CA: WasmCacheAccess, { + // Get the token if the key is a balance or minter key + let token = if let Some([token, _]) = is_any_token_balance_key(key) { + Some(token) + } else { + is_any_minted_balance_key(key).or_else(|| is_any_minter_key(key)) + }; + let write_log = unsafe { env.ctx.write_log.get() }; let storage = unsafe { env.ctx.storage.get() }; for addr in key.find_addresses() { + // skip if the address is a token address + if Some(&addr) == token { + continue; + } // skip the check for implicit and internal addresses if let Address::Implicit(_) | Address::Internal(_) = &addr { continue; diff --git a/tests/src/vm_host_env/ibc.rs b/tests/src/vm_host_env/ibc.rs index 9baafd6fc2..5858abe7d3 100644 --- a/tests/src/vm_host_env/ibc.rs +++ b/tests/src/vm_host_env/ibc.rs @@ -93,7 +93,7 @@ use namada::vm::{wasm, WasmCacheRwAccess}; use namada_test_utils::TestWasms; use namada_tx_prelude::BorshSerialize; -use crate::tx::{self, *}; +use crate::tx::*; const ADDRESS: Address = Address::Internal(InternalAddress::Ibc); @@ -235,10 +235,10 @@ pub fn init_storage() -> (Address, Address) { }); // initialize a token - let token = tx::ctx().init_account(code_hash).unwrap(); + let token = tx_host_env::ctx().init_account(code_hash).unwrap(); // initialize an account - let account = tx::ctx().init_account(code_hash).unwrap(); + let account = tx_host_env::ctx().init_account(code_hash).unwrap(); let key = token::balance_key(&token, &account); let init_bal = Amount::native_whole(100); let bytes = init_bal.try_to_vec().expect("encoding failed"); @@ -265,6 +265,22 @@ pub fn init_storage() -> (Address, Address) { env.wl_storage.storage.write(&key, &bytes).unwrap(); }); + // commit the initialized token and account + tx_host_env::with(|env| { + env.wl_storage.commit_tx(); + env.wl_storage.commit_block().unwrap(); + + // block header to check timeout timestamp + env.wl_storage + .storage + .set_header(tm_dummy_header()) + .unwrap(); + env.wl_storage + .storage + .begin_block(BlockHash::default(), BlockHeight(2)) + .unwrap(); + }); + (token, account) } diff --git a/wasm/wasm_source/Makefile b/wasm/wasm_source/Makefile index a2b5ab284d..f394179dc7 100644 --- a/wasm/wasm_source/Makefile +++ b/wasm/wasm_source/Makefile @@ -22,7 +22,6 @@ wasms += tx_withdraw wasms += vp_implicit wasms += vp_masp wasms += vp_testnet_faucet -wasms += vp_token wasms += vp_user wasms += vp_validator diff --git a/wasm/wasm_source/src/lib.rs b/wasm/wasm_source/src/lib.rs index 3d5be30b56..2fc69f65c9 100644 --- a/wasm/wasm_source/src/lib.rs +++ b/wasm/wasm_source/src/lib.rs @@ -33,8 +33,6 @@ pub mod vp_implicit; pub mod vp_masp; #[cfg(feature = "vp_testnet_faucet")] pub mod vp_testnet_faucet; -#[cfg(feature = "vp_token")] -pub mod vp_token; #[cfg(feature = "vp_user")] pub mod vp_user; #[cfg(feature = "vp_validator")] diff --git a/wasm/wasm_source/src/vp_token.rs b/wasm/wasm_source/src/vp_token.rs deleted file mode 100644 index 002553dd75..0000000000 --- a/wasm/wasm_source/src/vp_token.rs +++ /dev/null @@ -1,263 +0,0 @@ -//! A VP for a fungible token. Enforces that the total supply is unchanged in a -//! transaction that moves balance(s). - -use std::collections::BTreeSet; - -use namada_vp_prelude::address::{self, Address}; -use namada_vp_prelude::storage::KeySeg; -use namada_vp_prelude::{storage, token, *}; - -#[validity_predicate] -fn validate_tx( - ctx: &Ctx, - tx_data: Tx, - addr: Address, - keys_changed: BTreeSet, - verifiers: BTreeSet
, -) -> VpResult { - debug_log!( - "validate_tx called with token addr: {}, key_changed: {:?}, \ - verifiers: {:?}", - addr, - keys_changed, - verifiers - ); - - if !is_valid_tx(ctx, &tx_data)? { - return reject(); - } - - for key in keys_changed.iter() { - if key.is_validity_predicate().is_some() { - let vp_hash: Vec = ctx.read_bytes_post(key)?.unwrap(); - if !is_vp_whitelisted(ctx, &vp_hash)? { - return reject(); - } - } - } - - token_checks(ctx, &addr, &keys_changed, &verifiers) -} - -/// A token validity predicate checks that the total supply is preserved. -/// This implies that: -/// -/// - The value associated with the `total_supply` storage key may not change. -/// - For any balance changes, the total of outputs must be equal to the total -/// of inputs. -fn token_checks( - ctx: &Ctx, - token: &Address, - keys_touched: &BTreeSet, - verifiers: &BTreeSet
, -) -> VpResult { - for key in keys_touched.iter() { - let owner: Option<&Address> = token::is_balance_key(token, key); - - match owner { - None => { - if let Some(t) = token::is_any_minted_balance_key(key) { - if t == token { - // check if total supply is changed, which it should - // never be from a tx - let total_pre: token::Amount = - ctx.read_pre(key)?.unwrap(); - let total_post: token::Amount = - ctx.read_post(key)?.unwrap(); - if total_pre != total_post { - return reject(); - } - } - } else if key.segments.get(0) == Some(&token.to_db_key()) { - // Unknown changes to this address space are disallowed, but - // unknown changes anywhere else are permitted - return reject(); - } - } - Some(owner) => { - let pre: token::Amount = ctx.read_pre(key)?.unwrap_or_default(); - let post: token::Amount = - ctx.read_post(key)?.unwrap_or_default(); - // make sure that the spender approved the transaction - if post < pre - && !(verifiers.contains(owner) || *owner == address::masp()) - { - return reject(); - } - } - } - } - // The total change should be validated by multitoken VP - Ok(true) -} - -#[cfg(test)] -mod tests { - // Use this as `#[test]` annotation to enable logging - use namada::core::ledger::storage_api::token; - use namada::proto::Data; - use namada::types::transaction::TxType; - use namada_tests::log::test; - use namada_tests::tx::{self, TestTxEnv}; - use namada_tests::vp::*; - use namada_vp_prelude::storage_api::StorageWrite; - - use super::*; - - #[test] - fn test_transfer_inputs_eq_outputs_is_accepted() { - // Initialize a tx environment - let mut tx_env = TestTxEnv::default(); - let token = address::nam(); - let src = address::testing::established_address_1(); - let dest = address::testing::established_address_2(); - let total_supply = - token::Amount::from_uint(10_098_123, 0).expect("Test failed"); - - // Spawn the accounts to be able to modify their storage - tx_env.spawn_accounts([&token, &src, &dest]); - token::credit_tokens( - &mut tx_env.wl_storage, - &token, - &src, - total_supply, - ) - .unwrap(); - // Commit the initial state - tx_env.commit_tx_and_block(); - - // Initialize VP environment from a transaction - vp_host_env::init_from_tx(token.clone(), tx_env, |_address| { - // Apply a transfer - - let amount = token::Amount::from_uint(100, 0).expect("Test failed"); - token::transfer(tx::ctx(), &token, &src, &dest, amount).unwrap(); - }); - - let vp_env = vp_host_env::take(); - let mut tx_data = Tx::new(TxType::Raw); - tx_data.set_data(Data::new(vec![])); - let keys_changed: BTreeSet = - vp_env.all_touched_storage_keys(); - let verifiers = vp_env.get_verifiers(); - vp_host_env::set(vp_env); - - assert!( - validate_tx(&CTX, tx_data, token, keys_changed, verifiers).unwrap(), - "A transfer where inputs == outputs should be accepted" - ); - } - - #[test] - fn test_transfer_inputs_neq_outputs_is_rejected() { - // Initialize a tx environment - let mut tx_env = TestTxEnv::default(); - let token = address::nam(); - let src = address::testing::established_address_1(); - let dest = address::testing::established_address_2(); - let total_supply = - token::Amount::from_uint(10_098_123, 0).expect("Test failed"); - - // Spawn the accounts to be able to modify their storage - tx_env.spawn_accounts([&token, &src, &dest]); - token::credit_tokens( - &mut tx_env.wl_storage, - &token, - &src, - total_supply, - ) - .unwrap(); - // Commit the initial state - tx_env.commit_tx_and_block(); - - // Initialize VP environment from a transaction - vp_host_env::init_from_tx(token.clone(), tx_env, |_address| { - // Apply a transfer - - let amount_in = - token::Amount::from_uint(100, 0).expect("Test failed"); - let amount_out = - token::Amount::from_uint(900, 0).expect("Test failed"); - - let src_key = token::balance_key(&token, &src); - let src_balance = - token::read_balance(tx::ctx(), &token, &src).unwrap(); - let new_src_balance = src_balance + amount_out; - let dest_key = token::balance_key(&token, &dest); - let dest_balance = - token::read_balance(tx::ctx(), &token, &dest).unwrap(); - let new_dest_balance = dest_balance + amount_in; - tx::ctx().write(&src_key, new_src_balance).unwrap(); - tx::ctx().write(&dest_key, new_dest_balance).unwrap(); - }); - - let vp_env = vp_host_env::take(); - let mut tx_data = Tx::new(TxType::Raw); - tx_data.set_data(Data::new(vec![])); - let keys_changed: BTreeSet = - vp_env.all_touched_storage_keys(); - let verifiers = vp_env.get_verifiers(); - vp_host_env::set(vp_env); - - assert!( - !validate_tx(&CTX, tx_data, token, keys_changed, verifiers) - .unwrap(), - "A transfer where inputs != outputs should be rejected" - ); - } - - #[test] - fn test_total_supply_change_is_rejected() { - // Initialize a tx environment - let mut tx_env = TestTxEnv::default(); - let token = address::nam(); - let owner = address::testing::established_address_1(); - let total_supply = - token::Amount::from_uint(10_098_123, 0).expect("Test failed"); - - // Spawn the accounts to be able to modify their storage - tx_env.spawn_accounts([&token, &owner]); - token::credit_tokens( - &mut tx_env.wl_storage, - &token, - &owner, - total_supply, - ) - .unwrap(); - // Commit the initial state - tx_env.commit_tx_and_block(); - - let total_supply_key = token::minted_balance_key(&token); - - // Initialize VP environment from a transaction - vp_host_env::init_from_tx(token.clone(), tx_env, |_address| { - // Try to change total supply from a tx - - let current_supply = tx::ctx() - .read::(&total_supply_key) - .unwrap() - .unwrap_or_default(); - tx::ctx() - .write( - &total_supply_key, - current_supply - + token::Amount::from_uint(1, 0).expect("Test failed"), - ) - .unwrap(); - }); - - let vp_env = vp_host_env::take(); - let mut tx_data = Tx::new(TxType::Raw); - tx_data.set_data(Data::new(vec![])); - let keys_changed: BTreeSet = - vp_env.all_touched_storage_keys(); - let verifiers = vp_env.get_verifiers(); - vp_host_env::set(vp_env); - - assert!( - !validate_tx(&CTX, tx_data, token, keys_changed, verifiers) - .unwrap(), - "Change of a `total_supply` value should be rejected" - ); - } -} From 749a8ec5134ea3efcb98893cd17bd92c0cfabf9b Mon Sep 17 00:00:00 2001 From: yito88 Date: Fri, 14 Jul 2023 11:42:05 +0200 Subject: [PATCH 22/25] remove negative check --- shared/src/ledger/native_vp/multitoken.rs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/shared/src/ledger/native_vp/multitoken.rs b/shared/src/ledger/native_vp/multitoken.rs index 038e40349e..e4d3785ef2 100644 --- a/shared/src/ledger/native_vp/multitoken.rs +++ b/shared/src/ledger/native_vp/multitoken.rs @@ -8,7 +8,7 @@ use crate::ledger::native_vp::{self, Ctx, NativeVp}; use crate::ledger::storage; use crate::ledger::vp_env::VpEnv; use crate::proto::Tx; -use crate::types::address::{self, Address, InternalAddress}; +use crate::types::address::{Address, InternalAddress}; use crate::types::storage::{Key, KeySeg}; use crate::types::token::{ is_any_minted_balance_key, is_any_minter_key, is_any_token_balance_key, @@ -56,15 +56,10 @@ where let mut changes = HashMap::new(); let mut mints = HashMap::new(); for key in keys_changed { - if let Some([token, owner]) = is_any_token_balance_key(key) { + if let Some([token, _]) = is_any_token_balance_key(key) { let pre: Amount = self.ctx.read_pre(key)?.unwrap_or_default(); let post: Amount = self.ctx.read_post(key)?.unwrap_or_default(); let diff = post.change() - pre.change(); - if diff.is_negative() - && !(verifiers.contains(owner) || *owner == address::masp()) - { - return Ok(false); - } match changes.get_mut(token) { Some(change) => *change += diff, None => _ = changes.insert(token, diff), From cd95787afd8dcf4ef4ec3e6b29fa832e30334860 Mon Sep 17 00:00:00 2001 From: yito88 Date: Fri, 14 Jul 2023 12:16:50 +0200 Subject: [PATCH 23/25] remove test_invalid_sender --- shared/src/ledger/native_vp/multitoken.rs | 54 ----------------------- 1 file changed, 54 deletions(-) diff --git a/shared/src/ledger/native_vp/multitoken.rs b/shared/src/ledger/native_vp/multitoken.rs index e4d3785ef2..a932714240 100644 --- a/shared/src/ledger/native_vp/multitoken.rs +++ b/shared/src/ledger/native_vp/multitoken.rs @@ -285,60 +285,6 @@ mod tests { ); } - #[test] - fn test_invalid_sender() { - let mut wl_storage = TestWlStorage::default(); - let mut keys_changed = BTreeSet::new(); - - let sender = established_address_1(); - let sender_key = balance_key(&nam(), &sender); - let amount = Amount::native_whole(100); - wl_storage - .storage - .write(&sender_key, amount.try_to_vec().unwrap()) - .expect("write failed"); - - // transfer 10 - let amount = Amount::native_whole(90); - wl_storage - .write_log - .write(&sender_key, amount.try_to_vec().unwrap()) - .expect("write failed"); - keys_changed.insert(sender_key); - let receiver = established_address_2(); - let receiver_key = balance_key(&nam(), &receiver); - let amount = Amount::native_whole(10); - wl_storage - .write_log - .write(&receiver_key, amount.try_to_vec().unwrap()) - .expect("write failed"); - keys_changed.insert(receiver_key); - - let tx_index = TxIndex::default(); - let tx = dummy_tx(&wl_storage); - let gas_meter = VpGasMeter::new(0); - let (vp_wasm_cache, _vp_cache_dir) = wasm_cache(); - let verifiers = BTreeSet::new(); - // The sender is not set - let ctx = Ctx::new( - &ADDRESS, - &wl_storage.storage, - &wl_storage.write_log, - &tx, - &tx_index, - gas_meter, - &keys_changed, - &verifiers, - vp_wasm_cache, - ); - - let vp = MultitokenVp { ctx }; - assert!( - !vp.validate_tx(&tx, &keys_changed, &verifiers) - .expect("validation failed") - ); - } - #[test] fn test_valid_mint() { let mut wl_storage = TestWlStorage::default(); From 331cde0ec024008601a9be288989cb613b659e22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 19 Jul 2023 16:07:25 +0100 Subject: [PATCH 24/25] changelog: add #1693 --- .changelog/unreleased/miscellaneous/1693-ibc-multitoken.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/miscellaneous/1693-ibc-multitoken.md diff --git a/.changelog/unreleased/miscellaneous/1693-ibc-multitoken.md b/.changelog/unreleased/miscellaneous/1693-ibc-multitoken.md new file mode 100644 index 0000000000..1c90f3a4b8 --- /dev/null +++ b/.changelog/unreleased/miscellaneous/1693-ibc-multitoken.md @@ -0,0 +1,2 @@ +- Replaced token sub-prefix with a multitoken address and native VP for IBC and + ETH bridge. ([\#1693](https://github.com/anoma/namada/pull/1693)) \ No newline at end of file From ef2678cc4ccb0097c6a8dc208c0638592418da08 Mon Sep 17 00:00:00 2001 From: yito88 Date: Fri, 21 Jul 2023 20:49:42 +0200 Subject: [PATCH 25/25] fix tx_transfer --- tx_prelude/src/token.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/tx_prelude/src/token.rs b/tx_prelude/src/token.rs index 3cf1d8ead3..e950668d2b 100644 --- a/tx_prelude/src/token.rs +++ b/tx_prelude/src/token.rs @@ -19,7 +19,7 @@ pub fn transfer( shielded_hash: &Option, shielded: &Option, ) -> TxResult { - if amount.amount != Amount::default() { + if amount.amount != Amount::default() && src != dest { let src_key = token::balance_key(token, src); let dest_key = token::balance_key(token, dest); let src_bal: Option = ctx.read(&src_key)?; @@ -30,10 +30,8 @@ pub fn transfer( src_bal.spend(&amount.amount); let mut dest_bal: Amount = ctx.read(&dest_key)?.unwrap_or_default(); dest_bal.receive(&amount.amount); - if src != dest { - ctx.write(&src_key, src_bal)?; - ctx.write(&dest_key, dest_bal)?; - } + ctx.write(&src_key, src_bal)?; + ctx.write(&dest_key, dest_bal)?; } // If this transaction has a shielded component, then handle it