Skip to content

Commit

Permalink
SC-760 Reentrant invoke (#3446)
Browse files Browse the repository at this point in the history
* SC-760 Reentrant invoke

* Add test

* Additional tests

* Multiple call tests

* Add multichain test

* Rename Invoke to invoke

* Correct function name

* Correct InvokePaymentsLimitTest

* Add tests

Co-authored-by: Ivan Rakitnykh <[email protected]>
  • Loading branch information
xrtm000 and mrkraft authored Apr 29, 2021
1 parent f87d95d commit 425891f
Show file tree
Hide file tree
Showing 29 changed files with 464 additions and 111 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ object EnvironmentFunctionsBenchmark {
address => Address(ByteStr(address.bytes))
)
override def accountScript(addressOrAlias: Recipient): Option[Script] = ???
override def callScript(dApp: Address, func: String, args: List[EVALUATED], payments: Seq[(Option[Array[Byte]], Long)], availableComplexity: Int): Coeval[(Either[ValidationError, EVALUATED], Int)] = ???
override def callScript(dApp: Address, func: String, args: List[EVALUATED], payments: Seq[(Option[Array[Byte]], Long)], availableComplexity: Int, reentrant: Boolean): Coeval[(Either[ValidationError, EVALUATED], Int)] = ???
}

val environmentFunctions = new EnvironmentFunctions(defaultEnvironment)
Expand Down
9 changes: 8 additions & 1 deletion lang/doc/v5/funcs/blockchain-functions.hjson
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,18 @@
complexity: 200
}
{
name: "Invoke"
name: "invoke"
params: [ "Address|Alias", "String|Unit", "List[Any]", "List[AttachedPayment]" ]
doc: "Invoke function from other dApp."
paramsDoc: [ "dApp", "function name", "arguments", "Attached payments" ]
complexity: 75
}
{
name: "reentrantInvoke"
params: [ "Address|Alias", "String|Unit", "List[Any]", "List[AttachedPayment]" ]
doc: "Invoke function from other dApp allowing reentrancy to the current one."
paramsDoc: [ "dApp", "function name", "arguments", "Attached payments" ]
complexity: 75
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ package object utils {
override def transferTransactionFromProto(b: Array[Byte]): Option[Tx.Transfer] = ???
override def addressFromString(address: String): Either[String, Recipient.Address] = ???
override def accountScript(addressOrAlias: Recipient): Option[Script] = ???
override def callScript(dApp: Address, func: String, args: List[EVALUATED], payments: Seq[(Option[Array[Byte]], Long)], availableComplexity: Int): Coeval[(Either[ValidationError, EVALUATED], Int)] = ???
override def callScript(dApp: Address, func: String, args: List[EVALUATED], payments: Seq[(Option[Array[Byte]], Long)], availableComplexity: Int, reentrant: Boolean): Coeval[(Either[ValidationError, EVALUATED], Int)] = ???
}

val lazyContexts: Map[DirectiveSet, Coeval[CTX[Environment]]] = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ object FunctionIds {
val ACCOUNTSCRIPTHASH: Short = 1009

val CALLDAPP: Short = 1020
val CALLDAPPREENTRANT: Short = 1021

val DATA_LONG_FROM_ARRAY: Short = 1040
val DATA_BOOLEAN_FROM_ARRAY: Short = 1041
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -526,19 +526,20 @@ object Functions {
}
}

def callDAppF(version: StdLibVersion): BaseFunction[Environment] =
def callDAppF(version: StdLibVersion, reentrant: Boolean): BaseFunction[Environment] = {
val (id, name) = if (reentrant) (CALLDAPPREENTRANT, "reentrantInvoke") else (CALLDAPP, "invoke")
NativeFunction.withEnvironment[Environment](
"Invoke",
Map[StdLibVersion, Long](V4 -> 75L),
CALLDAPP,
name,
Map[StdLibVersion, Long](V5 -> 75L),
id,
ANY,
("dapp", addressOrAliasType),
("name", optionString),
("args", LIST(ANY)),
("payments", listPayment)
) {
new ContextfulNativeFunction[Environment](
"Invoke",
name,
ANY,
Seq(("dapp", BYTESTR), ("name", STRING), ("args", LIST(ANY)), ("payments", listPayment))
) {
Expand All @@ -550,14 +551,14 @@ object Functions {
args: List[EVALUATED],
availableComplexity: Int
): Coeval[F[(Either[ExecutionError, EVALUATED], Int)]] = {
val dappBytes = args match {
case (dapp: CaseObj) :: _ if dapp.caseType == addressType =>
dapp.fields("bytes") match {
val dAppBytes = args match {
case (dApp: CaseObj) :: _ if dApp.caseType == addressType =>
dApp.fields("bytes") match {
case CONST_BYTESTR(d) => d.pure[F]
case a => throw new IllegalArgumentException(s"Unexpected address bytes $a")
}
case (dapp: CaseObj) :: _ if dapp.caseType == aliasType =>
dapp.fields("alias") match {
case (dApp: CaseObj) :: _ if dApp.caseType == aliasType =>
dApp.fields("alias") match {
case CONST_STRING(a) => env.resolveAlias(a).map(_.explicitGet().bytes)
}
case args => throw new IllegalArgumentException(s"Unexpected recipient args $args")
Expand All @@ -571,7 +572,7 @@ object Functions {
case _ :: _ :: ARR(args) :: ARR(payments) :: Nil =>
env
.callScript(
Recipient.Address(dappBytes.asInstanceOf[ByteStr]),
Recipient.Address(dAppBytes.asInstanceOf[ByteStr]),
name,
args.toList,
payments.map {
Expand All @@ -582,16 +583,18 @@ object Functions {
}
case arg => throw new IllegalArgumentException(s"Unexpected payment arg $arg")
},
availableComplexity
availableComplexity,
reentrant
)
.map(_.map { case (result, complexity) => (result.leftMap(_.toString), complexity)})
case xs =>
val err = notImplemented[F, EVALUATED](s"Invoke(dapp: Address, function: String, args: List[Any], payments: List[Payment])", xs)
val err = notImplemented[F, EVALUATED](s"invoke(dApp: Address, function: String, args: List[Any], payments: List[Payment])", xs)
Coeval.now(err.map((_, 0)))
}
}
}
}
}

private def withExtract[C[_[_]]](f: BaseFunction[C], version: StdLibVersion): BaseFunction[C] = {
val args = f.signature.args.zip(f.args).map {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,8 @@ object WavesContext {
simplifiedLeaseActionConstructor,
detailedLeaseActionConstructor,
calculateLeaseId,
callDAppF(version),
callDAppF(version, reentrant = false),
callDAppF(version, reentrant = true),
isDataStorageUntouchedF
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ case class ErrorMessageEnvironment[F[_]](message: String) extends Environment[F]
args: List[EVALUATED],
payments: Seq[(Option[Array[Byte]], Long)],
availableComplexity: Int
): Coeval[F[(Either[ValidationError, EVALUATED], Int)]] = unavailable
, reentrant: Boolean): Coeval[F[(Either[ValidationError, EVALUATED], Int)]] = unavailable
}

case class BlockchainUnavailableException(message: String) extends RuntimeException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,5 +151,5 @@ private[repl] case class WebEnvironment(settings: NodeConnectionSettings) extend
client.get[F, A](url).map(_.map(ev))

override def accountScript(addressOrAlias: Recipient): Future[Option[Script]] = ???
override def callScript(dApp: Address, func: String, args: List[EVALUATED], payments: Seq[(Option[Array[Byte]], Long)], availableComplexity: Int): Coeval[Future[(Either[ValidationError, EVALUATED], Int)]] = ???
override def callScript(dApp: Address, func: String, args: List[EVALUATED], payments: Seq[(Option[Array[Byte]], Long)], availableComplexity: Int, reentrant: Boolean): Coeval[Future[(Either[ValidationError, EVALUATED], Int)]] = ???
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ trait Environment[F[_]] {
func: String,
args: List[EVALUATED],
payments: Seq[(Option[Array[Byte]], Long)],
availableComplexity: Int
availableComplexity: Int,
reentrant: Boolean
): Coeval[F[(Either[ValidationError, EVALUATED], Int)]]
}
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ object Common {
override def transferTransactionFromProto(b: Array[Byte]): Option[Tx.Transfer] = ???
override def addressFromString(address: String): Either[String, Recipient.Address] = ???
def accountScript(addressOrAlias: Recipient): Option[Script] = ???
override def callScript(dApp: Address, func: String, args: List[EVALUATED], payments: Seq[(Option[Array[Byte]], Long)], remainingComplexity: Int): Coeval[(Either[ValidationError, EVALUATED], Int)] = ???
override def callScript(dApp: Address, func: String, args: List[EVALUATED], payments: Seq[(Option[Array[Byte]], Long)], remainingComplexity: Int, reentrant: Boolean): Coeval[(Either[ValidationError, EVALUATED], Int)] = ???
}

def addressFromPublicKey(chainId: Byte, pk: Array[Byte], addressVersion: Byte = EnvironmentFunctions.AddressVersion): Array[Byte] = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1001,7 +1001,7 @@ class ContractCompilerTest extends PropSpec with PropertyChecks with Matchers wi
|
| @Callable(i)
| func foo(a:ByteVector) = {
| let r = Invoke(this, "bar", [], [])
| let r = invoke(this, "bar", [], [])
| [
| IntegerEntry("key", 1),
| BooleanEntry("key", true),
Expand Down Expand Up @@ -1039,7 +1039,7 @@ class ContractCompilerTest extends PropSpec with PropertyChecks with Matchers wi
|
| @Callable(i)
| func foo(a:ByteVector) = {
| let r = Invoke(this, "bar", [], [])
| let r = invoke(this, "bar", [], [])
| [
| IntegerEntry("key", 1),
| BooleanEntry("key", true),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class SelfPaymentDappToDappSuite extends BaseTransactionSuite {
|
|@Callable (i)
|func foo() = {
| strict inv = Invoke(this, "bar", [], [AttachedPayment(unit, 100)])
| strict inv = invoke(this, "bar", [], [AttachedPayment(unit, 100)])
| ([], nil)
|}
|
Expand All @@ -60,13 +60,13 @@ class SelfPaymentDappToDappSuite extends BaseTransactionSuite {
|
|@Callable (i)
|func foo() = {
| strict inv = Invoke(i.caller, "foo", [], [AttachedPayment(unit, 100)])
| strict inv = invoke(i.caller, "foo", [], [AttachedPayment(unit, 100)])
| ([], nil)
|}
|
|@Callable (i)
|func bar() = {
| strict inv = Invoke(i.caller, "bar", [], [AttachedPayment(unit, 100)])
| strict inv = invoke(i.caller, "bar", [], [AttachedPayment(unit, 100)])
| ([], nil)
|}
|
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package com.wavesplatform.state.diffs.invoke

import scala.util.Right

import cats.Id
import cats.implicits._
import com.wavesplatform.account._
Expand All @@ -14,25 +12,27 @@ import com.wavesplatform.lang.directives.values.{DApp => DAppType, _}
import com.wavesplatform.lang.script.ContractScript.ContractScriptImpl
import com.wavesplatform.lang.v1.ContractLimits
import com.wavesplatform.lang.v1.compiler.Terms._
import com.wavesplatform.lang.v1.evaluator.{ContractEvaluator, IncompleteResult, Log, ScriptResult, ScriptResultV3, ScriptResultV4}
import com.wavesplatform.lang.v1.evaluator.ctx.impl.unit
import com.wavesplatform.lang.v1.evaluator.{ContractEvaluator, IncompleteResult, Log, ScriptResult, ScriptResultV3, ScriptResultV4}
import com.wavesplatform.lang.v1.traits.Environment
import com.wavesplatform.lang.v1.traits.domain._
import com.wavesplatform.lang.v1.traits.domain.Tx.ScriptTransfer
import com.wavesplatform.lang.v1.traits.domain._
import com.wavesplatform.metrics._
import com.wavesplatform.state._
import com.wavesplatform.state.reader.CompositeBlockchain
import com.wavesplatform.transaction.{Transaction, TxValidationError}
import com.wavesplatform.transaction.Asset.IssuedAsset
import com.wavesplatform.transaction.TxValidationError._
import com.wavesplatform.transaction.smart.{DApp => DAppTarget, _}
import com.wavesplatform.transaction.smart.script.ScriptRunner
import com.wavesplatform.transaction.smart.script.ScriptRunner.TxOrd
import com.wavesplatform.transaction.smart.script.trace.{AssetVerifierTrace, CoevalR, TracedResult}
import com.wavesplatform.transaction.smart.script.trace.CoevalR.traced
import com.wavesplatform.transaction.smart.script.trace.{AssetVerifierTrace, CoevalR, TracedResult}
import com.wavesplatform.transaction.smart.{DApp => DAppTarget, _}
import com.wavesplatform.transaction.{Transaction, TxValidationError}
import monix.eval.Coeval
import shapeless.Coproduct

import scala.util.Right

object InvokeScriptDiff {
private val stats = TxProcessingStats
import stats.TxTimerExt
Expand All @@ -41,12 +41,12 @@ object InvokeScriptDiff {
blockchain: Blockchain,
blockTime: Long,
limitedExecution: Boolean,
totalComplexityLimit: Int, remainingComplexity: Int,
totalComplexityLimit: Int,
remainingComplexity: Int,
remainingCalls: Int,
remainingActions: Int,
remainingData: Int,
calledAddresses: Set[Address]
,
calledAddresses: Set[Address],
invocationRoot: DAppEnvironment.InvocationTreeTracker
)(
tx: InvokeScript
Expand Down Expand Up @@ -161,7 +161,7 @@ object InvokeScriptDiff {
tx.root,
tx.dAppAddress,
pk,
calledAddresses + dAppAddress,
calledAddresses,
limitedExecution,
totalComplexityLimit,
remainingCalls - 1,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ object InvokeScriptTransactionDiff {
Some(tx),
dAppAddress,
pk,
Set(tx.senderAddress, dAppAddress),
Set(tx.senderAddress),
limitedExecution,
ContractLimits.MaxTotalInvokeComplexity(version),
ContractLimits.MaxSyncDAppCalls(version),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,8 @@ class WavesEnvironment(
func: String,
args: List[EVALUATED],
payments: Seq[(Option[Array[Byte]], Long)],
availableComplexity: Int
availableComplexity: Int,
reentrant: Boolean
): Coeval[(Either[ValidationError, EVALUATED], Int)] = ???
}

Expand Down Expand Up @@ -295,7 +296,8 @@ class DAppEnvironment(
func: String,
args: List[EVALUATED],
payments: Seq[(Option[Array[Byte]], Long)],
availableComplexity: Int
availableComplexity: Int,
reentrant: Boolean
): Coeval[(Either[ValidationError, EVALUATED], Int)] = {
val invocation = InvokeScriptResult.Invocation(
account.Address.fromBytes(dApp.bytes.arr).explicitGet(),
Expand Down Expand Up @@ -338,7 +340,7 @@ class DAppEnvironment(
remainingCalls,
availableActions,
availableData,
calledAddresses,
if (reentrant) calledAddresses else calledAddresses + invoke.senderAddress,
invocationTracker
)(invoke).leftMap { err =>
invocationTracker.setError(err)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ class LeaseRouteSpec
|
|@Callable(inv)
|func callProxy(targetDapp: ByteVector, recipient: ByteVector, amount: Int) = {
| strict result = Invoke(Address(targetDapp), "leaseTo", [recipient, amount], [])
| strict result = invoke(Address(targetDapp), "leaseTo", [recipient, amount], [])
| []
|}
|""".stripMargin)),
Expand Down
10 changes: 5 additions & 5 deletions node/src/test/scala/com/wavesplatform/http/UtilsRouteSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -695,14 +695,14 @@ class UtilsRouteSpec extends RouteSpec("/utils") with RestAPISettingsHelper with
|func testCallable() = [BinaryEntry("test", i.caller.bytes)]
|
|@Callable(i)
|func testSyncInvoke() = {
| strict r = Invoke(this, "testCallable", [], [AttachedPayment(unit, 100)])
|func testSyncinvoke() = {
| strict r = invoke(this, "testCallable", [], [AttachedPayment(unit, 100)])
| [BinaryEntry("testSyncInvoke", i.caller.bytes)]
|}
|
|@Callable(i)
|func testSyncCallComplexityExcess() = {
| strict r = Invoke(this, "testSyncCallComplexityExcess", [], [])
| strict r = invoke(this, "testSyncCallComplexityExcess", [], [])
| []
|}
|
Expand Down Expand Up @@ -821,8 +821,8 @@ class UtilsRouteSpec extends RouteSpec("/utils") with RestAPISettingsHelper with
.returning(DefaultBlockchainSettings)
.anyNumberOfTimes()

evalScript(""" testSyncInvoke() """) ~> route ~> check {
responseAs[String] shouldBe """{"result":{"type":"Array","value":[{"type":"BinaryEntry","value":{"key":{"type":"String","value":"testSyncInvoke"},"value":{"type":"ByteVector","value":"11111111111111111111111111"}}}]},"complexity":99,"expr":" testSyncInvoke() ","address":"3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9"}"""
evalScript(""" testSyncinvoke() """) ~> route ~> check {
responseAs[String] shouldBe """{"result":{"type":"Array","value":[{"type":"BinaryEntry","value":{"key":{"type":"String","value":"testSyncInvoke"},"value":{"type":"ByteVector","value":"11111111111111111111111111"}}}]},"complexity":99,"expr":" testSyncinvoke() ","address":"3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9"}"""
}

val complexityLimit = 1234
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ class BigIntInvokeTest
| @Callable(i)
| func default() = {
| let address = Address(base58'$nextDApp')
| strict r = Invoke(address, "default", [], [])
| strict r = invoke(address, "default", [], [])
| if (r == toBigInt($bigIntValue))
| then []
| else throw("")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,13 +156,13 @@ class InvokeAssetChecksTest
|
|@Callable(i)
|func invalidLength() = {
| strict r = Invoke(callingDApp, "default", [], [AttachedPayment(base58'$invalidLengthAsset', 1)])
| strict r = invoke(callingDApp, "default", [], [AttachedPayment(base58'$invalidLengthAsset', 1)])
| []
|}
|
|@Callable(i)
|func unexisting() = {
| strict r = Invoke(callingDApp, "default", [], [AttachedPayment(base58'$unexistingAsset', 1)])
| strict r = invoke(callingDApp, "default", [], [AttachedPayment(base58'$unexistingAsset', 1)])
| []
|}
""".stripMargin
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class InvokePaymentsLimitTest
val nested = nestedInvoke.fold("") {
case (address, payments) =>
val paymentsStr = payments.map(p => s"AttachedPayment(base58'${p.assetId}', ${p.amount})").mkString("[", ", ", "]")
s""" strict r = Invoke(Address(base58'$address'), "default", [], $paymentsStr) """
s""" strict r = invoke(Address(base58'$address'), "default", [], $paymentsStr) """
}
TestCompiler(version).compileContract(
s"""
Expand Down
Loading

0 comments on commit 425891f

Please sign in to comment.