diff --git a/crates/sui-core/src/unit_tests/coin_deny_list_tests.rs b/crates/sui-core/src/unit_tests/coin_deny_list_tests.rs index e172bc9a788da..26c0768293c97 100644 --- a/crates/sui-core/src/unit_tests/coin_deny_list_tests.rs +++ b/crates/sui-core/src/unit_tests/coin_deny_list_tests.rs @@ -72,7 +72,6 @@ async fn test_regulated_coin_v1_creation() { // Test that a v2 regulated coin can be created and all the necessary objects are created with the right types. // Also test that we could create the deny list config for the coin and all types can be loaded in Rust. -#[ignore] #[tokio::test] async fn test_regulated_coin_v2_types() { let env = new_authority_and_publish("coin_deny_list_v2").await; diff --git a/crates/sui-e2e-tests/tests/move_test_code/sources/regulated_coin.move b/crates/sui-e2e-tests/tests/move_test_code/sources/regulated_coin.move new file mode 100644 index 0000000000000..aeb5814af29c2 --- /dev/null +++ b/crates/sui-e2e-tests/tests/move_test_code/sources/regulated_coin.move @@ -0,0 +1,26 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +module move_test_code::regulated_coin { + use sui::coin; + + public struct REGULATED_COIN has drop {} + + fun init(otw: REGULATED_COIN, ctx: &mut TxContext) { + let (mut treasury_cap, deny_cap, metadata) = coin::create_regulated_currency_v2( + otw, + 9, + b"RC", + b"REGULATED_COIN", + b"A new regulated coin", + option::none(), + true, + ctx + ); + let coin = coin::mint(&mut treasury_cap, 1000000, ctx); + transfer::public_transfer(coin, ctx.sender()); + transfer::public_transfer(deny_cap, ctx.sender()); + transfer::public_freeze_object(treasury_cap); + transfer::public_freeze_object(metadata); + } +} diff --git a/crates/sui-e2e-tests/tests/per_epoch_config_stress_tests.rs b/crates/sui-e2e-tests/tests/per_epoch_config_stress_tests.rs new file mode 100644 index 0000000000000..d7c1a10750d9e --- /dev/null +++ b/crates/sui-e2e-tests/tests/per_epoch_config_stress_tests.rs @@ -0,0 +1,253 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use move_core_types::ident_str; +use move_core_types::language_storage::TypeTag; +use rand::random; +use std::future::Future; +use std::path::PathBuf; +use std::sync::Arc; +use std::time::Duration; +use sui_json_rpc_types::SuiTransactionBlockEffectsAPI; +use sui_macros::sim_test; +use sui_types::base_types::SequenceNumber; +use sui_types::base_types::{EpochId, ObjectID, ObjectRef, SuiAddress}; +use sui_types::programmable_transaction_builder::ProgrammableTransactionBuilder; +use sui_types::transaction::{CallArg, ObjectArg, TransactionData}; +use sui_types::{SUI_DENY_LIST_OBJECT_ID, SUI_FRAMEWORK_PACKAGE_ID}; +use test_cluster::{TestCluster, TestClusterBuilder}; + +const DENY_ADDRESS: SuiAddress = SuiAddress::ZERO; + +#[sim_test] +async fn per_epoch_config_stress_test() { + let test_env = Arc::new(create_test_env().await); + let target_epoch = 10; + let mut gas_objects = test_env + .test_cluster + .wallet + .get_all_gas_objects_owned_by_address(test_env.regulated_coin_owner) + .await + .unwrap(); + let gas1 = gas_objects.pop().unwrap(); + let gas2 = gas_objects.pop().unwrap(); + let handle1 = { + let test_env = test_env.clone(); + tokio::spawn(async move { + run_thread(test_env, target_epoch, gas1.0, create_transfer_tx, true).await + }) + }; + let handle2 = { + let test_env = test_env.clone(); + tokio::spawn(async move { + run_thread(test_env, target_epoch, gas2.0, create_deny_tx, false).await + }) + }; + tokio::time::timeout(Duration::from_secs(600), async { + tokio::try_join!(handle1, handle2) + }) + .await + .unwrap() + .unwrap(); +} + +async fn run_thread( + test_env: Arc, + target_epoch: EpochId, + gas_id: ObjectID, + tx_creation_func: F, + tx_may_fail: bool, +) where + F: Fn(Arc, ObjectRef) -> Fut, + Fut: Future, +{ + let mut num_tx_succeeded = 0; + let mut num_tx_failed = 0; + loop { + let gas = test_env.get_latest_object_ref(&gas_id).await; + let tx_data = tx_creation_func(test_env.clone(), gas).await; + let tx = test_env.test_cluster.sign_transaction(&tx_data); + let effects = test_env + .test_cluster + .wallet + .execute_transaction_may_fail(tx) + .await + .unwrap() + .effects + .unwrap(); + if effects.status().is_ok() { + num_tx_succeeded += 1; + } else { + num_tx_failed += 1; + } + let executed_epoch = effects.executed_epoch(); + if executed_epoch >= target_epoch { + break; + } + } + if !tx_may_fail { + assert_eq!(num_tx_failed, 0); + } + assert!(num_tx_succeeded + num_tx_failed > 5); +} + +async fn create_deny_tx(test_env: Arc, gas: ObjectRef) -> TransactionData { + let deny: bool = random(); + test_env + .test_cluster + .test_transaction_builder_with_gas_object(test_env.regulated_coin_owner, gas) + .await + .move_call( + SUI_FRAMEWORK_PACKAGE_ID, + "coin", + if deny { + "deny_list_v2_add" + } else { + "deny_list_v2_remove" + }, + vec![ + CallArg::Object(ObjectArg::SharedObject { + id: SUI_DENY_LIST_OBJECT_ID, + initial_shared_version: test_env.deny_list_object_init_version, + mutable: true, + }), + CallArg::Object(ObjectArg::ImmOrOwnedObject( + test_env.get_latest_object_ref(&test_env.deny_cap_id).await, + )), + CallArg::Pure(bcs::to_bytes(&DENY_ADDRESS).unwrap()), + ], + ) + .with_type_args(vec![test_env.regulated_coin_type.clone()]) + .build() +} + +async fn create_transfer_tx(test_env: Arc, gas: ObjectRef) -> TransactionData { + let use_move: bool = random(); + if use_move { + create_move_transfer_tx(test_env, gas).await + } else { + create_native_transfer_tx(test_env, gas).await + } +} + +async fn create_move_transfer_tx(test_env: Arc, gas: ObjectRef) -> TransactionData { + test_env + .test_cluster + .test_transaction_builder_with_gas_object(test_env.regulated_coin_owner, gas) + .await + .move_call( + SUI_FRAMEWORK_PACKAGE_ID, + "pay", + "split_and_transfer", + vec![ + CallArg::Object(ObjectArg::ImmOrOwnedObject( + test_env + .get_latest_object_ref(&test_env.regulated_coin_id) + .await, + )), + CallArg::Pure(bcs::to_bytes(&1u64).unwrap()), + CallArg::Pure(bcs::to_bytes(&DENY_ADDRESS).unwrap()), + ], + ) + .with_type_args(vec![test_env.regulated_coin_type.clone()]) + .build() +} + +async fn create_native_transfer_tx(test_env: Arc, gas: ObjectRef) -> TransactionData { + let mut pt_builder = ProgrammableTransactionBuilder::new(); + let coin_input = pt_builder + .obj(ObjectArg::ImmOrOwnedObject( + test_env + .get_latest_object_ref(&test_env.regulated_coin_id) + .await, + )) + .unwrap(); + let amount_input = pt_builder.pure(1u64).unwrap(); + let split_coin = pt_builder.programmable_move_call( + SUI_FRAMEWORK_PACKAGE_ID, + ident_str!("coin").to_owned(), + ident_str!("split").to_owned(), + vec![test_env.regulated_coin_type.clone()], + vec![coin_input, amount_input], + ); + pt_builder.transfer_arg(DENY_ADDRESS, split_coin); + let pt = pt_builder.finish(); + test_env + .test_cluster + .test_transaction_builder_with_gas_object(test_env.regulated_coin_owner, gas) + .await + .programmable(pt) + .build() +} + +struct TestEnv { + test_cluster: TestCluster, + regulated_coin_id: ObjectID, + regulated_coin_type: TypeTag, + regulated_coin_owner: SuiAddress, + deny_cap_id: ObjectID, + deny_list_object_init_version: SequenceNumber, +} + +impl TestEnv { + async fn get_latest_object_ref(&self, object_id: &ObjectID) -> ObjectRef { + self.test_cluster + .get_object_from_fullnode_store(object_id) + .await + .unwrap() + .compute_object_reference() + } +} + +async fn create_test_env() -> TestEnv { + let test_cluster = TestClusterBuilder::new() + .with_epoch_duration_ms(1000) + .with_num_validators(5) + .build() + .await; + let deny_list_object_init_version = test_cluster + .get_object_from_fullnode_store(&SUI_DENY_LIST_OBJECT_ID) + .await + .unwrap() + .version(); + let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + path.push("tests/move_test_code"); + let tx_data = test_cluster + .test_transaction_builder() + .await + .publish(path) + .build(); + let effects = test_cluster + .sign_and_execute_transaction(&tx_data) + .await + .effects + .unwrap(); + let mut coin_id = None; + let mut coin_type = None; + let mut coin_owner = None; + let mut deny_cap = None; + for created in effects.created() { + let object_id = created.reference.object_id; + let object = test_cluster + .get_object_from_fullnode_store(&object_id) + .await + .unwrap(); + if object.is_package() { + continue; + } else if object.is_coin() { + coin_id = Some(object_id); + coin_type = object.coin_type_maybe(); + coin_owner = Some(created.owner.get_address_owner_address().unwrap()); + } else if object.type_().unwrap().is_coin_deny_cap_v2() { + deny_cap = Some(object_id); + } + } + TestEnv { + test_cluster, + regulated_coin_id: coin_id.unwrap(), + regulated_coin_type: coin_type.unwrap(), + regulated_coin_owner: coin_owner.unwrap(), + deny_cap_id: deny_cap.unwrap(), + deny_list_object_init_version, + } +}