diff --git a/crates/core/src/ibc.rs b/crates/core/src/ibc.rs index fe29f61fe7..6a5c954cce 100644 --- a/crates/core/src/ibc.rs +++ b/crates/core/src/ibc.rs @@ -1,16 +1,52 @@ //! IBC-related data types +use std::collections::{BTreeMap, BTreeSet}; use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use data_encoding::{DecodePartial, HEXLOWER, HEXLOWER_PERMISSIVE}; pub use ibc::*; +use masp_primitives::transaction::components::ValueSum; +use masp_primitives::transaction::TransparentAddress; use namada_macros::BorshDeserializer; #[cfg(feature = "migrations")] use namada_migrations::*; use serde::{Deserialize, Serialize}; use super::address::HASH_LEN; +use crate::address::Address; +use crate::masp::TAddrData; +use crate::{storage, token}; + +/// Abstract IBC storage read interface +pub trait Read { + /// Storage error + type Err; + + /// Extract MASP transaction from IBC envelope + fn try_extract_masp_tx_from_envelope( + tx_data: &[u8], + ) -> Result, Self::Err>; + + /// Apply relevant IBC packets to the changed balances structure + fn apply_ibc_packet( + storage: &S, + tx_data: &[u8], + acc: ChangedBalances, + keys_changed: &BTreeSet, + ) -> Result; +} + +/// Balances changed by a transaction +#[derive(Default, Debug, Clone)] +pub struct ChangedBalances { + /// Map between MASP transparent address and namada types + pub decoder: BTreeMap, + /// Balances before the tx + pub pre: BTreeMap>, + /// Balances after the tx + pub post: BTreeMap>, +} /// IBC token hash derived from a denomination. #[derive( diff --git a/crates/ibc/src/lib.rs b/crates/ibc/src/lib.rs index 34b0a0a9b4..61855ec50a 100644 --- a/crates/ibc/src/lib.rs +++ b/crates/ibc/src/lib.rs @@ -30,9 +30,12 @@ pub mod vp; use std::cell::RefCell; use std::collections::BTreeSet; use std::fmt::Debug; +use std::marker::PhantomData; use std::rc::Rc; pub use actions::transfer_over_ibc; +use apps::transfer::types::packet::PacketData; +use apps::transfer::types::PORT_ID_STR; use borsh::BorshDeserialize; pub use context::common::IbcCommonContext; pub use context::nft_transfer::NftTransferContext; @@ -65,6 +68,7 @@ use ibc::core::channel::types::commitment::compute_ack_commitment; use ibc::core::channel::types::msgs::{ MsgRecvPacket as IbcMsgRecvPacket, PacketMsg, }; +use ibc::core::channel::types::timeout::TimeoutHeight; use ibc::core::entrypoint::{execute, validate}; use ibc::core::handler::types::error::ContextError; use ibc::core::handler::types::events::Error as RawIbcEventError; @@ -77,17 +81,26 @@ pub use ibc::*; use masp_primitives::transaction::Transaction as MaspTransaction; pub use msg::*; use namada_core::address::{self, Address}; -use namada_core::arith::checked; +use namada_core::arith::{checked, CheckedAdd, CheckedSub}; +use namada_core::ibc::apps::nft_transfer::types::packet::PacketData as NftPacketData; +use namada_core::ibc::core::channel::types::commitment::{ + compute_packet_commitment, AcknowledgementCommitment, PacketCommitment, +}; +use namada_core::ibc::ChangedBalances; +use namada_core::masp::{addr_taddr, ibc_taddr, TAddrData}; use namada_core::token::Amount; use namada_events::EmitEvents; use namada_state::{ - DBIter, Key, State, StorageError, StorageHasher, StorageRead, StorageWrite, - WlState, DB, + DBIter, Key, ResultExt, State, StorageError, StorageHasher, StorageRead, + StorageWrite, WlState, DB, }; +use namada_token::transaction::components::ValueSum; use namada_token::Transfer; pub use nft::*; +use primitives::Timestamp; use prost::Message; use thiserror::Error; +use trace::{convert_to_address, ibc_trace_for_nft, is_sender_chain_source}; use crate::storage::{ channel_counter_key, client_counter_key, connection_counter_key, @@ -124,6 +137,402 @@ pub enum Error { Verifier(namada_storage::Error), } +struct IbcTransferInfo { + src_port_id: PortId, + src_channel_id: ChannelId, + timeout_height: TimeoutHeight, + timeout_timestamp: Timestamp, + packet_data: Vec, + ibc_traces: Vec, + amount: Amount, + receiver: String, +} + +impl TryFrom for IbcTransferInfo { + type Error = namada_storage::Error; + + fn try_from( + message: IbcMsgTransfer, + ) -> std::result::Result { + let packet_data = serde_json::to_vec(&message.packet_data) + .map_err(namada_storage::Error::new)?; + let ibc_traces = vec![message.packet_data.token.denom.to_string()]; + let amount = message + .packet_data + .token + .amount + .try_into() + .into_storage_result()?; + let receiver = message.packet_data.receiver.to_string(); + Ok(Self { + src_port_id: message.port_id_on_a, + src_channel_id: message.chan_id_on_a, + timeout_height: message.timeout_height_on_b, + timeout_timestamp: message.timeout_timestamp_on_b, + packet_data, + ibc_traces, + amount, + receiver, + }) + } +} + +impl TryFrom for IbcTransferInfo { + type Error = namada_storage::Error; + + fn try_from( + message: IbcMsgNftTransfer, + ) -> std::result::Result { + let packet_data = serde_json::to_vec(&message.packet_data) + .map_err(namada_storage::Error::new)?; + let ibc_traces = message + .packet_data + .token_ids + .0 + .iter() + .map(|token_id| { + ibc_trace_for_nft(&message.packet_data.class_id, token_id) + }) + .collect(); + let receiver = message.packet_data.receiver.to_string(); + Ok(Self { + src_port_id: message.port_id_on_a, + src_channel_id: message.chan_id_on_a, + timeout_height: message.timeout_height_on_b, + timeout_timestamp: message.timeout_timestamp_on_b, + packet_data, + ibc_traces, + amount: Amount::from_u64(1), + receiver, + }) + } +} + +/// IBC storage `Keys/Read/Write` implementation +#[derive(Debug)] +pub struct Store(PhantomData); + +impl namada_core::ibc::Read for Store +where + S: StorageRead, +{ + type Err = namada_storage::Error; + + fn try_extract_masp_tx_from_envelope( + tx_data: &[u8], + ) -> Result, Self::Err> + { + let msg = decode_message(tx_data).into_storage_result().ok(); + let tx = if let Some(IbcMessage::Envelope(ref envelope)) = msg { + Some(extract_masp_tx_from_envelope(envelope).ok_or_else(|| { + namada_storage::Error::new_const( + "Missing MASP transaction in IBC message", + ) + })?) + } else { + None + }; + Ok(tx) + } + + fn apply_ibc_packet( + storage: &S, + tx_data: &[u8], + mut acc: ChangedBalances, + keys_changed: &BTreeSet, + ) -> Result { + let msg = decode_message(tx_data).into_storage_result().ok(); + match msg { + None => {} + // This event is emitted on the sender + Some(IbcMessage::Transfer(msg)) => { + // Get the packet commitment from post-storage that corresponds + // to this event + let ibc_transfer = IbcTransferInfo::try_from(msg.message)?; + let receiver = ibc_transfer.receiver.clone(); + let addr = TAddrData::Ibc(receiver.clone()); + acc.decoder.insert(ibc_taddr(receiver), addr); + acc = apply_transfer_msg( + storage, + acc, + &ibc_transfer, + keys_changed, + )?; + } + Some(IbcMessage::NftTransfer(msg)) => { + let ibc_transfer = IbcTransferInfo::try_from(msg.message)?; + let receiver = ibc_transfer.receiver.clone(); + let addr = TAddrData::Ibc(receiver.clone()); + acc.decoder.insert(ibc_taddr(receiver), addr); + acc = apply_transfer_msg( + storage, + acc, + &ibc_transfer, + keys_changed, + )?; + } + // This event is emitted on the receiver + Some(IbcMessage::Envelope(envelope)) => { + if let MsgEnvelope::Packet(PacketMsg::Recv(msg)) = *envelope { + if msg.packet.port_id_on_b.as_str() == PORT_ID_STR { + let packet_data = serde_json::from_slice::( + &msg.packet.data, + ) + .map_err(namada_storage::Error::new)?; + let receiver = packet_data.receiver.to_string(); + let addr = TAddrData::Ibc(receiver.clone()); + acc.decoder.insert(ibc_taddr(receiver), addr); + let ibc_denom = packet_data.token.denom.to_string(); + let amount = packet_data + .token + .amount + .try_into() + .into_storage_result()?; + acc = apply_recv_msg( + storage, + acc, + &msg, + vec![ibc_denom], + amount, + keys_changed, + )?; + } else { + let packet_data = + serde_json::from_slice::( + &msg.packet.data, + ) + .map_err(namada_storage::Error::new)?; + let receiver = packet_data.receiver.to_string(); + let addr = TAddrData::Ibc(receiver.clone()); + acc.decoder.insert(ibc_taddr(receiver), addr); + let ibc_traces = packet_data + .token_ids + .0 + .iter() + .map(|token_id| { + ibc_trace_for_nft( + &packet_data.class_id, + token_id, + ) + }) + .collect(); + acc = apply_recv_msg( + storage, + acc, + &msg, + ibc_traces, + Amount::from_u64(1), + keys_changed, + )?; + } + } + } + } + Ok(acc) + } +} + +fn check_ibc_transfer( + storage: &S, + ibc_transfer: &IbcTransferInfo, + keys_changed: &BTreeSet, +) -> namada_storage::Result<()> +where + S: StorageRead, +{ + let IbcTransferInfo { + src_port_id, + src_channel_id, + timeout_height, + timeout_timestamp, + packet_data, + .. + } = ibc_transfer; + let sequence = + get_last_sequence_send(storage, src_port_id, src_channel_id)?; + let commitment_key = + storage::commitment_key(src_port_id, src_channel_id, sequence); + + if !keys_changed.contains(&commitment_key) { + return Err(namada_storage::Error::new_alloc(format!( + "Expected IBC transfer didn't happen: Port ID {src_port_id}, \ + Channel ID {src_channel_id}, Sequence {sequence}" + ))); + } + + // The commitment is also validated in IBC VP. Make sure that for when + // IBC VP isn't triggered. + let actual: PacketCommitment = storage + .read_bytes(&commitment_key)? + .ok_or(namada_storage::Error::new_alloc(format!( + "Packet commitment doesn't exist: Port ID {src_port_id}, Channel \ + ID {src_channel_id}, Sequence {sequence}" + )))? + .into(); + let expected = compute_packet_commitment( + packet_data, + timeout_height, + timeout_timestamp, + ); + if actual != expected { + return Err(namada_storage::Error::new_alloc(format!( + "Packet commitment mismatched: Port ID {src_port_id}, Channel ID \ + {src_channel_id}, Sequence {sequence}" + ))); + } + + Ok(()) +} + +fn check_packet_receiving( + msg: &IbcMsgRecvPacket, + keys_changed: &BTreeSet, +) -> namada_storage::Result<()> { + let receipt_key = storage::receipt_key( + &msg.packet.port_id_on_b, + &msg.packet.chan_id_on_b, + msg.packet.seq_on_a, + ); + if !keys_changed.contains(&receipt_key) { + return Err(namada_storage::Error::new_alloc(format!( + "The packet has not been received: Port ID {}, Channel ID {}, \ + Sequence {}", + msg.packet.port_id_on_b, + msg.packet.chan_id_on_b, + msg.packet.seq_on_a, + ))); + } + Ok(()) +} + +// Apply the given transfer message to the changed balances structure +fn apply_transfer_msg( + storage: &S, + mut acc: ChangedBalances, + ibc_transfer: &IbcTransferInfo, + keys_changed: &BTreeSet, +) -> namada_storage::Result +where + S: StorageRead, +{ + check_ibc_transfer(storage, ibc_transfer, keys_changed)?; + + let IbcTransferInfo { + ibc_traces, + src_port_id, + src_channel_id, + amount, + receiver, + .. + } = ibc_transfer; + + let receiver = ibc_taddr(receiver.clone()); + for ibc_trace in ibc_traces { + let token = convert_to_address(ibc_trace).into_storage_result()?; + let delta = ValueSum::from_pair(token, *amount); + // If there is a transfer to the IBC account, then deduplicate the + // balance increase since we already accounted for it above + if is_sender_chain_source(ibc_trace, src_port_id, src_channel_id) { + let ibc_taddr = addr_taddr(address::IBC); + let post_entry = acc + .post + .get(&ibc_taddr) + .cloned() + .unwrap_or(ValueSum::zero()); + acc.post.insert( + ibc_taddr, + checked!(post_entry - &delta) + .map_err(namada_storage::Error::new)?, + ); + } + // Record an increase to the balance of a specific IBC receiver + let post_entry = + acc.post.get(&receiver).cloned().unwrap_or(ValueSum::zero()); + acc.post.insert( + receiver, + checked!(post_entry + &delta) + .map_err(namada_storage::Error::new)?, + ); + } + + Ok(acc) +} + +// Check if IBC message was received successfully in this state transition +fn is_receiving_success( + storage: &S, + dst_port_id: &PortId, + dst_channel_id: &ChannelId, + sequence: Sequence, +) -> namada_storage::Result +where + S: StorageRead, +{ + // Ensure that the event corresponds to the current changes to storage + let ack_key = storage::ack_key(dst_port_id, dst_channel_id, sequence); // If the receive is a success, then the commitment is unique + let succ_ack_commitment = compute_ack_commitment( + &AcknowledgementStatus::success(ack_success_b64()).into(), + ); + Ok(match storage.read_bytes(&ack_key)? { + // Success happens only if commitment equals the above + Some(value) => { + AcknowledgementCommitment::from(value) == succ_ack_commitment + } + // Acknowledgement key non-existence is failure + None => false, + }) +} + +// Apply the given write acknowledge to the changed balances structure +fn apply_recv_msg( + storage: &S, + mut acc: ChangedBalances, + msg: &IbcMsgRecvPacket, + ibc_traces: Vec, + amount: Amount, + keys_changed: &BTreeSet, +) -> namada_storage::Result +where + S: StorageRead, +{ + check_packet_receiving(msg, keys_changed)?; + + // If the transfer was a failure, then enable funds to + // be withdrawn from the IBC internal address + if is_receiving_success( + storage, + &msg.packet.port_id_on_b, + &msg.packet.chan_id_on_b, + msg.packet.seq_on_a, + )? { + for ibc_trace in ibc_traces { + // Get the received token + let token = received_ibc_token( + ibc_trace, + &msg.packet.port_id_on_a, + &msg.packet.chan_id_on_a, + &msg.packet.port_id_on_b, + &msg.packet.chan_id_on_b, + ) + .into_storage_result()?; + let delta = ValueSum::from_pair(token.clone(), amount); + // Enable funds to be taken from the IBC internal + // address and be deposited elsewhere + // Required for the IBC internal Address to release + // funds + let ibc_taddr = addr_taddr(address::IBC); + let pre_entry = + acc.pre.get(&ibc_taddr).cloned().unwrap_or(ValueSum::zero()); + acc.pre.insert( + ibc_taddr, + checked!(pre_entry + &delta) + .map_err(namada_storage::Error::new)?, + ); + } + } + Ok(acc) +} + /// IBC actions to handle IBC operations #[derive(Debug)] pub struct IbcActions<'a, C> diff --git a/crates/sdk/src/validation.rs b/crates/sdk/src/validation.rs index 193cf16a91..e5de512c5c 100644 --- a/crates/sdk/src/validation.rs +++ b/crates/sdk/src/validation.rs @@ -3,7 +3,7 @@ use namada_vm::wasm::run::VpEvalWasm; use namada_vm::wasm::VpCache; -use namada_vp::native_vp::{self, CtxPreStorageRead}; +use namada_vp::native_vp::{self, CtxPostStorageRead, CtxPreStorageRead}; use crate::state::StateRead; use crate::{eth_bridge, governance, ibc, parameters, proof_of_stake, token}; @@ -88,6 +88,8 @@ pub type MaspVp<'a, S, CA> = token::vp::MaspVp< Eval, ParamsPreStore<'a, S, CA>, GovPreStore<'a, S, CA>, + IbcPostStore<'a, S, CA>, + TokenKeys, >; /// Native ETH bridge VP @@ -115,6 +117,10 @@ pub type PosPreStore<'a, S, CA> = proof_of_stake::Store< CtxPreStorageRead<'a, 'a, S, VpCache, Eval>, >; +/// Ibc store implementation over the native posterior context +pub type IbcPostStore<'a, S, CA> = + ibc::Store, Eval>>; + /// Token store impl over IBC pseudo-execution storage pub type TokenStoreForIbcExec<'a, S, CA> = token::Store< ibc::vp::context::PseudoExecutionStorage< diff --git a/crates/shielded_token/src/vp.rs b/crates/shielded_token/src/vp.rs index 9d30bbcfc7..cdcbe48243 100644 --- a/crates/shielded_token/src/vp.rs +++ b/crates/shielded_token/src/vp.rs @@ -12,30 +12,25 @@ use masp_primitives::transaction::components::{ I128Sum, TxIn, TxOut, ValueSum, }; use masp_primitives::transaction::{Transaction, TransparentAddress}; -use namada_core::address::Address; +use namada_core::address::{self, Address}; use namada_core::arith::{checked, CheckedAdd, CheckedSub}; use namada_core::booleans::BoolResultUnitExt; -use namada_core::borsh::BorshSerializeExt; use namada_core::collections::HashSet; -use namada_core::ibc::apps::nft_transfer::types::msgs::transfer::MsgTransfer as IbcMsgNftTransfer; -use namada_core::ibc::apps::nft_transfer::types::packet::PacketData as NftPacketData; -use namada_core::ibc::apps::transfer::types::msgs::transfer::MsgTransfer as IbcMsgTransfer; -use namada_core::ibc::apps::transfer::types::packet::PacketData; -use namada_core::masp::{addr_taddr, encode_asset_type, ibc_taddr, MaspEpoch}; +use namada_core::masp::{addr_taddr, encode_asset_type, MaspEpoch, TAddrData}; use namada_core::storage::Key; use namada_core::token::MaspDigitPos; use namada_core::uint::I320; -use namada_core::{governance, parameters, token}; +use namada_core::{governance, ibc, parameters, token}; use namada_gas::GasMetering; use namada_state::{ ConversionState, OptionExt, ResultExt, StateRead, StorageError, }; use namada_trans_token::read_denom; use namada_tx::BatchedTxRef; -use namada_vp::native_vp::{Ctx, CtxPreStorageRead, NativeVp, VpEvaluator}; +use namada_vp::native_vp::{ + Ctx, CtxPostStorageRead, CtxPreStorageRead, NativeVp, VpEvaluator, +}; use namada_vp::{native_vp, VpEnv}; -use ripemd::Digest as RipemdDigest; -use sha2::Digest as Sha2Digest; use thiserror::Error; use token::Amount; @@ -57,7 +52,7 @@ pub enum Error { pub type Result = std::result::Result; /// MASP VP -pub struct MaspVp<'ctx, S, CA, EVAL, Params, Gov> +pub struct MaspVp<'ctx, S, CA, EVAL, Params, Gov, Ibc, TransToken> where S: 'static + StateRead, EVAL: VpEvaluator<'ctx, S, CA, EVAL>, @@ -65,7 +60,7 @@ where /// Context to interact with the host structures. pub ctx: Ctx<'ctx, S, CA, EVAL>, /// Generic types for DI - pub _marker: PhantomData<(Params, Gov)>, + pub _marker: PhantomData<(Params, Gov, Ibc, TransToken)>, } // The balances changed by the transaction, split between masp and non-masp @@ -81,79 +76,8 @@ struct ChangedBalances { post: BTreeMap>, } -struct IbcTransferInfo { - src_port_id: PortId, - src_channel_id: ChannelId, - timeout_height: TimeoutHeight, - timeout_timestamp: Timestamp, - packet_data: Vec, - ibc_traces: Vec, - amount: Amount, - receiver: String, -} - -impl TryFrom for IbcTransferInfo { - type Error = Error; - - fn try_from( - message: IbcMsgTransfer, - ) -> std::result::Result { - let packet_data = serde_json::to_vec(&message.packet_data) - .map_err(native_vp::Error::new)?; - let ibc_traces = vec![message.packet_data.token.denom.to_string()]; - let amount = message - .packet_data - .token - .amount - .try_into() - .into_storage_result()?; - let receiver = message.packet_data.receiver.to_string(); - Ok(Self { - src_port_id: message.port_id_on_a, - src_channel_id: message.chan_id_on_a, - timeout_height: message.timeout_height_on_b, - timeout_timestamp: message.timeout_timestamp_on_b, - packet_data, - ibc_traces, - amount, - receiver, - }) - } -} - -impl TryFrom for IbcTransferInfo { - type Error = Error; - - fn try_from( - message: IbcMsgNftTransfer, - ) -> std::result::Result { - let packet_data = serde_json::to_vec(&message.packet_data) - .map_err(native_vp::Error::new)?; - let ibc_traces = message - .packet_data - .token_ids - .0 - .iter() - .map(|token_id| { - ibc_trace_for_nft(&message.packet_data.class_id, token_id) - }) - .collect(); - let receiver = message.packet_data.receiver.to_string(); - Ok(Self { - src_port_id: message.port_id_on_a, - src_channel_id: message.chan_id_on_a, - timeout_height: message.timeout_height_on_b, - timeout_timestamp: message.timeout_timestamp_on_b, - packet_data, - ibc_traces, - amount: Amount::from_u64(1), - receiver, - }) - } -} - -impl<'view, 'ctx: 'view, S, CA, EVAL, Params, Gov> - MaspVp<'ctx, S, CA, EVAL, Params, Gov> +impl<'view, 'ctx: 'view, S, CA, EVAL, Params, Gov, Ibc, TransToken> + MaspVp<'ctx, S, CA, EVAL, Params, Gov, Ibc, TransToken> where S: 'static + StateRead, CA: 'static + Clone, @@ -166,6 +90,11 @@ where CtxPreStorageRead<'view, 'ctx, S, CA, EVAL>, Err = StorageError, >, + Ibc: ibc::Read< + CtxPostStorageRead<'view, 'ctx, S, CA, EVAL>, + Err = StorageError, + >, + TransToken: token::Keys, { /// Instantiate MASP VP pub fn new(ctx: Ctx<'ctx, S, CA, EVAL>) -> Self { @@ -372,297 +301,6 @@ where Ok(()) } - fn check_ibc_transfer( - &self, - ibc_transfer: &IbcTransferInfo, - keys_changed: &BTreeSet, - ) -> Result<()> { - let IbcTransferInfo { - src_port_id, - src_channel_id, - timeout_height, - timeout_timestamp, - packet_data, - .. - } = ibc_transfer; - let sequence = get_last_sequence_send( - &self.ctx.post(), - src_port_id, - src_channel_id, - )?; - let commitment_key = - commitment_key(src_port_id, src_channel_id, sequence); - - if !keys_changed.contains(&commitment_key) { - return Err(Error::NativeVpError(native_vp::Error::AllocMessage( - format!( - "Expected IBC transfer didn't happen: Port ID \ - {src_port_id}, Channel ID {src_channel_id}, Sequence \ - {sequence}" - ), - ))); - } - - // The commitment is also validated in IBC VP. Make sure that for when - // IBC VP isn't triggered. - let actual: PacketCommitment = self - .ctx - .read_bytes_post(&commitment_key)? - .ok_or(Error::NativeVpError(native_vp::Error::AllocMessage( - format!( - "Packet commitment doesn't exist: Port ID {src_port_id}, \ - Channel ID {src_channel_id}, Sequence {sequence}" - ), - )))? - .into(); - let expected = compute_packet_commitment( - packet_data, - timeout_height, - timeout_timestamp, - ); - if actual != expected { - return Err(Error::NativeVpError(native_vp::Error::AllocMessage( - format!( - "Packet commitment mismatched: Port ID {src_port_id}, \ - Channel ID {src_channel_id}, Sequence {sequence}" - ), - ))); - } - - Ok(()) - } - - fn check_packet_receiving( - &self, - msg: &IbcMsgRecvPacket, - keys_changed: &BTreeSet, - ) -> Result<()> { - let receipt_key = receipt_key( - &msg.packet.port_id_on_b, - &msg.packet.chan_id_on_b, - msg.packet.seq_on_a, - ); - if !keys_changed.contains(&receipt_key) { - return Err(Error::NativeVpError(native_vp::Error::AllocMessage( - format!( - "The packet has not been received: Port ID {}, Channel \ - ID {}, Sequence {}", - msg.packet.port_id_on_b, - msg.packet.chan_id_on_b, - msg.packet.seq_on_a, - ), - ))); - } - Ok(()) - } - - // Apply the given transfer message to the changed balances structure - fn apply_transfer_msg( - &self, - mut acc: ChangedBalances, - ibc_transfer: &IbcTransferInfo, - keys_changed: &BTreeSet, - ) -> Result { - self.check_ibc_transfer(ibc_transfer, keys_changed)?; - - let IbcTransferInfo { - ibc_traces, - src_port_id, - src_channel_id, - amount, - receiver, - .. - } = ibc_transfer; - - let receiver = ibc_taddr(receiver.clone()); - for ibc_trace in ibc_traces { - let token = convert_to_address(ibc_trace).into_storage_result()?; - let delta = ValueSum::from_pair(token, *amount); - // If there is a transfer to the IBC account, then deduplicate the - // balance increase since we already accounted for it above - if is_sender_chain_source(ibc_trace, src_port_id, src_channel_id) { - let ibc_taddr = addr_taddr(IBC); - let post_entry = acc - .post - .get(&ibc_taddr) - .cloned() - .unwrap_or(ValueSum::zero()); - acc.post.insert( - ibc_taddr, - checked!(post_entry - &delta) - .map_err(native_vp::Error::new)?, - ); - } - // Record an increase to the balance of a specific IBC receiver - let post_entry = - acc.post.get(&receiver).cloned().unwrap_or(ValueSum::zero()); - acc.post.insert( - receiver, - checked!(post_entry + &delta).map_err(native_vp::Error::new)?, - ); - } - - Ok(acc) - } - - // Check if IBC message was received successfully in this state transition - fn is_receiving_success( - &self, - dst_port_id: &PortId, - dst_channel_id: &ChannelId, - sequence: Sequence, - ) -> Result { - // Ensure that the event corresponds to the current changes to storage - let ack_key = storage::ack_key(dst_port_id, dst_channel_id, sequence); - // If the receive is a success, then the commitment is unique - let succ_ack_commitment = compute_ack_commitment( - &AcknowledgementStatus::success(ack_success_b64()).into(), - ); - Ok(match self.ctx.read_bytes_post(&ack_key)? { - // Success happens only if commitment equals the above - Some(value) => { - AcknowledgementCommitment::from(value) == succ_ack_commitment - } - // Acknowledgement key non-existence is failure - None => false, - }) - } - - // Apply the given write acknowledge to the changed balances structure - fn apply_recv_msg( - &self, - mut acc: ChangedBalances, - msg: &IbcMsgRecvPacket, - ibc_traces: Vec, - amount: Amount, - keys_changed: &BTreeSet, - ) -> Result { - self.check_packet_receiving(msg, keys_changed)?; - - // If the transfer was a failure, then enable funds to - // be withdrawn from the IBC internal address - if self.is_receiving_success( - &msg.packet.port_id_on_b, - &msg.packet.chan_id_on_b, - msg.packet.seq_on_a, - )? { - for ibc_trace in ibc_traces { - // Get the received token - let token = namada_ibc::received_ibc_token( - ibc_trace, - &msg.packet.port_id_on_a, - &msg.packet.chan_id_on_a, - &msg.packet.port_id_on_b, - &msg.packet.chan_id_on_b, - ) - .into_storage_result() - .map_err(Error::NativeVpError)?; - let delta = ValueSum::from_pair(token.clone(), amount); - // Enable funds to be taken from the IBC internal - // address and be deposited elsewhere - // Required for the IBC internal Address to release - // funds - let ibc_taddr = addr_taddr(IBC); - let pre_entry = acc - .pre - .get(&ibc_taddr) - .cloned() - .unwrap_or(ValueSum::zero()); - acc.pre.insert( - ibc_taddr, - checked!(pre_entry + &delta) - .map_err(native_vp::Error::new)?, - ); - } - } - Ok(acc) - } - - // Apply relevant IBC packets to the changed balances structure - fn apply_ibc_packet( - &self, - mut acc: ChangedBalances, - ibc_msg: IbcMessage, - keys_changed: &BTreeSet, - ) -> Result { - match ibc_msg { - // This event is emitted on the sender - IbcMessage::Transfer(msg) => { - // Get the packet commitment from post-storage that corresponds - // to this event - let ibc_transfer = IbcTransferInfo::try_from(msg.message)?; - let receiver = ibc_transfer.receiver.clone(); - let addr = TAddrData::Ibc(receiver.clone()); - acc.decoder.insert(ibc_taddr(receiver), addr); - acc = - self.apply_transfer_msg(acc, &ibc_transfer, keys_changed)?; - } - IbcMessage::NftTransfer(msg) => { - let ibc_transfer = IbcTransferInfo::try_from(msg.message)?; - let receiver = ibc_transfer.receiver.clone(); - let addr = TAddrData::Ibc(receiver.clone()); - acc.decoder.insert(ibc_taddr(receiver), addr); - acc = - self.apply_transfer_msg(acc, &ibc_transfer, keys_changed)?; - } - // This event is emitted on the receiver - IbcMessage::Envelope(envelope) => { - if let MsgEnvelope::Packet(PacketMsg::Recv(msg)) = *envelope { - if msg.packet.port_id_on_b.as_str() == PORT_ID_STR { - let packet_data = serde_json::from_slice::( - &msg.packet.data, - ) - .map_err(native_vp::Error::new)?; - let receiver = packet_data.receiver.to_string(); - let addr = TAddrData::Ibc(receiver.clone()); - acc.decoder.insert(ibc_taddr(receiver), addr); - let ibc_denom = packet_data.token.denom.to_string(); - let amount = packet_data - .token - .amount - .try_into() - .into_storage_result()?; - acc = self.apply_recv_msg( - acc, - &msg, - vec![ibc_denom], - amount, - keys_changed, - )?; - } else { - let packet_data = - serde_json::from_slice::( - &msg.packet.data, - ) - .map_err(native_vp::Error::new)?; - let receiver = packet_data.receiver.to_string(); - let addr = TAddrData::Ibc(receiver.clone()); - acc.decoder.insert(ibc_taddr(receiver), addr); - let ibc_traces = packet_data - .token_ids - .0 - .iter() - .map(|token_id| { - ibc_trace_for_nft( - &packet_data.class_id, - token_id, - ) - }) - .collect(); - acc = self.apply_recv_msg( - acc, - &msg, - ibc_traces, - Amount::from_u64(1), - keys_changed, - )?; - } - } - } - } - Result::<_>::Ok(acc) - } - // Apply the balance change to the changed balances structure fn apply_balance_change( &self, @@ -674,7 +312,8 @@ where )?; // Record the token without an epoch to facilitate later decoding unepoched_tokens(token, denom, &mut result.tokens)?; - let counterpart_balance_key = balance_key(token, counterpart); + let counterpart_balance_key = + TransToken::balance_key(token, counterpart); let pre_balance: Amount = self .ctx .read_pre(&counterpart_balance_key)? @@ -722,13 +361,14 @@ where // Check that transfer is pinned correctly and record the balance changes fn validate_state_and_get_transfer_data( - &self, + &'view self, keys_changed: &BTreeSet, - ibc_msg: Option, + tx_data: &[u8], ) -> Result { // Get the changed balance keys - let mut counterparts_balances = - keys_changed.iter().filter_map(is_any_token_balance_key); + let mut counterparts_balances = keys_changed + .iter() + .filter_map(TransToken::is_any_token_balance_key); // Apply the balance changes to the changed balances structure let mut changed_balances = counterparts_balances @@ -736,23 +376,38 @@ where self.apply_balance_change(acc, account) })?; - let ibc_addr = TAddrData::Addr(IBC); + let ibc_addr = TAddrData::Addr(address::IBC); // Enable decoding the IBC address hash - changed_balances.decoder.insert(addr_taddr(IBC), ibc_addr); + changed_balances + .decoder + .insert(addr_taddr(address::IBC), ibc_addr); // Note the balance changes they imply - match ibc_msg { - Some(ibc_msg) => { - self.apply_ibc_packet(changed_balances, ibc_msg, keys_changed) - } - None => Ok(changed_balances), - } + let ChangedBalances { + tokens, + decoder, + pre, + post, + } = changed_balances; + let ibc::ChangedBalances { decoder, pre, post } = + Ibc::apply_ibc_packet( + &self.ctx.post(), + tx_data, + ibc::ChangedBalances { decoder, pre, post }, + keys_changed, + )?; + Ok(ChangedBalances { + tokens, + decoder, + pre, + post, + }) } // Check that MASP Transaction and state changes are valid fn is_valid_masp_transfer( &'view self, - tx_data: &BatchedTxRef<'_>, + batched_tx: &BatchedTxRef<'_>, keys_changed: &BTreeSet, ) -> Result<()> { let masp_epoch_multiplier = @@ -765,33 +420,33 @@ where Error::NativeVpError(native_vp::Error::new_const(msg)) })?; let conversion_state = self.ctx.state.in_mem().get_conversion_state(); - let ibc_msg = self.ctx.get_ibc_message(tx_data).ok(); - let shielded_tx = - if let Some(IbcMessage::Envelope(ref envelope)) = ibc_msg { - extract_masp_tx_from_envelope(envelope).ok_or_else(|| { - native_vp::Error::new_const( - "Missing MASP transaction in IBC message", - ) - })? - } else { - // Get the Transaction object from the actions - let masp_section_ref = - namada_tx::action::get_masp_section_ref(&self.ctx)? - .ok_or_else(|| { - native_vp::Error::new_const( - "Missing MASP section reference in action", - ) - })?; - tx_data - .tx - .get_masp_section(&masp_section_ref) - .cloned() + let tx_data = batched_tx + .tx + .data(batched_tx.cmt) + .ok_or_err_msg("No transaction data")?; + let shielded_tx = if let Some(tx) = + Ibc::try_extract_masp_tx_from_envelope(&tx_data)? + { + tx + } else { + // Get the Transaction object from the actions + let masp_section_ref = + namada_tx::action::get_masp_section_ref(&self.ctx)? .ok_or_else(|| { native_vp::Error::new_const( - "Missing MASP section in transaction", + "Missing MASP section reference in action", ) - })? - }; + })?; + batched_tx + .tx + .get_masp_section(&masp_section_ref) + .cloned() + .ok_or_else(|| { + native_vp::Error::new_const( + "Missing MASP section in transaction", + ) + })? + }; if u64::from(self.ctx.get_block_height()?) > u64::from(shielded_tx.expiry_height()) @@ -805,9 +460,9 @@ where // Check the validity of the keys and get the transfer data let mut changed_balances = - self.validate_state_and_get_transfer_data(keys_changed, ibc_msg)?; + self.validate_state_and_get_transfer_data(keys_changed, &tx_data)?; - let masp_address_hash = addr_taddr(MASP); + let masp_address_hash = addr_taddr(address::MASP); verify_sapling_balancing_value( changed_balances .pre @@ -864,7 +519,7 @@ where // Ensure that this transaction is authorized by all involved parties for signer in signers { - if let Some(TAddrData::Addr(IBC)) = + if let Some(TAddrData::Addr(address::IBC)) = changed_balances.decoder.get(&signer) { // If the IBC address is a signatory, then it means that either @@ -908,10 +563,10 @@ where namada_account::threshold(&self.ctx.pre(), signer)? .unwrap_or(1); let mut gas_meter = self.ctx.gas_meter.borrow_mut(); - tx_data + batched_tx .tx .verify_signatures( - &[tx_data.tx.raw_header_hash()], + &[batched_tx.tx.raw_header_hash()], public_keys_index_map, &Some(signer.clone()), threshold, @@ -1237,8 +892,8 @@ fn verify_sapling_balancing_value( } } -impl<'view, 'ctx: 'view, S, CA, EVAL, Params, Gov> NativeVp<'view> - for MaspVp<'ctx, S, CA, EVAL, Params, Gov> +impl<'view, 'ctx: 'view, S, CA, EVAL, Params, Gov, Ibc, TransToken> + NativeVp<'view> for MaspVp<'ctx, S, CA, EVAL, Params, Gov, Ibc, TransToken> where S: 'static + StateRead, CA: 'static + Clone, @@ -1251,6 +906,11 @@ where CtxPreStorageRead<'view, 'ctx, S, CA, EVAL>, Err = StorageError, >, + Ibc: ibc::Read< + CtxPostStorageRead<'view, 'ctx, S, CA, EVAL>, + Err = StorageError, + >, + TransToken: token::Keys, { type Error = Error; diff --git a/crates/vp_env/src/lib.rs b/crates/vp_env/src/lib.rs index c65075c8a2..7751632299 100644 --- a/crates/vp_env/src/lib.rs +++ b/crates/vp_env/src/lib.rs @@ -25,8 +25,7 @@ use namada_core::borsh::BorshDeserialize; use namada_core::hash::Hash; use namada_core::storage::{BlockHeight, Epoch, Epochs, Header, Key, TxIndex}; use namada_events::{Event, EventType}; -use namada_ibc::{decode_message, IbcMessage}; -use namada_storage::{OptionExt, ResultExt, StorageRead}; +use namada_storage::StorageRead; use namada_tx::BatchedTxRef; /// Validity predicate's environment is available for native VPs and WASM VPs @@ -120,18 +119,6 @@ where /// Get a tx hash fn get_tx_code_hash(&self) -> Result, namada_storage::Error>; - /// Get the IBC message from the data section - fn get_ibc_message( - &self, - batched_tx: &BatchedTxRef<'_>, - ) -> Result { - let data = batched_tx - .tx - .data(batched_tx.cmt) - .ok_or_err_msg("No transaction data")?; - decode_message(&data).into_storage_result() - } - /// Charge the provided gas for the current vp fn charge_gas(&self, used_gas: u64) -> Result<(), namada_storage::Error>;