Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

imp: ehnance ICS-07 CosmWasm tests using Timestamp::now() + add test_cw_client_expiry #1181

Merged
merged 1 commit into from
Apr 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 90 additions & 48 deletions ibc-clients/ics07-tendermint/cw-contract/src/tests/fixture.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
use std::ops::Add;
use std::time::Duration;

use cosmwasm_std::testing::mock_env;
use cosmwasm_std::{from_json, Deps, DepsMut, Empty, StdError};
use cosmwasm_std::{from_json, Deps, DepsMut, Empty, Response, StdError, StdResult};
use ibc_client_cw::types::{
CheckForMisbehaviourMsgRaw, ExportMetadataMsg, GenesisMetadata, InstantiateMsg, QueryMsg,
QueryResponse, StatusMsg, VerifyClientMessageRaw,
CheckForMisbehaviourMsgRaw, ContractError, ExportMetadataMsg, GenesisMetadata, InstantiateMsg,
MigrationPrefix, QueryMsg, QueryResponse, StatusMsg, UpdateStateMsgRaw,
UpdateStateOnMisbehaviourMsgRaw, VerifyClientMessageRaw,
};
use ibc_client_cw::utils::AnyCodec;
use ibc_client_tendermint::client_state::ClientState as TmClientState;
Expand All @@ -15,9 +14,10 @@ use ibc_core::client::types::{Height, Status};
use ibc_core::host::types::identifiers::ChainId;
use ibc_core::primitives::Timestamp;
use ibc_testkit::fixtures::clients::tendermint::ClientStateConfig;
use tendermint::Time;
use tendermint_testgen::{Generator, Validator};

use super::helper::{dummy_checksum, dummy_sov_consensus_state};
use super::helper::{dummy_checksum, dummy_sov_consensus_state, mock_env_with_timestamp_now};
use crate::entrypoint::TendermintContext;

/// Test fixture
Expand All @@ -26,63 +26,70 @@ pub struct Fixture {
pub chain_id: ChainId,
pub trusted_timestamp: Timestamp,
pub trusted_height: Height,
pub target_height: Height,
pub validators: Vec<Validator>,
pub migration_mode: bool,
pub migration_prefix: MigrationPrefix,
}

impl Default for Fixture {
fn default() -> Self {
Fixture {
chain_id: ChainId::new("test-chain").unwrap(),
// Returns a dummy timestamp for testing purposes. The value corresponds to the
// timestamp of the `mock_env()`.
trusted_timestamp: Timestamp::from_nanoseconds(1_571_797_419_879_305_533)
.expect("never fails"),
trusted_timestamp: Timestamp::now(),
trusted_height: Height::new(0, 5).unwrap(),
target_height: Height::new(0, 10).unwrap(),
validators: vec![
Validator::new("1").voting_power(40),
Validator::new("2").voting_power(30),
Validator::new("3").voting_power(30),
],
migration_mode: false,
migration_prefix: MigrationPrefix::None,
}
}
}

impl Fixture {
pub fn migration_mode(mut self) -> Self {
self.migration_mode = true;
self
pub fn set_migration_prefix(&mut self, migration_mode: MigrationPrefix) {
self.migration_prefix = migration_mode;
}

pub fn ctx_ref<'a>(&self, deps: Deps<'a, Empty>) -> TendermintContext<'a> {
let mut ctx = TendermintContext::new_ref(deps, mock_env()).expect("never fails");

if self.migration_mode {
ctx.set_subject_prefix();
let mut ctx =
TendermintContext::new_ref(deps, mock_env_with_timestamp_now()).expect("never fails");

match self.migration_prefix {
MigrationPrefix::None => {}
MigrationPrefix::Subject => {
ctx.set_subject_prefix();
}
MigrationPrefix::Substitute => {
ctx.set_substitute_prefix();
}
};

ctx
}

pub fn ctx_mut<'a>(&self, deps: DepsMut<'a, Empty>) -> TendermintContext<'a> {
let mut ctx = TendermintContext::new_mut(deps, mock_env()).expect("never fails");

if self.migration_mode {
ctx.set_subject_prefix();
let mut ctx =
TendermintContext::new_mut(deps, mock_env_with_timestamp_now()).expect("never fails");

match self.migration_prefix {
MigrationPrefix::None => {}
MigrationPrefix::Subject => {
ctx.set_subject_prefix();
}
MigrationPrefix::Substitute => {
ctx.set_substitute_prefix();
}
};

ctx
}

pub fn dummy_instantiate_msg(&self) -> InstantiateMsg {
// Setting the `trusting_period` to 1 second allows the quick client
// freeze for the `happy_cw_client_recovery` test.

// Setting the `trusting_period` to 1 second allows the quick
// client expiry for the tests.
let tm_client_state: TmClientState = ClientStateConfig::builder()
.chain_id("test-chain".parse().unwrap())
.chain_id(self.chain_id.clone())
.trusting_period(Duration::from_secs(1))
.latest_height(self.trusted_height)
.build()
Expand All @@ -99,19 +106,10 @@ impl Fixture {
}

fn dummy_header(&self, header_height: Height) -> Vec<u8> {
// NOTE: since mock context has a fixed timestamp, we only can add up
// to allowed clock drift (3s)
let future_time = self
.trusted_timestamp
.add(Duration::from_secs(2))
.expect("never fails")
.into_tm_time()
.expect("Time exists");

let header = tendermint_testgen::Header::new(&self.validators)
.chain_id(self.chain_id.as_str())
.height(header_height.revision_height())
.time(future_time)
.time(Time::now())
.next_validators(&self.validators)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can take it as argument to the method.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's refactor for parametrized testing in separate PRs.

.app_hash(vec![0; 32].try_into().expect("never fails"));

Expand All @@ -129,8 +127,8 @@ impl Fixture {
Header::encode_to_any_vec(tm_header)
}

pub fn dummy_client_message(&self) -> Vec<u8> {
self.dummy_header(self.target_height)
pub fn dummy_client_message(&self, target_height: Height) -> Vec<u8> {
self.dummy_header(target_height)
}

/// Constructs a dummy misbehaviour message that is one block behind the
Expand All @@ -142,39 +140,83 @@ impl Fixture {
}

pub fn verify_client_message(&self, deps: Deps<'_>, client_message: Vec<u8>) {
let resp = self.query(deps, VerifyClientMessageRaw { client_message }.into());
let resp = self
.query(deps, VerifyClientMessageRaw { client_message }.into())
.unwrap();

assert!(resp.is_valid);
assert!(resp.status.is_none());
assert!(resp.found_misbehaviour.is_none());
}

pub fn check_for_misbehaviour(&self, deps: Deps<'_>, client_message: Vec<u8>) {
let resp = self.query(deps, CheckForMisbehaviourMsgRaw { client_message }.into());
let resp = self
.query(deps, CheckForMisbehaviourMsgRaw { client_message }.into())
.unwrap();

assert!(resp.is_valid);
assert_eq!(resp.found_misbehaviour, Some(true));
}

pub fn check_client_status(&self, deps: Deps<'_>, expected: Status) {
let resp = self.query(deps, StatusMsg {}.into());
let resp = self.query(deps, StatusMsg {}.into()).unwrap();

assert_eq!(resp.status, Some(expected.to_string()));
}

pub fn get_metadata(&self, deps: Deps<'_>) -> Option<Vec<GenesisMetadata>> {
self.query(deps, ExportMetadataMsg {}.into())
.genesis_metadata
.map(|resp| resp.genesis_metadata)
.unwrap()
}

pub fn query(&self, deps: Deps<'_>, msg: QueryMsg) -> QueryResponse {
pub fn query(&self, deps: Deps<'_>, msg: QueryMsg) -> StdResult<QueryResponse> {
let ctx = self.ctx_ref(deps);

let resp_bytes = ctx
.query(msg)
.map_err(|e| StdError::generic_err(e.to_string()))
.map_err(|e| StdError::generic_err(e.to_string()))?;

from_json(resp_bytes)
}

pub fn create_client(&self, deps_mut: DepsMut<'_>) -> Result<Response, ContractError> {
let mut ctx = self.ctx_mut(deps_mut);

let instantiate_msg = self.dummy_instantiate_msg();

let data = ctx.instantiate(instantiate_msg)?;

Ok(Response::default().set_data(data))
}

pub fn update_client(
&self,
deps_mut: DepsMut<'_>,
target_height: Height,
) -> Result<Response, ContractError> {
let client_message = self.dummy_client_message(target_height);

self.verify_client_message(deps_mut.as_ref(), client_message.clone());

let mut ctx = self.ctx_mut(deps_mut);

let data = ctx.sudo(UpdateStateMsgRaw { client_message }.into())?;

Ok(Response::default().set_data(data))
}

pub fn update_client_on_misbehaviour(&self, deps_mut: DepsMut<'_>) -> Response {
let client_message = self.dummy_misbehaviour_message();

self.check_for_misbehaviour(deps_mut.as_ref(), client_message.clone());

let mut ctx = self.ctx_mut(deps_mut);

let data = ctx
.sudo(UpdateStateOnMisbehaviourMsgRaw { client_message }.into())
.unwrap();

from_json(resp_bytes).unwrap()
Response::default().set_data(data)
}
}
17 changes: 13 additions & 4 deletions ibc-clients/ics07-tendermint/cw-contract/src/tests/helper.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use std::str::FromStr;

use cosmwasm_std::testing::mock_info;
use cosmwasm_std::{coins, MessageInfo};
use cosmwasm_std::testing::{mock_env, mock_info};
use cosmwasm_std::{coins, Env, MessageInfo, Timestamp as CwTimestamp};
use ibc_client_tendermint::types::ConsensusState;
use ibc_core::primitives::Timestamp;
use ibc_core::primitives::Timestamp as IbcTimestamp;
use tendermint::Hash;

pub fn dummy_msg_info() -> MessageInfo {
Expand All @@ -15,7 +15,7 @@ pub fn dummy_checksum() -> Vec<u8> {
.expect("Never fails")
}

pub fn dummy_sov_consensus_state(timestamp: Timestamp) -> ConsensusState {
pub fn dummy_sov_consensus_state(timestamp: IbcTimestamp) -> ConsensusState {
ConsensusState::new(
vec![0].into(),
timestamp.into_tm_time().expect("Time exists"),
Expand All @@ -24,3 +24,12 @@ pub fn dummy_sov_consensus_state(timestamp: Timestamp) -> ConsensusState {
.expect("Never fails"),
)
}

/// Returns a mock environment with the current timestamp. This is define to use
/// for testing client expiry and other time-sensitive operations.
pub fn mock_env_with_timestamp_now() -> Env {
let mut env = mock_env();
let now_nanos = IbcTimestamp::now().nanoseconds();
env.block.time = CwTimestamp::from_nanos(now_nanos);
env
}
Loading
Loading