diff --git a/integration-tests/chain-signatures/tests/cases/mod.rs b/integration-tests/chain-signatures/tests/cases/mod.rs index 6d3a52a4b..fa1fbae6c 100644 --- a/integration-tests/chain-signatures/tests/cases/mod.rs +++ b/integration-tests/chain-signatures/tests/cases/mod.rs @@ -7,6 +7,8 @@ use crypto_shared::{self, derive_epsilon, derive_key, x_coordinate, ScalarExt}; use integration_tests_chain_signatures::containers::{self, DockerClient}; use integration_tests_chain_signatures::MultichainConfig; use k256::elliptic_curve::point::AffineCoordinates; +use mpc_contract::config::Config; +use mpc_contract::update::ProposeUpdateArgs; use mpc_node::kdf::into_eth_sig; use mpc_node::test_utils; use mpc_node::types::LatestBlockHeight; @@ -353,3 +355,40 @@ async fn test_multichain_reshare_with_lake_congestion() -> anyhow::Result<()> { }) .await } + +#[test(tokio::test)] +async fn test_multichain_update_contract() -> anyhow::Result<()> { + let config = MultichainConfig::default(); + with_multichain_nodes(config.clone(), |ctx| { + Box::pin(async move { + // Get into running state and produce a singular signature. + let state = wait_for::running_mpc(&ctx, Some(0)).await?; + wait_for::has_at_least_mine_triples(&ctx, 2).await?; + wait_for::has_at_least_mine_presignatures(&ctx, 1).await?; + actions::single_payload_signature_production(&ctx, &state).await?; + + // Perform update to the contract and see that the nodes are still properly running and picking + // up the new contract by first upgrading the contract, then trying to generate a new signature. + let id = ctx.propose_update_contract_default().await; + ctx.vote_update(id).await; + tokio::time::sleep(std::time::Duration::from_secs(3)).await; + wait_for::has_at_least_mine_presignatures(&ctx, 1).await?; + actions::single_payload_signature_production(&ctx, &state).await?; + + // Now do a config update and see if that also updates the same: + let id = ctx + .propose_update(ProposeUpdateArgs { + code: None, + config: Some(Config::default()), + }) + .await; + ctx.vote_update(id).await; + tokio::time::sleep(std::time::Duration::from_secs(3)).await; + wait_for::has_at_least_mine_presignatures(&ctx, 1).await?; + actions::single_payload_signature_production(&ctx, &state).await?; + + Ok(()) + }) + }) + .await +} diff --git a/integration-tests/chain-signatures/tests/lib.rs b/integration-tests/chain-signatures/tests/lib.rs index d2207a01c..f37ccaaf9 100644 --- a/integration-tests/chain-signatures/tests/lib.rs +++ b/integration-tests/chain-signatures/tests/lib.rs @@ -2,20 +2,25 @@ mod actions; mod cases; use crate::actions::wait_for; +use mpc_contract::update::{ProposeUpdateArgs, UpdateId}; use anyhow::anyhow; use futures::future::BoxFuture; use integration_tests_chain_signatures::containers::DockerClient; use integration_tests_chain_signatures::utils::{vote_join, vote_leave}; use integration_tests_chain_signatures::{run, utils, MultichainConfig, Nodes}; -use near_jsonrpc_client::JsonRpcClient; -use near_workspaces::types::SecretKey; +use near_jsonrpc_client::JsonRpcClient; +use near_workspaces::types::{NearToken, SecretKey}; use near_workspaces::{Account, AccountId}; use integration_tests_chain_signatures::local::NodeConfig; use std::str::FromStr; +const CURRENT_CONTRACT_DEPLOY_DEPOSIT: NearToken = NearToken::from_millinear(9000); +const CURRENT_CONTRACT_FILE_PATH: &str = + "../../target/wasm32-unknown-unknown/release/mpc_contract.wasm"; + pub struct MultichainTestContext<'a> { nodes: Nodes<'a>, rpc_client: near_fetch::Client, @@ -144,6 +149,55 @@ impl MultichainTestContext<'_> { Ok(self.nodes.kill_node(leaving_account_id).await.unwrap()) } + + pub async fn propose_update(&self, args: ProposeUpdateArgs) -> mpc_contract::update::UpdateId { + let accounts = self.nodes.near_accounts(); + accounts[0] + .call(self.nodes.ctx().mpc_contract.id(), "propose_update") + .args_borsh((args,)) + .max_gas() + .deposit(CURRENT_CONTRACT_DEPLOY_DEPOSIT) + .transact() + .await + .unwrap() + .json() + .unwrap() + } + + pub async fn propose_update_contract_default(&self) -> UpdateId { + let same_contract_bytes = std::fs::read(CURRENT_CONTRACT_FILE_PATH).unwrap(); + self.propose_update(ProposeUpdateArgs { + code: Some(same_contract_bytes), + config: None, + }) + .await + } + + pub async fn vote_update(&self, id: UpdateId) { + let participants = self.participant_accounts().await.unwrap(); + + let mut success = 0; + for account in participants.iter() { + let execution = account + .call(self.nodes.ctx().mpc_contract.id(), "vote_update") + .args_json((id,)) + .max_gas() + .transact() + .await + .unwrap() + .into_result(); + + match execution { + Ok(_) => success += 1, + Err(e) => tracing::warn!(?id, ?e, "Failed to vote for update"), + } + } + + assert!( + success >= self.cfg.threshold, + "did not successfully vote for update" + ); + } } pub async fn with_multichain_nodes(cfg: MultichainConfig, f: F) -> anyhow::Result<()>