From 59f3719322c9e1a3e65e14bf2dd1bbc1897b8263 Mon Sep 17 00:00:00 2001 From: Artyom Sayadyan Date: Fri, 26 Jun 2020 15:05:20 +0300 Subject: [PATCH] SC-552 Complexity benchmarks (#3152) --- benchmark/build.sbt | 13 +- benchmark/src/main/resources/application.conf | 3 - .../com/wavesplatform/state/DBState.scala | 48 ++ .../com/wavesplatform/state/ExtractInfo.scala | 150 ------ .../com/wavesplatform/state/Settings.scala | 27 +- .../v1/EnvironmentFunctionsBenchmark.scala | 2 +- .../lang/v1/PureFunctionsRebenchmark.scala | 470 ++++++++++++++++++ .../lang/v1/ScriptEvaluatorBenchmark.scala | 26 + .../state/LevelDBWriterBenchmark.scala | 2 +- .../state/WavesEnvironmentRebenchmark.scala | 215 ++++++++ .../lang/v1/compiler/Terms.scala | 2 +- .../v1/evaluator/ctx/NativeFunction.scala | 18 + .../v1/evaluator/ctx/impl/CryptoContext.scala | 34 +- .../v1/evaluator/ctx/impl/PureContext.scala | 188 +++++-- .../evaluator/ctx/impl/waves/Functions.scala | 64 ++- .../ctx/impl/waves/WavesContext.scala | 19 +- .../RideFunctionFamilySuite.scala | 10 +- .../smart/script/ScriptCompilerV1Test.scala | 17 +- .../estimator/FunctionComplexityTest.scala | 8 +- 19 files changed, 1034 insertions(+), 282 deletions(-) create mode 100644 benchmark/src/main/scala/com/wavesplatform/state/DBState.scala delete mode 100644 benchmark/src/main/scala/com/wavesplatform/state/ExtractInfo.scala create mode 100644 benchmark/src/test/scala/com/wavesplatform/lang/v1/PureFunctionsRebenchmark.scala create mode 100644 benchmark/src/test/scala/com/wavesplatform/state/WavesEnvironmentRebenchmark.scala diff --git a/benchmark/build.sbt b/benchmark/build.sbt index 4212dc561f..36bdf1370a 100644 --- a/benchmark/build.sbt +++ b/benchmark/build.sbt @@ -1,12 +1,8 @@ enablePlugins(JmhPlugin) -libraryDependencies += "org.scodec" %% "scodec-core" % "1.11.7" - -inTask(Compile / run)( - Seq( - fork := true, - javaOptions += s"-Dlogback.configurationFile=${(Compile / resourceDirectory).value / "logback.xml"}" - )) +libraryDependencies ++= Seq( + "org.scodec" %% "scodec-core" % "1.11.7" +) ++ Dependencies.logDeps // https://github.com/ktoso/sbt-jmh#adding-to-your-project inConfig(Jmh)( @@ -17,4 +13,5 @@ inConfig(Jmh)( // rewire tasks, so that 'jmh:run' automatically invokes 'jmh:compile' (otherwise a clean 'jmh:run' would fail) compile := compile.dependsOn(Test / compile).value, run := run.dependsOn(compile).evaluated - )) + ) +) diff --git a/benchmark/src/main/resources/application.conf b/benchmark/src/main/resources/application.conf index f8eba0ac92..ea09ad9cfc 100644 --- a/benchmark/src/main/resources/application.conf +++ b/benchmark/src/main/resources/application.conf @@ -7,9 +7,6 @@ waves.benchmark.state { rest-txs-file = "" rest-txs-from-height = 1 - txs-addresses-file = "" - txs-addresses-from-height = 1 - blocks-file = "" blocks-from-height = 1 diff --git a/benchmark/src/main/scala/com/wavesplatform/state/DBState.scala b/benchmark/src/main/scala/com/wavesplatform/state/DBState.scala new file mode 100644 index 0000000000..416376c3de --- /dev/null +++ b/benchmark/src/main/scala/com/wavesplatform/state/DBState.scala @@ -0,0 +1,48 @@ +package com.wavesplatform.state + +import java.io.File + +import com.wavesplatform.Application +import com.wavesplatform.account.AddressScheme +import com.wavesplatform.common.state.ByteStr +import com.wavesplatform.database.{LevelDBWriter, openDB} +import com.wavesplatform.lang.directives.DirectiveSet +import com.wavesplatform.settings.WavesSettings +import com.wavesplatform.transaction.smart.WavesEnvironment +import com.wavesplatform.utils.ScorexLogging +import monix.eval.Coeval +import org.iq80.leveldb.DB +import org.openjdk.jmh.annotations.{Param, Scope, State, TearDown} + +@State(Scope.Benchmark) +abstract class DBState extends ScorexLogging { + @Param(Array("waves.conf")) + var configFile = "" + + lazy val settings: WavesSettings = Application.loadApplicationConfig(Some(new File(configFile)).filter(_.exists())) + + lazy val db: DB = openDB(settings.dbSettings.directory) + + lazy val levelDBWriter: LevelDBWriter = + LevelDBWriter.readOnly( + db, + settings.copy(dbSettings = settings.dbSettings.copy(maxCacheSize = 1)) + ) + + AddressScheme.current = new AddressScheme { override val chainId: Byte = 'W' } + + lazy val environment = new WavesEnvironment( + AddressScheme.current.chainId, + Coeval.raiseError(new NotImplementedError("`tx` is not implemented")), + Coeval(levelDBWriter.height), + levelDBWriter, + null, + DirectiveSet.contractDirectiveSet, + ByteStr.empty + ) + + @TearDown + def close(): Unit = { + db.close() + } +} diff --git a/benchmark/src/main/scala/com/wavesplatform/state/ExtractInfo.scala b/benchmark/src/main/scala/com/wavesplatform/state/ExtractInfo.scala deleted file mode 100644 index d2d390441f..0000000000 --- a/benchmark/src/main/scala/com/wavesplatform/state/ExtractInfo.scala +++ /dev/null @@ -1,150 +0,0 @@ -package com.wavesplatform.state - -import java.io.{File, PrintWriter} -import java.util.concurrent.ThreadLocalRandom - -import com.typesafe.config.ConfigFactory -import com.wavesplatform.account.AddressScheme -import com.wavesplatform.block.Block -import com.wavesplatform.common.state.ByteStr -import com.wavesplatform.database.{DBExt, Keys, LevelDBFactory, LevelDBWriter, loadBlock} -import com.wavesplatform.lang.v1.traits.DataType -import com.wavesplatform.settings.{WavesSettings, loadConfig} -import com.wavesplatform.state.bench.DataTestData -import com.wavesplatform.transaction.assets.IssueTransaction -import com.wavesplatform.transaction.{Authorized, CreateAliasTransaction, DataTransaction, Transaction} -import com.wavesplatform.utils.ScorexLogging -import org.iq80.leveldb.{DB, Options} -import scodec.bits.BitVector - -import scala.jdk.CollectionConverters._ -import scala.collection.mutable -import scala.util.control.NonFatal - -/** - * Extracts data from the database to use it in RealDbBenchmark. - * Requires a separate main file because takes too long time to run. - */ -object ExtractInfo extends App with ScorexLogging { - - if (args.length < 1) { - log.error("Specify a path to the node config. Usage: benchmark/run /full/path/to/the/config.conf") - System.exit(1) - } - - val benchSettings = Settings.fromConfig(ConfigFactory.load()) - val wavesSettings = { - val config = loadConfig(ConfigFactory.parseFile(new File(args.head))) - WavesSettings.fromRootConfig(config) - } - - AddressScheme.current = new AddressScheme { - override val chainId: Byte = wavesSettings.blockchainSettings.addressSchemeCharacter.toByte - } - - val db: DB = { - val dir = new File(wavesSettings.dbSettings.directory) - if (!dir.isDirectory) throw new IllegalArgumentException(s"Can't find directory at '${wavesSettings.dbSettings.directory}'") - LevelDBFactory.factory.open(dir, new Options) - } - - try { - val state = LevelDBWriter.readOnly(db, wavesSettings) - - def nonEmptyBlockHeights(from: Int): Iterator[Integer] = - for { - height <- randomInts(from, state.height) - m <- db.get(Keys.blockMetaAt(Height(height.toInt))) - if m.transactionCount > 0 - } yield height - - def nonEmptyBlocks(from: Int): Iterator[Block] = - nonEmptyBlockHeights(from) - .flatMap(h => db.readOnly(ro => loadBlock(Height(h.toInt), ro))) - - val aliasTxs = nonEmptyBlocks(benchSettings.aliasesFromHeight) - .flatMap(_.transactionData) - .collect { - case _: CreateAliasTransaction => true - } - - val restTxs = nonEmptyBlocks(benchSettings.restTxsFromHeight) - .flatMap(_.transactionData) - - val accounts = for { - b <- nonEmptyBlocks(benchSettings.accountsFromHeight) - sender <- b.transactionData - .collect { - case tx: Transaction with Authorized => tx.sender - } - .take(100) - } yield sender.toAddress.stringRepr - write("accounts", benchSettings.accountsFile, takeUniq(1000, accounts)) - - val aliasTxIds = aliasTxs.map(_.asInstanceOf[CreateAliasTransaction].alias.stringRepr) - write("aliases", benchSettings.aliasesFile, aliasTxIds.take(1000)) - - val restTxIds = restTxs.map(_.id().toString) - write("rest transactions", benchSettings.restTxsFile, restTxIds.take(10000)) - - val assets = nonEmptyBlocks(benchSettings.assetsFromHeight) - .flatMap { b => - b.transactionData.collect { - case tx: IssueTransaction => tx.assetId - } - } - .map(_.toString) - - write("assets", benchSettings.assetsFile, takeUniq(300, assets)) - - val data = for { - b <- nonEmptyBlocks(benchSettings.dataFromHeight) - test <- b.transactionData - .collect { - case tx: DataTransaction => - val addr = ByteStr(tx.sender.toAddress.bytes) - tx.data.collectFirst { - case x: IntegerDataEntry => DataTestData(addr, x.key, DataType.Long) - case x: BooleanDataEntry => DataTestData(addr, x.key, DataType.Boolean) - case x: BinaryDataEntry => DataTestData(addr, x.key, DataType.ByteArray) - case x: StringDataEntry => DataTestData(addr, x.key, DataType.String) - } - } - .take(50) - r <- test - } yield { - val x: BitVector = DataTestData.codec.encode(r).require - x.toBase64 - } - write("data", benchSettings.dataFile, data.take(400)) - } catch { - case NonFatal(e) => log.error(e.getMessage, e) - } finally { - db.close() - log.info("Done") - } - - def takeUniq[T](size: Int, xs: Iterator[T]): mutable.Set[T] = { - val r = mutable.Set.empty[T] - xs.find { x => - r.add(x) - r.size == size - } - r - } - - def write(label: String, absolutePath: String, data: IterableOnce[String]): Unit = { - log.info(s"Writing $label to '$absolutePath'") - val printWriter = new PrintWriter(absolutePath) - data.iterator.foreach(printWriter.println) - printWriter.close() - } - - def randomInts(from: Int, to: Int): Iterator[Integer] = - ThreadLocalRandom - .current() - .ints(from, to) - .iterator() - .asScala - -} diff --git a/benchmark/src/main/scala/com/wavesplatform/state/Settings.scala b/benchmark/src/main/scala/com/wavesplatform/state/Settings.scala index a489a1f091..c5540ddc18 100644 --- a/benchmark/src/main/scala/com/wavesplatform/state/Settings.scala +++ b/benchmark/src/main/scala/com/wavesplatform/state/Settings.scala @@ -2,27 +2,20 @@ package com.wavesplatform.state import com.typesafe.config.Config import net.ceedubs.ficus.Ficus._ -import net.ceedubs.ficus.readers.ArbitraryTypeReader._ -case class Settings(networkConfigFile: String, - aliasesFile: String, - aliasesFromHeight: Int, - restTxsFile: String, - restTxsFromHeight: Int, - txsAddressesFile: String, - txsAddressesFromHeight: Int, - blocksFile: String, - blocksFromHeight: Int, - accountsFile: String, - accountsFromHeight: Int, - assetsFile: String, - assetsFromHeight: Int, - dataFile: String, - dataFromHeight: Int) +case class Settings( + networkConfigFile: String, + aliasesFile: String, + restTxsFile: String, + blocksFile: String, + accountsFile: String, + assetsFile: String, + dataFile: String +) object Settings { def fromConfig(config: Config): Settings = { - implicit val _ = net.ceedubs.ficus.readers.namemappers.HyphenNameMapper + import net.ceedubs.ficus.readers.ArbitraryTypeReader._ config.as[Settings]("waves.benchmark.state") } } 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 0b0e220e9e..ab5859e79d 100644 --- a/benchmark/src/test/scala/com/wavesplatform/lang/v1/EnvironmentFunctionsBenchmark.scala +++ b/benchmark/src/test/scala/com/wavesplatform/lang/v1/EnvironmentFunctionsBenchmark.scala @@ -120,7 +120,7 @@ object EnvironmentFunctionsBenchmark { override def txId: ByteStr = ByteStr(new Array[Byte](64)) override def addressFromString(addressStr: String): Either[String, Recipient.Address] = account.Address - .fromString(addressStr, chainId) + .fromString(addressStr) .bimap( _.toString, address => Address(ByteStr(address.bytes)) diff --git a/benchmark/src/test/scala/com/wavesplatform/lang/v1/PureFunctionsRebenchmark.scala b/benchmark/src/test/scala/com/wavesplatform/lang/v1/PureFunctionsRebenchmark.scala new file mode 100644 index 0000000000..e587563a40 --- /dev/null +++ b/benchmark/src/test/scala/com/wavesplatform/lang/v1/PureFunctionsRebenchmark.scala @@ -0,0 +1,470 @@ +package com.wavesplatform.lang.v1 + +import java.util.concurrent.{ThreadLocalRandom, TimeUnit} + +import cats.Id +import cats.kernel.Monoid +import com.google.common.primitives.Longs +import com.wavesplatform.common.state.ByteStr +import com.wavesplatform.common.utils._ +import com.wavesplatform.lang.directives.values.V4 +import com.wavesplatform.lang.v1.FunctionHeader.Native +import com.wavesplatform.lang.v1.PureFunctionsRebenchmark._ +import com.wavesplatform.lang.v1.compiler.Terms +import com.wavesplatform.lang.v1.compiler.Terms._ +import com.wavesplatform.lang.v1.evaluator.ctx.EvaluationContext +import com.wavesplatform.lang.v1.evaluator.ctx.impl.{CryptoContext, PureContext} +import com.wavesplatform.lang.v1.evaluator.{EvaluatorV2, FunctionIds, Log} +import com.wavesplatform.lang.v1.traits.Environment +import com.wavesplatform.lang.{ExecutionError, Global} +import org.openjdk.jmh.annotations._ +import org.openjdk.jmh.infra.Blackhole + +import scala.util.Random + +@OutputTimeUnit(TimeUnit.MICROSECONDS) +@BenchmarkMode(Array(Mode.AverageTime)) +@Threads(1) +@Fork(1) +@Warmup(iterations = 30) +@Measurement(iterations = 20) +class PureFunctionsRebenchmark { + @Benchmark + def parseIntValue(st: ParseIntVal, bh: Blackhole): Unit = + bh.consume(eval(st.expr)) + + @Benchmark + def splitString(st: SplitString, bh: Blackhole): Unit = + bh.consume(eval(st.expr)) + + @Benchmark + def toBase58(st: ToBase58, bh: Blackhole): Unit = + bh.consume(eval(st.expr)) + + @Benchmark + def fromBase58(st: FromBase58, bh: Blackhole): Unit = + bh.consume(eval(st.expr)) + + @Benchmark + def toBase64(st: ToBase64, bh: Blackhole): Unit = + bh.consume(eval(st.expr)) + + @Benchmark + def fromBase64(st: FromBase64, bh: Blackhole): Unit = + bh.consume(eval(st.expr)) + + @Benchmark + def sumString(st: SumString, bh: Blackhole): Unit = + bh.consume(eval(st.expr)) + + @Benchmark + def sumByteString(st: SumByteString, bh: Blackhole): Unit = + bh.consume(eval(st.expr)) + + @Benchmark + def longToBytes(st: LongToBytes, bh: Blackhole): Unit = + bh.consume(eval(st.expr)) + + @Benchmark + def stringToBytes(st: StringToBytes, bh: Blackhole): Unit = + bh.consume(eval(st.expr)) + + @Benchmark + def takeBytes(st: TakeBytes, bh: Blackhole): Unit = + bh.consume(eval(st.expr)) + + @Benchmark + def dropBytes(st: DropBytes, bh: Blackhole): Unit = + bh.consume(eval(st.expr)) + + @Benchmark + def takeString(st: TakeString, bh: Blackhole): Unit = + bh.consume(eval(st.expr)) + + @Benchmark + def dropString(st: DropString, bh: Blackhole): Unit = + bh.consume(eval(st.expr)) + + @Benchmark + def listAppend(st: ListAppend, bh: Blackhole): Unit = + bh.consume(eval(st.expr)) + + @Benchmark + def listConstructor(st: ListConstructor, bh: Blackhole): Unit = + bh.consume(eval(st.expr)) + + @Benchmark + def listConcat1(st: ListConcat1, bh: Blackhole): Unit = + bh.consume(eval(st.expr)) + + @Benchmark + def listConcat2(st: ListConcat2, bh: Blackhole): Unit = + bh.consume(eval(st.expr)) + + @Benchmark + def listConcat3(st: ListConcat3, bh: Blackhole): Unit = + bh.consume(eval(st.expr)) + + @Benchmark + def toUtf8String(st: ToUtf8String, bh: Blackhole): Unit = + bh.consume(eval(st.expr)) + + @Benchmark + def bytesToLong(st: BytesToLong, bh: Blackhole): Unit = + bh.consume(eval(st.expr)) + + @Benchmark + def stringIndexOf(st: StringIndexOf, bh: Blackhole): Unit = + bh.consume(eval(st.expr)) + + @Benchmark + def listGetElement1(st: ListGetElement1, bh: Blackhole): Unit = + bh.consume(eval(st.expr)) + + @Benchmark + def listGetElement2(st: ListGetElement2, bh: Blackhole): Unit = + bh.consume(eval(st.expr)) + + @Benchmark + def listGetElement3(st: ListGetElement3, bh: Blackhole): Unit = + bh.consume(eval(st.expr)) +} + +object PureFunctionsRebenchmark { + val context: EvaluationContext[Environment, Id] = + Monoid.combine( + PureContext.build(Global, V4).evaluationContext, + CryptoContext.build(Global, V4).evaluationContext + ).asInstanceOf[EvaluationContext[Environment, Id]] + + val eval: EXPR => Either[(ExecutionError, Log[Id]), (EVALUATED, Log[Id])] = + EvaluatorV2.applyCompleted(context, _, V4) + + def randomBytes(length: Int): Array[Byte] = { + val bytes = new Array[Byte](length) + ThreadLocalRandom.current().nextBytes(bytes) + bytes + } + + @State(Scope.Benchmark) + class SplitString { + val separator = "," + val separatedString = List.fill(1000)(Random.nextPrintableChar().toString * 31).mkString(separator) + val expr: EXPR = + FUNCTION_CALL( + PureContext.splitStr, + List( + CONST_STRING(separatedString).explicitGet(), + CONST_STRING(separator).explicitGet() + ) + ) + } + + @State(Scope.Benchmark) + class ParseIntVal { + val numStr = Long.MinValue.toString + val expr: EXPR = + FUNCTION_CALL( + PureContext.parseIntVal, + List( + CONST_STRING(numStr).explicitGet() + ) + ) + } + + @State(Scope.Benchmark) + class ToBase58 { + val bytes = randomBytes(64) + val expr: EXPR = + FUNCTION_CALL( + Native(FunctionIds.TOBASE58), + List( + CONST_BYTESTR(ByteStr(bytes)).explicitGet() + ) + ) + } + + @State(Scope.Benchmark) + class FromBase58 { + val string = Base58.encode(randomBytes(75)) // approximately MaxBase58String + val expr: EXPR = + FUNCTION_CALL( + Native(FunctionIds.FROMBASE58), + List( + CONST_STRING(string).explicitGet() + ) + ) + } + + @State(Scope.Benchmark) + class ToBase64 { + val bytes = randomBytes(32 * 1024 - 1) + val expr: EXPR = + FUNCTION_CALL( + Native(FunctionIds.TOBASE64), + List( + CONST_BYTESTR(ByteStr(bytes)).explicitGet() + ) + ) + } + + @State(Scope.Benchmark) + class FromBase64 { + val string = Base58.encode(randomBytes(32 * 1024)) // approximately MaxBase64String + val expr: EXPR = + FUNCTION_CALL( + Native(FunctionIds.FROMBASE64), + List( + CONST_STRING(string, reduceLimit = false).explicitGet() + ) + ) + } + + @State(Scope.Benchmark) + class SumString { + val string1 = "a" + val string2 = Random.nextPrintableChar().toString * 32766 + val expr: EXPR = + FUNCTION_CALL( + Native(FunctionIds.SUM_STRING), + List( + CONST_STRING(string1).explicitGet(), + CONST_STRING(string2).explicitGet() + ) + ) + } + + @State(Scope.Benchmark) + class SumByteString { + val byteString1 = ByteStr.fromBytes(1) + val byteString2 = ByteStr(Array.fill[Byte](32766)(-127)) + val expr: EXPR = + FUNCTION_CALL( + Native(FunctionIds.SUM_BYTES), + List( + CONST_BYTESTR(byteString1).explicitGet(), + CONST_BYTESTR(byteString2).explicitGet() + ) + ) + } + + @State(Scope.Benchmark) + class LongToBytes { + val long = Long.MinValue + val expr: EXPR = + FUNCTION_CALL( + Native(FunctionIds.LONG_TO_BYTES), + List( + CONST_LONG(long) + ) + ) + } + + @State(Scope.Benchmark) + class StringToBytes { + val string = Random.nextPrintableChar().toString * 32767 + val expr: EXPR = + FUNCTION_CALL( + Native(FunctionIds.STRING_TO_BYTES), + List( + CONST_STRING(string).explicitGet() + ) + ) + } + + @State(Scope.Benchmark) + class TakeBytes { + val bytes = ByteStr(Array.fill[Byte](32766)(-127)) + val expr: EXPR = + FUNCTION_CALL( + Native(FunctionIds.TAKE_BYTES), + List( + CONST_BYTESTR(bytes).explicitGet(), + CONST_LONG(32765) + ) + ) + } + + @State(Scope.Benchmark) + class DropBytes { + val bytes = ByteStr(Array.fill[Byte](Terms.DataTxMaxProtoBytes)(-127)) + val expr: EXPR = + FUNCTION_CALL( + Native(FunctionIds.DROP_BYTES), + List( + CONST_BYTESTR(bytes, CONST_BYTESTR.NoLimit).explicitGet(), + CONST_LONG(1) + ) + ) + } + + @State(Scope.Benchmark) + class TakeString { + val string = Random.nextPrintableChar().toString * 32766 + val expr: EXPR = + FUNCTION_CALL( + Native(FunctionIds.TAKE_STRING), + List( + CONST_STRING(string).explicitGet(), + CONST_LONG(32765) + ) + ) + } + + @State(Scope.Benchmark) + class DropString { + val string = Random.nextPrintableChar().toString * 32766 + val expr: EXPR = + FUNCTION_CALL( + Native(FunctionIds.DROP_STRING), + List( + CONST_STRING(string).explicitGet(), + CONST_LONG(1) + ) + ) + } + + @State(Scope.Benchmark) + class ListConstructor { + val list = Vector.fill(999)(CONST_LONG(1)) + val expr: EXPR = + FUNCTION_CALL( + Native(FunctionIds.CREATE_LIST), + List( + CONST_LONG(1), + ARR(list, limited = true).explicitGet() + ) + ) + } + + @State(Scope.Benchmark) + class ListAppend { + val list = Vector.fill(999)(CONST_LONG(1)) + val expr: EXPR = + FUNCTION_CALL( + Native(FunctionIds.APPEND_LIST), + List( + ARR(list, limited = true).explicitGet(), + CONST_LONG(1) + ) + ) + } + + @State(Scope.Benchmark) + class ListConcat1 { + val list1 = Vector.fill(999)(CONST_LONG(1)) + val list2 = Vector.fill(1)(CONST_LONG(1)) + val expr: EXPR = + FUNCTION_CALL( + Native(FunctionIds.CONCAT_LIST), + List( + ARR(list1, limited = true).explicitGet(), + ARR(list2, limited = true).explicitGet() + ) + ) + } + + @State(Scope.Benchmark) + class ListConcat2 { + val list1 = Vector.fill(1)(CONST_LONG(1)) + val list2 = Vector.fill(999)(CONST_LONG(1)) + val expr: EXPR = + FUNCTION_CALL( + Native(FunctionIds.CONCAT_LIST), + List( + ARR(list1, limited = true).explicitGet(), + ARR(list2, limited = true).explicitGet() + ) + ) + } + + @State(Scope.Benchmark) + class ListConcat3 { + val list1 = Vector.fill(500)(CONST_LONG(1)) + val list2 = Vector.fill(500)(CONST_LONG(1)) + val expr: EXPR = + FUNCTION_CALL( + Native(FunctionIds.CONCAT_LIST), + List( + ARR(list1, limited = true).explicitGet(), + ARR(list2, limited = true).explicitGet() + ) + ) + } + + @State(Scope.Benchmark) + class ToUtf8String { + val bytes = new Array[Byte](Terms.DataTxMaxProtoBytes) + Random.nextBytes(bytes) + val expr: EXPR = + FUNCTION_CALL( + Native(FunctionIds.UTF8STRING), + List( + CONST_BYTESTR(ByteStr(bytes), CONST_BYTESTR.NoLimit).explicitGet() + ) + ) + } + + @State(Scope.Benchmark) + class BytesToLong { + val longBytes = Longs.toByteArray(Long.MinValue) + val expr: EXPR = + FUNCTION_CALL( + Native(FunctionIds.BININT), + List( + CONST_BYTESTR(ByteStr(longBytes)).explicitGet() + ) + ) + } + + @State(Scope.Benchmark) + class StringIndexOf { + val expr: EXPR = + FUNCTION_CALL( + Native(FunctionIds.INDEXOF), + List( + CONST_STRING("b" * 32766 + "a").explicitGet(), + CONST_STRING("a").explicitGet() + ) + ) + } + + @State(Scope.Benchmark) + class ListGetElement1 { + val list = Vector.fill(1000)(CONST_LONG(1)) + val expr: EXPR = + FUNCTION_CALL( + Native(FunctionIds.GET_LIST), + List( + ARR(list, limited = true).explicitGet(), + CONST_LONG(1) + ) + ) + } + + @State(Scope.Benchmark) + class ListGetElement2 { + val list = Vector.fill(1000)(CONST_LONG(1)) + val expr: EXPR = + FUNCTION_CALL( + Native(FunctionIds.GET_LIST), + List( + ARR(list, limited = true).explicitGet(), + CONST_LONG(500) + ) + ) + } + + @State(Scope.Benchmark) + class ListGetElement3 { + val list = Vector.fill(1000)(CONST_LONG(1)) + val expr: EXPR = + FUNCTION_CALL( + Native(FunctionIds.GET_LIST), + List( + ARR(list, limited = true).explicitGet(), + CONST_LONG(1000) + ) + ) + } +} \ No newline at end of file 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 d9b72299d9..3b6ae8ab9c 100644 --- a/benchmark/src/test/scala/com/wavesplatform/lang/v1/ScriptEvaluatorBenchmark.scala +++ b/benchmark/src/test/scala/com/wavesplatform/lang/v1/ScriptEvaluatorBenchmark.scala @@ -16,6 +16,7 @@ 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.EnvironmentFunctionsBenchmark.{curve25519, randomBytes} import com.wavesplatform.lang.v1.evaluator.{EvaluatorV1, FunctionIds} import org.openjdk.jmh.annotations._ import org.openjdk.jmh.infra.Blackhole @@ -72,6 +73,9 @@ class ScriptEvaluatorBenchmark { @Benchmark def listMedianEqualElements(st: Median, bh: Blackhole): Unit = bh.consume(evaluatorV1.apply[EVALUATED](st.context, st.equalElements)) + + @Benchmark + def sigVerify32Kb(st: SigVerify32Kb, bh: Blackhole): Unit = bh.consume(evaluatorV1.apply[EVALUATED](st.context, st.expr)) } @State(Scope.Benchmark) @@ -240,3 +244,25 @@ class Median { ) } } + +@State(Scope.Benchmark) +class SigVerify32Kb { + val context: EvaluationContext[NoContext, Id] = + Monoid.combine(PureContext.build(Global, V4).evaluationContext, CryptoContext.build(Global, V4).evaluationContext) + + + val expr: EXPR = { + val (privateKey, publicKey) = curve25519.generateKeypair + val message = randomBytes(32 * 1024 - 1) + val signature = curve25519.sign(privateKey, message) + + FUNCTION_CALL( + Native(SIGVERIFY), + List( + CONST_BYTESTR(ByteStr(message)).explicitGet(), + CONST_BYTESTR(ByteStr(signature)).explicitGet(), + CONST_BYTESTR(ByteStr(publicKey)).explicitGet() + ) + ) + } +} diff --git a/benchmark/src/test/scala/com/wavesplatform/state/LevelDBWriterBenchmark.scala b/benchmark/src/test/scala/com/wavesplatform/state/LevelDBWriterBenchmark.scala index 176d6413ec..1ae2caed5f 100644 --- a/benchmark/src/test/scala/com/wavesplatform/state/LevelDBWriterBenchmark.scala +++ b/benchmark/src/test/scala/com/wavesplatform/state/LevelDBWriterBenchmark.scala @@ -60,7 +60,7 @@ object LevelDBWriterBenchmark { @State(Scope.Benchmark) class TransactionByAddressSt extends BaseSt { - val txsAddresses: Vector[Address] = load("transactionByAddress", benchSettings.txsAddressesFile)(x => Address.fromString(x).explicitGet()) + val txsAddresses: Vector[Address] = load("transactionByAddress", ???)(x => Address.fromString(x).explicitGet()) } @State(Scope.Benchmark) diff --git a/benchmark/src/test/scala/com/wavesplatform/state/WavesEnvironmentRebenchmark.scala b/benchmark/src/test/scala/com/wavesplatform/state/WavesEnvironmentRebenchmark.scala new file mode 100644 index 0000000000..27adb2a15f --- /dev/null +++ b/benchmark/src/test/scala/com/wavesplatform/state/WavesEnvironmentRebenchmark.scala @@ -0,0 +1,215 @@ +package com.wavesplatform.state + +import java.util.concurrent.TimeUnit + +import com.wavesplatform.account.{Address, Alias} +import com.wavesplatform.common.state.ByteStr +import com.wavesplatform.common.utils.EitherExt2 +import com.wavesplatform.database.{DBExt, KeyTags, Keys} +import com.wavesplatform.lang.v1.traits.DataType +import com.wavesplatform.lang.v1.traits.DataType.{Boolean, ByteArray, Long} +import com.wavesplatform.lang.v1.traits.domain.Recipient +import com.wavesplatform.transaction.DataTransaction +import com.wavesplatform.transaction.transfer.TransferTransaction +import org.openjdk.jmh.annotations._ +import org.openjdk.jmh.infra.Blackhole + +import scala.util.Random + +@OutputTimeUnit(TimeUnit.MICROSECONDS) +@BenchmarkMode(Array(Mode.AverageTime)) +@Threads(1) +@Fork(1) +@Warmup(iterations = 100) +@Measurement(iterations = 100) +class WavesEnvironmentRebenchmark { + import WavesEnvironmentRebenchmark._ + + @Benchmark + def resolveAlias(bh: Blackhole, st: St): Unit = { + val useUnexisting = Random.nextBoolean() + if (useUnexisting) { + bh.consume(st.environment.resolveAlias("unexisting_alias_zzzzz")) + } else { + val aliasNr = Random.nextInt(st.allAliases.size) + bh.consume(st.environment.resolveAlias(st.allAliases(aliasNr).name)) + } + } + + @Benchmark + def wavesBalanceOf(bh: Blackhole, st: St): Unit = { + val useUnexisting = Random.nextBoolean() + if (useUnexisting) { + bh.consume(st.environment.accountBalanceOf(Recipient.Address(ByteStr.fromBytes(1, 2, 3)), None)) + } else { + val addressNr = Random.nextInt(st.allAddresses.size) + bh.consume(st.environment.accountBalanceOf(st.allAddresses(addressNr), None)) + } + } + + @Benchmark + def assetBalanceOf(bh: Blackhole, st: St): Unit = { + val useUnexisting = Random.nextBoolean() + val addressNr = Random.nextInt(st.allAddresses.size) + if (useUnexisting) { + bh.consume(st.environment.accountBalanceOf(st.allAddresses(addressNr), Some(Array[Byte](1, 2, 3)))) + } else { + val aliasNr = Random.nextInt(st.allAssets.size) + bh.consume(st.environment.accountBalanceOf(st.allAddresses(addressNr), Some(st.allAssets(aliasNr)))) + } + } + + @Benchmark + def assetInfo(bh: Blackhole, st: St): Unit = { + val useUnexisting = Random.nextBoolean() + if (useUnexisting) { + bh.consume(st.environment.assetInfoById(Array[Byte](1, 2, 3))) + } else { + val aliasNr = Random.nextInt(st.allAssets.size) + bh.consume(st.environment.assetInfoById(st.allAssets(aliasNr))) + } + } + + @Benchmark + def transferTransactionById(bh: Blackhole, st: St): Unit = { + val useUnexisting = Random.nextBoolean() + if (useUnexisting) { + val transactionNr = Random.nextInt(st.allTransactions.size) + bh.consume(st.environment.transferTransactionById(st.allTransactions(transactionNr))) + } else { + val transactionNr = Random.nextInt(st.transferTransactions.size) + bh.consume(st.environment.transferTransactionById(st.transferTransactions(transactionNr).arr)) + } + } + + @Benchmark + def transactionHeightById(bh: Blackhole, st: St): Unit = { + val useUnexisting = Random.nextBoolean() + if (useUnexisting) { + bh.consume(st.environment.transactionHeightById(Array[Byte](1, 2, 3))) + } else { + val transactionNr = Random.nextInt(st.allTransactions.size) + bh.consume(st.environment.transactionHeightById(st.allTransactions(transactionNr))) + } + } + + @Benchmark + def blockInfoByHeight(bh: Blackhole, st: St): Unit = { + val indexNr = Random.nextInt(st.heights.size) + bh.consume(st.environment.blockInfoByHeight(st.heights(indexNr))) + } + + @Benchmark + def dataEntries(bh: Blackhole, st: St): Unit = { + val useUnexisting = Random.nextBoolean() + if (useUnexisting) { + val addressNr = Random.nextInt(st.allAddresses.size) + bh.consume(st.environment.data(st.allAddresses(addressNr), "unexisting_key", Long)) + } else { + val transactionNr = Random.nextInt(st.dataEntries.size) + val dataEntry = st.dataEntries(transactionNr)._1 + val address = st.dataEntries(transactionNr)._2 + val t = + dataEntry.`type` match { + case "string" => DataType.String + case "integer" => Long + case "boolean" => Boolean + case "binary" => ByteArray + } + bh.consume(st.environment.data(address, dataEntry.key, t)) + } + } + + @Benchmark + def biggestDataEntries(bh: Blackhole, st: St): Unit = { + val address = Recipient.Address( + ByteStr(Address.fromString("3PFfUN4dRAyMN4nxYayES1CRZHJjS8JVCHf").explicitGet().bytes) + ) + val checkBinaryOrString = Random.nextBoolean() + if (checkBinaryOrString) { + bh.consume(st.environment.data(address, "bigBinary", ByteArray)) + } else { + bh.consume(st.environment.data(address, "bigString", DataType.String)) + } + } +} + +object WavesEnvironmentRebenchmark { + class St extends DBState { + lazy val allAliases: Vector[Alias] = { + val builder = Vector.newBuilder[Alias] + db.iterateOver(KeyTags.AddressIdOfAlias) { e => + builder += Alias.fromBytes(e.getKey.drop(2)).explicitGet() + } + builder.result() + } + + lazy val allAssets: Vector[Array[Byte]] = { + val builder = Vector.newBuilder[Array[Byte]] + db.iterateOver(KeyTags.AssetDetailsHistory) { e => + builder += e.getKey.drop(2) + } + builder.result() + } + + lazy val allAddresses: IndexedSeq[Recipient.Address] = { + val builder = Vector.newBuilder[Recipient.Address] + db.iterateOver(KeyTags.AddressId) { entry => + builder += Recipient.Address(ByteStr(entry.getKey.drop(2))) + } + builder.result() + } + + lazy val allTransactions: IndexedSeq[Array[Byte]] = { + val txCountAtHeight = + Map.empty[Int, Int].withDefault(h => db.get(Keys.blockMetaAt(Height(h))).fold(0)(_.transactionCount)) + + (1 to (environment.height.toInt, 100)) + .flatMap { h => + val txCount = txCountAtHeight(h) + if (txCount == 0) + None + else + db.get(Keys.transactionAt(Height(h), TxNum(Random.nextInt(txCount).toShort))).map(_._1.id().arr) + } + } + + lazy val dataEntries: IndexedSeq[(DataEntry[_], Recipient.Address)] = { + val txCountAtHeight = + Map.empty[Int, Int].withDefault(h => db.get(Keys.blockMetaAt(Height(h))).fold(0)(_.transactionCount)) + + (1 to (environment.height.toInt, 10)) + .flatMap { h => + val txCount = txCountAtHeight(h) + if (txCount == 0) + None + else + db.get(Keys.transactionAt(Height(h), TxNum(Random.nextInt(txCount).toShort))) + .collect { case (dataTx: DataTransaction, true) if dataTx.data.nonEmpty => + ( + dataTx.data(Random.nextInt(dataTx.data.length)), + Recipient.Address(ByteStr(dataTx.sender.toAddress.bytes)) + ) + } + } + } + + lazy val transferTransactions: IndexedSeq[ByteStr] = { + val txCountAtHeight = + Map.empty[Int, Int].withDefault(h => db.get(Keys.blockMetaAt(Height(h))).fold(0)(_.transactionCount)) + + (1 to (environment.height.toInt, 100)) + .flatMap { h => + val txCount = txCountAtHeight(h) + if (txCount == 0) + None + else + db.get(Keys.transactionAt(Height(h), TxNum(Random.nextInt(txCount).toShort))) + .collect { case (transferTx: TransferTransaction, true) => transferTx.id() } + } + } + + lazy val heights: IndexedSeq[Int] = + (1 to (environment.height.toInt, 1000)).toVector + } +} diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/compiler/Terms.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/compiler/Terms.scala index 1484233cac..6c7b10dcf8 100644 --- a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/compiler/Terms.scala +++ b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/compiler/Terms.scala @@ -217,7 +217,7 @@ object Terms { override def hashCode(): Int = s.hashCode } object CONST_STRING { - def apply(s: String, reduceLimit: Boolean = true): Either[ExecutionError, EVALUATED] = { + def apply(s: String, reduceLimit: Boolean = true): Either[ExecutionError, CONST_STRING] = { val limit = if (reduceLimit) DataEntryValueMax else DataTxMaxBytes diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ctx/NativeFunction.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ctx/NativeFunction.scala index 1257e2835b..f8408fd7d1 100644 --- a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ctx/NativeFunction.scala +++ b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ctx/NativeFunction.scala @@ -53,6 +53,16 @@ object NativeFunction { args = args.map(_._1) ) + def withEnvironment[C[_[_]]](name: String, costByLibVersion: Map[StdLibVersion, Long], internalName: Short, resultType: TYPE, args: (String, TYPE)*)( + ev: ContextfulNativeFunction[C]): NativeFunction[C] = + new NativeFunction( + name = name, + costByLibVersion, + signature = FunctionTypeSignature(result = resultType, args = args.map(a => (a._1, a._2)), header = FunctionHeader.Native(internalName)), + ev = ev, + args = args.map(_._1) + ) + def apply[C[_[_]]](name: String, cost: Long, internalName: Short, resultType: TYPE, args: (String, TYPE)*)( evl: List[EVALUATED] => Either[ExecutionError, EVALUATED] ): NativeFunction[C] = @@ -61,6 +71,14 @@ object NativeFunction { evl(a._2).pure[F] }) + def apply[C[_[_]]](name: String, costByLibVersion: Map[StdLibVersion, Long], internalName: Short, resultType: TYPE, args: (String, TYPE)*)( + evl: List[EVALUATED] => Either[ExecutionError, EVALUATED] + ): NativeFunction[C] = + withEnvironment[C](name, costByLibVersion, internalName, resultType, args: _*)(new ContextfulNativeFunction[C](name, resultType, args.toSeq) { + override def ev[F[_]: Monad](a: (C[F], List[EVALUATED])): F[Either[ExecutionError, EVALUATED]] = + evl(a._2).pure[F] + }) + def apply[C[_[_]]](name: String, costByLibVersion: Map[StdLibVersion, Long], internalName: Short, diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ctx/impl/CryptoContext.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ctx/impl/CryptoContext.scala index e21a5b5581..7165066cfa 100644 --- a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ctx/impl/CryptoContext.scala +++ b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ctx/impl/CryptoContext.scala @@ -212,24 +212,50 @@ object CryptoContext { case xs => notImplemented[Id, EVALUATED](s"rsaVerify(digest: DigestAlgorithmType, message: ByteVector, sig: ByteVector, pub: ByteVector)", xs) } - def toBase58StringF: BaseFunction[NoContext] = NativeFunction("toBase58String", 10, TOBASE58, STRING, ("bytes", BYTESTR)) { + def toBase58StringF: BaseFunction[NoContext] = + NativeFunction( + "toBase58String", + Map[StdLibVersion, Long](V1 -> 10L, V2 -> 10L, V3 -> 10L, V4 -> 3L), + TOBASE58, + STRING, + ("bytes", BYTESTR) + ) { case CONST_BYTESTR(bytes: ByteStr) :: Nil => global.base58Encode(bytes.arr).flatMap(CONST_STRING(_, reduceLimit = version >= V4)) case xs => notImplemented[Id, EVALUATED]("toBase58String(bytes: ByteVector)", xs) } def fromBase58StringF: BaseFunction[NoContext] = - NativeFunction("fromBase58String", 10, FROMBASE58, BYTESTR, ("str", STRING)) { + NativeFunction( + "fromBase58String", + Map[StdLibVersion, Long](V1 -> 10L, V2 -> 10L, V3 -> 10L, V4 -> 1L), + FROMBASE58, + BYTESTR, + ("str", STRING) + ) { case CONST_STRING(str: String) :: Nil => global.base58Decode(str, global.MaxBase58String).flatMap(x => CONST_BYTESTR(ByteStr(x))) case xs => notImplemented[Id, EVALUATED]("fromBase58String(str: String)", xs) } - def toBase64StringF: BaseFunction[NoContext] = NativeFunction("toBase64String", 10, TOBASE64, STRING, ("bytes", BYTESTR)) { + def toBase64StringF: BaseFunction[NoContext] = + NativeFunction( + "toBase64String", + Map[StdLibVersion, Long](V1 -> 10L, V2 -> 10L, V3 -> 10L, V4 -> 35L), + TOBASE64, + STRING, + ("bytes", BYTESTR) + ) { case CONST_BYTESTR(bytes: ByteStr) :: Nil => global.base64Encode(bytes.arr).flatMap(CONST_STRING(_, reduceLimit = version >= V4)) case xs => notImplemented[Id, EVALUATED]("toBase64String(bytes: ByteVector)", xs) } def fromBase64StringF: BaseFunction[NoContext] = - NativeFunction("fromBase64String", 10, FROMBASE64, BYTESTR, ("str", STRING)) { + NativeFunction( + "fromBase64String", + Map[StdLibVersion, Long](V1 -> 10L, V2 -> 10L, V3 -> 10L, V4 -> 40L), + FROMBASE64, + BYTESTR, + ("str", STRING) + ) { case CONST_STRING(str: String) :: Nil => global.base64Decode(str, global.MaxBase64String).flatMap(x => CONST_BYTESTR(ByteStr(x))) case xs => notImplemented[Id, EVALUATED]("fromBase64String(str: String)", xs) } diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ctx/impl/PureContext.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ctx/impl/PureContext.scala index 032870457c..4f8fec5cf7 100644 --- a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ctx/impl/PureContext.scala +++ b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ctx/impl/PureContext.scala @@ -27,6 +27,7 @@ import com.wavesplatform.lang.v1.parser.BinaryOperation._ import com.wavesplatform.lang.v1.{BaseGlobal, CTX} import scala.annotation.tailrec +import scala.collection.mutable.ArrayBuffer import scala.util.{Success, Try} object PureContext { @@ -47,7 +48,13 @@ object PureContext { lazy val subLong: BaseFunction[NoContext] = createTryOp(SUB_OP, LONG, LONG, SUB_LONG)((a, b) => Math.subtractExact(a, b)) lazy val sumString: BaseFunction[NoContext] = - createRawOp(SUM_OP, STRING, STRING, SUM_STRING, 10) { + createRawOp( + SUM_OP, + STRING, + STRING, + SUM_STRING, + Map[StdLibVersion, Long](V1 -> 10L, V2 -> 10L, V3 -> 10L, V4 -> 20L) + ) { case (CONST_STRING(a), CONST_STRING(b)) => if (a.length + b.length <= Terms.DataEntryValueMax) { CONST_STRING(a + b) @@ -57,7 +64,13 @@ object PureContext { case _ => ??? } lazy val sumByteStr: BaseFunction[NoContext] = - createRawOp(SUM_OP, BYTESTR, BYTESTR, SUM_BYTES, 10) { + createRawOp( + SUM_OP, + BYTESTR, + BYTESTR, + SUM_BYTES, + Map[StdLibVersion, Long](V1 -> 10L, V2 -> 10L, V3 -> 10L, V4 -> 2L), + ) { case (CONST_BYTESTR(a), CONST_BYTESTR(b)) => if (a.arr.length + b.arr.length <= Terms.DataEntryValueMax) { CONST_BYTESTR(a ++ b) @@ -112,7 +125,7 @@ object PureContext { lazy val value: BaseFunction[NoContext] = UserFunction( "value", - 13L, + Map[StdLibVersion, Long](V1 -> 13, V2 -> 13, V3 -> 13, V4 -> 2), TYPEPARAM('T'), ("@a", PARAMETERIZEDUNION(List(TYPEPARAM('T'), UNIT)): TYPE) ) { @@ -126,7 +139,7 @@ object PureContext { lazy val valueOrElse: BaseFunction[NoContext] = UserFunction( "valueOrElse", - 13L, + Map[StdLibVersion, Long](V1 -> 13, V2 -> 13, V3 -> 13, V4 -> 2), TYPEPARAM('T'), ("@value", PARAMETERIZEDUNION(List(TYPEPARAM('T'), UNIT))), ("@alternative", TYPEPARAM('T')) @@ -141,7 +154,7 @@ object PureContext { lazy val valueOrErrorMessage: BaseFunction[NoContext] = UserFunction( "valueOrErrorMessage", - 13, + Map[StdLibVersion, Long](V1 -> 13, V2 -> 13, V3 -> 13, V4 -> 2), TYPEPARAM('T'), ("@a", PARAMETERIZEDUNION(List(TYPEPARAM('T'), UNIT))), ("@msg", STRING) @@ -226,7 +239,13 @@ object PureContext { } lazy val toBytesString: BaseFunction[NoContext] = - NativeFunction("toBytes", 1, STRING_TO_BYTES, BYTESTR, ("s", STRING)) { + NativeFunction( + "toBytes", + Map[StdLibVersion, Long](V1 -> 1L, V2 -> 1L, V3 -> 1L, V4 -> 8L), + STRING_TO_BYTES, + BYTESTR, + ("s", STRING) + ) { case CONST_STRING(s) :: Nil => CONST_BYTESTR(ByteStr(s.getBytes(StandardCharsets.UTF_8))) case xs => notImplemented[Id, EVALUATED]("toBytes(s: String)", xs) } @@ -249,19 +268,36 @@ object PureContext { } lazy val takeBytes: BaseFunction[NoContext] = - NativeFunction("take", 1, TAKE_BYTES, BYTESTR, ("xs", BYTESTR), ("number", LONG)) { + NativeFunction( + "take", + Map[StdLibVersion, Long](V1 -> 1L, V2 -> 1L, V3 -> 1L, V4 -> 6L), + TAKE_BYTES, + BYTESTR, + ("xs", BYTESTR), ("number", LONG) + ) { case CONST_BYTESTR(xs) :: CONST_LONG(number) :: Nil => CONST_BYTESTR(xs.take(number)) case xs => notImplemented[Id, EVALUATED]("take(xs: ByteVector, number: Int)", xs) } lazy val dropBytes: BaseFunction[NoContext] = - NativeFunction("drop", 1, DROP_BYTES, BYTESTR, ("xs", BYTESTR), ("number", LONG)) { + NativeFunction( + "drop", + Map[StdLibVersion, Long](V1 -> 1L, V2 -> 1L, V3 -> 1L, V4 -> 6L), + DROP_BYTES, + BYTESTR, + ("xs", BYTESTR), ("number", LONG) + ) { case CONST_BYTESTR(xs) :: CONST_LONG(number) :: Nil => CONST_BYTESTR(xs.drop(number)) case xs => notImplemented[Id, EVALUATED]("drop(xs: ByteVector, number: Int)", xs) } lazy val dropRightBytes: BaseFunction[NoContext] = - UserFunction("dropRight", "dropRightBytes", 19, BYTESTR, ("@xs", BYTESTR), ("@number", LONG)) { + UserFunction( + "dropRight", "dropRightBytes", + Map[StdLibVersion, Long](V1 -> 19L, V2 -> 19L, V3 -> 19L, V4 -> 6L), + BYTESTR, + ("@xs", BYTESTR), ("@number", LONG) + ) { FUNCTION_CALL( takeBytes, List( @@ -278,7 +314,12 @@ object PureContext { } lazy val takeRightBytes: BaseFunction[NoContext] = - UserFunction("takeRight", "takeRightBytes", 19, BYTESTR, ("@xs", BYTESTR), ("@number", LONG)) { + UserFunction( + "takeRight", "takeRightBytes", + Map[StdLibVersion, Long](V1 -> 19L, V2 -> 19L, V3 -> 19L, V4 -> 6L), + BYTESTR, + ("@xs", BYTESTR), ("@number", LONG) + ) { FUNCTION_CALL( dropBytes, List( @@ -297,7 +338,13 @@ object PureContext { private def trimLongToInt(x: Long): Int = Math.toIntExact(Math.max(Math.min(x, Int.MaxValue), Int.MinValue)) lazy val takeString: BaseFunction[NoContext] = - NativeFunction("take", 1, TAKE_STRING, STRING, ("xs", STRING), ("number", LONG)) { + NativeFunction( + "take", + Map[StdLibVersion, Long](V1 -> 1L, V2 -> 1L, V3 -> 1L, V4 -> 20L), + TAKE_STRING, + STRING, + ("xs", STRING), ("number", LONG) + ) { case CONST_STRING(xs) :: CONST_LONG(number) :: Nil => CONST_STRING(xs.take(trimLongToInt(number))) case xs => notImplemented[Id, EVALUATED]("take(xs: String, number: Int)", xs) } @@ -305,7 +352,7 @@ object PureContext { def listConstructor(checkSize: Boolean): NativeFunction[NoContext] = NativeFunction( "cons", - 2, + Map[StdLibVersion, Long](V1 -> 2L, V2 -> 2L, V3 -> 2L, V4 -> 1L), CREATE_LIST, PARAMETERIZEDLIST(PARAMETERIZEDUNION(List(TYPEPARAM('A'), TYPEPARAM('B')))), ("head", TYPEPARAM('A')), @@ -318,7 +365,7 @@ object PureContext { lazy val listAppend: NativeFunction[NoContext] = NativeFunction( LIST_APPEND_OP.func, - 3, + Map[StdLibVersion, Long](V1 -> 3L, V2 -> 3L, V3 -> 3L, V4 -> 1L), APPEND_LIST, PARAMETERIZEDLIST(PARAMETERIZEDUNION(List(TYPEPARAM('A'), TYPEPARAM('B')))), ("list", PARAMETERIZEDLIST(TYPEPARAM('A'))), @@ -331,7 +378,7 @@ object PureContext { lazy val listConcat: NativeFunction[NoContext] = NativeFunction( LIST_CONCAT_OP.func, - 10, + Map[StdLibVersion, Long](V1 -> 10L, V2 -> 10L, V3 -> 10L, V4 -> 4L), CONCAT_LIST, PARAMETERIZEDLIST(PARAMETERIZEDUNION(List(TYPEPARAM('A'), TYPEPARAM('B')))), ("list1", PARAMETERIZEDLIST(TYPEPARAM('A'))), @@ -342,13 +389,24 @@ object PureContext { } lazy val dropString: BaseFunction[NoContext] = - NativeFunction("drop", 1, DROP_STRING, STRING, ("xs", STRING), ("number", LONG)) { + NativeFunction( + "drop", + Map[StdLibVersion, Long](V1 -> 1L, V2 -> 1L, V3 -> 1L, V4 -> 20L), + DROP_STRING, + STRING, + ("xs", STRING), ("number", LONG) + ) { case CONST_STRING(xs) :: CONST_LONG(number) :: Nil => CONST_STRING(xs.drop(trimLongToInt(number))) case xs => notImplemented[Id, EVALUATED]("drop(xs: String, number: Int)", xs) } lazy val takeRightString: BaseFunction[NoContext] = - UserFunction("takeRight", 19, STRING, ("@xs", STRING), ("@number", LONG)) { + UserFunction( + "takeRight", + Map[StdLibVersion, Long](V1 -> 19L, V2 -> 19L, V3 -> 19L, V4 -> 20L), + STRING, + ("@xs", STRING), ("@number", LONG) + ) { FUNCTION_CALL( dropString, List( @@ -365,7 +423,12 @@ object PureContext { } lazy val dropRightString: BaseFunction[NoContext] = - UserFunction("dropRight", 19, STRING, ("@xs", STRING), ("@number", LONG)) { + UserFunction( + "dropRight", + Map[StdLibVersion, Long](V1 -> 19L, V2 -> 19L, V3 -> 19L, V4 -> 20L), + STRING, + ("@xs", STRING), ("@number", LONG) + ) { FUNCTION_CALL( takeString, List( @@ -384,7 +447,13 @@ object PureContext { val UTF8Decoder = UTF_8.newDecoder def toUtf8String(version: StdLibVersion): BaseFunction[NoContext] = - NativeFunction("toUtf8String", 20, UTF8STRING, STRING, ("u", BYTESTR)) { + NativeFunction( + "toUtf8String", + Map[StdLibVersion, Long](V1 -> 20L, V2 -> 20L, V3 -> 20L, V4 -> 7L), + UTF8STRING, + STRING, + ("u", BYTESTR) + ) { case CONST_BYTESTR(u) :: Nil => Try(ByteBuffer.wrap(u.arr)) .map(UTF8Decoder.decode) @@ -399,7 +468,7 @@ object PureContext { } lazy val toLong: BaseFunction[NoContext] = - NativeFunction("toInt", 10, BININT, LONG, ("bin", BYTESTR)) { + NativeFunction("toInt", Map[StdLibVersion, Long](V1 -> 10L, V2 -> 10L, V3 -> 10L, V4 -> 1L), BININT, LONG, ("bin", BYTESTR)) { case CONST_BYTESTR(u) :: Nil => Try(CONST_LONG(ByteBuffer.wrap(u.arr).getLong())).toEither.left.map { case _: BufferUnderflowException => "Buffer underflow" @@ -423,7 +492,14 @@ object PureContext { } lazy val indexOf: BaseFunction[NoContext] = - NativeFunction("indexOf", 20, INDEXOF, optionLong, ("str", STRING), ("substr", STRING)) { + NativeFunction( + "indexOf", + Map[StdLibVersion, Long](V1 -> 20L, V2 -> 20L, V3 -> 20L, V4 -> 3L), + INDEXOF, + optionLong, + ("str", STRING), + ("substr", STRING) + ) { case CONST_STRING(m) :: CONST_STRING(sub) :: Nil => Right({ val i = m.indexOf(sub) @@ -437,7 +513,13 @@ object PureContext { } lazy val indexOfN: BaseFunction[NoContext] = - NativeFunction("indexOf", 20, INDEXOFN, optionLong, ("str", STRING), ("substr", STRING), ("offset", LONG)) { + NativeFunction( + "indexOf", + Map[StdLibVersion, Long](V1 -> 20L, V2 -> 20L, V3 -> 20L, V4 -> 3L), + INDEXOFN, + optionLong, + ("str", STRING), ("substr", STRING), ("offset", LONG) + ) { case CONST_STRING(m) :: CONST_STRING(sub) :: CONST_LONG(off) :: Nil => Right(if (off >= 0 && off <= m.length) { val i = m.indexOf(sub, off.toInt) @@ -455,7 +537,7 @@ object PureContext { lazy val lastIndexOf: BaseFunction[NoContext] = NativeFunction( "lastIndexOf", - 20, + Map[StdLibVersion, Long](V1 -> 20L, V2 -> 20L, V3 -> 20L, V4 -> 3L), LASTINDEXOF, optionLong, ("str", STRING), @@ -476,7 +558,7 @@ object PureContext { lazy val lastIndexOfWithOffset: BaseFunction[NoContext] = NativeFunction( "lastIndexOf", - 20, + Map[StdLibVersion, Long](V1 -> 20L, V2 -> 20L, V3 -> 20L, V4 -> 3L), LASTINDEXOFN, optionLong, ("str", STRING), @@ -499,35 +581,37 @@ object PureContext { } lazy val splitStr: BaseFunction[NoContext] = - NativeFunction("split", 100, SPLIT, listString, ("str", STRING), ("separator", STRING)) { + NativeFunction("split", Map(V3 -> 100L, V4 -> 75L), SPLIT, listString, ("str", STRING), ("separator", STRING)) { case CONST_STRING(str) :: CONST_STRING(sep) :: Nil => - split(str, sep) - .traverse(CONST_STRING(_)) - .flatMap(s => ARR(s.toIndexedSeq, true)) - case xs => notImplemented[Id, EVALUATED]("split(str: String, separator: String)", xs) + ARR(split(str, sep).toIndexedSeq, limited = true) + case xs => + notImplemented[Id, EVALUATED]("split(str: String, separator: String)", xs) } - private def split(str: String, sep: String) = + private def split(str: String, sep: String): Iterable[CONST_STRING] = { if (str == "") listWithEmptyStr - else if (sep == "") 1 to str.length map (i => String.valueOf(str.charAt(i - 1))) toList - else splitRec(str, sep).reverse + else if (sep == "") + (1 to str.length) + .map(i => CONST_STRING(String.valueOf(str.charAt(i - 1))).explicitGet()) + else splitRec(str, sep) + } - private val listWithEmptyStr = List("") + private val listWithEmptyStr = List(CONST_STRING("").explicitGet()) @tailrec private def splitRec( str: String, sep: String, offset: Int = 0, - splitted: List[String] = Nil - ): List[String] = { + splitted: ArrayBuffer[CONST_STRING] = ArrayBuffer() + ): ArrayBuffer[CONST_STRING] = { val index = str.indexOf(sep, offset) - if (index == -1) str.substring(offset, str.length) :: splitted + if (index == -1) splitted += CONST_STRING(str.substring(offset, str.length)).explicitGet() else splitRec( str, sep, index + sep.length, - str.substring(offset, index) :: splitted + splitted += CONST_STRING(str.substring(offset, index)).explicitGet() ) } @@ -547,7 +631,12 @@ object PureContext { } lazy val contains: BaseFunction[NoContext] = - UserFunction("contains", 20, BOOLEAN, ("@source", STRING), ("@substr", STRING)) { + UserFunction( + "contains", + Map[StdLibVersion, Long](V1 -> 20L, V2 -> 20L, V3 -> 20L, V4 -> 3L), + BOOLEAN, + ("@source", STRING), ("@substr", STRING) + ) { FUNCTION_CALL( User("isDefined"), List( @@ -560,7 +649,12 @@ object PureContext { } lazy val parseInt: BaseFunction[NoContext] = - NativeFunction("parseInt", 20, PARSEINT, optionLong, ("str", STRING)) { + NativeFunction( + "parseInt", + Map[StdLibVersion, Long](V1 -> 20L, V2 -> 20L, V3 -> 20L, V4 -> 2L), + PARSEINT, + optionLong, + ("str", STRING)) { case CONST_STRING(u) :: Nil => Try(CONST_LONG(u.toLong)).orElse(Success(unit)).toEither.left.map(_.toString) case xs => notImplemented[Id, EVALUATED]("parseInt(str: String)", xs) } @@ -568,7 +662,7 @@ object PureContext { lazy val parseIntVal: BaseFunction[NoContext] = UserFunction( "parseIntValue", - 20, + Map[StdLibVersion, Long](V1 -> 20L, V2 -> 20L, V3 -> 20L, V4 -> 2L), LONG, ("str", STRING) ) { @@ -579,18 +673,26 @@ object PureContext { ) } - def createRawOp(op: BinaryOperation, t: TYPE, r: TYPE, func: Short, complicity: Int = 1)( + def createRawOp(op: BinaryOperation, t: TYPE, r: TYPE, func: Short, complexity: Int = 1)( body: (EVALUATED, EVALUATED) => Either[String, EVALUATED] ): BaseFunction[NoContext] = - NativeFunction(opsToFunctions(op), complicity, func, r, ("a", t), ("b", t)) { + NativeFunction(opsToFunctions(op), complexity, func, r, ("a", t), ("b", t)) { case a :: b :: Nil => body(a, b) case xs => notImplemented[Id, EVALUATED](s"${opsToFunctions(op)}(a: ${t.toString}, b: ${t.toString})", xs) } - def createOp(op: BinaryOperation, t: TYPE, r: TYPE, func: Short, complicity: Int = 1)( + def createRawOp(op: BinaryOperation, t: TYPE, r: TYPE, func: Short, complexity: Map[StdLibVersion, Long])( + body: (EVALUATED, EVALUATED) => Either[String, EVALUATED] + ): BaseFunction[NoContext] = + NativeFunction(opsToFunctions(op), complexity, func, r, ("a", t), ("b", t)) { + case a :: b :: Nil => body(a, b) + case xs => notImplemented[Id, EVALUATED](s"${opsToFunctions(op)}(a: ${t.toString}, b: ${t.toString})", xs) + } + + def createOp(op: BinaryOperation, t: TYPE, r: TYPE, func: Short, complexity: Int = 1)( body: (Long, Long) => Boolean ): BaseFunction[NoContext] = - NativeFunction(opsToFunctions(op), complicity, func, r, ("a", t), ("b", t)) { + NativeFunction(opsToFunctions(op), complexity, func, r, ("a", t), ("b", t)) { case CONST_LONG(a) :: CONST_LONG(b) :: Nil => Right(CONST_BOOLEAN(body(a, b))) case xs => notImplemented[Id, EVALUATED](s"${opsToFunctions(op)}(a: ${t.toString}, b: ${t.toString})", xs) } diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ctx/impl/waves/Functions.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ctx/impl/waves/Functions.scala index 713e8b0d4e..16623930d6 100644 --- a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ctx/impl/waves/Functions.scala +++ b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ctx/impl/waves/Functions.scala @@ -5,7 +5,7 @@ import cats.{Id, Monad} import com.wavesplatform.common.state.ByteStr import com.wavesplatform.common.utils.EitherExt2 import com.wavesplatform.lang.ExecutionError -import com.wavesplatform.lang.directives.values.{StdLibVersion, V4} +import com.wavesplatform.lang.directives.values._ import com.wavesplatform.lang.v1.FunctionHeader import com.wavesplatform.lang.v1.compiler.Terms import com.wavesplatform.lang.v1.compiler.Terms._ @@ -26,7 +26,7 @@ object Functions { val args = Seq(("addressOrAlias", addressOrAliasType), ("key", STRING)) NativeFunction.withEnvironment[Environment]( name, - 100, + Map[StdLibVersion, Long](V1 -> 100L, V2 -> 100L, V3 -> 100L, V4 -> 25L), internalName, UNION(dataType.innerType, UNIT), ("addressOrAlias", addressOrAliasType), @@ -94,7 +94,7 @@ object Functions { private def getDataByIndexF(name: String, dataType: DataType, version: StdLibVersion): BaseFunction[Environment] = UserFunction( name, - 30, + Map[StdLibVersion, Long](V1 -> 30L, V2 -> 30L, V3 -> 30L, V4 -> 4L), UNION(dataType.innerType, UNIT), ("@data", LIST(commonDataEntryType(version))), ("@index", LONG) @@ -117,18 +117,25 @@ object Functions { def getBinaryByIndexF(v: StdLibVersion): BaseFunction[Environment] = getDataByIndexF("getBinary", DataType.ByteArray, v) def getStringByIndexF(v: StdLibVersion): BaseFunction[Environment] = getDataByIndexF("getString", DataType.String, v) - private def secureHashExpr(xs: EXPR): EXPR = FUNCTION_CALL( - FunctionHeader.Native(KECCAK256), - List( - FUNCTION_CALL( - FunctionHeader.Native(BLAKE256), - List(xs) + private def secureHashExpr(xs: EXPR, version: StdLibVersion): EXPR = + FUNCTION_CALL( + FunctionHeader.Native(if (version >= V4) KECCAK256_LIM else KECCAK256), + List( + FUNCTION_CALL( + FunctionHeader.Native(if (version >= V4) BLAKE256_LIM else BLAKE256), + List(xs) + ) ) ) - ) - val addressFromPublicKeyF: BaseFunction[Environment] = - UserFunction.withEnvironment[Environment]("addressFromPublicKey", 82, addressType, ("@publicKey", BYTESTR))( + def addressFromPublicKeyF(version: StdLibVersion): BaseFunction[Environment] = + UserFunction.withEnvironment[Environment]( + name = "addressFromPublicKey", + internalName = "addressFromPublicKey", + Map[StdLibVersion, Long](V1 -> 82L, V2 -> 82L, V3 -> 82L, V4 -> 63L), + addressType, + ("@publicKey", BYTESTR) + )( new ContextfulUserFunction[Environment] { override def apply[F[_]: Monad](env: Environment[F]): EXPR = FUNCTION_CALL( @@ -145,7 +152,7 @@ object Functions { FUNCTION_CALL( PureContext.takeBytes, List( - secureHashExpr(REF("@publicKey")), + secureHashExpr(REF("@publicKey"), version), CONST_LONG(EnvironmentFunctions.HashLength) ) ) @@ -160,7 +167,7 @@ object Functions { FUNCTION_CALL( PureContext.takeBytes, List( - secureHashExpr(REF("@afpk_withoutChecksum")), + secureHashExpr(REF("@afpk_withoutChecksum"), version), CONST_LONG(EnvironmentFunctions.ChecksumLength) ) ) @@ -184,7 +191,7 @@ object Functions { str ) - private def verifyAddressChecksumExpr(addressBytes: EXPR): EXPR = FUNCTION_CALL( + private def verifyAddressChecksumExpr(addressBytes: EXPR, version: StdLibVersion): EXPR = FUNCTION_CALL( PureContext.eq, List( // actual checksum @@ -193,14 +200,17 @@ object Functions { FUNCTION_CALL( PureContext.takeBytes, List( - secureHashExpr(FUNCTION_CALL(PureContext.dropRightBytes, List(addressBytes, CONST_LONG(EnvironmentFunctions.ChecksumLength)))), + secureHashExpr( + FUNCTION_CALL(PureContext.dropRightBytes, List(addressBytes, CONST_LONG(EnvironmentFunctions.ChecksumLength))), + version + ), CONST_LONG(EnvironmentFunctions.ChecksumLength) ) ) ) ) - val addressFromStringF: BaseFunction[Environment] = + def addressFromStringF(version: StdLibVersion): BaseFunction[Environment] = UserFunction.withEnvironment("addressFromString", 124, optionAddress, ("@string", STRING)) { new ContextfulUserFunction[Environment] { override def apply[F[_]: Monad](env: Environment[F]): EXPR = @@ -242,7 +252,7 @@ object Functions { ) ), IF( - verifyAddressChecksumExpr(REF("@afs_addrBytes")), + verifyAddressChecksumExpr(REF("@afs_addrBytes"), version), FUNCTION_CALL(FunctionHeader.User("Address"), List(REF("@afs_addrBytes"))), REF("unit") ), @@ -283,7 +293,7 @@ object Functions { val addressFromRecipientF: BaseFunction[Environment] = NativeFunction.withEnvironment[Environment]( "addressFromRecipient", - 100, + Map[StdLibVersion, Long](V1 -> 100L, V2 -> 100L, V3 -> 100L, V4 -> 10L), ADDRESSFROMRECIPIENT, addressType, ("AddressOrAlias", addressOrAliasType) @@ -325,7 +335,7 @@ object Functions { val assetBalanceF: BaseFunction[Environment] = NativeFunction.withEnvironment[Environment]( "assetBalance", - 100, + Map[StdLibVersion, Long](V1 -> 100L, V2 -> 100L, V3 -> 100L, V4 -> 15L), ACCOUNTASSETBALANCE, LONG, ("addressOrAlias", addressOrAliasType), @@ -348,7 +358,7 @@ object Functions { val assetBalanceV4F: BaseFunction[Environment] = NativeFunction.withEnvironment[Environment]( "assetBalance", - 100, + 15, ACCOUNTASSETONLYBALANCE, LONG, ("addressOrAlias", addressOrAliasType), @@ -369,7 +379,7 @@ object Functions { val wavesBalanceV4F: BaseFunction[Environment] = NativeFunction.withEnvironment[Environment]( "wavesBalance", - 100, + Map[StdLibVersion, Long](V1 -> 100L, V2 -> 100L, V3 -> 100L, V4 -> 10L), ACCOUNTWAVESBALANCE, balanceDetailsType, ("addressOrAlias", addressOrAliasType) @@ -394,7 +404,7 @@ object Functions { def assetInfoF(version: StdLibVersion): BaseFunction[Environment] = NativeFunction.withEnvironment[Environment]( "assetInfo", - 100, + Map[StdLibVersion, Long](V1 -> 100L, V2 -> 100L, V3 -> 100L, V4 -> 50L), GETASSETINFOBYID, optionAsset(version), ("id", BYTESTR) @@ -422,7 +432,7 @@ object Functions { val txHeightByIdF: BaseFunction[Environment] = NativeFunction.withEnvironment[Environment]( "transactionHeightById", - 100, + Map[StdLibVersion, Long](V1 -> 100L, V2 -> 100L, V3 -> 100L, V4 -> 15L), TRANSACTIONHEIGHTBYID, optionLong, ("id", BYTESTR) @@ -443,7 +453,7 @@ object Functions { def blockInfoByHeightF(version: StdLibVersion): BaseFunction[Environment] = NativeFunction.withEnvironment[Environment]( "blockInfoByHeight", - 100, + Map[StdLibVersion, Long](V1 -> 100L, V2 -> 100L, V3 -> 100L, V4 -> 5L), BLOCKINFOBYHEIGHT, UNION(UNIT, blockInfo(version)), ("height", LONG) @@ -490,7 +500,7 @@ object Functions { getBooleanByIndexF(v), getBinaryByIndexF(v), getStringByIndexF(v), - if (v >= V4) addressFromStringV4 else addressFromStringF + if (v >= V4) addressFromStringV4 else addressFromStringF(v) ).map(withExtract) def txByIdF(proofsEnabled: Boolean, version: StdLibVersion): BaseFunction[Environment] = @@ -518,7 +528,7 @@ object Functions { def transferTxByIdF(proofsEnabled: Boolean, version: StdLibVersion): BaseFunction[Environment] = NativeFunction.withEnvironment[Environment]( "transferTransactionById", - 100, + Map[StdLibVersion, Long](V1 -> 100L, V2 -> 100L, V3 -> 100L, V4 -> 60L), TRANSFERTRANSACTIONBYID, UNION(buildTransferTransactionType(proofsEnabled, version), UNIT), ("id", BYTESTR) diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ctx/impl/waves/WavesContext.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ctx/impl/waves/WavesContext.scala index e57d531d84..a589d31dd7 100644 --- a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ctx/impl/waves/WavesContext.scala +++ b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ctx/impl/waves/WavesContext.scala @@ -21,22 +21,19 @@ object WavesContext { getBooleanFromStateF, getBinaryFromStateF, getStringFromStateF, - addressFromPublicKeyF, addressFromRecipientF, ) - private val v123OnlyFunctions = + private val balanceV123Functions = Array( assetBalanceF, - wavesBalanceF, - addressFromStringF + wavesBalanceF ) - private val v4Functions = + private val balanceV4Functions = Array( assetBalanceV4F, - wavesBalanceV4F, - addressFromStringV4 + wavesBalanceV4F ) private val invariableCtx = @@ -97,13 +94,15 @@ object WavesContext { getBooleanByIndexF(version), getBinaryByIndexF(version), getStringByIndexF(version), + addressFromPublicKeyF(version), + if (version >= V4) addressFromStringV4 else addressFromStringF(version), ) val versionSpecificFuncs = version match { - case V1 | V2 => Array(txByIdF(proofsEnabled, version)) ++ v123OnlyFunctions - case V3 => fromV3Funcs(proofsEnabled, version) ++ v123OnlyFunctions - case V4 => fromV4Funcs(proofsEnabled, version) ++ v4Functions + case V1 | V2 => Array(txByIdF(proofsEnabled, version)) ++ balanceV123Functions + case V3 => fromV3Funcs(proofsEnabled, version) ++ balanceV123Functions + case V4 => fromV4Funcs(proofsEnabled, version) ++ balanceV4Functions } commonFuncs ++ versionSpecificFuncs } diff --git a/node-it/src/test/scala/com/wavesplatform/it/sync/smartcontract/RideFunctionFamilySuite.scala b/node-it/src/test/scala/com/wavesplatform/it/sync/smartcontract/RideFunctionFamilySuite.scala index 1f0e298f4e..0397f7fe16 100644 --- a/node-it/src/test/scala/com/wavesplatform/it/sync/smartcontract/RideFunctionFamilySuite.scala +++ b/node-it/src/test/scala/com/wavesplatform/it/sync/smartcontract/RideFunctionFamilySuite.scala @@ -57,18 +57,18 @@ class RideFunctionFamilySuite extends BaseTransactionSuite with CancelAfterFailu )(f).stripMargin - test("function family in asset sript") { + test("function family in asset script") { val CompiledScript(scr, complexity, _) = sender.scriptCompile(ffAssetScript(4)) val EstimatedScript(_, _, ecomplexity, _) = sender.scriptEstimate(scr) ecomplexity shouldBe complexity - ecomplexity shouldBe 1035 + ecomplexity shouldBe 1019 val DecompiledScript(dec) = sender.scriptDecompile(scr) List("sigVerify_16Kb(a, a, a)", "rsaVerify_32Kb(SHA3512, a, a, a)", "blake2b256_64Kb(a)", "keccak256_128Kb(a)", "sha256(a)").forall(dec.contains) shouldBe true dec.contains("Native") shouldBe false } - test("function family in asset sript V3") { + test("function family in asset script V3") { assertApiError(sender.scriptCompile(ffAssetScript(3))) { error => error.statusCode shouldBe 400 error.id shouldBe ScriptCompilerError.Id @@ -87,7 +87,7 @@ class RideFunctionFamilySuite extends BaseTransactionSuite with CancelAfterFailu val CompiledScript(scr, complexity, _) = sender.scriptCompile(ffDApp(4)(hash)) val EstimatedScript(_, _, ecomplexity, _) = sender.scriptEstimate(scr) ecomplexity shouldBe complexity - ecomplexity shouldBe 438 + ecomplexity shouldBe 405 val DecompiledScript(dec) = sender.scriptDecompile(scr) names.forall(dec.contains) shouldBe true dec.contains("Native") shouldBe false @@ -96,7 +96,7 @@ class RideFunctionFamilySuite extends BaseTransactionSuite with CancelAfterFailu test("function family (verify)") { for(((f, names), c) <- List( - "sig" -> List("sigVerify(a, a, a)", "sigVerify_16Kb(a, a, a)", "sigVerify_32Kb(a, a, a)", "sigVerify_64Kb(a, a, a)", "sigVerify_128Kb(a, a, a)") -> 712, + "sig" -> List("sigVerify(a, a, a)", "sigVerify_16Kb(a, a, a)", "sigVerify_32Kb(a, a, a)", "sigVerify_64Kb(a, a, a)", "sigVerify_128Kb(a, a, a)") -> 711, "rsa" -> List("rsaVerify(NOALG, a, a, a)", "rsaVerify_32Kb(SHA256, a, a, a)", "rsaVerify_64Kb(SHA3256, a, a, a)", "rsaVerify_128Kb(NOALG, a, a, a)") -> 2945, "rsa16" -> List("rsaVerify_16Kb(MD5, a, a, a)", "rsaVerify_32Kb(SHA256, a, a, a)", "rsaVerify_64Kb(SHA3256, a, a, a)", "rsaVerify_128Kb(NOALG, a, a, a)") -> 2445 )) { diff --git a/node/src/test/scala/com/wavesplatform/transaction/smart/script/ScriptCompilerV1Test.scala b/node/src/test/scala/com/wavesplatform/transaction/smart/script/ScriptCompilerV1Test.scala index f8fe3fb691..0ef9d8fa26 100644 --- a/node/src/test/scala/com/wavesplatform/transaction/smart/script/ScriptCompilerV1Test.scala +++ b/node/src/test/scala/com/wavesplatform/transaction/smart/script/ScriptCompilerV1Test.scala @@ -203,9 +203,9 @@ class ScriptCompilerV1Test extends PropSpec with PropertyChecks with Matchers wi ): String = s""" | func script() = { - | let a0 = base58'' + | let a0 = "" | ${1 to assigns map (i => s"let a$i = a${i - 1} + a0") mkString " "} - | a$assigns == base58'' ${"&& true " * conjunctions} + | a$assigns == "" ${"&& true " * conjunctions} | } | | ${if (withVerifier) "@Verifier(tx) func verify() = " else ""} @@ -221,11 +221,14 @@ class ScriptCompilerV1Test extends PropSpec with PropertyChecks with Matchers wi val directives = buildDirectives(version, contentType, scriptType) val (assigns, conjunctions) = (version, contentType, scriptType) match { - case (V3 | V4, DApp, Account) => (155, 15) - case (V3 | V4, Expression, Account) => (155, 20) - case (V3 | V4, Expression, Asset) => (209, 7) - case (_, Expression, _) => (103, 14) - case _ => ??? + case (V3, DApp, Account) => (155, 15) + case (V3, Expression, Account) => (155, 20) + case (V3, Expression, Asset) => (209, 7) + case (V4, DApp, Account) => (101, 23) + case (V4, Expression, Account) => (101, 28) + case (V4, Expression, Asset) => (137, 6) + case (_, Expression, _) => (103, 14) + case _ => ??? } val withVerifier = contentType == DApp val validScript = directives + buildScript(assigns, conjunctions, withVerifier) diff --git a/node/src/test/scala/com/wavesplatform/transaction/smart/script/estimator/FunctionComplexityTest.scala b/node/src/test/scala/com/wavesplatform/transaction/smart/script/estimator/FunctionComplexityTest.scala index 2c728404d7..84323aab5b 100644 --- a/node/src/test/scala/com/wavesplatform/transaction/smart/script/estimator/FunctionComplexityTest.scala +++ b/node/src/test/scala/com/wavesplatform/transaction/smart/script/estimator/FunctionComplexityTest.scala @@ -120,18 +120,16 @@ class FunctionComplexityTest(estimator: ScriptEstimator) extends PropSpec with P } property("func complexity map size is equal stdLib SupportedVersions count") { - val supportedVersionCount = DirectiveDictionary[StdLibVersion].all.size - ctxV1.functions.foreach { func => - func.costByLibVersion.size shouldBe supportedVersionCount + func.costByLibVersion.size shouldBe DirectiveDictionary[StdLibVersion].all.size } ctxV2.functions.foreach { func => - func.costByLibVersion.size shouldBe supportedVersionCount + func.costByLibVersion.size shouldBe >= (DirectiveDictionary[StdLibVersion].all.count(_ >= V2)) } ctxV3.functions.foreach { func => - func.costByLibVersion.size shouldBe supportedVersionCount + func.costByLibVersion.size shouldBe >= (DirectiveDictionary[StdLibVersion].all.count(_ >= V3)) } }