WEP 12: dApp-to-dApp invocation

Abstract

We propose to add the ability to invoke a dApp callable function by a dApp script. The invocation is synchronous. The invoked function returns a value that the invoking function can use.

For this purpose, we suggest making the following improvements to Ride:

  • Add the Invoke function that sets the parameters of the dApp-to-dApp invocation.
  • Add eager variables that are evaluated before the next expression.
  • Modify the callable function result by adding a return value.

Motivation and purposes

Currently, Ride developers are constrained by the limitations of a single script. dApp scripts cannot interact with each other.

The new functionality enables implementing truly decentralized applications and scaling them by splitting the necessary logic between multiple scripts.

Specification

dApp-to-dApp invocation is processed as follows:

  1. A user sends an Invoke Script transaction that invokes the callable function 1.
  2. The callable function 1 invokes the callable function 2 via an eager variable initialized by the Invoke function.
  3. The callable function 2 is executed; the script actions and return value are calculated.
  4. The return value is assigned to the eager variable. The subsequent operations of callable function 1 are executed, taking into account script actions of callable function 2 (as if the actions are applied to the blockchain state).
  5. Finally, the script actions of callable functions 2 and 1 are applied to the blockchain state.

Features:

  • A dApp callable function can invoke a callable function of another dApp, or another callable function of the same dApp, or even itself.
  • dApp-to-dApp invocations can be nested.
  • A dApp-to-dApp invocation can contain payments that will be transferred from the balance of the invoking dApp to the balance of the invoked dApp.
  • All invoked callable functions are executed within a single Invoke Script transaction.
  • The total complexity is limited by 52,000 for all callable functions and asset scripts of involved smart assets. The sender’s account script complexity is not included in that limit.
  • The minimum fee for the Invoke Script transaction is increased by 0.005 WAVES for each dApp-to-dApp invocation.

Invoke

dApp-to-dApp invocation is performed by an eager variable initialized by the return value of an Invoke function.

An eager variable is defined with the strict keyword, unlike lazy variables defined with let.

Example:

strict z = Invoke(dapp,func,args,[AttachedPayment(unit,100000000)])

The Invoke function signature:

Invoke(dApp: Address|Alias, function: String, arguments: List[Boolean|ByteVector|Int|String|List[Boolean|ByteVector|Int|String]], payments: List[AttachedPayments]): T|Unit

Parameters:

Field Data type Description
dApp Address|Alias Address or alias of a dApp to invoke
function String Name of a callable function
arguments List[] Arguments
payments List[AttachedPayments] Payments to transfer from the invoking dApp to the invoked dApp, up to 2

Invocation fields

For dApp-to-dApp invocation, the fields of Invocation structure used by the invoked function are filled with the following values:

Field Value
caller Address of the dApp that invokes the callable function
callerPublicKey Public key of the dApp that invokes the callable function
payments Payments indicated in the Invoke function
transactionId
fee
feeAssetId
Values from the Invoke Script transaction, the same for all dApp-to-dApp invocations

Callable function result

In Standard library version 5, a callable function result is a Tuple of two elements:

  1. List of script actions.
  2. Return value.

Return is passed to the invoking function and assigned to an eager variable.

Example:

(
   [
      ScriptTransfer(i.caller,100,unit)
   ],
   42
)

In Standard library version 4 or 3, there is no return value, so unit is implied.

Transaction fail

If the callable function’s execution fails, the Invoke Script transaction could be rejected or saved on the blockchain as failed. This depends on whether the complexity of performed calculations has exceeded the threshold for saving a failed transaction (currently 1000). The complexity is summed up for all invocations.

Consider the example: callable function 1 performs calculations of 800 complexity, then invokes the callable function 2 which performed calculations of 300 complexity and then fails. The complexity 800 + 300 has exceeded the threshold, so the transaction is saved as failed, and the sender is charged a fee.

If the total complexity of executed callable functions and asset scripts exceeds the limit of 52,000, the transaction is saved as failed as well. For example, if the complexity of executed callable functions is 50,000 in total, and there is a smart asset in script action whose script’s complexity is 2500.

In case of failure, no payments and script actions are applied to the blockchain state, even if some of the invoked functions are executed completely. The only state change the failed transaction entails is charging the fee.

4 Likes

If i understand correctly there will be a race condition in the blockchain state as the appliance of script actions of callable functions will be at final step of execution but they are subject to change in the middle of execution process via continuations.

For example, i call withdraw() all my tokens from dApp with continuation and without, the continuation one goes well and without continuations goes well as changes were not applied at first step. So there will be double withdraw().

Looks like script actions must be applied there it was got not at final step. Or do you plan to lock called dApps too until execution ends?

I like the idea, have some points need be have adjustment but is promising since will allow we build “class” or at least use a entirely smart contract as a function.

My concern is implementations of loop calls between two or more smart contracts.

The return is a good point, even without dApp-dapp invoke, this exactly result is very useful on several situation.

Fail on second dApp not need reprents fail on first one if have good threatening.

Plus fee of dApps on first call dApp can works already first one will pay second one fee anyway.

correct, that’s why continuations and sync invocations are not to be part of one function.
yes, for continuations, script results are applied atomically in the last step.

We have a solution in mind for async invocations + continuations, but that’s for another day.

So WEP 12 is just atomic and there will be no continuations available for dApp-to-dApp calls for now?

yes. sync invocations + continuations just don’t work together. all kinds of inconsistencies, race conditions and unexpected behaviour can occur. the goal is to keep execution result 100% predictable at the ‘blockchain time’ of the invokescript transaction.