Skip to content

Commit

Permalink
SC-787 Fix error on set contract with sync call (wavesplatform#3520)
Browse files Browse the repository at this point in the history
  • Loading branch information
xrtm000 authored Oct 18, 2021
1 parent 88d076d commit a0e4f7d
Show file tree
Hide file tree
Showing 17 changed files with 260 additions and 72 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,49 @@ package com.wavesplatform.lang.contract

import com.wavesplatform.lang.contract.DApp.{CallableFunction, VerifierFunction}
import com.wavesplatform.lang.directives.values.StdLibVersion
import com.wavesplatform.lang.v1.FunctionHeader
import com.wavesplatform.lang.v1.FunctionHeader.Native
import com.wavesplatform.lang.v1.compiler.CompilationError.Generic
import com.wavesplatform.lang.v1.compiler.Terms.DECLARATION
import com.wavesplatform.lang.v1.compiler.Terms._
import com.wavesplatform.lang.v1.compiler.Types._
import com.wavesplatform.lang.v1.compiler.{CompilationError, Terms}
import com.wavesplatform.lang.v1.evaluator.FunctionIds.{CALLDAPP, CALLDAPPREENTRANT}
import com.wavesplatform.lang.v1.evaluator.ctx.impl.waves.Types
import com.wavesplatform.protobuf.dapp.DAppMeta

import scala.annotation.tailrec

case class DApp(
meta: DAppMeta,
decs: List[DECLARATION],
callableFuncs: List[CallableFunction],
verifierFuncOpt: Option[VerifierFunction]
)
) {
val verifierContainsSyncCall: Boolean =
verifierFuncOpt.map(_.u.body).exists(e => containsSyncCall(List(e)))

@tailrec private def containsSyncCall(e: List[EXPR]): Boolean = {
def funcBody(header: FunctionHeader): List[EXPR] =
decs.collect { case FUNC(header.funcName, _, body) => body }

def refBody(key: String): List[EXPR] =
decs.collect { case LET(`key`, body) => body }

e match {
case Nil => false
case FUNCTION_CALL(Native(CALLDAPP), _) :: _ => true
case FUNCTION_CALL(Native(CALLDAPPREENTRANT), _) :: _ => true
case GETTER(expr, _) :: l => containsSyncCall(expr :: l)
case LET_BLOCK(LET(_, value), body) :: l => containsSyncCall(value :: body :: l)
case BLOCK(LET(_, value), body) :: l => containsSyncCall(value :: body :: l)
case BLOCK(FUNC(_, _, value), body) :: l => containsSyncCall(value :: body :: l)
case IF(cond, ifTrue, ifFalse) :: l => containsSyncCall(cond :: ifTrue :: ifFalse :: l)
case FUNCTION_CALL(header, args) :: l => containsSyncCall(funcBody(header) ::: args ::: l)
case REF(key) :: l => containsSyncCall(refBody(key) ::: l)
case _ :: l => containsSyncCall(l)
}
}
}

object DApp {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,12 @@ object ContractScript {
private def estimateAnnotatedFunctions(
version: StdLibVersion,
dApp: DApp,
estimator: ScriptEstimator
estimator: ScriptEstimator,
fixEstimateOfVerifier: Boolean
): Either[String, Iterable[(String, Long)]] =
for {
callables <- estimateDeclarations(version, dApp, estimator, callables(dApp))
verifier <- dApp.verifierFuncOpt.traverse(estimateVerifier(version, dApp, estimator, _))
verifier <- dApp.verifierFuncOpt.traverse(estimateVerifier(version, dApp, estimator, fixEstimateOfVerifier, _))
} yield verifier ++ callables

def estimateUserFunctions(
Expand Down Expand Up @@ -107,13 +108,18 @@ object ContractScript {
version: StdLibVersion,
dApp: DApp,
estimator: ScriptEstimator,
fixEstimateOfVerifier: Boolean,
verifier: VerifierFunction
): Either[String, (String, Long)] =
estimator(
varNames(version, DAppType),
functionCosts(version, DAppType, isDAppVerifier = true),
constructExprFromDeclAndContext(dApp.decs, Some(verifier.annotation.invocationArgName), verifier.u)
).map((verifier.u.name, _))
): Either[String, (String, Long)] = {
if (dApp.verifierContainsSyncCall)
Left("DApp-to-dApp invocations are not allowed from verifier")
else
estimator(
varNames(version, DAppType),
functionCosts(version, DAppType, isDAppVerifier = !fixEstimateOfVerifier),
constructExprFromDeclAndContext(dApp.decs, Some(verifier.annotation.invocationArgName), verifier.u)
).map((verifier.u.name, _))
}

private[script] def constructExprFromDeclAndContext(
dec: List[DECLARATION],
Expand Down Expand Up @@ -143,10 +149,11 @@ object ContractScript {
version: StdLibVersion,
dApp: DApp,
estimator: ScriptEstimator,
fixEstimateOfVerifier: Boolean,
useReducedVerifierLimit: Boolean = true
): Either[String, (Long, Map[String, Long])] =
for {
(maxComplexity, complexities) <- estimateComplexityExact(version, dApp, estimator)
(maxComplexity, complexities) <- estimateComplexityExact(version, dApp, estimator, fixEstimateOfVerifier)
_ <- checkComplexity(version, dApp, maxComplexity, complexities, useReducedVerifierLimit)
} yield (maxComplexity._2, complexities)

Expand Down Expand Up @@ -185,10 +192,11 @@ object ContractScript {
def estimateComplexityExact(
version: StdLibVersion,
dApp: DApp,
estimator: ScriptEstimator
estimator: ScriptEstimator,
fixEstimateOfVerifier: Boolean
): Either[String, ((String, Long), Map[String, Long])] =
for {
annotatedFunctionComplexities <- estimateAnnotatedFunctions(version, dApp, estimator)
annotatedFunctionComplexities <- estimateAnnotatedFunctions(version, dApp, estimator, fixEstimateOfVerifier)
max = annotatedFunctionComplexities.toList.maximumOption(_._2 compareTo _._2).getOrElse(("", 0L))
} yield (max, annotatedFunctionComplexities.toMap)
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ object Script {
def complexityInfo(
script: Script,
estimator: ScriptEstimator,
fixEstimateOfVerifier: Boolean,
useContractVerifierLimit: Boolean
): Either[String, ComplexityInfo] =
(script: @unchecked) match {
Expand All @@ -87,6 +88,7 @@ object Script {
version,
contract,
estimator,
fixEstimateOfVerifier,
useContractVerifierLimit
)
complexityInfo = verifierFuncOpt.fold(
Expand All @@ -97,15 +99,16 @@ object Script {
} yield complexityInfo
}

def estimate(script: Script, estimator: ScriptEstimator, useContractVerifierLimit: Boolean): Either[String, Long] =
complexityInfo(script, estimator, useContractVerifierLimit)
def estimate(script: Script, estimator: ScriptEstimator, fixEstimateOfVerifier: Boolean, useContractVerifierLimit: Boolean): Either[String, Long] =
complexityInfo(script, estimator, fixEstimateOfVerifier, useContractVerifierLimit)
.map(_.maxComplexity)

def verifierComplexity(
script: Script,
estimator: ScriptEstimator,
fixEstimateOfVerifier: Boolean,
useContractVerifierLimit: Boolean
): Either[String, Long] =
complexityInfo(script, estimator, useContractVerifierLimit)
complexityInfo(script, estimator, fixEstimateOfVerifier, useContractVerifierLimit)
.map(_.verifierComplexity)
}
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ trait BaseGlobal {
compRes <- ContractCompiler.compileWithParseResult(input, ctx, stdLibVersion, needCompaction, removeUnusedCode)
(compDAppOpt, exprDApp, compErrorList) = compRes
complexityWithMap <- if (compDAppOpt.nonEmpty && compErrorList.isEmpty)
ContractScript.estimateComplexity(stdLibVersion, compDAppOpt.get, estimator)
ContractScript.estimateComplexity(stdLibVersion, compDAppOpt.get, estimator, fixEstimateOfVerifier = true)
else Right((0L, Map.empty[String, Long]))
bytes <- if (compDAppOpt.nonEmpty && compErrorList.isEmpty) serializeContract(compDAppOpt.get, stdLibVersion) else Right(Array.empty[Byte])
} yield (bytes, complexityWithMap, exprDApp, compErrorList))
Expand Down Expand Up @@ -191,7 +191,7 @@ trait BaseGlobal {
dApp <- ContractCompiler.compile(input, ctx, stdLibVersion, CallableFunction, needCompaction, removeUnusedCode)
userFunctionComplexities <- ContractScript.estimateUserFunctions(stdLibVersion, dApp, estimator)
globalVariableComplexities <- ContractScript.estimateGlobalVariables(stdLibVersion, dApp, estimator)
(maxComplexity, annotatedComplexities) <- ContractScript.estimateComplexityExact(stdLibVersion, dApp, estimator)
(maxComplexity, annotatedComplexities) <- ContractScript.estimateComplexityExact(stdLibVersion, dApp, estimator, fixEstimateOfVerifier = true)
(verifierComplexity, callableComplexities) = dApp.verifierFuncOpt.fold(
(0L, annotatedComplexities)
)(v => (annotatedComplexities(v.u.name), annotatedComplexities - v.u.name))
Expand All @@ -218,7 +218,7 @@ trait BaseGlobal {
): Either[String, Unit] =
for {
_ <- if (estimator == ScriptEstimatorV2)
ContractScript.estimateComplexity(version, dApp, ScriptEstimatorV1)
ContractScript.estimateComplexity(version, dApp, ScriptEstimatorV1, fixEstimateOfVerifier = true)
else
Right(())
_ <- ContractScript.checkComplexity(version, dApp, maxComplexity, complexities, useReducedVerifierLimit = true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class SetScriptTransactionGrpcSuite extends GrpcBaseTransactionSuite {
""".stripMargin

val script = ScriptCompiler(scriptText, isAssetScript = false, ScriptEstimatorV2).explicitGet()._1
val scriptComplexity = Script.estimate(Script.fromBase64String(script.bytes().base64).explicitGet(), ScriptEstimatorV3, useContractVerifierLimit = true).explicitGet()
val scriptComplexity = Script.estimate(Script.fromBase64String(script.bytes().base64).explicitGet(), ScriptEstimatorV3, fixEstimateOfVerifier = true, useContractVerifierLimit = true).explicitGet()
val setScriptTx = sender.setScript(contract, Right(Some(script)), setScriptFee, waitForTx = true)
val setScriptTxId = PBTransactions.vanilla(setScriptTx).explicitGet().id().toString

Expand Down
25 changes: 14 additions & 11 deletions node/src/main/scala/com/wavesplatform/api/http/UtilsApiRoute.scala
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,9 @@ case class UtilsApiRoute(

def compileCode: Route = path("script" / "compileCode") {
(post & entity(as[String])) { code =>
executeLimited(ScriptCompiler.compileAndEstimateCallables(code, estimator(), defaultStdLib = blockchain.actualRideVersion)) { result =>
val version = blockchain.actualRideVersion
val fixEstimateOfVerifier = blockchain.isFeatureActivated(BlockchainFeatures.RideV6)
executeLimited(ScriptCompiler.compileAndEstimateCallables(code, estimator(), version, fixEstimateOfVerifier)) { result =>
complete(
checkInvokeExpression(result)
.fold(
Expand All @@ -155,15 +157,9 @@ case class UtilsApiRoute(
def compileWithImports: Route = path("script" / "compileWithImports") {
import ScriptWithImportsRequest._
(post & entity(as[ScriptWithImportsRequest])) { req =>
def stdLib: StdLibVersion =
if (blockchain.isFeatureActivated(BlockchainFeatures.SynchronousCalls, blockchain.height)) {
V5
} else if (blockchain.isFeatureActivated(BlockchainFeatures.Ride4DApps, blockchain.height)) {
V4
} else {
StdLibVersion.VersionDic.default
}
executeLimited(ScriptCompiler.compile(req.script, estimator(), req.imports, stdLib)) { result =>
val version = blockchain.actualRideVersion
val fixEstimateOfVerifier = blockchain.isFeatureActivated(BlockchainFeatures.RideV6)
executeLimited(ScriptCompiler.compile(req.script, estimator(), req.imports, version, fixEstimateOfVerifier)) { result =>
complete(
checkInvokeExpression(result)
.fold(
Expand All @@ -189,7 +185,14 @@ case class UtilsApiRoute(
.left
.map(_.m)
.flatMap { script =>
Script.complexityInfo(script, estimator(), useContractVerifierLimit = false).map((script, _))
Script
.complexityInfo(
script,
estimator(),
fixEstimateOfVerifier = blockchain.isFeatureActivated(RideV6),
useContractVerifierLimit = false
)
.map((script, _))
}
) { result =>
complete(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,6 @@ object RideVersionProvider {
def actualRideVersion: StdLibVersion =
actualVersionByFeature
.collectFirst { case (feature, version) if b.isFeatureActivated(feature) => version }
.getOrElse(V2)
.getOrElse(V3)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,13 @@ object DiffsCommon {
blockchain.height > blockchain.settings.functionalitySettings.estimatorPreCheckHeight &&
!blockchain.isFeatureActivated(BlockchainFeatures.BlockV5)

val fixEstimateOfVerifier = blockchain.isFeatureActivated(BlockchainFeatures.RideV6)
val cost =
if (useV1PreCheck)
Script.verifierComplexity(script, ScriptEstimatorV1, !isAsset && blockchain.useReducedVerifierComplexityLimit) *>
Script.verifierComplexity(script, ScriptEstimatorV2, !isAsset && blockchain.useReducedVerifierComplexityLimit)
Script.verifierComplexity(script, ScriptEstimatorV1, fixEstimateOfVerifier, !isAsset && blockchain.useReducedVerifierComplexityLimit) *>
Script.verifierComplexity(script, ScriptEstimatorV2, fixEstimateOfVerifier, !isAsset && blockchain.useReducedVerifierComplexityLimit)
else
Script.verifierComplexity(script, blockchain.estimator, !isAsset && blockchain.useReducedVerifierComplexityLimit)
Script.verifierComplexity(script, blockchain.estimator, fixEstimateOfVerifier, !isAsset && blockchain.useReducedVerifierComplexityLimit)

cost.map((script, _))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import cats.syntax.either._
import cats.syntax.traverse._
import com.wavesplatform.features.ComplexityCheckPolicyProvider._
import com.wavesplatform.features.EstimatorProvider._
import com.wavesplatform.features.BlockchainFeatures.RideV6
import com.wavesplatform.lang.ValidationError
import com.wavesplatform.lang.contract.DApp
import com.wavesplatform.lang.directives.values.StdLibVersion
Expand Down Expand Up @@ -41,8 +42,14 @@ object SetScriptTransactionDiff {
val callables = dApp.copy(verifierFuncOpt = None)
val actualComplexities =
for {
currentComplexity <- ContractScript.estimateComplexity(version, callables, blockchain.estimator, blockchain.useReducedVerifierComplexityLimit)
nextComplexities <- estimateNext(blockchain, version, callables)
currentComplexity <- ContractScript.estimateComplexity(
version,
callables,
blockchain.estimator,
fixEstimateOfVerifier = blockchain.isFeatureActivated(RideV6),
useReducedVerifierLimit = blockchain.useReducedVerifierComplexityLimit
)
nextComplexities <- estimateNext(blockchain, version, callables)
complexitiesByEstimator = (currentComplexity :: nextComplexities).mapWithIndex {
case ((_, complexitiesByCallable), i) => (i + blockchain.estimator.version, complexitiesByCallable)
}.toMap
Expand All @@ -58,7 +65,10 @@ object SetScriptTransactionDiff {
): Either[String, List[(Long, Map[String, Long])]] =
ScriptEstimator.all
.drop(blockchain.estimator.version)
.traverse(se => ContractScript.estimateComplexityExact(version, dApp, se)
.map { case ((_, maxComplexity), complexities) => (maxComplexity, complexities) }
.traverse(
se =>
ContractScript
.estimateComplexityExact(version, dApp, se, fixEstimateOfVerifier = blockchain.isFeatureActivated(RideV6))
.map { case ((_, maxComplexity), complexities) => (maxComplexity, complexities) }
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import com.wavesplatform.account._
import com.wavesplatform.common.state.ByteStr
import com.wavesplatform.common.utils.EitherExt2
import com.wavesplatform.features.BlockchainFeatures
import com.wavesplatform.features.BlockchainFeatures.RideV6
import com.wavesplatform.features.EstimatorProvider._
import com.wavesplatform.features.FunctionCallPolicyProvider._
import com.wavesplatform.lang._
Expand Down Expand Up @@ -298,7 +299,7 @@ object InvokeScriptTransactionDiff {
val version = tx.expression.stdLibVersion
val estimator = blockchain.estimator
ContractScript
.estimateComplexity(version, dApp, estimator)
.estimateComplexity(version, dApp, estimator, fixEstimateOfVerifier = blockchain.isFeatureActivated(RideV6))
.leftMap(GenericError(_))
.map {
case (_, complexities) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,54 +15,58 @@ object ScriptCompiler extends ScorexLogging {
def apply(
scriptText: String,
isAssetScript: Boolean,
estimator: ScriptEstimator
estimator: ScriptEstimator,
fixEstimateOfVerifier: Boolean = true
): Either[String, (Script, Long)] =
for {
directives <- DirectiveParser(scriptText)
directiveSet <- Directive.extractDirectives(directives, StdLibVersion.VersionDic.default)
scriptType = ScriptType.isAssetScript(isAssetScript)
result <- applyAndEstimate(scriptText, directiveSet.copy(scriptType = scriptType), estimator, Script.estimate)
result <- applyAndEstimate(scriptText, directiveSet.copy(scriptType = scriptType), estimator, Script.estimate, fixEstimateOfVerifier)
} yield result

def compile(
scriptText: String,
estimator: ScriptEstimator,
libraries: Map[String, String] = Map(),
defaultStdLib: => StdLibVersion = StdLibVersion.VersionDic.default
defaultStdLib: => StdLibVersion = StdLibVersion.VersionDic.default,
fixEstimateOfVerifier: Boolean = true
): Either[String, (Script, Long)] =
compileAndEstimate(scriptText, estimator, libraries, Script.estimate, defaultStdLib)
compileAndEstimate(scriptText, estimator, libraries, Script.estimate, defaultStdLib, fixEstimateOfVerifier)

def compileAndEstimateCallables(
scriptText: String,
estimator: ScriptEstimator,
libraries: Map[String, String] = Map(),
defaultStdLib: => StdLibVersion = StdLibVersion.VersionDic.default
defaultStdLib: => StdLibVersion,
fixEstimateOfVerifier: Boolean
): Either[String, (Script, Script.ComplexityInfo)] =
compileAndEstimate(scriptText, estimator, libraries, Script.complexityInfo, defaultStdLib)
compileAndEstimate(scriptText, estimator, Map(), Script.complexityInfo, defaultStdLib, fixEstimateOfVerifier)

def compileAndEstimate[C](
private def compileAndEstimate[C](
scriptText: String,
estimator: ScriptEstimator,
libraries: Map[String, String] = Map(),
estimate: (Script, ScriptEstimator, Boolean) => Either[String, C],
defaultStdLib: => StdLibVersion = StdLibVersion.VersionDic.default
libraries: Map[String, String],
estimate: (Script, ScriptEstimator, Boolean, Boolean) => Either[String, C],
defaultStdLib: => StdLibVersion,
fixEstimateOfVerifier: Boolean
): Either[String, (Script, C)] =
for {
directives <- DirectiveParser(scriptText)
directiveSet <- Directive.extractDirectives(directives, defaultStdLib)
linkedInput <- ScriptPreprocessor(scriptText, libraries, directiveSet.imports)
result <- applyAndEstimate(linkedInput, directiveSet, estimator, estimate)
result <- applyAndEstimate(linkedInput, directiveSet, estimator, estimate, fixEstimateOfVerifier)
} yield result

private def applyAndEstimate[C](
scriptText: String,
directiveSet: DirectiveSet,
estimator: ScriptEstimator,
estimate: (Script, ScriptEstimator, Boolean) => Either[String, C]
estimate: (Script, ScriptEstimator, Boolean, Boolean) => Either[String, C],
fixEstimateOfVerifier: Boolean
): Either[String, (Script, C)] =
for {
script <- tryCompile(scriptText, directiveSet)
complexity <- estimate(script, estimator, directiveSet.scriptType != Asset)
complexity <- estimate(script, estimator, fixEstimateOfVerifier, directiveSet.scriptType != Asset)
} yield (script, complexity)

private def tryCompile(src: String, directiveSet: DirectiveSet): Either[String, Script] = {
Expand Down
Loading

0 comments on commit a0e4f7d

Please sign in to comment.