Skip to content
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

[Move] Add Transfer::transfer_object_to_id #737

Merged
merged 1 commit into from
Mar 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -55,4 +66,4 @@ module ObjectOwner::ObjectOwner {
let Child { id } = child;
ID::delete(id);
}
}
}
25 changes: 25 additions & 0 deletions sui_core/src/unit_tests/move_integration_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
23 changes: 11 additions & 12 deletions sui_programmability/framework/sources/ID.move
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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 {
Expand All @@ -76,18 +76,18 @@ module Sui::ID {

// === reads ===

/// Get the underyling `ID` of `obj`
/// Get the underlying `ID` of `obj`
public fun id<T: key>(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<T: key>(obj: &T): vector<u8> {
let versioned_id = get_versioned_id(obj);
inner_bytes(versioned_id)
}

/// Get the raw bytes of `id`
public fun bytes(id: &ID): vector<u8> {
BCS::to_bytes(&id.bytes)
Expand All @@ -103,28 +103,27 @@ 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<T: key>(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<T: key>(obj: &T): u64 {
let versioned_id = get_versioned_id(obj);
versioned_id.version
}

/// 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<T: key>(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.
Expand All @@ -147,5 +146,5 @@ module Sui::ID {
// === internal functions ===

/// Convert raw bytes into an address
native fun bytes_to_address(bytes: vector<u8>): address;
native fun bytes_to_address(bytes: vector<u8>): address;
}
26 changes: 13 additions & 13 deletions sui_programmability/framework/sources/TestScenario.move
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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:
Expand All @@ -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<SomeObject>(scenario);
/// // use it to test some function that needs this value
/// SomeObject::some_function(obj)
/// SomeObject::some_function(obj)
/// }
/// ... // more txes
/// ```
Expand All @@ -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],
Expand Down Expand Up @@ -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;
Expand All @@ -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<T: key>(scenario: &mut Scenario): T {
let sender = sender(scenario);
Expand All @@ -137,7 +137,7 @@ module Sui::TestScenario {
public fun remove_nested_object<T1: key, T2: key>(
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`.
Expand All @@ -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<T1: key, T2: key>(
_scenario: &mut Scenario, _parent_obj: &T1, _child_id: ID
): T2 {
): T2 {
// TODO: implement me
abort(200)
}
Expand All @@ -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
Expand All @@ -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
}
Expand Down Expand Up @@ -264,7 +264,7 @@ module Sui::TestScenario {
native fun get_inventory<T: key>(signer_address: address, tx_end_index: u64): vector<T>;

/// Test-only function for deleting an arbitrary object. Useful for eliminating objects without the `drop` ability.
native fun delete_object_for_testing<T>(t: T);
native fun delete_object_for_testing<T>(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;
Expand All @@ -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<u8>);
}
}
23 changes: 21 additions & 2 deletions sui_programmability/framework/sources/Transfer.move
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -43,14 +43,33 @@ module Sui::Transfer {
/// Returns a non-droppable struct ChildRef that represents the ownership.
public fun transfer_to_object<T: key, R: key>(obj: T, owner: &mut R): ChildRef<T> {
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),
child_id: obj_id,
}
}

/// 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<T: key>(obj: T, owner_id: VersionedID): (VersionedID, ChildRef<T>) {
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.
Expand Down