MystenLabs / sui

Sui, a next-generation smart contract platform with high throughput, low latency, and an asset-oriented programming model powered by the Move programming language
https://sui.io
Apache License 2.0
6.21k stars 11.2k forks source link

Feature: Add Memos to TxContext #6231

Open PaulFidika opened 1 year ago

PaulFidika commented 1 year ago

In an entry transaction, be able to attach arbitrary data to the ctx (Transaction Context) as a sequence of bytes. This data can be read by on-chain modules using the tx_context::memo(ctx) API, and it will be logged along with the rest of a transaction's info for reference by off-chain processes.

Off-Chain Example:

Many centralized exchanges (Coinbase) instruct depositors to do a simple transfer of coins to a fixed address (sui::transfer::transfer(coin, address)), and attach a memo to the transaction that specifies the coinbase-account that deposit was intended for.

On-Chain Example:

I would intend to use modules for additional on-chain permissioning. For example, suppose you want to deposit Coin into another person’s vault, but this vault requires permission. It would work something like:

public entry fun deposit(vault: &mut Vault, coin: Coin<Sui>, ctx: &TxContext) {
  if (vault.require_permission) {
    assert!(has_permission(vault, ctx), ENOT_PERMISSION);
  };
  coin::merge_into(&mut vault.balance, coin);
}

public fun has_permission(vault: &mut Vault, ctx: &TxContext): bool {
  let signature_bytes = tx_context::memo(ctx);
  let owner = &vault.owner;
  let nonce = &vault.nonce;
  let sender = tx_context::sender(ctx);
  // Check that the bytes match a signature of nonce from the owner address
  validate_signature(signature_bytes, owner, sender + nonce)
  vault.nonce = vault.nonce + 1;
  ...
}

the has_permission does a cryptographic check like “did the vault-owner-address produce a valid signature of (sender-address + vault.nonce)”? If yes, then return true, otherwise return false.

The bytes could be included explicitly as an argument in the deposit function, but then we’d have to add arguments to both our functions, and a lot of the time we won’t be using those bytes, so callers would be specifying lots of empty u8 vectors for no reason.

Additional idea - Passing Variables Down:

We could allow on-chain functions to attach arbitrary data to ctx, and then pass those down to other functions further down on the chain, without needing to explicitly pass every variable. This would work much like contexts in React do, where you wrap functions in a context, and inside of that wrapper all children can access variables within the wrapping-context. (Because ctx does not have a UID, we are currently unable to use dynamic fields for this purpose.) This should be considered an optional bonus.

sblackshear commented 1 year ago

In your has_permission example, what would go wrong if signature_bytes were instead a tx argument?

PaulFidika commented 1 year ago

In your has_permission example, what would go wrong if signature_bytes were instead a tx argument?

It could definitely be a transaction argument, but being able to attach arbitrary bytes to the ctx object simplifies APIs. You could imagine serializing optional data that the module then deserializes and uses to fill in the properties of a struct it's building, for example.

sblackshear commented 1 year ago

It could definitely be a transaction argument, but being able to attach arbitrary bytes to the ctx object simplifies APIs.

Could you help me understand how this simplifies things? It seems like this is expanding the API surface to accomplish things that could already be done with tx arguments. We try really hard to keep API surfaces minimal so that there's a clear way for programmers to accomplish a given task (e.g., "use tx arguments for this" rather than "use tx arguments in this situation and memos in this situation") and only add new ones when they solve well-motivated problems that fundamentally aren't possible with the existing features.

You could imagine serializing optional data that the module then deserializes and uses to fill in the properties of a struct it's building, for example.

E.g., I believe this can already be done with transaction arguments, but let me know if I'm missing something.

PaulFidika commented 1 year ago

Could you help me understand how this simplifies things? It seems like this is expanding the API surface to accomplish things that could already be done with tx arguments. We try really hard to keep API surfaces minimal so that there's a clear way for programmers to accomplish a given task (e.g., "use tx arguments for this" rather than "use tx arguments in this situation and memos in this situation") and only add new ones when they solve well-motivated problems that fundamentally aren't possible with the existing features.

You could imagine serializing optional data that the module then deserializes and uses to fill in the properties of a struct it's building, for example.

E.g., I believe this can already be done with transaction arguments, but let me know if I'm missing something.

Okay, I see what you're saying. My thoughts were that it would add flexibility to entry-functions if callers could attach arbitrary optional binary data along with their call. But philosophically it's probably better to keep all function arguments explicit; I can simply add bytes: vector<u8> as a function argument where needed, and allow callers to pass-in empty-vectors if the argument is considered optional data. Maybe I could use a naming convention like "bytes_opt" with "opt" meaning it should be considered optional data?

How do you feel about memos for offline-purposes though? To leave data behind for indexers to better understand what a transaction was for? For example, a sui-transfer could attach an invoice-number as a memo, so that the merchant can match the transfer they received to a payment-invoice they sent out.

bytedeveloperr commented 1 year ago

I agree with @PaulFidika.

This concept/feature will be very useful in a sui payment framework as it will make it very easy to find and validate payments off-chain without having to store the payment information on a centralized server.

for a payment framework, it could work like this:

PS: this is be similar to how solanapay works

This can also be achieved by writing a custom package and requiring the reference as an argument but It would be a waste because we have the standard coin payment methods provided by Sui already.

Also, this is one of the numerous use cases this feature would bring to Sui and also this could be a starting point for a Sui payment and ecommerce framework standard.