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

Transfer to Object #12658

Closed
tzakian opened this issue Jun 23, 2023 · 15 comments · Fixed by #12611, #12987 or #12988
Closed

Transfer to Object #12658

tzakian opened this issue Jun 23, 2023 · 15 comments · Fixed by #12611, #12987 or #12988
Assignees
Labels
design devx move Type: Major Feature Major new functionality or integration, which has a significant impact on the network

Comments

@tzakian
Copy link
Contributor

tzakian commented Jun 23, 2023

Transfer to Object

Motivation & Overview

Currently, objects can be transferred to any address using the sui::transfer::transfer and sui::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 and sui::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 object p, p must then receive c to do anything with it. To receive the object c a new Receiving(o: ObjectRef) argument type for Programmable Transaction Blocks (PTBs) is needed that takes an Object Reference containing the to-be-received object’s ObjectID, Version, and Digest (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:

/// The below type and function will be added to the existing `sui::transfer` module.
module sui::transfer {
    ...
    /// Internals of this struct are opaque. NB that this represents the ability
    /// to receive an object of type `T`. Cannot be stored.
    struct Receiving<phantom T: key> has drop { ... }
    ...
    /// Given mutable (i.e., locked) access to the `parent` and a `Receiving`
    /// object referencing an object owned by `parent` discharge the `Receiving` obligation
    /// and return the corresponding object.
    /// NB: &mut UID here allows the defining module of the parent type to
    /// define custom access/permission policies around receiving objects sent
    /// to objects of a type that it defines. You can see this more in the examples.
    public native fun receive<T: key>(parent: &mut UID, object: Receiving<T>): T;
}

Each Receiving argument referring to a sent object of type T in a PTB will result in exactly one Argument whose Move type is a sui::transfer::Receiving<T>. This argument can then be used to receive the sent object of type T with the sui::transfer::receive function.

When the sui::transfer::receive function is called, a mutable reference to the parent object’s UID must be passed. Since there is no way to get a mutable reference to the UID 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-in UID actually owns the object referenced by the Receiving 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 the drop ability, the existence of a Receiving<T> argument represents the ability, but not the obligation to receive the object of type T specified by the Object Reference in the PTB's Receiving argument during that transaction. Some, none, or all Receiving arguments in a PTB can be used without issue. Any object that corresponds to a Receiving 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 either object or ObjectRef 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 ID 0xc0ffee that was sent to your account at 0xcafe you could do so with the following:

... // boilerplate includes and setup
const tx = new TransactionBlock();
tx.moveCall({
  target: `${examplePackageId}::account::accept_payment`,
  arguments: [tx.object("0xcafe"), tx.object("0xc0ffee")]
});
const result = await signer.signAndExecuteTransactionBlock({
      transactionBlock: tx,
  });
...

Additionally just as with object arguments that also have an ObjectRef constructor where you provide an explicit object ID, version, and digest if you want, there is also a ReceivingRef 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 a SUI coin that was sent to it, it would look something like the following:

... // setup client as normal
client
  .transaction_builder()
  .move_call(
     sending_account,
     example_package_id,
     "account",
     "accept_payment",
     vec!["0x2::sui::SUI"],
     vec![
       SuiJsonValue::from_object_id("0xcafe"),
       SuiJsonValue::from_object_id("0xc0ffee")
     ],
   ...)

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., callingsui_getOwnedObjects on an address that corresponds to an object O will return the list of all objects that are owned by the object O.

Examples

Basic Account

Below is a basic Account abstraction that could be built with the functionality defined in the transfer 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 the Account object was transferred, wrapped, moved to a dynamic field etc. In particular there is a stable ID for a given Account object across the object's lifecycle.

module examples::account {
    use sui::transfer::{Self, Receiving};
    use sui::coin::{Self, Coin};
    use sui::object;
    use sui::dynamic_field as df;
    use sui::tx_context::TxContext;

    const EBalanceDNE: u64 = 1;

    /// Account object that `Coin`s can be sent to. Balances of different types
    /// are held as dynamic fields indexed by the `Coin` type's `type_name`.
    struct Account has key {
        id: object::UID,
    }

    /// Dynamic field key representing a balance of a particular coin type.
    struct AccountBalance<phantom T> has copy, drop, store { }

    /// Accept a coin sent to the `Account` object and then join it to the balance for each coin type.
    /// Dynamic fields are used to index the balances by their coin type.
    public fun accept_payment<T>(account: &mut Account, sent: Receiving<Coin<T>>) {
        // Receive the coin
        let coin = transfer::receive(&mut account.id, sent);
        let account_balance_type = AccountBalance<T>{};
        let account_uid = &mut account.id;

        // If a balance of that type already exists, then add to it, otherwise create one.
        if (df::exists_(account_uid, account_balance_type)) {
            let balance: &mut Coin<T> = df::borrow_mut(account_uid, account_balance_type);
            coin::join(balance, coin);
        } else {
            df::add(account_uid, account_balance_type, coin);
        }
    }

    /// Withdraw `amount` of coins of type `T` from `account`.
    public fun withdraw<T>(account: &mut Account, amount: u64, ctx: &mut TxContext): Coin<T> {
        let account_balance_type = AccountBalance<T>{};
        let account_uid = &mut account.id;
        // Make sure what we are withdrawing exists
        assert!(df::exists_(account_uid, account_balance_type), EBalanceDNE);
        let balance: &mut Coin<T> = df::borrow_mut(account_uid, account_balance_type);
        coin::split(balance, amount, ctx)
    }

    /// Can transfer this account to a different address
    /// (e.g., to another non-object address, or to another object). 
    public fun transfer_account(account: Account, to: address, _ctx: &mut TxContext) {
        // Perform some authorization checks here and if they pass then transfer the account
        // ...
        transfer::transfer(account, to);
    }
}

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.

module examples::shared_object_auth {
    use sui::transfer::{Self, Receiving};
    use sui::object;
    use sui::tx_context::TxContext;

    struct SharedObject has key {
        id: object::UID,
    }

    struct AccessCap has drop {}

    public fun create(ctx: &mut TxContext) {
        let s = SharedObject { id: object::new(ctx), };
        transfer::share_object(s);
    }

    /// Function that authorizes access to the inbox of the shared object.
    public fun authorize_inbox_access(_ctx: &mut TxContext): AccessCap {
        // Perform some authorization checks here....
        AccessCap {}
    }

    /// If the ability to receive objects sent to the shared object in this transaction has been granted, then
    /// the transaction can use the `AccessCap` to retrieve the sent object (and they will then need to do something with it).
    public fun receive_object<T: key>(obj: &mut SharedObject, sent: Receiving<T>, _access_cap: &AccessCap): T {
        transfer::receive(&mut obj.id, sent)
    }
}

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 of Coin<T> that has been sent to a NamedAccount object that it controls and will then add that coin to the NamedAccount's balance for that coin type.

module examples::account_manager {
    use sui::object::{Self, UID};
    use std::string::String;
    use sui::coin::{Self, Coin};
    use sui::tx_context::TxContext;
    use sui::dynamic_field as df;
    use sui::transfer::{Self, Receiving};

    /// A Named account.
    struct NamedAccount has key, store {
        id: UID,
        name: String,
    }

    /// An account manager that controls (possibly multiple) named accounts.
    /// Each named account can have balances of many different types that it holds.
    struct AccountManager has key {
        id: UID,
    }

    /// Dynamic field key representing a balance of a particular coin type.
    struct AccountBalance<phantom T> has copy, drop, store {}

    /// Accept a payment of type `T` that has been sent to the named account with
    /// name `account_name`. The received payment will be placed under the corresponding
    /// named account's account balances. 
    public fun accept_payment_on_named_account<T: key>(
        account_manager: &mut AccountManager,
        account_name: String,
        sent: Receiving<Coin<T>>,
        ctx: &mut TxContext
    ) {
        // Determine if the named account exists. If not, then add it.
        if (!df::exists_(&account_manager.id, account_name)) {
            df::add(&mut account_manager.id, account_name, NamedAccount {
                id: object::new(ctx),
                name: account_name,
            });
        };

        // Get the named account and receive the payment.
        let named_account: &mut NamedAccount = df::borrow_mut(&mut account_manager.id, account_name);
        let received = transfer::receive(&mut named_account.id, sent);

        // Add the received amount to the account balance. If a balance of that
        // type doesn't exist yet, then create one.
        let account_balance_type = AccountBalance<T>{};
        if (!df::exists_(&named_account.id, account_balance_type)) {
            df::add(&mut named_account.id, account_balance_type, received);
        } else {
            let balance: &mut Coin<T> = df::borrow_mut(&mut named_account.id, account_balance_type);
            coin::join(balance, received);
        };
    }
}
@tzakian tzakian added Type: Major Feature Major new functionality or integration, which has a significant impact on the network devx move design labels Jun 23, 2023
@tzakian tzakian self-assigned this Jun 23, 2023
@tzakian tzakian linked a pull request Jun 23, 2023 that will close this issue
7 tasks
@tzakian tzakian changed the title [Move] Transfer to Object Transfer to Object Jun 23, 2023
@ISayHelloworld
Copy link

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.

@tzakian
Copy link
Contributor Author

tzakian commented Aug 14, 2023

Hi @ISayHelloworld

The key insight here is that the Receiving struct does not contain or wrap the object that is transferred. Instead, you can view the Receiving struct as a ticket (or capability) that allows you to retrieve and load the object during execution.

What happens is that when A sends the coin C to B it sets owner of the object C as B. After that, if the owner of B wants to receive C and do something with it, it can do so by saying it wants to receive C with this Receiving struct, and by calling the transfer::receive function along with the mutable reference to B's UID. The VM will then go and actually load C at that point and return it back for you to use in the rest of the transaction.

tzakian added a commit that referenced this issue Sep 22, 2023
## 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
@tzakian tzakian reopened this Sep 26, 2023
@tzakian
Copy link
Contributor Author

tzakian commented Sep 26, 2023

Not done -- accidentally got closed by the landing of the main PR.

@FrankC01
Copy link

FrankC01 commented Sep 29, 2023

@tzakian

Are references allowed in function args (i.e. &Receiving<T>)?
If so, are mutable allowed as well (i.e. &mut Receiving<T>)?

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,
  });
...

@tzakian
Copy link
Contributor Author

tzakian commented Sep 29, 2023

@FrankC01

Both are allowed (so both &Receiving<T> and &mut Receiving<T>.

To your second question: you can use command output as input to functions that want Receiving arguments (the PTB layer will do the typechecking to verify the passed-in result is a Receiving type). However, importantly any Receiving<T> that will be seen during execution must have appeared in the transaction's inputs. As an example you could have Move function like something like this:

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,
  });
...

@FrankC01
Copy link

@tzakian Good to know, thanks for confirming!

@FrankC01
Copy link

FrankC01 commented Sep 30, 2023

@tzakian

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 "0x2::coin::Coin<0x2::sui::SUI>" as Coin satisfies key + store

However, methinks your example accept_paymet is questionable as just type_argument "0x2::sui::SUI" fails the key+store constraint. Pointing this out in case it is wrong and you want to fix to prevent others scratching their heads or it is me and I'm missing something in assumptions or how things work.

@tzakian
Copy link
Contributor Author

tzakian commented Oct 3, 2023

Great catch! Thank you for pointing it out.

This is indeed an oversight -- I'll update. The only restriction is that the type T in Receiving<T> has key. So there shouldn't need to be any restrictions on T in accept_payment since Coin<T> has key for any T.

@FrankC01
Copy link

FrankC01 commented Oct 3, 2023

@tzakian

That fixes the type constraint however; it is failing in inspect and/or run against devnet (1.11.0):
https://suiexplorer.com/txblock/AVxhurNcmYqP2yHoGbysGaQdGH4Cp6EbfATh6SSNj5oh?network=devnet

 "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"
      }
} 

@tzakian
Copy link
Contributor Author

tzakian commented Oct 3, 2023

@FrankC01

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 0x0f7f7288b07de5a31ac8915b503f6d7a09d05400d4a40d594d3f7a279d34050b to 0x7e685a1bdc34671488560ce26952bc1f57937b0e7fc465da0a6cc80740e28708 and then the call to accept_payment should work.

@FrankC01
Copy link

FrankC01 commented Oct 3, 2023

Yep, that did it! (May want to add to example....) thanks @tzakian ... pysui now support it... with confidence!

https://suiexplorer.com/object/0x7e685a1bdc34671488560ce26952bc1f57937b0e7fc465da0a6cc80740e28708?network=devnet

@tzakian
Copy link
Contributor Author

tzakian commented Oct 3, 2023

Amazing!

@tzakian tzakian reopened this Oct 23, 2023
@tzakian
Copy link
Contributor Author

tzakian commented Oct 23, 2023

Receiving object support for typescript and Rust has now been landed in main. The feature is only turned on for devnet. Trying to receive an object in testnet or mainnet will fail at the moment as the feature is not turned on for these networks.

@monday-kelvin
Copy link

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?

bmwill pushed a commit to move-language/move-sui that referenced this issue Apr 18, 2024
## 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
@stefan-mysten
Copy link
Contributor

I think this is now completed and available on mainnet, so we can close the issue.

alexsporn pushed a commit to iotaledger/iota that referenced this issue Sep 6, 2024
## 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
design devx move Type: Major Feature Major new functionality or integration, which has a significant impact on the network
Projects
None yet
5 participants