Conversation
Is it right to read it like: "Token contract creates a Promise with a call to the |
Co-Authored-By: Alexey <alexey@fckt.dev>
More like "Token contract creates a safe that creates a Promise towards the contract itself with a call to the |
|
I think it's worth discussing the relationship of (un)lock and adjusting allowance. IMO you have a cleaner implementation where only
|
|
@robrobbins
There are 2 transactions getting executed in parallel, where |
|
@zmanian also linked to https://agoric.com/documentation/zoe/guide/ for a different approach of cross-contract value interaction. |
|
Also this may be of interest https://github.com/cosmos/ics/tree/master/ideation/ics-027-direct-interchain-exchange |
|
@evgenykuzyakov Do you believe this NEP should remain open or should we close it in favor of #122? |
|
Hi @evgenykuzyakov! Since we haven't heard back for two months, we are closing this NEP. If you or anyone is interested in revisiting it in the future, please submit a new NEP. |
Summary
Introducing a new concept of safes that allows to securely lock some data from a contract with
automatic unlock mechanism.
Motivation
There are a few NEPs that proposed solutions to address the cross-shard communication problem.
For example when an decentralized exchange tries to swap 2 tokens without owning them.
One solution is to introduce locks with automatic unlock capabilities, but without explicitly exposing the locked structure.
While it solves the issue for a simple exchange use-case. It has some limitations and complexity for non trivial use cases.
It might lead to an unexpected behavior when a token is behind the proxy contract.
This proposal is to introduce explicit locked data storage which we call a
safethat can't be copied and always resolved at the end.It's a familiar concept for an asynchronous development similar to guards.
When a guard is released, the destructor (or Drop in Rust) is called and the lock can be resolved.
Guide-level explanation
We introduce a new concept which we call a
safe.Example:
When a decentralized exchange tries to move some tokens, it first has to acquire and lock the funds.
alice.unlockon itself.dex.Now Dex has this safe from the token contract.
Dex can read the content of the safe and assert the content is correct.
transferon the token contract and pass this safe with this promise.400to the new owner.400by the transferred amount. E.g.dex.Transfer has completed successfully, but
dexmay want to do more transfers. It's safe to drop the safe now.NOTE, that the promise is always called even if the content of the safe was fully used.
It's because the promise is fully prepaid during the creation of the safe.
alice.Reference-level explanation
Runtime API
Introducing new Runtime API to handle safes:
API to create safes and read/write content
Passing safes
If you don't return or pass a safe, then this safe will be dropped at the end of the contract execution.
Receiving safes
Safes can be received in two ways:
Low-level contract example
This is still pseudo-code. But it should highlight how safes work.
E.g. this code don't use registers and assumes core functions return vectors.
On access to safes from multiple actions.
Since safes are passed to a promise and not to a particular function call.
Let's say a promise contains 2 function calls.
All safe(s) will be given to the first function call. If the function call doesn't consume a safe, it will be passed towards the next function call.
Once the safe reaches the last action and the safe is not consumed by the last action, the safe will be dropped.
If the content of the safe is modified by the contract during one of the function call, but then the next action fails. The content of the safe
is reverted to the original content, the content before the first action has started.
Runtime internal implementation
To handle safes properly we need the following:
promise_attach_safe)safe_return).Tracking safes
The easiest option to handle safes is to accumulate all safes at the beginning of action receipt processing.
Same way we accumulate input_data, we can accumulate safes with content. Let's introduce
Safedata structure:Then we add a new vector of
all_safesintoapply_action_receiptwithin a Runtime.This vector allows us to drop all safes with the original content in case any action fails during
processing of this
ActionReceipt.Processing safes with actions
Each action will receive a mutable reference to
input_safesandpromise_results_safes.If any action fails, then we don't care about
input_safesandpromise_results_safesanymore, becausewe'll just drop safes from
original_safes.A function call on an account that owns a safe may update the content of the safe.
A function call can also consume some safes from either vector, or create new safes and add them to
input_safes.Newly created safes that are not consumed will be passed to the next action as an input, so it can act the safe if needed.
The content of the safes will be handled through
RuntimeExtcrate.Inside a VMLogic, we'll track safes the following way.
Handling of returned safes:
safe_returncall will panic immediately.NOTE: Even though the safe can be attached to the promise, with multiple outgoing dependencies, you can do this by
attaching it directly instead of relying on
safe_return.consumed_safes_idxsandreturned_safes_idxs. NOTE: if the outgoing dependencies are empty, the safe will not be returnedanywhere, so it effectively will be dropped after this action.
We also need to update
Promiseenum to indicate safe resolving promise:RuntimeExtneeds the following methods:Need to add the following fields to the
ActionResult:Collecting safes after successful execution of a Function Call action:
VMOutcomeshould contain the following fields fromVMLogic:new_safes_idxsconsumed_safes_idxsreturned_safes_idxsRuntimeExtshould returnmut safesback toRuntime.Runtimeshould do the following:safesby removing allconsumed_safes_idxs:returned_safesinActionResult.new_safes_idxstoinput_safes_idxs.input_safes_idxsandpromise_results_safes_idxsby retaining only safeindices that were not consumed and remapping the old indices to the new indices.
Merging
ActionResult:ActionResult, all oldreturned_safesshould be moved to olddropped_safes.The reason for this is there shouldn't be any
outgoing_dependenciesin the oldActionResult.Because the old action was not the last action and only the last action can have
outgoing_dependencies.ActionResultresult isErr, all newreturned_safesshould be moved to newdropped_safes.dropped_safesare added after olddropped_safes.Update
ActionReceiptandDataReceiptNeed to add one field to
ActionReceipt:Also need to add one field to
DataReceipt:Resolving safes at the end.
If the
ActionResultresult isErr:original_safesIf the
ActionResultresult isOk, we have safes in the following fields:returned_safesanddropped_safesin theActionResultReceiptin theActionResultsafesthat were not consumed and should be dropped.return_dataisPromiseIndexallreturned_safesshould be moved todropped_safes.outgoing_dependenciesallreturned_safesshould be moved todropped_safes.Handling
returned_safesforOkwith exactly 1 outgoing dependency:returned_safestosafesfrom the outgoingDataReceipt.Dropping safes:
Receiptwith aDataReceipt.receiver_idissafe.owner_id.data_idissafe.data_id.datais theSome(safe.content).