From 6f0fd94e4d1a48f6cba8435eac1e644447494743 Mon Sep 17 00:00:00 2001 From: yito88 Date: Wed, 31 May 2023 19:07:08 +0900 Subject: [PATCH 01/14] 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 bee874bb0e..57d8257c3e 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 444d110242..47f0a7ba99 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( @@ -2237,7 +2263,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 da0043046f..f75ded64e6 100644 --- a/shared/src/ledger/ibc/vp/token.rs +++ b/shared/src/ledger/ibc/vp/token.rs @@ -189,7 +189,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 f4344e33b2..ea23f001e1 100644 --- a/tests/src/vm_host_env/mod.rs +++ b/tests/src/vm_host_env/mod.rs @@ -1258,7 +1258,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 367dbd2522a0b909772c0714880ebc4c80e203c6 Mon Sep 17 00:00:00 2001 From: yito88 Date: Fri, 2 Jun 2023 23:41:41 +0900 Subject: [PATCH 02/14] change multitoken --- apps/src/lib/client/rpc.rs | 17 ++- core/src/ledger/ibc/context/storage.rs | 23 ++- core/src/ledger/ibc/context/transfer_mod.rs | 80 +++-------- 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 | 81 +++++------ shared/src/ledger/ibc/vp/context.rs | 150 +++++++++++++++----- 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 | 141 +++++++----------- wasm/wasm_source/src/vp_implicit.rs | 3 +- wasm/wasm_source/src/vp_token.rs | 28 +--- wasm/wasm_source/src/vp_user.rs | 3 +- wasm/wasm_source/src/vp_validator.rs | 3 +- 22 files changed, 459 insertions(+), 563 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 0ced390bfe..13b6dfe90c 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -272,12 +272,11 @@ pub async fn query_transparent_balance< match (args.token, args.owner) { (Some(token), Some(owner)) => { let key = match &args.sub_prefix { - Some(sub_prefix) => { - let sub_prefix = Key::parse(sub_prefix).unwrap(); - let prefix = - token::multitoken_balance_prefix(&token, &sub_prefix); + Some(sp) => { + let sub_prefix = + Address::decode(sp).expect("Invalid sub_prefix"); token::multitoken_balance_key( - &prefix, + &sub_prefix, &owner.address().unwrap(), ) } @@ -477,8 +476,8 @@ fn print_balances( writeln!(w, "Token {}", token_alias).unwrap(); let print_num = balances - .filter_map( - |(key, balance)| match token::is_any_multitoken_balance_key(&key) { + .filter_map(|(key, balance)| { + match token::is_multitoken_balance_key(&key) { Some((sub_prefix, owner)) => Some(( owner.clone(), format!( @@ -498,8 +497,8 @@ fn print_balances( ), ) }), - }, - ) + } + }) .filter_map(|(o, s)| match target { Some(t) if o == *t => Some(s), Some(_) => None, 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 73961a1e28..94f7b94114 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,41 +446,21 @@ 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, amount + from, to, amount ), }, )) @@ -492,32 +472,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, amount + "Minting a coin failed: account {}, amount {}", + account, amount ), }, )) @@ -529,32 +497,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, amount + "Burning a coin failed: account {}, amount {}", + account, amount ), }, )) 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 57d8257c3e..ea6ac4da97 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 = "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, - "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 9859427282..c1708047b4 100644 --- a/core/src/types/address.rs +++ b/core/src/types/address.rs @@ -52,10 +52,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 @@ -82,18 +80,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. @@ -231,11 +227,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() } @@ -245,6 +236,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 @@ -318,6 +313,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", @@ -325,15 +324,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()), )), @@ -530,12 +520,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 @@ -546,22 +530,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 { @@ -577,12 +549,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(), } ) } @@ -857,30 +828,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 c865cfe9a2..abb6fb7d80 100644 --- a/core/src/types/token.rs +++ b/core/src/types/token.rs @@ -11,7 +11,9 @@ use serde::{Deserialize, Serialize}; use thiserror::Error; use crate::ibc::applications::transfer::Amount as IbcAmount; -use crate::types::address::{masp, Address, DecodeError as AddressError}; +use crate::types::address::{ + masp, Address, DecodeError as AddressError, InternalAddress, +}; use crate::types::hash::Hash; use crate::types::storage::{DbKeySeg, Key, KeySeg}; @@ -331,6 +333,8 @@ impl TryFrom for Amount { /// Key segment for a balance key pub const BALANCE_STORAGE_KEY: &str = "balance"; +/// Key segment for multitoken minter +pub const MINTER_STORAGE_KEY: &str = "minter"; /// Key segment for head shielded transaction pointer key pub const HEAD_TX_KEY: &str = "head-tx"; /// Key segment prefix for shielded transaction key @@ -357,23 +361,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>( @@ -427,43 +445,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 owner. -pub fn is_any_multitoken_balance_key(key: &Key) -> Option<(Key, &Address)> { - match key.segments.first() { - Some(DbKeySeg::AddressSeg(_)) => multitoken_balance_owner(key), - _ => None, - } -} - -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, @@ -492,7 +483,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: Amount, /// 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 c8e74979bd..2b9e0910c9 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; @@ -11,7 +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::{is_any_token_balance_key, Amount}; +use namada_core::types::token::{self, Amount}; use super::Error; use crate::ledger::native_vp::CtxPreStorageRead; @@ -115,48 +115,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_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 @@ -250,16 +313,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 47f0a7ba99..88ab1e50f8 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}; @@ -2516,7 +2514,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::whole(100); wl_storage .write_log @@ -2669,7 +2667,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::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 7f2f1ddaca..b417e6ea0c 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 be85f0609e..ec2ce420ea 100644 --- a/shared/src/ledger/tx.rs +++ b/shared/src/ledger/tx.rs @@ -1061,13 +1061,17 @@ where // 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)), }; @@ -1247,13 +1251,17 @@ where token_exists_or_err(token.clone(), args.tx.force, client).await?; // 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)), }; diff --git a/tests/src/e2e/ibc_tests.rs b/tests/src/e2e/ibc_tests.rs index 6f02918d6d..bf8fa4e7ed 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 tx_args = [ @@ -792,7 +788,7 @@ fn transfer_received_token( "--token", NAM, "--sub-prefix", - &sub_prefix, + &ibc_token, "--amount", "50000", "--gas-amount", @@ -827,13 +823,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, @@ -842,7 +832,7 @@ fn transfer_back( NAM, &Amount::whole(50000), port_channel_id_b, - Some(sub_prefix), + Some(ibc_token), None, )?; let packet = match get_event(test_b, height)? { @@ -1271,7 +1261,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 @@ -1284,8 +1274,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", @@ -1294,11 +1283,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(); @@ -1316,8 +1305,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)); @@ -1328,11 +1316,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(); @@ -1345,11 +1333,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(); @@ -1372,7 +1360,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 @@ -1385,8 +1373,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", @@ -1395,11 +1382,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 41f1b7dcde..29cff91ad5 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 ea23f001e1..11d1c87584 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}; @@ -1169,10 +1171,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 @@ -1228,7 +1230,7 @@ mod tests { assert_eq!(balance, Some(Amount::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") @@ -1251,13 +1253,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::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| { @@ -1270,11 +1284,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"); @@ -1300,11 +1310,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); @@ -1312,14 +1319,10 @@ mod tests { env.wl_storage.read(&balance_key).expect("read error") }); assert_eq!(balance, Some(Amount::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::whole(100))); + assert_eq!(minted, Some(Amount::whole(0))); } #[test] @@ -1380,20 +1383,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::whole(100))); + let minted: Option = tx_host_env::with(|env| { + env.wl_storage.read(&minted_key).expect("read error") + }); + assert_eq!(minted, Some(Amount::whole(100))); } #[test] @@ -1420,7 +1426,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::whole(100).try_to_vec().unwrap(); tx_host_env::with(|env| { @@ -1472,7 +1478,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); @@ -1509,9 +1516,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::whole(100).try_to_vec().unwrap(); tx_host_env::with(|env| { @@ -1523,8 +1534,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(), @@ -1568,7 +1577,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); @@ -1670,9 +1680,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")); } @@ -1759,9 +1769,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 251d9a21f9..fe93354e67 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: Amount, key: &Option, shielded_hash: &Option, @@ -22,67 +22,24 @@ pub fn transfer( ) -> TxResult { if 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 = match src { - Address::Internal(InternalAddress::IbcMint) => Some(Amount::max()), - Address::Internal(InternalAddress::IbcBurn) => { - log_string("invalid transfer from the burn address"); - unreachable!() - } - _ => ctx.read(&src_key)?, - }; + 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); - 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(), - }; + let mut dest_bal: Amount = ctx.read(&dest_key)?.unwrap_or_default(); dest_bal.receive(&amount); if src != dest { - 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)?; } } @@ -132,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_balance_key(src_key); - let src_bal: Option = match src_owner { - Some(Address::Internal(InternalAddress::IbcMint)) => { - Some(Amount::max()) - } - 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 86eb66c792..96156d79f4 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> { 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_any_multitoken_balance_key(key) + } else if let Some((_, address)) = token::is_multitoken_balance_key(key) { Self::Token(address) } 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 8ccc2c9053..3ded9e94c1 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 = 0; 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,24 +74,9 @@ fn token_checks( } Some(owner) => { // accumulate the change - let pre: token::Amount = match owner { - Address::Internal(InternalAddress::IbcMint) => { - token::Amount::max() - } - Address::Internal(InternalAddress::IbcBurn) => { - token::Amount::default() - } - _ => ctx.read_pre(key)?.unwrap_or_default(), - }; - let post: token::Amount = match owner { - Address::Internal(InternalAddress::IbcMint) => { - ctx.read_temp(key)?.unwrap_or_else(token::Amount::max) - } - Address::Internal(InternalAddress::IbcBurn) => { - ctx.read_temp(key)?.unwrap_or_default() - } - _ => ctx.read_post(key)?.unwrap_or_default(), - }; + 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 diff --git a/wasm/wasm_source/src/vp_user.rs b/wasm/wasm_source/src/vp_user.rs index 3c1f8bbcbe..1e88ec5f0d 100644 --- a/wasm/wasm_source/src/vp_user.rs +++ b/wasm/wasm_source/src/vp_user.rs @@ -26,8 +26,7 @@ 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_any_multitoken_balance_key(key) + } else if let Some((_, address)) = token::is_multitoken_balance_key(key) { Self::Token(address) } 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 c896515daf..1200952f3d 100644 --- a/wasm/wasm_source/src/vp_validator.rs +++ b/wasm/wasm_source/src/vp_validator.rs @@ -28,8 +28,7 @@ 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_any_multitoken_balance_key(key) + } else if let Some((_, address)) = token::is_multitoken_balance_key(key) { Self::Token(address) } else if proof_of_stake::is_pos_key(key) { From 9c6f1a66bc05b9f590a6a66b1648981a3b429ddb Mon Sep 17 00:00:00 2001 From: yito88 Date: Thu, 8 Jun 2023 22:53:47 +0900 Subject: [PATCH 03/14] change to multitokens --- apps/src/lib/cli.rs | 17 - apps/src/lib/client/rpc.rs | 43 +- .../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 | 8 +- core/src/types/token.rs | 88 ++--- shared/src/ledger/args.rs | 6 - shared/src/ledger/ibc/vp/context.rs | 30 +- shared/src/ledger/ibc/vp/token.rs | 374 ------------------ shared/src/ledger/protocol/mod.rs | 11 +- 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 | 369 ----------------- 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 | 20 +- wasm/wasm_source/src/vp_testnet_faucet.rs | 14 +- wasm/wasm_source/src/vp_token.rs | 46 +-- wasm/wasm_source/src/vp_user.rs | 21 +- wasm/wasm_source/src/vp_validator.rs | 21 +- wasm_for_tests/wasm_source/src/lib.rs | 47 +-- 33 files changed, 209 insertions(+), 1321 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 c190ad7bb8..2b9676a0f8 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -2329,7 +2329,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"); @@ -3009,7 +3008,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(), @@ -3023,7 +3021,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 = AMOUNT.parse(matches); let tx_code_path = PathBuf::from(TX_TRANSFER_WASM); Self { @@ -3031,7 +3028,6 @@ pub mod args { source, target, token, - sub_prefix, amount, native_token: (), tx_code_path, @@ -3049,7 +3045,6 @@ pub mod args { to produce the signature.", )) .arg(TOKEN.def().about("The transfer token.")) - .arg(SUB_PREFIX.def().about("The token's sub prefix.")) .arg(AMOUNT.def().about("The amount to transfer in decimal.")) } } @@ -3061,7 +3056,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, @@ -3078,7 +3072,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); @@ -3090,7 +3083,6 @@ pub mod args { source, receiver, token, - sub_prefix, amount, port_id, channel_id, @@ -3110,7 +3102,6 @@ pub mod args { "The receiver address on the destination chain as string.", )) .arg(TOKEN.def().about("The transfer token.")) - .arg(SUB_PREFIX.def().about("The token's sub prefix.")) .arg(AMOUNT.def().about("The amount to transfer in decimal.")) .arg(PORT_ID.def().about("The port ID.")) .arg(CHANNEL_ID.def().about("The channel ID.")) @@ -3787,7 +3778,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, } } } @@ -3798,13 +3788,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, } } @@ -3825,11 +3813,6 @@ pub mod args { "Whether not to automatically perform conversions.", ), ) - .arg( - SUB_PREFIX.def().about( - "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 13b6dfe90c..782cc94786 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -271,28 +271,10 @@ 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()), - }; + let key = token::balance_key(&token, &owner.address().unwrap()); let token_alias = lookup_alias(wallet, &token); match query_storage_value::(client, &key).await { - Some(balance) => match &args.sub_prefix { - Some(sub_prefix) => { - println!( - "{} with {}: {}", - token_alias, sub_prefix, balance - ); - } - None => println!("{}: {}", token_alias, balance), - }, + Some(balance) => println!("{}: {}", token_alias, balance), None => { println!("No {} balance found for {}", token_alias, owner) } @@ -477,27 +459,16 @@ 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, + ": {}, owned by {}", balance, lookup_alias(wallet, owner) ), - )), - None => token::is_any_token_balance_key(&key).map(|owner| { - ( - owner.clone(), - format!( - ": {}, owned by {}", - balance, - 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 8948b60b5f..98e5361082 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -20,7 +20,7 @@ use namada::proof_of_stake::{ use namada::types::address::Address; 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, }; @@ -697,7 +697,7 @@ where .expect("PoS inflation rate 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 94f7b94114..db7d0464e3 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 8cccc2d3a6..5dccebc3fd 100644 --- a/core/src/ledger/storage_api/token.rs +++ b/core/src/ledger/storage_api/token.rs @@ -5,8 +5,8 @@ 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_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. @@ -31,7 +31,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) } @@ -91,7 +91,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 abb6fb7d80..79081f07b4 100644 --- a/core/src/types/token.rs +++ b/core/src/types/token.rs @@ -343,51 +343,35 @@ 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"; /// 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") } @@ -401,22 +385,33 @@ 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, } } /// Check if the given storage key is balance key for unspecified token. If it -/// is, returns the owner. -pub fn is_any_token_balance_key(key: &Key) -> Option<&Address> { +/// is, returns the token and owner. +pub fn is_any_token_balance_key(key: &Key) -> Option<(&Address, &Address)> { match &key.segments[..] { [ - DbKeySeg::AddressSeg(_), - DbKeySeg::StringSeg(key), + DbKeySeg::AddressSeg(addr), + DbKeySeg::AddressSeg(token), + DbKeySeg::StringSeg(balance), DbKeySeg::AddressSeg(owner), - ] if key == BALANCE_STORAGE_KEY => Some(owner), + ] if *addr == Address::Internal(InternalAddress::Multitoken) + && balance == BALANCE_STORAGE_KEY => + { + Some((token, owner)) + } _ => None, } } @@ -431,33 +426,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 } } @@ -482,8 +456,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: Amount, /// The unused storage location at which to place TxId diff --git a/shared/src/ledger/args.rs b/shared/src/ledger/args.rs index ad1436d3be..23151bfcc9 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: token::Amount, /// Native token address @@ -136,8 +134,6 @@ pub struct TxIbcTransfer { pub receiver: String, /// Transferred token address pub token: C::Address, - /// Transferred token address - pub sub_prefix: Option, /// Transferred token amount pub amount: token::Amount, /// Port ID @@ -306,8 +302,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 2b9e0910c9..daf2246cbe 100644 --- a/shared/src/ledger/ibc/vp/context.rs +++ b/shared/src/ledger/ibc/vp/context.rs @@ -118,17 +118,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"); @@ -153,10 +146,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) @@ -164,7 +157,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) @@ -181,7 +174,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) @@ -193,10 +186,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) @@ -204,7 +197,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) @@ -318,7 +311,6 @@ where _src: &Address, _dest: &Address, _token: &Address, - _sub_prefix: Option
, _amount: Amount, ) -> Result<(), Self::Error> { unimplemented!("Validation doesn't transfer") @@ -327,7 +319,7 @@ where fn mint_token( &mut self, _target: &Address, - _sub_prefix: &Address, + _token: &Address, _amount: Amount, ) -> Result<(), Self::Error> { unimplemented!("Validation doesn't mint") @@ -336,7 +328,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 f75ded64e6..0000000000 --- a/shared/src/ledger/ibc/vp/token.rs +++ /dev/null @@ -1,374 +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 == 0) { - 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().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().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 b417e6ea0c..8a7721b655 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/tx.rs b/shared/src/ledger/tx.rs index ec2ce420ea..17cc702c7d 100644 --- a/shared/src/ledger/tx.rs +++ b/shared/src/ledger/tx.rs @@ -45,7 +45,7 @@ use crate::types::control_flow::{time, ProceedOrElse}; use crate::types::hash::Hash; 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}; @@ -1060,21 +1060,7 @@ where 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, @@ -1091,13 +1077,8 @@ where .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 token = Coin { - denom, + denom: token.to_string(), amount: args.amount.to_string(), }; @@ -1250,22 +1231,9 @@ where // 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)), - }; - check_balance_too_low_err::( + let balance_key = token::balance_key(&token, &source); + + check_balance_too_low_err( &token, &source, args.amount, @@ -1381,7 +1349,6 @@ where source: source.clone(), target: target.clone(), token: token.clone(), - sub_prefix: sub_prefix.clone(), 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 0441532c95..521c4d3db4 100644 --- a/tests/src/e2e.rs +++ b/tests/src/e2e.rs @@ -15,6 +15,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 bf8fa4e7ed..6cc9489621 100644 --- a/tests/src/e2e/ibc_tests.rs +++ b/tests/src/e2e/ibc_tests.rs @@ -718,7 +718,6 @@ fn transfer_token( &Amount::whole(100000), port_channel_id_a, None, - None, )?; let packet = match get_event(test_a, height)? { Some(IbcEvent::SendPacket(event)) => event.packet, @@ -786,8 +785,6 @@ fn transfer_received_token( "--target", ALBERT, "--token", - NAM, - "--sub-prefix", &ibc_token, "--amount", "50000", @@ -829,10 +826,9 @@ fn transfer_back( test_b, BERTHA, &receiver, - NAM, + ibc_token, &Amount::whole(50000), port_channel_id_b, - Some(ibc_token), None, )?; let packet = match get_event(test_b, height)? { @@ -891,7 +887,6 @@ fn transfer_timeout( NAM, &Amount::whole(100000), port_channel_id_a, - None, Some(Duration::new(5, 0)), )?; let packet = match get_event(test_a, height)? { @@ -1025,7 +1020,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)); @@ -1053,11 +1047,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"); @@ -1277,17 +1267,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(); @@ -1310,34 +1292,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(); @@ -1376,17 +1342,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 4b07075fe8..1a155f0c9e 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -425,7 +425,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::Amount::whole(10), key: None, shielded: None, diff --git a/tests/src/e2e/multitoken_tests.rs b/tests/src/e2e/multitoken_tests.rs deleted file mode 100644 index 460395cc59..0000000000 --- a/tests/src/e2e/multitoken_tests.rs +++ /dev/null @@ -1,369 +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::from(100_000_000); - helpers::mint_red_tokens( - &test, - &rpc_addr, - &multitoken_vp_addr, - &albert_addr, - &albert_starting_red_balance, - )?; - - let transfer_amount = token::Amount::from(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::from(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::from(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::from(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::from(100_000_000); - helpers::mint_red_tokens( - &test, - &rpc_addr, - &multitoken_vp_addr, - &albert_addr, - &albert_starting_red_balance, - )?; - - let transfer_amount = token::Amount::from(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::from(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::from(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::from(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 7008910b5e..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 std::str::FromStr; - -use borsh::BorshSerialize; -use color_eyre::eyre::Result; -use eyre::Context; -use namada_core::types::address::Address; -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(); - 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) - .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 29cff91ad5..90b6f4c23e 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 11d1c87584..42e723f0f2 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}; @@ -1254,15 +1253,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::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) @@ -1385,7 +1381,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 fe93354e67..65bdedbcb3 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: Amount, key: &Option, shielded_hash: &Option, shielded: &Option, ) -> TxResult { if 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 6d710298b2..1b08bef8f2 100644 --- a/wasm/wasm_source/src/tx_bond.rs +++ b/wasm/wasm_source/src/tx_bond.rs @@ -96,7 +96,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 1727035d2f..d8899b6c5f 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 ef4c355b45..b671b8fffe 100644 --- a/wasm/wasm_source/src/tx_unbond.rs +++ b/wasm/wasm_source/src/tx_unbond.rs @@ -102,12 +102,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 a0d93851fc..6be4322919 100644 --- a/wasm/wasm_source/src/tx_withdraw.rs +++ b/wasm/wasm_source/src/tx_withdraw.rs @@ -106,12 +106,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 96156d79f4..3bf7dee192 100644 --- a/wasm/wasm_source/src/vp_implicit.rs +++ b/wasm/wasm_source/src/vp_implicit.rs @@ -28,9 +28,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(address) } else if proof_of_stake::is_pos_key(key) { @@ -339,7 +337,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); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { @@ -349,7 +347,6 @@ mod tests { &source, address, &token, - None, amount, &None, &None, @@ -414,7 +411,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| { @@ -487,7 +484,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); @@ -538,7 +535,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| { @@ -548,7 +545,6 @@ mod tests { address, &target, &token, - None, amount, &None, &None, @@ -588,7 +584,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); @@ -600,7 +596,6 @@ mod tests { address, &target, &token, - None, amount, &None, &None, @@ -646,7 +641,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); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { @@ -657,7 +652,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 430cb55b21..43f1892d84 100644 --- a/wasm/wasm_source/src/vp_testnet_faucet.rs +++ b/wasm/wasm_source/src/vp_testnet_faucet.rs @@ -40,7 +40,8 @@ fn validate_tx( } for key in keys_changed.iter() { - let is_valid = if let Some(owner) = token::is_any_token_balance_key(key) + let is_valid = if let Some((_, owner)) = + token::is_any_token_balance_key(key) { if owner == &addr { let pre: token::Amount = ctx.read_pre(key)?.unwrap_or_default(); @@ -151,7 +152,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); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { @@ -161,7 +162,6 @@ mod tests { &source, address, &token, - None, amount, &None, &None, @@ -305,13 +305,13 @@ 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(); // 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(); @@ -347,7 +347,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(); // Construct a PoW solution like a client would @@ -362,7 +362,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 3ded9e94c1..5b8a8ad329 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 = 0; 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,29 +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 < 0 - && !(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 == 0) + // The total change should be validated by multitoken VP + Ok(true) } #[cfg(test)] @@ -222,7 +224,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 1e88ec5f0d..7014052638 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(address) } else if proof_of_stake::is_pos_key(key) { Self::PoS @@ -233,7 +230,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); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { @@ -243,7 +240,6 @@ mod tests { &source, address, &token, - None, amount, &None, &None, @@ -281,7 +277,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| { @@ -291,7 +287,6 @@ mod tests { address, &target, &token, - None, amount, &None, &None, @@ -331,7 +326,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); @@ -343,7 +338,6 @@ mod tests { address, &target, &token, - None, amount, &None, &None, @@ -414,7 +408,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| { @@ -487,7 +481,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); @@ -538,7 +532,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); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { @@ -549,7 +543,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 1200952f3d..aa2f446f70 100644 --- a/wasm/wasm_source/src/vp_validator.rs +++ b/wasm/wasm_source/src/vp_validator.rs @@ -26,10 +26,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(address) } else if proof_of_stake::is_pos_key(key) { Self::PoS @@ -242,7 +239,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); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { @@ -252,7 +249,6 @@ mod tests { &source, address, &token, - None, amount, &None, &None, @@ -290,7 +286,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| { @@ -300,7 +296,6 @@ mod tests { address, &target, &token, - None, amount, &None, &None, @@ -340,7 +335,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); @@ -352,7 +347,6 @@ mod tests { address, &target, &token, - None, amount, &None, &None, @@ -423,7 +417,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| { @@ -502,7 +496,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); @@ -559,7 +553,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); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { @@ -570,7 +564,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 a37039e9c4..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); - 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 efc48b74e8e890e6c8704663dee20693a3a3adf7 Mon Sep 17 00:00:00 2001 From: yito88 Date: Fri, 9 Jun 2023 00:01:29 +0900 Subject: [PATCH 04/14] 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 3b9c48cfa74b3d7e605aa71a2e9ee4900b902526 Mon Sep 17 00:00:00 2001 From: yito88 Date: Tue, 13 Jun 2023 10:23:02 +0900 Subject: [PATCH 05/14] fix balance print --- apps/src/lib/client/rpc.rs | 91 +++++++++++++++++++++----------------- 1 file changed, 51 insertions(+), 40 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 782cc94786..d9ebfe397d 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -268,7 +268,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 key = token::balance_key(&token, &owner.address().unwrap()); @@ -281,20 +284,15 @@ 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( - wallet, - balances, - &token, - owner.address().as_ref(), - ); - } + let balances = + query_storage_prefix::(client, &prefix).await; + if let Some(balances) = balances { + print_balances( + wallet, + balances, + None, + owner.address().as_ref(), + ); } } (Some(token), None) => { @@ -302,18 +300,14 @@ pub async fn query_transparent_balance< let balances = query_storage_prefix::(client, &prefix).await; if let Some(balances) = balances { - print_balances(wallet, balances, &token, None); + print_balances(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(wallet, balances, &token, None); - } + let balances = + query_storage_prefix::(client, &prefix).await; + if let Some(balances) = balances { + print_balances(wallet, balances, None, None); } } } @@ -448,19 +442,18 @@ pub async fn query_pinned_balance< fn print_balances( 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 {}", @@ -470,25 +463,43 @@ 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(), } } } From eb84e365c95f4c46a83bca0bea046a4202090d02 Mon Sep 17 00:00:00 2001 From: yito88 Date: Thu, 15 Jun 2023 16:15:53 +0900 Subject: [PATCH 06/14] 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 db7d0464e3..f997893e5f 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() @@ -472,10 +466,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() @@ -497,11 +489,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) @@ -555,16 +545,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 34c96773245b9cd314b11523797085839f23c233 Mon Sep 17 00:00:00 2001 From: yito88 Date: Thu, 15 Jun 2023 16:44:46 +0900 Subject: [PATCH 07/14] 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 c1708047b4..9ddfc0f601 100644 --- a/core/src/types/address.rs +++ b/core/src/types/address.rs @@ -53,7 +53,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 @@ -88,7 +88,7 @@ mod internal { "ano::Replay Protection "; pub const MULTITOKEN: &str = "ano::Multitoken "; - pub const MINT: &str = + pub const MINTED: &str = "ano::Multitoken Mint Address "; } @@ -239,7 +239,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 @@ -316,7 +316,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", @@ -533,7 +535,7 @@ pub enum InternalAddress { /// Multitoken Multitoken, /// Minted multitoken address - Mint, + Minted, } impl Display for InternalAddress { @@ -553,7 +555,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(), } ) } @@ -832,8 +834,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), @@ -847,7 +849,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 79081f07b4..8773255c3e 100644 --- a/core/src/types/token.rs +++ b/core/src/types/token.rs @@ -372,7 +372,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") } @@ -429,7 +429,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 8a7721b655..5aef2a09b8 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 fa377e356399fdf11fa70dd340fd145f81111730 Mon Sep 17 00:00:00 2001 From: yito88 Date: Mon, 19 Jun 2023 22:26:14 +0900 Subject: [PATCH 08/14] 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 5dccebc3fd..ae4f6e0cd1 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 9ddfc0f601..7a881c88e2 100644 --- a/core/src/types/address.rs +++ b/core/src/types/address.rs @@ -52,8 +52,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 @@ -88,8 +86,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. @@ -239,7 +235,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 @@ -316,9 +311,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", @@ -534,8 +526,6 @@ pub enum InternalAddress { ReplayProtection, /// Multitoken Multitoken, - /// Minted multitoken address - Minted, } impl Display for InternalAddress { @@ -555,7 +545,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(), } ) } @@ -833,9 +822,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), @@ -849,7 +837,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 8773255c3e..18515f0308 100644 --- a/core/src/types/token.rs +++ b/core/src/types/token.rs @@ -335,6 +335,8 @@ impl TryFrom for Amount { pub const BALANCE_STORAGE_KEY: &str = "balance"; /// 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 key pub const HEAD_TX_KEY: &str = "head-tx"; /// Key segment prefix for shielded transaction key @@ -372,12 +374,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, @@ -426,12 +428,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 5aef2a09b8..5fe9bb84bd 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 b7895dfc95..16ccdbab87 100644 --- a/tests/src/e2e/helpers.rs +++ b/tests/src/e2e/helpers.rs @@ -24,8 +24,7 @@ use namada_apps::config::utils::convert_tm_addr_to_socket_addr; use namada_apps::config::{Config, TendermintMode}; 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 5b8a8ad329..621890ffec 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 a20738daa899a162f699430a3ef33d27ae3c36ef Mon Sep 17 00:00:00 2001 From: yito88 Date: Thu, 29 Jun 2023 11:14:54 +0200 Subject: [PATCH 09/14] 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 7bad2a272a5f0e091217cc3b367af4d988f83bdd Mon Sep 17 00:00:00 2001 From: yito88 Date: Sun, 2 Jul 2023 15:32:40 +0200 Subject: [PATCH 10/14] change eth_bridge balance keys to multitoken keys --- .../lib/node/ledger/shell/finalize_block.rs | 18 +- core/src/ledger/eth_bridge/storage/mod.rs | 20 ++ .../eth_bridge/storage/wrapped_erc20s.rs | 287 +++++++----------- core/src/types/address.rs | 35 ++- .../transactions/ethereum_events/events.rs | 108 ++++--- .../transactions/ethereum_events/mod.rs | 22 +- .../ethereum_bridge/bridge_pool_vp.rs | 22 +- .../ledger/native_vp/ethereum_bridge/vp.rs | 35 +-- shared/src/ledger/protocol/mod.rs | 3 +- shared/src/ledger/queries/shell/eth_bridge.rs | 10 +- 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 +- wasm/wasm_source/src/tx_bridge_pool.rs | 7 +- 15 files changed, 281 insertions(+), 328 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 98e5361082..1b5bf202cc 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -20,7 +20,7 @@ use namada::proof_of_stake::{ use namada::types::address::Address; 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, }; @@ -697,9 +697,9 @@ where .expect("PoS inflation rate 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)?; @@ -1616,10 +1616,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..2ebd23266c 100644 --- a/core/src/ledger/eth_bridge/storage/wrapped_erc20s.rs +++ b/core/src/ledger/eth_bridge/storage/wrapped_erc20s.rs @@ -1,68 +1,15 @@ //! 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}; +use crate::types::storage::{self, DbKeySeg}; +use crate::types::token::{balance_key, minted_balance_key, MINTED_STORAGE_KEY}; -#[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") +/// 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 +35,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 +66,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 +113,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 +200,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 +221,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 +235,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 +262,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 +293,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 +303,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/types/address.rs b/core/src/types/address.rs index 7a881c88e2..9f8cb0c9b5 100644 --- a/core/src/types/address.rs +++ b/core/src/types/address.rs @@ -96,6 +96,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)] @@ -229,6 +231,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() } @@ -326,6 +333,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", @@ -522,6 +546,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 @@ -543,6 +569,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(), } @@ -821,6 +848,7 @@ pub mod testing { InternalAddress::IbcToken(_) => {} InternalAddress::EthBridge => {} InternalAddress::EthBridgePool => {} + InternalAddress::Erc20(_) => {} InternalAddress::ReplayProtection => {} InternalAddress::Multitoken => {} /* Add new addresses in the * `prop_oneof` below. */ @@ -835,13 +863,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!( @@ -856,4 +884,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 29c5c2b9f2..af71bfdc5c 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,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, minted_balance_key, minter_key}; use crate::parameters::read_native_erc20_address; use crate::protocol::transactions::update; @@ -231,8 +229,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, @@ -248,7 +246,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, @@ -263,6 +261,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) } @@ -466,12 +469,9 @@ where let sender_balance_key = balance_key(&nam(), &transfer.transfer.sender); (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| { @@ -507,15 +507,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); })?; @@ -648,12 +648,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( @@ -661,8 +657,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( @@ -670,11 +665,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); @@ -749,7 +746,7 @@ mod tests { assert_eq!( stored_keys_count(&wl_storage), - initial_stored_keys_count + 2 + initial_stored_keys_count + 3 ); } @@ -775,13 +772,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(); @@ -803,7 +800,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], @@ -842,10 +839,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())); @@ -976,20 +975,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 = @@ -1118,12 +1112,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, @@ -1152,14 +1146,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"); @@ -1178,19 +1172,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() ); @@ -1206,13 +1200,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 e9c97a0b1f..2b413315ab 100644 --- a/ethereum_bridge/src/protocol/transactions/ethereum_events/mod.rs +++ b/ethereum_bridge/src/protocol/transactions/ethereum_events/mod.rs @@ -284,7 +284,7 @@ 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 +335,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 +343,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 +376,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 +385,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)?, @@ -434,7 +435,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![ @@ -443,8 +444,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/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 3c5b670017..79b5b5743e 100644 --- a/shared/src/ledger/native_vp/ethereum_bridge/vp.rs +++ b/shared/src/ledger/native_vp/ethereum_bridge/vp.rs @@ -440,6 +440,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; @@ -566,10 +567,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); @@ -580,10 +580,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"), ), @@ -599,17 +599,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"), ), @@ -626,8 +626,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/protocol/mod.rs b/shared/src/ledger/protocol/mod.rs index 5fe9bb84bd..6c5f4539b5 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/eth_bridge.rs b/shared/src/ledger/queries/shell/eth_bridge.rs index 1cbeb31b36..29419167c0 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::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/tests/src/e2e/eth_bridge_tests.rs b/tests/src/e2e/eth_bridge_tests.rs index 8c547f3a7a..f36ed31b83 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 dedd88a1eb..4ea9242b20 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; @@ -172,16 +171,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", @@ -207,10 +203,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 16ccdbab87..b7895dfc95 100644 --- a/tests/src/e2e/helpers.rs +++ b/tests/src/e2e/helpers.rs @@ -24,7 +24,8 @@ use namada_apps::config::utils::convert_tm_addr_to_socket_addr; use namada_apps::config::{Config, TendermintMode}; 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 6432f88b5b..a90115f89a 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/wasm/wasm_source/src/tx_bridge_pool.rs b/wasm/wasm_source/src/tx_bridge_pool.rs index 9d3835231c..2ffda82b4a 100644 --- a/wasm/wasm_source/src/tx_bridge_pool.rs +++ b/wasm/wasm_source/src/tx_bridge_pool.rs @@ -18,7 +18,6 @@ fn apply_tx(ctx: &mut Ctx, signed: Tx) -> TxResult { payer, &bridge_pool::BRIDGE_POOL_ADDRESS, &address::nam(), - None, amount, &None, &None, @@ -38,7 +37,6 @@ fn apply_tx(ctx: &mut Ctx, signed: Tx) -> TxResult { sender, ð_bridge::ADDRESS, &address::nam(), - None, amount, &None, &None, @@ -46,13 +44,12 @@ fn apply_tx(ctx: &mut Ctx, signed: Tx) -> TxResult { )?; } else { // Otherwise we escrow ERC20 tokens. - let sub_prefix = wrapped_erc20s::sub_prefix(&asset); + let token = wrapped_erc20s::token(&asset); token::transfer( ctx, sender, &bridge_pool::BRIDGE_POOL_ADDRESS, - ð_bridge::ADDRESS, - Some(sub_prefix), + &token, amount, &None, &None, From ea5204789e1c92383f6f3da502efe83b02481b51 Mon Sep 17 00:00:00 2001 From: yito88 Date: Sun, 2 Jul 2023 19:07:01 +0200 Subject: [PATCH 11/14] revert unexpected changes --- core/src/ledger/eth_bridge/storage/wrapped_erc20s.rs | 4 +++- .../src/protocol/transactions/ethereum_events/mod.rs | 4 +++- tests/src/e2e/helpers.rs | 1 + 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/core/src/ledger/eth_bridge/storage/wrapped_erc20s.rs b/core/src/ledger/eth_bridge/storage/wrapped_erc20s.rs index 2ebd23266c..beb4a61723 100644 --- a/core/src/ledger/eth_bridge/storage/wrapped_erc20s.rs +++ b/core/src/ledger/eth_bridge/storage/wrapped_erc20s.rs @@ -5,7 +5,9 @@ use eyre::eyre; use crate::types::address::{Address, InternalAddress}; use crate::types::ethereum_events::EthAddress; use crate::types::storage::{self, DbKeySeg}; -use crate::types::token::{balance_key, minted_balance_key, MINTED_STORAGE_KEY}; +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 { diff --git a/ethereum_bridge/src/protocol/transactions/ethereum_events/mod.rs b/ethereum_bridge/src/protocol/transactions/ethereum_events/mod.rs index 2b413315ab..fd26db4a65 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::{balance_key, minted_balance_key, minter_key, Amount}; + use namada_core::types::token::{ + balance_key, minted_balance_key, minter_key, Amount, + }; use super::*; use crate::protocol::transactions::utils::GetVoters; diff --git a/tests/src/e2e/helpers.rs b/tests/src/e2e/helpers.rs index b7895dfc95..7e3ea20bdc 100644 --- a/tests/src/e2e/helpers.rs +++ b/tests/src/e2e/helpers.rs @@ -51,6 +51,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 20c54747cdef09e89f84db1b5e4ac6f4c808b0c9 Mon Sep 17 00:00:00 2001 From: yito88 Date: Tue, 4 Jul 2023 14:34:21 +0200 Subject: [PATCH 12/14] modify EthBridge VP and EthBridgePool VP for multitoken --- .../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 | 209 +++++------------- 4 files changed, 66 insertions(+), 237 deletions(-) delete mode 100644 shared/src/ledger/native_vp/ethereum_bridge/authorize.rs 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 79b5b5743e..a21deb41f9 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}; -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,161 +223,94 @@ 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 == 0 { - assert_eq!(balance_b_delta, 0); - tracing::debug!("Rejecting transaction as no balance change"); + if sender_balance_delta >= 0 { + assert!(receiver_balance_delta <= 0); + tracing::debug!( + "Rejecting transaction as no balance change or invalid change" + ); return Ok(None); } - if balance_a_post < 0 { + if sender_balance_post < 0 { tracing::debug!( - ?balance_a_post, + ?sender_balance_post, "Rejecting transaction as balance is negative" ); return Ok(None); } - if balance_b_post < 0 { + if receiver_balance_post < 0 { tracing::debug!( - ?balance_b_post, + ?receiver_balance_post, "Rejecting transaction as balance is negative" ); return Ok(None); } - if balance_a_delta < 0 { - 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( - u64::try_from(balance_b_delta) - .expect("This should not fail"), - ), - ))) - } else { - unreachable!() - } - } else { - assert!(balance_b_delta < 0); - 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( - u64::try_from(balance_a_delta) - .expect("This should not fail"), - ), - ))) - } else { - unreachable!() - } - } + Ok(Some(Amount::from( + u64::try_from(receiver_balance_delta).expect("This should not fail"), + ))) } /// Return the delta between `balance_pre` and `balance_post`, erroring if there From a3070c131f26a59fda5be61e1e9836d8546af1bb Mon Sep 17 00:00:00 2001 From: yito88 Date: Wed, 5 Jul 2023 11:02:15 +0200 Subject: [PATCH 13/14] 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 a21deb41f9..e972f50c3e 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 f36ed31b83..8c547f3a7a 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 8fa683b315e2c9caef1e02fb65e2675b4d925c83 Mon Sep 17 00:00:00 2001 From: yito88 Date: Wed, 5 Jul 2023 15:12:06 +0200 Subject: [PATCH 14/14] fix multitoken vp to check the minter --- core/src/types/token.rs | 17 ++ shared/src/ledger/native_vp/multitoken.rs | 188 ++++++++++++++++++++-- 2 files changed, 192 insertions(+), 13 deletions(-) diff --git a/core/src/types/token.rs b/core/src/types/token.rs index 18515f0308..5cc1a01315 100644 --- a/core/src/types/token.rs +++ b/core/src/types/token.rs @@ -428,6 +428,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 9c985b9dab..7d8a26eaa1 100644 --- a/shared/src/ledger/native_vp/multitoken.rs +++ b/shared/src/ledger/native_vp/multitoken.rs @@ -9,9 +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, + is_any_minted_balance_key, is_any_minter_key, is_any_token_balance_key, + minter_key, Amount, }; use crate::vm::WasmCacheAccess; @@ -72,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); } } @@ -94,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; @@ -106,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::{ @@ -249,16 +301,19 @@ 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 target_key = balance_key(&token, &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 minted_key = minted_balance_key(&token); let amount = Amount::whole(100); wl_storage .write_log @@ -267,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()) @@ -306,6 +361,13 @@ 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); @@ -325,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 @@ -462,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::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::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") + ); + } }