From 4c43604535641019a7f9d136398ada2448e835a9 Mon Sep 17 00:00:00 2001 From: Lior Bondarevski Date: Sun, 28 Aug 2022 23:32:05 +0300 Subject: [PATCH 1/2] Channel connect is now workins (Including submessages) --- .../src/contract_operations.rs | 34 ++- .../enclaves/shared/contract-engine/src/io.rs | 196 +++++++++++------- .../shared/cosmwasm-v1-types/src/ibc.rs | 14 ++ .../cosmwasm-v1-types/src/results/response.rs | 4 +- go-cosmwasm/lib.go | 1 + x/compute/internal/keeper/ibc_test.go | 121 +++++++++-- x/compute/internal/keeper/keeper.go | 1 + x/compute/internal/keeper/msg_dispatcher.go | 16 +- x/compute/internal/keeper/relay.go | 7 +- .../keeper/testdata/ibc/src/contract.rs | 8 +- 10 files changed, 294 insertions(+), 108 deletions(-) diff --git a/cosmwasm/enclaves/shared/contract-engine/src/contract_operations.rs b/cosmwasm/enclaves/shared/contract-engine/src/contract_operations.rs index 2b0da1b0d..714ed49fe 100644 --- a/cosmwasm/enclaves/shared/contract-engine/src/contract_operations.rs +++ b/cosmwasm/enclaves/shared/contract-engine/src/contract_operations.rs @@ -30,7 +30,10 @@ use super::contract_validation::{ verify_params, ContractKey, }; use super::gas::WasmCosts; -use super::io::{encrypt_output, finalize_raw_output, RawWasmOutput}; +use super::io::{ + encrypt_output, finalize_raw_output, manipulate_callback_sig_for_plaintext, + set_all_logs_to_plaintext, +}; use super::module_cache::create_module_instance; use super::types::{IoNonce, SecretMessage}; use super::wasm::{ContractInstance, ContractOperation, Engine}; @@ -158,6 +161,7 @@ pub fn init( reply_params, &canonical_sender_address, false, + false, )?; Ok(output) @@ -361,7 +365,7 @@ pub fn parse_message( let decrypted_msg = secret_msg.msg.clone(); Ok(ParsedMessage { - should_validate_sig_info: true, + should_validate_sig_info: false, was_msg_encrypted: false, secret_msg, decrypted_msg, @@ -690,6 +694,18 @@ pub fn parse_message( }; } +pub fn is_ibc_msg(handle_type: HandleType) -> bool { + match handle_type { + HandleType::HANDLE_TYPE_EXECUTE | HandleType::HANDLE_TYPE_REPLY => false, + HandleType::HANDLE_TYPE_IBC_CHANNEL_OPEN + | HandleType::HANDLE_TYPE_IBC_CHANNEL_CONNECT + | HandleType::HANDLE_TYPE_IBC_CHANNEL_CLOSE + | HandleType::HANDLE_TYPE_IBC_PACKET_RECEIVE + | HandleType::HANDLE_TYPE_IBC_PACKET_ACK + | HandleType::HANDLE_TYPE_IBC_PACKET_TIMEOUT => true, + } +} + #[cfg_attr(feature = "cargo-clippy", allow(clippy::too_many_arguments))] pub fn handle( context: Ctx, @@ -804,7 +820,7 @@ pub fn handle( // This wrapper is used to coalesce all errors in this block to one object // so we can `.map_err()` in one place for all of them let output = coalesce!(EnclaveError, { - let vec_ptr = engine.handle(env_ptr, msg_info_ptr, msg_ptr, parsed_handle_type)?; + let vec_ptr = engine.handle(env_ptr, msg_info_ptr, msg_ptr, parsed_handle_type.clone())?; let mut output = engine.extract_vector(vec_ptr)?; @@ -831,15 +847,14 @@ pub fn handle( reply_params, &canonical_sender_address, false, + is_ibc_msg(parsed_handle_type.clone()), )?; } else { - let raw_output: RawWasmOutput = serde_json::from_slice(&output).map_err(|err| { - warn!("got an error while trying to deserialize output bytes into json"); - trace!("output: {:?} error: {:?}", output, err); - EnclaveError::FailedToDeserialize - })?; + let mut raw_output = manipulate_callback_sig_for_plaintext(output)?; + set_all_logs_to_plaintext(&mut raw_output); - let finalized_output = finalize_raw_output(raw_output, false); + let finalized_output = finalize_raw_output(raw_output, false, is_ibc_msg(parsed_handle_type), false); + trace!("Wasm output for plaintext message is: {:?}", finalized_output); output = serde_json::to_vec(&finalized_output).map_err(|err| { debug!( @@ -942,6 +957,7 @@ pub fn query( None, // Not used for queries (Query response is not replied to the caller), &CanonicalAddr(Binary(Vec::new())), // Not used for queries (used only for replies) true, + false, )?; Ok(output) }) diff --git a/cosmwasm/enclaves/shared/contract-engine/src/io.rs b/cosmwasm/enclaves/shared/contract-engine/src/io.rs index 6d747cddf..f3251cf6d 100644 --- a/cosmwasm/enclaves/shared/contract-engine/src/io.rs +++ b/cosmwasm/enclaves/shared/contract-engine/src/io.rs @@ -6,7 +6,7 @@ use crate::contract_validation::ReplyParams; /// use super::types::{IoNonce, SecretMessage}; use enclave_cosmwasm_v010_types::encoding::Binary; -use enclave_cosmwasm_v010_types::types::{CanonicalAddr, Coin}; +use enclave_cosmwasm_v010_types::types::{CanonicalAddr, Coin, LogAttribute}; use enclave_cosmwasm_v1_types::results::{Event, Reply, ReplyOn, SubMsgResponse, SubMsgResult}; use enclave_ffi_types::EnclaveError; @@ -52,10 +52,6 @@ pub enum RawWasmOutput { internal_reply_enclave_sig: Option, internal_msg_id: Option, }, - OkIBCBasic { - #[serde(rename = "Ok")] - ok: enclave_cosmwasm_v1_types::ibc::IbcBasicResponse, - }, OkIBCPacketReceive { #[serde(rename = "Ok")] ok: enclave_cosmwasm_v1_types::ibc::IbcReceiveResponse, @@ -193,7 +189,12 @@ fn b64_encode(data: &[u8]) -> String { base64::encode(data) } -pub fn finalize_raw_output(raw_output: RawWasmOutput, is_query_output: bool) -> WasmOutput { +pub fn finalize_raw_output( + raw_output: RawWasmOutput, + is_query_output: bool, + is_ibc: bool, + is_msg_encrypted: bool, +) -> WasmOutput { return match raw_output { RawWasmOutput::Err { err, @@ -217,7 +218,10 @@ pub fn finalize_raw_output(raw_output: RawWasmOutput, is_query_output: bool) -> } else { WasmOutput { v010: Some(V010WasmOutput { - err: Some(err), + err: match is_msg_encrypted { + true => Some(err), + false => Some(json!({"generic_err":{"msg":err}})), + }, ok: None, }), v1: None, @@ -251,18 +255,37 @@ pub fn finalize_raw_output(raw_output: RawWasmOutput, is_query_output: bool) -> ok, internal_reply_enclave_sig, internal_msg_id, - } => WasmOutput { - v010: None, - v1: Some(V1WasmOutput { - err: None, - ok: Some(ok), - }), - ibc_basic: None, - ibc_packet_receive: None, - ibc_open_channel: None, - query: None, - internal_reply_enclave_sig, - internal_msg_id, + } => match is_ibc { + false => WasmOutput { + v010: None, + v1: Some(V1WasmOutput { + err: None, + ok: Some(ok), + }), + ibc_basic: None, + ibc_packet_receive: None, + ibc_open_channel: None, + query: None, + internal_reply_enclave_sig, + internal_msg_id, + }, + true => WasmOutput { + v010: None, + v1: None, + ibc_basic: Some(IBCOutput { + err: None, + ok: Some(enclave_cosmwasm_v1_types::ibc::IbcBasicResponse::new( + ok.messages, + ok.attributes, + ok.events, + )), + }), + ibc_packet_receive: None, + ibc_open_channel: None, + query: None, + internal_reply_enclave_sig, + internal_msg_id, + }, }, RawWasmOutput::QueryOkV010 { ok } | RawWasmOutput::QueryOkV1 { ok } => WasmOutput { v010: None, @@ -277,19 +300,6 @@ pub fn finalize_raw_output(raw_output: RawWasmOutput, is_query_output: bool) -> internal_reply_enclave_sig: None, internal_msg_id: None, }, - RawWasmOutput::OkIBCBasic { ok } => WasmOutput { - v010: None, - v1: None, - ibc_basic: Some(IBCOutput { - err: None, - ok: Some(ok), - }), - ibc_packet_receive: None, - ibc_open_channel: None, - query: None, - internal_reply_enclave_sig: None, - internal_msg_id: None, - }, RawWasmOutput::OkIBCPacketReceive { ok } => WasmOutput { v010: None, v1: None, @@ -322,6 +332,82 @@ pub fn finalize_raw_output(raw_output: RawWasmOutput, is_query_output: bool) -> }; } +pub fn manipulate_callback_sig_for_plaintext( + output: Vec, +) -> Result { + let mut raw_output: RawWasmOutput = serde_json::from_slice(&output).map_err(|err| { + warn!("got an error while trying to deserialize output bytes into json"); + trace!("output: {:?} error: {:?}", output, err); + EnclaveError::FailedToDeserialize + })?; + + match &mut raw_output { + RawWasmOutput::OkV1 { ok, .. } => { + for sub_msg in &mut ok.messages { + if let enclave_cosmwasm_v1_types::results::CosmosMsg::Wasm(wasm_msg) = + &mut sub_msg.msg + { + match wasm_msg { + enclave_cosmwasm_v1_types::results::WasmMsg::Execute { + callback_sig, + .. + } + | enclave_cosmwasm_v1_types::results::WasmMsg::Instantiate { + callback_sig, + .. + } => *callback_sig = Some(vec![]), + } + } + } + } + RawWasmOutput::OkIBCPacketReceive { ok } => { + for sub_msg in &mut ok.messages { + if let enclave_cosmwasm_v1_types::results::CosmosMsg::Wasm(wasm_msg) = + &mut sub_msg.msg + { + match wasm_msg { + enclave_cosmwasm_v1_types::results::WasmMsg::Execute { + callback_sig, + .. + } + | enclave_cosmwasm_v1_types::results::WasmMsg::Instantiate { + callback_sig, + .. + } => *callback_sig = Some(vec![]), + } + } + } + } + _ => {} + } + + Ok(raw_output) +} + +pub fn set_attributes_to_plaintext(attributes: &mut Vec) { + for attr in attributes { + attr.encrypted = false; + } +} + +pub fn set_all_logs_to_plaintext(raw_output: &mut RawWasmOutput) { + match raw_output { + RawWasmOutput::OkV1 { ok, .. } => { + set_attributes_to_plaintext(&mut ok.attributes); + for ev in &mut ok.events { + set_attributes_to_plaintext(&mut ev.attributes); + } + } + RawWasmOutput::OkIBCPacketReceive { ok } => { + set_attributes_to_plaintext(&mut ok.attributes); + for ev in &mut ok.events { + set_attributes_to_plaintext(&mut ev.attributes); + } + } + _ => {} + } +} + pub fn encrypt_output( output: Vec, secret_msg: &SecretMessage, @@ -330,6 +416,7 @@ pub fn encrypt_output( reply_params: Option, sender_addr: &CanonicalAddr, is_query_output: bool, + is_ibc_output: bool, ) -> Result, EnclaveError> { // When encrypting an output we might encrypt an output that is a reply to a caller contract (Via "Reply" endpoint). // Therefore if reply_recipient_contract_hash is not "None" we append it to any encrypted data besided submessages that are irrelevant for replies. @@ -353,10 +440,7 @@ pub fn encrypt_output( internal_msg_id, } => { let encrypted_err = encrypt_serializable(&encryption_key, err, &reply_params)?; - - // Putting the error inside a 'generic_err' envelope, so we can encrypt the error itself *err = json!({"generic_err":{"msg":encrypted_err}}); - let msg_id = match reply_params { Some(ref r) => { let encrypted_id = Binary::from_base64(&encrypt_preserialized_string( @@ -527,6 +611,11 @@ pub fn encrypt_output( } if let Some(data) = &mut ok.data { + if is_ibc_output { + warn!("IBC output should not containt any data"); + return Err(EnclaveError::InternalError); + } + *data = Binary::from_base64(&encrypt_serializable( &encryption_key, data, @@ -597,41 +686,6 @@ pub fn encrypt_output( None => None, // Not a reply, we don't need enclave sig } } - RawWasmOutput::OkIBCBasic { ok } => { - for sub_msg in &mut ok.messages { - if let enclave_cosmwasm_v1_types::results::CosmosMsg::Wasm(wasm_msg) = - &mut sub_msg.msg - { - encrypt_v1_wasm_msg( - wasm_msg, - &sub_msg.reply_on, - sub_msg.id, - secret_msg.nonce, - secret_msg.user_public_key, - contract_addr, - contract_hash, - )?; - - // The ID can be extracted from the encrypted wasm msg - // We don't encrypt it here to remain with the same type (u64) - sub_msg.id = 0; - } - } - - // v1: The attributes that will be emitted as part of a "wasm" event. - for attr in ok.attributes.iter_mut().filter(|attr| attr.encrypted) { - attr.key = encrypt_preserialized_string(&encryption_key, &attr.key, &None)?; - attr.value = encrypt_preserialized_string(&encryption_key, &attr.value, &None)?; - } - - // v1: Extra, custom events separate from the main wasm one. These will have "wasm-"" prepended to the type. - for event in ok.events.iter_mut() { - for attr in event.attributes.iter_mut().filter(|attr| attr.encrypted) { - attr.key = encrypt_preserialized_string(&encryption_key, &attr.key, &None)?; - attr.value = encrypt_preserialized_string(&encryption_key, &attr.value, &None)?; - } - } - } RawWasmOutput::OkIBCPacketReceive { ok } => { for sub_msg in &mut ok.messages { if let enclave_cosmwasm_v1_types::results::CosmosMsg::Wasm(wasm_msg) = @@ -676,7 +730,7 @@ pub fn encrypt_output( RawWasmOutput::OkIBCOpenChannel { ok: _ } => {} }; - let final_output = finalize_raw_output(output, is_query_output); + let final_output = finalize_raw_output(output, is_query_output, is_ibc_output, true); trace!("WasmOutput: {:?}", final_output); let encrypted_output = serde_json::to_vec(&final_output).map_err(|err| { diff --git a/cosmwasm/enclaves/shared/cosmwasm-v1-types/src/ibc.rs b/cosmwasm/enclaves/shared/cosmwasm-v1-types/src/ibc.rs index 751bb4caf..53956fce0 100644 --- a/cosmwasm/enclaves/shared/cosmwasm-v1-types/src/ibc.rs +++ b/cosmwasm/enclaves/shared/cosmwasm-v1-types/src/ibc.rs @@ -74,6 +74,20 @@ where pub events: Vec, } +impl IbcBasicResponse { + pub fn new( + messages: Vec>, + attributes: Vec, + events: Vec, + ) -> Self { + IbcBasicResponse { + messages, + attributes, + events, + } + } +} + // This defines the return value on packet response processing. // This "success" case should be returned even in application-level errors, // Where the acknowledgement bytes contain an encoded error message to be returned to diff --git a/cosmwasm/enclaves/shared/cosmwasm-v1-types/src/results/response.rs b/cosmwasm/enclaves/shared/cosmwasm-v1-types/src/results/response.rs index 1a6054a0c..fceff9804 100644 --- a/cosmwasm/enclaves/shared/cosmwasm-v1-types/src/results/response.rs +++ b/cosmwasm/enclaves/shared/cosmwasm-v1-types/src/results/response.rs @@ -1,9 +1,9 @@ use serde::{Deserialize, Serialize}; use std::fmt; -use enclave_cosmwasm_v010_types::{encoding::Binary, types::LogAttribute}; +use enclave_cosmwasm_v010_types::{encoding::Binary, types::Empty, types::LogAttribute}; -use super::{Empty, Event, SubMsg}; +use super::{Event, SubMsg}; /// A response of a contract entry point, such as `instantiate` or `execute`. /// diff --git a/go-cosmwasm/lib.go b/go-cosmwasm/lib.go index dd28a2ddf..3add1592a 100644 --- a/go-cosmwasm/lib.go +++ b/go-cosmwasm/lib.go @@ -263,6 +263,7 @@ func (w *Wasmer) Execute( return nil, gasUsed, err } + fmt.Printf("LIORRR data %+v", data) var resp ContractExecResponse err = json.Unmarshal(data, &resp) diff --git a/x/compute/internal/keeper/ibc_test.go b/x/compute/internal/keeper/ibc_test.go index b67838c60..8d4edcd64 100644 --- a/x/compute/internal/keeper/ibc_test.go +++ b/x/compute/internal/keeper/ibc_test.go @@ -2,12 +2,14 @@ package keeper import ( "encoding/hex" + "fmt" "math" "testing" crypto "github.com/cosmos/cosmos-sdk/crypto/types" sdk "github.com/cosmos/cosmos-sdk/types" cosmwasm "github.com/enigmampc/SecretNetwork/go-cosmwasm/types" + v010cosmwasm "github.com/enigmampc/SecretNetwork/go-cosmwasm/types/v010" v1types "github.com/enigmampc/SecretNetwork/go-cosmwasm/types/v1" "github.com/enigmampc/SecretNetwork/x/compute/internal/types" "github.com/stretchr/testify/require" @@ -18,7 +20,7 @@ func ibcChannelConnectHelper( t *testing.T, keeper Keeper, ctx sdk.Context, contractAddr sdk.AccAddress, creatorPrivKey crypto.PrivKey, gas uint64, shouldSendOpenAck bool, channel v1types.IBCChannel, -) cosmwasm.StdError { +) (sdk.Context, []ContractEvent, cosmwasm.StdError) { // create new ctx with the same storage and a gas limit // this is to reset the event manager, so we won't get // events from past calls @@ -53,10 +55,13 @@ func ibcChannelConnectHelper( require.NotZero(t, gasMeter.GetWasmCounter(), err) if err != nil { - return cosmwasm.StdError{GenericErr: &cosmwasm.GenericErr{Msg: err.Error()}} + return ctx, nil, cosmwasm.StdError{GenericErr: &cosmwasm.GenericErr{Msg: err.Error()}} } - return cosmwasm.StdError{} + // wasmEvents comes from all the callbacks as well + wasmEvents := tryDecryptWasmEvents(ctx, []byte{}, true) + + return ctx, wasmEvents, cosmwasm.StdError{} } func ibcChannelOpenHelper( @@ -393,17 +398,60 @@ func TestIBCChannelConnect(t *testing.T) { require.Empty(t, err) for _, test := range []struct { + description string connectionID string output string + isSuccess bool hasAttributes bool hasEvents bool }{ { - connectionID: "1", - output: ``, - isSuccuss: true, - balancesBefore: "5000assaf,200000denom 5000assaf,5000denom", - balancesAfter: "4998assaf,199998denom 5000assaf,5002denom", + description: "Default", + connectionID: "0", + output: "4", + isSuccess: true, + hasAttributes: false, + hasEvents: false, + }, + { + description: "SubmessageNoReply", + connectionID: "1", + output: "10", + isSuccess: true, + hasAttributes: false, + hasEvents: false, + }, + { + description: "SubmessageWithReply", + connectionID: "2", + output: "17", + isSuccess: true, + hasAttributes: false, + hasEvents: false, + }, + { + description: "Attributes", + connectionID: "3", + output: "7", + isSuccess: true, + hasAttributes: true, + hasEvents: false, + }, + { + description: "Events", + connectionID: "4", + output: "8", + isSuccess: true, + hasAttributes: false, + hasEvents: true, + }, + { + description: "Error", + connectionID: "5", + output: "", + isSuccess: false, + hasAttributes: false, + hasEvents: false, }, } { t.Run(test.description, func(t *testing.T) { @@ -412,16 +460,57 @@ func TestIBCChannelConnect(t *testing.T) { CounterpartyEndpoint: createIBCEndpoint(PortIDForContract(contractAddress), "channel.1"), Order: v1types.Unordered, Version: "1", - ConnectionID: "1", + ConnectionID: test.connectionID, } - err = ibcChannelConnectHelper(t, keeper, ctx, contractAddress, privKeyA, defaultGasForTests, false, ibcChannel) - require.Empty(t, err) - - queryRes, err := queryHelper(t, keeper, ctx, contractAddress, `{"q":{}}`, true, true, math.MaxUint64) - require.Empty(t, err) - - require.Equal(t, "3", queryRes) + ctx, events, err := ibcChannelConnectHelper(t, keeper, ctx, contractAddress, privKeyA, defaultGasForTests, false, ibcChannel) + + if !test.isSuccess { + require.Contains(t, fmt.Sprintf("%+v", err), "Intentional") + } else { + require.Empty(t, err) + if test.hasAttributes { + require.Equal(t, + []ContractEvent{ + { + {Key: "contract_address", Value: contractAddress.String()}, + {Key: "attr1", Value: "😗"}, + }, + }, + events, + ) + } + + if test.hasEvents { + hadCyber1 := false + evts := ctx.EventManager().Events() + for _, e := range evts { + if e.Type == "wasm-cyber1" { + require.False(t, hadCyber1) + attrs, err := parseAndDecryptAttributes(e.Attributes, []byte{}, false) + require.Empty(t, err) + + require.Equal(t, + []v010cosmwasm.LogAttribute{ + {Key: "contract_address", Value: contractAddress.String()}, + {Key: "attr1", Value: "🤯"}, + }, + attrs, + ) + + hadCyber1 = true + } + } + + require.True(t, hadCyber1) + } + + queryRes, err := queryHelper(t, keeper, ctx, contractAddress, `{"q":{}}`, true, true, math.MaxUint64) + + require.Empty(t, err) + + require.Equal(t, test.output, queryRes) + } }) } } diff --git a/x/compute/internal/keeper/keeper.go b/x/compute/internal/keeper/keeper.go index 5f1b3a01f..2cb0b9968 100644 --- a/x/compute/internal/keeper/keeper.go +++ b/x/compute/internal/keeper/keeper.go @@ -885,6 +885,7 @@ func (k *Keeper) handleContractResponse( ogCosmosMessageVersion wasmTypes.CosmosMsgVersion, ) ([]byte, error) { events := types.ContractLogsToSdkEvents(logs, contractAddr) + ctx.EventManager().EmitEvents(events) if len(evts) > 0 { diff --git a/x/compute/internal/keeper/msg_dispatcher.go b/x/compute/internal/keeper/msg_dispatcher.go index 80486471f..17ef1d2ac 100644 --- a/x/compute/internal/keeper/msg_dispatcher.go +++ b/x/compute/internal/keeper/msg_dispatcher.go @@ -139,7 +139,19 @@ func (e UnsupportedRequest) Error() string { // Reply is encrypted only when it is a contract reply func isReplyEncrypted(msg v1wasmTypes.CosmosMsg, reply v1wasmTypes.Reply) bool { - return (msg.Wasm != nil) + if msg.Wasm == nil { + return false + } + + if msg.Wasm.Execute != nil { + return len(msg.Wasm.Execute.CallbackCodeHash) != 0 + } + + if msg.Wasm.Instantiate != nil { + return len(msg.Wasm.Instantiate.CallbackCodeHash) != 0 + } + + return true } // Issue #759 - we don't return error string for worries of non-determinism @@ -177,7 +189,7 @@ func (d MessageDispatcher) DispatchSubmessages(ctx sdk.Context, contractAddr sdk switch msg.ReplyOn { case v1wasmTypes.ReplySuccess, v1wasmTypes.ReplyError, v1wasmTypes.ReplyAlways, v1wasmTypes.ReplyNever: default: - return nil, sdkerrors.Wrap(types.ErrInvalid, "replyOn value") + return nil, sdkerrors.Wrap(types.ErrInvalid, "ReplyOn value") } // first, we build a sub-context which we can use inside the submessages diff --git a/x/compute/internal/keeper/relay.go b/x/compute/internal/keeper/relay.go index 4497b95c9..8fe04024d 100644 --- a/x/compute/internal/keeper/relay.go +++ b/x/compute/internal/keeper/relay.go @@ -132,10 +132,9 @@ func (k Keeper) OnConnectChannel( if err != nil { return sdkerrors.Wrap(types.ErrExecuteFailed, err.Error()) } - err = k.parseThenHandleIBCBasicContractResponse(ctx, contractAddress, msgBz, res) if err != nil { - sdkerrors.Wrap(err, "ibc-connect-channel") + return sdkerrors.Wrap(err, "ibc-connect-channel") } return nil } @@ -246,7 +245,7 @@ func (k Keeper) OnAckPacket( err = k.parseThenHandleIBCBasicContractResponse(ctx, contractAddress, msgBz, res) if err != nil { - sdkerrors.Wrap(err, "ibc-ack-packet") + return sdkerrors.Wrap(err, "ibc-ack-packet") } return nil } @@ -275,7 +274,7 @@ func (k Keeper) OnTimeoutPacket( err = k.parseThenHandleIBCBasicContractResponse(ctx, contractAddress, msgBz, res) if err != nil { - sdkerrors.Wrap(err, "ibc-timeout-packet") + return sdkerrors.Wrap(err, "ibc-timeout-packet") } return nil } diff --git a/x/compute/internal/keeper/testdata/ibc/src/contract.rs b/x/compute/internal/keeper/testdata/ibc/src/contract.rs index 8d24233eb..182027ba5 100644 --- a/x/compute/internal/keeper/testdata/ibc/src/contract.rs +++ b/x/compute/internal/keeper/testdata/ibc/src/contract.rs @@ -1,10 +1,9 @@ use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; use crate::state::{count, count_read}; use cosmwasm_std::{ - coins, entry_point, to_binary, Binary, CosmosMsg, Deps, DepsMut, Env, Event, - Ibc3ChannelOpenResponse, IbcBasicResponse, IbcChannelConnectMsg, IbcChannelOpenMsg, - IbcChannelOpenResponse, MessageInfo, Reply, ReplyOn, Response, StdError, StdResult, SubMsg, - SubMsgResult, WasmMsg, + entry_point, to_binary, Binary, CosmosMsg, Deps, DepsMut, Env, Event, Ibc3ChannelOpenResponse, + IbcBasicResponse, IbcChannelConnectMsg, IbcChannelOpenMsg, IbcChannelOpenResponse, MessageInfo, + Reply, ReplyOn, Response, StdError, StdResult, SubMsg, SubMsgResult, WasmMsg, }; pub const IBC_APP_VERSION: &str = "ibc-v1"; @@ -129,6 +128,7 @@ pub fn ibc_channel_connect( 3 => Ok(IbcBasicResponse::new().add_attribute("attr1", "😗")), 4 => Ok(IbcBasicResponse::new() .add_event(Event::new("cyber1".to_string()).add_attribute("attr1", "🤯"))), + 5 => Err(StdError::generic_err("Intentional")), _ => Err(StdError::generic_err("Unsupported channel connect type")), } } From 4058c9e4c2558dc10e13afd94902bbf89b24ae6e Mon Sep 17 00:00:00 2001 From: Lior Bondarevski Date: Sun, 28 Aug 2022 23:48:28 +0300 Subject: [PATCH 2/2] Channel close tests --- x/compute/internal/keeper/ibc_test.go | 179 +++++++++++++++++- .../keeper/testdata/ibc/src/contract.rs | 98 ++++++---- 2 files changed, 240 insertions(+), 37 deletions(-) diff --git a/x/compute/internal/keeper/ibc_test.go b/x/compute/internal/keeper/ibc_test.go index 8d4edcd64..85d0c7282 100644 --- a/x/compute/internal/keeper/ibc_test.go +++ b/x/compute/internal/keeper/ibc_test.go @@ -113,7 +113,7 @@ func ibcChannelCloseHelper( t *testing.T, keeper Keeper, ctx sdk.Context, contractAddr sdk.AccAddress, creatorPrivKey crypto.PrivKey, gas uint64, shouldSendCloseConfirn bool, channel v1types.IBCChannel, -) cosmwasm.StdError { +) (sdk.Context, []ContractEvent, cosmwasm.StdError) { // create new ctx with the same storage and a gas limit // this is to reset the event manager, so we won't get // events from past calls @@ -147,10 +147,13 @@ func ibcChannelCloseHelper( require.NotZero(t, gasMeter.GetWasmCounter(), err) if err != nil { - return cosmwasm.StdError{GenericErr: &cosmwasm.GenericErr{Msg: err.Error()}} + return ctx, nil, cosmwasm.StdError{GenericErr: &cosmwasm.GenericErr{Msg: err.Error()}} } - return cosmwasm.StdError{} + // wasmEvents comes from all the callbacks as well + wasmEvents := tryDecryptWasmEvents(ctx, []byte{}, true) + + return ctx, wasmEvents, cosmwasm.StdError{} } func createIBCEndpoint(port string, channel string) v1types.IBCEndpoint { @@ -514,3 +517,173 @@ func TestIBCChannelConnect(t *testing.T) { }) } } + +func TestIBCChannelConnectOpenAck(t *testing.T) { + ctx, keeper, codeID, _, walletA, privKeyA, _, _ := setupTest(t, "./testdata/ibc/contract.wasm", sdk.NewCoins()) + + _, _, contractAddress, _, err := initHelper(t, keeper, ctx, codeID, walletA, privKeyA, `{"init":{}}`, true, true, defaultGasForTests) + require.Empty(t, err) + + ibcChannel := v1types.IBCChannel{ + Endpoint: createIBCEndpoint(PortIDForContract(contractAddress), "channel.0"), + CounterpartyEndpoint: createIBCEndpoint(PortIDForContract(contractAddress), "channel.1"), + Order: v1types.Unordered, + Version: "1", + ConnectionID: "1", + } + + ctx, _, err = ibcChannelConnectHelper(t, keeper, ctx, contractAddress, privKeyA, defaultGasForTests, true, ibcChannel) + require.Empty(t, err) + + queryRes, err := queryHelper(t, keeper, ctx, contractAddress, `{"q":{}}`, true, true, math.MaxUint64) + require.Empty(t, err) + + require.Equal(t, "3", queryRes) +} + +func TestIBCChannelClose(t *testing.T) { + ctx, keeper, codeID, _, walletA, privKeyA, _, _ := setupTest(t, "./testdata/ibc/contract.wasm", sdk.NewCoins()) + + _, _, contractAddress, _, err := initHelper(t, keeper, ctx, codeID, walletA, privKeyA, `{"init":{}}`, true, true, defaultGasForTests) + require.Empty(t, err) + + for _, test := range []struct { + description string + connectionID string + output string + isSuccess bool + hasAttributes bool + hasEvents bool + }{ + { + description: "Default", + connectionID: "0", + output: "6", + isSuccess: true, + hasAttributes: false, + hasEvents: false, + }, + { + description: "SubmessageNoReply", + connectionID: "1", + output: "12", + isSuccess: true, + hasAttributes: false, + hasEvents: false, + }, + { + description: "SubmessageWithReply", + connectionID: "2", + output: "19", + isSuccess: true, + hasAttributes: false, + hasEvents: false, + }, + { + description: "Attributes", + connectionID: "3", + output: "9", + isSuccess: true, + hasAttributes: true, + hasEvents: false, + }, + { + description: "Events", + connectionID: "4", + output: "10", + isSuccess: true, + hasAttributes: false, + hasEvents: true, + }, + { + description: "Error", + connectionID: "5", + output: "", + isSuccess: false, + hasAttributes: false, + hasEvents: false, + }, + } { + t.Run(test.description, func(t *testing.T) { + ibcChannel := v1types.IBCChannel{ + Endpoint: createIBCEndpoint(PortIDForContract(contractAddress), "channel.0"), + CounterpartyEndpoint: createIBCEndpoint(PortIDForContract(contractAddress), "channel.1"), + Order: v1types.Unordered, + Version: "1", + ConnectionID: test.connectionID, + } + + ctx, events, err := ibcChannelCloseHelper(t, keeper, ctx, contractAddress, privKeyA, defaultGasForTests, true, ibcChannel) + + if !test.isSuccess { + require.Contains(t, fmt.Sprintf("%+v", err), "Intentional") + } else { + require.Empty(t, err) + if test.hasAttributes { + require.Equal(t, + []ContractEvent{ + { + {Key: "contract_address", Value: contractAddress.String()}, + {Key: "attr1", Value: "😗"}, + }, + }, + events, + ) + } + + if test.hasEvents { + hadCyber1 := false + evts := ctx.EventManager().Events() + for _, e := range evts { + if e.Type == "wasm-cyber1" { + require.False(t, hadCyber1) + attrs, err := parseAndDecryptAttributes(e.Attributes, []byte{}, false) + require.Empty(t, err) + + require.Equal(t, + []v010cosmwasm.LogAttribute{ + {Key: "contract_address", Value: contractAddress.String()}, + {Key: "attr1", Value: "🤯"}, + }, + attrs, + ) + + hadCyber1 = true + } + } + + require.True(t, hadCyber1) + } + + queryRes, err := queryHelper(t, keeper, ctx, contractAddress, `{"q":{}}`, true, true, math.MaxUint64) + + require.Empty(t, err) + + require.Equal(t, test.output, queryRes) + } + }) + } +} + +func TestIBCChannelCloseInit(t *testing.T) { + ctx, keeper, codeID, _, walletA, privKeyA, _, _ := setupTest(t, "./testdata/ibc/contract.wasm", sdk.NewCoins()) + + _, _, contractAddress, _, err := initHelper(t, keeper, ctx, codeID, walletA, privKeyA, `{"init":{}}`, true, true, defaultGasForTests) + require.Empty(t, err) + + ibcChannel := v1types.IBCChannel{ + Endpoint: createIBCEndpoint(PortIDForContract(contractAddress), "channel.0"), + CounterpartyEndpoint: createIBCEndpoint(PortIDForContract(contractAddress), "channel.1"), + Order: v1types.Unordered, + Version: "1", + ConnectionID: "1", + } + + ctx, _, err = ibcChannelCloseHelper(t, keeper, ctx, contractAddress, privKeyA, defaultGasForTests, false, ibcChannel) + require.Empty(t, err) + + queryRes, err := queryHelper(t, keeper, ctx, contractAddress, `{"q":{}}`, true, true, math.MaxUint64) + require.Empty(t, err) + + require.Equal(t, "5", queryRes) +} diff --git a/x/compute/internal/keeper/testdata/ibc/src/contract.rs b/x/compute/internal/keeper/testdata/ibc/src/contract.rs index 182027ba5..6d93290d9 100644 --- a/x/compute/internal/keeper/testdata/ibc/src/contract.rs +++ b/x/compute/internal/keeper/testdata/ibc/src/contract.rs @@ -2,8 +2,9 @@ use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; use crate::state::{count, count_read}; use cosmwasm_std::{ entry_point, to_binary, Binary, CosmosMsg, Deps, DepsMut, Env, Event, Ibc3ChannelOpenResponse, - IbcBasicResponse, IbcChannelConnectMsg, IbcChannelOpenMsg, IbcChannelOpenResponse, MessageInfo, - Reply, ReplyOn, Response, StdError, StdResult, SubMsg, SubMsgResult, WasmMsg, + IbcBasicResponse, IbcChannelCloseMsg, IbcChannelConnectMsg, IbcChannelOpenMsg, + IbcChannelOpenResponse, MessageInfo, Reply, ReplyOn, Response, StdError, StdResult, SubMsg, + SubMsgResult, WasmMsg, }; pub const IBC_APP_VERSION: &str = "ibc-v1"; @@ -78,6 +79,41 @@ pub fn increment(deps: DepsMut, c: u64) -> StdResult { Ok(resp) } +pub fn get_resp_based_on_num(env: Env, num: u64) -> StdResult { + match num { + 0 => Ok(IbcBasicResponse::default()), + 1 => Ok(IbcBasicResponse::new().add_submessage(SubMsg { + id: 1, + msg: CosmosMsg::Wasm(WasmMsg::Execute { + code_hash: env.contract.code_hash, + contract_addr: env.contract.address.into_string(), + msg: Binary::from("{\"increment\":{\"addition\":5}}".as_bytes().to_vec()), + funds: vec![], + }) + .into(), + reply_on: ReplyOn::Never, + gas_limit: None, + })), + 2 => Ok(IbcBasicResponse::new().add_submessage(SubMsg { + id: 1, + msg: CosmosMsg::Wasm(WasmMsg::Execute { + code_hash: env.contract.code_hash, + contract_addr: env.contract.address.into_string(), + msg: Binary::from("{\"increment\":{\"addition\":5}}".as_bytes().to_vec()), + funds: vec![], + }) + .into(), + reply_on: ReplyOn::Always, + gas_limit: None, + })), + 3 => Ok(IbcBasicResponse::new().add_attribute("attr1", "😗")), + 4 => Ok(IbcBasicResponse::new() + .add_event(Event::new("cyber1".to_string()).add_attribute("attr1", "🤯"))), + 5 => Err(StdError::generic_err("Intentional")), + _ => Err(StdError::generic_err("Unsupported channel connect type")), + } +} + #[entry_point] pub fn ibc_channel_connect( deps: DepsMut, @@ -99,39 +135,33 @@ pub fn ibc_channel_connect( count(deps.storage).save(&(num + 4))?; - match num { - 0 => Ok(IbcBasicResponse::default()), - 1 => Ok(IbcBasicResponse::new().add_submessage(SubMsg { - id: 1, - msg: CosmosMsg::Wasm(WasmMsg::Execute { - code_hash: env.contract.code_hash, - contract_addr: env.contract.address.into_string(), - msg: Binary::from("{\"increment\":{\"addition\":5}}".as_bytes().to_vec()), - funds: vec![], - }) - .into(), - reply_on: ReplyOn::Never, - gas_limit: None, - })), - 2 => Ok(IbcBasicResponse::new().add_submessage(SubMsg { - id: 1, - msg: CosmosMsg::Wasm(WasmMsg::Execute { - code_hash: env.contract.code_hash, - contract_addr: env.contract.address.into_string(), - msg: Binary::from("{\"increment\":{\"addition\":5}}".as_bytes().to_vec()), - funds: vec![], - }) - .into(), - reply_on: ReplyOn::Always, - gas_limit: None, - })), - 3 => Ok(IbcBasicResponse::new().add_attribute("attr1", "😗")), - 4 => Ok(IbcBasicResponse::new() - .add_event(Event::new("cyber1".to_string()).add_attribute("attr1", "🤯"))), - 5 => Err(StdError::generic_err("Intentional")), - _ => Err(StdError::generic_err("Unsupported channel connect type")), - } + get_resp_based_on_num(env, num) } _ => Err(StdError::generic_err("Unsupported channel connect")), } } + +#[entry_point] +/// On closed channel, we take all tokens from reflect contract to this contract. +/// We also delete the channel entry from accounts. +pub fn ibc_channel_close( + deps: DepsMut, + env: Env, + msg: IbcChannelCloseMsg, +) -> StdResult { + match msg { + IbcChannelCloseMsg::CloseInit { channel: _ } => { + count(deps.storage).save(&5)?; + Ok(IbcBasicResponse::default()) + } + IbcChannelCloseMsg::CloseConfirm { channel } => { + let num: u64 = channel.connection_id.parse::().map_err(|err| { + StdError::generic_err(format!("Got an error from parsing: {:?}", err)) + })?; + + count(deps.storage).save(&(num + 6))?; + get_resp_based_on_num(env, num) + } + _ => Err(StdError::generic_err("Unsupported channel close")), + } +}