DApp-dApp interop with Ride V5
The release of Waves Node 1.3 Jumeirah is focused on Ride update( → V5), providing more features for dApp developers, with cross-dApp interop as key feature. Here’s a quick walkthrough guide on how to use it.
Return Value
With Ride V5, all @Callable functions return type change to tuple of Actions applied and Return Value.
Before:
{-# STDLIB_VERSION 4 #-}@Callable(inv)
func foo() = {
[DataEntry("invoked", true)] # applies state changes
}
After:
{-# STDLIB_VERSION 5#-}@Callable(inv)
func foo() = {
([DataEntry("invoked", true)], 42) # 1. applies state changes
# 2. returns 42 to the caller
}
Return value can either be of a primitive (Int
, String
, Boolean
, Bytevector
, Unit
(instance ()
)), or an aggregate ( List[]
, Tuple
(instances like (1,2)
,("hello", 42, true)
) type.
Simple example
Let’s say dAppA invokes dAppB deployed at 3Pm..2k
by calling foo()
function and processes its return value:
@Callable(inv)
func price(assetId: String) = {
let address = addressFromStringValue("3Pm..2k")
strict f = invoke(address, "foo", [], [])
let result = match f {
case i:Int => i + 1
case _ => throw("unexpected return value type")
([], result)}
Here, we use the new invoke()
function and pass the following arguments:
- Address of the dapp invoked:
Address
- @Callable function name invoked:
String
- List of arguments for the function invoked :
List[Any]
- Payments attached to the invocation :
List[AttachedPayment]
Then, we need to process the result. At compile stage we can’t know the result type of the invocation, so upon successful invocation we get a variable(f
in this example) of type Any
. Hence, we need to match it by type, and, if its type is what we expect, we can work with it (add 1 in this case), otherwise we fail the transaction.
Use `strict` when invoking dApps
The necessity to introduce new keyword comes from the laziness of ride language, and, in turn, the order “parts” of “script” are calculated. It has never been a problem so far since the computations were side-effectless(No write effects happened during the execution of a script).
Problem: Imagine we have a script that grabs a balance, invokes dapp, then grabs the balance again:
...# don't do this
let balanceBefore = wavesBalance(this)
let autotrade = invoke('swopfi-waves-usdn', 'swap', [10 waves])
let balanceAfter = wavesBalance(this)
if(balanceAfter < balanceBefore) then ... else ...
Since let
is lazy, the computations happen not at the time of definition, but at the time of the first access to the variable. e.g. balanceAfter
would be evaluated before balanceBefore
, and no autotrade
will be processed at all!
Solution: usestrict
instead of let
to ensure order of evaluation: strict variable is always evaluated before the next definition/expression.
strict
is a macro which ensures variable of the block is materialised before the expression of the block:
strict a = ... # 'strict' macro
a + b
is just
let a = ...
if(a!=a) then throw("fatal") else # inserted by macro
a + b
in the bytecode.
With s
...# the correct waystrict balanceBefore = wavesBalance(this)
strict autotrade = invoke('swopfi-waves-usdn', 'swap', 10 waves)
strict balanceAfter = wavesBalance(this)
if(balanceAfter < balanceBefore) then ... else ...
Now all the computations happen in the exact order of the definitions.
`invoke` vs `reentrantInvoke`
In short, reentrantInvoke
works the same as invoke
, but allows reentrancy: dAppA can call dAppB, which calls dAppA, only if dAppA invoked dAppB with reentrantInvoke
function. Reentrancy is a dangerous piece of tech, misuse of which caused a lot of hacks around the space, so unless you exactly know what this is and why you need it, just ignore it and always use invoke
. It doesn’t allow reentrant calls, so it makes your system safe from user-deployed code. More on that in the docs.
Exceptions
If an exception is raised during transaction execution, the whole transaction is failed and no data/transfers/other actions occur in state of blockchain.
Try out
The feature is activated at stagenet and testnet and will be available at mainnet soon after the release, when voting and activation is complete.
TL;DR: To invoke a dApp from Ride, use invoke()
function with strict
keyword and match return value to the type you expect.