From 17899f46b4f8ec7001fcd48368604310500c987b Mon Sep 17 00:00:00 2001 From: Eugene Boguslavsky Date: Fri, 24 May 2024 18:47:02 -0700 Subject: [PATCH] =?UTF-8?q?Revert=20"Revert=20"[native-bridge]=20-=20add?= =?UTF-8?q?=20e2e=20test=20to=20test=20Sui=20bridge=20pause."=20(#1?= =?UTF-8?q?=E2=80=A6"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 5094b7eb643259ce415203540e9dc05a4213ffbb. --- crates/sui-bridge/src/e2e_tests/basic.rs | 190 +++++++++--------- crates/sui-bridge/src/e2e_tests/complex.rs | 132 ++++++++++++ crates/sui-bridge/src/e2e_tests/mod.rs | 1 + crates/sui-bridge/src/e2e_tests/test_utils.rs | 42 +++- 4 files changed, 267 insertions(+), 98 deletions(-) create mode 100644 crates/sui-bridge/src/e2e_tests/complex.rs diff --git a/crates/sui-bridge/src/e2e_tests/basic.rs b/crates/sui-bridge/src/e2e_tests/basic.rs index d71790aa3a7de..072d8e6a99304 100644 --- a/crates/sui-bridge/src/e2e_tests/basic.rs +++ b/crates/sui-bridge/src/e2e_tests/basic.rs @@ -3,6 +3,7 @@ use crate::abi::{eth_sui_bridge, EthBridgeEvent, EthSuiBridge}; use crate::client::bridge_authority_aggregator::BridgeAuthorityAggregator; +use crate::e2e_tests::test_utils::BridgeTestCluster; use crate::e2e_tests::test_utils::{get_signatures, BridgeTestClusterBuilder}; use crate::events::{ SuiBridgeEvent, SuiToEthTokenBridgeV1, TokenTransferApproved, TokenTransferClaimed, @@ -20,6 +21,7 @@ use std::collections::{HashMap, HashSet}; use std::path::Path; +use anyhow::anyhow; use std::sync::Arc; use sui_json_rpc_types::{ SuiExecutionStatus, SuiTransactionBlockEffectsAPI, SuiTransactionBlockResponse, @@ -31,6 +33,7 @@ use sui_types::bridge::{BridgeChainId, BridgeTokenMetadata, BRIDGE_MODULE_NAME, use sui_types::programmable_transaction_builder::ProgrammableTransactionBuilder; use sui_types::transaction::{ObjectArg, TransactionData}; use sui_types::{TypeTag, BRIDGE_PACKAGE_ID}; +use tap::TapFallible; use tracing::info; #[tokio::test(flavor = "multi_thread", worker_threads = 8)] @@ -46,31 +49,18 @@ async fn test_bridge_from_eth_to_sui_to_eth() { .build() .await; - let (eth_signer, eth_address) = bridge_test_cluster + let (eth_signer, _) = bridge_test_cluster .get_eth_signer_and_address() .await .unwrap(); - let sui_client = bridge_test_cluster.sui_client(); - let sui_bridge_client = bridge_test_cluster.sui_bridge_client().await.unwrap(); let sui_address = bridge_test_cluster.sui_user_address(); let amount = 42; let sui_amount = amount * 100_000_000; - initiate_bridge_eth_to_sui( - &sui_bridge_client, - ð_signer, - bridge_test_cluster.contracts().sui_bridge, - sui_address, - eth_address, - eth_chain_id, - sui_chain_id, - amount, - sui_amount, - TOKEN_ID_ETH, - 0, - ) - .await; + initiate_bridge_eth_to_sui(&bridge_test_cluster, amount, sui_amount, TOKEN_ID_ETH, 0) + .await + .unwrap(); let events = bridge_test_cluster .new_bridge_events( HashSet::from_iter([ @@ -83,7 +73,8 @@ async fn test_bridge_from_eth_to_sui_to_eth() { // There are exactly 1 approved and 1 claimed event assert_eq!(events.len(), 2); - let eth_coin = sui_client + let eth_coin = bridge_test_cluster + .sui_client() .coin_read_api() .get_all_coins(sui_address, None, None) .await @@ -97,28 +88,17 @@ async fn test_bridge_from_eth_to_sui_to_eth() { // Now let the recipient send the coin back to ETH let eth_address_1 = EthAddress::random(); - let bridge_obj_arg = sui_bridge_client - .get_mutable_bridge_object_arg_must_succeed() - .await; let nonce = 0; - let sui_token_type_tags = sui_bridge_client.get_token_id_map().await.unwrap(); - let sui_to_eth_bridge_action = initiate_bridge_sui_to_eth( - &sui_bridge_client, - &sui_client, - sui_address, - bridge_test_cluster.wallet_mut(), - eth_chain_id, - sui_chain_id, + &bridge_test_cluster, eth_address_1, eth_coin.object_ref(), nonce, - bridge_obj_arg, sui_amount, - &sui_token_type_tags, ) - .await; + .await + .unwrap(); let events = bridge_test_cluster .new_bridge_events( HashSet::from_iter([ @@ -133,7 +113,8 @@ async fn test_bridge_from_eth_to_sui_to_eth() { assert_eq!(events.len(), 2); // Test `get_parsed_token_transfer_message` - let parsed_msg = sui_bridge_client + let parsed_msg = bridge_test_cluster + .bridge_client() .get_parsed_token_transfer_message(sui_chain_id, nonce) .await .unwrap() @@ -153,7 +134,7 @@ async fn test_bridge_from_eth_to_sui_to_eth() { assert_eq!(parsed_msg.parsed_payload.amount, sui_amount); let message = eth_sui_bridge::Message::from(sui_to_eth_bridge_action); - let signatures = get_signatures(&sui_bridge_client, nonce, sui_chain_id).await; + let signatures = get_signatures(bridge_test_cluster.bridge_client(), nonce, sui_chain_id).await; let eth_sui_bridge = EthSuiBridge::new( bridge_test_cluster.contracts().sui_bridge, @@ -195,8 +176,6 @@ async fn test_add_new_coins_on_sui() { ) .await; - let sui_bridge_client = bridge_test_cluster.sui_bridge_client().await.unwrap(); - info!("Starting bridge cluster"); bridge_test_cluster.set_approved_governance_actions_for_next_start(vec![ @@ -212,7 +191,8 @@ async fn test_add_new_coins_on_sui() { info!("Bridge cluster is up"); let bridge_committee = Arc::new( - sui_bridge_client + bridge_test_cluster + .bridge_client() .get_bridge_committee() .await .expect("Failed to get bridge committee"), @@ -245,7 +225,11 @@ async fn test_add_new_coins_on_sui() { info!("Approved new token"); // Assert new token is correctly added - let treasury_summary = sui_bridge_client.get_treasury_summary().await.unwrap(); + let treasury_summary = bridge_test_cluster + .bridge_client() + .get_treasury_summary() + .await + .unwrap(); assert_eq!(treasury_summary.id_token_type_map.len(), 5); // 4 + 1 new token let (id, _type) = treasury_summary .id_token_type_map @@ -272,29 +256,29 @@ pub(crate) async fn deposit_native_eth_to_sol_contract( signer: &EthSigner, contract_address: EthAddress, sui_recipient_address: SuiAddress, - sui_chain_id: u8, + sui_chain_id: BridgeChainId, amount: u64, ) -> ContractCall { let contract = EthSuiBridge::new(contract_address, signer.clone().into()); let sui_recipient_address = sui_recipient_address.to_vec().into(); let amount = U256::from(amount) * U256::exp10(18); // 1 ETH contract - .bridge_eth(sui_recipient_address, sui_chain_id) + .bridge_eth(sui_recipient_address, sui_chain_id as u8) .value(amount) } async fn deposit_eth_to_sui_package( sui_client: &SuiClient, sui_address: SuiAddress, - wallet_context: &mut WalletContext, - target_chain: u8, + wallet_context: &WalletContext, + target_chain: BridgeChainId, target_address: EthAddress, token: ObjectRef, bridge_object_arg: ObjectArg, sui_token_type_tags: &HashMap, -) -> SuiTransactionBlockResponse { +) -> Result { let mut builder = ProgrammableTransactionBuilder::new(); - let arg_target_chain = builder.pure(target_chain).unwrap(); + let arg_target_chain = builder.pure(target_chain as u8).unwrap(); let arg_target_address = builder.pure(target_address.as_bytes()).unwrap(); let arg_token = builder.obj(ObjectArg::ImmOrOwnedObject(token)).unwrap(); let arg_bridge = builder.obj(bridge_object_arg).unwrap(); @@ -325,26 +309,29 @@ async fn deposit_eth_to_sui_package( .unwrap(), ); let tx = wallet_context.sign_transaction(&tx_data); - wallet_context.execute_transaction_must_succeed(tx).await + wallet_context.execute_transaction_may_fail(tx).await } -async fn initiate_bridge_eth_to_sui( - sui_bridge_client: &SuiBridgeClient, - eth_signer: &EthSigner, - sui_bridge_contract_address: EthAddress, - sui_address: SuiAddress, - eth_address: EthAddress, - eth_chain_id: u8, - sui_chain_id: u8, +pub async fn initiate_bridge_eth_to_sui( + bridge_test_cluster: &BridgeTestCluster, amount: u64, sui_amount: u64, token_id: u8, nonce: u64, -) { +) -> Result<(), anyhow::Error> { info!("Depositing Eth to Solidity contract"); + let (eth_signer, eth_address) = bridge_test_cluster + .get_eth_signer_and_address() + .await + .unwrap(); + + let sui_address = bridge_test_cluster.sui_user_address(); + let sui_chain_id = bridge_test_cluster.sui_chain_id(); + let eth_chain_id = bridge_test_cluster.eth_chain_id(); + let eth_tx = deposit_native_eth_to_sol_contract( - eth_signer, - sui_bridge_contract_address, + ð_signer, + bridge_test_cluster.contracts().sui_bridge, sui_address, sui_chain_id, amount, @@ -364,9 +351,9 @@ async fn initiate_bridge_eth_to_sui( unreachable!(); }; // assert eth log matches - assert_eq!(eth_bridge_event.source_chain_id, eth_chain_id); + assert_eq!(eth_bridge_event.source_chain_id, eth_chain_id as u8); assert_eq!(eth_bridge_event.nonce, nonce); - assert_eq!(eth_bridge_event.destination_chain_id, sui_chain_id); + assert_eq!(eth_bridge_event.destination_chain_id, sui_chain_id as u8); assert_eq!(eth_bridge_event.token_id, token_id); assert_eq!(eth_bridge_event.sui_adjusted_amount, sui_amount); assert_eq!(eth_bridge_event.sender_address, eth_address); @@ -374,40 +361,58 @@ async fn initiate_bridge_eth_to_sui( info!("Deposited Eth to Solidity contract"); wait_for_transfer_action_status( - sui_bridge_client, + bridge_test_cluster.bridge_client(), eth_chain_id, - 0, + nonce, BridgeActionStatus::Claimed, ) - .await; - info!("Eth to Sui bridge transfer claimed"); + .await + .tap_ok(|_| { + info!("Eth to Sui bridge transfer claimed"); + }) } -async fn initiate_bridge_sui_to_eth( - sui_bridge_client: &SuiBridgeClient, - sui_client: &SuiClient, - sui_address: SuiAddress, - wallet_context: &mut WalletContext, - eth_chain_id: u8, - sui_chain_id: u8, +pub async fn initiate_bridge_sui_to_eth( + bridge_test_cluster: &BridgeTestCluster, eth_address: EthAddress, token: ObjectRef, nonce: u64, - bridge_object_arg: ObjectArg, sui_amount: u64, - sui_token_type_tags: &HashMap, -) -> SuiToEthBridgeAction { - let resp = deposit_eth_to_sui_package( +) -> Result { + let bridge_object_arg = bridge_test_cluster + .bridge_client() + .get_mutable_bridge_object_arg_must_succeed() + .await; + let sui_client = bridge_test_cluster.sui_client(); + let token_types = bridge_test_cluster + .bridge_client() + .get_token_id_map() + .await + .unwrap(); + let sui_address = bridge_test_cluster.sui_user_address(); + + let resp = match deposit_eth_to_sui_package( sui_client, sui_address, - wallet_context, - eth_chain_id, + bridge_test_cluster.wallet(), + bridge_test_cluster.eth_chain_id(), eth_address, token, bridge_object_arg, - sui_token_type_tags, + &token_types, ) - .await; + .await + { + Ok(resp) => { + if !resp.status_ok().unwrap() { + return Err(anyhow!("Sui TX error")); + } else { + resp + } + } + Err(e) => return Err(e), + }; + let sui_events = resp.events.unwrap().data; let bridge_event = sui_events .iter() @@ -426,12 +431,12 @@ async fn initiate_bridge_sui_to_eth( info!("Deposited Eth to move package"); assert_eq!(bridge_event.sui_bridge_event.nonce, nonce); assert_eq!( - bridge_event.sui_bridge_event.sui_chain_id as u8, - sui_chain_id + bridge_event.sui_bridge_event.sui_chain_id, + bridge_test_cluster.sui_chain_id() ); assert_eq!( - bridge_event.sui_bridge_event.eth_chain_id as u8, - eth_chain_id + bridge_event.sui_bridge_event.eth_chain_id, + bridge_test_cluster.eth_chain_id() ); assert_eq!(bridge_event.sui_bridge_event.sui_address, sui_address); assert_eq!(bridge_event.sui_bridge_event.eth_address, eth_address); @@ -443,38 +448,39 @@ async fn initiate_bridge_sui_to_eth( // Wait for the bridge action to be approved wait_for_transfer_action_status( - sui_bridge_client, - sui_chain_id, + bridge_test_cluster.bridge_client(), + bridge_test_cluster.sui_chain_id(), nonce, BridgeActionStatus::Approved, ) - .await; + .await + .unwrap(); info!("Sui to Eth bridge transfer approved"); - bridge_event + Ok(bridge_event) } async fn wait_for_transfer_action_status( sui_bridge_client: &SuiBridgeClient, - chain_id: u8, + chain_id: BridgeChainId, nonce: u64, status: BridgeActionStatus, -) { +) -> Result<(), anyhow::Error> { // Wait for the bridge action to be approved let now = std::time::Instant::now(); loop { let res = sui_bridge_client - .get_token_transfer_action_onchain_status_until_success(chain_id, nonce) + .get_token_transfer_action_onchain_status_until_success(chain_id as u8, nonce) .await; if res == status { - break; + return Ok(()); } if now.elapsed().as_secs() > 30 { - panic!( - "Timeout waiting for token transfer action to be {:?}. chain_id: {chain_id}, nonce: {nonce}. Time elapsed: {:?}", + return Err(anyhow!( + "Timeout waiting for token transfer action to be {:?}. chain_id: {chain_id:?}, nonce: {nonce}. Time elapsed: {:?}", status, now.elapsed(), - ); + )); } tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; } diff --git a/crates/sui-bridge/src/e2e_tests/complex.rs b/crates/sui-bridge/src/e2e_tests/complex.rs new file mode 100644 index 0000000000000..4c59c7f645631 --- /dev/null +++ b/crates/sui-bridge/src/e2e_tests/complex.rs @@ -0,0 +1,132 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use crate::client::bridge_authority_aggregator::BridgeAuthorityAggregator; +use crate::e2e_tests::basic::initiate_bridge_eth_to_sui; +use crate::e2e_tests::basic::initiate_bridge_sui_to_eth; +use crate::e2e_tests::test_utils::BridgeTestClusterBuilder; +use crate::sui_transaction_builder::build_sui_transaction; +use crate::types::{BridgeAction, EmergencyAction}; +use crate::types::{BridgeActionStatus, EmergencyActionType}; +use ethers::types::Address as EthAddress; +use std::sync::Arc; +use sui_json_rpc_types::SuiExecutionStatus; +use sui_json_rpc_types::SuiTransactionBlockEffectsAPI; +use sui_types::bridge::{BridgeChainId, TOKEN_ID_ETH}; +use tracing::info; + +#[tokio::test] +async fn test_sui_bridge_paused() { + telemetry_subscribers::init_for_testing(); + + // approve pause action in bridge nodes + let pause_action = BridgeAction::EmergencyAction(EmergencyAction { + nonce: 0, + chain_id: BridgeChainId::SuiCustom, + action_type: EmergencyActionType::Pause, + }); + + let unpause_action = BridgeAction::EmergencyAction(EmergencyAction { + nonce: 1, + chain_id: BridgeChainId::SuiCustom, + action_type: EmergencyActionType::Unpause, + }); + + // Setup bridge test env + let bridge_test_cluster = BridgeTestClusterBuilder::new() + .with_eth_env(true) + .with_approved_governance_actions(vec![ + vec![pause_action.clone(), unpause_action.clone()], + vec![pause_action.clone(), unpause_action.clone()], + vec![pause_action.clone(), unpause_action.clone()], + vec![], + ]) + .with_bridge_cluster(true) + .build() + .await; + + let bridge_client = bridge_test_cluster.bridge_client(); + let sui_address = bridge_test_cluster.sui_user_address(); + let sui_token_type_tags = bridge_client.get_token_id_map().await.unwrap(); + + // verify bridge are not paused + assert!(!bridge_client.get_bridge_summary().await.unwrap().is_frozen); + + // try bridge from eth and verify it works on sui + initiate_bridge_eth_to_sui(&bridge_test_cluster, 10, 10 * 100_000_000, TOKEN_ID_ETH, 0) + .await + .unwrap(); + // verify Eth was transferred to Sui address + let eth_coin_type = sui_token_type_tags.get(&TOKEN_ID_ETH).unwrap(); + let eth_coin = bridge_client + .sui_client() + .coin_read_api() + .get_coins(sui_address, Some(eth_coin_type.to_string()), None, None) + .await + .unwrap() + .data; + assert_eq!(1, eth_coin.len()); + + // get pause bridge signatures from committee + let bridge_committee = Arc::new(bridge_client.get_bridge_committee().await.unwrap()); + let agg = BridgeAuthorityAggregator::new(bridge_committee); + let certified_action = agg + .request_committee_signatures(pause_action) + .await + .unwrap(); + + // execute pause bridge on sui + let gas = bridge_test_cluster + .wallet() + .get_one_gas_object_owned_by_address(sui_address) + .await + .unwrap() + .unwrap(); + + let tx = build_sui_transaction( + sui_address, + &gas, + certified_action, + bridge_client + .get_mutable_bridge_object_arg_must_succeed() + .await, + &sui_token_type_tags, + 1000, + ) + .unwrap(); + + let response = bridge_test_cluster.sign_and_execute_transaction(&tx).await; + assert_eq!( + response.effects.unwrap().status(), + &SuiExecutionStatus::Success + ); + info!("Bridge paused"); + + // verify bridge paused + assert!(bridge_client.get_bridge_summary().await.unwrap().is_frozen); + + // Transfer from eth to sui should fail on Sui + let eth_to_sui_bridge_action = + initiate_bridge_eth_to_sui(&bridge_test_cluster, 10, 10 * 100_000_000, TOKEN_ID_ETH, 1) + .await; + assert!(eth_to_sui_bridge_action.is_err()); + // message should not be recorded on Sui when the bridge is paused + let res = bridge_test_cluster + .bridge_client() + .get_token_transfer_action_onchain_status_until_success( + bridge_test_cluster.eth_chain_id() as u8, + 1, + ) + .await; + assert_eq!(BridgeActionStatus::NotFound, res); + // Transfer from Sui to eth should fail + let sui_to_eth_bridge_action = initiate_bridge_sui_to_eth( + &bridge_test_cluster, + EthAddress::random(), + eth_coin.first().unwrap().object_ref(), + 0, + 10, + ) + .await; + assert!(sui_to_eth_bridge_action.is_err()) +} diff --git a/crates/sui-bridge/src/e2e_tests/mod.rs b/crates/sui-bridge/src/e2e_tests/mod.rs index 0f30720708fdd..26ee8f143271a 100644 --- a/crates/sui-bridge/src/e2e_tests/mod.rs +++ b/crates/sui-bridge/src/e2e_tests/mod.rs @@ -2,4 +2,5 @@ // SPDX-License-Identifier: Apache-2.0 mod basic; +mod complex; pub mod test_utils; diff --git a/crates/sui-bridge/src/e2e_tests/test_utils.rs b/crates/sui-bridge/src/e2e_tests/test_utils.rs index 43be5d62176fa..a69fb5d60341f 100644 --- a/crates/sui-bridge/src/e2e_tests/test_utils.rs +++ b/crates/sui-bridge/src/e2e_tests/test_utils.rs @@ -72,16 +72,21 @@ pub const TEST_PK: &str = "0x4bbbf85ce3377467afe5d46f804f221813b2bb87f24d81f60f1 /// structs that are needed for testing. pub struct BridgeTestCluster { pub test_cluster: TestCluster, + bridge_client: SuiBridgeClient, eth_environment: EthBridgeEnvironment, bridge_node_handles: Option>>, approved_governance_actions_for_next_start: Option>>, bridge_tx_cursor: Option, + eth_chain_id: BridgeChainId, + sui_chain_id: BridgeChainId, } pub struct BridgeTestClusterBuilder { with_eth_env: bool, with_bridge_cluster: bool, approved_governance_actions: Option>>, + eth_chain_id: BridgeChainId, + sui_chain_id: BridgeChainId, } impl Default for BridgeTestClusterBuilder { @@ -96,6 +101,8 @@ impl BridgeTestClusterBuilder { with_eth_env: false, with_bridge_cluster: false, approved_governance_actions: None, + eth_chain_id: BridgeChainId::EthCustom, + sui_chain_id: BridgeChainId::SuiCustom, } } @@ -117,6 +124,16 @@ impl BridgeTestClusterBuilder { self } + pub fn with_sui_chain_id(mut self, chain_id: BridgeChainId) -> Self { + self.sui_chain_id = chain_id; + self + } + + pub fn with_eth_chain_id(mut self, chain_id: BridgeChainId) -> Self { + self.eth_chain_id = chain_id; + self + } + pub async fn build(self) -> BridgeTestCluster { init_all_struct_tags(); std::env::set_var("__TEST_ONLY_CONSENSUS_USE_LONG_MIN_ROUND_DELAY", "1"); @@ -145,13 +162,18 @@ impl BridgeTestClusterBuilder { .await, ); } - + let bridge_client = SuiBridgeClient::new(&test_cluster.fullnode_handle.rpc_url) + .await + .unwrap(); BridgeTestCluster { test_cluster, + bridge_client, eth_environment, bridge_node_handles, approved_governance_actions_for_next_start: self.approved_governance_actions, bridge_tx_cursor: None, + sui_chain_id: self.sui_chain_id, + eth_chain_id: self.eth_chain_id, } } @@ -199,18 +221,26 @@ impl BridgeTestCluster { Ok((eth_signer, eth_address)) } - pub async fn sui_bridge_client(&self) -> anyhow::Result { - SuiBridgeClient::new(&self.test_cluster.fullnode_handle.rpc_url).await + pub fn bridge_client(&self) -> &SuiBridgeClient { + &self.bridge_client } - pub fn sui_client(&self) -> SuiClient { - self.test_cluster.fullnode_handle.sui_client.clone() + pub fn sui_client(&self) -> &SuiClient { + &self.test_cluster.fullnode_handle.sui_client } pub fn sui_user_address(&self) -> SuiAddress { self.test_cluster.get_address_0() } + pub fn sui_chain_id(&self) -> BridgeChainId { + self.sui_chain_id + } + + pub fn eth_chain_id(&self) -> BridgeChainId { + self.eth_chain_id + } + pub fn contracts(&self) -> &DeployedSolContracts { self.eth_environment.contracts() } @@ -223,7 +253,7 @@ impl BridgeTestCluster { self.test_cluster.wallet_mut() } - pub fn wallet(&mut self) -> &WalletContext { + pub fn wallet(&self) -> &WalletContext { &self.test_cluster.wallet }