-
Notifications
You must be signed in to change notification settings - Fork 11.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Transfer to Object #12658
Comments
I'm not sure i understand right, ppl A wrap the coin into the Receiving struct and send it to ppl B, B accept it in by calling accept payment. but how can A wrap the item to be sent into the Receiving struct? should it provide a "wrap" method? by the way, this will be very helpful for me to construct a "bag" system in on-chain game. |
The key insight here is that the What happens is that when |
## Description This PR implements the core transfer-to-object functionality. In particular it implements the ability to "receive" an object that was sent to the address (object ID) of another object using one of the `transfer` or `transfer_public` functions in the `transfer` module. More detail is given on the programming model in the attached issue so I will not go into that. SDK support for receiving objects has been added in the two PRs stacked on this one: * #12987 Adds the `Receiving` type to the json-rpc types, and adds support receiving objects in the Typescript SDK. * #12988 Adds support for receiving objects in the Rust SDK * #13420 Adds pruning of the `per_epoch_object_marker` table at epoch boundaries ## Test Plan I've written a number of tests for this that I believe cover things: * Execution-correctness tests for this in the transactional tests * Tests for effect computation in the new sui-core `transfer_to_object.rs` tests (e.g., receive-then-unwrap, receive-unwrap-wrap, etc). * Tests for lock-freeness of receiving arguments (i.e., that the object identified by the `Receiving` argument is not locked at signing) in the sui-core `transfer_to_object.rs` tests * Tests that dependencies are correctly registered, and notified in the transaction manager for `Receiving` arguments to transactions (see new tests in the `transaction_manager_tests.rs` file). A more detailed listing of the tests: * PTBs - Receive object and return to PTB - Do not do anything with the returned (non-drop) value [`receive_return_object_dont_touch.move`] - Call transfer object on it [`receive_return_object_then_transfer.move`] - Basic "can receive and then do something in the function" [`basic_receive.move`] - Duplicate "Receive" arguments to the PTB [`duplicate_receive_argument.move`] - Pass but don't use `Receiving` argument, then later use it in PTB. - By immut ref [`pass_receiver_immut_then_reuse.move`] - By mut ref [`pass_receiver_mut_then_reuse.move`] - By value and returning it [`pass_through_then_receive.move`] - Various combinations of receivership being passed [`receive_by_ref.move`] (checking borrow/borrow_mut, and restore rules for PTB execution) - Receive object of different type [`receive_invalid_type.move`] - Receive object with non-address owner ownership [`receive_object_owner.move`] - Reuse of input receiving argument [`take_receiver_then_try_to_reuse.move`] * Type malleability [`receive_invalid_param_ty.move`] - Pass receiver into a non-receiver type - primitive type - struct type with same layout - struct type with different layout - Pass non-receiver into a receiver type - primitive type - struct type with same layout - struct type with different layout * Resource conservation/Effects calculation (both transactional tests and sui-core tests for explicit effects checks) - Do various things with object after receiving it: - Immediately place it as a dynamic field [`receive_dof_and_mutate.move`] - Immediately add a dynamic field to it [`receive_add_dof_and_mutate.move`] - Immediately add a dynamic field to it, add as a dynamic field to parent object, then mutate both [`receive_add_dof_and_mutate.move`] - Immediately transfer it [`receive_and_send_back.move`] - Immediately delete it [`receive_and_deleted.move`] - Immediately wrap it [`receive_and_wrap.move`] - Immediately abort [`receive_and_abort.move`] - Don't use it [`receive_by_value_flow_through.move`] - Receive multiple times in a row making sure effects stay in-sync as expected [`receive_multiple_times_in_row.move`] - Shared objects - Make sure we can receive if object is transferred to an object which is already shared [`shared_parent/basic_receive.move`] - Make sure we can receive if object is transferred to an object which is then shared [`shared_parent/transfer_then_share.move`] - Non-usage of receiving object argument off a shared parent object [`shared_parent/drop_receiving.move`] - Receive object off of shared parent, add as dynamic field of shared parent and then mutate through the parent [`shared_parent/receive_dof_and_mutate.move`] - Send and receive the same object to the same shared parent multiple times [`shared_parent/receive_multiple_times_in_row.move`] - MVCC -- Test that we calculate contained UIDs correctly when we receive an object. This is tested in [`mvcc/receive_object_dof.move`] and [`mvcc/receive_object_split_changes_dof.move`] - Sui core tests checking explicit parts of the calculated effects to make sure they match what we expect: - Immediately unwrap then transfer inner object [`transfer_to_object_tests.rs/test_tto_unwrap_transfer`] - Immediately unwrap then delete inner object as well [`transfer_to_object_tests.rs/test_tto_unwrap_delete`] - Immediately unwrap then add inner object as dynamic field [`transfer_to_object_tests.rs/test_tto_unwrap_add_as_dynamic_field`] - Immediately unwrap, then wrap again -- this is part of the above since adding a dynamic field wraps the object - Basic object receive [`transfer_to_object_tests/test_tto_transfer`] - Pass but don't ise Receiving argument [`transfer_to_object_tests/test_tto_unused_receiver`] - Pass by different references [`transfer_to_object_tests/test_tto_pass_receiving_by_refs`] - Receive and immediately delete [`transfer_to_object_tests/test_tto_delete`] - Receive, wrap, and then transfer wrapped object [`transfer_to_object_tests/test_tto_wrap`] * Sui Core for object locking and transaction dependendency calculation in effects - Test that receiving object arguments are not locked, and that different orders of execution for two certs that want to receive the same argument (but only one is valid) can both be run in either order, and both return the same execution effects in either order [`transfer_to_object_tests/test_tt_not_locked`] - Test that transaction dependencies are added correctly: - Basic test that we add transaction dependendency if we execute successfully and receive the object [`transfer_to_object_tests/test_tto_valid_dependencies`] - Similar case for if we delete the object immediately [`transfer_to_object_tests/test_tto_valid_dependencies_delete_on_receive`] - That we don't register the transaction dependendency if we don't receive the object [`transfer_to_object_tests/test_tto_dependencies_dont_receive`] - That we don't register the transaction dependendency if we don't receive the object and we abort [`transfer_to_object_tests/test_tto_dependencies_dont_receive_but_abort`] - That we register the dependendency if we received the object, even if we then went on to abort in the transaction [`transfer_to_object_tests/test_tto_dependencies_receive_and_abort`] - Dynamic object field spoofing: make sure we don't accidentally register a dynamic object field load of an object that we want to receive at a different version as a receivership of that object (i.e., don't register the transaction dependendency) [`transfer_to_object_tests/receive_and_dof_interleave`] ## Additional tests - PTBs - `MakeMoveVec`: - create but don't use [receive_many_move_vec.move] - pass vec by value but don't receive [receive_many_move_vec.move] - pass vec by ref then use value to receive in later command [receive_many_move_vec.move] - Pass vec by mut ref and pop/receive some, then receive rest in other call [receive_many_move_vec.move] - Pass vec by mut ref, only receive some [receive_many_move_vec.move] - Pass vec by value, only receive some [receive_many_move_vec.move] - Pass vec by value, receive all [receive_many_move_vec.move] - Pack receiving tickets into a struct (some/all) then receive transitively [receive_duo_struct.move] - Type mismatches: - Receiving and phony struct with same struct layout and right type args ([receive_invalid_param_ty.move]) - Receiving with mismatched type args [move_vec_receiving_types.move] - Receiving with multiple different type args [move_vec_receiving_types.move] - `TransferObjects` - Try to transfer receiving ticket [receive_ticket_coin_operations.move] - `SplitCoins` - Try to split a receiving ticket [receive_ticket_coin_operations.move] - `MergeCoins` - Try to merge a receiving ticket [receive_ticket_coin_operations.move] - MVCC [`receive_object_access_through_parent[dof/df].move`] - Transaction input checks (in sui-core tests) - Delete between cert and execution [tests in `test_tto_not_locked`in the sui-core tests - Cert denial if sending a transaction where `input_objects \intersect receiving_object != {}` [`test_tto_intersection_input_and_receiving_objects`] - Type-fixing for receiving arguments [pt_receive_type_fixing.move] --- If your changes are not user-facing and not a breaking change, you can skip the following section. Otherwise, please indicate what changed, and then add to the Release Notes section as highlighted during the release process. ### Type of Change (Check all that apply) - [X] protocol change - [X] user-visible impact - [ ] breaking change for a client SDKs - [X] breaking change for FNs (FN binary must upgrade) - [X] breaking change for validators or node operators (must upgrade binaries) - [ ] breaking change for on-chain data layout - [ ] necessitate either a data wipe or data migration ### Release notes Added the ability to receive objects off of another object. This is currently only turned on in devnet. More information on transfer-to-object, receiving objects off of other objects, and current SDK support can be found in the GitHub issue which can be found here: #12658
Not done -- accidentally got closed by the landing of the main PR. |
Are references allowed in function args (i.e. Which would also lead me to assuming you can't use a command output as input: ...
const tx = new TransactionBlock();
const [coin] = tx.splitCoins(tx.gas,[tx.pure(1000000)]);
tx.moveCall({
target: `${examplePackageId}::account::accept_payment`,
arguments: [tx.object("0xcafe"), coin]
});
const result = await signer.signAndExecuteTransactionBlock({
transactionBlock: tx,
});
... |
Both are allowed (so both To your second question: you can use command output as input to functions that want public fun pass_through<T: key>(x: Receiving<T>): Receiving<T> { x } and this PTB would be valid: ...
const tx = new TransactionBlock();
const [recv] = tx.moveCall({
target: `${examplePackageId}::account::pass_through`,
arguments: [tx.object("0xc0ffee")]
});
tx.moveCall({
target: `${examplePackageId}::account::accept_payment`,
arguments: [tx.object("0xcafe"), recv]
});
const result = await signer.signAndExecuteTransactionBlock({
transactionBlock: tx,
});
... |
@tzakian Good to know, thanks for confirming! |
So I've got all manner of things tested and working: /// Pass through object
public fun pass_through<T: key + store>(sent: Receiving<T>): Receiving<T> {
sent
}
/// Pass through object ref - This only inspects without error but execution fails with invalid return
public fun pass_through_ref<T: key + store>(sent: &Receiving<T>): &Receiving<T> {
sent
}
/// Pass through object mutable ref - This only inspects without error but execution fails with invalid return
public fun pass_through_mut<T: key + store>(sent: &mut Receiving<T>): &mut Receiving<T> {
sent
}
/// Pass in object mutable ref
public fun pass_in_mut_payment<T: key + store>(_sent: &mut Receiving<T>) {
}
/// Reference object
public fun pass_in_ref_payment<T: key + store>(_sent: &Receiving<T>) {
} These are fine whether I use explicit or result of previous commend. I'm using a gas coin as T so type_argument is However, methinks your example |
Great catch! Thank you for pointing it out. This is indeed an oversight -- I'll update. The only restriction is that the type |
That fixes the type constraint however; it is failing in inspect and/or run against devnet (1.11.0): "effects": {
"status": {
"status": "failure",
"error": "MoveAbort(MoveLocation { module: ModuleId { address: 0000000000000000000000000000000000000000000000000000000000000002, name: Identifier(\"transfer\") }, function: 10, instruction: 0, function_name: Some(\"receive_impl\") }, 3) in command 0"
}
} |
Had a look and it looks like the issue is that the object that you want to receive (https://suiexplorer.com/object/0x0f7f7288b07de5a31ac8915b503f6d7a09d05400d4a40d594d3f7a279d34050b?network=devnet) is not owned by the object you're passing as the parent (https://suiexplorer.com/object/0x7e685a1bdc34671488560ce26952bc1f57937b0e7fc465da0a6cc80740e28708?network=devnet). You'll need to first transfer |
Yep, that did it! (May want to add to example....) thanks @tzakian ... pysui now support it... with confidence! |
Amazing! |
Receiving object support for typescript and Rust has now been landed in |
hello, if I wrongly transfer a coin, like usdt or Cetus, to the sui gas object that I owned, can I retrieve the coin by using the function mentioned here? |
## Description This PR implements the core transfer-to-object functionality. In particular it implements the ability to "receive" an object that was sent to the address (object ID) of another object using one of the `transfer` or `transfer_public` functions in the `transfer` module. More detail is given on the programming model in the attached issue so I will not go into that. SDK support for receiving objects has been added in the two PRs stacked on this one: * #12987 Adds the `Receiving` type to the json-rpc types, and adds support receiving objects in the Typescript SDK. * #12988 Adds support for receiving objects in the Rust SDK * #13420 Adds pruning of the `per_epoch_object_marker` table at epoch boundaries ## Test Plan I've written a number of tests for this that I believe cover things: * Execution-correctness tests for this in the transactional tests * Tests for effect computation in the new sui-core `transfer_to_object.rs` tests (e.g., receive-then-unwrap, receive-unwrap-wrap, etc). * Tests for lock-freeness of receiving arguments (i.e., that the object identified by the `Receiving` argument is not locked at signing) in the sui-core `transfer_to_object.rs` tests * Tests that dependencies are correctly registered, and notified in the transaction manager for `Receiving` arguments to transactions (see new tests in the `transaction_manager_tests.rs` file). A more detailed listing of the tests: * PTBs - Receive object and return to PTB - Do not do anything with the returned (non-drop) value [`receive_return_object_dont_touch.move`] - Call transfer object on it [`receive_return_object_then_transfer.move`] - Basic "can receive and then do something in the function" [`basic_receive.move`] - Duplicate "Receive" arguments to the PTB [`duplicate_receive_argument.move`] - Pass but don't use `Receiving` argument, then later use it in PTB. - By immut ref [`pass_receiver_immut_then_reuse.move`] - By mut ref [`pass_receiver_mut_then_reuse.move`] - By value and returning it [`pass_through_then_receive.move`] - Various combinations of receivership being passed [`receive_by_ref.move`] (checking borrow/borrow_mut, and restore rules for PTB execution) - Receive object of different type [`receive_invalid_type.move`] - Receive object with non-address owner ownership [`receive_object_owner.move`] - Reuse of input receiving argument [`take_receiver_then_try_to_reuse.move`] * Type malleability [`receive_invalid_param_ty.move`] - Pass receiver into a non-receiver type - primitive type - struct type with same layout - struct type with different layout - Pass non-receiver into a receiver type - primitive type - struct type with same layout - struct type with different layout * Resource conservation/Effects calculation (both transactional tests and sui-core tests for explicit effects checks) - Do various things with object after receiving it: - Immediately place it as a dynamic field [`receive_dof_and_mutate.move`] - Immediately add a dynamic field to it [`receive_add_dof_and_mutate.move`] - Immediately add a dynamic field to it, add as a dynamic field to parent object, then mutate both [`receive_add_dof_and_mutate.move`] - Immediately transfer it [`receive_and_send_back.move`] - Immediately delete it [`receive_and_deleted.move`] - Immediately wrap it [`receive_and_wrap.move`] - Immediately abort [`receive_and_abort.move`] - Don't use it [`receive_by_value_flow_through.move`] - Receive multiple times in a row making sure effects stay in-sync as expected [`receive_multiple_times_in_row.move`] - Shared objects - Make sure we can receive if object is transferred to an object which is already shared [`shared_parent/basic_receive.move`] - Make sure we can receive if object is transferred to an object which is then shared [`shared_parent/transfer_then_share.move`] - Non-usage of receiving object argument off a shared parent object [`shared_parent/drop_receiving.move`] - Receive object off of shared parent, add as dynamic field of shared parent and then mutate through the parent [`shared_parent/receive_dof_and_mutate.move`] - Send and receive the same object to the same shared parent multiple times [`shared_parent/receive_multiple_times_in_row.move`] - MVCC -- Test that we calculate contained UIDs correctly when we receive an object. This is tested in [`mvcc/receive_object_dof.move`] and [`mvcc/receive_object_split_changes_dof.move`] - Sui core tests checking explicit parts of the calculated effects to make sure they match what we expect: - Immediately unwrap then transfer inner object [`transfer_to_object_tests.rs/test_tto_unwrap_transfer`] - Immediately unwrap then delete inner object as well [`transfer_to_object_tests.rs/test_tto_unwrap_delete`] - Immediately unwrap then add inner object as dynamic field [`transfer_to_object_tests.rs/test_tto_unwrap_add_as_dynamic_field`] - Immediately unwrap, then wrap again -- this is part of the above since adding a dynamic field wraps the object - Basic object receive [`transfer_to_object_tests/test_tto_transfer`] - Pass but don't ise Receiving argument [`transfer_to_object_tests/test_tto_unused_receiver`] - Pass by different references [`transfer_to_object_tests/test_tto_pass_receiving_by_refs`] - Receive and immediately delete [`transfer_to_object_tests/test_tto_delete`] - Receive, wrap, and then transfer wrapped object [`transfer_to_object_tests/test_tto_wrap`] * Sui Core for object locking and transaction dependendency calculation in effects - Test that receiving object arguments are not locked, and that different orders of execution for two certs that want to receive the same argument (but only one is valid) can both be run in either order, and both return the same execution effects in either order [`transfer_to_object_tests/test_tt_not_locked`] - Test that transaction dependencies are added correctly: - Basic test that we add transaction dependendency if we execute successfully and receive the object [`transfer_to_object_tests/test_tto_valid_dependencies`] - Similar case for if we delete the object immediately [`transfer_to_object_tests/test_tto_valid_dependencies_delete_on_receive`] - That we don't register the transaction dependendency if we don't receive the object [`transfer_to_object_tests/test_tto_dependencies_dont_receive`] - That we don't register the transaction dependendency if we don't receive the object and we abort [`transfer_to_object_tests/test_tto_dependencies_dont_receive_but_abort`] - That we register the dependendency if we received the object, even if we then went on to abort in the transaction [`transfer_to_object_tests/test_tto_dependencies_receive_and_abort`] - Dynamic object field spoofing: make sure we don't accidentally register a dynamic object field load of an object that we want to receive at a different version as a receivership of that object (i.e., don't register the transaction dependendency) [`transfer_to_object_tests/receive_and_dof_interleave`] ## Additional tests - PTBs - `MakeMoveVec`: - create but don't use [receive_many_move_vec.move] - pass vec by value but don't receive [receive_many_move_vec.move] - pass vec by ref then use value to receive in later command [receive_many_move_vec.move] - Pass vec by mut ref and pop/receive some, then receive rest in other call [receive_many_move_vec.move] - Pass vec by mut ref, only receive some [receive_many_move_vec.move] - Pass vec by value, only receive some [receive_many_move_vec.move] - Pass vec by value, receive all [receive_many_move_vec.move] - Pack receiving tickets into a struct (some/all) then receive transitively [receive_duo_struct.move] - Type mismatches: - Receiving and phony struct with same struct layout and right type args ([receive_invalid_param_ty.move]) - Receiving with mismatched type args [move_vec_receiving_types.move] - Receiving with multiple different type args [move_vec_receiving_types.move] - `TransferObjects` - Try to transfer receiving ticket [receive_ticket_coin_operations.move] - `SplitCoins` - Try to split a receiving ticket [receive_ticket_coin_operations.move] - `MergeCoins` - Try to merge a receiving ticket [receive_ticket_coin_operations.move] - MVCC [`receive_object_access_through_parent[dof/df].move`] - Transaction input checks (in sui-core tests) - Delete between cert and execution [tests in `test_tto_not_locked`in the sui-core tests - Cert denial if sending a transaction where `input_objects \intersect receiving_object != {}` [`test_tto_intersection_input_and_receiving_objects`] - Type-fixing for receiving arguments [pt_receive_type_fixing.move] --- If your changes are not user-facing and not a breaking change, you can skip the following section. Otherwise, please indicate what changed, and then add to the Release Notes section as highlighted during the release process. ### Type of Change (Check all that apply) - [X] protocol change - [X] user-visible impact - [ ] breaking change for a client SDKs - [X] breaking change for FNs (FN binary must upgrade) - [X] breaking change for validators or node operators (must upgrade binaries) - [ ] breaking change for on-chain data layout - [ ] necessitate either a data wipe or data migration ### Release notes Added the ability to receive objects off of another object. This is currently only turned on in devnet. More information on transfer-to-object, receiving objects off of other objects, and current SDK support can be found in the GitHub issue which can be found here: MystenLabs/sui#12658
I think this is now completed and available on mainnet, so we can close the issue. |
## Description This PR implements the core transfer-to-object functionality. In particular it implements the ability to "receive" an object that was sent to the address (object ID) of another object using one of the `transfer` or `transfer_public` functions in the `transfer` module. More detail is given on the programming model in the attached issue so I will not go into that. SDK support for receiving objects has been added in the two PRs stacked on this one: * #12987 Adds the `Receiving` type to the json-rpc types, and adds support receiving objects in the Typescript SDK. * #12988 Adds support for receiving objects in the Rust SDK * #13420 Adds pruning of the `per_epoch_object_marker` table at epoch boundaries ## Test Plan I've written a number of tests for this that I believe cover things: * Execution-correctness tests for this in the transactional tests * Tests for effect computation in the new sui-core `transfer_to_object.rs` tests (e.g., receive-then-unwrap, receive-unwrap-wrap, etc). * Tests for lock-freeness of receiving arguments (i.e., that the object identified by the `Receiving` argument is not locked at signing) in the sui-core `transfer_to_object.rs` tests * Tests that dependencies are correctly registered, and notified in the transaction manager for `Receiving` arguments to transactions (see new tests in the `transaction_manager_tests.rs` file). A more detailed listing of the tests: * PTBs - Receive object and return to PTB - Do not do anything with the returned (non-drop) value [`receive_return_object_dont_touch.move`] - Call transfer object on it [`receive_return_object_then_transfer.move`] - Basic "can receive and then do something in the function" [`basic_receive.move`] - Duplicate "Receive" arguments to the PTB [`duplicate_receive_argument.move`] - Pass but don't use `Receiving` argument, then later use it in PTB. - By immut ref [`pass_receiver_immut_then_reuse.move`] - By mut ref [`pass_receiver_mut_then_reuse.move`] - By value and returning it [`pass_through_then_receive.move`] - Various combinations of receivership being passed [`receive_by_ref.move`] (checking borrow/borrow_mut, and restore rules for PTB execution) - Receive object of different type [`receive_invalid_type.move`] - Receive object with non-address owner ownership [`receive_object_owner.move`] - Reuse of input receiving argument [`take_receiver_then_try_to_reuse.move`] * Type malleability [`receive_invalid_param_ty.move`] - Pass receiver into a non-receiver type - primitive type - struct type with same layout - struct type with different layout - Pass non-receiver into a receiver type - primitive type - struct type with same layout - struct type with different layout * Resource conservation/Effects calculation (both transactional tests and sui-core tests for explicit effects checks) - Do various things with object after receiving it: - Immediately place it as a dynamic field [`receive_dof_and_mutate.move`] - Immediately add a dynamic field to it [`receive_add_dof_and_mutate.move`] - Immediately add a dynamic field to it, add as a dynamic field to parent object, then mutate both [`receive_add_dof_and_mutate.move`] - Immediately transfer it [`receive_and_send_back.move`] - Immediately delete it [`receive_and_deleted.move`] - Immediately wrap it [`receive_and_wrap.move`] - Immediately abort [`receive_and_abort.move`] - Don't use it [`receive_by_value_flow_through.move`] - Receive multiple times in a row making sure effects stay in-sync as expected [`receive_multiple_times_in_row.move`] - Shared objects - Make sure we can receive if object is transferred to an object which is already shared [`shared_parent/basic_receive.move`] - Make sure we can receive if object is transferred to an object which is then shared [`shared_parent/transfer_then_share.move`] - Non-usage of receiving object argument off a shared parent object [`shared_parent/drop_receiving.move`] - Receive object off of shared parent, add as dynamic field of shared parent and then mutate through the parent [`shared_parent/receive_dof_and_mutate.move`] - Send and receive the same object to the same shared parent multiple times [`shared_parent/receive_multiple_times_in_row.move`] - MVCC -- Test that we calculate contained UIDs correctly when we receive an object. This is tested in [`mvcc/receive_object_dof.move`] and [`mvcc/receive_object_split_changes_dof.move`] - Sui core tests checking explicit parts of the calculated effects to make sure they match what we expect: - Immediately unwrap then transfer inner object [`transfer_to_object_tests.rs/test_tto_unwrap_transfer`] - Immediately unwrap then delete inner object as well [`transfer_to_object_tests.rs/test_tto_unwrap_delete`] - Immediately unwrap then add inner object as dynamic field [`transfer_to_object_tests.rs/test_tto_unwrap_add_as_dynamic_field`] - Immediately unwrap, then wrap again -- this is part of the above since adding a dynamic field wraps the object - Basic object receive [`transfer_to_object_tests/test_tto_transfer`] - Pass but don't ise Receiving argument [`transfer_to_object_tests/test_tto_unused_receiver`] - Pass by different references [`transfer_to_object_tests/test_tto_pass_receiving_by_refs`] - Receive and immediately delete [`transfer_to_object_tests/test_tto_delete`] - Receive, wrap, and then transfer wrapped object [`transfer_to_object_tests/test_tto_wrap`] * Sui Core for object locking and transaction dependendency calculation in effects - Test that receiving object arguments are not locked, and that different orders of execution for two certs that want to receive the same argument (but only one is valid) can both be run in either order, and both return the same execution effects in either order [`transfer_to_object_tests/test_tt_not_locked`] - Test that transaction dependencies are added correctly: - Basic test that we add transaction dependendency if we execute successfully and receive the object [`transfer_to_object_tests/test_tto_valid_dependencies`] - Similar case for if we delete the object immediately [`transfer_to_object_tests/test_tto_valid_dependencies_delete_on_receive`] - That we don't register the transaction dependendency if we don't receive the object [`transfer_to_object_tests/test_tto_dependencies_dont_receive`] - That we don't register the transaction dependendency if we don't receive the object and we abort [`transfer_to_object_tests/test_tto_dependencies_dont_receive_but_abort`] - That we register the dependendency if we received the object, even if we then went on to abort in the transaction [`transfer_to_object_tests/test_tto_dependencies_receive_and_abort`] - Dynamic object field spoofing: make sure we don't accidentally register a dynamic object field load of an object that we want to receive at a different version as a receivership of that object (i.e., don't register the transaction dependendency) [`transfer_to_object_tests/receive_and_dof_interleave`] ## Additional tests - PTBs - `MakeMoveVec`: - create but don't use [receive_many_move_vec.move] - pass vec by value but don't receive [receive_many_move_vec.move] - pass vec by ref then use value to receive in later command [receive_many_move_vec.move] - Pass vec by mut ref and pop/receive some, then receive rest in other call [receive_many_move_vec.move] - Pass vec by mut ref, only receive some [receive_many_move_vec.move] - Pass vec by value, only receive some [receive_many_move_vec.move] - Pass vec by value, receive all [receive_many_move_vec.move] - Pack receiving tickets into a struct (some/all) then receive transitively [receive_duo_struct.move] - Type mismatches: - Receiving and phony struct with same struct layout and right type args ([receive_invalid_param_ty.move]) - Receiving with mismatched type args [move_vec_receiving_types.move] - Receiving with multiple different type args [move_vec_receiving_types.move] - `TransferObjects` - Try to transfer receiving ticket [receive_ticket_coin_operations.move] - `SplitCoins` - Try to split a receiving ticket [receive_ticket_coin_operations.move] - `MergeCoins` - Try to merge a receiving ticket [receive_ticket_coin_operations.move] - MVCC [`receive_object_access_through_parent[dof/df].move`] - Transaction input checks (in sui-core tests) - Delete between cert and execution [tests in `test_tto_not_locked`in the sui-core tests - Cert denial if sending a transaction where `input_objects \intersect receiving_object != {}` [`test_tto_intersection_input_and_receiving_objects`] - Type-fixing for receiving arguments [pt_receive_type_fixing.move] --- If your changes are not user-facing and not a breaking change, you can skip the following section. Otherwise, please indicate what changed, and then add to the Release Notes section as highlighted during the release process. ### Type of Change (Check all that apply) - [X] protocol change - [X] user-visible impact - [ ] breaking change for a client SDKs - [X] breaking change for FNs (FN binary must upgrade) - [X] breaking change for validators or node operators (must upgrade binaries) - [ ] breaking change for on-chain data layout - [ ] necessitate either a data wipe or data migration ### Release notes Added the ability to receive objects off of another object. This is currently only turned on in devnet. More information on transfer-to-object, receiving objects off of other objects, and current SDK support can be found in the GitHub issue which can be found here: MystenLabs/sui#12658
Transfer to Object
Motivation & Overview
Currently, objects can be transferred to any address using the
sui::transfer::transfer
andsui::transfer::public_transfer
functions. Objects that have been transferred to an account address become owned by that account and transactions sent from that account can use the object. However, if the address that was transferred to belongs to another object instead of an account, the sent object (currently) becomes inaccessible.The goal of Transfer to Object is to allow accessing or “receiving” objects that have been sent to another object. It does so by establishing a form of parent-child authentication relationship, and allowing objects that have been transferred to an object to be received by the (possibly transitive) owner of the parent object.
The access control for receiving an object that has been sent to another object is defined by the module that defines the type of the parent (receiving) object. These restrictions for accessing sent "child" objects are enforced dynamically through providing mutable access to the parent object's
UID
during execution. This supports sending to and receiving from owned objects, dynamic field objects and wrapped objects. Sending to Shared Objects is also planned to be supported.One of the benefits that Transfer to Object seeks to provide is the ability to have a stable ID for e.g., an on-chain wallet or account that can be transferred and remains stable whatever the state of the object that is being sent to; if an object is transferred all of that object's child objects move with it, and the object's address remains stable whether it has been transferred, wrapped, or is held as a dynamic field (etc.).
Programming Model
Sending an Owned Object
From a programmability perspective, an object can be transferred to another object using the existing
sui::transfer::transfer
andsui::transfer::transfer_public
functions. From this -- the "sending side" -- there are no programmability changes or additions.Receiving an Owned Object
Once an object
c
has been sent to another objectp
,p
must then receivec
to do anything with it. To receive the objectc
a newReceiving(o: ObjectRef)
argument type for Programmable Transaction Blocks (PTBs) is needed that takes an Object Reference containing the to-be-received object’sObjectID
,Version
, andDigest
(just as owned object arguments for PTBs do today). However,Receiving
PTB arguments will not be passed as an owned value or mutable reference within the transaction. To explain further we first need to define the core of the receiving interface in Move:Each
Receiving
argument referring to a sent object of typeT
in a PTB will result in exactly oneArgument
whose Move type is asui::transfer::Receiving<T>
. This argument can then be used to receive the sent object of typeT
with thesui::transfer::receive
function.When the
sui::transfer::receive
function is called, a mutable reference to the parent object’sUID
must be passed. Since there is no way to get a mutable reference to theUID
of an object unless the defining module of the object exposes it, this then allows the module that defines the type of the parent object that is receiving the child object to define access control policies and other restrictions on receiving objects that have been sent to it (see the Shared Object Authorization example below for an example of this pattern). The fact that the passed-inUID
actually owns the object referenced by theReceiving
parameter is dynamically checked and enforced. This allows access to objects that have been sent to, e.g., dynamic fields where the ownership chain can only be established dynamically.Note: since
sui::transfer::Receiving
only has thedrop
ability, the existence of aReceiving<T>
argument represents the ability, but not the obligation to receive the object of typeT
specified by the Object Reference in the PTB'sReceiving
argument during that transaction. Some, none, or allReceiving
arguments in a PTB can be used without issue. Any object that corresponds to aReceiving
argument will remain untouched (in particular, its object reference will remain the same) unless it is received.SDK Support
Typescript
From an SDK standpoint, when creating transactions you will interact with
Receiving
transaction inputs almost exactly as you would with eitherobject
orObjectRef
in the Typescript SDK. For example, if in the Basic Account example below you wanted to send a transaction that received a coin object with ID0xc0ffee
that was sent to your account at0xcafe
you could do so with the following:Additionally just as with
object
arguments that also have anObjectRef
constructor where you provide an explicit object ID, version, and digest if you want, there is also aReceivingRef
that takes the same arguments corresponding to a receiving argument.Rust
Similarly to the typescript case you will create and interact with
Receiving
transaction inputs in much the same way you would create and interact with object inputs to a transaction. For example if we wanted to same example that we had in Typescript above but using the Rust SDK and accepting aSUI
coin that was sent to it, it would look something like the following:RPC
Additionally, since object-owned objects are representationally the same on-chain as normal owned objects today, existing RPC-functionality for owned objects works for them out of the box the same as today. E.g., calling
sui_getOwnedObjects
on an address that corresponds to an objectO
will return the list of all objects that are owned by the objectO
.Examples
Basic Account
Below is a basic
Account
abstraction that could be built with the functionality defined in thetransfer
module above for receiving objects transferred to another object. Importantly, the address that coins would be sent to would remain the same regardless of whether theAccount
object was transferred, wrapped, moved to a dynamic field etc. In particular there is a stable ID for a givenAccount
object across the object's lifecycle.Basic Shared-Object with Authorization to Receive Objects
The below shows how one might enforce authenticated access against a shared object's owned objects. The authorization function would need to be filled in.
Dynamic Authorization and Access to Sent Objects
The below shows an example of accessing objects that have been sent to dynamic fields of another object. In this case,
accept_payment_on_named_account
will accept a payment ofCoin<T>
that has been sent to aNamedAccount
object that it controls and will then add that coin to theNamedAccount
's balance for that coin type.The text was updated successfully, but these errors were encountered: