diff --git a/cosmwasm/enclaves/shared/contract-engine/src/contract_operations.rs b/cosmwasm/enclaves/shared/contract-engine/src/contract_operations.rs index 56c607523..75540fbdd 100644 --- a/cosmwasm/enclaves/shared/contract-engine/src/contract_operations.rs +++ b/cosmwasm/enclaves/shared/contract-engine/src/contract_operations.rs @@ -11,13 +11,16 @@ use log::*; use crate::contract_validation::{ReplyParams, ValidatedMessage}; use crate::external::results::{HandleSuccess, InitSuccess, QuerySuccess}; -use crate::message::{parse_message, ParsedMessage}; +use crate::message::{is_ibc_msg, parse_message, ParsedMessage}; use super::contract_validation::{ generate_encryption_key, validate_contract_key, validate_msg, 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}; @@ -119,6 +122,7 @@ pub fn init( reply_params, &canonical_sender_address, false, + false, )?; Ok(output) @@ -169,8 +173,6 @@ pub fn handle( let canonical_contract_address = to_canonical(contract_address)?; - let canonical_sender_address = to_canonical(sender)?; - let contract_key = base_env.get_contract_key()?; validate_contract_key(&contract_key, &canonical_contract_address, &contract_code)?; @@ -189,12 +191,18 @@ pub fn handle( contract_hash_for_validation, } = parse_message(msg, &parsed_sig_info, &parsed_handle_type)?; + let mut canonical_sender_address = CanonicalAddr::from_vec(vec![]); + if should_validate_sig_info || was_msg_encrypted { + canonical_sender_address = to_canonical(sender)?; + } + // There is no signature to verify when the input isn't signed. // Receiving unsigned messages is only possible in Handle. (Init tx are always signed) // All of these functions go through handle but the data isn't signed: // Reply (that is not WASM reply) if should_validate_sig_info { // Verify env parameters against the signed tx + verify_params( &parsed_sig_info, sent_funds, @@ -225,8 +233,9 @@ pub fn handle( secret_msg.user_public_key, )?; - let mut versioned_env = - base_env.clone().into_versioned_env(&engine.contract_instance.cosmwasm_api_version); + let mut versioned_env = base_env + .clone() + .into_versioned_env(&engine.contract_instance.cosmwasm_api_version); versioned_env.set_contract_hash(&contract_hash); @@ -239,7 +248,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)?; @@ -249,15 +258,6 @@ pub fn handle( ); if was_msg_encrypted { - - let canonical_sender_address = CanonicalAddr::from_human(&sender).map_err(|err| { - warn!( - "handle got an error while trying to deserialize sender from bech32 string to bytes {:?}: {}", - sender, err - ); - EnclaveError::FailedToDeserialize - })?; - output = encrypt_output( output, &secret_msg, @@ -266,15 +266,18 @@ 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 finalized_output = finalize_raw_output(raw_output, false); + 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, 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!( @@ -371,6 +374,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 3e2f8690f..2c1c8f9fd 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 cw_types_v010::encoding::Binary; -use cw_types_v010::types::{CanonicalAddr, Coin}; +use cw_types_v010::types::{CanonicalAddr, Coin, LogAttribute}; use cw_types_v1::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: cw_types_v1::ibc::IbcBasicResponse, - }, OkIBCPacketReceive { #[serde(rename = "Ok")] ok: cw_types_v1::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(cw_types_v1::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,70 @@ 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 cw_types_v1::results::CosmosMsg::Wasm(wasm_msg) = &mut sub_msg.msg { + match wasm_msg { + cw_types_v1::results::WasmMsg::Execute { callback_sig, .. } + | cw_types_v1::results::WasmMsg::Instantiate { callback_sig, .. } => { + *callback_sig = Some(vec![]) + } + } + } + } + } + RawWasmOutput::OkIBCPacketReceive { ok } => { + for sub_msg in &mut ok.messages { + if let cw_types_v1::results::CosmosMsg::Wasm(wasm_msg) = &mut sub_msg.msg { + match wasm_msg { + cw_types_v1::results::WasmMsg::Execute { callback_sig, .. } + | cw_types_v1::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 +404,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 +428,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( @@ -525,6 +597,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, @@ -595,39 +672,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 cw_types_v1::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 cw_types_v1::results::CosmosMsg::Wasm(wasm_msg) = &mut sub_msg.msg { @@ -670,7 +714,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/contract-engine/src/message.rs b/cosmwasm/enclaves/shared/contract-engine/src/message.rs index d57fd4cec..605a48332 100644 --- a/cosmwasm/enclaves/shared/contract-engine/src/message.rs +++ b/cosmwasm/enclaves/shared/contract-engine/src/message.rs @@ -198,7 +198,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, @@ -361,12 +361,12 @@ pub fn parse_message( redact_custom_events(&mut parsed_encrypted_reply); let serialized_encrypted_reply : Vec = serde_json::to_vec(&parsed_encrypted_reply).map_err(|err| { - warn!( + warn!( "got an error while trying to serialize encrypted reply into bytes {:?}: {}", parsed_encrypted_reply, err ); - EnclaveError::FailedToSerialize - })?; + EnclaveError::FailedToSerialize + })?; let reply_secret_msg = SecretMessage { nonce: orig_secret_msg.nonce, @@ -454,12 +454,12 @@ pub fn parse_message( })?; let serialized_encrypted_reply : Vec = serde_json::to_vec(&parsed_encrypted_reply).map_err(|err| { - warn!( + warn!( "got an error while trying to serialize encrypted reply into bytes {:?}: {}", parsed_encrypted_reply, err ); - EnclaveError::FailedToSerialize - })?; + EnclaveError::FailedToSerialize + })?; let reply_secret_msg = SecretMessage { nonce: orig_secret_msg.nonce, @@ -526,3 +526,15 @@ 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, + } +} diff --git a/cosmwasm/enclaves/shared/cosmwasm-types/v1.0/src/ibc.rs b/cosmwasm/enclaves/shared/cosmwasm-types/v1.0/src/ibc.rs index 9091ae093..3d38d90b5 100644 --- a/cosmwasm/enclaves/shared/cosmwasm-types/v1.0/src/ibc.rs +++ b/cosmwasm/enclaves/shared/cosmwasm-types/v1.0/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-types/v1.0/src/results/response.rs b/cosmwasm/enclaves/shared/cosmwasm-types/v1.0/src/results/response.rs index 956636af7..c6f005472 100644 --- a/cosmwasm/enclaves/shared/cosmwasm-types/v1.0/src/results/response.rs +++ b/cosmwasm/enclaves/shared/cosmwasm-types/v1.0/src/results/response.rs @@ -1,9 +1,9 @@ use serde::{Deserialize, Serialize}; use std::fmt; -use cw_types_v010::{encoding::Binary, types::LogAttribute}; +use cw_types_v010::{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 bf83e12a5..cf478cf31 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..85d0c7282 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( @@ -108,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 @@ -142,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 { @@ -393,17 +401,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 +463,227 @@ 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) + 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) + } + }) + } +} + +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) - queryRes, err := queryHelper(t, keeper, ctx, contractAddress, `{"q":{}}`, true, true, math.MaxUint64) - 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, + } - require.Equal(t, "3", queryRes) + 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/keeper.go b/x/compute/internal/keeper/keeper.go index b3be628b9..691658b49 100644 --- a/x/compute/internal/keeper/keeper.go +++ b/x/compute/internal/keeper/keeper.go @@ -836,6 +836,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 161201802..c654a9514 100644 --- a/x/compute/internal/keeper/msg_dispatcher.go +++ b/x/compute/internal/keeper/msg_dispatcher.go @@ -141,7 +141,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 @@ -179,7 +191,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..6d93290d9 100644 --- a/x/compute/internal/keeper/testdata/ibc/src/contract.rs +++ b/x/compute/internal/keeper/testdata/ibc/src/contract.rs @@ -1,8 +1,8 @@ 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, + entry_point, to_binary, Binary, CosmosMsg, Deps, DepsMut, Env, Event, Ibc3ChannelOpenResponse, + IbcBasicResponse, IbcChannelCloseMsg, IbcChannelConnectMsg, IbcChannelOpenMsg, IbcChannelOpenResponse, MessageInfo, Reply, ReplyOn, Response, StdError, StdResult, SubMsg, SubMsgResult, WasmMsg, }; @@ -79,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, @@ -100,38 +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", "🤯"))), - _ => 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")), + } +}