From 3a9ebfd5d18d7a0cb8b7325fc8fce69cedd59e43 Mon Sep 17 00:00:00 2001 From: Xun Li Date: Thu, 10 Mar 2022 13:49:58 -0800 Subject: [PATCH] [Move] Add Transfer::transfer_object_to_id --- .../object_owner/sources/ObjectOwner.move | 13 +++++++++- .../src/unit_tests/move_integration_tests.rs | 25 ++++++++++++++++++ sui_programmability/framework/sources/ID.move | 23 ++++++++-------- .../framework/sources/TestScenario.move | 26 +++++++++---------- .../framework/sources/Transfer.move | 23 ++++++++++++++-- 5 files changed, 82 insertions(+), 28 deletions(-) diff --git a/sui_core/src/unit_tests/data/object_owner/sources/ObjectOwner.move b/sui_core/src/unit_tests/data/object_owner/sources/ObjectOwner.move index 88da32de10923..6ef333adb91ec 100644 --- a/sui_core/src/unit_tests/data/object_owner/sources/ObjectOwner.move +++ b/sui_core/src/unit_tests/data/object_owner/sources/ObjectOwner.move @@ -27,6 +27,17 @@ module ObjectOwner::ObjectOwner { ); } + public fun create_parent_and_child(ctx: &mut TxContext) { + let parent_id = TxContext::new_id(ctx); + let child = Child { id: TxContext::new_id(ctx) }; + let (parent_id, child_ref) = Transfer::transfer_to_object_id(child, parent_id); + let parent = Parent { + id: parent_id, + child: Option::some(child_ref), + }; + Transfer::transfer(parent, TxContext::sender(ctx)); + } + public fun add_child(parent: &mut Parent, child: Child, _ctx: &mut TxContext) { let child_ref = Transfer::transfer_to_object(child, parent); Option::fill(&mut parent.child, child_ref); @@ -55,4 +66,4 @@ module ObjectOwner::ObjectOwner { let Child { id } = child; ID::delete(id); } -} \ No newline at end of file +} diff --git a/sui_core/src/unit_tests/move_integration_tests.rs b/sui_core/src/unit_tests/move_integration_tests.rs index 94914ebefd301..0693438ffd732 100644 --- a/sui_core/src/unit_tests/move_integration_tests.rs +++ b/sui_core/src/unit_tests/move_integration_tests.rs @@ -435,6 +435,31 @@ async fn test_object_owning_another_object() { .await .unwrap(); assert!(effects.status.is_ok()); + + // Create a parent and a child together. This tests the + // Transfer::transfer_to_object_id() API. + let effects = call_move( + &authority, + &gas, + &sender, + &sender_key, + &package, + "ObjectOwner", + "create_parent_and_child", + vec![], + vec![], + vec![], + vec![], + ) + .await + .unwrap(); + assert!(effects.status.is_ok()); + assert_eq!(effects.created.len(), 2); + // Check that one of them is the parent and the other is the child. + assert!( + (effects.created[0].1 == sender && effects.created[1].1 == effects.created[0].0 .0) + || (effects.created[1].1 == sender && effects.created[0].1 == effects.created[1].0 .0) + ); } async fn build_and_publish_test_package( diff --git a/sui_programmability/framework/sources/ID.move b/sui_programmability/framework/sources/ID.move index 97a0be7703445..f579ba4da3d86 100644 --- a/sui_programmability/framework/sources/ID.move +++ b/sui_programmability/framework/sources/ID.move @@ -8,7 +8,7 @@ module Sui::ID { #[test_only] friend Sui::TestScenario; - + /// Version of an object ID created by the current transaction. const INITIAL_VERSION: u64 = 0; @@ -67,7 +67,7 @@ module Sui::ID { }; ID { bytes: bytes_to_address(bytes) } } - + /// Create a new `VersionedID`. Only callable by `TxContext`. /// This is the only way to create either a `VersionedID` or a `UniqueID`. public(friend) fun new_versioned_id(bytes: address): VersionedID { @@ -76,18 +76,18 @@ module Sui::ID { // === reads === - /// Get the underyling `ID` of `obj` + /// Get the underlying `ID` of `obj` public fun id(obj: &T): &ID { let versioned_id = get_versioned_id(obj); inner(versioned_id) } - /// Get raw bytes for the underyling `ID` of `obj` + /// Get raw bytes for the underlying `ID` of `obj` public fun id_bytes(obj: &T): vector { let versioned_id = get_versioned_id(obj); inner_bytes(versioned_id) } - + /// Get the raw bytes of `id` public fun bytes(id: &ID): vector { BCS::to_bytes(&id.bytes) @@ -103,14 +103,13 @@ module Sui::ID { bytes(inner(versioned_id)) } - /// Get the id of `obj` as an address. + /// Get the inner bytes of `id` as an address. // Only used by `Transfer` and `TestSecnario`, but may expose in the future - public(friend) fun id_address(obj: &T): address { - let id = id(obj); + public(friend) fun id_address(id: &ID): address { id.bytes } - /// Get the `version` of `obj`. + /// Get the `version` of `obj`. // Private and unused for now, but may expose in the future fun version(obj: &T): u64 { let versioned_id = get_versioned_id(obj); @@ -118,13 +117,13 @@ module Sui::ID { } /// Return `true` if `obj` was created by the current transaction, - /// `false` otherwise. + /// `false` otherwise. // Private and unused for now, but may expose in the future fun created_by_current_tx(obj: &T): bool { version(obj) == INITIAL_VERSION } - /// Get the VersionedID for `obj`. + /// Get the VersionedID for `obj`. // Safe because Sui has an extra // bytecode verifier pass that forces every struct with // the `key` ability to have a distinguished `VersionedID` field. @@ -147,5 +146,5 @@ module Sui::ID { // === internal functions === /// Convert raw bytes into an address - native fun bytes_to_address(bytes: vector): address; + native fun bytes_to_address(bytes: vector): address; } diff --git a/sui_programmability/framework/sources/TestScenario.move b/sui_programmability/framework/sources/TestScenario.move index d39c4333b6b45..cdddcf3ed14b5 100644 --- a/sui_programmability/framework/sources/TestScenario.move +++ b/sui_programmability/framework/sources/TestScenario.move @@ -25,7 +25,7 @@ module Sui::TestScenario { /// transfer the object to the user. const EEMPTY_INVENTORY: u64 = 3; - /// Expected 1 object of this type in the tx sender's inventory, but found >1. + /// Expected 1 object of this type in the tx sender's inventory, but found >1. /// Consider using TestScenario::remove_object_by_id to select a specific object const EINVENTORY_AMBIGUITY: u64 = 4; @@ -34,7 +34,7 @@ module Sui::TestScenario { const EALREADY_REMOVED_OBJECT: u64 = 5; /// Utility for mocking a multi-transaction Sui execution in a single Move procedure. - /// A `Scenario` maintains a view of the global object pool built up by the execution. + /// A `Scenario` maintains a view of the global object pool built up by the execution. /// These objects can be accessed via functions like `remove_object`, which gives the /// transaction sender access to (only) objects in their inventory. /// Example usage: @@ -49,12 +49,12 @@ module Sui::TestScenario { /// Transfer::transfer(some_object, copy addr2) /// }; /// // end the first transaction and begin a new one where addr2 is the sender - /// TestScenario::next_tx(scenario, &addr2) + /// TestScenario::next_tx(scenario, &addr2) /// { /// // remove the SomeObject value from addr2's inventory /// let obj = TestScenario::remove_object(scenario); /// // use it to test some function that needs this value - /// SomeObject::some_function(obj) + /// SomeObject::some_function(obj) /// } /// ... // more txes /// ``` @@ -70,7 +70,7 @@ module Sui::TestScenario { /// Begin a new multi-transaction test scenario in a context where `sender` is the tx sender public fun begin(sender: &address): Scenario { - Scenario { + Scenario { ctx: TxContext::new_from_address(*sender, 0), removed: Vector::empty(), event_start_indexes: vector[0], @@ -102,7 +102,7 @@ module Sui::TestScenario { }; // reset `removed` for the next tx scenario.removed = Vector::empty(); - + // start index for the next tx is the end index for the current one let new_total_events = num_events(); let tx_event_count = new_total_events - old_total_events; @@ -122,7 +122,7 @@ module Sui::TestScenario { /// - If the object was previously removed, it was subsequently replaced via a call to `return_object`. /// Aborts if there is no object of type `T` in the inventory of the tx sender /// Aborts if there is >1 object of type `T` in the inventory of the tx sender--this function - /// only succeeds when the object to choose is unambiguous. In cases where there are multiple `T`'s, + /// only succeeds when the object to choose is unambiguous. In cases where there are multiple `T`'s, /// the caller should resolve the ambiguity by using `remove_object_by_id`. public fun remove_object(scenario: &mut Scenario): T { let sender = sender(scenario); @@ -137,7 +137,7 @@ module Sui::TestScenario { public fun remove_nested_object( scenario: &mut Scenario, parent_obj: &T1 ): T2 { - remove_unique_object(scenario, ID::id_address(parent_obj)) + remove_unique_object(scenario, ID::id_address(ID::id(parent_obj))) } /// Same as `remove_object`, but returns the object of type `T` with object ID `id`. @@ -152,7 +152,7 @@ module Sui::TestScenario { /// Should only be used in cases where the parent object has more than one child of type `T`. public fun remove_nested_object_by_id( _scenario: &mut Scenario, _parent_obj: &T1, _child_id: ID - ): T2 { + ): T2 { // TODO: implement me abort(200) } @@ -170,7 +170,7 @@ module Sui::TestScenario { assert!(is_mem, ECANT_RETURN_OBJECT); Vector::remove(removed, idx); - // Model object return as a self transfer. Because the events are the source of truth for all object values + // Model object return as a self transfer. Because the events are the source of truth for all object values // in the inventory, we must put any state change future txes want to see in an event. It would not be safe // to do (e.g.) `delete_object_for_testing(t)` instead. // TODO: do this with a special test-only event to enable writing tests that look directly at system events @@ -189,7 +189,7 @@ module Sui::TestScenario { res } - /// Return the `TxContext` asociated with this `scenario` + /// Return the `TxContext` associated with this `scenario` public fun ctx(scenario: &mut Scenario): &mut TxContext { &mut scenario.ctx } @@ -264,7 +264,7 @@ module Sui::TestScenario { native fun get_inventory(signer_address: address, tx_end_index: u64): vector; /// Test-only function for deleting an arbitrary object. Useful for eliminating objects without the `drop` ability. - native fun delete_object_for_testing(t: T); + native fun delete_object_for_testing(t: T); /// Return the total number of events emitted by all txes in the current VM execution, including both user-defined events and system events native fun num_events(): u64; @@ -278,4 +278,4 @@ module Sui::TestScenario { /// Emit a special, test-only event recording that `object_id` was wrapped native fun emit_wrapped_object_event(object_id: vector); -} \ No newline at end of file +} diff --git a/sui_programmability/framework/sources/Transfer.move b/sui_programmability/framework/sources/Transfer.move index 79f53383fda3f..1c55a4d1f459b 100644 --- a/sui_programmability/framework/sources/Transfer.move +++ b/sui_programmability/framework/sources/Transfer.move @@ -1,5 +1,5 @@ module Sui::Transfer { - use Sui::ID::{Self, ID}; + use Sui::ID::{Self, ID, VersionedID}; // To allow access to transfer_to_object_unsafe. friend Sui::Collection; @@ -43,7 +43,7 @@ module Sui::Transfer { /// Returns a non-droppable struct ChildRef that represents the ownership. public fun transfer_to_object(obj: T, owner: &mut R): ChildRef { let obj_id = *ID::id(&obj); - let owner_id = ID::id_address(owner); + let owner_id = ID::id_address(ID::id(owner)); transfer_internal(obj, owner_id, true); ChildRef { parent_id: ID::new(owner_id), @@ -51,6 +51,25 @@ module Sui::Transfer { } } + /// Similar to transfer_to_object where we want to transfer an object to another object. + /// However, in the case when we haven't yet created the parent object (typically during + /// parent object construction), and all we have is just a parent object ID, we could + /// use this function to transfer an object to the parent object identified by its id. + /// The child object is specified in `obj`, and the parent object id is specified in `owner_id`. + /// The function consumes `owner_id` to make sure that the caller actually owns the id. + /// The `owner_id` will be returned (so that it can be used to continue creating the parent object), + /// along returned is the ChildRef as a reference to the ownership. + public fun transfer_to_object_id(obj: T, owner_id: VersionedID): (VersionedID, ChildRef) { + let obj_id = *ID::id(&obj); + let inner_owner_id = *ID::inner(&owner_id); + transfer_internal(obj, ID::id_address(&inner_owner_id), true); + let child_ref = ChildRef { + parent_id: inner_owner_id, + child_id: obj_id, + }; + (owner_id, child_ref) + } + /// Similar to transfer_to_object, to transfer an object to another object. /// However it does not return the ChildRef. This can be unsafe to use since there is /// no longer guarantee that the ID stored in the parent actually represent ownership.