From d2c77d7f64fca679f2d419eb1e5890edb573ed03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Wed, 14 Feb 2024 11:15:51 +0000 Subject: [PATCH] integration test for failed DKG redoing --- contracts/Cargo.lock | 3 + contracts/coconut-test/Cargo.toml | 4 + contracts/coconut-test/src/helpers.rs | 10 +- .../src/redoing_upon_threshold.rs | 29 ++ contracts/coconut-test/src/test_wrapper.rs | 418 ++++++++++++++++++ contracts/coconut-test/src/tests.rs | 2 + 6 files changed, 457 insertions(+), 9 deletions(-) create mode 100644 contracts/coconut-test/src/redoing_upon_threshold.rs create mode 100644 contracts/coconut-test/src/test_wrapper.rs diff --git a/contracts/Cargo.lock b/contracts/Cargo.lock index bcd4743994..fdab88b8b7 100644 --- a/contracts/Cargo.lock +++ b/contracts/Cargo.lock @@ -178,6 +178,7 @@ dependencies = [ name = "coconut-test" version = "0.1.0" dependencies = [ + "bs58", "cosmwasm-std", "cosmwasm-storage", "cw-controllers", @@ -194,8 +195,10 @@ dependencies = [ "nym-coconut-dkg-common", "nym-group-contract-common", "nym-multisig-contract-common", + "rand_chacha 0.2.2", "schemars", "serde", + "subtle-encoding", "thiserror", ] diff --git a/contracts/coconut-test/Cargo.toml b/contracts/coconut-test/Cargo.toml index b7497b060b..22475dda1f 100644 --- a/contracts/coconut-test/Cargo.toml +++ b/contracts/coconut-test/Cargo.toml @@ -20,6 +20,8 @@ cw-storage-plus = { workspace = true } cw-controllers = { workspace = true } cw-utils = { workspace = true } +subtle-encoding = { version = "0.5", features = ["bech32-preview"] } +bs58 = "0.4.0" schemars = "0.8" serde = { version = "1.0.103", default-features = false, features = ["derive"] } thiserror = { workspace = true } @@ -30,6 +32,8 @@ cw-multi-test = { workspace = true } cw3-flex-multisig = { path = "../multisig/cw3-flex-multisig" } cw4-group = { path = "../multisig/cw4-group" } +rand_chacha = "0.2" + [[test]] name = "coconut-test" path = "src/tests.rs" diff --git a/contracts/coconut-test/src/helpers.rs b/contracts/coconut-test/src/helpers.rs index e0f667612d..df67e9c311 100644 --- a/contracts/coconut-test/src/helpers.rs +++ b/contracts/coconut-test/src/helpers.rs @@ -21,14 +21,6 @@ pub struct MigrateMsg { pub coconut_dkg_address: String, } -#[entry_point] -pub fn migrate(deps: DepsMut<'_>, _env: Env, msg: MigrateMsg) -> Result { - let mut cfg = CONFIG.load(deps.storage)?; - cfg.coconut_bandwidth_addr = deps.api.addr_validate(&msg.coconut_bandwidth_address)?; - CONFIG.save(deps.storage, &cfg)?; - Ok(Default::default()) -} - pub fn mock_app(init_funds: &[Coin]) -> App { AppBuilder::new().build(|router, _, storage| { router @@ -65,7 +57,7 @@ pub fn contract_multisig() -> Box> { cw3_flex_multisig::contract::instantiate, cw3_flex_multisig::contract::query, ) - .with_migrate(migrate); + .with_migrate(cw3_flex_multisig::contract::migrate); Box::new(contract) } diff --git a/contracts/coconut-test/src/redoing_upon_threshold.rs b/contracts/coconut-test/src/redoing_upon_threshold.rs new file mode 100644 index 0000000000..8e196453fe --- /dev/null +++ b/contracts/coconut-test/src/redoing_upon_threshold.rs @@ -0,0 +1,29 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::test_wrapper::TestSetup; +use nym_coconut_dkg_common::types::EpochState; + +#[test] +fn adding_enough_members_causes_dkg_redoing() { + let mut test = TestSetup::new(); + + let member1 = test.add_mock_group_member(None); + let member2 = test.add_mock_group_member(None); + + test.begin_dkg(); + test.full_dummy_dkg(vec![member1, member2], false); + + let member3 = test.add_mock_group_member(None); + let member4 = test.add_mock_group_member(None); + let member5 = test.add_mock_group_member(None); + + test.skip_to_dkg_state_end(); + test.unchecked_advance_dkg_epoch(); + + let epoch = test.epoch(); + assert_eq!( + epoch.state, + EpochState::PublicKeySubmission { resharing: false } + ); +} diff --git a/contracts/coconut-test/src/test_wrapper.rs b/contracts/coconut-test/src/test_wrapper.rs new file mode 100644 index 0000000000..f69dd0737c --- /dev/null +++ b/contracts/coconut-test/src/test_wrapper.rs @@ -0,0 +1,418 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::helpers::{contract_bandwidth, contract_dkg, contract_group, contract_multisig}; +use cosmwasm_std::testing::mock_dependencies; +use cosmwasm_std::{coins, Addr, Api, CanonicalAddr}; +use cw3::{Cw3Contract, ProposalListResponse, Status, Vote}; +use cw4::{Cw4Contract, Member}; +use cw_multi_test::{App, AppBuilder, Executor}; +use cw_utils::{Duration, Threshold}; +use nym_coconut_bandwidth_contract_common::msg::InstantiateMsg as BandwidthInstantiateMsg; +use nym_coconut_dkg_common::dealing::{chunk_dealing, DealingChunkInfo, MAX_DEALING_CHUNK_SIZE}; +use nym_coconut_dkg_common::msg::ExecuteMsg as DkgExecuteMsg; +use nym_coconut_dkg_common::msg::InstantiateMsg as DkgInstantiateMsg; +use nym_coconut_dkg_common::msg::QueryMsg as DkgQueryMsg; +use nym_coconut_dkg_common::types::{Epoch, State}; +use nym_group_contract_common::msg::InstantiateMsg as GroupInstantiateMsg; +use nym_multisig_contract_common::msg::InstantiateMsg as MultisigInstantiateMsg; +use nym_multisig_contract_common::msg::MigrateMsg as MultisigMigrateMsg; +use rand_chacha::rand_core::{RngCore, SeedableRng}; +use rand_chacha::ChaCha20Rng; +use subtle_encoding::bech32; + +pub const PREFIX: &str = "n"; +pub const TEST_DENOM: &str = "unym"; + +pub const BANDWIDTH_POOL: &str = "pool"; + +pub const BLOCK_TIME_SECS: u64 = 6; + +pub fn test_rng() -> ChaCha20Rng { + let dummy_seed = [42u8; 32]; + ChaCha20Rng::from_seed(dummy_seed) +} + +pub fn random_address(rng: &mut ChaCha20Rng) -> Addr { + let mut bytes = [0u8; 32]; + rng.fill_bytes(&mut bytes); + + Addr::unchecked(bech32::encode(PREFIX, bytes)) +} + +pub struct TestSetup { + pub app: App, + pub rng: ChaCha20Rng, + pub global_admin: Addr, + + pub multisig_contract: Cw3Contract, + pub group_contract: Cw4Contract, + pub dkg_contract: Addr, + pub bandwidth_contract: Addr, +} + +impl TestSetup { + pub fn new() -> Self { + let mut rng = test_rng(); + + let global_admin = random_address(&mut rng); + let mut app = AppBuilder::new().build(|router, _, storage| { + router + .bank + .init_balance(storage, &global_admin, coins(1000000000000000, TEST_DENOM)) + .unwrap(); + }); + + let group_id = app.store_code(contract_group()); + let multisig_id = app.store_code(contract_multisig()); + let bandwidth_id = app.store_code(contract_bandwidth()); + let dkg_id = app.store_code(contract_dkg()); + + // 1. init group contract + let group_contract = app + .instantiate_contract( + group_id, + global_admin.clone(), + &GroupInstantiateMsg { + admin: Some(global_admin.to_string()), + members: vec![], + }, + &[], + "group contract", + Some(global_admin.to_string()), + ) + .unwrap(); + + // 2. init multisig contract + let multisig_contract = app + .instantiate_contract( + multisig_id, + global_admin.clone(), + &MultisigInstantiateMsg { + group_addr: group_contract.to_string(), + coconut_bandwidth_contract_address: group_contract.to_string(), + coconut_dkg_contract_address: group_contract.to_string(), + threshold: Threshold::AbsolutePercentage { + percentage: "0.67".parse().unwrap(), + }, + max_voting_period: Duration::Time(3600), + executor: None, + proposal_deposit: None, + }, + &[], + "multisig contract", + Some(global_admin.to_string()), + ) + .unwrap(); + + // 3. init bandwidth contract + let bandwidth_contract = app + .instantiate_contract( + bandwidth_id, + global_admin.clone(), + &BandwidthInstantiateMsg { + multisig_addr: multisig_contract.to_string(), + pool_addr: BANDWIDTH_POOL.to_string(), + mix_denom: TEST_DENOM.to_string(), + }, + &[], + "bandwidth contract", + Some(global_admin.to_string()), + ) + .unwrap(); + + // 4. init dkg contract + let dkg_contract = app + .instantiate_contract( + dkg_id, + global_admin.clone(), + &DkgInstantiateMsg { + group_addr: group_contract.to_string(), + multisig_addr: multisig_contract.to_string(), + time_configuration: None, + mix_denom: TEST_DENOM.to_string(), + key_size: 5, + }, + &[], + "dkg contract", + Some(global_admin.to_string()), + ) + .unwrap(); + + // 5.migrate multisig contract with addresses of bandwidth and dkg contracts + app.migrate_contract( + global_admin.clone(), + multisig_contract.clone(), + &MultisigMigrateMsg { + coconut_bandwidth_address: bandwidth_contract.to_string(), + coconut_dkg_address: dkg_contract.to_string(), + }, + multisig_id, + ) + .unwrap(); + + TestSetup { + app, + rng, + global_admin, + multisig_contract: Cw3Contract(multisig_contract), + group_contract: Cw4Contract(group_contract), + dkg_contract, + bandwidth_contract, + } + } + + pub fn random_address(&mut self) -> Addr { + random_address(&mut self.rng) + } + + pub fn next_block(&mut self) { + self.app.update_block(|block| { + block.height += 1; + block.time = block.time.plus_seconds(BLOCK_TIME_SECS); + }) + } + + // if we ever want to expand those tests, those queries should be moved to contract specific structs + // (kinda like what cw4 has) + pub fn dkg_state(&self) -> State { + self.app + .wrap() + .query_wasm_smart(&self.dkg_contract, &DkgQueryMsg::GetState {}) + .unwrap() + } + + pub fn epoch(&self) -> Epoch { + self.app + .wrap() + .query_wasm_smart(&self.dkg_contract, &DkgQueryMsg::GetCurrentEpochState {}) + .unwrap() + } + + // TODO: this will not go beyond first page + pub fn all_proposals(&self) -> ProposalListResponse { + self.app + .wrap() + .query_wasm_smart( + &self.multisig_contract.0, + &nym_multisig_contract_common::msg::QueryMsg::ListProposals { + start_after: None, + limit: None, + }, + ) + .unwrap() + } + + pub fn admin(&self) -> Addr { + self.global_admin.clone() + } + + pub fn add_mock_group_member(&mut self, weight: Option) -> Addr { + let member_addr = self.random_address(); + let weight = weight.unwrap_or(10); + + self.app + .execute_contract( + self.admin(), + self.group_contract.addr(), + &nym_group_contract_common::msg::ExecuteMsg::UpdateMembers { + remove: vec![], + add: vec![Member { + addr: member_addr.to_string(), + weight, + }], + }, + &[], + ) + .unwrap(); + + member_addr + } + + pub fn begin_dkg(&mut self) { + self.app + .execute_contract( + self.admin(), + self.dkg_contract.clone(), + &DkgExecuteMsg::InitiateDkg {}, + &[], + ) + .unwrap(); + } + + pub fn skip_to_dkg_state_end(&mut self) { + let epoch = self.epoch(); + + self.app.update_block(|block| { + block.height += 42; + if let Some(finish_timestamp) = epoch.finish_timestamp { + block.time = finish_timestamp.plus_seconds(BLOCK_TIME_SECS); + } else { + block.time = block.time.plus_seconds(BLOCK_TIME_SECS) + } + }); + } + + pub fn advance_dkg_epoch(&mut self) { + self.skip_to_dkg_state_end(); + self.unchecked_advance_dkg_epoch(); + } + + pub fn unchecked_advance_dkg_epoch(&mut self) { + self.app + .execute_contract( + self.admin(), + self.dkg_contract.clone(), + &DkgExecuteMsg::AdvanceEpochState {}, + &[], + ) + .unwrap(); + } + + pub fn submit_dummy_dkg_keys(&mut self, member: &Addr, resharing: bool) { + let mut bte_key_with_proof = [0u8; 32]; + self.rng.fill_bytes(&mut bte_key_with_proof); + let bte_key_with_proof = bs58::encode(&bte_key_with_proof).into_string(); + + let mut identity_key = [0u8; 32]; + self.rng.fill_bytes(&mut identity_key); + let identity_key = bs58::encode(&identity_key).into_string(); + + let mut announce_address = [0u8; 16]; + self.rng.fill_bytes(&mut announce_address); + let announce_address = bs58::encode(&announce_address).into_string(); + + self.app + .execute_contract( + member.clone(), + self.dkg_contract.clone(), + &DkgExecuteMsg::RegisterDealer { + bte_key_with_proof, + identity_key, + announce_address, + resharing, + }, + &[], + ) + .unwrap(); + } + + pub fn submit_dummy_dealings(&mut self, member: &Addr, resharing: bool) { + let dealings = self.dkg_state().key_size; + + for dealing_index in 0..dealings { + let mut dealing_bytes = vec![0u8; 5000]; + self.rng.fill_bytes(&mut dealing_bytes); + + let chunks = DealingChunkInfo::construct(dealing_bytes.len(), MAX_DEALING_CHUNK_SIZE); + self.app + .execute_contract( + member.clone(), + self.dkg_contract.clone(), + &DkgExecuteMsg::CommitDealingsMetadata { + dealing_index, + chunks, + resharing, + }, + &[], + ) + .unwrap(); + self.next_block(); + + let chunks = chunk_dealing(dealing_index, dealing_bytes, MAX_DEALING_CHUNK_SIZE); + for (_, chunk) in chunks { + self.app + .execute_contract( + member.clone(), + self.dkg_contract.clone(), + &DkgExecuteMsg::CommitDealingsChunk { chunk, resharing }, + &[], + ) + .unwrap(); + self.next_block(); + } + } + } + + pub fn submit_dummy_vk_key(&mut self, member: &Addr, resharing: bool) { + let mut derived_vk = vec![0u8; 256]; + self.rng.fill_bytes(&mut derived_vk); + + let share = bs58::encode(&derived_vk).into_string(); + + self.app + .execute_contract( + member.clone(), + self.dkg_contract.clone(), + &DkgExecuteMsg::CommitVerificationKeyShare { share, resharing }, + &[], + ) + .unwrap(); + } + + pub fn validate_dummy_keys(&mut self, member: &Addr) { + for proposal in self.all_proposals().proposals { + if proposal.status == Status::Open { + self.app + .execute_contract( + member.clone(), + self.multisig_contract.addr(), + &nym_multisig_contract_common::msg::ExecuteMsg::Vote { + proposal_id: proposal.id, + vote: Vote::Yes, + }, + &[], + ) + .unwrap(); + } + } + } + + pub fn finalize_dummy_dkg(&mut self) { + for proposal in self.all_proposals().proposals { + assert_eq!(proposal.status, Status::Passed); + + self.app + .execute_contract( + self.admin(), + self.multisig_contract.addr(), + &nym_multisig_contract_common::msg::ExecuteMsg::Execute { + proposal_id: proposal.id, + }, + &[], + ) + .unwrap(); + } + } + + pub fn full_dummy_dkg(&mut self, members: Vec, resharing: bool) { + for member in &members { + self.submit_dummy_dkg_keys(member, resharing); + self.next_block(); + } + self.advance_dkg_epoch(); + + for member in &members { + self.submit_dummy_dealings(member, resharing); + self.next_block(); + } + self.advance_dkg_epoch(); + + for member in &members { + self.submit_dummy_vk_key(member, resharing); + self.next_block(); + } + self.advance_dkg_epoch(); + + for member in &members { + self.validate_dummy_keys(member); + self.next_block(); + } + self.advance_dkg_epoch(); + + self.finalize_dummy_dkg(); + self.next_block(); + + self.advance_dkg_epoch(); + } +} diff --git a/contracts/coconut-test/src/tests.rs b/contracts/coconut-test/src/tests.rs index 2ba5f4b2e8..3ad7c1bd64 100644 --- a/contracts/coconut-test/src/tests.rs +++ b/contracts/coconut-test/src/tests.rs @@ -3,5 +3,7 @@ mod deposit_and_release; mod helpers; +mod redoing_upon_threshold; mod spend_credential_creates_proposal; mod submit_vk_creates_proposal; +mod test_wrapper;