diff --git a/Jenkinsfile b/Jenkinsfile deleted file mode 100644 index b1b769b2eef..00000000000 --- a/Jenkinsfile +++ /dev/null @@ -1,119 +0,0 @@ -#!/usr/bin/env groovy - -@Library('jenkins-shared-lib') -import devops.waves.* -ut = new utils() -def buildTasks = [:] -def repo_url = 'https://github.com/wavesplatform/Waves.git' - -properties([ - parameters([ - listGitBranches( - branchFilter: 'origin/(.*)', - credentialsId: '', - defaultValue: '', - name: 'branch', - listSize: '20', - quickFilterEnabled: false, - remoteURL: repo_url, - selectedValue: 'NONE', - sortMode: 'DESCENDING_SMART', - type: 'PT_BRANCH')]) -]) - -stage('Aborting this build'){ - // On the first launch pipeline doesn't have any parameters configured and must skip all the steps - if (env.BUILD_NUMBER == '1'){ - echo "This is the first run of the pipeline! It is now should be configured and ready to go!" - currentBuild.result = Constants.PIPELINE_ABORTED - return - } - if (! params.branch ) { - echo "Aborting this build. Please run it again with the required parameters specified." - currentBuild.result = Constants.PIPELINE_ABORTED - return - } - else - echo "Parameters are specified. Branch: ${branch}" -} - -if (currentBuild.result == Constants.PIPELINE_ABORTED){ - return -} - -timeout(time:90, unit:'MINUTES') { - node('wavesnode'){ - currentBuild.result = Constants.PIPELINE_SUCCESS - timestamps { - wrap([$class: 'AnsiColorBuildWrapper', 'colorMapName': 'XTerm']) { - try { - withEnv(["SBT_THREAD_NUMBER=7"]) { - - currentBuild.displayName = "#${env.BUILD_NUMBER} - ${branch}" - - stage('Checkout') { - sh 'env' - step([$class: 'WsCleanup']) - checkout([ - $class: 'GitSCM', - branches: [[ name: branch ]], - doGenerateSubmoduleConfigurations: false, - extensions: [], - submoduleCfg: [], - userRemoteConfigs: [[url: repo_url]] - ]) - } - - stage('Unit Test') { - ut.sbt '-mem 10240 checkPR' - } - - stage('Check containers') { - sh 'docker rmi com.wavesplatform/it com.wavesplatform/node-it com.wavesplatform/dex-it || true' - sh 'docker ps -a' - sh 'docker images' - sh 'docker network ls' - sh 'rm -rf it/target || true' - } - - stage('Integration Test') { - try { - ut.sbt '-mem 40960 clean it/test' - } - catch (err) {} - finally{ - dir('it/target/logs') { - sh "tar -czvf logs.tar.gz * || true" - } - dir('node-it/target/logs') { - sh "tar -czvf node.logs.tar.gz * || true" - } - } - } - - stage('Docker cleanup') { - sh "docker system prune -af --volumes" - } - } - } - catch (err) { - currentBuild.result = Constants.PIPELINE_FAILURE - println("ERROR caught") - println(err) - println(err.getMessage()) - println(err.getStackTrace()) - println(err.getCause()) - println(err.getLocalizedMessage()) - println(err.toString()) - } - finally{ - logArchiveLocation = findFiles(glob: '**/*logs.tar.gz') - logArchiveLocation.each { - archiveArtifacts it.path - } - ut.notifySlack("mtuktarov-test", currentBuild.result) - } - } - } - } -} diff --git a/build.sbt b/build.sbt index 941ad38d997..89e4ac79904 100644 --- a/build.sbt +++ b/build.sbt @@ -53,6 +53,15 @@ lazy val `lang-tests` = project .in(file("lang/tests")) .dependsOn(`lang-testkit`) +lazy val `lang-tests-js` = project + .in(file("lang/tests-js")) + .enablePlugins(ScalaJSPlugin) + .dependsOn(`lang-js`) + .settings( + libraryDependencies += Dependencies.scalaJsTest.value, + testFrameworks += new TestFramework("utest.runner.Framework") + ) + lazy val node = project.dependsOn(`lang-jvm`, `lang-testkit` % "test") lazy val `grpc-server` = project.dependsOn(node % "compile;test->test;runtime->provided") @@ -100,6 +109,7 @@ lazy val `waves-node` = (project in file(".")) `lang-js`, `lang-jvm`, `lang-tests`, + `lang-tests-js`, `lang-testkit`, `repl-js`, `repl-jvm`, @@ -188,6 +198,7 @@ checkPRRaw := Def (`lang-tests` / Test / test).value (`repl-jvm` / Test / test).value (`lang-js` / Compile / fastOptJS).value + (`lang-tests-js` / Test / test).value (`grpc-server` / Test / test).value (node / Test / test).value (`repl-js` / Compile / fastOptJS).value diff --git a/grpc-server/src/main/scala/com/wavesplatform/events/Repo.scala b/grpc-server/src/main/scala/com/wavesplatform/events/Repo.scala index c58817155e3..1ecfea396be 100644 --- a/grpc-server/src/main/scala/com/wavesplatform/events/Repo.scala +++ b/grpc-server/src/main/scala/com/wavesplatform/events/Repo.scala @@ -73,7 +73,7 @@ class Repo(db: DB, blocksApi: CommonBlocksApi)(implicit s: Scheduler) extends Bl db.put(keyForHeight(ls.keyBlock.height), ls.solidify().protobuf.update(_.append.block.optionalBlock := None).toByteArray) ) - val ba = BlockAppended.from(block, diff, blockchainBeforeWithMinerReward) + val ba = BlockAppended.from(block, diff, blockchainBeforeWithMinerReward, minerReward) liquidState = Some(LiquidState(ba, Seq.empty)) handlers.forEach(_.handleUpdate(ba)) } diff --git a/grpc-server/src/main/scala/com/wavesplatform/events/events.scala b/grpc-server/src/main/scala/com/wavesplatform/events/events.scala index 7439cd6fd48..52011f47c10 100644 --- a/grpc-server/src/main/scala/com/wavesplatform/events/events.scala +++ b/grpc-server/src/main/scala/com/wavesplatform/events/events.scala @@ -24,7 +24,7 @@ import com.wavesplatform.transaction.assets.exchange.ExchangeTransaction import com.wavesplatform.transaction.lease.LeaseTransaction import com.wavesplatform.transaction.smart.InvokeScriptTransaction import com.wavesplatform.transaction.transfer.{MassTransferTransaction, TransferTransaction} -import com.wavesplatform.transaction.{Asset, Authorized, EthereumTransaction, GenesisTransaction} +import com.wavesplatform.transaction.{Asset, Authorized, EthereumTransaction} import scala.collection.mutable import scala.collection.mutable.ArrayBuffer @@ -557,21 +557,18 @@ final case class BlockAppended( } object BlockAppended { - def from(block: Block, diff: DetailedDiff, blockchainBeforeWithMinerReward: Blockchain): BlockAppended = { + def from(block: Block, diff: DetailedDiff, blockchainBeforeWithMinerReward: Blockchain, minerReward: Option[Long]): BlockAppended = { + val height = blockchainBeforeWithMinerReward.height val (blockStateUpdate, txsStateUpdates, txsMetadata, refAssets) = StateUpdate.container(blockchainBeforeWithMinerReward, diff, block.sender.toAddress) // updatedWavesAmount can change as a result of either genesis transactions or miner rewards - val updatedWavesAmount = blockchainBeforeWithMinerReward.height match { - // genesis case - case 0 => block.transactionData.collect { case GenesisTransaction(_, amount, _, _, _) => amount.value }.sum - // miner reward case - case height => blockchainBeforeWithMinerReward.wavesAmount(height).toLong - } + val wavesAmount = blockchainBeforeWithMinerReward.wavesAmount(height).toLong + val updatedWavesAmount = wavesAmount + minerReward.filter(_ => height > 0).getOrElse(0L) BlockAppended( block.id(), - blockchainBeforeWithMinerReward.height + 1, + height + 1, block, updatedWavesAmount, blockStateUpdate, diff --git a/grpc-server/src/test/scala/com/wavesplatform/events/BlockchainUpdatesSpec.scala b/grpc-server/src/test/scala/com/wavesplatform/events/BlockchainUpdatesSpec.scala index df1f428313c..7ef8910792f 100644 --- a/grpc-server/src/test/scala/com/wavesplatform/events/BlockchainUpdatesSpec.scala +++ b/grpc-server/src/test/scala/com/wavesplatform/events/BlockchainUpdatesSpec.scala @@ -14,6 +14,7 @@ import com.wavesplatform.events.protobuf.BlockchainUpdated.Rollback.RollbackType import com.wavesplatform.events.protobuf.BlockchainUpdated.Update import com.wavesplatform.events.protobuf.serde.* import com.wavesplatform.events.protobuf.{TransactionMetadata, BlockchainUpdated as PBBlockchainUpdated} +import com.wavesplatform.features.BlockchainFeatures.BlockReward import com.wavesplatform.history.Domain import com.wavesplatform.lang.directives.values.V5 import com.wavesplatform.lang.v1.FunctionHeader @@ -278,9 +279,74 @@ class BlockchainUpdatesSpec extends FreeSpec with WithBUDomain with ScalaFutures ) } - "should include correct waves amount" in withNEmptyBlocksSubscription(settings = currentSettings) { result => - val balances = result.collect { case b if b.update.isAppend => b.getAppend.getBlock.updatedWavesAmount } - balances shouldBe Seq(10000000000000000L, 10000000600000000L, 10000001200000000L) + "should include correct waves amount" - { + val totalWaves = 100_000_000_0000_0000L + val reward = 6_0000_0000 + + "on preactivated block reward" in { + val settings = currentSettings.setFeaturesHeight((BlockReward, 0)) + + withDomainAndRepo(settings) { case (d, repo) => + d.appendBlock() + d.blockchain.wavesAmount(1) shouldBe totalWaves + reward + repo.getBlockUpdate(1).getUpdate.vanillaAppend.updatedWavesAmount shouldBe totalWaves + reward + + d.appendBlock() + d.blockchain.wavesAmount(2) shouldBe totalWaves + reward * 2 + repo.getBlockUpdate(2).getUpdate.vanillaAppend.updatedWavesAmount shouldBe totalWaves + reward * 2 + } + } + + "on activation of block reward" in { + val settings = currentSettings.setFeaturesHeight((BlockReward, 3)) + + withNEmptyBlocksSubscription(settings = settings, count = 3) { result => + val balances = result.collect { case b if b.update.isAppend => b.getAppend.getBlock.updatedWavesAmount } + balances shouldBe Seq(totalWaves, totalWaves, totalWaves + reward, totalWaves + reward * 2) + } + + withDomainAndRepo(settings) { case (d, repo) => + d.appendBlock() + d.blockchain.wavesAmount(1) shouldBe totalWaves + repo.getBlockUpdate(1).getUpdate.vanillaAppend.updatedWavesAmount shouldBe totalWaves + + d.appendBlock() + d.blockchain.wavesAmount(2) shouldBe totalWaves + repo.getBlockUpdate(2).getUpdate.vanillaAppend.updatedWavesAmount shouldBe totalWaves + + d.appendBlock() + d.blockchain.wavesAmount(3) shouldBe totalWaves + reward + repo.getBlockUpdate(3).getUpdate.vanillaAppend.updatedWavesAmount shouldBe totalWaves + reward + + d.appendBlock() + d.blockchain.wavesAmount(4) shouldBe totalWaves + reward * 2 + repo.getBlockUpdate(4).getUpdate.vanillaAppend.updatedWavesAmount shouldBe totalWaves + reward * 2 + } + } + + "on rollbacks" in { + withDomainAndRepo(currentSettings) { case (d, repo) => + d.appendBlock() + + // block and micro append + val block = d.appendBlock() + block.sender shouldBe defaultSigner.publicKey + + d.appendMicroBlock(TxHelpers.transfer(defaultSigner)) + d.blockchain.wavesAmount(2) shouldBe totalWaves + reward * 2 + repo.getBlockUpdate(2).getUpdate.vanillaAppend.updatedWavesAmount shouldBe totalWaves + reward * 2 + + // micro rollback + d.appendKeyBlock(Some(block.id())) + d.blockchain.wavesAmount(3) shouldBe totalWaves + reward * 3 + repo.getBlockUpdate(3).getUpdate.vanillaAppend.updatedWavesAmount shouldBe totalWaves + reward * 3 + + // block rollback + d.rollbackTo(2) + d.blockchain.wavesAmount(2) shouldBe totalWaves + reward * 2 + repo.getBlockUpdate(2).getUpdate.vanillaAppend.updatedWavesAmount shouldBe totalWaves + reward * 2 + } + } } "should include correct heights" in withNEmptyBlocksSubscription(settings = currentSettings) { result => diff --git a/lang/js/src/main/scala/JsAPI.scala b/lang/js/src/main/scala/JsAPI.scala index dbebc859f63..92a8d947a3a 100644 --- a/lang/js/src/main/scala/JsAPI.scala +++ b/lang/js/src/main/scala/JsAPI.scala @@ -159,13 +159,19 @@ object JsAPI { "ast" -> toJs(expr), "complexity" -> complexity.toDouble ) - case CompileResult.DApp(_, di, error) => + case CompileResult.DApp(_, di, meta, error) => + val mappedMeta = + meta.argsWithFuncName.map { case (func, argsWithName) => + func -> argsWithName.map { case (arg, argType) => arg -> argType.name }.toJSArray + }.toJSDictionary + val compactNameToOriginalName: Map[String, String] = di.dApp.meta.compactNameAndOriginalNamePairList.map(pair => pair.compactName -> pair.originalName).toMap val resultFields: Seq[(String, Any)] = Seq( "result" -> Global.toBuffer(di.bytes), "ast" -> toJs(di.dApp), + "meta" -> mappedMeta, "complexity" -> di.maxComplexity._2.toDouble, "verifierComplexity" -> di.verifierComplexity.toDouble, "callableComplexities" -> di.callableComplexities.view.mapValues(_.toDouble).toMap.toJSDictionary, @@ -176,13 +182,12 @@ object JsAPI { compactNameToOriginalName.getOrElse(name, name) -> complexity.toDouble }.toJSDictionary ) - val errorFieldOpt: Seq[(String, Any)] = { + val errorFieldOpt: Seq[(String, Any)] = error .fold( error => Seq("error" -> error), _ => Seq() ) - } js.Dynamic.literal.applyDynamic("apply")((resultFields ++ errorFieldOpt)*) } ) diff --git a/lang/js/src/main/scala/JsApiUtils.scala b/lang/js/src/main/scala/JsApiUtils.scala index 46798a3a502..516b9026a23 100644 --- a/lang/js/src/main/scala/JsApiUtils.scala +++ b/lang/js/src/main/scala/JsApiUtils.scala @@ -55,11 +55,11 @@ object JsApiUtils { } jObj.applyDynamic("apply")( - "type" -> "DAPP", - "posStart" -> ast.position.start, - "posEnd" -> ast.position.end, - "decList" -> ast.decs.map(serDec).toJSArray, - "annFuncList" -> ast.fs.map(serAnnFunc) + "type" -> "DAPP", + "posStart" -> ast.position.start, + "posEnd" -> ast.position.end, + "decList" -> ast.decs.map(serDec).toJSArray, + "annFuncList" -> ast.fs.map(serAnnFunc).toJSArray ) } diff --git a/lang/js/src/main/scala/com/wavesplatform/lang/Global.scala b/lang/js/src/main/scala/com/wavesplatform/lang/Global.scala index bfd721e0379..1e172440c11 100644 --- a/lang/js/src/main/scala/com/wavesplatform/lang/Global.scala +++ b/lang/js/src/main/scala/com/wavesplatform/lang/Global.scala @@ -1,38 +1,30 @@ package com.wavesplatform.lang -import java.math.{BigInteger, BigDecimal => BD} - -import cats.syntax.either._ +import cats.syntax.either.* +import com.wavesplatform.common.utils.{Base58, Base64} import com.wavesplatform.lang.v1.BaseGlobal import com.wavesplatform.lang.v1.evaluator.ctx.impl.Rounding import com.wavesplatform.lang.v1.evaluator.ctx.impl.crypto.RSA.DigestAlgorithm +import java.math.{BigInteger, BigDecimal as BD} import scala.collection.mutable -import scala.scalajs.js.JSConverters._ +import scala.scalajs.js.JSConverters.* import scala.scalajs.js.typedarray.{ArrayBuffer, Int8Array} import scala.util.Try object Global extends BaseGlobal { - def base58Encode(input: Array[Byte]): Either[String, String] = Right(impl.Global.base58Encode(toBuffer(input))) + def base58Encode(input: Array[Byte]): Either[String, String] = Right(Base58.encode(input)) override def base58Decode(input: String, limit: Int): Either[String, Array[Byte]] = for { _ <- Either.cond(input.length <= limit, {}, s"Input is too long (${input.length}), limit is $limit") - x <- impl.Global - .base58Decode(input) - .toOption - .map(toArray) - .toRight("Cannot decode") + x <- Try(Base58.decode(input)).toEither.leftMap(_.getMessage) } yield x - override def base64Encode(input: Array[Byte]): Either[String, String] = Right(impl.Global.base64Encode(toBuffer(input))) + override def base64Encode(input: Array[Byte]): Either[String, String] = Right(Base64.encode(input)) override def base64Decode(input: String, limit: Int): Either[String, Array[Byte]] = for { _ <- Either.cond(input.length <= limit, {}, s"Input is too long (${input.length}), limit is $limit") - x <- impl.Global - .base64Decode(input) - .toOption - .map(toArray) - .toRight("Cannot decode") + x <- Try(Base64.decode(input)).toEither.leftMap(_.getMessage) } yield x private val hex: Array[Char] = Array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f') diff --git a/lang/js/src/main/scala/com/wavesplatform/lang/impl/Global.scala b/lang/js/src/main/scala/com/wavesplatform/lang/impl/Global.scala index 245b2900fda..c35ac7c67d8 100644 --- a/lang/js/src/main/scala/com/wavesplatform/lang/impl/Global.scala +++ b/lang/js/src/main/scala/com/wavesplatform/lang/impl/Global.scala @@ -5,16 +5,11 @@ import com.wavesplatform.lang.v1.evaluator.ctx.impl.crypto.RSA.DigestAlgorithm import scala.scalajs.js import scala.scalajs.js.annotation.JSGlobalScope import scala.scalajs.js.typedarray.ArrayBuffer -import scala.scalajs.js.{Object, Promise, UndefOr, native} +import scala.scalajs.js.{Object, Promise, native} @native @JSGlobalScope object Global extends Object { - def base58Encode(input: ArrayBuffer): String = native - def base58Decode(input: String): UndefOr[ArrayBuffer] = native - def base64Encode(input: ArrayBuffer): String = native - def base64Decode(input: String): UndefOr[ArrayBuffer] = native - def curve25519verify(message: ArrayBuffer, sig: ArrayBuffer, pub: ArrayBuffer): Boolean = native def rsaVerify(alg: DigestAlgorithm, message: ArrayBuffer, sig: ArrayBuffer, pub: ArrayBuffer): Boolean = native def keccak256(message: ArrayBuffer): ArrayBuffer = native diff --git a/lang/jvm/src/main/scala/com/wavesplatform/lang/Lang.scala b/lang/jvm/src/main/scala/com/wavesplatform/lang/Lang.scala index ae604cd8e42..0f0580480e8 100644 --- a/lang/jvm/src/main/scala/com/wavesplatform/lang/Lang.scala +++ b/lang/jvm/src/main/scala/com/wavesplatform/lang/Lang.scala @@ -2,8 +2,11 @@ package com.wavesplatform.lang import com.wavesplatform.lang.v1.compiler.Terms import com.wavesplatform.lang.v1.compiler.Terms.EXPR +import com.wavesplatform.lang.v1.estimator.v3.ScriptEstimatorV3 import com.wavesplatform.lang.v1.serialization.SerdeV1 +import scala.jdk.CollectionConverters.* + object Lang { def compile(input: String): EXPR = compile(input, API.latestEstimatorVersion) @@ -21,6 +24,22 @@ object Lang { } ) + def compileDApp(input: String): DAppWithMeta = + API + .compile(input, ScriptEstimatorV3(fixOverflow = true, overhead = false)) + .flatMap { + case r: CompileResult.DApp => + val javaMeta = Meta( + r.meta.argsWithFuncName.view + .mapValues(_.map { case (argName, argType) => ArgNameWithType(argName, argType.name) }.asJava) + .toMap + .asJava + ) + Right(DAppWithMeta(r.dAppInfo.dApp, javaMeta)) + case _ => Left("not a dApp") + } + .fold(e => throw new IllegalArgumentException(e), identity) + def parseAndCompile(input: String, needCompaction: Boolean, removeUnusedCode: Boolean): CompileAndParseResult = parseAndCompile(input, API.latestEstimatorVersion, needCompaction, removeUnusedCode) @@ -32,6 +51,5 @@ object Lang { res => res ) - def serializable(expr: Terms.EXPR): Array[Byte] = SerdeV1.serialize(expr) - + def serialize(expr: Terms.EXPR): Array[Byte] = SerdeV1.serialize(expr) } diff --git a/lang/jvm/src/main/scala/com/wavesplatform/lang/model.scala b/lang/jvm/src/main/scala/com/wavesplatform/lang/model.scala new file mode 100644 index 00000000000..8442bd9bdb2 --- /dev/null +++ b/lang/jvm/src/main/scala/com/wavesplatform/lang/model.scala @@ -0,0 +1,11 @@ +package com.wavesplatform.lang + +import com.wavesplatform.lang.contract.DApp + +import scala.beans.BeanProperty + +case class ArgNameWithType(@BeanProperty name: String, @BeanProperty `type`: String) + +case class DAppWithMeta(@BeanProperty dApp: DApp, @BeanProperty meta: Meta) + +case class Meta(@BeanProperty functionSignatures: java.util.Map[String, java.util.List[ArgNameWithType]]) diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/API.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/API.scala index 6ea71f8342e..d46d0cd402d 100644 --- a/lang/shared/src/main/scala/com/wavesplatform/lang/API.scala +++ b/lang/shared/src/main/scala/com/wavesplatform/lang/API.scala @@ -1,5 +1,7 @@ package com.wavesplatform.lang +import cats.implicits.toBifunctorOps +import com.wavesplatform.lang.contract.meta.FunctionSignatures import com.wavesplatform.lang.directives.Directive.extractDirectives import com.wavesplatform.lang.directives.values.{Call, Expression, Library, StdLibVersion, DApp as DAppType} import com.wavesplatform.lang.directives.{DirectiveParser, DirectiveSet} @@ -49,7 +51,7 @@ object CompileResult { override val maxComplexity: Long = complexity } - case class DApp(version: StdLibVersion, dAppInfo: DAppInfo, error: Either[String, Unit]) extends CompileResult { + case class DApp(version: StdLibVersion, dAppInfo: DAppInfo, meta: FunctionSignatures, error: Either[String, Unit]) extends CompileResult { override def bytes: Array[Byte] = dAppInfo.bytes override def verifierComplexity: Long = dAppInfo.verifierComplexity override def callableComplexities: Map[String, Long] = dAppInfo.callableComplexities @@ -204,8 +206,9 @@ object API { case (DAppType, _) => // Just ignore stdlib version here G.compileContract(input, ctx, version, estimator, needCompaction, removeUnusedCode) - .map { di => - CompileResult.DApp(version, di, G.checkContract(version, di.dApp, di.maxComplexity, di.annotatedComplexities, estimator)) + .flatMap { di => + val check = G.checkContract(version, di.dApp, di.maxComplexity, di.annotatedComplexities, estimator) + G.dAppFuncTypes(di.dApp).bimap(_.m, CompileResult.DApp(version, di, _, check)) } } } diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/contract/meta/ParsedMeta.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/contract/meta/ParsedMeta.scala index 7fc3ded9164..5cda7613855 100644 --- a/lang/shared/src/main/scala/com/wavesplatform/lang/contract/meta/ParsedMeta.scala +++ b/lang/shared/src/main/scala/com/wavesplatform/lang/contract/meta/ParsedMeta.scala @@ -8,5 +8,5 @@ case class ParsedMeta( case class FunctionSignatures( version: Int, - argsWithFuncName: List[(String, List[(String, FINAL)])] + argsWithFuncName: Map[String, List[(String, FINAL)]] ) diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/BaseGlobal.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/BaseGlobal.scala index 9314e3794a3..f57daa92dec 100644 --- a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/BaseGlobal.scala +++ b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/BaseGlobal.scala @@ -247,19 +247,15 @@ trait BaseGlobal { .fromBase64String(compiledCode.trim) .map(script => Script.decompile(script)._1) - def dAppFuncTypes(compiledCode: String): Either[ScriptParseError, FunctionSignatures] = - for { - script <- Script.fromBase64String(compiledCode.trim) - result <- dAppFuncTypes(script) - } yield result - def dAppFuncTypes(script: Script): Either[ScriptParseError, FunctionSignatures] = script match { - case ContractScriptImpl(_, dApp) => - MetaMapper.dicFromProto(dApp).bimap(ScriptParseError, combineMetaWithDApp(_, dApp)) - case _ => Left(ScriptParseError("Expected DApp")) + case ContractScriptImpl(_, dApp) => dAppFuncTypes(dApp) + case _ => Left(ScriptParseError("Expected DApp")) } + def dAppFuncTypes(dApp: DApp): Either[ScriptParseError, FunctionSignatures] = + MetaMapper.dicFromProto(dApp).bimap(ScriptParseError, combineMetaWithDApp(_, dApp)) + private def combineMetaWithDApp(meta: ParsedMeta, dApp: DApp): FunctionSignatures = { val argTypesWithFuncName = meta.callableFuncTypes.fold(List.empty[(String, List[(String, FINAL)])])(types => @@ -268,7 +264,7 @@ trait BaseGlobal { func.u.name -> (func.u.args zip argTypes) } ) - FunctionSignatures(meta.version, argTypesWithFuncName) + FunctionSignatures(meta.version, argTypesWithFuncName.toMap) } def merkleVerify(rootBytes: Array[Byte], proofBytes: Array[Byte], valueBytes: Array[Byte]): Boolean diff --git a/lang/tests-js/src/test/scala/DAppComplexities.scala b/lang/tests-js/src/test/scala/DAppComplexities.scala new file mode 100644 index 00000000000..1876fb85215 --- /dev/null +++ b/lang/tests-js/src/test/scala/DAppComplexities.scala @@ -0,0 +1,7 @@ +case class DAppComplexities( + complexity: Int, + verifierComplexity: Int, + callableComplexities: Map[String, Int], + userFunctionComplexities: Map[String, Int], + globalVariableComplexities: Map[String, Int] +) diff --git a/lang/tests-js/src/test/scala/Global.scala b/lang/tests-js/src/test/scala/Global.scala new file mode 100644 index 00000000000..f8a49245087 --- /dev/null +++ b/lang/tests-js/src/test/scala/Global.scala @@ -0,0 +1,10 @@ +import scala.scalajs.js.annotation.JSExportTopLevel +import scala.scalajs.js.typedarray.ArrayBuffer + +object Global { + @JSExportTopLevel("blake2b256") + def blake2b256(message: ArrayBuffer): ArrayBuffer = message + + @JSExportTopLevel("keccak256") + def keccak256(message: ArrayBuffer): ArrayBuffer = message +} diff --git a/lang/tests-js/src/test/scala/JsAPITest.scala b/lang/tests-js/src/test/scala/JsAPITest.scala new file mode 100644 index 00000000000..80bc6fe89bf --- /dev/null +++ b/lang/tests-js/src/test/scala/JsAPITest.scala @@ -0,0 +1,53 @@ +import com.wavesplatform.lang.directives.values.{V5, V6} +import utest.* + +object JsAPITest extends JsTestBase { + private def simpleDApp(result: String): String = + dApp( + s""" + |@Callable(i) + |func f() = $result + """.stripMargin, + V6 + ) + + val tests: Tests = Tests { + test("expression error and success") { + assertCompileError("1 + 1", "Script should return boolean") + assertCompileSuccess("true") + } + + test("dApp error and success") { + assertCompileError(simpleDApp("true"), "CallableFunction needs to return") + assertCompileSuccess(simpleDApp("[]")) + } + + test("expression complexity") { + expressionComplexity("sigVerify(base16'', base58'', base64'')", V5) ==> 200 + expressionComplexity("sigVerify(base16'', base58'', base64'')") ==> 180 + } + + test("dApp complexities") { + val r = dAppComplexities( + """ + | let x = 1 + 1 + | func f(list: List[Int]) =list.size() + | + | @Callable(i) + | func c1() = [] + | + | @Callable(i) + | func c2() = [IntegerEntry("key", x)] + | + | @Verifier(tx) + | func verify() = sigVerify(tx.bodyBytes, tx.proofs[0], tx.senderPublicKey) + """.stripMargin + ) + r.complexity ==> 182 + r.verifierComplexity ==> 182 + r.callableComplexities ==> Map("c1" -> 1, "c2" -> 4) + r.userFunctionComplexities ==> Map("f" -> 2) + r.globalVariableComplexities ==> Map("x" -> 1) + } + } +} diff --git a/lang/tests-js/src/test/scala/JsTestBase.scala b/lang/tests-js/src/test/scala/JsTestBase.scala new file mode 100644 index 00000000000..6f2e970a747 --- /dev/null +++ b/lang/tests-js/src/test/scala/JsTestBase.scala @@ -0,0 +1,48 @@ +import com.wavesplatform.lang.directives.values.{StdLibVersion, V6} +import utest.{TestSuite, assert} + +import scala.scalajs.js.{Dictionary, isUndefined} + +abstract class JsTestBase extends TestSuite { + protected def assertCompileError(code: String, expectingError: String, estimator: Int = 3): Unit = { + val error = JsAPI.compile(code, estimator).error + assert(error.toString.contains(expectingError)) + } + + protected def assertCompileSuccess(code: String, estimator: Int = 3): Unit = { + val error = JsAPI.compile(code, estimator).error + assert(isUndefined(error)) + } + + protected def expressionComplexity(code: String, version: StdLibVersion = V6, estimator: Int = 3): Int = + JsAPI.compile(expression(code, version), estimator).complexity.asInstanceOf[Int] + + protected def dAppComplexities(code: String, version: StdLibVersion = V6, estimator: Int = 3): DAppComplexities = { + val result = JsAPI.compile(dApp(code, version), estimator) + DAppComplexities( + result.complexity.asInstanceOf[Int], + result.verifierComplexity.asInstanceOf[Int], + result.callableComplexities.asInstanceOf[Dictionary[Int]].toMap, + result.userFunctionComplexities.asInstanceOf[Dictionary[Int]].toMap, + result.globalVariableComplexities.asInstanceOf[Dictionary[Int]].toMap + ) + } + + protected def expression(code: String, version: StdLibVersion): String = + s""" + |{-# STDLIB_VERSION ${version.id} #-} + |{-# CONTENT_TYPE EXPRESSION #-} + |{-# SCRIPT_TYPE ACCOUNT #-} + | + |$code + """.stripMargin + + protected def dApp(code: String, version: StdLibVersion): String = + s""" + |{-# STDLIB_VERSION ${version.id} #-} + |{-# CONTENT_TYPE DAPP #-} + |{-# SCRIPT_TYPE ACCOUNT #-} + | + |$code + """.stripMargin +} diff --git a/node/src/main/resources/swagger-ui/openapi.yaml b/node/src/main/resources/swagger-ui/openapi.yaml index 21e18522fe5..4554403842e 100644 --- a/node/src/main/resources/swagger-ui/openapi.yaml +++ b/node/src/main/resources/swagger-ui/openapi.yaml @@ -79,7 +79,7 @@ paths: description: >- Get a list addresses in the [node wallet](https://docs.waves.tech/en/waves-node/how-to-work-with-node-wallet) - by a given range of indices. Max range {from}-{to} is 100 addresses + by a given range of indices. Max range {from}-{to} is 1000 addresses. operationId: getWalletAddressesRange parameters: - name: from @@ -200,8 +200,8 @@ paths: in: query description: >- For balance at height requests. Max number of blocks back from the - current height is set by `waves.db.max-rollback-depth`, 2000 by - default + current height is set by `rest-api.distribution-address-limit`, 1000 by + default. schema: type: integer - name: asset @@ -251,7 +251,7 @@ paths: description: >- For balance at height requests. Max number of blocks back from the current height is set by - `waves.db.max-rollback-depth`, 2000 by default + `rest-api.distribution-address-limit`, 1000 by default. asset: allOf: - $ref: '#/components/schemas/AssetId' @@ -286,7 +286,9 @@ paths: tags: - addresses summary: Data by keys or regexp - description: Read account data entries by given keys or a regular expression + description: >- + Read account data entries by given keys or a regular expression. + Limited by `rest-api.data-keys-request-limit`, 1000 by default. operationId: getDataByFilter parameters: - $ref: '#/components/parameters/address' @@ -320,7 +322,9 @@ paths: tags: - addresses summary: Data by keys - description: Read account data entries by given keys + description: >- + Read account data entries by given keys. + Limited by `rest-api.data-keys-request-limit`, 1000 by default. operationId: getDataByMultipleKeysViaPost parameters: - $ref: '#/components/parameters/address' @@ -631,7 +635,7 @@ paths: summary: Blocks forged by address description: >- Get a list of blocks forged by a given address. Max range {from}-{to} is - 100 blocks + limited by `rest-api.blocks-request-limit`, 100 by default. operationId: getBlocksByAddress parameters: - name: from @@ -662,8 +666,8 @@ paths: - blocks summary: Block range description: >- - Get blocks at a given range of heights. Max range {from}-{to} is 100 - blocks + Get blocks at a given range of heights. Max range {from}-{to} is + limited by `rest-api.blocks-request-limit`, 100 by default. operationId: getBlocksRange parameters: - name: from @@ -818,7 +822,7 @@ paths: summary: Block headers at range description: >- Get block headers at a given range of heights. Max range {from}-{to} is - 100 blocks + limited by `rest-api.blocks-request-limit`, 100 by default. operationId: getBlockHeadersRange parameters: - name: from @@ -1195,7 +1199,7 @@ paths: tags: - transactions summary: Transactions info - description: Get transactions by IDs + description: Get transactions by IDs. Limited by `rest-api.transactions-by-address-limit`, 1000 by default. operationId: getMultipleTxs parameters: - name: id @@ -1506,7 +1510,7 @@ paths: Get [merkle proofs](https://docs.waves.tech/en/blockchain/block/merkle-root#proof-of-transaction-in-block) for given transactions. For dozens of transactions, better use the POST - method + method. Limited by `rest-api.transactions-by-address-limit`, 1000 by default. operationId: getMerkleProofs parameters: - name: id @@ -1533,7 +1537,7 @@ paths: description: >- Get [merkle proofs](https://docs.waves.tech/en/blockchain/block/merkle-root#proof-of-transaction-in-block) - for given transactions + for given transactions. Limited by `rest-api.transactions-by-address-limit`, 1000 by default. operationId: getMerkleProofsViaPost requestBody: description: Transaction IDs @@ -1798,6 +1802,37 @@ paths: properties: expr: type: string + id: + type: string + example: "C8AgSFP8y91XUTpGtEQAQyjsSemxoY61ocGM852DFKF6" + nullable: true + fee: + type: number + example: 500000 + nullable: true + feeAssetId: + type: string + example: null + nullable: true + sender: + type: string + example: "3Mds6m8XZf4biC72NRkFx2kyoBdC9UvYRUR" + nullable: true + senderPublicKey: + type: string + example: "3T9fL3XpeaHYbumohePPUmeuUqhyEeyzifi9vKouV8QoNtAT6kYV1oPAe9e2KCVquPcyXJpr2QwUiQKEUQPGZnc6" + nullable: true + payment: + type: array + items: + type: object + properties: + amount: + $ref: '#/components/schemas/Amount' + assetId: + allOf: + - $ref: '#/components/schemas/AssetId' + nullable: true required: true responses: '200': @@ -1817,6 +1852,16 @@ paths: type: type: string value: { } + '400': + description: Conflicting request structure + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ApiError' + example: + error: 198 + message: Conflicting request structure. Both expression and invocation structure were sent /utils/seed: get: tags: @@ -2091,7 +2136,8 @@ paths: description: >- Get detailed information about given assets. See [fields descriptions](https://docs.waves.tech/en/blockchain/token/#custom-token-parameters). - For dozens of assets, better use the POST method + For dozens of assets, better use the POST method. + Limited by `rest-api.asset-details-limit`, 100 by default. operationId: getMultipleAssetDetails parameters: - name: id @@ -2124,7 +2170,8 @@ paths: summary: Information about multiple assets description: >- Get detailed information about given assets. See [fields - descriptions](https://docs.waves.tech/en/blockchain/token/#custom-token-parameters) + descriptions](https://docs.waves.tech/en/blockchain/token/#custom-token-parameters). + Limited by `rest-api.asset-details-limit`, 100 by default. operationId: getMultipleAssetDetailsViaPost parameters: - name: full @@ -2229,7 +2276,7 @@ paths: in: path description: >- For balance at height requests. Max number of blocks back from the - current height is set by `waves.db.max-rollback-depth`, 2000 by + current height is set by `waves.rest-api.distribution-address-limit`, 1000 by default required: true schema: diff --git a/node/src/main/scala/com/wavesplatform/Application.scala b/node/src/main/scala/com/wavesplatform/Application.scala index bcb8f51e929..729949a69e3 100644 --- a/node/src/main/scala/com/wavesplatform/Application.scala +++ b/node/src/main/scala/com/wavesplatform/Application.scala @@ -16,6 +16,7 @@ import com.wavesplatform.api.http.alias.AliasApiRoute import com.wavesplatform.api.http.assets.AssetsApiRoute import com.wavesplatform.api.http.eth.EthRpcRoute import com.wavesplatform.api.http.leasing.LeaseApiRoute +import com.wavesplatform.api.http.utils.UtilsApiRoute import com.wavesplatform.common.state.ByteStr import com.wavesplatform.common.utils.EitherExt2 import com.wavesplatform.consensus.PoSSelector diff --git a/node/src/main/scala/com/wavesplatform/api/http/ApiError.scala b/node/src/main/scala/com/wavesplatform/api/http/ApiError.scala index 77d3768c0cf..f0cae25590c 100644 --- a/node/src/main/scala/com/wavesplatform/api/http/ApiError.scala +++ b/node/src/main/scala/com/wavesplatform/api/http/ApiError.scala @@ -448,4 +448,10 @@ object ApiError { override val message = "Asset ID was not specified" override val code = StatusCodes.BadRequest } + + case object ConflictingRequestStructure extends ApiError { + override val id = 198 + override val message = "Conflicting request structure. Both expression and invocation structure were sent" + override val code = StatusCodes.BadRequest + } } diff --git a/node/src/main/scala/com/wavesplatform/api/http/JsonFormats.scala b/node/src/main/scala/com/wavesplatform/api/http/JsonFormats.scala index 52a10481162..5bea5fc759d 100644 --- a/node/src/main/scala/com/wavesplatform/api/http/JsonFormats.scala +++ b/node/src/main/scala/com/wavesplatform/api/http/JsonFormats.scala @@ -18,7 +18,6 @@ trait JsonFormats { (o: FunctionSignatures) => Json.obj( "version" -> o.version.toString, - "isArrayArguments" -> true, "callableFuncTypes" -> Json.obj( o.argsWithFuncName.map { case (functionName, args) => @@ -31,7 +30,7 @@ trait JsonFormats { ) } functionName -> functionArgs - }* + }.toSeq* ) ) diff --git a/node/src/main/scala/com/wavesplatform/api/http/UtilsApiRoute.scala b/node/src/main/scala/com/wavesplatform/api/http/utils/UtilsApiRoute.scala similarity index 54% rename from node/src/main/scala/com/wavesplatform/api/http/UtilsApiRoute.scala rename to node/src/main/scala/com/wavesplatform/api/http/utils/UtilsApiRoute.scala index e7bb77edc18..1252bfe4872 100644 --- a/node/src/main/scala/com/wavesplatform/api/http/UtilsApiRoute.scala +++ b/node/src/main/scala/com/wavesplatform/api/http/utils/UtilsApiRoute.scala @@ -1,52 +1,34 @@ -package com.wavesplatform.api.http - -import java.security.SecureRandom - +package com.wavesplatform.api.http.utils import akka.http.scaladsl.server.{PathMatcher1, Route} import cats.syntax.either.* -import cats.syntax.semigroup.* -import com.wavesplatform.account.{Address, AddressOrAlias, AddressScheme, PublicKey} -import com.wavesplatform.api.http.ApiError.{CustomValidationError, ScriptCompilerError, TooBigArrayAllocation} +import com.wavesplatform.account.Address +import com.wavesplatform.api.http.* +import com.wavesplatform.api.http.ApiError.{ConflictingRequestStructure, CustomValidationError, ScriptCompilerError, TooBigArrayAllocation, WrongJson} import com.wavesplatform.api.http.requests.{ScriptWithImportsRequest, byteStrFormat} import com.wavesplatform.common.state.ByteStr import com.wavesplatform.common.utils.* import com.wavesplatform.crypto import com.wavesplatform.features.BlockchainFeatures import com.wavesplatform.features.BlockchainFeatures.{RideV6, SynchronousCalls} -import com.wavesplatform.features.EstimatorProvider.* -import com.wavesplatform.features.EvaluatorFixProvider.* -import com.wavesplatform.lang.contract.DApp -import com.wavesplatform.lang.directives.DirectiveSet -import com.wavesplatform.lang.directives.values.{DApp as DAppType, *} +import com.wavesplatform.lang.directives.values.* import com.wavesplatform.lang.script.Script import com.wavesplatform.lang.script.Script.ComplexityInfo -import com.wavesplatform.lang.v1.compiler.Terms.{EVALUATED, EXPR} -import com.wavesplatform.lang.v1.compiler.{ContractScriptCompactor, ExpressionCompiler, Terms} +import com.wavesplatform.lang.v1.ContractLimits import com.wavesplatform.lang.v1.estimator.ScriptEstimator -import com.wavesplatform.lang.v1.evaluator.ContractEvaluator.LogExtraInfo -import com.wavesplatform.lang.v1.evaluator.ctx.impl.waves.WavesContext -import com.wavesplatform.lang.v1.evaluator.ctx.impl.{CryptoContext, PureContext} -import com.wavesplatform.lang.v1.evaluator.{ContractEvaluator, EvaluatorV2} import com.wavesplatform.lang.v1.serialization.SerdeV1 -import com.wavesplatform.lang.v1.traits.Environment -import com.wavesplatform.lang.v1.traits.domain.Recipient -import com.wavesplatform.lang.v1.{ContractLimits, FunctionHeader} -import com.wavesplatform.lang.{API, CommonError, CompileResult, Global, ValidationError} +import com.wavesplatform.lang.{API, CompileResult} import com.wavesplatform.serialization.ScriptValuesJson import com.wavesplatform.settings.RestAPISettings +import com.wavesplatform.state.Blockchain import com.wavesplatform.state.diffs.FeeValidation -import com.wavesplatform.state.diffs.invoke.InvokeScriptTransactionLike -import com.wavesplatform.state.{Blockchain, Diff} -import com.wavesplatform.transaction.TransactionType.TransactionType -import com.wavesplatform.transaction.TxValidationError.{FailedTransactionError, GenericError, InvokeRejectError} +import com.wavesplatform.transaction.TxValidationError.{GenericError, InvokeRejectError} import com.wavesplatform.transaction.smart.script.ScriptCompiler -import com.wavesplatform.transaction.smart.{BlockchainContext, DAppEnvironment, InvokeScriptTransaction} -import com.wavesplatform.transaction.{Asset, TransactionType} +import com.wavesplatform.transaction.smart.script.trace.TraceStep import com.wavesplatform.utils.Time -import monix.eval.Coeval import monix.execution.Scheduler import play.api.libs.json.* -import shapeless.Coproduct + +import java.security.SecureRandom case class UtilsApiRoute( timeService: Time, @@ -272,154 +254,63 @@ case class UtilsApiRoute( }) def evaluate: Route = - (path("script" / "evaluate" / ScriptedAddress) & jsonPostD[JsObject]) { (address, obj) => + (path("script" / "evaluate" / ScriptedAddress) & jsonPostD[JsObject] & parameter("trace".as[Boolean] ? false)) { (address, request, trace) => val scriptInfo = blockchain.accountScript(address).get val pk = scriptInfo.publicKey val script = scriptInfo.script - def parseCall(js: JsReadable) = { - val binaryCall = js - .asOpt[ByteStr] - .toRight(GenericError("Unable to parse expr bytes")) - .flatMap(ScriptCallEvaluator.parseBinaryCall) - - val textCall = js - .asOpt[String] - .toRight(GenericError("Unable to read expr string")) - .flatMap(ScriptCallEvaluator.compile(script.stdLibVersion)) - - binaryCall.orElse(textCall) + val simpleExpr = request.value.get("expr").map(parseCall(_, script.stdLibVersion)) + val exprFromInvocation = + request + .asOpt[UtilsInvocationRequest] + .map(_.toInvocation.flatMap(UtilsEvaluator.toExpr(script, _))) + + val exprE = (simpleExpr, exprFromInvocation) match { + case (Some(_), Some(_)) if request.fields.size > 1 => Left(ConflictingRequestStructure.json) + case (None, None) => Left(WrongJson().json) + case (Some(expr), _) => Right(expr) + case (None, Some(expr)) => Right(expr) } - val result = - for { - expr <- parseCall(obj \ "expr") - (result, complexity) <- ScriptCallEvaluator.executeExpression(blockchain, script, address, pk, settings.evaluateScriptComplexityLimit)(expr) - } yield Json.obj("result" -> ScriptValuesJson.serializeValue(result), "complexity" -> complexity) - - val requestData = obj ++ Json.obj("address" -> address.toString) - val responseJson = result - .recover { + val apiResult = exprE.flatMap { exprE => + val evaluated = for { + expr <- exprE + limit = settings.evaluateScriptComplexityLimit + (result, complexity, log) <- UtilsEvaluator.executeExpression(blockchain, script, address, pk, limit)(expr) + } yield Json.obj( + "result" -> ScriptValuesJson.serializeValue(result), + "complexity" -> complexity + ) ++ (if (trace) Json.obj(TraceStep.logJson(log)) else Json.obj()) + evaluated.leftMap { case e: InvokeRejectError => Json.obj("error" -> ApiError.ScriptExecutionError.Id, "message" -> e.toStringWithLog(maxTxErrorLogSize)) - case other => ApiError.fromValidationError(other).json + case e => ApiError.fromValidationError(e).json } - .explicitGet() ++ requestData + }.merge - complete(responseJson) + complete(apiResult ++ request ++ Json.obj("address" -> address.toString)) } + private def parseCall(js: JsReadable, version: StdLibVersion) = { + val binaryCall = js + .asOpt[ByteStr] + .toRight(GenericError("Unable to parse expr bytes")) + .flatMap(bytes => SerdeV1.deserialize(bytes.arr).bimap(GenericError(_), _._1)) + + val textCall = js + .asOpt[String] + .toRight(GenericError("Unable to read expr string")) + .flatMap(UtilsEvaluator.compile(version)) + + binaryCall.orElse(textCall) + } + private[this] val ScriptedAddress: PathMatcher1[Address] = AddrSegment.map { case address: Address if blockchain.hasAccountScript(address) => address - case other => - throw ApiException(CustomValidationError(s"Address $other is not dApp")) + case other => throw ApiException(CustomValidationError(s"Address $other is not dApp")) } } object UtilsApiRoute { val MaxSeedSize = 1024 val DefaultSeedSize = 32 - - private object ScriptCallEvaluator { - def compile(stdLibVersion: StdLibVersion)(str: String): Either[GenericError, EXPR] = { - val ctx = - PureContext.build(stdLibVersion, useNewPowPrecision = true).withEnvironment[Environment] |+| - CryptoContext.build(Global, stdLibVersion).withEnvironment[Environment] |+| - WavesContext.build(Global, DirectiveSet(stdLibVersion, Account, Expression).explicitGet()) - - ExpressionCompiler - .compileUntyped(str, ctx.compilerContext.copy(arbitraryDeclarations = true)) - .leftMap(GenericError(_)) - } - - def parseBinaryCall(bs: ByteStr): Either[ValidationError, EXPR] = { - SerdeV1 - .deserialize(bs.arr) - .left - .map(GenericError(_)) - .map(_._1) - } - - def executeExpression(blockchain: Blockchain, script: Script, address: Address, pk: PublicKey, limit: Int)( - expr: EXPR - ): Either[ValidationError, (EVALUATED, Int)] = { - for { - ds <- DirectiveSet(script.stdLibVersion, Account, DAppType).leftMap(GenericError(_)) - ctx = BlockchainContext - .build( - ds, - new DAppEnvironment( - AddressScheme.current.chainId, - Coeval.raiseError(new IllegalStateException("No input entity available")), - Coeval.evalOnce(blockchain.height), - blockchain, - Coproduct[Environment.Tthis](Recipient.Address(ByteStr(address.bytes))), - ds, - new InvokeScriptTransactionLike { - override def dApp: AddressOrAlias = address - - override def funcCall: Terms.FUNCTION_CALL = Terms.FUNCTION_CALL(FunctionHeader.User(""), Nil) - - override def payments: Seq[InvokeScriptTransaction.Payment] = Seq.empty - - override def root: InvokeScriptTransactionLike = this - - override val sender: PublicKey = PublicKey(ByteStr(new Array[Byte](32))) - - override def assetFee: (Asset, Long) = Asset.Waves -> 0L - - override def timestamp: Long = System.currentTimeMillis() - - override def chainId: Byte = AddressScheme.current.chainId - - override def id: Coeval[ByteStr] = Coeval.evalOnce(ByteStr.empty) - - override def checkedAssets: Seq[Asset.IssuedAsset] = Seq.empty - - override val tpe: TransactionType = TransactionType.InvokeScript - }, - address, - pk, - Set.empty[Address], - limitedExecution = false, - limit, - remainingCalls = ContractLimits.MaxSyncDAppCalls(script.stdLibVersion), - availableActions = ContractLimits.MaxCallableActionsAmountBeforeV6(script.stdLibVersion), - availableBalanceActions = ContractLimits.MaxBalanceScriptActionsAmountV6, - availableAssetActions = ContractLimits.MaxAssetScriptActionsAmountV6, - availablePayments = ContractLimits.MaxTotalPaymentAmountRideV6, - availableData = ContractLimits.MaxWriteSetSize, - availableDataSize = ContractLimits.MaxTotalWriteSetSizeInBytes, - currentDiff = Diff.empty, - invocationRoot = DAppEnvironment.InvocationTreeTracker(DAppEnvironment.DAppInvocation(address, null, Nil)) - ), - fixUnicodeFunctions = true, - useNewPowPrecision = true - ) - call = ContractEvaluator.buildSyntheticCall(ContractScriptCompactor.decompact(script.expr.asInstanceOf[DApp]), expr) - limitedResult <- EvaluatorV2 - .applyLimitedCoeval( - call, - LogExtraInfo(), - limit, - ctx, - script.stdLibVersion, - correctFunctionCallScope = blockchain.checkEstimatorSumOverflow, - newMode = blockchain.newEvaluatorMode, - checkConstructorArgsTypes = true - ) - .value() - .leftMap { case (err, _, log) => - val msg = err match { - case CommonError(_, Some(fte: FailedTransactionError)) => fte.error.getOrElse(err.message) - case _ => err.message - } - InvokeRejectError(msg, log) - } - result <- limitedResult match { - case (eval: EVALUATED, unusedComplexity, _) => Right((eval, limit - unusedComplexity)) - case (_: EXPR, _, log) => Left(InvokeRejectError(s"Calculation complexity limit exceeded", log)) - } - } yield result - } - } } diff --git a/node/src/main/scala/com/wavesplatform/api/http/utils/UtilsEvaluator.scala b/node/src/main/scala/com/wavesplatform/api/http/utils/UtilsEvaluator.scala new file mode 100644 index 00000000000..a429b6e2c16 --- /dev/null +++ b/node/src/main/scala/com/wavesplatform/api/http/utils/UtilsEvaluator.scala @@ -0,0 +1,107 @@ +package com.wavesplatform.api.http.utils + +import cats.Id +import cats.syntax.either.* +import com.wavesplatform.account.{Address, AddressOrAlias, AddressScheme, PublicKey} +import com.wavesplatform.common.state.ByteStr +import com.wavesplatform.features.EstimatorProvider.* +import com.wavesplatform.features.EvaluatorFixProvider.* +import com.wavesplatform.lang.contract.DApp +import com.wavesplatform.lang.directives.DirectiveSet +import com.wavesplatform.lang.directives.values.{DApp as DAppType, *} +import com.wavesplatform.lang.script.Script +import com.wavesplatform.lang.v1.compiler.Terms.{EVALUATED, EXPR} +import com.wavesplatform.lang.v1.compiler.{ContractScriptCompactor, ExpressionCompiler, Terms} +import com.wavesplatform.lang.v1.evaluator.ContractEvaluator.{Invocation, LogExtraInfo} +import com.wavesplatform.lang.v1.evaluator.{ContractEvaluator, EvaluatorV2, Log} +import com.wavesplatform.lang.v1.traits.Environment.Tthis +import com.wavesplatform.lang.v1.traits.domain.Recipient +import com.wavesplatform.lang.v1.{ContractLimits, FunctionHeader} +import com.wavesplatform.lang.{ValidationError, utils} +import com.wavesplatform.state.diffs.invoke.InvokeScriptTransactionLike +import com.wavesplatform.state.{Blockchain, Diff} +import com.wavesplatform.transaction.Asset.IssuedAsset +import com.wavesplatform.transaction.TransactionType.TransactionType +import com.wavesplatform.transaction.TxValidationError.{GenericError, InvokeRejectError} +import com.wavesplatform.transaction.smart.* +import com.wavesplatform.transaction.smart.InvokeScriptTransaction.Payment +import com.wavesplatform.transaction.{Asset, TransactionType} +import monix.eval.Coeval +import shapeless.Coproduct + +object UtilsEvaluator { + def compile(version: StdLibVersion)(str: String): Either[GenericError, EXPR] = + ExpressionCompiler + .compileUntyped(str, utils.compilerContext(version, Expression, isAssetScript = false).copy(arbitraryDeclarations = true)) + .leftMap(GenericError(_)) + + def toExpr(script: Script, invocation: Invocation): Either[ValidationError, EXPR] = + ContractEvaluator + .buildExprFromInvocation(script.expr.asInstanceOf[DApp], invocation, script.stdLibVersion) + .bimap(e => GenericError(e.message), _.expr) + + def executeExpression(blockchain: Blockchain, script: Script, address: Address, pk: PublicKey, limit: Int)( + expr: EXPR + ): Either[ValidationError, (EVALUATED, Int, Log[Id])] = + for { + ds <- DirectiveSet(script.stdLibVersion, Account, DAppType).leftMap(GenericError(_)) + invoke = new InvokeScriptTransactionLike { + override def dApp: AddressOrAlias = address + override def funcCall: Terms.FUNCTION_CALL = Terms.FUNCTION_CALL(FunctionHeader.User(""), Nil) + // Payments, that are mapped to RIDE structure, is taken from Invocation, + // while this field used for validation inside InvokeScriptTransactionDiff, + // that unused in the current implementation. + override def payments: Seq[Payment] = Seq.empty + override def root: InvokeScriptTransactionLike = this + override val sender: PublicKey = PublicKey(ByteStr(new Array[Byte](32))) + override def assetFee: (Asset, Long) = Asset.Waves -> 0L + override def timestamp: Long = System.currentTimeMillis() + override def chainId: Byte = AddressScheme.current.chainId + override def id: Coeval[ByteStr] = Coeval.evalOnce(ByteStr.empty) + override def checkedAssets: Seq[IssuedAsset] = Seq.empty + override val tpe: TransactionType = TransactionType.InvokeScript + } + environment = new DAppEnvironment( + AddressScheme.current.chainId, + Coeval.raiseError(new IllegalStateException("No input entity available")), + Coeval.evalOnce(blockchain.height), + blockchain, + Coproduct[Tthis](Recipient.Address(ByteStr(address.bytes))), + ds, + invoke, + address, + pk, + Set.empty[Address], + limitedExecution = false, + limit, + remainingCalls = ContractLimits.MaxSyncDAppCalls(script.stdLibVersion), + availableActions = ContractLimits.MaxCallableActionsAmountBeforeV6(script.stdLibVersion), + availableBalanceActions = ContractLimits.MaxBalanceScriptActionsAmountV6, + availableAssetActions = ContractLimits.MaxAssetScriptActionsAmountV6, + availablePayments = ContractLimits.MaxTotalPaymentAmountRideV6, + availableData = ContractLimits.MaxWriteSetSize, + availableDataSize = ContractLimits.MaxTotalWriteSetSizeInBytes, + currentDiff = Diff.empty, + invocationRoot = DAppEnvironment.InvocationTreeTracker(DAppEnvironment.DAppInvocation(address, null, Nil)) + ) + ctx = BlockchainContext.build(ds, environment, fixUnicodeFunctions = true, useNewPowPrecision = true) + call = ContractEvaluator.buildSyntheticCall(ContractScriptCompactor.decompact(script.expr.asInstanceOf[DApp]), expr) + limitedResult <- EvaluatorV2 + .applyLimitedCoeval( + call, + LogExtraInfo(), + limit, + ctx, + script.stdLibVersion, + correctFunctionCallScope = blockchain.checkEstimatorSumOverflow, + newMode = blockchain.newEvaluatorMode, + checkConstructorArgsTypes = true + ) + .value() + .leftMap { case (err, _, log) => InvokeRejectError(err.message, log) } + result <- limitedResult match { + case (eval: EVALUATED, unusedComplexity, log) => Right((eval, limit - unusedComplexity, log)) + case (_: EXPR, _, log) => Left(InvokeRejectError(s"Calculation complexity limit exceeded", log)) + } + } yield result +} diff --git a/node/src/main/scala/com/wavesplatform/api/http/utils/UtilsInvocationRequest.scala b/node/src/main/scala/com/wavesplatform/api/http/utils/UtilsInvocationRequest.scala new file mode 100644 index 00000000000..5bc0d917349 --- /dev/null +++ b/node/src/main/scala/com/wavesplatform/api/http/utils/UtilsInvocationRequest.scala @@ -0,0 +1,55 @@ +package com.wavesplatform.api.http.utils + +import cats.implicits.{toBifunctorOps, toTraverseOps} +import com.wavesplatform.account.{Address, PublicKey} +import com.wavesplatform.api.http.requests.InvokeScriptRequest +import com.wavesplatform.api.http.requests.InvokeScriptRequest.FunctionCallPart +import com.wavesplatform.api.http.utils.UtilsInvocationRequest.empty32Bytes +import com.wavesplatform.common.state.ByteStr +import com.wavesplatform.lang.ValidationError +import com.wavesplatform.lang.directives.values.V6 +import com.wavesplatform.lang.v1.evaluator.ContractEvaluator.Invocation +import com.wavesplatform.lang.v1.traits.domain.Recipient.Address as RideAddress +import com.wavesplatform.state.diffs.FeeValidation.{FeeConstants, FeeUnit} +import com.wavesplatform.transaction.TxValidationError.GenericError +import com.wavesplatform.transaction.smart.AttachedPaymentExtractor +import com.wavesplatform.transaction.smart.InvokeScriptTransaction.Payment +import com.wavesplatform.transaction.{TransactionType, smart} +import play.api.libs.json.{Json, Reads} + +case class UtilsInvocationRequest( + call: FunctionCallPart = FunctionCallPart("default", Nil), + id: String = ByteStr(empty32Bytes).toString, + fee: Long = FeeConstants(TransactionType.InvokeScript) * FeeUnit, + feeAssetId: Option[String] = None, + sender: Option[String] = None, + senderPublicKey: String = ByteStr(empty32Bytes).toString, + payment: Seq[Payment] = Nil +) { + def toInvocation: Either[ValidationError, Invocation] = + for { + senderPK <- PublicKey.fromBase58String(senderPublicKey) + id <- decodeBase58(id) + functionCall = InvokeScriptRequest.buildFunctionCall(call) + feeAssetId <- feeAssetId.traverse(decodeBase58) + sender <- + if (sender.nonEmpty || senderPK.arr.sameElements(empty32Bytes)) + sender + .map(Address.fromString(_, None).map(a => RideAddress(ByteStr(a.bytes)))) + .getOrElse(Right(RideAddress(ByteStr(new Array[Byte](26))))) + else + Right(RideAddress(ByteStr(senderPK.toAddress.bytes))) + payments <- AttachedPaymentExtractor + .extractPayments(payment, V6, blockchainAllowsMultiPayment = true, smart.DApp) + .leftMap(GenericError(_)) + } yield Invocation(functionCall, sender, senderPK, sender, senderPK, payments, id, fee, feeAssetId) + + private def decodeBase58(base58: String): Either[ValidationError, ByteStr] = + ByteStr.decodeBase58(base58).toEither.leftMap(e => GenericError(String.valueOf(e.getMessage))) +} + +object UtilsInvocationRequest { + private val empty32Bytes = new Array[Byte](32) + + implicit val reads: Reads[UtilsInvocationRequest] = Json.using[Json.WithDefaultValues].reads[UtilsInvocationRequest] +} diff --git a/node/src/main/scala/com/wavesplatform/state/BlockchainUpdaterImpl.scala b/node/src/main/scala/com/wavesplatform/state/BlockchainUpdaterImpl.scala index fda70ed343c..7a7b95fd36a 100644 --- a/node/src/main/scala/com/wavesplatform/state/BlockchainUpdaterImpl.scala +++ b/node/src/main/scala/com/wavesplatform/state/BlockchainUpdaterImpl.scala @@ -333,11 +333,11 @@ class BlockchainUpdaterImpl( block, hitSource, differResult.carry, - reward + None ) miner.scheduleMining(Some(tempBlockchain)) - blockchainUpdateTriggers.onProcessBlock(block, differResult.detailedDiff, reward, referencedBlockchain) + blockchainUpdateTriggers.onProcessBlock(block, differResult.detailedDiff, reward, this) leveldb.append(liquidDiffWithCancelledLeases, carry, totalFee, prevReward, prevHitSource, referencedForgedBlock) BlockStats.appended(referencedForgedBlock, referencedLiquidDiff.scriptsComplexity) diff --git a/node/src/main/scala/com/wavesplatform/transaction/ABIConverter.scala b/node/src/main/scala/com/wavesplatform/transaction/ABIConverter.scala index 25fe7e6e7c6..a7e18ffdf92 100644 --- a/node/src/main/scala/com/wavesplatform/transaction/ABIConverter.scala +++ b/node/src/main/scala/com/wavesplatform/transaction/ABIConverter.scala @@ -179,7 +179,7 @@ final case class ABIConverter(script: Script) { private[this] def functionsWithArgs: Seq[(String, List[(String, Types.FINAL)])] = { funcsWithTypes match { - case Right(signatures) => signatures.argsWithFuncName + case Right(signatures) => signatures.argsWithFuncName.toSeq case Left(_) => Nil } } diff --git a/node/src/main/scala/com/wavesplatform/transaction/smart/AttachedPaymentExtractor.scala b/node/src/main/scala/com/wavesplatform/transaction/smart/AttachedPaymentExtractor.scala index 57a7a7b63aa..097dc956516 100644 --- a/node/src/main/scala/com/wavesplatform/transaction/smart/AttachedPaymentExtractor.scala +++ b/node/src/main/scala/com/wavesplatform/transaction/smart/AttachedPaymentExtractor.scala @@ -1,12 +1,13 @@ package com.wavesplatform.transaction.smart import com.wavesplatform.common.state.ByteStr -import com.wavesplatform.state.diffs.invoke.InvokeScriptLike -import com.wavesplatform.features.MultiPaymentPolicyProvider._ +import com.wavesplatform.features.MultiPaymentPolicyProvider.* import com.wavesplatform.lang.directives.values.StdLibVersion import com.wavesplatform.lang.v1.traits.domain.AttachedPayments -import com.wavesplatform.lang.v1.traits.domain.AttachedPayments._ +import com.wavesplatform.lang.v1.traits.domain.AttachedPayments.* import com.wavesplatform.state.Blockchain +import com.wavesplatform.state.diffs.invoke.InvokeScriptLike +import com.wavesplatform.transaction.smart.InvokeScriptTransaction.Payment object AttachedPaymentExtractor { def extractPayments( @@ -15,25 +16,33 @@ object AttachedPaymentExtractor { blockchain: Blockchain, targetScript: AttachedPaymentTarget ): Either[String, AttachedPayments] = - if (tx.payments.size <= 1) + extractPayments(tx.payments, version, blockchain.allowsMultiPayment, targetScript) + + def extractPayments( + payments: Seq[Payment], + version: StdLibVersion, + blockchainAllowsMultiPayment: Boolean, + targetScript: AttachedPaymentTarget + ): Either[String, AttachedPayments] = + if (payments.size <= 1) if (version.supportsMultiPayment) - multiple(tx) + multiple(payments) else - single(tx) - else if (!blockchain.allowsMultiPayment) + single(payments) + else if (!blockchainAllowsMultiPayment) Left("Multiple payments isn't allowed now") else if (!version.supportsMultiPayment) Left(scriptErrorMessage(targetScript, version)) - else if (tx.payments.size > version.maxPayments) - Left(s"Script payment amount=${tx.payments.size} should not exceed ${version.maxPayments}") + else if (payments.size > version.maxPayments) + Left(s"Script payment amount=${payments.size} should not exceed ${version.maxPayments}") else - multiple(tx) + multiple(payments) - private def single(tx: InvokeScriptLike) = - Right(AttachedPayments.Single(tx.payments.headOption.map(p => (p.amount, p.assetId.compatId)))) + private def single(payments: Seq[Payment]) = + Right(AttachedPayments.Single(payments.headOption.map(p => (p.amount, p.assetId.compatId)))) - private def multiple(tx: InvokeScriptLike) = - Right(AttachedPayments.Multi(tx.payments.map(p => (p.amount, p.assetId.compatId)))) + private def multiple(payments: Seq[Payment]) = + Right(AttachedPayments.Multi(payments.map(p => (p.amount, p.assetId.compatId)))) private def scriptErrorMessage(apt: AttachedPaymentTarget, version: StdLibVersion): String = { val name = (apt: @unchecked) match { diff --git a/node/src/main/scala/com/wavesplatform/transaction/smart/script/ScriptCompiler.scala b/node/src/main/scala/com/wavesplatform/transaction/smart/script/ScriptCompiler.scala index f0f6770f698..a225952a449 100644 --- a/node/src/main/scala/com/wavesplatform/transaction/smart/script/ScriptCompiler.scala +++ b/node/src/main/scala/com/wavesplatform/transaction/smart/script/ScriptCompiler.scala @@ -29,6 +29,6 @@ object ScriptCompiler { API.compile(scriptText, estimator, libraries = libraries, defaultStdLib = defaultStdLib).map { case CompileResult.Expression(v, _, complexity, expr, _, isFreeCall) => (ExprScriptImpl(v, isFreeCall, expr), complexity) case CompileResult.Library(v, _, complexity, expr) => (ExprScriptImpl(v, isFreeCall = false, expr), complexity) - case CompileResult.DApp(v, r, _) => (ContractScriptImpl(v, r.dApp), r.verifierComplexity) + case CompileResult.DApp(v, r, _, _) => (ContractScriptImpl(v, r.dApp), r.verifierComplexity) } } diff --git a/node/src/main/scala/com/wavesplatform/transaction/smart/script/trace/TraceStep.scala b/node/src/main/scala/com/wavesplatform/transaction/smart/script/trace/TraceStep.scala index a5b0c02d12a..9a06dbe6749 100644 --- a/node/src/main/scala/com/wavesplatform/transaction/smart/script/trace/TraceStep.scala +++ b/node/src/main/scala/com/wavesplatform/transaction/smart/script/trace/TraceStep.scala @@ -116,7 +116,7 @@ object TraceStep { case a => Json.obj("error" -> a.toString) } - private[trace] def logJson(l: Log[Id]): (String, JsValueWrapper) = + def logJson(l: Log[Id]): (String, JsValueWrapper) = "vars" -> l.collect { case (k, Right(v)) if !LogKeys.TraceExcluded.exists(k.contains) => Json.obj("name" -> k) ++ ScriptValuesJson.serializeValue(v) case (k, Left(CommonError(_, Some(fte: FailedTransactionError)))) => Json.obj("name" -> k, "error" -> fte.error) diff --git a/node/src/test/scala/com/wavesplatform/features/RideV6ActivationTest.scala b/node/src/test/scala/com/wavesplatform/features/RideV6FailRejectTest.scala similarity index 78% rename from node/src/test/scala/com/wavesplatform/features/RideV6ActivationTest.scala rename to node/src/test/scala/com/wavesplatform/features/RideV6FailRejectTest.scala index b95b9e1a2af..87331b6da75 100644 --- a/node/src/test/scala/com/wavesplatform/features/RideV6ActivationTest.scala +++ b/node/src/test/scala/com/wavesplatform/features/RideV6FailRejectTest.scala @@ -15,12 +15,15 @@ import com.wavesplatform.test.* import com.wavesplatform.transaction.Asset.{IssuedAsset, Waves} import com.wavesplatform.transaction.assets.IssueTransaction import com.wavesplatform.transaction.smart.InvokeScriptTransaction -import com.wavesplatform.transaction.{Transaction, TxHelpers} +import com.wavesplatform.transaction.smart.InvokeScriptTransaction.Payment +import com.wavesplatform.transaction.utils.EthConverters.* +import com.wavesplatform.transaction.utils.EthTxGenerator +import com.wavesplatform.transaction.{EthereumTransaction, Transaction, TxHelpers, TxVersion} import org.scalatest.{EitherValues, OptionValues} import java.nio.charset.StandardCharsets -class RideV6ActivationTest extends FreeSpec with WithDomain with OptionValues with EitherValues { +class RideV6FailRejectTest extends FreeSpec with WithDomain with OptionValues with EitherValues { private val otherChainId = (AddressScheme.current.chainId + 1).toByte private val invalidAssetId = IssuedAsset(ByteStr(("1" * 5).getBytes(StandardCharsets.UTF_8))) @@ -32,6 +35,12 @@ class RideV6ActivationTest extends FreeSpec with WithDomain with OptionValues wi private val bobAddr = bob.toAddress private val bobOtherChainAddr = bob.toAddress(otherChainId) + private val invoker = TxHelpers.signer(3) + private val invokerAddr = invoker.toAddress + + private val ethInvoker = invoker.toEthKeyPair + private val ethInvokerAddr = ethInvoker.toWavesAddress + private val aliceRegularAssetTx = TxHelpers.issue(issuer = alice, amount = Long.MaxValue - 1) private val aliceRegularAssetId = aliceRegularAssetTx.id() private val aliceNotReIssuableAssetTx = TxHelpers.issue(issuer = alice, amount = 100, reissuable = false) @@ -39,10 +48,18 @@ class RideV6ActivationTest extends FreeSpec with WithDomain with OptionValues wi private val aliceLeasingId = aliceLeasingTx.id() private val aliceInvokeTx = TxHelpers.invoke( dApp = aliceAddr, - invoker = alice, + invoker = invoker, func = Some("foo"), fee = 3.waves ) + private val ethAliceInvokeTx = EthTxGenerator.generateEthInvoke( + keyPair = ethInvoker, + address = aliceAddr, + funcName = "foo", + args = Seq.empty, + payments = Seq.empty, + fee = 3.waves + ) private val bobAssetTx = TxHelpers.issue(issuer = bob) private val bobAssetId = bobAssetTx.id() @@ -50,8 +67,10 @@ class RideV6ActivationTest extends FreeSpec with WithDomain with OptionValues wi private val bobLeasingId = bobLeasingTx.id() private val defaultInitWavesBalances = Map( - aliceAddr -> ENOUGH_AMT, - bobAddr -> ENOUGH_AMT + aliceAddr -> ENOUGH_AMT, + bobAddr -> ENOUGH_AMT, + invokerAddr -> ENOUGH_AMT, + ethInvokerAddr -> ENOUGH_AMT ) private val defaultScript = mkScriptWithOneAction("""IntegerEntry("i", 1)""") @@ -60,6 +79,98 @@ class RideV6ActivationTest extends FreeSpec with WithDomain with OptionValues wi "After RideV6 activation" - { val cases = Seq( + { + val aliceSmartAssetTx = TxHelpers.issue( + issuer = alice, + script = Some( + mkAssetScript( + """ + | match tx { + | case _: ReissueTransaction => false + | case _ => true + | } + |""".stripMargin + ) + ) + ) + Case( + "NODE-124 Asset script can forbid reissue", + "Transaction is not allowed by script of the asset", + { targetComplexity => + val baseComplexity = 1 + 1 + 1 + 1 // 1 for strict, 1 for list, 1 for Reissue, 1 for asset script + mkFooScript( + s""" + |strict complexInt = ${mkIntExprWithComplexity(targetComplexity - baseComplexity)} + |[Reissue(base58'${aliceSmartAssetTx.id()}', 10, true)] + |""".stripMargin + ) + }, + knownTxs = Seq(aliceSmartAssetTx) + ) + }, { + val aliceSmartAssetTx = TxHelpers.issue( + issuer = alice, + script = Some( + mkAssetScript( + """ + | match tx { + | case _: BurnTransaction => false + | case _ => true + | } + |""".stripMargin + ) + ) + ) + Case( + "NODE-125 Asset script can forbid burn", + "Transaction is not allowed by script of the asset", + { targetComplexity => + val baseComplexity = 1 + 1 + 1 + 1 // 1 for strict, 1 for list, 1 for Burn, 1 for asset script + mkFooScript( + s""" + |strict complexInt = ${mkIntExprWithComplexity(targetComplexity - baseComplexity)} + |[Burn(base58'${aliceSmartAssetTx.id()}', 10)] + |""".stripMargin + ) + }, + knownTxs = Seq(aliceSmartAssetTx) + ) + }, { + val bobSmartAssetTx = TxHelpers.issue( + issuer = bob, + script = Some( + mkAssetScript( + s""" + |match tx { + | case tr: InvokeScriptTransaction => throw() + | case _ => true + |} + |""".stripMargin + ) + ) + ) + Case( + "NODE-522 DApp completes successfully, but asset script fails for payments", + s"Transaction is not allowed by script of the asset ${bobSmartAssetTx.id()}", + { targetComplexity => + val baseComplexity = 1 + 2 // 1 for strict, 2 for asset script + mkFooScript( + s""" + |strict complexInt = ${mkIntExprWithComplexity(targetComplexity - baseComplexity)} + |[] + |""".stripMargin + ) + }, + invokeTx = TxHelpers + .invoke(dApp = aliceAddr, invoker = invoker, func = Some("foo"), fee = 3.waves, payments = Seq(Payment(1, bobSmartAssetTx.asset))), + ethInvokeTx = Some(EthTxGenerator.generateEthInvoke(ethInvoker, aliceAddr, "foo", Seq.empty, Seq(Payment(1, bobSmartAssetTx.asset)))), + knownTxs = Seq( + bobSmartAssetTx, + TxHelpers.transfer(bob, invokerAddr, 1, bobSmartAssetTx.asset), + TxHelpers.transfer(bob, ethInvokerAddr, 1, bobSmartAssetTx.asset) + ) + ) + }, Case( "NODE-540 If an invoke exceeds the limit of writes", "Stored data count limit is exceeded", @@ -175,96 +286,116 @@ class RideV6ActivationTest extends FreeSpec with WithDomain with OptionValues wi }, supportedVersions = Seq(V3) ) - ) ++ - Seq( + ) ++ { + val dataEntries = Seq( "Binary" -> s"""BinaryEntry("", base58'${Base58.encode("test".getBytes(StandardCharsets.UTF_8))}')""", "Boolean" -> """BooleanEntry("", false)""", "Delete" -> """DeleteEntry("")""", "Integer" -> """IntegerEntry("", 0)""", "String" -> """StringEntry("", "lie")""" - ).map { case (entryType, entry) => + ) + + def mkDAppFunc(entry: String): Int => StdLibVersion => Script = { targetComplexity => + val baseComplexity = 1 + 2 + 1 + 1 // 1 for strict, 2 for list (two elements), 1 for IntegerEntry, 1 for test entry + mkFooScript( + s""" + | strict complexInt = ${mkIntExprWithComplexity(targetComplexity - baseComplexity)} + | [IntegerEntry("k", 1), $entry] + |""".stripMargin + ) + } + + dataEntries.map { case (entryType, entry) => Case( s"NODE-546 If an invoke writes an empty $entryType key to the state", - "Empty keys aren't allowed in tx version >= 2", - { targetComplexity => - val baseComplexity = 1 + 2 + 1 + 1 // 1 for strict, 2 for list (two elements), 1 for IntegerEntry, 1 for test entry - mkFooScript( - s""" - | strict complexInt = ${mkIntExprWithComplexity(targetComplexity - baseComplexity)} - | [IntegerEntry("k", 1), $entry] - |""".stripMargin - ) - } + "Data entry key should not be empty", + mkDAppFunc(entry), + invokeTx = aliceInvokeTx.copy(version = TxVersion.V1).signWith(invoker.privateKey) ) - } ++ { - def mkNode548OneActionTypeScript(actionSrc: String): Int => StdLibVersion => Script = { targetComplexity => - // 1+1 for strict, 1 for Address, 101 for list, 101 for actions - val baseComplexity = 1 + 1 + 1 + 101 + 101 - mkFooScript( - s""" strict complexInt = ${mkIntExprWithComplexity(targetComplexity - baseComplexity)} - | strict to = Address(base58'$bobAddr') - | [${(1 to 101).map(_ => actionSrc).mkString(", ")}] - | """.stripMargin + } ++ + dataEntries.map { case (entryType, entry) => + Case( + s"NODE-546 If an invoke writes an empty $entryType key to the state (invoke version >= 2)", + "Empty keys aren't allowed in tx version >= 2", + mkDAppFunc(entry), + ethInvokeTx = None ) } + } ++ { + def mkNode548OneActionTypeScript(actionSrc: String): Int => StdLibVersion => Script = { targetComplexity => + // 1+1 for strict, 1 for Address, 101 for list, 101 for actions + val baseComplexity = 1 + 1 + 1 + 101 + 101 + mkFooScript( + s""" strict complexInt = ${mkIntExprWithComplexity(targetComplexity - baseComplexity)} + | strict to = Address(base58'$bobAddr') + | [${(1 to 101).map(_ => actionSrc).mkString(", ")}] + | """.stripMargin + ) + } + Seq( + Case( + "NODE-548 If an invoke exceeds the limit of ScriptTransfer actions", + "Actions count limit is exceeded", + mkNode548OneActionTypeScript("ScriptTransfer(to, 1, unit)"), + supportedVersions = Seq(V4) + ) + ) ++ Seq( + "ScriptTransfer" -> "ScriptTransfer(to, 1, unit)", + "Lease" -> "Lease(to, 1)", + "LeaseCancel" -> s"LeaseCancel(base58'$bobLeasingId')" + ).flatMap { case (actionType, actionSrc) => Seq( Case( - "NODE-548 If an invoke exceeds the limit of ScriptTransfer actions", + s"NODE-548 If an invoke exceeds the limit of $actionType actions", "Actions count limit is exceeded", - mkNode548OneActionTypeScript("ScriptTransfer(to, 1, unit)"), - supportedVersions = Seq(V4) - ) - ) ++ Seq( - "ScriptTransfer" -> "ScriptTransfer(to, 1, unit)", - "Lease" -> "Lease(to, 1)", - "LeaseCancel" -> s"LeaseCancel(base58'$bobLeasingId')" - ).flatMap { case (actionType, actionSrc) => - Seq( - Case( - s"NODE-548 If an invoke exceeds the limit of $actionType actions", - "Actions count limit is exceeded", - mkNode548OneActionTypeScript(actionSrc), - supportedVersions = Seq(V5) - ), - Case( - s"NODE-548 If an invoke exceeds the limit of $actionType actions", - "ScriptTransfer, Lease, LeaseCancel actions count limit is exceeded", - mkNode548OneActionTypeScript(actionSrc), - supportedVersions = Seq(V6) - ) - ) - } ++ Seq( - V5 -> "Actions count limit is exceeded", - V6 -> "ScriptTransfer, Lease, LeaseCancel actions count limit is exceeded" - ).map { case (v, rejectError) => + mkNode548OneActionTypeScript(actionSrc), + supportedVersions = Seq(V5) + ), Case( - "NODE-548 If an invoke exceeds the limit of mixed non-data actions", - rejectError, - { targetComplexity => - val baseComplexity = 1 + 1 + 101 + 101 // 1 for strict, 1 for Address, 101 for list, 101 for actions - mkFooScript( - s""" strict complexInt = ${mkIntExprWithComplexity(targetComplexity - baseComplexity)} - | let to = Address(base58'$bobAddr') - | [ - | ${(1 to 34).map(_ => s"""ScriptTransfer(to, 1, unit)""").mkString(", ")}, - | ${(1 to 34).map(_ => s"""Lease(to, 1)""").mkString(", ")}, - | ${(1 to 33).map(_ => s"""LeaseCancel(base58'$bobLeasingId')""").mkString(", ")} - | ] - | """.stripMargin - ) - }, - supportedVersions = Seq(v) + s"NODE-548 If an invoke exceeds the limit of $actionType actions", + "ScriptTransfer, Lease, LeaseCancel actions count limit is exceeded", + mkNode548OneActionTypeScript(actionSrc), + supportedVersions = Seq(V6) ) - } - } ++ + ) + } ++ Seq( + V5 -> "Actions count limit is exceeded", + V6 -> "ScriptTransfer, Lease, LeaseCancel actions count limit is exceeded" + ).map { case (v, rejectError) => + Case( + "NODE-548 If an invoke exceeds the limit of mixed non-data actions", + rejectError, + { targetComplexity => + val baseComplexity = 1 + 1 + 101 + 101 // 1 for strict, 1 for Address, 101 for list, 101 for actions + mkFooScript( + s""" strict complexInt = ${mkIntExprWithComplexity(targetComplexity - baseComplexity)} + | let to = Address(base58'$bobAddr') + | [ + | ${(1 to 34).map(_ => s"""ScriptTransfer(to, 1, unit)""").mkString(", ")}, + | ${(1 to 34).map(_ => s"""Lease(to, 1)""").mkString(", ")}, + | ${(1 to 33).map(_ => s"""LeaseCancel(base58'$bobLeasingId')""").mkString(", ")} + | ] + | """.stripMargin + ) + }, + supportedVersions = Seq(v) + ) + } + } ++ Seq( Case( "NODE-550 If an invoke sends a self-payment", "DApp self-payment is forbidden since V4", - invokeTx = aliceInvokeTx // self-payment, because this is a self-invoke - .copy(payments = Seq(InvokeScriptTransaction.Payment(1, Waves))) - .signWith(alice.privateKey) + invokeTx = TxHelpers + .invoke( + dApp = aliceAddr, + invoker = alice, + func = Some("foo"), + fee = 3.waves, + payments = Seq(InvokeScriptTransaction.Payment(1, Waves)) + ), + ethInvokeTx = None ), Case( "NODE-552 If an invoke sends a ScriptTransfer for himself", @@ -298,8 +429,10 @@ class RideV6ActivationTest extends FreeSpec with WithDomain with OptionValues wi ) }, initBalances = Map( - aliceAddr -> Long.MaxValue, - bobAddr -> ENOUGH_AMT + aliceAddr -> Long.MaxValue, + bobAddr -> ENOUGH_AMT, + invokerAddr -> ENOUGH_AMT, + ethInvokerAddr -> ENOUGH_AMT ) ), Case( @@ -729,22 +862,12 @@ class RideV6ActivationTest extends FreeSpec with WithDomain with OptionValues wi testCase.title - { testCase.supportedVersions.foreach { v => s"$v" - { - "<=1000 complexity - rejected" in test(ContractLimits.FailFreeInvokeComplexity - 300) { d => - d.createDiffE(testCase.invokeTx) should produce(testCase.rejectError) - } + "<=1000 complexity - rejected" in rejectTxTest(testCase.invokeTx) + ">1000 complexity - failed" in failTxTest(testCase.invokeTx) - ">1000 complexity - failed" in { - val complexity = ContractLimits.FailFreeInvokeComplexity + 1 - test(complexity) { d => - val diff = d.createDiffE(testCase.invokeTx).value - val (_, scriptResult) = diff.scriptResults.headOption.value - scriptResult.error.value.text should include(testCase.rejectError) - - d.appendBlock(testCase.invokeTx) - val invokeTxMeta = d.transactionsApi.transactionById(testCase.invokeTx.id()).value - invokeTxMeta.spentComplexity shouldBe complexity - invokeTxMeta.succeeded shouldBe false - } + testCase.ethInvokeTx.foreach { ethInvoke => + "<=1000 complexity - rejected (Ethereum)" in rejectTxTest(ethInvoke) + ">1000 complexity - failed (Ethereum)" in failTxTest(ethInvoke) } def test(complexity: Int)(f: Domain => Unit): Unit = @@ -756,6 +879,25 @@ class RideV6ActivationTest extends FreeSpec with WithDomain with OptionValues wi d.appendBlock((testCase.knownTxs :+ setScriptTx)*) f(d) } + + def rejectTxTest(invoke: Transaction): Unit = + test(ContractLimits.FailFreeInvokeComplexity - 300) { d => + d.createDiffE(invoke) should produce(testCase.rejectError) + } + + def failTxTest(invoke: Transaction): Unit = { + val complexity = ContractLimits.FailFreeInvokeComplexity + 1 + test(complexity) { d => + val diff = d.createDiffE(invoke).value + val (_, scriptResult) = diff.scriptResults.headOption.value + scriptResult.error.value.text should include(testCase.rejectError) + + d.appendBlock(invoke) + val invokeTxMeta = d.transactionsApi.transactionById(invoke.id()).value + invokeTxMeta.spentComplexity shouldBe complexity + invokeTxMeta.succeeded shouldBe false + } + } } } } @@ -770,6 +912,7 @@ class RideV6ActivationTest extends FreeSpec with WithDomain with OptionValues wi rejectError: String, mkDApp: Int => StdLibVersion => Script = defaultScript, invokeTx: InvokeScriptTransaction = aliceInvokeTx, + ethInvokeTx: Option[EthereumTransaction] = Some(ethAliceInvokeTx), knownTxs: Seq[Transaction] = Seq.empty, initBalances: Map[Address, Long] = defaultInitWavesBalances, supportedVersions: Seq[StdLibVersion] = inV4V6 diff --git a/node/src/test/scala/com/wavesplatform/http/AddressRouteSpec.scala b/node/src/test/scala/com/wavesplatform/http/AddressRouteSpec.scala index c58b278baf4..3c51e57d1b6 100644 --- a/node/src/test/scala/com/wavesplatform/http/AddressRouteSpec.scala +++ b/node/src/test/scala/com/wavesplatform/http/AddressRouteSpec.scala @@ -287,7 +287,6 @@ class AddressRouteSpec extends RouteSpec("/addresses") with PathMockFactory with val response = responseAs[JsObject] (response \ "address").as[String] shouldBe allAddresses(3).toString (response \ "meta" \ "version").as[String] shouldBe "1" - (response \ "meta" \ "isArrayArguments").as[Boolean] shouldBe true (response \ "meta" \ "callableFuncTypes" \ "call1" \ 0 \ "name").as[String] shouldBe "a" (response \ "meta" \ "callableFuncTypes" \ "call1" \ 0 \ "type").as[String] shouldBe "Int" (response \ "meta" \ "callableFuncTypes" \ "call1" \ 1 \ "name").as[String] shouldBe "b" diff --git a/node/src/test/scala/com/wavesplatform/http/UtilsRouteSpec.scala b/node/src/test/scala/com/wavesplatform/http/UtilsRouteSpec.scala index 388c00d3d71..ac8f0776a45 100644 --- a/node/src/test/scala/com/wavesplatform/http/UtilsRouteSpec.scala +++ b/node/src/test/scala/com/wavesplatform/http/UtilsRouteSpec.scala @@ -3,9 +3,9 @@ package com.wavesplatform.http import akka.http.scaladsl.testkit.RouteTestTimeout import com.google.protobuf.ByteString import com.wavesplatform.account.Address -import com.wavesplatform.api.http.ApiError.TooBigArrayAllocation -import com.wavesplatform.api.http.UtilsApiRoute +import com.wavesplatform.api.http.ApiError.{ScriptExecutionError, TooBigArrayAllocation} import com.wavesplatform.api.http.requests.ScriptWithImportsRequest +import com.wavesplatform.api.http.utils.{UtilsApiRoute, UtilsInvocationRequest} import com.wavesplatform.common.state.ByteStr import com.wavesplatform.common.utils.{Base58, EitherExt2} import com.wavesplatform.crypto @@ -14,7 +14,7 @@ import com.wavesplatform.features.BlockchainFeatures import com.wavesplatform.history.DefaultBlockchainSettings import com.wavesplatform.lang.contract.DApp import com.wavesplatform.lang.contract.DApp.{CallableAnnotation, CallableFunction, VerifierAnnotation, VerifierFunction} -import com.wavesplatform.lang.directives.values.{V2, V3, V5, V6} +import com.wavesplatform.lang.directives.values.{V2, V3, V6} import com.wavesplatform.lang.script.v1.ExprScript import com.wavesplatform.lang.script.{ContractScript, Script} import com.wavesplatform.lang.v1.FunctionHeader @@ -29,7 +29,9 @@ import com.wavesplatform.protobuf.dapp.DAppMeta.CallableFuncSignature import com.wavesplatform.settings.TestSettings import com.wavesplatform.state.diffs.FeeValidation import com.wavesplatform.state.{Blockchain, IntegerDataEntry, LeaseBalance} +import com.wavesplatform.test.DomainPresets.{RideV5, RideV6} import com.wavesplatform.transaction.TxHelpers +import com.wavesplatform.transaction.TxHelpers.* import com.wavesplatform.transaction.smart.script.ScriptCompiler import com.wavesplatform.utils.{Schedulers, Time} import io.netty.util.HashedWheelTimer @@ -432,7 +434,6 @@ class UtilsRouteSpec extends RouteSpec("/utils") with RestAPISettingsHelper with Post(routePath("/script/meta"), dappBase64) ~> route ~> check { val json = responseAs[JsObject] json("version").as[String] shouldBe "1" - json("isArrayArguments").as[Boolean] shouldBe true json("callableFuncTypes") shouldBe JsObject( Seq( "func1" -> JsArray( @@ -462,7 +463,6 @@ class UtilsRouteSpec extends RouteSpec("/utils") with RestAPISettingsHelper with Post(routePath("/script/meta"), dAppBase64) ~> route ~> check { val json = responseAs[JsValue] json("version").as[String] shouldBe "1" - json("isArrayArguments").as[Boolean] shouldBe true json("callableFuncTypes") shouldBe JsObject( Seq( "init" -> JsArray( @@ -828,53 +828,52 @@ class UtilsRouteSpec extends RouteSpec("/utils") with RestAPISettingsHelper with } } - routePath("/script/evaluate/{address}") in withDomain(DomainPresets.RideV6) { d => - val blockchain = d.blockchain - val api = utilsApi.copy(blockchain = blockchain) - val route = seal(api.route) - + routePath("/script/evaluate/{address}") - { val letFromContract = 1000 - val testScript = TxHelpers.scriptV5(s""" - |let letFromContract = $letFromContract - | - |func test(i: Int) = i * 10 - |func testB() = true - |func testBS() = base58'MATCHER' - |func testS() = "Test" - |func testF() = throw("Test") - |func testCompl() = ${"sigVerify(base58'', base58'', base58'') ||" * 200} true - |func testThis() = this - |func testListArg(list: List[String|ByteVector|Int], str: String, bytes: ByteVector) = list.containsElement(str) - | - |func nestedCalls(x: List[(String, String, List[Any])]) = { - | func call(a: String, x: (String, String, List[Any])) = { - | let (dAppAddress, funcName, args) = x - | strict res = Address(fromBase58String(dAppAddress)).invoke(funcName, args, []) - | a + res.exactAs[String] + "\n" - | } - | FOLD<20>(x, "", call) - |} - | - |@Callable(i) - |func getValue() = ([], "value") - | - |@Callable(i) - |func testCallable() = [BinaryEntry("test", i.caller.bytes)] - | - |@Callable(i) - |func testSyncinvoke() = { - | strict r = invoke(this, "testCallable", [], []) - | [BinaryEntry("testSyncInvoke", i.caller.bytes)] - |} - | - |@Callable(i) - |func testSyncCallComplexityExcess() = { - | strict r = invoke(this, "testSyncCallComplexityExcess", [], []) - | [] - |} - | - |@Callable(i) - |func testWriteEntryType(b: ByteVector) = [ BinaryEntry("bytes", b) ] """.stripMargin) + val testScript = TxHelpers.scriptV5( + s""" + |let letFromContract = $letFromContract + | + |func test(i: Int) = i * 10 + |func testB() = true + |func testBS() = base58'MATCHER' + |func testS() = "Test" + |func testF() = throw("Test") + |func testCompl() = ${"sigVerify(base58'', base58'', base58'') ||" * 200} true + |func testThis() = this + |func testListArg(list: List[String|ByteVector|Int], str: String, bytes: ByteVector) = list.containsElement(str) + | + |func nestedCalls(x: List[(String, String, List[Any])]) = { + | func call(a: String, x: (String, String, List[Any])) = { + | let (dAppAddress, funcName, args) = x + | strict res = Address(fromBase58String(dAppAddress)).invoke(funcName, args, []) + | a + res.exactAs[String] + "\n" + | } + | FOLD<20>(x, "", call) + |} + | + |@Callable(i) + |func getValue() = ([], "value") + | + |@Callable(i) + |func testCallable() = [BinaryEntry("test", i.caller.bytes)] + | + |@Callable(i) + |func testSyncinvoke() = { + | strict r = invoke(this, "testCallable", [], []) + | [BinaryEntry("testSyncInvoke", i.caller.bytes)] + |} + | + |@Callable(i) + |func testSyncCallComplexityExcess() = { + | strict r = invoke(this, "testSyncCallComplexityExcess", [], []) + | [] + |} + | + |@Callable(i) + |func testWriteEntryType(b: ByteVector) = [ BinaryEntry("bytes", b) ] + """.stripMargin + ) val dAppAccount = TxHelpers.defaultSigner val dAppAddress = TxHelpers.defaultSigner.toAddress @@ -894,201 +893,891 @@ class UtilsRouteSpec extends RouteSpec("/utils") with RestAPISettingsHelper with (fullJson \ "result").asOpt[JsObject].getOrElse(fullJson - "address" - "expr") } - evalScript("testNone()") ~> route ~> check { - responseAs[JsObject] shouldBe Json.obj("error" -> 199, "message" -> s"Address $dAppAddress is not dApp") - } + "simple expression" in { + withDomain(RideV5) { d => + val blockchain = d.blockchain + val api = utilsApi.copy(blockchain = blockchain) + val route = seal(api.route) - d.helpers.creditWavesToDefaultSigner() - d.helpers.setScript(dAppAccount, testScript) + evalScript("testNone()") ~> route ~> check { + responseAs[JsObject] shouldBe Json.obj("error" -> 199, "message" -> s"Address $dAppAddress is not dApp") + } - evalScript("testListArg([\"test\", 111, base64'dGVzdA==', false], \"test\", base58'aaa')") ~> route ~> check { - responseJson shouldBe Json.obj("type" -> "Boolean", "value" -> true) - } + d.helpers.creditWavesToDefaultSigner() + d.helpers.setScript(dAppAccount, testScript) - evalScript("testCallable()") ~> route ~> check { - responseAs[JsValue] should matchJson( - """{"result":{"type":"Array","value":[{"type":"BinaryEntry","value":{"key":{"type":"String","value":"test"},"value":{"type":"ByteVector","value":"11111111111111111111111111"}}}]},"complexity":2,"expr":"testCallable()","address":"3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9"}""" - ) - } + evalScript("testListArg([\"test\", 111, base64'dGVzdA==', false], \"test\", base58'aaa')") ~> route ~> check { + responseJson shouldBe Json.obj("type" -> "Boolean", "value" -> true) + } - evalScript("testThis()") ~> route ~> check { - responseJson shouldBe Json.obj( - "type" -> "Address", - "value" -> Json.obj("bytes" -> Json.obj("type" -> "ByteVector", "value" -> "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9")) - ) - } + evalScript("testCallable()") ~> route ~> check { + responseAs[JsValue] should matchJson( + """{"result":{"type":"Array","value":[{"type":"BinaryEntry","value":{"key":{"type":"String","value":"test"},"value":{"type":"ByteVector","value":"11111111111111111111111111"}}}]},"complexity":5,"expr":"testCallable()","address":"3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9"}""" + ) + } - evalScript("testNone()") ~> route ~> check { - responseJson shouldBe Json.obj( - "error" -> 306, - "message" -> "InvokeRejectError(error = Function or type 'testNone' not found, log = \n\ttestNone.@args = []\n)" - ) - } + evalScript("testThis()") ~> route ~> check { + responseJson shouldBe Json.obj( + "type" -> "Address", + "value" -> Json.obj("bytes" -> Json.obj("type" -> "ByteVector", "value" -> "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9")) + ) + } - evalScript("testCompl()") ~> route ~> check { - responseJson shouldBe Json.obj( - "error" -> 306, - "message" -> "InvokeRejectError(error = Calculation complexity limit exceeded, log = \n\ttestCompl.@args = []\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 25800\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 25600\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 25400\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 25200\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 25000\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 24800\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 24600\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 24400\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 24200\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 24000\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 23800\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 23600\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 23400\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 23200\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 23000\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 22800\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 22600\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 22400\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 22200\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 22000\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 21800\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 21600\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 21400\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 21200\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 21000\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 20800\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 20600\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 20400\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 20200\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 20000\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 19800\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 19600\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 19400\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 19200\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 19000\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 18800\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 18600\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 18400\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 18200\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 18000\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 17800\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 17600\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 17400\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 17200\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 17000\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 16800\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 16600\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 16400\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 16200\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 16000\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 15800\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 15600\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 15400\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 15200\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 15000\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 14800\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 14600\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 14400\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 14200\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 14000\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 13800\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 13600\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 13400\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 13200\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 13000\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 12800\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 12600\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 12400\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 12200\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 12000\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 11800\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 11600\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 11400\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 11200\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 11000\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 10800\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 10600\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 10400\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 10200\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 10000\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 9800\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 9600\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 9400\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 9200\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 9000\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 8800\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 8600\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 8400\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 8200\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 8000\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 7800\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 7600\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 7400\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 7200\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 7000\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 6800\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 6600\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 6400\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 6200\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 6000\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 5800\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 5600\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 5400\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 5200\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 5000\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 4800\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 4600\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 4400\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 4200\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 4000\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 3800\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 3600\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 3400\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 3200\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 3000\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 2800\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 2600\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 2400\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 2200\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 2000\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 1800\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 1600\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 1400\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 1200\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 1000\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 800\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 600\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 400\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 200\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 0\n)" - ) - } + evalScript("testNone()") ~> route ~> check { + responseJson shouldBe Json.obj( + "error" -> 306, + "message" -> "InvokeRejectError(error = Function or type 'testNone' not found, log = \n\ttestNone.@args = []\n)" + ) + } - evalScript("testF()") ~> route ~> check { - responseJson shouldBe Json.obj( - "error" -> 306, - "message" -> "InvokeRejectError(error = Test, log = \n\ttestF.@args = []\n\tthrow.@args = [\n\t\t\"Test\"\n\t]\n\tthrow.@complexity = 1\n\t@complexityLimit = 25999\n)" - ) - } + evalScript("testCompl()") ~> route ~> check { + responseJson shouldBe Json.obj( + "error" -> 306, + "message" -> "InvokeRejectError(error = Calculation complexity limit exceeded, log = \n\ttestCompl.@args = []\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 25800\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 25599\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 25398\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 25197\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 24996\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 24795\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 24594\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 24393\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 24192\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 23991\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 23790\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 23589\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 23388\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 23187\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 22986\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 22785\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 22584\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 22383\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 22182\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 21981\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 21780\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 21579\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 21378\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 21177\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 20976\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 20775\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 20574\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 20373\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 20172\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 19971\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 19770\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 19569\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 19368\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 19167\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 18966\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 18765\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 18564\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 18363\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 18162\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 17961\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 17760\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 17559\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 17358\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 17157\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 16956\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 16755\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 16554\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 16353\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 16152\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 15951\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 15750\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 15549\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 15348\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 15147\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 14946\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 14745\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 14544\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 14343\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 14142\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 13941\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 13740\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 13539\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 13338\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 13137\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 12936\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 12735\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 12534\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 12333\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 12132\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 11931\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 11730\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 11529\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 11328\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 11127\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 10926\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 10725\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 10524\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 10323\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 10122\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 9921\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 9720\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 9519\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 9318\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 9117\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 8916\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 8715\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 8514\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 8313\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 8112\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 7911\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 7710\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 7509\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 7308\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 7107\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 6906\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 6705\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 6504\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 6303\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 6102\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 5901\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 5700\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 5499\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 5298\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 5097\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 4896\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 4695\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 4494\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 4293\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 4092\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 3891\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 3690\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 3489\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 3288\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 3087\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 2886\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 2685\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 2484\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 2283\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 2082\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 1881\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 1680\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 1479\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 1278\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 1077\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 876\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 675\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 474\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 273\n\tsigVerify.@args = [\n\t\tbase58'',\n\t\tbase58'',\n\t\tbase58''\n\t]\n\tsigVerify.@complexity = 200\n\t@complexityLimit = 72\n)" + ) + } - evalScript("test(123)") ~> route ~> check { - responseJson shouldBe Json.obj("type" -> "Int", "value" -> 1230) - } + evalScript("testF()") ~> route ~> check { + responseJson shouldBe Json.obj( + "error" -> 306, + "message" -> "InvokeRejectError(error = Test, log = \n\ttestF.@args = []\n\tthrow.@args = [\n\t\t\"Test\"\n\t]\n\tthrow.@complexity = 1\n\t@complexityLimit = 25999\n)" + ) + } - evalBin(FUNCTION_CALL(FunctionHeader.User("test"), List(CONST_LONG(123)))) ~> route ~> check { - responseJson shouldBe Json.obj("type" -> "Int", "value" -> 1230) - } + evalScript("test(123)") ~> route ~> check { + responseJson shouldBe Json.obj("type" -> "Int", "value" -> 1230) + } - evalScript("testS()") ~> route ~> check { - responseJson shouldBe Json.obj("type" -> "String", "value" -> "Test") - } + evalBin(FUNCTION_CALL(FunctionHeader.User("test"), List(CONST_LONG(123)))) ~> route ~> check { + responseJson shouldBe Json.obj("type" -> "Int", "value" -> 1230) + } - evalScript("testB()") ~> route ~> check { - responseJson shouldBe Json.obj("type" -> "Boolean", "value" -> true) - } + evalScript("testS()") ~> route ~> check { + responseJson shouldBe Json.obj("type" -> "String", "value" -> "Test") + } - evalScript("testBS()") ~> route ~> check { - responseJson shouldBe Json.obj("type" -> "ByteVector", "value" -> "MATCHER") - } + evalScript("testB()") ~> route ~> check { + responseJson shouldBe Json.obj("type" -> "Boolean", "value" -> true) + } - evalScript("""match test(123) { - | case i: Int => i * 123 - | case _ => throw("") - |}""".stripMargin) ~> route ~> check { - responseJson shouldBe Json.obj("type" -> "Int", "value" -> 151290) - } + evalScript("testBS()") ~> route ~> check { + responseJson shouldBe Json.obj("type" -> "ByteVector", "value" -> "MATCHER") + } - val expectingKey = "some" - val expectingValue = 1234 + evalScript("""match test(123) { + | case i: Int => i * 123 + | case _ => throw("") + |}""".stripMargin) ~> route ~> check { + responseJson shouldBe Json.obj("type" -> "Int", "value" -> 151290) + } - d.helpers.setData(dAppAccount, IntegerDataEntry(expectingKey, expectingValue)) - evalScript( - s""" - | this.getInteger("$expectingKey") == $expectingValue && - | height == height - """.stripMargin - ) ~> route ~> check { - responseJson shouldBe Json.obj("type" -> "Boolean", "value" -> true) - } + val expectingKey = "some" + val expectingValue = 1234 + + d.helpers.setData(dAppAccount, IntegerDataEntry(expectingKey, expectingValue)) + evalScript( + s""" + | this.getInteger("$expectingKey") == $expectingValue && + | height == height + """.stripMargin + ) ~> route ~> check { + responseJson shouldBe Json.obj("type" -> "Boolean", "value" -> true) + } - evalScript("letFromContract - 1") ~> route ~> check { - responseJson shouldBe Json.obj("type" -> "Int", "value" -> (letFromContract - 1)) - } + evalScript("letFromContract - 1") ~> route ~> check { + responseJson shouldBe Json.obj("type" -> "Int", "value" -> (letFromContract - 1)) + } - (() => utilsApi.blockchain.settings) - .when() - .returning(DefaultBlockchainSettings) - .anyNumberOfTimes() - (utilsApi.blockchain.leaseBalance _) - .when(*) - .returning(LeaseBalance.empty) - .anyNumberOfTimes() - - evalScript(""" testSyncinvoke() """) ~> route ~> check { - responseAs[JsValue] should matchJson("""{ - | "result" : { - | "type" : "Array", - | "value" : [ { - | "type" : "BinaryEntry", - | "value" : { - | "key" : { - | "type" : "String", - | "value" : "testSyncInvoke" - | }, - | "value" : { - | "type" : "ByteVector", - | "value" : "11111111111111111111111111" - | } - | } - | } ] - | }, - | "complexity" : 80, - | "expr" : " testSyncinvoke() ", - | "address" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9" - |}""".stripMargin) - } + (() => utilsApi.blockchain.settings) + .when() + .returning(DefaultBlockchainSettings) + .anyNumberOfTimes() + (utilsApi.blockchain.leaseBalance _) + .when(*) + .returning(LeaseBalance.empty) + .anyNumberOfTimes() + + evalScript(""" testSyncinvoke() """) ~> route ~> check { + responseAs[JsValue] should matchJson("""{ + | "result" : { + | "type" : "Array", + | "value" : [ { + | "type" : "BinaryEntry", + | "value" : { + | "key" : { + | "type" : "String", + | "value" : "testSyncInvoke" + | }, + | "value" : { + | "type" : "ByteVector", + | "value" : "11111111111111111111111111" + | } + | } + | } ] + | }, + | "complexity" : 92, + | "expr" : " testSyncinvoke() ", + | "address" : "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9" + |}""".stripMargin) + } - val complexityLimit = 200 - val customApi = api.copy(settings = restAPISettings.copy(evaluateScriptComplexityLimit = complexityLimit)) - evalScript(""" testSyncCallComplexityExcess() """) ~> customApi.route ~> check { - responseAs[JsValue] should matchJson( - s"""{"error":306,"message":"InvokeRejectError(error = Invoke complexity limit = 200 is exceeded, log = \\n\\ttestSyncCallComplexityExcess.@args = []\\n\\tinvoke.@args = [\\n\\t\\tAddress(\\n\\t\\t\\tbytes = base58'3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9'\\n\\t\\t),\\n\\t\\t\\"testSyncCallComplexityExcess\\",\\n\\t\\t[],\\n\\t\\t[]\\n\\t]\\n\\tinvoke.@complexity = 75\\n\\t@complexityLimit = 125\\n\\tr = FailedTransactionError(code = 1, error = Invoke complexity limit = 200 is exceeded, log = \\n\\t\\t@invokedDApp = Address(\\n\\t\\t\\tbytes = base58'3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9'\\n\\t\\t)\\n\\t\\t@invokedFuncName = \\"testSyncCallComplexityExcess\\"\\n\\t\\ti = Invocation(\\n\\t\\t\\toriginCaller = Address(\\n\\t\\t\\t\\tbytes = base58'3MuPKL2kQz1Gp9t7QwrDZN5F8m3u5Uzzo3e'\\n\\t\\t\\t)\\n\\t\\t\\tpayments = []\\n\\t\\t\\tcallerPublicKey = base58'9BUoYQYq7K38mkk61q8aMH9kD9fKSVL1Fib7FbH6nUkQ'\\n\\t\\t\\tfeeAssetId = Unit\\n\\t\\t\\toriginCallerPublicKey = base58'11111111111111111111111111111111'\\n\\t\\t\\ttransactionId = base58''\\n\\t\\t\\tcaller = Address(\\n\\t\\t\\t\\tbytes = base58'3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9'\\n\\t\\t\\t)\\n\\t\\t\\tfee = 0\\n\\t\\t)\\n\\t\\ttestSyncCallComplexityExcess.@args = []\\n\\t\\tinvoke.@args = [\\n\\t\\t\\tAddress(\\n\\t\\t\\t\\tbytes = base58'3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9'\\n\\t\\t\\t),\\n\\t\\t\\t\\"testSyncCallComplexityExcess\\",\\n\\t\\t\\t[],\\n\\t\\t\\t[]\\n\\t\\t]\\n\\t\\tinvoke.@complexity = 75\\n\\t\\t@complexityLimit = 50\\n\\t\\tr = FailedTransactionError(code = 1, error = Invoke complexity limit = 200 is exceeded, log = \\n\\t\\t\\t@invokedDApp = Address(\\n\\t\\t\\t\\tbytes = base58'3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9'\\n\\t\\t\\t)\\n\\t\\t\\t@invokedFuncName = \\"testSyncCallComplexityExcess\\"\\n\\t\\t\\ti = Invocation(\\n\\t\\t\\t\\toriginCaller = Address(\\n\\t\\t\\t\\t\\tbytes = base58'3MuPKL2kQz1Gp9t7QwrDZN5F8m3u5Uzzo3e'\\n\\t\\t\\t\\t)\\n\\t\\t\\t\\tpayments = []\\n\\t\\t\\t\\tcallerPublicKey = base58'9BUoYQYq7K38mkk61q8aMH9kD9fKSVL1Fib7FbH6nUkQ'\\n\\t\\t\\t\\tfeeAssetId = Unit\\n\\t\\t\\t\\toriginCallerPublicKey = base58'11111111111111111111111111111111'\\n\\t\\t\\t\\ttransactionId = base58''\\n\\t\\t\\t\\tcaller = Address(\\n\\t\\t\\t\\t\\tbytes = base58'3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9'\\n\\t\\t\\t\\t)\\n\\t\\t\\t\\tfee = 0\\n\\t\\t\\t)\\n\\t\\t\\ttestSyncCallComplexityExcess.@args = []\\n\\t\\t)\\n\\t)\\n)","expr":" testSyncCallComplexityExcess() ","address":"3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9"}""".stripMargin - ) - } + val complexityLimit = 200 + val customApi = api.copy(settings = restAPISettings.copy(evaluateScriptComplexityLimit = complexityLimit)) + evalScript(""" testSyncCallComplexityExcess() """) ~> customApi.route ~> check { + val response = responseAs[JsValue] + val message = + "InvokeRejectError(error = FailedTransactionError(code = 1, error = Invoke complexity limit = 200 is exceeded), log = \n\ttestSyncCallComplexityExcess.@args = []\n\tinvoke.@args = [\n\t\tAddress(\n\t\t\tbytes = base58'3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9'\n\t\t),\n\t\t\"testSyncCallComplexityExcess\",\n\t\t[],\n\t\t[]\n\t]\n\tinvoke.@complexity = 75\n\t@complexityLimit = 122\n\tr = FailedTransactionError(code = 1, error = Invoke complexity limit = 200 is exceeded, log = \n\t\t@invokedDApp = Address(\n\t\t\tbytes = base58'3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9'\n\t\t)\n\t\t@invokedFuncName = \"testSyncCallComplexityExcess\"\n\t\ti = Invocation(\n\t\t\toriginCaller = Address(\n\t\t\t\tbytes = base58'3MuPKL2kQz1Gp9t7QwrDZN5F8m3u5Uzzo3e'\n\t\t\t)\n\t\t\tpayments = []\n\t\t\tcallerPublicKey = base58'9BUoYQYq7K38mkk61q8aMH9kD9fKSVL1Fib7FbH6nUkQ'\n\t\t\tfeeAssetId = Unit\n\t\t\toriginCallerPublicKey = base58'11111111111111111111111111111111'\n\t\t\ttransactionId = base58''\n\t\t\tcaller = Address(\n\t\t\t\tbytes = base58'3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9'\n\t\t\t)\n\t\t\tfee = 0\n\t\t)\n\t\ttestSyncCallComplexityExcess.@args = []\n\t\tinvoke.@args = [\n\t\t\tAddress(\n\t\t\t\tbytes = base58'3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9'\n\t\t\t),\n\t\t\t\"testSyncCallComplexityExcess\",\n\t\t\t[],\n\t\t\t[]\n\t\t]\n\t\tinvoke.@complexity = 75\n\t\t@complexityLimit = 44\n\t\tr = FailedTransactionError(code = 1, error = Invoke complexity limit = 200 is exceeded, log = \n\t\t\t@invokedDApp = Address(\n\t\t\t\tbytes = base58'3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9'\n\t\t\t)\n\t\t\t@invokedFuncName = \"testSyncCallComplexityExcess\"\n\t\t\ti = Invocation(\n\t\t\t\toriginCaller = Address(\n\t\t\t\t\tbytes = base58'3MuPKL2kQz1Gp9t7QwrDZN5F8m3u5Uzzo3e'\n\t\t\t\t)\n\t\t\t\tpayments = []\n\t\t\t\tcallerPublicKey = base58'9BUoYQYq7K38mkk61q8aMH9kD9fKSVL1Fib7FbH6nUkQ'\n\t\t\t\tfeeAssetId = Unit\n\t\t\t\toriginCallerPublicKey = base58'11111111111111111111111111111111'\n\t\t\t\ttransactionId = base58''\n\t\t\t\tcaller = Address(\n\t\t\t\t\tbytes = base58'3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9'\n\t\t\t\t)\n\t\t\t\tfee = 0\n\t\t\t)\n\t\t\ttestSyncCallComplexityExcess.@args = []\n\t\t)\n\t)\n)" + (response \ "message").as[String] shouldBe message + (response \ "error").as[Int] shouldBe ScriptExecutionError.Id + } - evalScript(""" testWriteEntryType("abc") """) ~> route ~> check { - responseAs[JsValue] should matchJson( - """{"error":306,"message":"InvokeRejectError(error = Passed args (bytes, abc) are unsuitable for constructor BinaryEntry(String, ByteVector), log = \n\ttestWriteEntryType.@args = [\n\t\t\"abc\"\n\t]\n\tb = \"abc\"\n\tBinaryEntry.@args = [\n\t\t\"bytes\",\n\t\t\"abc\"\n\t]\n)","expr":" testWriteEntryType(\"abc\") ","address":"3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9"}""" - ) - } - evalScript(""" testWriteEntryType(base58'aaaa') """) ~> route ~> check { - responseAs[JsValue] should matchJson( - """{"result":{"type":"Array","value":[{"type":"BinaryEntry","value":{"key":{"type":"String","value":"bytes"},"value":{"type":"ByteVector","value":"aaaa"}}}]},"complexity":2,"expr":" testWriteEntryType(base58'aaaa') ","address":"3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9"}""" - ) - } + evalScript(""" testWriteEntryType("abc") """) ~> route ~> check { + responseAs[JsValue] should matchJson( + """{"error":306,"message":"InvokeRejectError(error = Passed args (bytes, abc) are unsuitable for constructor BinaryEntry(String, ByteVector), log = \n\ttestWriteEntryType.@args = [\n\t\t\"abc\"\n\t]\n\tb = \"abc\"\n\tBinaryEntry.@args = [\n\t\t\"bytes\",\n\t\t\"abc\"\n\t]\n)","expr":" testWriteEntryType(\"abc\") ","address":"3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9"}""" + ) + } + evalScript(""" testWriteEntryType(base58'aaaa') """) ~> route ~> check { + responseAs[JsValue] should matchJson( + """{"result":{"type":"Array","value":[{"type":"BinaryEntry","value":{"key":{"type":"String","value":"bytes"},"value":{"type":"ByteVector","value":"aaaa"}}}]},"complexity":3,"expr":" testWriteEntryType(base58'aaaa') ","address":"3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9"}""" + ) + } - evalScript(s"""parseBigIntValue("${PureContext.BigIntMax}")""") ~> route ~> check { - responseAs[JsValue] should matchJson( - s"""{"result":{"type":"BigInt","value":${PureContext.BigIntMax}},"complexity":65,"expr":"parseBigIntValue(\\"${PureContext.BigIntMax}\\")","address":"3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9"}""" - ) - } + evalScript(s"""parseBigIntValue("${PureContext.BigIntMax}")""") ~> route ~> check { + responseAs[JsValue] should matchJson( + s"""{"result":{"type":"BigInt","value":${PureContext.BigIntMax}},"complexity":65,"expr":"parseBigIntValue(\\"${PureContext.BigIntMax}\\")","address":"3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9"}""" + ) + } - val dAppAccount2 = TxHelpers.secondSigner - val dAppAddress2 = TxHelpers.secondAddress - d.helpers.creditWavesFromDefaultSigner(dAppAddress2) - val testScript2 = TxHelpers.scriptV5(s"""@Callable(i) - | func callable() = { - | strict a = sigVerify(base58'', base58'', base58'') - | strict r = Address(base58'$dAppAddress').invoke("testCallable", [], [AttachedPayment(unit, 100)]) - | [BinaryEntry("testSyncInvoke", i.caller.bytes)] - | }""".stripMargin) - d.helpers.setScript(dAppAccount2, testScript2) - - evalScript(""" callable() """, dAppAddress2) ~> route ~> check { - responseAs[JsValue] should matchJson( - """{"result":{"type":"Array","value":[{"type":"BinaryEntry","value":{"key":{"type":"String","value":"testSyncInvoke"},"value":{"type":"ByteVector","value":"11111111111111111111111111"}}}]},"complexity":284,"expr":" callable() ","address":"3MuVqVJGmFsHeuFni5RbjRmALuGCkEwzZtC"}""" - ) - } + val dAppAccount2 = TxHelpers.secondSigner + val dAppAddress2 = TxHelpers.secondAddress + d.helpers.creditWavesFromDefaultSigner(dAppAddress2) + val testScript2 = TxHelpers.scriptV5(s"""@Callable(i) + | func callable() = { + | strict a = sigVerify(base58'', base58'', base58'') + | strict r = Address(base58'$dAppAddress').invoke("testCallable", [], [AttachedPayment(unit, 100)]) + | [BinaryEntry("testSyncInvoke", i.caller.bytes)] + | }""".stripMargin) + d.helpers.setScript(dAppAccount2, testScript2) + + evalScript(""" callable() """, dAppAddress2) ~> route ~> check { + responseAs[JsValue] should matchJson( + """{"result":{"type":"Array","value":[{"type":"BinaryEntry","value":{"key":{"type":"String","value":"testSyncInvoke"},"value":{"type":"ByteVector","value":"11111111111111111111111111"}}}]},"complexity":297,"expr":" callable() ","address":"3MuVqVJGmFsHeuFni5RbjRmALuGCkEwzZtC"}""" + ) + } - val dApp2 = TxHelpers.scriptV5( - s""" - | @Callable(i) - | func call(a: Int, b: String) = ([], b + a.toString()) - """.stripMargin - ) - d.helpers.setScript(dAppAccount2, dApp2) + val dApp2 = TxHelpers.scriptV5( + s""" + | @Callable(i) + | func call(a: Int, b: String) = ([], b + a.toString()) + """.stripMargin + ) + d.helpers.setScript(dAppAccount2, dApp2) - evalScript(s"nestedCalls([(\"$dAppAddress2\", \"call\", [123, \"abc\"]), (\"$dAppAddress\", \"getValue\", [])])") ~> route ~> check { - (responseAs[JsValue] \ "result" \ "value").as[String] shouldBe "abc123\nvalue\n" + evalScript(s"nestedCalls([(\"$dAppAddress2\", \"call\", [123, \"abc\"]), (\"$dAppAddress\", \"getValue\", [])])") ~> route ~> check { + (responseAs[JsValue] \ "result" \ "value").as[String] shouldBe "abc123\nvalue\n" + } + } } - val compactedDApp = TestCompiler(V5).compileContract( - """ - | func user1() = 1 - | func user2() = 2 - | - | @Callable(i) - | func call() = ([], user1() + user2()) - """.stripMargin, - compact = true - ) - d.helpers.setScript(dAppAccount, compactedDApp) - evalScript("user1()") ~> route ~> check { - (responseAs[JsValue] \ "result" \ "value").as[Int] shouldBe 1 - } - evalScript("user2()") ~> route ~> check { - (responseAs[JsValue] \ "result" \ "value").as[Int] shouldBe 2 + "compacted dApp" in { + withDomain(RideV6) { d => + val blockchain = d.blockchain + val route = utilsApi.copy(blockchain = blockchain).route + + val compactedDApp = TestCompiler(V6).compileContract( + """ + | func user1() = 1 + | func user2() = 2 + | + | @Callable(i) + | func call() = ([], user1() + user2()) + """.stripMargin, + compact = true + ) + d.helpers.setScript(dAppAccount, compactedDApp) + + evalScript("user1()") ~> route ~> check { + (responseAs[JsValue] \ "result" \ "value").as[Int] shouldBe 1 + } + evalScript("user2()") ~> route ~> check { + (responseAs[JsValue] \ "result" \ "value").as[Int] shouldBe 2 + } + evalScript("call()") ~> route ~> check { + (responseAs[JsValue] \ "result" \ "value" \ "_2" \ "value").as[Int] shouldBe 3 + } + } } - evalScript("call()") ~> route ~> check { - (responseAs[JsValue] \ "result" \ "value" \ "_2" \ "value").as[Int] shouldBe 3 + + "invocation" - { + withDomain(RideV6) { d => + def dApp(caller: Address) = TestCompiler(V6).compileContract( + s""" + | @Callable(i) + | func f(arg1: Int, arg2: String) = { + | let check = + | this == Address(base58'$defaultAddress') && + | i.caller == Address(base58'$caller') && + | i.originCaller == Address(base58'$caller') && + | i.callerPublicKey == base58'${secondSigner.publicKey}' && + | i.originCallerPublicKey == base58'${secondSigner.publicKey}' && + | i.fee == 123456 && + | i.payments == [AttachedPayment(unit, 1)] && + | i.transactionId == base58'3My3KZgFQ3CrVHgz6vGRt8687sH4oAA1qp8' && + | i.feeAssetId == base58'abcd' && + | arg1 == 123 && arg2 == "abc" + | if (check) then [] else throw("wrong") + | } + | + | @Callable(i) + | func default() = { + | let check = + | this == Address(base58'$defaultAddress') && + | i.caller == Address(base58'${"1" * 26}') && + | i.originCaller == Address(base58'${"1" * 26}') && + | i.callerPublicKey == base58'${"1" * 32}' && + | i.originCallerPublicKey == base58'${"1" * 32}' && + | i.fee == 500000 && + | i.payments == [] && + | i.transactionId == base58'${"1" * 32}' && + | i.feeAssetId == unit + | if (check) then [] else throw("wrong") + | } + """.stripMargin + ) + d.appendBlock(setScript(defaultSigner, dApp(caller = secondAddress))) + + val route = utilsApi.copy(blockchain = d.blockchain).route + def invocation(senderAddress: Option[Address] = Some(secondAddress)) = + Json + .parse( + s""" + | { + | "call": { + | "function": "f", + | "args": [ + | { + | "type": "integer", + | "value": 123 + | }, + | { + | "type": "string", + | "value": "abc" + | } + | ] + | }, + | "id": "3My3KZgFQ3CrVHgz6vGRt8687sH4oAA1qp8", + | "fee": 123456, + | "feeAssetId": "abcd", + | ${senderAddress.fold("")(a => s""""sender": "$a",""")} + | "senderPublicKey": "${secondSigner.publicKey}", + | "payment": [ + | { + | "amount": 1, + | "assetId": null + | } + | ] + | } + """.stripMargin + ) + .as[JsObject] + + val expectedTrace = + Json.parse( + s""" + | [ + | { + | "name": "f.@args", + | "type": "Array", + | "value": [ + | { + | "type": "Int", + | "value": 123 + | }, + | { + | "type": "String", + | "value": "abc" + | } + | ] + | }, + | { + | "name": "Address.@args", + | "type": "Array", + | "value": [ + | { + | "type": "ByteVector", + | "value": "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9" + | } + | ] + | }, + | { + | "name": "Address.@complexity", + | "type": "Int", + | "value": 1 + | }, + | { + | "name": "@complexityLimit", + | "type": "Int", + | "value": 25999 + | }, + | { + | "name": "==.@args", + | "type": "Array", + | "value": [ + | { + | "type": "Address", + | "value": { + | "bytes": { + | "type": "ByteVector", + | "value": "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9" + | } + | } + | }, + | { + | "type": "Address", + | "value": { + | "bytes": { + | "type": "ByteVector", + | "value": "3MtGzgmNa5fMjGCcPi5nqMTdtZkfojyWHL9" + | } + | } + | } + | ] + | }, + | { + | "name": "==.@complexity", + | "type": "Int", + | "value": 1 + | }, + | { + | "name": "@complexityLimit", + | "type": "Int", + | "value": 25998 + | }, + | { + | "name": "i", + | "type": "Invocation", + | "value": { + | "originCaller": { + | "type": "Address", + | "value": { + | "bytes": { + | "type": "ByteVector", + | "value": "3MuVqVJGmFsHeuFni5RbjRmALuGCkEwzZtC" + | } + | } + | }, + | "payments": { + | "type": "Array", + | "value": [ + | { + | "type": "AttachedPayment", + | "value": { + | "amount": { + | "type": "Int", + | "value": 1 + | }, + | "assetId": { + | "type": "Unit", + | "value": {} + | } + | } + | } + | ] + | }, + | "callerPublicKey": { + | "type": "ByteVector", + | "value": "8h47fXqSctZ6sb3q6Sst9qH1UNzR5fjez2eEP6BvEfcr" + | }, + | "feeAssetId": { + | "type": "ByteVector", + | "value": "abcd" + | }, + | "originCallerPublicKey": { + | "type": "ByteVector", + | "value": "8h47fXqSctZ6sb3q6Sst9qH1UNzR5fjez2eEP6BvEfcr" + | }, + | "transactionId": { + | "type": "ByteVector", + | "value": "3My3KZgFQ3CrVHgz6vGRt8687sH4oAA1qp8" + | }, + | "caller": { + | "type": "Address", + | "value": { + | "bytes": { + | "type": "ByteVector", + | "value": "3MuVqVJGmFsHeuFni5RbjRmALuGCkEwzZtC" + | } + | } + | }, + | "fee": { + | "type": "Int", + | "value": 123456 + | } + | } + | }, + | { + | "name": "Address.@args", + | "type": "Array", + | "value": [ + | { + | "type": "ByteVector", + | "value": "3MuVqVJGmFsHeuFni5RbjRmALuGCkEwzZtC" + | } + | ] + | }, + | { + | "name": "Address.@complexity", + | "type": "Int", + | "value": 1 + | }, + | { + | "name": "@complexityLimit", + | "type": "Int", + | "value": 25997 + | }, + | { + | "name": "==.@args", + | "type": "Array", + | "value": [ + | { + | "type": "Address", + | "value": { + | "bytes": { + | "type": "ByteVector", + | "value": "3MuVqVJGmFsHeuFni5RbjRmALuGCkEwzZtC" + | } + | } + | }, + | { + | "type": "Address", + | "value": { + | "bytes": { + | "type": "ByteVector", + | "value": "3MuVqVJGmFsHeuFni5RbjRmALuGCkEwzZtC" + | } + | } + | } + | ] + | }, + | { + | "name": "==.@complexity", + | "type": "Int", + | "value": 1 + | }, + | { + | "name": "@complexityLimit", + | "type": "Int", + | "value": 25996 + | }, + | { + | "name": "Address.@args", + | "type": "Array", + | "value": [ + | { + | "type": "ByteVector", + | "value": "3MuVqVJGmFsHeuFni5RbjRmALuGCkEwzZtC" + | } + | ] + | }, + | { + | "name": "Address.@complexity", + | "type": "Int", + | "value": 1 + | }, + | { + | "name": "@complexityLimit", + | "type": "Int", + | "value": 25995 + | }, + | { + | "name": "==.@args", + | "type": "Array", + | "value": [ + | { + | "type": "Address", + | "value": { + | "bytes": { + | "type": "ByteVector", + | "value": "3MuVqVJGmFsHeuFni5RbjRmALuGCkEwzZtC" + | } + | } + | }, + | { + | "type": "Address", + | "value": { + | "bytes": { + | "type": "ByteVector", + | "value": "3MuVqVJGmFsHeuFni5RbjRmALuGCkEwzZtC" + | } + | } + | } + | ] + | }, + | { + | "name": "==.@complexity", + | "type": "Int", + | "value": 1 + | }, + | { + | "name": "@complexityLimit", + | "type": "Int", + | "value": 25994 + | }, + | { + | "name": "==.@args", + | "type": "Array", + | "value": [ + | { + | "type": "ByteVector", + | "value": "8h47fXqSctZ6sb3q6Sst9qH1UNzR5fjez2eEP6BvEfcr" + | }, + | { + | "type": "ByteVector", + | "value": "8h47fXqSctZ6sb3q6Sst9qH1UNzR5fjez2eEP6BvEfcr" + | } + | ] + | }, + | { + | "name": "==.@complexity", + | "type": "Int", + | "value": 1 + | }, + | { + | "name": "@complexityLimit", + | "type": "Int", + | "value": 25993 + | }, + | { + | "name": "==.@args", + | "type": "Array", + | "value": [ + | { + | "type": "ByteVector", + | "value": "8h47fXqSctZ6sb3q6Sst9qH1UNzR5fjez2eEP6BvEfcr" + | }, + | { + | "type": "ByteVector", + | "value": "8h47fXqSctZ6sb3q6Sst9qH1UNzR5fjez2eEP6BvEfcr" + | } + | ] + | }, + | { + | "name": "==.@complexity", + | "type": "Int", + | "value": 1 + | }, + | { + | "name": "@complexityLimit", + | "type": "Int", + | "value": 25992 + | }, + | { + | "name": "==.@args", + | "type": "Array", + | "value": [ + | { + | "type": "Int", + | "value": 123456 + | }, + | { + | "type": "Int", + | "value": 123456 + | } + | ] + | }, + | { + | "name": "==.@complexity", + | "type": "Int", + | "value": 1 + | }, + | { + | "name": "@complexityLimit", + | "type": "Int", + | "value": 25991 + | }, + | { + | "name": "AttachedPayment.@args", + | "type": "Array", + | "value": [ + | { + | "type": "Unit", + | "value": {} + | }, + | { + | "type": "Int", + | "value": 1 + | } + | ] + | }, + | { + | "name": "AttachedPayment.@complexity", + | "type": "Int", + | "value": 1 + | }, + | { + | "name": "@complexityLimit", + | "type": "Int", + | "value": 25990 + | }, + | { + | "name": "cons.@args", + | "type": "Array", + | "value": [ + | { + | "type": "AttachedPayment", + | "value": { + | "assetId": { + | "type": "Unit", + | "value": {} + | }, + | "amount": { + | "type": "Int", + | "value": 1 + | } + | } + | }, + | { + | "type": "Array", + | "value": [] + | } + | ] + | }, + | { + | "name": "cons.@complexity", + | "type": "Int", + | "value": 1 + | }, + | { + | "name": "@complexityLimit", + | "type": "Int", + | "value": 25989 + | }, + | { + | "name": "==.@args", + | "type": "Array", + | "value": [ + | { + | "type": "Array", + | "value": [ + | { + | "type": "AttachedPayment", + | "value": { + | "amount": { + | "type": "Int", + | "value": 1 + | }, + | "assetId": { + | "type": "Unit", + | "value": {} + | } + | } + | } + | ] + | }, + | { + | "type": "Array", + | "value": [ + | { + | "type": "AttachedPayment", + | "value": { + | "assetId": { + | "type": "Unit", + | "value": {} + | }, + | "amount": { + | "type": "Int", + | "value": 1 + | } + | } + | } + | ] + | } + | ] + | }, + | { + | "name": "==.@complexity", + | "type": "Int", + | "value": 1 + | }, + | { + | "name": "@complexityLimit", + | "type": "Int", + | "value": 25988 + | }, + | { + | "name": "==.@args", + | "type": "Array", + | "value": [ + | { + | "type": "ByteVector", + | "value": "3My3KZgFQ3CrVHgz6vGRt8687sH4oAA1qp8" + | }, + | { + | "type": "ByteVector", + | "value": "3My3KZgFQ3CrVHgz6vGRt8687sH4oAA1qp8" + | } + | ] + | }, + | { + | "name": "==.@complexity", + | "type": "Int", + | "value": 1 + | }, + | { + | "name": "@complexityLimit", + | "type": "Int", + | "value": 25987 + | }, + | { + | "name": "==.@args", + | "type": "Array", + | "value": [ + | { + | "type": "ByteVector", + | "value": "abcd" + | }, + | { + | "type": "ByteVector", + | "value": "abcd" + | } + | ] + | }, + | { + | "name": "==.@complexity", + | "type": "Int", + | "value": 1 + | }, + | { + | "name": "@complexityLimit", + | "type": "Int", + | "value": 25986 + | }, + | { + | "name": "arg1", + | "type": "Int", + | "value": 123 + | }, + | { + | "name": "==.@args", + | "type": "Array", + | "value": [ + | { + | "type": "Int", + | "value": 123 + | }, + | { + | "type": "Int", + | "value": 123 + | } + | ] + | }, + | { + | "name": "==.@complexity", + | "type": "Int", + | "value": 1 + | }, + | { + | "name": "@complexityLimit", + | "type": "Int", + | "value": 25985 + | }, + | { + | "name": "arg2", + | "type": "String", + | "value": "abc" + | }, + | { + | "name": "==.@args", + | "type": "Array", + | "value": [ + | { + | "type": "String", + | "value": "abc" + | }, + | { + | "type": "String", + | "value": "abc" + | } + | ] + | }, + | { + | "name": "==.@complexity", + | "type": "Int", + | "value": 1 + | }, + | { + | "name": "@complexityLimit", + | "type": "Int", + | "value": 25984 + | }, + | { + | "name": "check", + | "type": "Boolean", + | "value": true + | } + |] + """.stripMargin + ) + + "successful result" in { + Post(routePath(s"/script/evaluate/$defaultAddress"), invocation()) ~> route ~> check { + val json = responseAs[JsValue] + (json \ "complexity").as[Int] shouldBe 16 + (json \ "result").as[JsObject] shouldBe Json.obj("type" -> "Array", "value" -> JsArray()) + (json \ "vars").isEmpty shouldBe true + json.as[UtilsInvocationRequest] shouldBe invocation().as[UtilsInvocationRequest] + } + } + + "trace" in { + Post(routePath(s"/script/evaluate/$defaultAddress?trace=true"), invocation()) ~> route ~> check { + val json = responseAs[JsValue] + (json \ "complexity").as[Int] shouldBe 16 + (json \ "result").as[JsObject] shouldBe Json.obj("type" -> "Array", "value" -> JsArray()) + (json \ "vars").as[JsArray] shouldBe expectedTrace + } + } + + "all fields are empty (empty request body)" in { + Post(routePath(s"/script/evaluate/$defaultAddress"), Json.obj()) ~> route ~> check { + val json = responseAs[JsValue] + (json \ "complexity").as[Int] shouldBe 12 + (json \ "result").as[JsObject] shouldBe Json.obj("type" -> "Array", "value" -> JsArray()) + } + } + + "conflicting request structure" in { + Post(routePath(s"/script/evaluate/$defaultAddress"), Json.obj("expr" -> "true") ++ invocation()) ~> route ~> check { + val json = responseAs[JsValue] + (json \ "message").as[String] shouldBe "Conflicting request structure. Both expression and invocation structure were sent" + (json \ "error").as[Int] shouldBe 198 + } + } + + "sender address can be calculated from PK" in { + Post(routePath(s"/script/evaluate/$defaultAddress"), invocation(None)) ~> route ~> check { + val json = responseAs[JsValue] + (json \ "complexity").as[Int] shouldBe 16 + (json \ "result").as[JsObject] shouldBe Json.obj("type" -> "Array", "value" -> JsArray()) + (json \ "vars").isEmpty shouldBe true + json.as[UtilsInvocationRequest] shouldBe invocation(None).as[UtilsInvocationRequest] + } + } + + "sender address can differ from PK address" in withDomain(RideV6) { d => + val customSender = signer(2).toAddress + val route = utilsApi.copy(blockchain = d.blockchain).route + d.appendBlock(setScript(defaultSigner, dApp(caller = customSender))) + + Post(routePath(s"/script/evaluate/$defaultAddress"), invocation(Some(customSender))) ~> route ~> check { + val json = responseAs[JsValue] + (json \ "complexity").as[Int] shouldBe 16 + (json \ "result").as[JsObject] shouldBe Json.obj("type" -> "Array", "value" -> JsArray()) + (json \ "vars").isEmpty shouldBe true + json.as[UtilsInvocationRequest] shouldBe invocation(Some(customSender)).as[UtilsInvocationRequest] + } + } + } } } diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/ci/CallableV4DiffTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/ci/CallableV4DiffTest.scala index f53b19730f4..825e2b8f15f 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/ci/CallableV4DiffTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/ci/CallableV4DiffTest.scala @@ -16,7 +16,6 @@ import com.wavesplatform.state.{EmptyDataEntry, SponsorshipValue} import com.wavesplatform.test.* import com.wavesplatform.test.DomainPresets.{RideV4, RideV6} import com.wavesplatform.transaction.Asset.IssuedAsset -import com.wavesplatform.transaction.TxHelpers.{defaultSigner, secondSigner} import com.wavesplatform.transaction.assets.IssueTransaction import com.wavesplatform.transaction.smart.script.trace.{AssetVerifierTrace, InvokeScriptTrace} import com.wavesplatform.transaction.smart.{InvokeScriptTransaction, SetScriptTransaction} @@ -42,52 +41,6 @@ class CallableV4DiffTest extends PropSpec with WithDomain with EitherValues { } } - property("asset script can disallow reissue") { - val disallowReissueAsset = - assetVerifier( - """ - | match tx { - | case _: ReissueTransaction => false - | case _ => true - | } - """.stripMargin - ) - - Seq(true, false).foreach { fail => - val (_, setScript, invoke, issue, _, _, _) = paymentPreconditions(0.013.waves, fail, Some(disallowReissueAsset)) - withDomain(RideV6, AddrWithBalance.enoughBalances(defaultSigner, secondSigner)) { d => - d.appendBlock(setScript, issue) - if (fail) - d.appendAndAssertFailed(invoke, "Transaction is not allowed by script of the asset") - else - d.appendBlockE(invoke) should produce("Transaction is not allowed by script of the asset") - } - } - } - - property("asset script can disallow burn") { - val disallowBurnAsset = - assetVerifier( - """ - | match tx { - | case _: BurnTransaction => false - | case _ => true - | } - """.stripMargin - ) - - Seq(true, false).foreach { fail => - val (_, setScript, invoke, issue, _, _, _) = paymentPreconditions(0.013.waves, fail, Some(disallowBurnAsset)) - withDomain(RideV6, AddrWithBalance.enoughBalances(defaultSigner, secondSigner)) { d => - d.appendBlock(setScript, issue) - if (fail) - d.appendAndAssertFailed(invoke, "Transaction is not allowed by script of the asset") - else - d.appendBlockE(invoke) should produce("Transaction is not allowed by script of the asset") - } - } - } - property("asset script can allow burn and reissue") { val allowBurnAndReissueAsset = assetVerifier( diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/ci/InvokeFailAndRejectTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/ci/RideV5FailRejectTest.scala similarity index 91% rename from node/src/test/scala/com/wavesplatform/state/diffs/ci/InvokeFailAndRejectTest.scala rename to node/src/test/scala/com/wavesplatform/state/diffs/ci/RideV5FailRejectTest.scala index ae16f314074..7eb50e35918 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/ci/InvokeFailAndRejectTest.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/ci/RideV5FailRejectTest.scala @@ -3,7 +3,6 @@ package com.wavesplatform.state.diffs.ci import com.wavesplatform.TestValues.invokeFee import com.wavesplatform.db.WithDomain import com.wavesplatform.db.WithState.AddrWithBalance -import com.wavesplatform.features.BlockchainFeatures import com.wavesplatform.history.Domain import com.wavesplatform.lang.directives.values.* import com.wavesplatform.lang.script.v1.ExprScript.ExprScriptImpl @@ -17,7 +16,7 @@ import com.wavesplatform.transaction.TxHelpers.* import com.wavesplatform.transaction.smart.InvokeScriptTransaction.Payment import com.wavesplatform.transaction.{TransactionType, TxHelpers} -class InvokeFailAndRejectTest extends PropSpec with WithDomain { +class RideV5FailRejectTest extends PropSpec with WithDomain { import DomainPresets.* private val assetFailScript = TestCompiler(V5).compileExpression( @@ -27,11 +26,11 @@ class InvokeFailAndRejectTest extends PropSpec with WithDomain { """.stripMargin ) - private def assertForV5AndV6[A](assert: Domain => A): Unit = - Seq(RideV5, RideV6).foreach(withDomain(_, AddrWithBalance.enoughBalances(secondSigner))(assert)) + private def assert[A](assert: Domain => A): Unit = + withDomain(RideV5, AddrWithBalance.enoughBalances(secondSigner))(assert) property("invoke fails by payment script") { - assertForV5AndV6 { d => + assert { d => val i = issue(script = Some(assetFailScript)) val asset = IssuedAsset(i.id()) val dApp = TestCompiler(V5).compileContract( @@ -50,7 +49,7 @@ class InvokeFailAndRejectTest extends PropSpec with WithDomain { } property("invoke fails by ScriptTransfer script") { - assertForV5AndV6 { d => + assert { d => val i = issue(secondSigner, script = Some(assetFailScript)) val asset = IssuedAsset(i.id()) val dApp = TestCompiler(V5).compileContract( @@ -68,7 +67,7 @@ class InvokeFailAndRejectTest extends PropSpec with WithDomain { } property("invoke with ScriptTransfer fails by payment script") { - assertForV5AndV6 { d => + assert { d => val failAssetIssue = issue(script = Some(assetFailScript)) val trueAssetIssue = issue(secondSigner, script = Some(ExprScriptImpl(V3, false, TRUE))) val failAsset = IssuedAsset(failAssetIssue.id()) @@ -91,7 +90,7 @@ class InvokeFailAndRejectTest extends PropSpec with WithDomain { } property("invoke with failing payment is rejected due to dApp script") { - assertForV5AndV6 { d => + assert { d => val failAssetIssue = issue(script = Some(assetFailScript)) val failAsset = IssuedAsset(failAssetIssue.id()) val dApp = TestCompiler(V5).compileContract( @@ -124,6 +123,8 @@ class InvokeFailAndRejectTest extends PropSpec with WithDomain { d.appendBlock(setScript(secondSigner, dApp)) d.appendAndAssertFailed(invokeTx, "Transaction is not allowed by script of the asset") } + + // TODO: move test after bug fix NODE-2520 withDomain(RideV6, AddrWithBalance.enoughBalances(secondSigner, signer(10))) { d => d.appendBlock(issueTx) d.appendBlock(setScript(secondSigner, dApp)) @@ -181,8 +182,8 @@ class InvokeFailAndRejectTest extends PropSpec with WithDomain { } } - property("invoke is always rejected if action address is from other network before activation of RideV6") { - assertForV5AndV6 { d => + property("invoke is always rejected if action address is from other network") { + assert { d => Seq(true, false).foreach { above1000Complexity => val c = if (above1000Complexity) (1 to 5).map(_ => "sigVerify(base58'', base58'', base58'')").mkString(" || ") else "1" val dApp = TestCompiler(V5).compileContract( @@ -198,16 +199,13 @@ class InvokeFailAndRejectTest extends PropSpec with WithDomain { ) val invokeTx = invoke() d.appendBlock(setScript(secondSigner, dApp)) - if (d.blockchain.isFeatureActivated(BlockchainFeatures.RideV6) && above1000Complexity) - d.appendAndAssertFailed(invokeTx, "Address belongs to another network: expected: 84(T), actual: 87(W)") - else - d.appendBlockE(invokeTx) should produce("Address belongs to another network: expected: 84(T), actual: 87(W)") + d.appendBlockE(invokeTx) should produce("Address belongs to another network: expected: 84(T), actual: 87(W)") } } } property("invoke is rejected or failed if attached invoke address is from other network") { - assertForV5AndV6 { d => + assert { d => val sigVerify = s"strict c = ${(1 to 5).map(_ => "sigVerify(base58'', base58'', base58'')").mkString(" || ")}" Seq(false, true).foreach { complex => val dApp = TestCompiler(V5).compileContract( @@ -231,7 +229,7 @@ class InvokeFailAndRejectTest extends PropSpec with WithDomain { } property("invoke is rejected from account with non-boolean verifier") { - assertForV5AndV6 { d => + assert { d => val dApp = TestCompiler(V5).compileContract( s""" | @Callable(i) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 27778b7cbe0..423c8dafce2 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -39,7 +39,8 @@ object Dependencies { val catsCore = catsModule("core", "2.7.0") val shapeless = Def.setting("com.chuusai" %%% "shapeless" % "2.3.9") - val scalaTest = "org.scalatest" %% "scalatest" % "3.2.13" % Test + val scalaTest = "org.scalatest" %% "scalatest" % "3.2.13" % Test + val scalaJsTest = Def.setting("com.lihaoyi" %%% "utest" % "0.8.0" % Test) val sttp3 = "com.softwaremill.sttp.client3" % "core_2.13" % "3.5.2" // 3.6.x and later is built for Java 11 diff --git a/release-notes.md b/release-notes.md deleted file mode 100644 index e8b9c539730..00000000000 --- a/release-notes.md +++ /dev/null @@ -1,67 +0,0 @@ -**0.6.0** - -* The DEX's Order Match transaction has been changed. This is the main reason for restarting Testnet. Now, a second asset of transaction's pair is used to set an amount of the transaction. -* LPOS was implemented. New Leasing and Leasing Cancel transactions were added. -* New, HOCON based, configuration file. Old configuration file (JSON based) is supported in this release for backward compatibility. Automatic configuration file conversion added to DEB packages. - -**0.3.2** - -* By default walletDir and dataDir located in $HOME/waves - -**0.3.1** - -* HTTP API /scorex removed. Use /node instead. - -**0.2.2** - -* Switch network by "testnet" in settings. Default value is true." -* /scorex/* HTTP API deprecated. Use /node/* instead. -* All logs goes to stdout and stderr. Use "loggingLevel" in config. - -**0.2.1** - -* peers.dat format changed. Delete old version. -* Different HTTP status codes in replies in HTTP API were implemented -* Waves' Scorex v1.3.2 - -**0.2.0** - -* Peers blacklist ttl configuration via "p2p"/"blacklistResidenceTimeMilliseconds" -* Upgrade to Waves' Scorex v1.3.1 - -**0.2.0-RC7** - -* New API /waves/payment returns senderPublicKey -* New API /waves/create-signed-payment -* /waves/external-payment deprecated. - Use new /waves/broadcast-signed-payment. -* New API /waves/payment/signature -* minimumTxFee verification for API - -**0.2.0-RC5** - -* /waves/external-payment returns error for incorrect recipient - -**0.2.0-RC4** - -* Fixed issue with incorrect Handshake -* Balance with confirmations is the minimum balance -* /waves/external-payment returns error if account balance invalid -* New API method /consensus/generatingbalance/{address} - -**0.2.0-RC3** - -* Incompatible with 0.1.3 -* Upgrade to Scorex 1.2.8 -* New Address format -* New hash chain for Address - Blake2b256, Keccak32 -* New Testnet Genesis - -**0.1.3** - -* Upgrade to Scorex 1.2.6. -* New http api method /external-payment for lite client - -**0.1.2** - -* Upgrade to Scorex 1.2.4. Clean /scorex/waves/data/ before run. diff --git a/releaseJenkinsfile b/releaseJenkinsfile deleted file mode 100644 index 9c11472aef4..00000000000 --- a/releaseJenkinsfile +++ /dev/null @@ -1,191 +0,0 @@ -#!/usr/bin/env groovy -import groovy.json.JsonOutput -import devops.waves.* -@Library('jenkins-shared-lib') - -ut = new utils() -scripts = new scripts() -def artifactsDir="out" -def networks -def artifacts -def shaSumField = "## SHA256 Checksums\n```\n" -def user = "wavesplatform" -def repo = "Waves" -def repoUrl = "https://github.com/${user}/${repo}" -def dockerImageName = "wavesplatform/wavesnode" -def dockerImage -def wavesVersion -def useNodeSbtCache = false -def dockerRegistryCreds = 'dockerhub-wavesnode-creds' -def dockerRegistryAddress = 'https://index.docker.io/v1/' - -properties([ - - ut.buildDiscarderPropertyObject('14', '30'), - - parameters([ - ut.stringParameterDefinitionObject('tag','v0.0.0'), - ut.gitParameterDefinitionObject('branch', 'origin/(version-.*)', 'ASCENDING_SMART', 'NONE', repoUrl), - ut.extendedChoiceParameterDefinitionObject("network", "mainnet,testnet,stagenet", "mainnet", 0, "" ), - ut.cascadeChoiceParameterObject('dockerTags', "return ['latest:selected', binding.variables.get('tag').replace('v', '')+':selected' ]", 'tag','PARAMETER_TYPE_CHECK_BOX'), - ut.extendedChoiceParameterDefinitionObject('useNodeSbtCache', "yes", "yes", 0, "") - ]) -]) - -stage('Build information'){ - if (! params.tag || params.branch.contains('No values in a list') || ! params.network ) - { - echo "Aborting this build. Please run it again with the required parameters specified" - currentBuild.result = Constants.PIPELINE_ABORTED - return - } - else - { - networks = network.split(',').collect{it.toLowerCase()} - echo "Parameters are specified:" + params - if (params.useNodeSbtCache == "yes"){ - useNodeSbtCache = true - } - } -} - -if (currentBuild.result == Constants.PIPELINE_ABORTED){ - return -} - -node('wavesnode'){ - currentBuild.result = Constants.PIPELINE_SUCCESS - - timestamps { - wrap([$class: 'AnsiColorBuildWrapper', 'colorMapName': 'XTerm']) { - try { - currentBuild.displayName = "#${env.BUILD_NUMBER} - ${branch} - release ${tag}" - - stage('Checkout') { - sh 'env' - step([$class: 'WsCleanup']) - ut.checkout(branch, repoUrl) - sh "mkdir -p ${artifactsDir}/all" - } - - stage ('Build artifacts'){ - dir ('docker'){ - wavesVersion = tag.replace('v','') - def specifiedNetworks = networks.join(" ") - if (useNodeSbtCache){ - def text = readFile "Dockerfile" - sh """ - cp -R ${env.HOME}/.ivy2 ./.ivy2 - cp -R ${env.HOME}/.sbt ./.sbt - mkdir -p ./.ivy2 ./.sbt - """ - replacement="as builder\nCOPY ./.ivy2 /root/.ivy2\nCOPY ./.sbt /root/.sbt" - writeFile file: "Dockerfile", text: text.replace('as builder', replacement).replace('sbt "node/assembly"','true') - } - - def dockerImageBuilder = imageIt( - dockerRegistryAddress: dockerRegistryAddress, - dockerRegistryCreds: dockerRegistryCreds, - imageName: 'wavesbuilder', - dockerTag: wavesVersion, - args: "--build-arg WAVES_VERSION=${wavesVersion} --build-arg 'DEB_PACKAGE_NETWORKS=${specifiedNetworks}' --build-arg BRANCH=${branch} --target builder --no-cache", - skipPush: true - ) - dockerImage = imageIt( - dockerRegistryAddress: dockerRegistryAddress, - dockerRegistryCreds: dockerRegistryCreds, - imageName: dockerImageName, - dockerTag: wavesVersion, - args: "--build-arg WAVES_VERSION=${wavesVersion} --build-arg 'DEB_PACKAGE_NETWORKS=${specifiedNetworks}' --build-arg BRANCH=${branch}", - skipPush: true - ) - sh """ - id=\$(docker create wavesbuilder:${wavesVersion}) - docker cp \${id}:/out "${env.WORKSPACE}" - docker rm -v \${id} - """ - networks.each { - def networkIdentifier = (it == 'mainnet') ? '' : '-' + it - sh """ - mv "${env.WORKSPACE}/${artifactsDir}/${it}"/*.jar "${env.WORKSPACE}/${artifactsDir}/all/" - mv "${env.WORKSPACE}/${artifactsDir}/${it}"/*.tgz "${env.WORKSPACE}/${artifactsDir}/all/" - cp "${env.WORKSPACE}/${artifactsDir}/${it}"/*.deb "${env.WORKSPACE}/${artifactsDir}/all/" - """ - } - } - - dir (artifactsDir + '/all'){ - artifacts = findFiles(glob: '**/*') - artifacts.each{ - shaSumField += ut.shWithOutput("shasum -a 256 ${it.name}") + "\n" - } - } - } - - stage ('Create a release'){ - withCredentials([string(credentialsId: 'waves-release-github-token', variable: 'token')]) { - dir (artifactsDir + '/all'){ - def createReleaseBody = [ - tag_name: "${tag}", - target_commitish: "${branch}", - name: "Version ${tag.replace('v','')} (${networks.collect{ it.capitalize() }.join(" + ")})", - body: "# In this release\n${shaSumField}```", - draft: true, - prerelease: false] - def createReleaseBodyJson = JsonOutput.toJson(createReleaseBody) - def createReleaseUrl = "https://api.github.com/repos/${user}/${repo}/releases" - def id = ut.shWithOutput "curl -s -H 'Authorization:token ${token}' -X 'POST' -H 'Content-Type: application/json' -d '${createReleaseBodyJson}' ${createReleaseUrl} | grep -m 1 'id.:' | tr -cd '[0-9]='" - - artifacts.each{ - def contentType = (it.name.contains('tgz')) ? "application/gzip" : "application/octet-stream" - def uploadAssetsUrl = "https://uploads.github.com/repos/${user}/${repo}/releases/${id}/assets?name=${it.name}" - sh "curl -s -H 'Authorization:token ${token}' -X 'POST' -H 'Content-Type: ${contentType}' --data-binary @${it.name} ${uploadAssetsUrl}" - } - } - } - } - - withCredentials([sshUserPrivateKey(credentialsId: Constants.DEPLOYBOT_CREDS_ID, keyFileVariable: 'identity', passphraseVariable: '', usernameVariable: 'userName')]) { - def remote = [:] - remote.host = Constants.APT_REPO_CONTROLLER_SERVER - remote.name = Constants.APT_REPO_CONTROLLER_SERVER - remote.user = userName - remote.identityFile = identity - remote.allowAnyHosts = true - stage ('Updating APT repo'){ - networks.each { - ut.remotePut(remote, "${artifactsDir}/${it}", Constants.APT_PUBLISH_DIR) - } - ut.remoteExec (remote, "${Constants.APT_PUBLISH_DIR}/publish.sh") - } - } - - stage ('Pushing docker image'){ - docker.withRegistry(dockerRegistryAddress, dockerRegistryCreds) { - if (dockerTags.contains(wavesVersion)){ - dockerImage.push() - } - if (dockerTags.contains('latest')){ - dockerImage.push("latest") - } - } - } - } - catch (err) { - currentBuild.result = Constants.PIPELINE_FAILURE - println("ERROR caught") - println(err) - println(err.getMessage()) - println(err.getStackTrace()) - println(err.getCause()) - println(err.getLocalizedMessage()) - println(err.toString()) - } - finally{ - sh "tar -czvf artifacts.tar.gz -C ${artifactsDir} ." - archiveArtifacts artifacts: 'artifacts.tar.gz' - slackIt(buildStatus:currentBuild.result) - } - } - } -}