From aae6f5c37672095fb6e7ddc59683a52a92ff4f98 Mon Sep 17 00:00:00 2001 From: Ashok Menon Date: Fri, 12 Jul 2024 22:16:30 +0100 Subject: [PATCH] [Examples] Remove references to `sui_programmability` in codebase (#18595) ## Description Replace all references to Move packages in `sui_programmability` with equivalents in `./examples/move`, in preparation for deleting the programmability directory. In the process, I also: - removed the tic-tac-toe example from the Sui SDK, as it has been replaced by a more full-featured E2E example. - ported some modernised versions of the `basics` packages into the new `examples/move/basics` for use in tests. ## Test plan CI and, ``` sui$ cargo simtest ``` ## Stack - #18525 - #18526 - #18557 - #18558 --- ## Release notes Check each box that your changes affect. If none of the boxes relate to your changes, release notes aren't required. For each box you select, include information after the relevant heading that describes the impact of your changes that a user might notice and any actions they must take to implement updates. - [ ] Protocol: - [ ] Nodes (Validators and Full nodes): - [ ] Indexer: - [ ] JSON-RPC: - [ ] GraphQL: - [ ] CLI: - [ ] Rust SDK: --------- Co-authored-by: Ronny Roland --- crates/sui-core/src/test_utils.rs | 2 +- crates/sui-e2e-tests/tests/full_node_tests.rs | 13 +- .../src/metered_verifier.rs | 9 +- crates/sui-framework-tests/src/unit_tests.rs | 26 -- .../tests/rpc_server_tests.rs | 5 +- crates/sui-json/src/tests.rs | 285 ++++---------- crates/sui-proc-macros/src/lib.rs | 2 +- .../unit_tests/balance_changing_tx_tests.rs | 33 +- crates/sui-sdk/Cargo.toml | 5 - crates/sui-sdk/README.md | 34 +- crates/sui-sdk/examples/tic_tac_toe.rs | 368 ------------------ .../sui-test-transaction-builder/src/lib.rs | 14 +- docker/stress/Dockerfile | 2 +- examples/move/basics/sources/counter.move | 116 ++++++ .../move/basics/sources/object_basics.move | 63 +++ examples/move/basics/sources/random.move | 18 + .../move/basics/sources/resolve_args.move | 21 + 17 files changed, 340 insertions(+), 676 deletions(-) delete mode 100644 crates/sui-sdk/examples/tic_tac_toe.rs create mode 100644 examples/move/basics/sources/counter.move create mode 100644 examples/move/basics/sources/object_basics.move create mode 100644 examples/move/basics/sources/random.move create mode 100644 examples/move/basics/sources/resolve_args.move diff --git a/crates/sui-core/src/test_utils.rs b/crates/sui-core/src/test_utils.rs index bf5ef51b994e2..8786023364a24 100644 --- a/crates/sui-core/src/test_utils.rs +++ b/crates/sui-core/src/test_utils.rs @@ -191,7 +191,7 @@ pub fn create_fake_cert_and_effect_digest<'a>( } pub fn compile_basics_package() -> CompiledPackage { - compile_example_package("../../sui_programmability/examples/basics") + compile_example_package("../../examples/move/basics") } pub fn compile_managed_coin_package() -> CompiledPackage { diff --git a/crates/sui-e2e-tests/tests/full_node_tests.rs b/crates/sui-e2e-tests/tests/full_node_tests.rs index 43d7fc3becb15..841d149c8d4ae 100644 --- a/crates/sui-e2e-tests/tests/full_node_tests.rs +++ b/crates/sui-e2e-tests/tests/full_node_tests.rs @@ -22,7 +22,7 @@ use sui_sdk::wallet_context::WalletContext; use sui_storage::key_value_store::TransactionKeyValueStore; use sui_storage::key_value_store_metrics::KeyValueStoreMetrics; use sui_test_transaction_builder::{ - batch_make_transfer_transactions, create_devnet_nft, delete_devnet_nft, increment_counter, + batch_make_transfer_transactions, create_nft, delete_nft, increment_counter, publish_basics_package, publish_basics_package_and_make_counter, publish_nfts_package, TestTransactionBuilder, }; @@ -672,7 +672,7 @@ async fn test_full_node_event_read_api_ok() { // This is a poor substitute for the post processing taking some time sleep(Duration::from_millis(1000)).await; - let (_sender, _object_id, digest2) = create_devnet_nft(context, package_id).await; + let (_sender, _object_id, digest2) = create_nft(context, package_id).await; // Add a delay to ensure event processing is done after transaction commits. sleep(Duration::from_secs(5)).await; @@ -702,7 +702,7 @@ async fn test_full_node_event_query_by_module_ok() { // This is a poor substitute for the post processing taking some time sleep(Duration::from_millis(1000)).await; - let (_sender, _object_id, digest2) = create_devnet_nft(context, package_id).await; + let (_sender, _object_id, digest2) = create_nft(context, package_id).await; // Add a delay to ensure event processing is done after transaction commits. sleep(Duration::from_secs(5)).await; @@ -710,7 +710,7 @@ async fn test_full_node_event_query_by_module_ok() { // query by move event module let params = rpc_params![EventFilter::MoveEventModule { package: package_id, - module: ident_str!("devnet_nft").into() + module: ident_str!("testnet_nft").into() }]; let page: EventPage = jsonrpc_client .request("suix_queryEvents", params) @@ -980,7 +980,7 @@ async fn test_get_objects_read() -> Result<(), anyhow::Error> { let package_id = publish_nfts_package(&test_cluster.wallet).await.0; // Create the object - let (sender, object_id, _) = create_devnet_nft(&test_cluster.wallet, package_id).await; + let (sender, object_id, _) = create_nft(&test_cluster.wallet, package_id).await; let recipient = test_cluster.get_address_1(); assert_ne!(sender, recipient); @@ -1011,8 +1011,7 @@ async fn test_get_objects_read() -> Result<(), anyhow::Error> { .expect("Failed to transfer coins to recipient"); // Delete the object - let response = - delete_devnet_nft(&test_cluster.wallet, recipient, package_id, object_ref_v2).await; + let response = delete_nft(&test_cluster.wallet, recipient, package_id, object_ref_v2).await; assert_eq!( *response.effects.unwrap().status(), SuiExecutionStatus::Success diff --git a/crates/sui-framework-tests/src/metered_verifier.rs b/crates/sui-framework-tests/src/metered_verifier.rs index 9b276cd093341..0d30fe4febfbe 100644 --- a/crates/sui-framework-tests/src/metered_verifier.rs +++ b/crates/sui-framework-tests/src/metered_verifier.rs @@ -191,14 +191,12 @@ fn test_metered_move_bytecode_verifier() { // Check shared meter logic works across all publish in PT let mut packages = vec![]; let with_unpublished_deps = false; - let path = - PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../sui_programmability/examples/basics"); + let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../examples/move/basics"); let package = build(&path).unwrap(); packages.push(package.get_dependency_sorted_modules(with_unpublished_deps)); packages.push(package.get_dependency_sorted_modules(with_unpublished_deps)); - let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("../../sui_programmability/examples/fungible_tokens"); + let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../examples/move/coin"); let package = build(&path).unwrap(); packages.push(package.get_dependency_sorted_modules(with_unpublished_deps)); @@ -288,8 +286,7 @@ fn test_build_and_verify_programmability_examples() { let meter_config = protocol_config.meter_config(); let registry = &Registry::new(); let bytecode_verifier_metrics = Arc::new(BytecodeVerifierMetrics::new(registry)); - let examples = - PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../sui_programmability/examples"); + let examples = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../examples"); for example in std::fs::read_dir(examples).unwrap() { let Ok(example) = example else { continue }; diff --git a/crates/sui-framework-tests/src/unit_tests.rs b/crates/sui-framework-tests/src/unit_tests.rs index 928d30dd75168..c02aad95a77ee 100644 --- a/crates/sui-framework-tests/src/unit_tests.rs +++ b/crates/sui-framework-tests/src/unit_tests.rs @@ -52,32 +52,6 @@ fn run_bridge_tests() { check_move_unit_tests(&buf); } -#[test] -#[cfg_attr(msim, ignore)] -fn run_sui_programmability_examples_move_unit_tests() { - let examples_dir = Path::new(env!("CARGO_MANIFEST_DIR")) - .join("..") - .join("..") - .join("sui_programmability") - .join("examples"); - - for example in [ - "basics", - "capy", - "crypto", - "defi", - "fungible_tokens", - "games", - "move_tutorial", - "nfts", - "objects_tutorial", - ] { - let path = examples_dir.join(example); - check_package_builds(&path); - check_move_unit_tests(&path); - } -} - fn check_packages_recursively(path: &Path) -> io::Result<()> { for entry in fs::read_dir(path).unwrap() { let entry = entry?; diff --git a/crates/sui-json-rpc-tests/tests/rpc_server_tests.rs b/crates/sui-json-rpc-tests/tests/rpc_server_tests.rs index 4bbe6f20feace..e566183954a94 100644 --- a/crates/sui-json-rpc-tests/tests/rpc_server_tests.rs +++ b/crates/sui-json-rpc-tests/tests/rpc_server_tests.rs @@ -188,9 +188,8 @@ async fn test_publish() -> Result<(), anyhow::Error> { .await?; let gas = objects.data.first().unwrap().object().unwrap(); - let compiled_package = BuildConfig::new_for_testing().build(Path::new( - "../../sui_programmability/examples/fungible_tokens", - ))?; + let compiled_package = + BuildConfig::new_for_testing().build(Path::new("../../examples/move/basics"))?; let compiled_modules_bytes = compiled_package.get_package_base64(/* with_unpublished_deps */ false); let dependencies = compiled_package.get_dependency_original_package_ids(); diff --git a/crates/sui-json/src/tests.rs b/crates/sui-json/src/tests.rs index ee23f210d2d2c..a73e51e8cecfc 100644 --- a/crates/sui-json/src/tests.rs +++ b/crates/sui-json/src/tests.rs @@ -9,6 +9,7 @@ use move_core_types::annotated_value::{MoveFieldLayout, MoveStructLayout, MoveTy use move_core_types::language_storage::StructTag; use move_core_types::u256::U256; use move_core_types::{account_address::AccountAddress, ident_str, identifier::Identifier}; +use serde::Serialize; use serde_json::{json, Value}; use test_fuzz::runtime::num_traits::ToPrimitive; @@ -417,8 +418,7 @@ fn test_basic_args_linter_pure_args_good() { #[test] fn test_basic_args_linter_top_level() { - let path = - Path::new(env!("CARGO_MANIFEST_DIR")).join("../../sui_programmability/examples/nfts"); + let path = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../examples/move/basics"); let compiled_modules = BuildConfig::new_for_testing() .build(&path) .unwrap() @@ -429,236 +429,81 @@ fn test_basic_args_linter_top_level() { BuiltInFramework::genesis_move_packages(), ) .unwrap(); - let example_package = example_package.data.try_as_package().unwrap(); - - let module = Identifier::new("geniteam").unwrap(); - let function = Identifier::new("create_monster").unwrap(); - - /* - Function signature: - public fun create_monster( - _player: &mut Player, - farm: &mut Farm, - pet_monsters: &mut Collection, - monster_name: vector, - monster_img_index: u64, - breed: u8, - monster_affinity: u8, - monster_description: vector, - display: vector, - ctx: &mut TxContext - ) - */ - - let monster_name_raw = "MonsterName"; - let monster_img_id_raw = "12345678"; - let breed_raw = 89; - let monster_affinity_raw = 200; - let monster_description_raw = "MonsterDescription"; - let display_raw = "DisplayUrl"; - - let player_id = json!(format!("0x{}", ObjectID::random())); - // This is okay since not starting with 0x - let monster_name = json!(monster_name_raw); - // Well within U64 bounds - let monster_img_id = json!(monster_img_id_raw); - // Well within U8 bounds - let breed = json!(breed_raw); - // Well within U8 bounds - let monster_affinity = json!(monster_affinity_raw); - // This is okay since not starting with 0x - let monster_description = json!(monster_description_raw); - // This is okay since not starting with 0x - let display = json!(display_raw); - - // They have to be ordered - let args = vec![ - player_id, - monster_name.clone(), - monster_img_id.clone(), - breed, - monster_affinity.clone(), - monster_description.clone(), - display.clone(), + let package = example_package.data.try_as_package().unwrap(); + + let module = Identifier::new("resolve_args").unwrap(); + let function = Identifier::new("foo").unwrap(); + + // Function signature: + // foo( + // _foo: &mut Foo, + // _bar: vector, + // _name: vector, + // _index: u64, + // _flag: u8, + // _recipient: address, + // _ctx: &mut TxContext, + // ) + + let foo_id = ObjectID::random(); + let bar_id = ObjectID::random(); + let baz_id = ObjectID::random(); + let recipient_addr = SuiAddress::random_for_testing_only(); + + let foo = json!(foo_id.to_canonical_string(/* with_prefix */ true)); + let bar = json!([ + bar_id.to_canonical_string(/* with_prefix */ true), + baz_id.to_canonical_string(/* with_prefix */ true), + ]); + + let name = json!("Name"); + let index = json!("12345678"); + let flag = json!(89); + let recipient = json!(recipient_addr.to_string()); + + let args: Vec<_> = [ + foo.clone(), + bar.clone(), + name.clone(), + index.clone(), + flag, + recipient.clone(), ] - .iter() + .into_iter() .map(|q| SuiJsonValue::new(q.clone()).unwrap()) .collect(); - let json_args = - resolve_move_function_args(example_package, module.clone(), function.clone(), &[], args) - .unwrap(); - - assert!(!json_args.is_empty()); - - assert_eq!( - json_args[1].0, - ResolvedCallArg::Pure(bcs::to_bytes(&monster_name_raw.as_bytes().to_vec()).unwrap()) - ); - assert_eq!( - json_args[2].0, - ResolvedCallArg::Pure( - bcs::to_bytes(&(monster_img_id_raw.parse::().unwrap())).unwrap() - ), - ); - assert_eq!( - json_args[3].0, - ResolvedCallArg::Pure(bcs::to_bytes(&(breed_raw as u8)).unwrap()) - ); - assert_eq!( - json_args[4].0, - ResolvedCallArg::Pure(bcs::to_bytes(&(monster_affinity_raw as u8)).unwrap()), - ); - assert_eq!( - json_args[5].0, - ResolvedCallArg::Pure(bcs::to_bytes(&monster_description_raw.as_bytes().to_vec()).unwrap()), - ); - - // Breed is u8 so too large - let args = vec![ - monster_name, - monster_img_id, - json!(10000u64), - monster_affinity, - monster_description, - display, - ] - .iter() - .map(|q| SuiJsonValue::new(q.clone()).unwrap()) - .collect(); - assert!(resolve_move_function_args(example_package, module, function, &[], args,).is_err()); - - // Test with vecu8 as address - let path = - Path::new(env!("CARGO_MANIFEST_DIR")).join("../../sui_programmability/examples/basics"); - let compiled_modules = BuildConfig::new_for_testing() - .build(&path) - .unwrap() - .into_modules(); - let example_package = Object::new_package_for_testing( - &compiled_modules, - TransactionDigest::genesis_marker(), - BuiltInFramework::genesis_move_packages(), - ) - .unwrap(); - let framework_pkg = example_package.data.try_as_package().unwrap(); - - let module = Identifier::new("object_basics").unwrap(); - let function = Identifier::new("create").unwrap(); - - /* - Function signature: - public fun create(value: u64, recipient: vector, ctx: &mut TxContext) - */ - let value_raw = "29897"; - let address = SuiAddress::random_for_testing_only(); - - let value = json!(value_raw); - // Encode as hex string - let addr = json!(format!("{address}")); - - // They have to be ordered - let args = [value, addr] - .iter() - .map(|q| SuiJsonValue::new(q.clone()).unwrap()) - .collect(); - - let args = resolve_move_function_args(framework_pkg, module, function, &[], args).unwrap(); + let json_args: Vec<_> = + resolve_move_function_args(package, module.clone(), function.clone(), &[], args) + .unwrap() + .into_iter() + .map(|(arg, _)| arg) + .collect(); - assert_eq!( - args[0].0, - ResolvedCallArg::Pure(bcs::to_bytes(&(value_raw.parse::().unwrap())).unwrap()) - ); + use ResolvedCallArg as RCA; + fn pure(t: &T) -> RCA { + RCA::Pure(bcs::to_bytes(t).unwrap()) + } - // Need to verify this specially - // BCS serialzes addresses like vectors so there's a length prefix, which makes the vec longer by 1 assert_eq!( - args[1].0, - ResolvedCallArg::Pure(bcs::to_bytes(&AccountAddress::from(address)).unwrap()), + json_args, + vec![ + RCA::Object(foo_id), + RCA::ObjVec(vec![bar_id, baz_id]), + pure(&"Name"), + pure(&12345678u64), + pure(&89u8), + pure(&recipient_addr), + ], ); - // Test with object args - - let module = Identifier::new("object_basics").unwrap(); - let function = Identifier::new("transfer").unwrap(); - - /* - Function signature: - public fun transfer(o: Object, recipient: vector, _ctx: &mut TxContext) - */ - let object_id_raw = ObjectID::random(); - let address = SuiAddress::random_for_testing_only(); - - let object_id = json!(format!("{object_id_raw}")); - // Encode as hex string - let addr = json!(format!("{address}")); - - // They have to be ordered - let args = [object_id, addr] - .iter() + // Flag is u8 so too large + let args: Vec<_> = [foo, bar, name, index, json!(10000u64), recipient] + .into_iter() .map(|q| SuiJsonValue::new(q.clone()).unwrap()) .collect(); - let args = resolve_move_function_args(framework_pkg, module, function, &[], args).unwrap(); - - assert_eq!( - args[0].0, - ResolvedCallArg::Object( - ObjectID::from_hex_literal(&format!("0x{}", object_id_raw)).unwrap() - ) - ); - - // Need to verify this specially - // BCS serialzes addresses like vectors so there's a length prefix, which makes the vec longer by 1 - assert_eq!( - args[1].0, - ResolvedCallArg::Pure(bcs::to_bytes(&AccountAddress::from(address)).unwrap()) - ); - - // Test with object vector args - let path = Path::new(env!("CARGO_MANIFEST_DIR")) - .join("../sui-core/src/unit_tests/data/entry_point_vector"); - let compiled_modules = BuildConfig::new_for_testing() - .build(&path) - .unwrap() - .into_modules(); - let example_package = Object::new_package_for_testing( - &compiled_modules, - TransactionDigest::genesis_marker(), - BuiltInFramework::genesis_move_packages(), - ) - .unwrap(); - let example_package = example_package.data.try_as_package().unwrap(); - - let module = Identifier::new("entry_point_vector").unwrap(); - let function = Identifier::new("two_obj_vec_destroy").unwrap(); - - /* - Function signature: - public entry fun two_obj_vec_destroy(v: vector, _: &mut TxContext) - */ - let object_id_raw1 = ObjectID::random(); - let object_id_raw2 = ObjectID::random(); - let object_id1 = json!(format!("0x{}", object_id_raw1)); - let object_id2 = json!(format!("0x{}", object_id_raw2)); - - let args = vec![SuiJsonValue::new(Value::Array(vec![object_id1, object_id2])).unwrap()]; - - let args = resolve_move_function_args(example_package, module, function, &[], args).unwrap(); - - assert!(matches!(args[0].0, ResolvedCallArg::ObjVec { .. })); - - if let ResolvedCallArg::ObjVec(vec) = &args[0].0 { - assert_eq!(vec.len(), 2); - assert_eq!( - vec[0], - ObjectID::from_hex_literal(&format!("0x{}", object_id_raw1)).unwrap() - ); - assert_eq!( - vec[1], - ObjectID::from_hex_literal(&format!("0x{}", object_id_raw2)).unwrap() - ); - } + assert!(resolve_move_function_args(package, module, function, &[], args,).is_err()); } #[test] diff --git a/crates/sui-proc-macros/src/lib.rs b/crates/sui-proc-macros/src/lib.rs index d3844a7b195cf..ab0fca56a7f46 100644 --- a/crates/sui-proc-macros/src/lib.rs +++ b/crates/sui-proc-macros/src/lib.rs @@ -58,7 +58,7 @@ pub fn init_static_initializers(_args: TokenStream, item: TokenStream) -> TokenS register_package_hooks(Box::new(SuiPackageHooks {})); let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - path.extend(["..", "..", "sui_programmability", "examples", "basics"]); + path.extend(["..", "..", "examples", "move", "basics"]); let mut build_config = BuildConfig::default(); build_config.config.install_dir = Some(TempDir::new().unwrap().into_path()); diff --git a/crates/sui-rosetta/src/unit_tests/balance_changing_tx_tests.rs b/crates/sui-rosetta/src/unit_tests/balance_changing_tx_tests.rs index 2a548ee28336d..b0dba64531281 100644 --- a/crates/sui-rosetta/src/unit_tests/balance_changing_tx_tests.rs +++ b/crates/sui-rosetta/src/unit_tests/balance_changing_tx_tests.rs @@ -5,6 +5,7 @@ use crate::operations::Operations; use crate::types::{ConstructionMetadata, OperationStatus, OperationType}; use anyhow::anyhow; use move_core_types::identifier::Identifier; +use move_core_types::language_storage::StructTag; use rand::seq::{IteratorRandom, SliceRandom}; use serde_json::json; use shared_crypto::intent::Intent; @@ -34,6 +35,7 @@ use sui_types::transaction::{ TEST_ONLY_GAS_UNIT_FOR_HEAVY_COMPUTATION_STORAGE, TEST_ONLY_GAS_UNIT_FOR_SPLIT_COIN, TEST_ONLY_GAS_UNIT_FOR_STAKING, TEST_ONLY_GAS_UNIT_FOR_TRANSFER, }; +use sui_types::TypeTag; use test_cluster::TestClusterBuilder; #[tokio::test] @@ -138,13 +140,7 @@ async fn test_publish_and_move_call() { let addresses = network.get_addresses(); let sender = get_random_address(&addresses, vec![]); let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - path.extend([ - "..", - "..", - "sui_programmability", - "examples", - "fungible_tokens", - ]); + path.extend(["..", "..", "examples", "move", "coin"]); let compiled_package = BuildConfig::new_for_testing().build(&path).unwrap(); let compiled_modules_bytes = compiled_package.get_package_bytes(/* with_unpublished_deps */ false); @@ -181,8 +177,18 @@ async fn test_publish_and_move_call() { }) .unwrap(); - // TODO: Improve tx response to make it easier to find objects. - let treasury = find_module_object(&object_changes, "::TreasuryCap"); + let treasury = find_module_object(&object_changes, |type_| { + if type_.name.as_str() != "TreasuryCap" { + return false; + } + + let Some(TypeTag::Struct(otw)) = type_.type_params.first() else { + return false; + }; + + otw.name.as_str() == "MY_COIN" + }); + let treasury = treasury.clone().reference.to_object_ref(); let recipient = *addresses.choose(&mut OsRng).unwrap(); let pt = { @@ -190,7 +196,7 @@ async fn test_publish_and_move_call() { builder .move_call( *package, - Identifier::from_str("managed").unwrap(), + Identifier::from_str("my_coin").unwrap(), Identifier::from_str("mint").unwrap(), vec![], vec![ @@ -630,7 +636,10 @@ async fn test_delegation_parsing() -> Result<(), anyhow::Error> { Ok(()) } -fn find_module_object(changes: &[ObjectChange], object_type_name: &str) -> OwnedObjectRef { +fn find_module_object( + changes: &[ObjectChange], + type_pred: impl Fn(&StructTag) -> bool, +) -> OwnedObjectRef { let mut results: Vec<_> = changes .iter() .filter_map(|change| { @@ -643,7 +652,7 @@ fn find_module_object(changes: &[ObjectChange], object_type_name: &str) -> Owned .. } = change { - if object_type.to_string().contains(object_type_name) { + if type_pred(object_type) { return Some(OwnedObjectRef { owner: *owner, reference: SuiObjectRef { diff --git a/crates/sui-sdk/Cargo.toml b/crates/sui-sdk/Cargo.toml index 8203edde38044..f6126c5f09344 100644 --- a/crates/sui-sdk/Cargo.toml +++ b/crates/sui-sdk/Cargo.toml @@ -48,11 +48,6 @@ futures-core.workspace = true futures.workspace = true rand.workspace = true -[[example]] -name = "tic_tac_toe" -path = "examples/tic_tac_toe.rs" -test = false - [[example]] name = "coin_read_api" path = "examples/coin_read_api.rs" diff --git a/crates/sui-sdk/README.md b/crates/sui-sdk/README.md index 7ffd0273b2ea0..fc33635a66140 100644 --- a/crates/sui-sdk/README.md +++ b/crates/sui-sdk/README.md @@ -10,7 +10,7 @@ tokio = { version = "1.2", features = ["full"] } anyhow = "1.0" ``` -The main building block for the Sui Rust SDK is the `SuiClientBuilder`, which provides a simple and straightforward way of connecting to a Sui network and having access to the different available APIs. +The main building block for the Sui Rust SDK is the `SuiClientBuilder`, which provides a simple and straightforward way of connecting to a Sui network and having access to the different available APIs. In the following example, the application connects to the Sui `testnet` and `devnet` networks and prints out their respective RPC API versions. @@ -54,7 +54,7 @@ There are serveral files ending in `_api.rs` which provide code examples of the ### Prerequisites -Unless otherwise specified, most of these examples assume `Rust` and `cargo` are installed, and that there is an available internet connection. The examples connect to the Sui testnet (`https://fullnode.testnet.sui.io:443`) and execute different APIs using the active address from the local wallet. If there is no local wallet, it will create one, generate two addresses, set one of them to be active, and it will request 1 SUI from the testnet faucet for the active address. +Unless otherwise specified, most of these examples assume `Rust` and `cargo` are installed, and that there is an available internet connection. The examples connect to the Sui testnet (`https://fullnode.testnet.sui.io:443`) and execute different APIs using the active address from the local wallet. If there is no local wallet, it will create one, generate two addresses, set one of them to be active, and it will request 1 SUI from the testnet faucet for the active address. ### Running the existing examples @@ -78,9 +78,9 @@ The `SuiClientBuilder` struct provides a connection to the JSON-RPC server that - Testnet: https://fullnode.testnet.sui.io:443 - Mainnet: https://fullnode.mainnet.sui.io:443 -For all available servers, see [here](https://sui.io/networkinfo). +For all available servers, see [here](https://sui.io/networkinfo). -For running a local Sui network, please follow [this guide](https://docs.sui.io/build/sui-local-network) for installing Sui and [this guide](https://docs.sui.io/build/sui-local-network#start-the-local-network) for starting the local Sui network. +For running a local Sui network, please follow [this guide](https://docs.sui.io/build/sui-local-network) for installing Sui and [this guide](https://docs.sui.io/build/sui-local-network#start-the-local-network) for starting the local Sui network. ```rust @@ -121,7 +121,7 @@ async fn main() -> Result<(), anyhow::Error> { println!("Sui local network version: {}", sui_local.api_version()); let active_address = SuiAddress::from_str("")?; // change to your Sui address - + let total_balance = sui_local .coin_read_api() .get_all_balances(active_address) @@ -139,7 +139,7 @@ See the programmable transactions [example](https://github.com/MystenLabs/sui/bl ### Tic Tac Toe quick start -1. Prepare the environment +1. Prepare the environment 1. Install `sui` binary following the [Sui installation](https://github.com/MystenLabs/sui/blob/main/docs/content/guides/developer/getting-started/sui-install.mdx) docs. 1. [Connect to Sui Devnet](https://github.com/MystenLabs/sui/blob/main/docs/content/guides/developer/getting-started/connect.mdx). 1. [Make sure you have two addresses with gas](https://github.com/MystenLabs/sui/blob/main/docs/content/guides/developer/getting-started/get-address.mdx) by using the `new-address` command to create new addresses: @@ -152,32 +152,28 @@ See the programmable transactions [example](https://github.com/MystenLabs/sui/bl 2. Publish the move contract 1. [Download the Sui source code](https://github.com/MystenLabs/sui/blob/main/docs/content/guides/developer/getting-started/sui-install.mdx). - 1. Publish the [`games` package](https://github.com/MystenLabs/sui/tree/main/sui_programmability/examples/games) + 1. Publish the [`tic-tac-toe` package](https://github.com/MystenLabs/sui/tree/main/examples/tic-tac-toe/move) using the Sui client: ```shell - sui client publish --path /path-to-sui-source-code/sui_programmability/examples/games --gas-budget 10000 + sui client publish --path /path-to-sui-source-code/examples/tic-tac-toe/move ``` 1. Record the package object ID. 3. Create a new tic-tac-toe game - 1. Run the following command in the Sui source code directory to start a new game, replacing the game package objects ID with the one you recorded: - ```shell - cargo run --example tic-tac-toe -- --game-package-id <> new-game - ``` - This will create a game for the first two addresses in your keystore by default. If you want to specify the identity of each player, - use the following command and replace the variables with the actual player's addresses: + 1. Run the following command in the [`tic-tac-toe/cli` directory](https://github.com/MystenLabs/sui/tree/main/examples/tic-tac-toe/cli) to start a new game, replacing the game package objects ID with the one you recorded: ```shell - cargo run --example tic-tac-toe -- --game-package-id <> new-game --player-x <> --player-o <> + cargo run -- new --package-id <> <> ``` + This will create a game between the active address in the keystore, and the specified Player O. 1. Copy the game ID and pass it to your friend to join the game. -4. Joining the game +4. Making a move - Run the following command in the Sui source code directory to join the game, replacing the game ID and address accordingly: + Run the following command in the [`tic-tac-toe/cli` directory](https://github.com/MystenLabs/sui/tree/main/examples/tic-tac-toe/cli) to make a move in an existing game, as the active address in the CLI, replacing the game ID and address accordingly: ```shell - cargo run --example tic-tac-toe -- --game-package-id <> join-game --my-identity <
> --game-id <> + cargo run -- move --package-id <> --row $R --col $C <> ``` ## License -[SPDX-License-Identifier: Apache-2.0](https://github.com/MystenLabs/sui/blob/main/LICENSE) +[SPDX-License-Identifier: Apache-2.0](https://github.com/MystenLabs/sui/blob/main/LICENSE) diff --git a/crates/sui-sdk/examples/tic_tac_toe.rs b/crates/sui-sdk/examples/tic_tac_toe.rs deleted file mode 100644 index aa8d327d24917..0000000000000 --- a/crates/sui-sdk/examples/tic_tac_toe.rs +++ /dev/null @@ -1,368 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -use std::io::{stdin, stdout, Write}; -use std::path::PathBuf; -use std::str::FromStr; -use std::thread; -use std::time::Duration; - -use anyhow::anyhow; -use async_recursion::async_recursion; -use clap::Parser; -use clap::Subcommand; -use serde::Deserialize; - -use shared_crypto::intent::Intent; -use sui_json_rpc_types::{SuiObjectDataOptions, SuiTransactionBlockResponseOptions}; -use sui_keys::keystore::{AccountKeystore, FileBasedKeystore, Keystore}; -use sui_sdk::{ - json::SuiJsonValue, - rpc_types::{SuiData, SuiTransactionBlockEffectsAPI}, - types::{ - base_types::{ObjectID, SuiAddress}, - id::UID, - transaction::Transaction, - }, - SuiClient, SuiClientBuilder, -}; -use sui_types::quorum_driver_types::ExecuteTransactionRequestType; - -#[tokio::main] -async fn main() -> Result<(), anyhow::Error> { - let opts: TicTacToeOpts = TicTacToeOpts::parse(); - let keystore_path = opts.keystore_path.unwrap_or_else(default_keystore_path); - let keystore = Keystore::File(FileBasedKeystore::new(&keystore_path)?); - - let game = TicTacToe { - game_package_id: opts.game_package_id, - client: SuiClientBuilder::default() - .build(opts.rpc_server_url) - .await?, - keystore, - }; - - match opts.subcommand { - TicTacToeCommand::NewGame { player_x, player_o } => { - game.create_game(player_x, player_o).await?; - } - TicTacToeCommand::JoinGame { - my_identity, - game_id, - } => { - game.join_game(game_id, my_identity).await?; - } - } - - Ok(()) -} - -struct TicTacToe { - game_package_id: ObjectID, - client: SuiClient, - keystore: Keystore, -} - -impl TicTacToe { - async fn create_game( - &self, - player_x: Option, - player_o: Option, - ) -> Result<(), anyhow::Error> { - // Default player identity to first and second keys in the keystore if not provided. - let player_x = player_x.unwrap_or_else(|| self.keystore.addresses()[0]); - let player_o = player_o.unwrap_or_else(|| self.keystore.addresses()[1]); - - // Create a move call transaction using the TransactionBuilder API. - let create_game_call = self - .client - .transaction_builder() - .move_call( - player_x, - self.game_package_id, - "shared_tic_tac_toe", - "create_game", - vec![], - vec![ - SuiJsonValue::from_str(&player_x.to_string())?, - SuiJsonValue::from_str(&player_o.to_string())?, - ], - None, // The node will pick a gas object belong to the signer if not provided. - 1000, - None, - ) - .await?; - - // Sign transaction. - let signature = - self.keystore - .sign_secure(&player_x, &create_game_call, Intent::sui_transaction())?; - - // Execute the transaction. - - let response = self - .client - .quorum_driver_api() - .execute_transaction_block( - Transaction::from_data(create_game_call, vec![signature]), - SuiTransactionBlockResponseOptions::full_content(), - Some(ExecuteTransactionRequestType::WaitForLocalExecution), - ) - .await?; - - assert!(response.confirmed_local_execution.unwrap()); - - // We know `create_game` move function will create 1 object. - let game_id = response - .effects - .as_ref() - .unwrap() - .created() - .first() - .unwrap() - .reference - .object_id; - - println!("Created new game, game id : [{}]", game_id); - println!("Player X : {}", player_x); - println!("Player O : {}", player_o); - - self.join_game(game_id, player_x).await?; - Ok(()) - } - - async fn join_game( - &self, - game_id: ObjectID, - my_identity: SuiAddress, - ) -> Result<(), anyhow::Error> { - let game_state = self.fetch_game_state(game_id).await?; - if game_state.o_address == my_identity { - println!("You are player O") - } else if game_state.x_address == my_identity { - println!("You are player X") - } else { - return Err(anyhow!("You are not invited to the game.")); - } - self.next_turn(my_identity, game_state).await - } - - #[async_recursion] - async fn next_turn( - &self, - my_identity: SuiAddress, - game_state: TicTacToeState, - ) -> Result<(), anyhow::Error> { - game_state.print_game_board(); - - // return if game ended. - if game_state.game_status != 0 { - println!("Game ended."); - match game_state.game_status { - 1 => println!("Player X won!"), - 2 => println!("Player O won!"), - 3 => println!("It's a draw!"), - _ => {} - } - return Ok(()); - } - - if game_state.is_my_turn(my_identity) { - println!("It's your turn!"); - let row = get_row_col_input(true) - 1; - let col = get_row_col_input(false) - 1; - - // Create a move call transaction using the TransactionBuilder API. - let place_mark_call = self - .client - .transaction_builder() - .move_call( - my_identity, - self.game_package_id, - "shared_tic_tac_toe", - "place_mark", - vec![], - vec![ - SuiJsonValue::from_str(&game_state.info.object_id().to_hex_literal())?, - SuiJsonValue::from_str(&row.to_string())?, - SuiJsonValue::from_str(&col.to_string())?, - ], - None, - 1000, - None, - ) - .await?; - - // Sign transaction. - let signature = self.keystore.sign_secure( - &my_identity, - &place_mark_call, - Intent::sui_transaction(), - )?; - - // Execute the transaction. - let response = self - .client - .quorum_driver_api() - .execute_transaction_block( - Transaction::from_data(place_mark_call, vec![signature]), - SuiTransactionBlockResponseOptions::new().with_effects(), - Some(ExecuteTransactionRequestType::WaitForLocalExecution), - ) - .await?; - - assert!(response.confirmed_local_execution.unwrap()); - - // Print any execution error. - let status = response.effects.as_ref().unwrap().status(); - if status.is_err() { - eprintln!("{:?}", status); - } - // Proceed to next turn. - self.next_turn( - my_identity, - self.fetch_game_state(*game_state.info.object_id()).await?, - ) - .await?; - } else { - println!("Waiting for opponent..."); - // Sleep until my turn. - while !self - .fetch_game_state(*game_state.info.object_id()) - .await? - .is_my_turn(my_identity) - { - thread::sleep(Duration::from_secs(1)); - } - self.next_turn( - my_identity, - self.fetch_game_state(*game_state.info.object_id()).await?, - ) - .await?; - }; - Ok(()) - } - - // Retrieve the latest game state from the server. - async fn fetch_game_state(&self, game_id: ObjectID) -> Result { - // Get the raw BCS serialised move object data - let current_game = self - .client - .read_api() - .get_object_with_options(game_id, SuiObjectDataOptions::new().with_bcs()) - .await?; - current_game - .object()? - .bcs - .as_ref() - .unwrap() - .try_as_move() - .unwrap() - .deserialize() - } -} - -// Helper function for getting console input -fn get_row_col_input(is_row: bool) -> u8 { - let r_c = if is_row { "row" } else { "column" }; - print!("Enter {} number (1-3) : ", r_c); - let _ = stdout().flush(); - let mut s = String::new(); - stdin() - .read_line(&mut s) - .expect("Did not enter a correct string"); - - if let Ok(number) = s.trim().parse() { - if number > 0 && number < 4 { - return number; - } - } - get_row_col_input(is_row) -} - -// Clap command line args parser -#[derive(Parser)] -#[clap( - name = "tic-tac-toe", - about = "A Byzantine fault tolerant Tic-Tac-Toe with low-latency finality and high throughput", - rename_all = "kebab-case" -)] -struct TicTacToeOpts { - #[clap(long)] - game_package_id: ObjectID, - #[clap(long)] - keystore_path: Option, - #[clap(long, default_value = "https://fullnode.devnet.sui.io:443")] - rpc_server_url: String, - #[clap(subcommand)] - subcommand: TicTacToeCommand, -} - -fn default_keystore_path() -> PathBuf { - match dirs::home_dir() { - Some(v) => v.join(".sui").join("sui_config").join("sui.keystore"), - None => panic!("Cannot obtain home directory path"), - } -} - -#[derive(Subcommand)] -#[clap(rename_all = "kebab-case")] -enum TicTacToeCommand { - NewGame { - #[clap(long)] - player_x: Option, - #[clap(long)] - player_o: Option, - }, - JoinGame { - #[clap(long)] - my_identity: SuiAddress, - #[clap(long)] - game_id: ObjectID, - }, -} - -// Data structure mirroring move object `games::shared_tic_tac_toe::TicTacToe` for deserialization. -#[derive(Deserialize, Debug)] -struct TicTacToeState { - info: UID, - gameboard: Vec>, - cur_turn: u8, - game_status: u8, - x_address: SuiAddress, - o_address: SuiAddress, -} - -impl TicTacToeState { - fn print_game_board(&self) { - println!(" 1 2 3"); - print!(" ┌-----┬-----┬-----┐"); - let mut row_num = 1; - for row in &self.gameboard { - println!(); - print!("{} ", row_num); - for cell in row { - let mark = match cell { - 0 => "X", - 1 => "O", - _ => " ", - }; - print!("| {} ", mark) - } - println!("|"); - print!(" ├-----┼-----┼-----┤"); - row_num += 1; - } - print!("\r"); - println!(" └-----┴-----┴-----┘"); - } - - fn is_my_turn(&self, my_identity: SuiAddress) -> bool { - let current_player = if self.cur_turn % 2 == 0 { - self.x_address - } else { - self.o_address - }; - current_player == my_identity - } -} diff --git a/crates/sui-test-transaction-builder/src/lib.rs b/crates/sui-test-transaction-builder/src/lib.rs index ff230b4208279..95a27462e3508 100644 --- a/crates/sui-test-transaction-builder/src/lib.rs +++ b/crates/sui-test-transaction-builder/src/lib.rs @@ -149,8 +149,8 @@ impl TestTransactionBuilder { pub fn call_nft_create(self, package_id: ObjectID) -> Self { self.move_call( package_id, - "devnet_nft", - "mint", + "testnet_nft", + "mint_to_sender", vec![ CallArg::Pure(bcs::to_bytes("example_nft_name").unwrap()), CallArg::Pure(bcs::to_bytes("example_nft_description").unwrap()), @@ -164,7 +164,7 @@ impl TestTransactionBuilder { pub fn call_nft_delete(self, package_id: ObjectID, nft_to_delete: ObjectRef) -> Self { self.move_call( package_id, - "devnet_nft", + "testnet_nft", "burn", vec![CallArg::Object(ObjectArg::ImmOrOwnedObject(nft_to_delete))], ) @@ -264,7 +264,7 @@ impl TestTransactionBuilder { path } else { let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - path.extend(["..", "..", "sui_programmability", "examples", subpath]); + path.extend(["..", "..", "examples", "move", subpath]); path }; self.publish(path) @@ -661,7 +661,7 @@ pub async fn publish_nfts_package( let gas_price = context.get_reference_gas_price().await.unwrap(); let txn = context.sign_transaction( &TestTransactionBuilder::new(sender, gas_object, gas_price) - .publish_examples("nfts") + .publish_examples("nft") .build(), ); let resp = context.execute_transaction_must_succeed(txn).await; @@ -672,7 +672,7 @@ pub async fn publish_nfts_package( /// Pre-requisite: `publish_nfts_package` must be called before this function. Executes a /// transaction to create an NFT and returns the sender address, the object id of the NFT, and the /// digest of the transaction. -pub async fn create_devnet_nft( +pub async fn create_nft( context: &WalletContext, package_id: ObjectID, ) -> (SuiAddress, ObjectID, TransactionDigest) { @@ -700,7 +700,7 @@ pub async fn create_devnet_nft( } /// Executes a transaction to delete the given NFT. -pub async fn delete_devnet_nft( +pub async fn delete_nft( context: &WalletContext, sender: SuiAddress, package_id: ObjectID, diff --git a/docker/stress/Dockerfile b/docker/stress/Dockerfile index 71d7f09b53be3..e5202c0ba0657 100644 --- a/docker/stress/Dockerfile +++ b/docker/stress/Dockerfile @@ -7,7 +7,7 @@ ARG SUI_TOOLS_IMAGE_TAG RUN apt-get update && apt-get -y --no-install-recommends install wget=1.21-1+deb11u1 \ iputils-ping netcat procps bind9-host bind9-dnsutils curl iproute2 git ca-certificates awscli -# stress needs access to sui_programmability/examples/basics +# stress needs access to examples/move/basics RUN git clone https://github.com/MystenLabs/sui.git ; \ cd sui ; \ git checkout $SUI_TOOLS_IMAGE_TAG ; \ diff --git a/examples/move/basics/sources/counter.move b/examples/move/basics/sources/counter.move new file mode 100644 index 0000000000000..60521cbddbdf0 --- /dev/null +++ b/examples/move/basics/sources/counter.move @@ -0,0 +1,116 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +/// This example demonstrates a basic use of a shared object. +/// Rules: +/// - anyone can create and share a counter +/// - everyone can increment a counter by 1 +/// - the owner of the counter can reset it to any value +module basics::counter { + /// A shared counter. + public struct Counter has key { + id: UID, + owner: address, + value: u64 + } + + public fun owner(counter: &Counter): address { + counter.owner + } + + public fun value(counter: &Counter): u64 { + counter.value + } + + /// Create and share a Counter object. + public fun create(ctx: &mut TxContext) { + transfer::share_object(Counter { + id: object::new(ctx), + owner: tx_context::sender(ctx), + value: 0 + }) + } + + /// Increment a counter by 1. + public fun increment(counter: &mut Counter) { + counter.value = counter.value + 1; + } + + /// Set value (only runnable by the Counter owner) + public fun set_value(counter: &mut Counter, value: u64, ctx: &TxContext) { + assert!(counter.owner == ctx.sender(), 0); + counter.value = value; + } + + /// Assert a value for the counter. + public fun assert_value(counter: &Counter, value: u64) { + assert!(counter.value == value, 0) + } + + /// Delete counter (only runnable by the Counter owner) + public fun delete(counter: Counter, ctx: &TxContext) { + assert!(counter.owner == ctx.sender(), 0); + let Counter {id, owner:_, value:_} = counter; + id.delete(); + } +} + +#[test_only] +module basics::counter_test { + use sui::test_scenario as ts; + use basics::counter::{Self, Counter}; + + #[test] + fun test_counter() { + let owner = @0xC0FFEE; + let user1 = @0xA1; + + let mut ts = ts::begin(user1); + + { + ts.next_tx(owner); + counter::create(ts.ctx()); + }; + + { + ts.next_tx(user1); + let mut counter: Counter = ts.take_shared(); + + assert!(counter.owner() == owner); + assert!(counter.value() == 0); + + counter.increment(); + counter.increment(); + counter.increment(); + + ts::return_shared(counter); + }; + + { + ts.next_tx(owner); + let mut counter: Counter = ts.take_shared(); + + assert!(counter.owner() == owner); + assert!(counter.value() == 3); + + counter.set_value(100, ts.ctx()); + + ts::return_shared(counter); + }; + + { + ts.next_tx(user1); + let mut counter: Counter = ts.take_shared(); + + assert!(counter.owner() == owner); + assert!(counter.value() == 100); + + counter.increment(); + assert!(counter.value() == 101); + + ts::return_shared(counter); + }; + + ts.end(); + } +} diff --git a/examples/move/basics/sources/object_basics.move b/examples/move/basics/sources/object_basics.move new file mode 100644 index 0000000000000..b7e7a8aa59cda --- /dev/null +++ b/examples/move/basics/sources/object_basics.move @@ -0,0 +1,63 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +/// Test CTURD object basics (create, transfer, update, read, delete) +module basics::object_basics { + use sui::event; + + public struct Object has key, store { + id: UID, + value: u64, + } + + public struct Wrapper has key { + id: UID, + o: Object + } + + public struct NewValueEvent has copy, drop { + new_value: u64 + } + + public fun create(value: u64, recipient: address, ctx: &mut TxContext) { + transfer::public_transfer( + Object { id: object::new(ctx), value }, + recipient + ) + } + + public fun transfer(o: Object, recipient: address) { + transfer::public_transfer(o, recipient) + } + + public fun freeze_object(o: Object) { + transfer::public_freeze_object(o) + } + + public fun set_value(o: &mut Object, value: u64) { + o.value = value; + } + + // test that reading o2 and updating o1 works + public fun update(o1: &mut Object, o2: &Object) { + o1.value = o2.value; + // emit an event so the world can see the new value + event::emit(NewValueEvent { new_value: o2.value }) + } + + public fun delete(o: Object) { + let Object { id, value: _ } = o; + id.delete(); + } + + public fun wrap(o: Object, ctx: &mut TxContext) { + transfer::transfer(Wrapper { id: object::new(ctx), o }, ctx.sender()); + } + + #[lint_allow(self_transfer)] + public fun unwrap(w: Wrapper, ctx: &TxContext) { + let Wrapper { id, o } = w; + id.delete(); + transfer::public_transfer(o, ctx.sender()); + } +} diff --git a/examples/move/basics/sources/random.move b/examples/move/basics/sources/random.move new file mode 100644 index 0000000000000..f4dd9b88070a5 --- /dev/null +++ b/examples/move/basics/sources/random.move @@ -0,0 +1,18 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +/// This example demonstrates emitting a random u128 (e.g., for an offchain lottery) +module basics::random { + use sui::event; + use sui::random::Random; + + public struct RandomU128Event has copy, drop { + value: u128, + } + + entry fun new(r: &Random, ctx: &mut TxContext) { + let mut gen = r.new_generator(ctx); + let value = gen.generate_u128(); + event::emit(RandomU128Event { value }); + } +} diff --git a/examples/move/basics/sources/resolve_args.move b/examples/move/basics/sources/resolve_args.move new file mode 100644 index 0000000000000..786547820e21c --- /dev/null +++ b/examples/move/basics/sources/resolve_args.move @@ -0,0 +1,21 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +/// Functions and types to test argument resolution in JSON-RPC. +module basics::resolve_args { + public struct Foo has key { + id: UID, + } + + public fun foo( + _foo: &mut Foo, + _bar: vector, + _name: vector, + _index: u64, + _flag: u8, + _recipient: address, + _ctx: &mut TxContext, + ) { + abort 0 + } +}