From 370de5315c134fec1bc90d088e0fe18585b48a65 Mon Sep 17 00:00:00 2001 From: Artyom Sayadyan Date: Fri, 21 Oct 2022 16:28:54 +0300 Subject: [PATCH 01/11] Explicit conversion for annFuncList (#3774) --- lang/js/src/main/scala/JsApiUtils.scala | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) 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 ) } From eaee8b593687831ef55dc1d8aaf1cb20696ed384 Mon Sep 17 00:00:00 2001 From: Artyom Sayadyan Date: Mon, 24 Oct 2022 10:26:54 +0300 Subject: [PATCH 02/11] NODE-2485 JS API tests (#3739) --- build.sbt | 11 ++++ .../scala/com/wavesplatform/lang/Global.scala | 24 +++------ .../com/wavesplatform/lang/impl/Global.scala | 7 +-- .../src/test/scala/DAppComplexities.scala | 7 +++ lang/tests-js/src/test/scala/Global.scala | 10 ++++ lang/tests-js/src/test/scala/JsAPITest.scala | 53 +++++++++++++++++++ lang/tests-js/src/test/scala/JsTestBase.scala | 48 +++++++++++++++++ project/Dependencies.scala | 3 +- 8 files changed, 140 insertions(+), 23 deletions(-) create mode 100644 lang/tests-js/src/test/scala/DAppComplexities.scala create mode 100644 lang/tests-js/src/test/scala/Global.scala create mode 100644 lang/tests-js/src/test/scala/JsAPITest.scala create mode 100644 lang/tests-js/src/test/scala/JsTestBase.scala 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/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/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/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 From 2752ec3514c0b2f34e2f6193c52777c80b20aaa9 Mon Sep 17 00:00:00 2001 From: Ivan Mashonskii Date: Mon, 24 Oct 2022 14:16:04 +0300 Subject: [PATCH 03/11] NODE-2516 Improve invoke fail/reject tests (#3767) --- ...nTest.scala => RideV6FailRejectTest.scala} | 327 +++++++++++++----- .../state/diffs/ci/CallableV4DiffTest.scala | 47 --- ...tTest.scala => RideV5FailRejectTest.scala} | 30 +- 3 files changed, 249 insertions(+), 155 deletions(-) rename node/src/test/scala/com/wavesplatform/features/{RideV6ActivationTest.scala => RideV6FailRejectTest.scala} (78%) rename node/src/test/scala/com/wavesplatform/state/diffs/ci/{InvokeFailAndRejectTest.scala => RideV5FailRejectTest.scala} (91%) 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/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) From 807f1099483cfe062d2c37f7bed251fdda2a276f Mon Sep 17 00:00:00 2001 From: Artyom Sayadyan Date: Wed, 26 Oct 2022 16:00:26 +0300 Subject: [PATCH 04/11] NODE-2402 Invocation structure for /utils/evaluate/ (#3757) --- .../main/resources/swagger-ui/openapi.yaml | 41 + .../scala/com/wavesplatform/Application.scala | 1 + .../com/wavesplatform/api/http/ApiError.scala | 6 + .../api/http/{ => utils}/UtilsApiRoute.scala | 213 +--- .../api/http/utils/UtilsEvaluator.scala | 107 ++ .../http/utils/UtilsInvocationRequest.scala | 55 + .../smart/AttachedPaymentExtractor.scala | 37 +- .../smart/script/trace/TraceStep.scala | 2 +- .../wavesplatform/http/UtilsRouteSpec.scala | 1127 +++++++++++++---- 9 files changed, 1195 insertions(+), 394 deletions(-) rename node/src/main/scala/com/wavesplatform/api/http/{ => utils}/UtilsApiRoute.scala (54%) create mode 100644 node/src/main/scala/com/wavesplatform/api/http/utils/UtilsEvaluator.scala create mode 100644 node/src/main/scala/com/wavesplatform/api/http/utils/UtilsInvocationRequest.scala diff --git a/node/src/main/resources/swagger-ui/openapi.yaml b/node/src/main/resources/swagger-ui/openapi.yaml index 21e18522fe5..eb34f2e2597 100644 --- a/node/src/main/resources/swagger-ui/openapi.yaml +++ b/node/src/main/resources/swagger-ui/openapi.yaml @@ -1798,6 +1798,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 +1848,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: diff --git a/node/src/main/scala/com/wavesplatform/Application.scala b/node/src/main/scala/com/wavesplatform/Application.scala index 08cc4916dcc..073e0d9858e 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.consensus.PoSSelector import com.wavesplatform.database.{DBExt, Keys, openDB} 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/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/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/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/http/UtilsRouteSpec.scala b/node/src/test/scala/com/wavesplatform/http/UtilsRouteSpec.scala index 388c00d3d71..7ae90fcc7c1 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 @@ -828,53 +830,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 +895,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] + } + } + } } } From 099ee8d631b85cb0f778292381b3b09fb687a315 Mon Sep 17 00:00:00 2001 From: Artyom Sayadyan Date: Thu, 27 Oct 2022 09:11:57 +0300 Subject: [PATCH 05/11] NODE-2460 Return DApp meta in JS and Java API (#3702) --- lang/js/src/main/scala/JsAPI.scala | 11 +++++++--- .../scala/com/wavesplatform/lang/Lang.scala | 22 +++++++++++++++++-- .../scala/com/wavesplatform/lang/model.scala | 11 ++++++++++ .../scala/com/wavesplatform/lang/API.scala | 9 +++++--- .../lang/contract/meta/ParsedMeta.scala | 2 +- .../wavesplatform/lang/v1/BaseGlobal.scala | 16 +++++--------- .../wavesplatform/api/http/JsonFormats.scala | 3 +-- .../transaction/ABIConverter.scala | 2 +- .../smart/script/ScriptCompiler.scala | 2 +- .../wavesplatform/http/AddressRouteSpec.scala | 1 - .../wavesplatform/http/UtilsRouteSpec.scala | 2 -- 11 files changed, 55 insertions(+), 26 deletions(-) create mode 100644 lang/jvm/src/main/scala/com/wavesplatform/lang/model.scala 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/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/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/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/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/test/scala/com/wavesplatform/http/AddressRouteSpec.scala b/node/src/test/scala/com/wavesplatform/http/AddressRouteSpec.scala index 28030c0c15c..57c46bb9196 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 7ae90fcc7c1..ac8f0776a45 100644 --- a/node/src/test/scala/com/wavesplatform/http/UtilsRouteSpec.scala +++ b/node/src/test/scala/com/wavesplatform/http/UtilsRouteSpec.scala @@ -434,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( @@ -464,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( From b5c1bfa93ecabbad1932971bb3c46b832fc2c3b0 Mon Sep 17 00:00:00 2001 From: Artyom Sayadyan Date: Thu, 27 Oct 2022 14:45:25 +0300 Subject: [PATCH 06/11] Fix description for rollback depth in openapi.yaml (#3773) --- .../main/resources/swagger-ui/openapi.yaml | 38 +++++++++++-------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/node/src/main/resources/swagger-ui/openapi.yaml b/node/src/main/resources/swagger-ui/openapi.yaml index eb34f2e2597..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 @@ -2132,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 @@ -2165,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 @@ -2270,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: From 40af2a70268d651517c76144c08a6b5c05d65582 Mon Sep 17 00:00:00 2001 From: Sergey Nazarov Date: Fri, 28 Oct 2022 12:53:02 +0400 Subject: [PATCH 07/11] Removed unused files (#3780) --- Jenkinsfile | 119 ---------------------------- release-notes.md | 67 ---------------- releaseJenkinsfile | 191 --------------------------------------------- 3 files changed, 377 deletions(-) delete mode 100644 Jenkinsfile delete mode 100644 release-notes.md delete mode 100644 releaseJenkinsfile 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/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) - } - } - } -} From 9ead4d449dc83b1f58dcddcd97fb2617cb0c2833 Mon Sep 17 00:00:00 2001 From: Artyom Sayadyan Date: Fri, 28 Oct 2022 13:21:07 +0300 Subject: [PATCH 08/11] NODE-2519 BU Correct updated waves amount in response (#3768) --- .../scala/com/wavesplatform/events/Repo.scala | 2 +- .../com/wavesplatform/events/events.scala | 15 ++-- .../events/BlockchainUpdatesSpec.scala | 72 ++++++++++++++++++- .../state/BlockchainUpdaterImpl.scala | 4 +- 4 files changed, 78 insertions(+), 15 deletions(-) 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/node/src/main/scala/com/wavesplatform/state/BlockchainUpdaterImpl.scala b/node/src/main/scala/com/wavesplatform/state/BlockchainUpdaterImpl.scala index cc5566aca6d..9696ecf1a2e 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) From a4dd0906e3514796de93ae2028c7d14eab4d81a2 Mon Sep 17 00:00:00 2001 From: Artyom Sayadyan Date: Mon, 31 Oct 2022 15:09:22 +0300 Subject: [PATCH 09/11] NODE-2396 Improve parse error messages (#3769) --- .../wavesplatform/lang/v1/BaseGlobal.scala | 18 +- .../lang/v1/compiler/ContractCompiler.scala | 53 +- .../lang/v1/compiler/ExpressionCompiler.scala | 17 +- .../wavesplatform/lang/v1/parser/Parser.scala | 329 +++-- .../lang/v1/parser/ParserV2.scala | 525 ------- .../lang/v1/compiler/TestCompiler.scala | 18 +- .../wavesplatform/lang/IntegrationTest.scala | 10 +- .../wavesplatform/lang/ParserV2DAppTest.scala | 325 ----- .../lang/ParserV2ScriptTest.scala | 1276 ----------------- .../ContractCompilerWithParserV2Test.scala | 3 +- .../compiler/ExpressionCompilerV1Test.scala | 2 +- .../ExpressionCompilerWithParserV2Test.scala | 33 +- .../ScriptPreprocessorTest.scala | 8 +- .../{ => compiler}/TypeInferrerTest.scala | 6 +- .../estimator/CommonScriptEstimatorTest.scala | 5 +- .../estimator/FoldEstimationTest.scala | 2 +- .../estimator/FuncOverlapEstimatorTest.scala | 3 +- .../estimator/RecursiveFunctionTest.scala | 3 +- .../estimator/ScriptEstimatorTestBase.scala | 11 +- .../estimator/ScriptEstimatorV1V2Test.scala | 5 +- .../estimator/ScriptEstimatorV2Test.scala | 4 +- .../estimator/ScriptEstimatorV2V3Test.scala | 2 +- .../estimator/ScriptEstimatorV3Test.scala | 2 +- .../estimator/TypeCastComplexityTest.scala | 3 +- .../lang/{v1 => }/estimator/package.scala | 5 +- .../EvaluatorV1CaseObjField.scala | 14 +- .../SmartConstructorTest.scala | 2 +- .../lang/evaluator/TypeCastTest.scala | 4 +- .../{ => string}/MakeStringTest.scala | 7 +- .../{ => parser}/ContractParserTest.scala | 6 +- .../{ => parser}/DirectiveParserTest.scala | 8 +- .../lang/{ => parser}/ScriptParserTest.scala | 93 +- .../{compiler => parser}/UnderscoreTest.scala | 51 +- .../parser/error/CommonParseErrorTest.scala | 78 + .../error/CurlyBraceParseErrorTest.scala | 105 ++ .../parser/error/FuncDefParseErrorTest.scala | 263 ++++ .../parser/error/LetDefParseErrorTest.scala | 123 ++ .../lang/parser/error/ParseErrorTest.scala | 24 + .../error/RoundBraceParseErrorTest.scala | 174 +++ .../error/SquareBraceParseErrorTest.scala | 79 + .../lang/{ => serde}/ContractSerdeTest.scala | 8 +- .../lang/{ => serde}/SerdeTest.scala | 12 +- .../com/wavesplatform/lang/v1/ReplTest.scala | 4 +- .../lang/v1/repl/ReplEngine.scala | 2 +- 44 files changed, 1236 insertions(+), 2489 deletions(-) delete mode 100644 lang/shared/src/main/scala/com/wavesplatform/lang/v1/parser/ParserV2.scala delete mode 100644 lang/tests/src/test/scala/com/wavesplatform/lang/ParserV2DAppTest.scala delete mode 100644 lang/tests/src/test/scala/com/wavesplatform/lang/ParserV2ScriptTest.scala rename lang/tests/src/test/scala/com/wavesplatform/lang/{ => compiler}/ScriptPreprocessorTest.scala (97%) rename lang/tests/src/test/scala/com/wavesplatform/lang/{ => compiler}/TypeInferrerTest.scala (97%) rename lang/tests/src/test/scala/com/wavesplatform/lang/{v1 => }/estimator/CommonScriptEstimatorTest.scala (91%) rename lang/tests/src/test/scala/com/wavesplatform/lang/{v1 => }/estimator/FoldEstimationTest.scala (97%) rename lang/tests/src/test/scala/com/wavesplatform/lang/{v1 => }/estimator/FuncOverlapEstimatorTest.scala (97%) rename lang/tests/src/test/scala/com/wavesplatform/lang/{v1 => }/estimator/RecursiveFunctionTest.scala (96%) rename lang/tests/src/test/scala/com/wavesplatform/lang/{v1 => }/estimator/ScriptEstimatorTestBase.scala (92%) rename lang/tests/src/test/scala/com/wavesplatform/lang/{v1 => }/estimator/ScriptEstimatorV1V2Test.scala (97%) rename lang/tests/src/test/scala/com/wavesplatform/lang/{v1 => }/estimator/ScriptEstimatorV2Test.scala (96%) rename lang/tests/src/test/scala/com/wavesplatform/lang/{v1 => }/estimator/ScriptEstimatorV2V3Test.scala (97%) rename lang/tests/src/test/scala/com/wavesplatform/lang/{v1 => }/estimator/ScriptEstimatorV3Test.scala (99%) rename lang/tests/src/test/scala/com/wavesplatform/lang/{v1 => }/estimator/TypeCastComplexityTest.scala (92%) rename lang/tests/src/test/scala/com/wavesplatform/lang/{v1 => }/estimator/package.scala (90%) rename lang/tests/src/test/scala/com/wavesplatform/lang/{ => evaluator}/EvaluatorV1CaseObjField.scala (85%) rename lang/tests/src/test/scala/com/wavesplatform/lang/{ => evaluator}/SmartConstructorTest.scala (96%) rename lang/tests/src/test/scala/com/wavesplatform/lang/evaluator/{ => string}/MakeStringTest.scala (98%) rename lang/tests/src/test/scala/com/wavesplatform/lang/{ => parser}/ContractParserTest.scala (99%) rename lang/tests/src/test/scala/com/wavesplatform/lang/{ => parser}/DirectiveParserTest.scala (94%) rename lang/tests/src/test/scala/com/wavesplatform/lang/{ => parser}/ScriptParserTest.scala (93%) rename lang/tests/src/test/scala/com/wavesplatform/lang/{compiler => parser}/UnderscoreTest.scala (78%) create mode 100644 lang/tests/src/test/scala/com/wavesplatform/lang/parser/error/CommonParseErrorTest.scala create mode 100644 lang/tests/src/test/scala/com/wavesplatform/lang/parser/error/CurlyBraceParseErrorTest.scala create mode 100644 lang/tests/src/test/scala/com/wavesplatform/lang/parser/error/FuncDefParseErrorTest.scala create mode 100644 lang/tests/src/test/scala/com/wavesplatform/lang/parser/error/LetDefParseErrorTest.scala create mode 100644 lang/tests/src/test/scala/com/wavesplatform/lang/parser/error/ParseErrorTest.scala create mode 100644 lang/tests/src/test/scala/com/wavesplatform/lang/parser/error/RoundBraceParseErrorTest.scala create mode 100644 lang/tests/src/test/scala/com/wavesplatform/lang/parser/error/SquareBraceParseErrorTest.scala rename lang/tests/src/test/scala/com/wavesplatform/lang/{ => serde}/ContractSerdeTest.scala (97%) rename lang/tests/src/test/scala/com/wavesplatform/lang/{ => serde}/SerdeTest.scala (97%) 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 f57daa92dec..d9f3b9b11ae 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 @@ -112,16 +112,17 @@ trait BaseGlobal { compRes <- ExpressionCompiler.compileWithParseResult(input, context) (compExpr, exprScript, compErrorList) = compRes illegalBlockVersionUsage = letBlockOnly && com.wavesplatform.lang.v1.compiler.containsBlockV2(compExpr) - _ <- Either.cond(!illegalBlockVersionUsage, (), "UserFunctions are only enabled in STDLIB_VERSION >= 3") + _ <- Either.cond(!illegalBlockVersionUsage, (), "UserFunctions are only enabled in STDLIB_VERSION >= 3").leftMap((_, 0, 0)) bytes = if (compErrorList.isEmpty) serializeExpression(compExpr, stdLibVersion) else Array.empty[Byte] vars = utils.varNames(stdLibVersion, Expression) costs = utils.functionCosts(stdLibVersion, DAppType) - complexity <- if (compErrorList.isEmpty) estimator(vars, costs, compExpr) else Either.right(0L) + complexity <- if (compErrorList.isEmpty) estimator(vars, costs, compExpr).leftMap((_, 0, 0)) else Either.right(0L) } yield (bytes, complexity, exprScript, compErrorList)) - .recover { case e => - (Array.empty, 0, Expressions.SCRIPT(AnyPos, Expressions.INVALID(AnyPos, "Unknown error.")), List(Generic(0, 0, e))) + .recover { case (e, start, end) => + (Array.empty[Byte], 0L, Expressions.SCRIPT(AnyPos, Expressions.INVALID(AnyPos, "Unknown error.")), List(Generic(start, end, e))) } + .leftMap(_._1) } def parseAndCompileContract( @@ -137,13 +138,14 @@ trait BaseGlobal { (compDAppOpt, exprDApp, compErrorList) = compRes complexityWithMap <- if (compDAppOpt.nonEmpty && compErrorList.isEmpty) - ContractScript.estimateComplexity(stdLibVersion, compDAppOpt.get, estimator, fixEstimateOfVerifier = true) + ContractScript.estimateComplexity(stdLibVersion, compDAppOpt.get, estimator, fixEstimateOfVerifier = true).leftMap((_, 0, 0)) else Right((0L, Map.empty[String, Long])) - bytes <- if (compDAppOpt.nonEmpty && compErrorList.isEmpty) serializeContract(compDAppOpt.get, stdLibVersion) else Right(Array.empty[Byte]) + bytes <- if (compDAppOpt.nonEmpty && compErrorList.isEmpty) serializeContract(compDAppOpt.get, stdLibVersion).leftMap((_, 0, 0)) else Right(Array.empty[Byte]) } yield (bytes, complexityWithMap, exprDApp, compErrorList)) - .recover { case e => - (Array.empty, (0, Map.empty), Expressions.DAPP(AnyPos, List.empty, List.empty), List(Generic(0, 0, e))) + .recover { case (e, start, end) => + (Array.empty[Byte], (0L, Map.empty[String, Long]), Expressions.DAPP(AnyPos, List.empty, List.empty), List(Generic(start, end, e))) } + .leftMap(_._1) } val compileExpression: (String, CompilerContext, StdLibVersion, ScriptType, ScriptEstimator) => Either[String, (Array[Byte], EXPR, Long)] = diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/compiler/ContractCompiler.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/compiler/ContractCompiler.scala index eb32942b7ec..255a55dd774 100644 --- a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/compiler/ContractCompiler.scala +++ b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/compiler/ContractCompiler.scala @@ -375,7 +375,8 @@ object ContractCompiler { case Left(err) => Left(err) case Right(c) => Right(c) } - case f: fastparse.Parsed.Failure => Left(f.toString) + case f: fastparse.Parsed.Failure => + Left(Parser.toString(input, f)) } } @@ -386,34 +387,30 @@ object ContractCompiler { needCompaction: Boolean = false, removeUnusedCode: Boolean = false, saveExprContext: Boolean = true - ): Either[String, (Option[DApp], Expressions.DAPP, Iterable[CompilationError])] = { - Parser.parseDAPPWithErrorRecovery(input) match { - case Right((parseResult, removedCharPosOpt)) => - compileContract(parseResult, version, needCompaction, removeUnusedCode, ScriptResultSource.CallableFunction, saveExprContext) - .run(ctx) - .map( - _._2 - .map { compRes => - val errorList = - compRes._3 ++ - (if (removedCharPosOpt.isEmpty) Nil - else - List( - Generic( - removedCharPosOpt.get.start, - removedCharPosOpt.get.end, - "Parsing failed. Some chars was removed as result of recovery process." - ) - )) - (compRes._1, compRes._2, errorList) - } - .leftMap(e => s"Compilation failed: ${Show[CompilationError].show(e)}") - ) - .value - - case Left(error) => Left(error.toString) + ): Either[(String, Int, Int), (Option[DApp], Expressions.DAPP, Iterable[CompilationError])] = + Parser.parseDAPPWithErrorRecovery(input).flatMap { case (parseResult, removedCharPosOpt) => + compileContract(parseResult, version, needCompaction, removeUnusedCode, ScriptResultSource.CallableFunction, saveExprContext) + .run(ctx) + .map( + _._2 + .map { compRes => + val errorList = + compRes._3 ++ + (if (removedCharPosOpt.isEmpty) Nil + else + List( + Generic( + removedCharPosOpt.get.start, + removedCharPosOpt.get.end, + "Parsing failed. Some chars was removed as result of recovery process." + ) + )) + (compRes._1, compRes._2, errorList) + } + .leftMap(e => (s"Compilation failed: ${Show[CompilationError].show(e)}", e.start, e.end)) + ) + .value } - } def compileFreeCall( input: String, diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/compiler/ExpressionCompiler.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/compiler/ExpressionCompiler.scala index 2df41c8f404..e142d2651df 100644 --- a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/compiler/ExpressionCompiler.scala +++ b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/compiler/ExpressionCompiler.scala @@ -42,7 +42,7 @@ object ExpressionCompiler { def compile(input: String, ctx: CompilerContext, allowIllFormedStrings: Boolean = false): Either[String, (EXPR, FINAL)] = { Parser.parseExpr(input) match { case fastparse.Parsed.Success(xs, _) => ExpressionCompiler(ctx, xs, allowIllFormedStrings) - case f: fastparse.Parsed.Failure => Left(f.toString) + case f: fastparse.Parsed.Failure => Left(Parser.toString(input, f)) } } @@ -62,10 +62,10 @@ object ExpressionCompiler { input: String, ctx: CompilerContext, saveExprContext: Boolean = true - ): Either[String, (EXPR, Expressions.SCRIPT, Iterable[CompilationError])] = { - val res = Parser.parseExpressionWithErrorRecovery(input) - res match { - case Right((parseResult, removedCharPosOpt)) => + ): Either[(String, Int, Int), (EXPR, Expressions.SCRIPT, Iterable[CompilationError])] = + Parser + .parseExpressionWithErrorRecovery(input) + .flatMap { case (parseResult, removedCharPosOpt) => compileExprWithCtx(parseResult.expr, saveExprContext, allowIllFormedStrings = false) .run(ctx) .value @@ -86,11 +86,8 @@ object ExpressionCompiler { )) (compRes.expr, parseResult.copy(expr = compRes.parseNodeExpr), errorList) } - .leftMap(e => s"Compilation failed: ${Show[CompilationError].show(e)}") - - case Left(error) => Left(error.toString) - } - } + .leftMap(e => (s"Compilation failed: ${Show[CompilationError].show(e)}", e.start, e.end)) + } def compileDecls(input: String, ctx: CompilerContext): Either[String, EXPR] = { val adjustedDecls = s"$input\n${PureContext.unitVarName}" diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/parser/Parser.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/parser/Parser.scala index a65d81332d8..fa1e5b5aeea 100644 --- a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/parser/Parser.scala +++ b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/parser/Parser.scala @@ -9,12 +9,12 @@ import com.wavesplatform.lang.v1.evaluator.ctx.impl.PureContext.MaxListLengthV4 import com.wavesplatform.lang.v1.parser.BinaryOperation.* import com.wavesplatform.lang.v1.parser.Expressions.* import com.wavesplatform.lang.v1.parser.Expressions.PART.VALID +import com.wavesplatform.lang.v1.parser.Expressions.Pos.AnyPos import com.wavesplatform.lang.v1.parser.UnaryOperation.* import com.wavesplatform.lang.v1.{ContractLimits, compiler} import fastparse.* import fastparse.MultiLineWhitespace.* - -import scala.annotation.tailrec +import fastparse.Parsed.Failure object Parser { @@ -24,12 +24,14 @@ object Parser { def keywords = Set("let", "strict", "base58", "base64", "true", "false", "if", "then", "else", "match", "case", "func") def lowerChar[A: P] = CharIn("a-z") def upperChar[A: P] = CharIn("A-Z") - def char[A: P] = lowerChar | upperChar + def nonLatinChar[A: P] = (CharPred(_.isLetter) ~~/ Fail).opaque("only latin charset for definitions") + def char[A: P] = lowerChar | upperChar | nonLatinChar def digit[A: P] = CharIn("0-9") + def spaces[A: P] = CharIn(" \t\n\r") def unicodeSymbolP[A: P] = P("\\u" ~/ Pass ~~ (char | digit).repX(0, "", 4)) def notEndOfString[A: P] = CharPred(_ != '\"') def specialSymbols[A: P] = P("\\" ~~ AnyChar) - def comment[A: P]: P[Unit] = P("#" ~~ CharPred(_ != '\n').repX).rep.map(_ => ()) + def comment[A: P]: P[Unit] = P("#" ~~ CharPred(_ != '\n').repX).rep def directive[A: P]: P[Unit] = P("{-#" ~ CharPred(el => el != '\n' && el != '#').rep ~ "#-}").rep(0, comment).map(_ => ()) def unusedText[A: P] = comment ~ directive ~ comment @@ -96,10 +98,25 @@ object Parser { .filter { case (_, x, _) => !keywords.contains(x) } .map { case (start, x, end) => PART.VALID(Pos(start, end), x) } - def declNameP[A: P]: P[Unit] = "_".? ~~ char ~~ ("_".? ~~ (digit | char)).repX() ~~ "_".? + def declNameP[A: P](check: Boolean = false): P[Unit] = { + val exclude = Set('(', ')', ':', ']', '[', '=') + def symbolsForError = CharPred(c => !c.isWhitespace && !exclude.contains(c)) + def checkedUnderscore = ("_" ~~/ !"_".repX(1)).opaque("not more than 1 underscore in a row") + + def onlyChar = { + def error = ((digit | symbolsForError) ~~ &(digit | char) ~~/ Fail).opaque("""character or "_" at start of the definition""") + if (check) char | error else char + } + def charOrDigit = { + def error = (symbolsForError ~~ &(digit | char) ~~/ Fail).opaque("""character, digit or "_" for the definition""") + def r = checkedUnderscore | digit | char + if (check) r | error else r + } + checkedUnderscore.? ~~ onlyChar ~~ charOrDigit.repX() ~~ checkedUnderscore.? + } def correctLFunName[A: P]: P[PART[String]] = - (Index ~~ declNameP.! ~~ Index) + (Index ~~ declNameP().! ~~ Index) .filter { case (_, x, _) => !keywords.contains(x) } .map { case (start, x, end) => PART.VALID(Pos(start, end), x) } @@ -111,8 +128,8 @@ object Parser { } } - def anyVarName(implicit c: fastparse.P[Any]): P[PART[String]] = { - def nameP(implicit c: fastparse.P[Any]): P[Unit] = declNameP + def anyVarName(check: Boolean = false)(implicit c: fastparse.P[Any]): P[PART[String]] = { + def nameP(implicit c: fastparse.P[Any]): P[Unit] = declNameP(check) genericVarName(nameP(_)) } @@ -132,11 +149,7 @@ object Parser { def trueP[A: P]: P[TRUE] = P(Index ~~ "true".! ~~ !(char | digit) ~~ Index).map { case (start, _, end) => TRUE(Pos(start, end)) } def falseP[A: P]: P[FALSE] = P(Index ~~ "false".! ~~ !(char | digit) ~~ Index).map { case (start, _, end) => FALSE(Pos(start, end)) } - def curlyBracesP[A: P]: P[EXPR] = P("{" ~ baseExpr ~ "}") - - def refP[A: P]: P[REF] = P(correctVarName).map { x => - REF(Pos(x.position.start, x.position.end), x) - } + def curlyBracesP[A: P]: P[EXPR] = P("{" ~ comment ~ baseExpr ~ comment ~/ "}") def lfunP[A: P]: P[REF] = P(correctLFunName).map { x => REF(Pos(x.position.start, x.position.end), x) @@ -168,13 +181,13 @@ object Parser { def functionCallArgs[A: P]: P[Seq[EXPR]] = comment ~ baseExpr.rep(0, comment ~ "," ~ comment) ~ comment - def maybeFunctionCallP[A: P]: P[EXPR] = (Index ~~ lfunP ~~ P("(" ~/ functionCallArgs ~ ")").? ~~ Index).map { + def functionCallOrRef[A: P]: P[EXPR] = (Index ~~ lfunP ~~ P("(" ~ functionCallArgs.opaque("""")"""") ~/ ")").? ~~ Index).map { case (start, REF(_, functionName, _, _), Some(args), accessEnd) => FUNCTION_CALL(Pos(start, accessEnd), functionName, args.toList) case (_, id, None, _) => id } def foldMacroP[A: P]: P[EXPR] = - (Index ~~ P("FOLD<") ~~ Index ~~ digit.repX(1).! ~~ Index ~~ ">(" ~/ baseExpr ~ "," ~ baseExpr ~ "," ~ lfunP ~ ")" ~~ Index) + (Index ~~ P("FOLD<") ~~ Index ~~ digit.repX(1).! ~~ Index ~~ ">(" ~/ baseExpr ~ "," ~ baseExpr ~ "," ~ lfunP ~/ ")" ~~ Index) .map { case (start, limStart, limit, limEnd, list, acc, f, end) => val lim = limit.toInt if (lim < 1) @@ -185,7 +198,7 @@ object Parser { FOLD(Pos(start, end), lim, list, acc, f) } - def list[A: P]: P[EXPR] = (Index ~~ P("[") ~ functionCallArgs ~ P("]") ~~ Index).map { case (s, e, f) => + def list[A: P]: P[EXPR] = (Index ~~ P("[") ~/ functionCallArgs ~ P("]") ~~ Index).map { case (s, e, f) => val pos = Pos(s, f) e.foldRight(REF(pos, PART.VALID(pos, "nil")): EXPR) { (v, l) => FUNCTION_CALL(pos, PART.VALID(pos, "cons"), List(v, l)) @@ -198,7 +211,7 @@ object Parser { max = ContractLimits.MaxTupleSize ) ~ comment - def bracesOrTuple[A: P]: P[EXPR] = (Index ~~ P("(") ~ bracedArgs ~ P(")") ~~ Index).map { + def bracesOrTuple[A: P]: P[EXPR] = (Index ~~ "(" ~ bracedArgs ~/ ")" ~~ Index).map { case (_, Seq(expr), _) => expr case (s, elements, f) => FUNCTION_CALL( @@ -211,7 +224,7 @@ object Parser { def extractableAtom[A: P]: P[EXPR] = P( curlyBracesP | bracesOrTuple | byteVectorP | stringP | numberP | trueP | falseP | list | - maybeFunctionCallP + functionCallOrRef ) sealed trait Accessor @@ -227,14 +240,15 @@ object Parser { val KnownMethods: Set[String] = Set(ExactAs, As) } - def singleTypeP[A: P]: P[Single] = (anyVarName ~~ ("[" ~~ Index ~ unionTypeP ~ Index ~~ "]").?).map { case (t, param) => + def singleTypeP[A: P]: P[Single] = (anyVarName() ~~ ("[" ~~ Index ~ unionTypeP ~ Index ~~/ "]").?).map { case (t, param) => Single(t, param.map { case (start, param, end) => VALID(Pos(start, end), param) }) } def unionTypeP[A: P]: P[Type] = (Index ~ P("Any") ~ Index).map { case (start, end) => AnyType(Pos(start, end)) } | P(singleTypeP | tupleTypeP) .rep(1, comment ~ "|" ~ comment) - .map(Union.apply) + .map(Union(_)) + def tupleTypeP[A: P]: P[Tuple] = ("(" ~ P(unionTypeP).rep( @@ -242,23 +256,32 @@ object Parser { comment ~ "," ~ comment, ContractLimits.MaxTupleSize ) - ~ ")") + ~/ ")") .map(Tuple) def funcP(implicit c: fastparse.P[Any]): P[FUNC] = { - def funcname(implicit c: fastparse.P[Any]) = anyVarName - def argWithType(implicit c: fastparse.P[Any]) = anyVarName ~ ":" ~ unionTypeP ~ comment - def args(implicit c: fastparse.P[Any]) = "(" ~ comment ~ argWithType.rep(0, "," ~ comment) ~ ")" ~ comment - def funcHeader(implicit c: fastparse.P[Any]) = - Index ~~ "func" ~ funcname ~ comment ~ args ~ "=" ~ P(singleBaseExpr | ("{" ~ comment ~ baseExpr ~ "}")) ~~ Index - funcHeader.map { case (start, name, args, expr, end) => - FUNC(Pos(start, end), expr, name, args) + def funcName = anyVarName(check = true) + def funcKWAndName = "func" ~~ ((&(spaces) ~ funcName) | (&(spaces | "(") ~~/ Fail).opaque("function name")) + def argWithType = anyVarName(check = true) ~/ ":" ~/ unionTypeP ~ comment + def args(min: Int) = "(" ~ comment ~ argWithType.rep(min, "," ~ comment) ~ ")" ~ comment + def funcBody = singleBaseExpr + def correctFunc = Index ~~ funcKWAndName ~ comment ~/ args(min = 0) ~ ("=" ~ funcBody | "=" ~/ Fail.opaque("function body")) ~~ Index + def noKeyword = { + def noArgs = "(" ~ comment ~ ")" ~ comment + def validName = NoCut(funcName).filter(_.isInstanceOf[VALID[_]]) + def argsOrEqual = (NoCut(args(min = 1)) ~ "=".?) | (noArgs ~ "=" ~~ !"=") + (validName ~ comment ~ argsOrEqual ~/ funcBody.? ~~ Fail) + .asInstanceOf[P[Nothing]] + .opaque(""""func" keyword""") } + (noKeyword | correctFunc) + .map { case (start, name, args, expr, end) => FUNC(Pos(start, end), expr, name, args) } } - def annotationP[A: P]: P[ANNOTATION] = (Index ~~ "@" ~ anyVarName ~ comment ~ "(" ~ comment ~ anyVarName.rep(0, ",") ~ comment ~ ")" ~~ Index).map { - case (start, name: PART[String], args: Seq[PART[String]], end) => ANNOTATION(Pos(start, end), name, args) - } + def annotationP[A: P]: P[ANNOTATION] = + (Index ~~ "@" ~ anyVarName() ~ comment ~ "(" ~ comment ~ anyVarName().rep(0, ",") ~ comment ~/ ")" ~~ Index).map { + case (start, name: PART[String], args: Seq[PART[String]], end) => ANNOTATION(Pos(start, end), name, args) + } def annotatedFunc[A: P]: P[ANNOTATEDFUNC] = (Index ~~ annotationP.rep(1) ~ comment ~ funcP ~~ Index).map { case (start, as, f, end) => ANNOTATEDFUNC(Pos(start, end), as, f) @@ -287,7 +310,7 @@ object Parser { } def restMatchCaseInvalidP(implicit c: fastparse.P[Any]): P[String] = P((!P("=>") ~~ AnyChar.!).repX.map(_.mkString)) - def varDefP(implicit c: fastparse.P[Any]): P[Option[PART[String]]] = (anyVarName ~~ !("'" | "(")).map(Some(_)) | P("_").!.map(_ => None) + def varDefP(implicit c: fastparse.P[Any]): P[Option[PART[String]]] = (NoCut(anyVarName()) ~~ !("'" | "(")).map(Some(_)) | P("_").!.map(_ => None) def typesDefP(implicit c: fastparse.P[Any]) = ( @@ -299,8 +322,8 @@ object Parser { def pattern(implicit c: fastparse.P[Any]): P[Pattern] = (varDefP ~ comment ~ typesDefP).map { case (v, t) => TypedVar(v, t) } | - (Index ~ "(" ~ pattern.rep(min = 2, sep = ",") ~ ")" ~ Index).map(p => TuplePat(p._2, Pos(p._1, p._3))) | - (Index ~ anyVarName ~ "(" ~ (anyVarName ~ "=" ~ pattern).rep(sep = ",") ~ ")" ~ Index) + (Index ~ "(" ~ pattern.rep(min = 2, sep = ",") ~/ ")" ~ Index).map(p => TuplePat(p._2, Pos(p._1, p._3))) | + (Index ~ anyVarName() ~ "(" ~ (anyVarName() ~ "=" ~ pattern).rep(sep = ",") ~ ")" ~ Index) .map(p => ObjPat(p._3.map(kp => (PART.toOption(kp._1).get, kp._2)).toMap, Single(p._2, None), Pos(p._1, p._4))) | (Index ~ baseExpr.rep(min = 1, sep = "|") ~ Index).map(p => ConstsPat(p._2, Pos(p._1, p._3))) @@ -352,30 +375,31 @@ object Parser { case (start, e, cases, end) => MATCH(Pos(start, end), e, cases.toList) } - def accessorName(implicit c: fastparse.P[Any]): P[PART[String]] = { + def accessOrName(implicit c: fastparse.P[Any]): P[PART[String]] = { def nameP(implicit c: fastparse.P[Any]) = (char | "_") ~~ ("_".? ~~ (digit | char)).repX() genericVarName(nameP(_)) } def genericMethodName(implicit c: fastparse.P[Any]): P[PART[String]] = - accessorName.filter { + accessOrName.filter { case VALID(_, name) if GenericMethod.KnownMethods.contains(name) => true case _ => false } def accessP[A: P]: P[(Int, Accessor, Int)] = P( - (("" ~ comment ~ Index ~ "." ~/ comment ~ functionCallOrGetter) ~~ Index) | (Index ~~ "[" ~/ baseExpr.map(ListIndex) ~ "]" ~~ Index) + (("" ~ comment ~ Index ~ "." ~/ comment ~ getterOrOOPCall) ~~ Index) | (Index ~~ "[" ~/ baseExpr.map(ListIndex) ~ "]" ~~ Index) ) - def functionCallOrGetter[A: P]: P[Accessor] = - (genericMethodName ~~/ ("[" ~ unionTypeP ~ "]")).map { case (name, tpe) => + def getterOrOOPCall[A: P]: P[Accessor] = + (genericMethodName ~~/ ("[" ~ unionTypeP ~/ "]")).map { case (name, tpe) => GenericMethod(name, tpe) - } | (accessorName.map(Getter) ~/ comment ~~ ("(" ~/ comment ~ functionCallArgs ~/ comment ~ ")").?).map { case (g @ Getter(name), args) => - args.fold(g: Accessor)(Method(name, _)) + } | (accessOrName.map(Getter) ~/ comment ~~ ("(" ~/ comment ~ functionCallArgs.opaque("""")"""") ~ comment ~/ ")").?).map { + case (g @ Getter(name), args) => + args.fold(g: Accessor)(Method(name, _)) } def maybeAccessP[A: P]: P[EXPR] = - P(Index ~~ extractableAtom ~~ Index ~~ NoCut(accessP).repX) + P(Index ~~ extractableAtom ~~ Index ~~ accessP.repX) .map { case (start, obj, _, accessors) => accessors.foldLeft(obj) { case (e, (accessStart, a, accessEnd)) => a match { @@ -403,51 +427,55 @@ object Parser { } } - private def destructuredTupleValuesP[A: P]: P[Seq[(Int, Option[PART[String]])]] = - P("(") ~ - (Index ~ anyVarName.?).rep( + private def destructuredTupleValuesP[A: P]: P[Seq[(Int, PART[String])]] = + "(" ~ + (Index ~ anyVarName(check = true)).rep( ContractLimits.MinTupleSize, comment ~ "," ~ comment, ContractLimits.MaxTupleSize - ) ~ - P(")") - - private def letNameP[A: P]: P[Seq[(Int, Option[PART[String]])]] = - (Index ~ anyVarName.?).map(Seq(_)) + ) ~/ + ")" + + private def letNameP[A: P]: P[Seq[(Int, PART[String])]] = + (Index ~ anyVarName(check = true)).map(Seq(_)) + + def variableDefP[A: P](key: String): P[Seq[LET]] = { + def letNames = destructuredTupleValuesP | letNameP + def letKWAndNames = key ~~ ((&(spaces) ~ comment ~ letNames ~ comment) | (&(spaces) ~~/ Fail).opaque("variable name")) + def noKeyword = NoCut(letNames).filter(_.exists(_._2.isInstanceOf[VALID[_]])) ~ "=" ~~ !"=" ~/ baseExpr ~~ Fail + def noKeywordP = noKeyword.opaque(""""let" or "strict" keyword""").asInstanceOf[P[Nothing]] + def correctLets = P(Index ~~ letKWAndNames ~/ ("=" ~ baseExpr | "=" ~/ Fail.opaque("let body")) ~~ Index) + (correctLets | noKeywordP) + .map { case (start, names, value, end) => + val pos = Pos(start, end) + if (names.length == 1) + singleLet(pos, names, value) + else + desugaredMultipleLets(pos, names, value) + } + } - def variableDefP[A: P](key: String): P[Seq[LET]] = - P( - Index ~~ key ~~ &( - CharIn(" \t\n\r") - ) ~/ comment ~ (destructuredTupleValuesP | letNameP) ~ comment ~ Index ~ ("=" ~/ Index ~ baseExpr.?).? ~~ Index - ).map { case (start, names, valuePos, valueRaw, end) => - val value = extractValue(valuePos, valueRaw) - val pos = Pos(start, end) - if (names.length == 1) - names.map { case (nameStart, nameRaw) => - val name = extractName(Pos(nameStart, nameStart), nameRaw) - LET(pos, name, value) + private def singleLet(pos: Pos, names: Seq[(Int, PART[String])], value: EXPR) = + names.map { case (_, name) => LET(pos, name, value) } + + private def desugaredMultipleLets[A: P](pos: Pos, names: Seq[(Int, PART[String])], value: EXPR) = { + val exprRefName = "$t0" + s"${pos.start}${pos.end}" + val exprRef = LET(pos, VALID(pos, exprRefName), value) + val tupleValues = + names.zipWithIndex + .map { case ((nameStart, name), i) => + val namePos = Pos(nameStart, nameStart) + val getter = GETTER( + namePos, + REF(namePos, VALID(namePos, exprRefName)), + VALID(namePos, s"_${i + 1}") + ) + LET(pos, name, getter) } - else { - val exprRefName = "$t0" + s"${pos.start}${pos.end}" - val exprRef = LET(pos, VALID(pos, exprRefName), value) - val tupleValues = - names.zipWithIndex - .map { case ((nameStart, nameRaw), i) => - val namePos = Pos(nameStart, nameStart) - val name = extractName(namePos, nameRaw) - val getter = GETTER( - namePos, - REF(namePos, VALID(namePos, exprRefName)), - VALID(namePos, s"_${i + 1}") - ) - LET(pos, name, getter) - } - exprRef +: tupleValues - } - } + exprRef +: tupleValues + } - // Hack to force parse of "\n". Otherwise it is treated as a separator +// Hack to force parse of "\n". Otherwise it is treated as a separator def newLineSep(implicit c: fastparse.P[Any]) = { P(CharsWhileIn(" \t\r").repX ~~ "\n").repX(1) } @@ -469,41 +497,15 @@ object Parser { } } - private def extractName( - namePos: Pos, - nameRaw: Option[PART[String]] - ): PART[String] = - nameRaw.getOrElse(PART.INVALID(namePos, "expected a variable's name")) - - private def extractValue( - valuePos: Int, - valueRaw: Option[(Int, Option[EXPR])] - ): EXPR = - valueRaw - .map { case (pos, expr) => expr.getOrElse(INVALID(Pos(pos, pos), "expected a value's expression")) } - .getOrElse(INVALID(Pos(valuePos, valuePos), "expected a value")) - - def block[A: P]: P[EXPR] = blockOr(INVALID(_, "expected ';'")) - - private def blockOr(otherExpr: Pos => EXPR)(implicit c: fastparse.P[Any]): P[EXPR] = { - def declaration(implicit c: fastparse.P[Any]) = variableDefP("let") | funcP.map(Seq(_)) - - P( - Index ~~ - declaration.rep(1) ~/ - Pass ~~ - ( - ("" ~ ";") ~/ (baseExpr | invalid).? | - newLineSep ~/ (baseExpr | invalid).? | - (Index ~~ CharPred(_ != '\n').repX).map(pos => Some(otherExpr(Pos(pos, pos)))) - ) ~~ - Index - ).map { case (start, declarations, body, end) => - declarations.flatten.reverse - .foldLeft(body.getOrElse(INVALID(Pos(end, end), "expected a body"))) { (acc, l) => - BLOCK(Pos(start, end), l, acc) - } - } + private def block(alternative: => Option[P[EXPR]])(implicit c: fastparse.P[Any]): P[EXPR] = { + def spaceBetween = ("" ~ ";") | newLineSep + def notFound = ("" ~~/ Fail).opaque("expression") + def expr = (spaceBetween ~ baseExpr) | alternative.getOrElse(notFound) + P(Index ~~ declaration.rep(1) ~~/ expr ~~ Index) + .map { case (start, declarations, body, end) => + declarations.flatten.reverse + .foldLeft(body) { (acc, l) => BLOCK(Pos(start, end), l, acc) } + } } def baseAtom[A: P](epn: fastparse.P[Any] => P[EXPR]) = { @@ -511,10 +513,7 @@ object Parser { comment ~ P(foldMacroP | ifP | matchP | ep | maybeAccessP) ~ comment } - def baseExpr[A: P] = P(strictLetBlockP | binaryOp(baseAtom(block(_))(_), opsByPriority)) - - def blockOrDecl[A: P] = baseAtom(blockOr(p => REF(p, VALID(p, "unit")))(_)) - def baseExprOrDecl[A: P] = binaryOp(baseAtom(blockOrDecl(_))(_), opsByPriority) + def baseExpr[A: P] = P(strictLetBlockP | binaryOp(baseAtom(block(None)(_))(_), opsByPriority)) def singleBaseAtom[A: P] = comment ~ @@ -588,13 +587,15 @@ object Parser { }(c) def parseExpr(str: String): Parsed[EXPR] = { - def expr[A: P] = P(Start ~ unusedText ~ (baseExpr | invalid) ~ End) + def expr[A: P] = P(Start ~ unusedText ~ (baseExpr | invalid | ("" ~/ Fail).opaque("result expression")) ~ End) parse(str, expr(_), verboseFailures = true) } - def parseExprOrDecl(str: String): Parsed[EXPR] = { - def e[A: P] = P(Start ~ unusedText ~ (baseExprOrDecl | invalid) ~ End) - parse(str, e(_), verboseFailures = true) + def parseReplExpr(str: String): Parsed[EXPR] = { + def unit[A: P] = Pass(REF(AnyPos, VALID(AnyPos, "unit"))) + def replAtom[A: P] = baseAtom(block(Some(unit))(_)) + def replExpr[A: P] = binaryOp(baseAtom(replAtom(_))(_), opsByPriority) + parse(str, replExpr(_), verboseFailures = true) } def parseContract(str: String): Parsed[DAPP] = { @@ -619,8 +620,7 @@ object Parser { type RemovedCharPos = Pos - def parseExpressionWithErrorRecovery(scriptStr: String): Either[Throwable, (SCRIPT, Option[RemovedCharPos])] = { - + def parseExpressionWithErrorRecovery(scriptStr: String): Either[(String, Int, Int), (SCRIPT, Option[RemovedCharPos])] = { def parse(str: String): Either[Parsed.Failure, SCRIPT] = parseExpr(str) match { case Parsed.Success(resExpr, _) => Right(SCRIPT(resExpr.position, resExpr)) @@ -628,17 +628,15 @@ object Parser { } parseWithError[SCRIPT]( - new StringBuilder(scriptStr), - parse, - SCRIPT(Pos(0, scriptStr.length - 1), INVALID(Pos(0, scriptStr.length - 1), "Parsing failed. Unknown error.")) + scriptStr, + parse ).map { exprAndErrorIndexes => val removedCharPosOpt = if (exprAndErrorIndexes._2.isEmpty) None else Some(Pos(exprAndErrorIndexes._2.min, exprAndErrorIndexes._2.max)) (exprAndErrorIndexes._1, removedCharPosOpt) } } - def parseDAPPWithErrorRecovery(scriptStr: String): Either[Throwable, (DAPP, Option[RemovedCharPos])] = { - + def parseDAPPWithErrorRecovery(scriptStr: String): Either[(String, Int, Int), (DAPP, Option[RemovedCharPos])] = { def parse(str: String): Either[Parsed.Failure, DAPP] = parseContract(str) match { case Parsed.Success(resDAPP, _) => Right(resDAPP) @@ -646,49 +644,48 @@ object Parser { } parseWithError[DAPP]( - new StringBuilder(scriptStr), - parse, - DAPP(Pos(0, scriptStr.length - 1), List.empty, List.empty) + scriptStr, + parse ).map { dAppAndErrorIndexes => val removedCharPosOpt = if (dAppAndErrorIndexes._2.isEmpty) None else Some(Pos(dAppAndErrorIndexes._2.min, dAppAndErrorIndexes._2.max)) (dAppAndErrorIndexes._1, removedCharPosOpt) } } - @tailrec - private def clearChar(source: StringBuilder, pos: Int): Int = { - if (pos >= 0) { - if (" \n\r".contains(source.charAt(pos))) { - clearChar(source, pos - 1) - } else { - source.setCharAt(pos, ' ') - pos - } + private def parseWithError[T]( + source: String, + parse: String => Either[Parsed.Failure, T] + ): Either[(String, Int, Int), (T, Iterable[Int])] = + parse(source) + .map(dApp => (dApp, Nil)) + .leftMap(errorWithPosition(source, _)) + + private val moveRightKeywords = + Seq(""""func"""", """"let"""", " expression", "1 underscore", "end-of-input", "latin charset", "definition") + + private def errorPosition(input: String, f: Failure): (Int, Int) = + if (moveRightKeywords.exists(f.label.contains)) { + val end = input.indexWhere(_.isWhitespace, f.index) + (f.index, if (end == -1) f.index else end) } else { - 0 + val start = + if (input(f.index - 1).isWhitespace) + input.lastIndexWhere(!_.isWhitespace, f.index - 1) + else + input.lastIndexWhere(_.isWhitespace, f.index - 1) + 1 + (start, f.index) } - } - private def parseWithError[T]( - source: StringBuilder, - parse: String => Either[Parsed.Failure, T], - defaultResult: T - ): Either[Throwable, (T, Iterable[Int])] = { - parse(source.toString()) - .map(dApp => (dApp, Nil)) - .left - .flatMap { - case ex: Parsed.Failure => - val errorLastPos = ex.index - val lastRemovedCharPos = clearChar(source, errorLastPos - 1) - val posList = Set(errorLastPos, lastRemovedCharPos) - if (lastRemovedCharPos > 0) { - parseWithError(source, parse, defaultResult) - .map(dAppAndErrorIndexes => (dAppAndErrorIndexes._1, posList ++ dAppAndErrorIndexes._2.toList)) - } else { - Right((defaultResult, posList)) - } - case _ => Left(new Exception("Unknown parsing error.")) - } + private def errorWithPosition(input: String, f: Failure): (String, Int, Int) = { + val (start, end) = errorPosition(input, f) + val expectation = + if (f.label == "end-of-input" || f.label.contains("|") || f.label.contains("~")) + "illegal expression" + else + s"expected ${f.label}" + (s"Parse error: $expectation in $start-$end", start, end) } + + def toString(input: String, f: Failure): String = + errorWithPosition(input, f)._1 } diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/parser/ParserV2.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/parser/ParserV2.scala deleted file mode 100644 index 4908412cc6f..00000000000 --- a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/parser/ParserV2.scala +++ /dev/null @@ -1,525 +0,0 @@ -package com.wavesplatform.lang.v1.parser - -import com.wavesplatform.common.state.ByteStr -import com.wavesplatform.lang.v1.parser.BinaryOperation._ -import com.wavesplatform.lang.v1.parser.Expressions._ -import com.wavesplatform.lang.v1.parser.UnaryOperation._ -import org.parboiled2.{Rule1, _} - -import scala.collection.mutable - -sealed trait Accessor extends Positioned -case class MethodAcc(position: Pos, name: PART[String], args: Seq[EXPR]) extends Accessor -case class GetterAcc(position: Pos, name: PART[String]) extends Accessor -case class ListIndexAcc(position: Pos, index: EXPR) extends Accessor - -case class BinaryOpWithExpr(op: BinaryOperation, expr: EXPR) -case class IdAndTypes(id: PART[String], types: Seq[(PART[String], Option[PART[String]])]) - -class ParserV2(val input: ParserInput) extends Parser { - - private val Global = com.wavesplatform.lang.hacks.Global // Hack for IDEA - - def DAppRoot: Rule1[DAPP] = rule { - push(cursor) ~ WS ~ zeroOrMore(Directive ~ WS) ~ zeroOrMore(WS ~ Decl) ~ zeroOrMore(WS ~ AnnotatedFunc) ~ WS ~ push(cursor) ~ EOI ~> parseDAppRoot _ - } - - def ScriptRoot: Rule1[SCRIPT] = rule { - push(cursor) ~ WS ~ zeroOrMore(Directive ~ WS) ~ zeroOrMore(WS ~ Decl) ~ WS ~ optional(BlockDecExprSep) ~ WS ~ Expr ~ WS ~ push(cursor) ~ EOI ~> parseScriptRoot _ - } - - def Directive: Rule0 = rule { "{-#" ~ WS ~ oneOrMore(noneOf("\n#")) ~ WS ~ "#-}" } - - def Decl: Rule1[Declaration] = rule { Func | Let } - - def AnnotatedFunc: Rule1[ANNOTATEDFUNC] = rule { oneOrMore(Annotation).separatedBy(WS) ~ WS ~ Func ~ push(cursor) ~> parseAnnotatedFunc _ } - def Annotation: Rule1[ANNOTATION] = rule { - push(cursor) ~ "@" ~ IdentifierAtom ~ WS ~ "(" ~ WS ~ zeroOrMore(IdentifierAtom) - .separatedBy(WS ~ "," ~ WS) ~ WS ~ ")" ~ push(cursor) ~> parseAnnotation _ - } - - def Func: Rule1[FUNC] = rule { - push(cursor) ~ "func" ~ WS ~ IdentifierAtom ~ WS ~ "(" ~ WS ~ zeroOrMore(FuncArg) - .separatedBy(WS ~ "," ~ WS) ~ WS ~ ")" ~ WS ~ "=" ~ WS ~ Expr ~ push(cursor) ~> parseFunc _ - } - def FuncArg: Rule1[IdAndTypes] = rule { IdentifierAtom ~ WS ~ ":" ~ WS ~ GenericTypesAtom ~> IdAndTypes } - - def Let: Rule1[LET] = rule { push(cursor) ~ "let" ~ WS ~ IdentifierAtom ~ WS ~ "=" ~ WS ~ Expr ~ push(cursor) ~> parseLet _ } - - def Block: Rule1[EXPR] = rule { push(cursor) ~ "{" ~ zeroOrMore(WS ~ Decl) ~ WS ~ optional(BlockDecExprSep) ~ WS ~ Expr ~ WS ~ "}" ~ push(cursor) ~> parseBlock _ } - def BlockWithoutPar: Rule1[EXPR] = rule { push(cursor) ~ zeroOrMore(WS ~ Decl) ~ WS ~ optional(BlockDecExprSep) ~ WS ~ Expr ~ push(cursor) ~> parseBlock _ } - def BlockDecExprSep: Rule0 = rule { (";" | "\n") /*| fail("Parse Error: expected ';' or '\\n'")*/ } - - def Expr: Rule1[EXPR] = rule { OrOpAtom } - - def OrOpAtom: Rule1[EXPR] = rule { - push(cursor) ~ AndOpAtom ~ zeroOrMore(WS ~ OROP ~ WS ~ AndOpAtom ~> BinaryOpWithExpr) ~ push(cursor) ~> parseBinaryOperationAtom _ - } - def AndOpAtom: Rule1[EXPR] = rule { - push(cursor) ~ EqualityGroupOpAtom ~ zeroOrMore(WS ~ ANDOP ~ WS ~ EqualityGroupOpAtom ~> BinaryOpWithExpr) ~ push(cursor) ~> parseBinaryOperationAtom _ - } - def EqualityGroupOpAtom: Rule1[EXPR] = rule { - push(cursor) ~ CompareGroupOpAtom ~ zeroOrMore(WS ~ EQUALITY_GROUP_OP ~ WS ~ CompareGroupOpAtom ~> BinaryOpWithExpr) ~ push(cursor) ~> parseBinaryOperationAtom _ - } - def CompareGroupOpAtom: Rule1[EXPR] = rule { - push(cursor) ~ ConsOpAtom ~ zeroOrMore(WS ~ COMPARE_GROUP_OP ~ WS ~ ConsOpAtom ~> BinaryOpWithExpr) ~ push(cursor) ~> parseBinaryOperationAtom _ - } - def ConsOpAtom: Rule1[EXPR] = rule { - push(cursor) ~ SumGroupOpAtom ~ zeroOrMore(WS ~ CONSOP ~ WS ~ SumGroupOpAtom ~> BinaryOpWithExpr) ~ push(cursor) ~> parseBinaryOperationAtom _ - } - def SumGroupOpAtom: Rule1[EXPR] = rule { - push(cursor) ~ MultGroupOpAtom ~ zeroOrMore(WS ~ SUM_GROUP_OP ~ WS ~ MultGroupOpAtom ~> BinaryOpWithExpr) ~ push(cursor) ~> parseBinaryOperationAtom _ - } - def MultGroupOpAtom: Rule1[EXPR] = rule { - push(cursor) ~ AtomExpr ~ zeroOrMore(WS ~ MULT_GROUP_OP ~ WS ~ AtomExpr ~> BinaryOpWithExpr) ~ push(cursor) ~> parseBinaryOperationAtom _ - } - def AtomExpr: Rule1[EXPR] = rule { - push(cursor) ~ optional(UNARY_OP) ~ WS ~ (FoldMacro | GettableExpr | IfWithError | Match | ConstAtom) ~ push(cursor) ~> parseAtomExpr _ - } - - def FoldMacro: Rule1[EXPR] = rule { - push(cursor) ~ "FOLD" ~ WS ~ "<" ~ WS ~ capture(Digits) ~ WS ~ ">" ~ WS ~ "(" ~ WS ~ Expr ~ WS ~ "," ~ WS ~ Expr ~ WS ~ "," ~ WS ~ ReferenceAtom ~ WS ~ ")" ~ push( - cursor - ) ~> parseFoldExpr _ - } - - def GettableExpr: Rule1[EXPR] = rule { - (ParExpr | Block | FunctionCall | ReferenceAtom) ~ zeroOrMore(WS ~ (ListAccess | ("." ~ WSO ~ (FunctionCallAccess | IdentifierAtomAccess)))) ~ push( - cursor - ) ~> parseGettableExpr _ - } - def FunctionCallAccess: Rule1[Accessor] = rule { FunctionCall ~ push(cursor) ~> parseFunctionCallAccess _ } - def IdentifierAtomAccess: Rule1[Accessor] = rule { IdentifierAtom ~ push(cursor) ~> parseIdentifierAtomAccess _ } - def ListAccess: Rule1[Accessor] = rule { push(cursor) ~ "[" ~ WS ~ (Expr | ReferenceAtom) ~ WS ~ "]" ~ push(cursor) ~> parseListAccess _ } - - def ParExpr: Rule1[EXPR] = rule { "(" ~ WS ~ Expr ~ WS ~ ")" } - - def FunctionCall: Rule1[FUNCTION_CALL] = rule { - IdentifierAtom ~ WS ~ "(" ~ WS ~ zeroOrMore(Expr).separatedBy(WS ~ "," ~ WS) ~ WS ~ ")" ~ push(cursor) ~> parseFunctionCall _ - } - - def ListAtom: Rule1[EXPR] = rule { - push(cursor) ~ "[" ~ WS ~ zeroOrMore(Expr).separatedBy(WS ~ "," ~ WS) ~ WS ~ "]" ~ push(cursor) ~> parseListAtom _ - } //~ optional(WS ~ ListAccess) - - def IfWithError: Rule1[EXPR] = rule { If | FailedIfWithoutElse } - def If: Rule1[EXPR] = rule { push(cursor) ~ "if" ~ WS ~ Expr ~ WS ~ "then" ~ WS ~ Expr ~ WS ~ "else" ~ WS ~ Expr ~ push(cursor) ~> parseIf _ } - def FailedIfWithoutElse: Rule1[EXPR] = rule { push(cursor) ~ "if" ~ WS ~ Expr ~ WS ~ "then" ~ WS ~ Expr ~ push(cursor) ~> parseFailedIf _ } - - def Match: Rule1[EXPR] = rule { - push(cursor) ~ "match" ~ WS ~ Expr ~ WS ~ "{" ~ oneOrMore(WS ~ MatchCase) ~ WS ~ "}" ~ push(cursor) ~> parseMatch _ - } - def MatchCase: Rule1[MATCH_CASE] = rule { - push(cursor) ~ "case" ~ WS ~ ((IdentifierAtom ~ WS ~ optional(":" ~ WS ~ TypesAtom)) | DefaultMatchCasePart) ~ WS ~ "=>" ~ WS ~ BlockWithoutPar ~ push(cursor) ~> parseMatchCase _ - } - def DefaultMatchCasePart: Rule2[PART[String], Option[Seq[PART[String]]]] = rule { "_"~ push(PART.VALID(Pos(0, 0), "_")) ~ WS ~ optional(":" ~ WS ~ TypesAtom)} - - def OROP: Rule1[BinaryOperation] = rule { "||" ~ push(OR_OP) } - def ANDOP: Rule1[BinaryOperation] = rule { "&&" ~ push(AND_OP) } - - def EQUALITY_GROUP_OP: Rule1[BinaryOperation] = rule { EQOP | NEOP } - def EQOP: Rule1[BinaryOperation] = rule { "==" ~ push(EQ_OP) } - def NEOP: Rule1[BinaryOperation] = rule { "!=" ~ push(NE_OP) } - - def COMPARE_GROUP_OP: Rule1[BinaryOperation] = rule { GTOP | GEOP | LTOP | LEOP } - def GTOP: Rule1[BinaryOperation] = rule { ">" ~ "=".unary_! ~ push(GT_OP) } - def GEOP: Rule1[BinaryOperation] = rule { ">=" ~ push(GE_OP) } - def LTOP: Rule1[BinaryOperation] = rule { "<" ~ "=".unary_! ~ push(LT_OP) } - def LEOP: Rule1[BinaryOperation] = rule { "<=" ~ push(LE_OP) } - - def CONSOP: Rule1[BinaryOperation] = rule { "::" ~ push(CONS_OP) } - - def SUM_GROUP_OP: Rule1[BinaryOperation] = rule { SUMOP | SUBOP } - def SUMOP: Rule1[BinaryOperation] = rule { "+" ~ push(SUM_OP) } - def SUBOP: Rule1[BinaryOperation] = rule { "-" ~ push(SUB_OP) } - - def MULT_GROUP_OP: Rule1[BinaryOperation] = rule { MULOP | DIVOP | MODOP } - def MULOP: Rule1[BinaryOperation] = rule { "*" ~ push(MUL_OP) } - def DIVOP: Rule1[BinaryOperation] = rule { "/" ~ push(DIV_OP) } - def MODOP: Rule1[BinaryOperation] = rule { "%" ~ push(MOD_OP) } - - def UNARY_OP: Rule1[UnaryOperation] = rule { POSITIVEOP | NEGATIVEOP | NOTOP } - def POSITIVEOP: Rule1[UnaryOperation] = rule { "+" ~ push(POSITIVE_OP) } - def NEGATIVEOP: Rule1[UnaryOperation] = rule { "-" ~ push(NEGATIVE_OP) } - def NOTOP: Rule1[UnaryOperation] = rule { "!" ~ push(NOT_OP) } - - def ConstAtom: Rule1[EXPR] = rule { IntegerAtom | StringAtom | ByteVectorAtom | BooleanAtom | ListAtom } - - def IdentifierAtom: Rule1[PART[String]] = rule { - push(cursor) ~ capture((ReservedWords.unary_! ~ Char ~ zeroOrMore(Char | Digit)) | (ReservedWords ~ (Char | Digit) ~ zeroOrMore(Char | Digit))) ~ push(cursor) ~> parseIdentifierAtom _ - } - def ReferenceAtom: Rule1[EXPR] = rule { - push(cursor) ~ capture((ReservedWords.unary_! ~ Char ~ zeroOrMore(Char | Digit)) | (ReservedWords ~ (Char | Digit) ~ zeroOrMore(Char | Digit))) ~ push(cursor) ~> parseReferenceAtom _ - } - - def GenericTypesAtom: Rule1[Seq[(PART[String], Option[PART[String]])]] = rule { oneOrMore(OneGenericTypeAtom).separatedBy(WS ~ "|" ~ WS) } - def TypesAtom: Rule1[Seq[PART[String]]] = rule { oneOrMore(OneTypeAtom).separatedBy(WS ~ "|" ~ WS) } - def OneGenericTypeAtom: Rule1[(PART[String], Option[PART[String]])] = rule { - push(cursor) ~ capture(Char ~ zeroOrMore(Char | Digit)) ~ optional(WS ~ "[" ~ WS ~ OneTypeAtom ~ WS ~ "]" ~ WS) ~ push(cursor) ~> parseGenericTypeAtom _ - } - def OneTypeAtom: Rule1[PART[String]] = rule { push(cursor) ~ capture(Char ~ zeroOrMore(Char | Digit)) ~ push(cursor) ~> parseOneTypeAtom _ } - - def ByteVectorAtom: Rule1[EXPR] = rule { - "base" ~ capture(("58" | "64" | "16")) ~ "'" ~ push(cursor) ~ capture(zeroOrMore(noneOf("\'"))) ~ push(cursor) ~> parseByteVectorAtom _ ~ "'" - } - def ByteBaseChar: Rule0 = rule { LowerChar | UpperChar | Digit | "+" | "/" | "=" } - - def BooleanAtom: Rule1[EXPR] = rule { "true" ~ push(parseTrueAtom(cursor - 4)) | "false" ~ push(parseFalseAtom(cursor - 5)) } - - def StringAtom: Rule1[EXPR] = rule { "\"" ~ push(cursor) ~ zeroOrMore(UnicodeCharAtom | EscapedCharAtom | CharAtom) ~ push(cursor) ~> parseStringAtom _ ~ "\"" } - def UnicodeCharAtom: Rule1[String] = rule { "\\u" ~ optional(capture((1 to 4).times(Digit | Char))) ~> parseUnicodeCharAtom _ } - def EscapedCharAtom: Rule1[String] = rule { capture("\\" ~ ANY) } - def CharAtom: Rule1[String] = rule { capture(noneOf("\"")) } - def Char: Rule0 = rule { LowerChar | UpperChar } - def UpperChar: Rule0 = rule { CharPredicate.UpperAlpha } - def LowerChar: Rule0 = rule { CharPredicate.LowerAlpha } - - // as in old parser: "+ 1" error rule{group(optional(anyOf("+-")) ~ WS ~ oneOrMore(Digit)) ~> parseIntegerAtom} - def IntegerAtom: Rule1[EXPR] = rule { push(cursor) ~ capture(Digits) ~ push(cursor) ~> parseIntegerAtom _ } - def Digits: Rule0 = rule { oneOrMore(CharPredicate.Digit) } - def Digit: Rule0 = rule { CharPredicate.Digit } - - def Comment: Rule0 = rule { "#" ~ noneOf("-") ~ noneOf("}") ~ zeroOrMore(noneOf("\n")) ~ ("\n" | EOI) } - def WhiteSpace: Rule0 = rule { zeroOrMore(anyOf(" \n\r\t\f") | Comment) } - def WS: Rule0 = WhiteSpace - def WSO: Rule0 = rule { zeroOrMore(" ") } - - def ReservedWords: Rule0 = rule { "let" | "base16" | "base58" | "base64" | "true" | "false" | "if" | "then" | "else" | "match" | "case" | "func" } - - def parseDAppRoot(startPos: Int, decList: Seq[Declaration], annFuncList: Seq[ANNOTATEDFUNC], endPos: Int): DAPP = { - DAPP(Pos(startPos, endPos), decList.toList, annFuncList.toList) - } - - def parseScriptRoot(startPos: Int, decList: Seq[Declaration], expr: EXPR, endPos: Int): SCRIPT = { - val resExpr = decList.foldRight(expr) { (dec, combExpr) => - BLOCK( - Pos(dec.position.start, expr.position.end), - dec, - combExpr - ) - } - SCRIPT(Pos(startPos, endPos), resExpr) - } - - def parseBlock(startPos: Int, decList: Seq[Declaration], expr: EXPR, endPos: Int): EXPR = { - decList.foldRight(expr) { (dec, resultBlock) => - BLOCK(dec.position, dec, resultBlock) - } - } - - def parseAnnotatedFunc(annotationList: Seq[ANNOTATION], func: FUNC, endPos: Int): ANNOTATEDFUNC = { - ANNOTATEDFUNC(Pos(annotationList.head.position.start, endPos), annotationList, func) - } - - def parseAnnotation(startPos: Int, name: PART[String], args: Seq[PART[String]], endPos: Int): ANNOTATION = { - ANNOTATION(Pos(startPos, endPos), name, args) - } - - def parseFunc(startPos: Int, name: PART[String], argAndTypesList: Seq[IdAndTypes], expr: EXPR, endPos: Int): FUNC = { - FUNC( - Pos(startPos, endPos), - expr, - name, - argAndTypesList.map(el => (el.id, Union(el.types.map { - case (name, None) => Single(name, None) - case (name, Some(PART.INVALID(p, m))) => Single(name, Some(PART.INVALID(p, m))) - case (name, Some(a@PART.VALID(p, _))) => Single(name, Some(PART.VALID(p, Single(a, None)))) - }))) - ) - } - - def parseLet(startPos: Int, name: PART[String], value: EXPR, endPos: Int): LET = { - LET(Pos(startPos, endPos), name, value) - } - - def parseFoldExpr(startPos: Int, limitNumStr: String, list: EXPR, acc: EXPR, f: EXPR, endPos: Int): EXPR = { - val limit = limitNumStr.toInt - val pos = Pos(startPos, endPos) - FOLD(pos, limit, list, acc, f.asInstanceOf[REF]) - } - - def parseGettableExpr(expr: EXPR, accessors: Seq[Accessor], endPos: Int): EXPR = { - val res = accessors.foldLeft(expr) { (resExpr, accessor) => - accessor match { - case GetterAcc(pos, name) => GETTER(Pos(resExpr.position.start, pos.end), resExpr, name) - case MethodAcc(pos, name, args) => FUNCTION_CALL(Pos(resExpr.position.start, pos.end), name, (resExpr :: args.toList)) - case ListIndexAcc(pos, index) => - FUNCTION_CALL(Pos(resExpr.position.start, pos.end), PART.VALID(Pos(0, 0), "getElement"), List(resExpr, index)) - } - } - res - } - - def parseFunctionCallAccess(funcCall: FUNCTION_CALL, endPos: Int): Accessor = { - val pos = Pos(funcCall.position.start, endPos) - MethodAcc(pos, funcCall.name, funcCall.args) - } - - def parseIdentifierAtomAccess(id: PART[String], endPos: Int): Accessor = { - GetterAcc(Pos(id.position.start, endPos), id) - } - - def parseFunctionCall(id: PART[String], args: Seq[EXPR], endPos: Int): FUNCTION_CALL = { - FUNCTION_CALL(Pos(id.position.start, endPos), id, args.toList) - } - - def parseListAccess(startPos: Int, accessExpr: EXPR, endPos: Int): Accessor = { - ListIndexAcc(Pos(startPos, endPos), accessExpr) - } - - def parseListAtom(startPos: Int, elements: Seq[EXPR], endPos: Int): EXPR = { - val pos = Pos(startPos, endPos) - elements.foldRight(REF(pos, PART.VALID(pos, "nil")): EXPR) { (resultExpr, element) => - FUNCTION_CALL(pos, PART.VALID(pos, "cons"), List(resultExpr, element)) - } - } - - def parseIf(startPos: Int, cond: EXPR, ifTrue: EXPR, ifFalse: EXPR, endPos: Int): EXPR = { - IF(Pos(startPos, endPos), cond, ifTrue, ifFalse) - } - - def parseFailedIf(startPos: Int, cond: EXPR, ifTrue: EXPR, endPos: Int): EXPR = { - INVALID(Pos(startPos, endPos), "Incomplete if-then-else statement.") - } - - def parseMatch(startPos: Int, expr: EXPR, cases: Seq[MATCH_CASE], endPos: Int): EXPR = { - Expressions.MATCH(Pos(startPos, endPos), expr, cases) - } - - def parseMatchCase(startPos: Int, id: PART[String], types: Option[Seq[PART[String]]], expr: EXPR, endPos: Int): MATCH_CASE = { - val newVarName = id match { - case PART.VALID(pos, "_") => None - case _ => Some(id) - } - MATCH_CASE(Pos(startPos, endPos), TypedVar(newVarName, Union(types.getOrElse(Seq.empty).map(Single(_, None)))), expr) - } - - def parseBinaryOperationAtom(startPos: Int, leftExpr: EXPR, opAndExprList: Seq[BinaryOpWithExpr], endPos: Int): EXPR = { - opAndExprList.foldLeft(leftExpr) { (exprLeft: EXPR, opAndExprRight: BinaryOpWithExpr) => - { - val pos = Pos(exprLeft.position.start, opAndExprRight.expr.position.end) - opAndExprRight.op match { - case LT_OP => BINARY_OP(pos, opAndExprRight.expr, GT_OP, exprLeft) - case LE_OP => BINARY_OP(pos, opAndExprRight.expr, GE_OP, exprLeft) - case CONS_OP => FUNCTION_CALL(pos, PART.VALID(pos, "cons"), List(exprLeft, opAndExprRight.expr)) - case _ => BINARY_OP(pos, exprLeft, opAndExprRight.op, opAndExprRight.expr) - } - } - } - } - - def parseAtomExpr(startPos: Int, unOperationOpt: Option[UnaryOperation], expr: EXPR, endPos: Int): EXPR = { - unOperationOpt match { - case Some(POSITIVE_OP) | None => expr - case Some(op) => FUNCTION_CALL(Pos(startPos, endPos), PART.VALID(Pos(startPos, endPos), op.func), List(expr)) - } - } - - private def validateIdentifierStr(startPos: Int, endPos: Int, idStr: String): PART[String] = { - if (reservedWords.contains(idStr)) { - PART.INVALID(Pos(startPos, endPos), s"keywords are restricted: $idStr") - } else { - PART.VALID(Pos(startPos, endPos), idStr) - } - } - - def parseIdentifierAtom(startPos: Int, idStr: String, endPos: Int): PART[String] = { - validateIdentifierStr(startPos, endPos, idStr) - } - - def parseReferenceAtom(startPos: Int, refName: String, endPos: Int): EXPR = { - REF(Pos(startPos, endPos), validateIdentifierStr(startPos, endPos, refName)) - } - - def parseTypesAtom(argTypeList: List[PART[String]]): List[PART[String]] = { - argTypeList - } - - def parseGenericTypeAtom(startPos: Int, genericTypeName: String, typeName: Option[PART[String]], endPos: Int): (PART[String], Option[PART[String]]) = { - (PART.VALID(Pos(startPos, endPos), genericTypeName), typeName) - } - - def parseOneTypeAtom(startPos: Int, typeName: String, endPos: Int): PART[String] = { - PART.VALID(Pos(startPos, endPos), typeName) - } - - def parseByteVectorAtom(base: String, startPos: Int, byteStr: String, endPos: Int): EXPR = { - val decoded = base match { - case "16" => Global.base16Decode(byteStr, checkLength = false) - case "58" => Global.base58Decode(byteStr) - case "64" => Global.base64Decode(byteStr) - case _ => Left("Wrong input around 'base...' construction") - } - val result = decoded match { - case Left(err) => CONST_BYTESTR(Pos(startPos, endPos), PART.INVALID(Pos(startPos, endPos), err.toString)) - case Right(r) => CONST_BYTESTR(Pos(startPos, endPos), PART.VALID(Pos(startPos, endPos), ByteStr(r))) - } - result - } - - def parseTrueAtom(startPos: Int): EXPR = { - TRUE(Pos(startPos, startPos + 4)) - } - - def parseFalseAtom(startPos: Int): EXPR = { - FALSE(Pos(startPos, startPos + 5)) - } - - def parseStringAtom(startPos: Int, charList: Seq[String], endPos: Int): EXPR = { - - def validateStringParts(): (String, List[String]) = { - - var errors = new mutable.ListBuffer[String]() - val consumedString = new StringBuilder() - - charList.foreach { ch => - if (ch.startsWith("\\u")) { - if (ch.length == 6) { - val hexCode = ch.drop(2) - try { - val int = Integer.parseInt(hexCode, 16) - val unicodeSymbol = new String(Character.toChars(int)) - consumedString.append(unicodeSymbol) - } catch { - case _: NumberFormatException => - consumedString.append(ch) - errors :+= s"can't parse '$hexCode' as HEX string in '$ch'" - case _: IllegalArgumentException => - consumedString.append(ch) - errors :+= s"invalid UTF-8 symbol: '$ch'" - } - } else { - consumedString.append(ch) - errors :+= s"incomplete UTF-8 symbol definition: '$ch'" - } - } else if (ch.startsWith("\\")) { - if (ch.length == 2) { - consumedString.append(ch(1) match { - case 'b' => "\b" - case 'f' => "\f" - case 'n' => "\n" - case 'r' => "\r" - case 't' => "\t" - case '\\' => "\\" - case '"' => "\"" - case _ => - errors :+= s"""unknown escaped symbol: '$ch'. The valid are \b, \f, \n, \r, \t""" - ch - }) - } else { - consumedString.append(ch) - errors :+= s"""invalid escaped symbol: '$ch'. The valid are \b, \f, \n, \r, \t""" - } - } else { - consumedString.append(ch) - } - } - (consumedString.toString(), errors.toList) - } - - val pos = Pos(startPos, endPos) - - val resPart = validateStringParts() match { - case (resStr, Nil) => PART.VALID(pos, resStr) - case (_, errors) => PART.INVALID(pos, errors.mkString("; ")) - } - - CONST_STRING(pos, resPart) - } - - def parseUnicodeCharAtom(unicodeOpt: Option[String]): String = { - s"\\u${unicodeOpt.getOrElse("")}" - } - - def parseIntegerAtom(startPos: Int, numberStr: String, endPos: Int): EXPR = { - CONST_LONG(Pos(startPos, endPos), numberStr.toLong) - } - - private val reservedWords = Set("let", "base16", "base58", "base64", "true", "false", "if", "then", "else", "match", "case", "func") -} - -object ParserV2 { - - def tmpParseUnicodeChar(str: String) = { - new ParserV2(str).StringAtom.run().toEither - } - - type RemovedCharPos = Pos - - final case class ParsingError(start: Int, end: Int, message: String) - - def parseExpression(scriptStr: String): Either[Throwable, (SCRIPT, Option[RemovedCharPos])] = { - - def parse(str: String): Either[Throwable, SCRIPT] = new ParserV2(str).ScriptRoot.run().toEither - - parseWithError[SCRIPT]( - new StringBuilder(scriptStr), - parse, - SCRIPT(Pos(0, scriptStr.length - 1), INVALID(Pos(0, scriptStr.length - 1), "Parsing failed. Unknown error.")) - ).map { - exprAndErrorIndexes => - val removedCharPosOpt = if (exprAndErrorIndexes._2.isEmpty) None else Some(Pos(exprAndErrorIndexes._2.min, exprAndErrorIndexes._2.max)) - (exprAndErrorIndexes._1, removedCharPosOpt) - } - } - - def parseDAPP(scriptStr: String): Either[Throwable, (DAPP, Option[RemovedCharPos])] = { - - def parse(str: String): Either[Throwable, DAPP] = new ParserV2(str).DAppRoot.run().toEither - - parseWithError[DAPP]( - new StringBuilder(scriptStr), - parse, - DAPP(Pos(0, scriptStr.length - 1), List.empty, List.empty) - ).map { - dAppAndErrorIndexes => - val removedCharPosOpt = if (dAppAndErrorIndexes._2.isEmpty) None else Some(Pos(dAppAndErrorIndexes._2.min, dAppAndErrorIndexes._2.max)) - (dAppAndErrorIndexes._1, removedCharPosOpt) - } - } - - private def clearChar(source: StringBuilder, pos: Int): Int = { - if (pos >= 0) { - if (" \n\r".contains(source.charAt(pos))) { - clearChar(source, pos - 1) - } else { - source.setCharAt(pos, ' ') - pos - } - } else { - 0 - } - } - - private def parseWithError[T]( - source: StringBuilder, - parse: String => Either[Throwable, T], - defaultResult: T - ): Either[Throwable, (T, Iterable[Int])] = { - parse(source.toString()) - .map(dApp => (dApp, Nil)) - .left - .flatMap { - case ex: ParseError => { - val errorLastPos = ex.position.index - val errorMsg = (new ParserV2(source.toString())).formatError(ex, new ErrorFormatter(showTraces = false)) - if (errorMsg.contains("Unexpected end of input")) { - source.append("\nfalse") - parseWithError(source, parse, defaultResult) - .map(dAppAndErrorIndexes => (dAppAndErrorIndexes._1, errorLastPos :: dAppAndErrorIndexes._2.toList)) - } else { - val lastRemovedCharPos = clearChar(source, errorLastPos - 1) - val posList = Set(errorLastPos, lastRemovedCharPos) - if (lastRemovedCharPos > 0) { - parseWithError(source, parse, defaultResult) - .map(dAppAndErrorIndexes => (dAppAndErrorIndexes._1, posList ++ dAppAndErrorIndexes._2.toList)) - } else { - Right((defaultResult, posList)) - } - } - - } - case ex: Throwable => Left(ex) - } - } -} diff --git a/lang/testkit/src/main/scala/com/wavesplatform/lang/v1/compiler/TestCompiler.scala b/lang/testkit/src/main/scala/com/wavesplatform/lang/v1/compiler/TestCompiler.scala index ecdea6a28ae..2c6a835924f 100644 --- a/lang/testkit/src/main/scala/com/wavesplatform/lang/v1/compiler/TestCompiler.scala +++ b/lang/testkit/src/main/scala/com/wavesplatform/lang/v1/compiler/TestCompiler.scala @@ -47,6 +47,15 @@ class TestCompiler(version: StdLibVersion) { checkSize = checkSize ).explicitGet() + def compileExpressionE(script: String, allowIllFormedStrings: Boolean = false, checkSize: Boolean = true): Either[String, ExprScript] = + ExpressionCompiler.compile(script, expressionCompilerContext, allowIllFormedStrings).map( s => + ExprScript( + version, + s._1, + checkSize = checkSize + ).explicitGet() + ) + def compileAsset(script: String): Script = ExprScript(version, ExpressionCompiler.compile(script, assetCompilerContext).explicitGet()._1).explicitGet() @@ -59,7 +68,10 @@ class TestCompiler(version: StdLibVersion) { object TestCompiler { private val compilerByVersion = mutable.HashMap.empty[StdLibVersion, TestCompiler] def apply(version: StdLibVersion): TestCompiler = - compilerByVersion.getOrElse(version, compilerByVersion.synchronized { - compilerByVersion.getOrElseUpdate(version, new TestCompiler(version)) - }) + compilerByVersion.getOrElse( + version, + compilerByVersion.synchronized { + compilerByVersion.getOrElseUpdate(version, new TestCompiler(version)) + } + ) } diff --git a/lang/tests/src/test/scala/com/wavesplatform/lang/IntegrationTest.scala b/lang/tests/src/test/scala/com/wavesplatform/lang/IntegrationTest.scala index 74b82644231..fa3e0898174 100755 --- a/lang/tests/src/test/scala/com/wavesplatform/lang/IntegrationTest.scala +++ b/lang/tests/src/test/scala/com/wavesplatform/lang/IntegrationTest.scala @@ -21,7 +21,6 @@ import com.wavesplatform.lang.v1.evaluator.ctx.impl.* import com.wavesplatform.lang.v1.evaluator.ctx.impl.PureContext.MaxListLengthV4 import com.wavesplatform.lang.v1.evaluator.ctx.impl.waves.WavesContext import com.wavesplatform.lang.v1.evaluator.{Contextful, ContextfulVal, EvaluatorV2} -import com.wavesplatform.lang.v1.parser.Parser import com.wavesplatform.lang.v1.traits.Environment import com.wavesplatform.lang.v1.traits.domain.Recipient.{Address, Alias} import com.wavesplatform.lang.v1.traits.domain.{Issue, Lease} @@ -51,8 +50,6 @@ class IntegrationTest extends PropSpec with Inside { version: StdLibVersion, env: C[Id] ): Either[String, T] = { - val untyped = Parser.parseExpr(code).get.value - val f: BaseFunction[C] = NativeFunction( "fn1", @@ -91,7 +88,7 @@ class IntegrationTest extends PropSpec with Inside { ) ) - val compiled = ExpressionCompiler(ctx.compilerContext, untyped) + val compiled = ExpressionCompiler.compile(code, ctx.compilerContext) val evalCtx = ctx.evaluationContext(env).asInstanceOf[EvaluationContext[Environment, Id]] compiled.flatMap(v => EvaluatorV2 @@ -121,7 +118,7 @@ class IntegrationTest extends PropSpec with Inside { | case _ => throw() |} """.stripMargin - eval[EVALUATED](src) should produce("can't parse the expression") + eval[EVALUATED](src) should produce("Parse error: expected expression in 39-40") } property("Exception handling") { @@ -955,7 +952,7 @@ class IntegrationTest extends PropSpec with Inside { | } | WriteSet(result) | } - | + | true """.stripMargin eval[EVALUATED](script, None) should produce("expected: List[T], actual: List[DataEntry]|String") @@ -1081,6 +1078,7 @@ class IntegrationTest extends PropSpec with Inside { | ScriptTransfer(Address(base58'bbbb'), 2, base58'xxx') | ] | let ts = TransferSet(tsArr) + | true """.stripMargin val scriptResultScript = diff --git a/lang/tests/src/test/scala/com/wavesplatform/lang/ParserV2DAppTest.scala b/lang/tests/src/test/scala/com/wavesplatform/lang/ParserV2DAppTest.scala deleted file mode 100644 index 4ac5465f3ea..00000000000 --- a/lang/tests/src/test/scala/com/wavesplatform/lang/ParserV2DAppTest.scala +++ /dev/null @@ -1,325 +0,0 @@ -package com.wavesplatform.lang - -import com.wavesplatform.lang.v1.parser.Expressions.Pos.AnyPos -import com.wavesplatform.lang.v1.parser.Expressions._ -import com.wavesplatform.lang.v1.parser.{Expressions, Parser, ParserV2} -import com.wavesplatform.test._ -import org.scalatest.exceptions.TestFailedException - -class ParserV2DAppTest extends PropSpec { - - private def parse(x: String): DAPP = ParserV2.parseDAPP(x) match { - case Right((parsedScript, _)) => parsedScript - case _ => throw new TestFailedException("Test failed", 0) - } - - private def cleanOffsets(l: LET): LET = - l.copy(Pos(0, 0), name = cleanOffsets(l.name), value = cleanOffsets(l.value)) // , types = l.types.map(cleanOffsets(_)) - - private def cleanOffsets[T](p: PART[T]): PART[T] = p match { - case PART.VALID(_, x) => PART.VALID(AnyPos, x) - case PART.INVALID(_, x) => PART.INVALID(AnyPos, x) - } - - private def cleanOffsets(expr: EXPR): EXPR = expr match { - case x: CONST_LONG => x.copy(position = Pos(0, 0)) - case x: REF => x.copy(position = Pos(0, 0), key = cleanOffsets(x.key)) - case x: CONST_STRING => x.copy(position = Pos(0, 0), value = cleanOffsets(x.value)) - case x: CONST_BYTESTR => x.copy(position = Pos(0, 0), value = cleanOffsets(x.value)) - case x: TRUE => x.copy(position = Pos(0, 0)) - case x: FALSE => x.copy(position = Pos(0, 0)) - case x: BINARY_OP => x.copy(position = Pos(0, 0), a = cleanOffsets(x.a), b = cleanOffsets(x.b)) - case x: IF => x.copy(position = Pos(0, 0), cond = cleanOffsets(x.cond), ifTrue = cleanOffsets(x.ifTrue), ifFalse = cleanOffsets(x.ifFalse)) - case x @ BLOCK(_, l: Expressions.LET, _, _, _) => x.copy(position = Pos(0, 0), let = cleanOffsets(l), body = cleanOffsets(x.body)) - case x: FUNCTION_CALL => x.copy(position = Pos(0, 0), name = cleanOffsets(x.name), args = x.args.map(cleanOffsets(_))) - case _ => throw new NotImplementedError(s"toString for ${expr.getClass.getSimpleName}") - } - - property("simple 1-annotated function") { - val code = - """ - | - | @Ann(foo) - | func bar(arg:Baz) = { - | 3 - | } - | - | - |""".stripMargin - parse(code) shouldBe DAPP( - AnyPos, - List.empty, - List( - ANNOTATEDFUNC( - AnyPos, - List(Expressions.ANNOTATION(AnyPos, PART.VALID(AnyPos, "Ann"), List(PART.VALID(AnyPos, "foo")))), - Expressions.FUNC( - AnyPos, - CONST_LONG(AnyPos, 3), - PART.VALID(AnyPos, "bar"), - List((PART.VALID(AnyPos, "arg"), Single(PART.VALID(AnyPos, "Baz"), None))) - ) - ) - ) - ) - } - - property("simple 2-annotated function") { - val code = - """ - | func foo() = { - | true - | } - | - | @Ann(foo) - | @Ioann(zoo, shazoo) - | func bar(arg:Baz) = { - | 3 - | } - | - | - |""".stripMargin - parse(code) shouldBe DAPP( - AnyPos, - List( - FUNC( - AnyPos, - TRUE(AnyPos), - PART.VALID(AnyPos, "foo"), - List.empty - ) - ), - List( - ANNOTATEDFUNC( - AnyPos, - List( - Expressions.ANNOTATION(AnyPos, PART.VALID(AnyPos, "Ann"), List(PART.VALID(AnyPos, "foo"))), - Expressions.ANNOTATION(AnyPos, PART.VALID(AnyPos, "Ioann"), List(PART.VALID(AnyPos, "zoo"), PART.VALID(AnyPos, "shazoo"))) - ), - Expressions.FUNC( - AnyPos, - CONST_LONG(AnyPos, 3), - PART.VALID(AnyPos, "bar"), - List((PART.VALID(AnyPos, "arg"), Single(PART.VALID(AnyPos, "Baz"), None))) - ) - ) - ) - ) - } - - property("contract script with comment in the beginning") { - val code = - """ - | # comment1 - | # comment2 - | func foo() = { - | true # comment3 - | } - | # comment4 - | # comment5 - | @Ann(foo) - | func bar(arg:Baz) = { - | 3 # comment6 - | } - | - |""".stripMargin - parse(code) shouldBe DAPP( - AnyPos, - List( - FUNC( - AnyPos, - TRUE(AnyPos), - PART.VALID(AnyPos, "foo"), - List.empty - ) - ), - List( - ANNOTATEDFUNC( - AnyPos, - List(Expressions.ANNOTATION(AnyPos, PART.VALID(AnyPos, "Ann"), List(PART.VALID(AnyPos, "foo")))), - Expressions.FUNC( - AnyPos, - CONST_LONG(AnyPos, 3), - PART.VALID(AnyPos, "bar"), - List((PART.VALID(AnyPos, "arg"), Single(PART.VALID(AnyPos, "Baz"), None))) - ) - ) - ) - ) - } - - property("functions without body brackets with comments") { - val code = - """ - | # comment - | func foo() = 42 + 42 - 1 - | - | @Ann(x) - | func bar(arg:ArgType) = foo() # comment - | # comment - | @Ann(y) - | func baz(arg:ArgType) = if (10 < 15) then true else false - | - |""".stripMargin - parse(code) - } - - property("parse directives as comments (ignore)") { - val code = - """ - | # comment - | {-# STDLIB_VERSION 3 #-} - | {-# TEST_TEST 123 #-} - | # comment - | - | @Ann(foo) - | func bar(arg:Baz) = { - | 3 - | } - | - | - |""".stripMargin - parse(code) shouldBe DAPP( - AnyPos, - List.empty, - List( - ANNOTATEDFUNC( - AnyPos, - List(Expressions.ANNOTATION(AnyPos, PART.VALID(AnyPos, "Ann"), List(PART.VALID(AnyPos, "foo")))), - Expressions.FUNC( - AnyPos, - CONST_LONG(AnyPos, 3), - PART.VALID(AnyPos, "bar"), - List((PART.VALID(AnyPos, "arg"), Single(PART.VALID(AnyPos, "Baz"), None))) - ) - ) - ) - ) - } - - property("functions with comment after first body bracket") { - val code = - """ - | - | #@Callable(i) - | func foo() = 42 + 42 - 1 - | - | @Ann(x) - | func bar(arg:ArgType) = { # more comments - | foo() # comment - | } - | - |""".stripMargin - parse(code) - } - - property("contract with comments in different places") { - val code = - """ - | # comment - | {-# STDLIB_VERSION 3 #-} # comment - | {-# TEST_TEST 123 #-} # comment - | # comment - | # comment # comment - | func foo1 # comment - | ( # comment - | ) # comment - | = 42 + 42 - 1 # comment - | # comment - | @Callable(i) # comment - | func foo # comment - | ( # comment - | ) # comment - | = 42 + 42 - 1 # comment - | # comment - | @Ann # comment - | ( # comment - | x # comment - | ) # comment - | func bar # comment - | ( # comment - | arg:ArgType, # comment - | arg2:ArgType # comment - | ) # comment - | = { # more comments - | foo() # comment - | } # comment - | # comment - |""".stripMargin - parse(code) - } - - property("disallow function declarations after annotated funcions.") { - val code = - """ - | # comment - | {-# STDLIB_VERSION 3 #-} - | {-# TEST_TEST 123 #-} - | # comment - | - | @Ann(foo) - | func bar(arg:Baz) = { - | 3 - | } - | - | func baz(arg:Int) = { - | 4 - | } - |""".stripMargin - Parser.parseContract(code).toString.contains("Local functions should be defined before @Callable one") shouldBe true - } - - property("disallow value declarations after annotated funcions.") { - val code = - """ - | # comment - | {-# STDLIB_VERSION 3 #-} - | {-# TEST_TEST 123 #-} - | # comment - | - | @Ann(foo) - | func bar(arg:Baz) = { - | 3 - | } - | - | let baz = 4 - | - |""".stripMargin - Parser.parseContract(code).toString.contains("Local functions should be defined before @Callable one") shouldBe true - } - - property("Unary expr") { - val code = - """{-# STDLIB_VERSION 4 #-} - |{-# SCRIPT_TYPE ACCOUNT #-} - |{-# CONTENT_TYPE DAPP #-} - | - |let a10 = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"] - | - |func deleteEntry(acc: List[DeleteEntry], e: String) = DeleteEntry(e) :: acc - | - |func t() = delateEntry("q") :: FOLD<10>(a10, [], deleteEntry) - | - |@Callable(i) func f() = [] - |""".stripMargin - ParserV2.parseDAPP(code) shouldBe Symbol("right") - } - - property("FOLD expr") { - val code = - """{-# STDLIB_VERSION 4 #-} - |{-# SCRIPT_TYPE ACCOUNT #-} - |{-# CONTENT_TYPE DAPP #-} - | - |let a10 = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"] - | - |func deleteEntry(acc: List[DeleteEntry], e: String) = DeleteEntry(e) :: acc - | - |@Callable(i) func delete100Entries() = FOLD<10>(a10, [], deleteEntry) - | - |@Callable(i) func delete(k: String) = [DeleteEntry(k)] - |""".stripMargin - ParserV2.parseDAPP(code) shouldBe Symbol("right") - } - -} diff --git a/lang/tests/src/test/scala/com/wavesplatform/lang/ParserV2ScriptTest.scala b/lang/tests/src/test/scala/com/wavesplatform/lang/ParserV2ScriptTest.scala deleted file mode 100644 index 472f13c863c..00000000000 --- a/lang/tests/src/test/scala/com/wavesplatform/lang/ParserV2ScriptTest.scala +++ /dev/null @@ -1,1276 +0,0 @@ -package com.wavesplatform.lang - -import com.wavesplatform.common.state.ByteStr -import com.wavesplatform.common.utils.Base58 -import com.wavesplatform.lang.v1.parser.BinaryOperation._ -import com.wavesplatform.lang.v1.parser.Expressions.Pos.AnyPos -import com.wavesplatform.lang.v1.parser.Expressions._ -import com.wavesplatform.lang.v1.parser.{BinaryOperation, Parser, ParserV2} -import com.wavesplatform.test._ -import fastparse.Parsed.Success -import org.scalatest.exceptions.TestFailedException - -class ParserV2ScriptTest extends PropSpec { - - private def parse(x: String): EXPR = ParserV2.parseExpression(x) match { - case Right((parsedScript, _)) => - parsedScript.expr - case _ => throw new TestFailedException("Test failed", 0) - } - - property("priority in binary expressions") { - parse("1 == 0 || 3 == 2") shouldBe BINARY_OP( - AnyPos, - BINARY_OP(AnyPos, CONST_LONG(AnyPos, 1), EQ_OP, CONST_LONG(AnyPos, 0)), - OR_OP, - BINARY_OP(AnyPos, CONST_LONG(AnyPos, 3), EQ_OP, CONST_LONG(AnyPos, 2)) - ) - parse("3 + 2 > 2 + 1") shouldBe BINARY_OP( - AnyPos, - BINARY_OP(AnyPos, CONST_LONG(AnyPos, 3), SUM_OP, CONST_LONG(AnyPos, 2)), - GT_OP, - BINARY_OP(AnyPos, CONST_LONG(AnyPos, 2), SUM_OP, CONST_LONG(AnyPos, 1)) - ) - parse("1 >= 0 || 3 > 2") shouldBe BINARY_OP( - AnyPos, - BINARY_OP(AnyPos, CONST_LONG(AnyPos, 1), GE_OP, CONST_LONG(AnyPos, 0)), - OR_OP, - BINARY_OP(AnyPos, CONST_LONG(AnyPos, 3), GT_OP, CONST_LONG(AnyPos, 2)) - ) - } - - property("bytestr expressions") { - parse("false || sigVerify(base58'333', base58'222', base58'111')") shouldBe BINARY_OP( - AnyPos, - FALSE(AnyPos), - OR_OP, - FUNCTION_CALL( - AnyPos, - PART.VALID(AnyPos, "sigVerify"), - List( - CONST_BYTESTR(AnyPos, PART.VALID(AnyPos, ByteStr(Base58.decode("333")))), - CONST_BYTESTR(AnyPos, PART.VALID(AnyPos, ByteStr(Base58.decode("222")))), - CONST_BYTESTR(AnyPos, PART.VALID(AnyPos, ByteStr(Base58.decode("111")))) - ) - ) - ) - } - - property("valid non-empty base58 definition") { - parse("base58'bQbp'") shouldBe CONST_BYTESTR(AnyPos, PART.VALID(AnyPos, ByteStr("foo".getBytes("UTF-8")))) - } - - property("valid empty base58 definition") { - parse("base58''") shouldBe CONST_BYTESTR(AnyPos, PART.VALID(AnyPos, ByteStr.empty)) - } - - property("invalid base58 definition") { - parse("base58' bQbp'") shouldBe CONST_BYTESTR(AnyPos, PART.INVALID(AnyPos, "can't parse Base58 string")) - } - - property("valid non-empty base64 definition") { - parse("base64'TElLRQ=='") shouldBe CONST_BYTESTR(AnyPos, PART.VALID(AnyPos, ByteStr("LIKE".getBytes("UTF-8")))) - } - - property("valid empty base64 definition") { - parse("base64''") shouldBe CONST_BYTESTR(AnyPos, PART.VALID(AnyPos, ByteStr.empty)) - } - - property("invalid base64 definition") { - parse("base64'mid-size'") shouldBe CONST_BYTESTR(AnyPos, PART.INVALID(AnyPos, "can't parse Base64 string")) - } - - property("valid empty base16 definition") { - parse("base16''") shouldBe CONST_BYTESTR(AnyPos, PART.VALID(AnyPos, ByteStr.empty)) - } - - property("valid non-empty base16 definition") { - parse("base16'0123456789abcdef123456789ABCDEF0ABCDEFfabcde'") shouldBe - CONST_BYTESTR( - AnyPos, - PART.VALID( - AnyPos, - ByteStr( - Array[Short](0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0, 0xAB, 0xCD, 0xEF, 0xfa, 0xbc, - 0xde).map(_.toByte) - ) - ) - ) - } - - property("invalid base16 definition") { - parse("base16'mid-size'") shouldBe CONST_BYTESTR(Pos(7, 15), PART.INVALID(Pos(7, 15), "Unrecognized character: m"), None) - parse("base16'123'") shouldBe CONST_BYTESTR(AnyPos, PART.INVALID(Pos(7, 10), "Invalid input length 3")) - } - - property("literal too long") { - import Global.MaxLiteralLength - val longLiteral = "A" * (MaxLiteralLength + 1) - val to = 8 + MaxLiteralLength - parse(s"base58'$longLiteral'") shouldBe - CONST_BYTESTR(Pos(0, to + 1), PART.INVALID(Pos(8, to), s"base58Decode input exceeds $MaxLiteralLength")) - } - - property("string is consumed fully") { - parse(""" " fooo bar" """) shouldBe CONST_STRING(Pos(1, 17), PART.VALID(Pos(2, 16), " fooo bar")) - } - - property("string literal with unicode chars") { - val stringWithUnicodeChars = "❤✓☀★☂♞☯☭☢€☎∞❄♫\u20BD" - - parse( - s""" - | - | "$stringWithUnicodeChars" - | - """.stripMargin - ) shouldBe CONST_STRING(Pos(3, 20), PART.VALID(Pos(4, 19), stringWithUnicodeChars)) - } - - property("string literal with unicode chars in language") { - parse("\"\\u1234\"") shouldBe CONST_STRING(Pos(0, 8), PART.VALID(Pos(1, 7), "ሴ")) - } - - property("should parse invalid unicode symbols") { - parse("\"\\uqwer\"") shouldBe CONST_STRING( - AnyPos, - PART.INVALID(AnyPos, "can't parse 'qwer' as HEX string in '\\uqwer'") - ) - } - - property("should parse incomplete unicode symbol definition") { - parse("\"\\u12 test\"") shouldBe CONST_STRING(AnyPos, PART.INVALID(AnyPos, "incomplete UTF-8 symbol definition: '\\u12'")) - parse("\"\\u\"") shouldBe CONST_STRING(AnyPos, PART.INVALID(AnyPos, "incomplete UTF-8 symbol definition: '\\u'")) - } - - property("string literal with special symbols") { - parse("\"\\t\\n\\r\\\\\\\"\"") shouldBe CONST_STRING(AnyPos, PART.VALID(AnyPos, "\t\n\r\\\"")) - } - - property("should parse invalid special symbols") { - parse("\"\\ test\"") shouldBe CONST_STRING(AnyPos, PART.INVALID(AnyPos, "unknown escaped symbol: '\\ '. The valid are \b, \f, \n, \r, \t")) - } - - property("block: multiline without ;") { - val s = - """let q = 1 - |c""".stripMargin - parse(s) shouldBe BLOCK( - AnyPos, - LET(AnyPos, PART.VALID(AnyPos, "q"), CONST_LONG(AnyPos, 1)), - REF(AnyPos, PART.VALID(AnyPos, "c")) - ) - } - - property("block: func") { - val s = - """func q(x: Int, y: Boolean) = { 42 } - |c""".stripMargin - parse(s) shouldBe BLOCK( - AnyPos, - FUNC( - AnyPos, - CONST_LONG(AnyPos, 42), - PART.VALID(AnyPos, "q"), - Seq( - (PART.VALID(AnyPos, "x"), Single(PART.VALID(AnyPos, "Int"), None)), - (PART.VALID(AnyPos, "y"), Single(PART.VALID(AnyPos, "Boolean"), None)) - ) - ), - REF(AnyPos, PART.VALID(AnyPos, "c")) - ) - } - - property("block: func with union") { - val s = - """func q(x: Int | String) = { 42 } - |c""".stripMargin - parse(s) shouldBe BLOCK( - AnyPos, - FUNC( - AnyPos, - CONST_LONG(AnyPos, 42), - PART.VALID(AnyPos, "q"), - Seq((PART.VALID(AnyPos, "x"), Union(Seq(Single(PART.VALID(AnyPos, "Int"), None), Single(PART.VALID(AnyPos, "String"), None))))) - ), - REF(AnyPos, PART.VALID(AnyPos, "c")) - ) - } - - property("block: multiline with ; at end of let") { - val s = - """let q = 1; - |c""".stripMargin - parse(s) shouldBe BLOCK( - AnyPos, - LET(AnyPos, PART.VALID(AnyPos, "q"), CONST_LONG(AnyPos, 1)), - REF(AnyPos, PART.VALID(AnyPos, "c")) - ) - } - - property("block: multiline with ; at start of body") { - val s = - """let q = 1 - |; c""".stripMargin - parse(s) shouldBe BLOCK( - AnyPos, - LET(AnyPos, PART.VALID(AnyPos, "q"), CONST_LONG(AnyPos, 1)), - REF(AnyPos, PART.VALID(AnyPos, "c")) - ) - } - - property("block: oneline") { - val s = "let q = 1; c" - parse(s) shouldBe BLOCK( - AnyPos, - LET(AnyPos, PART.VALID(AnyPos, "q"), CONST_LONG(AnyPos, 1)), - REF(AnyPos, PART.VALID(AnyPos, "c")) - ) - } - - ignore("block: invalid") { - val s = "let q = 1 c" - parse(s) shouldBe BLOCK( - AnyPos, - LET(AnyPos, PART.VALID(AnyPos, "q"), CONST_LONG(AnyPos, 1)), - INVALID(AnyPos, "expected ';'") - ) - } - - ignore("should parse a binary operation with block operand") { - val script = - """let x = a && - |let y = 1 - |true - |true""".stripMargin - - parse(script) shouldBe BLOCK( - AnyPos, - LET( - AnyPos, - PART.VALID(AnyPos, "x"), - BINARY_OP( - AnyPos, - REF(AnyPos, PART.VALID(AnyPos, "a")), - AND_OP, - BLOCK(AnyPos, LET(AnyPos, PART.VALID(AnyPos, "y"), CONST_LONG(AnyPos, 1)), TRUE(AnyPos)) - ) - ), - TRUE(AnyPos) - ) - } - - ignore("reserved keywords are invalid variable names in block: if") { - val script = - s"""let if = 1 - |true""".stripMargin - parse(script) shouldBe BLOCK( - AnyPos, - LET(AnyPos, PART.INVALID(AnyPos, "keywords are restricted: if"), CONST_LONG(AnyPos, 1)), - TRUE(AnyPos) - ) - } - - ignore("reserved keywords are invalid variable names in block: let") { - val script = - s"""let let = 1 - |true""".stripMargin - parse(script) shouldBe BLOCK( - AnyPos, - LET(AnyPos, PART.INVALID(AnyPos, "keywords are restricted: let"), CONST_LONG(AnyPos, 1)), - TRUE(AnyPos) - ) - } - - List("then", "else", "true").foreach { keyword => - ignore(s"reserved keywords are invalid variable names in block: $keyword") { - val script = - s"""let ${keyword.padTo(4, " ").mkString} = 1 - |true""".stripMargin - parse(script) shouldBe BLOCK( - AnyPos, - LET(AnyPos, PART.INVALID(AnyPos, s"keywords are restricted: $keyword"), CONST_LONG(AnyPos, 1)), - TRUE(AnyPos) - ) - } - } - - ignore("reserved keywords are invalid variable names in block: false") { - val script = - s"""let false = 1 - |true""".stripMargin - parse(script) shouldBe BLOCK( - AnyPos, - LET(AnyPos, PART.INVALID(AnyPos, "keywords are restricted: false"), CONST_LONG(AnyPos, 1)), - TRUE(AnyPos) - ) - } - - ignore("reserved keywords are invalid variable names in expr: let") { - val script = "let + 1" - parse(script) shouldBe BLOCK( - AnyPos, - LET(AnyPos, PART.INVALID(AnyPos, "expected a variable's name"), INVALID(AnyPos, "expected a value")), - INVALID(AnyPos, "expected ';'") - ) - } - - ignore("reserved keywords are invalid variable names in expr: if") { - val script = "if + 1" - parse(script) shouldBe BINARY_OP( - AnyPos, - IF(AnyPos, INVALID(AnyPos, "expected a condition"), INVALID(AnyPos, "expected a true branch"), INVALID(AnyPos, "expected a false branch")), - BinaryOperation.SUM_OP, - CONST_LONG(AnyPos, 1) - ) - } - - ignore("reserved keywords are invalid variable names in expr: then") { - val script = "then + 1" - parse(script) shouldBe BINARY_OP( - AnyPos, - IF( - AnyPos, - INVALID(AnyPos, "expected a condition"), - INVALID(AnyPos, "expected a true branch's expression"), - INVALID(AnyPos, "expected a false branch") - ), - BinaryOperation.SUM_OP, - CONST_LONG(AnyPos, 1) - ) - } - - ignore("reserved keywords are invalid variable names in expr: else") { - val script = "else + 1" - parse(script) shouldBe BINARY_OP( - AnyPos, - IF( - AnyPos, - INVALID(AnyPos, "expected a condition"), - INVALID(AnyPos, "expected a true branch"), - INVALID(AnyPos, "expected a false branch's expression") - ), - BinaryOperation.SUM_OP, - CONST_LONG(AnyPos, 1) - ) - } - - property("multisig sample") { - val script = - """ - | - |let A = base58'PK1PK1PK1PK1PK1' - |let B = base58'PK2PK2PK2PK2PK2' - |let C = base58'PK3PK3PK3PK3PK3' - | - |let W = tx.bodyBytes - |let P = tx.PROOF - |let V = sigVerify(W,P,A) - | - |let AC = if(V) then 1 else 0 - |let BC = if(sigVerify(tx.bodyBytes,tx.PROOF,B)) then 1 else 0 - |let CC = if(sigVerify(tx.bodyBytes,tx.PROOF,C)) then 1 else 0 - | - | AC + BC+ CC >= 2 - | - """.stripMargin - parse(script) // gets parsed, but later will fail on type check! - } - - property("function call") { - parse("FOO(1,2)".stripMargin) shouldBe FUNCTION_CALL(AnyPos, PART.VALID(AnyPos, "FOO"), List(CONST_LONG(AnyPos, 1), CONST_LONG(AnyPos, 2))) - parse("FOO(X)".stripMargin) shouldBe FUNCTION_CALL(AnyPos, PART.VALID(AnyPos, "FOO"), List(REF(AnyPos, PART.VALID(AnyPos, "X")))) - } - - property("isDefined") { - parse("isDefined(X)") shouldBe FUNCTION_CALL(AnyPos, PART.VALID(AnyPos, "isDefined"), List(REF(AnyPos, PART.VALID(AnyPos, "X")))) - } - - property("extract") { - parse("if(isDefined(X)) then extract(X) else Y") shouldBe IF( - AnyPos, - FUNCTION_CALL(AnyPos, PART.VALID(AnyPos, "isDefined"), List(REF(AnyPos, PART.VALID(AnyPos, "X")))), - FUNCTION_CALL(AnyPos, PART.VALID(AnyPos, "extract"), List(REF(AnyPos, PART.VALID(AnyPos, "X")))), - REF(AnyPos, PART.VALID(AnyPos, "Y")) - ) - } - - property("getter: spaces from left") { - parse("xxx .yyy") shouldBe GETTER(AnyPos, REF(AnyPos, PART.VALID(AnyPos, "xxx")), PART.VALID(AnyPos, "yyy")) - } - - property("getter: spaces from right") { - parse("xxx. yyy") shouldBe GETTER(AnyPos, REF(AnyPos, PART.VALID(AnyPos, "xxx")), PART.VALID(AnyPos, "yyy")) - } - - property("getter: no spaces") { - parse("xxx.yyy") shouldBe GETTER(AnyPos, REF(AnyPos, PART.VALID(AnyPos, "xxx")), PART.VALID(AnyPos, "yyy")) - } - - property("getter on function result") { - parse("xxx(yyy).zzz") shouldBe GETTER( - AnyPos, - FUNCTION_CALL(AnyPos, PART.VALID(AnyPos, "xxx"), List(REF(AnyPos, PART.VALID(AnyPos, "yyy")))), - PART.VALID(AnyPos, "zzz") - ) - } - - property("getter on round braces") { - parse("(xxx(yyy)).zzz") shouldBe GETTER( - AnyPos, - FUNCTION_CALL(AnyPos, PART.VALID(AnyPos, "xxx"), List(REF(AnyPos, PART.VALID(AnyPos, "yyy")))), - PART.VALID(AnyPos, "zzz") - ) - } - - property("getter on curly braces") { - parse("{xxx(yyy)}.zzz") shouldBe GETTER( - AnyPos, - FUNCTION_CALL(AnyPos, PART.VALID(AnyPos, "xxx"), List(REF(AnyPos, PART.VALID(AnyPos, "yyy")))), - PART.VALID(AnyPos, "zzz") - ) - } - - property("getter on block") { - parse( - """{ - | let yyy = aaa(bbb) - | xxx(yyy) - |}.zzz""".stripMargin - ) shouldBe GETTER( - AnyPos, - BLOCK( - AnyPos, - LET( - AnyPos, - PART.VALID(AnyPos, "yyy"), - FUNCTION_CALL(AnyPos, PART.VALID(AnyPos, "aaa"), List(REF(AnyPos, PART.VALID(AnyPos, "bbb")))) - ), - FUNCTION_CALL(AnyPos, PART.VALID(AnyPos, "xxx"), List(REF(AnyPos, PART.VALID(AnyPos, "yyy")))) - ), - PART.VALID(AnyPos, "zzz") - ) - } - - property("multiple getters") { - parse("x.y.z") shouldBe GETTER(AnyPos, GETTER(AnyPos, REF(AnyPos, PART.VALID(AnyPos, "x")), PART.VALID(AnyPos, "y")), PART.VALID(AnyPos, "z")) - } - - property("array accessor") { - parse("x[0]") shouldBe FUNCTION_CALL(AnyPos, PART.VALID(AnyPos, "getElement"), List(REF(AnyPos, PART.VALID(AnyPos, "x")), CONST_LONG(AnyPos, 0))) - } - - property("multiple array accessors") { - parse("x[0][1]") shouldBe FUNCTION_CALL( - AnyPos, - PART.VALID(AnyPos, "getElement"), - List( - FUNCTION_CALL(AnyPos, PART.VALID(AnyPos, "getElement"), List(REF(AnyPos, PART.VALID(AnyPos, "x")), CONST_LONG(AnyPos, 0))), - CONST_LONG(AnyPos, 1) - ) - ) - } - - property("accessor and getter") { - parse("x[0].y") shouldBe GETTER( - AnyPos, - FUNCTION_CALL(AnyPos, PART.VALID(AnyPos, "getElement"), List(REF(AnyPos, PART.VALID(AnyPos, "x")), CONST_LONG(AnyPos, 0))), - PART.VALID(AnyPos, "y") - ) - } - - property("getter and accessor") { - parse("x.y[0]") shouldBe FUNCTION_CALL( - AnyPos, - PART.VALID(AnyPos, "getElement"), - List( - GETTER(AnyPos, REF(AnyPos, PART.VALID(AnyPos, "x")), PART.VALID(AnyPos, "y")), - CONST_LONG(AnyPos, 0) - ) - ) - } - - property("function call and accessor") { - parse("x(y)[0]") shouldBe FUNCTION_CALL( - AnyPos, - PART.VALID(AnyPos, "getElement"), - List( - FUNCTION_CALL(AnyPos, PART.VALID(AnyPos, "x"), List(REF(AnyPos, PART.VALID(AnyPos, "y")))), - CONST_LONG(AnyPos, 0) - ) - ) - } - - property("braces in block's let and body") { - val text = - """let a = (foo) - |(bar)""".stripMargin - parse(text) shouldBe BLOCK( - AnyPos, - LET(AnyPos, PART.VALID(AnyPos, "a"), REF(AnyPos, PART.VALID(AnyPos, "foo"))), - REF(AnyPos, PART.VALID(AnyPos, "bar")) - ) - } - - property("crypto functions: sha256") { - val text = "❤✓☀★☂♞☯☭☢€☎∞❄♫\u20BD=test message" - val encodedText = Base58.encode(text.getBytes("UTF-8")) - - parse(s"sha256(base58'$encodedText')".stripMargin) shouldBe - FUNCTION_CALL( - Pos(0, 96), - PART.VALID(Pos(0, 6), "sha256"), - List(CONST_BYTESTR(Pos(7, 95), PART.VALID(Pos(15, 94), ByteStr(text.getBytes("UTF-8"))))) - ) - } - - property("crypto functions: blake2b256") { - val text = "❤✓☀★☂♞☯☭☢€☎∞❄♫\u20BD=test message" - val encodedText = Base58.encode(text.getBytes("UTF-8")) - - parse(s"blake2b256(base58'$encodedText')".stripMargin) shouldBe - FUNCTION_CALL(AnyPos, PART.VALID(AnyPos, "blake2b256"), List(CONST_BYTESTR(AnyPos, PART.VALID(AnyPos, ByteStr(text.getBytes("UTF-8")))))) - } - - property("crypto functions: keccak256") { - val text = "❤✓☀★☂♞☯☭☢€☎∞❄♫\u20BD=test message" - val encodedText = Base58.encode(text.getBytes("UTF-8")) - - parse(s"keccak256(base58'$encodedText')".stripMargin) shouldBe - FUNCTION_CALL(AnyPos, PART.VALID(AnyPos, "keccak256"), List(CONST_BYTESTR(AnyPos, PART.VALID(AnyPos, ByteStr(text.getBytes("UTF-8")))))) - } - - ignore("should parse a binary operation without a second operand") { - val script = "a &&" - parse(script) shouldBe BINARY_OP( - AnyPos, - REF(AnyPos, PART.VALID(AnyPos, "a")), - AND_OP, - INVALID(AnyPos, "expected a second operator") - ) - } - - property("simple matching") { - val code = - """match tx { - | case a: TypeA => 0 - | case b: TypeB => 1 - |}""".stripMargin - parse(code) shouldBe MATCH( - AnyPos, - REF(AnyPos, PART.VALID(AnyPos, "tx")), - List( - MATCH_CASE(AnyPos, Some(PART.VALID(AnyPos, "a")), List(PART.VALID(AnyPos, "TypeA")), CONST_LONG(AnyPos, 0)), - MATCH_CASE(AnyPos, Some(PART.VALID(AnyPos, "b")), List(PART.VALID(AnyPos, "TypeB")), CONST_LONG(AnyPos, 1)) - ) - ) - } - - property("multiple union type matching") { - val code = - """match tx { - | case txa: TypeA => 0 - | case underscore : TypeB | TypeC => 1 - |}""".stripMargin - parse(code) shouldBe MATCH( - AnyPos, - REF(AnyPos, PART.VALID(AnyPos, "tx")), - List( - MATCH_CASE(AnyPos, Some(PART.VALID(AnyPos, "txa")), List(PART.VALID(AnyPos, "TypeA")), CONST_LONG(AnyPos, 0)), - MATCH_CASE( - AnyPos, - Some(PART.VALID(AnyPos, "underscore")), - List(PART.VALID(AnyPos, "TypeB"), PART.VALID(AnyPos, "TypeC")), - CONST_LONG(AnyPos, 1) - ) - ) - ) - } - - property("matching expression") { - val code = - """match foo(x) + bar { - | case x:TypeA => 0 - | case y:TypeB | TypeC => 1 - |}""".stripMargin - parse(code) shouldBe MATCH( - AnyPos, - BINARY_OP( - AnyPos, - FUNCTION_CALL(AnyPos, PART.VALID(AnyPos, "foo"), List(REF(AnyPos, PART.VALID(AnyPos, "x")))), - BinaryOperation.SUM_OP, - REF(AnyPos, PART.VALID(AnyPos, "bar")) - ), - List( - MATCH_CASE(AnyPos, Some(PART.VALID(AnyPos, "x")), List(PART.VALID(AnyPos, "TypeA")), CONST_LONG(AnyPos, 0)), - MATCH_CASE(AnyPos, Some(PART.VALID(AnyPos, "y")), List(PART.VALID(AnyPos, "TypeB"), PART.VALID(AnyPos, "TypeC")), CONST_LONG(AnyPos, 1)) - ) - ) - } - - property("pattern matching - allow shadowing") { - val code = - """match p { - | case p: PointA | PointB => true - | case _ => false - |}""".stripMargin - parse(code) shouldBe MATCH( - AnyPos, - REF(AnyPos, PART.VALID(AnyPos, "p")), - List( - MATCH_CASE( - AnyPos, - Some(PART.VALID(AnyPos, "p")), - List(PART.VALID(AnyPos, "PointA"), PART.VALID(AnyPos, "PointB")), - TRUE(AnyPos) - ), - MATCH_CASE( - AnyPos, - None, - List.empty, - FALSE(AnyPos) - ) - ) - ) - } - - property("pattern matching with valid case, but no type is defined") { - parse("match tx { case x => 1 } ") shouldBe MATCH( - AnyPos, - REF(AnyPos, PART.VALID(AnyPos, "tx")), - List( - MATCH_CASE( - AnyPos, - Some(PART.VALID(AnyPos, "x")), - List.empty, - CONST_LONG(AnyPos, 1) - ) - ) - ) - } - - property("pattern matching with valid case, placeholder instead of variable name") { - parse("match tx { case _:TypeA => 1 } ") shouldBe MATCH( - AnyPos, - REF(AnyPos, PART.VALID(AnyPos, "tx")), - List( - MATCH_CASE( - AnyPos, - None, - List(PART.VALID(AnyPos, "TypeA")), - CONST_LONG(AnyPos, 1) - ) - ) - ) - } - - ignore("pattern matching with no cases") { - parse("match tx { } ") shouldBe INVALID(AnyPos, "pattern matching requires case branches") - } - - ignore("pattern matching with invalid case - no variable, type and expr are defined") { - parse("match tx { case => } ") shouldBe MATCH( - AnyPos, - REF(AnyPos, PART.VALID(AnyPos, "tx")), - List( - MATCH_CASE( - AnyPos, - Some(PART.INVALID(AnyPos, "invalid syntax, should be: `case varName: Type => expr` or `case _ => expr`")), - List.empty, - INVALID(AnyPos, "expected expression") - ) - ) - ) - } - - ignore("pattern matching with invalid case - no variable and type are defined") { - parse("match tx { case => 1 } ") shouldBe MATCH( - AnyPos, - REF(AnyPos, PART.VALID(AnyPos, "tx")), - List( - MATCH_CASE( - AnyPos, - Some(PART.INVALID(AnyPos, "invalid syntax, should be: `case varName: Type => expr` or `case _ => expr`")), - List.empty, - CONST_LONG(AnyPos, 1) - ) - ) - ) - } - - ignore("pattern matching with invalid case - no expr is defined") { - parse("match tx { case TypeA => } ") shouldBe MATCH( - AnyPos, - REF(AnyPos, PART.VALID(AnyPos, "tx")), - List( - MATCH_CASE(AnyPos, Some(PART.VALID(AnyPos, "TypeA")), Seq.empty, INVALID(AnyPos, "expected expression")) - ) - ) - } - - ignore("pattern matching with invalid case - no var is defined") { - parse("match tx { case :TypeA => 1 } ") shouldBe MATCH( - AnyPos, - REF(AnyPos, PART.VALID(AnyPos, "tx")), - List( - MATCH_CASE( - AnyPos, - Some(PART.INVALID(AnyPos, "invalid syntax, should be: `case varName: Type => expr` or `case _ => expr`")), - Seq.empty, - CONST_LONG(AnyPos, 1) - ) - ) - ) - } - - ignore("pattern matching with invalid case - expression in variable definition") { - parse("match tx { case 1 + 1 => 1 } ") shouldBe MATCH( - AnyPos, - REF(AnyPos, PART.VALID(AnyPos, "tx")), - List( - MATCH_CASE( - AnyPos, - Some(PART.INVALID(AnyPos, "invalid syntax, should be: `case varName: Type => expr` or `case _ => expr`")), - List.empty, - CONST_LONG(AnyPos, 1) - ) - ) - ) - } - - ignore("pattern matching with default case - no type is defined, one separator") { - parse("match tx { case _: | => 1 } ") shouldBe MATCH( - AnyPos, - REF(AnyPos, PART.VALID(AnyPos, "tx")), - List( - MATCH_CASE( - AnyPos, - None, - Seq(PART.INVALID(AnyPos, "the type for variable should be specified: `case varName: Type => expr`")), - CONST_LONG(AnyPos, 1) - ) - ) - ) - } - - ignore("pattern matching with default case - no type is defined, multiple separators") { - parse("match tx { case _: |||| => 1 } ") shouldBe MATCH( - AnyPos, - REF(AnyPos, PART.VALID(AnyPos, "tx")), - List( - MATCH_CASE( - AnyPos, - None, - Seq(PART.INVALID(AnyPos, "the type for variable should be specified: `case varName: Type => expr`")), - CONST_LONG(AnyPos, 1) - ) - ) - ) - } - - ignore("pattern matching - incomplete binary operation") { - val script = - """match tx { - | case a => true && - | case b => 1 - |}""".stripMargin - - parse(script) shouldBe - MATCH( - AnyPos, - REF(AnyPos, PART.VALID(AnyPos, "tx")), - List( - MATCH_CASE( - AnyPos, - Some(PART.VALID(AnyPos, "a")), - List(), - BINARY_OP(AnyPos, TRUE(AnyPos), AND_OP, INVALID(AnyPos, "expected a second operator")) - ), - MATCH_CASE(AnyPos, Some(PART.VALID(AnyPos, "b")), List(), CONST_LONG(AnyPos, 1)) - ) - ) - } - - ignore("pattern matching - incomplete binary operation with block") { - val script = - """match tx { - | case a => - | let x = true - | x && - | case b => 1 - |}""".stripMargin - - parse(script) shouldBe MATCH( - AnyPos, - REF(AnyPos, PART.VALID(AnyPos, "tx")), - List( - MATCH_CASE( - AnyPos, - Some(PART.VALID(AnyPos, "a")), - List(), - BLOCK( - AnyPos, - LET(AnyPos, PART.VALID(AnyPos, "x"), TRUE(AnyPos)), - BINARY_OP(AnyPos, REF(AnyPos, PART.VALID(AnyPos, "x")), AND_OP, INVALID(AnyPos, "expected a second operator")) - ) - ), - MATCH_CASE(AnyPos, Some(PART.VALID(AnyPos, "b")), List.empty, CONST_LONG(AnyPos, 1)) - ) - ) - } - - property("if expressions") { - parse("if (10 < 15) then true else false") shouldBe IF( - AnyPos, - BINARY_OP(AnyPos, CONST_LONG(AnyPos, 15), GT_OP, CONST_LONG(AnyPos, 10)), - TRUE(AnyPos), - FALSE(AnyPos) - ) - parse("if 10 < 15 then true else false") shouldBe IF( - AnyPos, - BINARY_OP(AnyPos, CONST_LONG(AnyPos, 15), GT_OP, CONST_LONG(AnyPos, 10)), - TRUE(AnyPos), - FALSE(AnyPos) - ) - parse(s"""if (10 < 16) - |then true - |else false""".stripMargin) shouldBe IF( - AnyPos, - BINARY_OP(AnyPos, CONST_LONG(AnyPos, 16), GT_OP, CONST_LONG(AnyPos, 10)), - TRUE(AnyPos), - FALSE(AnyPos) - ) - - parse(s"""if 10 < 17 - |then true - |else false""".stripMargin) shouldBe IF( - AnyPos, - BINARY_OP(AnyPos, CONST_LONG(AnyPos, 17), GT_OP, CONST_LONG(AnyPos, 10)), - TRUE(AnyPos), - FALSE(AnyPos) - ) - } - - ignore("underscore in numbers") { - parse("100_000_000") shouldBe CONST_LONG(AnyPos, 100000000) - } - - property("comments - the whole line at start") { - val code = - """# foo - |true""".stripMargin - - parse(code) shouldBe TRUE(AnyPos) - } - - property("comments - the whole line at end") { - val code = - """true - |# foo""".stripMargin - - parse(code) shouldBe TRUE(AnyPos) - } - - property("comments - block - after let") { - val s = - """let # foo - | x = true - |x""".stripMargin - parse(s) shouldBe BLOCK( - AnyPos, - LET(AnyPos, PART.VALID(AnyPos, "x"), TRUE(AnyPos)), - REF(AnyPos, PART.VALID(AnyPos, "x")) - ) - } - - property("comments - block - before assignment") { - val s = - """let x # foo - | = true - |x""".stripMargin - parse(s) shouldBe BLOCK( - AnyPos, - LET(AnyPos, PART.VALID(AnyPos, "x"), TRUE(AnyPos)), - REF(AnyPos, PART.VALID(AnyPos, "x")) - ) - } - - property("comments - block - between LET and BODY (full line)") { - val code = - """let x = true - |# foo - |x""".stripMargin - - parse(code) shouldBe BLOCK( - AnyPos, - LET(AnyPos, PART.VALID(AnyPos, "x"), TRUE(AnyPos)), - REF(AnyPos, PART.VALID(AnyPos, "x")) - ) - } - - property("comments - block - between LET and BODY (at end of a line)") { - val code = - """let x = true # foo - |x""".stripMargin - - parse(code) shouldBe BLOCK( - AnyPos, - LET(AnyPos, PART.VALID(AnyPos, "x"), TRUE(AnyPos)), - REF(AnyPos, PART.VALID(AnyPos, "x")) - ) - } - - property("comments - if - after condition") { - val code = - """if 10 < 15 # test - |then true else false""".stripMargin - - parse(code) shouldBe IF( - AnyPos, - BINARY_OP(AnyPos, CONST_LONG(AnyPos, 15), GT_OP, CONST_LONG(AnyPos, 10)), - TRUE(AnyPos), - FALSE(AnyPos) - ) - } - - property("comments - pattern matching - after case") { - val code = - """match p { - | case # test - | p: PointA | PointB => true - | case _ => false - |}""".stripMargin - parse(code) shouldBe MATCH( - AnyPos, - REF(AnyPos, PART.VALID(AnyPos, "p")), - List( - MATCH_CASE( - AnyPos, - Some(PART.VALID(AnyPos, "p")), - List(PART.VALID(AnyPos, "PointA"), PART.VALID(AnyPos, "PointB")), - TRUE(AnyPos) - ), - MATCH_CASE( - AnyPos, - None, - List.empty, - FALSE(AnyPos) - ) - ) - ) - } - - property("comments - pattern matching - after variable") { - val code = - """match p { - | case p # test - | : PointA - | | PointB => true - | case _ => false - |}""".stripMargin - parse(code) shouldBe MATCH( - AnyPos, - REF(AnyPos, PART.VALID(AnyPos, "p")), - List( - MATCH_CASE( - AnyPos, - Some(PART.VALID(AnyPos, "p")), - List(PART.VALID(AnyPos, "PointA"), PART.VALID(AnyPos, "PointB")), - TRUE(AnyPos) - ), - MATCH_CASE( - AnyPos, - None, - List.empty, - FALSE(AnyPos) - ) - ) - ) - } - - property("comments - pattern matching - before types") { - val code = - """match p { - | case p: # test - | PointA | PointB => true - | case _ => false - |}""".stripMargin - parse(code) shouldBe MATCH( - AnyPos, - REF(AnyPos, PART.VALID(AnyPos, "p")), - List( - MATCH_CASE( - AnyPos, - Some(PART.VALID(AnyPos, "p")), - List(PART.VALID(AnyPos, "PointA"), PART.VALID(AnyPos, "PointB")), - TRUE(AnyPos) - ), - MATCH_CASE( - AnyPos, - None, - List.empty, - FALSE(AnyPos) - ) - ) - ) - } - - property("comments - pattern matching - before a value's block") { - val code = - """match p { - | case p: PointA | PointB # test - | => true - | case _ => false - |}""".stripMargin - parse(code) shouldBe MATCH( - AnyPos, - REF(AnyPos, PART.VALID(AnyPos, "p")), - List( - MATCH_CASE( - AnyPos, - Some(PART.VALID(AnyPos, "p")), - List(PART.VALID(AnyPos, "PointA"), PART.VALID(AnyPos, "PointB")), - TRUE(AnyPos) - ), - MATCH_CASE( - AnyPos, - None, - List.empty, - FALSE(AnyPos) - ) - ) - ) - } - - property("comments - pattern matching - in a type definition - 1") { - val code = - """match p { - | case p : PointA # foo - | | PointB # bar - | => true - | case _ => false - |}""".stripMargin - parse(code) shouldBe MATCH( - AnyPos, - REF(AnyPos, PART.VALID(AnyPos, "p")), - List( - MATCH_CASE( - AnyPos, - Some(PART.VALID(AnyPos, "p")), - List(PART.VALID(AnyPos, "PointA"), PART.VALID(AnyPos, "PointB")), - TRUE(AnyPos) - ), - MATCH_CASE( - AnyPos, - None, - List.empty, - FALSE(AnyPos) - ) - ) - ) - } - - property("comments - pattern matching - in a type definition - 2") { - val code = - """match p { - | case p: PointA | # foo - | PointB # bar - | => true - | case _ => false - |}""".stripMargin - parse(code) shouldBe MATCH( - AnyPos, - REF(AnyPos, PART.VALID(AnyPos, "p")), - List( - MATCH_CASE( - AnyPos, - Some(PART.VALID(AnyPos, "p")), - List(PART.VALID(AnyPos, "PointA"), PART.VALID(AnyPos, "PointB")), - TRUE(AnyPos) - ), - MATCH_CASE( - AnyPos, - None, - List.empty, - FALSE(AnyPos) - ) - ) - ) - } - - property("comments - pattern matching - between cases") { - val code = - """match p { - | # foo - | case p: PointA | PointB => true - | # bar - | case _ => false - | # baz - |}""".stripMargin - parse(code) shouldBe MATCH( - AnyPos, - REF(AnyPos, PART.VALID(AnyPos, "p")), - List( - MATCH_CASE( - AnyPos, - Some(PART.VALID(AnyPos, "p")), - List(PART.VALID(AnyPos, "PointA"), PART.VALID(AnyPos, "PointB")), - TRUE(AnyPos) - ), - MATCH_CASE( - AnyPos, - None, - List.empty, - FALSE(AnyPos) - ) - ) - ) - } - - property("comments - getter - before dot") { - val code = - """x # foo - |.y""".stripMargin - - parse(code) shouldBe GETTER( - AnyPos, - REF(AnyPos, PART.VALID(AnyPos, "x")), - PART.VALID(AnyPos, "y") - ) - } - - ignore("comments - getter - after dot") { - val code = - """x. # foo - |y""".stripMargin - - parse(code) shouldBe GETTER( - AnyPos, - REF(AnyPos, PART.VALID(AnyPos, "x")), - PART.VALID(AnyPos, "y") - ) - } - - property("comments - function call") { - val code = - """f( - | # foo - | 1 # bar - | # baz - | , 2 - | # quux - |)""".stripMargin - - parse(code) shouldBe FUNCTION_CALL( - AnyPos, - PART.VALID(AnyPos, "f"), - List(CONST_LONG(AnyPos, 1), CONST_LONG(AnyPos, 2)) - ) - } - - property("comments - array") { - val code = - """xs[ - | # foo - | 1 - | # bar - |]""".stripMargin - - parse(code) shouldBe FUNCTION_CALL( - AnyPos, - PART.VALID(AnyPos, "getElement"), - List(REF(AnyPos, PART.VALID(AnyPos, "xs")), CONST_LONG(AnyPos, 1)) - ) - } - - property("comments - in func and around") { - val code = - """ - | - | # comment 1 - | func foo() = # comment 2 - | { # more comments - | throw() - | } # comment 3 - | - | foo() - | - """.stripMargin - - parse(code) - } - - property("operations priority") { - parse("a-b+c") shouldBe BINARY_OP( - AnyPos, - BINARY_OP(AnyPos, REF(AnyPos, PART.VALID(AnyPos, "a")), SUB_OP, REF(AnyPos, PART.VALID(AnyPos, "b"))), - SUM_OP, - REF(AnyPos, PART.VALID(AnyPos, "c")) - ) - parse("a+b-c") shouldBe BINARY_OP( - AnyPos, - BINARY_OP(AnyPos, REF(AnyPos, PART.VALID(AnyPos, "a")), SUM_OP, REF(AnyPos, PART.VALID(AnyPos, "b"))), - SUB_OP, - REF(AnyPos, PART.VALID(AnyPos, "c")) - ) - parse("a+b*c") shouldBe BINARY_OP( - AnyPos, - REF(AnyPos, PART.VALID(AnyPos, "a")), - SUM_OP, - BINARY_OP(AnyPos, REF(AnyPos, PART.VALID(AnyPos, "b")), MUL_OP, REF(AnyPos, PART.VALID(AnyPos, "c"))) - ) - parse("a*b-c") shouldBe BINARY_OP( - AnyPos, - BINARY_OP(AnyPos, REF(AnyPos, PART.VALID(AnyPos, "a")), MUL_OP, REF(AnyPos, PART.VALID(AnyPos, "b"))), - SUB_OP, - REF(AnyPos, PART.VALID(AnyPos, "c")) - ) - parse("a/b*c") shouldBe BINARY_OP( - AnyPos, - BINARY_OP(AnyPos, REF(AnyPos, PART.VALID(AnyPos, "a")), DIV_OP, REF(AnyPos, PART.VALID(AnyPos, "b"))), - MUL_OP, - REF(AnyPos, PART.VALID(AnyPos, "c")) - ) - - parse("a*b+c*d") shouldBe BINARY_OP( - AnyPos, - BINARY_OP(AnyPos, REF(AnyPos, PART.VALID(AnyPos, "a")), MUL_OP, REF(AnyPos, PART.VALID(AnyPos, "b"))), - SUM_OP, - BINARY_OP(AnyPos, REF(AnyPos, PART.VALID(AnyPos, "c")), MUL_OP, REF(AnyPos, PART.VALID(AnyPos, "d"))) - ) - - parse("a=d") shouldBe BINARY_OP( - AnyPos, - BINARY_OP(AnyPos, REF(AnyPos, PART.VALID(AnyPos, "b")), GT_OP, REF(AnyPos, PART.VALID(AnyPos, "a"))), - EQ_OP, - BINARY_OP(AnyPos, REF(AnyPos, PART.VALID(AnyPos, "c")), GE_OP, REF(AnyPos, PART.VALID(AnyPos, "d"))) - ) - } - - property("allow name starts with kerword") { - parse("ifx") shouldBe REF(AnyPos, PART.VALID(AnyPos, "ifx")) - parse("thenx") shouldBe REF(AnyPos, PART.VALID(AnyPos, "thenx")) - parse("elsex") shouldBe REF(AnyPos, PART.VALID(AnyPos, "elsex")) - parse("matchx") shouldBe REF(AnyPos, PART.VALID(AnyPos, "matchx")) - parse("truex") shouldBe REF(AnyPos, PART.VALID(AnyPos, "truex")) - parse("falsex") shouldBe REF(AnyPos, PART.VALID(AnyPos, "falsex")) - } - - property("parser StackOverflow check") { - val depth = 10000 - val lastStmt = (1 to depth).foldLeft("i0") { (acc, i) => - s"$acc + i$i" - } - val manyLets = (1 to depth).foldLeft("let i0 = 1") { (acc, i) => - s"$acc\nlet i$i = 1" - } - val script = s"$manyLets\n$lastStmt" - - Parser.parseExpr(script) shouldBe an[Success[_]] - } - - property("underscore") { - val script = "groth16Verify_15inputs(base64'ZGdnZHMK',base64'ZGdnZHMK',base64'ZGdnZHMK')" - Parser.parseExpr(script) shouldBe an[Success[_]] - } -} diff --git a/lang/tests/src/test/scala/com/wavesplatform/lang/compiler/ContractCompilerWithParserV2Test.scala b/lang/tests/src/test/scala/com/wavesplatform/lang/compiler/ContractCompilerWithParserV2Test.scala index 040950baac3..90b5c8c37e8 100644 --- a/lang/tests/src/test/scala/com/wavesplatform/lang/compiler/ContractCompilerWithParserV2Test.scala +++ b/lang/tests/src/test/scala/com/wavesplatform/lang/compiler/ContractCompilerWithParserV2Test.scala @@ -1,5 +1,6 @@ package com.wavesplatform.lang.compiler +import cats.implicits.toBifunctorOps import com.wavesplatform.lang.contract.DApp import com.wavesplatform.lang.directives.{Directive, DirectiveParser} import com.wavesplatform.lang.utils @@ -15,7 +16,7 @@ class ContractCompilerWithParserV2Test extends PropSpec { directives <- DirectiveParser(script) ds <- Directive.extractDirectives(directives) ctx = utils.compilerContext(ds) - compResult <- ContractCompiler.compileWithParseResult(script, ctx, ds.stdLibVersion, saveExprContext) + compResult <- ContractCompiler.compileWithParseResult(script, ctx, ds.stdLibVersion, saveExprContext).leftMap(_._1) } yield compResult result diff --git a/lang/tests/src/test/scala/com/wavesplatform/lang/compiler/ExpressionCompilerV1Test.scala b/lang/tests/src/test/scala/com/wavesplatform/lang/compiler/ExpressionCompilerV1Test.scala index adcee589157..55c78214ccf 100644 --- a/lang/tests/src/test/scala/com/wavesplatform/lang/compiler/ExpressionCompilerV1Test.scala +++ b/lang/tests/src/test/scala/com/wavesplatform/lang/compiler/ExpressionCompilerV1Test.scala @@ -243,7 +243,7 @@ class ExpressionCompilerV1Test extends PropSpec { | case t1: (Int, String) => t1._2 | case t2: (Boolean, Int, ByteVector) => t2._1 | } - | + | true """.stripMargin val expr2 = Parser.parseExpr(script2).get.value ExpressionCompiler(compilerContextV4, expr2) should produce( diff --git a/lang/tests/src/test/scala/com/wavesplatform/lang/compiler/ExpressionCompilerWithParserV2Test.scala b/lang/tests/src/test/scala/com/wavesplatform/lang/compiler/ExpressionCompilerWithParserV2Test.scala index 1bf9bfbd29e..48c11cf5bdc 100644 --- a/lang/tests/src/test/scala/com/wavesplatform/lang/compiler/ExpressionCompilerWithParserV2Test.scala +++ b/lang/tests/src/test/scala/com/wavesplatform/lang/compiler/ExpressionCompilerWithParserV2Test.scala @@ -1,13 +1,14 @@ package com.wavesplatform.lang.compiler +import cats.implicits.toBifunctorOps import com.wavesplatform.common.utils.EitherExt2 import com.wavesplatform.lang.directives.{Directive, DirectiveParser} import com.wavesplatform.lang.utils import com.wavesplatform.lang.v1.compiler.ExpressionCompiler -import com.wavesplatform.lang.v1.compiler.Types._ +import com.wavesplatform.lang.v1.compiler.Types.* import com.wavesplatform.lang.v1.parser.Expressions +import com.wavesplatform.lang.v1.parser.Expressions.* import com.wavesplatform.lang.v1.parser.Expressions.Pos.AnyPos -import com.wavesplatform.lang.v1.parser.Expressions._ import com.wavesplatform.test.PropSpec class ExpressionCompilerWithParserV2Test extends PropSpec { @@ -18,7 +19,7 @@ class ExpressionCompilerWithParserV2Test extends PropSpec { directives <- DirectiveParser(script) ds <- Directive.extractDirectives(directives) ctx = utils.compilerContext(ds) - compResult <- ExpressionCompiler.compileWithParseResult(script, ctx, saveExprContext) + compResult <- ExpressionCompiler.compileWithParseResult(script, ctx, saveExprContext).leftMap(_._1) } yield compResult result.map(_._2.expr) @@ -26,19 +27,19 @@ class ExpressionCompilerWithParserV2Test extends PropSpec { property("simple test") { val script = """ - |{-# STDLIB_VERSION 3 #-} - |{-# CONTENT_TYPE EXPRESSION #-} - |{-# SCRIPT_TYPE ACCOUNT #-} - | - |let foo = 1234567 - |let bar = 987654 - | - |if (foo + bar > 123456) then - | true - |else - | false - | - |""".stripMargin + |{-# STDLIB_VERSION 3 #-} + |{-# CONTENT_TYPE EXPRESSION #-} + |{-# SCRIPT_TYPE ACCOUNT #-} + | + |let foo = 1234567 + |let bar = 987654 + | + |if (foo + bar > 123456) then + | true + |else + | false + | + |""".stripMargin val result = compile(script) diff --git a/lang/tests/src/test/scala/com/wavesplatform/lang/ScriptPreprocessorTest.scala b/lang/tests/src/test/scala/com/wavesplatform/lang/compiler/ScriptPreprocessorTest.scala similarity index 97% rename from lang/tests/src/test/scala/com/wavesplatform/lang/ScriptPreprocessorTest.scala rename to lang/tests/src/test/scala/com/wavesplatform/lang/compiler/ScriptPreprocessorTest.scala index 759318980e3..f5f1593ce6f 100644 --- a/lang/tests/src/test/scala/com/wavesplatform/lang/ScriptPreprocessorTest.scala +++ b/lang/tests/src/test/scala/com/wavesplatform/lang/compiler/ScriptPreprocessorTest.scala @@ -1,7 +1,7 @@ -package com.wavesplatform.lang +package com.wavesplatform.lang.compiler -import cats.implicits._ import cats.Id +import cats.implicits.* import cats.kernel.Monoid import com.wavesplatform.lang.directives.values.V3 import com.wavesplatform.lang.directives.{Directive, DirectiveParser} @@ -11,11 +11,11 @@ import com.wavesplatform.lang.v1.compiler.ExpressionCompiler import com.wavesplatform.lang.v1.compiler.Terms.{CONST_BOOLEAN, EVALUATED} import com.wavesplatform.lang.v1.evaluator.Contextful.NoContext import com.wavesplatform.lang.v1.evaluator.EvaluatorV1 -import com.wavesplatform.lang.v1.evaluator.EvaluatorV1._ +import com.wavesplatform.lang.v1.evaluator.EvaluatorV1.* import com.wavesplatform.lang.v1.evaluator.ctx.impl.PureContext import com.wavesplatform.lang.v1.parser.Parser import com.wavesplatform.lang.v1.testing.ScriptGenParser -import com.wavesplatform.test._ +import com.wavesplatform.test.* class ScriptPreprocessorTest extends PropSpec with ScriptGenParser { private val evaluator = new EvaluatorV1[Id, NoContext]() diff --git a/lang/tests/src/test/scala/com/wavesplatform/lang/TypeInferrerTest.scala b/lang/tests/src/test/scala/com/wavesplatform/lang/compiler/TypeInferrerTest.scala similarity index 97% rename from lang/tests/src/test/scala/com/wavesplatform/lang/TypeInferrerTest.scala rename to lang/tests/src/test/scala/com/wavesplatform/lang/compiler/TypeInferrerTest.scala index 064b3eaa6b1..aab547b817d 100644 --- a/lang/tests/src/test/scala/com/wavesplatform/lang/TypeInferrerTest.scala +++ b/lang/tests/src/test/scala/com/wavesplatform/lang/compiler/TypeInferrerTest.scala @@ -1,8 +1,8 @@ -package com.wavesplatform.lang +package com.wavesplatform.lang.compiler import com.wavesplatform.lang.v1.compiler.TypeInferrer -import com.wavesplatform.lang.v1.compiler.Types._ -import com.wavesplatform.test._ +import com.wavesplatform.lang.v1.compiler.Types.* +import com.wavesplatform.test.* class TypeInferrerTest extends FreeSpec { diff --git a/lang/tests/src/test/scala/com/wavesplatform/lang/v1/estimator/CommonScriptEstimatorTest.scala b/lang/tests/src/test/scala/com/wavesplatform/lang/estimator/CommonScriptEstimatorTest.scala similarity index 91% rename from lang/tests/src/test/scala/com/wavesplatform/lang/v1/estimator/CommonScriptEstimatorTest.scala rename to lang/tests/src/test/scala/com/wavesplatform/lang/estimator/CommonScriptEstimatorTest.scala index e6497aa7d4f..33a590f6340 100644 --- a/lang/tests/src/test/scala/com/wavesplatform/lang/v1/estimator/CommonScriptEstimatorTest.scala +++ b/lang/tests/src/test/scala/com/wavesplatform/lang/estimator/CommonScriptEstimatorTest.scala @@ -1,7 +1,8 @@ -package com.wavesplatform.lang.v1.estimator +package com.wavesplatform.lang.estimator import com.wavesplatform.lang.v1.FunctionHeader.Native -import com.wavesplatform.lang.v1.compiler.Terms._ +import com.wavesplatform.lang.v1.compiler.Terms.* +import com.wavesplatform.lang.v1.estimator.ScriptEstimatorV1 import com.wavesplatform.lang.v1.estimator.v2.ScriptEstimatorV2 import com.wavesplatform.lang.v1.estimator.v3.ScriptEstimatorV3 import com.wavesplatform.lang.v1.evaluator.FunctionIds.SUM_LONG diff --git a/lang/tests/src/test/scala/com/wavesplatform/lang/v1/estimator/FoldEstimationTest.scala b/lang/tests/src/test/scala/com/wavesplatform/lang/estimator/FoldEstimationTest.scala similarity index 97% rename from lang/tests/src/test/scala/com/wavesplatform/lang/v1/estimator/FoldEstimationTest.scala rename to lang/tests/src/test/scala/com/wavesplatform/lang/estimator/FoldEstimationTest.scala index 4f0aa7fd202..33d27aaa8a2 100644 --- a/lang/tests/src/test/scala/com/wavesplatform/lang/v1/estimator/FoldEstimationTest.scala +++ b/lang/tests/src/test/scala/com/wavesplatform/lang/estimator/FoldEstimationTest.scala @@ -1,4 +1,4 @@ -package com.wavesplatform.lang.v1.estimator +package com.wavesplatform.lang.estimator import com.wavesplatform.lang.v1.estimator.v3.ScriptEstimatorV3 diff --git a/lang/tests/src/test/scala/com/wavesplatform/lang/v1/estimator/FuncOverlapEstimatorTest.scala b/lang/tests/src/test/scala/com/wavesplatform/lang/estimator/FuncOverlapEstimatorTest.scala similarity index 97% rename from lang/tests/src/test/scala/com/wavesplatform/lang/v1/estimator/FuncOverlapEstimatorTest.scala rename to lang/tests/src/test/scala/com/wavesplatform/lang/estimator/FuncOverlapEstimatorTest.scala index b6b458c0b57..b231c0b4364 100644 --- a/lang/tests/src/test/scala/com/wavesplatform/lang/v1/estimator/FuncOverlapEstimatorTest.scala +++ b/lang/tests/src/test/scala/com/wavesplatform/lang/estimator/FuncOverlapEstimatorTest.scala @@ -1,9 +1,10 @@ -package com.wavesplatform.lang.v1.estimator +package com.wavesplatform.lang.estimator import com.wavesplatform.lang.directives.values.V3 import com.wavesplatform.lang.utils.functionCosts import com.wavesplatform.lang.v1.FunctionHeader.{Native, User} import com.wavesplatform.lang.v1.compiler.Terms.{BLOCK, CONST_LONG, FUNC, FUNCTION_CALL, IF, LET, REF, TRUE} +import com.wavesplatform.lang.v1.estimator.ScriptEstimatorV1 import com.wavesplatform.lang.v1.estimator.v2.ScriptEstimatorV2 import com.wavesplatform.lang.v1.estimator.v3.ScriptEstimatorV3 import com.wavesplatform.lang.v1.evaluator.FunctionIds.SUM_LONG diff --git a/lang/tests/src/test/scala/com/wavesplatform/lang/v1/estimator/RecursiveFunctionTest.scala b/lang/tests/src/test/scala/com/wavesplatform/lang/estimator/RecursiveFunctionTest.scala similarity index 96% rename from lang/tests/src/test/scala/com/wavesplatform/lang/v1/estimator/RecursiveFunctionTest.scala rename to lang/tests/src/test/scala/com/wavesplatform/lang/estimator/RecursiveFunctionTest.scala index c3e861edcc1..46a558a62ab 100644 --- a/lang/tests/src/test/scala/com/wavesplatform/lang/v1/estimator/RecursiveFunctionTest.scala +++ b/lang/tests/src/test/scala/com/wavesplatform/lang/estimator/RecursiveFunctionTest.scala @@ -1,10 +1,11 @@ -package com.wavesplatform.lang.v1.estimator +package com.wavesplatform.lang.estimator import com.wavesplatform.lang.directives.values.V3 import com.wavesplatform.lang.utils.functionCosts import com.wavesplatform.lang.v1.FunctionHeader import com.wavesplatform.lang.v1.FunctionHeader.User import com.wavesplatform.lang.v1.compiler.Terms.* +import com.wavesplatform.lang.v1.estimator.ScriptEstimatorV1 import com.wavesplatform.lang.v1.estimator.v2.ScriptEstimatorV2 import com.wavesplatform.lang.v1.estimator.v3.ScriptEstimatorV3 import com.wavesplatform.test.* diff --git a/lang/tests/src/test/scala/com/wavesplatform/lang/v1/estimator/ScriptEstimatorTestBase.scala b/lang/tests/src/test/scala/com/wavesplatform/lang/estimator/ScriptEstimatorTestBase.scala similarity index 92% rename from lang/tests/src/test/scala/com/wavesplatform/lang/v1/estimator/ScriptEstimatorTestBase.scala rename to lang/tests/src/test/scala/com/wavesplatform/lang/estimator/ScriptEstimatorTestBase.scala index 5a792f33d60..cae1ea2107b 100644 --- a/lang/tests/src/test/scala/com/wavesplatform/lang/v1/estimator/ScriptEstimatorTestBase.scala +++ b/lang/tests/src/test/scala/com/wavesplatform/lang/estimator/ScriptEstimatorTestBase.scala @@ -1,22 +1,23 @@ -package com.wavesplatform.lang.v1.estimator +package com.wavesplatform.lang.estimator import cats.kernel.Monoid import com.wavesplatform.common.utils.EitherExt2 import com.wavesplatform.lang.directives.DirectiveSet -import com.wavesplatform.lang.directives.values._ +import com.wavesplatform.lang.directives.values.* import com.wavesplatform.lang.utils.functionCosts import com.wavesplatform.lang.v1.compiler.ExpressionCompiler -import com.wavesplatform.lang.v1.compiler.Terms._ +import com.wavesplatform.lang.v1.compiler.Terms.* +import com.wavesplatform.lang.v1.estimator.ScriptEstimator import com.wavesplatform.lang.v1.evaluator.Contextful.NoContext import com.wavesplatform.lang.v1.evaluator.ContextfulVal -import com.wavesplatform.lang.v1.evaluator.FunctionIds._ +import com.wavesplatform.lang.v1.evaluator.FunctionIds.* import com.wavesplatform.lang.v1.evaluator.ctx.impl.waves.{Types, WavesContext} import com.wavesplatform.lang.v1.evaluator.ctx.impl.{CryptoContext, PureContext} import com.wavesplatform.lang.v1.parser.Parser import com.wavesplatform.lang.v1.traits.Environment import com.wavesplatform.lang.v1.{CTX, FunctionHeader} import com.wavesplatform.lang.{Common, Global, utils} -import com.wavesplatform.test._ +import com.wavesplatform.test.* import monix.eval.Coeval class ScriptEstimatorTestBase(estimators: ScriptEstimator*) extends PropSpec { diff --git a/lang/tests/src/test/scala/com/wavesplatform/lang/v1/estimator/ScriptEstimatorV1V2Test.scala b/lang/tests/src/test/scala/com/wavesplatform/lang/estimator/ScriptEstimatorV1V2Test.scala similarity index 97% rename from lang/tests/src/test/scala/com/wavesplatform/lang/v1/estimator/ScriptEstimatorV1V2Test.scala rename to lang/tests/src/test/scala/com/wavesplatform/lang/estimator/ScriptEstimatorV1V2Test.scala index 1890e62fbd5..0b401b1109e 100644 --- a/lang/tests/src/test/scala/com/wavesplatform/lang/v1/estimator/ScriptEstimatorV1V2Test.scala +++ b/lang/tests/src/test/scala/com/wavesplatform/lang/estimator/ScriptEstimatorV1V2Test.scala @@ -1,11 +1,12 @@ -package com.wavesplatform.lang.v1.estimator +package com.wavesplatform.lang.estimator import com.wavesplatform.common.utils.EitherExt2 import com.wavesplatform.lang.directives.values.V3 import com.wavesplatform.lang.utils.functionCosts import com.wavesplatform.lang.v1.FunctionHeader import com.wavesplatform.lang.v1.compiler.Terms -import com.wavesplatform.lang.v1.compiler.Terms._ +import com.wavesplatform.lang.v1.compiler.Terms.* +import com.wavesplatform.lang.v1.estimator.ScriptEstimatorV1 import com.wavesplatform.lang.v1.estimator.v2.ScriptEstimatorV2 import com.wavesplatform.lang.v1.evaluator.ctx.impl.PureContext.sumLong import monix.eval.Coeval diff --git a/lang/tests/src/test/scala/com/wavesplatform/lang/v1/estimator/ScriptEstimatorV2Test.scala b/lang/tests/src/test/scala/com/wavesplatform/lang/estimator/ScriptEstimatorV2Test.scala similarity index 96% rename from lang/tests/src/test/scala/com/wavesplatform/lang/v1/estimator/ScriptEstimatorV2Test.scala rename to lang/tests/src/test/scala/com/wavesplatform/lang/estimator/ScriptEstimatorV2Test.scala index 3d2f6a173bc..a5e71db79b4 100644 --- a/lang/tests/src/test/scala/com/wavesplatform/lang/v1/estimator/ScriptEstimatorV2Test.scala +++ b/lang/tests/src/test/scala/com/wavesplatform/lang/estimator/ScriptEstimatorV2Test.scala @@ -1,8 +1,8 @@ -package com.wavesplatform.lang.v1.estimator +package com.wavesplatform.lang.estimator import com.wavesplatform.common.utils.EitherExt2 import com.wavesplatform.lang.directives.values.V3 -import com.wavesplatform.lang.utils._ +import com.wavesplatform.lang.utils.* import com.wavesplatform.lang.v1.estimator.v2.ScriptEstimatorV2 class ScriptEstimatorV2Test extends ScriptEstimatorTestBase(ScriptEstimatorV2) { diff --git a/lang/tests/src/test/scala/com/wavesplatform/lang/v1/estimator/ScriptEstimatorV2V3Test.scala b/lang/tests/src/test/scala/com/wavesplatform/lang/estimator/ScriptEstimatorV2V3Test.scala similarity index 97% rename from lang/tests/src/test/scala/com/wavesplatform/lang/v1/estimator/ScriptEstimatorV2V3Test.scala rename to lang/tests/src/test/scala/com/wavesplatform/lang/estimator/ScriptEstimatorV2V3Test.scala index 74e8369a2bb..d01b1ef30bc 100644 --- a/lang/tests/src/test/scala/com/wavesplatform/lang/v1/estimator/ScriptEstimatorV2V3Test.scala +++ b/lang/tests/src/test/scala/com/wavesplatform/lang/estimator/ScriptEstimatorV2V3Test.scala @@ -1,4 +1,4 @@ -package com.wavesplatform.lang.v1.estimator +package com.wavesplatform.lang.estimator import com.wavesplatform.lang.v1.compiler.Terms.EXPR import com.wavesplatform.lang.v1.estimator.v2.ScriptEstimatorV2 diff --git a/lang/tests/src/test/scala/com/wavesplatform/lang/v1/estimator/ScriptEstimatorV3Test.scala b/lang/tests/src/test/scala/com/wavesplatform/lang/estimator/ScriptEstimatorV3Test.scala similarity index 99% rename from lang/tests/src/test/scala/com/wavesplatform/lang/v1/estimator/ScriptEstimatorV3Test.scala rename to lang/tests/src/test/scala/com/wavesplatform/lang/estimator/ScriptEstimatorV3Test.scala index 56601b425f6..d21993a9519 100644 --- a/lang/tests/src/test/scala/com/wavesplatform/lang/v1/estimator/ScriptEstimatorV3Test.scala +++ b/lang/tests/src/test/scala/com/wavesplatform/lang/estimator/ScriptEstimatorV3Test.scala @@ -1,4 +1,4 @@ -package com.wavesplatform.lang.v1.estimator +package com.wavesplatform.lang.estimator import com.wavesplatform.common.utils.EitherExt2 import com.wavesplatform.lang.directives.values.* diff --git a/lang/tests/src/test/scala/com/wavesplatform/lang/v1/estimator/TypeCastComplexityTest.scala b/lang/tests/src/test/scala/com/wavesplatform/lang/estimator/TypeCastComplexityTest.scala similarity index 92% rename from lang/tests/src/test/scala/com/wavesplatform/lang/v1/estimator/TypeCastComplexityTest.scala rename to lang/tests/src/test/scala/com/wavesplatform/lang/estimator/TypeCastComplexityTest.scala index caf13b4c761..615936e90c6 100644 --- a/lang/tests/src/test/scala/com/wavesplatform/lang/v1/estimator/TypeCastComplexityTest.scala +++ b/lang/tests/src/test/scala/com/wavesplatform/lang/estimator/TypeCastComplexityTest.scala @@ -1,4 +1,5 @@ -package com.wavesplatform.lang.v1.estimator +package com.wavesplatform.lang.estimator + import com.wavesplatform.lang.v1.estimator.v3.ScriptEstimatorV3 class TypeCastComplexityTest extends ScriptEstimatorTestBase(ScriptEstimatorV3(fixOverflow = true, overhead = false)) { diff --git a/lang/tests/src/test/scala/com/wavesplatform/lang/v1/estimator/package.scala b/lang/tests/src/test/scala/com/wavesplatform/lang/estimator/package.scala similarity index 90% rename from lang/tests/src/test/scala/com/wavesplatform/lang/v1/estimator/package.scala rename to lang/tests/src/test/scala/com/wavesplatform/lang/estimator/package.scala index a5f824b1a64..f72f749463c 100644 --- a/lang/tests/src/test/scala/com/wavesplatform/lang/v1/estimator/package.scala +++ b/lang/tests/src/test/scala/com/wavesplatform/lang/estimator/package.scala @@ -1,16 +1,17 @@ -package com.wavesplatform.lang.v1 +package com.wavesplatform.lang import cats.syntax.semigroup.* import com.wavesplatform.lang.directives.DirectiveSet import com.wavesplatform.lang.directives.values.V3 +import com.wavesplatform.lang.v1.FunctionHeader import com.wavesplatform.lang.v1.compiler.Terms import com.wavesplatform.lang.v1.compiler.Terms.EXPR +import com.wavesplatform.lang.v1.estimator.ScriptEstimator import com.wavesplatform.lang.v1.evaluator.ContractEvaluator.LogExtraInfo import com.wavesplatform.lang.v1.evaluator.EvaluatorV2 import com.wavesplatform.lang.v1.evaluator.ctx.impl.PureContext import com.wavesplatform.lang.v1.evaluator.ctx.impl.waves.WavesContext import com.wavesplatform.lang.v1.traits.Environment -import com.wavesplatform.lang.{Common, Global} import monix.eval.Coeval package object estimator { diff --git a/lang/tests/src/test/scala/com/wavesplatform/lang/EvaluatorV1CaseObjField.scala b/lang/tests/src/test/scala/com/wavesplatform/lang/evaluator/EvaluatorV1CaseObjField.scala similarity index 85% rename from lang/tests/src/test/scala/com/wavesplatform/lang/EvaluatorV1CaseObjField.scala rename to lang/tests/src/test/scala/com/wavesplatform/lang/evaluator/EvaluatorV1CaseObjField.scala index 0e1eb1116be..b9a9b8650c3 100644 --- a/lang/tests/src/test/scala/com/wavesplatform/lang/EvaluatorV1CaseObjField.scala +++ b/lang/tests/src/test/scala/com/wavesplatform/lang/evaluator/EvaluatorV1CaseObjField.scala @@ -1,16 +1,16 @@ -package com.wavesplatform.lang +package com.wavesplatform.lang.evaluator import cats.Id import cats.kernel.Monoid -import com.wavesplatform.lang.Common._ -import com.wavesplatform.lang.Testing._ +import com.wavesplatform.lang.Common.* +import com.wavesplatform.lang.Testing.* import com.wavesplatform.lang.directives.values.V1 -import com.wavesplatform.lang.v1.compiler.Terms._ +import com.wavesplatform.lang.v1.compiler.Terms.* import com.wavesplatform.lang.v1.evaluator.Contextful.NoContext -import com.wavesplatform.lang.v1.evaluator.ctx.EvaluationContext._ -import com.wavesplatform.lang.v1.evaluator.ctx._ +import com.wavesplatform.lang.v1.evaluator.ctx.* +import com.wavesplatform.lang.v1.evaluator.ctx.EvaluationContext.* import com.wavesplatform.lang.v1.evaluator.ctx.impl.PureContext -import com.wavesplatform.lang.v1.evaluator.ctx.impl.PureContext._ +import com.wavesplatform.lang.v1.evaluator.ctx.impl.PureContext.* import com.wavesplatform.test.PropSpec class EvaluatorV1CaseObjField extends PropSpec { diff --git a/lang/tests/src/test/scala/com/wavesplatform/lang/SmartConstructorTest.scala b/lang/tests/src/test/scala/com/wavesplatform/lang/evaluator/SmartConstructorTest.scala similarity index 96% rename from lang/tests/src/test/scala/com/wavesplatform/lang/SmartConstructorTest.scala rename to lang/tests/src/test/scala/com/wavesplatform/lang/evaluator/SmartConstructorTest.scala index 8718eb71013..42d1790e096 100644 --- a/lang/tests/src/test/scala/com/wavesplatform/lang/SmartConstructorTest.scala +++ b/lang/tests/src/test/scala/com/wavesplatform/lang/evaluator/SmartConstructorTest.scala @@ -1,4 +1,4 @@ -package com.wavesplatform.lang +package com.wavesplatform.lang.evaluator import com.wavesplatform.common.state.ByteStr import com.wavesplatform.lang.v1.compiler.Terms diff --git a/lang/tests/src/test/scala/com/wavesplatform/lang/evaluator/TypeCastTest.scala b/lang/tests/src/test/scala/com/wavesplatform/lang/evaluator/TypeCastTest.scala index 81f1163611b..4a6e18ea63f 100644 --- a/lang/tests/src/test/scala/com/wavesplatform/lang/evaluator/TypeCastTest.scala +++ b/lang/tests/src/test/scala/com/wavesplatform/lang/evaluator/TypeCastTest.scala @@ -81,10 +81,10 @@ class TypeCastTest extends EvaluatorSpec { } property("type cast to concrete list is not supported") { - eval("func f(a: Any) = a.as[List[Int]]")(V3) should produce( + eval("func f(a: Any) = a.as[List[Int]]; true")(V3) should produce( "Type cast to List is allowed only if expecting type is List[Any] in 17-32" ) - eval("func f(a: Any) = a.exactAs[List[Int]]")(V3) should produce( + eval("func f(a: Any) = a.exactAs[List[Int]]; true")(V3) should produce( "Type cast to List is allowed only if expecting type is List[Any] in 17-37" ) } diff --git a/lang/tests/src/test/scala/com/wavesplatform/lang/evaluator/MakeStringTest.scala b/lang/tests/src/test/scala/com/wavesplatform/lang/evaluator/string/MakeStringTest.scala similarity index 98% rename from lang/tests/src/test/scala/com/wavesplatform/lang/evaluator/MakeStringTest.scala rename to lang/tests/src/test/scala/com/wavesplatform/lang/evaluator/string/MakeStringTest.scala index fe0d61b2c3a..4bd0daee6e8 100644 --- a/lang/tests/src/test/scala/com/wavesplatform/lang/evaluator/MakeStringTest.scala +++ b/lang/tests/src/test/scala/com/wavesplatform/lang/evaluator/string/MakeStringTest.scala @@ -1,12 +1,13 @@ -package com.wavesplatform.lang.evaluator +package com.wavesplatform.lang.evaluator.string import com.wavesplatform.common.state.ByteStr import com.wavesplatform.common.utils.EitherExt2 import com.wavesplatform.lang.directives.values.{StdLibVersion, V4, V5, V6} -import com.wavesplatform.lang.v1.compiler.Terms.{CONST_BOOLEAN, CONST_BYTESTR, CONST_LONG, CONST_STRING, EXPR, FUNCTION_CALL, REF} -import com.wavesplatform.lang.v1.evaluator.ctx.impl.PureContext +import com.wavesplatform.lang.evaluator.EvaluatorSpec import com.wavesplatform.lang.v1.FunctionHeader.Native +import com.wavesplatform.lang.v1.compiler.Terms.{CONST_BOOLEAN, CONST_BYTESTR, CONST_LONG, CONST_STRING, EXPR, FUNCTION_CALL, REF} import com.wavesplatform.lang.v1.evaluator.FunctionIds +import com.wavesplatform.lang.v1.evaluator.ctx.impl.PureContext import com.wavesplatform.lang.v1.evaluator.ctx.impl.PureContext.MaxListLengthV4 import com.wavesplatform.test.produce diff --git a/lang/tests/src/test/scala/com/wavesplatform/lang/ContractParserTest.scala b/lang/tests/src/test/scala/com/wavesplatform/lang/parser/ContractParserTest.scala similarity index 99% rename from lang/tests/src/test/scala/com/wavesplatform/lang/ContractParserTest.scala rename to lang/tests/src/test/scala/com/wavesplatform/lang/parser/ContractParserTest.scala index b59d4cf83ad..ce2f0957459 100644 --- a/lang/tests/src/test/scala/com/wavesplatform/lang/ContractParserTest.scala +++ b/lang/tests/src/test/scala/com/wavesplatform/lang/parser/ContractParserTest.scala @@ -1,10 +1,10 @@ -package com.wavesplatform.lang +package com.wavesplatform.lang.parser +import com.wavesplatform.lang.v1.parser.Expressions.* import com.wavesplatform.lang.v1.parser.Expressions.Pos.AnyPos -import com.wavesplatform.lang.v1.parser.Expressions._ import com.wavesplatform.lang.v1.parser.{Expressions, Parser} import com.wavesplatform.lang.v1.testing.ScriptGenParser -import com.wavesplatform.test._ +import com.wavesplatform.test.* import fastparse.Parsed.{Failure, Success} import org.scalatest.exceptions.TestFailedException diff --git a/lang/tests/src/test/scala/com/wavesplatform/lang/DirectiveParserTest.scala b/lang/tests/src/test/scala/com/wavesplatform/lang/parser/DirectiveParserTest.scala similarity index 94% rename from lang/tests/src/test/scala/com/wavesplatform/lang/DirectiveParserTest.scala rename to lang/tests/src/test/scala/com/wavesplatform/lang/parser/DirectiveParserTest.scala index 2ff3e15d472..5582d3e9a34 100644 --- a/lang/tests/src/test/scala/com/wavesplatform/lang/DirectiveParserTest.scala +++ b/lang/tests/src/test/scala/com/wavesplatform/lang/parser/DirectiveParserTest.scala @@ -1,9 +1,9 @@ -package com.wavesplatform.lang +package com.wavesplatform.lang.parser -import com.wavesplatform.lang.directives.DirectiveKey._ -import com.wavesplatform.lang.directives.values._ +import com.wavesplatform.lang.directives.DirectiveKey.* +import com.wavesplatform.lang.directives.values.* import com.wavesplatform.lang.directives.{Directive, DirectiveParser} -import com.wavesplatform.test._ +import com.wavesplatform.test.* class DirectiveParserTest extends PropSpec { diff --git a/lang/tests/src/test/scala/com/wavesplatform/lang/ScriptParserTest.scala b/lang/tests/src/test/scala/com/wavesplatform/lang/parser/ScriptParserTest.scala similarity index 93% rename from lang/tests/src/test/scala/com/wavesplatform/lang/ScriptParserTest.scala rename to lang/tests/src/test/scala/com/wavesplatform/lang/parser/ScriptParserTest.scala index 35c239268fe..938ef5641e7 100644 --- a/lang/tests/src/test/scala/com/wavesplatform/lang/ScriptParserTest.scala +++ b/lang/tests/src/test/scala/com/wavesplatform/lang/parser/ScriptParserTest.scala @@ -1,14 +1,15 @@ -package com.wavesplatform.lang +package com.wavesplatform.lang.parser import com.wavesplatform.common.state.ByteStr import com.wavesplatform.common.utils.Base58 -import com.wavesplatform.lang.v1.parser.BinaryOperation._ +import com.wavesplatform.lang.hacks.Global.MaxLiteralLength +import com.wavesplatform.lang.v1.parser.BinaryOperation.* +import com.wavesplatform.lang.v1.parser.Expressions.* import com.wavesplatform.lang.v1.parser.Expressions.Pos.AnyPos -import com.wavesplatform.lang.v1.parser.Expressions._ import com.wavesplatform.lang.v1.parser.Parser.GenericMethod import com.wavesplatform.lang.v1.parser.{BinaryOperation, Expressions, Parser} import com.wavesplatform.lang.v1.testing.ScriptGenParser -import com.wavesplatform.test._ +import com.wavesplatform.test.* import fastparse.Parsed.{Failure, Success} import org.scalacheck.Gen import org.scalatest.exceptions.TestFailedException @@ -16,8 +17,13 @@ import org.scalatest.exceptions.TestFailedException class ScriptParserTest extends PropSpec with ScriptGenParser { private def parse(x: String): EXPR = Parser.parseExpr(x) match { - case Success(r, _) => r - case f: Failure => throw new TestFailedException(f.msg, 0) + case Success(r, _) => r + case f: Failure => throw new TestFailedException(f.msg, 0) + } + + private def parseE(x: String): Either[String, EXPR] = Parser.parseExpr(x) match { + case Success(r, _) => Right(r) + case f: Failure => Left(f.msg) } private def cleanOffsets(l: LET): LET = @@ -29,14 +35,14 @@ class ScriptParserTest extends PropSpec with ScriptGenParser { } private def cleanOffsets(expr: EXPR): EXPR = expr match { - case x: CONST_LONG => x.copy(position = Pos(0, 0)) - case x: REF => x.copy(position = Pos(0, 0), key = cleanOffsets(x.key)) - case x: CONST_STRING => x.copy(position = Pos(0, 0), value = cleanOffsets(x.value)) - case x: CONST_BYTESTR => x.copy(position = Pos(0, 0), value = cleanOffsets(x.value)) - case x: TRUE => x.copy(position = Pos(0, 0)) - case x: FALSE => x.copy(position = Pos(0, 0)) - case x: BINARY_OP => x.copy(position = Pos(0, 0), a = cleanOffsets(x.a), b = cleanOffsets(x.b)) - case x: IF => x.copy(position = Pos(0, 0), cond = cleanOffsets(x.cond), ifTrue = cleanOffsets(x.ifTrue), ifFalse = cleanOffsets(x.ifFalse)) + case x: CONST_LONG => x.copy(position = Pos(0, 0)) + case x: REF => x.copy(position = Pos(0, 0), key = cleanOffsets(x.key)) + case x: CONST_STRING => x.copy(position = Pos(0, 0), value = cleanOffsets(x.value)) + case x: CONST_BYTESTR => x.copy(position = Pos(0, 0), value = cleanOffsets(x.value)) + case x: TRUE => x.copy(position = Pos(0, 0)) + case x: FALSE => x.copy(position = Pos(0, 0)) + case x: BINARY_OP => x.copy(position = Pos(0, 0), a = cleanOffsets(x.a), b = cleanOffsets(x.b)) + case x: IF => x.copy(position = Pos(0, 0), cond = cleanOffsets(x.cond), ifTrue = cleanOffsets(x.ifTrue), ifFalse = cleanOffsets(x.ifFalse)) case x @ BLOCK(_, l: Expressions.LET, _, _, _) => x.copy(position = Pos(0, 0), let = cleanOffsets(l), body = cleanOffsets(x.body)) case x: FUNCTION_CALL => x.copy(position = Pos(0, 0), name = cleanOffsets(x.name), args = x.args.map(cleanOffsets(_))) case _ => throw new NotImplementedError(s"toString for ${expr.getClass.getSimpleName}") @@ -48,19 +54,17 @@ class ScriptParserTest extends PropSpec with ScriptGenParser { str <- toString(expr) } yield (expr, str) - forAll(testGen) { - case (expr, str) => - withClue(str) { - cleanOffsets(parse(str)) shouldBe expr - } + forAll(testGen) { case (expr, str) => + withClue(str) { + cleanOffsets(parse(str)) shouldBe expr + } } } - private def multiLineExprTests(tests: (String, Gen[EXPR])*): Unit = tests.foreach { - case (label, gen) => - property(s"multiline expressions: $label") { - genElementCheck(gen) - } + private def multiLineExprTests(tests: (String, Gen[EXPR])*): Unit = tests.foreach { case (label, gen) => + property(s"multiline expressions: $label") { + genElementCheck(gen) + } } private val gas = 50 @@ -152,7 +156,7 @@ class ScriptParserTest extends PropSpec with ScriptGenParser { PART.VALID( AnyPos, ByteStr( - Array[Short](0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0, 0xAB, 0xCD, 0xEF, 0xfa, 0xbc, + Array[Short](0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0xab, 0xcd, 0xef, 0xfa, 0xbc, 0xde).map(_.toByte) ) ) @@ -165,7 +169,6 @@ class ScriptParserTest extends PropSpec with ScriptGenParser { } property("literal too long") { - import Global.MaxLiteralLength val longLiteral = "A" * (MaxLiteralLength + 1) val to = 8 + MaxLiteralLength parse(s"base58'$longLiteral'") shouldBe @@ -233,7 +236,10 @@ class ScriptParserTest extends PropSpec with ScriptGenParser { AnyPos, CONST_LONG(AnyPos, 42), PART.VALID(AnyPos, "q"), - Seq((PART.VALID(AnyPos, "x"), Single(PART.VALID(AnyPos, "Int"), None)), (PART.VALID(AnyPos, "y"), Single(PART.VALID(AnyPos, "Boolean"), None))) + Seq( + (PART.VALID(AnyPos, "x"), Single(PART.VALID(AnyPos, "Int"), None)), + (PART.VALID(AnyPos, "y"), Single(PART.VALID(AnyPos, "Boolean"), None)) + ) ), REF(AnyPos, PART.VALID(AnyPos, "c")) ) @@ -288,11 +294,8 @@ class ScriptParserTest extends PropSpec with ScriptGenParser { property("block: invalid") { val s = "let q = 1 c" - parse(s) shouldBe BLOCK( - AnyPos, - LET(AnyPos, PART.VALID(AnyPos, "q"), CONST_LONG(AnyPos, 1)), - INVALID(AnyPos, "expected ';'") - ) + parseE(s) should produce("Expected expression") + } property("should parse a binary operation with block operand") { @@ -366,11 +369,7 @@ class ScriptParserTest extends PropSpec with ScriptGenParser { property("reserved keywords are invalid variable names in expr: let") { val script = "let + 1" - parse(script) shouldBe BLOCK( - AnyPos, - LET(AnyPos, PART.INVALID(AnyPos, "expected a variable's name"), INVALID(AnyPos, "expected a value")), - INVALID(AnyPos, "expected ';'") - ) + parseE(script) should produce("Expected variable name") } property("reserved keywords are invalid variable names in expr: if") { @@ -519,7 +518,11 @@ class ScriptParserTest extends PropSpec with ScriptGenParser { } property("array accessor with index from variable") { - parse("x[ind]") shouldBe FUNCTION_CALL(AnyPos, PART.VALID(AnyPos, "getElement"), List(REF(AnyPos, PART.VALID(AnyPos, "x")), REF(AnyPos, PART.VALID(AnyPos, "ind")))) + parse("x[ind]") shouldBe FUNCTION_CALL( + AnyPos, + PART.VALID(AnyPos, "getElement"), + List(REF(AnyPos, PART.VALID(AnyPos, "x")), REF(AnyPos, PART.VALID(AnyPos, "ind"))) + ) parse( """ |x[ @@ -528,7 +531,11 @@ class ScriptParserTest extends PropSpec with ScriptGenParser { |#comment |] |""".stripMargin - ) shouldBe FUNCTION_CALL(AnyPos, PART.VALID(AnyPos, "getElement"), List(REF(AnyPos, PART.VALID(AnyPos, "x")), REF(AnyPos, PART.VALID(AnyPos, "ind")))) + ) shouldBe FUNCTION_CALL( + AnyPos, + PART.VALID(AnyPos, "getElement"), + List(REF(AnyPos, PART.VALID(AnyPos, "x")), REF(AnyPos, PART.VALID(AnyPos, "ind"))) + ) } property("multiple array accessors") { @@ -922,8 +929,8 @@ class ScriptParserTest extends PropSpec with ScriptGenParser { FALSE(AnyPos) ) parse(s"""if (10 < 15) - |then true - |else false""".stripMargin) shouldBe IF( + |then true + |else false""".stripMargin) shouldBe IF( AnyPos, BINARY_OP(AnyPos, CONST_LONG(AnyPos, 15), LT_OP, CONST_LONG(AnyPos, 10)), TRUE(AnyPos), @@ -931,8 +938,8 @@ class ScriptParserTest extends PropSpec with ScriptGenParser { ) parse(s"""if 10 < 15 - |then true - |else false""".stripMargin) shouldBe IF( + |then true + |else false""".stripMargin) shouldBe IF( AnyPos, BINARY_OP(AnyPos, CONST_LONG(AnyPos, 15), LT_OP, CONST_LONG(AnyPos, 10)), TRUE(AnyPos), diff --git a/lang/tests/src/test/scala/com/wavesplatform/lang/compiler/UnderscoreTest.scala b/lang/tests/src/test/scala/com/wavesplatform/lang/parser/UnderscoreTest.scala similarity index 78% rename from lang/tests/src/test/scala/com/wavesplatform/lang/compiler/UnderscoreTest.scala rename to lang/tests/src/test/scala/com/wavesplatform/lang/parser/UnderscoreTest.scala index 7bae8df9886..7f1d9cd68ee 100644 --- a/lang/tests/src/test/scala/com/wavesplatform/lang/compiler/UnderscoreTest.scala +++ b/lang/tests/src/test/scala/com/wavesplatform/lang/parser/UnderscoreTest.scala @@ -1,13 +1,13 @@ -package com.wavesplatform.lang.compiler +package com.wavesplatform.lang.parser import com.wavesplatform.common.utils.EitherExt2 import com.wavesplatform.lang.directives.values.{Expression, V3, V5} -import com.wavesplatform.lang.utils._ +import com.wavesplatform.lang.utils.* import com.wavesplatform.lang.v1.FunctionHeader.{Native, User} -import com.wavesplatform.lang.v1.compiler.Terms._ +import com.wavesplatform.lang.v1.compiler.Terms.* import com.wavesplatform.lang.v1.compiler.{Decompiler, ExpressionCompiler} import com.wavesplatform.lang.v1.evaluator.ctx.impl.PureContext -import com.wavesplatform.test.{PropSpec, _} +import com.wavesplatform.test.{PropSpec, *} class UnderscoreTest extends PropSpec { private def compile(script: String): Either[String, EXPR] = @@ -43,15 +43,15 @@ class UnderscoreTest extends PropSpec { } property("two or more underscores in a row are prohibited") { - assert("__aa_a_a_", "_ff_f_f_", "_xx_x_x_") should produce("Parsed.Failure") - assert("_aa__a_a_", "_ff_f_f_", "_xx_x_x_") should produce("Parsed.Failure") - assert("_aa_a_a__", "_ff_f_f_", "_xx_x_x_") should produce("Parsed.Failure") - assert("_aa_a_a_", "__ff_f_f_", "_xx_x_x_") should produce("Parsed.Failure") - assert("_aa_a_a_", "_ff__f_f_", "_xx_x_x_") should produce("Parsed.Failure") - assert("_aa_a_a_", "_ff_f_f__", "_xx_x_x_") should produce("Parsed.Failure") - assert("_aa_a_a_", "_ff_f_f_", "__xx_x_x_") should produce("Parsed.Failure") - assert("_aa_a_a_", "_ff_f_f_", "_xx__x_x_") should produce("Parsed.Failure") - assert("_aa_a_a_", "_ff_f_f_", "_xx_x_x__") should produce("Parsed.Failure") + assert("__aa_a_a_", "_ff_f_f_", "_xx_x_x_") should produce("expected not more than 1 underscore in a row in 6-15") + assert("_aa__a_a_", "_ff_f_f_", "_xx_x_x_") should produce("expected not more than 1 underscore in a row in 9-15") + assert("_aa_a_a__", "_ff_f_f_", "_xx_x_x_") should produce("expected not more than 1 underscore in a row in 13-15") + assert("_aa_a_a_", "__ff_f_f_", "_xx_x_x_") should produce("expected not more than 1 underscore in a row in 25-44") + assert("_aa_a_a_", "_ff__f_f_", "_xx_x_x_") should produce("expected not more than 1 underscore in a row in 28-44") + assert("_aa_a_a_", "_ff_f_f__", "_xx_x_x_") should produce("expected not more than 1 underscore in a row in 32-44") + assert("_aa_a_a_", "_ff_f_f_", "__xx_x_x_") should produce("expected not more than 1 underscore in a row in 34-44") + assert("_aa_a_a_", "_ff_f_f_", "_xx__x_x_") should produce("expected not more than 1 underscore in a row in 37-44") + assert("_aa_a_a_", "_ff_f_f_", "_xx_x_x__") should produce("expected not more than 1 underscore in a row in 41-44") } property("internal functions can't be used directly") { @@ -74,7 +74,8 @@ class UnderscoreTest extends PropSpec { CONST_BOOLEAN(true), FUNCTION_CALL(Native(2), List(CONST_STRING("Match error").explicitGet())) ) - )) + ) + ) } property("FOLD with underscores") { @@ -98,10 +99,13 @@ class UnderscoreTest extends PropSpec { Native(1100), List( CONST_LONG(2), - FUNCTION_CALL(Native(1100), - List(CONST_LONG(3), - FUNCTION_CALL(Native(1100), - List(CONST_LONG(4), FUNCTION_CALL(Native(1100), List(CONST_LONG(5), REF("nil"))))))) + FUNCTION_CALL( + Native(1100), + List( + CONST_LONG(3), + FUNCTION_CALL(Native(1100), List(CONST_LONG(4), FUNCTION_CALL(Native(1100), List(CONST_LONG(5), REF("nil"))))) + ) + ) ) ) ) @@ -127,9 +131,11 @@ class UnderscoreTest extends PropSpec { FUNC( "$f0_2", List(s"$$a", s"$$i"), - IF(FUNCTION_CALL(Native(103), List(REF(s"$$i"), REF(s"$$s"))), - REF(s"$$a"), - FUNCTION_CALL(Native(2), List(CONST_STRING("List size exceeds 1").explicitGet()))) + IF( + FUNCTION_CALL(Native(103), List(REF(s"$$i"), REF(s"$$s"))), + REF(s"$$a"), + FUNCTION_CALL(Native(2), List(CONST_STRING("List size exceeds 1").explicitGet())) + ) ), FUNCTION_CALL(User(s"$$f0_2"), List(FUNCTION_CALL(User(s"$$f0_1"), List(REF(s"$$acc0"), CONST_LONG(0))), CONST_LONG(1))) ) @@ -138,6 +144,7 @@ class UnderscoreTest extends PropSpec { ) ) ) - )) + ) + ) } } diff --git a/lang/tests/src/test/scala/com/wavesplatform/lang/parser/error/CommonParseErrorTest.scala b/lang/tests/src/test/scala/com/wavesplatform/lang/parser/error/CommonParseErrorTest.scala new file mode 100644 index 00000000000..30bebdaa1a0 --- /dev/null +++ b/lang/tests/src/test/scala/com/wavesplatform/lang/parser/error/CommonParseErrorTest.scala @@ -0,0 +1,78 @@ +package com.wavesplatform.lang.parser.error + +import com.wavesplatform.lang.directives.DirectiveSet +import com.wavesplatform.lang.directives.values.{Expression, V6} +import com.wavesplatform.lang.utils +import com.wavesplatform.lang.v1.compiler.{ContractCompiler, ExpressionCompiler, TestCompiler} + +class CommonParseErrorTest extends ParseErrorTest { + property("empty script as expression") { + TestCompiler(V6).compileExpressionE("") shouldBe Left("Parse error: expected result expression in 0-0") + } + + property("empty script with comment as expression") { + TestCompiler(V6).compileExpressionE("#abcd") shouldBe Left("Parse error: expected result expression in 5-5") + } + + property("illegal line break inside comment") { + assert( + """ + | # comment + | # comment + | break + | # comment + | # comment + | func(a: Int) = a + """.stripMargin, + """Parse error: illegal expression""", + 24, + 29, + "break", + onlyDApp = true + ) + } + + property("cyrillic charset for definition") { + assert( + """ + | func кириллица() = [] + """.stripMargin, + """Parse error: expected only latin charset for definitions""", + 7, + 18, + "кириллица()" + ) + } + + property("chinese charset for definition") { + assert( + """ + | let 煊镕不 = [] + """.stripMargin, + """Parse error: expected only latin charset for definitions""", + 6, + 9, + "煊镕不" + ) + } + + property("alternative compile methods contains same message and error indexes") { + ContractCompiler + .compileWithParseResult( + """ + | let 煊镕不 = [] + """.stripMargin, + utils.compilerContext(DirectiveSet.contractDirectiveSet), + V6 + ) shouldBe Left(("Parse error: expected only latin charset for definitions in 6-9", 6, 9)) + + ExpressionCompiler + .compileWithParseResult( + """ + | let 煊镕不 = [] + | true + """.stripMargin, + utils.compilerContext(V6, Expression, false) + ) shouldBe Left(("Parse error: expected only latin charset for definitions in 6-9", 6, 9)) + } +} diff --git a/lang/tests/src/test/scala/com/wavesplatform/lang/parser/error/CurlyBraceParseErrorTest.scala b/lang/tests/src/test/scala/com/wavesplatform/lang/parser/error/CurlyBraceParseErrorTest.scala new file mode 100644 index 00000000000..2f7eeb4e980 --- /dev/null +++ b/lang/tests/src/test/scala/com/wavesplatform/lang/parser/error/CurlyBraceParseErrorTest.scala @@ -0,0 +1,105 @@ +package com.wavesplatform.lang.parser.error + +class CurlyBraceParseErrorTest extends ParseErrorTest { + property("missing closing curly brace of match block") { + assert( + """ + | let a = if (true) then 1 else "" + | let b = match a { + | case _: Int => throw() + | case _: String => throw() + | + | func f() = 1 + """.stripMargin, + """Parse error: expected "}"""", + 110, + 114, + ")\n\n " + ) + } + + property("missing closing curly brace of case block") { + assert( + """ + | let a = if (true) then 1 else "" + | let b = match a { + | case _: Int => { + | let x = 1 + | let y = 1 + | throw() + | case _: String => throw() + | } + """.stripMargin, + """Parse error: expected "}"""", + 115, + 120, + ")\n " + ) + } + + property("missing closing curly brace of condition block") { + assert( + """ + | let a = + | if { + | let x = 1 + | let y = 1 + | y == x + | then 1 + | else "" + | func f() = true + """.stripMargin, + """Parse error: expected "}"""", + 58, + 63, + "x\n " + ) + } + + property("missing closing curly brace of then block") { + assert( + """ + | let a = + | if { + | let x = 1 + | let y = 1 + | y == x + | } + | then { + | func g() = throw() + | g() + | else "" + | func f() = true + """.stripMargin, + """Parse error: expected "}"""", + 106, + 111, + ")\n " + ) + } + + property("missing closing curly brace of else block") { + assert( + """ + | let a = + | if { + | let x = 1 + | let y = 1 + | y == x + | } + | then { + | func g() = throw() + | g() + | } else { + | func h() = throw() + | {if {h()} then {h()} else {h()}} + | + | func f() = true + """.stripMargin, + """Parse error: expected "}"""", + 180, + 184, + "}\n\n " + ) + } +} diff --git a/lang/tests/src/test/scala/com/wavesplatform/lang/parser/error/FuncDefParseErrorTest.scala b/lang/tests/src/test/scala/com/wavesplatform/lang/parser/error/FuncDefParseErrorTest.scala new file mode 100644 index 00000000000..015f02386b2 --- /dev/null +++ b/lang/tests/src/test/scala/com/wavesplatform/lang/parser/error/FuncDefParseErrorTest.scala @@ -0,0 +1,263 @@ +package com.wavesplatform.lang.parser.error + +class FuncDefParseErrorTest extends ParseErrorTest { + property("missing opening brace of function arguments definition") { + assert( + """ + | let x = 1 + | let y = 1 + | func f a: Int) = a + """.stripMargin, + """Parse error: expected "("""", + 29, + 31, + "f " + ) + } + + property("missing closing brace of function arguments definition") { + assert( + """ + | let x = 1 + | let y = 1 + | func f(a: Int = a + """.stripMargin, + """Parse error: expected ")"""", + 36, + 38, + "t " + ) + } + + property("missing '=' of function arguments definition") { + assert( + """ + | let x = 1 + | let y = 1 + | func f(a: Int) a + """.stripMargin, + """Parse error: expected "="""", + 37, + 39, + ") " + ) + } + + property("Java style type definition") { + assert( + """ + | let x = 1 + | let y = 1 + | func f(Int a) = a + """.stripMargin, + """Parse error: expected ":"""", + 33, + 35, + "t " + ) + } + + property("missing arguments types") { + assert( + """ + | let x = 1 + | let y = 1 + | func f(a, b, c) = a + """.stripMargin, + """Parse error: expected ":"""", + 29, + 32, + "f(a" + ) + } + + property("showing first of multiple errors") { + assert( + """ + | let x = 1 + | let y = 1 + | func f(a Int, b: String, c) a + """.stripMargin, + """Parse error: expected ":"""", + 31, + 33, + "a " + ) + } + + property("missing 'func' keyword") { + assert( + """ + | let x = 1 + | let y = 1 + | f(a: Int) = a + """.stripMargin, + """Parse error: expected "func" keyword""", + 24, + 28, + "f(a:" + ) + } + + property("'func' keyword without definition") { + assert( + """ + | let x = 1 + | let y = 1 + | func + """.stripMargin, + """Parse error: expected function name""", + 24, + 28, + "func", + endExpr = false + ) + } + + property("missing function name") { + assert( + """ + | let x = 1 + | let y = 1 + | func (a: Int) = a + """.stripMargin, + """Parse error: expected function name""", + 24, + 28, + "func" + ) + } + + property("missing function name without space") { + assert( + """ + | let x = 1 + | let y = 1 + | func(a: Int) = a + """.stripMargin, + """Parse error: expected function name""", + 24, + 28, + "func" + ) + } + + property("missing function body") { + assert( + """ + | let x = 1 + | let y = 1 + | func f(a: Int) = + """.stripMargin, + """Parse error: expected function body""", + 39, + 47, + "=\n ", + endExpr = false + ) + } + + property("missing closing curly brace of function body") { + assert( + """ + | func f(a: Int) = { + | true + | + """.stripMargin, + """Parse error: expected "}"""", + 27, + 36, + "e\n\n ", + endExpr = false + ) + } + + property("missing result expression") { + assert( + """ + | func f() = { + | let a = 1 + | } + """.stripMargin, + """Parse error: expected expression""", + 26, + 27, + "1" + ) + } + + property("forbid definition without space") { + assert( + """ + | funcf() = 1 + """.stripMargin, + """Parse error: expected "func" keyword""", + 2, + 9, + "funcf()", + onlyDApp = true + ) + } + + property("forbid definition without space for callable") { + assert( + """ + | @Callable(i) + | funccall() = [] + """.stripMargin, + """Parse error: expected "func" keyword""", + 16, + 26, + "funccall()", + onlyDApp = true + ) + } + + property("function name started from digit") { + assert( + """ + | func 1call() = 1 + """.stripMargin, + """Parse error: expected character or "_" at start of the definition""", + 7, + 14, + "1call()" + ) + } + + property("missing type after parameter definition with ':'") { + assert( + """ + | func call(a:) + """.stripMargin, + """Parse error: illegal expression""", + 7, + 14, + "call(a:" + ) + } + + property("function argument started from digit") { + assert( + """ + | func call(1a:Int) + """.stripMargin, + """Parse error: expected character or "_" at start of the definition""", + 12, + 19, + "1a:Int)" + ) + } + + property("illegal character in function name") { + assert( + """ + | func c@ll() + """.stripMargin, + """Parse error: expected character, digit or "_" for the definition""", + 8, + 13, + "@ll()" + ) + } +} diff --git a/lang/tests/src/test/scala/com/wavesplatform/lang/parser/error/LetDefParseErrorTest.scala b/lang/tests/src/test/scala/com/wavesplatform/lang/parser/error/LetDefParseErrorTest.scala new file mode 100644 index 00000000000..e6558c9249f --- /dev/null +++ b/lang/tests/src/test/scala/com/wavesplatform/lang/parser/error/LetDefParseErrorTest.scala @@ -0,0 +1,123 @@ +package com.wavesplatform.lang.parser.error + +class LetDefParseErrorTest extends ParseErrorTest { + property("missing '=' of let definition") { + assert( + """ + | let x = 1 + | let y x + """.stripMargin, + """Parse error: expected "="""", + 17, + 19, + "y " + ) + } + + property("missing 'let' keyword") { + assert( + """ + | let x = 1 + | y = x + """.stripMargin, + """Parse error: expected "let" or "strict" keyword""", + 13, + 14, + "y" + ) + } + + property("'let' keyword without definition") { + assert( + """ + | let x = 1 + | let + """.stripMargin, + """Parse error: expected variable name""", + 13, + 16, + "let", + endExpr = false + ) + } + + property("missing variable name") { + assert( + """ + | let x = 1 + | let = 1 + """.stripMargin, + """Parse error: expected variable name""", + 13, + 16, + "let" + ) + } + + property("missing let body") { + assert( + """ + | let x = 1 + | let y = + """.stripMargin, + """Parse error: expected let body""", + 19, + 27, + "=\n ", + endExpr = false + ) + } + + property("missing closing curly brace of let body") { + assert( + """ + | let x = { + | true + | + """.stripMargin, + """Parse error: expected "}"""", + 18, + 27, + "e\n\n ", + endExpr = false + ) + } + + property("missing result expression") { + assert( + """ + | let x = { + | func f() = { 1 } + | } + """.stripMargin, + """Parse error: expected expression""", + 30, + 31, + "}" + ) + } + + property("let name started from digit") { + assert( + """ + | let 1call = 1 + """.stripMargin, + """Parse error: expected character or "_" at start of the definition""", + 6, + 11, + "1call" + ) + } + + property("illegal character in let name") { + assert( + """ + | let c@ll + """.stripMargin, + """Parse error: expected character, digit or "_" for the definition""", + 7, + 10, + "@ll" + ) + } +} diff --git a/lang/tests/src/test/scala/com/wavesplatform/lang/parser/error/ParseErrorTest.scala b/lang/tests/src/test/scala/com/wavesplatform/lang/parser/error/ParseErrorTest.scala new file mode 100644 index 00000000000..48e87ec5c2f --- /dev/null +++ b/lang/tests/src/test/scala/com/wavesplatform/lang/parser/error/ParseErrorTest.scala @@ -0,0 +1,24 @@ +package com.wavesplatform.lang.parser.error + +import com.wavesplatform.lang.directives.values.V6 +import com.wavesplatform.lang.v1.compiler.TestCompiler +import com.wavesplatform.test.PropSpec +import org.scalatest.Assertion + +abstract class ParseErrorTest extends PropSpec { + protected def assert( + script: String, + error: String, + start: Int, + end: Int, + highlighting: String, + endExpr: Boolean = true, + onlyDApp: Boolean = false + ): Assertion = { + val fullError = s"$error in $start-$end" + val expr = if (endExpr) script + "\ntrue" else script + TestCompiler(V6).compile(script) shouldBe Left(fullError) + if (!onlyDApp) TestCompiler(V6).compileExpressionE(expr) shouldBe Left(fullError) + script.slice(start, end) shouldBe highlighting + } +} diff --git a/lang/tests/src/test/scala/com/wavesplatform/lang/parser/error/RoundBraceParseErrorTest.scala b/lang/tests/src/test/scala/com/wavesplatform/lang/parser/error/RoundBraceParseErrorTest.scala new file mode 100644 index 00000000000..fe5390bd622 --- /dev/null +++ b/lang/tests/src/test/scala/com/wavesplatform/lang/parser/error/RoundBraceParseErrorTest.scala @@ -0,0 +1,174 @@ +package com.wavesplatform.lang.parser.error + +class RoundBraceParseErrorTest extends ParseErrorTest { + property("missing closing round brace of expression") { + assert( + """ + | let a = 1 + | let b = (2 - (a + 1) / 3 + | let c = a + b + """.stripMargin, + """Parse error: expected ")"""", + 36, + 39, + "3\n " + ) + } + + property("missing closing round brace of function call") { + assert( + """ + | func f(a: Int, b: Int) = a + b + | let c = f(1, 2 + | func g() = c + """.stripMargin, + """Parse error: expected ")"""", + 47, + 50, + "2\n " + ) + } + + property("missing closing round brace of function call without arguments") { + assert( + """ + | func f() = 0 + | let a = f( + | func g() = a + """.stripMargin, + """Parse error: expected ")"""", + 25, + 28, + "(\n ", + endExpr = false + ) + } + + property("missing closing round brace of OOP style call") { + assert( + """ + | func f(a: Int, b: Int) = a + b + | let c = 1.f(2 + | func g() = c + """.stripMargin, + """Parse error: expected ")"""", + 46, + 49, + "2\n " + ) + } + + property("missing closing round brace of OOP style call without arguments") { + assert( + """ + | let c = "".parseInt( + | func g() = c + """.stripMargin, + """Parse error: expected ")"""", + 21, + 24, + "(\n ", + endExpr = false + ) + } + + property("missing closing round brace of FOLD macro") { + assert( + """ + | func sum(a:Int, b:Int) = a + b + | let arr = [1, 2, 3, 4, 5] + | let r = FOLD<5>(arr, 9, sum + | func f() = 1 + """.stripMargin, + """Parse error: expected ")"""", + 87, + 90, + "m\n " + ) + } + + property("missing closing round brace of tuple value definition") { + assert( + """ + | let a = (1, 2 + | let b = 1 + """.stripMargin, + """Parse error: expected ")"""", + 14, + 17, + "2\n " + ) + } + + property("missing closing round brace of tuple type definition") { + assert( + """ + | func f(a: Int, b: (Int, (String, List[Int]), c: List[Boolean]) = 1 + | let b = 1 + """.stripMargin, + """Parse error: expected ")"""", + 47, + 48, + "c" + ) + } + + property("missing closing round brace of tuple destructure") { + assert( + """ + | let (a, b, c = (1, 2, 3) + | func f() = 1 + """.stripMargin, + """Parse error: expected ")"""", + 13, + 15, + "c " + ) + } + + property("missing closing round brace of annotation argument") { + assert( + """ + | @Callable(i + | func f() = [] + """.stripMargin, + """Parse error: expected ")"""", + 12, + 15, + "i\n ", + onlyDApp = true + ) + } + + property("missing closing round brace of tuple match pattern") { + assert( + """ + | let x = + | match (1, (base58'', if (true) then 1 else "")) { + | case (_: Int, (_, _: String) => true + | case _ => throw() + | } + """.stripMargin, + """Parse error: expected ")"""", + 95, + 97, + ") " + ) + } + + property("missing closing round brace of object match pattern") { + assert( + """ + | let x = + | match Address(base58'') { + | case Address(bytes = base58'' => true + | case _ => throw() + | } + """.stripMargin, + """Parse error: expected ")"""", + 72, + 74, + "' " + ) + } +} diff --git a/lang/tests/src/test/scala/com/wavesplatform/lang/parser/error/SquareBraceParseErrorTest.scala b/lang/tests/src/test/scala/com/wavesplatform/lang/parser/error/SquareBraceParseErrorTest.scala new file mode 100644 index 00000000000..619628c4915 --- /dev/null +++ b/lang/tests/src/test/scala/com/wavesplatform/lang/parser/error/SquareBraceParseErrorTest.scala @@ -0,0 +1,79 @@ +package com.wavesplatform.lang.parser.error + +class SquareBraceParseErrorTest extends ParseErrorTest { + property("missing closing square brace of list definition") { + assert( + """ + | let x = [1, 2, 3 + | func f() = true + """.stripMargin, + """Parse error: expected "]"""", + 17, + 20, + "3\n " + ) + } + + property("missing closing square brace of deep list definition") { + assert( + """ + | let x = [[1], 2, [, [[[]]] + | func f() = true + """.stripMargin, + """Parse error: expected "]"""", + 19, + 20, + "[" + ) + } + + property("missing closing square brace of accessing by index") { + assert( + """ + | let list = [1, 2, 3] + | let a = list[1 + | func f() = true + """.stripMargin, + """Parse error: expected "]"""", + 37, + 40, + "1\n " + ) + } + + property("missing closing square brace of accessing by expression index") { + assert( + """ + | let a = [[1]][[1, 2, 3][1] + | func f() = true + """.stripMargin, + """Parse error: expected "]"""", + 27, + 30, + "]\n " + ) + } + + property("missing closing square brace of generic type") { + assert( + """func f(a: List[List[String]|Int, b: ByteVector) = true""", + """Parse error: expected "]"""", + 10, + 31, + "List[List[String]|Int" + ) + } + + property("missing closing square brace of generic call") { + assert( + """ + | let a = (if true then 1 else unit).exactAs[Int + | func f() = 1 + """.stripMargin, + """Parse error: expected "]"""", + 47, + 50, + "t\n " + ) + } +} diff --git a/lang/tests/src/test/scala/com/wavesplatform/lang/ContractSerdeTest.scala b/lang/tests/src/test/scala/com/wavesplatform/lang/serde/ContractSerdeTest.scala similarity index 97% rename from lang/tests/src/test/scala/com/wavesplatform/lang/ContractSerdeTest.scala rename to lang/tests/src/test/scala/com/wavesplatform/lang/serde/ContractSerdeTest.scala index 300a44322ad..7a119572612 100644 --- a/lang/tests/src/test/scala/com/wavesplatform/lang/ContractSerdeTest.scala +++ b/lang/tests/src/test/scala/com/wavesplatform/lang/serde/ContractSerdeTest.scala @@ -1,15 +1,15 @@ -package com.wavesplatform.lang +package com.wavesplatform.lang.serde import com.google.protobuf.ByteString import com.wavesplatform.common.utils.EitherExt2 -import com.wavesplatform.lang.contract.DApp._ import com.wavesplatform.lang.contract.DApp +import com.wavesplatform.lang.contract.DApp.* import com.wavesplatform.lang.contract.serialization.{ContractSerDe, ContractSerDeV1, ContractSerDeV2} import com.wavesplatform.lang.v1.ContractLimits -import com.wavesplatform.lang.v1.compiler.Terms._ +import com.wavesplatform.lang.v1.compiler.Terms.* import com.wavesplatform.protobuf.dapp.DAppMeta import com.wavesplatform.protobuf.dapp.DAppMeta.CallableFuncSignature -import com.wavesplatform.test._ +import com.wavesplatform.test.* import org.scalatest.Assertion class ContractSerdeTest extends FreeSpec { diff --git a/lang/tests/src/test/scala/com/wavesplatform/lang/SerdeTest.scala b/lang/tests/src/test/scala/com/wavesplatform/lang/serde/SerdeTest.scala similarity index 97% rename from lang/tests/src/test/scala/com/wavesplatform/lang/SerdeTest.scala rename to lang/tests/src/test/scala/com/wavesplatform/lang/serde/SerdeTest.scala index 2525f333a31..0d9c56bd73e 100644 --- a/lang/tests/src/test/scala/com/wavesplatform/lang/SerdeTest.scala +++ b/lang/tests/src/test/scala/com/wavesplatform/lang/serde/SerdeTest.scala @@ -1,20 +1,20 @@ -package com.wavesplatform.lang +package com.wavesplatform.lang.serde -import java.nio.charset.StandardCharsets import com.wavesplatform.common.state.ByteStr import com.wavesplatform.common.utils.EitherExt2 -import com.wavesplatform.lang.directives.values._ +import com.wavesplatform.lang.directives.values.* import com.wavesplatform.lang.script.Script -import com.wavesplatform.lang.v1.compiler.Terms._ +import com.wavesplatform.lang.v1.FunctionHeader +import com.wavesplatform.lang.v1.compiler.Terms.* import com.wavesplatform.lang.v1.compiler.Types.CASETYPEREF import com.wavesplatform.lang.v1.compiler.{ExpressionCompiler, Terms} import com.wavesplatform.lang.v1.evaluator.ctx.impl.PureContext import com.wavesplatform.lang.v1.parser.Expressions -import com.wavesplatform.lang.v1.FunctionHeader import com.wavesplatform.lang.v1.serialization.{SerdeV1, SerdeV2} -import com.wavesplatform.test._ +import com.wavesplatform.test.* import org.scalacheck.{Arbitrary, Gen} +import java.nio.charset.StandardCharsets import scala.util.Try class SerdeTest extends FreeSpec { diff --git a/repl/jvm/src/test/scala/com/wavesplatform/lang/v1/ReplTest.scala b/repl/jvm/src/test/scala/com/wavesplatform/lang/v1/ReplTest.scala index d3ad28c6531..60e4925a951 100644 --- a/repl/jvm/src/test/scala/com/wavesplatform/lang/v1/ReplTest.scala +++ b/repl/jvm/src/test/scala/com/wavesplatform/lang/v1/ReplTest.scala @@ -44,8 +44,8 @@ class ReplTest extends AnyPropSpec with Matchers { property("syntax errors") { val repl = Repl() - await(repl.execute(""" let a = {{1} """)) shouldBe Left("Compilation failed: [expected a value's expression in 9-9]") - await(repl.execute(""" 1 %% 2 """)) shouldBe Left("Compilation failed: [expected a second operator in 4-4]") + await(repl.execute("""let a = {{1}""")) shouldBe Left("Can't parse 'let a = {{1}'") + await(repl.execute("""1 %% 2""")) shouldBe Left("Compilation failed: [expected a second operator in 3-3]") } property("logic errors") { diff --git a/repl/shared/src/main/scala/com/wavesplatform/lang/v1/repl/ReplEngine.scala b/repl/shared/src/main/scala/com/wavesplatform/lang/v1/repl/ReplEngine.scala index f7ce2ba03b8..270f8230578 100644 --- a/repl/shared/src/main/scala/com/wavesplatform/lang/v1/repl/ReplEngine.scala +++ b/repl/shared/src/main/scala/com/wavesplatform/lang/v1/repl/ReplEngine.scala @@ -37,7 +37,7 @@ class ReplEngine[F[_]: Monad] { private def parse(expr: String): Either[String, EXPR] = Parser - .parseExprOrDecl(expr) + .parseReplExpr(expr) .fold( { case _ => Left(s"Can't parse '$expr'") }, { case (result, _) => Right(result) } From e9e1e1983fbe536aaf638c3a8022410c5834e6d0 Mon Sep 17 00:00:00 2001 From: Artyom Sayadyan Date: Tue, 1 Nov 2022 13:49:08 +0300 Subject: [PATCH 10/11] NODE-2526 Fix IDE highlighting type error (#3777) --- lang/js/src/main/scala/JsApiUtils.scala | 7 +++ .../lang/v1/compiler/ExpressionCompiler.scala | 48 ++++++++--------- lang/tests-js/src/test/scala/JsAPITest.scala | 53 +++++++++++++++++++ .../ExpressionCompilerWithParserV2Test.scala | 8 +-- 4 files changed, 86 insertions(+), 30 deletions(-) diff --git a/lang/js/src/main/scala/JsApiUtils.scala b/lang/js/src/main/scala/JsApiUtils.scala index 516b9026a23..fdf15621f4d 100644 --- a/lang/js/src/main/scala/JsApiUtils.scala +++ b/lang/js/src/main/scala/JsApiUtils.scala @@ -144,6 +144,13 @@ object JsApiUtils { mergeJSObjects(commonDataObj, additionalDataObj) } + case Expressions.FOLD(_, limit, value, acc, func, _, _) => + val additionalDataObj = jObj.applyDynamic("apply")( + "name" -> s"FOLD<$limit>", + "args" -> js.Array(serExpr(value), serExpr(acc), func.key.toString: js.Any) + ) + mergeJSObjects(commonDataObj, additionalDataObj) + case Expressions.MATCH(_, expr, cases, _, ctxOpt) => { val additionalDataObj = jObj.applyDynamic("apply")( "expr" -> serExpr(expr), diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/compiler/ExpressionCompiler.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/compiler/ExpressionCompiler.scala index e142d2651df..fae94c28753 100644 --- a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/compiler/ExpressionCompiler.scala +++ b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/compiler/ExpressionCompiler.scala @@ -117,16 +117,16 @@ object ExpressionCompiler { .recover { case err => CompilationStepResultExpr(ctx, FAILED_EXPR(), NOTHING, expr, List(err)) } expr match { - case x: Expressions.CONST_LONG => CompilationStepResultExpr(ctx, CONST_LONG(x.value): EXPR, LONG: FINAL, x: Expressions.EXPR).pure[CompileM] + case x: Expressions.CONST_LONG => CompilationStepResultExpr(ctx, CONST_LONG(x.value), LONG, x).pure[CompileM] case x: Expressions.CONST_BYTESTR => handlePart(x.value).flatMap(b => liftEither(adjustByteStr(x, b))) case x: Expressions.CONST_STRING => handlePart(x.value).flatMap(s => liftEither(adjustStr(x, s))) - case x: Expressions.TRUE => CompilationStepResultExpr(ctx, TRUE: EXPR, BOOLEAN: FINAL, x: Expressions.EXPR).pure[CompileM] - case x: Expressions.FALSE => CompilationStepResultExpr(ctx, FALSE: EXPR, BOOLEAN: FINAL, x: Expressions.EXPR).pure[CompileM] + case x: Expressions.TRUE => CompilationStepResultExpr(ctx, TRUE, BOOLEAN, x).pure[CompileM] + case x: Expressions.FALSE => CompilationStepResultExpr(ctx, FALSE, BOOLEAN, x).pure[CompileM] case x: Expressions.INVALID => CompilationStepResultExpr( ctx, - FAILED_EXPR(): EXPR, + FAILED_EXPR(), NOTHING, x: Expressions.EXPR, List(Generic(x.position.start, x.position.end, x.message)) @@ -138,7 +138,7 @@ object ExpressionCompiler { case Expressions.REF(p, key, _, _) => compileRef(p, key, saveExprContext) case Expressions.FUNCTION_CALL(p, name, args, _, _) => compileFunctionCall(p, name, args, saveExprContext, allowIllFormedStrings) case Expressions.MATCH(p, ex, cases, _, _) => compileMatch(p, ex, cases.toList, saveExprContext, allowIllFormedStrings) - case Expressions.FOLD(p, limit, list, acc, f, _, _) => compileFold(p, limit, list, acc, f.key) + case f: Expressions.FOLD => compileFold(f) case Expressions.GENERIC_FUNCTION_CALL(p, e, name, t, _, _) => compileGenericFunctionCall(p, e, name, t, saveExprContext, allowIllFormedStrings) case Expressions.BINARY_OP(p, a, op, b, _, _) => @@ -176,7 +176,8 @@ object ExpressionCompiler { condWithErr._1.parseNodeExpr, ifTrue.parseNodeExpr, ifFalse.parseNodeExpr, - ctxOpt = saveExprContext.toOption(ctx.getSimpleContext()) + Some(t), + saveExprContext.toOption(ctx.getSimpleContext()) ) errorList = condWithErr._1.errors ++ ifTrue.errors ++ ifFalse.errors @@ -185,7 +186,7 @@ object ExpressionCompiler { ctx, IF(condWithErr._1.expr, ifTrue.expr, ifFalse.expr), t, - parseNodeExpr.copy(resultType = Some(t)), + parseNodeExpr, errorList ) } else { @@ -507,7 +508,7 @@ object ExpressionCompiler { compLetResult.parseNodeExpr, compiledBody.parseNodeExpr, compiledBody.parseNodeExpr.resultType, - ctxOpt = saveExprContext.toOption(compiledBody.ctx.getSimpleContext()) + saveExprContext.toOption(compiledBody.ctx.getSimpleContext()) ) result = if (!compLetResult.dec.isItFailed) { LET_BLOCK(compLetResult.dec.asInstanceOf[LET], compiledBody.expr) @@ -604,7 +605,8 @@ object ExpressionCompiler { p, namePart, compiledArgs.map(_.parseNodeExpr), - ctxOpt = saveExprContext.toOption(ctx.getSimpleContext()) + funcCallWithErr._1.map(_._2), + saveExprContext.toOption(ctx.getSimpleContext()) ) result = if (errorList.isEmpty) { @@ -647,7 +649,7 @@ object ExpressionCompiler { ctx, REF(keyWithErr._1.get), typeWithErr._1.get, - Expressions.REF(p, keyPart, None, ctxOpt = saveExprContext.toOption(ctx.getSimpleContext())) + Expressions.REF(p, keyPart, typeWithErr._1, saveExprContext.toOption(ctx.getSimpleContext())) ) } else { CompilationStepResultExpr( @@ -660,23 +662,17 @@ object ExpressionCompiler { } } yield result - private def compileFold( - p: Pos, - limit: Int, - list: Expressions.EXPR, - acc: Expressions.EXPR, - func: PART[String] - ): CompileM[CompilationStepResultExpr] = + private def compileFold(fold: Expressions.FOLD): CompileM[CompilationStepResultExpr] = for { - (compiledList, listType, _, compileListErrors) <- compileExpr(list) - name = s"FOLD<$limit>" + (compiledList, listType, _, compileListErrors) <- compileExpr(fold.list) + name = s"FOLD<${fold.limit}>" listInnerType <- (listType match { case list: LIST => Right(list.innerType) - case other => Left(Generic(p.start, p.end, s"First $name argument should be List[A], but $other found")) + case other => Left(Generic(fold.position.start, fold.position.end, s"First $name argument should be List[A], but $other found")) }).toCompileM - (compiledAcc, accType, accRaw, compileAccErrors) <- compileExpr(acc) - funcName <- handlePart(func) - ctx <- get[Id, CompilerContext, CompilationError] + (compiledAcc, accType, _, compileAccErrors) <- compileExpr(fold.acc) + funcName <- handlePart(fold.func.key) + ctx <- get[Id, CompilerContext, CompilationError] compiledFunc <- ctx .functionTypeSignaturesByName(funcName, args = 2) .collectFirst { @@ -686,14 +682,14 @@ object ExpressionCompiler { .getOrElse { val accTypeStr = if (accType == NOTHING) ANY else accType val listInnerTypeStr = if (listInnerType == NOTHING) ANY else listInnerType - Left(Generic(p.start, p.end, s"Can't find suitable function $funcName(a: $accTypeStr, b: $listInnerTypeStr) for $name")) + Left(Generic(fold.position.start, fold.position.end, s"Can't find suitable function $funcName(a: $accTypeStr, b: $listInnerTypeStr) for $name")) } .toCompileM _ <- set[Id, CompilerContext, CompilationError](ctx.copy(foldIdx = ctx.foldIdx + 1)) resultType = compiledFunc.args.head._2.asInstanceOf[FINAL] compiledFold <- { - val unwrapped = CompilerMacro.unwrapFold(ctx.foldIdx, limit, compiledList, compiledAcc, compiledFunc.header) - CompilationStepResultExpr(ctx, unwrapped, resultType, accRaw, compileListErrors ++ compileAccErrors) + val unwrapped = CompilerMacro.unwrapFold(ctx.foldIdx, fold.limit, compiledList, compiledAcc, compiledFunc.header) + CompilationStepResultExpr(ctx, unwrapped, resultType, fold.copy(resultType = Some(resultType)), compileListErrors ++ compileAccErrors) .asRight[CompilationError] .toCompileM } diff --git a/lang/tests-js/src/test/scala/JsAPITest.scala b/lang/tests-js/src/test/scala/JsAPITest.scala index 80bc6fe89bf..88f74e9100c 100644 --- a/lang/tests-js/src/test/scala/JsAPITest.scala +++ b/lang/tests-js/src/test/scala/JsAPITest.scala @@ -1,6 +1,8 @@ import com.wavesplatform.lang.directives.values.{V5, V6} import utest.* +import scala.scalajs.js.{Array, Dynamic, JSON} + object JsAPITest extends JsTestBase { private def simpleDApp(result: String): String = dApp( @@ -49,5 +51,56 @@ object JsAPITest extends JsTestBase { r.userFunctionComplexities ==> Map("f" -> 2) r.globalVariableComplexities ==> Map("x" -> 1) } + + test("AST result type for declarations") { + val compiled = JsAPI.parseAndCompile( + dApp( + """ + | func sum(acc: List[Int], elem: Int) = acc :+ elem + | let arr = [1, 2, 3, 4, 5] + | let letFold = FOLD<5>(arr, [], sum) + | + | @Callable(i) + | func default() = { + | let letCall = i.caller.toString() + | let letIf = if (true) then 1 else "" + | let letMatch = match letIf { + | case _: Int => true + | case _: String => Address(base58'') + | } + | func funcRef() = letCall + | [] + | } + """.stripMargin, + V6 + ), + 3 + ) + val callables = compiled.dAppAst.annFuncList.asInstanceOf[Array[Dynamic]] + + val invocation = callables(0).func.expr.dec.expr.args.asInstanceOf[Array[Dynamic]].apply(0).ref + invocation.name ==> "i" + invocation.resultType.`type` ==> "Invocation" + + val letCall = callables(0).func.expr.dec + letCall.name.value ==> "letCall" + letCall.expr.resultType.`type` ==> "String" + + val letIf = callables(0).func.expr.body.dec + letIf.name.value ==> "letIf" + JSON.stringify(letIf.expr.resultType.unionTypes) ==> """[{"type":"Int"},{"type":"String"}]""" + + val letMatch = callables(0).func.expr.body.body.dec + letMatch.name.value ==> "letMatch" + JSON.stringify(letMatch.expr.resultType.unionTypes) ==> """[{"type":"Boolean"},{"type":"Address"}]""" + + val funcRef = callables(0).func.expr.body.body.body.dec + funcRef.name.value ==> "funcRef" + funcRef.expr.resultType.`type` ==> "String" + + val letFold = compiled.dAppAst.decList.asInstanceOf[Array[Dynamic]].apply(2) + letFold.name.value ==> "letFold" + JSON.stringify(letFold.expr.resultType) ==> """{"listOf":{"type":"Int"}}""" + } } } diff --git a/lang/tests/src/test/scala/com/wavesplatform/lang/compiler/ExpressionCompilerWithParserV2Test.scala b/lang/tests/src/test/scala/com/wavesplatform/lang/compiler/ExpressionCompilerWithParserV2Test.scala index 48c11cf5bdc..8735be1687f 100644 --- a/lang/tests/src/test/scala/com/wavesplatform/lang/compiler/ExpressionCompilerWithParserV2Test.scala +++ b/lang/tests/src/test/scala/com/wavesplatform/lang/compiler/ExpressionCompilerWithParserV2Test.scala @@ -39,7 +39,7 @@ class ExpressionCompilerWithParserV2Test extends PropSpec { |else | false | - |""".stripMargin + """.stripMargin val result = compile(script) @@ -60,13 +60,13 @@ class ExpressionCompilerWithParserV2Test extends PropSpec { FUNCTION_CALL( AnyPos, PART.VALID(AnyPos, "+"), - List(REF(AnyPos, PART.VALID(AnyPos, "foo"), None, None), REF(AnyPos, PART.VALID(AnyPos, "bar"), None, None)), - None, + List(REF(AnyPos, PART.VALID(AnyPos, "foo"), Some(LONG), None), REF(AnyPos, PART.VALID(AnyPos, "bar"), Some(LONG), None)), + Some(LONG), None ), CONST_LONG(AnyPos, 123456, None) ), - None, + Some(BOOLEAN), None ), TRUE(AnyPos, None), From 32e1a9b1d1842b659c40277394f81939fbcb5691 Mon Sep 17 00:00:00 2001 From: Sergey Nazarov Date: Wed, 2 Nov 2022 11:47:39 +0400 Subject: [PATCH 11/11] Fixed benchmark compilation (#3781) --- .../com/wavesplatform/RollbackBenchmark.scala | 12 ++-- .../v1/AddressFromPublicKeyBenchmark.scala | 2 +- .../lang/v1/BigIntToStringBenchmark.scala | 2 +- .../v1/EnvironmentFunctionsBenchmark.scala | 16 ++--- .../lang/v1/EvaluatorV2Benchmark.scala | 11 ++-- .../wavesplatform/lang/v1/FoldBenchmark.scala | 2 +- .../lang/v1/FractionBigIntBenchmark.scala | 6 +- .../lang/v1/FractionIntBenchmark.scala | 31 +++++----- .../lang/v1/ListIndexOfBenchmark.scala | 6 +- .../lang/v1/PowBigIntBenchmark.scala | 2 +- .../lang/v1/PowIntBenchmark.scala | 4 +- .../lang/v1/PureFunctionsRebenchmark.scala | 6 +- .../lang/v1/ScriptEvaluatorBenchmark.scala | 38 ++++++------ .../lang/v1/SqrtCbrtBigIntBenchmark.scala | 2 +- .../lang/v1/SqrtIntBenchmark.scala | 4 +- .../com/wavesplatform/lang/v1/package.scala | 3 +- .../protobuf/SmartNoSmartBenchmark.scala | 58 ++++++++++++++++--- build.sbt | 2 +- 18 files changed, 125 insertions(+), 82 deletions(-) diff --git a/benchmark/src/test/scala/com/wavesplatform/RollbackBenchmark.scala b/benchmark/src/test/scala/com/wavesplatform/RollbackBenchmark.scala index 5a957e59c91..dd79c49b02a 100644 --- a/benchmark/src/test/scala/com/wavesplatform/RollbackBenchmark.scala +++ b/benchmark/src/test/scala/com/wavesplatform/RollbackBenchmark.scala @@ -7,11 +7,11 @@ import com.google.protobuf.ByteString import com.wavesplatform.account.{Address, AddressScheme, KeyPair} import com.wavesplatform.block.Block import com.wavesplatform.common.state.ByteStr -import com.wavesplatform.common.utils._ -import com.wavesplatform.database.{openDB, LevelDBWriter} +import com.wavesplatform.common.utils.* +import com.wavesplatform.database.{LevelDBWriter, openDB} import com.wavesplatform.protobuf.transaction.PBRecipients import com.wavesplatform.state.{Diff, Portfolio} -import com.wavesplatform.transaction.{GenesisTransaction, Proofs} +import com.wavesplatform.transaction.{GenesisTransaction, Proofs, TxDecimals, TxPositiveAmount} import com.wavesplatform.transaction.Asset.IssuedAsset import com.wavesplatform.transaction.assets.IssueTransaction import com.wavesplatform.utils.{NTP, ScorexLogging} @@ -40,11 +40,11 @@ object RollbackBenchmark extends ScorexLogging { issuer.publicKey, ByteString.copyFromUtf8("asset-" + i), ByteString.EMPTY, - 100000e2.toLong, - 2.toByte, + TxPositiveAmount.unsafeFrom(100000e2.toLong), + TxDecimals.unsafeFrom(2.toByte), false, None, - 1e8.toLong, + TxPositiveAmount.unsafeFrom(1e8.toLong), time.getTimestamp(), Proofs(ByteStr(new Array[Byte](64))), AddressScheme.current.chainId diff --git a/benchmark/src/test/scala/com/wavesplatform/lang/v1/AddressFromPublicKeyBenchmark.scala b/benchmark/src/test/scala/com/wavesplatform/lang/v1/AddressFromPublicKeyBenchmark.scala index 2b67cc8d769..4d1259a5397 100644 --- a/benchmark/src/test/scala/com/wavesplatform/lang/v1/AddressFromPublicKeyBenchmark.scala +++ b/benchmark/src/test/scala/com/wavesplatform/lang/v1/AddressFromPublicKeyBenchmark.scala @@ -29,7 +29,7 @@ class AddressFromPublicKeyBenchmark { @State(Scope.Benchmark) class PkSt extends EthHelpers { val ds = DirectiveSet(V6, Account, Expression).fold(null, identity) - val ctx = lazyContexts(ds).value().evaluationContext(EnvironmentFunctionsBenchmark.environment) + val ctx = lazyContexts((ds, true)).value().evaluationContext(EnvironmentFunctionsBenchmark.environment) val wavesPk = ByteStr(curve25519.generateKeypair._2) val exprWaves = TestCompiler(V6).compileExpression(s"addressFromPublicKey(base58'$wavesPk')").expr.asInstanceOf[EXPR] diff --git a/benchmark/src/test/scala/com/wavesplatform/lang/v1/BigIntToStringBenchmark.scala b/benchmark/src/test/scala/com/wavesplatform/lang/v1/BigIntToStringBenchmark.scala index 5315fbf5744..16b8652c738 100644 --- a/benchmark/src/test/scala/com/wavesplatform/lang/v1/BigIntToStringBenchmark.scala +++ b/benchmark/src/test/scala/com/wavesplatform/lang/v1/BigIntToStringBenchmark.scala @@ -27,7 +27,7 @@ class BigIntToStringBenchmark { @State(Scope.Benchmark) class BigIntToStringSt { val ds = DirectiveSet(V5, Account, Expression).fold(null, identity) - val ctx = lazyContexts(ds).value().evaluationContext(Common.emptyBlockchainEnvironment()) + val ctx = lazyContexts((ds, true)).value().evaluationContext(Common.emptyBlockchainEnvironment()) val expr = FUNCTION_CALL( Native(BIGINT_TO_STRING), diff --git a/benchmark/src/test/scala/com/wavesplatform/lang/v1/EnvironmentFunctionsBenchmark.scala b/benchmark/src/test/scala/com/wavesplatform/lang/v1/EnvironmentFunctionsBenchmark.scala index bd502a56974..38d9b1e11b0 100644 --- a/benchmark/src/test/scala/com/wavesplatform/lang/v1/EnvironmentFunctionsBenchmark.scala +++ b/benchmark/src/test/scala/com/wavesplatform/lang/v1/EnvironmentFunctionsBenchmark.scala @@ -4,7 +4,7 @@ import java.nio.charset.StandardCharsets import java.util.concurrent.{ThreadLocalRandom, TimeUnit} import cats.Id -import cats.syntax.bifunctor._ +import cats.syntax.bifunctor.* import com.wavesplatform.account import com.wavesplatform.account.PublicKey import com.wavesplatform.common.state.ByteStr @@ -13,18 +13,19 @@ import com.wavesplatform.crypto.Curve25519 import com.wavesplatform.lang.directives.DirectiveSet import com.wavesplatform.lang.directives.values.{Account, DApp, V4} import com.wavesplatform.lang.script.Script -import com.wavesplatform.lang.v1.EnvironmentFunctionsBenchmark._ +import com.wavesplatform.lang.v1.EnvironmentFunctionsBenchmark.* import com.wavesplatform.lang.v1.compiler.Terms.{CONST_STRING, EVALUATED, EXPR, FUNCTION_CALL} +import com.wavesplatform.lang.v1.evaluator.Log import com.wavesplatform.lang.v1.evaluator.ctx.EvaluationContext import com.wavesplatform.lang.v1.evaluator.ctx.impl.EnvironmentFunctions import com.wavesplatform.lang.v1.evaluator.ctx.impl.waves.{Functions, WavesContext} -import com.wavesplatform.lang.v1.traits._ +import com.wavesplatform.lang.v1.traits.* import com.wavesplatform.lang.v1.traits.domain.Recipient.Address import com.wavesplatform.lang.v1.traits.domain.{BlockInfo, Recipient, ScriptAssetInfo, Tx} import com.wavesplatform.lang.{Common, Global, ValidationError} import com.wavesplatform.wallet.Wallet import monix.eval.Coeval -import org.openjdk.jmh.annotations._ +import org.openjdk.jmh.annotations.* import org.openjdk.jmh.infra.Blackhole import scala.util.Random @@ -88,7 +89,7 @@ class EnvironmentFunctionsBenchmark { @Benchmark def addressFromString(st: AddressFromString, bh: Blackhole): Unit = { val i = Random.nextInt(100) - bh.consume(eval(st.ctx, st.expr(i), V4, true)) + bh.consume(eval(st.ctx, st.expr(i), V4)) } } @@ -130,14 +131,15 @@ object EnvironmentFunctionsBenchmark { Right(Address(ByteStr(PublicKey(publicKey).toAddress.bytes))) override def accountScript(addressOrAlias: Recipient): Option[Script] = ??? - override def callScript( + + def callScript( dApp: Address, func: String, args: List[EVALUATED], payments: Seq[(Option[Array[Byte]], Long)], availableComplexity: Int, reentrant: Boolean - ): Coeval[(Either[ValidationError, EVALUATED], Int)] = ??? + ): Coeval[Id[(Either[ValidationError, (EVALUATED, Log[Id])], Int)]] = ??? } val environmentFunctions = new EnvironmentFunctions(environment) diff --git a/benchmark/src/test/scala/com/wavesplatform/lang/v1/EvaluatorV2Benchmark.scala b/benchmark/src/test/scala/com/wavesplatform/lang/v1/EvaluatorV2Benchmark.scala index 5e54cd583e2..93042b254d1 100644 --- a/benchmark/src/test/scala/com/wavesplatform/lang/v1/EvaluatorV2Benchmark.scala +++ b/benchmark/src/test/scala/com/wavesplatform/lang/v1/EvaluatorV2Benchmark.scala @@ -5,21 +5,22 @@ import java.util.concurrent.TimeUnit import cats.Id import com.wavesplatform.lang.Common import com.wavesplatform.lang.directives.values.{V1, V3} -import com.wavesplatform.lang.v1.EvaluatorV2Benchmark._ +import com.wavesplatform.lang.v1.EvaluatorV2Benchmark.* import com.wavesplatform.lang.v1.compiler.Terms.{EXPR, IF, TRUE} import com.wavesplatform.lang.v1.compiler.TestCompiler -import com.wavesplatform.lang.v1.evaluator.ctx.EvaluationContext +import com.wavesplatform.lang.v1.evaluator.EvaluatorV2 +import com.wavesplatform.lang.v1.evaluator.ctx.{EvaluationContext, LoggedEvaluationContext} import com.wavesplatform.lang.v1.evaluator.ctx.impl.PureContext import com.wavesplatform.lang.v1.traits.Environment -import org.openjdk.jmh.annotations._ +import org.openjdk.jmh.annotations.* import org.openjdk.jmh.infra.Blackhole import scala.annotation.tailrec object EvaluatorV2Benchmark { - val pureContext: CTX[Environment] = PureContext.build(V1, useNewPowPrecision = true, useNewPowPrecision = true).withEnvironment[Environment] + val pureContext: CTX[Environment] = PureContext.build(V1, useNewPowPrecision = true).withEnvironment[Environment] val pureEvalContext: EvaluationContext[Environment, Id] = pureContext.evaluationContext(Common.emptyBlockchainEnvironment()) - val evaluatorV2: EvaluatorV2 = new EvaluatorV2(LoggedEvaluationContext(_ => _ => (), pureEvalContext), V1, true) + val evaluatorV2: EvaluatorV2 = new EvaluatorV2(LoggedEvaluationContext(_ => _ => (), pureEvalContext), V1, true, true) } @OutputTimeUnit(TimeUnit.MILLISECONDS) diff --git a/benchmark/src/test/scala/com/wavesplatform/lang/v1/FoldBenchmark.scala b/benchmark/src/test/scala/com/wavesplatform/lang/v1/FoldBenchmark.scala index 941e880d9c0..90c9fa685bd 100644 --- a/benchmark/src/test/scala/com/wavesplatform/lang/v1/FoldBenchmark.scala +++ b/benchmark/src/test/scala/com/wavesplatform/lang/v1/FoldBenchmark.scala @@ -46,7 +46,7 @@ class FoldBenchmark { @State(Scope.Benchmark) class FoldSt { val ds = DirectiveSet(V5, Account, Expression).fold(null, identity) - val ctx = lazyContexts(ds).value().evaluationContext(Common.emptyBlockchainEnvironment()) + val ctx = lazyContexts((ds, true)).value().evaluationContext(Common.emptyBlockchainEnvironment()) val function = "func f(acc: Boolean, elem: ByteVector) = acc && sigVerify(elem, base58'', base58'')" diff --git a/benchmark/src/test/scala/com/wavesplatform/lang/v1/FractionBigIntBenchmark.scala b/benchmark/src/test/scala/com/wavesplatform/lang/v1/FractionBigIntBenchmark.scala index 6fa1c468f7d..a781505c3a4 100644 --- a/benchmark/src/test/scala/com/wavesplatform/lang/v1/FractionBigIntBenchmark.scala +++ b/benchmark/src/test/scala/com/wavesplatform/lang/v1/FractionBigIntBenchmark.scala @@ -10,7 +10,7 @@ import com.wavesplatform.lang.v1.FunctionHeader.Native import com.wavesplatform.lang.v1.compiler.Terms.{CONST_BIGINT, FUNCTION_CALL} import com.wavesplatform.lang.v1.evaluator.FunctionIds.{FRACTION_BIGINT, FRACTION_BIGINT_ROUNDS} import com.wavesplatform.lang.v1.evaluator.ctx.impl.{PureContext, Rounding} -import org.openjdk.jmh.annotations.{State, _} +import org.openjdk.jmh.annotations.* import org.openjdk.jmh.infra.Blackhole @OutputTimeUnit(TimeUnit.MICROSECONDS) @@ -42,7 +42,7 @@ class FractionBigIntBenchmark { @State(Scope.Benchmark) class FractionBigIntSt { val ds = DirectiveSet(V5, Account, Expression).fold(null, identity) - val ctx = lazyContexts(ds).value().evaluationContext(Common.emptyBlockchainEnvironment()) + val ctx = lazyContexts(ds -> true).value().evaluationContext(Common.emptyBlockchainEnvironment()) val max = CONST_BIGINT(PureContext.BigIntMax) val halfMax = CONST_BIGINT(PureContext.BigIntMax / 2) @@ -62,7 +62,7 @@ class FractionBigIntSt { val expr3 = FUNCTION_CALL( Native(FRACTION_BIGINT), - List(maxSqrt, maxSqrt, maxSqrt) + List(maxSqrt, maxSqrt, three) ) val expr1Round = FUNCTION_CALL( diff --git a/benchmark/src/test/scala/com/wavesplatform/lang/v1/FractionIntBenchmark.scala b/benchmark/src/test/scala/com/wavesplatform/lang/v1/FractionIntBenchmark.scala index 82ccbee348d..8d67d71f904 100644 --- a/benchmark/src/test/scala/com/wavesplatform/lang/v1/FractionIntBenchmark.scala +++ b/benchmark/src/test/scala/com/wavesplatform/lang/v1/FractionIntBenchmark.scala @@ -4,11 +4,12 @@ import java.util.concurrent.TimeUnit import com.wavesplatform.lang.Common import com.wavesplatform.lang.directives.DirectiveSet -import com.wavesplatform.lang.directives.values.{Account, Expression, V6} +import com.wavesplatform.lang.directives.values.{Account, Expression, V5, V6} import com.wavesplatform.lang.utils.lazyContexts -import com.wavesplatform.lang.v1.compiler.Terms.EXPR import com.wavesplatform.lang.v1.compiler.TestCompiler -import org.openjdk.jmh.annotations.{State, _} +import com.wavesplatform.lang.v1.evaluator.ContractEvaluator.LogExtraInfo +import com.wavesplatform.lang.v1.evaluator.EvaluatorV2 +import org.openjdk.jmh.annotations.* import org.openjdk.jmh.infra.Blackhole @OutputTimeUnit(TimeUnit.MICROSECONDS) @@ -19,22 +20,22 @@ import org.openjdk.jmh.infra.Blackhole @Measurement(iterations = 10, time = 1) class FractionIntBenchmark { @Benchmark - def fraction1(bh: Blackhole, s: St): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr1, V5, true)) + def fraction1(bh: Blackhole, s: St): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr1, LogExtraInfo(), V5, true, true)) @Benchmark - def fraction2(bh: Blackhole, s: St): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr2, V5, true)) + def fraction2(bh: Blackhole, s: St): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr2, LogExtraInfo(), V5, true, true)) @Benchmark - def fraction3(bh: Blackhole, s: St): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr3, V5, true)) + def fraction3(bh: Blackhole, s: St): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr3, LogExtraInfo(), V5, true, true)) @Benchmark - def fraction1Round(bh: Blackhole, s: St): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr1Round, V5, true)) + def fraction1Round(bh: Blackhole, s: St): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr1Round, LogExtraInfo(), V5, true, true)) @Benchmark - def fraction2Round(bh: Blackhole, s: St): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr2Round, V5, true)) + def fraction2Round(bh: Blackhole, s: St): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr2Round, LogExtraInfo(), V5, true, true)) @Benchmark - def fraction3Round(bh: Blackhole, s: St): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr3Round, V5, true)) + def fraction3Round(bh: Blackhole, s: St): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr3Round, LogExtraInfo(), V5, true, true)) } @State(Scope.Benchmark) @@ -45,11 +46,11 @@ class St { val max = Long.MaxValue val maxSqrt = 3037000499L - val expr1 = TestCompiler(V6).compileExpression(s"fraction($max / 2, 3, 3)").expr.asInstanceOf[EXPR] - val expr2 = TestCompiler(V6).compileExpression(s"fraction($max, -$max, -$max)").expr.asInstanceOf[EXPR] - val expr3 = TestCompiler(V6).compileExpression(s"fraction($maxSqrt, $maxSqrt, $maxSqrt)").expr.asInstanceOf[EXPR] + val expr1 = TestCompiler(V6).compileExpression(s"fraction($max / 2, 3, 3)").expr + val expr2 = TestCompiler(V6).compileExpression(s"fraction($max, -$max, -$max)").expr + val expr3 = TestCompiler(V6).compileExpression(s"fraction($maxSqrt, $maxSqrt, $maxSqrt)").expr - val expr1Round = TestCompiler(V6).compileExpression(s"fraction($max / 2, 3, 3, HALFEVEN)").expr.asInstanceOf[EXPR] - val expr2Round = TestCompiler(V6).compileExpression(s"fraction($max, -$max, -$max, HALFEVEN)").expr.asInstanceOf[EXPR] - val expr3Round = TestCompiler(V6).compileExpression(s"fraction($maxSqrt, $maxSqrt, $maxSqrt, HALFEVEN)").expr.asInstanceOf[EXPR] + val expr1Round = TestCompiler(V6).compileExpression(s"fraction($max / 2, 3, 3, HALFEVEN)").expr + val expr2Round = TestCompiler(V6).compileExpression(s"fraction($max, -$max, -$max, HALFEVEN)").expr + val expr3Round = TestCompiler(V6).compileExpression(s"fraction($maxSqrt, $maxSqrt, $maxSqrt, HALFEVEN)").expr } diff --git a/benchmark/src/test/scala/com/wavesplatform/lang/v1/ListIndexOfBenchmark.scala b/benchmark/src/test/scala/com/wavesplatform/lang/v1/ListIndexOfBenchmark.scala index 84d9d683f31..fff55fcfaf5 100644 --- a/benchmark/src/test/scala/com/wavesplatform/lang/v1/ListIndexOfBenchmark.scala +++ b/benchmark/src/test/scala/com/wavesplatform/lang/v1/ListIndexOfBenchmark.scala @@ -6,7 +6,7 @@ import com.wavesplatform.common.utils.EitherExt2 import com.wavesplatform.lang.v1.ListIndexOfBenchmark.ListIndexOfSt import com.wavesplatform.lang.v1.compiler.Terms.{CONST_STRING, EVALUATED} import com.wavesplatform.lang.v1.evaluator.ctx.impl.PureContext -import org.openjdk.jmh.annotations._ +import org.openjdk.jmh.annotations.* import org.openjdk.jmh.infra.Blackhole @OutputTimeUnit(TimeUnit.MICROSECONDS) @@ -41,10 +41,10 @@ object ListIndexOfBenchmark { val listWithMaxCmpWeightElements = IndexedSeq.fill(1000)(CONST_STRING("a" * (ContractLimits.MaxCmpWeight.toInt - 1) + "b").explicitGet()) val listWithMaxSizeElements = IndexedSeq.fill(1000)(CONST_STRING(("a" * (150 * 1024 - 1)) + "b").explicitGet()) - def indexOf(list: Seq[EVALUATED], element: EVALUATED): Either[String, EVALUATED] = + def indexOf(list: Seq[EVALUATED], element: EVALUATED) = PureContext.genericListIndexOf(element, list.indexOf, list.indexWhere) - def lastIndexOf(list: Seq[EVALUATED], element: EVALUATED): Either[String, EVALUATED] = + def lastIndexOf(list: Seq[EVALUATED], element: EVALUATED) = PureContext.genericListIndexOf(element, list.lastIndexOf(_), list.lastIndexWhere) } } diff --git a/benchmark/src/test/scala/com/wavesplatform/lang/v1/PowBigIntBenchmark.scala b/benchmark/src/test/scala/com/wavesplatform/lang/v1/PowBigIntBenchmark.scala index 1fe76d638be..4dd18892968 100644 --- a/benchmark/src/test/scala/com/wavesplatform/lang/v1/PowBigIntBenchmark.scala +++ b/benchmark/src/test/scala/com/wavesplatform/lang/v1/PowBigIntBenchmark.scala @@ -51,7 +51,7 @@ class PowBigIntBenchmark { @State(Scope.Benchmark) class PowBigIntSt { val ds = DirectiveSet(V5, Account, Expression).fold(null, identity) - val ctx = lazyContexts(ds).value().evaluationContext(Common.emptyBlockchainEnvironment()) + val ctx = lazyContexts(ds -> true).value().evaluationContext(Common.emptyBlockchainEnvironment()) val max = PureContext.BigIntMax val min = PureContext.BigIntMin diff --git a/benchmark/src/test/scala/com/wavesplatform/lang/v1/PowIntBenchmark.scala b/benchmark/src/test/scala/com/wavesplatform/lang/v1/PowIntBenchmark.scala index 4dff1649aca..568d2017025 100644 --- a/benchmark/src/test/scala/com/wavesplatform/lang/v1/PowIntBenchmark.scala +++ b/benchmark/src/test/scala/com/wavesplatform/lang/v1/PowIntBenchmark.scala @@ -8,7 +8,7 @@ import com.wavesplatform.lang.directives.values.{Account, Expression, V5} import com.wavesplatform.lang.utils.lazyContexts import com.wavesplatform.lang.v1.compiler.Terms.EXPR import com.wavesplatform.lang.v1.compiler.TestCompiler -import org.openjdk.jmh.annotations.{State, _} +import org.openjdk.jmh.annotations.* import org.openjdk.jmh.infra.Blackhole @OutputTimeUnit(TimeUnit.MICROSECONDS) @@ -52,7 +52,7 @@ class PowIntBenchmark { @State(Scope.Benchmark) class PowIntSt { val ds = DirectiveSet(V5, Account, Expression).fold(null, identity) - val ctx = lazyContexts(ds).value().evaluationContext(Common.emptyBlockchainEnvironment()) + val ctx = lazyContexts((ds, true)).value().evaluationContext(Common.emptyBlockchainEnvironment()) val max = Long.MaxValue diff --git a/benchmark/src/test/scala/com/wavesplatform/lang/v1/PureFunctionsRebenchmark.scala b/benchmark/src/test/scala/com/wavesplatform/lang/v1/PureFunctionsRebenchmark.scala index 5ed10c8c78e..6b731b0b82e 100644 --- a/benchmark/src/test/scala/com/wavesplatform/lang/v1/PureFunctionsRebenchmark.scala +++ b/benchmark/src/test/scala/com/wavesplatform/lang/v1/PureFunctionsRebenchmark.scala @@ -218,14 +218,14 @@ class PureFunctionsRebenchmark { object PureFunctionsRebenchmark { val context: EvaluationContext[Environment, Id] = - lazyContexts(DirectiveSet(V5, Account, Expression).explicitGet())() + lazyContexts(DirectiveSet(V5, Account, Expression).explicitGet() -> true)() .evaluationContext(Common.emptyBlockchainEnvironment()) val eval: EXPR => (Log[Id], Int, Either[ExecutionError, EVALUATED]) = - v1.eval(context, _, V4, true) + v1.eval(context, _, V4) val evalV5: EXPR => (Log[Id], Int, Either[ExecutionError, EVALUATED]) = - v1.eval(context, _, V5, true) + v1.eval(context, _, V5) def randomBytes(length: Int): Array[Byte] = { val bytes = new Array[Byte](length) diff --git a/benchmark/src/test/scala/com/wavesplatform/lang/v1/ScriptEvaluatorBenchmark.scala b/benchmark/src/test/scala/com/wavesplatform/lang/v1/ScriptEvaluatorBenchmark.scala index 8d877836b35..1ebc949f183 100644 --- a/benchmark/src/test/scala/com/wavesplatform/lang/v1/ScriptEvaluatorBenchmark.scala +++ b/benchmark/src/test/scala/com/wavesplatform/lang/v1/ScriptEvaluatorBenchmark.scala @@ -11,15 +11,15 @@ import com.wavesplatform.lang.Global import com.wavesplatform.lang.directives.values.{V1, V4} import com.wavesplatform.lang.v1.EnvironmentFunctionsBenchmark.{curve25519, randomBytes} import com.wavesplatform.lang.v1.FunctionHeader.Native -import com.wavesplatform.lang.v1.ScriptEvaluatorBenchmark._ -import com.wavesplatform.lang.v1.compiler.Terms._ +import com.wavesplatform.lang.v1.ScriptEvaluatorBenchmark.* +import com.wavesplatform.lang.v1.compiler.Terms.* import com.wavesplatform.lang.v1.evaluator.Contextful.NoContext -import com.wavesplatform.lang.v1.evaluator.EvaluatorV1._ +import com.wavesplatform.lang.v1.evaluator.EvaluatorV1.* import com.wavesplatform.lang.v1.evaluator.FunctionIds.{FROMBASE58, SIGVERIFY, TOBASE58} import com.wavesplatform.lang.v1.evaluator.ctx.EvaluationContext import com.wavesplatform.lang.v1.evaluator.ctx.impl.{CryptoContext, PureContext} import com.wavesplatform.lang.v1.evaluator.{EvaluatorV1, FunctionIds} -import org.openjdk.jmh.annotations._ +import org.openjdk.jmh.annotations.* import org.openjdk.jmh.infra.Blackhole import scala.util.Random @@ -27,7 +27,7 @@ import scala.util.Random object ScriptEvaluatorBenchmark { val version = V1 val pureEvalContext: EvaluationContext[NoContext, Id] = - PureContext.build(V1, useNewPowPrecision = true, useNewPowPrecision = true).evaluationContext + PureContext.build(V1, useNewPowPrecision = true).evaluationContext val evaluatorV1: EvaluatorV1[Id, NoContext] = new EvaluatorV1[Id, NoContext]() } @@ -112,8 +112,8 @@ class Base58Perf { val encode: EXPR = { val base58Count = 120 - val sum = (1 to base58Count).foldRight[EXPR](CONST_LONG(0)) { - case (i, e) => FUNCTION_CALL(PureContext.sumLong, List(REF("v" + i), e)) + val sum = (1 to base58Count).foldRight[EXPR](CONST_LONG(0)) { case (i, e) => + FUNCTION_CALL(PureContext.sumLong, List(REF("v" + i), e)) } (1 to base58Count) .map { i => @@ -132,8 +132,8 @@ class Base58Perf { val decode: EXPR = { val base58Count = 60 - val sum = (1 to base58Count).foldRight[EXPR](CONST_LONG(0)) { - case (i, e) => FUNCTION_CALL(PureContext.sumLong, List(REF("v" + i), e)) + val sum = (1 to base58Count).foldRight[EXPR](CONST_LONG(0)) { case (i, e) => + FUNCTION_CALL(PureContext.sumLong, List(REF("v" + i), e)) } (1 to base58Count) .map { i => @@ -155,8 +155,8 @@ class Signatures { val expr: EXPR = { val sigCount = 20 - val sum = (1 to sigCount).foldRight[EXPR](CONST_LONG(0)) { - case (i, e) => FUNCTION_CALL(PureContext.sumLong, List(REF("v" + i), e)) + val sum = (1 to sigCount).foldRight[EXPR](CONST_LONG(0)) { case (i, e) => + FUNCTION_CALL(PureContext.sumLong, List(REF("v" + i), e)) } (1 to sigCount) .map { i => @@ -183,8 +183,8 @@ class Signatures { ) ) } - .foldRight[EXPR](FUNCTION_CALL(PureContext.eq, List(sum, CONST_LONG(sigCount)))) { - case (let, e) => BLOCK(let, e) + .foldRight[EXPR](FUNCTION_CALL(PureContext.eq, List(sum, CONST_LONG(sigCount)))) { case (let, e) => + BLOCK(let, e) } } } @@ -196,8 +196,8 @@ class Concat { private val Steps = 180 private def expr(init: EXPR, func: FunctionHeader, operand: EXPR, count: Int) = - (1 to count).foldLeft[EXPR](init) { - case (e, _) => FUNCTION_CALL(func, List(e, operand)) + (1 to count).foldLeft[EXPR](init) { case (e, _) => + FUNCTION_CALL(func, List(e, operand)) } val strings: EXPR = expr( @@ -218,7 +218,7 @@ class Concat { @State(Scope.Benchmark) class Median { - val context: EvaluationContext[NoContext, Id] = PureContext.build(V4, useNewPowPrecision = true, useNewPowPrecision = true).evaluationContext + val context: EvaluationContext[NoContext, Id] = PureContext.build(V4, useNewPowPrecision = true).evaluationContext val randomElements: Array[EXPR] = (1 to 10000).map { _ => @@ -261,9 +261,7 @@ class Median { @State(Scope.Benchmark) class SigVerify32Kb { val context: EvaluationContext[NoContext, Id] = - Monoid.combine( - PureContext.build(V4, useNewPowPrecision = true, useNewPowPrecision =true).evaluationContext, - CryptoContext.build(Global, V4).evaluationContext) + Monoid.combine(PureContext.build(V4, useNewPowPrecision = true).evaluationContext, CryptoContext.build(Global, V4).evaluationContext) val expr: EXPR = { val (privateKey, publicKey) = curve25519.generateKeypair @@ -285,7 +283,7 @@ class SigVerify32Kb { class ListRemoveByIndex { val context: EvaluationContext[NoContext, Id] = Monoid.combine( - PureContext.build(V4, useNewPowPrecision = true, useNewPowPrecision = true).evaluationContext, + PureContext.build(V4, useNewPowPrecision = true).evaluationContext, CryptoContext.build(Global, V4).evaluationContext ) diff --git a/benchmark/src/test/scala/com/wavesplatform/lang/v1/SqrtCbrtBigIntBenchmark.scala b/benchmark/src/test/scala/com/wavesplatform/lang/v1/SqrtCbrtBigIntBenchmark.scala index 2a7657854fd..e83bee310ae 100644 --- a/benchmark/src/test/scala/com/wavesplatform/lang/v1/SqrtCbrtBigIntBenchmark.scala +++ b/benchmark/src/test/scala/com/wavesplatform/lang/v1/SqrtCbrtBigIntBenchmark.scala @@ -33,7 +33,7 @@ class SqrtCbrtBigIntBenchmark { @State(Scope.Benchmark) class SqrtBigIntSt { val ds = DirectiveSet(V5, Account, Expression).fold(null, identity) - val ctx = lazyContexts(ds).value().evaluationContext(Common.emptyBlockchainEnvironment()) + val ctx = lazyContexts(ds -> true).value().evaluationContext(Common.emptyBlockchainEnvironment()) val max = PureContext.BigIntMax val min = PureContext.BigIntMin diff --git a/benchmark/src/test/scala/com/wavesplatform/lang/v1/SqrtIntBenchmark.scala b/benchmark/src/test/scala/com/wavesplatform/lang/v1/SqrtIntBenchmark.scala index 67ec4cd0d69..40da5f2e3b7 100644 --- a/benchmark/src/test/scala/com/wavesplatform/lang/v1/SqrtIntBenchmark.scala +++ b/benchmark/src/test/scala/com/wavesplatform/lang/v1/SqrtIntBenchmark.scala @@ -8,7 +8,7 @@ import com.wavesplatform.lang.directives.values.{Account, Expression, V5} import com.wavesplatform.lang.utils.lazyContexts import com.wavesplatform.lang.v1.compiler.Terms.EXPR import com.wavesplatform.lang.v1.compiler.TestCompiler -import org.openjdk.jmh.annotations.{State, _} +import org.openjdk.jmh.annotations.* import org.openjdk.jmh.infra.Blackhole @OutputTimeUnit(TimeUnit.MICROSECONDS) @@ -28,7 +28,7 @@ class SqrtIntBenchmark { @State(Scope.Benchmark) class SqrtIntSt { val ds = DirectiveSet(V5, Account, Expression).fold(null, identity) - val ctx = lazyContexts(ds).value().evaluationContext(Common.emptyBlockchainEnvironment()) + val ctx = lazyContexts(ds -> true).value().evaluationContext(Common.emptyBlockchainEnvironment()) val expr1 = compile(s"pow(${Long.MaxValue}, 0, 5, 1, 8, DOWN)") val expr2 = compile(s"pow(${Long.MaxValue}, 8, 5, 1, 8, DOWN)") diff --git a/benchmark/src/test/scala/com/wavesplatform/lang/v1/package.scala b/benchmark/src/test/scala/com/wavesplatform/lang/v1/package.scala index 586b8c9a9a1..aeed9077baf 100644 --- a/benchmark/src/test/scala/com/wavesplatform/lang/v1/package.scala +++ b/benchmark/src/test/scala/com/wavesplatform/lang/v1/package.scala @@ -5,6 +5,7 @@ import com.wavesplatform.lang.directives.values.StdLibVersion import com.wavesplatform.lang.v1.FunctionHeader.Native import com.wavesplatform.lang.v1.compiler.Terms import com.wavesplatform.lang.v1.compiler.Terms.{CONST_BIGINT, CONST_LONG, EXPR, FUNCTION_CALL} +import com.wavesplatform.lang.v1.evaluator.ContractEvaluator.LogExtraInfo import com.wavesplatform.lang.v1.evaluator.FunctionIds.POW_BIGINT import com.wavesplatform.lang.v1.evaluator.ctx.EvaluationContext import com.wavesplatform.lang.v1.evaluator.ctx.impl.Rounding @@ -30,5 +31,5 @@ package object v1 { expr: EXPR, stdLibVersion: StdLibVersion ): (Log[Id], Int, Either[ExecutionError, Terms.EVALUATED]) = - EvaluatorV2.applyCompleted(ctx, expr, stdLibVersion, newMode = true, correctFunctionCallScope = true) + EvaluatorV2.applyCompleted(ctx, expr, LogExtraInfo(), stdLibVersion, newMode = true, correctFunctionCallScope = true) } diff --git a/benchmark/src/test/scala/com/wavesplatform/serialization/protobuf/SmartNoSmartBenchmark.scala b/benchmark/src/test/scala/com/wavesplatform/serialization/protobuf/SmartNoSmartBenchmark.scala index 51419433561..7c976ed47a9 100644 --- a/benchmark/src/test/scala/com/wavesplatform/serialization/protobuf/SmartNoSmartBenchmark.scala +++ b/benchmark/src/test/scala/com/wavesplatform/serialization/protobuf/SmartNoSmartBenchmark.scala @@ -4,11 +4,11 @@ import java.util.concurrent.TimeUnit import com.wavesplatform.account.{AddressScheme, PublicKey} import com.wavesplatform.common.state.ByteStr -import com.wavesplatform.common.utils._ +import com.wavesplatform.common.utils.* import com.wavesplatform.serialization.protobuf.SmartNoSmartBenchmark.ExchangeTransactionSt -import com.wavesplatform.transaction.assets.exchange._ -import com.wavesplatform.transaction.{Proofs, TxVersion} -import org.openjdk.jmh.annotations._ +import com.wavesplatform.transaction.assets.exchange.* +import com.wavesplatform.transaction.{Proofs, TxExchangeAmount, TxExchangePrice, TxMatcherFee, TxOrderPrice, TxPositiveAmount, TxVersion} +import org.openjdk.jmh.annotations.* import org.openjdk.jmh.infra.Blackhole //noinspection ScalaStyle @@ -21,15 +21,27 @@ import org.openjdk.jmh.infra.Blackhole class SmartNoSmartBenchmark { @Benchmark def smartExchangeTX_test(st: ExchangeTransactionSt, bh: Blackhole): Unit = { - import st._ + import st.* val exchangeTransaction = ExchangeTransaction.create(TxVersion.V2, buy, sell, 2, 5000000000L, 1, 1, 1, 1526992336241L, proofs) bh.consume(exchangeTransaction.explicitGet()) } @Benchmark def unsafeExchangeTX_test(st: ExchangeTransactionSt, bh: Blackhole): Unit = { - import st._ - val exchangeTransaction = ExchangeTransaction(TxVersion.V2, buy, sell, 2, 5000000000L, 1, 1, 1, 1526992336241L, proofs, AddressScheme.current.chainId) + import st.* + val exchangeTransaction = ExchangeTransaction( + TxVersion.V2, + buy, + sell, + TxExchangeAmount.unsafeFrom(2), + TxExchangePrice.unsafeFrom(5000000000L), + 1, + 1, + TxPositiveAmount.unsafeFrom(1), + 1526992336241L, + proofs, + AddressScheme.current.chainId + ) bh.consume(exchangeTransaction) } } @@ -37,9 +49,37 @@ class SmartNoSmartBenchmark { object SmartNoSmartBenchmark { @State(Scope.Benchmark) class ExchangeTransactionSt { - val buy = Order(TxVersion.V2, PublicKey.fromBase58String("BqeJY8CP3PeUDaByz57iRekVUGtLxoow4XxPvXfHynaZ").explicitGet(), PublicKey.fromBase58String("Fvk5DXmfyWVZqQVBowUBMwYtRAHDtdyZNNeRrwSjt6KP").explicitGet(), AssetPair.createAssetPair("WAVES", "9ZDWzK53XT5bixkmMwTJi2YzgxCqn5dUajXFcT2HcFDy").get, OrderType.BUY, 2, 6000000000L, 1526992336241L, 1529584336241L, 1, proofs = Proofs(Seq(ByteStr.decodeBase58("2bkuGwECMFGyFqgoHV4q7GRRWBqYmBFWpYRkzgYANR4nN2twgrNaouRiZBqiK2RJzuo9NooB9iRiuZ4hypBbUQs").get))) + val buy = Order( + TxVersion.V2, + OrderAuthentication.OrderProofs( + PublicKey.fromBase58String("BqeJY8CP3PeUDaByz57iRekVUGtLxoow4XxPvXfHynaZ").explicitGet(), + Proofs(Seq(ByteStr.decodeBase58("2bkuGwECMFGyFqgoHV4q7GRRWBqYmBFWpYRkzgYANR4nN2twgrNaouRiZBqiK2RJzuo9NooB9iRiuZ4hypBbUQs").get)) + ), + PublicKey.fromBase58String("Fvk5DXmfyWVZqQVBowUBMwYtRAHDtdyZNNeRrwSjt6KP").explicitGet(), + AssetPair.createAssetPair("WAVES", "9ZDWzK53XT5bixkmMwTJi2YzgxCqn5dUajXFcT2HcFDy").get, + OrderType.BUY, + TxExchangeAmount.unsafeFrom(2), + TxOrderPrice.unsafeFrom(6000000000L), + 1526992336241L, + 1529584336241L, + TxMatcherFee.unsafeFrom(1) + ) - val sell = Order(TxVersion.V1, PublicKey.fromBase58String("7E9Za8v8aT6EyU1sX91CVK7tWUeAetnNYDxzKZsyjyKV").explicitGet(), PublicKey.fromBase58String("Fvk5DXmfyWVZqQVBowUBMwYtRAHDtdyZNNeRrwSjt6KP").explicitGet(), AssetPair.createAssetPair("WAVES", "9ZDWzK53XT5bixkmMwTJi2YzgxCqn5dUajXFcT2HcFDy").get, OrderType.SELL, 3, 5000000000L, 1526992336241L, 1529584336241L, 2, proofs = Proofs(ByteStr.decodeBase58("2R6JfmNjEnbXAA6nt8YuCzSf1effDS4Wkz8owpCD9BdCNn864SnambTuwgLRYzzeP5CAsKHEviYKAJ2157vdr5Zq").get)) + val sell = Order( + TxVersion.V1, + OrderAuthentication.OrderProofs( + PublicKey.fromBase58String("7E9Za8v8aT6EyU1sX91CVK7tWUeAetnNYDxzKZsyjyKV").explicitGet(), + Proofs(ByteStr.decodeBase58("2R6JfmNjEnbXAA6nt8YuCzSf1effDS4Wkz8owpCD9BdCNn864SnambTuwgLRYzzeP5CAsKHEviYKAJ2157vdr5Zq").get) + ), + PublicKey.fromBase58String("Fvk5DXmfyWVZqQVBowUBMwYtRAHDtdyZNNeRrwSjt6KP").explicitGet(), + AssetPair.createAssetPair("WAVES", "9ZDWzK53XT5bixkmMwTJi2YzgxCqn5dUajXFcT2HcFDy").get, + OrderType.SELL, + TxExchangeAmount.unsafeFrom(3), + TxOrderPrice.unsafeFrom(5000000000L), + 1526992336241L, + 1529584336241L, + TxMatcherFee.unsafeFrom(2) + ) val proofs = Proofs(Seq(ByteStr.decodeBase58("5NxNhjMrrH5EWjSFnVnPbanpThic6fnNL48APVAkwq19y2FpQp4tNSqoAZgboC2ykUfqQs9suwBQj6wERmsWWNqa").get)) } diff --git a/build.sbt b/build.sbt index 89e4ac79904..027fd51327a 100644 --- a/build.sbt +++ b/build.sbt @@ -194,7 +194,6 @@ checkPRRaw := Def .sequential( `waves-node` / clean, Def.task { - (Test / compile).value (`lang-tests` / Test / test).value (`repl-jvm` / Test / test).value (`lang-js` / Compile / fastOptJS).value @@ -203,6 +202,7 @@ checkPRRaw := Def (node / Test / test).value (`repl-js` / Compile / fastOptJS).value (`node-it` / Test / compile).value + (benchmark / Test / compile).value } ) .value