From 7e5c5183df4a6f9854afdb84c5bef155ed69b9d2 Mon Sep 17 00:00:00 2001 From: Timothy Zakian Date: Tue, 11 Jul 2023 14:09:44 -0700 Subject: [PATCH] Transfer-to-object --- .../tests/mvcc/receive_object_dof.exp | 112 ++++++++ .../tests/mvcc/receive_object_dof.move | 119 ++++++++ .../mvcc/receive_object_split_changes_dof.exp | 83 ++++++ .../receive_object_split_changes_dof.move | 84 ++++++ .../tests/receive_object/basic_receive.exp | 39 +++ .../tests/receive_object/basic_receive.move | 49 ++++ .../tests/receive_object/drop_receiving.exp | 23 ++ .../tests/receive_object/drop_receiving.move | 50 ++++ .../duplicate_receive_argument.exp | 21 ++ .../duplicate_receive_argument.move | 46 ++++ .../pass_through_then_receive.exp | 25 ++ .../pass_through_then_receive.move | 44 +++ .../receive_object/receive_and_deleted.exp | 38 +++ .../receive_object/receive_and_deleted.move | 46 ++++ .../receive_object/receive_and_send_back.exp | 39 +++ .../receive_object/receive_and_send_back.move | 47 ++++ .../tests/receive_object/receive_and_wrap.exp | 39 +++ .../receive_object/receive_and_wrap.move | 59 ++++ .../tests/receive_object/receive_by_ref.exp | 73 +++++ .../tests/receive_object/receive_by_ref.move | 82 ++++++ .../receive_by_value_flow_through.exp | 29 ++ .../receive_by_value_flow_through.move | 41 +++ .../receive_object/receive_dof_and_mutate.exp | 59 ++++ .../receive_dof_and_mutate.move | 56 ++++ .../receive_invalid_param_ty.exp | 60 ++++ .../receive_invalid_param_ty.move | 69 +++++ .../receive_object/receive_invalid_type.exp | 25 ++ .../receive_object/receive_invalid_type.move | 41 +++ .../receive_multiple_times_in_row.exp | 59 ++++ .../receive_multiple_times_in_row.move | 65 +++++ .../receive_object/receive_object_cyclic.exp | 11 + .../receive_object/receive_object_cyclic.move | 43 +++ .../receive_object/receive_object_owner.exp | 35 +++ .../receive_object/receive_object_owner.move | 42 +++ .../shared_parent/basic_receive.exp | 39 +++ .../shared_parent/basic_receive.move | 46 ++++ .../shared_parent/drop_receiving.exp | 23 ++ .../shared_parent/drop_receiving.move | 49 ++++ .../shared_parent/receive_dof_and_mutate.exp | 59 ++++ .../shared_parent/receive_dof_and_mutate.move | 56 ++++ .../receive_multiple_times_in_row.exp | 59 ++++ .../receive_multiple_times_in_row.move | 66 +++++ .../shared_parent/transfer_then_share.exp | 39 +++ .../shared_parent/transfer_then_share.move | 47 ++++ .../take_receiver_then_try_to_reuse.exp | 25 ++ .../take_receiver_then_try_to_reuse.move | 44 +++ .../tests/shared/upgrade.exp | 12 +- crates/sui-benchmark/src/lib.rs | 3 + .../sui-config/src/transaction_deny_config.rs | 13 + crates/sui-core/src/authority.rs | 11 +- .../sui-core/src/authority/authority_store.rs | 216 +++++++++++++-- crates/sui-core/src/lib.rs | 3 + .../sui-core/src/transaction_input_checker.rs | 7 + crates/sui-core/src/transaction_manager.rs | 102 ++++--- .../src/transaction_signing_filter.rs | 21 ++ .../src/unit_tests/data/tto/Move.toml | 9 + .../src/unit_tests/data/tto/sources/tto1.move | 40 +++ .../unit_tests/move_package_upgrade_tests.rs | 7 +- .../unit_tests/transaction_manager_tests.rs | 10 +- .../unit_tests/transfer_to_object_tests.rs | 260 ++++++++++++++++++ crates/sui-core/tests/staged/sui.yaml | 7 + crates/sui-framework/docs/transfer.md | 94 ++++++- .../sui-framework/sources/transfer.move | 21 +- .../sui-json-rpc-types/src/sui_transaction.rs | 11 + crates/sui-move/src/unit_test.rs | 1 + crates/sui-open-rpc/spec/openrpc.json | 2 + crates/sui-protocol-config/src/lib.rs | 23 ++ ...ocol_config__test__Mainnet_version_18.snap | 2 + ...ocol_config__test__Testnet_version_18.snap | 2 + ...sui_protocol_config__test__version_18.snap | 2 + crates/sui-replay/src/replay.rs | 42 +++ crates/sui-replay/src/types.rs | 6 + ...ests__network_config_snapshot_matches.snap | 7 + ..._populated_genesis_snapshot_matches-2.snap | 28 +- .../sui-transactional-test-runner/src/args.rs | 75 +++-- .../src/test_adapter.rs | 1 + crates/sui-types/src/execution.rs | 50 +++- crates/sui-types/src/execution_mode.rs | 5 + crates/sui-types/src/in_memory_storage.rs | 22 ++ crates/sui-types/src/lib.rs | 13 +- crates/sui-types/src/storage.rs | 59 ++++ crates/sui-types/src/temporary_store.rs | 31 ++- crates/sui-types/src/transaction.rs | 70 ++++- crates/sui-types/src/transfer.rs | 60 ++++ .../receive_with_and_without_store.exp | 11 + .../receive_with_and_without_store.mvir | 25 ++ .../private_generics/receive_without_key.exp | 5 + .../private_generics/receive_without_key.mvir | 12 + .../latest/sui-adapter/src/adapter.rs | 2 + .../src/programmable_transactions/context.rs | 71 +++-- .../programmable_transactions/execution.rs | 50 +++- .../sui-move-natives/src/dynamic_field.rs | 26 +- .../latest/sui-move-natives/src/lib.rs | 55 +++- .../src/object_runtime/mod.rs | 67 +++-- .../src/object_runtime/object_store.rs | 112 ++++++++ .../latest/sui-move-natives/src/transfer.rs | 90 +++++- .../sui-verifier/src/private_generics.rs | 2 + .../src/programmable_transactions/context.rs | 35 ++- .../programmable_transactions/execution.rs | 1 + 99 files changed, 4007 insertions(+), 210 deletions(-) create mode 100644 crates/sui-adapter-transactional-tests/tests/mvcc/receive_object_dof.exp create mode 100644 crates/sui-adapter-transactional-tests/tests/mvcc/receive_object_dof.move create mode 100644 crates/sui-adapter-transactional-tests/tests/mvcc/receive_object_split_changes_dof.exp create mode 100644 crates/sui-adapter-transactional-tests/tests/mvcc/receive_object_split_changes_dof.move create mode 100644 crates/sui-adapter-transactional-tests/tests/receive_object/basic_receive.exp create mode 100644 crates/sui-adapter-transactional-tests/tests/receive_object/basic_receive.move create mode 100644 crates/sui-adapter-transactional-tests/tests/receive_object/drop_receiving.exp create mode 100644 crates/sui-adapter-transactional-tests/tests/receive_object/drop_receiving.move create mode 100644 crates/sui-adapter-transactional-tests/tests/receive_object/duplicate_receive_argument.exp create mode 100644 crates/sui-adapter-transactional-tests/tests/receive_object/duplicate_receive_argument.move create mode 100644 crates/sui-adapter-transactional-tests/tests/receive_object/pass_through_then_receive.exp create mode 100644 crates/sui-adapter-transactional-tests/tests/receive_object/pass_through_then_receive.move create mode 100644 crates/sui-adapter-transactional-tests/tests/receive_object/receive_and_deleted.exp create mode 100644 crates/sui-adapter-transactional-tests/tests/receive_object/receive_and_deleted.move create mode 100644 crates/sui-adapter-transactional-tests/tests/receive_object/receive_and_send_back.exp create mode 100644 crates/sui-adapter-transactional-tests/tests/receive_object/receive_and_send_back.move create mode 100644 crates/sui-adapter-transactional-tests/tests/receive_object/receive_and_wrap.exp create mode 100644 crates/sui-adapter-transactional-tests/tests/receive_object/receive_and_wrap.move create mode 100644 crates/sui-adapter-transactional-tests/tests/receive_object/receive_by_ref.exp create mode 100644 crates/sui-adapter-transactional-tests/tests/receive_object/receive_by_ref.move create mode 100644 crates/sui-adapter-transactional-tests/tests/receive_object/receive_by_value_flow_through.exp create mode 100644 crates/sui-adapter-transactional-tests/tests/receive_object/receive_by_value_flow_through.move create mode 100644 crates/sui-adapter-transactional-tests/tests/receive_object/receive_dof_and_mutate.exp create mode 100644 crates/sui-adapter-transactional-tests/tests/receive_object/receive_dof_and_mutate.move create mode 100644 crates/sui-adapter-transactional-tests/tests/receive_object/receive_invalid_param_ty.exp create mode 100644 crates/sui-adapter-transactional-tests/tests/receive_object/receive_invalid_param_ty.move create mode 100644 crates/sui-adapter-transactional-tests/tests/receive_object/receive_invalid_type.exp create mode 100644 crates/sui-adapter-transactional-tests/tests/receive_object/receive_invalid_type.move create mode 100644 crates/sui-adapter-transactional-tests/tests/receive_object/receive_multiple_times_in_row.exp create mode 100644 crates/sui-adapter-transactional-tests/tests/receive_object/receive_multiple_times_in_row.move create mode 100644 crates/sui-adapter-transactional-tests/tests/receive_object/receive_object_cyclic.exp create mode 100644 crates/sui-adapter-transactional-tests/tests/receive_object/receive_object_cyclic.move create mode 100644 crates/sui-adapter-transactional-tests/tests/receive_object/receive_object_owner.exp create mode 100644 crates/sui-adapter-transactional-tests/tests/receive_object/receive_object_owner.move create mode 100644 crates/sui-adapter-transactional-tests/tests/receive_object/shared_parent/basic_receive.exp create mode 100644 crates/sui-adapter-transactional-tests/tests/receive_object/shared_parent/basic_receive.move create mode 100644 crates/sui-adapter-transactional-tests/tests/receive_object/shared_parent/drop_receiving.exp create mode 100644 crates/sui-adapter-transactional-tests/tests/receive_object/shared_parent/drop_receiving.move create mode 100644 crates/sui-adapter-transactional-tests/tests/receive_object/shared_parent/receive_dof_and_mutate.exp create mode 100644 crates/sui-adapter-transactional-tests/tests/receive_object/shared_parent/receive_dof_and_mutate.move create mode 100644 crates/sui-adapter-transactional-tests/tests/receive_object/shared_parent/receive_multiple_times_in_row.exp create mode 100644 crates/sui-adapter-transactional-tests/tests/receive_object/shared_parent/receive_multiple_times_in_row.move create mode 100644 crates/sui-adapter-transactional-tests/tests/receive_object/shared_parent/transfer_then_share.exp create mode 100644 crates/sui-adapter-transactional-tests/tests/receive_object/shared_parent/transfer_then_share.move create mode 100644 crates/sui-adapter-transactional-tests/tests/receive_object/take_receiver_then_try_to_reuse.exp create mode 100644 crates/sui-adapter-transactional-tests/tests/receive_object/take_receiver_then_try_to_reuse.move create mode 100644 crates/sui-core/src/unit_tests/data/tto/Move.toml create mode 100644 crates/sui-core/src/unit_tests/data/tto/sources/tto1.move create mode 100644 crates/sui-core/src/unit_tests/transfer_to_object_tests.rs create mode 100644 crates/sui-types/src/transfer.rs create mode 100644 crates/sui-verifier-transactional-tests/tests/private_generics/receive_with_and_without_store.exp create mode 100644 crates/sui-verifier-transactional-tests/tests/private_generics/receive_with_and_without_store.mvir create mode 100644 crates/sui-verifier-transactional-tests/tests/private_generics/receive_without_key.exp create mode 100644 crates/sui-verifier-transactional-tests/tests/private_generics/receive_without_key.mvir diff --git a/crates/sui-adapter-transactional-tests/tests/mvcc/receive_object_dof.exp b/crates/sui-adapter-transactional-tests/tests/mvcc/receive_object_dof.exp new file mode 100644 index 0000000000000..7d7f4e25ec137 --- /dev/null +++ b/crates/sui-adapter-transactional-tests/tests/mvcc/receive_object_dof.exp @@ -0,0 +1,112 @@ +processed 24 tasks + +init: +A: object(0,0) + +task 1 'publish'. lines 6-61: +created: object(1,0) +mutated: object(0,1) +gas summary: computation_cost: 1000000, storage_cost: 10548800, storage_rebate: 0, non_refundable_storage_fee: 0 + +task 2 'run'. lines 63-63: +created: object(2,0), object(2,1), object(2,2), object(2,3) +mutated: object(0,0) +gas summary: computation_cost: 1000000, storage_cost: 7273200, storage_rebate: 0, non_refundable_storage_fee: 0 + +task 3 'view-object'. lines 65-65: +Owner: Object ID: ( fake(2,2) ) +Version: 2 +Contents: sui::dynamic_field::Field, sui::object::ID> {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,0)}}, name: sui::dynamic_object_field::Wrapper {name: 0u64}, value: sui::object::ID {bytes: fake(2,1)}} + +task 4 'view-object'. lines 67-67: +Owner: Object ID: ( fake(2,0) ) +Version: 2 +Contents: tto::M1::A {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,1)}}, value: 0u64} + +task 5 'view-object'. lines 69-69: +Owner: Account Address ( fake(2,3) ) +Version: 2 +Contents: tto::M1::A {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,2)}}, value: 0u64} + +task 6 'view-object'. lines 71-71: +Owner: Account Address ( A ) +Version: 2 +Contents: tto::M1::A {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,3)}}, value: 0u64} + +task 7 'run'. lines 73-73: +created: object(7,0) +mutated: object(0,0), object(2,2), object(2,3) +gas summary: computation_cost: 1000000, storage_cost: 5996400, storage_rebate: 3506184, non_refundable_storage_fee: 35416 + +task 8 'view-object'. lines 75-77: +Owner: Object ID: ( fake(2,2) ) +Version: 2 +Contents: sui::dynamic_field::Field, sui::object::ID> {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,0)}}, name: sui::dynamic_object_field::Wrapper {name: 0u64}, value: sui::object::ID {bytes: fake(2,1)}} + +task 9 'view-object'. lines 78-78: +Owner: Object ID: ( fake(2,0) ) +Version: 2 +Contents: tto::M1::A {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,1)}}, value: 0u64} + +task 10 'view-object'. lines 80-80: +Owner: Object ID: ( fake(7,0) ) +Version: 3 +Contents: tto::M1::A {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,2)}}, value: 0u64} + +task 11 'view-object'. lines 82-82: +Owner: Account Address ( A ) +Version: 3 +Contents: tto::M1::A {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,3)}}, value: 0u64} + +task 12 'programmable'. lines 84-85: +mutated: object(0,0), object(2,1), object(2,2), object(2,3) +gas summary: computation_cost: 1000000, storage_cost: 4818400, storage_rebate: 4770216, non_refundable_storage_fee: 48184 + +task 13 'view-object'. lines 87-89: +Owner: Object ID: ( fake(2,2) ) +Version: 2 +Contents: sui::dynamic_field::Field, sui::object::ID> {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,0)}}, name: sui::dynamic_object_field::Wrapper {name: 0u64}, value: sui::object::ID {bytes: fake(2,1)}} + +task 14 'view-object'. lines 90-90: +Owner: Object ID: ( fake(2,0) ) +Version: 4 +Contents: tto::M1::A {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,1)}}, value: 3u64} + +task 15 'view-object'. lines 92-92: +Owner: Object ID: ( fake(7,0) ) +Version: 4 +Contents: tto::M1::A {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,2)}}, value: 2u64} + +task 16 'view-object'. lines 94-94: +Owner: Account Address ( A ) +Version: 4 +Contents: tto::M1::A {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,3)}}, value: 1u64} + +task 17 'programmable'. lines 96-99: +mutated: object(0,0), object(2,3) +deleted: object(2,0), object(2,1) +gas summary: computation_cost: 1000000, storage_cost: 2264800, storage_rebate: 5936436, non_refundable_storage_fee: 59964 + +task 18 'programmable'. lines 101-102: +mutated: object(_), object(2,3) +gas summary: computation_cost: 500000, storage_cost: 2264800, storage_rebate: 1264032, non_refundable_storage_fee: 12768 + +task 19 'programmable'. lines 104-105: +mutated: object(_), object(2,3) +gas summary: computation_cost: 500000, storage_cost: 2264800, storage_rebate: 1264032, non_refundable_storage_fee: 12768 + +task 20 'programmable'. lines 107-110: +mutated: object(_), object(2,3) +gas summary: computation_cost: 500000, storage_cost: 2264800, storage_rebate: 1264032, non_refundable_storage_fee: 12768 + +task 21 'programmable'. lines 112-113: +Error: Transaction Effects Status: MoveAbort(MoveLocation { module: ModuleId { address: tto, name: Identifier("M1") }, function: 4, instruction: 10, function_name: Some("check") }, 0) in command 0 +Execution Error: MoveAbort(MoveLocation { module: ModuleId { address: tto, name: Identifier("M1") }, function: 4, instruction: 10, function_name: Some("check") }, 0) in command 0 + +task 22 'programmable'. lines 115-116: +Error: Transaction Effects Status: MoveAbort(MoveLocation { module: ModuleId { address: sui, name: Identifier("dynamic_field") }, function: 11, instruction: 0, function_name: Some("borrow_child_object") }, 1) in command 0 +Execution Error: MoveAbort(MoveLocation { module: ModuleId { address: sui, name: Identifier("dynamic_field") }, function: 11, instruction: 0, function_name: Some("borrow_child_object") }, 1) in command 0 + +task 23 'programmable'. lines 118-119: +Error: Transaction Effects Status: MoveAbort(MoveLocation { module: ModuleId { address: tto, name: Identifier("M1") }, function: 4, instruction: 10, function_name: Some("check") }, 0) in command 0 +Execution Error: MoveAbort(MoveLocation { module: ModuleId { address: tto, name: Identifier("M1") }, function: 4, instruction: 10, function_name: Some("check") }, 0) in command 0 diff --git a/crates/sui-adapter-transactional-tests/tests/mvcc/receive_object_dof.move b/crates/sui-adapter-transactional-tests/tests/mvcc/receive_object_dof.move new file mode 100644 index 0000000000000..5b2ee39eb2a02 --- /dev/null +++ b/crates/sui-adapter-transactional-tests/tests/mvcc/receive_object_dof.move @@ -0,0 +1,119 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +//# init --addresses tto=0x0 --accounts A + +//# publish +module tto::M1 { + use std::option::{Self, Option}; + use sui::object::{Self, UID}; + use sui::tx_context::{Self, TxContext}; + use sui::transfer::{Self, Receiving}; + use sui::dynamic_object_field as dof; + + const KEY: u64 = 0; + + struct A has key, store { + id: UID, + value: u64, + } + + + public fun start(ctx: &mut TxContext) { + let a = A { id: object::new(ctx), value: 0 }; + let a_address = object::id_address(&a); + let b = A { id: object::new(ctx), value: 0 }; + dof::add(&mut b.id, KEY, A { id: object::new(ctx), value: 0 }); + transfer::public_transfer(a, tx_context::sender(ctx)); + transfer::public_transfer(b, a_address); + } + + public entry fun receive(parent: &mut A, x: Receiving) { + let b = transfer::receive(&mut parent.id, x); + dof::add(&mut parent.id, KEY, b); + } + + public fun set(grand: &mut A, v1: u64, v2: u64, v3: u64) { + grand.value = v1; + let parent: &mut A = dof::borrow_mut(&mut grand.id, KEY); + parent.value = v2; + let child: &mut A = dof::borrow_mut(&mut parent.id, KEY); + child.value = v3; + } + + public fun remove(grand: &mut A) { + let parent: &mut A = dof::borrow_mut(&mut grand.id, KEY); + let A { id, value: _ } = dof::remove(&mut parent.id, KEY); + object::delete(id); + } + + public fun check(grand: &A, v1: u64, v2: u64, v3: Option) { + assert!(grand.value == v1, 0); + let parent: &A = dof::borrow(&grand.id, KEY); + assert!(parent.value == v2, 0); + if (option::is_some(&v3)) { + let child: &A = dof::borrow(&parent.id, KEY); + assert!(&child.value == option::borrow(&v3), 0); + } else { + assert!(!dof::exists_(&parent.id, KEY), 0); + } + } +} + +//# run tto::M1::start --sender A + +//# view-object 2,0 + +//# view-object 2,1 + +//# view-object 2,2 + +//# view-object 2,3 + +//# run tto::M1::receive --args object(2,3) receiving(2,2) --sender A + +//# view-object 2,0 + +// The grand parent +//# view-object 2,1 + +//# view-object 2,2 + +//# view-object 2,3 + +//# programmable --sender A --inputs object(2,3) 1 2 3 +//> tto::M1::set(Input(0), Input(1), Input(2), Input(3)) + +//# view-object 2,0 + +// The grand parent +//# view-object 2,1 + +//# view-object 2,2 + +//# view-object 2,3 + +//# programmable --sender A --inputs object(2,3) +//> tto::M1::remove(Input(0)) + +// dev-inspect with 'check' and correct values + +//# programmable --sender A --inputs object(2,3)@3 0 0 vector[0] --dev-inspect +//> tto::M1::check(Input(0), Input(1), Input(2), Input(3)) + +//# programmable --sender A --inputs object(2,3)@4 1 2 vector[3] --dev-inspect +//> tto::M1::check(Input(0), Input(1), Input(2), Input(3)) + +//# programmable --sender A --inputs object(2,3)@5 1 2 vector[] --dev-inspect +//> tto::M1::check(Input(0), Input(1), Input(2), Input(3)) + +// dev-inspect with 'check' and _incorrect_ values + +//# programmable --sender A --inputs object(2,3)@4 0 0 vector[0] --dev-inspect +//> tto::M1::check(Input(0), Input(1), Input(2), Input(3)) + +//# programmable --sender A --inputs object(2,3)@5 1 2 vector[3] --dev-inspect +//> tto::M1::check(Input(0), Input(1), Input(2), Input(3)) + +//# programmable --sender A --inputs object(2,3)@3 1 2 vector[] --dev-inspect +//> tto::M1::check(Input(0), Input(1), Input(2), Input(3)) diff --git a/crates/sui-adapter-transactional-tests/tests/mvcc/receive_object_split_changes_dof.exp b/crates/sui-adapter-transactional-tests/tests/mvcc/receive_object_split_changes_dof.exp new file mode 100644 index 0000000000000..48f6fa17b7fa0 --- /dev/null +++ b/crates/sui-adapter-transactional-tests/tests/mvcc/receive_object_split_changes_dof.exp @@ -0,0 +1,83 @@ +processed 17 tasks + +init: +A: object(0,0) + +task 1 'publish'. lines 6-54: +created: object(1,0) +mutated: object(0,1) +gas summary: computation_cost: 1000000, storage_cost: 9606400, storage_rebate: 0, non_refundable_storage_fee: 0 + +task 2 'run'. lines 56-56: +created: object(2,0), object(2,1), object(2,2), object(2,3), object(2,4), object(2,5) +mutated: object(0,0) +gas summary: computation_cost: 1000000, storage_cost: 11004800, storage_rebate: 0, non_refundable_storage_fee: 0 + +task 3 'view-object'. lines 58-58: +Owner: Object ID: ( fake(2,5) ) +Version: 2 +Contents: sui::dynamic_field::Field, sui::object::ID> {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,0)}}, name: sui::dynamic_object_field::Wrapper {name: 0u64}, value: sui::object::ID {bytes: fake(2,2)}} + +task 4 'view-object'. lines 60-60: +Owner: Object ID: ( fake(2,3) ) +Version: 2 +Contents: sui::dynamic_field::Field, sui::object::ID> {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,1)}}, name: sui::dynamic_object_field::Wrapper {name: 0u64}, value: sui::object::ID {bytes: fake(2,4)}} + +task 5 'view-object'. lines 62-62: +Owner: Object ID: ( fake(2,0) ) +Version: 2 +Contents: tto::M1::A {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,2)}}, value: 0u64} + +task 6 'view-object'. lines 64-64: +Owner: Account Address ( A ) +Version: 2 +Contents: tto::M1::A {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,3)}}, value: 0u64} + +task 7 'view-object'. lines 66-66: +Owner: Object ID: ( fake(2,1) ) +Version: 2 +Contents: tto::M1::A {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,4)}}, value: 0u64} + +task 8 'view-object'. lines 68-68: +Owner: Account Address ( A ) +Version: 2 +Contents: tto::M1::A {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,5)}}, value: 0u64} + +task 9 'run'. lines 70-70: +mutated: object(0,0), object(2,3), object(2,4), object(2,5) +gas summary: computation_cost: 1000000, storage_cost: 4818400, storage_rebate: 4770216, non_refundable_storage_fee: 48184 + +task 10 'view-object'. lines 72-72: +Owner: Object ID: ( fake(2,5) ) +Version: 2 +Contents: sui::dynamic_field::Field, sui::object::ID> {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,0)}}, name: sui::dynamic_object_field::Wrapper {name: 0u64}, value: sui::object::ID {bytes: fake(2,2)}} + +task 11 'view-object'. lines 74-74: +Owner: Object ID: ( fake(2,3) ) +Version: 2 +Contents: sui::dynamic_field::Field, sui::object::ID> {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,1)}}, name: sui::dynamic_object_field::Wrapper {name: 0u64}, value: sui::object::ID {bytes: fake(2,4)}} + +task 12 'view-object'. lines 76-76: +Owner: Object ID: ( fake(2,0) ) +Version: 2 +Contents: tto::M1::A {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,2)}}, value: 0u64} + +task 13 'view-object'. lines 78-78: +Owner: Account Address ( fake(2,5) ) +Version: 3 +Contents: tto::M1::A {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,3)}}, value: 40u64} + +task 14 'view-object'. lines 80-80: +Owner: Object ID: ( fake(2,1) ) +Version: 3 +Contents: tto::M1::A {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,4)}}, value: 40u64} + +task 15 'view-object'. lines 82-82: +Owner: Account Address ( A ) +Version: 3 +Contents: tto::M1::A {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,5)}}, value: 0u64} + +task 16 'run'. lines 84-84: +created: object(16,0) +mutated: object(0,0), object(2,3), object(2,5) +gas summary: computation_cost: 1000000, storage_cost: 5996400, storage_rebate: 3506184, non_refundable_storage_fee: 35416 diff --git a/crates/sui-adapter-transactional-tests/tests/mvcc/receive_object_split_changes_dof.move b/crates/sui-adapter-transactional-tests/tests/mvcc/receive_object_split_changes_dof.move new file mode 100644 index 0000000000000..5c1b690430f7f --- /dev/null +++ b/crates/sui-adapter-transactional-tests/tests/mvcc/receive_object_split_changes_dof.move @@ -0,0 +1,84 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +//# init --addresses tto=0x0 --accounts A + +//# publish +module tto::M1 { + // use std::option::{Self, Option}; + use sui::object::{Self, UID}; + use sui::tx_context::{Self, TxContext}; + use sui::transfer::{Self, Receiving}; + use sui::dynamic_object_field as dof; + + const KEY: u64 = 0; + const BKEY: u64 = 1; + + struct A has key, store { + id: UID, + value: u64, + } + + public fun start(ctx: &mut TxContext) { + let a_parent = A { id: object::new(ctx), value: 0 }; + let a_child = A { id: object::new(ctx), value: 0 }; + + let b_parent = A { id: object::new(ctx), value: 0 }; + let b_child = A { id: object::new(ctx), value: 0 }; + dof::add(&mut a_parent.id, KEY, a_child); + dof::add(&mut b_parent.id, KEY, b_child); + transfer::public_transfer(a_parent, tx_context::sender(ctx)); + transfer::public_transfer(b_parent, tx_context::sender(ctx)); + } + + public entry fun receive(a_parent: &mut A, x: Receiving,apv: u64, acv: u64, bpv: u64, bcv: u64) { + let b_parent = transfer::receive(&mut a_parent.id, x); + dof::add(&mut a_parent.id, BKEY, b_parent); + let b_parent: &A = dof::borrow(&a_parent.id, BKEY); + let b_child: &A = dof::borrow(&b_parent.id, KEY); + let a_child: &A = dof::borrow(&a_parent.id, KEY); + assert!(a_parent.value == apv, 0); + assert!(a_child.value == acv, 1); + assert!(b_parent.value == bpv, 2); + assert!(b_child.value == bcv, 3); + } + + public entry fun mutate(b_parent: A, a_parent: &A) { + let b_child: &mut A = dof::borrow_mut(&mut b_parent.id, KEY); + b_parent.value = 40; + b_child.value = 40; + let a_address = object::id_address(a_parent); + transfer::public_transfer(b_parent, a_address); + } + +} + +//# run tto::M1::start --sender A + +//# view-object 2,0 + +//# view-object 2,1 + +//# view-object 2,2 + +//# view-object 2,3 + +//# view-object 2,4 + +//# view-object 2,5 + +//# run tto::M1::mutate --args object(2,3) object(2,5) --sender A + +//# view-object 2,0 + +//# view-object 2,1 + +//# view-object 2,2 + +//# view-object 2,3 + +//# view-object 2,4 + +//# view-object 2,5 + +//# run tto::M1::receive --args object(2,5) receiving(2,3) 0 0 40 40 --sender A diff --git a/crates/sui-adapter-transactional-tests/tests/receive_object/basic_receive.exp b/crates/sui-adapter-transactional-tests/tests/receive_object/basic_receive.exp new file mode 100644 index 0000000000000..8839e207784e3 --- /dev/null +++ b/crates/sui-adapter-transactional-tests/tests/receive_object/basic_receive.exp @@ -0,0 +1,39 @@ +processed 9 tasks + +task 1 'publish'. lines 6-35: +created: object(1,0) +mutated: object(0,0) +gas summary: computation_cost: 1000000, storage_cost: 7600000, storage_rebate: 0, non_refundable_storage_fee: 0 + +task 2 'run'. lines 37-37: +created: object(2,0), object(2,1) +mutated: object(0,0) +gas summary: computation_cost: 1000000, storage_cost: 3420000, storage_rebate: 978120, non_refundable_storage_fee: 9880 + +task 3 'view-object'. lines 39-39: +Owner: Account Address ( _ ) +Version: 3 +Contents: tto::M1::A {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,0)}}} + +task 4 'view-object'. lines 41-41: +Owner: Account Address ( fake(2,0) ) +Version: 3 +Contents: tto::M1::B {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,1)}}} + +task 5 'run'. lines 43-43: +mutated: object(0,0), object(2,0), object(2,1) +gas summary: computation_cost: 1000000, storage_cost: 3420000, storage_rebate: 3385800, non_refundable_storage_fee: 34200 + +task 6 'view-object'. lines 45-45: +Owner: Account Address ( _ ) +Version: 4 +Contents: tto::M1::A {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,0)}}} + +task 7 'view-object'. lines 47-47: +Owner: Account Address ( _ ) +Version: 4 +Contents: tto::M1::B {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,1)}}} + +task 8 'run'. lines 49-49: +Error: Transaction Effects Status: Move Runtime Abort. Location: sui::transfer::receive_impl (function index 10) at offset 0, Abort Code: 3 +Execution Error: ExecutionError: ExecutionError { inner: ExecutionErrorInner { kind: MoveAbort(MoveLocation { module: ModuleId { address: sui, name: Identifier("transfer") }, function: 10, instruction: 0, function_name: Some("receive_impl") }, 3), source: Some(VMError { major_status: ABORTED, sub_status: Some(3), message: None, exec_state: None, location: Module(ModuleId { address: sui, name: Identifier("transfer") }), indices: [], offsets: [(FunctionDefinitionIndex(10), 0)] }), command: Some(0) } } diff --git a/crates/sui-adapter-transactional-tests/tests/receive_object/basic_receive.move b/crates/sui-adapter-transactional-tests/tests/receive_object/basic_receive.move new file mode 100644 index 0000000000000..a244374e2dc4c --- /dev/null +++ b/crates/sui-adapter-transactional-tests/tests/receive_object/basic_receive.move @@ -0,0 +1,49 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +//# init --addresses tto=0x0 + +//# publish +module tto::M1 { + use sui::object::{Self, UID}; + use sui::tx_context::{Self, TxContext}; + use sui::transfer::{Self, Receiving}; + + struct A has key, store { + id: UID, + } + + struct B has key, store { + id: UID, + } + + public fun start(ctx: &mut TxContext) { + let a = A { id: object::new(ctx) }; + let a_address = object::id_address(&a); + let b = B { id: object::new(ctx) }; + transfer::public_transfer(a, tx_context::sender(ctx)); + transfer::public_transfer(b, a_address); + } + + public entry fun receiver(parent: &mut A, x: Receiving) { + let b = transfer::receive(&mut parent.id, x); + transfer::public_transfer(b, @tto); + } + + public fun invalid_call_immut_ref(_parent: &mut A, _x: &Receiving) { } + public fun invalid_call_mut_ref(_parent: &mut A, _x: &mut Receiving) { } +} + +//# run tto::M1::start + +//# view-object 2,0 + +//# view-object 2,1 + +//# run tto::M1::receiver --args object(2,0) receiving(2,1) + +//# view-object 2,0 + +//# view-object 2,1 + +//# run tto::M1::receiver --args object(2,0) receiving(2,1)@3 diff --git a/crates/sui-adapter-transactional-tests/tests/receive_object/drop_receiving.exp b/crates/sui-adapter-transactional-tests/tests/receive_object/drop_receiving.exp new file mode 100644 index 0000000000000..3e2ba9f3edb78 --- /dev/null +++ b/crates/sui-adapter-transactional-tests/tests/receive_object/drop_receiving.exp @@ -0,0 +1,23 @@ +processed 6 tasks + +task 1 'publish'. lines 6-36: +created: object(1,0) +mutated: object(0,0) +gas summary: computation_cost: 1000000, storage_cost: 7144000, storage_rebate: 0, non_refundable_storage_fee: 0 + +task 2 'run'. lines 38-38: +created: object(2,0), object(2,1) +mutated: object(0,0) +gas summary: computation_cost: 1000000, storage_cost: 3420000, storage_rebate: 978120, non_refundable_storage_fee: 9880 + +task 3 'programmable'. lines 40-43: +mutated: object(0,0), object(2,0), object(2,1) +gas summary: computation_cost: 1000000, storage_cost: 3420000, storage_rebate: 3385800, non_refundable_storage_fee: 34200 + +task 4 'programmable'. lines 44-48: +mutated: object(0,0), object(2,0) +gas summary: computation_cost: 1000000, storage_cost: 2204000, storage_rebate: 2181960, non_refundable_storage_fee: 22040 + +task 5 'programmable'. lines 49-50: +mutated: object(0,0), object(2,0) +gas summary: computation_cost: 1000000, storage_cost: 2204000, storage_rebate: 2181960, non_refundable_storage_fee: 22040 diff --git a/crates/sui-adapter-transactional-tests/tests/receive_object/drop_receiving.move b/crates/sui-adapter-transactional-tests/tests/receive_object/drop_receiving.move new file mode 100644 index 0000000000000..d9dd3af6deab3 --- /dev/null +++ b/crates/sui-adapter-transactional-tests/tests/receive_object/drop_receiving.move @@ -0,0 +1,50 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +//# init --addresses tto=0x0 + +//# publish +module tto::M1 { + use sui::object::{Self, UID}; + use sui::tx_context::{Self, TxContext}; + use sui::transfer::{Self, Receiving}; + + struct A has key, store { + id: UID, + } + + struct B has key, store { + id: UID, + } + + public fun start(ctx: &mut TxContext) { + let a = A { id: object::new(ctx) }; + let a_address = object::id_address(&a); + let b = B { id: object::new(ctx) }; + transfer::public_transfer(a, tx_context::sender(ctx)); + transfer::public_transfer(b, a_address); + } + + public entry fun send_back(parent: &mut A, x: Receiving) { + let b = transfer::receive(&mut parent.id, x); + let parent_address = object::id_address(parent); + transfer::public_transfer(b, parent_address); + } + + public entry fun nop(_parent: &mut A) { } + public entry fun nop_with_receiver(_parent: &mut A, _x: Receiving) { } +} + +//# run tto::M1::start + +//# programmable --inputs object(2,0) receiving(2,1) +//> tto::M1::send_back(Input(0), Input(1)) + +// Include the receiving argument, but don't use it at the PTB level +//# programmable --inputs object(2,0) receiving(2,1) +//> tto::M1::nop(Input(0)) + +// Include the receiving argument, but don't use it at the Move level. The +// receiving object should not be mutated by this. +//# programmable --inputs object(2,0) receiving(2,1) +//> tto::M1::nop_with_receiver(Input(0), Input(1)) diff --git a/crates/sui-adapter-transactional-tests/tests/receive_object/duplicate_receive_argument.exp b/crates/sui-adapter-transactional-tests/tests/receive_object/duplicate_receive_argument.exp new file mode 100644 index 0000000000000..9f37f4a8a751a --- /dev/null +++ b/crates/sui-adapter-transactional-tests/tests/receive_object/duplicate_receive_argument.exp @@ -0,0 +1,21 @@ +processed 6 tasks + +task 1 'publish'. lines 6-33: +created: object(1,0) +mutated: object(0,0) +gas summary: computation_cost: 1000000, storage_cost: 6756400, storage_rebate: 0, non_refundable_storage_fee: 0 + +task 2 'run'. lines 35-35: +created: object(2,0), object(2,1) +mutated: object(0,0) +gas summary: computation_cost: 1000000, storage_cost: 3420000, storage_rebate: 978120, non_refundable_storage_fee: 9880 + +task 3 'programmable'. lines 37-40: +mutated: object(0,0), object(2,0), object(2,1) +gas summary: computation_cost: 1000000, storage_cost: 3420000, storage_rebate: 3385800, non_refundable_storage_fee: 34200 + +task 4 'programmable'. lines 41-44: +Error: Error checking transaction input objects: DuplicateObjectRefInput + +task 5 'programmable'. lines 45-46: +Error: Error checking transaction input objects: IncorrectUserSignature { error: "Object 0x6b7c4000d7f46a0cf15aeab3134157cbdecc6d25aa828d74c8bd36f91314a17e is owned by account address 0xacc3895100e62eddf620872c5c762488afac12c37c4e1141ca0a88052738a9e1, but given owner/signer address is 0xfccc9a421bbb13c1a66a1aa98f0ad75029ede94857779c6915b44f94068b921e" } diff --git a/crates/sui-adapter-transactional-tests/tests/receive_object/duplicate_receive_argument.move b/crates/sui-adapter-transactional-tests/tests/receive_object/duplicate_receive_argument.move new file mode 100644 index 0000000000000..e12bbb883a184 --- /dev/null +++ b/crates/sui-adapter-transactional-tests/tests/receive_object/duplicate_receive_argument.move @@ -0,0 +1,46 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +//# init --addresses tto=0x0 + +//# publish +module tto::M1 { + use sui::object::{Self, UID}; + use sui::tx_context::{Self, TxContext}; + use sui::transfer::{Self, Receiving}; + + struct A has key, store { + id: UID, + } + + struct B has key, store { + id: UID, + } + + public fun start(ctx: &mut TxContext) { + let a = A { id: object::new(ctx) }; + let a_address = object::id_address(&a); + let b = B { id: object::new(ctx) }; + transfer::public_transfer(a, tx_context::sender(ctx)); + transfer::public_transfer(b, a_address); + } + + public entry fun send_back(parent: &mut A, x: Receiving) { + let b = transfer::receive(&mut parent.id, x); + let parent_address = object::id_address(parent); + transfer::public_transfer(b, parent_address); + } +} + +//# run tto::M1::start + +//# programmable --inputs object(2,0) receiving(2,1) +//> tto::M1::send_back(Input(0), Input(1)) + +// Duplicate object ref in input +//# programmable --inputs object(2,0) receiving(2,1) receiving(2,1) +//> tto::M1::send_back(Input(0), Input(1)) + +// Invalid signature for the receiving object since we try to use it as a normal input +//# programmable --inputs object(2,1) receiving(2,1) +//> tto::M1::send_back(Input(0), Input(1)) diff --git a/crates/sui-adapter-transactional-tests/tests/receive_object/pass_through_then_receive.exp b/crates/sui-adapter-transactional-tests/tests/receive_object/pass_through_then_receive.exp new file mode 100644 index 0000000000000..eb2cdf49a7d2d --- /dev/null +++ b/crates/sui-adapter-transactional-tests/tests/receive_object/pass_through_then_receive.exp @@ -0,0 +1,25 @@ +processed 6 tasks + +task 1 'publish'. lines 6-34: +created: object(1,0) +mutated: object(0,0) +gas summary: computation_cost: 1000000, storage_cost: 7182000, storage_rebate: 0, non_refundable_storage_fee: 0 + +task 2 'run'. lines 36-36: +created: object(2,0), object(2,1) +mutated: object(0,0) +gas summary: computation_cost: 1000000, storage_cost: 3420000, storage_rebate: 978120, non_refundable_storage_fee: 9880 + +task 3 'view-object'. lines 38-38: +Owner: Account Address ( _ ) +Version: 3 +Contents: tto::M1::A {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,0)}}} + +task 4 'view-object'. lines 40-40: +Owner: Account Address ( fake(2,0) ) +Version: 3 +Contents: tto::M1::B {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,1)}}} + +task 5 'programmable'. lines 42-44: +mutated: object(0,0), object(2,0), object(2,1) +gas summary: computation_cost: 1000000, storage_cost: 3420000, storage_rebate: 3385800, non_refundable_storage_fee: 34200 diff --git a/crates/sui-adapter-transactional-tests/tests/receive_object/pass_through_then_receive.move b/crates/sui-adapter-transactional-tests/tests/receive_object/pass_through_then_receive.move new file mode 100644 index 0000000000000..a9f93760dc92a --- /dev/null +++ b/crates/sui-adapter-transactional-tests/tests/receive_object/pass_through_then_receive.move @@ -0,0 +1,44 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +//# init --addresses tto=0x0 + +//# publish +module tto::M1 { + use sui::object::{Self, UID}; + use sui::tx_context::{Self, TxContext}; + use sui::transfer::{Self, Receiving}; + + struct A has key, store { + id: UID, + } + + struct B has key, store { + id: UID, + } + + public fun start(ctx: &mut TxContext) { + let a = A { id: object::new(ctx) }; + let a_address = object::id_address(&a); + let b = B { id: object::new(ctx) }; + transfer::public_transfer(a, tx_context::sender(ctx)); + transfer::public_transfer(b, a_address); + } + + public entry fun pass_through(x: Receiving): Receiving { x } + + public entry fun receiver(parent: &mut A, x: Receiving) { + let b = transfer::receive(&mut parent.id, x); + transfer::public_transfer(b, @tto); + } +} + +//# run tto::M1::start + +//# view-object 2,0 + +//# view-object 2,1 + +//# programmable --inputs object(2,0) receiving(2,1) +//> 0: tto::M1::pass_through(Input(1)); +//> tto::M1::receiver(Input(0), Result(0)); diff --git a/crates/sui-adapter-transactional-tests/tests/receive_object/receive_and_deleted.exp b/crates/sui-adapter-transactional-tests/tests/receive_object/receive_and_deleted.exp new file mode 100644 index 0000000000000..a716c73766b7f --- /dev/null +++ b/crates/sui-adapter-transactional-tests/tests/receive_object/receive_and_deleted.exp @@ -0,0 +1,38 @@ +processed 9 tasks + +task 1 'publish'. lines 6-32: +created: object(1,0) +mutated: object(0,0) +gas summary: computation_cost: 1000000, storage_cost: 6726000, storage_rebate: 0, non_refundable_storage_fee: 0 + +task 2 'run'. lines 34-34: +created: object(2,0), object(2,1) +mutated: object(0,0) +gas summary: computation_cost: 1000000, storage_cost: 3420000, storage_rebate: 978120, non_refundable_storage_fee: 9880 + +task 3 'view-object'. lines 36-36: +Owner: Account Address ( _ ) +Version: 3 +Contents: tto::M1::A {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,0)}}} + +task 4 'view-object'. lines 38-38: +Owner: Account Address ( fake(2,0) ) +Version: 3 +Contents: tto::M1::B {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,1)}}} + +task 5 'run'. lines 40-40: +mutated: object(0,0), object(2,0) +deleted: object(2,1) +gas summary: computation_cost: 1000000, storage_cost: 2204000, storage_rebate: 3385800, non_refundable_storage_fee: 34200 + +task 6 'view-object'. lines 42-42: +Owner: Account Address ( _ ) +Version: 4 +Contents: tto::M1::A {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,0)}}} + +task 7 'view-object'. lines 44-44: +No object at id 2,1 + +task 8 'run'. lines 46-46: +Error: Transaction Effects Status: Move Runtime Abort. Location: sui::transfer::receive_impl (function index 10) at offset 0, Abort Code: 3 +Execution Error: ExecutionError: ExecutionError { inner: ExecutionErrorInner { kind: MoveAbort(MoveLocation { module: ModuleId { address: sui, name: Identifier("transfer") }, function: 10, instruction: 0, function_name: Some("receive_impl") }, 3), source: Some(VMError { major_status: ABORTED, sub_status: Some(3), message: None, exec_state: None, location: Module(ModuleId { address: sui, name: Identifier("transfer") }), indices: [], offsets: [(FunctionDefinitionIndex(10), 0)] }), command: Some(0) } } diff --git a/crates/sui-adapter-transactional-tests/tests/receive_object/receive_and_deleted.move b/crates/sui-adapter-transactional-tests/tests/receive_object/receive_and_deleted.move new file mode 100644 index 0000000000000..07ae24d3af15e --- /dev/null +++ b/crates/sui-adapter-transactional-tests/tests/receive_object/receive_and_deleted.move @@ -0,0 +1,46 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +//# init --addresses tto=0x0 + +//# publish +module tto::M1 { + use sui::object::{Self, UID}; + use sui::tx_context::{Self, TxContext}; + use sui::transfer::{Self, Receiving}; + + struct A has key, store { + id: UID, + } + + struct B has key, store { + id: UID, + } + + public fun start(ctx: &mut TxContext) { + let a = A { id: object::new(ctx) }; + let a_address = object::id_address(&a); + let b = B { id: object::new(ctx) }; + transfer::public_transfer(a, tx_context::sender(ctx)); + transfer::public_transfer(b, a_address); + } + + public entry fun deleter(parent: &mut A, x: Receiving) { + let B { id } = transfer::receive(&mut parent.id, x); + object::delete(id); + } +} + +//# run tto::M1::start + +//# view-object 2,0 + +//# view-object 2,1 + +//# run tto::M1::deleter --args object(2,0) receiving(2,1) + +//# view-object 2,0 + +//# view-object 2,1 + +//# run tto::M1::deleter --args object(2,0) receiving(2,1)@3 diff --git a/crates/sui-adapter-transactional-tests/tests/receive_object/receive_and_send_back.exp b/crates/sui-adapter-transactional-tests/tests/receive_object/receive_and_send_back.exp new file mode 100644 index 0000000000000..0874e1ae3a1f7 --- /dev/null +++ b/crates/sui-adapter-transactional-tests/tests/receive_object/receive_and_send_back.exp @@ -0,0 +1,39 @@ +processed 9 tasks + +task 1 'publish'. lines 6-33: +created: object(1,0) +mutated: object(0,0) +gas summary: computation_cost: 1000000, storage_cost: 6756400, storage_rebate: 0, non_refundable_storage_fee: 0 + +task 2 'run'. lines 35-35: +created: object(2,0), object(2,1) +mutated: object(0,0) +gas summary: computation_cost: 1000000, storage_cost: 3420000, storage_rebate: 978120, non_refundable_storage_fee: 9880 + +task 3 'view-object'. lines 37-37: +Owner: Account Address ( _ ) +Version: 3 +Contents: tto::M1::A {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,0)}}} + +task 4 'view-object'. lines 39-39: +Owner: Account Address ( fake(2,0) ) +Version: 3 +Contents: tto::M1::B {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,1)}}} + +task 5 'run'. lines 41-41: +mutated: object(0,0), object(2,0), object(2,1) +gas summary: computation_cost: 1000000, storage_cost: 3420000, storage_rebate: 3385800, non_refundable_storage_fee: 34200 + +task 6 'view-object'. lines 43-43: +Owner: Account Address ( _ ) +Version: 4 +Contents: tto::M1::A {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,0)}}} + +task 7 'view-object'. lines 45-45: +Owner: Account Address ( fake(2,0) ) +Version: 4 +Contents: tto::M1::B {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,1)}}} + +task 8 'run'. lines 47-47: +Error: Transaction Effects Status: Move Runtime Abort. Location: sui::transfer::receive_impl (function index 10) at offset 0, Abort Code: 3 +Execution Error: ExecutionError: ExecutionError { inner: ExecutionErrorInner { kind: MoveAbort(MoveLocation { module: ModuleId { address: sui, name: Identifier("transfer") }, function: 10, instruction: 0, function_name: Some("receive_impl") }, 3), source: Some(VMError { major_status: ABORTED, sub_status: Some(3), message: None, exec_state: None, location: Module(ModuleId { address: sui, name: Identifier("transfer") }), indices: [], offsets: [(FunctionDefinitionIndex(10), 0)] }), command: Some(0) } } diff --git a/crates/sui-adapter-transactional-tests/tests/receive_object/receive_and_send_back.move b/crates/sui-adapter-transactional-tests/tests/receive_object/receive_and_send_back.move new file mode 100644 index 0000000000000..e71c7a4e76de5 --- /dev/null +++ b/crates/sui-adapter-transactional-tests/tests/receive_object/receive_and_send_back.move @@ -0,0 +1,47 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +//# init --addresses tto=0x0 + +//# publish +module tto::M1 { + use sui::object::{Self, UID}; + use sui::tx_context::{Self, TxContext}; + use sui::transfer::{Self, Receiving}; + + struct A has key, store { + id: UID, + } + + struct B has key, store { + id: UID, + } + + public fun start(ctx: &mut TxContext) { + let a = A { id: object::new(ctx) }; + let a_address = object::id_address(&a); + let b = B { id: object::new(ctx) }; + transfer::public_transfer(a, tx_context::sender(ctx)); + transfer::public_transfer(b, a_address); + } + + public entry fun send_back(parent: &mut A, x: Receiving) { + let b = transfer::receive(&mut parent.id, x); + let parent_address = object::id_address(parent); + transfer::public_transfer(b, parent_address); + } +} + +//# run tto::M1::start + +//# view-object 2,0 + +//# view-object 2,1 + +//# run tto::M1::send_back --args object(2,0) receiving(2,1) + +//# view-object 2,0 + +//# view-object 2,1 + +//# run tto::M1::send_back --args object(2,0) receiving(2,1)@3 diff --git a/crates/sui-adapter-transactional-tests/tests/receive_object/receive_and_wrap.exp b/crates/sui-adapter-transactional-tests/tests/receive_object/receive_and_wrap.exp new file mode 100644 index 0000000000000..5b775ca732474 --- /dev/null +++ b/crates/sui-adapter-transactional-tests/tests/receive_object/receive_and_wrap.exp @@ -0,0 +1,39 @@ +processed 9 tasks + +task 1 'publish'. lines 6-44: +created: object(1,0) +mutated: object(0,0) +gas summary: computation_cost: 1000000, storage_cost: 8238400, storage_rebate: 0, non_refundable_storage_fee: 0 + +task 2 'run'. lines 46-46: +created: object(2,0), object(2,1) +mutated: object(0,0) +gas summary: computation_cost: 1000000, storage_cost: 3420000, storage_rebate: 978120, non_refundable_storage_fee: 9880 + +task 3 'view-object'. lines 48-48: +Owner: Account Address ( _ ) +Version: 3 +Contents: tto::M1::A {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,0)}}} + +task 4 'view-object'. lines 50-50: +Owner: Account Address ( fake(2,0) ) +Version: 3 +Contents: tto::M1::B {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,1)}}} + +task 5 'run'. lines 52-52: +created: object(5,0) +mutated: object(0,0), object(2,0) +wrapped: object(2,1) +gas summary: computation_cost: 1000000, storage_cost: 3708800, storage_rebate: 3385800, non_refundable_storage_fee: 34200 + +task 6 'view-object'. lines 54-54: +Owner: Account Address ( _ ) +Version: 4 +Contents: tto::M1::A {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,0)}}} + +task 7 'view-object'. lines 56-56: +No object at id 2,1 + +task 8 'run'. lines 58-58: +Error: Transaction Effects Status: Move Runtime Abort. Location: sui::transfer::receive_impl (function index 10) at offset 0, Abort Code: 3 +Execution Error: ExecutionError: ExecutionError { inner: ExecutionErrorInner { kind: MoveAbort(MoveLocation { module: ModuleId { address: sui, name: Identifier("transfer") }, function: 10, instruction: 0, function_name: Some("receive_impl") }, 3), source: Some(VMError { major_status: ABORTED, sub_status: Some(3), message: None, exec_state: None, location: Module(ModuleId { address: sui, name: Identifier("transfer") }), indices: [], offsets: [(FunctionDefinitionIndex(10), 0)] }), command: Some(0) } } diff --git a/crates/sui-adapter-transactional-tests/tests/receive_object/receive_and_wrap.move b/crates/sui-adapter-transactional-tests/tests/receive_object/receive_and_wrap.move new file mode 100644 index 0000000000000..b377180e6ccd4 --- /dev/null +++ b/crates/sui-adapter-transactional-tests/tests/receive_object/receive_and_wrap.move @@ -0,0 +1,59 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +//# init --addresses tto=0x0 + +//# publish +module tto::M1 { + use sui::object::{Self, UID}; + use sui::tx_context::{Self, TxContext}; + use sui::transfer::{Self, Receiving}; + + struct Wrapper has key, store { + id: UID, + elem: B + } + + struct A has key, store { + id: UID, + } + + struct B has key, store { + id: UID, + } + + public fun start(ctx: &mut TxContext) { + let a = A { id: object::new(ctx) }; + let a_address = object::id_address(&a); + let b = B { id: object::new(ctx) }; + transfer::public_transfer(a, tx_context::sender(ctx)); + transfer::public_transfer(b, a_address); + } + + public entry fun wrapper(parent: &mut A, x: Receiving, ctx: &mut TxContext) { + let b = transfer::receive(&mut parent.id, x); + let wrapper = Wrapper { + id: object::new(ctx), + elem: b + }; + transfer::public_transfer(wrapper, @tto); + } + + public fun invalid_call_immut_ref(_parent: &mut A, _x: &Receiving) { } + public fun invalid_call_mut_ref(_parent: &mut A, _x: &mut Receiving) { } +} + +//# run tto::M1::start + +//# view-object 2,0 + +//# view-object 2,1 + +//# run tto::M1::wrapper --args object(2,0) receiving(2,1) + +//# view-object 2,0 + +//# view-object 2,1 + +//# run tto::M1::wrapper --args object(2,0) receiving(2,1)@3 + diff --git a/crates/sui-adapter-transactional-tests/tests/receive_object/receive_by_ref.exp b/crates/sui-adapter-transactional-tests/tests/receive_object/receive_by_ref.exp new file mode 100644 index 0000000000000..caabc4cbfa399 --- /dev/null +++ b/crates/sui-adapter-transactional-tests/tests/receive_object/receive_by_ref.exp @@ -0,0 +1,73 @@ +processed 18 tasks + +task 1 'publish'. lines 6-41: +created: object(1,0) +mutated: object(0,0) +gas summary: computation_cost: 1000000, storage_cost: 10115600, storage_rebate: 0, non_refundable_storage_fee: 0 + +task 2 'run'. lines 43-43: +created: object(2,0), object(2,1) +mutated: object(0,0) +gas summary: computation_cost: 1000000, storage_cost: 3420000, storage_rebate: 978120, non_refundable_storage_fee: 9880 + +task 3 'view-object'. lines 45-45: +Owner: Account Address ( _ ) +Version: 3 +Contents: tto::M1::A {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,0)}}} + +task 4 'view-object'. lines 47-47: +Owner: Account Address ( fake(2,0) ) +Version: 3 +Contents: tto::M1::B {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,1)}}} + +task 5 'run'. lines 49-49: +mutated: object(0,0), object(2,0) +gas summary: computation_cost: 1000000, storage_cost: 2204000, storage_rebate: 2181960, non_refundable_storage_fee: 22040 + +task 6 'run'. lines 51-51: +mutated: object(0,0), object(2,0) +gas summary: computation_cost: 1000000, storage_cost: 2204000, storage_rebate: 2181960, non_refundable_storage_fee: 22040 + +task 7 'run'. lines 53-53: +Error: Transaction Effects Status: Invalid public Move function signature. Unsupported return type for return value 0 +Execution Error: ExecutionError: ExecutionError { inner: ExecutionErrorInner { kind: InvalidPublicFunctionReturnType { idx: 0 }, source: None, command: Some(0) } } + +task 8 'run'. lines 55-55: +Error: Transaction Effects Status: Invalid public Move function signature. Unsupported return type for return value 0 +Execution Error: ExecutionError: ExecutionError { inner: ExecutionErrorInner { kind: InvalidPublicFunctionReturnType { idx: 0 }, source: None, command: Some(0) } } + +task 9 'programmable'. lines 57-58: +mutated: object(0,0) +gas summary: computation_cost: 1000000, storage_cost: 988000, storage_rebate: 978120, non_refundable_storage_fee: 9880 + +task 10 'programmable'. lines 60-61: +Error: Transaction Effects Status: Invalid command argument at 1. Invalid usage of value. Mutably borrowed values require unique usage. Immutably borrowed values cannot be taken or borrowed mutably. Taken values cannot be used again. +Execution Error: ExecutionError: ExecutionError { inner: ExecutionErrorInner { kind: CommandArgumentError { arg_idx: 1, kind: InvalidValueUsage }, source: None, command: Some(0) } } + +task 11 'programmable'. lines 63-64: +Error: Transaction Effects Status: Invalid command argument at 1. Invalid usage of value. Mutably borrowed values require unique usage. Immutably borrowed values cannot be taken or borrowed mutably. Taken values cannot be used again. +Execution Error: ExecutionError: ExecutionError { inner: ExecutionErrorInner { kind: CommandArgumentError { arg_idx: 1, kind: InvalidValueUsage }, source: None, command: Some(0) } } + +task 12 'programmable'. lines 66-67: +Error: Transaction Effects Status: Invalid command argument at 1. Invalid usage of value. Mutably borrowed values require unique usage. Immutably borrowed values cannot be taken or borrowed mutably. Taken values cannot be used again. +Execution Error: ExecutionError: ExecutionError { inner: ExecutionErrorInner { kind: CommandArgumentError { arg_idx: 1, kind: InvalidValueUsage }, source: None, command: Some(0) } } + +task 13 'programmable'. lines 69-70: +Error: Transaction Effects Status: Invalid command argument at 1. Invalid usage of value. Mutably borrowed values require unique usage. Immutably borrowed values cannot be taken or borrowed mutably. Taken values cannot be used again. +Execution Error: ExecutionError: ExecutionError { inner: ExecutionErrorInner { kind: CommandArgumentError { arg_idx: 1, kind: InvalidValueUsage }, source: None, command: Some(0) } } + +task 14 'programmable'. lines 72-73: +Error: Transaction Effects Status: Invalid command argument at 1. Invalid usage of value. Mutably borrowed values require unique usage. Immutably borrowed values cannot be taken or borrowed mutably. Taken values cannot be used again. +Execution Error: ExecutionError: ExecutionError { inner: ExecutionErrorInner { kind: CommandArgumentError { arg_idx: 1, kind: InvalidValueUsage }, source: None, command: Some(0) } } + +task 15 'programmable'. lines 75-76: +Error: Transaction Effects Status: Invalid command argument at 1. Invalid usage of value. Mutably borrowed values require unique usage. Immutably borrowed values cannot be taken or borrowed mutably. Taken values cannot be used again. +Execution Error: ExecutionError: ExecutionError { inner: ExecutionErrorInner { kind: CommandArgumentError { arg_idx: 1, kind: InvalidValueUsage }, source: None, command: Some(0) } } + +task 16 'programmable'. lines 78-79: +Error: Transaction Effects Status: Invalid command argument at 1. Invalid usage of value. Mutably borrowed values require unique usage. Immutably borrowed values cannot be taken or borrowed mutably. Taken values cannot be used again. +Execution Error: ExecutionError: ExecutionError { inner: ExecutionErrorInner { kind: CommandArgumentError { arg_idx: 1, kind: InvalidValueUsage }, source: None, command: Some(0) } } + +task 17 'programmable'. lines 81-82: +Error: Transaction Effects Status: Invalid command argument at 1. Invalid usage of value. Mutably borrowed values require unique usage. Immutably borrowed values cannot be taken or borrowed mutably. Taken values cannot be used again. +Execution Error: ExecutionError: ExecutionError { inner: ExecutionErrorInner { kind: CommandArgumentError { arg_idx: 1, kind: InvalidValueUsage }, source: None, command: Some(0) } } diff --git a/crates/sui-adapter-transactional-tests/tests/receive_object/receive_by_ref.move b/crates/sui-adapter-transactional-tests/tests/receive_object/receive_by_ref.move new file mode 100644 index 0000000000000..c9cf2b375c08e --- /dev/null +++ b/crates/sui-adapter-transactional-tests/tests/receive_object/receive_by_ref.move @@ -0,0 +1,82 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +//# init --addresses tto=0x0 + +//# publish +module tto::M1 { + use sui::object::{Self, UID}; + use sui::tx_context::{Self, TxContext}; + use sui::transfer::{Self, Receiving}; + + struct A has key, store { + id: UID, + } + + struct B has key, store { + id: UID, + } + + public fun start(ctx: &mut TxContext) { + let a = A { id: object::new(ctx) }; + let a_address = object::id_address(&a); + let b = B { id: object::new(ctx) }; + transfer::public_transfer(a, tx_context::sender(ctx)); + transfer::public_transfer(b, a_address); + } + + public fun invalid_call_immut_ref(_parent: &mut A, _x: &Receiving) { } + public fun invalid_call_mut_ref(_parent: &mut A, _x: &mut Receiving) { } + public fun invalid_call_mut_ref_ret(_parent: &mut A, x: &mut Receiving): &mut Receiving { x } + public fun invalid_call_mut_ref_immut_ret(_parent: &mut A, x: &mut Receiving): &Receiving { x } + public fun immut_immut_ref(_x: &Receiving, _y: &Receiving) { } + public fun immut_mut_ref(_x: &Receiving, _y: &mut Receiving) { } + public fun mut_immut_ref(_x: &mut Receiving, _y: &Receiving) { } + public fun mut_mut_ref(_x: &mut Receiving, _y: &mut Receiving) { } + public fun take_mut_ref(_x: Receiving, _y: &mut Receiving) { } + public fun take_immut_ref(_x: Receiving, _y: &Receiving) { } + public fun immut_ref_take(_x: &Receiving, _y: Receiving) { } + public fun mut_ref_take(_x: &mut Receiving, _y: Receiving) { } + public fun double_take(_x: Receiving, _y: Receiving) { } +} + +//# run tto::M1::start + +//# view-object 2,0 + +//# view-object 2,1 + +//# run tto::M1::invalid_call_mut_ref --args object(2,0) receiving(2,1) + +//# run tto::M1::invalid_call_immut_ref --args object(2,0) receiving(2,1) + +//# run tto::M1::invalid_call_mut_ref_ret --args object(2,0) receiving(2,1) + +//# run tto::M1::invalid_call_mut_ref_immut_ret --args object(2,0) receiving(2,1) + +//# programmable --inputs receiving(2,1) +//> tto::M1::immut_immut_ref(Input(0), Input(0)) + +//# programmable --inputs receiving(2,1) +//> tto::M1::immut_mut_ref(Input(0), Input(0)) + +//# programmable --inputs receiving(2,1) +//> tto::M1::mut_immut_ref(Input(0), Input(0)) + +//# programmable --inputs receiving(2,1) +//> tto::M1::mut_mut_ref(Input(0), Input(0)) + +//# programmable --inputs receiving(2,1) +//> tto::M1::take_mut_ref(Input(0), Input(0)) + +//# programmable --inputs receiving(2,1) +//> tto::M1::take_immut_ref(Input(0), Input(0)) + +//# programmable --inputs receiving(2,1) +//> tto::M1::immut_ref_take(Input(0), Input(0)) + +//# programmable --inputs receiving(2,1) +//> tto::M1::mut_ref_take(Input(0), Input(0)) + +//# programmable --inputs receiving(2,1) +//> tto::M1::double_take(Input(0), Input(0)) diff --git a/crates/sui-adapter-transactional-tests/tests/receive_object/receive_by_value_flow_through.exp b/crates/sui-adapter-transactional-tests/tests/receive_object/receive_by_value_flow_through.exp new file mode 100644 index 0000000000000..cfcf70240cd12 --- /dev/null +++ b/crates/sui-adapter-transactional-tests/tests/receive_object/receive_by_value_flow_through.exp @@ -0,0 +1,29 @@ +processed 7 tasks + +task 1 'publish'. lines 6-31: +created: object(1,0) +mutated: object(0,0) +gas summary: computation_cost: 1000000, storage_cost: 6437200, storage_rebate: 0, non_refundable_storage_fee: 0 + +task 2 'run'. lines 33-33: +created: object(2,0), object(2,1) +mutated: object(0,0) +gas summary: computation_cost: 1000000, storage_cost: 3420000, storage_rebate: 978120, non_refundable_storage_fee: 9880 + +task 3 'view-object'. lines 35-35: +Owner: Account Address ( _ ) +Version: 3 +Contents: tto::M1::A {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,0)}}} + +task 4 'view-object'. lines 37-37: +Owner: Account Address ( fake(2,0) ) +Version: 3 +Contents: tto::M1::B {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,1)}}} + +task 5 'run'. lines 39-39: +mutated: object(0,0), object(2,0) +gas summary: computation_cost: 1000000, storage_cost: 2204000, storage_rebate: 2181960, non_refundable_storage_fee: 22040 + +task 6 'run'. lines 41-41: +mutated: object(0,0), object(2,0) +gas summary: computation_cost: 1000000, storage_cost: 2204000, storage_rebate: 2181960, non_refundable_storage_fee: 22040 diff --git a/crates/sui-adapter-transactional-tests/tests/receive_object/receive_by_value_flow_through.move b/crates/sui-adapter-transactional-tests/tests/receive_object/receive_by_value_flow_through.move new file mode 100644 index 0000000000000..960dc0a62c2cc --- /dev/null +++ b/crates/sui-adapter-transactional-tests/tests/receive_object/receive_by_value_flow_through.move @@ -0,0 +1,41 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +//# init --addresses tto=0x0 + +//# publish +module tto::M1 { + use sui::object::{Self, UID}; + use sui::tx_context::{Self, TxContext}; + use sui::transfer::{Self, Receiving}; + + struct A has key, store { + id: UID, + } + + struct B has key, store { + id: UID, + } + + public fun start(ctx: &mut TxContext) { + let a = A { id: object::new(ctx) }; + let a_address = object::id_address(&a); + let b = B { id: object::new(ctx) }; + transfer::public_transfer(a, tx_context::sender(ctx)); + transfer::public_transfer(b, a_address); + } + + // XXX: These are both counted as wrapped which is wrong! + public fun flow(_parent: &mut A, x: Receiving): Receiving { x } + public fun drop(_parent: &mut A, _x: Receiving) { } +} + +//# run tto::M1::start + +//# view-object 2,0 + +//# view-object 2,1 + +//# run tto::M1::flow --args object(2,0) receiving(2,1) + +//# run tto::M1::drop --args object(2,0) receiving(2,1) diff --git a/crates/sui-adapter-transactional-tests/tests/receive_object/receive_dof_and_mutate.exp b/crates/sui-adapter-transactional-tests/tests/receive_object/receive_dof_and_mutate.exp new file mode 100644 index 0000000000000..5c051c5a257ad --- /dev/null +++ b/crates/sui-adapter-transactional-tests/tests/receive_object/receive_dof_and_mutate.exp @@ -0,0 +1,59 @@ +processed 12 tasks + +init: +A: object(0,0) + +task 1 'publish'. lines 6-36: +created: object(1,0) +mutated: object(0,1) +gas summary: computation_cost: 1000000, storage_cost: 7828000, storage_rebate: 0, non_refundable_storage_fee: 0 + +task 2 'run'. lines 38-38: +created: object(2,0), object(2,1), object(2,2), object(2,3) +mutated: object(0,0) +gas summary: computation_cost: 1000000, storage_cost: 7273200, storage_rebate: 0, non_refundable_storage_fee: 0 + +task 3 'view-object'. lines 40-40: +Owner: Object ID: ( fake(2,2) ) +Version: 2 +Contents: sui::dynamic_field::Field, sui::object::ID> {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,0)}}, name: sui::dynamic_object_field::Wrapper {name: 0u64}, value: sui::object::ID {bytes: fake(2,3)}} + +task 4 'view-object'. lines 42-42: +Owner: Account Address ( A ) +Version: 2 +Contents: tto::M1::A {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,1)}}, value: 0u64} + +task 5 'view-object'. lines 44-44: +Owner: Account Address ( fake(2,1) ) +Version: 2 +Contents: tto::M1::A {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,2)}}, value: 0u64} + +task 6 'view-object'. lines 46-46: +Owner: Object ID: ( fake(2,0) ) +Version: 2 +Contents: tto::M1::A {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,3)}}, value: 0u64} + +task 7 'run'. lines 48-48: +created: object(7,0) +mutated: object(0,0), object(2,1), object(2,2) +gas summary: computation_cost: 1000000, storage_cost: 5996400, storage_rebate: 3506184, non_refundable_storage_fee: 35416 + +task 8 'view-object'. lines 50-50: +Owner: Object ID: ( fake(2,2) ) +Version: 2 +Contents: sui::dynamic_field::Field, sui::object::ID> {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,0)}}, name: sui::dynamic_object_field::Wrapper {name: 0u64}, value: sui::object::ID {bytes: fake(2,3)}} + +task 9 'view-object'. lines 52-52: +Owner: Account Address ( A ) +Version: 3 +Contents: tto::M1::A {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,1)}}, value: 0u64} + +task 10 'view-object'. lines 54-54: +Owner: Object ID: ( fake(7,0) ) +Version: 3 +Contents: tto::M1::A {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,2)}}, value: 100u64} + +task 11 'view-object'. lines 56-56: +Owner: Object ID: ( fake(2,0) ) +Version: 2 +Contents: tto::M1::A {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,3)}}, value: 0u64} diff --git a/crates/sui-adapter-transactional-tests/tests/receive_object/receive_dof_and_mutate.move b/crates/sui-adapter-transactional-tests/tests/receive_object/receive_dof_and_mutate.move new file mode 100644 index 0000000000000..55a023d59c4cc --- /dev/null +++ b/crates/sui-adapter-transactional-tests/tests/receive_object/receive_dof_and_mutate.move @@ -0,0 +1,56 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +//# init --addresses tto=0x0 --accounts A + +//# publish +module tto::M1 { + use sui::object::{Self, UID}; + use sui::tx_context::{Self, TxContext}; + use sui::transfer::{Self, Receiving}; + use sui::dynamic_object_field as dof; + + const KEY: u64 = 0; + + struct A has key, store { + id: UID, + value: u64, + } + + public fun start(ctx: &mut TxContext) { + let a = A { id: object::new(ctx), value: 0 }; + let a_address = object::id_address(&a); + let b = A { id: object::new(ctx), value: 0 }; + dof::add(&mut b.id, KEY, A { id: object::new(ctx), value: 0 }); + transfer::public_transfer(a, tx_context::sender(ctx)); + transfer::public_transfer(b, a_address); + } + + public entry fun receive(parent: &mut A, x: Receiving) { + let b = transfer::receive(&mut parent.id, x); + dof::add(&mut parent.id, KEY, b); + let _: &A = dof::borrow(&parent.id, KEY); + let x: &mut A = dof::borrow_mut(&mut parent.id, KEY); + x.value = 100; + } +} + +//# run tto::M1::start --sender A + +//# view-object 2,0 + +//# view-object 2,1 + +//# view-object 2,2 + +//# view-object 2,3 + +//# run tto::M1::receive --args object(2,1) receiving(2,2) --sender A + +//# view-object 2,0 + +//# view-object 2,1 + +//# view-object 2,2 + +//# view-object 2,3 diff --git a/crates/sui-adapter-transactional-tests/tests/receive_object/receive_invalid_param_ty.exp b/crates/sui-adapter-transactional-tests/tests/receive_object/receive_invalid_param_ty.exp new file mode 100644 index 0000000000000..3de80d6774c85 --- /dev/null +++ b/crates/sui-adapter-transactional-tests/tests/receive_object/receive_invalid_param_ty.exp @@ -0,0 +1,60 @@ +processed 15 tasks + +task 1 'publish'. lines 6-43: +created: object(1,0) +mutated: object(0,0) +gas summary: computation_cost: 1000000, storage_cost: 8519600, storage_rebate: 0, non_refundable_storage_fee: 0 + +task 2 'run'. lines 45-45: +created: object(2,0), object(2,1) +mutated: object(0,0) +gas summary: computation_cost: 1000000, storage_cost: 3420000, storage_rebate: 978120, non_refundable_storage_fee: 9880 + +task 3 'view-object'. lines 47-47: +Owner: Account Address ( _ ) +Version: 3 +Contents: tto::M1::A {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,0)}}} + +task 4 'view-object'. lines 49-49: +Owner: Account Address ( fake(2,0) ) +Version: 3 +Contents: tto::M1::B {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,1)}}} + +task 5 'run'. lines 51-51: +Error: Transaction Effects Status: Invalid command argument at 0. The type of the value does not match the expected type +Execution Error: ExecutionError: ExecutionError { inner: ExecutionErrorInner { kind: CommandArgumentError { arg_idx: 0, kind: TypeMismatch }, source: None, command: Some(0) } } + +task 6 'run'. lines 53-53: +Error: Transaction Effects Status: Invalid command argument at 0. The type of the value does not match the expected type +Execution Error: ExecutionError: ExecutionError { inner: ExecutionErrorInner { kind: CommandArgumentError { arg_idx: 0, kind: TypeMismatch }, source: None, command: Some(0) } } + +task 7 'run'. lines 55-55: +Error: Transaction Effects Status: Invalid command argument at 0. The type of the value does not match the expected type +Execution Error: ExecutionError: ExecutionError { inner: ExecutionErrorInner { kind: CommandArgumentError { arg_idx: 0, kind: TypeMismatch }, source: None, command: Some(0) } } + +task 8 'run'. lines 57-57: +Error: Transaction Effects Status: Invalid command argument at 0. The type of the value does not match the expected type +Execution Error: ExecutionError: ExecutionError { inner: ExecutionErrorInner { kind: CommandArgumentError { arg_idx: 0, kind: TypeMismatch }, source: None, command: Some(0) } } + +task 9 'run'. lines 59-59: +Error: Transaction Effects Status: Invalid command argument at 0. The type of the value does not match the expected type +Execution Error: ExecutionError: ExecutionError { inner: ExecutionErrorInner { kind: CommandArgumentError { arg_idx: 0, kind: TypeMismatch }, source: None, command: Some(0) } } + +task 10 'run'. lines 61-61: +Error: Error checking transaction input objects: IncorrectUserSignature { error: "Object 0xd38c9db0c1935b185ec59bacb9e6ce2e736579cedabb791fce9a8165cef6c319 is owned by account address 0xa0027b0c13d66e2958feb9c56af104ea20ddfcb46a3ea93b9ba5d3c2d9b94d42, but given owner/signer address is 0xfccc9a421bbb13c1a66a1aa98f0ad75029ede94857779c6915b44f94068b921e" } + +task 11 'run'. lines 63-63: +Error: Transaction Effects Status: Invalid command argument at 0. The type of the value does not match the expected type +Execution Error: ExecutionError: ExecutionError { inner: ExecutionErrorInner { kind: CommandArgumentError { arg_idx: 0, kind: TypeMismatch }, source: None, command: Some(0) } } + +task 12 'run'. lines 65-65: +mutated: object(0,0) +gas summary: computation_cost: 1000000, storage_cost: 988000, storage_rebate: 978120, non_refundable_storage_fee: 9880 + +task 13 'run'. lines 67-67: +Error: Transaction Effects Status: Invalid command argument at 0. The argument cannot be instantiated from raw bytes +Execution Error: ExecutionError: ExecutionError { inner: ExecutionErrorInner { kind: CommandArgumentError { arg_idx: 0, kind: InvalidUsageOfPureArg }, source: Some("Non-primitive argument at index 0. If it is an object, it must be populated by an object"), command: Some(0) } } + +task 14 'run'. lines 69-69: +Error: Transaction Effects Status: Invalid command argument at 0. The argument cannot be instantiated from raw bytes +Execution Error: ExecutionError: ExecutionError { inner: ExecutionErrorInner { kind: CommandArgumentError { arg_idx: 0, kind: InvalidUsageOfPureArg }, source: Some("Non-primitive argument at index 0. If it is an object, it must be populated by an object"), command: Some(0) } } diff --git a/crates/sui-adapter-transactional-tests/tests/receive_object/receive_invalid_param_ty.move b/crates/sui-adapter-transactional-tests/tests/receive_object/receive_invalid_param_ty.move new file mode 100644 index 0000000000000..9c93c35ff6c4f --- /dev/null +++ b/crates/sui-adapter-transactional-tests/tests/receive_object/receive_invalid_param_ty.move @@ -0,0 +1,69 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +//# init --addresses tto=0x0 + +//# publish +module tto::M1 { + use sui::object::{Self, UID, ID}; + use sui::tx_context::{Self, TxContext}; + use sui::transfer::{Self, Receiving}; + + struct A has key, store { + id: UID, + } + + struct B has key, store { + id: UID, + } + + struct Fake has drop { } + + struct FakeSameLayout has drop { + id: ID, + version: u64, + } + + public fun start(ctx: &mut TxContext) { + let a = A { id: object::new(ctx) }; + let a_address = object::id_address(&a); + let b = B { id: object::new(ctx) }; + transfer::public_transfer(a, tx_context::sender(ctx)); + transfer::public_transfer(b, a_address); + } + + public entry fun receiver(_x: u64) { } + public fun receiver2(_x: Fake) { } + public fun receiver3(_x: &Fake) { } + + public fun receiver4(_x: FakeSameLayout) { } + public fun receiver5(_x: &FakeSameLayout) { } + + public fun receiver6(_x: Receiving) { } +} + +//# run tto::M1::start + +//# view-object 2,0 + +//# view-object 2,1 + +//# run tto::M1::receiver --args receiving(2,1) + +//# run tto::M1::receiver2 --args receiving(2,1) + +//# run tto::M1::receiver3 --args receiving(2,1) + +//# run tto::M1::receiver4 --args receiving(2,1) + +//# run tto::M1::receiver5 --args receiving(2,1) + +//# run tto::M1::receiver6 --args object(2,1) + +//# run tto::M1::receiver6 --args object(2,0) + +//# run tto::M1::receiver6 --args receiving(2,0) + +//# run tto::M1::receiver6 --args 0 + +//# run tto::M1::receiver6 --args vector[0,0,0,0,0,0,0,0,0,0] diff --git a/crates/sui-adapter-transactional-tests/tests/receive_object/receive_invalid_type.exp b/crates/sui-adapter-transactional-tests/tests/receive_object/receive_invalid_type.exp new file mode 100644 index 0000000000000..4f4a7306b4ad7 --- /dev/null +++ b/crates/sui-adapter-transactional-tests/tests/receive_object/receive_invalid_type.exp @@ -0,0 +1,25 @@ +processed 6 tasks + +task 1 'publish'. lines 6-32: +created: object(1,0) +mutated: object(0,0) +gas summary: computation_cost: 1000000, storage_cost: 6923600, storage_rebate: 0, non_refundable_storage_fee: 0 + +task 2 'run'. lines 34-34: +created: object(2,0), object(2,1) +mutated: object(0,0) +gas summary: computation_cost: 1000000, storage_cost: 3420000, storage_rebate: 978120, non_refundable_storage_fee: 9880 + +task 3 'view-object'. lines 36-36: +Owner: Account Address ( _ ) +Version: 3 +Contents: tto::M1::A {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,0)}}} + +task 4 'view-object'. lines 38-40: +Owner: Account Address ( fake(2,0) ) +Version: 3 +Contents: tto::M1::B {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,1)}}} + +task 5 'run'. lines 41-41: +Error: Transaction Effects Status: Move Runtime Abort. Location: sui::transfer::receive_impl (function index 10) at offset 0, Abort Code: 2 +Execution Error: ExecutionError: ExecutionError { inner: ExecutionErrorInner { kind: MoveAbort(MoveLocation { module: ModuleId { address: sui, name: Identifier("transfer") }, function: 10, instruction: 0, function_name: Some("receive_impl") }, 2), source: Some(VMError { major_status: ABORTED, sub_status: Some(2), message: None, exec_state: None, location: Module(ModuleId { address: sui, name: Identifier("transfer") }), indices: [], offsets: [(FunctionDefinitionIndex(10), 0)] }), command: Some(0) } } diff --git a/crates/sui-adapter-transactional-tests/tests/receive_object/receive_invalid_type.move b/crates/sui-adapter-transactional-tests/tests/receive_object/receive_invalid_type.move new file mode 100644 index 0000000000000..bdb1be6ccb00c --- /dev/null +++ b/crates/sui-adapter-transactional-tests/tests/receive_object/receive_invalid_type.move @@ -0,0 +1,41 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +//# init --addresses tto=0x0 + +//# publish +module tto::M1 { + use sui::object::{Self, UID}; + use sui::tx_context::{Self, TxContext}; + use sui::transfer::{Self, Receiving}; + + struct A has key, store { + id: UID, + } + + struct B has key, store { + id: UID, + } + + public fun start(ctx: &mut TxContext) { + let a = A { id: object::new(ctx) }; + let a_address = object::id_address(&a); + let b = B { id: object::new(ctx) }; + transfer::public_transfer(a, tx_context::sender(ctx)); + transfer::public_transfer(b, a_address); + } + + public entry fun receiver(parent: &mut A, x: Receiving) { + let b = transfer::receive(&mut parent.id, x); + transfer::public_transfer(b, @tto); + } +} + +//# run tto::M1::start + +//# view-object 2,0 + +//# view-object 2,1 + +// TYPE_MISMATCH +//# run tto::M1::receiver --args object(2,0) receiving(2,1) diff --git a/crates/sui-adapter-transactional-tests/tests/receive_object/receive_multiple_times_in_row.exp b/crates/sui-adapter-transactional-tests/tests/receive_object/receive_multiple_times_in_row.exp new file mode 100644 index 0000000000000..2ecbcddd577a1 --- /dev/null +++ b/crates/sui-adapter-transactional-tests/tests/receive_object/receive_multiple_times_in_row.exp @@ -0,0 +1,59 @@ +processed 13 tasks + +init: +A: object(0,0) + +task 1 'publish'. lines 6-38: +created: object(1,0) +mutated: object(0,1) +gas summary: computation_cost: 1000000, storage_cost: 7007200, storage_rebate: 0, non_refundable_storage_fee: 0 + +task 2 'run'. lines 40-40: +created: object(2,0), object(2,1) +mutated: object(0,1) +gas summary: computation_cost: 1000000, storage_cost: 3420000, storage_rebate: 978120, non_refundable_storage_fee: 9880 + +task 3 'run'. lines 42-42: +created: object(3,0) +mutated: object(0,0) +gas summary: computation_cost: 1000000, storage_cost: 2204000, storage_rebate: 0, non_refundable_storage_fee: 0 + +task 4 'view-object'. lines 44-44: +Owner: Account Address ( _ ) +Version: 3 +Contents: tto::M1::A {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,0)}}} + +task 5 'view-object'. lines 46-48: +Owner: Account Address ( fake(2,0) ) +Version: 3 +Contents: tto::M1::B {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,1)}}} + +task 6 'run'. lines 49-49: +mutated: object(0,1), object(2,0), object(2,1) +gas summary: computation_cost: 1000000, storage_cost: 3420000, storage_rebate: 3385800, non_refundable_storage_fee: 34200 + +task 7 'view-object'. lines 51-51: +Owner: Account Address ( _ ) +Version: 4 +Contents: tto::M1::A {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,0)}}} + +task 8 'view-object'. lines 53-55: +Owner: Account Address ( fake(2,0) ) +Version: 4 +Contents: tto::M1::B {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,1)}}} + +task 9 'run'. lines 56-58: +Error: Transaction Effects Status: Move Runtime Abort. Location: sui::transfer::receive_impl (function index 10) at offset 0, Abort Code: 3 +Execution Error: ExecutionError: ExecutionError { inner: ExecutionErrorInner { kind: MoveAbort(MoveLocation { module: ModuleId { address: sui, name: Identifier("transfer") }, function: 10, instruction: 0, function_name: Some("receive_impl") }, 3), source: Some(VMError { major_status: ABORTED, sub_status: Some(3), message: None, exec_state: None, location: Module(ModuleId { address: sui, name: Identifier("transfer") }), indices: [], offsets: [(FunctionDefinitionIndex(10), 0)] }), command: Some(0) } } + +task 10 'run'. lines 59-61: +mutated: object(0,1), object(2,0), object(2,1) +gas summary: computation_cost: 1000000, storage_cost: 3420000, storage_rebate: 3385800, non_refundable_storage_fee: 34200 + +task 11 'run'. lines 62-64: +Error: Transaction Effects Status: Move Runtime Abort. Location: sui::transfer::receive_impl (function index 10) at offset 0, Abort Code: 3 +Execution Error: ExecutionError: ExecutionError { inner: ExecutionErrorInner { kind: MoveAbort(MoveLocation { module: ModuleId { address: sui, name: Identifier("transfer") }, function: 10, instruction: 0, function_name: Some("receive_impl") }, 3), source: Some(VMError { major_status: ABORTED, sub_status: Some(3), message: None, exec_state: None, location: Module(ModuleId { address: sui, name: Identifier("transfer") }), indices: [], offsets: [(FunctionDefinitionIndex(10), 0)] }), command: Some(0) } } + +task 12 'run'. lines 65-65: +mutated: object(0,1), object(2,0), object(2,1) +gas summary: computation_cost: 1000000, storage_cost: 3420000, storage_rebate: 3385800, non_refundable_storage_fee: 34200 diff --git a/crates/sui-adapter-transactional-tests/tests/receive_object/receive_multiple_times_in_row.move b/crates/sui-adapter-transactional-tests/tests/receive_object/receive_multiple_times_in_row.move new file mode 100644 index 0000000000000..c5aecd09186e1 --- /dev/null +++ b/crates/sui-adapter-transactional-tests/tests/receive_object/receive_multiple_times_in_row.move @@ -0,0 +1,65 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +//# init --addresses tto=0x0 --accounts A + +//# publish +module tto::M1 { + use sui::object::{Self, UID}; + use sui::tx_context::{Self, TxContext}; + use sui::transfer::{Self, Receiving}; + + struct A has key, store { + id: UID, + } + + struct B has key, store { + id: UID, + } + + public fun start(ctx: &mut TxContext) { + let a = A { id: object::new(ctx) }; + let a_address = object::id_address(&a); + let b = B { id: object::new(ctx) }; + transfer::public_transfer(a, tx_context::sender(ctx)); + transfer::public_transfer(b, a_address); + } + + public fun middle(ctx: &mut TxContext) { + let a = A { id: object::new(ctx) }; + transfer::public_transfer(a, tx_context::sender(ctx)); + } + + public entry fun send_back(parent: &mut A, x: Receiving) { + let b = transfer::receive(&mut parent.id, x); + let parent_address = object::id_address(parent); + transfer::public_transfer(b, parent_address); + } +} + +//# run tto::M1::start + +//# run tto::M1::middle --sender A + +//# view-object 2,0 + +//# view-object 2,1 + +// Can receive the object and then send it +//# run tto::M1::send_back --args object(2,0) receiving(2,1) + +//# view-object 2,0 + +//# view-object 2,1 + +// Can no longer receive that object at the previous version number +//# run tto::M1::send_back --args object(2,0) receiving(2,1)@3 + +// Can receive the object at the new version number +//# run tto::M1::send_back --args object(2,0) receiving(2,1)@4 + +// Cannot try and receive the object with an invalid owner even if it has the right type +//# run tto::M1::send_back --summarize --args object(3,0) receiving(2,1)@6 --sender A + +// Can run still receive and send back so state is all good still, and version number hasn't been incremented for the object +//# run tto::M1::send_back --args object(2,0) receiving(2,1)@6 diff --git a/crates/sui-adapter-transactional-tests/tests/receive_object/receive_object_cyclic.exp b/crates/sui-adapter-transactional-tests/tests/receive_object/receive_object_cyclic.exp new file mode 100644 index 0000000000000..25ab8124fe414 --- /dev/null +++ b/crates/sui-adapter-transactional-tests/tests/receive_object/receive_object_cyclic.exp @@ -0,0 +1,11 @@ +processed 3 tasks + +task 1 'publish'. lines 6-28: +created: object(1,0) +mutated: object(0,0) +gas summary: computation_cost: 1000000, storage_cost: 5852000, storage_rebate: 0, non_refundable_storage_fee: 0 + +task 2 'run'. lines 30-43: +created: object(2,0), object(2,1) +mutated: object(0,0) +gas summary: computation_cost: 1000000, storage_cost: 3420000, storage_rebate: 978120, non_refundable_storage_fee: 9880 diff --git a/crates/sui-adapter-transactional-tests/tests/receive_object/receive_object_cyclic.move b/crates/sui-adapter-transactional-tests/tests/receive_object/receive_object_cyclic.move new file mode 100644 index 0000000000000..74c97c1633575 --- /dev/null +++ b/crates/sui-adapter-transactional-tests/tests/receive_object/receive_object_cyclic.move @@ -0,0 +1,43 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +//# init --addresses tto=0x0 + +//# publish +module tto::M1 { + use sui::object::{Self, UID}; + use sui::tx_context::TxContext; + use sui::transfer::{Self}; + + struct A has key, store { + id: UID, + } + + struct B has key, store { + id: UID, + } + + public fun start(ctx: &mut TxContext) { + let a = A { id: object::new(ctx) }; + let a_address = object::id_address(&a); + let b = B { id: object::new(ctx) }; + let b_address = object::id_address(&b); + transfer::public_transfer(a, b_address); + transfer::public_transfer(b, a_address); + } +} + +//# run tto::M1::start + +// //# view-object 2,0 +// +// //# view-object 2,1 +// +// //# run tto::M1::receiver --args object(2,0) receiving(2,1) +// +// //# view-object 2,0 +// +// //# view-object 2,1 +// +// //# run tto::M1::receiver --args object(2,0) receiving(2,1)@3 +// diff --git a/crates/sui-adapter-transactional-tests/tests/receive_object/receive_object_owner.exp b/crates/sui-adapter-transactional-tests/tests/receive_object/receive_object_owner.exp new file mode 100644 index 0000000000000..47275bf3373ff --- /dev/null +++ b/crates/sui-adapter-transactional-tests/tests/receive_object/receive_object_owner.exp @@ -0,0 +1,35 @@ +processed 8 tasks + +init: +A: object(0,0) + +task 1 'publish'. lines 6-30: +created: object(1,0) +mutated: object(0,1) +gas summary: computation_cost: 1000000, storage_cost: 6634800, storage_rebate: 0, non_refundable_storage_fee: 0 + +task 2 'run'. lines 32-32: +created: object(2,0), object(2,1), object(2,2) +mutated: object(0,0) +gas summary: computation_cost: 1000000, storage_cost: 5996400, storage_rebate: 0, non_refundable_storage_fee: 0 + +task 3 'view-object'. lines 34-34: +Owner: Object ID: ( fake(2,2) ) +Version: 2 +Contents: sui::dynamic_field::Field, sui::object::ID> {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,0)}}, name: sui::dynamic_object_field::Wrapper {name: 0u64}, value: sui::object::ID {bytes: fake(2,1)}} + +task 4 'view-object'. lines 36-36: +Owner: Object ID: ( fake(2,0) ) +Version: 2 +Contents: tto::M1::A {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,1)}}, value: 0u64} + +task 5 'view-object'. lines 38-38: +Owner: Account Address ( A ) +Version: 2 +Contents: tto::M1::A {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,2)}}, value: 0u64} + +task 6 'run'. lines 40-40: +Error: Error checking transaction input objects: ObjectNotFound { object_id: 0xc8a6ab1286d28dc445a92bd8852fbbf41a97d17f0ff8fe74f44d5ca31bc5ac6e, version: Some(SequenceNumber(2)) } + +task 7 'run'. lines 42-42: +Error: Error checking transaction input objects: ObjectNotFound { object_id: 0xc8182c524f27fa3e1e79ef9f3e478d5b27637f94e5c7664eee82881e2381ed93, version: Some(SequenceNumber(2)) } diff --git a/crates/sui-adapter-transactional-tests/tests/receive_object/receive_object_owner.move b/crates/sui-adapter-transactional-tests/tests/receive_object/receive_object_owner.move new file mode 100644 index 0000000000000..865180c6525a1 --- /dev/null +++ b/crates/sui-adapter-transactional-tests/tests/receive_object/receive_object_owner.move @@ -0,0 +1,42 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +//# init --addresses tto=0x0 --accounts A + +//# publish +module tto::M1 { + use sui::object::{Self, UID}; + use sui::tx_context::{Self, TxContext}; + use sui::transfer::{Self, Receiving}; + use sui::dynamic_object_field as dof; + + const KEY: u64 = 0; + + struct A has key, store { + id: UID, + value: u64, + } + + public fun start(ctx: &mut TxContext) { + let a = A { id: object::new(ctx), value: 0 }; + dof::add(&mut a.id, KEY, A { id: object::new(ctx), value: 0 }); + transfer::public_transfer(a, tx_context::sender(ctx)); + } + + public entry fun receive(parent: &mut A, x: Receiving) { + let b = transfer::receive(&mut parent.id, x); + dof::add(&mut parent.id, KEY, b); + } +} + +//# run tto::M1::start --sender A + +//# view-object 2,0 + +//# view-object 2,1 + +//# view-object 2,2 + +//# run tto::M1::receive --args object(2,2) receiving(2,1) --sender A + +//# run tto::M1::receive --args object(2,2) receiving(2,0) --sender A diff --git a/crates/sui-adapter-transactional-tests/tests/receive_object/shared_parent/basic_receive.exp b/crates/sui-adapter-transactional-tests/tests/receive_object/shared_parent/basic_receive.exp new file mode 100644 index 0000000000000..9afb85550faaf --- /dev/null +++ b/crates/sui-adapter-transactional-tests/tests/receive_object/shared_parent/basic_receive.exp @@ -0,0 +1,39 @@ +processed 9 tasks + +task 1 'publish'. lines 6-32: +created: object(1,0) +mutated: object(0,0) +gas summary: computation_cost: 1000000, storage_cost: 6969200, storage_rebate: 0, non_refundable_storage_fee: 0 + +task 2 'run'. lines 34-34: +created: object(2,0), object(2,1) +mutated: object(0,0) +gas summary: computation_cost: 1000000, storage_cost: 3420000, storage_rebate: 978120, non_refundable_storage_fee: 9880 + +task 3 'view-object'. lines 36-36: +Owner: Shared +Version: 3 +Contents: tto::M1::A {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,0)}}} + +task 4 'view-object'. lines 38-38: +Owner: Account Address ( fake(2,0) ) +Version: 3 +Contents: tto::M1::B {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,1)}}} + +task 5 'run'. lines 40-40: +mutated: object(0,0), object(2,0), object(2,1) +gas summary: computation_cost: 1000000, storage_cost: 3420000, storage_rebate: 3385800, non_refundable_storage_fee: 34200 + +task 6 'view-object'. lines 42-42: +Owner: Shared +Version: 4 +Contents: tto::M1::A {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,0)}}} + +task 7 'view-object'. lines 44-44: +Owner: Account Address ( _ ) +Version: 4 +Contents: tto::M1::B {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,1)}}} + +task 8 'run'. lines 46-46: +Error: Transaction Effects Status: Move Runtime Abort. Location: sui::transfer::receive_impl (function index 10) at offset 0, Abort Code: 3 +Cannot return execution error with shared objects. Debug of error: MoveAbort(MoveLocation { module: ModuleId { address: sui, name: Identifier("transfer") }, function: 10, instruction: 0, function_name: Some("receive_impl") }, 3) at command Some(0) diff --git a/crates/sui-adapter-transactional-tests/tests/receive_object/shared_parent/basic_receive.move b/crates/sui-adapter-transactional-tests/tests/receive_object/shared_parent/basic_receive.move new file mode 100644 index 0000000000000..7d4e47d4e9237 --- /dev/null +++ b/crates/sui-adapter-transactional-tests/tests/receive_object/shared_parent/basic_receive.move @@ -0,0 +1,46 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +//# init --addresses tto=0x0 + +//# publish +module tto::M1 { + use sui::object::{Self, UID}; + use sui::tx_context::TxContext; + use sui::transfer::{Self, Receiving}; + + struct A has key, store { + id: UID, + } + + struct B has key, store { + id: UID, + } + + public fun start(ctx: &mut TxContext) { + let a = A { id: object::new(ctx) }; + let a_address = object::id_address(&a); + let b = B { id: object::new(ctx) }; + transfer::public_share_object(a); + transfer::public_transfer(b, a_address); + } + + public entry fun receiver(parent: &mut A, x: Receiving) { + let b = transfer::receive(&mut parent.id, x); + transfer::public_transfer(b, @tto); + } +} + +//# run tto::M1::start + +//# view-object 2,0 + +//# view-object 2,1 + +//# run tto::M1::receiver --args object(2,0) receiving(2,1) + +//# view-object 2,0 + +//# view-object 2,1 + +//# run tto::M1::receiver --args object(2,0) receiving(2,1)@3 diff --git a/crates/sui-adapter-transactional-tests/tests/receive_object/shared_parent/drop_receiving.exp b/crates/sui-adapter-transactional-tests/tests/receive_object/shared_parent/drop_receiving.exp new file mode 100644 index 0000000000000..305fe29335f95 --- /dev/null +++ b/crates/sui-adapter-transactional-tests/tests/receive_object/shared_parent/drop_receiving.exp @@ -0,0 +1,23 @@ +processed 6 tasks + +task 1 'publish'. lines 6-35: +created: object(1,0) +mutated: object(0,0) +gas summary: computation_cost: 1000000, storage_cost: 7182000, storage_rebate: 0, non_refundable_storage_fee: 0 + +task 2 'run'. lines 37-37: +created: object(2,0), object(2,1) +mutated: object(0,0) +gas summary: computation_cost: 1000000, storage_cost: 3420000, storage_rebate: 978120, non_refundable_storage_fee: 9880 + +task 3 'programmable'. lines 39-42: +mutated: object(0,0), object(2,0), object(2,1) +gas summary: computation_cost: 1000000, storage_cost: 3420000, storage_rebate: 3385800, non_refundable_storage_fee: 34200 + +task 4 'programmable'. lines 43-47: +mutated: object(0,0), object(2,0) +gas summary: computation_cost: 1000000, storage_cost: 2204000, storage_rebate: 2181960, non_refundable_storage_fee: 22040 + +task 5 'programmable'. lines 48-49: +mutated: object(0,0), object(2,0) +gas summary: computation_cost: 1000000, storage_cost: 2204000, storage_rebate: 2181960, non_refundable_storage_fee: 22040 diff --git a/crates/sui-adapter-transactional-tests/tests/receive_object/shared_parent/drop_receiving.move b/crates/sui-adapter-transactional-tests/tests/receive_object/shared_parent/drop_receiving.move new file mode 100644 index 0000000000000..353c4d3d48b39 --- /dev/null +++ b/crates/sui-adapter-transactional-tests/tests/receive_object/shared_parent/drop_receiving.move @@ -0,0 +1,49 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +//# init --addresses tto=0x0 + +//# publish +module tto::M1 { + use sui::object::{Self, UID}; + use sui::tx_context::TxContext; + use sui::transfer::{Self, Receiving}; + + struct A has key, store { + id: UID, + } + + struct B has key, store { + id: UID, + } + public fun start(ctx: &mut TxContext) { + let a = A { id: object::new(ctx) }; + let a_address = object::id_address(&a); + let b = B { id: object::new(ctx) }; + transfer::public_share_object(a); + transfer::public_transfer(b, a_address); + } + + public entry fun send_back(parent: &mut A, x: Receiving) { + let b = transfer::receive(&mut parent.id, x); + let parent_address = object::id_address(parent); + transfer::public_transfer(b, parent_address); + } + + public entry fun nop(_parent: &mut A) { } + public entry fun nop_with_receiver(_parent: &mut A, _x: Receiving) { } +} + +//# run tto::M1::start + +//# programmable --inputs object(2,0) receiving(2,1) +//> tto::M1::send_back(Input(0), Input(1)) + +// Include the receiving argument, but don't use it at the PTB level +//# programmable --inputs object(2,0) receiving(2,1) +//> tto::M1::nop(Input(0)) + +// Include the receiving argument, but don't use it at the Move level. The +// receiving object should not be mutated by this. +//# programmable --inputs object(2,0) receiving(2,1) +//> tto::M1::nop_with_receiver(Input(0), Input(1)) diff --git a/crates/sui-adapter-transactional-tests/tests/receive_object/shared_parent/receive_dof_and_mutate.exp b/crates/sui-adapter-transactional-tests/tests/receive_object/shared_parent/receive_dof_and_mutate.exp new file mode 100644 index 0000000000000..4609e158c07e5 --- /dev/null +++ b/crates/sui-adapter-transactional-tests/tests/receive_object/shared_parent/receive_dof_and_mutate.exp @@ -0,0 +1,59 @@ +processed 12 tasks + +init: +A: object(0,0) + +task 1 'publish'. lines 6-36: +created: object(1,0) +mutated: object(0,1) +gas summary: computation_cost: 1000000, storage_cost: 7881200, storage_rebate: 0, non_refundable_storage_fee: 0 + +task 2 'run'. lines 38-38: +created: object(2,0), object(2,1), object(2,2), object(2,3) +mutated: object(0,1) +gas summary: computation_cost: 1000000, storage_cost: 7273200, storage_rebate: 978120, non_refundable_storage_fee: 9880 + +task 3 'view-object'. lines 40-40: +Owner: Object ID: ( fake(2,3) ) +Version: 3 +Contents: sui::dynamic_field::Field, sui::object::ID> {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,0)}}, name: sui::dynamic_object_field::Wrapper {name: 0u64}, value: sui::object::ID {bytes: fake(2,2)}} + +task 4 'view-object'. lines 42-42: +Owner: Shared +Version: 3 +Contents: tto::M1::A {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,1)}}, value: 0u64} + +task 5 'view-object'. lines 44-44: +Owner: Object ID: ( fake(2,0) ) +Version: 3 +Contents: tto::M1::A {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,2)}}, value: 0u64} + +task 6 'view-object'. lines 46-46: +Owner: Account Address ( fake(2,1) ) +Version: 3 +Contents: tto::M1::A {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,3)}}, value: 0u64} + +task 7 'run'. lines 48-48: +created: object(7,0) +mutated: object(0,1), object(2,1), object(2,3) +gas summary: computation_cost: 1000000, storage_cost: 5996400, storage_rebate: 3506184, non_refundable_storage_fee: 35416 + +task 8 'view-object'. lines 50-50: +Owner: Object ID: ( fake(2,3) ) +Version: 3 +Contents: sui::dynamic_field::Field, sui::object::ID> {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,0)}}, name: sui::dynamic_object_field::Wrapper {name: 0u64}, value: sui::object::ID {bytes: fake(2,2)}} + +task 9 'view-object'. lines 52-52: +Owner: Shared +Version: 4 +Contents: tto::M1::A {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,1)}}, value: 0u64} + +task 10 'view-object'. lines 54-54: +Owner: Object ID: ( fake(2,0) ) +Version: 3 +Contents: tto::M1::A {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,2)}}, value: 0u64} + +task 11 'view-object'. lines 56-56: +Owner: Object ID: ( fake(7,0) ) +Version: 4 +Contents: tto::M1::A {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,3)}}, value: 100u64} diff --git a/crates/sui-adapter-transactional-tests/tests/receive_object/shared_parent/receive_dof_and_mutate.move b/crates/sui-adapter-transactional-tests/tests/receive_object/shared_parent/receive_dof_and_mutate.move new file mode 100644 index 0000000000000..1ab1c5301dab7 --- /dev/null +++ b/crates/sui-adapter-transactional-tests/tests/receive_object/shared_parent/receive_dof_and_mutate.move @@ -0,0 +1,56 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +//# init --addresses tto=0x0 --accounts A + +//# publish +module tto::M1 { + use sui::object::{Self, UID}; + use sui::tx_context::TxContext; + use sui::transfer::{Self, Receiving}; + use sui::dynamic_object_field as dof; + + const KEY: u64 = 0; + + struct A has key, store { + id: UID, + value: u64, + } + + public fun start(ctx: &mut TxContext) { + let a = A { id: object::new(ctx), value: 0 }; + let a_address = object::id_address(&a); + let b = A { id: object::new(ctx), value: 0 }; + dof::add(&mut b.id, KEY, A { id: object::new(ctx), value: 0 }); + transfer::public_share_object(a); + transfer::public_transfer(b, a_address); + } + + public entry fun receive(parent: &mut A, x: Receiving) { + let b = transfer::receive(&mut parent.id, x); + dof::add(&mut parent.id, KEY, b); + let _: &A = dof::borrow(&parent.id, KEY); + let x: &mut A = dof::borrow_mut(&mut parent.id, KEY); + x.value = 100; + } +} + +//# run tto::M1::start + +//# view-object 2,0 + +//# view-object 2,1 + +//# view-object 2,2 + +//# view-object 2,3 + +//# run tto::M1::receive --args object(2,1) receiving(2,3) + +//# view-object 2,0 + +//# view-object 2,1 + +//# view-object 2,2 + +//# view-object 2,3 diff --git a/crates/sui-adapter-transactional-tests/tests/receive_object/shared_parent/receive_multiple_times_in_row.exp b/crates/sui-adapter-transactional-tests/tests/receive_object/shared_parent/receive_multiple_times_in_row.exp new file mode 100644 index 0000000000000..76af55f82f548 --- /dev/null +++ b/crates/sui-adapter-transactional-tests/tests/receive_object/shared_parent/receive_multiple_times_in_row.exp @@ -0,0 +1,59 @@ +processed 13 tasks + +init: +A: object(0,0) + +task 1 'publish'. lines 6-39: +created: object(1,0) +mutated: object(0,1) +gas summary: computation_cost: 1000000, storage_cost: 7182000, storage_rebate: 0, non_refundable_storage_fee: 0 + +task 2 'run'. lines 41-41: +created: object(2,0), object(2,1) +mutated: object(0,1) +gas summary: computation_cost: 1000000, storage_cost: 3420000, storage_rebate: 978120, non_refundable_storage_fee: 9880 + +task 3 'run'. lines 43-43: +created: object(3,0) +mutated: object(0,0) +gas summary: computation_cost: 1000000, storage_cost: 2204000, storage_rebate: 0, non_refundable_storage_fee: 0 + +task 4 'view-object'. lines 45-45: +Owner: Shared +Version: 3 +Contents: tto::M1::A {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,0)}}} + +task 5 'view-object'. lines 47-49: +Owner: Account Address ( fake(2,0) ) +Version: 3 +Contents: tto::M1::B {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,1)}}} + +task 6 'run'. lines 50-50: +mutated: object(0,1), object(2,0), object(2,1) +gas summary: computation_cost: 1000000, storage_cost: 3420000, storage_rebate: 3385800, non_refundable_storage_fee: 34200 + +task 7 'view-object'. lines 52-52: +Owner: Shared +Version: 4 +Contents: tto::M1::A {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,0)}}} + +task 8 'view-object'. lines 54-56: +Owner: Account Address ( fake(2,0) ) +Version: 4 +Contents: tto::M1::B {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,1)}}} + +task 9 'run'. lines 57-59: +Error: Transaction Effects Status: Move Runtime Abort. Location: sui::transfer::receive_impl (function index 10) at offset 0, Abort Code: 3 +Cannot return execution error with shared objects. Debug of error: MoveAbort(MoveLocation { module: ModuleId { address: sui, name: Identifier("transfer") }, function: 10, instruction: 0, function_name: Some("receive_impl") }, 3) at command Some(0) + +task 10 'run'. lines 60-62: +mutated: object(0,1), object(2,0), object(2,1) +gas summary: computation_cost: 1000000, storage_cost: 3420000, storage_rebate: 3385800, non_refundable_storage_fee: 34200 + +task 11 'run'. lines 63-65: +Error: Transaction Effects Status: Move Runtime Abort. Location: sui::transfer::receive_impl (function index 10) at offset 0, Abort Code: 3 +Execution Error: ExecutionError: ExecutionError { inner: ExecutionErrorInner { kind: MoveAbort(MoveLocation { module: ModuleId { address: sui, name: Identifier("transfer") }, function: 10, instruction: 0, function_name: Some("receive_impl") }, 3), source: Some(VMError { major_status: ABORTED, sub_status: Some(3), message: None, exec_state: None, location: Module(ModuleId { address: sui, name: Identifier("transfer") }), indices: [], offsets: [(FunctionDefinitionIndex(10), 0)] }), command: Some(0) } } + +task 12 'run'. lines 66-66: +mutated: object(0,1), object(2,0), object(2,1) +gas summary: computation_cost: 1000000, storage_cost: 3420000, storage_rebate: 3385800, non_refundable_storage_fee: 34200 diff --git a/crates/sui-adapter-transactional-tests/tests/receive_object/shared_parent/receive_multiple_times_in_row.move b/crates/sui-adapter-transactional-tests/tests/receive_object/shared_parent/receive_multiple_times_in_row.move new file mode 100644 index 0000000000000..ffe72183d3c1e --- /dev/null +++ b/crates/sui-adapter-transactional-tests/tests/receive_object/shared_parent/receive_multiple_times_in_row.move @@ -0,0 +1,66 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +//# init --addresses tto=0x0 --accounts A + +//# publish +module tto::M1 { + use sui::object::{Self, UID}; + use sui::tx_context::{Self, TxContext}; + use sui::transfer::{Self, Receiving}; + + struct A has key, store { + id: UID, + } + + struct B has key, store { + id: UID, + } + + public fun start(ctx: &mut TxContext) { + let a = A { id: object::new(ctx) }; + let a_address = object::id_address(&a); + let b = B { id: object::new(ctx) }; + transfer::public_share_object(a); + transfer::public_transfer(b, a_address); + } + + + public fun middle(ctx: &mut TxContext) { + let a = A { id: object::new(ctx) }; + transfer::public_transfer(a, tx_context::sender(ctx)); + } + + public entry fun send_back(parent: &mut A, x: Receiving) { + let b = transfer::receive(&mut parent.id, x); + let parent_address = object::id_address(parent); + transfer::public_transfer(b, parent_address); + } +} + +//# run tto::M1::start + +//# run tto::M1::middle --sender A + +//# view-object 2,0 + +//# view-object 2,1 + +// Can receive the object and then send it +//# run tto::M1::send_back --args object(2,0) receiving(2,1) + +//# view-object 2,0 + +//# view-object 2,1 + +// Can no longer receive that object at the previous version number +//# run tto::M1::send_back --args object(2,0) receiving(2,1)@3 + +// Can receive the object at the new version number +//# run tto::M1::send_back --args object(2,0) receiving(2,1)@4 + +// Cannot try and receive the object with an invalid owner even if it has the right type +//# run tto::M1::send_back --summarize --args object(3,0) receiving(2,1)@6 --sender A + +// Can run still receive and send back so state is all good still, and version number hasn't been incremented for the object +//# run tto::M1::send_back --args object(2,0) receiving(2,1)@6 diff --git a/crates/sui-adapter-transactional-tests/tests/receive_object/shared_parent/transfer_then_share.exp b/crates/sui-adapter-transactional-tests/tests/receive_object/shared_parent/transfer_then_share.exp new file mode 100644 index 0000000000000..06eedacd5252b --- /dev/null +++ b/crates/sui-adapter-transactional-tests/tests/receive_object/shared_parent/transfer_then_share.exp @@ -0,0 +1,39 @@ +processed 9 tasks + +task 1 'publish'. lines 6-33: +created: object(1,0) +mutated: object(0,0) +gas summary: computation_cost: 1000000, storage_cost: 6916000, storage_rebate: 0, non_refundable_storage_fee: 0 + +task 2 'run'. lines 35-35: +created: object(2,0), object(2,1) +mutated: object(0,0) +gas summary: computation_cost: 1000000, storage_cost: 3420000, storage_rebate: 978120, non_refundable_storage_fee: 9880 + +task 3 'view-object'. lines 37-37: +Owner: Shared +Version: 3 +Contents: tto::M1::A {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,0)}}} + +task 4 'view-object'. lines 39-39: +Owner: Account Address ( fake(2,0) ) +Version: 3 +Contents: tto::M1::B {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,1)}}} + +task 5 'run'. lines 41-41: +mutated: object(0,0), object(2,0), object(2,1) +gas summary: computation_cost: 1000000, storage_cost: 3420000, storage_rebate: 3385800, non_refundable_storage_fee: 34200 + +task 6 'view-object'. lines 43-43: +Owner: Shared +Version: 4 +Contents: tto::M1::A {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,0)}}} + +task 7 'view-object'. lines 45-45: +Owner: Account Address ( _ ) +Version: 4 +Contents: tto::M1::B {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,1)}}} + +task 8 'run'. lines 47-47: +Error: Transaction Effects Status: Move Runtime Abort. Location: sui::transfer::receive_impl (function index 10) at offset 0, Abort Code: 3 +Cannot return execution error with shared objects. Debug of error: MoveAbort(MoveLocation { module: ModuleId { address: sui, name: Identifier("transfer") }, function: 10, instruction: 0, function_name: Some("receive_impl") }, 3) at command Some(0) diff --git a/crates/sui-adapter-transactional-tests/tests/receive_object/shared_parent/transfer_then_share.move b/crates/sui-adapter-transactional-tests/tests/receive_object/shared_parent/transfer_then_share.move new file mode 100644 index 0000000000000..1bf62f2a5cf47 --- /dev/null +++ b/crates/sui-adapter-transactional-tests/tests/receive_object/shared_parent/transfer_then_share.move @@ -0,0 +1,47 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +//# init --addresses tto=0x0 + +//# publish +module tto::M1 { + use sui::object::{Self, UID}; + use sui::tx_context::TxContext; + use sui::transfer::{Self, Receiving}; + + struct A has key, store { + id: UID, + } + + struct B has key, store { + id: UID, + } + + public fun start(ctx: &mut TxContext) { + let a = A { id: object::new(ctx) }; + let a_address = object::id_address(&a); + let b = B { id: object::new(ctx) }; + // transfer to 'a' first, then share it + transfer::public_transfer(b, a_address); + transfer::public_share_object(a); + } + + public entry fun receiver(parent: &mut A, x: Receiving) { + let b = transfer::receive(&mut parent.id, x); + transfer::public_transfer(b, @tto); + } +} + +//# run tto::M1::start + +//# view-object 2,0 + +//# view-object 2,1 + +//# run tto::M1::receiver --args object(2,0) receiving(2,1) + +//# view-object 2,0 + +//# view-object 2,1 + +//# run tto::M1::receiver --args object(2,0) receiving(2,1)@3 diff --git a/crates/sui-adapter-transactional-tests/tests/receive_object/take_receiver_then_try_to_reuse.exp b/crates/sui-adapter-transactional-tests/tests/receive_object/take_receiver_then_try_to_reuse.exp new file mode 100644 index 0000000000000..bd3828f07c231 --- /dev/null +++ b/crates/sui-adapter-transactional-tests/tests/receive_object/take_receiver_then_try_to_reuse.exp @@ -0,0 +1,25 @@ +processed 6 tasks + +task 1 'publish'. lines 6-34: +created: object(1,0) +mutated: object(0,0) +gas summary: computation_cost: 1000000, storage_cost: 7182000, storage_rebate: 0, non_refundable_storage_fee: 0 + +task 2 'run'. lines 36-36: +created: object(2,0), object(2,1) +mutated: object(0,0) +gas summary: computation_cost: 1000000, storage_cost: 3420000, storage_rebate: 978120, non_refundable_storage_fee: 9880 + +task 3 'view-object'. lines 38-38: +Owner: Account Address ( _ ) +Version: 3 +Contents: tto::M1::A {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,0)}}} + +task 4 'view-object'. lines 40-40: +Owner: Account Address ( fake(2,0) ) +Version: 3 +Contents: tto::M1::B {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,1)}}} + +task 5 'programmable'. lines 42-44: +Error: Transaction Effects Status: Invalid command argument at 1. Invalid usage of value. Mutably borrowed values require unique usage. Immutably borrowed values cannot be taken or borrowed mutably. Taken values cannot be used again. +Execution Error: ExecutionError: ExecutionError { inner: ExecutionErrorInner { kind: CommandArgumentError { arg_idx: 1, kind: InvalidValueUsage }, source: None, command: Some(1) } } diff --git a/crates/sui-adapter-transactional-tests/tests/receive_object/take_receiver_then_try_to_reuse.move b/crates/sui-adapter-transactional-tests/tests/receive_object/take_receiver_then_try_to_reuse.move new file mode 100644 index 0000000000000..0d34ce41a623b --- /dev/null +++ b/crates/sui-adapter-transactional-tests/tests/receive_object/take_receiver_then_try_to_reuse.move @@ -0,0 +1,44 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +//# init --addresses tto=0x0 + +//# publish +module tto::M1 { + use sui::object::{Self, UID}; + use sui::tx_context::{Self, TxContext}; + use sui::transfer::{Self, Receiving}; + + struct A has key, store { + id: UID, + } + + struct B has key, store { + id: UID, + } + + public fun start(ctx: &mut TxContext) { + let a = A { id: object::new(ctx) }; + let a_address = object::id_address(&a); + let b = B { id: object::new(ctx) }; + transfer::public_transfer(a, tx_context::sender(ctx)); + transfer::public_transfer(b, a_address); + } + + public entry fun pass_through(x: Receiving): Receiving { x } + + public entry fun receiver(parent: &mut A, x: Receiving) { + let b = transfer::receive(&mut parent.id, x); + transfer::public_transfer(b, @tto); + } +} + +//# run tto::M1::start + +//# view-object 2,0 + +//# view-object 2,1 + +//# programmable --inputs object(2,0) receiving(2,1) +//> tto::M1::pass_through(Input(1)); +//> tto::M1::receiver(Input(0), Input(1)); diff --git a/crates/sui-adapter-transactional-tests/tests/shared/upgrade.exp b/crates/sui-adapter-transactional-tests/tests/shared/upgrade.exp index 82e295bb5bfd8..76a6fb7909c80 100644 --- a/crates/sui-adapter-transactional-tests/tests/shared/upgrade.exp +++ b/crates/sui-adapter-transactional-tests/tests/shared/upgrade.exp @@ -19,13 +19,13 @@ Version: 2 Contents: t::m::Obj {id: sui::object::UID {id: sui::object::ID {bytes: fake(2,2)}}} task 4 'run'. lines 46-46: -Error: Transaction Effects Status: Move Runtime Abort. Location: sui::transfer::share_object_impl (function index 7) at offset 0, Abort Code: 0 -Execution Error: ExecutionError: ExecutionError { inner: ExecutionErrorInner { kind: MoveAbort(MoveLocation { module: ModuleId { address: sui, name: Identifier("transfer") }, function: 7, instruction: 0, function_name: Some("share_object_impl") }, 0), source: Some(VMError { major_status: ABORTED, sub_status: Some(0), message: None, exec_state: None, location: Module(ModuleId { address: sui, name: Identifier("transfer") }), indices: [], offsets: [(FunctionDefinitionIndex(7), 0)] }), command: Some(0) } } +Error: Transaction Effects Status: Move Runtime Abort. Location: sui::transfer::share_object_impl (function index 8) at offset 0, Abort Code: 0 +Execution Error: ExecutionError: ExecutionError { inner: ExecutionErrorInner { kind: MoveAbort(MoveLocation { module: ModuleId { address: sui, name: Identifier("transfer") }, function: 8, instruction: 0, function_name: Some("share_object_impl") }, 0), source: Some(VMError { major_status: ABORTED, sub_status: Some(0), message: None, exec_state: None, location: Module(ModuleId { address: sui, name: Identifier("transfer") }), indices: [], offsets: [(FunctionDefinitionIndex(8), 0)] }), command: Some(0) } } task 5 'run'. lines 48-48: -Error: Transaction Effects Status: Move Runtime Abort. Location: sui::transfer::share_object_impl (function index 7) at offset 0, Abort Code: 0 -Execution Error: ExecutionError: ExecutionError { inner: ExecutionErrorInner { kind: MoveAbort(MoveLocation { module: ModuleId { address: sui, name: Identifier("transfer") }, function: 7, instruction: 0, function_name: Some("share_object_impl") }, 0), source: Some(VMError { major_status: ABORTED, sub_status: Some(0), message: None, exec_state: None, location: Module(ModuleId { address: sui, name: Identifier("transfer") }), indices: [], offsets: [(FunctionDefinitionIndex(7), 0)] }), command: Some(0) } } +Error: Transaction Effects Status: Move Runtime Abort. Location: sui::transfer::share_object_impl (function index 8) at offset 0, Abort Code: 0 +Execution Error: ExecutionError: ExecutionError { inner: ExecutionErrorInner { kind: MoveAbort(MoveLocation { module: ModuleId { address: sui, name: Identifier("transfer") }, function: 8, instruction: 0, function_name: Some("share_object_impl") }, 0), source: Some(VMError { major_status: ABORTED, sub_status: Some(0), message: None, exec_state: None, location: Module(ModuleId { address: sui, name: Identifier("transfer") }), indices: [], offsets: [(FunctionDefinitionIndex(8), 0)] }), command: Some(0) } } task 6 'run'. lines 50-50: -Error: Transaction Effects Status: Move Runtime Abort. Location: sui::transfer::share_object_impl (function index 7) at offset 0, Abort Code: 0 -Execution Error: ExecutionError: ExecutionError { inner: ExecutionErrorInner { kind: MoveAbort(MoveLocation { module: ModuleId { address: sui, name: Identifier("transfer") }, function: 7, instruction: 0, function_name: Some("share_object_impl") }, 0), source: Some(VMError { major_status: ABORTED, sub_status: Some(0), message: None, exec_state: None, location: Module(ModuleId { address: sui, name: Identifier("transfer") }), indices: [], offsets: [(FunctionDefinitionIndex(7), 0)] }), command: Some(0) } } +Error: Transaction Effects Status: Move Runtime Abort. Location: sui::transfer::share_object_impl (function index 8) at offset 0, Abort Code: 0 +Execution Error: ExecutionError: ExecutionError { inner: ExecutionErrorInner { kind: MoveAbort(MoveLocation { module: ModuleId { address: sui, name: Identifier("transfer") }, function: 8, instruction: 0, function_name: Some("share_object_impl") }, 0), source: Some(VMError { major_status: ABORTED, sub_status: Some(0), message: None, exec_state: None, location: Module(ModuleId { address: sui, name: Identifier("transfer") }), indices: [], offsets: [(FunctionDefinitionIndex(8), 0)] }), command: Some(0) } } diff --git a/crates/sui-benchmark/src/lib.rs b/crates/sui-benchmark/src/lib.rs index ffac4fccd646f..a7e0c42aafbd2 100644 --- a/crates/sui-benchmark/src/lib.rs +++ b/crates/sui-benchmark/src/lib.rs @@ -857,6 +857,9 @@ impl From for BenchMoveCallArg { initial_shared_version, mutable, } => BenchMoveCallArg::Shared((id, initial_shared_version, mutable)), + ObjectArg::Receiving(_) => { + unimplemented!("Receiving is not supported for benchmarks") + } }, } } diff --git a/crates/sui-config/src/transaction_deny_config.rs b/crates/sui-config/src/transaction_deny_config.rs index 9bc84fede54c5..ec039a3a367e6 100644 --- a/crates/sui-config/src/transaction_deny_config.rs +++ b/crates/sui-config/src/transaction_deny_config.rs @@ -58,6 +58,10 @@ pub struct TransactionDenyConfig { #[serde(skip)] address_deny_set: OnceCell>, + + /// Whether receiving objects transferred to other objects is allowed + #[serde(default)] + receiving_objects_disabled: bool, // TODO: We could consider add a deny list for types that we want to disable public transfer. // TODO: We could also consider disable more types of commands, such as transfer, split and etc. } @@ -93,6 +97,10 @@ impl TransactionDenyConfig { pub fn user_transaction_disabled(&self) -> bool { self.user_transaction_disabled } + + pub fn receiving_objects_disabled(&self) -> bool { + self.receiving_objects_disabled + } } #[derive(Default)] @@ -129,6 +137,11 @@ impl TransactionDenyConfigBuilder { self } + pub fn disable_receiving_objects(mut self) -> Self { + self.config.receiving_objects_disabled = true; + self + } + pub fn add_denied_object(mut self, id: ObjectID) -> Self { self.config.object_deny_list.push(id); self diff --git a/crates/sui-core/src/authority.rs b/crates/sui-core/src/authority.rs index 4f39b4bf69a26..e4225b0c8339d 100644 --- a/crates/sui-core/src/authority.rs +++ b/crates/sui-core/src/authority.rs @@ -1084,7 +1084,16 @@ impl AuthorityState { let output_keys: Vec<_> = inner_temporary_store .written .iter() - .map(|(_, ((id, seq, _), obj, _))| InputKey(*id, (!obj.is_package()).then_some(*seq))) + .map(|(_, ((id, seq, _), obj, _))| { + if obj.is_package() { + InputKey::Package { id: *id } + } else { + InputKey::Versioned { + id: *id, + version: *seq, + } + } + }) .collect(); self.commit_certificate(inner_temporary_store, certificate, effects, epoch_store) diff --git a/crates/sui-core/src/authority/authority_store.rs b/crates/sui-core/src/authority/authority_store.rs index 377dd44028569..4dedb1357b25b 100644 --- a/crates/sui-core/src/authority/authority_store.rs +++ b/crates/sui-core/src/authority/authority_store.rs @@ -23,7 +23,8 @@ use sui_types::message_envelope::Message; use sui_types::messages_checkpoint::ECMHLiveObjectSetDigest; use sui_types::object::Owner; use sui_types::storage::{ - get_module_by_id, BackingPackageStore, ChildObjectResolver, DeleteKind, ObjectKey, ObjectStore, + get_module_by_id, BackingPackageStore, ChildObjectResolver, DeleteKind, MarkerKind, ObjectKey, + ObjectStore, }; use sui_types::sui_system_state::get_sui_system_state; use sui_types::{base_types::SequenceNumber, fp_bail, fp_ensure, storage::ParentSync}; @@ -540,6 +541,62 @@ impl AuthorityStore { Ok(result) } + pub fn have_received_object_at_version( + &self, + object_id: &ObjectID, + version: VersionNumber, + epoch_id: EpochId, + ) -> Result { + let object_key = ( + epoch_id, + ObjectKey(*object_id, version), + MarkerKind::Received, + ); + Ok(self + .perpetual_tables + .object_per_epoch_marker_table + .get(&object_key)? + .is_some()) + } + + pub fn check_receiving_objects( + &self, + receiving_objects: &[ObjectRef], + input_objects_len: usize, + protocol_config: &ProtocolConfig, + epoch_id: EpochId, + ) -> Result<(), SuiError> { + // Count receiving objects towards the input object limit as they are passed in the PTB + // args and they will (most likely) incur an object load at runtime. + fp_ensure!( + receiving_objects.len() + input_objects_len + <= protocol_config.max_input_objects() as usize, + UserInputError::SizeLimitExceeded { + limit: "maximum input and receiving objects in a transaction".to_string(), + value: protocol_config.max_input_objects().to_string() + } + .into() + ); + + // Since we're at signing we check that every object reference that we are receiving is the + // most recent version of that object. If it's been received at the version specified we + // let it through to allow the transaction to run and fail to unlock any other objects in + // the transaction. Otherwise, we return an error. + for (object_id, version, _) in receiving_objects { + fp_ensure!( + self.get_object_by_key(object_id, *version)? + .is_some_and(|x| x.owner.is_address_owned()) + || self.have_received_object_at_version(object_id, *version, epoch_id)?, + UserInputError::ObjectNotFound { + object_id: *object_id, + version: Some(*version), + } + .into() + ); + } + Ok(()) + } + pub fn check_input_objects( &self, objects: &[InputObjectKind], @@ -608,43 +665,62 @@ impl AuthorityStore { } else { LockMode::ReadOnly }; - (InputKey(*id, Some(*version)), lock_mode) + (InputKey::Versioned{ id: *id, version: *version}, lock_mode) } // TODO: use ReadOnly lock? - InputObjectKind::MovePackage(id) => (InputKey(*id, None), LockMode::Default), + InputObjectKind::MovePackage(id) => (InputKey::Package { id: *id }, LockMode::Default), // Cannot use ReadOnly lock because we do not know if the object is immutable. - InputObjectKind::ImmOrOwnedMoveObject(objref) => (InputKey(objref.0, Some(objref.1)), LockMode::Default), + InputObjectKind::ImmOrOwnedMoveObject(objref) => (InputKey::Versioned {id: objref.0, version: objref.1}, LockMode::Default), } }) .collect() } /// Checks if the input object identified by the InputKey exists, with support for non-system - /// packages i.e. when version is None. - pub fn multi_input_objects_exist( + /// packages i.e. when version is None. If the input object doesn't exist and it's a receiving + /// object, we also check if the object exists in the object marker table and view it as + /// existing if it is in the table. + pub fn multi_input_objects_available( &self, keys: impl Iterator + Clone, + epoch_id: EpochId, ) -> Result, SuiError> { - let (keys_with_version, keys_without_version): (Vec<_>, Vec<_>) = - keys.enumerate().partition(|(_, key)| key.1.is_some()); + let (keys_with_version, keys_without_version): (Vec<_>, Vec<_>) = keys + .enumerate() + .partition(|(_, key)| key.version().is_some()); - let versioned_results = keys_with_version.iter().map(|(idx, _)| *idx).zip( + let mut versioned_results = vec![]; + for ((idx, input_key), obj_wrapper_opt) in keys_with_version.iter().zip( self.perpetual_tables .objects .multi_get( keys_with_version .iter() - .map(|(_, k)| ObjectKey(k.0, k.1.unwrap())), + .map(|(_, k)| ObjectKey(k.id(), k.version().unwrap())), )? - .into_iter() - .map(|o| o.is_some()), - ); + .into_iter(), + ) { + match obj_wrapper_opt { + Some(_) => versioned_results.push((*idx, true)), + None => { + // For any objects that are unable to be fetched, lookup and determine if + // the object exists in the object marker table as well. If so we will then mark it as + // "available" to let it progress through. + let has_received = self.have_received_object_at_version( + &input_key.id(), + input_key.version().unwrap(), + epoch_id, + )?; + versioned_results.push((*idx, has_received)); + } + } + } let unversioned_results = keys_without_version.into_iter().map(|(idx, key)| { ( idx, match self - .get_latest_object_ref_or_tombstone(key.0) + .get_latest_object_ref_or_tombstone(key.id()) .expect("read cannot fail") { None => false, @@ -654,6 +730,7 @@ impl AuthorityStore { }); let mut results = versioned_results + .into_iter() .chain(unversioned_results) .collect::>(); results.sort_by_key(|(idx, _)| *idx); @@ -1027,8 +1104,8 @@ impl AuthorityStore { &self, write_batch: &mut DBBatch, inner_temporary_store: InnerTemporaryStore, - _transaction: &VerifiedTransaction, - _epoch_id: EpochId, + transaction: &VerifiedTransaction, + epoch_id: EpochId, ) -> SuiResult { let InnerTemporaryStore { objects, @@ -1044,6 +1121,38 @@ impl AuthorityStore { trace!(written =? written.values().map(|((obj_id, ver, _), _, _)| (obj_id, ver)).collect::>(), "batch_update_objects: temp store written"); + // Get the list of objects that could be received in this transaction. + let possible_to_receive = transaction + .transaction_data() + .receiving_objects() + .unwrap_or(vec![]); + // Use this to get the actual set of objects that have been receivied -- any received + // object will show up as a write or delete. + let received_objects: Vec<_> = possible_to_receive + .into_iter() + .flat_map( + |obj_ref| match (written.get(&obj_ref.0), deleted.get(&obj_ref.0)) { + (Some(_), _) | (_, Some(_)) => Some(obj_ref), + _ => None, + }, + ) + .collect(); + + // Insert each received object into the received objects marker table + write_batch.insert_batch( + &self.perpetual_tables.object_per_epoch_marker_table, + received_objects.iter().map(|(object_id, version, _)| { + ( + ( + epoch_id, + ObjectKey(*object_id, *version), + MarkerKind::Received, + ), + (), + ) + }), + )?; + let owned_inputs: Vec<_> = active_inputs .iter() .filter(|(id, _, _)| objects.get(id).unwrap().is_address_owned()) @@ -1136,7 +1245,12 @@ impl AuthorityStore { self.check_owned_object_locks_exist(&owned_inputs)?; self.initialize_locks_impl(write_batch, &new_locks_to_init, false)?; - self.delete_locks(write_batch, &owned_inputs) + self.delete_locks(write_batch, &owned_inputs)?; + + // Make sure to delete the locks for any received objects. + // Any objects that occur as a `Receiving` argument but have not been received will not + // have their locks touched. + self.delete_locks(write_batch, &received_objects) } /// Acquires a lock for a transaction on the given objects if they have all been initialized previously @@ -1829,7 +1943,6 @@ impl AuthorityStore { } } - #[cfg(msim)] pub fn remove_all_versions_of_object(&self, object_id: ObjectID) { let entries: Vec<_> = self .perpetual_tables @@ -1895,6 +2008,34 @@ impl ChildObjectResolver for AuthorityStore { } Ok(Some(child_object)) } + + fn receive_object_at_version( + &self, + owner: &ObjectID, + receiving_object_id: &ObjectID, + receive_object_at_version: SequenceNumber, + epoch_id: EpochId, + ) -> SuiResult> { + let Some(recv_object) = self.get_object_by_key(receiving_object_id, receive_object_at_version)? else { + return Ok(None) + }; + + // Invalid access -- treat as the object does not exist. + if recv_object.owner != Owner::AddressOwner((*owner).into()) { + return Ok(None); + } + + // If we've already received the object at the version, then we say that it doesn't exist. + if self.have_received_object_at_version( + receiving_object_id, + receive_object_at_version, + epoch_id, + )? { + return Ok(None); + } + + Ok(Some(recv_object)) + } } impl ParentSync for AuthorityStore { @@ -2026,14 +2167,47 @@ impl From for LockDetailsWrapper { /// A potential input to a transaction. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct InputKey(pub ObjectID, pub Option); +pub enum InputKey { + Versioned { + id: ObjectID, + version: SequenceNumber, + }, + Package { + id: ObjectID, + }, + Receiving { + id: ObjectID, + version: SequenceNumber, + }, +} + +impl InputKey { + pub fn id(&self) -> ObjectID { + match self { + InputKey::Versioned { id, .. } => *id, + InputKey::Package { id } => *id, + InputKey::Receiving { id, .. } => *id, + } + } + + pub fn version(&self) -> Option { + match self { + InputKey::Versioned { version, .. } => Some(*version), + InputKey::Package { .. } => None, + InputKey::Receiving { version, .. } => Some(*version), + } + } +} impl From<&Object> for InputKey { fn from(obj: &Object) -> Self { if obj.is_package() { - InputKey(obj.id(), None) + InputKey::Package { id: obj.id() } } else { - InputKey(obj.id(), Some(obj.version())) + InputKey::Versioned { + id: obj.id(), + version: obj.version(), + } } } } diff --git a/crates/sui-core/src/lib.rs b/crates/sui-core/src/lib.rs index c67885a414ef3..2e30bfa6a422a 100644 --- a/crates/sui-core/src/lib.rs +++ b/crates/sui-core/src/lib.rs @@ -48,6 +48,9 @@ mod move_package_upgrade_tests; mod pay_sui_tests; pub mod test_authority_clients; #[cfg(test)] +#[path = "unit_tests/transfer_to_object_tests.rs"] +mod transfer_to_object_tests; +#[cfg(test)] #[path = "unit_tests/type_param_tests.rs"] mod type_param_tests; diff --git a/crates/sui-core/src/transaction_input_checker.rs b/crates/sui-core/src/transaction_input_checker.rs index 4f3e324fcaf9c..5b54d5a8342cb 100644 --- a/crates/sui-core/src/transaction_input_checker.rs +++ b/crates/sui-core/src/transaction_input_checker.rs @@ -61,9 +61,11 @@ pub async fn check_transaction_input( transaction.check_version_supported(epoch_store.protocol_config())?; transaction.validity_check(epoch_store.protocol_config())?; let input_objects = transaction.input_objects()?; + let receiving_objects = transaction.receiving_objects()?; transaction_signing_filter::check_transaction_for_signing( transaction, &input_objects, + &receiving_objects, transaction_deny_config, store, )?; @@ -74,6 +76,7 @@ pub async fn check_transaction_input( let objects = store.check_input_objects(&input_objects, epoch_store.protocol_config())?; let gas_status = get_gas_status(&objects, transaction.gas(), epoch_store, transaction).await?; let input_objects = check_objects(transaction, input_objects, objects)?; + store.check_receiving_objects(&receiving_objects, input_objects.len(), epoch_store.protocol_config(), epoch_store.epoch())?; Ok((gas_status, input_objects)) } @@ -87,6 +90,7 @@ pub async fn check_transaction_input_with_given_gas( transaction.check_version_supported(epoch_store.protocol_config())?; transaction.validity_check_no_gas_check(epoch_store.protocol_config())?; check_non_system_packages_to_be_published(transaction, epoch_store.protocol_config(), metrics)?; + let receiving_objects = transaction.receiving_objects()?; let mut input_objects = transaction.input_objects()?; let mut objects = store.check_input_objects(&input_objects, epoch_store.protocol_config())?; @@ -96,6 +100,7 @@ pub async fn check_transaction_input_with_given_gas( let gas_status = get_gas_status(&objects, &[gas_object_ref], epoch_store, transaction).await?; let input_objects = check_objects(transaction, input_objects, objects)?; + store.check_receiving_objects(&receiving_objects, input_objects.len(), epoch_store.protocol_config(), epoch_store.epoch())?; Ok((gas_status, input_objects)) } @@ -160,6 +165,7 @@ pub async fn check_certificate_input( ); let tx_data = &cert.data().intent_message().value; + let receiving_objects = tx_data.receiving_objects()?; let input_object_kinds = tx_data.input_objects()?; let input_object_data = if tx_data.is_change_epoch_tx() { // When changing the epoch, we update a the system object, which is shared, without going @@ -171,6 +177,7 @@ pub async fn check_certificate_input( let gas_status = get_gas_status(&input_object_data, tx_data.gas(), epoch_store, tx_data).await?; let input_objects = check_objects(tx_data, input_object_kinds, input_object_data)?; + store.check_receiving_objects(&receiving_objects, input_objects.len(), epoch_store.protocol_config(), epoch_store.epoch())?; Ok((gas_status, input_objects)) } diff --git a/crates/sui-core/src/transaction_manager.rs b/crates/sui-core/src/transaction_manager.rs index 52aa4e1767853..c4fdf2d41df31 100644 --- a/crates/sui-core/src/transaction_manager.rs +++ b/crates/sui-core/src/transaction_manager.rs @@ -140,15 +140,15 @@ impl CacheInner { } fn insert(&mut self, object: &InputKey) { - if let Some(version) = object.1 { + if let Some(version) = object.version() { if let Some((previous_id, previous_version)) = - self.versioned_cache.push(object.0, version) + self.versioned_cache.push(object.id(), version) { - if previous_id == object.0 && previous_version > version { + if previous_id == object.id() && previous_version > version { // do not allow highest known version to decrease // This should not be possible unless bugs are introduced elsewhere in this // module. - self.versioned_cache.put(object.0, previous_version); + self.versioned_cache.put(object.id(), previous_version); } else { self.metrics .transaction_manager_object_cache_evictions @@ -158,11 +158,11 @@ impl CacheInner { self.metrics .transaction_manager_object_cache_size .set(self.versioned_cache.len() as i64); - } else if let Some((previous_id, _)) = self.unversioned_cache.push(object.0, ()) { + } else if let Some((previous_id, _)) = self.unversioned_cache.push(object.id(), ()) { // lru_cache will does not check if the value being evicted is the same as the value // being inserted, so we do need to check if the id is different before counting this // as an eviction. - if previous_id != object.0 { + if previous_id != object.id() { self.metrics .transaction_manager_package_cache_evictions .inc(); @@ -176,8 +176,8 @@ impl CacheInner { // Returns Some(true/false) for a definitive result. Returns None if the caller must defer to // the db. fn is_object_available(&mut self, object: &InputKey) -> Option { - if let Some(version) = object.1 { - if let Some(current) = self.versioned_cache.get(&object.0) { + if let Some(version) = object.version() { + if let Some(current) = self.versioned_cache.get(&object.id()) { self.metrics.transaction_manager_object_cache_hits.inc(); Some(*current >= version) } else { @@ -186,7 +186,7 @@ impl CacheInner { } } else { self.unversioned_cache - .get(&object.0) + .get(&object.id()) .tap_some(|_| self.metrics.transaction_manager_package_cache_hits.inc()) .tap_none(|| self.metrics.transaction_manager_package_cache_misses.inc()) .map(|_| true) @@ -305,15 +305,18 @@ impl Inner { return ready_certificates; } - let input_count = self.input_objects.get_mut(&input_key.0).unwrap_or_else(|| { - panic!( - "# of transactions waiting on object {:?} cannot be 0", - input_key.0 - ) - }); + let input_count = self + .input_objects + .get_mut(&input_key.id()) + .unwrap_or_else(|| { + panic!( + "# of transactions waiting on object {:?} cannot be 0", + input_key.id() + ) + }); *input_count -= digests.len(); if *input_count == 0 { - self.input_objects.remove(&input_key.0); + self.input_objects.remove(&input_key.id()); } for digest in digests { @@ -461,17 +464,34 @@ impl TransactionManager { .value .input_objects() .expect("input_objects() cannot fail"); - let input_object_locks = self.authority_store.get_input_object_locks( + let mut input_object_locks = self.authority_store.get_input_object_locks( &digest, &input_object_kinds, epoch_store, ); + if input_object_kinds.len() != input_object_locks.len() { error!("Duplicated input objects: {:?}", input_object_kinds); } + + let receiving_object_entries = cert + .data() + .intent_message() + .value + .receiving_objects() + .expect("receiving_objects() cannot fail"); + for entry in receiving_object_entries { + let key = InputKey::Receiving { + id: entry.0, + version: entry.1, + }; + input_object_locks.insert(key, LockMode::Default); + } + for key in input_object_locks.keys() { object_availability.insert(*key, None); } + (cert, fx_digest, input_object_locks) }) .collect(); @@ -497,7 +517,10 @@ impl TransactionManager { // So missing objects' availability are checked again after releasing the TM lock. let cache_miss_availibility = self .authority_store - .multi_input_objects_exist(input_object_cache_misses.iter().cloned()) + .multi_input_objects_available( + input_object_cache_misses.iter().cloned(), + epoch_store.epoch(), + ) .expect("Checking object existence cannot fail!") .into_iter() .zip(input_object_cache_misses.into_iter()); @@ -511,7 +534,7 @@ impl TransactionManager { let _scope = monitored_scope("TransactionManager::enqueue::wlock"); for (available, key) in cache_miss_availibility { - if available && key.1.is_none() { + if available && key.version().is_none() { // Mutable objects obtained from cache_miss_availability usually will not be read // again, so we do not want to evict other objects in order to insert them into the // cache. However, packages will likely be read often, so we do want to insert them @@ -635,7 +658,7 @@ impl TransactionManager { } if acquire { pending_cert.acquiring_locks.insert(key, lock_mode); - let input_count = inner.input_objects.entry(key.0).or_default(); + let input_count = inner.input_objects.entry(key.id()).or_default(); *input_count += 1; } else { pending_cert.acquired_locks.insert(key, lock_mode); @@ -795,15 +818,11 @@ impl TransactionManager { let cert = pending_certificate.certificate; let expected_effects_digest = pending_certificate.expected_effects_digest; trace!(tx_digest = ?cert.digest(), "certificate ready"); + let tx_data = &cert.data().intent_message().value; // Record as an executing certificate. assert_eq!( pending_certificate.acquired_locks.len(), - cert.data() - .intent_message() - .value - .input_objects() - .unwrap() - .len() + tx_data.input_objects().unwrap().len() + tx_data.receiving_objects().unwrap().len(), ); assert!(inner .executing_certificates @@ -915,7 +934,7 @@ mod test { // insert 10 unique unversioned objects for i in 0..10 { let object = ObjectID::new([i; 32]); - let input_key = InputKey(object, None); + let input_key = InputKey::Package { id: object }; assert_eq!(cache.is_object_available(&input_key), None); cache.insert(&input_key); assert_eq!(cache.is_object_available(&input_key), Some(true)); @@ -924,14 +943,17 @@ mod test { // first 5 have been evicted for i in 0..5 { let object = ObjectID::new([i; 32]); - let input_key = InputKey(object, None); + let input_key = InputKey::Package { id: object }; assert_eq!(cache.is_object_available(&input_key), None); } // insert 10 unique versioned objects for i in 0..10 { let object = ObjectID::new([i; 32]); - let input_key = InputKey(object, Some((i as u64).into())); + let input_key = InputKey::Versioned { + id: object, + version: (i as u64).into(), + }; assert_eq!(cache.is_object_available(&input_key), None); cache.insert(&input_key); assert_eq!(cache.is_object_available(&input_key), Some(true)); @@ -940,26 +962,38 @@ mod test { // first 5 versioned objects have been evicted for i in 0..5 { let object = ObjectID::new([i; 32]); - let input_key = InputKey(object, Some((i as u64).into())); + let input_key = InputKey::Versioned { + id: object, + version: (i as u64).into(), + }; assert_eq!(cache.is_object_available(&input_key), None); } // but versioned objects do not cause evictions of unversioned objects for i in 5..10 { let object = ObjectID::new([i; 32]); - let input_key = InputKey(object, None); + let input_key = InputKey::Package { id: object }; assert_eq!(cache.is_object_available(&input_key), Some(true)); } // object 9 is available at version 9 let object = ObjectID::new([9; 32]); - let input_key = InputKey(object, Some(9.into())); + let input_key = InputKey::Versioned { + id: object, + version: 9.into(), + }; assert_eq!(cache.is_object_available(&input_key), Some(true)); // but not at version 10 - let input_key = InputKey(object, Some(10.into())); + let input_key = InputKey::Versioned { + id: object, + version: 10.into(), + }; assert_eq!(cache.is_object_available(&input_key), Some(false)); // it is available at version 8 (this case can be used by readonly shared objects) - let input_key = InputKey(object, Some(8.into())); + let input_key = InputKey::Versioned { + id: object, + version: 8.into(), + }; assert_eq!(cache.is_object_available(&input_key), Some(true)); } } diff --git a/crates/sui-core/src/transaction_signing_filter.rs b/crates/sui-core/src/transaction_signing_filter.rs index 3b7bec33d0d1a..8ec58509b4471 100644 --- a/crates/sui-core/src/transaction_signing_filter.rs +++ b/crates/sui-core/src/transaction_signing_filter.rs @@ -3,6 +3,7 @@ use sui_config::transaction_deny_config::TransactionDenyConfig; use sui_types::{ + base_types::ObjectRef, error::{SuiError, SuiResult, UserInputError}, storage::BackingPackageStore, transaction::{Command, InputObjectKind, TransactionData, TransactionDataAPI}, @@ -29,6 +30,7 @@ macro_rules! deny_if_true { pub fn check_transaction_for_signing( tx_data: &TransactionData, input_objects: &[InputObjectKind], + receiving_objects: &[ObjectRef], filter_config: &TransactionDenyConfig, package_store: &impl BackingPackageStore, ) -> SuiResult { @@ -40,6 +42,25 @@ pub fn check_transaction_for_signing( check_package_dependencies(filter_config, tx_data, package_store)?; + check_receiving_objects(filter_config, receiving_objects)?; + + Ok(()) +} + +fn check_receiving_objects( + filter_config: &TransactionDenyConfig, + receiving_objects: &[ObjectRef], +) -> SuiResult { + deny_if_true!( + filter_config.receiving_objects_disabled() && !receiving_objects.is_empty(), + "Receiving objects is temporarily disabled".to_string() + ); + for (id, _, _) in receiving_objects { + deny_if_true!( + filter_config.get_object_deny_set().contains(id), + format!("Access to object {:?} is temporarily disabled", id) + ); + } Ok(()) } diff --git a/crates/sui-core/src/unit_tests/data/tto/Move.toml b/crates/sui-core/src/unit_tests/data/tto/Move.toml new file mode 100644 index 0000000000000..162ee2126f44c --- /dev/null +++ b/crates/sui-core/src/unit_tests/data/tto/Move.toml @@ -0,0 +1,9 @@ +[package] +name = "tto" +version = "0.0.1" + +[dependencies] +Sui = { local = "../../../../../sui-framework/packages/sui-framework" } + +[addresses] +tto = "0x0" diff --git a/crates/sui-core/src/unit_tests/data/tto/sources/tto1.move b/crates/sui-core/src/unit_tests/data/tto/sources/tto1.move new file mode 100644 index 0000000000000..26b0fe2fcdf58 --- /dev/null +++ b/crates/sui-core/src/unit_tests/data/tto/sources/tto1.move @@ -0,0 +1,40 @@ +module tto::M1 { + use sui::object::{Self, UID}; + use sui::tx_context::{Self, TxContext}; + use sui::transfer::{Self, Receiving}; + + struct A has key, store { + id: UID, + } + + struct B has key, store { + id: UID, + } + + public fun start(ctx: &mut TxContext) { + let a = A { id: object::new(ctx) }; + let a_address = object::id_address(&a); + let b = B { id: object::new(ctx) }; + transfer::public_transfer(a, tx_context::sender(ctx)); + transfer::public_transfer(b, a_address); + } + + public entry fun receiver(parent: &mut A, x: Receiving) { + let b = transfer::receive(&mut parent.id, x); + transfer::public_transfer(b, @tto); + } + + public entry fun send_back(parent: &mut A, x: Receiving) { + let b = transfer::receive(&mut parent.id, x); + let parent_address = object::id_address(parent); + transfer::public_transfer(b, parent_address); + } + + public entry fun deleter(parent: &mut A, x: Receiving) { + let B { id } = transfer::receive(&mut parent.id, x); + object::delete(id); + } + + public fun call_immut_ref(_parent: &mut A, _x: &Receiving) { } + public fun call_mut_ref(_parent: &mut A, _x: &mut Receiving) { } +} diff --git a/crates/sui-core/src/unit_tests/move_package_upgrade_tests.rs b/crates/sui-core/src/unit_tests/move_package_upgrade_tests.rs index 366cc99e56d01..94315b57b77ae 100644 --- a/crates/sui-core/src/unit_tests/move_package_upgrade_tests.rs +++ b/crates/sui-core/src/unit_tests/move_package_upgrade_tests.rs @@ -28,6 +28,7 @@ use crate::authority::{ move_integration_tests::build_and_publish_test_package_with_upgrade_cap, AuthorityState, }; +#[macro_export] macro_rules! move_call { {$builder:expr, ($addr:expr)::$module_name:ident::$func:ident($($args:expr),* $(,)?)} => { $builder.programmable_move_call( @@ -102,7 +103,7 @@ pub fn build_upgrade_txn( builder.finish() } -struct UpgradeStateRunner { +pub struct UpgradeStateRunner { pub sender: SuiAddress, pub sender_key: AccountKeyPair, pub gas_object_id: ObjectID, @@ -114,10 +115,6 @@ struct UpgradeStateRunner { impl UpgradeStateRunner { pub async fn new(base_package_name: &str) -> Self { telemetry_subscribers::init_for_testing(); - let _dont_remove = ProtocolConfig::apply_overrides_for_testing(|_, mut config| { - config.set_package_upgrades_for_testing(true); - config - }); let (sender, sender_key): (_, AccountKeyPair) = get_key_pair(); let gas_object_id = ObjectID::random(); let gas_object = Object::with_id_owner_for_testing(gas_object_id, sender); diff --git a/crates/sui-core/src/unit_tests/transaction_manager_tests.rs b/crates/sui-core/src/unit_tests/transaction_manager_tests.rs index 447c8d82a7b3d..6b18a11914f40 100644 --- a/crates/sui-core/src/unit_tests/transaction_manager_tests.rs +++ b/crates/sui-core/src/unit_tests/transaction_manager_tests.rs @@ -64,7 +64,10 @@ fn make_transaction(gas_object: Object, input: Vec) -> VerifiedExecutab fn get_input_keys(objects: &[Object]) -> Vec { objects .iter() - .map(|object| InputKey(object.id(), Some(object.version()))) + .map(|object| InputKey::Versioned { + id: object.id(), + version: object.version(), + }) .collect() } @@ -251,7 +254,10 @@ async fn transaction_manager_read_lock() { // Notify TM about availability of the shared object. transaction_manager.objects_available( - vec![InputKey(shared_object.id(), Some(shared_version))], + vec![InputKey::Versioned { + id: shared_object.id(), + version: shared_version, + }], &state.epoch_store_for_testing(), ); diff --git a/crates/sui-core/src/unit_tests/transfer_to_object_tests.rs b/crates/sui-core/src/unit_tests/transfer_to_object_tests.rs new file mode 100644 index 0000000000000..ce55639db0dae --- /dev/null +++ b/crates/sui-core/src/unit_tests/transfer_to_object_tests.rs @@ -0,0 +1,260 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use std::collections::HashSet; + +use sui_types::{ + base_types::{ObjectID, ObjectRef, SuiAddress}, + effects::TransactionEffects, + object::Owner, + programmable_transaction_builder::ProgrammableTransactionBuilder, + transaction::ObjectArg, +}; + +use crate::{move_call, move_package_upgrade_tests::UpgradeStateRunner}; +use move_core_types::ident_str; + +// The primary use for these tests is to make sure the generated effect sets match what we expect +// when receiving an object. + +fn get_parent_and_child( + created: &[(ObjectRef, Owner)], +) -> (&(ObjectRef, Owner), &(ObjectRef, Owner)) { + // make sure there is an object with an `AddressOwner` who matches the object ID of another + // object. + let created_addrs: HashSet<_> = created.iter().map(|((i, _, _), _)| i).collect(); + let child = created + .iter() + .find(|(_, owner)| match owner { + Owner::AddressOwner(j) => created_addrs.contains(&ObjectID::from(*j)), + _ => false, + }) + .unwrap(); + let parent = created + .iter() + .find(|(_, owner)| match owner { + Owner::AddressOwner(j) => !created_addrs.contains(&ObjectID::from(*j)), + _ => false, + }) + .unwrap(); + (parent, child) +} + +#[tokio::test] +async fn test_tto_transfer() { + let mut runner = UpgradeStateRunner::new("tto").await; + let TransactionEffects::V1(effects) = runner + .run({ + let mut builder = ProgrammableTransactionBuilder::new(); + move_call! { + builder, + (runner.package.0)::M1::start() + }; + builder.finish() + }) + .await; + + let (parent, child) = get_parent_and_child(&effects.created); + + // No receive the sent object + let TransactionEffects::V1(effects) = runner + .run({ + let mut builder = ProgrammableTransactionBuilder::new(); + let parent = builder.obj(ObjectArg::ImmOrOwnedObject(parent.0)).unwrap(); + let child = builder.obj(ObjectArg::Receiving(child.0)).unwrap(); + move_call! { + builder, + (runner.package.0)::M1::receiver(parent, child) + }; + builder.finish() + }) + .await; + + assert!(effects.status.is_ok()); + assert!(effects.created.is_empty()); + assert!(effects.unwrapped.is_empty()); + assert!(effects.deleted.is_empty()); + assert!(effects.unwrapped_then_deleted.is_empty()); + assert!(effects.wrapped.is_empty()); + + for (obj_ref, owner) in effects.mutated.iter() { + if obj_ref.0 == child.0 .0 { + // Child should be sent to 0x0 + assert_eq!(owner, &Owner::AddressOwner(SuiAddress::ZERO)); + // It's version should be bumped as well + assert!(obj_ref.1 > child.0 .1); + } + if obj_ref.0 == parent.0 .0 { + // owner of the parent stays the same + assert_eq!(owner, &parent.1); + // parent version is also bumped + assert!(obj_ref.1 > parent.0 .1); + } + } +} + +#[tokio::test] +async fn test_tto_unused_receiver() { + let mut runner = UpgradeStateRunner::new("tto").await; + let TransactionEffects::V1(effects) = runner + .run({ + let mut builder = ProgrammableTransactionBuilder::new(); + move_call! { + builder, + (runner.package.0)::M1::start() + }; + builder.finish() + }) + .await; + + let (parent, child) = get_parent_and_child(&effects.created); + + let TransactionEffects::V1(effects) = runner + .run({ + let mut builder = ProgrammableTransactionBuilder::new(); + builder.obj(ObjectArg::ImmOrOwnedObject(parent.0)).unwrap(); + builder.obj(ObjectArg::Receiving(child.0)).unwrap(); + builder.finish() + }) + .await; + + assert!(effects.status.is_ok()); + assert!(effects.created.is_empty()); + assert!(effects.unwrapped.is_empty()); + assert!(effects.deleted.is_empty()); + assert!(effects.unwrapped_then_deleted.is_empty()); + assert!(effects.wrapped.is_empty()); + + // If the receiving argument is not used it should not be modified! + assert!(!effects + .modified_at_versions + .iter() + .any(|(i, _)| i == &child.0 .0)); + // Since the parent was not used but it was an input object, it should be modified + assert!(effects + .modified_at_versions + .iter() + .any(|(i, _)| i == &parent.0 .0)); + + // Make sure parent exists in mutated, and the version is bumped. + for (obj_ref, owner) in effects.mutated.iter() { + if obj_ref.0 == parent.0 .0 { + // owner of the parent stays the same + assert_eq!(owner, &parent.1); + // parent version is also bumped + assert!(obj_ref.1 > parent.0 .1); + } + } +} + +#[tokio::test] +async fn test_tto_pass_receiving_by_refs() { + let mut runner = UpgradeStateRunner::new("tto").await; + let TransactionEffects::V1(effects) = runner + .run({ + let mut builder = ProgrammableTransactionBuilder::new(); + move_call! { + builder, + (runner.package.0)::M1::start() + }; + builder.finish() + }) + .await; + + let (parent, child) = get_parent_and_child(&effects.created); + + let TransactionEffects::V1(effects) = runner + .run({ + let mut builder = ProgrammableTransactionBuilder::new(); + let parent = builder.obj(ObjectArg::ImmOrOwnedObject(parent.0)).unwrap(); + let child = builder.obj(ObjectArg::Receiving(child.0)).unwrap(); + move_call! { + builder, + (runner.package.0)::M1::call_immut_ref(parent, child) + }; + move_call! { + builder, + (runner.package.0)::M1::call_mut_ref(parent, child) + }; + builder.finish() + }) + .await; + + assert!(effects.status.is_ok()); + assert!(effects.created.is_empty()); + assert!(effects.unwrapped.is_empty()); + assert!(effects.deleted.is_empty()); + assert!(effects.unwrapped_then_deleted.is_empty()); + assert!(effects.wrapped.is_empty()); + + // If the receiving argument is not used it should not be modified! + assert!(!effects + .modified_at_versions + .iter() + .any(|(i, _)| i == &child.0 .0)); + // Since the parent was not used but it was an input object, it should be modified + assert!(effects + .modified_at_versions + .iter() + .any(|(i, _)| i == &parent.0 .0)); + + // Make sure parent exists in mutated, and the version is bumped. + for (obj_ref, owner) in effects.mutated.iter() { + if obj_ref.0 == parent.0 .0 { + // owner of the parent stays the same + assert_eq!(owner, &parent.1); + // parent version is also bumped + assert!(obj_ref.1 > parent.0 .1); + } + } +} + +#[tokio::test] +async fn test_tto_delete() { + let mut runner = UpgradeStateRunner::new("tto").await; + let TransactionEffects::V1(effects) = runner + .run({ + let mut builder = ProgrammableTransactionBuilder::new(); + move_call! { + builder, + (runner.package.0)::M1::start() + }; + builder.finish() + }) + .await; + + let (parent, child) = get_parent_and_child(&effects.created); + + let TransactionEffects::V1(effects) = runner + .run({ + let mut builder = ProgrammableTransactionBuilder::new(); + let parent = builder.obj(ObjectArg::ImmOrOwnedObject(parent.0)).unwrap(); + let child = builder.obj(ObjectArg::Receiving(child.0)).unwrap(); + move_call! { + builder, + (runner.package.0)::M1::deleter(parent, child) + }; + builder.finish() + }) + .await; + + assert!(effects.status.is_ok()); + assert!(effects.created.is_empty()); + assert!(effects.unwrapped.is_empty()); + assert!(effects.unwrapped_then_deleted.is_empty()); + assert!(effects.wrapped.is_empty()); + // Deleted should be non-empty + assert_eq!(effects.deleted.len(), 1); + // Deleted should contain the child object + assert_eq!(effects.deleted[0].0, child.0 .0); + + // Make sure parent exists in mutated, and the version is bumped. + for (obj_ref, owner) in effects.mutated.iter() { + if obj_ref.0 == parent.0 .0 { + // owner of the parent stays the same + assert_eq!(owner, &parent.1); + // parent version is also bumped + assert!(obj_ref.1 > parent.0 .1); + } + } +} diff --git a/crates/sui-core/tests/staged/sui.yaml b/crates/sui-core/tests/staged/sui.yaml index 156d44223b81b..3804c9efcd250 100644 --- a/crates/sui-core/tests/staged/sui.yaml +++ b/crates/sui-core/tests/staged/sui.yaml @@ -581,6 +581,13 @@ ObjectArg: - initial_shared_version: TYPENAME: SequenceNumber - mutable: BOOL + 2: + Receiving: + NEWTYPE: + TUPLE: + - TYPENAME: ObjectID + - TYPENAME: SequenceNumber + - TYPENAME: ObjectDigest ObjectDigest: NEWTYPESTRUCT: TYPENAME: Digest diff --git a/crates/sui-framework/docs/transfer.md b/crates/sui-framework/docs/transfer.md index b3619cc7d3c90..9764e412b4eda 100644 --- a/crates/sui-framework/docs/transfer.md +++ b/crates/sui-framework/docs/transfer.md @@ -5,6 +5,7 @@ +- [Struct `Receiving`](#0x2_transfer_Receiving) - [Constants](#@Constants_0) - [Function `transfer`](#0x2_transfer_transfer) - [Function `public_transfer`](#0x2_transfer_public_transfer) @@ -12,14 +13,52 @@ - [Function `public_freeze_object`](#0x2_transfer_public_freeze_object) - [Function `share_object`](#0x2_transfer_share_object) - [Function `public_share_object`](#0x2_transfer_public_share_object) +- [Function `receive`](#0x2_transfer_receive) - [Function `freeze_object_impl`](#0x2_transfer_freeze_object_impl) - [Function `share_object_impl`](#0x2_transfer_share_object_impl) - [Function `transfer_impl`](#0x2_transfer_transfer_impl) +- [Function `receive_impl`](#0x2_transfer_receive_impl) -
+
use 0x2::object;
+
+ + + + + +## Struct `Receiving` + +This represents an ability to receive an object of type T. +Internals of this struct are opaque outside this module. + + +
struct Receiving<T: key> has drop
+
+ + + +
+Fields + +
+
+id: object::ID +
+
+
+
+version: u64 +
+
+ +
+
+ + +
@@ -211,6 +250,37 @@ The object must have store to be shared outside of its module. + + + + +## Function `receive` + +Given mutable (i.e., locked) access to the parent and a Receiving +argument referencing an object owned by parent use the to_receive argument +to receive and return the corresponding owned object. + + +
public fun receive<T: key>(parent: &mut object::UID, to_receive: transfer::Receiving<T>): T
+
+ + + +
+Implementation + + +
public fun receive<T: key>(parent: &mut object::UID, to_receive: Receiving<T>): T {
+    let Receiving {
+        id,
+        version,
+    } = to_receive;
+    receive_impl(object::uid_to_address(parent), id, version)
+}
+
+ + +
@@ -326,4 +396,26 @@ The object must have store to be shared outside of its module. + + + + +## Function `receive_impl` + + + +
fun receive_impl<T: key>(parent: address, to_receive: object::ID, version: u64): T
+
+ + + +
+Implementation + + +
native fun receive_impl<T: key>(parent: address, to_receive: object::ID, version: u64): T;
+
+ + +
diff --git a/crates/sui-framework/packages/sui-framework/sources/transfer.move b/crates/sui-framework/packages/sui-framework/sources/transfer.move index 40c5bd438f9f9..1b349edd867a7 100644 --- a/crates/sui-framework/packages/sui-framework/sources/transfer.move +++ b/crates/sui-framework/packages/sui-framework/sources/transfer.move @@ -3,12 +3,18 @@ module sui::transfer { - use sui::object; + use sui::object::{Self, ID}; use sui::prover; #[test_only] friend sui::test_scenario; + /// This represents an ability to `receive` an object of type `T`. + /// Internals of this struct are opaque outside this module. + struct Receiving has drop { + id: ID, + version: u64, + } /// Shared an object that was previously created. Shared objects must currently /// be constructed in the transaction they are created. @@ -70,6 +76,17 @@ module sui::transfer { share_object_impl(obj) } + /// Given mutable (i.e., locked) access to the `parent` and a `Receiving` + /// argument referencing an object owned by `parent` use the `to_receive` argument + /// to receive and return the corresponding owned object. + public fun receive(parent: &mut object::UID, to_receive: Receiving): T { + let Receiving { + id, + version, + } = to_receive; + receive_impl(object::uid_to_address(parent), id, version) + } + public(friend) native fun freeze_object_impl(obj: T); spec freeze_object_impl { @@ -107,4 +124,6 @@ module sui::transfer { ensures [abstract] global(object::id(obj).bytes).owner == recipient; ensures [abstract] global(object::id(obj).bytes).status == prover::OWNED; } + + native fun receive_impl(parent: address, to_receive: object::ID, version: u64): T; } diff --git a/crates/sui-json-rpc-types/src/sui_transaction.rs b/crates/sui-json-rpc-types/src/sui_transaction.rs index 5dcffad036f2b..c4851e27c1e20 100644 --- a/crates/sui-json-rpc-types/src/sui_transaction.rs +++ b/crates/sui-json-rpc-types/src/sui_transaction.rs @@ -1530,6 +1530,13 @@ impl SuiCallArg { initial_shared_version, mutable, }), + // TODO(tzakian)[tto] + CallArg::Object(ObjectArg::Receiving(_obj_ref)) => std::todo!( + "Implement SuiCallArg::try_from for CallArg::Object(ObjectArg::Receiving(obj_ref))" + ), + // { + // SuiCallArg::Object(SuiObjectArg::Receiving(SuiObjectRef::from(obj_ref))) + // } }) } @@ -1594,6 +1601,10 @@ pub enum SuiObjectArg { initial_shared_version: SequenceNumber, mutable: bool, }, + // TODO(tzakian)[tto]: uncomment this when we are ready to expose this to the RPC interface. + // A reference to a Move object that's going to be received in the transaction. + // #[serde(rename_all = "camelCase")] + // Receiving(SuiObjectRef), } #[serde_as] diff --git a/crates/sui-move/src/unit_test.rs b/crates/sui-move/src/unit_test.rs index 71dad7575e473..107d29adb9e91 100644 --- a/crates/sui-move/src/unit_test.rs +++ b/crates/sui-move/src/unit_test.rs @@ -121,6 +121,7 @@ fn new_testing_object_and_natives_cost_runtime(ext: &mut NativeContextExtensions false, &ProtocolConfig::get_for_min_version(), metrics, + 0, // epoch id )); ext.add(NativesCostTable::from_protocol_config( &ProtocolConfig::get_for_min_version(), diff --git a/crates/sui-open-rpc/spec/openrpc.json b/crates/sui-open-rpc/spec/openrpc.json index acd90517178cd..a2223353b46cc 100644 --- a/crates/sui-open-rpc/spec/openrpc.json +++ b/crates/sui-open-rpc/spec/openrpc.json @@ -1365,6 +1365,7 @@ "no_extraneous_module_bytes": false, "package_digest_hash_module": false, "package_upgrades": true, + "receive_objects": false, "scoring_decision_with_validity_cutoff": true, "simplified_unwrap_then_delete": false, "txn_base_cost_as_multiplier": false, @@ -1826,6 +1827,7 @@ "transfer_freeze_object_cost_base": { "u64": "52" }, + "transfer_receive_object_cost_base": null, "transfer_share_object_cost_base": { "u64": "52" }, diff --git a/crates/sui-protocol-config/src/lib.rs b/crates/sui-protocol-config/src/lib.rs index bb8a839a8b972..bba2e25bbb9cc 100644 --- a/crates/sui-protocol-config/src/lib.rs +++ b/crates/sui-protocol-config/src/lib.rs @@ -246,6 +246,10 @@ struct FeatureFlags { // If true, then the new algorithm for the leader election schedule will be used #[serde(skip_serializing_if = "is_false")] narwhal_new_leader_election_schedule: bool, + + // Enable receiving sent objects + #[serde(skip_serializing_if = "is_false")] + receive_objects: bool, } fn is_false(b: &bool) -> bool { @@ -605,6 +609,9 @@ pub struct ProtocolConfig { transfer_freeze_object_cost_base: Option, // Cost params for the Move native function `share_object(obj: T)` transfer_share_object_cost_base: Option, + // Cost params for the Move native function + // `receive_object(p: &mut UID, recv: ReceivingT)` + transfer_receive_object_cost_base: Option, // TxContext // Cost params for the Move native function `transfer_impl(obj: T, recipient: address)` @@ -738,6 +745,17 @@ impl ProtocolConfig { } } + pub fn check_receiving_objects_supported(&self) -> Result<(), Error> { + if self.feature_flags.receive_objects { + Ok(()) + } else { + Err(Error(format!( + "receiving objects is not support at {:?}", + self.version + ))) + } + } + pub fn package_upgrades_supported(&self) -> bool { self.feature_flags.package_upgrades } @@ -1063,6 +1081,7 @@ impl ProtocolConfig { transfer_freeze_object_cost_base: Some(52), // Cost params for the Move native function `share_object(obj: T)` transfer_share_object_cost_base: Some(52), + transfer_receive_object_cost_base: None, // `tx_context` module // Cost params for the Move native function `transfer_impl(obj: T, recipient: address)` @@ -1314,6 +1333,10 @@ impl ProtocolConfig { cfg.feature_flags.txn_base_cost_as_multiplier = true; // this is a multiplier of the gas price cfg.base_tx_cost_fixed = Some(1_000); + // TODO(tzakian)[tto] This should only be set in the protocol version that we + // release with. + cfg.transfer_receive_object_cost_base = Some(52); + cfg.feature_flags.receive_objects = true; cfg } // Use this template when making changes: diff --git a/crates/sui-protocol-config/src/snapshots/sui_protocol_config__test__Mainnet_version_18.snap b/crates/sui-protocol-config/src/snapshots/sui_protocol_config__test__Mainnet_version_18.snap index a0ef828c1dce8..f2be0cfc84618 100644 --- a/crates/sui-protocol-config/src/snapshots/sui_protocol_config__test__Mainnet_version_18.snap +++ b/crates/sui-protocol-config/src/snapshots/sui_protocol_config__test__Mainnet_version_18.snap @@ -22,6 +22,7 @@ feature_flags: simplified_unwrap_then_delete: true upgraded_multisig_supported: true txn_base_cost_as_multiplier: true + receive_objects: true max_tx_size_bytes: 131072 max_input_objects: 2048 max_size_written_objects: 5000000 @@ -121,6 +122,7 @@ object_record_new_uid_cost_base: 52 transfer_transfer_internal_cost_base: 52 transfer_freeze_object_cost_base: 52 transfer_share_object_cost_base: 52 +transfer_receive_object_cost_base: 52 tx_context_derive_id_cost_base: 52 types_is_one_time_witness_cost_base: 52 types_is_one_time_witness_type_tag_cost_per_byte: 2 diff --git a/crates/sui-protocol-config/src/snapshots/sui_protocol_config__test__Testnet_version_18.snap b/crates/sui-protocol-config/src/snapshots/sui_protocol_config__test__Testnet_version_18.snap index 0ca6c89d356d2..5f237ce4689b6 100644 --- a/crates/sui-protocol-config/src/snapshots/sui_protocol_config__test__Testnet_version_18.snap +++ b/crates/sui-protocol-config/src/snapshots/sui_protocol_config__test__Testnet_version_18.snap @@ -23,6 +23,7 @@ feature_flags: simplified_unwrap_then_delete: true upgraded_multisig_supported: true txn_base_cost_as_multiplier: true + receive_objects: true max_tx_size_bytes: 131072 max_input_objects: 2048 max_size_written_objects: 5000000 @@ -122,6 +123,7 @@ object_record_new_uid_cost_base: 52 transfer_transfer_internal_cost_base: 52 transfer_freeze_object_cost_base: 52 transfer_share_object_cost_base: 52 +transfer_receive_object_cost_base: 52 tx_context_derive_id_cost_base: 52 types_is_one_time_witness_cost_base: 52 types_is_one_time_witness_type_tag_cost_per_byte: 2 diff --git a/crates/sui-protocol-config/src/snapshots/sui_protocol_config__test__version_18.snap b/crates/sui-protocol-config/src/snapshots/sui_protocol_config__test__version_18.snap index 947b490e10bd6..21fea58728efa 100644 --- a/crates/sui-protocol-config/src/snapshots/sui_protocol_config__test__version_18.snap +++ b/crates/sui-protocol-config/src/snapshots/sui_protocol_config__test__version_18.snap @@ -24,6 +24,7 @@ feature_flags: simplified_unwrap_then_delete: true upgraded_multisig_supported: true txn_base_cost_as_multiplier: true + receive_objects: true max_tx_size_bytes: 131072 max_input_objects: 2048 max_size_written_objects: 5000000 @@ -123,6 +124,7 @@ object_record_new_uid_cost_base: 52 transfer_transfer_internal_cost_base: 52 transfer_freeze_object_cost_base: 52 transfer_share_object_cost_base: 52 +transfer_receive_object_cost_base: 52 tx_context_derive_id_cost_base: 52 types_is_one_time_witness_cost_base: 52 types_is_one_time_witness_type_tag_cost_per_byte: 2 diff --git a/crates/sui-replay/src/replay.rs b/crates/sui-replay/src/replay.rs index 3c0ebe2cff0c4..eb2d8cb704ebd 100644 --- a/crates/sui-replay/src/replay.rs +++ b/crates/sui-replay/src/replay.rs @@ -1682,6 +1682,48 @@ impl ChildObjectResolver for LocalExec { ); res } + + fn receive_object_at_version( + &self, + owner: &ObjectID, + receiving_object_id: &ObjectID, + receive_object_at_version: SequenceNumber, + _epoch_id: EpochId, + ) -> SuiResult> { + fn inner( + self_: &LocalExec, + owner: &ObjectID, + receiving_object_id: &ObjectID, + receive_object_at_version: SequenceNumber, + ) -> SuiResult> { + let recv_object = match self_.get_object(receiving_object_id)? { + None => return Ok(None), + Some(o) => o, + }; + if recv_object.version() != receive_object_at_version { + return Err(SuiError::Unknown(format!( + "Invariant Violation. Replay loaded child_object {receiving_object_id} at version \ + {receive_object_at_version} but expected the version to be == {receive_object_at_version}" + ))); + } + if recv_object.owner != Owner::AddressOwner((*owner).into()) { + return Ok(None); + } + Ok(Some(recv_object)) + } + + let res = inner(self, owner, receiving_object_id, receive_object_at_version); + self.exec_store_events + .lock() + .expect("Unable to lock events list") + .push(ExecutionStoreEvent::ReceiveObject { + owner: *owner, + receive: *receiving_object_id, + receive_at_version: receive_object_at_version, + result: res.clone(), + }); + res + } } impl ParentSync for LocalExec { diff --git a/crates/sui-replay/src/types.rs b/crates/sui-replay/src/types.rs index 6b9d815905450..0d1624ec7ebff 100644 --- a/crates/sui-replay/src/types.rs +++ b/crates/sui-replay/src/types.rs @@ -276,4 +276,10 @@ pub enum ExecutionStoreEvent { id: ModuleId, result: SuiResult>, }, + ReceiveObject { + owner: ObjectID, + receive: ObjectID, + receive_at_version: SequenceNumber, + result: SuiResult>, + }, } diff --git a/crates/sui-swarm-config/tests/snapshots/snapshot_tests__network_config_snapshot_matches.snap b/crates/sui-swarm-config/tests/snapshots/snapshot_tests__network_config_snapshot_matches.snap index 9d209f7d9c6b3..41f0b9d8b936b 100644 --- a/crates/sui-swarm-config/tests/snapshots/snapshot_tests__network_config_snapshot_matches.snap +++ b/crates/sui-swarm-config/tests/snapshots/snapshot_tests__network_config_snapshot_matches.snap @@ -79,6 +79,7 @@ validator_configs: package_upgrade_disabled: false shared_object_disabled: false user_transaction_disabled: false + receiving_objects_disabled: false certificate-deny-config: {} state-debug-dump-config: {} state-archive-write-config: @@ -163,6 +164,7 @@ validator_configs: package_upgrade_disabled: false shared_object_disabled: false user_transaction_disabled: false + receiving_objects_disabled: false certificate-deny-config: {} state-debug-dump-config: {} state-archive-write-config: @@ -247,6 +249,7 @@ validator_configs: package_upgrade_disabled: false shared_object_disabled: false user_transaction_disabled: false + receiving_objects_disabled: false certificate-deny-config: {} state-debug-dump-config: {} state-archive-write-config: @@ -331,6 +334,7 @@ validator_configs: package_upgrade_disabled: false shared_object_disabled: false user_transaction_disabled: false + receiving_objects_disabled: false certificate-deny-config: {} state-debug-dump-config: {} state-archive-write-config: @@ -415,6 +419,7 @@ validator_configs: package_upgrade_disabled: false shared_object_disabled: false user_transaction_disabled: false + receiving_objects_disabled: false certificate-deny-config: {} state-debug-dump-config: {} state-archive-write-config: @@ -499,6 +504,7 @@ validator_configs: package_upgrade_disabled: false shared_object_disabled: false user_transaction_disabled: false + receiving_objects_disabled: false certificate-deny-config: {} state-debug-dump-config: {} state-archive-write-config: @@ -583,6 +589,7 @@ validator_configs: package_upgrade_disabled: false shared_object_disabled: false user_transaction_disabled: false + receiving_objects_disabled: false certificate-deny-config: {} state-debug-dump-config: {} state-archive-write-config: diff --git a/crates/sui-swarm-config/tests/snapshots/snapshot_tests__populated_genesis_snapshot_matches-2.snap b/crates/sui-swarm-config/tests/snapshots/snapshot_tests__populated_genesis_snapshot_matches-2.snap index b0635730c8e1b..724cf10c4491a 100644 --- a/crates/sui-swarm-config/tests/snapshots/snapshot_tests__populated_genesis_snapshot_matches-2.snap +++ b/crates/sui-swarm-config/tests/snapshots/snapshot_tests__populated_genesis_snapshot_matches-2.snap @@ -240,13 +240,13 @@ validators: next_epoch_worker_address: ~ extra_fields: id: - id: "0xbc33d2e5c49afc1c1a9aeb606b5045285d62690045828621fc261b92ca044cb3" + id: "0xe258eafe6b00a84e6087f8758dd02091736a7c6ffbf898b05db7518b80a57f30" size: 0 voting_power: 10000 - operation_cap_id: "0x424a382efc96d984bcff9b1fe86e49e15f0a400b9379f0e3d1e8c97d593a3c4b" + operation_cap_id: "0xbf51cd998d4c6b67113dfdf429e0d99c220ffd1991c7ac7144bd692a5e7a0b37" gas_price: 1000 staking_pool: - id: "0x465b5b1e707a9d21c904271b55a38d9cd3907199e326b2df6f007c24137749f1" + id: "0x1a42a9c6280fa1c86cf6e6d400d8f7cc93ab4f9e9f83d3bfc1d2f577ba8eca8e" activation_epoch: 0 deactivation_epoch: ~ sui_balance: 20000000000000000 @@ -254,14 +254,14 @@ validators: value: 0 pool_token_balance: 20000000000000000 exchange_rates: - id: "0xfa04aab675f2d5c61ffcb5e46aae28180ba77fa451449d1387348e3a636a0c68" + id: "0x3b44b69c59a413b44f3d574afcba06622bf4f1db494fcf44186d6c42fe0ce64b" size: 1 pending_stake: 0 pending_total_sui_withdraw: 0 pending_pool_token_withdraw: 0 extra_fields: id: - id: "0xcbe6f3cb9b5c0b9bf844b7ed7869c3db76536a9a460972482dd3b7cb7f92462b" + id: "0xb95f04412f853853cdb4a23bb569814f3fec0db07d280592c9cab71823f6cd81" size: 0 commission_rate: 200 next_epoch_stake: 20000000000000000 @@ -269,27 +269,27 @@ validators: next_epoch_commission_rate: 200 extra_fields: id: - id: "0x7b9cd1165db6ca5241f2bdfc38261dbb8bb58b76fe94bae4df9068ee20316e07" + id: "0xde65ac285e38974ca08e8826cfe2075ca15bbeb1e2dcef5021407bbc55d3d739" size: 0 pending_active_validators: contents: - id: "0x86adf3cfca174b0e4f090acb6b052b2829b3703305f8011700ee979e74063187" + id: "0x344f7efa2d29fa3225f119dd0426afc0b22f8c5e127377ad92efac77170374fd" size: 0 pending_removals: [] staking_pool_mappings: - id: "0xe9d8f670db5dc0709ced9637228b87c638ec4bab3696f7e813ba5657be1e61b5" + id: "0x6c39405d11ac477fcb4c4f614997f536439ebe46f7fd939436ea340ddb82b23e" size: 1 inactive_validators: - id: "0x8f1f5b046a5af0d52762f0ef38280af3a338645389f18089466d67e159c29c46" + id: "0x891aa177f19ed92939b0607d33540538ad957a517c546231e91d5ec8eee5d57d" size: 0 validator_candidates: - id: "0x9b6d0ed963c8967d811a757ce710f37bb9af8f0618ab16191f76a8caef8b3ea5" + id: "0xe4184b1a0b619ee4b79888f8070c400c3512a14ea6091d7da16c08384a74021f" size: 0 at_risk_validators: contents: [] extra_fields: id: - id: "0x4b8cc15efb002e2c85c95cbf66e75b5c3b71542436a794df35a9e3993abb484e" + id: "0x61a99cb77f826beadc19fd21e2afdff13a464e5d28de95c2678bd10cbd714b58" size: 0 storage_fund: total_object_storage_rebates: @@ -306,7 +306,7 @@ parameters: validator_low_stake_grace_period: 7 extra_fields: id: - id: "0xd18abc8eb691d5072fcb6baaac319974c83d6d7d8a8a6e9cafbdfcf141800702" + id: "0xbe277768fa8506de251769353e4a201f27327ecc10dc28c339c714e8e0ed4723" size: 0 reference_gas_price: 1000 validator_report_records: @@ -320,7 +320,7 @@ stake_subsidy: stake_subsidy_decrease_rate: 1000 extra_fields: id: - id: "0x5eba9713cc76bdc9dd38cb398e350ca2267ed49760e15c552511d083d80576b2" + id: "0xe971211d731216f4d68e2f57abb4ed2228b27561c7dfe8ab8100398351da84b4" size: 0 safe_mode: false safe_mode_storage_rewards: @@ -332,6 +332,6 @@ safe_mode_non_refundable_storage_fee: 0 epoch_start_timestamp_ms: 10 extra_fields: id: - id: "0x63e0f3b038b1e50c782e99f3e6308ceb6b4008072dfa2fe2d6d060c4df49a057" + id: "0x436a9807a1478639aa0974f16f39734db051049bab343c7f8e1924a6f90f64c3" size: 0 diff --git a/crates/sui-transactional-test-runner/src/args.rs b/crates/sui-transactional-test-runner/src/args.rs index e9694815be52d..6a8d274e4e440 100644 --- a/crates/sui-transactional-test-runner/src/args.rs +++ b/crates/sui-transactional-test-runner/src/args.rs @@ -13,7 +13,7 @@ use move_symbol_pool::Symbol; use move_transactional_test_runner::tasks::SyntaxChoice; use sui_types::base_types::{SequenceNumber, SuiAddress}; use sui_types::move_package::UpgradePolicy; -use sui_types::object::Owner; +use sui_types::object::{Object, Owner}; use sui_types::programmable_transaction_builder::ProgrammableTransactionBuilder; use sui_types::storage::ObjectStore; use sui_types::transaction::{Argument, CallArg, ObjectArg}; @@ -163,6 +163,7 @@ pub enum SuiSubcommand { pub enum SuiExtraValueArgs { Object(FakeID, Option), Digest(String), + Receiving(FakeID, Option), } pub enum SuiValue { @@ -170,14 +171,41 @@ pub enum SuiValue { Object(FakeID, Option), ObjVec(Vec<(FakeID, Option)>), Digest(String), + Receiving(FakeID, Option), } impl SuiExtraValueArgs { fn parse_object_value<'a, I: Iterator>( parser: &mut MoveCLParser<'a, ValueToken, I>, ) -> anyhow::Result { + let (fake_id, version) = Self::parse_receiving_or_object_value(parser, "object")?; + Ok(SuiExtraValueArgs::Object(fake_id, version)) + } + + fn parse_receiving_value<'a, I: Iterator>( + parser: &mut MoveCLParser<'a, ValueToken, I>, + ) -> anyhow::Result { + let (fake_id, version) = Self::parse_receiving_or_object_value(parser, "receiving")?; + Ok(SuiExtraValueArgs::Receiving(fake_id, version)) + } + + fn parse_digest_value<'a, I: Iterator>( + parser: &mut MoveCLParser<'a, ValueToken, I>, + ) -> anyhow::Result { + let contents = parser.advance(ValueToken::Ident)?; + ensure!(contents == "digest"); + parser.advance(ValueToken::LParen)?; + let package = parser.advance(ValueToken::Ident)?; + parser.advance(ValueToken::RParen)?; + Ok(SuiExtraValueArgs::Digest(package.to_owned())) + } + + fn parse_receiving_or_object_value<'a, I: Iterator>( + parser: &mut MoveCLParser<'a, ValueToken, I>, + ident_name: &str, + ) -> anyhow::Result<(FakeID, Option)> { let contents = parser.advance(ValueToken::Ident)?; - ensure!(contents == "object"); + ensure!(contents == ident_name); parser.advance(ValueToken::LParen)?; let i_str = parser.advance(ValueToken::Number)?; let (i, _) = parse_u256(i_str)?; @@ -204,18 +232,7 @@ impl SuiExtraValueArgs { } else { None }; - Ok(SuiExtraValueArgs::Object(fake_id, version)) - } - - fn parse_digest_value<'a, I: Iterator>( - parser: &mut MoveCLParser<'a, ValueToken, I>, - ) -> anyhow::Result { - let contents = parser.advance(ValueToken::Ident)?; - ensure!(contents == "digest"); - parser.advance(ValueToken::LParen)?; - let package = parser.advance(ValueToken::Ident)?; - parser.advance(ValueToken::RParen)?; - Ok(SuiExtraValueArgs::Digest(package.to_owned())) + Ok((fake_id, version)) } } @@ -226,6 +243,7 @@ impl SuiValue { SuiValue::Object(_, _) => panic!("unexpected nested Sui object in args"), SuiValue::ObjVec(_) => panic!("unexpected nested Sui object vector in args"), SuiValue::Digest(_) => panic!("unexpected nested Sui package digest in args"), + SuiValue::Receiving(_, _) => panic!("unexpected nested Sui receiving object in args"), } } @@ -235,14 +253,15 @@ impl SuiValue { SuiValue::Object(id, version) => (id, version), SuiValue::ObjVec(_) => panic!("unexpected nested Sui object vector in args"), SuiValue::Digest(_) => panic!("unexpected nested Sui package digest in args"), + SuiValue::Receiving(_, _) => panic!("unexpected nested Sui receiving object in args"), } } - fn object_arg( + fn resolve_object( fake_id: FakeID, version: Option, test_adapter: &SuiTestAdapter, - ) -> anyhow::Result { + ) -> anyhow::Result { let id = match test_adapter.fake_to_real_object_id(fake_id) { Some(id) => id, None => bail!("INVALID TEST. Unknown object, object({})", fake_id), @@ -256,6 +275,25 @@ impl SuiValue { Ok(Some(obj)) => obj, Err(_) | Ok(None) => bail!("INVALID TEST. Could not load object argument {}", id), }; + Ok(obj) + } + + fn receiving_arg( + fake_id: FakeID, + version: Option, + test_adapter: &SuiTestAdapter, + ) -> anyhow::Result { + let obj = Self::resolve_object(fake_id, version, test_adapter)?; + Ok(ObjectArg::Receiving(obj.compute_object_reference())) + } + + fn object_arg( + fake_id: FakeID, + version: Option, + test_adapter: &SuiTestAdapter, + ) -> anyhow::Result { + let obj = Self::resolve_object(fake_id, version, test_adapter)?; + let id = obj.id(); match obj.owner { Owner::Shared { initial_shared_version, @@ -277,6 +315,9 @@ impl SuiValue { CallArg::Object(Self::object_arg(fake_id, version, test_adapter)?) } SuiValue::MoveValue(v) => CallArg::Pure(v.simple_serialize().unwrap()), + SuiValue::Receiving(fake_id, version) => { + CallArg::Object(Self::receiving_arg(fake_id, version, test_adapter)?) + } SuiValue::ObjVec(_) => bail!("obj vec is not supported as an input"), SuiValue::Digest(pkg) => { let pkg = Symbol::from(pkg); @@ -316,6 +357,7 @@ impl ParsableValue for SuiExtraValueArgs { match parser.peek()? { (ValueToken::Ident, "object") => Some(Self::parse_object_value(parser)), (ValueToken::Ident, "digest") => Some(Self::parse_digest_value(parser)), + (ValueToken::Ident, "receiving") => Some(Self::parse_receiving_value(parser)), _ => None, } } @@ -359,6 +401,7 @@ impl ParsableValue for SuiExtraValueArgs { match self { SuiExtraValueArgs::Object(id, version) => Ok(SuiValue::Object(id, version)), SuiExtraValueArgs::Digest(pkg) => Ok(SuiValue::Digest(pkg)), + SuiExtraValueArgs::Receiving(id, version) => Ok(SuiValue::Receiving(id, version)), } } } diff --git a/crates/sui-transactional-test-runner/src/test_adapter.rs b/crates/sui-transactional-test-runner/src/test_adapter.rs index 161105aa96a59..fb1ed4cb4d68e 100644 --- a/crates/sui-transactional-test-runner/src/test_adapter.rs +++ b/crates/sui-transactional-test-runner/src/test_adapter.rs @@ -934,6 +934,7 @@ impl<'a> MoveTestAdapter<'a> for SuiTestAdapter<'a> { } SuiValue::Digest(_) => bail!("digest is not supported as an input"), SuiValue::ObjVec(_) => bail!("obj vec is not supported as an input"), + SuiValue::Receiving(_, _) => bail!("receiving is not supported as an input"), }; let value = NumericalAddress::new(value.into_bytes(), NumberFormat::Hex); self.compiled_state diff --git a/crates/sui-types/src/execution.rs b/crates/sui-types/src/execution.rs index 7c17ed4966138..d603b174d5ec9 100644 --- a/crates/sui-types/src/execution.rs +++ b/crates/sui-types/src/execution.rs @@ -19,6 +19,7 @@ use crate::{ execution_status::CommandArgumentError, object::Owner, storage::{BackingPackageStore, ChildObjectResolver, ObjectChange, StorageView}, + transfer::Receiving, }; pub trait SuiResolver: @@ -73,11 +74,17 @@ pub struct ExecutionResults { } #[derive(Clone, Debug)] -pub struct InputObjectMetadata { - pub id: ObjectID, - pub is_mutable_input: bool, - pub owner: Owner, - pub version: SequenceNumber, +pub enum InputObjectMetadata { + Receiving { + id: ObjectID, + version: SequenceNumber, + }, + InputObject { + id: ObjectID, + is_mutable_input: bool, + owner: Owner, + version: SequenceNumber, + }, } #[derive(Clone, Debug)] @@ -107,6 +114,7 @@ pub enum UsageKind { pub enum Value { Object(ObjectValue), Raw(RawValueType, Vec), + Receiving(ObjectID, SequenceNumber), } #[derive(Debug, Clone)] @@ -151,6 +159,22 @@ pub enum CommandKind<'a> { Upgrade, } +impl InputObjectMetadata { + pub fn id(&self) -> ObjectID { + match self { + InputObjectMetadata::Receiving { id, .. } => *id, + InputObjectMetadata::InputObject { id, .. } => *id, + } + } + + pub fn version(&self) -> SequenceNumber { + match self { + InputObjectMetadata::Receiving { version, .. } => *version, + InputObjectMetadata::InputObject { version, .. } => *version, + } + } +} + impl InputValue { pub fn new_object(object_metadata: InputObjectMetadata, value: ObjectValue) -> Self { InputValue { @@ -165,6 +189,13 @@ impl InputValue { inner: ResultValue::new(Value::Raw(ty, value)), } } + + pub fn new_receiving_object(id: ObjectID, version: SequenceNumber) -> Self { + InputValue { + object_metadata: Some(InputObjectMetadata::Receiving { id, version }), + inner: ResultValue::new(Value::Receiving(id, version)), + } + } } impl ResultValue { @@ -182,6 +213,7 @@ impl Value { Value::Object(_) => false, Value::Raw(RawValueType::Any, _) => true, Value::Raw(RawValueType::Loaded { abilities, .. }, _) => abilities.has_copy(), + Value::Receiving(_, _) => false, } } @@ -189,6 +221,9 @@ impl Value { match self { Value::Object(obj_value) => obj_value.write_bcs_bytes(buf), Value::Raw(_, bytes) => buf.extend(bytes), + Value::Receiving(id, version) => { + buf.extend(&Receiving::new(*id, *version).to_bcs_bytes()) + } } } @@ -205,6 +240,9 @@ impl Value { }, _, ) => *used_in_non_entry_move_call, + // Only thing you can do with a `Receiving` is consume it, so once it's used it + // can't be used again. + Value::Receiving(_, _) => false, } } } @@ -252,6 +290,7 @@ impl TryFromValue for ObjectValue { Value::Object(o) => Ok(o), Value::Raw(RawValueType::Any, _) => Err(CommandArgumentError::TypeMismatch), Value::Raw(RawValueType::Loaded { .. }, _) => Err(CommandArgumentError::TypeMismatch), + Value::Receiving(_, _) => Err(CommandArgumentError::TypeMismatch), } } } @@ -274,6 +313,7 @@ fn try_from_value_prim<'a, T: Deserialize<'a>>( ) -> Result { match value { Value::Object(_) => Err(CommandArgumentError::TypeMismatch), + Value::Receiving(_, _) => Err(CommandArgumentError::TypeMismatch), Value::Raw(RawValueType::Any, bytes) => { bcs::from_bytes(bytes).map_err(|_| CommandArgumentError::InvalidBCSBytes) } diff --git a/crates/sui-types/src/execution_mode.rs b/crates/sui-types/src/execution_mode.rs index ffbd302cdfced..20e4610a9d114 100644 --- a/crates/sui-types/src/execution_mode.rs +++ b/crates/sui-types/src/execution_mode.rs @@ -7,6 +7,7 @@ use crate::{ error::ExecutionError, execution::{RawValueType, Value}, transaction::Argument, + transfer::Receiving, type_resolver::TypeTagResolver, }; @@ -256,6 +257,10 @@ fn value_to_bytes_and_tag( let tag = resolver.get_type_tag(ty)?; (tag, bytes.clone()) } + Value::Receiving(id, seqno) => ( + Receiving::type_tag(), + Receiving::new(*id, *seqno).to_bcs_bytes(), + ), }; Ok((bytes, type_tag)) } diff --git a/crates/sui-types/src/in_memory_storage.rs b/crates/sui-types/src/in_memory_storage.rs index 5d99e33f99038..08280bbfee7b5 100644 --- a/crates/sui-types/src/in_memory_storage.rs +++ b/crates/sui-types/src/in_memory_storage.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use crate::base_types::VersionNumber; +use crate::committee::EpochId; use crate::storage::get_module_by_id; use crate::{ base_types::{ObjectID, ObjectRef, SequenceNumber}, @@ -59,6 +60,27 @@ impl ChildObjectResolver for InMemoryStorage { } Ok(Some(child_object)) } + + fn receive_object_at_version( + &self, + owner: &ObjectID, + receiving_object_id: &ObjectID, + receive_object_at_version: SequenceNumber, + _epoch_id: EpochId, + ) -> SuiResult> { + let recv_object = match self.persistent.get(receiving_object_id).cloned() { + None => return Ok(None), + Some(obj) => obj, + }; + if recv_object.owner != Owner::AddressOwner((*owner).into()) { + return Ok(None); + } + + if recv_object.version() != receive_object_at_version { + return Ok(None); + } + Ok(Some(recv_object)) + } } impl ParentSync for InMemoryStorage { diff --git a/crates/sui-types/src/lib.rs b/crates/sui-types/src/lib.rs index d48986fe5c882..17fc4e3fd66a0 100644 --- a/crates/sui-types/src/lib.rs +++ b/crates/sui-types/src/lib.rs @@ -20,6 +20,7 @@ use base_types::ObjectID; pub use mysten_network::multiaddr; use crate::base_types::{RESOLVED_ASCII_STR, RESOLVED_UTF8_STR}; +use crate::transfer::RESOLVED_RECEIVING_STRUCT; use crate::{base_types::RESOLVED_STD_OPTION, id::RESOLVED_SUI_ID}; #[macro_use] @@ -66,6 +67,7 @@ pub mod sui_serde; pub mod sui_system_state; pub mod temporary_store; pub mod transaction; +pub mod transfer; pub mod type_resolver; pub mod versioned; pub mod zk_login_authenticator; @@ -196,10 +198,15 @@ pub fn is_primitive( S::StructInstantiation(idx, targs) => { let resolved_struct = resolve_struct(view, *idx); - // is option of a primitive - resolved_struct == RESOLVED_STD_OPTION + // option is a primitive + (resolved_struct == RESOLVED_STD_OPTION && targs.len() == 1 - && is_primitive(view, function_type_args, &targs[0]) + && is_primitive(view, function_type_args, &targs[0])) || + // `Receiving` struct in the `sui::transfer` module is also a primitive + ( + resolved_struct == RESOLVED_RECEIVING_STRUCT + && targs.len() == 1 + ) } S::Vector(inner) => is_primitive(view, function_type_args, inner), diff --git a/crates/sui-types/src/storage.rs b/crates/sui-types/src/storage.rs index a6b5d3048ee77..cd5ed11e1a5de 100644 --- a/crates/sui-types/src/storage.rs +++ b/crates/sui-types/src/storage.rs @@ -111,12 +111,26 @@ impl StorageView for T {} /// An abstraction of the (possibly distributed) store for objects. This /// API only allows for the retrieval of objects, not any state changes pub trait ChildObjectResolver { + /// `child` must have an `ObjectOwner` ownership equal to `owner`. fn read_child_object( &self, parent: &ObjectID, child: &ObjectID, child_version_upper_bound: SequenceNumber, ) -> SuiResult>; + + /// `receiving_object_id` must have an `AddressOwner` ownership equal to `owner`. + /// `receive_object_at_version` must be the exact version at which the object will be received, + /// and it cannot have been previously received at that version. NB: An object not existing at + /// that version, and not having valid access to the object will be treated exactly the same + /// and `Ok(None)` must be returned. + fn receive_object_at_version( + &self, + owner: &ObjectID, + receiving_object_id: &ObjectID, + receive_object_at_version: SequenceNumber, + epoch_id: EpochId, + ) -> SuiResult>; } /// An abstraction of the (possibly distributed) store for objects, and (soon) events and transactions @@ -268,6 +282,21 @@ impl ChildObjectResolver for std::sync::Arc { child_version_upper_bound, ) } + fn receive_object_at_version( + &self, + owner: &ObjectID, + receiving_object_id: &ObjectID, + receive_object_at_version: SequenceNumber, + epoch_id: EpochId, + ) -> SuiResult> { + ChildObjectResolver::receive_object_at_version( + self.as_ref(), + owner, + receiving_object_id, + receive_object_at_version, + epoch_id, + ) + } } impl ChildObjectResolver for &S { @@ -279,6 +308,21 @@ impl ChildObjectResolver for &S { ) -> SuiResult> { ChildObjectResolver::read_child_object(*self, parent, child, child_version_upper_bound) } + fn receive_object_at_version( + &self, + owner: &ObjectID, + receiving_object_id: &ObjectID, + receive_object_at_version: SequenceNumber, + epoch_id: EpochId, + ) -> SuiResult> { + ChildObjectResolver::receive_object_at_version( + *self, + owner, + receiving_object_id, + receive_object_at_version, + epoch_id, + ) + } } impl ChildObjectResolver for &mut S { @@ -290,6 +334,21 @@ impl ChildObjectResolver for &mut S { ) -> SuiResult> { ChildObjectResolver::read_child_object(*self, parent, child, child_version_upper_bound) } + fn receive_object_at_version( + &self, + owner: &ObjectID, + receiving_object_id: &ObjectID, + receive_object_at_version: SequenceNumber, + epoch_id: EpochId, + ) -> SuiResult> { + ChildObjectResolver::receive_object_at_version( + *self, + owner, + receiving_object_id, + receive_object_at_version, + epoch_id, + ) + } } pub trait ReadStore { diff --git a/crates/sui-types/src/temporary_store.rs b/crates/sui-types/src/temporary_store.rs index c5384b240bb4b..154f2163ca812 100644 --- a/crates/sui-types/src/temporary_store.rs +++ b/crates/sui-types/src/temporary_store.rs @@ -676,10 +676,12 @@ impl<'backing> TemporaryStore<'backing> { panic!("Mutated object must exist in the store: ID = {:?}", id) }); match &old_obj.owner { - Owner::ObjectOwner(_parent) => { + // ObjectOwner = dynamic field mutations + // AddressOwner = received object + Owner::ObjectOwner(_) | Owner::AddressOwner(_) => { objs_to_authenticate.push(*id); } - Owner::AddressOwner(_) | Owner::Shared { .. } => { + Owner::Shared { .. } => { unreachable!("Should already be in authenticated_objs") } Owner::Immutable => { @@ -709,10 +711,10 @@ impl<'backing> TemporaryStore<'backing> { // get owner at beginning of tx let old_obj = self.store.get_object(id)?.unwrap(); match &old_obj.owner { - Owner::ObjectOwner(_) => { + Owner::AddressOwner(_) | Owner::ObjectOwner(_) => { objs_to_authenticate.push(*id); } - Owner::AddressOwner(_) | Owner::Shared { .. } => { + Owner::Shared { .. } => { unreachable!("Should already be in authenticated_objs") } Owner::Immutable => unreachable!("Immutable objects cannot be deleted"), @@ -747,7 +749,7 @@ impl<'backing> TemporaryStore<'backing> { continue; }; let parent = match &old_obj.owner { - Owner::ObjectOwner(parent) => ObjectID::from(*parent), + Owner::ObjectOwner(parent) | Owner::AddressOwner(parent) => ObjectID::from(*parent), owner => panic!( "Unauthenticated root at {to_authenticate:?} with owner {owner:?}\n\ Potentially covering objects in: {covered:#?}", @@ -1123,6 +1125,25 @@ impl<'backing> ChildObjectResolver for TemporaryStore<'backing> { .read_child_object(parent, child, child_version_upper_bound) } } + + fn receive_object_at_version( + &self, + owner: &ObjectID, + receiving_object_id: &ObjectID, + receive_object_at_version: SequenceNumber, + epoch_id: EpochId, + ) -> SuiResult> { + // You should never be able to try and receive an object after deleting it or writing it in the same + // transaction since `Receiving` doesn't have copy. + debug_assert!(self.deleted.get(receiving_object_id).is_none()); + debug_assert!(self.written.get(receiving_object_id).is_none()); + self.store.receive_object_at_version( + owner, + receiving_object_id, + receive_object_at_version, + epoch_id, + ) + } } impl<'backing> Storage for TemporaryStore<'backing> { diff --git a/crates/sui-types/src/transaction.rs b/crates/sui-types/src/transaction.rs index 585e103b56214..41e9635e80759 100644 --- a/crates/sui-types/src/transaction.rs +++ b/crates/sui-types/src/transaction.rs @@ -96,6 +96,8 @@ pub enum ObjectArg { initial_shared_version: SequenceNumber, mutable: bool, }, + // A Move object that can be received in this transaction. + Receiving(ObjectRef), } fn type_tag_validity_check( @@ -203,7 +205,7 @@ pub enum TransactionKind { } impl VersionedProtocolMessage for TransactionKind { - fn check_version_supported(&self, _protocol_config: &ProtocolConfig) -> SuiResult { + fn check_version_supported(&self, protocol_config: &ProtocolConfig) -> SuiResult { // This code does nothing right now - it exists to cause a compiler error when new // enumerants are added to TransactionKind. // @@ -212,8 +214,19 @@ impl VersionedProtocolMessage for TransactionKind { match &self { TransactionKind::ChangeEpoch(_) | TransactionKind::Genesis(_) - | TransactionKind::ConsensusCommitPrologue(_) - | TransactionKind::ProgrammableTransaction(_) => Ok(()), + | TransactionKind::ConsensusCommitPrologue(_) => Ok(()), + TransactionKind::ProgrammableTransaction(pt) => { + // NB: we don't use the `receiving_objects` method here since we don't want to check + // for any validity requirements such as duplicate receiving inputs at this point. + let has_receiving_objects = pt + .inputs + .iter() + .find(|arg| !arg.receiving_objects().is_empty()); + if has_receiving_objects.is_some() { + protocol_config.check_receiving_objects_supported()?; + } + Ok(()) + } } } } @@ -239,6 +252,19 @@ impl CallArg { mutable, }] } + // Receiving objects are not part of the input objects. + CallArg::Object(ObjectArg::Receiving(_)) => vec![], + } + } + + fn receiving_objects(&self) -> Vec { + match self { + CallArg::Pure(_) => vec![], + CallArg::Object(o) => match o { + ObjectArg::ImmOrOwnedObject(_) => vec![], + ObjectArg::SharedObject { .. } => vec![], + ObjectArg::Receiving(obj_ref) => vec![*obj_ref], + }, } } @@ -323,7 +349,9 @@ impl ObjectArg { pub fn id(&self) -> ObjectID { match self { - ObjectArg::ImmOrOwnedObject((id, _, _)) | ObjectArg::SharedObject { id, .. } => *id, + ObjectArg::Receiving((id, _, _)) + | ObjectArg::ImmOrOwnedObject((id, _, _)) + | ObjectArg::SharedObject { id, .. } => *id, } } } @@ -635,6 +663,21 @@ impl ProgrammableTransaction { .collect()) } + fn receiving_objects(&self) -> UserInputResult> { + let ProgrammableTransaction { inputs, .. } = self; + let receiving_objects = inputs + .iter() + .flat_map(|arg| arg.receiving_objects()) + .collect::>(); + + // all objects being received must be unique + let mut used = HashSet::new(); + if !receiving_objects.iter().all(|o| used.insert(o.0)) { + return Err(UserInputError::DuplicateObjectRefInput); + } + Ok(receiving_objects) + } + fn validity_check(&self, config: &ProtocolConfig) -> UserInputResult { let ProgrammableTransaction { inputs, commands } = self; fp_ensure!( @@ -657,7 +700,9 @@ impl ProgrammableTransaction { self.inputs .iter() .filter_map(|arg| match arg { - CallArg::Pure(_) | CallArg::Object(ObjectArg::ImmOrOwnedObject(_)) => None, + CallArg::Pure(_) + | CallArg::Object(ObjectArg::Receiving(_)) + | CallArg::Object(ObjectArg::ImmOrOwnedObject(_)) => None, CallArg::Object(ObjectArg::SharedObject { id, initial_shared_version, @@ -868,6 +913,15 @@ impl TransactionKind { } } + fn receiving_objects(&self) -> UserInputResult> { + Ok(match &self { + TransactionKind::ChangeEpoch(_) + | TransactionKind::Genesis(_) + | TransactionKind::ConsensusCommitPrologue(_) => vec![], + TransactionKind::ProgrammableTransaction(pt) => pt.receiving_objects()?, + }) + } + /// Return the metadata of each of the input objects for the transaction. /// For a Move object, we attach the object reference; /// for a Move package, we provide the object id only since they never change on chain. @@ -1437,6 +1491,8 @@ pub trait TransactionDataAPI { fn input_objects(&self) -> UserInputResult>; + fn receiving_objects(&self) -> UserInputResult>; + fn validity_check(&self, config: &ProtocolConfig) -> UserInputResult; fn validity_check_no_gas_check(&self, config: &ProtocolConfig) -> UserInputResult; @@ -1536,6 +1592,10 @@ impl TransactionDataAPI for TransactionDataV1 { Ok(inputs) } + fn receiving_objects(&self) -> UserInputResult> { + self.kind.receiving_objects() + } + fn validity_check(&self, config: &ProtocolConfig) -> UserInputResult { fp_ensure!(!self.gas().is_empty(), UserInputError::MissingGasPayment); fp_ensure!( diff --git a/crates/sui-types/src/transfer.rs b/crates/sui-types/src/transfer.rs new file mode 100644 index 0000000000000..0f43c881b90dc --- /dev/null +++ b/crates/sui-types/src/transfer.rs @@ -0,0 +1,60 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use move_core_types::{ + account_address::AccountAddress, + ident_str, + identifier::IdentStr, + language_storage::{StructTag, TypeTag}, +}; +use serde::{Deserialize, Serialize}; + +use crate::{ + base_types::{ObjectID, SequenceNumber}, + id::ID, + SUI_FRAMEWORK_ADDRESS, +}; + +const TRANSFER_MODULE_NAME: &IdentStr = ident_str!("transfer"); +const INBOX_REFERENCE_STRUCT_NAME: &IdentStr = ident_str!("Receiving"); + +pub const RESOLVED_RECEIVING_STRUCT: (&AccountAddress, &IdentStr, &IdentStr) = ( + &SUI_FRAMEWORK_ADDRESS, + TRANSFER_MODULE_NAME, + INBOX_REFERENCE_STRUCT_NAME, +); + +/// Rust version of the Move sui::transfer::Receiving type +#[derive(Clone, Serialize, Deserialize, Debug)] +pub struct Receiving { + pub id: ID, + pub version: SequenceNumber, +} + +impl Receiving { + pub fn new(id: ObjectID, version: SequenceNumber) -> Self { + Self { + id: ID::new(id), + version, + } + } + + pub fn to_bcs_bytes(&self) -> Vec { + bcs::to_bytes(self).expect("Value representation is owned and should always serialize") + } + + pub fn struct_tag() -> StructTag { + StructTag { + address: SUI_FRAMEWORK_ADDRESS, + module: TRANSFER_MODULE_NAME.to_owned(), + name: INBOX_REFERENCE_STRUCT_NAME.to_owned(), + // TODO: this should really include the type parameters eventually when we add type + // parameters to the other polymorphic types like this. + type_params: vec![], + } + } + + pub fn type_tag() -> TypeTag { + TypeTag::Struct(Box::new(Self::struct_tag())) + } +} diff --git a/crates/sui-verifier-transactional-tests/tests/private_generics/receive_with_and_without_store.exp b/crates/sui-verifier-transactional-tests/tests/private_generics/receive_with_and_without_store.exp new file mode 100644 index 0000000000000..70aa43bbd70e6 --- /dev/null +++ b/crates/sui-verifier-transactional-tests/tests/private_generics/receive_with_and_without_store.exp @@ -0,0 +1,11 @@ +processed 2 tasks + +task 0 'publish'. lines 1-12: +created: object(1,0) +mutated: object(0,0) +gas summary: computation_cost: 1000000, storage_cost: 4362400, storage_rebate: 0, non_refundable_storage_fee: 0 + +task 1 'publish'. lines 14-25: +created: object(2,0) +mutated: object(0,0) +gas summary: computation_cost: 1000000, storage_cost: 4377600, storage_rebate: 978120, non_refundable_storage_fee: 9880 diff --git a/crates/sui-verifier-transactional-tests/tests/private_generics/receive_with_and_without_store.mvir b/crates/sui-verifier-transactional-tests/tests/private_generics/receive_with_and_without_store.mvir new file mode 100644 index 0000000000000..c93eaa4e9a424 --- /dev/null +++ b/crates/sui-verifier-transactional-tests/tests/private_generics/receive_with_and_without_store.mvir @@ -0,0 +1,25 @@ +//# publish +module 0x0.m { + import 0x2.transfer; + import 0x2.object; + + receive_good(m: &mut object.UID, r: transfer.Receiving): T { + let x: T; + label l0: + x = transfer.receive(move(m), move(r)); + return move(x); + } +} + +//# publish +module 0x0.m1 { + import 0x2.transfer; + import 0x2.object; + + receive_good(m: &mut object.UID, r: transfer.Receiving): T { + let x: T; + label l0: + x = transfer.receive(move(m), move(r)); + return move(x); + } +} diff --git a/crates/sui-verifier-transactional-tests/tests/private_generics/receive_without_key.exp b/crates/sui-verifier-transactional-tests/tests/private_generics/receive_without_key.exp new file mode 100644 index 0000000000000..61c788d6086de --- /dev/null +++ b/crates/sui-verifier-transactional-tests/tests/private_generics/receive_without_key.exp @@ -0,0 +1,5 @@ +processed 1 task + +task 0 'publish'. lines 1-12: +Error: Transaction Effects Status: Move Bytecode Verification Error. Please run the Bytecode Verifier for more information. +Execution Error: ExecutionError: ExecutionError { inner: ExecutionErrorInner { kind: VMVerificationOrDeserializationError, source: Some(VMError { major_status: CONSTRAINT_NOT_SATISFIED, sub_status: None, message: Some("expected type with abilities [Key, ] got type actual TypeParameter(0) with incompatible abilities []"), exec_state: None, location: Module(ModuleId { address: _, name: Identifier("m2") }), indices: [(Signature, 0), (FunctionHandle, 0)], offsets: [] }), command: Some(0) } } diff --git a/crates/sui-verifier-transactional-tests/tests/private_generics/receive_without_key.mvir b/crates/sui-verifier-transactional-tests/tests/private_generics/receive_without_key.mvir new file mode 100644 index 0000000000000..49deadeb1ab3a --- /dev/null +++ b/crates/sui-verifier-transactional-tests/tests/private_generics/receive_without_key.mvir @@ -0,0 +1,12 @@ +//# publish +module 0x0.m2 { + import 0x2.transfer; + import 0x2.object; + + receive_bad(m: &mut object.UID, r: transfer.Receiving): T { + let x: T; + label l0: + x = transfer.receive(move(m), move(r)); + return move(x); + } +} diff --git a/sui-execution/latest/sui-adapter/src/adapter.rs b/sui-execution/latest/sui-adapter/src/adapter.rs index 3703973034ac0..d338097009aa4 100644 --- a/sui-execution/latest/sui-adapter/src/adapter.rs +++ b/sui-execution/latest/sui-adapter/src/adapter.rs @@ -114,6 +114,7 @@ pub fn new_native_extensions<'r>( is_metered: bool, protocol_config: &ProtocolConfig, metrics: Arc, + current_epoch_id: EpochId, ) -> NativeContextExtensions<'r> { let mut extensions = NativeContextExtensions::default(); extensions.add(ObjectRuntime::new( @@ -122,6 +123,7 @@ pub fn new_native_extensions<'r>( is_metered, protocol_config, metrics, + current_epoch_id, )); extensions.add(NativesCostTable::from_protocol_config(protocol_config)); extensions diff --git a/sui-execution/latest/sui-adapter/src/programmable_transactions/context.rs b/sui-execution/latest/sui-adapter/src/programmable_transactions/context.rs index e90ed3b470d67..461ab6e742e17 100644 --- a/sui-execution/latest/sui-adapter/src/programmable_transactions/context.rs +++ b/sui-execution/latest/sui-adapter/src/programmable_transactions/context.rs @@ -27,7 +27,6 @@ use sui_move_natives::object_runtime::{ self, get_all_uids, max_event_error, ObjectRuntime, RuntimeResults, }; use sui_protocol_config::ProtocolConfig; -use sui_types::gas::GasCharger; use sui_types::{ balance::Balance, base_types::{MoveObjectType, ObjectID, SequenceNumber, SuiAddress, TxContext}, @@ -47,6 +46,7 @@ use sui_types::{ transaction::{Argument, CallArg, ObjectArg}, type_resolver::TypeTagResolver, }; +use sui_types::{committee::EpochId, gas::GasCharger}; use sui_types::{ error::command_argument_error, execution::{CommandKind, ObjectContents, TryFromValue, Value}, @@ -128,6 +128,7 @@ impl<'vm, 'state, 'a> ExecutionContext<'vm, 'state, 'a> { !gas_charger.is_unmetered(), protocol_config, metrics.clone(), + tx_context.epoch(), ); let mut input_object_map = BTreeMap::new(); let inputs = inputs @@ -193,6 +194,7 @@ impl<'vm, 'state, 'a> ExecutionContext<'vm, 'state, 'a> { !gas_charger.is_unmetered(), protocol_config, metrics.clone(), + tx_context.epoch(), ); // Set the profiler if in debug mode @@ -360,7 +362,7 @@ impl<'vm, 'state, 'a> ExecutionContext<'vm, 'state, 'a> { // Immutable objects and shared objects cannot be taken by value if matches!( input_metadata_opt, - Some(InputObjectMetadata { + Some(InputObjectMetadata::InputObject { owner: Owner::Immutable | Owner::Shared { .. }, .. }) @@ -404,7 +406,7 @@ impl<'vm, 'state, 'a> ExecutionContext<'vm, 'state, 'a> { // error if taken return Err(CommandArgumentError::InvalidValueUsage); }; - if input_metadata_opt.is_some() && !input_metadata_opt.unwrap().is_mutable_input { + if let Some(InputObjectMetadata::InputObject { is_mutable_input: false, .. }) = input_metadata_opt { return Err(CommandArgumentError::InvalidObjectByMutRef); } // if it is copyable, don't take it as we allow for the value to be copied even if @@ -458,15 +460,28 @@ impl<'vm, 'state, 'a> ExecutionContext<'vm, 'state, 'a> { The take+restore is an implementation detail of mutable references" ); // restore is exclusively used for mut - let Ok((_, value_opt)) = self.borrow_mut_impl(arg, None) else { + let Ok((input_object_metadata, value_opt)) = self.borrow_mut_impl(arg, None) else { invariant_violation!("Should be able to borrow argument to restore it") }; - let old_value = value_opt.replace(value); - assert_invariant!( - old_value.is_none() || old_value.unwrap().is_copyable(), - "Should never restore a non-taken value, unless it is copyable. \ - The take+restore is an implementation detail of mutable references" - ); + match input_object_metadata { + Some(InputObjectMetadata::Receiving{ id, version }) => { + let old_value = value_opt.replace(Value::Receiving(*id, *version)); + // If the value is receiving and it's being restored it must have been taken since + // the `Receiving` struct is not copy. + assert_invariant!( + old_value.is_none(), + "Should never restore a receiving value without having taken it" + ); + } + _ => { + let old_value = value_opt.replace(value); + assert_invariant!( + old_value.is_none() || old_value.unwrap().is_copyable(), + "Should never restore a non-taken value, unless it is copyable. \ + The take+restore is an implementation detail of mutable references" + ); + } + } Ok(()) } @@ -562,11 +577,21 @@ impl<'vm, 'state, 'a> ExecutionContext<'vm, 'state, 'a> { object_metadata: object_metadata_opt, inner: ResultValue { value, .. }, } = input; + let Some(object_metadata) = object_metadata_opt else { return Ok(()) }; - let is_mutable_input = object_metadata.is_mutable_input; - let owner = object_metadata.owner; - let id = object_metadata.id; - input_object_metadata.insert(object_metadata.id, object_metadata); + let (id, owner_opt, is_mutable_input) = match &object_metadata { + InputObjectMetadata::InputObject { id, owner, is_mutable_input, .. } => { + (*id, Some(*owner), *is_mutable_input) + } + InputObjectMetadata::Receiving { id, .. } => { + (*id, None, false) + } + }; + input_object_metadata.insert(id, object_metadata); + let Some(owner) = owner_opt else { + return Ok(()) + }; + let Some(Value::Object(object_value)) = value else { by_value_inputs.insert(id); return Ok(()) @@ -576,7 +601,7 @@ impl<'vm, 'state, 'a> ExecutionContext<'vm, 'state, 'a> { } Ok(()) }; - let gas_id_opt = gas.object_metadata.as_ref().map(|info| info.id); + let gas_id_opt = gas.object_metadata.as_ref().map(|info| info.id()); add_input_object_write(gas)?; for input in inputs { add_input_object_write(input)? @@ -626,6 +651,8 @@ impl<'vm, 'state, 'a> ExecutionContext<'vm, 'state, 'a> { )); } } + // Receiving arguments can be dropped without being received + Some(Value::Receiving(_, _)) => (), } } } @@ -680,6 +707,7 @@ impl<'vm, 'state, 'a> ExecutionContext<'vm, 'state, 'a> { !gas_charger.is_unmetered(), protocol_config, metrics, + tx_context.epoch(), ); for (id, additional_write) in additional_writes { let AdditionalWrite { @@ -762,10 +790,10 @@ impl<'vm, 'state, 'a> ExecutionContext<'vm, 'state, 'a> { let old_version = match input_object_metadata.get(&id) { Some(metadata) => { assert_invariant!( - !matches!(metadata.owner, Owner::Immutable), + !matches!(metadata, InputObjectMetadata::InputObject { owner: Owner::Immutable, .. }), "Attempting to delete immutable object {id} via delete kind {delete_kind}" ); - metadata.version + metadata.version() } None => { match loaded_child_objects.get(&id) { @@ -918,6 +946,7 @@ pub(crate) fn new_session<'state, 'vm>( is_metered: bool, protocol_config: &ProtocolConfig, metrics: Arc, + current_epoch_id: EpochId, ) -> Session<'state, 'vm, LinkageView<'state>> { vm.new_session_with_extensions( linkage, @@ -927,6 +956,7 @@ pub(crate) fn new_session<'state, 'vm>( is_metered, protocol_config, metrics, + current_epoch_id, ), ) } @@ -1138,7 +1168,7 @@ fn load_object<'vm, 'state>( }; let owner = obj.owner; let version = obj.version(); - let object_metadata = InputObjectMetadata { + let object_metadata = InputObjectMetadata::InputObject { id, is_mutable_input, owner, @@ -1210,6 +1240,7 @@ fn load_object_arg<'vm, 'state>( /* imm override */ !mutable, id, ), + ObjectArg::Receiving((id, version, _)) => Ok(InputValue::new_receiving_object(id, version)), } } @@ -1298,12 +1329,12 @@ unsafe fn create_written_object<'vm, 'state>( ); let old_obj_ver = metadata_opt - .map(|metadata| metadata.version) + .map(|metadata| metadata.version()) .or_else(|| loaded_child_version_opt.copied()); debug_assert!( (write_kind == WriteKind::Mutate) == old_obj_ver.is_some(), - "Inconsistent state: write_kind: {write_kind:?}, old ver: {old_obj_ver:?}" + "Inconsistent state: write_kind: {write_kind:?}, old ver: {old_obj_ver:?} id: {id:?}" ); let type_tag = session diff --git a/sui-execution/latest/sui-adapter/src/programmable_transactions/execution.rs b/sui-execution/latest/sui-adapter/src/programmable_transactions/execution.rs index f1bb51957e0a5..afc01e2e3f453 100644 --- a/sui-execution/latest/sui-adapter/src/programmable_transactions/execution.rs +++ b/sui-execution/latest/sui-adapter/src/programmable_transactions/execution.rs @@ -49,6 +49,7 @@ use sui_types::{ }, storage::get_packages, transaction::{Argument, Command, ProgrammableMoveCall, ProgrammableTransaction}, + transfer::RESOLVED_RECEIVING_STRUCT, SUI_FRAMEWORK_ADDRESS, }; use sui_types::{ @@ -1276,8 +1277,9 @@ fn check_param_type( idx: usize, value: &Value, param_ty: &Type, -) -> Result<(), ExecutionError> { - let ty = match value { +) -> Result<(), ExecutionError> +{ + match value { // For dev-spect, allow any BCS bytes. This does mean internal invariants for types can // be violated (like for string or Option) Value::Raw(RawValueType::Any, _) if Mode::allow_arbitrary_values() => return Ok(()), @@ -1307,18 +1309,42 @@ fn check_param_type( Mode::allow_arbitrary_values() || !abilities.has_key(), "Raw value should never be an object" ); - ty + if ty != param_ty { + return Err(command_argument_error( + CommandArgumentError::TypeMismatch, + idx, + )) + } + } + Value::Object(obj) => { + let ty = &obj.type_; + if ty != param_ty { + return Err(command_argument_error( + CommandArgumentError::TypeMismatch, + idx, + )) + } + } + Value::Receiving(_, _) => { + if let Type::StructInstantiation(sidx, targs) = param_ty { + let Some(s) = context + .session + .get_struct_type(*sidx) else { + invariant_violation!("sui::transfer::Receiving struct not found in session") + }; + let resolved_struct = get_struct_ident(&s); + if resolved_struct == RESOLVED_RECEIVING_STRUCT && targs.len() == 1 { + return Ok(()) + } + } + + return Err(command_argument_error( + CommandArgumentError::TypeMismatch, + idx, + )) } - Value::Object(obj) => &obj.type_, - }; - if ty != param_ty { - Err(command_argument_error( - CommandArgumentError::TypeMismatch, - idx, - )) - } else { - Ok(()) } + Ok(()) } fn get_struct_ident(s: &StructType) -> (&AccountAddress, &IdentStr, &IdentStr) { diff --git a/sui-execution/latest/sui-move-natives/src/dynamic_field.rs b/sui-execution/latest/sui-move-natives/src/dynamic_field.rs index 726c957d5f98f..2304c14792f5f 100644 --- a/sui-execution/latest/sui-move-natives/src/dynamic_field.rs +++ b/sui-execution/latest/sui-move-natives/src/dynamic_field.rs @@ -11,7 +11,6 @@ use move_core_types::{ account_address::AccountAddress, gas_algebra::InternalGas, language_storage::{StructTag, TypeTag}, - value::MoveTypeLayout, vm_status::StatusCode, }; use move_vm_runtime::native_charge_gas_early_exit; @@ -39,7 +38,8 @@ macro_rules! get_or_fetch_object { ); assert!($ty_args.is_empty()); - let (tag, layout, annotated_layout) = match get_tag_and_layouts($context, &child_ty)? { + let (tag, layout, annotated_layout) = match crate::get_tag_and_layouts($context, &child_ty)? + { Some(res) => res, None => { return Ok(NativeResult::err( @@ -497,25 +497,3 @@ pub fn has_child_object_with_ty( smallvec![Value::bool(has_child)], )) } - -fn get_tag_and_layouts( - context: &NativeContext, - ty: &Type, -) -> PartialVMResult> { - let tag = match context.type_to_type_tag(ty)? { - TypeTag::Struct(s) => s, - _ => { - return Err( - PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR) - .with_message("Sui verifier guarantees this is a struct".to_string()), - ) - } - }; - let Some(layout) = context.type_to_type_layout(ty)? else { - return Ok(None) - }; - let Some(annotated_layout) = context.type_to_fully_annotated_layout(ty)? else { - return Ok(None) - }; - Ok(Some((*tag, layout, annotated_layout))) -} diff --git a/sui-execution/latest/sui-move-natives/src/lib.rs b/sui-execution/latest/sui-move-natives/src/lib.rs index 8f1f05edcfd17..29d82e0dcc192 100644 --- a/sui-execution/latest/sui-move-natives/src/lib.rs +++ b/sui-execution/latest/sui-move-natives/src/lib.rs @@ -35,16 +35,24 @@ use self::{ }; use better_any::{Tid, TidAble}; use move_binary_format::errors::{PartialVMError, PartialVMResult}; -use move_core_types::{gas_algebra::InternalGas, identifier::Identifier}; +use move_core_types::{ + gas_algebra::InternalGas, + identifier::Identifier, + language_storage::{StructTag, TypeTag}, + value::MoveTypeLayout, + vm_status::StatusCode, +}; use move_stdlib::natives::{GasParameters, NurseryGasParameters}; -use move_vm_runtime::native_functions::{NativeFunction, NativeFunctionTable}; +use move_vm_runtime::native_functions::{NativeContext, NativeFunction, NativeFunctionTable}; use move_vm_types::{ + loaded_data::runtime_types::Type, natives::function::NativeResult, values::{Struct, Value}, }; use std::sync::Arc; use sui_protocol_config::ProtocolConfig; use sui_types::{MOVE_STDLIB_ADDRESS, SUI_FRAMEWORK_ADDRESS, SUI_SYSTEM_ADDRESS}; +use transfer::TransferReceiveObjectInternalCostParams; mod address; mod crypto; @@ -129,6 +137,9 @@ pub struct NativesCostTable { // hmac pub hmac_hmac_sha3_256_cost_params: HmacHmacSha3256CostParams, + + // Receive object + pub transfer_receive_object_internal_cost_params: TransferReceiveObjectInternalCostParams, } impl NativesCostTable { @@ -465,6 +476,12 @@ impl NativesCostTable { .hmac_hmac_sha3_256_input_cost_per_block() .into(), }, + transfer_receive_object_internal_cost_params: TransferReceiveObjectInternalCostParams { + transfer_receive_object_internal_cost_base: protocol_config + .transfer_receive_object_cost_base_as_option() + .unwrap_or(0) + .into(), + }, } } } @@ -641,6 +658,11 @@ pub fn all_natives(silent: bool) -> NativeFunctionTable { "share_object_impl", make_native!(transfer::share_object), ), + ( + "transfer", + "receive_impl", + make_native!(transfer::receive_object_internal), + ), ( "tx_context", "derive_id", @@ -701,6 +723,12 @@ pub fn all_natives(silent: bool) -> NativeFunctionTable { .collect() } +// ID { bytes: address } +// Extract the first field of the struct to get the address bytes. +pub fn get_receiver_object_id(object: Value) -> Result { + get_nested_struct_field(object, &[0]) +} + // Object { id: UID { id: ID { bytes: address } } .. } // Extract the first field of the struct 3 times to get the id bytes. pub fn get_object_id(object: Value) -> Result { @@ -721,6 +749,29 @@ pub fn get_nth_struct_field(v: Value, n: usize) -> Result Ok(itr.nth(n).unwrap()) } +/// Returns the struct tag, non-annotated type layout, and fully annotated type layout of `ty`. +pub(crate) fn get_tag_and_layouts( + context: &NativeContext, + ty: &Type, +) -> PartialVMResult> { + let tag = match context.type_to_type_tag(ty)? { + TypeTag::Struct(s) => s, + _ => { + return Err( + PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR) + .with_message("Sui verifier guarantees this is a struct".to_string()), + ) + } + }; + let Some(layout) = context.type_to_type_layout(ty)? else { + return Ok(None) + }; + let Some(annotated_layout) = context.type_to_fully_annotated_layout(ty)? else { + return Ok(None) + }; + Ok(Some((*tag, layout, annotated_layout))) +} + #[macro_export] macro_rules! make_native { ($native: expr) => { diff --git a/sui-execution/latest/sui-move-natives/src/object_runtime/mod.rs b/sui-execution/latest/sui-move-natives/src/object_runtime/mod.rs index 62c9877cf936b..01ec2436bce45 100644 --- a/sui-execution/latest/sui-move-natives/src/object_runtime/mod.rs +++ b/sui-execution/latest/sui-move-natives/src/object_runtime/mod.rs @@ -22,6 +22,7 @@ use std::{ use sui_protocol_config::{check_limit_by_meter, LimitThresholdCrossed, ProtocolConfig}; use sui_types::{ base_types::{MoveObjectType, ObjectID, SequenceNumber, SuiAddress}, + committee::EpochId, error::{ExecutionError, ExecutionErrorKind, VMMemoryLimitExceededSubStatusCode}, id::UID, metrics::LimitsMetrics, @@ -80,6 +81,7 @@ pub(crate) struct ObjectRuntimeState { // TODO these struct tags can be removed if type_to_type_tag was exposed in the session transfers: LinkedHashMap, events: Vec<(Type, StructTag, Value)>, + received: Set, } #[derive(Clone)] @@ -163,6 +165,7 @@ impl<'a> ObjectRuntime<'a> { is_metered: bool, protocol_config: &ProtocolConfig, metrics: Arc, + epoch_id: EpochId, ) -> Self { let mut input_object_owners = BTreeMap::new(); let mut root_version = BTreeMap::new(); @@ -185,6 +188,7 @@ impl<'a> ObjectRuntime<'a> { is_metered, LocalProtocolConfig::new(protocol_config), metrics.clone(), + epoch_id, ), test_inventories: TestInventories::new(), state: ObjectRuntimeState { @@ -193,6 +197,7 @@ impl<'a> ObjectRuntime<'a> { deleted_ids: Set::new(), transfers: LinkedHashMap::new(), events: vec![], + received: Set::new(), }, is_metered, constants: LocalProtocolConfig::new(protocol_config), @@ -334,6 +339,28 @@ impl<'a> ObjectRuntime<'a> { .object_exists_and_has_type(parent, child, child_type) } + pub(super) fn receive_object( + &mut self, + parent: ObjectID, + child: ObjectID, + child_version: SequenceNumber, + child_ty: &Type, + child_layout: &MoveTypeLayout, + child_fully_annotated_layout: &MoveTypeLayout, + child_move_type: MoveObjectType, + ) -> PartialVMResult>> { + self.state.received.insert(child, ()); + self.object_store.receive_object( + parent, + child, + child_version, + child_ty, + child_layout, + child_fully_annotated_layout, + child_move_type, + ) + } + pub(crate) fn get_or_fetch_child_object( &mut self, parent: ObjectID, @@ -482,6 +509,7 @@ impl ObjectRuntimeState { deleted_ids, transfers, events: user_events, + received, } = self; let input_owner_map = input_objects .iter() @@ -501,17 +529,19 @@ impl ObjectRuntimeState { let writes: LinkedHashMap<_, _> = transfers .into_iter() .map(|(id, (owner, type_, value))| { - let write_kind = - if input_objects.contains_key(&id) || loaded_child_objects.contains_key(&id) { - debug_assert!(!new_ids.contains_key(&id)); - WriteKind::Mutate - } else if id == SUI_SYSTEM_STATE_OBJECT_ID || new_ids.contains_key(&id) { - // SUI_SYSTEM_STATE_OBJECT_ID is only transferred during genesis - // TODO find a way to insert this in the new_ids during genesis transactions - WriteKind::Create - } else { - WriteKind::Unwrap - }; + let write_kind = if input_objects.contains_key(&id) + || loaded_child_objects.contains_key(&id) + || received.contains_key(&id) + { + debug_assert!(!new_ids.contains_key(&id)); + WriteKind::Mutate + } else if id == SUI_SYSTEM_STATE_OBJECT_ID || new_ids.contains_key(&id) { + // SUI_SYSTEM_STATE_OBJECT_ID is only transferred during genesis + // TODO find a way to insert this in the new_ids during genesis transactions + WriteKind::Create + } else { + WriteKind::Unwrap + }; (id, (write_kind, owner, type_, value)) }) .collect(); @@ -520,18 +550,21 @@ impl ObjectRuntimeState { .into_iter() .map(|(id, ())| { debug_assert!(!new_ids.contains_key(&id)); - let delete_kind = - if input_objects.contains_key(&id) || loaded_child_objects.contains_key(&id) { - DeleteKind::Normal - } else { - DeleteKind::UnwrapThenDelete - }; + let delete_kind = if input_objects.contains_key(&id) + || loaded_child_objects.contains_key(&id) + || received.contains_key(&id) + { + DeleteKind::Normal + } else { + DeleteKind::UnwrapThenDelete + }; (id, delete_kind) }) .collect(); // remaining by value objects must be wrapped let remaining_by_value_objects = by_value_inputs .into_iter() + .chain(received.keys().copied()) .filter(|id| { !writes.contains_key(id) && !deletions.contains_key(id) diff --git a/sui-execution/latest/sui-move-natives/src/object_runtime/object_store.rs b/sui-execution/latest/sui-move-natives/src/object_runtime/object_store.rs index 6f31f1af27cb3..a0a83b0d6d76c 100644 --- a/sui-execution/latest/sui-move-natives/src/object_runtime/object_store.rs +++ b/sui-execution/latest/sui-move-natives/src/object_runtime/object_store.rs @@ -15,6 +15,7 @@ use std::{ use sui_protocol_config::{check_limit_by_meter, LimitThresholdCrossed}; use sui_types::{ base_types::{MoveObjectType, ObjectID, SequenceNumber}, + committee::EpochId, error::VMMemoryLimitExceededSubStatusCode, metrics::LimitsMetrics, object::{Data, MoveObject, Owner}, @@ -54,6 +55,8 @@ struct Inner<'a> { constants: LocalProtocolConfig, // Metrics for reporting exceeded limits metrics: Arc, + // Epoch ID for the current transaction. Used for receiving objects. + current_epoch_id: EpochId, } // maintains the runtime GlobalValues for child objects and manages the fetching of objects @@ -79,6 +82,48 @@ pub(crate) enum ObjectResult { } impl<'a> Inner<'a> { + fn receive_object_from_store( + &self, + owner: ObjectID, + child: ObjectID, + version: SequenceNumber, + ) -> PartialVMResult> { + let child_opt = self + .resolver + .receive_object_at_version(&owner, &child, version, self.current_epoch_id) + .map_err(|msg| { + PartialVMError::new(StatusCode::STORAGE_ERROR).with_message(format!("{msg}")) + })?; + let obj_opt = if let Some(object) = child_opt { + // guard against bugs in `receive_object_at_version`: if it returns a child object such that + // C.parent != parent, we raise an invariant violation since that should be checked by + // `receive_object_at_version`. + if object.owner != Owner::AddressOwner(owner.into()) { + return Err( + PartialVMError::new(StatusCode::STORAGE_ERROR).with_message(format!( + "Bad owner for {child}. \ + Expected owner {owner} but found owner {}", + object.owner + )), + ); + } + match object.data { + Data::Package(_) => { + return Err(PartialVMError::new(StatusCode::STORAGE_ERROR).with_message( + format!( + "Mismatched object type for {child}. \ + Expected a Move object but found a Move package" + ), + )) + } + Data::Move(mo @ MoveObject { .. }) => Some(mo), + } + } else { + None + }; + Ok(obj_opt) + } + fn get_or_fetch_object_from_store( &mut self, parent: ObjectID, @@ -225,6 +270,34 @@ impl<'a> Inner<'a> { } } +fn deserialize_move_object( + obj: &MoveObject, + child_ty: &Type, + child_ty_layout: &MoveTypeLayout, + child_move_type: MoveObjectType, +) -> PartialVMResult> { + let child_id = obj.id(); + // object exists, but the type does not match + if obj.type_() != &child_move_type { + return Ok(ObjectResult::MismatchedType); + } + let value = match Value::simple_deserialize(obj.contents(), child_ty_layout) { + Some(v) => v, + None => { + return Err( + PartialVMError::new(StatusCode::FAILED_TO_DESERIALIZE_RESOURCE).with_message( + format!("Failed to deserialize object {child_id} with type {child_move_type}",), + ), + ) + } + }; + Ok(ObjectResult::Loaded(( + child_ty.clone(), + child_move_type, + value, + ))) +} + impl<'a> ObjectStore<'a> { pub(super) fn new( resolver: &'a dyn ChildObjectResolver, @@ -232,6 +305,7 @@ impl<'a> ObjectStore<'a> { is_metered: bool, constants: LocalProtocolConfig, metrics: Arc, + current_epoch_id: EpochId, ) -> Self { Self { inner: Inner { @@ -241,6 +315,7 @@ impl<'a> ObjectStore<'a> { is_metered, constants: constants.clone(), metrics, + current_epoch_id, }, store: BTreeMap::new(), is_metered, @@ -248,6 +323,43 @@ impl<'a> ObjectStore<'a> { } } + pub(super) fn receive_object( + &mut self, + parent: ObjectID, + child: ObjectID, + child_version: SequenceNumber, + child_ty: &Type, + child_layout: &MoveTypeLayout, + child_fully_annotated_layout: &MoveTypeLayout, + child_move_type: MoveObjectType, + ) -> PartialVMResult>> { + let obj = match self + .inner + .receive_object_from_store(parent, child, child_version)? + { + Some(obj) if obj.version() == child_version => obj, + _ => return Ok(None), + }; + + let deserialized_value = + match deserialize_move_object(&obj, child_ty, child_layout, child_move_type)? { + ObjectResult::MismatchedType => ObjectResult::MismatchedType, + ObjectResult::Loaded((_, _, gv)) => ObjectResult::Loaded(gv), + }; + // Find all UIDs inside of the value and update the object parent maps with the contained + // UIDs in the received value. They should all have an upper bound version as the receiving object. + let contained_uids = + get_all_uids(child_fully_annotated_layout, obj.contents()).map_err(|e| { + PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR).with_message( + format!("Failed to find UIDs for receiving object. ERROR: {e}"), + ) + })?; + for id in contained_uids { + self.inner.root_version.insert(id, child_version); + } + Ok(Some(deserialized_value)) + } + pub(super) fn object_exists( &mut self, parent: ObjectID, diff --git a/sui-execution/latest/sui-move-natives/src/transfer.rs b/sui-execution/latest/sui-move-natives/src/transfer.rs index d6fa07f4a1d00..c1a53b757ea00 100644 --- a/sui-execution/latest/sui-move-natives/src/transfer.rs +++ b/sui-execution/latest/sui-move-natives/src/transfer.rs @@ -2,7 +2,10 @@ // SPDX-License-Identifier: Apache-2.0 use super::object_runtime::{ObjectRuntime, TransferResult}; -use crate::NativesCostTable; +use crate::{ + get_receiver_object_id, get_tag_and_layouts, object_runtime::object_store::ObjectResult, + NativesCostTable, +}; use move_binary_format::errors::{PartialVMError, PartialVMResult}; use move_core_types::{ account_address::AccountAddress, gas_algebra::InternalGas, language_storage::TypeTag, @@ -14,9 +17,92 @@ use move_vm_types::{ }; use smallvec::smallvec; use std::collections::VecDeque; -use sui_types::{base_types::SequenceNumber, object::Owner}; +use sui_types::{ + base_types::{MoveObjectType, ObjectID, SequenceNumber}, + object::Owner, +}; const E_SHARED_NON_NEW_OBJECT: u64 = 0; +const E_BCS_SERIALIZATION_FAILURE: u64 = 1; +const E_RECEIVING_OBJECT_TYPE_MISMATCH: u64 = 2; +// Represents both the case where the object does not exist and the case where the object is not +// able to be accessed through the parent that is passed-in. +const E_UNABLE_TO_RECEIVE_OBJECT: u64 = 3; + +#[derive(Clone, Debug)] +pub struct TransferReceiveObjectInternalCostParams { + pub transfer_receive_object_internal_cost_base: InternalGas, +} +/*************************************************************************************************** +* native fun receive_object_internal +* Implementation of the Move native function `receive_object_internal(parent: &mut UID, rec: Receiver): T` +* gas cost: transfer_receive_object_internal_cost_base | covers various fixed costs in the oper +**************************************************************************************************/ + +pub fn receive_object_internal( + context: &mut NativeContext, + mut ty_args: Vec, + mut args: VecDeque, +) -> PartialVMResult { + debug_assert!(ty_args.len() == 1); + debug_assert!(args.len() == 3); + let transfer_receive_object_internal_cost_params = context + .extensions_mut() + .get::() + .transfer_receive_object_internal_cost_params + .clone(); + native_charge_gas_early_exit!( + context, + transfer_receive_object_internal_cost_params.transfer_receive_object_internal_cost_base + ); + let child_ty = ty_args.pop().unwrap(); + let child_receiver_sequence_number: SequenceNumber = pop_arg!(args, u64).into(); + let child_receiver_object_id = args.pop_back().unwrap(); + let parent = pop_arg!(args, AccountAddress).into(); + assert!(args.is_empty()); + let child_id: ObjectID = get_receiver_object_id(child_receiver_object_id.copy_value().unwrap()) + .unwrap() + .value_as::() + .unwrap() + .into(); + assert!(ty_args.is_empty()); + + let Some((tag, layout, annotated_layout)) = get_tag_and_layouts(context, &child_ty)? else { + return Ok(NativeResult::err( + context.gas_used(), + E_BCS_SERIALIZATION_FAILURE, + )) + }; + + let object_runtime: &mut ObjectRuntime = context.extensions_mut().get_mut(); + let child = match object_runtime.receive_object( + parent, + child_id, + child_receiver_sequence_number, + &child_ty, + &layout, + &annotated_layout, + MoveObjectType::from(tag), + ) { + // NB: Loaded and doesn't exist and inauthenticated read should lead to the exact same error + Ok(None) => { + return Ok(NativeResult::err( + context.gas_used(), + E_UNABLE_TO_RECEIVE_OBJECT, + )) + } + Ok(Some(ObjectResult::Loaded(gv))) => gv, + Ok(Some(ObjectResult::MismatchedType)) => { + return Ok(NativeResult::err( + context.gas_used(), + E_RECEIVING_OBJECT_TYPE_MISMATCH, + )) + } + Err(x) => return Err(x), + }; + + Ok(NativeResult::ok(context.gas_used(), smallvec![child])) +} #[derive(Clone, Debug)] pub struct TransferInternalCostParams { diff --git a/sui-execution/latest/sui-verifier/src/private_generics.rs b/sui-execution/latest/sui-verifier/src/private_generics.rs index 29213d150c0ac..f7d5ca092ce71 100644 --- a/sui-execution/latest/sui-verifier/src/private_generics.rs +++ b/sui-execution/latest/sui-verifier/src/private_generics.rs @@ -23,6 +23,7 @@ pub const PUBLIC_TRANSFER_FUNCTIONS: &[&IdentStr] = &[ ident_str!("public_transfer"), ident_str!("public_freeze_object"), ident_str!("public_share_object"), + ident_str!("receive"), ]; pub const PRIVATE_TRANSFER_FUNCTIONS: &[&IdentStr] = &[ ident_str!("transfer"), @@ -33,6 +34,7 @@ pub const TRANSFER_IMPL_FUNCTIONS: &[&IdentStr] = &[ ident_str!("transfer_impl"), ident_str!("freeze_object_impl"), ident_str!("share_object_impl"), + ident_str!("receive_impl"), ]; /// All transfer functions (the functions in `sui::transfer`) are "private" in that they are diff --git a/sui-execution/v0/sui-adapter/src/programmable_transactions/context.rs b/sui-execution/v0/sui-adapter/src/programmable_transactions/context.rs index 2b4e2af84eb4a..d2025e88e11ed 100644 --- a/sui-execution/v0/sui-adapter/src/programmable_transactions/context.rs +++ b/sui-execution/v0/sui-adapter/src/programmable_transactions/context.rs @@ -366,7 +366,7 @@ impl<'vm, 'state, 'a> ExecutionContext<'vm, 'state, 'a> { // Immutable objects and shared objects cannot be taken by value if matches!( input_metadata_opt, - Some(InputObjectMetadata { + Some(InputObjectMetadata::InputObject { owner: Owner::Immutable | Owner::Shared { .. }, .. }) @@ -410,7 +410,7 @@ impl<'vm, 'state, 'a> ExecutionContext<'vm, 'state, 'a> { // error if taken return Err(CommandArgumentError::InvalidValueUsage); }; - if input_metadata_opt.is_some() && !input_metadata_opt.unwrap().is_mutable_input { + if let Some(InputObjectMetadata::InputObject { is_mutable_input: false, .. }) = input_metadata_opt { return Err(CommandArgumentError::InvalidObjectByMutRef); } // if it is copyable, don't take it as we allow for the value to be copied even if @@ -569,20 +569,25 @@ impl<'vm, 'state, 'a> ExecutionContext<'vm, 'state, 'a> { inner: ResultValue { value, .. }, } = input; let Some(object_metadata) = object_metadata_opt else { return Ok(()) }; - let is_mutable_input = object_metadata.is_mutable_input; - let owner = object_metadata.owner; - let id = object_metadata.id; - input_object_metadata.insert(object_metadata.id, object_metadata); + let InputObjectMetadata::InputObject { + is_mutable_input, + owner, + id, + .. + } = &object_metadata else { + invariant_violation!("Found non-input object metadata for input object when adding writes to input objects"); + }; + input_object_metadata.insert(object_metadata.id(), object_metadata.clone()); let Some(Value::Object(object_value)) = value else { - by_value_inputs.insert(id); + by_value_inputs.insert(*id); return Ok(()) }; - if is_mutable_input { - add_additional_write(&mut additional_writes, owner, object_value)?; + if *is_mutable_input { + add_additional_write(&mut additional_writes, *owner, object_value)?; } Ok(()) }; - let gas_id_opt = gas.object_metadata.as_ref().map(|info| info.id); + let gas_id_opt = gas.object_metadata.as_ref().map(|info| info.id()); add_input_object_write(gas)?; for input in inputs { add_input_object_write(input)? @@ -632,6 +637,7 @@ impl<'vm, 'state, 'a> ExecutionContext<'vm, 'state, 'a> { )); } } + Some(Value::Receiving(_, _)) => invariant_violation!("Impossible to hit Receiving in v0"), } } } @@ -768,10 +774,10 @@ impl<'vm, 'state, 'a> ExecutionContext<'vm, 'state, 'a> { let old_version = match input_object_metadata.get(&id) { Some(metadata) => { assert_invariant!( - !matches!(metadata.owner, Owner::Immutable), + !matches!(metadata, InputObjectMetadata::InputObject { owner: Owner::Immutable, .. }), "Attempting to delete immutable object {id} via delete kind {delete_kind}" ); - metadata.version + metadata.version() } None => { match loaded_child_objects.get(&id) { @@ -1144,7 +1150,7 @@ fn load_object<'vm, 'state>( }; let owner = obj.owner; let version = obj.version(); - let object_metadata = InputObjectMetadata { + let object_metadata = InputObjectMetadata::InputObject { id, is_mutable_input, owner, @@ -1216,6 +1222,7 @@ fn load_object_arg<'vm, 'state>( /* imm override */ !mutable, id, ), + ObjectArg::Receiving(_) => invariant_violation!("Impossible to hit Receiving in v0"), } } @@ -1304,7 +1311,7 @@ unsafe fn create_written_object<'vm, 'state>( ); let old_obj_ver = metadata_opt - .map(|metadata| metadata.version) + .map(|metadata| metadata.version()) .or_else(|| loaded_child_version_opt.copied()); debug_assert!( diff --git a/sui-execution/v0/sui-adapter/src/programmable_transactions/execution.rs b/sui-execution/v0/sui-adapter/src/programmable_transactions/execution.rs index 5fd00980f71b8..7f9f187f7efb1 100644 --- a/sui-execution/v0/sui-adapter/src/programmable_transactions/execution.rs +++ b/sui-execution/v0/sui-adapter/src/programmable_transactions/execution.rs @@ -1371,6 +1371,7 @@ fn check_param_type( ty } Value::Object(obj) => &obj.type_, + Value::Receiving(_, _) => invariant_violation!("Receiving value should never occur in v0 execution"), }; if ty != param_ty { Err(command_argument_error(