integration test for failed DKG redoing
This commit is contained in:
Generated
+3
@@ -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",
|
||||
]
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -21,14 +21,6 @@ pub struct MigrateMsg {
|
||||
pub coconut_dkg_address: String,
|
||||
}
|
||||
|
||||
#[entry_point]
|
||||
pub fn migrate(deps: DepsMut<'_>, _env: Env, msg: MigrateMsg) -> Result<Response, ContractError> {
|
||||
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<dyn Contract<Empty>> {
|
||||
cw3_flex_multisig::contract::instantiate,
|
||||
cw3_flex_multisig::contract::query,
|
||||
)
|
||||
.with_migrate(migrate);
|
||||
.with_migrate(cw3_flex_multisig::contract::migrate);
|
||||
Box::new(contract)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// 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 }
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,418 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// 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<u64>) -> 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<Addr>, 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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user