Skip to content

Commit

Permalink
Merge pull request #20 from neutron-org/feat/add-mock-submsg-failures…
Browse files Browse the repository at this point in the history
…-to-interchain-txs

[NTRN-561] add mock submsg failures to interchain txs contract
  • Loading branch information
pr0n00gler authored Jul 13, 2023
2 parents de3b4af + 81199e1 commit 1c8f5b2
Show file tree
Hide file tree
Showing 5 changed files with 203 additions and 39 deletions.
126 changes: 102 additions & 24 deletions contracts/neutron_interchain_txs/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,20 @@ use cosmos_sdk_proto::cosmos::staking::v1beta1::{
use cosmwasm_std::entry_point;
use cosmwasm_std::{
to_binary, Binary, Coin as CosmosCoin, CosmosMsg, CustomQuery, Deps, DepsMut, Env, MessageInfo,
Reply, Response, StdError, StdResult, SubMsg, Uint128,
Reply, ReplyOn, Response, StdError, StdResult, SubMsg, Uint128,
};
use cw2::set_contract_version;
use prost::Message;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

use crate::integration_tests_mock_handlers::{set_sudo_failure_mock, unset_sudo_failure_mock};
use crate::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg};
use crate::integration_tests_mock_handlers::{
set_sudo_failure_mock, set_sudo_submsg_failure_in_reply_mock, set_sudo_submsg_failure_mock,
unset_sudo_failure_mock,
};
use crate::msg::{
AcknowledgementResultsResponse, ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg,
};
use neutron_sdk::{
bindings::{
msg::{IbcFee, MsgSubmitTxResponse, NeutronMsg},
Expand All @@ -45,8 +50,9 @@ use neutron_sdk::{
use crate::storage::{
add_error_to_queue, read_errors_from_queue, read_reply_payload, read_sudo_payload,
save_reply_payload, save_sudo_payload, AcknowledgementResult, IntegrationTestsSudoMock,
SudoPayload, ACKNOWLEDGEMENT_RESULTS, IBC_FEE, INTEGRATION_TESTS_SUDO_MOCK,
INTERCHAIN_ACCOUNTS, SUDO_PAYLOAD_REPLY_ID,
IntegrationTestsSudoSubmsgMock, SudoPayload, ACKNOWLEDGEMENT_RESULTS, IBC_FEE,
INTEGRATION_TESTS_SUDO_FAILURE_MOCK, INTEGRATION_TESTS_SUDO_SUBMSG_FAILURE_MOCK,
INTERCHAIN_ACCOUNTS, SUDO_FAILING_SUBMSG_REPLY_ID, SUDO_PAYLOAD_REPLY_ID,
};

// Default timeout for SubmitTX is two weeks
Expand Down Expand Up @@ -129,11 +135,17 @@ pub fn execute(
timeout_fee,
} => execute_set_fees(deps, denom, recv_fee, ack_fee, timeout_fee),
ExecuteMsg::CleanAckResults {} => execute_clean_ack_results(deps),
// Used only in integration tests framework to simulate failures.
// After executing this message, contract fail, all of this happening
// in sudo callback handler.

// The section below is used only in integration tests framework to simulate failures.
ExecuteMsg::IntegrationTestsSetSudoFailureMock {} => set_sudo_failure_mock(deps),
ExecuteMsg::IntegrationTestsSetSudoSubmsgFailureMock {} => {
set_sudo_submsg_failure_mock(deps)
}
ExecuteMsg::IntegrationTestsSetSudoSubmsgReplyFailureMock {} => {
set_sudo_submsg_failure_in_reply_mock(deps)
}
ExecuteMsg::IntegrationTestsUnsetSudoFailureMock {} => unset_sudo_failure_mock(deps),
ExecuteMsg::IntegrationTestsSudoSubmsg {} => integration_tests_sudo_submsg(deps),
}
}

Expand All @@ -151,6 +163,7 @@ pub fn query(deps: Deps<NeutronQuery>, env: Env, msg: QueryMsg) -> NeutronResult
interchain_account_id,
sequence_id,
} => query_acknowledgement_result(deps, env, interchain_account_id, sequence_id),
QueryMsg::AcknowledgementResults {} => query_acknowledgement_results(deps),
QueryMsg::ErrorsQueue {} => query_errors_queue(deps),
}
}
Expand Down Expand Up @@ -190,6 +203,22 @@ pub fn query_acknowledgement_result(
Ok(to_binary(&res)?)
}

pub fn query_acknowledgement_results(deps: Deps<NeutronQuery>) -> NeutronResult<Binary> {
let results: Vec<AcknowledgementResultsResponse> = ACKNOWLEDGEMENT_RESULTS
.range(deps.storage, None, None, cosmwasm_std::Order::Ascending)
.take(100)
.map(|p| {
p.map(|(key_pair, ack_result)| AcknowledgementResultsResponse {
ack_result,
port_id: key_pair.0,
sequence_id: key_pair.1,
})
})
.collect::<StdResult<Vec<AcknowledgementResultsResponse>>>()?;

Ok(to_binary(&results)?)
}

pub fn query_errors_queue(deps: Deps<NeutronQuery>) -> NeutronResult<Binary> {
let res = read_errors_from_queue(deps.storage)?;
Ok(to_binary(&res)?)
Expand Down Expand Up @@ -358,42 +387,77 @@ fn execute_clean_ack_results(deps: DepsMut) -> StdResult<Response<NeutronMsg>> {
Ok(Response::default())
}

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn sudo(deps: DepsMut, env: Env, msg: SudoMsg) -> StdResult<Response> {
deps.api
.debug(format!("WASMDEBUG: sudo: received sudo msg: {:?}", msg).as_str());

if let Some(IntegrationTestsSudoMock::Enabled {}) =
INTEGRATION_TESTS_SUDO_MOCK.may_load(deps.storage)?
fn integration_tests_sudo_submsg(deps: DepsMut) -> StdResult<Response<NeutronMsg>> {
if let Some(IntegrationTestsSudoSubmsgMock::Enabled {}) =
INTEGRATION_TESTS_SUDO_SUBMSG_FAILURE_MOCK.may_load(deps.storage)?
{
// Used only in integration tests framework to simulate failures.
deps.api
.debug("WASMDEBUG: sudo: mocked failure on the handler");
.debug("WASMDEBUG: sudo: mocked submsg failure on the handler");

return Err(StdError::GenericErr {
msg: "Integations test mock error".to_string(),
msg: "Integations test mock submsg error".to_string(),
});
}
Ok(Response::default())
}

match msg {
SudoMsg::Response { request, data } => sudo_response(deps, request, data),
SudoMsg::Error { request, details } => sudo_error(deps, request, details),
SudoMsg::Timeout { request } => sudo_timeout(deps, env, request),
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn sudo(deps: DepsMut, env: Env, msg: SudoMsg) -> StdResult<Response> {
let api = deps.api;
api.debug(format!("WASMDEBUG: sudo: received sudo msg: {:?}", msg).as_str());

let failure_mock_enabled = Some(IntegrationTestsSudoMock::Enabled {})
== INTEGRATION_TESTS_SUDO_FAILURE_MOCK.may_load(deps.storage)?;
let failure_submsg_mock_enabled = {
let m = INTEGRATION_TESTS_SUDO_SUBMSG_FAILURE_MOCK.may_load(deps.storage)?;
m == Some(IntegrationTestsSudoSubmsgMock::Enabled {})
|| m == Some(IntegrationTestsSudoSubmsgMock::EnabledInReply {})
};

let mut resp: Response = match msg {
SudoMsg::Response { request, data } => sudo_response(deps, request, data)?,
SudoMsg::Error { request, details } => sudo_error(deps, request, details)?,
SudoMsg::Timeout { request } => sudo_timeout(deps, env.clone(), request)?,
SudoMsg::OpenAck {
port_id,
channel_id,
counterparty_channel_id,
counterparty_version,
} => sudo_open_ack(
deps,
env,
env.clone(),
port_id,
channel_id,
counterparty_channel_id,
counterparty_version,
),
_ => Ok(Response::default()),
)?,
_ => Response::default(),
};

if failure_mock_enabled {
// Used only in integration tests framework to simulate failures.
api.debug("WASMDEBUG: sudo: mocked failure on the handler");

return Err(StdError::GenericErr {
msg: "Integations test mock error".to_string(),
});
}

if failure_submsg_mock_enabled {
resp = resp.add_submessage(SubMsg {
id: SUDO_FAILING_SUBMSG_REPLY_ID,
msg: CosmosMsg::Wasm(cosmwasm_std::WasmMsg::Execute {
contract_addr: env.contract.address.to_string(),
msg: to_binary(&ExecuteMsg::IntegrationTestsSudoSubmsg {})?,
funds: vec![],
}),
gas_limit: None,
reply_on: ReplyOn::Success,
})
};

Ok(resp)
}

#[cfg_attr(not(feature = "library"), entry_point)]
Expand Down Expand Up @@ -677,6 +741,20 @@ pub fn reply(deps: DepsMut, env: Env, msg: Reply) -> StdResult<Response> {
.debug(format!("WASMDEBUG: reply msg: {:?}", msg).as_str());
match msg.id {
SUDO_PAYLOAD_REPLY_ID => prepare_sudo_payload(deps, env, msg),
SUDO_FAILING_SUBMSG_REPLY_ID => {
if let Some(IntegrationTestsSudoSubmsgMock::EnabledInReply {}) =
INTEGRATION_TESTS_SUDO_SUBMSG_FAILURE_MOCK.may_load(deps.storage)?
{
// Used only in integration tests framework to simulate failures.
deps.api
.debug("WASMDEBUG: sudo: mocked reply failure on the handler");

return Err(StdError::GenericErr {
msg: "Integations test mock reply error".to_string(),
});
}
Ok(Response::default())
}
_ => Err(StdError::generic_err(format!(
"unsupported reply message id {}",
msg.id
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,32 @@
use crate::storage::{IntegrationTestsSudoMock, INTEGRATION_TESTS_SUDO_MOCK};
use crate::storage::{
IntegrationTestsSudoMock, IntegrationTestsSudoSubmsgMock, INTEGRATION_TESTS_SUDO_FAILURE_MOCK,
INTEGRATION_TESTS_SUDO_SUBMSG_FAILURE_MOCK,
};
use cosmwasm_std::{DepsMut, Response, StdResult};
use neutron_sdk::bindings::msg::NeutronMsg;

pub fn set_sudo_failure_mock(deps: DepsMut) -> StdResult<Response<NeutronMsg>> {
INTEGRATION_TESTS_SUDO_MOCK.save(deps.storage, &IntegrationTestsSudoMock::Enabled)?;
INTEGRATION_TESTS_SUDO_FAILURE_MOCK.save(deps.storage, &IntegrationTestsSudoMock::Enabled)?;
Ok(Response::default())
}

pub fn set_sudo_submsg_failure_mock(deps: DepsMut) -> StdResult<Response<NeutronMsg>> {
INTEGRATION_TESTS_SUDO_SUBMSG_FAILURE_MOCK
.save(deps.storage, &IntegrationTestsSudoSubmsgMock::Enabled)?;
Ok(Response::default())
}

pub fn set_sudo_submsg_failure_in_reply_mock(deps: DepsMut) -> StdResult<Response<NeutronMsg>> {
INTEGRATION_TESTS_SUDO_SUBMSG_FAILURE_MOCK.save(
deps.storage,
&IntegrationTestsSudoSubmsgMock::EnabledInReply,
)?;
Ok(Response::default())
}

pub fn unset_sudo_failure_mock(deps: DepsMut) -> StdResult<Response<NeutronMsg>> {
INTEGRATION_TESTS_SUDO_MOCK.save(deps.storage, &IntegrationTestsSudoMock::Disabled)?;
INTEGRATION_TESTS_SUDO_FAILURE_MOCK.save(deps.storage, &IntegrationTestsSudoMock::Disabled)?;
INTEGRATION_TESTS_SUDO_SUBMSG_FAILURE_MOCK
.save(deps.storage, &IntegrationTestsSudoSubmsgMock::Disabled)?;
Ok(Response::default())
}
24 changes: 22 additions & 2 deletions contracts/neutron_interchain_txs/src/msg.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::storage::AcknowledgementResult;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

Expand All @@ -18,6 +19,8 @@ pub enum QueryMsg {
interchain_account_id: String,
sequence_id: u64,
},
// this query returns all acknowledgements stored in the contract's state
AcknowledgementResults {},
// this query returns non-critical errors list
ErrorsQueue {},
}
Expand Down Expand Up @@ -57,10 +60,27 @@ pub enum ExecuteMsg {
},
CleanAckResults {},
/// Used only in integration tests framework to simulate failures.
/// After executing this message, contract will fail, all of this happening
/// in sudo callback handler.
/// After executing this message, any sudo call to the contract will result in an error.
IntegrationTestsSetSudoFailureMock {},
/// Used only in integration tests framework to simulate failures.
/// After executing this message, any sudo call to the contract will result in an submessage
/// processing error.
IntegrationTestsSetSudoSubmsgFailureMock {},
/// Used only in integration tests framework to simulate failures.
/// After executing this message, any sudo call to the contract will result in an submessage
/// reply processing error.
IntegrationTestsSetSudoSubmsgReplyFailureMock {},
/// Used only in integration tests framework to simulate failures.
/// After executing this message, contract will revert back to normal behaviour.
IntegrationTestsUnsetSudoFailureMock {},
/// Used only in integration tests framework to simulate failures.
/// If the IntegrationTestsSetSudoSubmsgFailureMock has been called, this message will fail.
IntegrationTestsSudoSubmsg {},
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
pub struct AcknowledgementResultsResponse {
pub ack_result: AcknowledgementResult,
pub port_id: String,
pub sequence_id: u64,
}
18 changes: 15 additions & 3 deletions contracts/neutron_interchain_txs/src/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub struct SudoPayload {
}

pub const SUDO_PAYLOAD_REPLY_ID: u64 = 1;
pub const SUDO_FAILING_SUBMSG_REPLY_ID: u64 = 2;

pub const IBC_FEE: Item<IbcFee> = Item::new("ibc_fee");
pub const REPLY_ID_STORAGE: Item<Vec<u8>> = Item::new("reply_queue_id");
Expand Down Expand Up @@ -81,12 +82,23 @@ pub fn save_sudo_payload(
SUDO_PAYLOAD.save(store, (channel_id, seq_id), &to_vec(&payload)?)
}

/// Used only in integration tests framework to simulate failures.
pub const INTEGRATION_TESTS_SUDO_MOCK: Item<IntegrationTestsSudoMock> =
Item::new("integration_tests_sudo_mock");
/// Used only in integration tests framework to simulate failures in sudo handler.
pub const INTEGRATION_TESTS_SUDO_FAILURE_MOCK: Item<IntegrationTestsSudoMock> =
Item::new("integration_tests_sudo_failure_mock");
/// Used only in integration tests framework to simulate failures in submessages created in
/// sudo handler.
pub const INTEGRATION_TESTS_SUDO_SUBMSG_FAILURE_MOCK: Item<IntegrationTestsSudoSubmsgMock> =
Item::new("integration_tests_sudo_submsg_failure_mock");

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
pub enum IntegrationTestsSudoMock {
Enabled,
Disabled,
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
pub enum IntegrationTestsSudoSubmsgMock {
Enabled,
EnabledInReply,
Disabled,
}
49 changes: 42 additions & 7 deletions contracts/neutron_interchain_txs/src/testing/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,23 @@
// See the License for the specific language governing permissions and
// limitations under the License.

use std::marker::PhantomData;

use crate::contract::sudo;
use crate::msg::ExecuteMsg;
use crate::{
contract::query_errors_queue,
contract::{execute, query_errors_queue},
storage::{add_error_to_queue, read_errors_from_queue, ERRORS_QUEUE},
};

use cosmwasm_std::{
from_binary,
testing::{MockApi, MockQuerier, MockStorage},
OwnedDeps,
testing::{
mock_dependencies as cw_mock_dependencies, mock_env, mock_info, MockApi, MockQuerier,
MockStorage,
},
OwnedDeps, StdError,
};

use neutron_sdk::bindings::query::NeutronQuery;
use neutron_sdk::sudo::msg::{RequestPacket, SudoMsg};
use std::marker::PhantomData;

pub fn mock_dependencies() -> OwnedDeps<MockStorage, MockApi, MockQuerier, NeutronQuery> {
OwnedDeps {
Expand Down Expand Up @@ -90,3 +93,35 @@ fn test_errors_queue() {
]
);
}

#[test]
fn test_failure_mocks() {
let mut deps = cw_mock_dependencies();
execute(
deps.as_mut(),
mock_env(),
mock_info("", &[]),
ExecuteMsg::IntegrationTestsSetSudoFailureMock {},
)
.unwrap();

let src_port = String::from("src_port");
let src_channel = String::from("src_channel");
let dst_port = String::from("dst_port");
let dst_channel = String::from("dst_channel");
let sudo_resp = SudoMsg::Timeout {
request: RequestPacket {
sequence: Some(1u64),
source_port: Some(src_port),
source_channel: Some(src_channel),
destination_port: Some(dst_port),
destination_channel: Some(dst_channel),
data: None,
timeout_height: None,
timeout_timestamp: None,
},
};

let err = sudo(deps.as_mut(), mock_env(), sudo_resp).unwrap_err();
assert_eq!(err, StdError::generic_err("Integations test mock error"));
}

0 comments on commit 1c8f5b2

Please sign in to comment.