diff --git a/build.sbt b/build.sbt
index 0c8bfebbe8..2dadab320c 100644
--- a/build.sbt
+++ b/build.sbt
@@ -1,5 +1,4 @@
import scala.language.postfixOps
-import scala.util.Try
import scala.sys.process._
organization := "org.scorexfoundation"
@@ -55,17 +54,11 @@ dynverSeparator in ThisBuild := "-"
val bouncycastleBcprov = "org.bouncycastle" % "bcprov-jdk15on" % "1.64"
val scrypto = "org.scorexfoundation" %% "scrypto" % "2.1.10"
val scorexUtil = "org.scorexfoundation" %% "scorex-util" % "0.1.8"
-val macroCompat = "org.typelevel" %% "macro-compat" % "1.1.1"
-val paradise = "org.scalamacros" %% "paradise" % "2.1.0" cross CrossVersion.full
val debox = "org.spire-math" %% "debox" % "0.8.0"
val kiama = "org.bitbucket.inkytonik.kiama" %% "kiama" % "2.1.0"
val fastparse = "com.lihaoyi" %% "fastparse" % "1.0.0"
val commonsIo = "commons-io" % "commons-io" % "2.5"
-
-val specialVersion = "0.6.1"
-val meta = "io.github.scalan" %% "meta" % specialVersion
-val plugin = "io.github.scalan" %% "plugin" % specialVersion
-val libraryconf = "io.github.scalan" %% "library-conf" % specialVersion
+val commonsMath3 = "org.apache.commons" % "commons-math3" % "3.2"
val testingDependencies = Seq(
"org.scalatest" %% "scalatest" % "3.0.5" % "test",
@@ -144,11 +137,7 @@ pgpSecretRing := file("ci/secring.asc")
pgpPassphrase := sys.env.get("PGP_PASSPHRASE").map(_.toArray)
usePgpKeyHex("C1FD62B4D44BDF702CDF2B726FF59DA944B150DD")
-def libraryDefSettings = commonSettings ++ testSettings ++ Seq(
- scalacOptions ++= Seq(
-// s"-Xplugin:${file(".").absolutePath }/scalanizer/target/scala-2.12/scalanizer-assembly-core-opt-0d03a785-SNAPSHOT.jar"
- )
-)
+def libraryDefSettings = commonSettings ++ testSettings
lazy val common = Project("common", file("common"))
.settings(commonSettings ++ testSettings,
@@ -160,9 +149,8 @@ lazy val common = Project("common", file("common"))
lazy val libraryapi = Project("library-api", file("library-api"))
.dependsOn(common % allConfigDependency)
- .settings(libraryDefSettings :+ addCompilerPlugin(paradise),
- libraryDependencies ++= Seq(
- ))
+ .settings(libraryDefSettings,
+ libraryDependencies ++= Seq())
.settings(publish / skip := true)
lazy val libraryimpl = Project("library-impl", file("library-impl"))
@@ -184,46 +172,11 @@ lazy val library = Project("library", file("library"))
libraryDependencies ++= Seq( debox ))
.settings(publish / skip := true)
-lazy val sigmaconf = Project("sigma-conf", file("sigma-conf"))
- .settings(commonSettings,
- libraryDependencies ++= (
- if(scalaBinaryVersion.value == "2.11")
- Seq.empty
- else
- Seq(plugin, libraryconf)
- ),
- skip in compile := scalaBinaryVersion.value == "2.11"
- )
- .settings(publish / skip := true)
-
-lazy val scalanizer = Project("scalanizer", file("scalanizer"))
- .dependsOn(sigmaconf, libraryapi, libraryimpl)
- .settings(commonSettings,
- libraryDependencies ++= (
- if(scalaBinaryVersion.value == "2.11")
- Seq.empty
- else
- Seq(meta, plugin)
- ),
- skip in compile := scalaBinaryVersion.value == "2.11",
- assemblyOption in assembly ~= { _.copy(includeScala = false, includeDependency = true) },
- assemblyMergeStrategy in assembly := {
- case PathList("scalan", xs @ _*) => MergeStrategy.first
- case other => (assemblyMergeStrategy in assembly).value(other)
- },
- artifact in(Compile, assembly) := {
- val art = (artifact in(Compile, assembly)).value
- art.withClassifier(Some("assembly"))
- },
- addArtifact(artifact in(Compile, assembly), assembly)
- )
- .settings(publish / skip := true)
-
lazy val sigmaapi = Project("sigma-api", file("sigma-api"))
.dependsOn(common, libraryapi)
- .settings(libraryDefSettings :+ addCompilerPlugin(paradise),
+ .settings(libraryDefSettings,
libraryDependencies ++= Seq(
- macroCompat, scrypto, bouncycastleBcprov
+ scrypto, bouncycastleBcprov
))
.settings(publish / skip := true)
@@ -256,7 +209,7 @@ lazy val sigmastate = (project in file("sigmastate"))
.dependsOn(sigmaimpl % allConfigDependency, sigmalibrary % allConfigDependency)
.settings(libraryDefSettings)
.settings(libraryDependencies ++= Seq(
- scorexUtil, kiama, fastparse,
+ scorexUtil, kiama, fastparse, commonsMath3,
if (scalaVersion.value == scala211) circeCore211 else circeCore,
if (scalaVersion.value == scala211) circeGeneric211 else circeGeneric,
if (scalaVersion.value == scala211) circeParser211 else circeParser
@@ -266,7 +219,7 @@ lazy val sigmastate = (project in file("sigmastate"))
lazy val sigma = (project in file("."))
.aggregate(
sigmastate, common, core, libraryapi, libraryimpl, library,
- sigmaapi, sigmaimpl, sigmalibrary, sigmaconf, scalanizer)
+ sigmaapi, sigmaimpl, sigmalibrary)
.settings(libraryDefSettings, rootSettings)
.settings(publish / aggregate := false)
.settings(publishLocal / aggregate := false)
@@ -342,7 +295,6 @@ commands += Command.command("ergoItTest") { state =>
def runSpamTestTask(task: String, sigmastateVersion: String, log: Logger): Unit = {
val spamBranch = "master"
val envVars = Seq("SIGMASTATE_VERSION" -> sigmastateVersion,
- "SPECIAL_VERSION" -> specialVersion,
// SSH_SPAM_REPO_KEY should be set (see Jenkins Credentials Binding Plugin)
"GIT_SSH_COMMAND" -> "ssh -i $SSH_SPAM_REPO_KEY")
diff --git a/common/src/main/java/java7/compat/Math.java b/common/src/main/java/java7/compat/Math.java
new file mode 100644
index 0000000000..72ed8d5de0
--- /dev/null
+++ b/common/src/main/java/java7/compat/Math.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (c) 1994, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package java7.compat;
+
+/**
+ * Contains methods introduced since Java 1.8 which are not available in Java 1.7.
+ * Using this methods supports compatibility with Java 1.7 in non-JVM contexts like
+ * RoboVM.
+ * The implementations are copies from JDK 1.8 sources.
+ *
+ * See
+ * this issue
+ */
+public final class Math {
+ /**
+ * Returns the sum of its arguments,
+ * throwing an exception if the result overflows an {@code int}.
+ *
+ * @param x the first value
+ * @param y the second value
+ * @return the result
+ * @throws ArithmeticException if the result overflows an int
+ * @since 1.8
+ */
+ public static int addExact(int x, int y) {
+ int r = x + y;
+ // HD 2-12 Overflow iff both arguments have the opposite sign of the result
+ if (((x ^ r) & (y ^ r)) < 0) {
+ throw new ArithmeticException("integer overflow");
+ }
+ return r;
+ }
+
+ /**
+ * Returns the sum of its arguments,
+ * throwing an exception if the result overflows a {@code long}.
+ *
+ * @param x the first value
+ * @param y the second value
+ * @return the result
+ * @throws ArithmeticException if the result overflows a long
+ * @since 1.8
+ */
+ public static long addExact(long x, long y) {
+ long r = x + y;
+ // HD 2-12 Overflow iff both arguments have the opposite sign of the result
+ if (((x ^ r) & (y ^ r)) < 0) {
+ throw new ArithmeticException("long overflow");
+ }
+ return r;
+ }
+
+ /**
+ * Returns the difference of the arguments,
+ * throwing an exception if the result overflows an {@code int}.
+ *
+ * @param x the first value
+ * @param y the second value to subtract from the first
+ * @return the result
+ * @throws ArithmeticException if the result overflows an int
+ * @since 1.8
+ */
+ public static int subtractExact(int x, int y) {
+ int r = x - y;
+ // HD 2-12 Overflow iff the arguments have different signs and
+ // the sign of the result is different than the sign of x
+ if (((x ^ y) & (x ^ r)) < 0) {
+ throw new ArithmeticException("integer overflow");
+ }
+ return r;
+ }
+
+ /**
+ * Returns the difference of the arguments,
+ * throwing an exception if the result overflows a {@code long}.
+ *
+ * @param x the first value
+ * @param y the second value to subtract from the first
+ * @return the result
+ * @throws ArithmeticException if the result overflows a long
+ * @since 1.8
+ */
+ public static long subtractExact(long x, long y) {
+ long r = x - y;
+ // HD 2-12 Overflow iff the arguments have different signs and
+ // the sign of the result is different than the sign of x
+ if (((x ^ y) & (x ^ r)) < 0) {
+ throw new ArithmeticException("long overflow");
+ }
+ return r;
+ }
+
+ /**
+ * Returns the product of the arguments,
+ * throwing an exception if the result overflows an {@code int}.
+ *
+ * @param x the first value
+ * @param y the second value
+ * @return the result
+ * @throws ArithmeticException if the result overflows an int
+ * @since 1.8
+ */
+ public static int multiplyExact(int x, int y) {
+ long r = (long)x * (long)y;
+ if ((int)r != r) {
+ throw new ArithmeticException("integer overflow");
+ }
+ return (int)r;
+ }
+
+ /**
+ * Returns the product of the arguments,
+ * throwing an exception if the result overflows a {@code long}.
+ *
+ * @param x the first value
+ * @param y the second value
+ * @return the result
+ * @throws ArithmeticException if the result overflows a long
+ * @since 1.8
+ */
+ public static long multiplyExact(long x, long y) {
+ long r = x * y;
+ long ax = java.lang.Math.abs(x);
+ long ay = java.lang.Math.abs(y);
+ if (((ax | ay) >>> 31 != 0)) {
+ // Some bits greater than 2^31 that might cause overflow
+ // Check the result using the divide operator
+ // and check for the special case of Long.MIN_VALUE * -1
+ if (((y != 0) && (r / y != x)) ||
+ (x == Long.MIN_VALUE && y == -1)) {
+ throw new ArithmeticException("long overflow");
+ }
+ }
+ return r;
+ }
+}
diff --git a/common/src/main/scala/scalan/AnyVals.scala b/common/src/main/scala/scalan/AnyVals.scala
index 6da152e524..e1d5235d42 100644
--- a/common/src/main/scala/scalan/AnyVals.scala
+++ b/common/src/main/scala/scalan/AnyVals.scala
@@ -45,5 +45,14 @@ class AVHashMap[K,V](val hashMap: HashMap[K,V]) extends AnyVal {
object AVHashMap {
/** Helper method to create a new map with the given capacity. */
def apply[K,V](initialCapacity: Int) = new AVHashMap[K,V](new HashMap[K,V](initialCapacity))
+
+ /** Helper method to create a new map from the sequence of K, V pairs. */
+ def fromSeq[K,V](items: Seq[(K, V)]): AVHashMap[K,V] = {
+ val map = new AVHashMap[K,V](new HashMap[K,V](items.length))
+ items.foreach { case (k, v) =>
+ map.put(k, v)
+ }
+ map
+ }
}
diff --git a/common/src/main/scala/scalan/ExactIntegral.scala b/common/src/main/scala/scalan/ExactIntegral.scala
index 74d85462ab..57e6e55dd3 100644
--- a/common/src/main/scala/scalan/ExactIntegral.scala
+++ b/common/src/main/scala/scalan/ExactIntegral.scala
@@ -2,56 +2,61 @@ package scalan
import scalan.util.Extensions._
-import scala.math.Numeric.{ByteIsIntegral, LongIsIntegral, ShortIsIntegral, IntIsIntegral}
-
-/** Integral operations with overflow checks.
- * Raise exception when overflow is detected.
- * Each instance of this typeclass overrides three methods `plus`, `minus`, `times`.
- * All other methods are implemented by delegating to the corresponding Integral instance from
- * standard Scala library.
+/** Type-class which defines the operations on Integral types (Byte, Short, Int, Long, BigInt)
+ * with overflow checks.
+ *
+ * An exception is raised when an overflow is detected.
+ * Each concrete instance of this type-class overrides three methods `plus`, `minus`,
+ * `times`.
+ *
+ * By default all the methods are implemented by delegating to the corresponding Integral
+ * instance from the standard Scala library.
+ *
* This trait is used in core IR to avoid implicitly using standard scala implementations.
*/
-trait ExactIntegral[T] extends Integral[T] {
- val n: Integral[T]
- override def quot(x: T, y: T): T = n.quot(x, y)
- override def rem(x: T, y: T): T = n.rem(x, y)
- override def negate(x: T): T = n.negate(x)
- override def fromInt(x: Int): T = n.fromInt(x)
- override def toInt(x: T): Int = n.toInt(x)
- override def toLong(x: T): Long = n.toLong(x)
- override def toFloat(x: T): Float = n.toFloat(x)
- override def toDouble(x: T): Double = n.toDouble(x)
- override def compare(x: T, y: T): Int = n.compare(x, y)
+trait ExactIntegral[T] extends ExactNumeric[T] {
+ protected val n: scala.math.Integral[T]
+
+ /** Integer division operation `x / y`. */
+ def quot(x: T, y: T): T = n.quot(x, y)
+
+ /** Operation which returns remainder from dividing x by y.
+ * The exact rules are defined in the concrete instance of the type T.
+ * A default implementation delegates to Integral[T].rem method for the corresponding
+ * type T.
+ * The default implementation can be overridden for any concrete type T.
+ */
+ def divisionRemainder(x: T, y: T): T = n.rem(x, y)
}
-/** ExactNumeric instances for all types. */
+/** ExactIntegral instances for all types. */
object ExactIntegral {
implicit object ByteIsExactIntegral extends ExactIntegral[Byte] {
- val n = ByteIsIntegral
+ val n = scala.math.Numeric.ByteIsIntegral
override def plus(x: Byte, y: Byte): Byte = x.addExact(y)
override def minus(x: Byte, y: Byte): Byte = x.subtractExact(y)
override def times(x: Byte, y: Byte): Byte = x.multiplyExact(y)
}
implicit object ShortIsExactIntegral extends ExactIntegral[Short] {
- val n = ShortIsIntegral
+ val n = scala.math.Numeric.ShortIsIntegral
override def plus(x: Short, y: Short): Short = x.addExact(y)
override def minus(x: Short, y: Short): Short = x.subtractExact(y)
override def times(x: Short, y: Short): Short = x.multiplyExact(y)
}
implicit object IntIsExactIntegral extends ExactIntegral[Int] {
- val n = IntIsIntegral
- override def plus(x: Int, y: Int): Int = Math.addExact(x, y)
- override def minus(x: Int, y: Int): Int = Math.subtractExact(x, y)
- override def times(x: Int, y: Int): Int = Math.multiplyExact(x, y)
+ val n = scala.math.Numeric.IntIsIntegral
+ override def plus(x: Int, y: Int): Int = java7.compat.Math.addExact(x, y)
+ override def minus(x: Int, y: Int): Int = java7.compat.Math.subtractExact(x, y)
+ override def times(x: Int, y: Int): Int = java7.compat.Math.multiplyExact(x, y)
}
implicit object LongIsExactIntegral extends ExactIntegral[Long] {
- val n = LongIsIntegral
- override def plus(x: Long, y: Long): Long = Math.addExact(x, y)
- override def minus(x: Long, y: Long): Long = Math.subtractExact(x, y)
- override def times(x: Long, y: Long): Long = Math.multiplyExact(x, y)
+ val n = scala.math.Numeric.LongIsIntegral
+ override def plus(x: Long, y: Long): Long = java7.compat.Math.addExact(x, y)
+ override def minus(x: Long, y: Long): Long = java7.compat.Math.subtractExact(x, y)
+ override def times(x: Long, y: Long): Long = java7.compat.Math.multiplyExact(x, y)
}
}
diff --git a/common/src/main/scala/scalan/ExactNumeric.scala b/common/src/main/scala/scalan/ExactNumeric.scala
index 734938ce34..5967785de5 100644
--- a/common/src/main/scala/scalan/ExactNumeric.scala
+++ b/common/src/main/scala/scalan/ExactNumeric.scala
@@ -1,8 +1,6 @@
package scalan
-import scalan.util.Extensions._
-
-import scala.math.Numeric.{ByteIsIntegral, LongIsIntegral, ShortIsIntegral, IntIsIntegral}
+import scalan.ExactIntegral._
/** Numeric operations with overflow checks.
* Raise exception when overflow is detected.
@@ -12,50 +10,37 @@ import scala.math.Numeric.{ByteIsIntegral, LongIsIntegral, ShortIsIntegral, IntI
* This trait is used in core IR to avoid implicitly using standard scala implementations
*/
trait ExactNumeric[T] {
- val n: Numeric[T]
+ protected val n: Numeric[T]
+
+ /** Addition operation `x + y`. */
def plus(x: T, y: T): T
+
+ /** Subtraction operation `x - y`. */
def minus(x: T, y: T): T
+
+ /** Multiplication operation `x * y`. */
def times(x: T, y: T): T
+
+ /** Returns negative value `-x`. */
def negate(x: T): T = n.negate(x)
+
+ /** Returns a value of type T, which corresponds to the given integer value `x`. */
def fromInt(x: Int): T = n.fromInt(x)
+
def toInt(x: T): Int = n.toInt(x)
def toLong(x: T): Long = n.toLong(x)
- def toFloat(x: T): Float = n.toFloat(x)
- def toDouble(x: T): Double = n.toDouble(x)
- def compare(x: T, y: T): Int = n.compare(x, y)
- def zero: T = n.zero
- def one: T = n.one
- def abs(x: T): T = n.abs(x)
+
+ /** A value of type T which corresponds to integer 0. */
+ lazy val zero: T = fromInt(0)
+
+ /** A value of type T which corresponds to integer 1. */
+ lazy val one: T = fromInt(1)
}
-/** ExactNumeric instances for all types. */
object ExactNumeric {
-
- implicit object ByteIsExactNumeric extends ExactNumeric[Byte] {
- val n = ByteIsIntegral
- override def plus(x: Byte, y: Byte): Byte = x.addExact(y)
- override def minus(x: Byte, y: Byte): Byte = x.subtractExact(y)
- override def times(x: Byte, y: Byte): Byte = x.multiplyExact(y)
- }
-
- implicit object ShortIsExactNumeric extends ExactNumeric[Short] {
- val n = ShortIsIntegral
- override def plus(x: Short, y: Short): Short = x.addExact(y)
- override def minus(x: Short, y: Short): Short = x.subtractExact(y)
- override def times(x: Short, y: Short): Short = x.multiplyExact(y)
- }
-
- implicit object IntIsExactNumeric extends ExactNumeric[Int] {
- val n = IntIsIntegral
- override def plus(x: Int, y: Int): Int = Math.addExact(x, y)
- override def minus(x: Int, y: Int): Int = Math.subtractExact(x, y)
- override def times(x: Int, y: Int): Int = Math.multiplyExact(x, y)
- }
-
- implicit object LongIsExactNumeric extends ExactNumeric[Long] {
- val n = LongIsIntegral
- override def plus(x: Long, y: Long): Long = Math.addExact(x, y)
- override def minus(x: Long, y: Long): Long = Math.subtractExact(x, y)
- override def times(x: Long, y: Long): Long = Math.multiplyExact(x, y)
- }
+ /** ExactNumeric instances are the same as ExactIntegral.
+ * This values are implicitly used wherever ExactNumeric is needed (see usages).
+ */
+ implicit def IntIsExactNumeric: ExactNumeric[Int] = IntIsExactIntegral
+ implicit def LongIsExactNumeric: ExactNumeric[Long] = LongIsExactIntegral
}
diff --git a/core/src/main/scala/scalan/DefRewriting.scala b/core/src/main/scala/scalan/DefRewriting.scala
index 3b88b1638e..f915239d46 100644
--- a/core/src/main/scala/scalan/DefRewriting.scala
+++ b/core/src/main/scala/scalan/DefRewriting.scala
@@ -60,10 +60,6 @@ trait DefRewriting { scalan: Scalan =>
case NumericToInt(_) if x.elem == IntElement => x
// (x: Long).toLong => x
case NumericToLong(_) if x.elem == LongElement => x
- // (x: Float).toFloat => x
- case NumericToFloat(_) if x.elem == FloatElement => x
- // (x: Double).toDouble => x
- case NumericToDouble(_) if x.elem == DoubleElement => x
case _ if op == Not => x.node match {
// Rule: !(x op y) ==>
diff --git a/core/src/main/scala/scalan/compilation/GraphVizExport.scala b/core/src/main/scala/scalan/compilation/GraphVizExport.scala
index 7c3b4e20bd..099593773a 100644
--- a/core/src/main/scala/scalan/compilation/GraphVizExport.scala
+++ b/core/src/main/scala/scalan/compilation/GraphVizExport.scala
@@ -104,8 +104,6 @@ trait GraphVizExport extends Base { self: Scalan =>
case Second(pair) => s"$pair._2"
case ApplyBinOp(op, lhs, rhs) => s"$lhs ${op.opName} $rhs"
case ApplyUnOp(op, arg) => op match {
- case NumericToFloat(_) => s"$arg.toFloat"
- case NumericToDouble(_) => s"$arg.toDouble"
case NumericToInt(_) => s"$arg.toInt"
case ToString() => s"$arg.toString"
case HashCode() => s"$arg.hashCode"
diff --git a/core/src/main/scala/scalan/primitives/NumericOps.scala b/core/src/main/scala/scalan/primitives/NumericOps.scala
index deca140dc4..39c93b49ae 100644
--- a/core/src/main/scala/scalan/primitives/NumericOps.scala
+++ b/core/src/main/scala/scalan/primitives/NumericOps.scala
@@ -11,9 +11,6 @@ trait NumericOps extends Base { self: Scalan =>
def -(y: Ref[T]): Ref[T] = NumericMinus(n)(x.elem).apply(x, y)
def *(y: Ref[T]): Ref[T] = NumericTimes(n)(x.elem).apply(x, y)
def unary_- : Ref[T] = NumericNegate(n)(x.elem).apply(x)
- def abs: Ref[T] = Abs(n)(x.elem).apply(x)
- def toFloat: Ref[Float] = NumericToFloat(n).apply(x)
- def toDouble: Ref[Double] = NumericToDouble(n).apply(x)
def toInt: Ref[Int] = NumericToInt(n).apply(x)
def toLong: Ref[Long] = NumericToLong(n).apply(x)
}
@@ -58,16 +55,6 @@ trait NumericOps extends Base { self: Scalan =>
override def applySeq(x: T): T = n.negate(x)
}
- /** Descriptor of unary `ToDouble` conversion operation. */
- case class NumericToDouble[T](n: ExactNumeric[T]) extends UnOp[T,Double]("ToDouble") {
- override def applySeq(x: T): Double = n.toDouble(x)
- }
-
- /** Descriptor of unary `ToFloat` conversion operation. */
- case class NumericToFloat[T](n: ExactNumeric[T]) extends UnOp[T, Float]("ToFloat") {
- override def applySeq(x: T): Float = n.toFloat(x)
- }
-
/** Descriptor of unary `ToInt` conversion operation. */
case class NumericToInt[T](n: ExactNumeric[T]) extends UnOp[T,Int]("ToInt") {
override def applySeq(x: T): Int = n.toInt(x)
@@ -78,24 +65,14 @@ trait NumericOps extends Base { self: Scalan =>
override def applySeq(x: T): Long = n.toLong(x)
}
- /** Descriptor of unary `abs` operation. */
- case class Abs[T: Elem](n: ExactNumeric[T]) extends UnOp[T, T]("Abs") {
- override def applySeq(x: T): T = n.abs(x)
- }
-
/** Descriptor of binary `/` operation (integral division). */
case class IntegralDivide[T](i: ExactIntegral[T])(implicit elem: Elem[T]) extends DivOp[T]("/", i) {
override def applySeq(x: T, y: T): T = i.quot(x, y)
}
- /** Descriptor of binary `%` operation (reminder of integral division). */
-case class IntegralMod[T](i: ExactIntegral[T])(implicit elem: Elem[T]) extends DivOp[T]("%", i) {
- /** Note, this is implemented using `ExactIntegral.rem` method which delegates to
- * `scala.math.Integral.rem`. The later also implements `%` operator in Scala for
- * numeric types.
- * @see sigmastate.eval.NumericOps.BigIntIsIntegral
- */
- override def applySeq(x: T, y: T): T = i.rem(x, y)
+ /** Descriptor of binary `%` operation (remainder of integral division). */
+ case class IntegralMod[T](i: ExactIntegral[T])(implicit elem: Elem[T]) extends DivOp[T]("%", i) {
+ override def applySeq(x: T, y: T): T = i.divisionRemainder(x, y)
}
/** Compares the given value with zero of the given ExactNumeric instance. */
diff --git a/docs/spec/generated/ergotree_serialization1.tex b/docs/spec/generated/ergotree_serialization1.tex
index 3db72a9d30..ae73a7eb17 100644
--- a/docs/spec/generated/ergotree_serialization1.tex
+++ b/docs/spec/generated/ergotree_serialization1.tex
@@ -375,7 +375,7 @@ \subsubsection{\lst{Division} operation (OpCode 157)}
\subsubsection{\lst{Modulo} operation (OpCode 158)}
\label{sec:serialization:operation:Modulo}
-Reminder from division of the first operand by the second operand. See~\hyperref[sec:appendix:primops:Modulo]{\lst{\%}}
+Remainder from division of the first operand by the second operand. See~\hyperref[sec:appendix:primops:Modulo]{\lst{\%}}
\noindent
\(\begin{tabularx}{\textwidth}{| l | l | l | X |}
diff --git a/docs/spec/generated/predeffunc_rows.tex b/docs/spec/generated/predeffunc_rows.tex
index 401d345644..65ae8bf6d4 100644
--- a/docs/spec/generated/predeffunc_rows.tex
+++ b/docs/spec/generated/predeffunc_rows.tex
@@ -44,7 +44,7 @@
\hline
157 & \hyperref[sec:serialization:operation:Division]{\lst{Division}} & \parbox{4cm}{\lst{/:} \\ \lst{(T, T)} \\ \lst{ => T}} & Integer division of the first operand by the second operand. \\
\hline
- 158 & \hyperref[sec:serialization:operation:Modulo]{\lst{Modulo}} & \parbox{4cm}{\lst{\%:} \\ \lst{(T, T)} \\ \lst{ => T}} & Reminder from division of the first operand by the second operand. \\
+ 158 & \hyperref[sec:serialization:operation:Modulo]{\lst{Modulo}} & \parbox{4cm}{\lst{\%:} \\ \lst{(T, T)} \\ \lst{ => T}} & Remainder from division of the first operand by the second operand. \\
\hline
161 & \hyperref[sec:serialization:operation:Min]{\lst{Min}} & \parbox{4cm}{\lst{min:} \\ \lst{(T, T)} \\ \lst{ => T}} & Minimum value of two operands. \\
\hline
diff --git a/docs/spec/generated/predeffunc_sections.tex b/docs/spec/generated/predeffunc_sections.tex
index 8d6ac2f782..ee62c477c3 100644
--- a/docs/spec/generated/predeffunc_sections.tex
+++ b/docs/spec/generated/predeffunc_sections.tex
@@ -540,7 +540,7 @@ \subsubsection{\lst{\%} method (Code 158)}
\noindent
\begin{tabularx}{\textwidth}{| l | X |}
\hline
- \bf{Description} & Reminder from division of the first operand by the second operand. \\
+ \bf{Description} & Remainder from division of the first operand by the second operand. \\
\hline
\bf{Parameters} &
diff --git a/library-impl/src/main/scala/special/collection/MonoidInstances.scala b/library-impl/src/main/scala/special/collection/MonoidInstances.scala
index 1183b9378e..d33c9bafae 100644
--- a/library-impl/src/main/scala/special/collection/MonoidInstances.scala
+++ b/library-impl/src/main/scala/special/collection/MonoidInstances.scala
@@ -21,8 +21,8 @@ class MonoidBuilderInst extends MonoidBuilder {
}
class IntPlusMonoid(val zero: Int) extends Monoid[Int] {
- def plus(x: Int, y: Int): Int = Math.addExact(x, y)
- def power(x: Int, n: Int): Int = Math.multiplyExact(x, n)
+ def plus(x: Int, y: Int): Int = java7.compat.Math.addExact(x, y)
+ def power(x: Int, n: Int): Int = java7.compat.Math.multiplyExact(x, n)
}
@Internal
@@ -42,8 +42,8 @@ class IntMinMonoid(val zero: Int) extends Monoid[Int] {
}
class LongPlusMonoid(val zero: Long) extends Monoid[Long] {
- def plus(x: Long, y: Long): Long = Math.addExact(x, y)
- def power(x: Long, n: Int): Long = Math.multiplyExact(x, n.toLong)
+ def plus(x: Long, y: Long): Long = java7.compat.Math.addExact(x, y)
+ def power(x: Long, n: Int): Long = java7.compat.Math.multiplyExact(x, n.toLong)
}
@Internal
diff --git a/library/src/main/scala/scalan/Library.scala b/library/src/main/scala/scalan/Library.scala
index 8874c50d93..4058baff9d 100644
--- a/library/src/main/scala/scalan/Library.scala
+++ b/library/src/main/scala/scalan/Library.scala
@@ -4,7 +4,7 @@ import scala.language.implicitConversions
import special.collection._
import special.wrappers.{WrappersSpecModule, WrappersModule}
import scalan.util.{MemoizedFunc}
-import scalan.ExactNumeric._
+import scalan.ExactIntegral._
trait Library extends Scalan
with WrappersModule
diff --git a/scalanizer/src/main/resources/scalac-plugin.xml b/scalanizer/src/main/resources/scalac-plugin.xml
deleted file mode 100644
index 7dd58fc0bb..0000000000
--- a/scalanizer/src/main/resources/scalac-plugin.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
- scalanizer
- special.sigma.scalanizer.SigmaPlugin
-
\ No newline at end of file
diff --git a/scalanizer/src/main/scala/special/sigma/scalanizer/SigmaPlugin.scala b/scalanizer/src/main/scala/special/sigma/scalanizer/SigmaPlugin.scala
deleted file mode 100644
index fd5c45ee0c..0000000000
--- a/scalanizer/src/main/scala/special/sigma/scalanizer/SigmaPlugin.scala
+++ /dev/null
@@ -1,19 +0,0 @@
-package special.sigma.scalanizer
-
-import scala.tools.nsc.Global
-import scalan.meta.{ConfMap, TargetModuleConf, SourceModuleConf}
-import scalan.meta.scalanizer.ScalanizerConfig
-import scalan.plugin.{ScalanizerPluginConfig, ScalanizerPlugin}
-import special.sigma.config.SigmaLibraryConfig
-
-class SigmaPlugin(g: Global) extends ScalanizerPlugin(g) { plugin =>
- override def createScalanizerConfig(): ScalanizerConfig = new SigmaScalanizerConfig
-}
-
-class SigmaScalanizerConfig extends ScalanizerPluginConfig {
- val sigma = new SigmaLibraryConfig()
-
- /** Modules that contain units to be virtualized by scalan-meta. */
- override val sourceModules: ConfMap[SourceModuleConf] = ConfMap(sigma.sourceModules: _*)
- override val targetModules: ConfMap[TargetModuleConf] = ConfMap(sigma.targetModules: _*)
-}
diff --git a/sigma-conf/src/main/scala/special/sigma/config/SigmaLibraryConfig.scala b/sigma-conf/src/main/scala/special/sigma/config/SigmaLibraryConfig.scala
deleted file mode 100644
index 2e93a3fb91..0000000000
--- a/sigma-conf/src/main/scala/special/sigma/config/SigmaLibraryConfig.scala
+++ /dev/null
@@ -1,34 +0,0 @@
-package special.sigma.config
-
-import special.library.config.SpecialLibraryConfig
-import scalan.meta.ScalanAst.WrapperConf
-import scalan.meta.{LibraryConfig, ConfMap, TargetModuleConf, SourceModuleConf}
-
-class SigmaLibraryConfig extends LibraryConfig {
- def name = "sigma"
- def baseDir = ""
- val specialLibrary = new SpecialLibraryConfig
-
- def wrapperConfigs: Map[String, WrapperConf] = List[WrapperConf](
-// example wrapper declaration
-// WrapperConf(baseDir, packageName = "special.sigma", name = "SigmaPredef",
-// annotations = List(classOf[WithMethodCallRecognizers]).map(_.getSimpleName))
- ).map(w => (w.name, w)).toMap
-
- val ApiModule: SourceModuleConf = new SourceModuleConf(baseDir, "sigma-api")
- .moduleDependencies(specialLibrary.ApiModule)
- .addUnit("special/sigma/SigmaDsl.scala")
- .addUnit("special/sigma/CostedObjects.scala")
-
- val ImplModule = new SourceModuleConf(baseDir, "sigma-impl")
- .moduleDependencies(specialLibrary.ApiModule, specialLibrary.ImplModule)
- .dependsOn(ApiModule)
-
- val TargetModule = new TargetModuleConf(baseDir, "sigma-library",
- sourceModules = ConfMap()
- .add(ApiModule)
- .add(ImplModule))
-
- def sourceModules = List(ApiModule, ImplModule)
- def targetModules = List(TargetModule)
-}
diff --git a/sigma-impl/src/main/scala/sigma/types/View.scala b/sigma-impl/src/main/scala/sigma/types/View.scala
deleted file mode 100644
index fafb84e277..0000000000
--- a/sigma-impl/src/main/scala/sigma/types/View.scala
+++ /dev/null
@@ -1,12 +0,0 @@
-package sigma.types
-
-import spire.util.Opt
-
-object View {
- def mkPrimView[Val](value: Val): Opt[PrimView[Val]] = (value match {
- case x: scala.Boolean => Opt(CBoolean(x))
- case x: scala.Byte => Opt(CByte(x))
- case x: scala.Int => Opt(CInt(x))
- case _ => Opt.empty[Val]
- }).asInstanceOf[Opt[PrimView[Val]]]
-}
diff --git a/sigmastate/src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala b/sigmastate/src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala
index 19211b6aa0..894dcd3bbf 100644
--- a/sigmastate/src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala
+++ b/sigmastate/src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala
@@ -8,7 +8,7 @@ import scorex.util.{bytesToId, ModifierId}
import sigmastate.Values._
import sigmastate._
import sigmastate.SType.AnyOps
-import sigmastate.serialization.SigmaSerializer
+import sigmastate.serialization.{SigmaSerializer, ValueSerializer}
import sigmastate.utils.{SigmaByteReader, SigmaByteWriter}
import special.collection.Coll
import sigmastate.eval._
@@ -187,8 +187,8 @@ object ErgoBoxCandidate {
val tree = DefaultSerializer.deserializeErgoTree(r, SigmaSerializer.MaxPropositionSize) // READ
val creationHeight = r.getUInt().toInt // READ
val nTokens = r.getUByte() // READ
- val tokenIds = new Array[Array[Byte]](nTokens)
- val tokenAmounts = new Array[Long](nTokens)
+ val tokenIds = ValueSerializer.newArray[Array[Byte]](nTokens)
+ val tokenAmounts = ValueSerializer.newArray[Long](nTokens)
if (digestsInTx != null) {
val nDigests = digestsInTx.length
cfor(0)(_ < nTokens, _ + 1) { i =>
diff --git a/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeContext.scala b/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeContext.scala
index 22edda7ab0..33c1ea0a4a 100644
--- a/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeContext.scala
+++ b/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeContext.scala
@@ -8,7 +8,8 @@ import sigmastate.Values._
import sigmastate._
import sigmastate.eval.Extensions._
import sigmastate.eval._
-import sigmastate.interpreter.{ContextExtension, InterpreterContext}
+import sigmastate.interpreter.ErgoTreeEvaluator.DataEnv
+import sigmastate.interpreter.{ContextExtension, InterpreterContext, ErgoTreeEvaluator}
import sigmastate.serialization.OpCodes
import sigmastate.serialization.OpCodes.OpCode
import special.collection.Coll
@@ -129,7 +130,7 @@ class ErgoLikeContext(val lastBlockUtxoRoot: AvlTreeData,
import Evaluation._
def contextVars(m: Map[Byte, AnyValue]): Coll[AnyValue] = {
- val maxKey = if (m.keys.isEmpty) 0 else m.keys.max
+ val maxKey = if (m.keys.isEmpty) 0 else m.keys.max // TODO optimize: max takes 90% of this method
val res = new Array[AnyValue](maxKey + 1)
for ((id, v) <- m) {
res(id) = v
@@ -228,38 +229,70 @@ object ErgoLikeContext {
/** When interpreted evaluates to a ByteArrayConstant built from Context.minerPubkey */
case object MinerPubkey extends NotReadyValueByteArray with ValueCompanion {
override def opCode: OpCode = OpCodes.MinerPubkeyCode
+ /** Cost of calling Context.minerPubkey Scala method. */
+ override val costKind = FixedCost(20)
override val opType = SFunc(SContext, SCollection.SByteArray)
override def companion = this
+ protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = {
+ addCost(this.costKind)
+ E.context.minerPubKey
+ }
}
/** When interpreted evaluates to a IntConstant built from Context.currentHeight */
case object Height extends NotReadyValueInt with ValueCompanion {
override def companion = this
override def opCode: OpCode = OpCodes.HeightCode
+ /** Cost of: 1) Calling Context.HEIGHT Scala method. */
+ override val costKind = FixedCost(26)
override val opType = SFunc(SContext, SInt)
+ protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = {
+ addCost(this.costKind)
+ E.context.HEIGHT
+ }
}
/** When interpreted evaluates to a collection of BoxConstant built from Context.boxesToSpend */
case object Inputs extends LazyCollection[SBox.type] with ValueCompanion {
override def companion = this
override def opCode: OpCode = OpCodes.InputsCode
+ /** Cost of: 1) Calling Context.INPUTS Scala method. */
+ override val costKind = FixedCost(10)
override def tpe = SCollection.SBoxArray
override val opType = SFunc(SContext, tpe)
+ protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = {
+ addCost(this.costKind)
+ E.context.INPUTS
+ }
}
/** When interpreted evaluates to a collection of BoxConstant built from Context.spendingTransaction.outputs */
case object Outputs extends LazyCollection[SBox.type] with ValueCompanion {
override def companion = this
override def opCode: OpCode = OpCodes.OutputsCode
+ /** Cost of: 1) Calling Context.OUTPUTS Scala method. */
+ override val costKind = FixedCost(10)
override def tpe = SCollection.SBoxArray
override val opType = SFunc(SContext, tpe)
+ protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = {
+ addCost(this.costKind)
+ E.context.OUTPUTS
+ }
}
/** When interpreted evaluates to a AvlTreeConstant built from Context.lastBlockUtxoRoot */
case object LastBlockUtxoRootHash extends NotReadyValueAvlTree with ValueCompanion {
override def companion = this
override def opCode: OpCode = OpCodes.LastBlockUtxoRootHashCode
+
+ /** Cost of: 1) Calling Context.LastBlockUtxoRootHash Scala method. */
+ override val costKind = FixedCost(15)
+
override val opType = SFunc(SContext, tpe)
+ protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = {
+ addCost(this.costKind)
+ E.context.LastBlockUtxoRootHash
+ }
}
@@ -267,7 +300,13 @@ case object LastBlockUtxoRootHash extends NotReadyValueAvlTree with ValueCompani
case object Self extends NotReadyValueBox with ValueCompanion {
override def companion = this
override def opCode: OpCode = OpCodes.SelfCode
+ /** Cost of: 1) Calling Context.SELF Scala method. */
+ override val costKind = FixedCost(10)
override val opType = SFunc(SContext, SBox)
+ protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = {
+ addCost(this.costKind)
+ E.context.SELF
+ }
}
/** When interpreted evaluates to the singleton instance of [[special.sigma.Context]].
@@ -276,8 +315,16 @@ case object Self extends NotReadyValueBox with ValueCompanion {
case object Context extends NotReadyValue[SContext.type] with ValueCompanion {
override def companion = this
override def opCode: OpCode = OpCodes.ContextCode
+
+ /** Cost of: 1) accessing global Context instance. */
+ override val costKind = FixedCost(1)
+
override def tpe: SContext.type = SContext
override val opType: SFunc = SFunc(SUnit, SContext)
+ protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = {
+ addCost(this.costKind)
+ E.context
+ }
}
/** When interpreted evaluates to the singleton instance of [[special.sigma.SigmaDslBuilder]].
@@ -286,6 +333,12 @@ case object Context extends NotReadyValue[SContext.type] with ValueCompanion {
case object Global extends NotReadyValue[SGlobal.type] with ValueCompanion {
override def companion = this
override def opCode: OpCode = OpCodes.GlobalCode
+ /** Cost of: 1) accessing Global instance. */
+ override val costKind = FixedCost(5)
override def tpe: SGlobal.type = SGlobal
override val opType: SFunc = SFunc(SUnit, SGlobal)
+ protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = {
+ addCost(this.costKind)
+ CostingSigmaDslBuilder
+ }
}
diff --git a/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeInterpreter.scala b/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeInterpreter.scala
index 57fc66853e..7e54647a06 100644
--- a/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeInterpreter.scala
+++ b/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeInterpreter.scala
@@ -40,5 +40,5 @@ class ErgoLikeInterpreter(implicit val IR: IRContext) extends Interpreter {
}.orElse(d.default)
case _ => super.substDeserialize(context, updateContext, node)
}
-
+
}
\ No newline at end of file
diff --git a/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeTransaction.scala b/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeTransaction.scala
index c747ff9684..b673a3bb86 100644
--- a/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeTransaction.scala
+++ b/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeTransaction.scala
@@ -8,7 +8,7 @@ import sigmastate.SType._
import sigmastate.eval.Extensions._
import sigmastate.eval._
import sigmastate.interpreter.ProverResult
-import sigmastate.serialization.SigmaSerializer
+import sigmastate.serialization.{SigmaSerializer, ValueSerializer}
import sigmastate.utils.{SigmaByteReader, SigmaByteWriter}
import special.collection.ExtensionMethods._
import spire.syntax.all.cfor
@@ -147,28 +147,28 @@ object ErgoLikeTransactionSerializer extends SigmaSerializer[ErgoLikeTransaction
override def parse(r: SigmaByteReader): ErgoLikeTransaction = {
// parse transaction inputs
val inputsCount = r.getUShort()
- val inputs = new Array[Input](inputsCount)
+ val inputs = ValueSerializer.newArray[Input](inputsCount)
cfor(0)(_ < inputsCount, _ + 1) { i =>
inputs(i) = Input.serializer.parse(r)
}
// parse transaction data inputs
val dataInputsCount = r.getUShort()
- val dataInputs = new Array[DataInput](dataInputsCount)
+ val dataInputs = ValueSerializer.newArray[DataInput](dataInputsCount)
cfor(0)(_ < dataInputsCount, _ + 1) { i =>
dataInputs(i) = DataInput(ADKey @@ r.getBytes(ErgoBox.BoxId.size))
}
// parse distinct ids of tokens in transaction outputs
val tokensCount = r.getUInt().toInt
- val tokens = new Array[Array[Byte]](tokensCount)
+ val tokens = ValueSerializer.newArray[Array[Byte]](tokensCount)
cfor(0)(_ < tokensCount, _ + 1) { i =>
tokens(i) = r.getBytes(TokenId.size)
}
// parse outputs
val outsCount = r.getUShort()
- val outputCandidates = new Array[ErgoBoxCandidate](outsCount)
+ val outputCandidates = ValueSerializer.newArray[ErgoBoxCandidate](outsCount)
cfor(0)(_ < outsCount, _ + 1) { i =>
outputCandidates(i) = ErgoBoxCandidate.serializer.parseBodyWithIndexedDigests(tokens, r)
}
diff --git a/sigmastate/src/main/scala/org/ergoplatform/ErgoScriptPredef.scala b/sigmastate/src/main/scala/org/ergoplatform/ErgoScriptPredef.scala
index 6699cb1d91..049ceb9029 100644
--- a/sigmastate/src/main/scala/org/ergoplatform/ErgoScriptPredef.scala
+++ b/sigmastate/src/main/scala/org/ergoplatform/ErgoScriptPredef.scala
@@ -9,7 +9,7 @@ import sigmastate.eval.IRContext
import sigmastate.interpreter.CryptoConstants
import sigmastate.lang.Terms.ValueOps
import sigmastate.{SLong, _}
-import sigmastate.lang.{SigmaCompiler, TransformingSigmaBuilder}
+import sigmastate.lang.{TransformingSigmaBuilder, SigmaCompiler, CompilerSettings}
import sigmastate.serialization.ErgoTreeSerializer.DefaultSerializer
import sigmastate.utxo._
@@ -18,7 +18,8 @@ object ErgoScriptPredef {
import sigmastate.interpreter.Interpreter._
def compileWithCosting(env: ScriptEnv, code: String, networkPrefix: NetworkPrefix)(implicit IR: IRContext): Value[SType] = {
- val compiler = new SigmaCompiler(networkPrefix, TransformingSigmaBuilder)
+ val compiler = new SigmaCompiler(CompilerSettings(
+ networkPrefix, TransformingSigmaBuilder, lowerMethodCalls = true))
val interProp = compiler.typecheck(env, code)
val IR.Pair(calcF, _) = IR.doCosting(env, interProp)
IR.buildTree(calcF)
diff --git a/sigmastate/src/main/scala/sigmastate/CostKind.scala b/sigmastate/src/main/scala/sigmastate/CostKind.scala
new file mode 100644
index 0000000000..85d20c9799
--- /dev/null
+++ b/sigmastate/src/main/scala/sigmastate/CostKind.scala
@@ -0,0 +1,41 @@
+package sigmastate
+
+/** Cost descriptor of a single operation, usually associated with
+ * [[sigmastate.interpreter.OperationDesc]].
+ */
+sealed abstract class CostKind
+
+/** Descriptor of the simple fixed cost.
+ * @param cost given cost of the operation */
+case class FixedCost(cost: Int) extends CostKind
+
+/** Cost of operation over collection of the known length.
+ * See for example [[Exists]], [[MapCollection]].
+ * @param baseCost cost of operation factored out of the loop iterations
+ * @param perChunkCost cost associated with each chunk of items
+ * @param chunkSize number of items in a chunk
+ */
+case class PerItemCost(baseCost: Int, perChunkCost: Int, chunkSize: Int) extends CostKind {
+ /** Compute number of chunks necessary to cover the given number of items. */
+ def chunks(nItems: Int) = (nItems - 1) / chunkSize + 1
+
+ /** Computes the cost for the given number of items. */
+ def cost (nItems: Int): Int = {
+ val nChunks = chunks(nItems)
+ Math.addExact(baseCost, Math.multiplyExact(perChunkCost, nChunks))
+ }
+}
+
+/** Descriptor of the cost which depends on type. */
+abstract class TypeBasedCost extends CostKind {
+ /** Returns cost value depending on the given type. */
+ def costFunc(tpe: SType): Int
+}
+
+/** Cost of operation cannot be described using fixed set of parameters.
+ * In this case the operation cost is a sum of sub-operation costs.
+ * See [[EQ]], [[NEQ]]. */
+case object DynamicCost extends CostKind
+
+
+
diff --git a/sigmastate/src/main/scala/sigmastate/DataValueComparer.scala b/sigmastate/src/main/scala/sigmastate/DataValueComparer.scala
new file mode 100644
index 0000000000..959cbd2dcb
--- /dev/null
+++ b/sigmastate/src/main/scala/sigmastate/DataValueComparer.scala
@@ -0,0 +1,405 @@
+package sigmastate
+
+import scalan.{AVHashMap, Nullable, RType}
+import scalan.RType._
+import sigmastate.Values.SigmaBoolean
+import sigmastate.basics.DLogProtocol.ProveDlog
+import sigmastate.basics.ProveDHTuple
+import sigmastate.eval.SigmaDsl
+import sigmastate.interpreter.CryptoConstants.EcPointType
+import spire.sp
+import sigmastate.interpreter.{ErgoTreeEvaluator, NamedDesc, OperationCostInfo}
+import special.sigma.{AvlTree, AvlTreeRType, BigInt, BigIntRType, Box, BoxRType, GroupElement, GroupElementRType, Header, HeaderRType, PreHeader, PreHeaderRType, SigmaProp}
+import special.collection.{Coll, CollOverArray, PairOfCols}
+import spire.syntax.all.cfor
+
+/** Implementation of data equality for two arbitrary ErgoTree data types.
+ * @see [[DataValueComparer.equalDataValues]]
+ */
+object DataValueComparer {
+
+ /** NOTE: The cost of most equality operations depends on the position in `match` statement.
+ * Thus the full cost to compare x and y equals DispatchCost * OperationCost, where
+ * DispatchCost = CasePosition * CostOf_MatchType,
+ * OperationCost is the type specific cost.
+ * For this reason reordering of cases may lead to divergence between an estimated and
+ * the actual execution cost (time).
+ * The constants are part of the consensus protocol and cannot be changed without forking.
+ */
+ final val CostOf_MatchType = 1
+ final val CostKind_MatchType = FixedCost(CostOf_MatchType)
+ final val OpDesc_MatchType = NamedDesc("MatchType")
+ final val MatchType = OperationCostInfo(CostKind_MatchType, OpDesc_MatchType)
+
+ final val CostKind_EQ_Prim = FixedCost(3) // case 1
+ final val OpDesc_EQ_Prim = NamedDesc("EQ_Prim")
+ final val EQ_Prim = OperationCostInfo(CostKind_EQ_Prim, OpDesc_EQ_Prim)
+
+
+ /** Equals two Colls of non-primitive (boxed) types.
+ */
+ final val CostKind_EQ_Coll = PerItemCost(10, 2, 1) // case 2
+ final val OpDesc_EQ_Coll = NamedDesc("EQ_Coll")
+ final val EQ_Coll = OperationCostInfo(CostKind_EQ_Coll, OpDesc_EQ_Coll)
+
+ final val CostKind_EQ_Tuple = FixedCost(4) // case 3
+ final val OpDesc_EQ_Tuple = NamedDesc("EQ_Tuple")
+ final val EQ_Tuple = OperationCostInfo(CostKind_EQ_Tuple, OpDesc_EQ_Tuple)
+
+ /** NOTE: the value is set based on benchmarking of SigmaDslSpecification. */
+ final val CostKind_EQ_GroupElement = FixedCost(172) // case 4
+ final val OpDesc_EQ_GroupElement = NamedDesc("EQ_GroupElement")
+ final val EQ_GroupElement = OperationCostInfo(CostKind_EQ_GroupElement, OpDesc_EQ_GroupElement)
+
+ final val CostKind_EQ_BigInt = FixedCost(5) // case 5
+ final val OpDesc_EQ_BigInt = NamedDesc("EQ_BigInt")
+ final val EQ_BigInt = OperationCostInfo(CostKind_EQ_BigInt, OpDesc_EQ_BigInt)
+
+ final val CostKind_EQ_AvlTree = FixedCost(3 + (6 * CostOf_MatchType) / 2) // case 6
+ final val OpDesc_EQ_AvlTree = NamedDesc("EQ_AvlTree")
+ final val EQ_AvlTree = OperationCostInfo(CostKind_EQ_AvlTree, OpDesc_EQ_AvlTree)
+
+ // TODO v5.0: update value after serialization is avoided to compute ErgoBox.id
+ final val CostKind_EQ_Box = FixedCost(6) // case 7
+ final val OpDesc_EQ_Box = NamedDesc("EQ_Box")
+ final val EQ_Box = OperationCostInfo(CostKind_EQ_Box, OpDesc_EQ_Box)
+
+ /** NOTE: In the formula `(7 + 1)` the 1 corresponds to the second type match. */
+ final val CostKind_EQ_Option = FixedCost(1 + (7 + 1) * CostOf_MatchType / 2 - 1) // case 8
+ final val OpDesc_EQ_Option = NamedDesc("EQ_Option")
+ final val EQ_Option = OperationCostInfo(CostKind_EQ_Option, OpDesc_EQ_Option)
+
+ final val CostKind_EQ_PreHeader = FixedCost(4) // case 9
+ final val OpDesc_EQ_PreHeader = NamedDesc("EQ_PreHeader")
+ final val EQ_PreHeader = OperationCostInfo(CostKind_EQ_PreHeader, OpDesc_EQ_PreHeader)
+
+ final val CostKind_EQ_Header = FixedCost(6) // case 10
+ final val OpDesc_EQ_Header = NamedDesc("EQ_Header")
+ final val EQ_Header = OperationCostInfo(CostKind_EQ_Header, OpDesc_EQ_Header)
+
+ /** Equals two CollOverArray of Boolean type. */
+ final val CostKind_EQ_COA_Boolean = PerItemCost(15, 2, 128)
+ final val OpDesc_EQ_COA_Boolean = NamedDesc("EQ_COA_Boolean")
+ final val EQ_COA_Boolean = OperationCostInfo(CostKind_EQ_COA_Boolean, OpDesc_EQ_COA_Boolean)
+
+ /** Equals two CollOverArray of Byte type. */
+ final val CostKind_EQ_COA_Byte = PerItemCost(15, 2, 128)
+ final val OpDesc_EQ_COA_Byte = NamedDesc("EQ_COA_Byte")
+ final val EQ_COA_Byte = OperationCostInfo(CostKind_EQ_COA_Byte, OpDesc_EQ_COA_Byte)
+
+ /** Equals two CollOverArray of Short type. */
+ final val CostKind_EQ_COA_Short = PerItemCost(15, 2, 96)
+ final val OpDesc_EQ_COA_Short = NamedDesc("EQ_COA_Short")
+ final val EQ_COA_Short = OperationCostInfo(CostKind_EQ_COA_Short, OpDesc_EQ_COA_Short)
+
+ /** Equals two CollOverArray of Int type. */
+ final val CostKind_EQ_COA_Int = PerItemCost(15, 2, 64)
+ final val OpDesc_EQ_COA_Int = NamedDesc("EQ_COA_Int")
+ final val EQ_COA_Int = OperationCostInfo(CostKind_EQ_COA_Int, OpDesc_EQ_COA_Int)
+
+ /** Equals two CollOverArray of Long type. */
+ final val CostKind_EQ_COA_Long = PerItemCost(15, 2, 48)
+ final val OpDesc_EQ_COA_Long = NamedDesc("EQ_COA_Long")
+ final val EQ_COA_Long = OperationCostInfo(CostKind_EQ_COA_Long, OpDesc_EQ_COA_Long)
+
+ /** Equals two CollOverArray of GroupElement type. */
+ final val CostKind_EQ_COA_GroupElement = PerItemCost(15, 5, 1)
+ final val OpDesc_EQ_COA_GroupElement = NamedDesc("EQ_COA_GroupElement")
+ final val EQ_COA_GroupElement = OperationCostInfo(CostKind_EQ_COA_GroupElement, OpDesc_EQ_COA_GroupElement)
+
+ /** Equals two CollOverArray of BigInt type. */
+ final val CostKind_EQ_COA_BigInt = PerItemCost(15, 7, 5)
+ final val OpDesc_EQ_COA_BigInt = NamedDesc("EQ_COA_BigInt")
+ final val EQ_COA_BigInt = OperationCostInfo(CostKind_EQ_COA_BigInt, OpDesc_EQ_COA_BigInt)
+
+ /** Equals two CollOverArray of AvlTree type. */
+ final val CostKind_EQ_COA_AvlTree = PerItemCost(15, 5, 2)
+ final val OpDesc_EQ_COA_AvlTree = NamedDesc("EQ_COA_AvlTree")
+ final val EQ_COA_AvlTree = OperationCostInfo(CostKind_EQ_COA_AvlTree, OpDesc_EQ_COA_AvlTree)
+
+ // TODO v5.0: update value after serialization is avoided to compute ErgoBox.id
+ /** Equals two CollOverArray of Box type. */
+ final val CostKind_EQ_COA_Box = PerItemCost(15, 5, 1)
+ final val OpDesc_EQ_COA_Box = NamedDesc("EQ_COA_Box")
+ final val EQ_COA_Box = OperationCostInfo(CostKind_EQ_COA_Box, OpDesc_EQ_COA_Box)
+
+ /** Equals two CollOverArray of PreHeader type. */
+ final val CostKind_EQ_COA_PreHeader = PerItemCost(15, 3, 1)
+ final val OpDesc_EQ_COA_PreHeader = NamedDesc("EQ_COA_PreHeader")
+ final val EQ_COA_PreHeader = OperationCostInfo(CostKind_EQ_COA_PreHeader, OpDesc_EQ_COA_PreHeader)
+
+ /** Equals two CollOverArray of Header type. */
+ final val CostKind_EQ_COA_Header = PerItemCost(15, 5, 1)
+ final val OpDesc_EQ_COA_Header = NamedDesc("EQ_COA_Header")
+ final val EQ_COA_Header = OperationCostInfo(CostKind_EQ_COA_Header, OpDesc_EQ_COA_Header)
+
+ val descriptors: AVHashMap[RType[_], (OperationCostInfo[FixedCost], OperationCostInfo[PerItemCost])] =
+ AVHashMap.fromSeq(Array[(RType[_], (OperationCostInfo[FixedCost], OperationCostInfo[PerItemCost]))](
+ (BigIntRType, (EQ_BigInt, EQ_COA_BigInt)),
+ (GroupElementRType, (EQ_GroupElement, EQ_COA_GroupElement)),
+ (AvlTreeRType, (EQ_AvlTree, EQ_COA_AvlTree)),
+ (BoxRType, (EQ_Box, EQ_COA_Box)),
+ (PreHeaderRType, (EQ_PreHeader, EQ_COA_PreHeader)),
+ (HeaderRType, (EQ_Header, EQ_COA_Header))
+ ))
+
+ type COA[A] = CollOverArray[A]
+ type POC[A,B] = PairOfCols[A, B]
+
+ /** This method is specialized for numeric types and thus Scala generates four
+ * specialized methods (one for each type) which implement unboxed comparison or arrays
+ * in a most efficient way. This efficient implementation is reflected in the cost
+ * parameters, which are part of the protocol. Thus any alternative protocol
+ * implementation should implement comparison is the same way.
+ */
+ private def equalCOA_Prim[@sp(Boolean, Byte, Short, Int, Long) A]
+ (c1: COA[A], c2: COA[A], costInfo: OperationCostInfo[PerItemCost])
+ (implicit E: ErgoTreeEvaluator): Boolean = {
+ var okEqual = true
+ E.addSeqCost(costInfo.costKind, costInfo.opDesc) { () =>
+ // TODO v5.0: this loop is bounded when MaxCollSize limit is enforced
+ val len = c1.length
+ var i = 0
+ val a1 = c1.toArray
+ val a2 = c2.toArray
+ while (i < len && okEqual) {
+ okEqual = a1(i) == a2(i)
+ i += 1
+ }
+ i // return the number of actually compared items
+ }
+ okEqual
+ }
+
+ /** Compare two collections for equality. Used when the element type A is NOT known
+ * statically. When the type A is scalar, each collection item is boxed before
+ * comparison, which have significant performace overhead.
+ * For this reason, this method is used as a fallback case.
+ */
+ def equalColls[A](c1: Coll[A], c2: Coll[A])(implicit E: ErgoTreeEvaluator): Boolean = {
+ var okEqual = true
+ E.addSeqCost(CostKind_EQ_Coll, OpDesc_EQ_Coll) { () =>
+ // TODO v5.0: this loop is bounded when MaxCollSize limit is enforced
+ val len = c1.length
+ var i = 0
+ while(i < len && okEqual) {
+ okEqual = equalDataValues(c1(i), c2(i))
+ i += 1
+ }
+ i
+ }
+ okEqual
+ }
+
+ /** Compares two collections by dispatching to the most efficient implementation
+ * depending on the actual type A.
+ * */
+ def equalColls_Dispatch[A](coll1: Coll[A], coll2: Coll[A])(implicit E: ErgoTreeEvaluator): Boolean = {
+ coll1.tItem match {
+ case BooleanType =>
+ equalCOA_Prim(
+ coll1.asInstanceOf[COA[Boolean]],
+ coll2.asInstanceOf[COA[Boolean]], EQ_COA_Boolean)
+
+ case ByteType =>
+ equalCOA_Prim(
+ coll1.asInstanceOf[COA[Byte]],
+ coll2.asInstanceOf[COA[Byte]], EQ_COA_Byte)
+
+ case ShortType =>
+ equalCOA_Prim(
+ coll1.asInstanceOf[COA[Short]],
+ coll2.asInstanceOf[COA[Short]], EQ_COA_Short)
+
+ case IntType =>
+ equalCOA_Prim(
+ coll1.asInstanceOf[COA[Int]],
+ coll2.asInstanceOf[COA[Int]], EQ_COA_Int)
+
+ case LongType =>
+ equalCOA_Prim(
+ coll1.asInstanceOf[COA[Long]],
+ coll2.asInstanceOf[COA[Long]], EQ_COA_Long)
+
+ case t =>
+ descriptors.get(t) match {
+ case Nullable((_, info)) =>
+ equalCOA_Prim(
+ coll1.asInstanceOf[COA[A]],
+ coll2.asInstanceOf[COA[A]], info)
+ case _ =>
+ equalColls(coll1, coll2)
+ }
+ }
+ }
+
+ /** Compare equality of two sequences of SigmaBoolean trees. */
+ def equalSigmaBooleans(xs: Seq[SigmaBoolean], ys: Seq[SigmaBoolean])
+ (implicit E: ErgoTreeEvaluator): Boolean = {
+ val len = xs.length
+ if (len != ys.length) return false
+ var okEqual = true
+ cfor(0)(_ < len && okEqual, _ + 1) { i =>
+ okEqual = equalSigmaBoolean(xs(i), ys(i))
+ }
+ okEqual
+ }
+
+ /** Compare equality of two SigmaBoolean trees. */
+ def equalSigmaBoolean(l: SigmaBoolean, r: SigmaBoolean)
+ (implicit E: ErgoTreeEvaluator): Boolean = {
+ E.addCost(MatchType) // once for every node of the SigmaBoolean tree
+ l match {
+ case ProveDlog(x) => r match {
+ case ProveDlog(y) => equalECPoint(x, y)
+ case _ => false
+ }
+ case x: ProveDHTuple => r match {
+ case y: ProveDHTuple =>
+ equalECPoint(x.gv, y.gv) && equalECPoint(x.hv, y.hv) &&
+ equalECPoint(x.uv, y.uv) && equalECPoint(x.vv, y.vv)
+ case _ => false
+ }
+ case x: TrivialProp => r match {
+ case y: TrivialProp => x.condition == y.condition
+ case _ => false
+ }
+ case CAND(children) if r.isInstanceOf[CAND] =>
+ equalSigmaBooleans(children, r.asInstanceOf[CAND].children)
+ case COR(children) if r.isInstanceOf[COR] =>
+ equalSigmaBooleans(children, r.asInstanceOf[COR].children)
+ case CTHRESHOLD(k, children) if r.isInstanceOf[CTHRESHOLD] =>
+ val sb2 = r.asInstanceOf[CTHRESHOLD]
+ k == sb2.k && equalSigmaBooleans(children, sb2.children)
+ case _ =>
+ ErgoTreeEvaluator.error(
+ s"Cannot compare SigmaBoolean values $l and $r: unknown type")
+ }
+ }
+
+ /** Returns true if the given GroupElement is equal to the given object. */
+ def equalGroupElement(ge1: GroupElement, r: Any)(implicit E: ErgoTreeEvaluator): Boolean = {
+ var okEqual = true
+ E.addFixedCost(EQ_GroupElement) {
+ okEqual = ge1 == r
+ }
+ okEqual
+ }
+
+ /** Returns true if the given EcPointType is equal to the given object. */
+ def equalECPoint(p1: EcPointType, r: Any)(implicit E: ErgoTreeEvaluator): Boolean = {
+ var okEqual = true
+ E.addFixedCost(EQ_GroupElement) {
+ okEqual = p1 == r
+ }
+ okEqual
+ }
+
+ // TODO v5.0: introduce a new limit on structural depth of data values
+ /** Generic comparison of any two data values. The method dispatches on a type of the
+ * left value and then performs the specific comparison.
+ */
+ def equalDataValues(l: Any, r: Any)(implicit E: ErgoTreeEvaluator): Boolean = {
+ var okEqual: Boolean = false
+ l match {
+ case _: java.lang.Number | _: Boolean => /** case 1 (see [[EQ_Prim]]) */
+ E.addFixedCost(EQ_Prim) {
+ okEqual = l == r
+ }
+
+ case coll1: Coll[a] => /** case 2 (see [[EQ_Coll]]) */
+ E.addCost(MatchType) // for second match below
+ okEqual = r match {
+ case coll2: Coll[_] =>
+ val len = coll1.length
+ if (len != coll2.length || coll1.tItem != coll2.tItem)
+ return false
+
+ equalColls_Dispatch(coll1, coll2.asInstanceOf[Coll[a]])
+
+ case _ => false
+ }
+
+ case tup1: Tuple2[_,_] => /** case 3 (see [[EQ_Tuple]]) */
+ E.addFixedCost(EQ_Tuple) {
+ okEqual = r match {
+ case tup2: Tuple2[_,_] =>
+ equalDataValues(tup1._1, tup2._1) && equalDataValues(tup1._2, tup2._2)
+ case _ => false
+ }
+ }
+
+ case ge1: GroupElement => /** case 4 (see [[EQ_GroupElement]]) */
+ okEqual = equalGroupElement(ge1, r)
+
+ case bi: BigInt => /** case 5 (see [[EQ_BigInt]]) */
+ E.addFixedCost(EQ_BigInt) {
+ okEqual = bi == r
+ }
+
+ case sp1: SigmaProp =>
+ E.addCost(MatchType) // for second match below
+ okEqual = r match {
+ case sp2: SigmaProp =>
+ equalSigmaBoolean(
+ SigmaDsl.toSigmaBoolean(sp1),
+ SigmaDsl.toSigmaBoolean(sp2))
+ case _ => false
+ }
+
+ case bi: AvlTree => /** case 6 (see [[EQ_AvlTree]]) */
+ E.addFixedCost(EQ_AvlTree) {
+ okEqual = bi == r
+ }
+
+ case opt1: Option[_] => /** case 7 (see [[EQ_Option]]) */
+ E.addFixedCost(EQ_Option) {
+ okEqual = r match {
+ case opt2: Option[_] =>
+ if (opt1.isDefined) {
+ if (opt2.isDefined) {
+ equalDataValues(opt1.get, opt2.get)
+ } else
+ false // right is not Some
+ } else {
+ // here left in None
+ opt2.isEmpty // return if the right is also None
+ }
+ case _ =>
+ false // right is not an Option
+ }
+ }
+ case ph: PreHeader => /** case 8 (see [[EQ_PreHeader]]) */
+ E.addFixedCost(EQ_PreHeader) {
+ okEqual = ph == r
+ }
+ case h: Header => /** case 9 (see [[EQ_Header]]) */
+ E.addFixedCost(EQ_Header) {
+ okEqual = h == r
+ }
+ case box: Box => /** case 10 (see [[EQ_Box]]) */
+ E.addFixedCost(EQ_Box) {
+ okEqual = box == r
+ }
+ case s1: String =>
+ E.addCost(MatchType) // for second match below
+ okEqual = r match {
+ case s2: String =>
+ val len = s1.length
+ if (len != s2.length)
+ return false
+ E.addSeqCost(EQ_COA_Short, len) { () =>
+ s1 == s2
+ }
+ case _ => false
+ }
+ case _: Unit =>
+ okEqual = r.isInstanceOf[Unit]
+
+ case _ =>
+ ErgoTreeEvaluator.error(s"Cannot compare $l and $r: unknown type")
+ }
+ okEqual
+ }
+
+}
diff --git a/sigmastate/src/main/scala/sigmastate/SigSerializer.scala b/sigmastate/src/main/scala/sigmastate/SigSerializer.scala
index cbcb93f0a9..a1133bc8eb 100644
--- a/sigmastate/src/main/scala/sigmastate/SigSerializer.scala
+++ b/sigmastate/src/main/scala/sigmastate/SigSerializer.scala
@@ -4,15 +4,14 @@ import com.typesafe.scalalogging.LazyLogging
import gf2t.GF2_192_Poly
import org.bouncycastle.util.BigIntegers
import scorex.util.encode.Base16
-import sigmastate.SigSerializer.{logger, readBytesChecked}
import sigmastate.Values.SigmaBoolean
import sigmastate.basics.DLogProtocol.{ProveDlog, SecondDLogProverMessage}
import sigmastate.basics.VerifierMessage.Challenge
import sigmastate.basics.{ProveDHTuple, SecondDiffieHellmanTupleProverMessage}
import sigmastate.interpreter.CryptoConstants
import sigmastate.lang.exceptions.SerializerException
-import sigmastate.serialization.SigmaSerializer
-import sigmastate.utils.{Helpers, SigmaByteReader, SigmaByteWriter}
+import sigmastate.serialization.{SigmaSerializer, ValueSerializer}
+import sigmastate.utils.{SigmaByteReader, SigmaByteWriter, Helpers}
import spire.syntax.all.cfor
object SigSerializer extends LazyLogging {
@@ -171,7 +170,7 @@ object SigSerializer extends LazyLogging {
case and: CAND =>
// Verifier Step 2: If the node is AND, then all of its children get e_0 as the challenge
val nChildren = and.children.length
- val children = new Array[UncheckedSigmaTree](nChildren)
+ val children = ValueSerializer.newArray[UncheckedSigmaTree](nChildren)
cfor(0)(_ < nChildren, _ + 1) { i =>
children(i) = parseAndComputeChallenges(and.children(i), r, challenge)
}
@@ -184,7 +183,7 @@ object SigSerializer extends LazyLogging {
// Read all the children but the last and compute the XOR of all the challenges including e_0
val nChildren = or.children.length
- val children = new Array[UncheckedSigmaTree](nChildren)
+ val children = ValueSerializer.newArray[UncheckedSigmaTree](nChildren)
val xorBuf = challenge.clone()
val iLastChild = nChildren - 1
cfor(0)(_ < iLastChild, _ + 1) { i =>
@@ -211,7 +210,7 @@ object SigSerializer extends LazyLogging {
hex => warn(s"Invalid coeffBytes for $th: $hex"))
val polynomial = GF2_192_Poly.fromByteArray(challenge, coeffBytes)
- val children = new Array[UncheckedSigmaTree](nChildren)
+ val children = ValueSerializer.newArray[UncheckedSigmaTree](nChildren)
cfor(0)(_ < nChildren, _ + 1) { i =>
val c = Challenge @@ polynomial.evaluate((i + 1).toByte).toByteArray
children(i) = parseAndComputeChallenges(th.children(i), r, c)
diff --git a/sigmastate/src/main/scala/sigmastate/Values.scala b/sigmastate/src/main/scala/sigmastate/Values.scala
index 86512a5e13..ed198420d7 100644
--- a/sigmastate/src/main/scala/sigmastate/Values.scala
+++ b/sigmastate/src/main/scala/sigmastate/Values.scala
@@ -4,15 +4,15 @@ import java.math.BigInteger
import java.util
import java.util.Objects
-import org.bitbucket.inkytonik.kiama.relation.Tree
-import org.bitbucket.inkytonik.kiama.rewriting.Rewriter.{strategy, everywherebu, count}
+import org.bitbucket.inkytonik.kiama.rewriting.Rewriter.{count, everywherebu, strategy}
+import org.ergoplatform.settings.ErgoAlgos
import org.ergoplatform.validation.ValidationException
import scalan.{Nullable, RType}
import scalan.util.CollectionUtil._
-import sigmastate.SCollection.{SIntArray, SByteArray}
+import sigmastate.SCollection.{SByteArray, SIntArray}
import sigmastate.interpreter.CryptoConstants.EcPointType
-import sigmastate.interpreter.CryptoConstants
-import sigmastate.serialization.{OpCodes, ConstantStore, _}
+import sigmastate.interpreter.{CompanionDesc, CryptoConstants, ErgoTreeEvaluator, NamedDesc}
+import sigmastate.serialization.{ConstantStore, OpCodes, _}
import sigmastate.serialization.OpCodes._
import sigmastate.TrivialProp.{FalseProp, TrueProp}
import sigmastate.Values.ErgoTree.substConstants
@@ -24,6 +24,7 @@ import special.sigma.Extensions._
import sigmastate.eval._
import sigmastate.eval.Extensions._
import scalan.util.Extensions.ByteOps
+import sigmastate.interpreter.ErgoTreeEvaluator._
import spire.syntax.all.cfor
import scala.language.implicitConversions
@@ -32,20 +33,24 @@ import sigmastate.lang.CheckingSigmaBuilder._
import sigmastate.serialization.ErgoTreeSerializer.DefaultSerializer
import sigmastate.serialization.transformers.ProveDHTupleSerializer
import sigmastate.utils.{SigmaByteReader, SigmaByteWriter}
-import special.sigma.{AvlTree, PreHeader, Header, _}
+import special.sigma.{AvlTree, Header, PreHeader, _}
import sigmastate.lang.SourceContext
+import sigmastate.lang.exceptions.InterpreterException
import special.collection.Coll
import scala.collection.mutable
object Values {
- type SigmaTree = Tree[SigmaNode, SValue]
type SValue = Value[SType]
- type Idn = String
- trait Value[+S <: SType] extends SigmaNode {
+ /** Base class for all ErgoTree expression nodes.
+ * @see [[sigmastate.Values.ErgoTree]]
+ */
+ abstract class Value[+S <: SType] extends SigmaNode {
+ /** The companion node descriptor with opCode, cost and other metadata. */
def companion: ValueCompanion
+
/** Unique id of the node class used in serialization of ErgoTree. */
def opCode: OpCode = companion.opCode
@@ -89,6 +94,78 @@ object Values {
} else {
sys.error("_sourceContext can be set only once")
}
+
+ /** Defines an evaluation semantics of this tree node (aka Value or expression) in the given data environment.
+ * Should be implemented by all the ErgoTree nodes (aka operations).
+ * Thus, the ErgoTree interpreter implementation consists of combined implementations of this method.
+ * NOTE, this method shouldn't be called directly, instead use `evalTo` method.
+ *
+ * @param E Evaluator which defines evaluation context, cost accumulator, settings etc.
+ * @param env immutable map, which binds variables (given by ids) to the values
+ * @return the data value which is the result of evaluation
+ */
+ protected def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any =
+ sys.error(s"Should be overriden in ${this.getClass}: $this")
+
+ /** Evaluates this node to the value of the given expected type.
+ * This method should called from all `eval` implementations.
+ *
+ * @tparam T expected type of the resulting value
+ * @param E Evaluator which defines evaluation context, cost accumulator, settings etc.
+ * @param env immutable map, which binds variables (given by ids) to the values
+ * @return the data value which is the result of evaluation
+ */
+ @inline
+ final def evalTo[T](env: DataEnv)(implicit E: ErgoTreeEvaluator): T = {
+ if (E.settings.isMeasureOperationTime) E.profiler.onBeforeNode(this)
+ val v = eval(env)
+ if (E.settings.isMeasureOperationTime) E.profiler.onAfterNode(this)
+ v.asInstanceOf[T]
+ }
+
+ /** Add the cost given by the kind to the accumulator and associate it with this operation
+ * node.
+ */
+ @inline
+ final def addCost(costKind: FixedCost)(implicit E: ErgoTreeEvaluator): Unit = {
+ E.addCost(costKind, this.companion.opDesc)
+ }
+
+ /** Add the cost given by the descriptor to the accumulator and associate it with this operation
+ * node.
+ */
+ @inline
+ final def addCost[R](costKind: TypeBasedCost, tpe: SType)(block: () => R)(implicit E: ErgoTreeEvaluator): R = {
+ E.addTypeBasedCost(costKind, tpe, this.companion.opDesc)(block)
+ }
+
+ /** Add the cost of a repeated operation to the accumulator and associate it with this
+ * operation. The number of items (loop iterations) is known in advance (like in
+ * Coll.map operation)
+ *
+ * @param costKind cost descriptor of the operation
+ * @param nItems number of operations known in advance (before loop execution)
+ */
+ @inline
+ final def addSeqCostNoOp(costKind: PerItemCost, nItems: Int)
+ (implicit E: ErgoTreeEvaluator): Unit = {
+ E.addSeqCostNoOp(costKind, nItems, this.companion.opDesc)
+ }
+
+ /** Add the cost of a repeated operation to the accumulator and associate it with this
+ * operation. The number of items (loop iterations) is known in advance (like in
+ * Coll.map operation)
+ *
+ * @param costKind cost descriptor of the operation
+ * @param nItems number of operations known in advance (before loop execution)
+ * @param block operation executed under the given cost
+ * @tparam R result type of the operation
+ */
+ @inline
+ final def addSeqCost[R](costKind: PerItemCost, nItems: Int)
+ (block: () => R)(implicit E: ErgoTreeEvaluator): R = {
+ E.addSeqCost(costKind, nItems, this.companion.opDesc)(block)
+ }
}
object Value {
@@ -112,7 +189,7 @@ object Values {
object Typed {
def unapply(v: SValue): Option[(SValue, SType)] = Some((v, v.tpe))
}
- def notSupportedError(v: SValue, opName: String) =
+ def notSupportedError(v: Any, opName: String) =
throw new IllegalArgumentException(s"Method $opName is not supported for node $v")
/** Immutable empty array of values. Can be used to avoid allocation. */
@@ -131,14 +208,47 @@ object Values {
val c = count(deserializeNode)(exp)
c > 0
}
+
+ def typeError(node: SValue, evalResult: Any) = {
+ val tpe = node.tpe
+ throw new InterpreterException(
+ s"""Invalid type returned by evaluator:
+ | expression: $node
+ | expected type: $tpe
+ | resulting value: $evalResult
+ """.stripMargin)
+ }
+
+ def typeError(tpe: SType, evalResult: Any) = {
+ throw new InterpreterException(
+ s"""Invalid type returned by evaluator:
+ | expected type: $tpe
+ | resulting value: $evalResult
+ """.stripMargin)
+ }
+
+ def checkType(node: SValue, evalResult: Any) = {
+ val tpe = node.tpe
+ if (!SType.isValueOfType(evalResult, tpe))
+ typeError(node, evalResult)
+ }
+
+ def checkType(tpe: SType, evalResult: Any) = {
+ if (!SType.isValueOfType(evalResult, tpe))
+ typeError(tpe, evalResult)
+ }
}
+ /** Base class for all companion objects which are used as operation descriptors. */
trait ValueCompanion extends SigmaNodeCompanion {
import ValueCompanion._
/** Unique id of the node class used in serialization of ErgoTree. */
def opCode: OpCode
- override def toString: Idn = s"${this.getClass.getSimpleName}(${opCode.toUByte})"
+ /** Returns cost descriptor of this operation. */
+ def costKind: CostKind
+
+ override def toString: String = s"${this.getClass.getSimpleName}(${opCode.toUByte})"
def typeName: String = this.getClass.getSimpleName.replace("$", "")
@@ -150,13 +260,26 @@ object Values {
init()
+ val opDesc = CompanionDesc(this)
}
object ValueCompanion {
private val _allOperations: mutable.HashMap[Byte, ValueCompanion] = mutable.HashMap.empty
lazy val allOperations = _allOperations.toMap
}
- trait EvaluatedValue[+S <: SType] extends Value[S] {
+ /** Should be inherited by companion objects of operations with fixed cost kind. */
+ trait FixedCostValueCompanion extends ValueCompanion {
+ /** Returns cost descriptor of this operation. */
+ override def costKind: FixedCost
+ }
+
+ /** Should be inherited by companion objects of operations with per-item cost kind. */
+ trait PerItemCostValueCompanion extends ValueCompanion {
+ /** Returns cost descriptor of this operation. */
+ override def costKind: PerItemCost
+ }
+
+ abstract class EvaluatedValue[+S <: SType] extends Value[S] {
val value: S#WrappedType
def opType: SFunc = {
val resType = tpe match {
@@ -170,7 +293,7 @@ object Values {
}
}
- trait Constant[+S <: SType] extends EvaluatedValue[S] {}
+ abstract class Constant[+S <: SType] extends EvaluatedValue[S] {}
case class ConstantNode[S <: SType](value: S#WrappedType, tpe: S) extends Constant[S] {
require(Constant.isCorrectType(value, tpe), s"Invalid type of constant value $value, expected type $tpe")
@@ -178,6 +301,11 @@ object Values {
override def opCode: OpCode = companion.opCode
override def opName: String = s"Const"
+ protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = {
+ addCost(Constant.costKind)
+ value
+ }
+
override def equals(obj: scala.Any): Boolean = (obj != null) && (this.eq(obj.asInstanceOf[AnyRef]) || (obj match {
case c: Constant[_] => tpe == c.tpe && Objects.deepEquals(value, c.value)
case _ => false
@@ -198,8 +326,10 @@ object Values {
}
}
- object Constant extends ValueCompanion {
+ object Constant extends FixedCostValueCompanion {
override def opCode: OpCode = ConstantCode
+ /** Cost of: returning value from Constant node. */
+ override val costKind = FixedCost(5)
/** Immutable empty array, can be used to save allocations in many places. */
val EmptyArray = Array.empty[Constant[SType]]
@@ -253,9 +383,18 @@ object Values {
case class ConstantPlaceholder[S <: SType](id: Int, override val tpe: S) extends Value[S] {
def opType = SFunc(SInt, tpe)
override def companion: ValueCompanion = ConstantPlaceholder
+ override protected def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = {
+ val c = E.constants(id)
+ addCost(ConstantPlaceholder.costKind)
+ val res = c.value
+ Value.checkType(c, res)
+ res
+ }
}
object ConstantPlaceholder extends ValueCompanion {
override def opCode: OpCode = ConstantPlaceholderCode
+ /** Cost of: accessing Constant in array by index. */
+ override val costKind = FixedCost(1)
}
trait NotReadyValue[S <: SType] extends Value[S] {
@@ -278,6 +417,7 @@ object Values {
object TaggedVariable extends ValueCompanion {
override def opCode: OpCode = TaggedVariableCode
+ override def costKind: CostKind = FixedCost(1)
def apply[T <: SType](varId: Byte, tpe: T): TaggedVariable[T] =
TaggedVariableNode(varId, tpe)
}
@@ -288,7 +428,8 @@ object Values {
override def companion: ValueCompanion = UnitConstant
}
object UnitConstant extends ValueCompanion {
- override val opCode = UnitConstantCode
+ override def opCode = UnitConstantCode
+ override def costKind = Constant.costKind
}
type BoolValue = Value[SBoolean.type]
@@ -528,9 +669,14 @@ object Values {
case object GroupGenerator extends EvaluatedValue[SGroupElement.type] with ValueCompanion {
override def opCode: OpCode = OpCodes.GroupGeneratorCode
+ override val costKind = FixedCost(10)
override def tpe = SGroupElement
override val value = SigmaDsl.GroupElement(CryptoConstants.dlogGroup.generator)
override def companion = this
+ protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = {
+ addCost(costKind)
+ SigmaDsl.groupGenerator
+ }
}
@@ -552,11 +698,13 @@ object Values {
object TrueLeaf extends ConstantNode[SBoolean.type](true, SBoolean) with ValueCompanion {
override def companion = this
override def opCode: OpCode = TrueCode
+ override def costKind: FixedCost = Constant.costKind
}
object FalseLeaf extends ConstantNode[SBoolean.type](false, SBoolean) with ValueCompanion {
override def companion = this
override def opCode: OpCode = FalseCode
+ override def costKind: FixedCost = Constant.costKind
}
trait NotReadyValueBoolean extends NotReadyValue[SBoolean.type] {
@@ -569,6 +717,8 @@ object Values {
trait SigmaBoolean {
/** Unique id of the node class used in serialization of SigmaBoolean. */
val opCode: OpCode
+ /** Size of the proposition tree (number of nodes). */
+ def size: Int
}
object SigmaBoolean {
@@ -581,29 +731,41 @@ object Values {
*
* HOTSPOT: don't beautify the code
*/
- def estimateCost(sb: SigmaBoolean): Int = sb match {
- case _: ProveDlog => CostTable.proveDlogEvalCost
- case _: ProveDHTuple => CostTable.proveDHTupleEvalCost
- case and: CAND =>
- childrenCost(and.children)
- case or: COR =>
- childrenCost(or.children)
- case th: CTHRESHOLD =>
- childrenCost(th.children)
- case _ =>
- CostTable.MinimalCost
+ def estimateCost(sb: SigmaBoolean): Int = {
+ /** Compute the total cost of the given children. */
+ def childrenCost(children: Seq[SigmaBoolean]): Int = {
+ val childrenArr = children.toArray
+ val nChildren = childrenArr.length
+ var sum = 0
+ cfor(0)(_ < nChildren, _ + 1) { i =>
+ val c = estimateCost(childrenArr(i))
+ sum = java7.compat.Math.addExact(sum, c)
+ }
+ sum
+ }
+
+ sb match {
+ case _: ProveDlog => CostTable.proveDlogEvalCost
+ case _: ProveDHTuple => CostTable.proveDHTupleEvalCost
+ case and: CAND =>
+ childrenCost(and.children)
+ case or: COR =>
+ childrenCost(or.children)
+ case th: CTHRESHOLD =>
+ childrenCost(th.children)
+ case _ =>
+ CostTable.MinimalCost
+ }
}
- /** Compute the total cost of the given children. */
- private def childrenCost(children: Seq[SigmaBoolean]): Int = {
- val childrenArr = children.toArray
- val nChildren = childrenArr.length
- var sum = 0
- cfor(0)(_ < nChildren, _ + 1) { i =>
- val c = estimateCost(childrenArr(i))
- sum = Math.addExact(sum, c)
+ /** Compute total size of the trees in the collection of children. */
+ def totalSize(children: Seq[SigmaBoolean]): Int = {
+ var res = 0
+ val len = children.length
+ cfor(0)(_ < len, _ + 1) { i =>
+ res += children(i).size
}
- sum
+ res
}
/** HOTSPOT: don't beautify this code */
@@ -655,14 +817,14 @@ object Values {
case ProveDiffieHellmanTupleCode => dhtSerializer.parse(r)
case AndCode =>
val n = r.getUShort()
- val children = new Array[SigmaBoolean](n)
+ val children = ValueSerializer.newArray[SigmaBoolean](n)
cfor(0)(_ < n, _ + 1) { i =>
children(i) = serializer.parse(r)
}
CAND(children)
case OrCode =>
val n = r.getUShort()
- val children = new Array[SigmaBoolean](n)
+ val children = ValueSerializer.newArray[SigmaBoolean](n)
cfor(0)(_ < n, _ + 1) { i =>
children(i) = serializer.parse(r)
}
@@ -670,7 +832,7 @@ object Values {
case AtLeastCode =>
val k = r.getUShort()
val n = r.getUShort()
- val children = new Array[SigmaBoolean](n)
+ val children = ValueSerializer.newArray[SigmaBoolean](n)
cfor(0)(_ < n, _ + 1) { i =>
children(i) = serializer.parse(r)
}
@@ -686,18 +848,39 @@ object Values {
def tpe = SBox
}
+ // TODO refactor: only Constant make sense to inherit from EvaluatedValue
case class Tuple(items: IndexedSeq[Value[SType]]) extends EvaluatedValue[STuple] with EvaluatedCollection[SAny.type, STuple] {
override def companion = Tuple
override def elementType = SAny
lazy val tpe = STuple(items.map(_.tpe))
- lazy val value = {
+ lazy val value = { // TODO coverage
val xs = items.cast[EvaluatedValue[SAny.type]].map(_.value)
Colls.fromArray(xs.toArray(SAny.classTag.asInstanceOf[ClassTag[SAny.WrappedType]]))(RType.AnyType)
}
+ protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = {
+ // in v5.0 version we support only tuples of 2 elements to be equivalent with v4.x
+ if (items.length != 2)
+ error(s"Invalid tuple $this")
+
+ val item0 = items(0)
+ val x = item0.evalTo[Any](env)
+ Value.checkType(item0, x)
+
+ val item1 = items(1)
+ val y = item1.evalTo[Any](env)
+ Value.checkType(item1, y)
+
+ val res = (x, y) // special representation for pairs (to pass directly to Coll primitives)
+
+ addCost(Tuple.costKind)
+ res
+ }
}
- object Tuple extends ValueCompanion {
+ object Tuple extends FixedCostValueCompanion {
override def opCode: OpCode = TupleCode
+ /** Cost of: 1) allocating a new tuple (of limited max size)*/
+ override val costKind = FixedCost(15)
def apply(items: Value[SType]*): Tuple = Tuple(items.toIndexedSeq)
}
@@ -713,6 +896,7 @@ object Values {
}
object SomeValue extends ValueCompanion {
override val opCode = SomeValueCode
+ override def costKind: CostKind = Constant.costKind
}
case class NoneValue[T <: SType](elemType: T) extends OptionValue[T] {
@@ -722,6 +906,7 @@ object Values {
}
object NoneValue extends ValueCompanion {
override val opCode = NoneValueCode
+ override def costKind: CostKind = Constant.costKind
}
case class ConcreteCollection[V <: SType](items: Seq[Value[V]], elementType: V)
@@ -745,16 +930,32 @@ object Values {
else ConcreteCollection
val tpe = SCollection[V](elementType)
+ implicit lazy val tElement: RType[V#WrappedType] = Evaluation.stypeToRType(elementType)
// TODO refactor: this method is not used and can be removed
lazy val value = {
val xs = items.cast[EvaluatedValue[V]].map(_.value)
- val tElement = Evaluation.stypeToRType(elementType)
- Colls.fromArray(xs.toArray(elementType.classTag.asInstanceOf[ClassTag[V#WrappedType]]))(tElement)
+ Colls.fromArray(xs.toArray(elementType.classTag.asInstanceOf[ClassTag[V#WrappedType]]))
+ }
+
+ protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = {
+ val len = items.length
+ addCost(ConcreteCollection.costKind)
+ val is = Array.ofDim[V#WrappedType](len)(tElement.classTag)
+ cfor(0)(_ < len, _ + 1) { i =>
+ val item = items(i)
+ val itemV = item.evalTo[V#WrappedType](env)
+ Value.checkType(item, itemV) // necessary because cast to V#WrappedType is erased
+ is(i) = itemV
+ }
+ Colls.fromArray(is)
}
}
object ConcreteCollection extends ValueCompanion {
override def opCode: OpCode = ConcreteCollectionCode
+ /** Cost of: allocating new collection
+ * @see ConcreteCollection_PerItem */
+ override val costKind = FixedCost(20)
def fromSeq[V <: SType](items: Seq[Value[V]])(implicit tV: V): ConcreteCollection[V] =
ConcreteCollection(items, tV)
@@ -764,6 +965,7 @@ object Values {
}
object ConcreteCollectionBooleanConstant extends ValueCompanion {
override def opCode: OpCode = ConcreteCollectionBooleanConstantCode
+ override def costKind = ConcreteCollection.costKind
}
trait LazyCollection[V <: SType] extends NotReadyValue[SCollection[V]]
@@ -835,31 +1037,43 @@ object Values {
override val rhs: SValue) extends BlockItem {
require(id >= 0, "id must be >= 0")
override def companion = if (tpeArgs.isEmpty) ValDef else FunDef
- def tpe: SType = rhs.tpe
- def isValDef: Boolean = tpeArgs.isEmpty
+ override def tpe: SType = rhs.tpe
+ override def isValDef: Boolean = tpeArgs.isEmpty
/** This is not used as operation, but rather to form a program structure */
- def opType: SFunc = Value.notSupportedError(this, "opType")
+ override def opType: SFunc = Value.notSupportedError(this, "opType")
}
object ValDef extends ValueCompanion {
- def opCode: OpCode = ValDefCode
+ override def opCode: OpCode = ValDefCode
+ override def costKind = Value.notSupportedError(this, "costKind")
def apply(id: Int, rhs: SValue): ValDef = ValDef(id, Nil, rhs)
}
object FunDef extends ValueCompanion {
- def opCode: OpCode = FunDefCode
+ override def opCode: OpCode = FunDefCode
+ override def costKind = Value.notSupportedError(this, "costKind")
def unapply(d: BlockItem): Option[(Int, Seq[STypeVar], SValue)] = d match {
case ValDef(id, targs, rhs) if !d.isValDef => Some((id, targs, rhs))
case _ => None
}
}
- /** Special node which represents a reference to ValDef in was introduced as result of CSE. */
+ /** Special node which represents a reference to ValDef it was introduced as result of
+ * CSE. */
case class ValUse[T <: SType](valId: Int, tpe: T) extends NotReadyValue[T] {
override def companion = ValUse
/** This is not used as operation, but rather to form a program structure */
def opType: SFunc = Value.notSupportedError(this, "opType")
+
+ protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = {
+ addCost(ValUse.costKind)
+ val res = env.getOrElse(valId, error(s"cannot resolve $this"))
+ Value.checkType(this, res)
+ res
+ }
}
- object ValUse extends ValueCompanion {
+ object ValUse extends FixedCostValueCompanion {
override def opCode: OpCode = ValUseCode
+ /** Cost of: 1) Lookup in immutable HashMap by valId: Int 2) alloc of Some(v) */
+ override val costKind = FixedCost(5)
}
/** The order of ValDefs in the block is used to assign ids to ValUse(id) nodes
@@ -872,11 +1086,31 @@ object Values {
case class BlockValue(items: IndexedSeq[BlockItem], result: SValue) extends NotReadyValue[SType] {
override def companion = BlockValue
def tpe: SType = result.tpe
+
/** This is not used as operation, but rather to form a program structure */
def opType: SFunc = Value.notSupportedError(this, "opType")
+
+ protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = {
+ var curEnv = env
+ val len = items.length
+ addSeqCostNoOp(BlockValue.costKind, len)
+ cfor(0)(_ < len, _ + 1) { i =>
+ val vd = items(i).asInstanceOf[ValDef]
+ val v = vd.rhs.evalTo[Any](curEnv)
+ Value.checkType(vd, v)
+ E.addFixedCost(FuncValue.AddToEnvironmentDesc_CostKind,
+ FuncValue.AddToEnvironmentDesc) {
+ curEnv = curEnv + (vd.id -> v)
+ }
+ }
+ val res = result.evalTo[Any](curEnv)
+ Value.checkType(result, res)
+ res
+ }
}
object BlockValue extends ValueCompanion {
override def opCode: OpCode = BlockValueCode
+ override val costKind = PerItemCost(1, 1, 10)
}
/**
* @param args parameters list, where each parameter has an id and a type.
@@ -894,9 +1128,55 @@ object Values {
}
/** This is not used as operation, but rather to form a program structure */
override def opType: SFunc = SFunc(mutable.WrappedArray.empty, tpe)
+
+ protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = {
+ addCost(FuncValue.costKind)
+ if (args.length == 0) {
+ // TODO coverage
+ () => {
+ body.evalTo[Any](env)
+ }
+ }
+ else if (args.length == 1) {
+ val arg0 = args(0)
+ (vArg: Any) => {
+ Value.checkType(arg0._2, vArg)
+ var env1: DataEnv = null
+ E.addFixedCost(FuncValue.AddToEnvironmentDesc_CostKind,
+ FuncValue.AddToEnvironmentDesc) {
+ env1 = env + (arg0._1 -> vArg)
+ }
+ val res = body.evalTo[Any](env1)
+ Value.checkType(body, res)
+ res
+ }
+ }
+ else {
+ // TODO coverage
+ (vArgs: Seq[Any]) => {
+ var env1 = env
+ val len = args.length
+ cfor(0)(_ < len, _ + 1) { i =>
+ val id = args(i)._1
+ val v = vArgs(i)
+ E.addFixedCost(FuncValue.AddToEnvironmentDesc_CostKind,
+ FuncValue.AddToEnvironmentDesc) {
+ env1 = env1 + (id -> v)
+ }
+ }
+ body.evalTo[Any](env1)
+ }
+ }
+ }
}
- object FuncValue extends ValueCompanion {
+ object FuncValue extends FixedCostValueCompanion {
+ val AddToEnvironmentDesc = NamedDesc("AddToEnvironment")
+ /** Cost of: adding value to evaluator environment */
+ val AddToEnvironmentDesc_CostKind = FixedCost(5)
override def opCode: OpCode = FuncValueCode
+ /** Cost of: 1) switch on the number of args 2) allocating a new Scala closure
+ * Old cost: ("Lambda", "() => (D1) => R", lambdaCost),*/
+ override val costKind = FixedCost(5)
def apply(argId: Int, tArg: SType, body: SValue): FuncValue =
FuncValue(IndexedSeq((argId,tArg)), body)
}
@@ -1031,6 +1311,9 @@ object Values {
_bytes
}
+ /** Hexadecimal encoded string of ErgoTree.bytes. */
+ final def bytesHex: String = ErgoAlgos.encode(bytes)
+
private var _complexity: Int = givenComplexity
/** Structural complexity estimation of this tree.
diff --git a/sigmastate/src/main/scala/sigmastate/basics/DLogProtocol.scala b/sigmastate/src/main/scala/sigmastate/basics/DLogProtocol.scala
index 364dc41e57..75db9c69d2 100644
--- a/sigmastate/src/main/scala/sigmastate/basics/DLogProtocol.scala
+++ b/sigmastate/src/main/scala/sigmastate/basics/DLogProtocol.scala
@@ -25,7 +25,7 @@ object DLogProtocol {
/** Construct a new SigmaBoolean value representing public key of discrete logarithm signature protocol. */
case class ProveDlog(value: EcPointType)
extends SigmaProofOfKnowledgeLeaf[DLogSigmaProtocol, DLogProverInput] {
-
+ override def size: Int = 1
override val opCode: OpCode = OpCodes.ProveDlogCode
lazy val h: EcPointType = value
lazy val pkBytes: Array[Byte] = GroupElementSerializer.toBytes(h)
@@ -72,7 +72,7 @@ object DLogProtocol {
GroupElementSerializer.toBytes(ecData)
}
- override def toString: Idn = s"FirstDLogProverMessage(${Base16.encode(bytes)})"
+ override def toString = s"FirstDLogProverMessage(${Base16.encode(bytes)})"
}
case class SecondDLogProverMessage(z: BigInt) extends SecondProverMessage {
diff --git a/sigmastate/src/main/scala/sigmastate/basics/DiffieHellmanTupleProtocol.scala b/sigmastate/src/main/scala/sigmastate/basics/DiffieHellmanTupleProtocol.scala
index bda3f4ec56..5316bc70c9 100644
--- a/sigmastate/src/main/scala/sigmastate/basics/DiffieHellmanTupleProtocol.scala
+++ b/sigmastate/src/main/scala/sigmastate/basics/DiffieHellmanTupleProtocol.scala
@@ -63,9 +63,9 @@ case class SecondDiffieHellmanTupleProverMessage(z: BigInteger) extends SecondPr
/** Construct a new SigmaProp value representing public key of Diffie Hellman signature protocol.
* Common input: (g,h,u,v) */
case class ProveDHTuple(gv: EcPointType, hv: EcPointType, uv: EcPointType, vv: EcPointType)
- extends SigmaProtocolCommonInput[DiffieHellmanTupleProtocol]
- with SigmaProofOfKnowledgeLeaf[DiffieHellmanTupleProtocol, DiffieHellmanTupleProverInput] {
+ extends SigmaProofOfKnowledgeLeaf[DiffieHellmanTupleProtocol, DiffieHellmanTupleProverInput] {
override val opCode: OpCode = OpCodes.ProveDiffieHellmanTupleCode
+ override def size: Int = 4 // one node for each EcPoint
lazy val g = gv
lazy val h = hv
lazy val u = uv
diff --git a/sigmastate/src/main/scala/sigmastate/eval/BigIntegerOps.scala b/sigmastate/src/main/scala/sigmastate/eval/BigIntegerOps.scala
index a03ba6fca0..9ff7c033dc 100644
--- a/sigmastate/src/main/scala/sigmastate/eval/BigIntegerOps.scala
+++ b/sigmastate/src/main/scala/sigmastate/eval/BigIntegerOps.scala
@@ -2,7 +2,7 @@ package sigmastate.eval
import java.math.BigInteger
-import scalan.{ExactNumeric, ExactOrderingImpl}
+import scalan.{ExactNumeric, ExactOrderingImpl, ExactIntegral}
import scala.math.{Integral, Ordering}
import special.sigma._
@@ -24,24 +24,30 @@ object OrderingOps {
object NumericOps {
- trait BigIntegerIsIntegral extends Integral[BigInteger] {
- def quot(x: BigInteger, y: BigInteger): BigInteger = x.divide(y)
- def rem(x: BigInteger, y: BigInteger): BigInteger = x.remainder(y)
- def plus(x: BigInteger, y: BigInteger): BigInteger = x.add(y)
- def minus(x: BigInteger, y: BigInteger): BigInteger = x.subtract(y)
- def times(x: BigInteger, y: BigInteger): BigInteger = x.multiply(y)
- def negate(x: BigInteger): BigInteger = x.negate()
- def fromInt(x: Int): BigInteger = BigInteger.valueOf(x)
- def toInt(x: BigInteger): Int = x.intValueExact()
- def toLong(x: BigInteger): Long = x.longValueExact()
- def toFloat(x: BigInteger): Float = x.floatValue()
- def toDouble(x: BigInteger): Double = x.doubleValue()
- }
- implicit object BigIntegerIsIntegral extends BigIntegerIsIntegral with OrderingOps.BigIntegerOrdering
-
+ /** Base implementation of Integral methods for BigInt. */
trait BigIntIsIntegral extends Integral[BigInt] {
+ /** This method should not be used in v4.x */
def quot(x: BigInt, y: BigInt): BigInt = x.divide(y)
- def rem(x: BigInt, y: BigInt): BigInt = x.remainder(y)
+
+ /** This method is used in ErgoTreeEvaluator based interpreter, to implement
+ * '%' operation of ErgoTree (i.e. `%: (T, T) => T` operation) for all
+ * numeric types T including BigInt.
+ *
+ * In the v4.x interpreter, however, the `%` operation is implemented using
+ * [[CBigInt]].mod method (see implementation in [[TestBigInt]], which
+ * delegates to [[java.math.BigInteger]].mod method.
+ *
+ * Even though this method is called `rem`, the semantics of ErgoTree
+ * language requires it to correspond to [[java.math.BigInteger]].mod
+ * method.
+ *
+ * For this reason we define implementation of this `rem` method using
+ * [[BigInt]].mod.
+ *
+ * NOTE: This method should not be used in v4.x
+ */
+ def rem(x: BigInt, y: BigInt): BigInt = x.mod(y)
+
def plus(x: BigInt, y: BigInt): BigInt = x.add(y)
def minus(x: BigInt, y: BigInt): BigInt = x.subtract(y)
def times(x: BigInt, y: BigInt): BigInt = x.multiply(y)
@@ -55,23 +61,32 @@ object NumericOps {
/** The instance of Integral for BigInt.
*
- * Note: ExactIntegral is not defined for [[special.sigma.BigInt]].
- * This is because arithmetic BigInt operations are handled specially
+ * Note: ExactIntegral was not defined for [[special.sigma.BigInt]] in v4.x.
+ * This is because arithmetic BigInt operations were handled in a special way
* (see `case op: ArithOp[t] if op.tpe == SBigInt =>` in RuntimeCosting.scala).
- * As result [[scalan.primitives.UnBinOps.ApplyBinOp]] nodes are not created for BigInt
- * operations, and hence operation descriptors such as
+ * As result [[scalan.primitives.UnBinOps.ApplyBinOp]] nodes were not created for
+ * BigInt operations in v4.x., and hence operation descriptors such as
* [[scalan.primitives.NumericOps.IntegralDivide]] and
- * [[scalan.primitives.NumericOps.IntegralMod]] are not used for BigInt.
+ * [[scalan.primitives.NumericOps.IntegralMod]] were not used for BigInt.
+ * NOTE: this instance is used in the new v5.0 interpreter.
*/
- implicit object BigIntIsIntegral extends BigIntIsIntegral with OrderingOps.BigIntOrdering
+ object BigIntIsIntegral extends BigIntIsIntegral with OrderingOps.BigIntOrdering
- implicit object BigIntIsExactNumeric extends ExactNumeric[BigInt] {
+ /** The instance of [[ExactIntegral]] typeclass for [[BigInt]]. */
+ implicit object BigIntIsExactIntegral extends ExactIntegral[BigInt] {
val n = BigIntIsIntegral
override def plus(x: BigInt, y: BigInt): BigInt = n.plus(x, y)
override def minus(x: BigInt, y: BigInt): BigInt = n.minus(x, y)
override def times(x: BigInt, y: BigInt): BigInt = n.times(x, y)
+
+ override def quot(x: BigInt, y: BigInt): BigInt =
+ ??? // this method should not be used in v4.x
+
+ override def divisionRemainder(x: BigInt, y: BigInt): BigInt =
+ ??? // this method should not be used in v4.x
}
+ /** The instance of [[scalan.ExactOrdering]] typeclass for [[BigInt]]. */
implicit object BigIntIsExactOrdering extends ExactOrderingImpl[BigInt](BigIntIsIntegral)
}
diff --git a/sigmastate/src/main/scala/sigmastate/eval/CostingDataContext.scala b/sigmastate/src/main/scala/sigmastate/eval/CostingDataContext.scala
index 9b7ad8a88c..07d3705711 100644
--- a/sigmastate/src/main/scala/sigmastate/eval/CostingDataContext.scala
+++ b/sigmastate/src/main/scala/sigmastate/eval/CostingDataContext.scala
@@ -691,7 +691,7 @@ class CostingSigmaDslBuilder extends TestSigmaDslBuilder { dsl =>
case _ => sys.error(s"Cannot evaluate substConstants($scriptBytes, $positions, $newValues): cannot lift value $v")
})
- val res = SubstConstants.eval(scriptBytes.toArray, positions.toArray, typedNewVals)(validationSettings)
+ val (res, _) = SubstConstants.eval(scriptBytes.toArray, positions.toArray, typedNewVals)(validationSettings)
Colls.fromArray(res)
}
diff --git a/sigmastate/src/main/scala/sigmastate/eval/Evaluation.scala b/sigmastate/src/main/scala/sigmastate/eval/Evaluation.scala
index ce7107476e..b36f5998e2 100644
--- a/sigmastate/src/main/scala/sigmastate/eval/Evaluation.scala
+++ b/sigmastate/src/main/scala/sigmastate/eval/Evaluation.scala
@@ -347,10 +347,9 @@ trait Evaluation extends RuntimeCosting { IR: IRContext =>
case arr: Array[_] => s"Array(${trim(arr).mkString(",")})"
case col: special.collection.Coll[_] => s"Coll(${trim(col.toArray).mkString(",")})"
case p: SGroupElement => p.showToString
- case ProveDlog(GroupElementConstant(g)) => s"ProveDlog(${g.showToString})"
- case ProveDHTuple(
- GroupElementConstant(g), GroupElementConstant(h), GroupElementConstant(u), GroupElementConstant(v)) =>
- s"ProveDHT(${g.showToString},${h.showToString},${u.showToString},${v.showToString})"
+ case ProveDlog(g) => s"ProveDlog(${showECPoint(g)})"
+ case ProveDHTuple(g, h, u, v) =>
+ s"ProveDHT(${showECPoint(g)},${showECPoint(h)},${showECPoint(u)},${showECPoint(v)})"
case _ => x.toString
}
sym match {
@@ -370,13 +369,13 @@ trait Evaluation extends RuntimeCosting { IR: IRContext =>
case _ => error(s"Cannot find value in environment for $s (dataEnv = $dataEnv)")
}
- /** Incapsulate simple monotonic (add only) counter with reset. */
+ /** Encapsulate simple monotonic (add only) counter with reset. */
class CostCounter(val initialCost: Int) {
private var _currentCost: Int = initialCost
@inline def += (n: Int) = {
// println(s"${_currentCost} + $n")
- this._currentCost = java.lang.Math.addExact(this._currentCost, n)
+ this._currentCost = java7.compat.Math.addExact(this._currentCost, n)
}
@inline def currentCost: Int = _currentCost
}
@@ -470,7 +469,7 @@ trait Evaluation extends RuntimeCosting { IR: IRContext =>
if (costLimit.isDefined) {
val limit = costLimit.get
val loopCost = if (_loopStack.isEmpty) 0 else _loopStack.head.accumulatedCost
- val accumulatedCost = java.lang.Math.addExact(cost, loopCost)
+ val accumulatedCost = java7.compat.Math.addExact(cost, loopCost)
if (accumulatedCost > limit) {
throw new CostLimitException(accumulatedCost, Evaluation.msgCostLimitError(accumulatedCost, limit), None)
}
@@ -497,7 +496,7 @@ trait Evaluation extends RuntimeCosting { IR: IRContext =>
if (_loopStack.nonEmpty && _loopStack.head.body == body) {
// every time we exit the body of the loop we need to update accumulated cost
val h = _loopStack.head
- h.accumulatedCost = java.lang.Math.addExact(h.accumulatedCost, deltaCost)
+ h.accumulatedCost = java7.compat.Math.addExact(h.accumulatedCost, deltaCost)
}
}
@@ -866,7 +865,7 @@ object Evaluation {
* @throws CostLimitException
*/
def addCostChecked(current: Long, more: Long, limit: Long): Long = {
- val newCost = Math.addExact(current, more)
+ val newCost = java7.compat.Math.addExact(current, more)
if (newCost > limit) {
throw new CostLimitException(
estimatedCost = newCost,
diff --git a/sigmastate/src/main/scala/sigmastate/eval/IRContext.scala b/sigmastate/src/main/scala/sigmastate/eval/IRContext.scala
index f5b59538c2..f102ceeb9a 100644
--- a/sigmastate/src/main/scala/sigmastate/eval/IRContext.scala
+++ b/sigmastate/src/main/scala/sigmastate/eval/IRContext.scala
@@ -134,7 +134,7 @@ trait IRContext extends Evaluation with TreeBuilding {
!!!(s"Estimated cost $estimatedCost should be equal $accCost")
}
- val scaledCost = JMath.multiplyExact(estimatedCost.toLong, CostTable.costFactorIncrease.toLong) / CostTable.costFactorDecrease
+ val scaledCost = java7.compat.Math.multiplyExact(estimatedCost.toLong, CostTable.costFactorIncrease.toLong) / CostTable.costFactorDecrease
val totalCost = Evaluation.addCostChecked(initCost, scaledCost, maxCost)
totalCost.toInt
}
diff --git a/sigmastate/src/main/scala/sigmastate/eval/Profiler.scala b/sigmastate/src/main/scala/sigmastate/eval/Profiler.scala
new file mode 100644
index 0000000000..cfc3cba11c
--- /dev/null
+++ b/sigmastate/src/main/scala/sigmastate/eval/Profiler.scala
@@ -0,0 +1,341 @@
+package sigmastate.eval
+
+import sigmastate.{FixedCost, SMethod}
+import sigmastate.Values.SValue
+import sigmastate.serialization.OpCodes
+import sigmastate.serialization.OpCodes.OpCode
+import sigmastate.serialization.ValueSerializer.getSerializer
+import scalan.util.Extensions.ByteOps
+import debox.{Buffer => DBuffer, Map => DMap}
+import org.apache.commons.math3.util.Precision
+import sigmastate.interpreter.{CostItem, FixedCostItem, SeqCostItem, TypeBasedCostItem}
+import sigmastate.lang.Terms.{MethodCall, PropertyCall}
+import spire.{math, sp}
+
+import scala.reflect.ClassTag
+
+/** Holds a series of profile measurements associated with a key.
+ * Allows to compute simple statistic data.
+ * @tparam V type of the measured numeric value
+ */
+abstract class StatHolder[@sp (Long, Double) V] {
+ /** How many data points have been collected */
+ def count: Int
+
+ /** Sum of all data points */
+ def sum: V
+
+ /** Returns arithmetic average value. */
+ def avg: V
+
+ /** Returns arithmetic mean value (excluding 10% of smallest and 10% of highest values).
+ */
+ def mean: (V, Int)
+}
+
+/** Collects profiler measured data points associated with keys.
+ * Group points by key into [[StatHolder]]s.
+ * @tparam K type of the mapping key
+ * @tparam V type of the measured numeric value
+ */
+class StatCollection[@sp(Int) K, @sp(Long, Double) V]
+ (implicit n: math.Numeric[V], ctK: ClassTag[K], ctV: ClassTag[V]) {
+
+ private def calcAvg(buf: DBuffer[V]): V = {
+ n.div(buf.sum, n.fromInt(buf.length))
+ }
+
+ // NOTE: this class is mutable so better to keep it private
+ private class StatHolderImpl extends StatHolder[V] {
+ final val NumMaxPoints = 10000
+
+ val dataPoints: DBuffer[V] = DBuffer.ofSize[V](256)
+
+ def addPoint(point: V) = {
+ // collect data points until the threshold
+ if (dataPoints.length < NumMaxPoints) {
+ dataPoints += point
+ }
+ }
+
+ override def count: Int = dataPoints.length
+ override def sum: V = dataPoints.sum
+ override def avg: V = calcAvg(dataPoints)
+
+ override def mean: (V, Int) = {
+ val nCropped = dataPoints.length / 10
+ if (nCropped == 0) {
+ (calcAvg(dataPoints), dataPoints.length)
+ }
+ else {
+ val sorted = dataPoints.copy()
+ sorted.sort
+ val slice = sorted.slice(nCropped, sorted.length - nCropped)
+ (calcAvg(slice), slice.length)
+ }
+ }
+ }
+
+ /** Timings of op codes. For performance debox.Map is used, which keeps keys unboxed. */
+ private val opStat = DMap[K, StatHolderImpl]()
+
+ /** Returns arithmetic mean value (excluding 10% of smallest and 10% of highest values)
+ * for the given key.
+ */
+ final def getMean(key: K): Option[(V, Int)] = opStat.get(key).map(_.mean)
+
+ /** Update measurement stats for a given operation. */
+ final def addPoint(key: K, point: V) = {
+ val item = opStat.getOrElse(key, null)
+ if (item != null) {
+ item.addPoint(point)
+ } else {
+ val item = new StatHolderImpl
+ item.addPoint(point)
+ opStat(key) = item
+ }
+ }
+
+ /** Maps each entry of the collected mapping to a new array of values using the given
+ * function.
+ */
+ final def mapToArray[@sp C: ClassTag](f: (K, StatHolder[V]) => C): Array[C] = {
+ opStat.mapToArray(f)
+ }
+}
+
+/** A simple profiler to measure average execution times of ErgoTree operations. */
+class Profiler {
+
+ // NOTE: this class is mutable so better to keep it private
+ private class OpStat(
+ /** The node on the evaluation stack. */
+ val node: SValue,
+ /** The time then this node evaluation was started. */
+ val outerStart: Long,
+ /** The accumulated time of evaluating all the sub-nodes */
+ var innerTime: Long,
+ /** The time then this nodes evaluation finished */
+ val outerEnd: Long
+ )
+
+ /** If every recursive evaluation of every Value is marked with
+ * [[onBeforeNode()]]/[[onAfterNode()]], then this stack corresponds to the stack of
+ * recursive invocations of the evaluator. */
+ private var opStack: List[OpStat] = Nil
+
+ /** Called from evaluator immediately before the evaluator start recursive evaluation of
+ * the given node.
+ */
+ def onBeforeNode(node: SValue) = {
+ val t = System.nanoTime()
+ opStack = new OpStat(node, t, 0, t) :: opStack
+ }
+
+ /** Called from evaluator immediately after the evaluator finishes recursive evaluation
+ * of the given node.
+ */
+ def onAfterNode(node: SValue) = {
+ val t = System.nanoTime()
+
+ val op = opStack.head // always non empty at this point
+ opStack = opStack.tail // pop current op
+ assert(op.node.opCode == node.opCode, s"Inconsistent stack at ${op :: opStack}")
+
+ val opFullTime = t - op.outerStart // full time spent in this op
+
+ // add this time to parent's innerTime (if any parent)
+ if (opStack.nonEmpty) {
+ val parent = opStack.head
+ parent.innerTime += opFullTime
+ } else {
+ // we are on top level, do nothing
+ }
+
+ val opSelfTime = opFullTime - op.innerTime
+
+ // update timing stats
+ addOpTime(node.opCode, opSelfTime)
+ }
+
+ /** Timings of op codes. For performance debox implementation of Map is used. */
+ private val opStat = new StatCollection[Int, Long]()
+
+ /** Update time measurement stats for a given operation. */
+ @inline private final def addOpTime(op: OpCode, time: Long) = {
+ opStat.addPoint(OpCode.raw(op), time)
+ }
+
+ /** Timings of method calls */
+ private val mcStat = new StatCollection[Int, Long]()
+
+ /** Update time measurement stats for a given method. */
+ @inline private final def addMcTime(typeId: Byte, methodId: Byte, time: Long) = {
+ val key = typeId << 8 | methodId
+ mcStat.addPoint(key, time)
+ }
+
+ /** Wrapper class which implements special equality between CostItem instances,
+ * suitable for collecting of the statistics. */
+ class CostItemKey(val costItem: CostItem) {
+ override def hashCode(): Int = costItem match {
+ case sci: SeqCostItem => 31 * sci.opDesc.hashCode + sci.chunks
+ case _ => costItem.hashCode()
+ }
+
+ override def equals(obj: scala.Any): Boolean = (obj != null) &&
+ (this.eq(obj.asInstanceOf[AnyRef]) || {
+ (obj match {
+ case that: CostItemKey =>
+ this.costItem match {
+ case sciThis: SeqCostItem =>
+ that.costItem match {
+ case sciThat: SeqCostItem =>
+ sciThis.opDesc == sciThat.opDesc && sciThis.chunks == sciThat.chunks
+ case _ => false
+ }
+ case _ => this.costItem == that.costItem
+ }
+ case _ => false
+ })
+ })
+
+ }
+
+ /** Timings of cost items */
+ private val costItemsStat = new StatCollection[CostItemKey, Long]()
+
+ def addCostItem(costItem: CostItem, time: Long) = {
+ costItemsStat.addPoint(new CostItemKey(costItem), time)
+ }
+
+ /** Estimation cost for each script */
+ private val estimationCostStat = new StatCollection[String, Int]()
+ /** Estimation cost for each script */
+ private val measuredTimeStat = new StatCollection[String, Long]()
+
+ /** Returns relative error between estimated and actual values. */
+ def relativeError(est: Double, act: Double): Double = {
+ val delta = Math.abs(est - act)
+ delta / act
+ }
+
+ /** Adds estimated cost and actual measured time data point to the StatCollection for
+ * the given script.
+ */
+ def addEstimation(script: String, cost: Int, actualTimeNano: Long) = {
+ estimationCostStat.addPoint(script, cost)
+ measuredTimeStat.addPoint(script, actualTimeNano)
+ }
+
+ def suggestCost(time: Long): Int = {
+ ((time - 1) / 100 + 1).toInt
+ }
+
+ /** Prints the operation timing table using collected execution profile information.
+ */
+ def generateReport(): String = {
+ val opCodeLines = opStat.mapToArray { case (key, stat) =>
+ val (time, count) = stat.mean
+ val opCode = OpCode @@ key.toByte
+ val ser = getSerializer(opCode)
+ val opDesc = ser.opDesc
+ val (opName, cost) = opDesc.costKind match {
+ case FixedCost(c) if opDesc != MethodCall && opDesc != PropertyCall =>
+ (opDesc.typeName, c)
+ case _ => ("", 0)
+ }
+ val suggestedCost = suggestCost(time)
+ val warn = if (suggestedCost > cost) "!!!" else ""
+ val comment = s"count: $count, suggestedCost: $suggestedCost, actualCost: $cost$warn"
+ (opName, (opCode.toUByte - OpCodes.LastConstantCode).toString, time, comment)
+ }.filter(_._1.nonEmpty).sortBy(_._3)(Ordering[Long].reverse)
+
+ val mcLines = mcStat.mapToArray { case (key, stat) =>
+ val methodId = (key & 0xFF).toByte
+ val typeId = (key >> 8).toByte
+ val (time, count) = stat.mean
+ val m = SMethod.fromIds(typeId, methodId)
+ val typeName = m.objType.typeName
+ (s"$typeName.${m.name}", typeId, methodId, time, count.toString)
+ }.sortBy(r => (r._2,r._3))(Ordering[(Byte,Byte)].reverse)
+
+ val ciLines = costItemsStat.mapToArray { case (ciKey, stat) =>
+ val (name, timePerItem, time, comment) = {
+ val (time, count) = stat.mean
+ val suggestedCost = suggestCost(time)
+ val warn = if (suggestedCost > ciKey.costItem.cost) "!!!" else ""
+ ciKey.costItem match {
+ case ci: FixedCostItem =>
+ val comment = s"count: $count, suggested: $suggestedCost, actCost: ${ci.cost}$warn"
+ (ci.opName, time, time, comment)
+ case ci: TypeBasedCostItem =>
+ val comment = s"count: $count, suggested: $suggestedCost, actCost: ${ci.cost}$warn"
+ (ci.opName, time, time, comment)
+ case ci @ SeqCostItem(_, costKind, nItems) =>
+ val nChunks = ci.chunks
+ val timePerChunk = if (nChunks > 0) time / nChunks else time
+ val name = s"${ci.opName}(nChunks: $nChunks)"
+ val comment = s"count: $count, suggested: $suggestedCost, actCost: ${ci.cost}$warn, kind: $costKind"
+ (name, timePerChunk, time, comment)
+ }
+ }
+ (name, timePerItem, time, comment)
+ }.sortBy({ case (name, tpi, t, c) => (name, tpi)})(Ordering[(String, Long)])
+
+ val estLines = estimationCostStat.mapToArray { case (script, stat) =>
+ val (cost, count) = stat.mean
+ val (timeNano, _) = measuredTimeStat.getMean(script).get
+ val actualTimeMicro = timeNano.toDouble / 100
+ val actualCost = cost.toDouble
+ val error = relativeError(actualCost, actualTimeMicro)
+ (script, error, cost, Math.round(actualTimeMicro), count.toString)
+ }.sortBy(_._2)(Ordering[Double].reverse)
+
+
+ val rows = opCodeLines
+ .map { case (opName, opCode, time, comment) =>
+ val key = s"$opName".padTo(26, ' ')
+ s"$key -> time: $time ns, $comment "
+ }
+ .mkString("\n")
+
+ val mcRows = mcLines
+ .map { case (opName, typeId, methodId, time, count) =>
+ val key = s"($typeId.toByte, $methodId.toByte)".padTo(25, ' ')
+ s"$key -> $time, // count = $count, $opName "
+ }
+ .mkString("\n")
+
+ val ciRows = ciLines
+ .map { case (opName, timePerItem, time, comment) =>
+ val key = s"$opName".padTo(40, ' ')
+ val totalTime = if (time != timePerItem) s"($time)" else ""
+ s"$key -> $timePerItem${totalTime} ns, $comment"
+ }
+ .mkString("\n")
+
+ val estRows = estLines
+ .map { case (opName, error, cost, time, count) =>
+ val key = s"$opName".padTo(30, ' ')
+ val warn = if (cost < time) "!!!" else ""
+ val err = Precision.round(error, 4)
+ s"$key -> ($err, $cost$warn, $time), // count = $count "
+ }
+ .mkString("\n")
+
+ s"""
+ |-----------
+ |$rows
+ |-----------
+ |$mcRows
+ |-----------
+ |$ciRows
+ |-----------
+ |$estRows
+ |-----------
+ """.stripMargin
+ }
+
+}
+
diff --git a/sigmastate/src/main/scala/sigmastate/eval/RuntimeCosting.scala b/sigmastate/src/main/scala/sigmastate/eval/RuntimeCosting.scala
index acb955264a..f6ae303cf3 100644
--- a/sigmastate/src/main/scala/sigmastate/eval/RuntimeCosting.scala
+++ b/sigmastate/src/main/scala/sigmastate/eval/RuntimeCosting.scala
@@ -191,18 +191,15 @@ trait RuntimeCosting extends CostingRules { IR: IRContext =>
constCost(tpe)
}
- val UpcastBigIntOpType = SFunc(sigmastate.Upcast.tT, SBigInt)
- val DowncastBigIntOpType = SFunc(SBigInt, sigmastate.Upcast.tR)
-
def costOf(v: SValue): Ref[Int] = v match {
case l: Terms.Lambda =>
constCost(l.tpe)
case l: FuncValue =>
constCost(l.tpe)
case sigmastate.Upcast(_, SBigInt) =>
- costOf("Upcast", UpcastBigIntOpType)
+ costOf("Upcast", sigmastate.Upcast.BigIntOpType)
case sigmastate.Downcast(v, _) if v.tpe == SBigInt =>
- costOf("Downcast", DowncastBigIntOpType)
+ costOf("Downcast", sigmastate.Downcast.BigIntOpType)
case _ =>
costOf(v.opName, v.opType)
}
@@ -884,11 +881,11 @@ trait RuntimeCosting extends CostingRules { IR: IRContext =>
import NumericOps._
private lazy val elemToExactNumericMap = Map[Elem[_], ExactNumeric[_]](
- (ByteElement, ByteIsExactNumeric),
- (ShortElement, ShortIsExactNumeric),
- (IntElement, IntIsExactNumeric),
- (LongElement, LongIsExactNumeric),
- (bigIntElement, BigIntIsExactNumeric)
+ (ByteElement, ByteIsExactIntegral),
+ (ShortElement, ShortIsExactIntegral),
+ (IntElement, IntIsExactIntegral),
+ (LongElement, LongIsExactIntegral),
+ (bigIntElement, BigIntIsExactIntegral)
)
private lazy val elemToExactIntegralMap = Map[Elem[_], ExactIntegral[_]](
(ByteElement, ByteIsExactIntegral),
diff --git a/sigmastate/src/main/scala/sigmastate/eval/Sized.scala b/sigmastate/src/main/scala/sigmastate/eval/Sized.scala
index a6362ffbb1..47aefed55b 100644
--- a/sigmastate/src/main/scala/sigmastate/eval/Sized.scala
+++ b/sigmastate/src/main/scala/sigmastate/eval/Sized.scala
@@ -38,7 +38,14 @@ trait SizedLowPriority {
object Sized extends SizedLowPriority {
def apply[T](implicit sz: Sized[T]): Sized[T] = sz
+
+ /** Creates a size descriptor for a given data instance provided T is Sized.
+ * @param x data instance
+ * @tparam T Sized data type
+ */
def sizeOf[T: Sized](x: T): Size[T] = Sized[T].size(x)
+
+ /** Helper constructor to support Scala 2.11. */
def instance[T](f: T => Size[T]) = new Sized[T] {
override def size(x: T): Size[T] = f(x)
}
@@ -63,6 +70,13 @@ object Sized extends SizedLowPriority {
implicit val SigmaPropIsSized: Sized[SigmaProp] = Sized.instance((_: SigmaProp) => SizeSigmaProp)
implicit val AvlTreeIsSized: Sized[AvlTree] = Sized.instance((_: AvlTree) => SizeAvlTree)
+ /** Constructs a new [[Sized]] instance for a given [[SType]] type descriptor. */
+ def stypeToSized[T <: SType](t: T): Sized[T#WrappedType] = {
+ val rtype = Evaluation.stypeToRType(t)
+ typeToSized[T#WrappedType](rtype)
+ }
+
+ /** Constructs a new [[Sized]] instance for a given [[RType]] type descriptor. */
def typeToSized[T](t: RType[T]): Sized[T] = (t match {
case BooleanType => BooleanIsSized
case ByteType => ByteIsSized
diff --git a/sigmastate/src/main/scala/sigmastate/interpreter/CostAccumulator.scala b/sigmastate/src/main/scala/sigmastate/interpreter/CostAccumulator.scala
new file mode 100644
index 0000000000..62f42bca4f
--- /dev/null
+++ b/sigmastate/src/main/scala/sigmastate/interpreter/CostAccumulator.scala
@@ -0,0 +1,76 @@
+package sigmastate.interpreter
+
+import sigmastate.lang.exceptions.CostLimitException
+
+/** Encapsulate simple monotonic (add only) counter with reset. */
+class CostCounter(val initialCost: Int) {
+ private var _currentCost: Int = initialCost
+
+ @inline def += (n: Int) = {
+ this._currentCost = java.lang.Math.addExact(this._currentCost, n)
+ }
+ @inline def currentCost: Int = _currentCost
+ @inline def resetCost() = { _currentCost = initialCost }
+}
+
+/** Implements finite state machine with stack of graph blocks (scopes),
+ * which correspond to lambdas and thunks.
+ * It accepts messages: startScope(), endScope(), add(), reset()
+ * At any time `totalCost` is the currently accumulated cost. */
+class CostAccumulator(initialCost: Int, costLimit: Option[Long]) {
+
+ @inline private def initialStack() = List(new Scope(initialCost))
+ private var _scopeStack: List[Scope] = initialStack
+
+ @inline def currentScope: Scope = _scopeStack.head
+
+ /** Represents a single scope during execution of the graph.
+ * The lifetime of each instance is bound to scope execution.
+ * When the evaluation enters a new scope (e.g. calling a lambda) a new Scope instance is created and pushed
+ * to _scopeStack, then is starts receiving `add` method calls.
+ * When the evaluation leaves the scope, the top is popped off the stack. */
+ class Scope(initialCost: Int) extends CostCounter(initialCost) {
+
+
+ @inline def add(opCost: Int): Unit = {
+ this += opCost
+ }
+
+ /** Called by nested Scopes to communicate accumulated cost back to parent scope.
+ * When current scope terminates, it communicates accumulated cost up to its parent scope.
+ * This value is used at the root scope to obtain total accumulated scope.
+ */
+ private var _resultRegister: Int = 0
+ @inline def childScopeResult: Int = _resultRegister
+ @inline def childScopeResult_=(resultCost: Int): Unit = {
+ _resultRegister = resultCost
+ }
+
+ }
+
+ /** Called once for each operation of a scope (lambda or thunk).
+ * @throws CostLimitException when current accumulated cost exceeds `costLimit`
+ */
+ def add(opCost: Int): Unit = {
+ currentScope.add(opCost)
+
+ // check that we are still withing the limit
+ if (costLimit.isDefined) {
+ val limit = costLimit.get
+ // the cost we accumulated so far
+ val accumulatedCost = currentScope.currentCost
+ if (accumulatedCost > limit) {
+ throw new CostLimitException(
+ accumulatedCost, CostLimitException.msgCostLimitError(accumulatedCost, limit), None)
+ }
+ }
+ }
+
+ /** Resets this accumulator into initial state to be ready for new graph execution. */
+ @inline def reset() = {
+ _scopeStack = initialStack()
+ }
+
+ /** Returns total accumulated cost */
+ @inline def totalCost: Int = currentScope.currentCost
+}
diff --git a/sigmastate/src/main/scala/sigmastate/interpreter/CostDetails.scala b/sigmastate/src/main/scala/sigmastate/interpreter/CostDetails.scala
new file mode 100644
index 0000000000..cb3ae724e4
--- /dev/null
+++ b/sigmastate/src/main/scala/sigmastate/interpreter/CostDetails.scala
@@ -0,0 +1,57 @@
+package sigmastate.interpreter
+
+import scala.collection.mutable
+
+/** Abstract representation of cost results obtained during evaluation. */
+abstract class CostDetails {
+ /** The total cost of evaluation. */
+ def cost: Int
+ /** The trace of costed operations performed during evaluation. */
+ def trace: Seq[CostItem]
+ /** Actual execution time (in nanoseconds) if defined. */
+ def actualTimeNano: Option[Long]
+}
+
+/** Detailed results of cost evaluation represented by trace.
+ * NOTE: the `trace` is obtained during execution of [[ErgoTree]] operations.
+ * @param trace accumulated trace of all cost items (empty for AOT costing)
+ * @param actualTimeNano measured time of execution (if some)
+ */
+case class TracedCost(trace: Seq[CostItem],
+ actualTimeNano: Option[Long] = None) extends CostDetails {
+ /** Total cost of all cost items. */
+ def cost: Int = trace.foldLeft(0)(_ + _.cost)
+}
+
+/** Result of cost evaluation represented using simple given value.
+ * Used to represent cost of AOT costing.
+ * @param cost the given value of the total cost
+ * @param actualTimeNano measured time of execution (if some)
+ */
+case class GivenCost(cost: Int,
+ actualTimeNano: Option[Long] = None) extends CostDetails {
+ /** The trait is empty for this representation of CostDetails.
+ */
+ override def trace: Seq[CostItem] = mutable.WrappedArray.empty
+}
+
+object CostDetails {
+ /** Empty sequence of cost items. Should be used whenever possible to avoid allocations. */
+ val EmptyTrace: Seq[CostItem] = mutable.WrappedArray.empty
+
+ /** CostDetails with empty trace have also zero total cost. */
+ val ZeroCost = TracedCost(EmptyTrace)
+
+ /** Helper factory method to create CostDetails from the given trace. */
+ def apply(trace: Seq[CostItem]): CostDetails = TracedCost(trace)
+
+ /** Helper recognizer to work with different representations of costs in patterns
+ * uniformly.
+ */
+ def unapply(d: CostDetails): Option[(Int, Seq[CostItem])] = d match {
+ case TracedCost(t, _) => Some((d.cost, t))
+ case GivenCost(c, _) => Some((c, EmptyTrace))
+ case _ => None
+ }
+}
+
diff --git a/sigmastate/src/main/scala/sigmastate/interpreter/CostItem.scala b/sigmastate/src/main/scala/sigmastate/interpreter/CostItem.scala
new file mode 100644
index 0000000000..f6479fc315
--- /dev/null
+++ b/sigmastate/src/main/scala/sigmastate/interpreter/CostItem.scala
@@ -0,0 +1,94 @@
+package sigmastate.interpreter
+
+import sigmastate.{FixedCost, PerItemCost, SMethod, SType, TypeBasedCost}
+import sigmastate.Values.{FixedCostValueCompanion, PerItemCostValueCompanion, ValueCompanion}
+import sigmastate.lang.Terms.MethodCall
+
+/** An item in the cost accumulation trace of a [[sigmastate.Values.ErgoTree]] evaluation. */
+abstract class CostItem {
+ def opName: String
+ def cost: Int
+}
+
+/** An item in the cost accumulation trace of a [[sigmastate.Values.ErgoTree]] evaluation.
+ * Represents cost of simple operation.
+ * Used for debugging, testing and profiling of costing.
+ * @param opDesc descriptor of the ErgoTree operation
+ * @param costKind kind of the cost to be added to accumulator
+ */
+case class FixedCostItem(opDesc: OperationDesc, costKind: FixedCost) extends CostItem {
+ override def opName: String = opDesc.operationName
+ override def cost: Int = costKind.cost
+}
+object FixedCostItem {
+ def apply(companion: FixedCostValueCompanion): FixedCostItem = {
+ FixedCostItem(companion.opDesc, companion.costKind)
+ }
+ def apply(method: SMethod, costKind: FixedCost): FixedCostItem = {
+ FixedCostItem(MethodDesc(method), costKind)
+ }
+}
+
+/** An item in the cost accumulation trace of a [[sigmastate.Values.ErgoTree]] evaluation.
+ * Represents cost of an operation which depends on type (e.g. type of arguments).
+ * Used for debugging, testing and profiling of costing.
+ * @param opDesc descriptor of the ErgoTree operation
+ * @param costKind type based cost descriptor added to accumulator
+ * @param tpe concrete type on this the operation is executed
+ * @see [[sigmastate.LE]], [[sigmastate.GT]]
+ */
+case class TypeBasedCostItem(
+ opDesc: OperationDesc,
+ costKind: TypeBasedCost,
+ tpe: SType) extends CostItem {
+ override def opName: String = {
+ val name = opDesc.operationName
+ s"$name[$tpe]"
+ }
+ override def cost: Int = costKind.costFunc(tpe)
+ override def equals(obj: Any): Boolean =
+ (this eq obj.asInstanceOf[AnyRef]) || (obj != null && (obj match {
+ case that: TypeBasedCostItem =>
+ opDesc == that.opDesc && tpe == that.tpe
+ case _ => false
+ }))
+ override def hashCode(): Int = 31 * opDesc.hashCode() + tpe.hashCode()
+}
+object TypeBasedCostItem {
+ def apply(companion: ValueCompanion, tpe: SType): TypeBasedCostItem = {
+ TypeBasedCostItem(companion.opDesc, companion.costKind.asInstanceOf[TypeBasedCost], tpe)
+ }
+}
+
+/** An item in the cost accumulation trace of a [[sigmastate.Values.ErgoTree]] evaluation.
+ * Represents cost of a sequence of operation.
+ * Used for debugging, testing and profiling of costing.
+ *
+ * @param opDesc descriptor of the ErgoTree operation
+ * @param costKind descriptor of the cost added to accumulator
+ * @param nItems number of items in the sequence
+ */
+case class SeqCostItem(opDesc: OperationDesc, costKind: PerItemCost, nItems: Int)
+ extends CostItem {
+ override def opName: String = opDesc.operationName
+ override def cost: Int = costKind.cost(nItems)
+ /** How many data chunks in this cost item. */
+ def chunks: Int = costKind.chunks(nItems)
+}
+object SeqCostItem {
+ def apply(companion: PerItemCostValueCompanion, nItems: Int): SeqCostItem =
+ SeqCostItem(companion.opDesc, companion.costKind, nItems)
+}
+
+/** An item in the cost accumulation trace of a [[sigmastate.Values.ErgoTree]] evaluation.
+ * Represents cost of MethodCall operation.
+ * Used for debugging, testing and profiling of costing.
+ *
+ * @param items cost details obtained as part of MethodCall evaluation
+ */
+case class MethodCallCostItem(items: CostDetails) extends CostItem {
+ override def opName: String = MethodCall.typeName
+ override def cost: Int = items.cost
+}
+
+
diff --git a/sigmastate/src/main/scala/sigmastate/interpreter/CryptoConstants.scala b/sigmastate/src/main/scala/sigmastate/interpreter/CryptoConstants.scala
index f097acb8af..1da6c8c27e 100644
--- a/sigmastate/src/main/scala/sigmastate/interpreter/CryptoConstants.scala
+++ b/sigmastate/src/main/scala/sigmastate/interpreter/CryptoConstants.scala
@@ -29,11 +29,12 @@ object CryptoConstants {
val hashLength: Int = hashLengthBits / 8
- //size of challenge in Sigma protocols, in bits
- //if this anything but 192, threshold won't work, because we have polynomials over GF(2^192) and no others
- //so DO NOT change the value without implementing polynomials over GF(2^soundnessBits) first
- //and changing code that calls on GF2_192 and GF2_192_Poly classes!!!
- //we get the challenge by reducing hash function output to proper value
+ /** A size of challenge in Sigma protocols, in bits.
+ * If this anything but 192, threshold won't work, because we have polynomials over GF(2^192) and no others.
+ * So DO NOT change the value without implementing polynomials over GF(2^soundnessBits) first
+ * and changing code that calls on GF2_192 and GF2_192_Poly classes!!!
+ * We get the challenge by reducing hash function output to proper value.
+ */
implicit val soundnessBits: Int = 192.ensuring(_ < groupSizeBits, "2^t < q condition is broken!")
def secureRandomBytes(howMany: Int): Array[Byte] = {
diff --git a/sigmastate/src/main/scala/sigmastate/interpreter/CryptoFunctions.scala b/sigmastate/src/main/scala/sigmastate/interpreter/CryptoFunctions.scala
index 008048605c..44cbfa73e3 100644
--- a/sigmastate/src/main/scala/sigmastate/interpreter/CryptoFunctions.scala
+++ b/sigmastate/src/main/scala/sigmastate/interpreter/CryptoFunctions.scala
@@ -5,8 +5,16 @@ import scorex.crypto.hash.Blake2b256
object CryptoFunctions {
lazy val soundnessBytes: Int = CryptoConstants.soundnessBits / 8
+ /** Hashes the given `input` into 32 bytes hash, then returns the first `soundnessBytes`
+ * bytes of the hash in a new array.
+ * The method trims `32 - soundnessBytes` bytes at the end of the hash array.
+ *
+ * @hotspot don't beautify this code. Used in Interpreter.verify. */
def hashFn(input: Array[Byte]): Array[Byte] = {
- Blake2b256.hash(input).take(soundnessBytes)
+ val h = Blake2b256.hash(input)
+ val res = new Array[Byte](soundnessBytes)
+ Array.copy(h, 0, res, 0, soundnessBytes)
+ res
}
}
diff --git a/sigmastate/src/main/scala/sigmastate/interpreter/ErgoTreeEvaluator.scala b/sigmastate/src/main/scala/sigmastate/interpreter/ErgoTreeEvaluator.scala
new file mode 100644
index 0000000000..a531263d3f
--- /dev/null
+++ b/sigmastate/src/main/scala/sigmastate/interpreter/ErgoTreeEvaluator.scala
@@ -0,0 +1,419 @@
+package sigmastate.interpreter
+
+import org.ergoplatform.ErgoLikeContext
+import org.ergoplatform.SigmaConstants.ScriptCostLimit
+import sigmastate.{FixedCost, PerItemCost, SType, TypeBasedCost}
+import sigmastate.Values._
+import sigmastate.eval.Profiler
+import sigmastate.interpreter.ErgoTreeEvaluator.DataEnv
+import sigmastate.interpreter.Interpreter.ReductionResult
+import special.sigma.{Context, SigmaProp}
+import scalan.util.Extensions._
+import sigmastate.lang.Terms.MethodCall
+import spire.syntax.all.cfor
+
+import scala.collection.mutable
+import scala.util.DynamicVariable
+
+/** Configuration parameters of the evaluation run. */
+case class EvalSettings(
+ /** Used together with [[ErgoTreeEvaluator.profiler]] to measure individual operations timings. */
+ isMeasureOperationTime: Boolean,
+ /** Used together with [[ErgoTreeEvaluator.profiler]] to measure script timings. */
+ isMeasureScriptTime: Boolean,
+ /** Used by [[ErgoTreeEvaluator]] to conditionally perform debug mode operations. */
+ isDebug: Boolean = false,
+ /** Used by [[ErgoTreeEvaluator]] to conditionally emit log messages. */
+ isLogEnabled: Boolean = false,
+ /** Used by [[ErgoTreeEvaluator]] to conditionally build a trace of added costs.
+ * @see Value.addCost
+ */
+ costTracingEnabled: Boolean = false,
+ /** Profiler which, when defined, should be used in [[ErgoTreeEvaluator]] constructor. */
+ profilerOpt: Option[Profiler] = None,
+ /** Should be set to true, if evaluation is performed as part of test suite.
+ * In such a case, additional operations may be performed (such as sanity checks). */
+ isTestRun: Boolean = false,
+ /** If true, then expected test vectors are pretty-printed. */
+ printTestVectors: Boolean = false)
+
+/** Implements a simple and fast direct-style interpreter of ErgoTrees.
+ *
+ * ### Motivation
+ * [[ErgoTree]] is a simple declarative intermediate representation for Ergo contracts. It is
+ * designed to be compact in serialized form and directly executable, i.e. no additional
+ * transformation is necessary before it can be efficiently executed.
+ *
+ * This class implements a big-step recursive interpreter that works directly with
+ * ErgoTree HOAS. Because of this the evaluator is very simple and follows denotational
+ * semantics of ErgoTree (see https://ergoplatform.org/docs/ErgoTree.pdf). Or, the other
+ * way around, this implementation of ErgoTreeEvaluator is purely functional with
+ * immutable data structures and can be used as definition of ErgoTree's semantics.
+ *
+ * ### Implementation
+ * ErgoTreeEvaluator takes ErgoTree directly as it is deserialized as part of a
+ * transaction. No additional transformation is performed.
+ * ErgoTree is interpreted directly and all the intermediate data is stored in the
+ * runtime types.
+ * The runtime types are such types as [[special.collection.Coll]],
+ * [[special.sigma.SigmaProp]], [[special.sigma.AvlTree]], [[BigInt]], etc.
+ * It also use immutable Map to keep current [[DataEnv]] of computed [[ValDef]]s, as
+ * result only addition is used from the map, and deletion is essentially a garbage
+ * collection.
+ *
+ * ### Performance
+ * Since this interpreter directly works with SigmaDsl types (Coll, BigInt, SigmaProp
+ * etc), it turns out to be very fast. Since it also does JIT style costing instead of
+ * AOT style, it is 5-6x faster than existing implementation.
+ *
+ * @param context Represents blockchain data context for ErgoTree evaluation
+ * @param constants Segregated constants from ErgoTree, to lookup them from
+ * [[ConstantPlaceholder]] evaluation.
+ * @param coster Accumulates computation costs.
+ * @param profiler Performs operations profiling and time measurements (if enabled in settings).
+ * @param settings Settings to be used during evaluation.
+ */
+class ErgoTreeEvaluator(
+ val context: Context,
+ val constants: Seq[Constant[SType]],
+ protected val coster: CostAccumulator,
+ val profiler: Profiler,
+ val settings: EvalSettings) {
+
+ /** Evaluates the given expression in the given data environment. */
+ def eval(env: DataEnv, exp: SValue): Any = {
+ ErgoTreeEvaluator.currentEvaluator.withValue(this) {
+ exp.evalTo[Any](env)(this)
+ }
+ }
+
+ /** Evaluates the given expression in the given data environment and accrue the cost
+ * into the `coster` of this evaluator.
+ * @return the value of the expression and the total accumulated cost in the coster.
+ * The returned cost includes the initial cost accumulated in the `coster`
+ * prior to calling this method. */
+ def evalWithCost(env: DataEnv, exp: SValue): (Any, Int) = {
+ val res = eval(env, exp)
+ val cost = coster.totalCost
+ (res, cost)
+ }
+
+ /** Trace of cost items accumulated during execution of `eval` method. Call
+ * [[scala.collection.mutable.ArrayBuffer.clear()]] before each `eval` invocation. */
+ private lazy val costTrace = {
+ val b = mutable.ArrayBuilder.make[CostItem]
+ b.sizeHint(1000)
+ b
+ }
+
+ /** Adds the given cost to the `coster`. If tracing is enabled, associates the cost with
+ * the given operation.
+ *
+ * @param costKind kind of the cost to be added to `coster`
+ * @param opDesc operation descriptor to associate the cost with (when costTracingEnabled)
+ */
+ final def addCost(costKind: FixedCost, opDesc: OperationDesc): Unit = {
+ coster.add(costKind.cost)
+ if (settings.costTracingEnabled) {
+ costTrace += FixedCostItem(opDesc, costKind)
+ }
+ }
+
+ @inline final def addCost(costInfo: OperationCostInfo[FixedCost]): Unit = {
+ addCost(costInfo.costKind, costInfo.opDesc)
+ }
+
+ /** Add the cost given by the cost descriptor and the type to the accumulator and
+ * associate it with this operation descriptor.
+ *
+ * @param costKind descriptor of the cost
+ * @param tpe specific type for which the cost should be computed by this descriptor
+ * (see costFunc method)
+ * @param opDesc operation which is associated with this cost
+ */
+ @inline
+ final def addTypeBasedCost[R](costKind: TypeBasedCost,
+ tpe: SType, opDesc: OperationDesc)(block: () => R): R = {
+ var costItem: TypeBasedCostItem = null
+ if (settings.costTracingEnabled) {
+ costItem = TypeBasedCostItem(opDesc, costKind, tpe)
+ costTrace += costItem
+ }
+ if (settings.isMeasureOperationTime) {
+ if (costItem == null) {
+ costItem = TypeBasedCostItem(opDesc, costKind, tpe)
+ }
+ val start = System.nanoTime()
+ val cost = costKind.costFunc(tpe) // should be measured as part of the operation
+ coster.add(cost)
+ val res = block()
+ val end = System.nanoTime()
+ profiler.addCostItem(costItem, end - start)
+ res
+ } else {
+ val cost = costKind.costFunc(tpe)
+ coster.add(cost)
+ block()
+ }
+ }
+
+ /** Adds the given cost to the `coster`. If tracing is enabled, associates the cost with
+ * the given operation.
+ * @param costKind kind of the cost to be added to `coster`
+ * @param opDesc the operation descriptor to associate the cost with (when costTracingEnabled)
+ * @param block operation executed under the given cost
+ * @hotspot don't beautify the code
+ */
+ final def addFixedCost(costKind: FixedCost, opDesc: OperationDesc)(block: => Unit): Unit = {
+ var costItem: FixedCostItem = null
+ if (settings.costTracingEnabled) {
+ costItem = FixedCostItem(opDesc, costKind)
+ costTrace += costItem
+ }
+ if (settings.isMeasureOperationTime) {
+ if (costItem == null) {
+ costItem = FixedCostItem(opDesc, costKind)
+ }
+ val start = System.nanoTime()
+ coster.add(costKind.cost)
+ val _ = block
+ val end = System.nanoTime()
+ profiler.addCostItem(costItem, end - start)
+ } else {
+ coster.add(costKind.cost)
+ block
+ }
+ }
+
+ @inline
+ final def addFixedCost(costInfo: OperationCostInfo[FixedCost])(block: => Unit): Unit = {
+ addFixedCost(costInfo.costKind, costInfo.opDesc)(block)
+ }
+
+ /** Adds the given cost to the `coster`. If tracing is enabled, creates a new cost item
+ * with the given operation.
+ *
+ * @param costKind the cost to be added to `coster` for each item
+ * @param nItems the number of items
+ * @param opDesc the operation to associate the cost with (when costTracingEnabled)
+ * @hotspot don't beautify the code
+ */
+ final def addSeqCostNoOp(costKind: PerItemCost, nItems: Int, opDesc: OperationDesc): Unit = {
+ var costItem: SeqCostItem = null
+ if (settings.costTracingEnabled) {
+ costItem = SeqCostItem(opDesc, costKind, nItems)
+ costTrace += costItem
+ }
+ val cost = costKind.cost(nItems)
+ coster.add(cost)
+ }
+
+ /** Adds the given cost to the `coster`. If tracing is enabled, creates a new cost item
+ * with the given operation.
+ *
+ * @param costKind the cost to be added to `coster` for each item
+ * @param nItems the number of items
+ * @param opDesc the operation to associate the cost with (when costTracingEnabled)
+ * @param block operation executed under the given cost
+ * @tparam R result type of the operation
+ * @hotspot don't beautify the code
+ */
+ final def addSeqCost[R](costKind: PerItemCost, nItems: Int, opDesc: OperationDesc)(block: () => R): R = {
+ var costItem: SeqCostItem = null
+ if (settings.costTracingEnabled) {
+ costItem = SeqCostItem(opDesc, costKind, nItems)
+ costTrace += costItem
+ }
+ if (settings.isMeasureOperationTime) {
+ if (costItem == null) {
+ costItem = SeqCostItem(opDesc, costKind, nItems)
+ }
+ val start = System.nanoTime()
+ val cost = costKind.cost(nItems) // should be measured as part of the operation
+ coster.add(cost)
+ val res = block()
+ val end = System.nanoTime()
+ profiler.addCostItem(costItem, end - start)
+ res
+ } else {
+ val cost = costKind.cost(nItems)
+ coster.add(cost)
+ block()
+ }
+ }
+
+ /** Adds the cost to the `coster`. See the other overload for details. */
+ @inline
+ final def addSeqCost[R](costInfo: OperationCostInfo[PerItemCost], nItems: Int)
+ (block: () => R): R = {
+ addSeqCost(costInfo.costKind, nItems, costInfo.opDesc)(block)
+ }
+
+ /** Adds the cost to the `coster`. If tracing is enabled, creates a new cost item with
+ * the given operation descriptor and cost kind. If time measuring is enabled also
+ * performs profiling.
+ *
+ * WARNING: The cost is accumulated AFTER the block is executed.
+ * Each usage of this method should be accompanied with a proof of why this cannot lead
+ * to unbounded execution (see all usages).
+ *
+ * @param costKind the cost descriptor to be used to compute the cost based on the
+ * actual number of items returned by the `block`
+ * @param opDesc the operation to associate the cost with (when costTracingEnabled)
+ * @param block operation executed under the given cost descriptors, returns the
+ * actual number of items processed
+ * @hotspot don't beautify the code
+ */
+ final def addSeqCost(costKind: PerItemCost, opDesc: OperationDesc)(block: () => Int): Unit = {
+ var costItem: SeqCostItem = null
+ var nItems = 0
+ if (settings.isMeasureOperationTime) {
+ val start = System.nanoTime()
+ nItems = block()
+ val cost = costKind.cost(nItems) // should be measured as part of the operation
+ coster.add(cost)
+ val end = System.nanoTime()
+
+ costItem = SeqCostItem(opDesc, costKind, nItems)
+ profiler.addCostItem(costItem, end - start)
+ } else {
+ nItems = block()
+ val cost = costKind.cost(nItems)
+ coster.add(cost)
+ }
+ if (settings.costTracingEnabled) {
+ if (costItem == null)
+ costItem = SeqCostItem(opDesc, costKind, nItems)
+ costTrace += costItem
+ }
+ }
+
+ /** Adds the cost to the `coster`. See the other overload for details. */
+ final def addSeqCost(costInfo: OperationCostInfo[PerItemCost])(block: () => Int): Unit = {
+ addSeqCost(costInfo.costKind, costInfo.opDesc)(block)
+ }
+}
+
+object ErgoTreeEvaluator {
+ /** Immutable data environment used to assign data values to graph nodes. */
+ type DataEnv = Map[Int, Any]
+
+ /** Size of data block in bytes. Used in JIT cost calculations.
+ * @see [[sigmastate.NEQ]],
+ */
+ val DataBlockSize: Int = 512
+
+ /** Empty data environment. */
+ val EmptyDataEnv: DataEnv = Map.empty
+
+ /** A profiler which is used by default if [[EvalSettings.isMeasureOperationTime]] is enabled. */
+ val DefaultProfiler = new Profiler
+
+ /** Default global [[EvalSettings]] instance. */
+ val DefaultEvalSettings = EvalSettings(
+ isMeasureOperationTime = false,
+ isMeasureScriptTime = false)
+
+ /** Helper method to compute cost details for the given method call. */
+ def calcCost(mc: MethodCall, obj: Any, args: Array[Any])
+ (implicit E: ErgoTreeEvaluator): CostDetails = {
+ // add approximated cost of invoked method (if specified)
+ val cost = mc.method.costFunc match {
+ case Some(costFunc) => costFunc(E, mc, obj, args)
+ case _ => CostDetails.ZeroCost // TODO v5.0: throw exception if not defined
+ }
+ cost
+ }
+
+ /** Evaluator currently is being executed on the current thread.
+ * This variable is set in a single place, specifically in the `eval` method of
+ * [[ErgoTreeEvaluator]].
+ * @see getCurrentEvaluator
+ */
+ private[sigmastate] val currentEvaluator = new DynamicVariable[ErgoTreeEvaluator](null)
+
+ /** Returns a current evaluator for the current thread. */
+ def getCurrentEvaluator: ErgoTreeEvaluator = currentEvaluator.value
+
+ /** Creates a new [[ErgoTreeEvaluator]] instance with the given profiler and settings.
+ * The returned evaluator can be used to initialize the `currentEvaluator` variable.
+ * As a result, cost-aware operations (code blocks) can be implemented, even when those
+ * operations don't involve ErgoTree evaluation.
+ * As an example, see methods in [[sigmastate.SigSerializer]] and
+ * [[sigmastate.FiatShamirTree]] where cost-aware code blocks are used.
+ */
+ def forProfiling(profiler: Profiler, evalSettings: EvalSettings): ErgoTreeEvaluator = {
+ val acc = new CostAccumulator(0, Some(ScriptCostLimit.value))
+ new ErgoTreeEvaluator(
+ context = null,
+ constants = mutable.WrappedArray.empty,
+ acc, profiler, evalSettings.copy(profilerOpt = Some(profiler)))
+ }
+
+ /** Evaluate the given [[ErgoTree]] in the given Ergo context using the given settings.
+ * The given ErgoTree is evaluated as-is and is not changed during evaluation.
+ *
+ * @param context [[ErgoLikeContext]] used for script execution
+ * @param ergoTree script represented as [[ErgoTree]]
+ * @param evalSettings evaluation settings
+ * @return a sigma protocol proposition (as [[SigmaBoolean]]) and accumulated JIT cost estimation.
+ */
+ def evalToCrypto(context: ErgoLikeContext, ergoTree: ErgoTree, evalSettings: EvalSettings): ReductionResult = {
+ val (res, cost) = eval(context, ergoTree.constants, ergoTree.toProposition(replaceConstants = false), evalSettings)
+ val sb = res match {
+ case sp: SigmaProp =>
+ sigmastate.eval.SigmaDsl.toSigmaBoolean(sp)
+ case sb: SigmaBoolean => sb
+ case _ => error(s"Expected SigmaBoolean but was: $res")
+ }
+ ReductionResult(sb, cost)
+ }
+
+ /** Evaluate the given expression in the given Ergo context using the given settings.
+ * The given Value is evaluated as-is and is not changed during evaluation.
+ *
+ * @param context [[ErgoLikeContext]] used for script execution
+ * @param constants collection of segregated constants which can be refered by
+ * [[ConstantPlaceholder]]s in `exp`
+ * @param exp ErgoTree expression represented as [[Value]]
+ * @param evalSettings evaluation settings
+ * @return 1) the result of evaluating `exp` in a given context and
+ * 2) an accumulated JIT cost estimation.
+ */
+ def eval(context: ErgoLikeContext,
+ constants: Seq[Constant[SType]],
+ exp: SValue,
+ evalSettings: EvalSettings): (Any, Int) = {
+ val costAccumulator = new CostAccumulator(context.initCost.toIntExact, Some(context.costLimit))
+ val sigmaContext = context.toSigmaContext(isCost = false)
+ eval(sigmaContext, costAccumulator, constants, exp, evalSettings)
+ }
+
+ /** Evaluate the given expression in the given Ergo context using the given settings.
+ * The given Value is evaluated as-is and is not changed during evaluation.
+ *
+ * @param sigmaContext [[special.sigma.Context]] instance used for script execution
+ * @param costAccumulator [[CostAccumulator]] instance used for accumulating costs
+ * @param constants collection of segregated constants which can be refered by
+ * [[ConstantPlaceholder]]s in `exp`
+ * @param exp ErgoTree expression represented as [[sigmastate.Values.Value]]
+ * @param evalSettings evaluation settings
+ * @return 1) the result of evaluating `exp` in a given context and
+ * 2) an accumulated JIT cost estimation.
+ */
+ def eval(sigmaContext: Context,
+ costAccumulator: CostAccumulator,
+ constants: Seq[Constant[SType]],
+ exp: SValue,
+ evalSettings: EvalSettings): (Any, Int) = {
+ val evaluator = new ErgoTreeEvaluator(
+ sigmaContext, constants, costAccumulator, DefaultProfiler, evalSettings)
+ val res = evaluator.eval(Map(), exp)
+ val cost = costAccumulator.totalCost
+ (res, cost)
+ }
+
+ def error(msg: String) = sys.error(msg)
+}
+
+
diff --git a/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala b/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala
index bdb379f72f..4fafa5ef0d 100644
--- a/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala
+++ b/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala
@@ -114,14 +114,14 @@ trait Interpreter extends ScorexLogging {
/** This method is used in both prover and verifier to compute SigmaBoolean value.
* As the first step the cost of computing the `exp` expression in the given context is estimated.
- * If cost is above limit
- * then exception is returned and `exp` is not executed
- * else `exp` is computed in the given context and the resulting SigmaBoolean returned.
+ * If cost is above limit then exception is returned and `exp` is not executed
+ * else `exp` is computed in the given context and the resulting SigmaBoolean returned.
*
- * @param context the context in which `exp` should be executed
- * @param env environment of system variables used by the interpreter internally
- * @param exp expression to be executed in the given `context`
- * @return result of script reduction
+ * @param context the context in which `exp` should be executed
+ * @param env environment of variables used by the interpreter internally.
+ * Note, this is not system environment variables.
+ * @param exp expression to be executed in the given `context`
+ * @return result of script reduction
* @see `ReductionResult`
*/
def reduceToCrypto(context: CTX, env: ScriptEnv, exp: Value[SType]): Try[ReductionResult] = Try {
@@ -150,10 +150,12 @@ trait Interpreter extends ScorexLogging {
}
}
+ /** Helper convenience overload which uses empty environment.
+ * @see other overloads for details.
+ */
def reduceToCrypto(context: CTX, exp: Value[SType]): Try[ReductionResult] =
reduceToCrypto(context, Interpreter.emptyEnv, exp)
-
/**
* Full reduction of initial expression given in the ErgoTree form to a SigmaBoolean value
* (which encodes whether a sigma-protocol proposition or a boolean value, so true or false).
diff --git a/sigmastate/src/main/scala/sigmastate/interpreter/InterpreterContext.scala b/sigmastate/src/main/scala/sigmastate/interpreter/InterpreterContext.scala
index fd9f054ca7..84f9569c6f 100644
--- a/sigmastate/src/main/scala/sigmastate/interpreter/InterpreterContext.scala
+++ b/sigmastate/src/main/scala/sigmastate/interpreter/InterpreterContext.scala
@@ -3,6 +3,7 @@ package sigmastate.interpreter
import org.ergoplatform.validation.SigmaValidationSettings
import sigmastate.SType
import sigmastate.Values.EvaluatedValue
+import sigmastate.interpreter.ContextExtension.VarBinding
import sigmastate.serialization.SigmaSerializer
import sigmastate.utils.{SigmaByteReader, SigmaByteWriter}
import special.sigma
@@ -20,13 +21,18 @@ import special.sigma.AnyValue
* @param values internal container of the key-value pairs
*/
case class ContextExtension(values: Map[Byte, EvaluatedValue[_ <: SType]]) {
- def add(bindings: (Byte, EvaluatedValue[_ <: SType])*): ContextExtension =
+ def add(bindings: VarBinding*): ContextExtension =
ContextExtension(values ++ bindings)
}
object ContextExtension {
+ /** Immutable instance of empty ContextExtension, which can be shared to avoid
+ * allocations. */
val empty = ContextExtension(Map())
+ /** Type of context variable binding. */
+ type VarBinding = (Byte, EvaluatedValue[_ <: SType])
+
object serializer extends SigmaSerializer[ContextExtension, ContextExtension] {
override def serialize(obj: ContextExtension, w: SigmaByteWriter): Unit = {
@@ -91,7 +97,7 @@ trait InterpreterContext {
def withExtension(newExtension: ContextExtension): InterpreterContext
/** Creates a new instance with given bindings added to extension. */
- def withBindings(bindings: (Byte, EvaluatedValue[_ <: SType])*): InterpreterContext = {
+ def withBindings(bindings: VarBinding*): InterpreterContext = {
val ext = extension.add(bindings: _*)
withExtension(ext)
}
@@ -104,6 +110,13 @@ trait InterpreterContext {
* These types are used internally by ErgoTree interpreter.
* Thus, this method performs transformation from Ergo to internal Sigma representation
* of all context data.
+ *
+ * @param isCost == true if the resulting context will be used in AOT cost estimation
+ * otherwise it should be false
+ * @param extensions additional context variables which will be merged with those in the
+ * `extension` of this instance, overriding existing bindings in case
+ * variable ids overlap.
+ *
* @see sigmastate.eval.Evaluation
*/
def toSigmaContext(isCost: Boolean, extensions: Map[Byte, AnyValue] = Map()): sigma.Context
diff --git a/sigmastate/src/main/scala/sigmastate/interpreter/OperationDesc.scala b/sigmastate/src/main/scala/sigmastate/interpreter/OperationDesc.scala
new file mode 100644
index 0000000000..ca7fc9b217
--- /dev/null
+++ b/sigmastate/src/main/scala/sigmastate/interpreter/OperationDesc.scala
@@ -0,0 +1,43 @@
+package sigmastate.interpreter
+
+import sigmastate.{CostKind, SMethod}
+import sigmastate.Values.ValueCompanion
+
+/** Each costable operation is described in one of the following ways:
+ * 1) using [[ValueCompanion]] - operation with separate node class
+ * 2) using [[SMethod]] - operation represented as method.
+ * 3) using string name - intermediate sub-operation present in cost model, but
+ * which is not a separate operation of ErgoTree.
+ */
+abstract class OperationDesc {
+ def operationName: String
+}
+
+/** Operation descriptor based on [[ValueCompanion]]. */
+case class CompanionDesc(companion: ValueCompanion) extends OperationDesc {
+ override def operationName: String = companion.typeName
+}
+
+/** Operation descriptor based on [[SMethod]]. */
+case class MethodDesc(method: SMethod) extends OperationDesc {
+ override def operationName: String = method.opName
+
+ override def toString: String = s"MethodDesc(${method.opName})"
+
+ override def hashCode(): Int = (method.objType.typeId << 8) | method.methodId
+
+ override def equals(obj: Any): Boolean =
+ this.eq(obj.asInstanceOf[AnyRef]) || (obj != null && (obj match {
+ case that: MethodDesc =>
+ method.objType.typeId == that.method.objType.typeId &&
+ method.methodId == that.method.methodId
+ case _ => false
+ }))
+}
+
+/** Operation descriptor based on name. */
+case class NamedDesc(operationName: String) extends OperationDesc
+
+/** Operation costing descriptors combined together. */
+case class OperationCostInfo[C <: CostKind](costKind: C, opDesc: OperationDesc)
+
diff --git a/sigmastate/src/main/scala/sigmastate/interpreter/ProverInterpreter.scala b/sigmastate/src/main/scala/sigmastate/interpreter/ProverInterpreter.scala
index a7b174ed6c..9f3f70d62e 100644
--- a/sigmastate/src/main/scala/sigmastate/interpreter/ProverInterpreter.scala
+++ b/sigmastate/src/main/scala/sigmastate/interpreter/ProverInterpreter.scala
@@ -59,12 +59,12 @@ trait ProverInterpreter extends Interpreter with ProverUtils with AttributionCor
* https://ergoplatform.org/docs/ErgoScript.pdf , Appendix A
*
*/
- // todo: if we are concerned about timing attacks against the prover, we should make sure that this code
- // todo: takes the same amount of time regardless of which nodes are real and which nodes are simulated
- // todo: In particular, we should avoid the use of exists and forall, because they short-circuit the evaluation
- // todo: once the right value is (or is not) found. We should also make all loops look similar, the same
- // todo: amount of copying is done regardless of what's real or simulated,
- // todo: real vs. simulated computations take the same time, etc.
+ // TODO: if we are concerned about timing attacks against the prover, we should make sure that this code
+ // takes the same amount of time regardless of which nodes are real and which nodes are simulated
+ // In particular, we should avoid the use of exists and forall, because they short-circuit the evaluation
+ // once the right value is (or is not) found. We should also make all loops look similar, the same
+ // amount of copying is done regardless of what's real or simulated,
+ // real vs. simulated computations take the same time, etc.
protected def prove(unprovenTree: UnprovenTree, message: Array[Byte], hintsBag: HintsBag): ProofT = {
// Prover Step 1: Mark as real everything the prover can prove
diff --git a/sigmastate/src/main/scala/sigmastate/interpreter/ProverResult.scala b/sigmastate/src/main/scala/sigmastate/interpreter/ProverResult.scala
index 9c14d295c9..8e8c979ebd 100644
--- a/sigmastate/src/main/scala/sigmastate/interpreter/ProverResult.scala
+++ b/sigmastate/src/main/scala/sigmastate/interpreter/ProverResult.scala
@@ -3,7 +3,6 @@ package sigmastate.interpreter
import java.util
import scorex.util.encode.Base16
-import sigmastate.Values.Idn
import sigmastate.serialization.SigmaSerializer
import sigmastate.utils.{SigmaByteReader, SigmaByteWriter}
@@ -22,7 +21,7 @@ class ProverResult(val proof: Array[Byte], val extension: ContextExtension) {
case _ => false
}
- override def toString: Idn = s"ProverResult(${Base16.encode(proof)},$extension)"
+ override def toString = s"ProverResult(${Base16.encode(proof)},$extension)"
}
object ProverResult {
diff --git a/sigmastate/src/main/scala/sigmastate/lang/SigmaCompiler.scala b/sigmastate/src/main/scala/sigmastate/lang/SigmaCompiler.scala
index d41c2c1220..d5fe6d93c5 100644
--- a/sigmastate/src/main/scala/sigmastate/lang/SigmaCompiler.scala
+++ b/sigmastate/src/main/scala/sigmastate/lang/SigmaCompiler.scala
@@ -11,10 +11,24 @@ import sigmastate.lang.SigmaPredef.PredefinedFuncRegistry
import sigmastate.lang.syntax.ParserException
/**
- * @param networkPrefix network prefix to decode an ergo address from string (PK op)
- * @param builder
+ * @param networkPrefix network prefix to decode an ergo address from string (PK op)
+ * @param builder used to create ErgoTree nodes
+ * @param lowerMethodCalls if true, then MethodCall nodes are lowered to ErgoTree nodes
+ * when [[sigmastate.SMethod.irInfo.irBuilder]] is defined. For
+ * example, in the `coll.map(x => x+1)` code, the `map` method
+ * call can be lowered to MapCollection node.
+ * The lowering if preferable, because it is more compact (1 byte
+ * for MapCollection instead of 3 bytes for MethodCall).
*/
-class SigmaCompiler(networkPrefix: NetworkPrefix, builder: SigmaBuilder) {
+case class CompilerSettings(
+ networkPrefix: NetworkPrefix,
+ builder: SigmaBuilder,
+ lowerMethodCalls: Boolean
+)
+
+class SigmaCompiler(settings: CompilerSettings) {
+ @inline final def builder = settings.builder
+ @inline final def networkPrefix = settings.networkPrefix
def parse(x: String): SValue = {
SigmaParser(x, builder) match {
@@ -28,7 +42,7 @@ class SigmaCompiler(networkPrefix: NetworkPrefix, builder: SigmaBuilder) {
val predefinedFuncRegistry = new PredefinedFuncRegistry(builder)
val binder = new SigmaBinder(env, builder, networkPrefix, predefinedFuncRegistry)
val bound = binder.bind(parsed)
- val typer = new SigmaTyper(builder, predefinedFuncRegistry)
+ val typer = new SigmaTyper(builder, predefinedFuncRegistry, settings.lowerMethodCalls)
val typed = typer.typecheck(bound)
typed
}
@@ -53,6 +67,6 @@ class SigmaCompiler(networkPrefix: NetworkPrefix, builder: SigmaBuilder) {
}
object SigmaCompiler {
- def apply(networkPrefix: NetworkPrefix, builder: SigmaBuilder = TransformingSigmaBuilder): SigmaCompiler =
- new SigmaCompiler(networkPrefix, builder)
+ def apply(settings: CompilerSettings): SigmaCompiler =
+ new SigmaCompiler(settings)
}
diff --git a/sigmastate/src/main/scala/sigmastate/lang/SigmaPredef.scala b/sigmastate/src/main/scala/sigmastate/lang/SigmaPredef.scala
index ca7d94e147..942a110d52 100644
--- a/sigmastate/src/main/scala/sigmastate/lang/SigmaPredef.scala
+++ b/sigmastate/src/main/scala/sigmastate/lang/SigmaPredef.scala
@@ -446,7 +446,7 @@ object SigmaPredef {
Seq(ArgInfo("left", "left operand"), ArgInfo("right", "right operand"))),
binaryOp("/", ArithOp.Division, "Integer division of the first operand by the second operand.",
Seq(ArgInfo("left", "left operand"), ArgInfo("right", "right operand"))),
- binaryOp("%", ArithOp.Modulo, "Reminder from division of the first operand by the second operand.",
+ binaryOp("%", ArithOp.Modulo, "Remainder from division of the first operand by the second operand.",
Seq(ArgInfo("left", "left operand"), ArgInfo("right", "right operand"))),
binaryOp("min", ArithOp.Min, "Minimum value of two operands.",
Seq(ArgInfo("left", "left operand"), ArgInfo("right", "right operand"))),
diff --git a/sigmastate/src/main/scala/sigmastate/lang/SigmaTyper.scala b/sigmastate/src/main/scala/sigmastate/lang/SigmaTyper.scala
index 79f24ab509..f7855fcf0b 100644
--- a/sigmastate/src/main/scala/sigmastate/lang/SigmaTyper.scala
+++ b/sigmastate/src/main/scala/sigmastate/lang/SigmaTyper.scala
@@ -18,7 +18,9 @@ import scala.collection.mutable.ArrayBuffer
/**
* Type inference and analysis for Sigma expressions.
*/
-class SigmaTyper(val builder: SigmaBuilder, predefFuncRegistry: PredefinedFuncRegistry) {
+class SigmaTyper(val builder: SigmaBuilder,
+ predefFuncRegistry: PredefinedFuncRegistry,
+ lowerMethodCalls: Boolean) {
import SigmaTyper._
import builder._
import predefFuncRegistry._
@@ -35,7 +37,7 @@ class SigmaTyper(val builder: SigmaBuilder, predefFuncRegistry: PredefinedFuncRe
args: IndexedSeq[SValue]) = {
val global = Global.withPropagatedSrcCtx(srcCtx)
val node = for {
- pf <- method.irInfo.irBuilder
+ pf <- method.irInfo.irBuilder if lowerMethodCalls
res <- pf.lift((builder, global, method, args, EmptySubst))
} yield res
node.getOrElse(mkMethodCall(global, method, args, EmptySubst).withPropagatedSrcCtx(srcCtx))
@@ -105,8 +107,12 @@ class SigmaTyper(val builder: SigmaBuilder, predefFuncRegistry: PredefinedFuncRe
if (method.irInfo.irBuilder.isDefined && !tRes.isFunc) {
// this is MethodCall of parameter-less property, so invoke builder and/or fallback to just MethodCall
val methodConcrType = method.withSType(SFunc(newObj.tpe, tRes))
- methodConcrType.irInfo.irBuilder.flatMap(_.lift(builder, newObj, methodConcrType, IndexedSeq(), Map()))
- .getOrElse(mkMethodCall(newObj, methodConcrType, IndexedSeq(), Map()))
+ val nodeOpt = methodConcrType.irInfo.irBuilder match {
+ case Some(b) if lowerMethodCalls =>
+ b.lift(builder, newObj, methodConcrType, IndexedSeq(), Map())
+ case _ => None
+ }
+ nodeOpt.getOrElse(mkMethodCall(newObj, methodConcrType, IndexedSeq(), Map()))
} else {
mkSelect(newObj, n, Some(tRes))
}
@@ -133,7 +139,7 @@ class SigmaTyper(val builder: SigmaBuilder, predefFuncRegistry: PredefinedFuncRe
obj.tpe match {
case p: SProduct =>
p.method(n) match {
- case Some(method @ SMethod(_, _, genFunTpe @ SFunc(_, _, _), _, _, _)) =>
+ case Some(method @ SMethod(_, _, genFunTpe @ SFunc(_, _, _), _, _, _, _, _)) =>
val subst = Map(genFunTpe.tpeParams.head.ident -> rangeTpe)
val concrFunTpe = applySubst(genFunTpe, subst)
val expectedArgs = concrFunTpe.asFunc.tDom.tail
@@ -142,7 +148,9 @@ class SigmaTyper(val builder: SigmaBuilder, predefFuncRegistry: PredefinedFuncRe
|| !expectedArgs.zip(newArgTypes).forall { case (ea, na) => ea == SAny || ea == na })
error(s"For method $n expected args: $expectedArgs; actual: $newArgTypes", sel.sourceContext)
if (method.irInfo.irBuilder.isDefined) {
- method.irInfo.irBuilder.flatMap(_.lift(builder, newObj, method, newArgs, subst))
+ method.irInfo.irBuilder
+ .filter(_ => lowerMethodCalls)
+ .flatMap(_.lift(builder, newObj, method, newArgs, subst))
.getOrElse(mkMethodCall(newObj, method, newArgs, subst))
} else {
val newSelect = mkSelect(newObj, n, Some(concrFunTpe)).withSrcCtx(sel.sourceContext)
@@ -175,7 +183,9 @@ class SigmaTyper(val builder: SigmaBuilder, predefFuncRegistry: PredefinedFuncRe
|| !expectedArgs.zip(newArgTypes).forall { case (ea, na) => ea == SAny || ea == na })
error(s"For method $n expected args: $expectedArgs; actual: $newArgTypes", sel.sourceContext)
val methodConcrType = method.withSType(concrFunTpe.asFunc.withReceiverType(newObj.tpe))
- methodConcrType.irInfo.irBuilder.flatMap(_.lift(builder, newObj, methodConcrType, newArgs, Map()))
+ methodConcrType.irInfo.irBuilder
+ .filter(_ => lowerMethodCalls)
+ .flatMap(_.lift(builder, newObj, methodConcrType, newArgs, Map()))
.getOrElse(mkMethodCall(newObj, methodConcrType, newArgs, Map()))
case _ =>
val newSelect = mkSelect(newObj, n, Some(concrFunTpe)).withSrcCtx(sel.sourceContext)
@@ -288,7 +298,9 @@ class SigmaTyper(val builder: SigmaBuilder, predefFuncRegistry: PredefinedFuncRe
}
case _ => EmptySubst
}
- method.irInfo.irBuilder.flatMap(_.lift(builder, newObj, method, newArgs, typeSubst))
+ method.irInfo.irBuilder
+ .filter(_ => lowerMethodCalls)
+ .flatMap(_.lift(builder, newObj, method, newArgs, typeSubst))
.getOrElse(mkMethodCall(newObj, method, newArgs, typeSubst))
case _ =>
diff --git a/sigmastate/src/main/scala/sigmastate/lang/Terms.scala b/sigmastate/src/main/scala/sigmastate/lang/Terms.scala
index 333a08dc0c..5ba0449c6f 100644
--- a/sigmastate/src/main/scala/sigmastate/lang/Terms.scala
+++ b/sigmastate/src/main/scala/sigmastate/lang/Terms.scala
@@ -2,14 +2,17 @@ package sigmastate.lang
import org.bitbucket.inkytonik.kiama.rewriting.Rewriter._
import scalan.Nullable
-import sigmastate.SCollection.{SByteArray, SIntArray}
+import sigmastate.SCollection.{SIntArray, SByteArray}
import sigmastate.Values._
import sigmastate.utils.Overloading.Overload1
import sigmastate._
+import sigmastate.interpreter.ErgoTreeEvaluator
+import sigmastate.interpreter.ErgoTreeEvaluator.DataEnv
import sigmastate.serialization.OpCodes
import sigmastate.serialization.OpCodes.OpCode
import sigmastate.lang.TransformingSigmaBuilder._
+import scala.collection.mutable
import scala.language.implicitConversions
import scala.collection.mutable.WrappedArray
import spire.syntax.all.cfor
@@ -28,6 +31,7 @@ object Terms {
}
object Block extends ValueCompanion {
override def opCode: OpCode = OpCodes.Undefined
+ override def costKind: CostKind = Value.notSupportedError(this, "costKind")
def apply(let: Val, result: SValue)(implicit o1: Overload1): Block =
Block(Seq(let), result)
}
@@ -51,6 +55,7 @@ object Terms {
}
object ZKProofBlock extends ValueCompanion {
override def opCode: OpCode = OpCodes.Undefined
+ override def costKind: CostKind = Value.notSupportedError(this, "costKind")
val OpType = SFunc(SSigmaProp, SBoolean)
}
@@ -78,6 +83,7 @@ object Terms {
}
object ValNode extends ValueCompanion {
override def opCode: OpCode = OpCodes.Undefined
+ override def costKind: CostKind = Value.notSupportedError(this, "costKind")
}
/** Frontend node to select a field from an object. Should be transformed to SelectField*/
@@ -94,6 +100,7 @@ object Terms {
}
object Select extends ValueCompanion {
override def opCode: OpCode = OpCodes.Undefined
+ override def costKind: CostKind = Value.notSupportedError(this, "costKind")
}
/** Frontend node to represent variable names parsed in a source code.
@@ -105,6 +112,7 @@ object Terms {
}
object Ident extends ValueCompanion {
override def opCode: OpCode = OpCodes.Undefined
+ override def costKind: CostKind = Value.notSupportedError(this, "costKind")
def apply(name: String): Ident = Ident(name, NoType)
}
@@ -129,9 +137,32 @@ object Terms {
}
SFunc(argTypes, tpe)
}
+
+ protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = {
+ addCost(Apply.costKind)
+ if (args.isEmpty) {
+ // TODO coverage
+ val fV = func.evalTo[() => Any](env)
+ fV()
+ }
+ else if (args.length == 1) {
+ val fV = func.evalTo[Any => Any](env)
+ val argV = args(0).evalTo[Any](env)
+ fV(argV)
+ }
+ else {
+ // TODO coverage
+ val f = func.evalTo[Seq[Any] => Any](env)
+ val argsV = args.map(a => a.evalTo[Any](env))
+ f(argsV)
+ }
+ }
}
- object Apply extends ValueCompanion {
+ object Apply extends FixedCostValueCompanion {
override def opCode: OpCode = OpCodes.FuncApplyCode
+ /** Cost of: 1) switch on the number of args 2) Scala method call 3) add args to env
+ * Old cost: lambdaInvoke == 30 */
+ override val costKind = FixedCost(30)
}
/** Apply types for type parameters of input value. */
@@ -148,6 +179,7 @@ object Terms {
}
object ApplyTypes extends ValueCompanion {
override def opCode: OpCode = OpCodes.Undefined
+ override def costKind: CostKind = Value.notSupportedError(this, "costKind")
}
/** Frontend node to represent potential method call in a source code.
@@ -159,21 +191,22 @@ object Terms {
}
object MethodCallLike extends ValueCompanion {
override def opCode: OpCode = OpCodes.Undefined
+ override def costKind: CostKind = Value.notSupportedError(this, "costKind")
}
/** Represents in ErgoTree an invocation of method of the object `obj` with arguments `args`.
* The SMethod instances in STypeCompanions may have type STypeIdent in methods types,
* but valid ErgoTree should have SMethod instances specialized for specific types of
* obj and args using `specializeFor`.
- * This means, if we save typeId, mathodId, and we save all the arguments,
+ * This means, if we save typeId, methodId, and we save all the arguments,
* we can restore the specialized SMethod instance.
* This work by induction, if we assume all arguments are monomorphic,
* then we can make MethodCall monomorphic.
* Thus, all ErgoTree instances are monomorphic by construction.
*
- * @param obj object on which method will be invoked
- * @param method method to be invoked
- * @param args arguments passed to the method on invocation
+ * @param obj object on which method will be invoked
+ * @param method method to be invoked
+ * @param args arguments passed to the method on invocation
* @param typeSubst a map of concrete type for each generic type parameter
*/
case class MethodCall(obj: Value[SType],
@@ -186,9 +219,48 @@ object Terms {
case f: SFunc => f.tRange.withSubstTypes(typeSubst)
case t => t.withSubstTypes(typeSubst)
}
+
+ /** @hotspot don't beautify this code */
+ protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = {
+ val objV = obj.evalTo[Any](env)
+ addCost(MethodCall.costKind) // MethodCall overhead
+ method.costKind match {
+ case fixed: FixedCost =>
+ val extra = method.extraDescriptors
+ val extraLen = extra.length
+ val len = args.length
+ val argsBuf = new Array[Any](len + extraLen)
+ cfor(0)(_ < len, _ + 1) { i =>
+ argsBuf(i) = args(i).evalTo[Any](env)
+ }
+ cfor(0)(_ < extraLen, _ + 1) { i =>
+ argsBuf(len + i) = extra(i)
+ }
+ var res: Any = null
+ E.addFixedCost(fixed, method.opDesc) {
+ res = method.invokeFixed(objV, argsBuf)
+ }
+ res
+ case _ =>
+ val len = args.length
+ val argsBuf = new Array[Any](len + 3)
+ argsBuf(0) = this
+ argsBuf(1) = objV
+ cfor(0)(_ < len, _ + 1) { i =>
+ argsBuf(i + 2) = args(i).evalTo[Any](env)
+ }
+ argsBuf(argsBuf.length - 1) = E
+
+ val evalMethod = method.genericMethod.evalMethod
+ evalMethod.invoke(method.objType, argsBuf.asInstanceOf[Array[AnyRef]]:_*)
+ }
+ }
}
+
object MethodCall extends ValueCompanion {
override def opCode: OpCode = OpCodes.MethodCallCode
+ /** Cost of: 1) packing args into Array 2) java.lang.reflect.Method.invoke */
+ override val costKind = FixedCost(4)
/** Helper constructor which allows to cast the resulting node to the specified
* [[sigmastate.Values.Value]] type `T`.
@@ -201,8 +273,10 @@ object Terms {
MethodCall(obj, method, args, typeSubst).asInstanceOf[T]
}
}
- object PropertyCall extends ValueCompanion {
+ object PropertyCall extends FixedCostValueCompanion {
override def opCode: OpCode = OpCodes.PropertyCallCode
+ /** Cost of: 1) packing args into Array 2) java.lang.reflect.Method.invoke */
+ override val costKind = FixedCost(4)
}
case class STypeParam(ident: STypeVar, upperBound: Option[SType] = None, lowerBound: Option[SType] = None) {
@@ -231,6 +305,7 @@ object Terms {
}
object Lambda extends ValueCompanion {
override def opCode: OpCode = OpCodes.Undefined
+ override def costKind: CostKind = Value.notSupportedError(this, "costKind")
def apply(args: IndexedSeq[(String,SType)], resTpe: SType, body: Value[SType]): Lambda =
Lambda(Nil, args, resTpe, Some(body))
def apply(args: IndexedSeq[(String,SType)], resTpe: SType, body: Option[Value[SType]]): Lambda =
diff --git a/sigmastate/src/main/scala/sigmastate/lang/exceptions/Exceptions.scala b/sigmastate/src/main/scala/sigmastate/lang/exceptions/Exceptions.scala
index 34fe32bfaa..af3aafd1e2 100644
--- a/sigmastate/src/main/scala/sigmastate/lang/exceptions/Exceptions.scala
+++ b/sigmastate/src/main/scala/sigmastate/lang/exceptions/Exceptions.scala
@@ -36,3 +36,6 @@ class InterpreterException(message: String, source: Option[SourceContext] = None
class CostLimitException(val estimatedCost: Long, message: String, cause: Option[Throwable] = None)
extends SigmaException(message, None, cause)
+object CostLimitException {
+ def msgCostLimitError(cost: Long, limit: Long) = s"Estimated execution cost $cost exceeds the limit $limit"
+}
diff --git a/sigmastate/src/main/scala/sigmastate/serialization/BlockValueSerializer.scala b/sigmastate/src/main/scala/sigmastate/serialization/BlockValueSerializer.scala
index c0461b8148..6bd7cd91ec 100644
--- a/sigmastate/src/main/scala/sigmastate/serialization/BlockValueSerializer.scala
+++ b/sigmastate/src/main/scala/sigmastate/serialization/BlockValueSerializer.scala
@@ -29,7 +29,7 @@ case class BlockValueSerializer(cons: (IndexedSeq[BlockItem], Value[SType]) => V
BlockItem.EmptySeq
else {
// HOTSPOT:: allocate new array only if it is not empty
- val buf = new Array[BlockItem](itemsSize)
+ val buf = ValueSerializer.newArray[BlockItem](itemsSize)
cfor(0)(_ < itemsSize, _ + 1) { i =>
buf(i) = r.getValue().asInstanceOf[BlockItem]
}
diff --git a/sigmastate/src/main/scala/sigmastate/serialization/ConcreteCollectionBooleanConstantSerializer.scala b/sigmastate/src/main/scala/sigmastate/serialization/ConcreteCollectionBooleanConstantSerializer.scala
index 36a0e22801..0eb17c0aa6 100644
--- a/sigmastate/src/main/scala/sigmastate/serialization/ConcreteCollectionBooleanConstantSerializer.scala
+++ b/sigmastate/src/main/scala/sigmastate/serialization/ConcreteCollectionBooleanConstantSerializer.scala
@@ -36,7 +36,7 @@ case class ConcreteCollectionBooleanConstantSerializer(cons: (IndexedSeq[Value[S
// reusing pre-allocated immutable instances
Value.EmptySeq.asInstanceOf[IndexedSeq[Value[SBoolean.type]]]
} else {
- val items = new Array[BoolValue](size)
+ val items = ValueSerializer.newArray[BoolValue](size)
cfor(0)(_ < size, _ + 1) { i =>
items(i) = BooleanConstant.fromBoolean(bits(i))
}
diff --git a/sigmastate/src/main/scala/sigmastate/serialization/ConcreteCollectionSerializer.scala b/sigmastate/src/main/scala/sigmastate/serialization/ConcreteCollectionSerializer.scala
index d44f3adf2a..31ada23423 100644
--- a/sigmastate/src/main/scala/sigmastate/serialization/ConcreteCollectionSerializer.scala
+++ b/sigmastate/src/main/scala/sigmastate/serialization/ConcreteCollectionSerializer.scala
@@ -29,7 +29,7 @@ case class ConcreteCollectionSerializer(cons: (IndexedSeq[Value[SType]], SType)
// reusing pre-allocated immutable instances
Value.EmptySeq
} else {
- val values = new Array[SValue](size)
+ val values = ValueSerializer.newArray[SValue](size)
cfor(0)(_ < size, _ + 1) { i =>
val v = r.getValue() // READ
values(i) = v
diff --git a/sigmastate/src/main/scala/sigmastate/serialization/ErgoTreeSerializer.scala b/sigmastate/src/main/scala/sigmastate/serialization/ErgoTreeSerializer.scala
index a4aa1b33b7..6dee796e68 100644
--- a/sigmastate/src/main/scala/sigmastate/serialization/ErgoTreeSerializer.scala
+++ b/sigmastate/src/main/scala/sigmastate/serialization/ErgoTreeSerializer.scala
@@ -224,7 +224,7 @@ class ErgoTreeSerializer {
val nConsts = r.getUInt().toInt
if (nConsts > 0) {
// HOTSPOT:: allocate new array only if it is not empty
- val res = new Array[Constant[SType]](nConsts)
+ val res = ValueSerializer.newArray[Constant[SType]](nConsts)
cfor(0)(_ < nConsts, _ + 1) { i =>
res(i) = constantSerializer.deserialize(r)
}
@@ -246,9 +246,24 @@ class ErgoTreeSerializer {
(header, sizeOpt, constants, treeBytes)
}
+ /** Transforms serialized bytes of ErgoTree with segregated constants by
+ * replacing constants at given positions with new values. This operation
+ * allow to use serialized scripts as pre-defined templates.
+ * See [[sigmastate.SubstConstants]] for details.
+ *
+ * @param scriptBytes serialized ErgoTree with ConstantSegregationFlag set to 1.
+ * @param positions zero based indexes in ErgoTree.constants array which
+ * should be replaced with new values
+ * @param newVals new values to be injected into the corresponding
+ * positions in ErgoTree.constants array
+ * @return a pair (newBytes, len), where:
+ * newBytes - the original array scriptBytes such that only specified constants
+ * are replaced and all other bytes remain exactly the same
+ * len - length of the `constants` array of the given ErgoTree bytes
+ */
def substituteConstants(scriptBytes: Array[Byte],
positions: Array[Int],
- newVals: Array[Value[SType]])(implicit vs: SigmaValidationSettings): Array[Byte] = {
+ newVals: Array[Value[SType]])(implicit vs: SigmaValidationSettings): (Array[Byte], Int) = {
require(positions.length == newVals.length,
s"expected positions and newVals to have the same length, got: positions: ${positions.toSeq},\n newVals: ${newVals.toSeq}")
val r = SigmaSerializer.startReader(scriptBytes)
@@ -276,7 +291,7 @@ class ErgoTreeSerializer {
}
w.putBytes(treeBytes)
- w.toBytes
+ (w.toBytes, constants.length)
}
}
diff --git a/sigmastate/src/main/scala/sigmastate/serialization/FuncValueSerializer.scala b/sigmastate/src/main/scala/sigmastate/serialization/FuncValueSerializer.scala
index 5448963a65..2bb35be485 100644
--- a/sigmastate/src/main/scala/sigmastate/serialization/FuncValueSerializer.scala
+++ b/sigmastate/src/main/scala/sigmastate/serialization/FuncValueSerializer.scala
@@ -27,7 +27,7 @@ case class FuncValueSerializer(cons: (IndexedSeq[(Int, SType)], Value[SType]) =>
override def parse(r: SigmaByteReader): Value[SType] = {
val argsSize = r.getUInt().toIntExact
- val args = new Array[(Int, SType)](argsSize)
+ val args = ValueSerializer.newArray[(Int, SType)](argsSize)
cfor(0)(_ < argsSize, _ + 1) { i =>
val id = r.getUInt().toInt
val tpe = r.getType()
diff --git a/sigmastate/src/main/scala/sigmastate/serialization/GroupElementSerializer.scala b/sigmastate/src/main/scala/sigmastate/serialization/GroupElementSerializer.scala
index 4998ab94d7..d5aa3b8427 100644
--- a/sigmastate/src/main/scala/sigmastate/serialization/GroupElementSerializer.scala
+++ b/sigmastate/src/main/scala/sigmastate/serialization/GroupElementSerializer.scala
@@ -26,7 +26,7 @@ object GroupElementSerializer extends SigmaSerializer[EcPointType, EcPointType]
val normed = point.normalize()
val ySign = normed.getAffineYCoord.testBitZero()
val X = normed.getXCoord.getEncoded
- val PO = new Array[Byte](X.length + 1)
+ val PO = ValueSerializer.newArray[Byte](X.length + 1)
PO(0) = (if (ySign) 0x03 else 0x02).toByte
System.arraycopy(X, 0, PO, 1, X.length)
PO
diff --git a/sigmastate/src/main/scala/sigmastate/serialization/MethodCallSerializer.scala b/sigmastate/src/main/scala/sigmastate/serialization/MethodCallSerializer.scala
index 9e609a8763..3b86a30bf6 100644
--- a/sigmastate/src/main/scala/sigmastate/serialization/MethodCallSerializer.scala
+++ b/sigmastate/src/main/scala/sigmastate/serialization/MethodCallSerializer.scala
@@ -53,7 +53,7 @@ case class MethodCallSerializer(cons: (Value[SType], SMethod, IndexedSeq[Value[S
val types: Seq[SType] =
if (nArgs == 0) SType.EmptySeq
else {
- val types = new Array[SType](nArgs)
+ val types = ValueSerializer.newArray[SType](nArgs)
cfor(0)(_ < nArgs, _ + 1) { i =>
types(i) = args(i).tpe
}
diff --git a/sigmastate/src/main/scala/sigmastate/serialization/SigmaSerializer.scala b/sigmastate/src/main/scala/sigmastate/serialization/SigmaSerializer.scala
index c486cf34bb..bd00670129 100644
--- a/sigmastate/src/main/scala/sigmastate/serialization/SigmaSerializer.scala
+++ b/sigmastate/src/main/scala/sigmastate/serialization/SigmaSerializer.scala
@@ -10,6 +10,8 @@ import sigmastate.utils._
import scorex.util.serialization._
import sigmastate.serialization.OpCodes.OpCode
+import scala.reflect.ClassTag
+
object SigmaSerializer {
type Position = Int
type Consumed = Int
@@ -104,6 +106,17 @@ abstract class SigmaSerializer[TFamily, T <: TFamily] extends Serializer[TFamily
trait SigmaSerializerCompanion[TFamily] {
+ /** Maximum length of a serializable collection. */
+ val MaxArrayLength: Int = 100000
+
+ /** Allocates a new array with `len` items of type `A`. */
+ final def newArray[A](len: Int)(implicit tA: ClassTag[A]): Array[A] = {
+ if (len > MaxArrayLength)
+ throw new SerializerException(
+ s"Cannot allocate array of $tA with $len items: max limit is $MaxArrayLength")
+ new Array[A](len)
+ }
+
def getSerializer(opCode: OpCode): SigmaSerializer[TFamily, _ <: TFamily]
def deserialize(r: SigmaByteReader): TFamily
def serialize(v: TFamily, w: SigmaByteWriter): Unit
diff --git a/sigmastate/src/main/scala/sigmastate/serialization/TupleSerializer.scala b/sigmastate/src/main/scala/sigmastate/serialization/TupleSerializer.scala
index f579803f62..ee84f994ef 100644
--- a/sigmastate/src/main/scala/sigmastate/serialization/TupleSerializer.scala
+++ b/sigmastate/src/main/scala/sigmastate/serialization/TupleSerializer.scala
@@ -14,6 +14,7 @@ case class TupleSerializer(cons: Seq[Value[SType]] => Value[SType])
val itemInfo: DataInfo[SValue] = ArgInfo("item_i", "tuple's item in i-th position")
override def serialize(obj: Tuple, w: SigmaByteWriter): Unit = {
+ // TODO refactor: avoid usage of extension method `length`
val length = obj.length
w.putUByte(length, numItemsInfo)
foreach(numItemsInfo.info.name, obj.items) { i =>
@@ -23,7 +24,7 @@ case class TupleSerializer(cons: Seq[Value[SType]] => Value[SType])
override def parse(r: SigmaByteReader): Value[SType] = {
val size = r.getByte()
- val values = new Array[SValue](size) // assume size > 0 so always create a new array
+ val values = ValueSerializer.newArray[SValue](size) // assume size > 0 so always create a new array
cfor(0)(_ < size, _ + 1) { i =>
values(i) = r.getValue()
}
diff --git a/sigmastate/src/main/scala/sigmastate/serialization/TypeSerializer.scala b/sigmastate/src/main/scala/sigmastate/serialization/TypeSerializer.scala
index fb51333d51..4ac603714e 100644
--- a/sigmastate/src/main/scala/sigmastate/serialization/TypeSerializer.scala
+++ b/sigmastate/src/main/scala/sigmastate/serialization/TypeSerializer.scala
@@ -174,7 +174,7 @@ object TypeSerializer {
c match {
case STuple.TupleTypeCode => {
val len = r.getUByte()
- val items = new Array[SType](len)
+ val items = ValueSerializer.newArray[SType](len)
cfor(0)(_ < len, _ + 1) { i =>
items(i) = deserialize(r, depth + 1)
}
diff --git a/sigmastate/src/main/scala/sigmastate/serialization/ValDefSerializer.scala b/sigmastate/src/main/scala/sigmastate/serialization/ValDefSerializer.scala
index e485df7580..ff093d061f 100644
--- a/sigmastate/src/main/scala/sigmastate/serialization/ValDefSerializer.scala
+++ b/sigmastate/src/main/scala/sigmastate/serialization/ValDefSerializer.scala
@@ -33,7 +33,7 @@ case class ValDefSerializer(override val opDesc: ValueCompanion) extends ValueSe
val tpeArgs: Seq[STypeVar] = opCode match {
case FunDefCode =>
val nTpeArgs = r.getByte()
- val inputs = new Array[STypeVar](nTpeArgs)
+ val inputs = ValueSerializer.newArray[STypeVar](nTpeArgs)
cfor(0)(_ < nTpeArgs, _ + 1) { i =>
inputs(i) = r.getType().asInstanceOf[STypeVar]
}
diff --git a/sigmastate/src/main/scala/sigmastate/serialization/ValueSerializer.scala b/sigmastate/src/main/scala/sigmastate/serialization/ValueSerializer.scala
index bc826dbf71..af7b9a4a5e 100644
--- a/sigmastate/src/main/scala/sigmastate/serialization/ValueSerializer.scala
+++ b/sigmastate/src/main/scala/sigmastate/serialization/ValueSerializer.scala
@@ -197,7 +197,7 @@ object ValueSerializer extends SigmaSerializerCompanion[Value[SType]] {
def name = s"Serializer of ${serializer.opDesc}"
override def parent: Scope = null
override def showInScope(v: String): String = name + "/" + v
- override def toString: Idn = s"SerScope(${serializer.opDesc}, $children)"
+ override def toString: String = s"SerScope(${serializer.opDesc}, $children)"
}
case class DataScope(parent: Scope, data: DataInfo[_]) extends Scope {
diff --git a/sigmastate/src/main/scala/sigmastate/serialization/transformers/SigmaTransformerSerializer.scala b/sigmastate/src/main/scala/sigmastate/serialization/transformers/SigmaTransformerSerializer.scala
index ab195146af..7e30a5786a 100644
--- a/sigmastate/src/main/scala/sigmastate/serialization/transformers/SigmaTransformerSerializer.scala
+++ b/sigmastate/src/main/scala/sigmastate/serialization/transformers/SigmaTransformerSerializer.scala
@@ -18,7 +18,7 @@ case class SigmaTransformerSerializer[I <: SigmaPropValue, O <: SigmaPropValue]
override def parse(r: SigmaByteReader): SigmaPropValue = {
val itemsSize = r.getUInt().toInt
- val res = new Array[SigmaPropValue](itemsSize)
+ val res = ValueSerializer.newArray[SigmaPropValue](itemsSize)
cfor(0)(_ < itemsSize, _ + 1) { i =>
res(i) = r.getValue().asInstanceOf[SigmaPropValue]
}
diff --git a/sigmastate/src/main/scala/sigmastate/trees.scala b/sigmastate/src/main/scala/sigmastate/trees.scala
index d2c32cb158..d30592c1b3 100644
--- a/sigmastate/src/main/scala/sigmastate/trees.scala
+++ b/sigmastate/src/main/scala/sigmastate/trees.scala
@@ -2,16 +2,28 @@ package sigmastate
import org.ergoplatform.SigmaConstants
import org.ergoplatform.validation.SigmaValidationSettings
+import scalan.{ExactIntegral, ExactNumeric, ExactOrdering, Nullable}
import scalan.OverloadHack.Overloaded1
-import scorex.crypto.hash.{Sha256, Blake2b256, CryptographicHash32}
+import scorex.crypto.hash.{Blake2b256, CryptographicHash32, Sha256}
import sigmastate.Operations._
-import sigmastate.SCollection.{SIntArray, SByteArray}
+import sigmastate.SCollection.{SByteArray, SIntArray}
import sigmastate.SOption.SIntOption
import sigmastate.Values._
-import sigmastate.basics.{SigmaProtocol, SigmaProtocolPrivateInput, SigmaProtocolCommonInput}
+import sigmastate.basics.{SigmaProtocol, SigmaProtocolCommonInput, SigmaProtocolPrivateInput}
+import sigmastate.interpreter.ErgoTreeEvaluator
+import sigmastate.interpreter.ErgoTreeEvaluator.DataEnv
import sigmastate.serialization.OpCodes._
import sigmastate.serialization._
-import sigmastate.utxo.{Transformer, SimpleTransformerCompanion}
+import sigmastate.utxo.{SimpleTransformerCompanion, Transformer}
+import debox.{Map => DMap}
+import scalan.ExactIntegral._
+import scalan.ExactOrdering._
+import sigmastate.ArithOp.OperationImpl
+import sigmastate.eval.NumericOps.{BigIntIsExactIntegral, BigIntIsExactOrdering}
+import sigmastate.eval.{Colls, SigmaDsl}
+import sigmastate.lang.TransformingSigmaBuilder
+import special.collection.Coll
+import special.sigma.{GroupElement, SigmaProp}
import scala.collection.mutable
import scala.collection.mutable.ArrayBuffer
@@ -37,6 +49,7 @@ trait SigmaProofOfKnowledgeLeaf[SP <: SigmaProtocol[SP], S <: SigmaProtocolPriva
case class CAND(override val children: Seq[SigmaBoolean]) extends SigmaConjecture {
/** The same code is used for AND operation, but they belong to different type hierarchies. */
override val opCode: OpCode = OpCodes.AndCode
+ override val size: Int = SigmaBoolean.totalSize(children) + 1
}
object CAND {
@@ -72,6 +85,7 @@ object CAND {
case class COR(children: Seq[SigmaBoolean]) extends SigmaConjecture {
/** The same code is also used for OR operation, but they belong to different type hierarchies. */
override val opCode: OpCode = OpCodes.OrCode
+ override val size: Int = SigmaBoolean.totalSize(children) + 1
}
object COR {
@@ -109,6 +123,7 @@ case class CTHRESHOLD(k: Int, children: Seq[SigmaBoolean]) extends SigmaConjectu
require(k >= 0 && k <= children.length && children.length <= 255)
override val opCode: OpCode = OpCodes.AtLeastCode
+ override val size: Int = SigmaBoolean.totalSize(children) + 1
}
@@ -129,10 +144,12 @@ object TrivialProp {
val FalseProp = new TrivialProp(false) {
override val opCode: OpCode = OpCodes.TrivialPropFalseCode
+ override def size: Int = 1
override def toString = "FalseProp"
}
val TrueProp = new TrivialProp(true) {
override val opCode: OpCode = OpCodes.TrivialPropTrueCode
+ override def size: Int = 1
override def toString = "TrueProp"
}
}
@@ -145,9 +162,15 @@ case class BoolToSigmaProp(value: BoolValue) extends SigmaPropValue {
override def companion = BoolToSigmaProp
override def tpe = SSigmaProp
override def opType = BoolToSigmaProp.OpType
+ protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = {
+ val v = value.evalTo[Boolean](env)
+ addCost(BoolToSigmaProp.costKind)
+ SigmaDsl.sigmaProp(v)
+ }
}
object BoolToSigmaProp extends ValueCompanion {
override def opCode: OpCode = OpCodes.BoolToSigmaPropCode
+ override val costKind = FixedCost(15)
val OpType = SFunc(SBoolean, SSigmaProp)
}
@@ -157,9 +180,15 @@ case class CreateProveDlog(value: Value[SGroupElement.type]) extends SigmaPropVa
override def companion = CreateProveDlog
override def tpe = SSigmaProp
override def opType = CreateProveDlog.OpType
+ protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = {
+ val v = value.evalTo[GroupElement](env)
+ addCost(CreateProveDlog.costKind)
+ SigmaDsl.proveDlog(v)
+ }
}
object CreateProveDlog extends ValueCompanion {
override def opCode: OpCode = OpCodes.ProveDlogCode
+ override val costKind = FixedCost(10)
val OpType = SFunc(SGroupElement, SSigmaProp)
}
@@ -175,6 +204,7 @@ case class CreateAvlTree(operationFlags: ByteValue,
}
object CreateAvlTree extends ValueCompanion {
override def opCode: OpCode = OpCodes.AvlTreeCode
+ override def costKind: CostKind = Value.notSupportedError(this, "costKind")
val OpType = SFunc(Array(SByte, SByteArray, SInt, SIntOption), SAvlTree)
}
@@ -188,9 +218,18 @@ case class CreateProveDHTuple(gv: Value[SGroupElement.type],
override def companion = CreateProveDHTuple
override def tpe = SSigmaProp
override def opType = SFunc(IndexedSeq(SGroupElement, SGroupElement, SGroupElement, SGroupElement), SSigmaProp)
+ protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = {
+ val g = gv.evalTo[GroupElement](env)
+ val h = hv.evalTo[GroupElement](env)
+ val u = uv.evalTo[GroupElement](env)
+ val v = vv.evalTo[GroupElement](env)
+ addCost(CreateProveDHTuple.costKind)
+ SigmaDsl.proveDHTuple(g, h, u, v)
+ }
}
object CreateProveDHTuple extends ValueCompanion {
override def opCode: OpCode = OpCodes.ProveDiffieHellmanTupleCode
+ override val costKind = FixedCost(20)
}
trait SigmaTransformer[IV <: SigmaPropValue, OV <: SigmaPropValue] extends SigmaPropValue {
@@ -199,6 +238,7 @@ trait SigmaTransformer[IV <: SigmaPropValue, OV <: SigmaPropValue] extends Sigma
trait SigmaTransformerCompanion extends ValueCompanion {
def argInfos: Seq[ArgInfo]
}
+
/**
* AND conjunction for sigma propositions
*/
@@ -206,10 +246,25 @@ case class SigmaAnd(items: Seq[SigmaPropValue]) extends SigmaTransformer[SigmaPr
override def companion = SigmaAnd
override def tpe = SSigmaProp
override def opType = SigmaAnd.OpType
+ protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = {
+ val len = items.length
+ val is = new Array[SigmaProp](len)
+ cfor(0)(_ < len, _ + 1) { i =>
+ is(i) = items(i).evalTo[SigmaProp](env)
+ }
+ addSeqCost(SigmaAnd.costKind, len) { () =>
+ SigmaDsl.allZK(Colls.fromArray(is))
+ }
+ }
}
object SigmaAnd extends SigmaTransformerCompanion {
val OpType = SFunc(SCollection.SSigmaPropArray, SSigmaProp)
override def opCode: OpCode = OpCodes.SigmaAndCode
+ /** BaseCost:
+ * - constructing new CSigmaProp and allocation collection
+ * - one iteration over collection of items
+ */
+ override val costKind = PerItemCost(baseCost = 10, perChunkCost = 2, chunkSize = 1)
override def argInfos: Seq[ArgInfo] = SigmaAndInfo.argInfos
def apply(first: SigmaPropValue, second: SigmaPropValue, tail: SigmaPropValue*): SigmaAnd = SigmaAnd(Array(first, second) ++ tail)
}
@@ -221,11 +276,25 @@ case class SigmaOr(items: Seq[SigmaPropValue]) extends SigmaTransformer[SigmaPro
override def companion = SigmaOr
override def tpe = SSigmaProp
override def opType = SigmaOr.OpType
+ protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = {
+ val len = items.length
+ val is = new Array[SigmaProp](len)
+ cfor(0)(_ < len, _ + 1) { i =>
+ is(i) = items(i).evalTo[SigmaProp](env)
+ }
+ addSeqCost(SigmaOr.costKind, len) { () =>
+ SigmaDsl.anyZK(Colls.fromArray(is))
+ }
+ }
}
object SigmaOr extends SigmaTransformerCompanion {
val OpType = SFunc(SCollection.SSigmaPropArray, SSigmaProp)
override def opCode: OpCode = OpCodes.SigmaOrCode
+ /** BaseCost:
+ * - constructing new CSigmaProp and allocation collection
+ * - one iteration over collection of items */
+ override val costKind = PerItemCost(baseCost = 10, perChunkCost = 2, chunkSize = 1)
override def argInfos: Seq[ArgInfo] = SigmaOrInfo.argInfos
def apply(head: SigmaPropValue, tail: SigmaPropValue*): SigmaOr = SigmaOr(head +: tail)
}
@@ -243,10 +312,30 @@ case class OR(input: Value[SCollection[SBoolean.type]])
extends Transformer[SCollection[SBoolean.type], SBoolean.type] with NotReadyValueBoolean {
override def companion = OR
override def opType = OR.OpType
+ protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = {
+ val inputV = input.evalTo[Coll[Boolean]](env)
+ var res = false
+ val len = inputV.length
+ var i = 0
+ E.addSeqCost(OR.costKind, this.companion.opDesc) { () =>
+ // this loop is bounded since ErgoTree is bounded by MaxBoxSize
+ while (i < len && !res) {
+ res ||= inputV(i)
+ i += 1
+ }
+ i // return actual number of processed items
+ }
+ res
+ }
}
object OR extends LogicalTransformerCompanion {
override def opCode: OpCode = OrCode
+ /** Base cost: operations factored out of reduction loop.
+ * Per-chunk cost: cost of scala `||` operations amortized over a chunk of boolean values.
+ * @see BinOr
+ * @see AND */
+ override val costKind = PerItemCost(5, 5, 64/*size of cache line in bytes*/)
override def argInfos: Seq[ArgInfo] = Operations.ORInfo.argInfos
def apply(children: Seq[Value[SBoolean.type]]): OR =
@@ -261,10 +350,35 @@ case class XorOf(input: Value[SCollection[SBoolean.type]])
extends Transformer[SCollection[SBoolean.type], SBoolean.type] with NotReadyValueBoolean {
override def companion = XorOf
override def opType = XorOf.OpType
+ protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = {
+ val inputV = input.evalTo[Coll[Boolean]](env)
+ val len = inputV.length
+ addSeqCost(XorOf.costKind, len) { () =>
+ val res = if (E.context.activatedScriptVersion >= 2) {
+ if (len == 0) false
+ else if (len == 1) inputV(0)
+ else {
+ var res = inputV(0)
+ cfor(1)(_ < len, _ + 1) { i =>
+ res ^= inputV(i)
+ }
+ res
+ }
+ } else {
+ SigmaDsl.xorOf(inputV)
+ }
+ res
+ }
+ }
}
object XorOf extends LogicalTransformerCompanion {
override def opCode: OpCode = XorOfCode
+ /** Base cost: operations factored out of reduction loop.
+ * Per-chunk cost: cost of scala `||` operations amortized over a chunk of boolean values.
+ * @see BinOr
+ * @see AND */
+ override val costKind = PerItemCost(20, 5, 32)
override def argInfos: Seq[ArgInfo] = Operations.XorOfInfo.argInfos
def apply(children: Seq[Value[SBoolean.type]]): XorOf =
@@ -279,10 +393,30 @@ case class AND(input: Value[SCollection[SBoolean.type]])
with NotReadyValueBoolean {
override def companion = AND
override def opType = AND.OpType
+ protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = {
+ val inputV = input.evalTo[Coll[Boolean]](env)
+ var res = true
+ val len = inputV.length
+ var i = 0
+ E.addSeqCost(AND.costKind, this.companion.opDesc) { () =>
+ // this loop is bounded since ErgoTree is bounded by MaxBoxSize
+ while (i < len && res) {
+ res &&= inputV(i)
+ i += 1
+ }
+ i // return actual number of processed items
+ }
+ res
+ }
}
object AND extends LogicalTransformerCompanion {
override def opCode: OpCode = AndCode
+ /** Base cost: operations factored out of reduction loop.
+ * Per-chunk cost: cost of scala `&&` operations amortized over a chunk of boolean values.
+ * @see BinAnd
+ * @see OR */
+ override val costKind = PerItemCost(10, 5, 32/* half size of cache line in bytes */)
override def argInfos: Seq[ArgInfo] = Operations.ANDInfo.argInfos
def apply(children: Seq[Value[SBoolean.type]]): AND =
@@ -304,10 +438,21 @@ case class AtLeast(bound: Value[SInt.type], input: Value[SCollection[SSigmaProp.
override def tpe: SSigmaProp.type = SSigmaProp
override def opType: SFunc = AtLeast.OpType
+ protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = {
+ val b = bound.evalTo[Int](env)
+ val props = input.evalTo[Coll[SigmaProp]](env)
+ addSeqCost(AtLeast.costKind, props.length) { () =>
+ SigmaDsl.atLeast(b, props)
+ }
+ }
}
object AtLeast extends ValueCompanion {
override def opCode: OpCode = AtLeastCode
+ /** Base cost: constructing new CSigmaProp value
+ * Per chunk cost: obtaining SigmaBooleans for each chunk in AtLeast
+ */
+ override val costKind = PerItemCost(20, 3, 5)
val OpType: SFunc = SFunc(Array(SInt, SCollection.SBooleanArray), SBoolean)
val MaxChildrenCount: Int = SigmaConstants.MaxChildrenCountForAtLeastOp.value
@@ -379,12 +524,33 @@ case class Upcast[T <: SNumericType, R <: SNumericType](input: Value[T], tpe: R)
require(input.tpe.isInstanceOf[SNumericType], s"Cannot create Upcast node for non-numeric type ${input.tpe}")
override def companion = Upcast
override def opType = Upcast.OpType
+
+ protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = {
+ val inputV = input.evalTo[AnyVal](env)
+ addCost(Upcast.costKind, tpe) { () =>
+ tpe.upcast(inputV)
+ }
+ }
}
/** Base class for Upcast and Downcast companion objects. */
trait NumericCastCompanion extends ValueCompanion {
def argInfos: Seq[ArgInfo]
val OpType = SFunc(Array(SType.tT), SType.tR)
+ /** Returns cost descriptor of this operation. */
+ def costKind: TypeBasedCost = NumericCastCostKind
+}
+
+/** Cost of:
+ * 1) converting numeric value to the numeric value of the given type, i.e. Byte -> Int
+ * NOTE: the cost of BigInt casting is the same in JITC (comparing to AOTC) to simplify
+ * implementation.
+ */
+object NumericCastCostKind extends TypeBasedCost {
+ override def costFunc(targetTpe: SType): Int = targetTpe match {
+ case SBigInt => 30
+ case _ => 10
+ }
}
object Upcast extends NumericCastCompanion {
@@ -392,6 +558,7 @@ object Upcast extends NumericCastCompanion {
override def argInfos: Seq[ArgInfo] = UpcastInfo.argInfos
def tT = SType.tT
def tR = SType.tR
+ val BigIntOpType = SFunc(tT, SBigInt) // TODO check usage
}
/**
@@ -402,6 +569,12 @@ case class Downcast[T <: SNumericType, R <: SNumericType](input: Value[T], tpe:
require(input.tpe.isInstanceOf[SNumericType], s"Cannot create Downcast node for non-numeric type ${input.tpe}")
override def companion = Downcast
override def opType = Downcast.OpType
+ protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = {
+ val inputV = input.evalTo[AnyVal](env)
+ addCost(Downcast.costKind, tpe) { () =>
+ tpe.downcast(inputV)
+ }
+ }
}
object Downcast extends NumericCastCompanion {
@@ -409,6 +582,7 @@ object Downcast extends NumericCastCompanion {
override def argInfos: Seq[ArgInfo] = DowncastInfo.argInfos
def tT = SType.tT
def tR = SType.tR
+ val BigIntOpType = SFunc(SBigInt, tR) // TODO check usage
}
/**
@@ -418,10 +592,16 @@ case class LongToByteArray(input: Value[SLong.type])
extends Transformer[SLong.type, SByteArray] with NotReadyValueByteArray {
override def companion = LongToByteArray
override def opType = LongToByteArray.OpType
+ protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = {
+ val inputV = input.evalTo[Long](env)
+ addCost(LongToByteArray.costKind)
+ SigmaDsl.longToByteArray(inputV)
+ }
}
object LongToByteArray extends SimpleTransformerCompanion {
val OpType = SFunc(SLong, SByteArray)
override def opCode: OpCode = OpCodes.LongToByteArrayCode
+ override val costKind = FixedCost(17)
override def argInfos: Seq[ArgInfo] = LongToByteArrayInfo.argInfos
}
@@ -432,10 +612,16 @@ case class ByteArrayToLong(input: Value[SByteArray])
extends Transformer[SByteArray, SLong.type] with NotReadyValueLong {
override def companion = ByteArrayToLong
override def opType = ByteArrayToLong.OpType
+ protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = {
+ val inputV = input.evalTo[Coll[Byte]](env)
+ addCost(ByteArrayToLong.costKind)
+ SigmaDsl.byteArrayToLong(inputV)
+ }
}
object ByteArrayToLong extends SimpleTransformerCompanion {
val OpType = SFunc(SByteArray, SLong)
override def opCode: OpCode = OpCodes.ByteArrayToLongCode
+ override val costKind = FixedCost(16)
override def argInfos: Seq[ArgInfo] = ByteArrayToLongInfo.argInfos
}
@@ -446,10 +632,16 @@ case class ByteArrayToBigInt(input: Value[SByteArray])
extends Transformer[SByteArray, SBigInt.type] with NotReadyValueBigInt {
override def companion = ByteArrayToBigInt
override val opType = ByteArrayToBigInt.OpType
+ protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = {
+ val inputV = input.evalTo[Coll[Byte]](env)
+ addCost(ByteArrayToBigInt.costKind)
+ SigmaDsl.byteArrayToBigInt(inputV)
+ }
}
object ByteArrayToBigInt extends SimpleTransformerCompanion {
val OpType = SFunc(SByteArray, SBigInt)
override def opCode: OpCode = OpCodes.ByteArrayToBigIntCode
+ override val costKind = FixedCost(30)
override def argInfos: Seq[ArgInfo] = ByteArrayToBigIntInfo.argInfos
}
@@ -460,10 +652,20 @@ case class DecodePoint(input: Value[SByteArray])
extends Transformer[SByteArray, SGroupElement.type] with NotReadyValueGroupElement {
override def companion = DecodePoint
override def opType = DecodePoint.OpType
+ protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = {
+ val inputV = input.evalTo[Coll[Byte]](env)
+ addCost(DecodePoint.costKind)
+ SigmaDsl.decodePoint(inputV)
+ }
}
-object DecodePoint extends SimpleTransformerCompanion {
+object DecodePoint extends SimpleTransformerCompanion with FixedCostValueCompanion {
val OpType = SFunc(SByteArray, SGroupElement)
override def opCode: OpCode = OpCodes.DecodePointCode
+ /** Cost of:
+ * 1) create reader and read bytes in a new array
+ * 2) calling curve.decodePoint and obtain EcPoint
+ * 3) wrap EcPoint in GroupElement*/
+ override val costKind = FixedCost(300)
override def argInfos: Seq[ArgInfo] = DecodePointInfo.argInfos
}
@@ -481,9 +683,42 @@ object CalcHash {
case class CalcBlake2b256(override val input: Value[SByteArray]) extends CalcHash {
override def companion = CalcBlake2b256
override val hashFn: CryptographicHash32 = Blake2b256
+ protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = {
+ val inputV = input.evalTo[Coll[Byte]](env)
+ addSeqCost(CalcBlake2b256.costKind, inputV.length) { () =>
+ SigmaDsl.blake2b256(inputV)
+ }
+ }
}
object CalcBlake2b256 extends SimpleTransformerCompanion {
override def opCode: OpCode = OpCodes.CalcBlake2b256Code
+
+ /** Cost of: of hashing 1 block of data.
+ *
+ * This cost is used as a baseline to connect cost units with absolute time.
+ * The block validation have 1000000 of cost units budget, and we want this to
+ * correspond to 1 second. Thus we can assume 1 cost unit == 1 micro-second.
+ *
+ * It takes approximately 1 micro-seconds on average to compute hash of 128 bytes
+ * block on MacBook Pro (16-inch, 2019) 2.3 GHz 8-Core Intel Core i9.
+ *
+ * Thus per block cost of Blake2b256 hashing can be limited by 1 cost units.
+ * However, on a less powerful processor it may take much more time, so we add
+ * a factor of 3 for that. Additionally, the interpreter has overhead so that
+ * performing 1000 of hashes in a tight loop is 3-4 times faster than doing the same
+ * via ErgoTreeEvaluator. Thus we should add another factor of 2 and this takes
+ * place for all operations. So we will use a total factor of 10 to convert
+ * actual operation micro-seconds time (obtained via benchmarking) to cost unit
+ * estimation (used for cost prediction).
+ *
+ * Cost_in_units = time_in_micro-seconds * 7 = 7
+ *
+ * NOTE, 128 is the size of message chunk processed by Blake2b256 algorithm.
+ *
+ * @see [[sigmastate.interpreter.ErgoTreeEvaluator.DataBlockSize]]
+ */
+ override val costKind = PerItemCost(baseCost = 20, perChunkCost = 7, chunkSize = 128)
+
override def argInfos: Seq[ArgInfo] = CalcBlake2b256Info.argInfos
}
@@ -493,9 +728,17 @@ object CalcBlake2b256 extends SimpleTransformerCompanion {
case class CalcSha256(override val input: Value[SByteArray]) extends CalcHash {
override def companion = CalcSha256
override val hashFn: CryptographicHash32 = Sha256
+ protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = {
+ val inputV = input.evalTo[Coll[Byte]](env)
+ addSeqCost(CalcSha256.costKind, inputV.length) { () =>
+ SigmaDsl.sha256(inputV)
+ }
+ }
}
object CalcSha256 extends SimpleTransformerCompanion {
override def opCode: OpCode = OpCodes.CalcSha256Code
+ /** perChunkCost - cost of hashing 64 bytes of data (see also CalcBlake2b256). */
+ override val costKind = PerItemCost(baseCost = 80, perChunkCost = 8, chunkSize = 64)
override def argInfos: Seq[ArgInfo] = CalcSha256Info.argInfos
}
@@ -517,15 +760,54 @@ object CalcSha256 extends SimpleTransformerCompanion {
case class SubstConstants[T <: SType](scriptBytes: Value[SByteArray], positions: Value[SIntArray], newValues: Value[SCollection[T]])
extends NotReadyValueByteArray {
override def companion = SubstConstants
- override val opType = SFunc(Array(SByteArray, SIntArray, SCollection(SType.tT)), SByteArray)
+ override val opType = SubstConstants.OpType
+ protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = {
+ val scriptBytesV = scriptBytes.evalTo[Coll[Byte]](env)
+ val positionsV = positions.evalTo[Coll[Int]](env)
+ val newValuesV = newValues.evalTo[Coll[T#WrappedType]](env)
+ var res: Coll[Byte] = null
+ E.addSeqCost(SubstConstants.costKind, SubstConstants.opDesc) { () =>
+ val typedNewVals: Array[SValue] = newValuesV.toArray.map { v =>
+ TransformingSigmaBuilder.liftAny(v) match {
+ case Nullable(v) => v
+ case _ => sys.error(s"Cannot evaluate substConstants($scriptBytesV, $positionsV, $newValuesV): cannot lift value $v")
+ }
+ }
+
+ val (newBytes, nConstants) = SubstConstants.eval(
+ scriptBytes = scriptBytesV.toArray,
+ positions = positionsV.toArray,
+ newVals = typedNewVals)(SigmaDsl.validationSettings)
+
+ res = Colls.fromArray(newBytes)
+ nConstants
+ }
+ res
+ }
}
object SubstConstants extends ValueCompanion {
override def opCode: OpCode = OpCodes.SubstConstantsCode
+ override val costKind = PerItemCost(100, 100, 1)
+ val OpType = SFunc(Array(SByteArray, SIntArray, SCollection(SType.tT)), SByteArray)
+
+ /** Transforms serialized bytes of ErgoTree with segregated constants by
+ * replacing constants at given positions with new values. This operation
+ * allow to use serialized scripts as pre-defined templates.
+ * See [[sigmastate.SubstConstants]] for details.
+ *
+ * @param scriptBytes serialized ErgoTree with ConstantSegregationFlag set to 1.
+ * @param positions zero based indexes in ErgoTree.constants array which
+ * should be replaced with new values
+ * @param newVals new values to be injected into the corresponding
+ * positions in ErgoTree.constants array
+ * @return original scriptBytes array where only specified constants are
+ * replaced and all other bytes remain exactly the same
+ */
def eval(scriptBytes: Array[Byte],
positions: Array[Int],
- newVals: Array[Value[SType]])(implicit vs: SigmaValidationSettings): Array[Byte] =
+ newVals: Array[Value[SType]])(implicit vs: SigmaValidationSettings): (Array[Byte], Int) =
ErgoTreeSerializer.DefaultSerializer.substituteConstants(scriptBytes, positions, newVals)
}
@@ -553,9 +835,10 @@ trait TwoArgumentOperationCompanion extends ValueCompanion {
def argInfos: Seq[ArgInfo]
}
+/** Represents binary operation with the given opCode. */
case class ArithOp[T <: SType](left: Value[T], right: Value[T], override val opCode: OpCode)
extends TwoArgumentsOperation[T, T, T] with NotReadyValue[T] {
- override def companion: ValueCompanion = ArithOp.operations(opCode)
+ override def companion: ArithOpCompanion = ArithOp.operations(opCode)
override def tpe: T = left.tpe
override val opType = SFunc(Array[SType](left.tpe, right.tpe), tpe)
override def opName: String = ArithOp.opcodeToArithOpName(opCode)
@@ -570,41 +853,183 @@ case class ArithOp[T <: SType](left: Value[T], right: Value[T], override val opC
case OpCodes.MinCode => s"Min($left, $right)"
case OpCodes.MaxCode => s"Max($left, $right)"
}
+
+ protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = {
+ val x = left.evalTo[Any](env)
+ val y = right.evalTo[Any](env)
+ companion.eval(this, tpe.typeCode, x, y) // NOTE: cost is added as part of eval call
+ }
}
/** NOTE: by-name argument is required for correct initialization order. */
-class ArithOpCompanion(val opCode: OpCode, val name: String, _argInfos: => Seq[ArgInfo]) extends TwoArgumentOperationCompanion {
+abstract class ArithOpCompanion(val opCode: OpCode, val name: String, _argInfos: => Seq[ArgInfo])
+ extends TwoArgumentOperationCompanion {
override def argInfos: Seq[ArgInfo] = _argInfos
+ override def costKind: TypeBasedCost
+ @inline final def eval(node: SValue, typeCode: SType.TypeCode, x: Any, y: Any)(implicit E: ErgoTreeEvaluator): Any = {
+ val impl = ArithOp.impls(typeCode)
+ node.addCost(costKind, impl.argTpe) { () =>
+ eval(impl, x, y)
+ }
+ }
+ def eval(impl: OperationImpl, x: Any, y: Any): Any
}
+
object ArithOp {
import OpCodes._
- object Plus extends ArithOpCompanion(PlusCode, "+", PlusInfo.argInfos)
- object Minus extends ArithOpCompanion(MinusCode, "-", MinusInfo.argInfos)
- object Multiply extends ArithOpCompanion(MultiplyCode, "*", MultiplyInfo.argInfos)
- object Division extends ArithOpCompanion(DivisionCode, "/", DivisionInfo.argInfos)
- object Modulo extends ArithOpCompanion(ModuloCode, "%", ModuloInfo.argInfos)
- object Min extends ArithOpCompanion(MinCode, "min", MinInfo.argInfos)
- object Max extends ArithOpCompanion(MaxCode, "max", MaxInfo.argInfos)
- val operations: Map[Byte, ArithOpCompanion] =
- Seq(Plus, Minus, Multiply, Division, Modulo, Min, Max).map(o => (o.opCode, o)).toMap
+ /** Addition operation `x + y`. */
+ object Plus extends ArithOpCompanion(PlusCode, "+", PlusInfo.argInfos) {
+ override def eval(impl: OperationImpl, x: Any, y: Any): Any = impl.i.plus(x, y)
+ /** Cost of:
+ * 1) resolving ArithOpCompanion by typeCode
+ * 2) calling method of Numeric
+ */
+ override val costKind = new TypeBasedCost {
+ override def costFunc(tpe: SType): Int = tpe match {
+ case SBigInt => 20
+ case _ => 15
+ }
+ }
+ }
+
+ /** Subtraction operation `x - y`. */
+ object Minus extends ArithOpCompanion(MinusCode, "-", MinusInfo.argInfos) {
+ override def eval(impl: OperationImpl, x: Any, y: Any): Any = impl.i.minus(x, y)
+ /** Cost of:
+ * 1) resolving ArithOpCompanion by typeCode
+ * 2) calling method of Numeric
+ */
+ override val costKind = new TypeBasedCost {
+ override def costFunc(tpe: SType): Int = tpe match {
+ case SBigInt => 20
+ case _ => 15
+ }
+ }
+ }
+
+ /** Multiplication operation `x * y`. */
+ object Multiply extends ArithOpCompanion(MultiplyCode, "*", MultiplyInfo.argInfos) {
+ override def eval(impl: OperationImpl, x: Any, y: Any): Any = impl.i.times(x, y)
+ /** Cost of:
+ * 1) resolving ArithOpCompanion by typeCode
+ * 2) calling method of Numeric
+ */
+ override val costKind = new TypeBasedCost {
+ override def costFunc(tpe: SType): Int = tpe match {
+ case SBigInt => 25
+ case _ => 15
+ }
+ }
+ }
+
+ /** Integer division operation `x / y`. */
+ object Division extends ArithOpCompanion(DivisionCode, "/", DivisionInfo.argInfos) {
+ override def eval(impl: OperationImpl, x: Any, y: Any): Any = impl.i.quot(x, y)
+ /** Cost of:
+ * 1) resolving ArithOpCompanion by typeCode
+ * 2) calling method of Integral
+ */
+ override val costKind = new TypeBasedCost {
+ override def costFunc(tpe: SType): Int = tpe match {
+ case SBigInt => 25
+ case _ => 15
+ }
+ }
+ }
+
+ /** Operation which returns remainder from dividing x by y.
+ * See ExactIntegral.divisionRemainder implementation for the concrete numeric type.
+ */
+ object Modulo extends ArithOpCompanion(ModuloCode, "%", ModuloInfo.argInfos) {
+ override def eval(impl: OperationImpl, x: Any, y: Any): Any = impl.i.divisionRemainder(x, y)
+ /** Cost of:
+ * 1) resolving ArithOpCompanion by typeCode
+ * 2) calling method of Integral
+ */
+ override val costKind = new TypeBasedCost {
+ override def costFunc(tpe: SType): Int = tpe match {
+ case SBigInt => 25
+ case _ => 15
+ }
+ }
+ }
+
+ /** Return `x` if `x` <= `y`, otherwise `y`. */
+ object Min extends ArithOpCompanion(MinCode, "min", MinInfo.argInfos) {
+ override def eval(impl: OperationImpl, x: Any, y: Any): Any = impl.o.min(x, y)
+ /** Cost of:
+ * 1) resolving ArithOpCompanion by typeCode
+ * 2) calling method of ExactOrdering
+ */
+ override val costKind = new TypeBasedCost {
+ override def costFunc(tpe: SType): Int = tpe match {
+ case SBigInt => 10
+ case _ => 5
+ }
+ }
+ }
+
+ /** Return `x` if `x` >= `y`, otherwise `y`. */
+ object Max extends ArithOpCompanion(MaxCode, "max", MaxInfo.argInfos) {
+ override def eval(impl: OperationImpl, x: Any, y: Any): Any = impl.o.max(x, y)
+ /** Cost of:
+ * 1) resolving ArithOpCompanion by typeCode
+ * 2) calling method of ExactOrdering
+ */
+ override val costKind = new TypeBasedCost {
+ override def costFunc(tpe: SType): Int = tpe match {
+ case SBigInt => 10
+ case _ => 5
+ }
+ }
+ }
+
+ private[sigmastate] val operations: DMap[Byte, ArithOpCompanion] =
+ DMap.fromIterable(Seq(Plus, Minus, Multiply, Division, Modulo, Min, Max).map(o => (o.opCode, o)))
+
+ /** Represents implementation of numeric Arith operations for the given type argTpe. */
+ class OperationImpl(_i: ExactIntegral[_], _o: ExactOrdering[_], val argTpe: SType) {
+ val i = _i.asInstanceOf[ExactIntegral[Any]]
+ val o = _o.asInstanceOf[ExactOrdering[Any]]
+ }
+
+ private[sigmastate] val impls: DMap[SType.TypeCode, OperationImpl] =
+ DMap.fromIterable(Seq(
+ SByte -> new OperationImpl(ByteIsExactIntegral, ByteIsExactOrdering, SByte),
+ SShort -> new OperationImpl(ShortIsExactIntegral, ShortIsExactOrdering, SShort),
+ SInt -> new OperationImpl(IntIsExactIntegral, IntIsExactOrdering, SInt),
+ SLong -> new OperationImpl(LongIsExactIntegral, LongIsExactOrdering, SLong),
+ SBigInt -> new OperationImpl(BigIntIsExactIntegral, BigIntIsExactOrdering, SBigInt)
+ ).map { case (t, n) => (t.typeCode, n) })
+ /** Returns operation name for the given opCode. */
def opcodeToArithOpName(opCode: Byte): String = operations.get(opCode) match {
case Some(c) => c.name
case _ => sys.error(s"Cannot find ArithOpName for opcode $opCode")
}
}
-/** Negation operation on numeric type T. */
+/** Negation operation on numeric type T.
+ * See ExactNumeric instance for the corresponding type T.
+ */
case class Negation[T <: SType](input: Value[T]) extends OneArgumentOperation[T, T] {
require(input.tpe.isNumTypeOrNoType, s"invalid type ${input.tpe}")
override def companion = Negation
override def tpe: T = input.tpe
+ protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = {
+ val inputV = input.evalTo[AnyVal](env)
+ val i = ArithOp.impls(input.tpe.typeCode).i
+ addCost(Negation.costKind)
+ i.negate(inputV)
+ }
}
object Negation extends OneArgumentOperationCompanion {
override def opCode: OpCode = OpCodes.NegationCode
+ override val costKind = FixedCost(30)
override def argInfos: Seq[ArgInfo] = NegationInfo.argInfos
}
+/** Not implemented in v4.x. */
case class BitInversion[T <: SType](input: Value[T]) extends OneArgumentOperation[T, T] {
require(input.tpe.isNumTypeOrNoType, s"invalid type ${input.tpe}")
override def companion = BitInversion
@@ -612,9 +1037,11 @@ case class BitInversion[T <: SType](input: Value[T]) extends OneArgumentOperatio
}
object BitInversion extends OneArgumentOperationCompanion {
override def opCode: OpCode = OpCodes.BitInversionCode
+ override def costKind: CostKind = Value.notSupportedError(this, "costKind")
override def argInfos: Seq[ArgInfo] = BitInversionInfo.argInfos
}
+/** ErgoTree node which represents a binary bit-wise operation with the given opCode. */
case class BitOp[T <: SType](left: Value[T], right: Value[T], override val opCode: OpCode)
extends TwoArgumentsOperation[T, T, T] with NotReadyValue[T] {
require(left.tpe.isNumTypeOrNoType && right.tpe.isNumTypeOrNoType, s"invalid types left:${left.tpe}, right:${right.tpe}")
@@ -623,18 +1050,30 @@ case class BitOp[T <: SType](left: Value[T], right: Value[T], override val opCod
override val opType = SFunc(Array[SType](left.tpe, right.tpe), tpe)
}
/** NOTE: by-name argument is required for correct initialization order. */
-class BitOpCompanion(val opCode: OpCode, val name: String, _argInfos: => Seq[ArgInfo]) extends TwoArgumentOperationCompanion {
+abstract class BitOpCompanion(val opCode: OpCode, val name: String, _argInfos: => Seq[ArgInfo]) extends TwoArgumentOperationCompanion {
override def argInfos: Seq[ArgInfo] = _argInfos
}
object BitOp {
import OpCodes._
- object BitOr extends BitOpCompanion(BitOrCode, "|", BitOrInfo.argInfos)
- object BitAnd extends BitOpCompanion(BitAndCode, "&", BitAndInfo.argInfos)
- object BitXor extends BitOpCompanion(BitXorCode, "^", BitXorInfo.argInfos)
- object BitShiftRight extends BitOpCompanion(BitShiftRightCode, ">>", BitShiftRightInfo.argInfos)
- object BitShiftLeft extends BitOpCompanion(BitShiftLeftCode, "<<", BitShiftLeftInfo.argInfos)
- object BitShiftRightZeroed extends BitOpCompanion(BitShiftRightZeroedCode, ">>>", BitShiftRightZeroedInfo.argInfos)
+ object BitOr extends BitOpCompanion(BitOrCode, "|", BitOrInfo.argInfos) {
+ override val costKind = FixedCost(1)
+ }
+ object BitAnd extends BitOpCompanion(BitAndCode, "&", BitAndInfo.argInfos) {
+ override val costKind = FixedCost(1)
+ }
+ object BitXor extends BitOpCompanion(BitXorCode, "^", BitXorInfo.argInfos) {
+ override val costKind = FixedCost(1)
+ }
+ object BitShiftRight extends BitOpCompanion(BitShiftRightCode, ">>", BitShiftRightInfo.argInfos) {
+ override val costKind = FixedCost(1)
+ }
+ object BitShiftLeft extends BitOpCompanion(BitShiftLeftCode, "<<", BitShiftLeftInfo.argInfos) {
+ override val costKind = FixedCost(1)
+ }
+ object BitShiftRightZeroed extends BitOpCompanion(BitShiftRightZeroedCode, ">>>", BitShiftRightZeroedInfo.argInfos) {
+ override val costKind = FixedCost(1)
+ }
val operations: Map[Byte, BitOpCompanion] =
Seq(BitOr, BitAnd, BitXor, BitShiftRight, BitShiftLeft, BitShiftRightZeroed).map(o => (o.opCode, o)).toMap
@@ -654,6 +1093,7 @@ case class ModQ(input: Value[SBigInt.type])
}
object ModQ extends ValueCompanion {
override def opCode: OpCode = OpCodes.ModQCode
+ override val costKind: CostKind = FixedCost(1)
}
case class ModQArithOp(left: Value[SBigInt.type], right: Value[SBigInt.type], override val opCode: OpCode)
@@ -664,6 +1104,7 @@ case class ModQArithOp(left: Value[SBigInt.type], right: Value[SBigInt.type], ov
}
abstract class ModQArithOpCompanion(val opCode: OpCode, val name: String) extends ValueCompanion {
def argInfos: Seq[ArgInfo]
+ override val costKind: CostKind = FixedCost(1)
}
trait OpGroup[C <: ValueCompanion] {
@@ -699,10 +1140,18 @@ case class Xor(override val left: Value[SByteArray],
with NotReadyValueByteArray {
override def companion = Xor
override def opType = Xor.OpType
+ protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = {
+ val lV = left.evalTo[Coll[Byte]](env)
+ val rV = right.evalTo[Coll[Byte]](env)
+ addSeqCost(Xor.costKind, lV.length) { () =>
+ Colls.xor(lV, rV)
+ }
+ }
}
object Xor extends TwoArgumentOperationCompanion {
val OpType = SFunc(Array(SByteArray, SByteArray), SByteArray)
override def opCode: OpCode = XorCode
+ override val costKind = PerItemCost(10, 2, 128)
override def argInfos: Seq[ArgInfo] = XorInfo.argInfos
}
@@ -712,10 +1161,19 @@ case class Exponentiate(override val left: Value[SGroupElement.type],
with NotReadyValueGroupElement {
override def companion = Exponentiate
override def opType = Exponentiate.OpType
+
+ protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = {
+ val leftV = left.evalTo[GroupElement](env)
+ val rightV = right.evalTo[special.sigma.BigInt](env)
+ addCost(Exponentiate.costKind)
+ leftV.exp(rightV)
+ }
}
-object Exponentiate extends TwoArgumentOperationCompanion {
+object Exponentiate extends TwoArgumentOperationCompanion with FixedCostValueCompanion {
val OpType = SFunc(Array(SGroupElement, SBigInt), SGroupElement)
override def opCode: OpCode = ExponentiateCode
+ /** Cost of: 1) calling EcPoint.multiply 2) wrapping in GroupElement */
+ override val costKind = FixedCost(900)
override def argInfos: Seq[ArgInfo] = ExponentiateInfo.argInfos
}
@@ -725,10 +1183,18 @@ case class MultiplyGroup(override val left: Value[SGroupElement.type],
with NotReadyValueGroupElement {
override def companion = MultiplyGroup
override def opType = MultiplyGroup.OpType
+ protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = {
+ val leftV = left.evalTo[GroupElement](env)
+ val rightV = right.evalTo[GroupElement](env)
+ addCost(MultiplyGroup.costKind)
+ leftV.multiply(rightV)
+ }
}
-object MultiplyGroup extends TwoArgumentOperationCompanion {
+object MultiplyGroup extends TwoArgumentOperationCompanion with FixedCostValueCompanion {
val OpType = SFunc(Array(SGroupElement, SGroupElement), SGroupElement)
override def opCode: OpCode = MultiplyGroupCode
+ /** Cost of: 1) calling EcPoint.add 2) wrapping in GroupElement */
+ override val costKind = FixedCost(40)
override def argInfos: Seq[ArgInfo] = MultiplyGroupInfo.argInfos
}
// Relation
@@ -738,6 +1204,7 @@ sealed trait Relation[LIV <: SType, RIV <: SType] extends Triple[LIV, RIV, SBool
trait SimpleRelation[T <: SType] extends Relation[T, T] {
override def opType = SimpleRelation.GenericOpType
+ lazy val opImpl = ArithOp.impls(left.tpe.typeCode)
}
object SimpleRelation {
val GenericOpType = SFunc(SType.IndexedSeqOfT2, SBoolean)
@@ -752,9 +1219,26 @@ trait RelationCompanion extends ValueCompanion {
*/
case class LT[T <: SType](override val left: Value[T], override val right: Value[T]) extends SimpleRelation[T] {
override def companion = LT
+ protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = {
+ val lV = left.evalTo[Any](env)
+ val rV = right.evalTo[Any](env)
+ addCost(LT.costKind, left.tpe) { () =>
+ opImpl.o.lt(lV, rV)
+ }
+ }
}
object LT extends RelationCompanion {
override def opCode: OpCode = LtCode
+ /** Cost of:
+ * 1) resolving ArithOpCompanion by typeCode
+ * 2) calling method of Numeric
+ */
+ override val costKind = new TypeBasedCost {
+ override def costFunc(tpe: SType): Int = tpe match {
+ case SBigInt => 20
+ case _ => 20
+ }
+ }
override def argInfos: Seq[ArgInfo] = LTInfo.argInfos
}
/**
@@ -762,19 +1246,53 @@ object LT extends RelationCompanion {
*/
case class LE[T <: SType](override val left: Value[T], override val right: Value[T]) extends SimpleRelation[T] {
override def companion = LE
+ protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = {
+ val lV = left.evalTo[Any](env)
+ val rV = right.evalTo[Any](env)
+ addCost(LE.costKind, left.tpe) { () =>
+ opImpl.o.lteq(lV, rV)
+ }
+ }
}
object LE extends RelationCompanion {
override def opCode: OpCode = LeCode
+ /** Cost of:
+ * 1) resolving ArithOpCompanion by typeCode
+ * 2) calling method of Numeric
+ */
+ override val costKind = new TypeBasedCost {
+ override def costFunc(tpe: SType): Int = tpe match {
+ case SBigInt => 20 // cf. comparisonBigInt
+ case _ => 20 // cf. comparisonCost
+ }
+ }
override def argInfos: Seq[ArgInfo] = LEInfo.argInfos
}
/**
- * Greater operation for SInt
+ * Greater operation for [[SNumericType]] values
*/
case class GT[T <: SType](override val left: Value[T], override val right: Value[T]) extends SimpleRelation[T] {
override def companion = GT
+ protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = {
+ val lV = left.evalTo[Any](env)
+ val rV = right.evalTo[Any](env)
+ addCost(GT.costKind, left.tpe) { () =>
+ opImpl.o.gt(lV, rV)
+ }
+ }
}
object GT extends RelationCompanion {
override def opCode: OpCode = GtCode
+ /** Cost of:
+ * 1) resolving ArithOpCompanion by typeCode
+ * 2) calling method of Numeric
+ */
+ override val costKind = new TypeBasedCost {
+ override def costFunc(tpe: SType): Int = tpe match {
+ case SBigInt => 20 // cf. comparisonBigInt
+ case _ => 20 // cf. comparisonCost
+ }
+ }
override def argInfos: Seq[ArgInfo] = GTInfo.argInfos
}
/**
@@ -782,9 +1300,26 @@ object GT extends RelationCompanion {
*/
case class GE[T <: SType](override val left: Value[T], override val right: Value[T]) extends SimpleRelation[T] {
override def companion = GE
+ protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = {
+ val lV = left.evalTo[Any](env)
+ val rV = right.evalTo[Any](env)
+ addCost(GE.costKind, left.tpe) { () =>
+ opImpl.o.gteq(lV, rV)
+ }
+ }
}
object GE extends RelationCompanion {
override def opCode: OpCode = GeCode
+ /** Cost of:
+ * 1) resolving ArithOpCompanion by typeCode
+ * 2) calling method of Numeric
+ */
+ override val costKind = new TypeBasedCost {
+ override def costFunc(tpe: SType): Int = tpe match {
+ case SBigInt => 20
+ case _ => 20
+ }
+ }
override def argInfos: Seq[ArgInfo] = GEInfo.argInfos
}
@@ -792,12 +1327,21 @@ object GE extends RelationCompanion {
* Equals operation for SType
* todo: make EQ to really accept only values of the same type, now EQ(TrueLeaf, IntConstant(5)) is valid
*/
-case class EQ[S <: SType](override val left: Value[S], override val right: Value[S])
- extends SimpleRelation[S] {
+case class EQ[S <: SType](
+ override val left: Value[S],
+ override val right: Value[S]) extends SimpleRelation[S] {
override def companion = EQ
+ protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = {
+ val l = left.evalTo[S#WrappedType](env)
+ Value.checkType(left, l) // necessary because cast to S#WrappedType is erased
+ val r = right.evalTo[S#WrappedType](env)
+ Value.checkType(right, r) // necessary because cast to S#WrappedType is erased
+ DataValueComparer.equalDataValues(l, r)
+ }
}
object EQ extends RelationCompanion {
override def opCode: OpCode = EqCode
+ override def costKind = DynamicCost
override def argInfos: Seq[ArgInfo] = EQInfo.argInfos
}
@@ -807,9 +1351,17 @@ object EQ extends RelationCompanion {
case class NEQ[S <: SType](override val left: Value[S], override val right: Value[S])
extends SimpleRelation[S] {
override def companion = NEQ
+ protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = {
+ val l = left.evalTo[S#WrappedType](env)
+ Value.checkType(left, l) // necessary because cast to S#WrappedType is erased
+ val r = right.evalTo[S#WrappedType](env)
+ Value.checkType(right, r) // necessary because cast to S#WrappedType is erased
+ !DataValueComparer.equalDataValues(l, r)
+ }
}
object NEQ extends RelationCompanion {
override def opCode: OpCode = NeqCode
+ override def costKind = DynamicCost
override def argInfos: Seq[ArgInfo] = NEQInfo.argInfos
}
@@ -821,10 +1373,18 @@ case class BinOr(override val left: BoolValue, override val right: BoolValue)
extends Relation[SBoolean.type, SBoolean.type] {
override def companion = BinOr
override def opType = BinOr.OpType
+ protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = {
+ val l = left.evalTo[Boolean](env)
+ addCost(BinOr.costKind)
+ l || right.evalTo[Boolean](env) // rely on short-cutting semantics of Scala's ||
+ }
}
-object BinOr extends RelationCompanion {
+object BinOr extends RelationCompanion with FixedCostValueCompanion {
val OpType = SFunc(Array(SBoolean, SBoolean), SBoolean)
override def opCode: OpCode = BinOrCode
+ /** Cost of: scala `||` operation
+ * Old cost: ("BinOr", "(Boolean, Boolean) => Boolean", logicCost) */
+ override val costKind = FixedCost(20)
override def argInfos: Seq[ArgInfo] = BinOrInfo.argInfos
}
@@ -836,10 +1396,18 @@ case class BinAnd(override val left: BoolValue, override val right: BoolValue)
extends Relation[SBoolean.type, SBoolean.type] {
override def companion = BinAnd
override def opType = BinAnd.OpType
+ protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = {
+ val l = left.evalTo[Boolean](env)
+ addCost(BinAnd.costKind)
+ l && right.evalTo[Boolean](env) // rely on short-cutting semantics of Scala's &&
+ }
}
-object BinAnd extends RelationCompanion {
+object BinAnd extends RelationCompanion with FixedCostValueCompanion {
val OpType = SFunc(Array(SBoolean, SBoolean), SBoolean)
override def opCode: OpCode = BinAndCode
+ /** Cost of: scala `&&` operation
+ * Old cost: ("BinAnd", "(Boolean, Boolean) => Boolean", logicCost) */
+ override val costKind = FixedCost(20)
override def argInfos: Seq[ArgInfo] = BinAndInfo.argInfos
}
@@ -847,10 +1415,19 @@ case class BinXor(override val left: BoolValue, override val right: BoolValue)
extends Relation[SBoolean.type, SBoolean.type] {
override def companion = BinXor
override def opType = BinXor.OpType
+ protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = {
+ val leftV = left.evalTo[Boolean](env)
+ val rightV = right.evalTo[Boolean](env)
+ addCost(BinXor.costKind)
+ leftV ^ rightV
+ }
}
-object BinXor extends RelationCompanion {
+object BinXor extends RelationCompanion with FixedCostValueCompanion {
val OpType = SFunc(Array(SBoolean, SBoolean), SBoolean)
override def opCode: OpCode = BinXorCode
+ /** Cost of: scala `^` operation
+ * Old cost: ("BinXor", "(Boolean, Boolean) => Boolean", logicCost) */
+ override val costKind = FixedCost(20)
override def argInfos: Seq[ArgInfo] = BinXorInfo.argInfos
}
@@ -886,6 +1463,7 @@ trait QuadrupleCompanion extends ValueCompanion {
}
object TreeLookup extends QuadrupleCompanion {
override def opCode: OpCode = OpCodes.AvlTreeGetCode
+ override def costKind: CostKind = Value.notSupportedError(this, "costKind")
override def argInfos: Seq[ArgInfo] = TreeLookupInfo.argInfos
}
@@ -904,9 +1482,25 @@ case class If[T <: SType](condition: Value[SBoolean.type], trueBranch: Value[T],
override lazy val first = condition
override lazy val second = trueBranch
override lazy val third = falseBranch
+ protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = {
+ val c = condition.evalTo[Boolean](env)
+ addCost(If.costKind)
+ if (c) {
+ val res = trueBranch.evalTo[T#WrappedType](env)
+ Value.checkType(trueBranch, res) // necessary because cast to T#WrappedType is erased
+ res
+ } else {
+ val res = falseBranch.evalTo[T#WrappedType](env)
+ Value.checkType(falseBranch, res) // necessary because cast to T#WrappedType is erased
+ res
+ }
+ }
}
object If extends QuadrupleCompanion {
override def opCode: OpCode = OpCodes.IfCode
+ /** Cost of: conditional switching to the right branch (excluding the cost both
+ * condition itself and the branches) */
+ override val costKind = FixedCost(10)
override def argInfos: Seq[ArgInfo] = IfInfo.argInfos
val GenericOpType = SFunc(Array(SBoolean, SType.tT, SType.tT), SType.tT)
}
@@ -914,10 +1508,17 @@ object If extends QuadrupleCompanion {
case class LogicalNot(input: Value[SBoolean.type]) extends NotReadyValueBoolean {
override def companion = LogicalNot
override def opType = LogicalNot.OpType
+ protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = {
+ val inputV = input.evalTo[Boolean](env)
+ addCost(LogicalNot.costKind)
+ !inputV
+ }
}
-object LogicalNot extends ValueCompanion {
+object LogicalNot extends FixedCostValueCompanion {
val OpType = SFunc(Array(SBoolean), SBoolean)
override def opCode: OpCode = OpCodes.LogicalNotCode
+ /** Cost of: scala `!` operation */
+ override val costKind = FixedCost(15)
}
diff --git a/sigmastate/src/main/scala/sigmastate/types.scala b/sigmastate/src/main/scala/sigmastate/types.scala
index 4f9f604d20..77c3fdce1a 100644
--- a/sigmastate/src/main/scala/sigmastate/types.scala
+++ b/sigmastate/src/main/scala/sigmastate/types.scala
@@ -1,18 +1,22 @@
package sigmastate
+import java.lang.reflect.Method
import java.math.BigInteger
import org.ergoplatform._
import org.ergoplatform.validation._
-import scalan.{RType, Nullable}
+import scalan.{Nullable, RType}
import scalan.RType.GeneralType
import sigmastate.SType.{TypeCode, AnyOps}
-import sigmastate.interpreter.CryptoConstants
+import sigmastate.interpreter._
import sigmastate.utils.Overloading.Overload1
import sigmastate.utils.SparseArrayContainer
import scalan.util.Extensions._
+import scorex.crypto.authds.{ADKey, ADValue}
+import scorex.crypto.authds.avltree.batch.{Lookup, Insert, Update, Remove}
+import scorex.crypto.hash.Blake2b256
import sigmastate.Values._
-import sigmastate.lang.Terms._
+import sigmastate.lang.Terms.{MethodCall, _}
import sigmastate.lang.{SigmaBuilder, SigmaTyper}
import sigmastate.SCollection._
import sigmastate.interpreter.CryptoConstants.{EcPointType, hashLength}
@@ -23,7 +27,7 @@ import sigmastate.eval.RuntimeCosting
import scala.language.implicitConversions
import scala.reflect.{ClassTag, classTag}
-import sigmastate.SMethod.MethodCallIrBuilder
+import sigmastate.SMethod.{InvokeDescBuilder, MethodCostFunc, givenCost, javaMethodOf, MethodCallIrBuilder}
import sigmastate.utxo.ByIndex
import sigmastate.utxo.ExtractCreationInfo
import sigmastate.utxo._
@@ -31,8 +35,12 @@ import special.sigma.{Header, Box, SigmaProp, AvlTree, SigmaDslBuilder, PreHeade
import sigmastate.lang.SigmaTyper.STypeSubst
import sigmastate.eval.Evaluation.stypeToRType
import sigmastate.eval._
+import sigmastate.lang.exceptions.MethodNotFound
import spire.syntax.all.cfor
+import scala.collection.mutable
+import scala.util.{Success, Failure}
+
/** Base type for all AST nodes of sigma lang. */
trait SigmaNode extends Product
@@ -115,6 +123,7 @@ object SType {
/** Costructs a collection type with the given type of elements. */
implicit def typeCollection[V <: SType](implicit tV: V): SCollection[V] = SCollection[V](tV)
+ /** RType descriptors for predefined types used in AOTC-based interpreter. */
implicit val SigmaBooleanRType: RType[SigmaBoolean] = RType.fromClassTag(classTag[SigmaBoolean])
implicit val ErgoBoxRType: RType[ErgoBox] = RType.fromClassTag(classTag[ErgoBox])
implicit val ErgoBoxCandidateRType: RType[ErgoBoxCandidate] = RType.fromClassTag(classTag[ErgoBoxCandidate])
@@ -151,6 +160,7 @@ object SType {
val paramR = STypeParam(tR)
val paramIV = STypeParam(tIV)
val paramOV = STypeParam(tOV)
+ val paramIVSeq: Seq[STypeParam] = Array(paramIV)
val IndexedSeqOfT1: IndexedSeq[SType] = Array(SType.tT)
val IndexedSeqOfT2: IndexedSeq[SType] = Array(SType.tT, SType.tT)
@@ -262,6 +272,8 @@ object SType {
def asOption[T <: SType]: SOption[T] = tpe.asInstanceOf[SOption[T]]
def whenFunc[T](action: SFunc => Unit) = if(tpe.isInstanceOf[SFunc]) action(tpe.asFunc)
def asCollection[T <: SType] = tpe.asInstanceOf[SCollection[T]]
+
+ /** Returns the [[ClassTag]] for the given [[SType]]. */
def classTag[T <: SType#WrappedType]: ClassTag[T] = (tpe match {
case SBoolean => reflect.classTag[Boolean]
case SByte => reflect.classTag[Byte]
@@ -286,6 +298,7 @@ object SType {
}
implicit class AnyOps(val x: Any) extends AnyVal {
+ /** Helper method to simplify type casts. */
def asWrappedType: SType#WrappedType = x.asInstanceOf[SType#WrappedType]
}
}
@@ -301,6 +314,9 @@ trait STypeCompanion {
/** Type identifier to use in method serialization */
def typeId: Byte
+ /** If this is SType instance then returns the name of the corresponding RType.
+ * Otherwise returns the name of type companion object (e.g. SCollection).
+ */
def typeName: String = {
this match {
case t: SType =>
@@ -319,8 +335,10 @@ trait STypeCompanion {
/** Lookup method by its id in this type. */
@inline def getMethodById(methodId: Byte): Option[SMethod] =
- _methodsMap.get(typeId)
- .flatMap(ms => ms.get(methodId))
+ _methodsMap.get(typeId) match {
+ case Some(ms) => ms.get(methodId)
+ case None => None
+ }
/** Lookup method in this type by method's id or throw ValidationException.
* This method can be used in trySoftForkable section to either obtain valid method
@@ -337,6 +355,10 @@ trait STypeCompanion {
/** CosterFactory associated with this type. */
def coster: Option[CosterFactory] = None
+
+ /** Class which represents values of this type. When method call is executed, the corresponding method
+ * of this class is invoked via reflection [[java.lang.reflect.Method]].invoke(). */
+ def reprClass: Class[_]
}
/** Defines recognizer method which allows the derived object to be used in patterns
@@ -401,6 +423,7 @@ case class Coster(selector: RuntimeCosting => RuntimeCosting#CostingHandler[_])
case class ArgInfo(name: String, description: String)
/** Meta information which can be attached to SMethod.
+ * @param opDesc optional operation descriptor
* @param description human readable description of the method
* @param args one item for each argument */
case class OperationInfo(opDesc: Option[ValueCompanion], description: String, args: Seq[ArgInfo]) {
@@ -424,9 +447,14 @@ object OperationInfo {
* (builder, obj, m, args, subst) it transforms it to a new ErgoTree
* node, which is then used in the resuting ErgoTree coming out of
* the ErgoScript compiler.
+ * @param javaMethod Java [[Method]] which should be used to evaluate
+ * [[sigmastate.lang.Terms.MethodCall]] node of ErgoTree.
+ * @param invokeDescsBuilder optional builder of additional type descriptors (see extraDescriptors)
*/
case class MethodIRInfo(
- irBuilder: Option[PartialFunction[(SigmaBuilder, SValue, SMethod, Seq[SValue], STypeSubst), SValue]]
+ irBuilder: Option[PartialFunction[(SigmaBuilder, SValue, SMethod, Seq[SValue], STypeSubst), SValue]],
+ javaMethod: Option[Method],
+ invokeDescsBuilder: Option[InvokeDescBuilder]
)
/** Represents method descriptor.
@@ -437,28 +465,112 @@ case class MethodIRInfo(
* where `stype.tDom`` - argument type and
* `stype.tRange` - method result type.
* @param methodId method code, it should be unique among methods of the same objType.
+ * @param costKind cost descriptor for this method
* @param irInfo meta information connecting SMethod with ErgoTree (see [[MethodIRInfo]])
* @param docInfo optional human readable method description data
+ * @param costFunc optional specification of how the cost should be computed for the
+ * given method call (See ErgoTreeEvaluator.calcCost method).
*/
case class SMethod(
objType: STypeCompanion,
name: String,
stype: SFunc,
methodId: Byte,
+ costKind: CostKind,
irInfo: MethodIRInfo,
- docInfo: Option[OperationInfo]) {
+ docInfo: Option[OperationInfo],
+ costFunc: Option[MethodCostFunc]) {
+
+ /** Operation descriptor of this method. */
+ lazy val opDesc = MethodDesc(this)
+
+ /** Finds and keeps the [[Method]] instance which corresponds to this method descriptor.
+ * The lazy value is forced only if irInfo.javaMethod == None
+ */
+ lazy val javaMethod: Method = {
+ irInfo.javaMethod.getOrElse {
+ val paramTypes = stype.tDom.drop(1).map(t => t match {
+ case _: STypeVar => classOf[AnyRef]
+ case _: SFunc => classOf[_ => _]
+ case _ => Evaluation.stypeToRType(t).classTag.runtimeClass
+ }).toArray
+ val m = objType.reprClass.getMethod(name, paramTypes:_*)
+ m
+ }
+ }
+
+ /** Additional type descriptors, which are necessary to perform invocation of Method
+ * associated with this instance.
+ * @see MethodCall.eval
+ */
+ lazy val extraDescriptors: Seq[RType[_]] = {
+ irInfo.invokeDescsBuilder match {
+ case Some(builder) =>
+ builder(stype).map(Evaluation.stypeToRType)
+ case None =>
+ mutable.WrappedArray.empty[RType[_]]
+ }
+ }
+
+ /** Invoke this method on the given object with the arguments.
+ * This is used for methods with FixedCost costKind. */
+ def invokeFixed(obj: Any, args: Array[Any])(implicit E: ErgoTreeEvaluator): AnyRef = {
+ javaMethod.invoke(obj, args.asInstanceOf[Array[AnyRef]]:_*)
+ }
+
+ // TODO optimize: avoid lookup when this SMethod is created via `specializeFor`
+ /** Return generic template of this method. */
+ @inline final def genericMethod: SMethod = {
+ objType.getMethodById(methodId).get
+ }
+
+ /** Returns Java refection [[Method]] which must be invoked to evaluate this method.
+ * The method is resolved by its name using `name + "_eval"` naming convention.
+ * @see `map_eval`, `flatMap_eval` and other `*_eval` methods.
+ * @hotspot don't beautify the code */
+ lazy val evalMethod: Method = {
+ val argTypes = stype.tDom
+ val nArgs = argTypes.length
+ val paramTypes = new Array[Class[_]](nArgs + 2)
+ paramTypes(0) = classOf[MethodCall]
+ cfor(0)(_ < nArgs, _ + 1) { i =>
+ paramTypes(i + 1) = argTypes(i) match {
+ case _: STypeVar => classOf[AnyRef]
+ case _: SFunc => classOf[_ => _]
+ case _: SCollectionType[_] => classOf[Coll[_]]
+ case _: SOption[_] => classOf[Option[_]]
+ case t =>
+ Evaluation.stypeToRType(t).classTag.runtimeClass
+ }
+ }
+ paramTypes(paramTypes.length - 1) = classOf[ErgoTreeEvaluator]
+
+ val methodName = name + "_eval"
+ val m = try {
+ objType.getClass().getMethod(methodName, paramTypes:_*)
+ }
+ catch { case e: MethodNotFound =>
+ throw new RuntimeException(s"Cannot find eval method def $methodName(${Seq(paramTypes:_*)})", e)
+ }
+ m
+ }
/** Create a new instance with the given stype. */
def withSType(newSType: SFunc): SMethod = copy(stype = newSType)
+ /** Create a new instance with the given cost function. */
+ def withCost(costFunc: MethodCostFunc): SMethod = copy(costFunc = Some(costFunc))
+
/** Create a new instance in which the `stype` field transformed using
* the given substitution. */
def withConcreteTypes(subst: Map[STypeVar, SType]): SMethod =
withSType(stype.withSubstTypes(subst).asFunc)
+ /** Name of a language operation represented by this method. */
+ def opName = objType.getClass.getSimpleName + "." + name
+
/** Returns [[OperationId]] for AOT costing. */
def opId: OperationId = {
- val opName = objType.getClass.getSimpleName + "." + name
OperationId(opName, stype)
}
@@ -494,8 +606,10 @@ case class SMethod(
/** Create a new instance with the given IR builder (aka MethodCall rewriter) parameter. */
def withIRInfo(
- irBuilder: PartialFunction[(SigmaBuilder, SValue, SMethod, Seq[SValue], STypeSubst), SValue]): SMethod = {
- this.copy(irInfo = MethodIRInfo(Some(irBuilder)))
+ irBuilder: PartialFunction[(SigmaBuilder, SValue, SMethod, Seq[SValue], STypeSubst), SValue],
+ javaMethod: Method = null,
+ invokeHandler: InvokeDescBuilder = null): SMethod = {
+ this.copy(irInfo = MethodIRInfo(Some(irBuilder), Option(javaMethod), Option(invokeHandler)))
}
/** Lookup [[ArgInfo]] for the given argName or throw an exception. */
@@ -505,6 +619,77 @@ case class SMethod(
object SMethod {
+ type RCosted[A] = RuntimeCosting#RCosted[A]
+
+ /** Type of functions used to assign cost to method call nodes.
+ * For a function `f: (mc, obj, args) => cost` it is called before the evaluation of
+ * the `mc` node with the given `obj` as method receiver and `args` as method
+ * arguments.
+ */
+ abstract class MethodCostFunc extends Function4[ErgoTreeEvaluator, MethodCall, Any, Array[Any], CostDetails] {
+ /**
+ * The function returns an estimated cost of evaluation BEFORE actual evaluation of
+ * the method. For this reason [[MethodCostFunc]] is not used for higher-order
+ * operations like `Coll.map`, `Coll.filter` etc.
+ */
+ def apply(E: ErgoTreeEvaluator, mc: MethodCall, obj: Any, args: Array[Any]): CostDetails
+ }
+
+ /** Returns a cost function which always returs the given cost. */
+ def givenCost(costKind: FixedCost): MethodCostFunc = new MethodCostFunc {
+ override def apply(E: ErgoTreeEvaluator,
+ mc: MethodCall,
+ obj: Any, args: Array[Any]): CostDetails = {
+ if (E.settings.costTracingEnabled)
+ TracedCost(Array(FixedCostItem(MethodDesc(mc.method), costKind)))
+ else
+ GivenCost(costKind.cost)
+ }
+ }
+
+ /** Returns a cost function which expects `obj` to be of `Coll[T]` type and
+ * uses its length to compute SeqCostItem */
+ def perItemCost(costKind: PerItemCost): MethodCostFunc = new MethodCostFunc {
+ override def apply(E: ErgoTreeEvaluator,
+ mc: MethodCall,
+ obj: Any, args: Array[Any]): CostDetails = obj match {
+ case coll: Coll[a] =>
+ if (E.settings.costTracingEnabled) {
+ val desc = MethodDesc(mc.method)
+ TracedCost(Array(SeqCostItem(desc, costKind, coll.length)))
+ }
+ else
+ GivenCost(costKind.cost(coll.length))
+ case _ =>
+ ErgoTreeEvaluator.error(
+ s"Invalid object $obj of method call $mc: Coll type is expected")
+ }
+ }
+
+ /** Some runtime methods (like Coll.map, Coll.flatMap) require additional RType descriptors.
+ * The builder can extract those descriptors from the given type of the method signature.
+ */
+ type InvokeDescBuilder = SFunc => Seq[SType]
+
+ /** Return [[Method]] descriptor for the given `methodName` on the given `cT` type.
+ * @param methodName the name of the method to lookup
+ * @param cT the class where to search the methodName
+ * @param cA1 the class of the method argument
+ */
+ def javaMethodOf[T, A1](methodName: String)
+ (implicit cT: ClassTag[T], cA1: ClassTag[A1]): Method =
+ cT.runtimeClass.getMethod(methodName, cA1.runtimeClass)
+
+ /** Return [[Method]] descriptor for the given `methodName` on the given `cT` type.
+ * @param methodName the name of the method to lookup
+ * @param cT the class where to search the methodName
+ * @param cA1 the class of the method's first argument
+ * @param cA2 the class of the method's second argument
+ */
+ def javaMethodOf[T, A1, A2]
+ (methodName: String)
+ (implicit cT: ClassTag[T], cA1: ClassTag[A1], cA2: ClassTag[A2]): Method =
+ cT.runtimeClass.getMethod(methodName, cA1.runtimeClass, cA2.runtimeClass)
/** Default fallback method call recognizer which builds MethodCall ErgoTree nodes. */
val MethodCallIrBuilder: PartialFunction[(SigmaBuilder, SValue, SMethod, Seq[SValue], STypeSubst), SValue] = {
@@ -513,8 +698,12 @@ object SMethod {
}
/** Convenience factory method. */
- def apply(objType: STypeCompanion, name: String, stype: SFunc, methodId: Byte): SMethod = {
- SMethod(objType, name, stype, methodId, MethodIRInfo(None), None)
+ def apply(objType: STypeCompanion, name: String, stype: SFunc,
+ methodId: Byte,
+ costKind: CostKind): SMethod = {
+ SMethod(
+ objType, name, stype, methodId, costKind,
+ MethodIRInfo(None, None, None), None, None)
}
/** Looks up [[SMethod]] instance for the given type and method ids.
@@ -578,6 +767,8 @@ object SPrimType {
/** Upper limit of the interval of valid type codes for primitive types */
final val MaxPrimTypeCode: Byte = 11: Byte
+
+ /** Max possible number of primitive types. */
final val PrimRange: Byte = (MaxPrimTypeCode + 1).toByte
}
@@ -614,11 +805,14 @@ trait SNumericType extends SProduct {
@inline def max(that: SNumericType): SNumericType =
if (this.numericTypeIndex > that.numericTypeIndex) this else that
+ /** Returns true if this numeric type is larger than that. */
+ @inline final def >(that: SNumericType): Boolean = this.numericTypeIndex > that.numericTypeIndex
+
/** Numeric types are ordered by the number of bytes to store the numeric values.
* @return index in the array of all numeric types. */
- @inline protected def numericTypeIndex: Int
+ protected def numericTypeIndex: Int
- override def toString: Idn = this.getClass.getSimpleName
+ override def toString: String = this.getClass.getSimpleName
}
object SNumericType extends STypeCompanion {
/** Array of all numeric types ordered by number of bytes in the representation. */
@@ -626,27 +820,66 @@ object SNumericType extends STypeCompanion {
// TODO HF (4h): this typeId is now shadowed by SGlobal.typeId
// see https://github.com/ScorexFoundation/sigmastate-interpreter/issues/667
- def typeId: TypeCode = 106: Byte
+ override def typeId: TypeCode = 106: Byte
+
+ /** Since this object is not used in SMethod instances. */
+ override def reprClass: Class[_] = sys.error(s"Shouldn't be called.")
+
+ /** Type variable used in generic signatures of method descriptors. */
val tNum = STypeVar("TNum")
- val ToByteMethod: SMethod = SMethod(this, "toByte", SFunc(tNum, SByte), 1)
+ /** Cost function which is assigned for numeric cast MethodCall nodes in ErgoTree.
+ * It is called as part of MethodCall.eval method. */
+ val costOfNumericCast: MethodCostFunc = new MethodCostFunc {
+ override def apply(E: ErgoTreeEvaluator,
+ mc: MethodCall,
+ obj: Any,
+ args: Array[Any]): CostDetails = {
+ val targetTpe = mc.method.stype.tRange
+ val cast = getNumericCast(mc.obj.tpe, mc.method.name, targetTpe).get
+ val costKind = if (cast == Downcast) Downcast.costKind else Upcast.costKind
+ TracedCost(Array(TypeBasedCostItem(MethodDesc(mc.method), costKind, targetTpe)))
+ }
+ }
+
+ /** The following SMethod instances are descriptors of methods available on all numeric
+ * types.
+ * @see `val methods` below
+ * */
+ val ToByteMethod: SMethod = SMethod(this, "toByte", SFunc(tNum, SByte), 1, null)
+ .withCost(costOfNumericCast)
.withInfo(PropertyCall, "Converts this numeric value to \\lst{Byte}, throwing exception if overflow.")
- val ToShortMethod: SMethod = SMethod(this, "toShort", SFunc(tNum, SShort), 2)
+ val ToShortMethod: SMethod = SMethod(this, "toShort", SFunc(tNum, SShort), 2, null)
+ .withCost(costOfNumericCast)
.withInfo(PropertyCall, "Converts this numeric value to \\lst{Short}, throwing exception if overflow.")
- val ToIntMethod: SMethod = SMethod(this, "toInt", SFunc(tNum, SInt), 3)
+ val ToIntMethod: SMethod = SMethod(this, "toInt", SFunc(tNum, SInt), 3, null)
+ .withCost(costOfNumericCast)
.withInfo(PropertyCall, "Converts this numeric value to \\lst{Int}, throwing exception if overflow.")
- val ToLongMethod: SMethod = SMethod(this, "toLong", SFunc(tNum, SLong), 4)
+ val ToLongMethod: SMethod = SMethod(this, "toLong", SFunc(tNum, SLong), 4, null)
+ .withCost(costOfNumericCast)
.withInfo(PropertyCall, "Converts this numeric value to \\lst{Long}, throwing exception if overflow.")
- val ToBigIntMethod: SMethod = SMethod(this, "toBigInt", SFunc(tNum, SBigInt), 5)
+ val ToBigIntMethod: SMethod = SMethod(this, "toBigInt", SFunc(tNum, SBigInt), 5, null)
+ .withCost(costOfNumericCast)
.withInfo(PropertyCall, "Converts this numeric value to \\lst{BigInt}")
- val ToBytesMethod: SMethod = SMethod(this, "toBytes", SFunc(tNum, SByteArray), 6)
+
+ /** Cost of: 1) creating Byte collection from a numeric value */
+ val ToBytes_CostKind = FixedCost(5)
+
+ val ToBytesMethod: SMethod = SMethod(
+ this, "toBytes", SFunc(tNum, SByteArray), 6, ToBytes_CostKind)
.withIRInfo(MethodCallIrBuilder)
.withInfo(PropertyCall,
""" Returns a big-endian representation of this numeric value in a collection of bytes.
| For example, the \lst{Int} value \lst{0x12131415} would yield the
| collection of bytes \lst{[0x12, 0x13, 0x14, 0x15]}.
""".stripMargin)
- val ToBitsMethod: SMethod = SMethod(this, "toBits", SFunc(tNum, SBooleanArray), 7)
+
+ /** Cost of: 1) creating Boolean collection (one bool for each bit) from a numeric
+ * value. */
+ val ToBits_CostKind = FixedCost(5)
+
+ val ToBitsMethod: SMethod = SMethod(
+ this, "toBits", SFunc(tNum, SBooleanArray), 7, ToBits_CostKind)
.withIRInfo(MethodCallIrBuilder)
.withInfo(PropertyCall,
""" Returns a big-endian representation of this numeric in a collection of Booleans.
@@ -667,8 +900,21 @@ object SNumericType extends STypeCompanion {
val castMethods: Array[String] =
Array(ToByteMethod, ToShortMethod, ToIntMethod, ToLongMethod, ToBigIntMethod)
.map(_.name)
+
+ /** Checks the given name is numeric type cast method (like toByte, toInt, etc.).*/
+ def isCastMethod(name: String): Boolean = castMethods.contains(name)
+
+ /** Convert the given method to a cast operation from fromTpe to resTpe. */
+ def getNumericCast(fromTpe: SType, methodName: String, resTpe: SType): Option[NumericCastCompanion] = (fromTpe, resTpe) match {
+ case (from: SNumericType, to: SNumericType) if isCastMethod(methodName) =>
+ val op = if (to > from) Upcast else Downcast
+ Some(op)
+ case _ => None // the method in not numeric type cast
+ }
+
}
+/** Base type for SBoolean and SSigmaProp. */
trait SLogical extends SType {
}
@@ -676,21 +922,26 @@ trait SLogical extends SType {
* @see `SGenericType`
*/
trait SMonoType extends SType with STypeCompanion {
- protected def property(name: String, tpeRes: SType, id: Byte): SMethod =
- SMethod(this, name, SFunc(this, tpeRes), id)
+ /** Helper method to create method descriptors for properties (i.e. methods without args). */
+ protected def propertyCall(name: String, tpeRes: SType, id: Byte, costKind: CostKind): SMethod =
+ SMethod(this, name, SFunc(this, tpeRes), id, costKind)
.withIRInfo(MethodCallIrBuilder)
.withInfo(PropertyCall, "")
+ /** Helper method to create method descriptors for properties (i.e. methods without args). */
protected def property(name: String, tpeRes: SType, id: Byte, valueCompanion: ValueCompanion): SMethod =
- SMethod(this, name, SFunc(this, tpeRes), id)
+ SMethod(this, name, SFunc(this, tpeRes), id, valueCompanion.costKind)
.withIRInfo(MethodCallIrBuilder)
.withInfo(valueCompanion, "")
}
+/** Descriptor of ErgoTree type `Boolean` holding `true` or `false` values. */
case object SBoolean extends SPrimType with SEmbeddable with SLogical with SProduct with SMonoType {
override type WrappedType = Boolean
override val typeCode: TypeCode = 1: Byte
override def typeId = typeCode
+ override val reprClass: Class[_] = classOf[Boolean]
+
val ToByte = "toByte"
protected override def getMethods() = super.getMethods()
/* TODO soft-fork: https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479
@@ -703,9 +954,11 @@ case object SBoolean extends SPrimType with SEmbeddable with SLogical with SProd
override def isConstantSize = true
}
+/** Descriptor of ErgoTree type `Byte` - 8-bit signed integer. */
case object SByte extends SPrimType with SEmbeddable with SNumericType with SMonoType {
override type WrappedType = Byte
override val typeCode: TypeCode = 2: Byte
+ override val reprClass: Class[_] = classOf[Byte]
override def typeId = typeCode
override def dataSize(v: SType#WrappedType): Long = 1
override def isConstantSize = true
@@ -714,7 +967,7 @@ case object SByte extends SPrimType with SEmbeddable with SNumericType with SMon
case b: Byte => b
case _ => sys.error(s"Cannot upcast value $v to the type $this")
}
- def downcast(v: AnyVal): Byte = v match {
+ override def downcast(v: AnyVal): Byte = v match {
case b: Byte => b
case s: Short => s.toByteExact
case i: Int => i.toByteExact
@@ -723,9 +976,11 @@ case object SByte extends SPrimType with SEmbeddable with SNumericType with SMon
}
}
+/** Descriptor of ErgoTree type `Short` - 16-bit signed integer. */
case object SShort extends SPrimType with SEmbeddable with SNumericType with SMonoType {
override type WrappedType = Short
override val typeCode: TypeCode = 3: Byte
+ override val reprClass: Class[_] = classOf[Short]
override def typeId = typeCode
override def dataSize(v: SType#WrappedType): Long = 2
override def isConstantSize = true
@@ -743,9 +998,11 @@ case object SShort extends SPrimType with SEmbeddable with SNumericType with SMo
}
}
+/** Descriptor of ErgoTree type `Int` - 32-bit signed integer. */
case object SInt extends SPrimType with SEmbeddable with SNumericType with SMonoType {
override type WrappedType = Int
override val typeCode: TypeCode = 4: Byte
+ override val reprClass: Class[_] = classOf[Int]
override def typeId = typeCode
override def dataSize(v: SType#WrappedType): Long = 4
override def isConstantSize = true
@@ -765,9 +1022,11 @@ case object SInt extends SPrimType with SEmbeddable with SNumericType with SMono
}
}
+/** Descriptor of ErgoTree type `Long` - 64-bit signed integer. */
case object SLong extends SPrimType with SEmbeddable with SNumericType with SMonoType {
override type WrappedType = Long
override val typeCode: TypeCode = 5: Byte
+ override val reprClass: Class[_] = classOf[Long]
override def typeId = typeCode
override def dataSize(v: SType#WrappedType): Long = 8
override def isConstantSize = true
@@ -792,6 +1051,8 @@ case object SLong extends SPrimType with SEmbeddable with SNumericType with SMon
case object SBigInt extends SPrimType with SEmbeddable with SNumericType with SMonoType {
override type WrappedType = BigInt
override val typeCode: TypeCode = 6: Byte
+ override val reprClass: Class[_] = classOf[BigInt]
+
override def typeId = typeCode
/** Type of Relation binary op like GE, LE, etc. */
@@ -806,8 +1067,6 @@ case object SBigInt extends SPrimType with SEmbeddable with SNumericType with SM
* In sigma we limit the size by the fixed constant and thus BigInt is a constant size type. */
override def isConstantSize = true
- val Max: BigInt = SigmaDsl.BigInt(CryptoConstants.dlogGroup.order)
-
override protected def numericTypeIndex: Int = 4
override def upcast(v: AnyVal): BigInt = {
@@ -831,15 +1090,21 @@ case object SBigInt extends SPrimType with SEmbeddable with SNumericType with SM
SigmaDsl.BigInt(bi)
}
- val ModQMethod = SMethod(this, "modQ", SFunc(this, SBigInt), 1)
+ /** The following `modQ` methods are not fully implemented in v4.x and this descriptors.
+ * This descritors are remain here in the code and are waiting for full implementation
+ * is upcoming soft-forks at which point the cost parameters should be calculated and
+ * changed.
+ */
+ val ModQMethod = SMethod(this, "modQ", SFunc(this, SBigInt), 1, FixedCost(1))
.withInfo(ModQ, "Returns this \\lst{mod} Q, i.e. remainder of division by Q, where Q is an order of the cryprographic group.")
- val PlusModQMethod = SMethod(this, "plusModQ", SFunc(IndexedSeq(this, SBigInt), SBigInt), 2)
+ val PlusModQMethod = SMethod(this, "plusModQ", SFunc(IndexedSeq(this, SBigInt), SBigInt), 2, FixedCost(1))
.withInfo(ModQArithOp.PlusModQ, "Adds this number with \\lst{other} by module Q.", ArgInfo("other", "Number to add to this."))
- val MinusModQMethod = SMethod(this, "minusModQ", SFunc(IndexedSeq(this, SBigInt), SBigInt), 3)
+ val MinusModQMethod = SMethod(this, "minusModQ", SFunc(IndexedSeq(this, SBigInt), SBigInt), 3, FixedCost(1))
.withInfo(ModQArithOp.MinusModQ, "Subtracts \\lst{other} number from this by module Q.", ArgInfo("other", "Number to subtract from this."))
- val MultModQMethod = SMethod(this, "multModQ", SFunc(IndexedSeq(this, SBigInt), SBigInt), 4)
+ val MultModQMethod = SMethod(this, "multModQ", SFunc(IndexedSeq(this, SBigInt), SBigInt), 4, FixedCost(1))
.withIRInfo(MethodCallIrBuilder)
.withInfo(MethodCall, "Multiply this number with \\lst{other} by module Q.", ArgInfo("other", "Number to multiply with this."))
+
protected override def getMethods() = super.getMethods() ++ Seq(
// ModQMethod,
// PlusModQMethod,
@@ -849,38 +1114,57 @@ case object SBigInt extends SPrimType with SEmbeddable with SNumericType with SM
)
}
-/** NOTE: this descriptor both type and type companion */
+/** Descriptor of type `String` which is not used in ErgoTree, but used in ErgoScript.
+ * NOTE: this descriptor both type and type companion */
case object SString extends SProduct with SMonoType {
override type WrappedType = String
override val typeCode: TypeCode = 102: Byte
override def typeId = typeCode
override def dataSize(v: SType#WrappedType): Long = v.asInstanceOf[String].length
override def isConstantSize = false
+ override def reprClass: Class[_] = classOf[String]
}
-/** NOTE: this descriptor both type and type companion */
+/** Descriptor of ErgoTree type `GroupElement`.
+ * NOTE: this descriptor both type and type companion */
case object SGroupElement extends SProduct with SPrimType with SEmbeddable with SMonoType {
override type WrappedType = GroupElement
override val typeCode: TypeCode = 7: Byte
+ override val reprClass: Class[_] = classOf[GroupElement]
+
override def typeId = typeCode
override def coster: Option[CosterFactory] = Some(Coster(_.GroupElementCoster))
- lazy val GetEncodedMethod: SMethod = SMethod(this, "getEncoded", SFunc(IndexedSeq(this), SByteArray), 2)
+ /** Cost of: 1) serializing EcPointType to bytes 2) packing them in Coll. */
+ val GetEncodedCostKind = FixedCost(250)
+
+ /** The following SMethod instances are descriptors of methods defined in `GroupElement` type. */
+ lazy val GetEncodedMethod: SMethod = SMethod(
+ this, "getEncoded", SFunc(Array(this), SByteArray), 2, GetEncodedCostKind)
.withIRInfo(MethodCallIrBuilder)
.withInfo(PropertyCall, "Get an encoding of the point value.")
- lazy val ExponentiateMethod: SMethod = SMethod(this, "exp", SFunc(IndexedSeq(this, SBigInt), this), 3)
+
+ lazy val ExponentiateMethod: SMethod = SMethod(
+ this, "exp", SFunc(Array(this, SBigInt), this), 3, Exponentiate.costKind)
.withIRInfo({ case (builder, obj, _, Seq(arg), _) =>
builder.mkExponentiate(obj.asGroupElement, arg.asBigInt)
})
.withInfo(Exponentiate,
"Exponentiate this \\lst{GroupElement} to the given number. Returns this to the power of k",
ArgInfo("k", "The power"))
- lazy val MultiplyMethod: SMethod = SMethod(this, "multiply", SFunc(IndexedSeq(this, SGroupElement), this), 4)
+
+ lazy val MultiplyMethod: SMethod = SMethod(
+ this, "multiply", SFunc(Array(this, SGroupElement), this), 4, MultiplyGroup.costKind)
.withIRInfo({ case (builder, obj, _, Seq(arg), _) =>
builder.mkMultiplyGroup(obj.asGroupElement, arg.asGroupElement)
})
.withInfo(MultiplyGroup, "Group operation.", ArgInfo("other", "other element of the group"))
- lazy val NegateMethod: SMethod = SMethod(this, "negate", SFunc(this, this), 5)
+
+ /** Cost of: 1) calling EcPoint.negate 2) wrapping in GroupElement. */
+ val Negate_CostKind = FixedCost(45)
+
+ lazy val NegateMethod: SMethod = SMethod(
+ this, "negate", SFunc(this, this), 5, Negate_CostKind)
.withIRInfo(MethodCallIrBuilder)
.withInfo(PropertyCall, "Inverse element of the group.")
@@ -898,10 +1182,12 @@ case object SGroupElement extends SProduct with SPrimType with SEmbeddable with
override def isConstantSize = true
}
+/** Descriptor of ErgoTree type `SigmaProp` which represent sigma-protocol propositions. */
case object SSigmaProp extends SProduct with SPrimType with SEmbeddable with SLogical with SMonoType {
import SType._
override type WrappedType = SigmaProp
override val typeCode: TypeCode = 8: Byte
+ override val reprClass: Class[_] = classOf[SigmaProp]
override def typeId = typeCode
/** The maximum size of SigmaProp value in serialized byte array representation. */
@@ -912,11 +1198,14 @@ case object SSigmaProp extends SProduct with SPrimType with SEmbeddable with SLo
override def isConstantSize = true
val PropBytes = "propBytes"
val IsProven = "isProven"
- lazy val PropBytesMethod = SMethod(this, PropBytes, SFunc(this, SByteArray), 1)
- .withInfo(SigmaPropBytes, "Serialized bytes of this sigma proposition taken as ErgoTree.")
- lazy val IsProvenMethod = SMethod(this, IsProven, SFunc(this, SBoolean), 2)
+ lazy val PropBytesMethod = SMethod(
+ this, PropBytes, SFunc(this, SByteArray), 1, SigmaPropBytes.costKind)
+ .withInfo(SigmaPropBytes, "Serialized bytes of this sigma proposition taken as ErgoTree.")
+
+ lazy val IsProvenMethod = SMethod(this, IsProven, SFunc(this, SBoolean), 2, null)
.withInfo(// available only at frontend of ErgoScript
"Verify that sigma proposition is proven.")
+
protected override def getMethods() = super.getMethods() ++ Seq(
PropBytesMethod, IsProvenMethod
)
@@ -951,26 +1240,26 @@ case class SOption[ElemType <: SType](elemType: ElemType) extends SProduct with
}
override def isConstantSize = elemType.isConstantSize
protected override def getMethods() = super.getMethods() ++ SOption.methods
-// override lazy val methods: Seq[SMethod] = {
-// val subst = Map(SOption.tT -> elemType)
-// SOption.methods.map { method =>
-// method.copy(stype = SigmaTyper.applySubst(method.stype, subst))
-// }
-// }
override def toString = s"Option[$elemType]"
override def toTermString: String = s"Option[${elemType.toTermString}]"
- lazy val typeParams: Seq[STypeParam] = Array(SType.paramT)
+ override lazy val typeParams: Seq[STypeParam] = Array(SType.paramT)
}
object SOption extends STypeCompanion {
+ /** Code of `Option[_]` type constructor. */
val OptionTypeConstrId = 3
+ /** Type code for `Option[T] for some T` type used in TypeSerializer. */
val OptionTypeCode: TypeCode = ((SPrimType.MaxPrimTypeCode + 1) * OptionTypeConstrId).toByte
+ /** Code of `Option[Coll[_]]` type constructor. */
val OptionCollectionTypeConstrId = 4
+ /** Type code for `Option[Coll[T]] for some T` type used in TypeSerializer. */
val OptionCollectionTypeCode: TypeCode = ((SPrimType.MaxPrimTypeCode + 1) * OptionCollectionTypeConstrId).toByte
+
override def typeId = OptionTypeCode
override val coster: Option[CosterFactory] = Some(Coster(_.OptionCoster))
+ override val reprClass: Class[_] = classOf[Option[_]]
type SBooleanOption = SOption[SBoolean.type]
type SByteOption = SOption[SByte.type]
@@ -982,6 +1271,7 @@ object SOption extends STypeCompanion {
type SBoxOption = SOption[SBox.type]
type SAvlTreeOption = SOption[SAvlTree.type]
+ /** This descriptors are instantiated once here and then reused. */
implicit val SByteOption = SOption(SByte)
implicit val SByteArrayOption = SOption(SByteArray)
implicit val SShortOption = SOption(SShort)
@@ -1000,34 +1290,41 @@ object SOption extends STypeCompanion {
val Fold = "fold"
import SType.{tT, tR, paramT, paramR}
+
+ /** Type descriptor of `this` argument used in the methods below. */
val ThisType = SOption(tT)
- val IsDefinedMethod = SMethod(this, IsDefined, SFunc(ThisType, SBoolean), 2)
+ /** The following SMethod instances are descriptors of methods defined in `Option` type. */
+ val IsDefinedMethod = SMethod(
+ this, IsDefined, SFunc(ThisType, SBoolean), 2, OptionIsDefined.costKind)
.withInfo(OptionIsDefined,
"Returns \\lst{true} if the option is an instance of \\lst{Some}, \\lst{false} otherwise.")
- val GetMethod = SMethod(this, Get, SFunc(ThisType, tT), 3)
+ val GetMethod = SMethod(this, Get, SFunc(ThisType, tT), 3, OptionGet.costKind)
.withInfo(OptionGet,
"""Returns the option's value. The option must be nonempty. Throws exception if the option is empty.""")
- val GetOrElseMethod = SMethod(this, GetOrElse, SFunc(IndexedSeq(ThisType, tT), tT, Seq(tT)), 4)
+ lazy val GetOrElseMethod = SMethod(
+ this, GetOrElse, SFunc(Array(ThisType, tT), tT, Array[STypeParam](tT)), 4, OptionGetOrElse.costKind)
.withInfo(OptionGetOrElse,
"""Returns the option's value if the option is nonempty, otherwise
|return the result of evaluating \lst{default}.
""".stripMargin, ArgInfo("default", "the default value"))
- val FoldMethod = SMethod(this, Fold, SFunc(Array(ThisType, tR, SFunc(tT, tR)), tR, Seq(tT, tR)), 5)
- .withInfo(MethodCall,
- """Returns the result of applying \lst{f} to this option's
- | value if the option is nonempty. Otherwise, evaluates
- | expression \lst{ifEmpty}.
- | This is equivalent to \lst{option map f getOrElse ifEmpty}.
- """.stripMargin,
- ArgInfo("ifEmpty", "the expression to evaluate if empty"),
- ArgInfo("f", "the function to apply if nonempty"))
-
- val MapMethod = SMethod(this, "map",
- SFunc(Array(ThisType, SFunc(tT, tR)), SOption(tR), Array(paramT, paramR)), 7)
+// TODO soft-fork: https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479
+// val FoldMethod = SMethod(
+// this, Fold, SFunc(Array(ThisType, tR, SFunc(tT, tR)), tR, Array[STypeParam](tT, tR)), 5, FixedCost(1))
+// .withInfo(MethodCall,
+// """Returns the result of applying \lst{f} to this option's
+// | value if the option is nonempty. Otherwise, evaluates
+// | expression \lst{ifEmpty}.
+// | This is equivalent to \lst{option map f getOrElse ifEmpty}.
+// """.stripMargin,
+// ArgInfo("ifEmpty", "the expression to evaluate if empty"),
+// ArgInfo("f", "the function to apply if nonempty"))
+
+ val MapMethod = SMethod(this, "map",
+ SFunc(Array(ThisType, SFunc(tT, tR)), SOption(tR), Array(paramT, paramR)), 7, FixedCost(20))
.withIRInfo(MethodCallIrBuilder)
.withInfo(MethodCall,
"""Returns a \lst{Some} containing the result of applying \lst{f} to this option's
@@ -1035,8 +1332,8 @@ object SOption extends STypeCompanion {
| Otherwise return \lst{None}.
""".stripMargin, ArgInfo("f", "the function to apply"))
- val FilterMethod = SMethod(this, "filter",
- SFunc(Array(ThisType, SFunc(tT, SBoolean)), ThisType, Array(paramT)), 8)
+ val FilterMethod = SMethod(this, "filter",
+ SFunc(Array(ThisType, SFunc(tT, SBoolean)), ThisType, Array(paramT)), 8, FixedCost(20))
.withIRInfo(MethodCallIrBuilder)
.withInfo(MethodCall,
"""Returns this option if it is nonempty and applying the predicate \lst{p} to
@@ -1054,15 +1351,16 @@ object SOption extends STypeCompanion {
FilterMethod
)
def apply[T <: SType](implicit elemType: T, ov: Overload1): SOption[T] = SOption(elemType)
-// def unapply[T <: SType](tOpt: SOption[T]): Option[T] = Some(tOpt.elemType)
}
+/** Base class for descriptors of `Coll[T]` ErgoTree type for some elemType T. */
trait SCollection[T <: SType] extends SProduct with SGenericType {
def elemType: T
override type WrappedType = Coll[T#WrappedType]
override def isConstantSize = false
}
+/** Descriptor of `Coll[T]` ErgoTree type for some elemType T. */
case class SCollectionType[T <: SType](elemType: T) extends SCollection[T] {
override val typeCode: TypeCode = SCollectionType.CollectionTypeCode
@@ -1071,37 +1369,51 @@ case class SCollectionType[T <: SType](elemType: T) extends SCollection[T] {
implicit val sT = Sized.typeToSized(Evaluation.stypeToRType(elemType))
Sized.sizeOf(coll).dataSize
}
- def typeParams: Seq[STypeParam] = SCollectionType.typeParams
+ override def typeParams: Seq[STypeParam] = SCollectionType.typeParams
protected override def getMethods() = super.getMethods() ++ SCollection.methods
override def toString = s"Coll[$elemType]"
override def toTermString = s"Coll[${elemType.toTermString}]"
}
object SCollectionType {
+ /** Code of `Coll[_]` type constructor. */
val CollectionTypeConstrId = 1
+
+ /** Type code for `Coll[T] for some T` type used in TypeSerializer. */
val CollectionTypeCode: TypeCode = ((SPrimType.MaxPrimTypeCode + 1) * CollectionTypeConstrId).toByte
+
+ /** Code of `Coll[Coll[_]]` type constructor. */
val NestedCollectionTypeConstrId = 2
+
+ /** Type code for `Coll[Coll[T]] for some T` type used in TypeSerializer. */
val NestedCollectionTypeCode: TypeCode = ((SPrimType.MaxPrimTypeCode + 1) * NestedCollectionTypeConstrId).toByte
+
+ /** Array of generic type parameters reused in all SCollectionType instances. */
val typeParams: Seq[STypeParam] = Array(SType.paramIV)
}
object SCollection extends STypeCompanion with MethodByNameUnapply {
+ override val reprClass: Class[_] = classOf[Coll[_]]
override def typeId = SCollectionType.CollectionTypeCode
override def coster: Option[CosterFactory] = Some(Coster(_.CollCoster))
- import SType.{tK, tV, paramIV, paramOV}
+ import SType.{tK, tV, paramIV, paramIVSeq, paramOV}
+ /** Helper descriptors reused across different method descriptors. */
def tIV = SType.tIV
def tOV = SType.tOV
+ /** This descriptors are instantiated once here and then reused. */
val ThisType = SCollection(tIV)
val tOVColl = SCollection(tOV)
val tPredicate = SFunc(tIV, SBoolean)
- val SizeMethod = SMethod(this, "size", SFunc(ThisType, SInt), 1)
+ /** The following SMethod instances are descriptors of methods defined in `Coll` type. */
+ val SizeMethod = SMethod(this, "size", SFunc(ThisType, SInt), 1, SizeOf.costKind)
.withInfo(SizeOf, "The size of the collection in elements.")
- val GetOrElseMethod = SMethod(this, "getOrElse", SFunc(IndexedSeq(ThisType, SInt, tIV), tIV, Seq(paramIV)), 2)
+ val GetOrElseMethod = SMethod(
+ this, "getOrElse", SFunc(Array(ThisType, SInt, tIV), tIV, paramIVSeq), 2, ByIndex.costKind)
.withIRInfo({ case (builder, obj, _, Seq(index, defaultValue), _) =>
val index1 = index.asValue[SInt.type]
val defaultValue1 = defaultValue.asValue[SType]
@@ -1111,7 +1423,8 @@ object SCollection extends STypeCompanion with MethodByNameUnapply {
ArgInfo("index", "index of the element of this collection"),
ArgInfo("default", "value to return when \\lst{index} is out of range"))
- val MapMethod = SMethod(this, "map", SFunc(IndexedSeq(ThisType, SFunc(tIV, tOV)), tOVColl, Seq(paramIV, paramOV)), 3)
+ val MapMethod = SMethod(this, "map",
+ SFunc(Array(ThisType, SFunc(tIV, tOV)), tOVColl, Array(paramIV, paramOV)), 3, MapCollection.costKind)
.withInfo(MapCollection,
""" Builds a new collection by applying a function to all elements of this collection.
| Returns a new collection of type \lst{Coll[B]} resulting from applying the given function
@@ -1119,19 +1432,35 @@ object SCollection extends STypeCompanion with MethodByNameUnapply {
""".stripMargin,
ArgInfo("f", "the function to apply to each element"))
- val ExistsMethod = SMethod(this, "exists", SFunc(IndexedSeq(ThisType, tPredicate), SBoolean, Seq(paramIV)), 4)
+ /** Implements evaluation of Coll.map method call ErgoTree node.
+ * Called via reflection based on naming convention.
+ * @see SMethod.evalMethod
+ */
+ def map_eval[A,B](mc: MethodCall, xs: Coll[A], f: A => B)(implicit E: ErgoTreeEvaluator): Coll[B] = {
+ val tpeB = mc.tpe.asInstanceOf[SCollection[SType]].elemType
+ val tB = Evaluation.stypeToRType(tpeB).asInstanceOf[RType[B]]
+ E.addSeqCostNoOp(MapCollection.costKind, xs.length, mc.method.opDesc)
+ xs.map(f)(tB)
+ }
+
+ val ExistsMethod = SMethod(this, "exists",
+ SFunc(Array(ThisType, tPredicate), SBoolean, paramIVSeq), 4, Exists.costKind)
.withInfo(Exists,
"""Tests whether a predicate holds for at least one element of this collection.
|Returns \lst{true} if the given predicate \lst{p} is satisfied by at least one element of this collection, otherwise \lst{false}
""".stripMargin,
ArgInfo("p", "the predicate used to test elements"))
- val FoldMethod = SMethod(this, "fold", SFunc(IndexedSeq(ThisType, tOV, SFunc(IndexedSeq(tOV, tIV), tOV)), tOV, Seq(paramIV, paramOV)), 5)
+ val FoldMethod = SMethod(
+ this, "fold",
+ SFunc(Array(ThisType, tOV, SFunc(Array(tOV, tIV), tOV)), tOV, Array(paramIV, paramOV)),
+ 5, Fold.costKind)
.withInfo(Fold, "Applies a binary operator to a start value and all elements of this collection, going left to right.",
ArgInfo("zero", "a starting value"),
ArgInfo("op", "the binary operator"))
- val ForallMethod = SMethod(this, "forall", SFunc(IndexedSeq(ThisType, tPredicate), SBoolean, Seq(paramIV)), 6)
+ val ForallMethod = SMethod(this, "forall",
+ SFunc(Array(ThisType, tPredicate), SBoolean, paramIVSeq), 6, ForAll.costKind)
.withInfo(ForAll,
"""Tests whether a predicate holds for all elements of this collection.
|Returns \lst{true} if this collection is empty or the given predicate \lst{p}
@@ -1139,7 +1468,8 @@ object SCollection extends STypeCompanion with MethodByNameUnapply {
""".stripMargin,
ArgInfo("p", "the predicate used to test elements"))
- val SliceMethod = SMethod(this, "slice", SFunc(IndexedSeq(ThisType, SInt, SInt), ThisType, Seq(paramIV)), 7)
+ val SliceMethod = SMethod(this, "slice",
+ SFunc(Array(ThisType, SInt, SInt), ThisType, paramIVSeq), 7, Slice.costKind)
.withInfo(Slice,
"""Selects an interval of elements. The returned collection is made up
| of all elements \lst{x} which satisfy the invariant:
@@ -1150,7 +1480,8 @@ object SCollection extends STypeCompanion with MethodByNameUnapply {
ArgInfo("from", "the lowest index to include from this collection"),
ArgInfo("until", "the lowest index to EXCLUDE from this collection"))
- val FilterMethod = SMethod(this, "filter", SFunc(IndexedSeq(ThisType, tPredicate), ThisType, Seq(paramIV)), 8)
+ val FilterMethod = SMethod(this, "filter",
+ SFunc(Array(ThisType, tPredicate), ThisType, paramIVSeq), 8, Filter.costKind)
.withIRInfo({
case (builder, obj, _, Seq(l), _) => builder.mkFilter(obj.asValue[SCollection[SType]], l.asFunc)
})
@@ -1161,14 +1492,17 @@ object SCollection extends STypeCompanion with MethodByNameUnapply {
""".stripMargin,
ArgInfo("p", "the predicate used to test elements."))
- val AppendMethod = SMethod(this, "append", SFunc(IndexedSeq(ThisType, ThisType), ThisType, Seq(paramIV)), 9)
- .withIRInfo({
- case (builder, obj, _, Seq(xs), _) =>
- builder.mkAppend(obj.asCollection[SType], xs.asCollection[SType])
- })
+ val AppendMethod = SMethod(this, "append",
+ SFunc(Array(ThisType, ThisType), ThisType, paramIVSeq), 9, Append.costKind)
+ .withIRInfo({
+ case (builder, obj, _, Seq(xs), _) =>
+ builder.mkAppend(obj.asCollection[SType], xs.asCollection[SType])
+ })
.withInfo(Append, "Puts the elements of other collection after the elements of this collection (concatenation of 2 collections)",
ArgInfo("other", "the collection to append at the end of this"))
- val ApplyMethod = SMethod(this, "apply", SFunc(IndexedSeq(ThisType, SInt), tIV, Seq(tIV)), 10)
+
+ val ApplyMethod = SMethod(this, "apply",
+ SFunc(Array(ThisType, SInt), tIV, Array[STypeParam](tIV)), 10, ByIndex.costKind)
.withInfo(ByIndex,
"""The element at given index.
| Indices start at \lst{0}; \lst{xs.apply(0)} is the first element of collection \lst{xs}.
@@ -1177,23 +1511,43 @@ object SCollection extends STypeCompanion with MethodByNameUnapply {
| Throws an exception if \lst{i < 0} or \lst{length <= i}
""".stripMargin, ArgInfo("i", "the index"))
- val BitShiftLeftMethod = SMethod(this, "<<",
- SFunc(IndexedSeq(ThisType, SInt), ThisType, Seq(paramIV)), 11)
- val BitShiftRightMethod = SMethod(this, ">>",
- SFunc(IndexedSeq(ThisType, SInt), ThisType, Seq(paramIV)), 12)
- val BitShiftRightZeroedMethod = SMethod(this, ">>>",
- SFunc(IndexedSeq(SCollection(SBoolean), SInt), SCollection(SBoolean)), 13)
+ /** Cost of creating a collection of indices */
+ val IndicesMethod_CostKind = PerItemCost(baseCost = 20, perChunkCost = 2, chunkSize = 16)
- val IndicesMethod = SMethod(this, "indices", SFunc(ThisType, SCollection(SInt)), 14)
+ val IndicesMethod = SMethod(
+ this, "indices", SFunc(ThisType, SCollection(SInt)), 14, IndicesMethod_CostKind)
.withIRInfo(MethodCallIrBuilder)
.withInfo(PropertyCall,
"""Produces the range of all indices of this collection as a new collection
| containing [0 .. length-1] values.
""".stripMargin)
+ /** Implements evaluation of Coll.indices method call ErgoTree node.
+ * Called via reflection based on naming convention.
+ * @see SMethod.evalMethod
+ */
+ def indices_eval[A, B](mc: MethodCall, xs: Coll[A])
+ (implicit E: ErgoTreeEvaluator): Coll[Int] = {
+ val m = mc.method
+ E.addSeqCost(m.costKind.asInstanceOf[PerItemCost], xs.length, m.opDesc) { () =>
+ xs.indices
+ }
+ }
+ /** BaseCost:
+ * 1) base cost of Coll.flatMap
+ * PerChunkCost:
+ * 1) cost of Coll.flatMap (per item)
+ * 2) new collection is allocated for each item
+ * 3) each collection is then appended to the resulting collection */
+ val FlatMapMethod_CostKind = PerItemCost(baseCost = 30, perChunkCost = 5, chunkSize = 8)
+
val FlatMapMethod = SMethod(this, "flatMap",
- SFunc(IndexedSeq(ThisType, SFunc(tIV, tOVColl)), tOVColl, Seq(paramIV, paramOV)), 15)
- .withIRInfo(MethodCallIrBuilder)
+ SFunc(Array(ThisType, SFunc(tIV, tOVColl)), tOVColl, Array(paramIV, paramOV)),
+ 15, FlatMapMethod_CostKind)
+ .withIRInfo(
+ MethodCallIrBuilder,
+ javaMethodOf[Coll[_], Function1[_,_], RType[_]]("flatMap"),
+ { mtype => Array(mtype.tRange.asCollection[SType].elemType) })
.withInfo(MethodCall,
""" Builds a new collection by applying a function to all elements of this collection
| and using the elements of the resulting collections.
@@ -1203,72 +1557,212 @@ object SCollection extends STypeCompanion with MethodByNameUnapply {
| \lst{f} to each element of this collection and concatenating the results.
""".stripMargin, ArgInfo("f", "the function to apply to each element."))
+ /** We assume all flatMap body patterns have similar executon cost. */
+ final val CheckFlatmapBody_Info = OperationCostInfo(
+ PerItemCost(20, 20, 1), NamedDesc("CheckFlatmapBody"))
+
+
+ /** This patterns recognize all expressions, which are allowed as lambda body
+ * of flatMap. Other bodies are rejected with throwing exception.
+ */
+ val flatMap_BodyPatterns = Array[PartialFunction[SValue, Int]](
+ { case MethodCall(ValUse(id, tpe), m, args, _) if args.isEmpty => id },
+ { case ExtractScriptBytes(ValUse(id, _)) => id },
+ { case ExtractId(ValUse(id, _)) => id },
+ { case SigmaPropBytes(ValUse(id, _)) => id },
+ { case ExtractBytes(ValUse(id, _)) => id },
+ { case ExtractBytesWithNoRef(ValUse(id, _)) => id }
+ )
+
+ /** Check the given expression is valid body of flatMap argument lambda.
+ * @param varId id of lambda variable (see [[FuncValue]].args)
+ * @param expr expression with is expected to use varId in ValUse node.
+ * @return true if the body is allowed
+ */
+ def isValidPropertyAccess(varId: Int, expr: SValue)
+ (implicit E: ErgoTreeEvaluator): Boolean = {
+ var found = false
+ // NOTE: the cost depends on the position of the pattern since
+ // we are checking until the first matching pattern found.
+ E.addSeqCost(CheckFlatmapBody_Info) { () =>
+ // the loop is bounded because flatMap_BodyPatterns is fixed
+ var i = 0
+ val nPatterns = flatMap_BodyPatterns.length
+ while (i < nPatterns && !found) {
+ val p = flatMap_BodyPatterns(i)
+ found = p.lift(expr) match {
+ case Some(id) => id == varId // `id` in the pattern is equal to lambda `varId`
+ case None => false
+ }
+ i += 1
+ }
+ i // how many patterns checked
+ }
+ found
+ }
+
+ /** Operation descriptor for matching `flatMap` method calls with valid lambdas. */
+ final val MatchSingleArgMethodCall_Info = OperationCostInfo(
+ FixedCost(30), NamedDesc("MatchSingleArgMethodCall"))
+
+ /** Recognizer of `flatMap` method calls with valid lambdas. */
+ object IsSingleArgMethodCall {
+ def unapply(mc:MethodCall)
+ (implicit E: ErgoTreeEvaluator): Nullable[(Int, SValue)] = {
+ var res: Nullable[(Int, SValue)] = Nullable.None
+ E.addFixedCost(MatchSingleArgMethodCall_Info) {
+ res = mc match {
+ case MethodCall(_, m, Seq(FuncValue(args, body)), _) if args.length == 1 =>
+ val id = args(0)._1
+ Nullable((id, body))
+ case _ =>
+ Nullable.None
+ }
+ }
+ res
+ }
+ }
+
+ /** Checks that the given [[MethodCall]] operation is valid flatMap. */
+ def checkValidFlatmap(mc: MethodCall)(implicit E: ErgoTreeEvaluator) = {
+ mc match {
+ case IsSingleArgMethodCall(varId, lambdaBody)
+ if isValidPropertyAccess(varId, lambdaBody) =>
+ // ok, do nothing
+ case _ =>
+ ErgoTreeEvaluator.error(
+ s"Unsupported lambda in flatMap: allowed usage `xs.flatMap(x => x.property)`: $mc")
+ }
+ }
+
+ /** Implements evaluation of Coll.flatMap method call ErgoTree node.
+ * Called via reflection based on naming convention.
+ * @see SMethod.evalMethod
+ */
+ def flatMap_eval[A, B](mc: MethodCall, xs: Coll[A], f: A => Coll[B])
+ (implicit E: ErgoTreeEvaluator): Coll[B] = {
+ checkValidFlatmap(mc)
+ val m = mc.method
+ var res: Coll[B] = null
+ E.addSeqCost(m.costKind.asInstanceOf[PerItemCost], m.opDesc) { () =>
+ val tpeB = mc.tpe.asInstanceOf[SCollection[SType]].elemType
+ val tB = Evaluation.stypeToRType(tpeB).asInstanceOf[RType[B]]
+ res = xs.flatMap(f)(tB)
+ res.length
+ }
+ res
+ }
val PatchMethod = SMethod(this, "patch",
- SFunc(IndexedSeq(ThisType, SInt, ThisType, SInt), ThisType, Seq(paramIV)), 19)
+ SFunc(Array(ThisType, SInt, ThisType, SInt), ThisType, paramIVSeq),
+ 19, PerItemCost(30, 2, 10))
.withIRInfo(MethodCallIrBuilder)
- .withInfo(MethodCall, "")
+ .withInfo(MethodCall,
+ "Produces a new Coll where a slice of elements in this Coll is replaced by another Coll.")
- val UpdatedMethod = SMethod(this, "updated",
- SFunc(IndexedSeq(ThisType, SInt, tIV), ThisType, Seq(paramIV)), 20)
- .withIRInfo(MethodCallIrBuilder).withInfo(MethodCall, "")
+ /** Implements evaluation of Coll.patch method call ErgoTree node.
+ * Called via reflection based on naming convention.
+ * @see SMethod.evalMethod
+ */
+ def patch_eval[A](mc: MethodCall, xs: Coll[A], from: Int, patch: Coll[A], replaced: Int)
+ (implicit E: ErgoTreeEvaluator): Coll[A] = {
+ val m = mc.method
+ val nItems = xs.length + patch.length
+ E.addSeqCost(m.costKind.asInstanceOf[PerItemCost], nItems, m.opDesc) { () =>
+ xs.patch(from, patch, replaced)
+ }
+ }
- val UpdateManyMethod = SMethod(this, "updateMany",
- SFunc(IndexedSeq(ThisType, SCollection(SInt), ThisType), ThisType, Seq(paramIV)), 21)
- .withIRInfo(MethodCallIrBuilder).withInfo(MethodCall, "")
+ val UpdatedMethod = SMethod(this, "updated",
+ SFunc(Array(ThisType, SInt, tIV), ThisType, paramIVSeq),
+ 20, PerItemCost(20, 1, 10))
+ .withIRInfo(MethodCallIrBuilder, javaMethodOf[Coll[_], Int, Any]("updated"))
+ .withInfo(MethodCall,
+ "A copy of this Coll with one single replaced element.")
- val UnionSetsMethod = SMethod(this, "unionSets",
- SFunc(IndexedSeq(ThisType, ThisType), ThisType, Seq(paramIV)), 22)
- .withIRInfo(MethodCallIrBuilder).withInfo(MethodCall, "")
+ /** Implements evaluation of Coll.updated method call ErgoTree node.
+ * Called via reflection based on naming convention.
+ * @see SMethod.evalMethod
+ */
+ def updated_eval[A](mc: MethodCall, coll: Coll[A], index: Int, elem: A)
+ (implicit E: ErgoTreeEvaluator): Coll[A] = {
+ val m = mc.method
+ val costKind = m.costKind.asInstanceOf[PerItemCost]
+ E.addSeqCost(costKind, coll.length, m.opDesc) { () =>
+ coll.updated(index, elem)
+ }
+ }
- val DiffMethod = SMethod(this, "diff",
- SFunc(IndexedSeq(ThisType, ThisType), ThisType, Seq(paramIV)), 23)
- .withIRInfo(MethodCallIrBuilder).withInfo(MethodCall, "")
- val IntersectMethod = SMethod(this, "intersect",
- SFunc(IndexedSeq(ThisType, ThisType), ThisType, Seq(paramIV)), 24)
+ val UpdateManyMethod = SMethod(this, "updateMany",
+ SFunc(Array(ThisType, SCollection(SInt), ThisType), ThisType, paramIVSeq),
+ 21, PerItemCost(20, 2, 10))
.withIRInfo(MethodCallIrBuilder).withInfo(MethodCall, "")
- val PrefixLengthMethod = SMethod(this, "prefixLength",
- SFunc(IndexedSeq(ThisType, tPredicate), SInt, Seq(paramIV)), 25)
- .withIRInfo(MethodCallIrBuilder).withInfo(MethodCall, "")
+ /** Implements evaluation of Coll.updateMany method call ErgoTree node.
+ * Called via reflection based on naming convention.
+ * @see SMethod.evalMethod
+ */
+ def updateMany_eval[A](mc: MethodCall, coll: Coll[A], indexes: Coll[Int], values: Coll[A])
+ (implicit E: ErgoTreeEvaluator): Coll[A] = {
+ val costKind = mc.method.costKind.asInstanceOf[PerItemCost]
+ E.addSeqCost(costKind, coll.length, mc.method.opDesc) { () =>
+ coll.updateMany(indexes, values)
+ }
+ }
val IndexOfMethod = SMethod(this, "indexOf",
- SFunc(IndexedSeq(ThisType, tIV, SInt), SInt, Seq(paramIV)), 26)
- .withIRInfo(MethodCallIrBuilder).withInfo(MethodCall, "")
+ SFunc(Array(ThisType, tIV, SInt), SInt, paramIVSeq), 26, PerItemCost(20, 10, 2))
+ .withIRInfo(MethodCallIrBuilder, javaMethodOf[Coll[_], Any, Int]("indexOf"))
+ .withInfo(MethodCall, "")
- val LastIndexOfMethod = SMethod(this, "lastIndexOf",
- SFunc(IndexedSeq(ThisType, tIV, SInt), SInt, Seq(paramIV)), 27)
- .withIRInfo(MethodCallIrBuilder).withInfo(MethodCall, "")
+ // TODO v5.0: optimize using specialization for numeric and predefined types
+ /** Implements evaluation of Coll.indexOf method call ErgoTree node.
+ * Called via reflection based on naming convention.
+ * @see SMethod.evalMethod
+ */
+ def indexOf_eval[A](mc: MethodCall, xs: Coll[A], elem: A, from: Int)
+ (implicit E: ErgoTreeEvaluator): Int = {
+ val costKind = mc.method.costKind.asInstanceOf[PerItemCost]
+ var res: Int = -1
+ E.addSeqCost(costKind, mc.method.opDesc) { () =>
+ // TODO v5.0: this loop is bounded when MaxCollSize limit is enforced
+ val len = xs.length
+ val start = math.max(from, 0)
+ var i = start
+ var different = true
+ while (i < len && different) {
+ different = !DataValueComparer.equalDataValues(xs(i), elem)
+ i += 1
+ }
+ if (!different)
+ res = i - 1
+ i - start // return number of performed iterations
+ }
+ res
+ }
- // TODO HF (1h): related to https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479
- lazy val FindMethod = SMethod(this, "find",
- SFunc(IndexedSeq(ThisType, tPredicate), SOption(tIV), Seq(paramIV)), 28)
- .withIRInfo(MethodCallIrBuilder).withInfo(MethodCall, "")
+ /** Cost descriptor of Coll.zip operation. */
+ val Zip_CostKind = PerItemCost(baseCost = 10, perChunkCost = 1, chunkSize = 10)
val ZipMethod = SMethod(this, "zip",
- SFunc(IndexedSeq(ThisType, tOVColl), SCollection(STuple(tIV, tOV)), Seq(tIV, tOV)), 29)
- .withIRInfo(MethodCallIrBuilder).withInfo(MethodCall, "")
-
- val DistinctMethod = SMethod(this, "distinct",
- SFunc(IndexedSeq(ThisType), ThisType, Seq(tIV)), 30)
- .withIRInfo(MethodCallIrBuilder).withInfo(PropertyCall, "")
-
- val StartsWithMethod = SMethod(this, "startsWith",
- SFunc(IndexedSeq(ThisType, ThisType, SInt), SBoolean, Seq(paramIV)), 31)
- .withIRInfo(MethodCallIrBuilder).withInfo(MethodCall, "")
-
- val EndsWithMethod = SMethod(this, "endsWith",
- SFunc(IndexedSeq(ThisType, ThisType), SBoolean, Seq(paramIV)), 32)
- .withIRInfo(MethodCallIrBuilder).withInfo(MethodCall, "")
+ SFunc(Array(ThisType, tOVColl), SCollection(STuple(tIV, tOV)), Array[STypeParam](tIV, tOV)),
+ 29, Zip_CostKind)
+ .withIRInfo(MethodCallIrBuilder)
+ .withInfo(MethodCall, "")
- val MapReduceMethod = SMethod(this, "mapReduce",
- SFunc(
- IndexedSeq(ThisType, SFunc(tIV, STuple(tK, tV)), SFunc(STuple(tV, tV), tV)),
- SCollection(STuple(tK, tV)),
- Seq(paramIV, STypeParam(tK), STypeParam(tV))), 34)
- .withIRInfo(MethodCallIrBuilder).withInfo(MethodCall, "")
+ /** Implements evaluation of Coll.zip method call ErgoTree node.
+ * Called via reflection based on naming convention.
+ * @see SMethod.evalMethod
+ */
+ def zip_eval[A, B](mc: MethodCall, xs: Coll[A], ys: Coll[B])
+ (implicit E: ErgoTreeEvaluator): Coll[(A,B)] = {
+ val m = mc.method
+ E.addSeqCost(m.costKind.asInstanceOf[PerItemCost], xs.length, m.opDesc) { () =>
+ xs.zip(ys)
+ }
+ }
- lazy val methods: Seq[SMethod] = Seq(
+ override lazy val methods: Seq[SMethod] = Seq(
SizeMethod,
GetOrElseMethod,
MapMethod,
@@ -1279,35 +1773,16 @@ object SCollection extends STypeCompanion with MethodByNameUnapply {
FilterMethod,
AppendMethod,
ApplyMethod,
- /* TODO HF (1h): https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479
- BitShiftLeftMethod,
- BitShiftRightMethod,
- BitShiftRightZeroedMethod,
- */
IndicesMethod,
FlatMapMethod,
PatchMethod,
UpdatedMethod,
UpdateManyMethod,
- /*TODO HF (1h): https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479
- UnionSetsMethod,
- DiffMethod,
- IntersectMethod,
- PrefixLengthMethod,
- */
IndexOfMethod,
- /* TODO HF (1h): https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479
- LastIndexOfMethod,
- FindMethod,
- */
ZipMethod
- /* TODO HF (1h): https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479
- DistinctMethod,
- StartsWithMethod,
- EndsWithMethod,
- MapReduceMethod,
- */
)
+
+ /** Helper constructors. */
def apply[T <: SType](elemType: T): SCollection[T] = SCollectionType(elemType)
def apply[T <: SType](implicit elemType: T, ov: Overload1): SCollection[T] = SCollectionType(elemType)
@@ -1321,6 +1796,7 @@ object SCollection extends STypeCompanion with MethodByNameUnapply {
type SBoxArray = SCollection[SBox.type]
type SAvlTreeArray = SCollection[SAvlTree.type]
+ /** This descriptors are instantiated once here and then reused. */
val SBooleanArray = SCollection(SBoolean)
val SByteArray = SCollection(SByte)
val SByteArray2 = SCollection(SCollection(SByte))
@@ -1335,6 +1811,7 @@ object SCollection extends STypeCompanion with MethodByNameUnapply {
val SHeaderArray = SCollection(SHeader)
}
+/** Type descriptor of tuple type. */
case class STuple(items: IndexedSeq[SType]) extends SCollection[SAny.type] {
import STuple._
override val typeCode = STuple.TupleTypeCode
@@ -1345,12 +1822,15 @@ case class STuple(items: IndexedSeq[SType]) extends SCollection[SAny.type] {
@volatile
private var _isConstantSizeCode: Byte = 0.toByte
- /** use lazy pattern to support O(1) amortized complexity over n invocations. */
+ /** use lazy pattern to support O(1) amortized complexity over n invocations.
+ * Not thread safe!
+ */
override def isConstantSize: Boolean = {
val code = _isConstantSizeCode
if (code == 0) {
val len = items.length
var isConst: Boolean = true
+ // looking for a first non-const item type, or run out of items
cfor(0)(_ < len && isConst, _ + 1) { i =>
val t = items(i)
isConst = t.isConstantSize
@@ -1391,47 +1871,63 @@ case class STuple(items: IndexedSeq[SType]) extends SCollection[SAny.type] {
protected override def getMethods() = {
val tupleMethods = Array.tabulate(items.size) { i =>
- SMethod(STuple, componentNameByIndex(i), SFunc(this, items(i)), (i + 1).toByte)
+ SMethod(
+ STuple, componentNameByIndex(i), SFunc(this, items(i)),
+ (i + 1).toByte, SelectField.costKind)
}
colMethods ++ tupleMethods
}
- val typeParams = Nil
+ override val typeParams = Nil
override def toTermString = s"(${items.map(_.toTermString).mkString(",")})"
override def toString = s"(${items.mkString(",")})"
}
object STuple extends STypeCompanion {
+ /** Code of `(_, T) for some embeddable T` type constructor. */
val Pair1TypeConstrId = 5
+ /** Type code for `(E, T) for some embeddable T` type used in TypeSerializer. */
val Pair1TypeCode: TypeCode = ((SPrimType.MaxPrimTypeCode + 1) * Pair1TypeConstrId).toByte
+ /** Code of `(T, _) for some embeddable T` type constructor. */
val Pair2TypeConstrId = 6
+ /** Type code for `(T, E) for some embeddable T` type used in TypeSerializer. */
val Pair2TypeCode: TypeCode = ((SPrimType.MaxPrimTypeCode + 1) * Pair2TypeConstrId).toByte
val TripleTypeCode: TypeCode = Pair2TypeCode
+ /** Type constructor code of symmetric pair `(T, T)` for some embeddable T. */
val PairSymmetricTypeConstrId = 7
+ /** Type code of symmetric pair `(T, T)` for some embeddable T. */
val PairSymmetricTypeCode: TypeCode = ((SPrimType.MaxPrimTypeCode + 1) * PairSymmetricTypeConstrId).toByte
val QuadrupleTypeCode: TypeCode = PairSymmetricTypeCode
+ /** Type code of generic tuple type. */
val TupleTypeCode = ((SPrimType.MaxPrimTypeCode + 1) * 8).toByte
- def typeId = TupleTypeCode
+ override def typeId = TupleTypeCode
- lazy val colMethods = {
+ override val reprClass: Class[_] = classOf[Product2[_,_]]
+
+ /** A list of Coll methods inherited from Coll type and available as method of tuple. */
+ lazy val colMethods: Seq[SMethod] = {
val subst = Map(SType.tIV -> SAny)
- // TODO: implement other
- val activeMethods = Set(1.toByte, 10.toByte)
+ // TODO: implement other methods
+ val activeMethods = Set(1.toByte /*Coll.size*/, 10.toByte /*Coll.apply*/)
SCollection.methods.filter(m => activeMethods.contains(m.methodId)).map { m =>
m.copy(stype = SigmaTyper.applySubst(m.stype, subst).asFunc)
}
}
- def methods: Seq[SMethod] = sys.error(s"Shouldn't be called.")
+ override def methods: Seq[SMethod] = sys.error(s"Shouldn't be called.")
+ /** Helper factory method. */
def apply(items: SType*): STuple = STuple(items.toArray)
- val MaxTupleLength: Int = SigmaConstants.MaxTupleLength.value
+
+ private val MaxTupleLength: Int = SigmaConstants.MaxTupleLength.value
private val componentNames = Array.tabulate(MaxTupleLength){ i => s"_${i + 1}" }
+
+ /** Returns method name for the tuple component accessor (i.e. `_1`, `_2`, etc.) */
def componentNameByIndex(i: Int): String =
try componentNames(i)
catch {
@@ -1450,6 +1946,7 @@ object SPair {
}
}
+/** Type descriptor of lambda types. */
case class SFunc(tDom: IndexedSeq[SType], tRange: SType, tpeParams: Seq[STypeParam] = Nil)
extends SType with SGenericType
{
@@ -1466,14 +1963,19 @@ case class SFunc(tDom: IndexedSeq[SType], tRange: SType, tpeParams: Seq[STypePa
}
override def dataSize(v: SType#WrappedType) = 8L
import SFunc._
- val typeParams: Seq[STypeParam] = tpeParams
+ override val typeParams: Seq[STypeParam] = tpeParams
+ /** Generalize this type and return a new descriptor. */
def getGenericType: SFunc = {
val typeParams: Seq[STypeParam] = tDom.zipWithIndex
.map { case (_, i) => STypeParam(SType.tD.name + (i + 1)) } :+ STypeParam(SType.tR.name)
val ts = typeParams.map(_.ident)
SFunc(ts.init.toIndexedSeq, ts.last, Nil)
}
+
+ /** Transform function into method type by adding the given `objType` as the first
+ * argument type (aka method receiver type).
+ */
def withReceiverType(objType: SType) = this.copy(tDom = objType +: tDom)
}
@@ -1483,7 +1985,9 @@ object SFunc {
val identity = { x: Any => x }
}
-
+/** Used by ErgoScript compiler IR and eliminated during compilation.
+ * It is not used in ErgoTree.
+ */
case class STypeApply(name: String, args: IndexedSeq[SType] = IndexedSeq()) extends SType {
override type WrappedType = Any
override val typeCode = STypeApply.TypeCode
@@ -1495,7 +1999,10 @@ object STypeApply {
val TypeCode = 94: Byte
}
-/** Type variable which is used in generic method/func signatures. */
+/** Type variable which is used in generic method/func signatures.
+ * Used by ErgoScript compiler IR and eliminated during compilation.
+ * It is not used in ErgoTree.
+ */
case class STypeVar(name: String) extends SType {
require(name.length <= 255, "name is too long")
override type WrappedType = Any
@@ -1517,10 +2024,12 @@ object STypeVar {
val EmptySeq: IndexedSeq[STypeVar] = EmptyArray
}
+/** Type descriptor of `Box` type of ErgoTree. */
case object SBox extends SProduct with SPredefType with SMonoType {
import ErgoBox._
override type WrappedType = Box
override val typeCode: TypeCode = 99: Byte
+ override val reprClass: Class[_] = classOf[Box]
override def typeId = typeCode
override def dataSize(v: SType#WrappedType): Long = {
val box = v.asInstanceOf[this.WrappedType]
@@ -1530,20 +2039,25 @@ case object SBox extends SProduct with SPredefType with SMonoType {
import SType.{tT, paramT}
+ /** Defined once here and then reused in SMethod descriptors. */
lazy val GetRegFuncType = SFunc(Array(SBox), SOption(tT), Array(paramT))
+ /** Creates a descriptor for the given register method. (i.e. R1, R2, etc) */
def registers(idOfs: Int): Seq[SMethod] = {
allRegisters.map { i =>
i match {
case r: MandatoryRegisterId =>
- SMethod(this, s"R${i.asIndex}", GetRegFuncType, (idOfs + i.asIndex + 1).toByte)
+ SMethod(this, s"R${i.asIndex}",
+ GetRegFuncType, (idOfs + i.asIndex + 1).toByte, ExtractRegisterAs.costKind)
.withInfo(ExtractRegisterAs, r.purpose)
case _ =>
- SMethod(this, s"R${i.asIndex}", GetRegFuncType, (idOfs + i.asIndex + 1).toByte)
+ SMethod(this, s"R${i.asIndex}",
+ GetRegFuncType, (idOfs + i.asIndex + 1).toByte, ExtractRegisterAs.costKind)
.withInfo(ExtractRegisterAs, "Non-mandatory register")
}
}
}
+
val PropositionBytes = "propositionBytes"
val Value = "value"
val Id = "id"
@@ -1553,34 +2067,40 @@ case object SBox extends SProduct with SPredefType with SMonoType {
val GetReg = "getReg"
// should be lazy, otherwise lead to initialization error
- lazy val ValueMethod = SMethod(this, Value, SFunc(SBox, SLong), 1)
+ lazy val ValueMethod = SMethod(
+ this, Value, SFunc(SBox, SLong), 1, ExtractAmount.costKind)
.withInfo(ExtractAmount,
"Mandatory: Monetary value, in Ergo tokens (NanoErg unit of measure)")
- lazy val PropositionBytesMethod = SMethod(this, PropositionBytes, SFunc(SBox, SByteArray), 2)
+ lazy val PropositionBytesMethod = SMethod(
+ this, PropositionBytes, SFunc(SBox, SByteArray), 2, ExtractScriptBytes.costKind)
.withInfo(ExtractScriptBytes,
"Serialized bytes of guarding script, which should be evaluated to true in order to\n" +
" open this box. (aka spend it in a transaction)")
- lazy val BytesMethod = SMethod(this, Bytes, SFunc(SBox, SByteArray), 3)
+ lazy val BytesMethod = SMethod(
+ this, Bytes, SFunc(SBox, SByteArray), 3, ExtractBytes.costKind)
.withInfo(ExtractBytes, "Serialized bytes of this box's content, including proposition bytes.")
- lazy val BytesWithoutRefMethod = SMethod(this, BytesWithoutRef, SFunc(SBox, SByteArray), 4)
+ lazy val BytesWithoutRefMethod = SMethod(
+ this, BytesWithoutRef, SFunc(SBox, SByteArray), 4, ExtractBytesWithNoRef.costKind)
.withInfo(ExtractBytesWithNoRef,
"Serialized bytes of this box's content, excluding transactionId and index of output.")
- lazy val IdMethod = SMethod(this, Id, SFunc(SBox, SByteArray), 5)
+ lazy val IdMethod = SMethod(this, Id, SFunc(SBox, SByteArray), 5, ExtractId.costKind)
.withInfo(ExtractId,
"Blake2b256 hash of this box's content, basically equals to \\lst{blake2b256(bytes)}")
- lazy val creationInfoMethod = SMethod(this, CreationInfo, ExtractCreationInfo.OpType, 6)
+ lazy val creationInfoMethod = SMethod(
+ this, CreationInfo, ExtractCreationInfo.OpType, 6, ExtractCreationInfo.costKind)
.withInfo(ExtractCreationInfo,
""" If \lst{tx} is a transaction which generated this box, then \lst{creationInfo._1}
| is a height of the tx's block. The \lst{creationInfo._2} is a serialized transaction
| identifier followed by box index in the transaction outputs.
""".stripMargin ) // see ExtractCreationInfo
- lazy val getRegMethod = SMethod(this, "getReg", SFunc(Array(SBox, SInt), SOption(tT), Array(paramT)), 7)
+ lazy val getRegMethod = SMethod(this, "getReg",
+ SFunc(Array(SBox, SInt), SOption(tT), Array(paramT)), 7, ExtractRegisterAs.costKind)
.withInfo(ExtractRegisterAs,
""" Extracts register by id and type.
| Type param \lst{T} expected type of the register.
@@ -1588,7 +2108,8 @@ case object SBox extends SProduct with SPredefType with SMonoType {
""".stripMargin,
ArgInfo("regId", "zero-based identifier of the register."))
- lazy val tokensMethod = SMethod(this, "tokens", SFunc(SBox, ErgoBox.STokensRegType), 8)
+ lazy val tokensMethod = SMethod(
+ this, "tokens", SFunc(SBox, ErgoBox.STokensRegType), 8, FixedCost(15))
.withIRInfo(MethodCallIrBuilder)
.withInfo(PropertyCall, "Secondary tokens")
@@ -1608,9 +2129,11 @@ case object SBox extends SProduct with SPredefType with SMonoType {
override val coster = Some(Coster(_.BoxCoster))
}
+/** Type descriptor of `AvlTree` type of ErgoTree. */
case object SAvlTree extends SProduct with SPredefType with SMonoType {
override type WrappedType = AvlTree
override val typeCode: TypeCode = 100: Byte
+ override val reprClass: Class[_] = classOf[AvlTree]
override def typeId = typeCode
override def dataSize(v: SType#WrappedType): Long = AvlTreeData.TreeDataSize
override def isConstantSize = true
@@ -1619,14 +2142,21 @@ case object SAvlTree extends SProduct with SPredefType with SMonoType {
lazy val TCollOptionCollByte = SCollection(SByteArrayOption)
lazy val CollKeyValue = SCollection(STuple(SByteArray, SByteArray))
- lazy val digestMethod = SMethod(this, "digest", SFunc(this, SByteArray), 1)
+ lazy val digestMethod = SMethod(this, "digest", SFunc(this, SByteArray), 1, FixedCost(15))
.withIRInfo(MethodCallIrBuilder)
.withInfo(PropertyCall,
"""Returns digest of the state represented by this tree.
| Authenticated tree \lst{digest} = \lst{root hash bytes} ++ \lst{tree height}
""".stripMargin)
- lazy val enabledOperationsMethod = SMethod(this, "enabledOperations", SFunc(this, SByte), 2)
+ /** Cost descriptor of `digest` method. */
+ lazy val digest_Info = {
+ val m = digestMethod
+ OperationCostInfo(m.costKind.asInstanceOf[FixedCost], m.opDesc)
+ }
+
+ lazy val enabledOperationsMethod = SMethod(
+ this, "enabledOperations", SFunc(this, SByte), 2, FixedCost(15))
.withIRInfo(MethodCallIrBuilder)
.withInfo(PropertyCall,
""" Flags of enabled operations packed in single byte.
@@ -1634,47 +2164,72 @@ case object SAvlTree extends SProduct with SPredefType with SMonoType {
| \lst{isUpdateAllowed == (enabledOperations & 0x02) != 0}\newline
| \lst{isRemoveAllowed == (enabledOperations & 0x04) != 0}
""".stripMargin)
- lazy val keyLengthMethod = SMethod(this, "keyLength", SFunc(this, SInt), 3)
+
+ lazy val keyLengthMethod = SMethod(
+ this, "keyLength", SFunc(this, SInt), 3, FixedCost(15))
.withIRInfo(MethodCallIrBuilder)
.withInfo(PropertyCall,
- """
- |
- """.stripMargin)
- lazy val valueLengthOptMethod = SMethod(this, "valueLengthOpt", SFunc(this, SIntOption), 4)
+ """
+ |
+ """.stripMargin)
+
+ lazy val valueLengthOptMethod = SMethod(
+ this, "valueLengthOpt", SFunc(this, SIntOption), 4, FixedCost(15))
.withIRInfo(MethodCallIrBuilder)
.withInfo(PropertyCall,
"""
|
""".stripMargin)
- lazy val isInsertAllowedMethod = SMethod(this, "isInsertAllowed", SFunc(this, SBoolean), 5)
+
+ lazy val isInsertAllowedMethod = SMethod(
+ this, "isInsertAllowed", SFunc(this, SBoolean), 5, FixedCost(15))
.withIRInfo(MethodCallIrBuilder)
.withInfo(PropertyCall,
"""
|
""".stripMargin)
- lazy val isUpdateAllowedMethod = SMethod(this, "isUpdateAllowed", SFunc(this, SBoolean), 6)
+
+ lazy val isInsertAllowed_Info = {
+ val m = isInsertAllowedMethod
+ OperationCostInfo(m.costKind.asInstanceOf[FixedCost], m.opDesc)
+ }
+
+ lazy val isUpdateAllowedMethod = SMethod(
+ this, "isUpdateAllowed", SFunc(this, SBoolean), 6, FixedCost(15))
.withIRInfo(MethodCallIrBuilder)
.withInfo(PropertyCall,
"""
|
""".stripMargin)
- lazy val isRemoveAllowedMethod = SMethod(this, "isRemoveAllowed", SFunc(this, SBoolean), 7)
+
+ lazy val isUpdateAllowed_Info = {
+ val m = isUpdateAllowedMethod
+ OperationCostInfo(m.costKind.asInstanceOf[FixedCost], m.opDesc)
+ }
+
+ lazy val isRemoveAllowedMethod = SMethod(
+ this, "isRemoveAllowed", SFunc(this, SBoolean), 7, FixedCost(15))
.withIRInfo(MethodCallIrBuilder)
.withInfo(PropertyCall,
"""
|
""".stripMargin)
+ lazy val isRemoveAllowed_Info = {
+ val m = isRemoveAllowedMethod
+ OperationCostInfo(m.costKind.asInstanceOf[FixedCost], m.opDesc)
+ }
+
lazy val updateOperationsMethod = SMethod(this, "updateOperations",
- SFunc(IndexedSeq(SAvlTree, SByte), SAvlTree), 8)
+ SFunc(Array(SAvlTree, SByte), SAvlTree), 8, FixedCost(45))
.withIRInfo(MethodCallIrBuilder)
.withInfo(MethodCall,
"""
|
""".stripMargin)
- lazy val containsMethod = SMethod(this, "contains",
- SFunc(IndexedSeq(SAvlTree, SByteArray, SByteArray), SBoolean), 9)
+ lazy val containsMethod = SMethod(this, "contains",
+ SFunc(Array(SAvlTree, SByteArray, SByteArray), SBoolean), 9, DynamicCost)
.withIRInfo(MethodCallIrBuilder)
.withInfo(MethodCall,
"""
@@ -1690,8 +2245,58 @@ case object SAvlTree extends SProduct with SPredefType with SMonoType {
|
""".stripMargin)
- lazy val getMethod = SMethod(this, "get",
- SFunc(IndexedSeq(SAvlTree, SByteArray, SByteArray), SByteArrayOption), 10)
+ /** The proof may contain keys, labels and values, we don't know for sure how many,
+ * but we assume the cost is O(proof.length).
+ * So the following is an approximation of the proof parsing cost.
+ */
+ final val CreateAvlVerifier_Info = OperationCostInfo(
+ PerItemCost(110, 20, 64), NamedDesc("CreateAvlVerifier"))
+
+ final val LookupAvlTree_Info = OperationCostInfo(
+ PerItemCost(40, 10, 1), NamedDesc("LookupAvlTree"))
+
+ final val InsertIntoAvlTree_Info = OperationCostInfo(
+ PerItemCost(40, 10, 1), NamedDesc("InsertIntoAvlTree"))
+
+ final val UpdateAvlTree_Info = OperationCostInfo(
+ PerItemCost(120, 20, 1), NamedDesc("UpdateAvlTree"))
+
+ final val RemoveAvlTree_Info = OperationCostInfo(
+ PerItemCost(100, 15, 1), NamedDesc("RemoveAvlTree"))
+
+ /** Creates [[AvlTreeVerifier]] for the given tree and proof. */
+ def createVerifier(tree: AvlTree, proof: Coll[Byte])(implicit E: ErgoTreeEvaluator) = {
+ // the cost of tree reconstruction from proof is O(proof.length)
+ E.addSeqCost(CreateAvlVerifier_Info, proof.length) { () =>
+ tree.createVerifier(proof)
+ }
+ }
+
+ /** Implements evaluation of AvlTree.contains method call ErgoTree node.
+ * Called via reflection based on naming convention.
+ * @see SMethod.evalMethod
+ */
+ def contains_eval(mc: MethodCall, tree: AvlTree, key: Coll[Byte], proof: Coll[Byte])
+ (implicit E: ErgoTreeEvaluator): Boolean = {
+ val bv = createVerifier(tree, proof)
+ val nItems = bv.treeHeight
+
+ var res = false
+ // the cost of tree lookup is O(bv.treeHeight)
+ E.addSeqCost(LookupAvlTree_Info, nItems) { () =>
+ res = bv.performOneOperation(Lookup(ADKey @@ key.toArray)) match {
+ case Success(r) => r match {
+ case Some(_) => true
+ case _ => false
+ }
+ case Failure(_) => false
+ }
+ }
+ res
+ }
+
+ lazy val getMethod = SMethod(this, "get",
+ SFunc(Array(SAvlTree, SByteArray, SByteArray), SByteArrayOption), 10, DynamicCost)
.withIRInfo(MethodCallIrBuilder)
.withInfo(MethodCall,
"""
@@ -1707,8 +2312,29 @@ case object SAvlTree extends SProduct with SPredefType with SMonoType {
|
""".stripMargin)
- lazy val getManyMethod = SMethod(this, "getMany",
- SFunc(IndexedSeq(SAvlTree, SByteArray2, SByteArray), TCollOptionCollByte), 11)
+ /** Implements evaluation of AvlTree.get method call ErgoTree node.
+ * Called via reflection based on naming convention.
+ * @see SMethod.evalMethod
+ */
+ def get_eval(mc: MethodCall, tree: AvlTree, key: Coll[Byte], proof: Coll[Byte])
+ (implicit E: ErgoTreeEvaluator): Option[Coll[Byte]] = {
+ val bv = createVerifier(tree, proof)
+ val nItems = bv.treeHeight
+
+ // the cost of tree lookup is O(bv.treeHeight)
+ E.addSeqCost(LookupAvlTree_Info, nItems) { () =>
+ bv.performOneOperation(Lookup(ADKey @@ key.toArray)) match {
+ case Success(r) => r match {
+ case Some(v) => Some(Colls.fromArray(v))
+ case _ => None
+ }
+ case Failure(_) => Interpreter.error(s"Tree proof is incorrect $tree")
+ }
+ }
+ }
+
+ lazy val getManyMethod = SMethod(this, "getMany",
+ SFunc(Array(SAvlTree, SByteArray2, SByteArray), TCollOptionCollByte), 11, DynamicCost)
.withIRInfo(MethodCallIrBuilder)
.withInfo(MethodCall,
"""
@@ -1722,8 +2348,30 @@ case object SAvlTree extends SProduct with SPredefType with SMonoType {
|
""".stripMargin)
- lazy val insertMethod = SMethod(this, "insert",
- SFunc(IndexedSeq(SAvlTree, CollKeyValue, SByteArray), SAvlTreeOption), 12)
+ /** Implements evaluation of AvlTree.getMany method call ErgoTree node.
+ * Called via reflection based on naming convention.
+ * @see SMethod.evalMethod
+ */
+ def getMany_eval(mc: MethodCall, tree: AvlTree, keys: Coll[Coll[Byte]], proof: Coll[Byte])
+ (implicit E: ErgoTreeEvaluator): Coll[Option[Coll[Byte]]] = {
+ val bv = createVerifier(tree, proof)
+ val nItems = bv.treeHeight
+ keys.map { key =>
+ // the cost of tree lookup is O(bv.treeHeight)
+ E.addSeqCost(LookupAvlTree_Info, nItems) { () =>
+ bv.performOneOperation(Lookup(ADKey @@ key.toArray)) match {
+ case Success(r) => r match {
+ case Some(v) => Some(Colls.fromArray(v))
+ case _ => None
+ }
+ case Failure(_) => Interpreter.error(s"Tree proof is incorrect $tree")
+ }
+ }
+ }
+ }
+
+ lazy val insertMethod = SMethod(this, "insert",
+ SFunc(Array(SAvlTree, CollKeyValue, SByteArray), SAvlTreeOption), 12, DynamicCost)
.withIRInfo(MethodCallIrBuilder)
.withInfo(MethodCall,
"""
@@ -1739,8 +2387,46 @@ case object SAvlTree extends SProduct with SPredefType with SMonoType {
|
""".stripMargin)
- lazy val updateMethod = SMethod(this, "update",
- SFunc(IndexedSeq(SAvlTree, CollKeyValue, SByteArray), SAvlTreeOption), 13)
+ /** Implements evaluation of AvlTree.insert method call ErgoTree node.
+ * Called via reflection based on naming convention.
+ * @see SMethod.evalMethod
+ */
+ def insert_eval(mc: MethodCall, tree: AvlTree, entries: Coll[(Coll[Byte], Coll[Byte])], proof: Coll[Byte])
+ (implicit E: ErgoTreeEvaluator): Option[AvlTree] = {
+ E.addCost(isInsertAllowed_Info)
+ if (!tree.isInsertAllowed) {
+ None
+ } else {
+ val bv = createVerifier(tree, proof)
+ // when the tree is empty we still need to add the insert cost
+ val nItems = Math.max(bv.treeHeight, 1)
+
+ entries.forall { case (key, value) =>
+ var res = true
+ // the cost of tree lookup is O(bv.treeHeight)
+ E.addSeqCost(InsertIntoAvlTree_Info, nItems) { () =>
+ val insert = Insert(ADKey @@ key.toArray, ADValue @@ value.toArray)
+ val insertRes = bv.performOneOperation(insert)
+ // TODO v5.0: throwing exception is not consistent with update semantics
+ // however it preserves v4.0 semantics
+ if (insertRes.isFailure) {
+ Interpreter.error(s"Incorrect insert for $tree (key: $key, value: $value, digest: ${tree.digest}): ${insertRes.failed.get}}")
+ }
+ res = insertRes.isSuccess
+ }
+ res
+ }
+ bv.digest match {
+ case Some(d) =>
+ E.addCost(updateDigest_Info)
+ Some(tree.updateDigest(Colls.fromArray(d)))
+ case _ => None
+ }
+ }
+ }
+
+ lazy val updateMethod = SMethod(this, "update",
+ SFunc(Array(SAvlTree, CollKeyValue, SByteArray), SAvlTreeOption), 13, DynamicCost)
.withIRInfo(MethodCallIrBuilder)
.withInfo(MethodCall,
"""
@@ -1756,8 +2442,43 @@ case object SAvlTree extends SProduct with SPredefType with SMonoType {
|
""".stripMargin)
- lazy val removeMethod = SMethod(this, "remove",
- SFunc(IndexedSeq(SAvlTree, SByteArray2, SByteArray), SAvlTreeOption), 14)
+ /** Implements evaluation of AvlTree.update method call ErgoTree node.
+ * Called via reflection based on naming convention.
+ * @see SMethod.evalMethod
+ */
+ def update_eval(mc: MethodCall, tree: AvlTree,
+ operations: Coll[(Coll[Byte], Coll[Byte])], proof: Coll[Byte])
+ (implicit E: ErgoTreeEvaluator): Option[AvlTree] = {
+ E.addCost(isUpdateAllowed_Info)
+ if (!tree.isUpdateAllowed) {
+ None
+ } else {
+ val bv = createVerifier(tree, proof)
+ // when the tree is empty we still need to add the insert cost
+ val nItems = Math.max(bv.treeHeight, 1)
+
+ // here we use forall as looping with fast break on first failed tree oparation
+ operations.forall { case (key, value) =>
+ var res = true
+ // the cost of tree update is O(bv.treeHeight)
+ E.addSeqCost(UpdateAvlTree_Info, nItems) { () =>
+ val op = Update(ADKey @@ key.toArray, ADValue @@ value.toArray)
+ val updateRes = bv.performOneOperation(op)
+ res = updateRes.isSuccess
+ }
+ res
+ }
+ bv.digest match {
+ case Some(d) =>
+ E.addCost(updateDigest_Info)
+ Some(tree.updateDigest(Colls.fromArray(d)))
+ case _ => None
+ }
+ }
+ }
+
+ lazy val removeMethod = SMethod(this, "remove",
+ SFunc(Array(SAvlTree, SByteArray2, SByteArray), SAvlTreeOption), 14, DynamicCost)
.withIRInfo(MethodCallIrBuilder)
.withInfo(MethodCall,
"""
@@ -1773,14 +2494,51 @@ case object SAvlTree extends SProduct with SPredefType with SMonoType {
|
""".stripMargin)
- lazy val updateDigestMethod = SMethod(this, "updateDigest",
- SFunc(IndexedSeq(SAvlTree, SByteArray), SAvlTree), 15)
+ /** Implements evaluation of AvlTree.remove method call ErgoTree node.
+ * Called via reflection based on naming convention.
+ * @see SMethod.evalMethod
+ */
+ def remove_eval(mc: MethodCall, tree: AvlTree,
+ operations: Coll[Coll[Byte]], proof: Coll[Byte])
+ (implicit E: ErgoTreeEvaluator): Option[AvlTree] = {
+ E.addCost(isRemoveAllowed_Info)
+ if (!tree.isRemoveAllowed) {
+ None
+ } else {
+ val bv = createVerifier(tree, proof)
+ // when the tree is empty we still need to add the insert cost
+ val nItems = Math.max(bv.treeHeight, 1)
+
+ cfor(0)(_ < operations.length, _ + 1) { i =>
+ E.addSeqCost(RemoveAvlTree_Info, nItems) { () =>
+ val key = operations(i).toArray
+ bv.performOneOperation(Remove(ADKey @@ key))
+ }
+ }
+
+ E.addCost(digest_Info)
+ bv.digest match {
+ case Some(d) =>
+ E.addCost(updateDigest_Info)
+ Some(tree.updateDigest(Colls.fromArray(d)))
+ case _ => None
+ }
+ }
+ }
+
+ lazy val updateDigestMethod = SMethod(this, "updateDigest",
+ SFunc(Array(SAvlTree, SByteArray), SAvlTree), 15, FixedCost(40))
.withIRInfo(MethodCallIrBuilder)
.withInfo(MethodCall,
"""
|
""".stripMargin)
+ lazy val updateDigest_Info = {
+ val m = updateDigestMethod
+ OperationCostInfo(m.costKind.asInstanceOf[FixedCost], m.opDesc)
+ }
+
protected override def getMethods(): Seq[SMethod] = super.getMethods() ++ Seq(
digestMethod,
enabledOperationsMethod,
@@ -1801,9 +2559,13 @@ case object SAvlTree extends SProduct with SPredefType with SMonoType {
override val coster = Some(Coster(_.AvlTreeCoster))
}
+/** Type descriptor of `Context` type of ErgoTree. */
case object SContext extends SProduct with SPredefType with SMonoType {
- override type WrappedType = ErgoLikeContext
+ override type WrappedType = Context
override val typeCode: TypeCode = 101: Byte
+
+ override def reprClass: Class[_] = classOf[Context]
+
override def typeId = typeCode
/** Approximate data size of the given context without ContextExtension. */
@@ -1814,17 +2576,18 @@ case object SContext extends SProduct with SPredefType with SMonoType {
import SType.{tT, paramT}
- lazy val dataInputsMethod = property("dataInputs", SBoxArray, 1)
- lazy val headersMethod = property("headers", SHeaderArray, 2)
- lazy val preHeaderMethod = property("preHeader", SPreHeader, 3)
+ lazy val dataInputsMethod = propertyCall("dataInputs", SBoxArray, 1, FixedCost(15))
+ lazy val headersMethod = propertyCall("headers", SHeaderArray, 2, FixedCost(15))
+ lazy val preHeaderMethod = propertyCall("preHeader", SPreHeader, 3, FixedCost(15))
lazy val inputsMethod = property("INPUTS", SBoxArray, 4, Inputs)
lazy val outputsMethod = property("OUTPUTS", SBoxArray, 5, Outputs)
lazy val heightMethod = property("HEIGHT", SInt, 6, Height)
lazy val selfMethod = property("SELF", SBox, 7, Self)
- lazy val selfBoxIndexMethod = property("selfBoxIndex", SInt, 8)
+ lazy val selfBoxIndexMethod = propertyCall("selfBoxIndex", SInt, 8, FixedCost(20))
lazy val lastBlockUtxoRootHashMethod = property("LastBlockUtxoRootHash", SAvlTree, 9, LastBlockUtxoRootHash)
lazy val minerPubKeyMethod = property("minerPubKey", SByteArray, 10, MinerPubkey)
- lazy val getVarMethod = SMethod(this, "getVar", SFunc(Array(SContext, SByte), SOption(tT), Array(paramT)), 11)
+ lazy val getVarMethod = SMethod(
+ this, "getVar", SFunc(Array(SContext, SByte), SOption(tT), Array(paramT)), 11, GetVar.costKind)
.withInfo(GetVar, "Get context variable with given \\lst{varId} and type.",
ArgInfo("varId", "\\lst{Byte} identifier of context variable"))
@@ -1835,9 +2598,11 @@ case object SContext extends SProduct with SPredefType with SMonoType {
override val coster = Some(Coster(_.ContextCoster))
}
+/** Type descriptor of `Header` type of ErgoTree. */
case object SHeader extends SProduct with SPredefType with SMonoType {
override type WrappedType = Header
override val typeCode: TypeCode = 104: Byte
+ override val reprClass: Class[_] = classOf[Header]
override def typeId = typeCode
/** Approximate data size of the given context without ContextExtension. */
@@ -1860,21 +2625,21 @@ case object SHeader extends SProduct with SPredefType with SMonoType {
}
override def isConstantSize = true
- lazy val idMethod = property("id", SByteArray, 1)
- lazy val versionMethod = property("version", SByte, 2)
- lazy val parentIdMethod = property("parentId", SByteArray, 3)
- lazy val ADProofsRootMethod = property("ADProofsRoot", SByteArray, 4)
- lazy val stateRootMethod = property("stateRoot", SAvlTree, 5)
- lazy val transactionsRootMethod = property("transactionsRoot", SByteArray, 6)
- lazy val timestampMethod = property("timestamp", SLong, 7)
- lazy val nBitsMethod = property("nBits", SLong, 8)
- lazy val heightMethod = property("height", SInt, 9)
- lazy val extensionRootMethod = property("extensionRoot", SByteArray, 10)
- lazy val minerPkMethod = property("minerPk", SGroupElement, 11)
- lazy val powOnetimePkMethod = property("powOnetimePk", SGroupElement, 12)
- lazy val powNonceMethod = property("powNonce", SByteArray, 13)
- lazy val powDistanceMethod = property("powDistance", SBigInt, 14)
- lazy val votesMethod = property("votes", SByteArray, 15)
+ lazy val idMethod = propertyCall("id", SByteArray, 1, FixedCost(10))
+ lazy val versionMethod = propertyCall("version", SByte, 2, FixedCost(10))
+ lazy val parentIdMethod = propertyCall("parentId", SByteArray, 3, FixedCost(10))
+ lazy val ADProofsRootMethod = propertyCall("ADProofsRoot", SByteArray, 4, FixedCost(10))
+ lazy val stateRootMethod = propertyCall("stateRoot", SAvlTree, 5, FixedCost(10))
+ lazy val transactionsRootMethod = propertyCall("transactionsRoot", SByteArray, 6, FixedCost(10))
+ lazy val timestampMethod = propertyCall("timestamp", SLong, 7, FixedCost(10))
+ lazy val nBitsMethod = propertyCall("nBits", SLong, 8, FixedCost(10))
+ lazy val heightMethod = propertyCall("height", SInt, 9, FixedCost(10))
+ lazy val extensionRootMethod = propertyCall("extensionRoot", SByteArray, 10, FixedCost(10))
+ lazy val minerPkMethod = propertyCall("minerPk", SGroupElement, 11, FixedCost(10))
+ lazy val powOnetimePkMethod = propertyCall("powOnetimePk", SGroupElement, 12, FixedCost(10))
+ lazy val powNonceMethod = propertyCall("powNonce", SByteArray, 13, FixedCost(10))
+ lazy val powDistanceMethod = propertyCall("powDistance", SBigInt, 14, FixedCost(10))
+ lazy val votesMethod = propertyCall("votes", SByteArray, 15, FixedCost(10))
protected override def getMethods() = super.getMethods() ++ Seq(
idMethod, versionMethod, parentIdMethod, ADProofsRootMethod, stateRootMethod, transactionsRootMethod,
@@ -1884,9 +2649,12 @@ case object SHeader extends SProduct with SPredefType with SMonoType {
override val coster = Some(Coster(_.HeaderCoster))
}
+/** Type descriptor of `PreHeader` type of ErgoTree. */
case object SPreHeader extends SProduct with SPredefType with SMonoType {
override type WrappedType = PreHeader
override val typeCode: TypeCode = 105: Byte
+ override val reprClass: Class[_] = classOf[PreHeader]
+
override def typeId = typeCode
/** Approximate data size of the given context without ContextExtension. */
@@ -1901,13 +2669,13 @@ case object SPreHeader extends SProduct with SPredefType with SMonoType {
}
override def isConstantSize = true
- lazy val versionMethod = property("version", SByte, 1)
- lazy val parentIdMethod = property("parentId", SByteArray, 2)
- lazy val timestampMethod = property("timestamp", SLong, 3)
- lazy val nBitsMethod = property("nBits", SLong, 4)
- lazy val heightMethod = property("height", SInt, 5)
- lazy val minerPkMethod = property("minerPk", SGroupElement, 6)
- lazy val votesMethod = property("votes", SByteArray, 7)
+ lazy val versionMethod = propertyCall("version", SByte, 1, FixedCost(10))
+ lazy val parentIdMethod = propertyCall("parentId", SByteArray, 2, FixedCost(10))
+ lazy val timestampMethod = propertyCall("timestamp", SLong, 3, FixedCost(10))
+ lazy val nBitsMethod = propertyCall("nBits", SLong, 4, FixedCost(10))
+ lazy val heightMethod = propertyCall("height", SInt, 5, FixedCost(10))
+ lazy val minerPkMethod = propertyCall("minerPk", SGroupElement, 6, FixedCost(10))
+ lazy val votesMethod = propertyCall("votes", SByteArray, 7, FixedCost(10))
protected override def getMethods() = super.getMethods() ++ Seq(
versionMethod, parentIdMethod, timestampMethod, nBitsMethod, heightMethod, minerPkMethod, votesMethod
@@ -1932,6 +2700,7 @@ case object SPreHeader extends SProduct with SPredefType with SMonoType {
case object SGlobal extends SProduct with SPredefType with SMonoType {
override type WrappedType = SigmaDslBuilder
override val typeCode: TypeCode = 106: Byte
+ override val reprClass: Class[_] = classOf[SigmaDslBuilder]
override def typeId = typeCode
/** Approximate data size of the given context without ContextExtension. */
override def dataSize(v: SType#WrappedType): Long = {
@@ -1941,10 +2710,13 @@ case object SGlobal extends SProduct with SPredefType with SMonoType {
import SType.tT
- lazy val groupGeneratorMethod = SMethod(this, "groupGenerator", SFunc(this, SGroupElement), 1)
+ lazy val groupGeneratorMethod = SMethod(
+ this, "groupGenerator", SFunc(this, SGroupElement), 1, GroupGenerator.costKind)
.withIRInfo({ case (builder, obj, method, args, tparamSubst) => GroupGenerator })
.withInfo(GroupGenerator, "")
- lazy val xorMethod = SMethod(this, "xor", SFunc(IndexedSeq(this, SByteArray, SByteArray), SByteArray), 2)
+
+ lazy val xorMethod = SMethod(
+ this, "xor", SFunc(Array(this, SByteArray, SByteArray), SByteArray), 2, Xor.costKind)
.withIRInfo({
case (_, _, _, Seq(l, r), _) => Xor(l.asByteArray, r.asByteArray)
})
diff --git a/sigmastate/src/main/scala/sigmastate/utils/SigmaByteReader.scala b/sigmastate/src/main/scala/sigmastate/utils/SigmaByteReader.scala
index c3d1826742..550743cb69 100644
--- a/sigmastate/src/main/scala/sigmastate/utils/SigmaByteReader.scala
+++ b/sigmastate/src/main/scala/sigmastate/utils/SigmaByteReader.scala
@@ -158,7 +158,7 @@ class SigmaByteReader(val r: Reader,
val size = getUInt().toIntExact
if (size == 0) Value.EmptySeq // quick short-cut when there is nothing to read
else {
- val xs = new Array[SValue](size)
+ val xs = ValueSerializer.newArray[SValue](size)
cfor(0)(_ < size, _ + 1) { i =>
xs(i) = getValue()
}
diff --git a/sigmastate/src/main/scala/sigmastate/utxo/ComplexityTableStat.scala b/sigmastate/src/main/scala/sigmastate/utxo/ComplexityTableStat.scala
index a8502849d3..c9f68138d9 100644
--- a/sigmastate/src/main/scala/sigmastate/utxo/ComplexityTableStat.scala
+++ b/sigmastate/src/main/scala/sigmastate/utxo/ComplexityTableStat.scala
@@ -9,7 +9,7 @@ import sigmastate.serialization.OpCodes
import scala.collection.mutable
object ComplexityTableStat {
- // NOTE: make immutable before making public
+ // NOTE: this class is mutable so better to keep it private
private class StatItem(
/** How many times the operation has been executed */
var count: Long,
diff --git a/sigmastate/src/main/scala/sigmastate/utxo/CostTable.scala b/sigmastate/src/main/scala/sigmastate/utxo/CostTable.scala
index 5084552d03..700ede9c30 100644
--- a/sigmastate/src/main/scala/sigmastate/utxo/CostTable.scala
+++ b/sigmastate/src/main/scala/sigmastate/utxo/CostTable.scala
@@ -1,6 +1,5 @@
package sigmastate.utxo
-import org.ergoplatform.SigmaConstants
import sigmastate.{Downcast, Upcast}
import sigmastate.lang.SigmaParser
import sigmastate.lang.Terms.OperationId
@@ -299,12 +298,6 @@ object CostTable {
CostTable(parsed.toMap)
}
- //Maximum cost of a script
- val ScriptLimit = SigmaConstants.ScriptCostLimit.value
-
- //Maximum number of expressions in initial(non-reduced script)
- val MaxExpressions = 300
-
}
diff --git a/sigmastate/src/main/scala/sigmastate/utxo/transformers.scala b/sigmastate/src/main/scala/sigmastate/utxo/transformers.scala
index 9deb31c0e1..d7c2086de6 100644
--- a/sigmastate/src/main/scala/sigmastate/utxo/transformers.scala
+++ b/sigmastate/src/main/scala/sigmastate/utxo/transformers.scala
@@ -7,14 +7,33 @@ import sigmastate._
import sigmastate.serialization.OpCodes.OpCode
import sigmastate.serialization.OpCodes
import org.ergoplatform.ErgoBox.RegisterId
+import scalan.RType
import sigmastate.Operations._
+import sigmastate.eval.{Evaluation, SigmaDsl}
+import sigmastate.interpreter.ErgoTreeEvaluator
+import sigmastate.interpreter.ErgoTreeEvaluator.{DataEnv, error}
import sigmastate.lang.exceptions.InterpreterException
+import special.collection.Coll
+import special.sigma.{Box, SigmaProp}
+// TODO refactor: remove this trait as it doesn't have semantic meaning
+/** Every operation is a transformer of some kind.
+ * This trait is used merely to simplify implementation and avoid copy-paste.
+ */
trait Transformer[IV <: SType, OV <: SType] extends NotReadyValue[OV] {
val input: Value[IV]
}
+/** Builds a new collection by applying a function to all elements of this collection.
+ *
+ * @param input the collection to be mapped
+ * @param mapper the function to apply to each element.
+ * @tparam IV the element type of the input collection.
+ * @tparam OV the element type of the returned collection.
+ * @return a new collection of type `Coll[OV]` resulting from applying the given function
+ * `mapper` to each element of this collection and collecting the results.
+ */
case class MapCollection[IV <: SType, OV <: SType](
input: Value[SCollection[IV]],
mapper: Value[SFunc])
@@ -22,21 +41,50 @@ case class MapCollection[IV <: SType, OV <: SType](
override def companion = MapCollection
override val tpe = SCollection[OV](mapper.tpe.tRange.asInstanceOf[OV])
override val opType = SCollection.MapMethod.stype.asFunc
+ protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = {
+ val inputV = input.evalTo[Coll[Any]](env)
+ val mapperV = mapper.evalTo[Any => Any](env)
+ val tResItem = Evaluation.stypeToRType(mapper.tpe.tRange).asInstanceOf[RType[Any]]
+ addSeqCostNoOp(MapCollection.costKind, inputV.length)
+ inputV.map(mapperV)(tResItem)
+ }
}
object MapCollection extends ValueCompanion {
override def opCode: OpCode = OpCodes.MapCollectionCode
+ /** Cost of: 1) obtain result RType 2) invoke map method 3) allocation of resulting
+ * collection */
+ override val costKind = PerItemCost(20, 1, 10)
}
+/** Puts the elements of other collection `col2` after the elements of `input` collection
+ * (concatenation of two collections).
+ */
case class Append[IV <: SType](input: Value[SCollection[IV]], col2: Value[SCollection[IV]])
extends Transformer[SCollection[IV], SCollection[IV]] {
override def companion = Append
override val tpe = input.tpe
override val opType = SCollection.AppendMethod.stype
+ protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = {
+ val inputV = input.evalTo[Coll[IV#WrappedType]](env)
+ val col2V = col2.evalTo[Coll[IV#WrappedType]](env)
+ addSeqCost(Append.costKind, inputV.length + col2V.length) { () =>
+ inputV.append(col2V)
+ }
+ }
}
object Append extends ValueCompanion {
override def opCode: OpCode = OpCodes.AppendCode
+ override val costKind = PerItemCost(10, 2, 100)
}
+/** Selects an interval of elements. The returned collection is made up
+ * of all elements `x` which satisfy the invariant:
+ * {{{
+ * from <= indexOf(x) < until
+ * }}}
+ * @param from the lowest index to include from this collection.
+ * @param until the lowest index to EXCLUDE from this collection.
+ */
case class Slice[IV <: SType](input: Value[SCollection[IV]], from: Value[SInt.type], until: Value[SInt.type])
extends Transformer[SCollection[IV], SCollection[IV]] {
override def companion = Slice
@@ -45,22 +93,49 @@ case class Slice[IV <: SType](input: Value[SCollection[IV]], from: Value[SInt.ty
val tpeColl = SCollection(input.tpe.typeParams.head.ident)
SFunc(Array(tpeColl, SInt, SInt), tpeColl)
}
+ protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = {
+ val inputV = input.evalTo[Coll[Any]](env)
+ val fromV = from.evalTo[Int](env)
+ val untilV = until.evalTo[Int](env)
+ val len = Math.max(0, untilV - fromV)
+ addSeqCost(Slice.costKind, len) { () =>
+ inputV.slice(fromV, untilV)
+ }
+ }
}
object Slice extends ValueCompanion {
override def opCode: OpCode = OpCodes.SliceCode
+ override val costKind = PerItemCost(10, 2, 100)
}
+/** Selects all elements of `input` collection which satisfy the condition.
+ *
+ * @param input the collection to be filtered
+ * @param condition the predicate used to test elements.
+ * @return a new collection consisting of all elements of this collection that satisfy
+ * the given `condition`. The order of the elements is preserved.
+ */
case class Filter[IV <: SType](input: Value[SCollection[IV]],
condition: Value[SFunc])
extends Transformer[SCollection[IV], SCollection[IV]] {
override def companion = Filter
override def tpe: SCollection[IV] = input.tpe
override val opType = SCollection.FilterMethod.stype
+ protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = {
+ val inputV = input.evalTo[Coll[Any]](env)
+ val conditionV = condition.evalTo[Any => Boolean](env)
+ addSeqCostNoOp(Filter.costKind, inputV.length)
+ inputV.filter(conditionV)
+ }
}
object Filter extends ValueCompanion {
override def opCode: OpCode = OpCodes.FilterCode
+ /** Cost of: 1) invoke Coll.filter method 2) allocation of resulting
+ * collection */
+ override val costKind = PerItemCost(20, 1, 10)
}
+/** Transforms a collection of values to a boolean (see [[Exists]], [[ForAll]]). */
trait BooleanTransformer[IV <: SType] extends Transformer[SCollection[IV], SBoolean.type] {
override val input: Value[SCollection[IV]]
val condition: Value[SFunc]
@@ -70,28 +145,73 @@ trait BooleanTransformerCompanion extends ValueCompanion {
def argInfos: Seq[ArgInfo]
}
+/** Tests whether a predicate holds for at least one element of this collection.
+ *
+ * @param input the collection to be tested
+ * @param condition the predicate used to test elements.
+ * @return `true` if the given `condition` is satisfied by at least one element of this
+ * collection, otherwise `false`
+ */
case class Exists[IV <: SType](override val input: Value[SCollection[IV]],
override val condition: Value[SFunc])
extends BooleanTransformer[IV] {
override def companion = Exists
override val opType = SCollection.ExistsMethod.stype
+ protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = {
+ val inputV = input.evalTo[Coll[Any]](env)
+ val conditionV = condition.evalTo[Any => Boolean](env)
+ addSeqCostNoOp(Exists.costKind, inputV.length)
+ inputV.exists(conditionV)
+ }
}
object Exists extends BooleanTransformerCompanion {
override def opCode: OpCode = OpCodes.ExistsCode
+ /** Cost of: invoke exists method */
+ override val costKind = PerItemCost(3, 1, 10)
override def argInfos: Seq[ArgInfo] = ExistsInfo.argInfos
}
+/** Tests whether a predicate holds for all elements of this collection.
+ *
+ * @param input the collection to be tested
+ * @param condition the predicate used to test elements.
+ * @return `true` if this collection is empty or the given `condition`
+ * holds for all elements of this collection, otherwise `false`.
+ */
case class ForAll[IV <: SType](override val input: Value[SCollection[IV]],
override val condition: Value[SFunc])
extends BooleanTransformer[IV] {
override def companion = ForAll
override val opType = SCollection.ForallMethod.stype
+ protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = {
+ val inputV = input.evalTo[Coll[Any]](env)
+ val conditionV = condition.evalTo[Any => Boolean](env)
+ addSeqCostNoOp(ForAll.costKind, inputV.length)
+ inputV.forall(conditionV)
+ }
}
object ForAll extends BooleanTransformerCompanion {
override def opCode: OpCode = OpCodes.ForAllCode
+ /** Cost of: invoke forall method */
+ override val costKind = PerItemCost(3, 1, 10)
override def argInfos: Seq[ArgInfo] = ForAllInfo.argInfos
}
+/** Applies a binary function to a start value and all elements of this collection,
+ * going left to right.
+ *
+ * @param input the collection to iterate
+ * @param zero the start value.
+ * @param foldOp the binary function.
+ * @tparam OV the result type of the binary operator.
+ * @return the result of inserting `foldOp` between consecutive elements of this collection,
+ * going left to right with the start value `zero` on the left:
+ * {{{
+ * foldOp(...foldOp(zero, x_1), x_2, ..., x_n)
+ * }}}
+ * where `x_1, ..., x_n` are the elements of this collection.
+ * Returns `zero` if this collection is empty.
+ */
case class Fold[IV <: SType, OV <: SType](input: Value[SCollection[IV]],
zero: Value[OV],
foldOp: Value[SFunc])
@@ -99,10 +219,19 @@ case class Fold[IV <: SType, OV <: SType](input: Value[SCollection[IV]],
override def companion = Fold
implicit override def tpe: OV = zero.tpe
val opType: SFunc = SCollection.FoldMethod.stype
+ protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = {
+ val inputV = input.evalTo[Coll[IV#WrappedType]](env)
+ val zeroV = zero.evalTo[OV#WrappedType](env)
+ Value.checkType(zero, zeroV) // necessary because cast to OV#WrappedType is erased
+ val foldOpV = foldOp.evalTo[((OV#WrappedType, IV#WrappedType)) => OV#WrappedType](env)
+ addSeqCostNoOp(Fold.costKind, inputV.length)
+ inputV.foldLeft(zeroV, foldOpV)
+ }
}
object Fold extends ValueCompanion {
override def opCode: OpCode = OpCodes.FoldCode
+ override val costKind = PerItemCost(3, 1, 10)
def sum[T <: SNumericType](input: Value[SCollection[T]], varId: Int)(implicit tT: T) =
Fold(input,
Constant(tT.upcast(0.toByte), tT),
@@ -113,6 +242,15 @@ object Fold extends ValueCompanion {
)
}
+/** The element of the collection or default value.
+ * If an index is out of bounds (`i < 0 || i >= length`) then `default` value is returned.
+ *
+ * @param input the zero-based indexed collection
+ * @param index the index of the requested element (zero-based)
+ * @tparam V the type of elements
+ * @return the element at the given index or `default` value if index is out or bounds
+ * @throws ArrayIndexOutOfBoundsException if `index < 0` or `length <= index`
+ */
case class ByIndex[V <: SType](input: Value[SCollection[V]],
index: Value[SInt.type],
default: Option[Value[V]] = None)
@@ -120,20 +258,53 @@ case class ByIndex[V <: SType](input: Value[SCollection[V]],
override def companion = ByIndex
override val tpe = input.tpe.elemType
override val opType = SCollection.ApplyMethod.stype.asFunc
+ protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = {
+ val inputV = input.evalTo[Coll[V#WrappedType]](env)
+ val indexV = index.evalTo[Int](env)
+ default match {
+ case Some(d) =>
+ val dV = d.evalTo[V#WrappedType](env)
+ Value.checkType(d, dV) // necessary because cast to V#WrappedType is erased
+ addCost(ByIndex.costKind)
+ inputV.getOrElse(indexV, dV)
+ case _ =>
+ addCost(ByIndex.costKind)
+ inputV.apply(indexV)
+ }
+ }
}
object ByIndex extends ValueCompanion {
override def opCode: OpCode = OpCodes.ByIndexCode
+ override val costKind = FixedCost(30)
}
-/** Select tuple field by its 1-based index. E.g. input._1 is transformed to SelectField(input, 1) */
+/** Select tuple field by its 1-based index. E.g. input._1 is transformed to
+ * SelectField(input, 1)
+ */
case class SelectField(input: Value[STuple], fieldIndex: Byte)
extends Transformer[STuple, SType] with NotReadyValue[SType] {
override def companion = SelectField
override val tpe = input.tpe.items(fieldIndex - 1)
override val opType = SFunc(input.tpe, tpe)
+
+ protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = {
+ val inputV = input.evalTo[Any](env)
+ addCost(SelectField.costKind)
+ inputV match {
+ case p: Tuple2[_,_] =>
+ if (fieldIndex == 1) p._1
+ else if (fieldIndex == 2) p._2
+ else error(s"Unknown fieldIndex $fieldIndex to select from $p: evaluating tree $this")
+ case _ =>
+ Value.typeError(input, inputV)
+ }
+ }
}
-object SelectField extends ValueCompanion {
+object SelectField extends FixedCostValueCompanion {
override def opCode: OpCode = OpCodes.SelectFieldCode
+ /** Cost of: 1) Calling Tuple2.{_1, _2} Scala methods.
+ * Old cost: ("SelectField", "() => Unit", selectField) */
+ override val costKind = FixedCost(10)
def typed[T <: SValue](input: Value[STuple], fieldIndex: Byte): T = {
SelectField(input, fieldIndex).asInstanceOf[T]
}
@@ -147,6 +318,7 @@ case class SigmaPropIsProven(input: Value[SSigmaProp.type])
}
object SigmaPropIsProven extends ValueCompanion {
override def opCode: OpCode = OpCodes.SigmaPropIsProvenCode
+ override def costKind: CostKind = Value.notSupportedError(this, "costKind")
}
/** Extract serialized bytes of a SigmaProp value */
@@ -155,86 +327,170 @@ case class SigmaPropBytes(input: Value[SSigmaProp.type])
override def companion = SigmaPropBytes
override def tpe = SByteArray
override val opType = SFunc(input.tpe, tpe)
+ protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = {
+ val inputV = input.evalTo[SigmaProp](env)
+ val numNodes = SigmaDsl.toSigmaBoolean(inputV).size
+ addSeqCost(SigmaPropBytes.costKind, numNodes) { () =>
+ inputV.propBytes
+ }
+ }
}
-object SigmaPropBytes extends ValueCompanion {
+object SigmaPropBytes extends PerItemCostValueCompanion {
override def opCode: OpCode = OpCodes.SigmaPropBytesCode
+ /** BaseCost: serializing one node of SigmaBoolean proposition
+ * PerChunkCost: serializing one node of SigmaBoolean proposition */
+ override val costKind = PerItemCost(baseCost = 35, perChunkCost = 6, chunkSize = 1)
}
trait SimpleTransformerCompanion extends ValueCompanion {
def argInfos: Seq[ArgInfo]
}
+
+/** The length of the collection (aka size). */
case class SizeOf[V <: SType](input: Value[SCollection[V]])
extends Transformer[SCollection[V], SInt.type] with NotReadyValueInt {
override def companion = SizeOf
override def opType = SizeOf.OpType
+ protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = {
+ val inputV = input.evalTo[Coll[Any]](env)
+ addCost(SizeOf.costKind)
+ inputV.length
+ }
}
object SizeOf extends SimpleTransformerCompanion {
val OpType = SFunc(SCollection(SType.tIV), SInt)
override def opCode: OpCode = OpCodes.SizeOfCode
+ /** Cost of: 1) calling Coll.length method (guaranteed to be O(1))
+ * Twice the cost of SelectField.
+ * Old cost: ("SizeOf", "(Coll[IV]) => Int", collLength) */
+ override val costKind = FixedCost(14)
override def argInfos: Seq[ArgInfo] = SizeOfInfo.argInfos
}
sealed trait Extract[V <: SType] extends Transformer[SBox.type, V] {
}
+/** Extracts the monetary value, in Ergo tokens (NanoErg unit of measure) from input Box. */
case class ExtractAmount(input: Value[SBox.type]) extends Extract[SLong.type] with NotReadyValueLong {
override def companion = ExtractAmount
override def opType = ExtractAmount.OpType
+ protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = {
+ val inputV = input.evalTo[Box](env)
+ addCost(ExtractAmount.costKind)
+ inputV.value
+ }
}
object ExtractAmount extends SimpleTransformerCompanion {
val OpType = SFunc(SBox, SLong)
override def opCode: OpCode = OpCodes.ExtractAmountCode
+ /** Cost of: 1) access `value` property of a [[special.sigma.Box]] */
+ override val costKind = FixedCost(8)
override def argInfos: Seq[ArgInfo] = ExtractAmountInfo.argInfos
}
+/** Extract serialized bytes of guarding script.
+ * As a reminder, the script should be evaluated to true in order to
+ * open this box. (aka spend it in a transaction).
+ */
case class ExtractScriptBytes(input: Value[SBox.type]) extends Extract[SByteArray] with NotReadyValueByteArray {
override def companion = ExtractScriptBytes
override def opType = ExtractScriptBytes.OpType
+ protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = {
+ val inputV = input.evalTo[Box](env)
+ addCost(ExtractScriptBytes.costKind)
+ inputV.propositionBytes
+ }
}
object ExtractScriptBytes extends SimpleTransformerCompanion {
val OpType = SFunc(SBox, SByteArray)
override def opCode: OpCode = OpCodes.ExtractScriptBytesCode
+
+ // TODO v5.0: ensure the following is true
+ /** The cost is fixed and doesn't include serialization of ErgoTree because
+ * the ErgoTree is expected to be constructed with non-null propositionBytes.
+ * This is (and must be) guaranteed by ErgoTree deserializer.
+ * CostOf: accessing ErgoBox.propositionBytes
+ */
+ override val costKind = FixedCost(10)
override def argInfos: Seq[ArgInfo] = ExtractScriptBytesInfo.argInfos
}
+/** Extracts serialized bytes of this box's content, including proposition bytes. */
case class ExtractBytes(input: Value[SBox.type]) extends Extract[SByteArray] with NotReadyValueByteArray {
override def companion = ExtractBytes
override def opType = ExtractBytes.OpType
+ protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = {
+ val inputV = input.evalTo[Box](env)
+ addCost(ExtractBytes.costKind)
+ inputV.bytes
+ }
}
object ExtractBytes extends SimpleTransformerCompanion {
val OpType = SFunc(SBox, SByteArray)
override def opCode: OpCode = OpCodes.ExtractBytesCode
+ /** The cost is fixed and doesn't include serialization of ErgoBox because
+ * the ErgoBox is expected to be constructed with non-null `bytes`.
+ * TODO v5.0: This is not, but must be guaranteed by ErgoBox deserializer. */
+ override val costKind = FixedCost(12)
override def argInfos: Seq[ArgInfo] = ExtractBytesInfo.argInfos
}
+/** Extracts serialized bytes of this box's content, excluding transactionId and index of output. */
case class ExtractBytesWithNoRef(input: Value[SBox.type]) extends Extract[SByteArray] with NotReadyValueByteArray {
override def companion = ExtractBytesWithNoRef
override def opType = ExtractBytesWithNoRef.OpType
+ protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = {
+ val inputV = input.evalTo[Box](env)
+ addCost(ExtractBytesWithNoRef.costKind)
+ inputV.bytesWithoutRef
+ }
}
object ExtractBytesWithNoRef extends SimpleTransformerCompanion {
val OpType = SFunc(SBox, SByteArray)
override def opCode: OpCode = OpCodes.ExtractBytesWithNoRefCode
+
+ /** The cost if fixed and doesn't include serialization of ErgoBox because
+ * the ErgoBox is expected to be constructed with non-null `bytes`. */
+ override val costKind = FixedCost(12)
+
override def argInfos: Seq[ArgInfo] = ExtractBytesWithNoRefInfo.argInfos
}
+/** Extracts Blake2b256 hash of this box's content, basically equals to `blake2b256(bytes)` */
case class ExtractId(input: Value[SBox.type]) extends Extract[SByteArray] with NotReadyValueByteArray {
override def companion = ExtractId
override def opType = ExtractId.OpType
+ protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = {
+ val inputV = input.evalTo[Box](env)
+ addCost(ExtractId.costKind)
+ inputV.id
+ }
}
object ExtractId extends SimpleTransformerCompanion {
val OpType = SFunc(SBox, SByteArray)
override def opCode: OpCode = OpCodes.ExtractIdCode
+ /** CostOf: cost of computing hash from `ErgoBox.bytes` */
+ override val costKind = FixedCost(12)
override def argInfos: Seq[ArgInfo] = ExtractIdInfo.argInfos
}
+/** See [[Box.getReg()]]*/
case class ExtractRegisterAs[V <: SType]( input: Value[SBox.type],
registerId: RegisterId,
override val tpe: SOption[V])
extends Extract[SOption[V]] with NotReadyValue[SOption[V]] {
override def companion = ExtractRegisterAs
override val opType = SFunc(ExtractRegisterAs.BoxAndByte, tpe)
+ lazy val tV = Evaluation.stypeToRType(tpe.elemType)
+ protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = {
+ val inputV = input.evalTo[Box](env)
+ addCost(ExtractRegisterAs.costKind)
+ inputV.getReg(registerId.number)(tV)
+ }
}
object ExtractRegisterAs extends ValueCompanion {
override def opCode: OpCode = OpCodes.ExtractRegisterAs
+ /** CostOf: 1) accessing `registers` collection 2) comparing types 3) allocating Some()*/
+ override val costKind = FixedCost(50)
//HOTSPOT:: avoids thousands of allocations per second
private val BoxAndByte: IndexedSeq[SType] = Array(SBox, SByte)
@@ -253,9 +509,15 @@ case class ExtractCreationInfo(input: Value[SBox.type]) extends Extract[STuple]
override def companion = ExtractCreationInfo
override def tpe: STuple = ResultType
override def opType = OpType
+ protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = {
+ val inputV = input.evalTo[Box](env)
+ addCost(ExtractCreationInfo.costKind)
+ inputV.creationInfo
+ }
}
object ExtractCreationInfo extends SimpleTransformerCompanion {
override def opCode: OpCode = OpCodes.ExtractCreationInfoCode
+ override val costKind = FixedCost(16)
override def argInfos: Seq[ArgInfo] = ExtractCreationInfoInfo.argInfos
val ResultType = STuple(SInt, SByteArray)
val OpType = SFunc(SBox, ResultType)
@@ -277,6 +539,7 @@ case class DeserializeContext[V <: SType](id: Byte, tpe: V) extends Deserialize[
}
object DeserializeContext extends ValueCompanion {
override def opCode: OpCode = OpCodes.DeserializeContextCode
+ override val costKind = PerItemCost(1, 10, 128)
}
/** Extract register of SELF box as Coll[Byte], deserialize it into Value and inline into executing script.
@@ -288,45 +551,91 @@ case class DeserializeRegister[V <: SType](reg: RegisterId, tpe: V, default: Opt
}
object DeserializeRegister extends ValueCompanion {
override def opCode: OpCode = OpCodes.DeserializeRegisterCode
+ override val costKind = PerItemCost(1, 10, 128)
}
+/** See [[special.sigma.Context.getVar()]] for detailed description. */
case class GetVar[V <: SType](varId: Byte, override val tpe: SOption[V]) extends NotReadyValue[SOption[V]] {
override def companion = GetVar
- override val opType = SFunc(Array(SContext, SByte), tpe)
+ override val opType = SFunc(Array(SContext, SByte), tpe) // TODO optimize: avoid Array allocation
+ protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = {
+ val t = Evaluation.stypeToRType(tpe.elemType)
+ addCost(GetVar.costKind)
+ E.context.getVar(varId)(t)
+ }
}
-object GetVar extends ValueCompanion {
+object GetVar extends FixedCostValueCompanion {
override def opCode: OpCode = OpCodes.GetVarCode
+ /** Cost of: 1) accessing to array of context vars by index
+ * Old cost: ("GetVar", "(Context, Byte) => Option[T]", getVarCost) */
+ override val costKind = FixedCost(10)
def apply[V <: SType](varId: Byte, innerTpe: V): GetVar[V] = GetVar[V](varId, SOption(innerTpe))
}
+/** Returns the option's value.
+ *
+ * @note The option must be nonempty.
+ * @throws java.util.NoSuchElementException if the option is empty.
+ */
case class OptionGet[V <: SType](input: Value[SOption[V]]) extends Transformer[SOption[V], V] {
override def companion = OptionGet
override val opType = SFunc(input.tpe, tpe)
override def tpe: V = input.tpe.elemType
override def toString: String = s"$input.get"
+ protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = {
+ val opt = input.evalTo[Option[V#WrappedType]](env)
+ addCost(OptionGet.costKind)
+ opt.get
+ }
}
-object OptionGet extends SimpleTransformerCompanion {
+object OptionGet extends SimpleTransformerCompanion with FixedCostValueCompanion {
override def opCode: OpCode = OpCodes.OptionGetCode
+ /** Cost of: 1) Calling Option.get Scala method. */
+ override val costKind = FixedCost(15)
override def argInfos: Seq[ArgInfo] = OptionGetInfo.argInfos
}
+/** Returns the option's value if the option is nonempty, otherwise
+ * return the result of evaluating `default`.
+ * NOTE: the `default` is evaluated even if the option contains the value
+ * i.e. not lazily.
+ *
+ * @param default the default expression.
+ */
case class OptionGetOrElse[V <: SType](input: Value[SOption[V]], default: Value[V])
extends Transformer[SOption[V], V] {
override def companion = OptionGetOrElse
override val opType = SFunc(IndexedSeq(input.tpe, tpe), tpe)
override def tpe: V = input.tpe.elemType
+ protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = {
+ val inputV = input.evalTo[Option[V#WrappedType]](env)
+ val dV = default.evalTo[V#WrappedType](env) // TODO v6.0: execute lazily
+ Value.checkType(default, dV) // necessary because cast to V#WrappedType is erased
+ addCost(OptionGetOrElse.costKind)
+ inputV.getOrElse(dV)
+ }
}
object OptionGetOrElse extends ValueCompanion {
override def opCode: OpCode = OpCodes.OptionGetOrElseCode
+ /** Cost of: 1) Calling Option.getOrElse Scala method. */
+ override val costKind = FixedCost(20)
}
+/** Returns false if the option is None, true otherwise. */
case class OptionIsDefined[V <: SType](input: Value[SOption[V]])
extends Transformer[SOption[V], SBoolean.type] {
override def companion = OptionIsDefined
override val opType = SFunc(input.tpe, SBoolean)
override def tpe= SBoolean
+ protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = {
+ val inputV = input.evalTo[Option[V#WrappedType]](env)
+ addCost(OptionIsDefined.costKind)
+ inputV.isDefined
+ }
}
object OptionIsDefined extends SimpleTransformerCompanion {
override def opCode: OpCode = OpCodes.OptionIsDefinedCode
+ /** Cost of: 1) Calling Option.isDefined Scala method. */
+ override val costKind = FixedCost(10)
override def argInfos: Seq[ArgInfo] = OptionIsDefinedInfo.argInfos
}
diff --git a/sigmastate/src/test/scala/org/ergoplatform/ErgoAddressSpecification.scala b/sigmastate/src/test/scala/org/ergoplatform/ErgoAddressSpecification.scala
index 743f95e90d..f8dca4c300 100644
--- a/sigmastate/src/test/scala/org/ergoplatform/ErgoAddressSpecification.scala
+++ b/sigmastate/src/test/scala/org/ergoplatform/ErgoAddressSpecification.scala
@@ -2,26 +2,27 @@ package org.ergoplatform
import java.math.BigInteger
-import org.ergoplatform.ErgoAddressEncoder.{MainnetNetworkPrefix, TestnetNetworkPrefix, hash256}
+import org.ergoplatform.ErgoAddressEncoder.{hash256, MainnetNetworkPrefix, TestnetNetworkPrefix}
import org.ergoplatform.SigmaConstants.ScriptCostLimit
import org.ergoplatform.validation.{ValidationException, ValidationRules}
import org.scalatest.{Assertion, TryValues}
import scorex.crypto.hash.Blake2b256
import sigmastate.basics.DLogProtocol
-import sigmastate.basics.DLogProtocol.{DLogProverInput, ProveDlog}
+import sigmastate.basics.DLogProtocol.{ProveDlog, DLogProverInput}
import sigmastate.serialization.ErgoTreeSerializer.DefaultSerializer
-import sigmastate.serialization.{GroupElementSerializer, ValueSerializer}
+import sigmastate.serialization.{ValueSerializer, GroupElementSerializer}
import scorex.util.encode.Base58
-import sigmastate.{CrossVersionProps, SType, SigmaAnd}
-import sigmastate.Values.{ByteArrayConstant, Constant, ErgoTree, EvaluatedValue, IntConstant, UnparsedErgoTree}
+import sigmastate.{CrossVersionProps, SigmaAnd, SType}
+import sigmastate.Values.{UnparsedErgoTree, Constant, EvaluatedValue, ByteArrayConstant, IntConstant, ErgoTree}
import sigmastate.eval.IRContext
import sigmastate.helpers._
import sigmastate.helpers.TestingHelpers._
+import sigmastate.interpreter.ContextExtension.VarBinding
import sigmastate.interpreter.CryptoConstants.dlogGroup
import sigmastate.interpreter.{ContextExtension, CostedProverResult}
-import sigmastate.interpreter.Interpreter.{ScriptEnv, ScriptNameProp}
+import sigmastate.interpreter.Interpreter.{ScriptNameProp, ScriptEnv}
import sigmastate.lang.Terms.ValueOps
-import sigmastate.lang.exceptions.{CostLimitException, CosterException}
+import sigmastate.lang.exceptions.{CosterException, CostLimitException}
import sigmastate.utils.Helpers._
import special.sigma.SigmaDslTesting
@@ -259,7 +260,7 @@ class ErgoAddressSpecification extends SigmaDslTesting
property("negative cases: deserialized script + costing exceptions") {
implicit lazy val IR = new TestingIRContext
- def testPay2SHAddress(address: Pay2SHAddress, script: (Byte, EvaluatedValue[_ <: SType]), costLimit: Int = ScriptCostLimit.value): CostedProverResult = {
+ def testPay2SHAddress(address: Pay2SHAddress, script: VarBinding, costLimit: Int = ScriptCostLimit.value): CostedProverResult = {
val boxToSpend = testBox(10, address.script, creationHeight = 5)
val ctx = copyContext(ErgoLikeContextTesting.dummy(boxToSpend, activatedVersionInTests)
.withExtension(ContextExtension(Seq(
diff --git a/sigmastate/src/test/scala/org/ergoplatform/ErgoScriptPredefSpec.scala b/sigmastate/src/test/scala/org/ergoplatform/ErgoScriptPredefSpec.scala
index 1d9991520b..fbf24cec1d 100644
--- a/sigmastate/src/test/scala/org/ergoplatform/ErgoScriptPredefSpec.scala
+++ b/sigmastate/src/test/scala/org/ergoplatform/ErgoScriptPredefSpec.scala
@@ -16,7 +16,7 @@ import sigmastate.interpreter.Interpreter.{ScriptNameProp, emptyEnv}
import sigmastate.interpreter.{ProverResult, ContextExtension}
import sigmastate.lang.Terms.ValueOps
import sigmastate.serialization.ValueSerializer
-import sigmastate.utxo.{CostTable, ExtractCreationInfo, ByIndex, SelectField}
+import sigmastate.utxo.{ExtractCreationInfo, ByIndex, SelectField}
import scalan.util.BenchmarkUtil._
import sigmastate.utils.Helpers._
@@ -164,7 +164,7 @@ class ErgoScriptPredefSpec extends SigmaTestingCommons with CrossVersionProps {
// collect coins during the fixed rate period
forAll(Gen.choose(1, settings.fixedRatePeriod)) { height =>
val currentRate = emission.minersRewardAtHeight(height)
- createRewardTx(currentRate, height, minerProp) shouldBe 'success
+ createRewardTx(currentRate, height, minerProp).getOrThrow
createRewardTx(currentRate + 1, height, minerProp) shouldBe 'failure
createRewardTx(currentRate - 1, height, minerProp) shouldBe 'failure
}
@@ -226,7 +226,7 @@ class ErgoScriptPredefSpec extends SigmaTestingCommons with CrossVersionProps {
boxesToSpend = inputBoxes,
spendingTransaction,
self = inputBoxes.head,
- activatedVersionInTests).withCostLimit(CostTable.ScriptLimit * 10)
+ activatedVersionInTests).withCostLimit(SigmaConstants.ScriptCostLimit.value * 10)
val pr = prover.prove(emptyEnv + (ScriptNameProp -> "tokenThresholdScript_prove"), prop, ctx, fakeMessage).getOrThrow
verifier.verify(emptyEnv + (ScriptNameProp -> "tokenThresholdScript_verify"), prop, ctx, pr, fakeMessage).getOrThrow._1 shouldBe true
diff --git a/sigmastate/src/test/scala/sigmastate/CalcSha256Specification.scala b/sigmastate/src/test/scala/sigmastate/CalcSha256Specification.scala
index 28a28c9d91..db3922960e 100644
--- a/sigmastate/src/test/scala/sigmastate/CalcSha256Specification.scala
+++ b/sigmastate/src/test/scala/sigmastate/CalcSha256Specification.scala
@@ -24,7 +24,7 @@ class CalcSha256Specification extends SigmaTestingCommons
("", "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"),
("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", "248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1"),
("abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu", "cf5b16a778af8380036ce59e7b0492370b249b11e8f07a51afac45037afee9d1"),
- (Array.fill[String](1000000)("a").mkString, "cdc76e5c9914fb9281a1c7e284d73e67f1809a48a497200e046d39ccc7112cd0")
+ ("a" * 1000, "41edece42d63e8d9bf515a9ba6932e1c20cbc9f5a5d134645adb5db1b9737ea3")
)
property("CalcSha256: Should pass standard tests.") {
@@ -33,7 +33,7 @@ class CalcSha256Specification extends SigmaTestingCommons
forAll(objects) { (in, result) =>
val expectedResult = decodeString(result)
val calcSha256 = EQ(CalcSha256(stringToByteConstant(in)), expectedResult)
- val res = int.reduceToCrypto(ctx, calcSha256).get.value
+ val res = int.reduceToCrypto(ctx, calcSha256.toSigmaProp).get.value
res shouldBe TrivialProp.TrueProp
}
}
diff --git a/sigmastate/src/test/scala/sigmastate/CrossVersionProps.scala b/sigmastate/src/test/scala/sigmastate/CrossVersionProps.scala
index 2dd25fb151..11ae6342b5 100644
--- a/sigmastate/src/test/scala/sigmastate/CrossVersionProps.scala
+++ b/sigmastate/src/test/scala/sigmastate/CrossVersionProps.scala
@@ -2,17 +2,51 @@ package sigmastate
import org.scalatest.{PropSpecLike, Tag}
import org.scalactic.source.Position
+import sigmastate.eval.Profiler
import spire.syntax.all.cfor
+import scala.util.DynamicVariable
+
trait CrossVersionProps extends PropSpecLike with TestsBase {
val printVersions: Boolean = false
+ /** Number of times each test property is warmed up (i.e. executed before final execution). */
+ def perTestWarmUpIters: Int = 0
+
+ private[sigmastate] val _warmupProfiler = new DynamicVariable[Option[Profiler]](None)
+ def warmupProfiler: Option[Profiler] = _warmupProfiler.value
+
+ protected def testFun_Run(testName: String, testFun: => Any): Unit = {
+ def msg = s"""property("$testName")(ActivatedVersion = $activatedVersionInTests; ErgoTree version = $ergoTreeVersionInTests)"""
+ if (printVersions) println(msg)
+ try testFun
+ catch {
+ case t: Throwable =>
+ if (!printVersions) {
+ // wasn't printed, print it now
+ println(msg)
+ }
+ throw t
+ }
+ }
+
override protected def property(testName: String, testTags: Tag*)
(testFun: => Any)
(implicit pos: Position): Unit = {
super.property(testName, testTags:_*) {
+ // do warmup if necessary
+ if (perTestWarmUpIters > 0) {
+ _warmupProfiler.withValue(Some(new Profiler)) {
+ cfor(0)(_ < perTestWarmUpIters, _ + 1) { _ =>
+ testFun_Run(testName, testFun)
+ }
+ }
+ System.gc()
+ Thread.sleep(100) // give it some time to finish warm-up
+ }
+
cfor(0)(_ < activatedVersions.length, _ + 1) { i =>
val activatedVersion = activatedVersions(i)
_currActivatedVersion.withValue(activatedVersion) {
@@ -22,22 +56,18 @@ trait CrossVersionProps extends PropSpecLike with TestsBase {
_ + 1) { j =>
val treeVersion = ergoTreeVersions(j)
_currErgoTreeVersion.withValue(treeVersion) {
- def msg = s"""property("$testName")(ActivatedVersion = $activatedVersionInTests; ErgoTree version = $ergoTreeVersionInTests)"""
- if (printVersions) println(msg)
- try testFun
- catch {
- case t: Throwable =>
- if (!printVersions) {
- // wasn't printed, print it now
- println(msg)
- }
- throw t
- }
+ testFun_Run(testName, testFun)
}
}
}
}
+
+ if (okRunTestsWithoutMCLowering) {
+ _lowerMethodCalls.withValue(false) {
+ testFun_Run(testName, testFun)
+ }
+ }
}
}
}
diff --git a/sigmastate/src/test/scala/sigmastate/SoftForkabilitySpecification.scala b/sigmastate/src/test/scala/sigmastate/SoftForkabilitySpecification.scala
index 27efc5a695..341c29428a 100644
--- a/sigmastate/src/test/scala/sigmastate/SoftForkabilitySpecification.scala
+++ b/sigmastate/src/test/scala/sigmastate/SoftForkabilitySpecification.scala
@@ -107,6 +107,7 @@ class SoftForkabilitySpecification extends SigmaTestingData with BeforeAndAfterA
override def companion = this
override val opCode: OpCode = Height2Code // use reserved code
override val opType = SFunc(SContext, SInt)
+ override val costKind: CostKind = Height.costKind
}
val Height2Ser = CaseObjectSerialization(Height2, Height2)
diff --git a/sigmastate/src/test/scala/sigmastate/TestingInterpreterSpecification.scala b/sigmastate/src/test/scala/sigmastate/TestingInterpreterSpecification.scala
index e50723f5f0..8f475aa3e7 100644
--- a/sigmastate/src/test/scala/sigmastate/TestingInterpreterSpecification.scala
+++ b/sigmastate/src/test/scala/sigmastate/TestingInterpreterSpecification.scala
@@ -48,30 +48,30 @@ class TestingInterpreterSpecification extends SigmaTestingCommons
forAll() { i: Int =>
val h = i.toAbs
whenever(h > 0 && h < Int.MaxValue - 1) {
- val dk1 = SigmaPropConstant(DLogProverInput.random().publicImage).isProven
+ val dk1 = SigmaPropConstant(DLogProverInput.random().publicImage)
val ctx = testingContext(h)
- prover.reduceToCrypto(ctx, AND(GE(Height, IntConstant(h - 1)), dk1)).get.value should(
+ prover.reduceToCrypto(ctx, SigmaAnd(GE(Height, IntConstant(h - 1)), dk1)).get.value should(
matchPattern { case _: SigmaBoolean => })
- prover.reduceToCrypto(ctx, AND(GE(Height, IntConstant(h)), dk1)).get.value should (
+ prover.reduceToCrypto(ctx, SigmaAnd(GE(Height, IntConstant(h)), dk1)).get.value should (
matchPattern { case _: SigmaBoolean => })
{
- val res = prover.reduceToCrypto(ctx, AND(GE(Height, IntConstant(h + 1)), dk1)).get.value
+ val res = prover.reduceToCrypto(ctx, SigmaAnd(GE(Height, IntConstant(h + 1)), dk1)).get.value
res should matchPattern { case TrivialProp.FalseProp => }
}
{
- val res = prover.reduceToCrypto(ctx, OR(GE(Height, IntConstant(h - 1)), dk1)).get.value
+ val res = prover.reduceToCrypto(ctx, SigmaOr(GE(Height, IntConstant(h - 1)), dk1)).get.value
res should matchPattern { case TrivialProp.TrueProp => }
}
{
- val res = prover.reduceToCrypto(ctx, OR(GE(Height, IntConstant(h)), dk1)).get.value
+ val res = prover.reduceToCrypto(ctx, SigmaOr(GE(Height, IntConstant(h)), dk1)).get.value
res should matchPattern { case TrivialProp.TrueProp => }
}
{
- val res = prover.reduceToCrypto(ctx, OR(GE(Height, IntConstant(h + 1)), dk1)).get.value
+ val res = prover.reduceToCrypto(ctx, SigmaOr(GE(Height, IntConstant(h + 1)), dk1)).get.value
res should matchPattern { case _: SigmaBoolean => }
}
}
@@ -83,32 +83,32 @@ class TestingInterpreterSpecification extends SigmaTestingCommons
val h = i.toAbs
whenever(h > 0 && h < Int.MaxValue - 1) {
- val dk1 = DLogProverInput.random().publicImage.isProven
- val dk2 = DLogProverInput.random().publicImage.isProven
+ val dk1 = DLogProverInput.random().publicImage
+ val dk2 = DLogProverInput.random().publicImage
val ctx = testingContext(h)
- assert(prover.reduceToCrypto(ctx, OR(
- AND(LE(Height, IntConstant(h + 1)), AND(dk1, dk2)),
- AND(GT(Height, IntConstant(h + 1)), dk1)
+ assert(prover.reduceToCrypto(ctx, SigmaOr(
+ SigmaAnd(LE(Height, IntConstant(h + 1)), SigmaAnd(dk1, dk2)),
+ SigmaAnd(GT(Height, IntConstant(h + 1)), dk1)
)).get.value.isInstanceOf[CAND])
- assert(prover.reduceToCrypto(ctx, OR(
- AND(LE(Height, IntConstant(h - 1)), AND(dk1, dk2)),
- AND(GT(Height, IntConstant(h - 1)), dk1)
+ assert(prover.reduceToCrypto(ctx, SigmaOr(
+ SigmaAnd(LE(Height, IntConstant(h - 1)), SigmaAnd(dk1, dk2)),
+ SigmaAnd(GT(Height, IntConstant(h - 1)), dk1)
)).get.value.isInstanceOf[ProveDlog])
- prover.reduceToCrypto(ctx, OR(
- AND(LE(Height, IntConstant(h - 1)), AND(dk1, dk2)),
- AND(GT(Height, IntConstant(h + 1)), dk1)
+ prover.reduceToCrypto(ctx, SigmaOr(
+ SigmaAnd(LE(Height, IntConstant(h - 1)), SigmaAnd(dk1, dk2)),
+ SigmaAnd(GT(Height, IntConstant(h + 1)), dk1)
)).get.value shouldBe TrivialProp.FalseProp
prover.reduceToCrypto(ctx,
- OR(
- OR(
- AND(LE(Height, IntConstant(h - 1)), AND(dk1, dk2)),
- AND(GT(Height, IntConstant(h + 1)), dk1)
+ SigmaOr(
+ SigmaOr(
+ SigmaAnd(LE(Height, IntConstant(h - 1)), SigmaAnd(dk1, dk2)),
+ SigmaAnd(GT(Height, IntConstant(h + 1)), dk1)
),
AND(GT(Height, IntConstant(h - 1)), LE(Height, IntConstant(h + 1)))
)
@@ -260,16 +260,16 @@ class TestingInterpreterSpecification extends SigmaTestingCommons
}
property("Evaluation example #1") {
- val dk1 = prover.dlogSecrets(0).publicImage.isProven
- val dk2 = prover.dlogSecrets(1).publicImage.isProven
+ val dk1 = prover.dlogSecrets(0).publicImage
+ val dk2 = prover.dlogSecrets(1).publicImage
val env1 = testingContext(99)
val env2 = testingContext(101)
- val prop = mkTestErgoTree(OR(
- AND(LE(Height, IntConstant(100)), AND(dk1, dk2)),
- AND(GT(Height, IntConstant(100)), dk1)
- ).toSigmaProp)
+ val prop = mkTestErgoTree(SigmaOr(
+ SigmaAnd(LE(Height, IntConstant(100)), SigmaAnd(dk1, dk2)),
+ SigmaAnd(GT(Height, IntConstant(100)), dk1)
+ ))
val challenge = Array.fill(32)(Random.nextInt(100).toByte)
diff --git a/sigmastate/src/test/scala/sigmastate/TestsBase.scala b/sigmastate/src/test/scala/sigmastate/TestsBase.scala
index dfb1619cac..48ba284840 100644
--- a/sigmastate/src/test/scala/sigmastate/TestsBase.scala
+++ b/sigmastate/src/test/scala/sigmastate/TestsBase.scala
@@ -7,7 +7,7 @@ import sigmastate.Values.{SValue, Value, SigmaPropValue, ErgoTree, SigmaBoolean}
import sigmastate.eval.IRContext
import sigmastate.interpreter.Interpreter
import sigmastate.interpreter.Interpreter.ScriptEnv
-import sigmastate.lang.{SigmaCompiler, TransformingSigmaBuilder}
+import sigmastate.lang.{TransformingSigmaBuilder, SigmaCompiler, CompilerSettings}
import sigmastate.lang.Terms.ValueOps
import sigmastate.serialization.ValueSerializer
@@ -49,7 +49,28 @@ trait TestsBase extends Matchers {
def mkTestErgoTree(prop: SigmaBoolean): ErgoTree =
ErgoTree.fromSigmaBoolean(ergoTreeHeaderInTests, prop)
- lazy val compiler = SigmaCompiler(TestnetNetworkPrefix, TransformingSigmaBuilder)
+ protected val _lowerMethodCalls = new DynamicVariable[Boolean](true)
+
+ /** Returns true if MethodCall nodes should be lowered by TypeChecker to the
+ * corresponding ErgoTree nodes. E.g. xs.map(f) --> MapCollection(xs, f).
+ * NOTE: The value of the flag is assigned dynamically using _lowerMethodCalls
+ * DynamicVariable. */
+ def lowerMethodCallsInTests: Boolean = _lowerMethodCalls.value
+
+ /** If true, then all suite properties are executed with _lowerMethodCalls set to false.
+ * This allow to test execution of MethodCall nodes in ErgoTree.
+ */
+ val okRunTestsWithoutMCLowering: Boolean = false
+
+ val defaultCompilerSettings: CompilerSettings = CompilerSettings(
+ TestnetNetworkPrefix, TransformingSigmaBuilder,
+ lowerMethodCalls = true
+ )
+
+ def compilerSettingsInTests: CompilerSettings =
+ defaultCompilerSettings.copy(lowerMethodCalls = lowerMethodCallsInTests)
+
+ def compiler = SigmaCompiler(compilerSettingsInTests)
def checkSerializationRoundTrip(v: SValue): Unit = {
val compiledTreeBytes = ValueSerializer.serialize(v)
diff --git a/sigmastate/src/test/scala/sigmastate/crypto/SigningSpecification.scala b/sigmastate/src/test/scala/sigmastate/crypto/SigningSpecification.scala
index 9390e1b4b3..5d880dc4c5 100644
--- a/sigmastate/src/test/scala/sigmastate/crypto/SigningSpecification.scala
+++ b/sigmastate/src/test/scala/sigmastate/crypto/SigningSpecification.scala
@@ -7,6 +7,10 @@ import sigmastate.Values.SigmaBoolean
import sigmastate.basics.DLogProtocol.DLogProverInput
import sigmastate.helpers.{ErgoLikeTestInterpreter, ErgoLikeTestProvingInterpreter, SigmaTestingCommons}
import sigmastate.interpreter.{ContextExtension, HintsBag, ProverResult}
+import sigmastate.serialization.ValueSerializer
+import sigmastate.serialization.transformers.ProveDHTupleSerializer
+import sigmastate.lang.StdSigmaBuilder
+import sigmastate.basics.ProveDHTuple
class SigningSpecification extends SigmaTestingCommons {
implicit lazy val IR: TestingIRContext = new TestingIRContext
@@ -29,6 +33,17 @@ class SigningSpecification extends SigmaTestingCommons {
printSimpleSignature(msg: Array[Byte])
}
+ property("ProveDHT signature test vector") {
+ // test vector data is generated by prover and tested in signing_spec::sig_test_vector_prove_dht
+ val msg = Base16.decode("1dc01772ee0171f5f614c673e3c7fa1107a8cf727bdf5a6dadb379e93c0d1d00").get
+ val pdht = ProveDHTupleSerializer(ProveDHTuple.apply).fromBytes(Base16.decode(input = "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f817980280c66feee88d56e47bf3f47c4109d9218c60c373a472a0d9537507c7ee828c4802a96f19e97df31606183c1719400682d1d40b1ce50c9a1ed1b19845e2b1b551bf0255ac02191cb229891fb1b674ea9df7fc8426350131d821fc4a53f29c3b1cb21a").get)
+ val signature = Base16.decode("eba93a69b28cfdea261e9ea8914fca9a0b3868d50ce68c94f32e875730f8ca361bd3783c5d3e25802e54f49bd4fb9fafe51f4e8aafbf9815").get
+ // check that signature is correct
+ val verifier = new ErgoLikeTestInterpreter
+ val proverResult = ProverResult(signature, ContextExtension.empty)
+ verifier.verify(pdht, fakeContext, proverResult, msg).get._1 shouldBe true
+ }
+
property("handle improper signature") {
val pi = new ErgoLikeTestProvingInterpreter()
val sigmaTree: SigmaBoolean = pi.publicKeys.head
@@ -62,6 +77,19 @@ class SigningSpecification extends SigmaTestingCommons {
verifier.verify(sigmaTree, fakeContext, proverResult, msg).get._1 shouldBe true
}
+ property("OR with ProveDHT signature test vector") {
+ // test vector data is generated by prover and tested in signing_spec::sig_test_vector_conj_or_prove_dht
+ val msg = Base16.decode("1dc01772ee0171f5f614c673e3c7fa1107a8cf727bdf5a6dadb379e93c0d1d00").get
+ val sk1 = DLogProverInput(BigInt("109749205800194830127901595352600384558037183218698112947062497909408298157746").bigInteger)
+ val pdht = ProveDHTupleSerializer(ProveDHTuple.apply).fromBytes(Base16.decode(input = "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f817980214487635ebffa60b13a166bd0721c5f0ab603fc74168d7764d7ec5ef2107f5d40334c5b7efa5a4a22b83d102d2e6521eaa660fa911c5a213af63c8460f2327513b026a0be2a277291d42daad3830cb16a4ef20e4f1f7c36384f3fee065f0f143a355").get)
+ val signature = Base16.decode("a80daebdcd57874296f49fd9910ddaefbf517ca076b6e16b97678e96a20239978836e7ec5b795cf3a55616d394f07c004f85e0d3e71880d4734b57ea874c7eba724e8887280f1affadaad962ee916b39207af2d2ab2a69a2e6f4d652f7389cc4f582bbe6d7937c59aa64cf2965a8b36a").get
+ // check that signature is correct
+ val verifier = new ErgoLikeTestInterpreter
+ val proverResult = ProverResult(signature, ContextExtension.empty)
+ val sigmaTree: SigmaBoolean = COR(Seq(sk1.publicImage, pdht))
+ verifier.verify(sigmaTree, fakeContext, proverResult, msg).get._1 shouldBe true
+ }
+
property("AND with OR signature test vector") {
val msg = Base16.decode("1dc01772ee0171f5f614c673e3c7fa1107a8cf727bdf5a6dadb379e93c0d1d00").get
val sk1 = DLogProverInput(BigInt("109749205800194830127901595352600384558037183218698112947062497909408298157746").bigInteger)
diff --git a/sigmastate/src/test/scala/sigmastate/eval/ErgoScriptTestkit.scala b/sigmastate/src/test/scala/sigmastate/eval/ErgoScriptTestkit.scala
index ac35f6844c..83798dfabc 100644
--- a/sigmastate/src/test/scala/sigmastate/eval/ErgoScriptTestkit.scala
+++ b/sigmastate/src/test/scala/sigmastate/eval/ErgoScriptTestkit.scala
@@ -4,17 +4,17 @@ import org.ergoplatform.ErgoAddressEncoder.TestnetNetworkPrefix
import org.ergoplatform.validation.ValidationSpecification
import scala.util.Success
-import sigmastate.{AvlTreeData, SType, TestsBase}
+import sigmastate.{AvlTreeData, TestsBase, SType}
import sigmastate.Values.{EvaluatedValue, SValue, SigmaPropConstant, Value, BigIntArrayConstant}
import org.ergoplatform.{Context => _, _}
import sigmastate.utxo.CostTable
import scalan.BaseCtxTests
-import sigmastate.lang.{LangTests, SigmaCompiler}
+import sigmastate.lang.{LangTests, SigmaCompiler, CompilerSettings}
import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, ErgoLikeContextTesting}
import sigmastate.helpers.TestingHelpers._
import sigmastate.interpreter.ContextExtension
import sigmastate.interpreter.Interpreter.ScriptEnv
-import special.sigma.{ContractsTestkit, Context => DContext, _}
+import special.sigma.{ContractsTestkit, Context => DContext}
import sigmastate.serialization.ErgoTreeSerializer.DefaultSerializer
import scala.language.implicitConversions
@@ -31,7 +31,11 @@ trait ErgoScriptTestkit extends ContractsTestkit with LangTests
import Size._
import BigInt._
- override lazy val compiler = new SigmaCompiler(TestnetNetworkPrefix, IR.builder)
+ override lazy val compiler = new SigmaCompiler(CompilerSettings(
+ TestnetNetworkPrefix,
+ IR.builder,
+ lowerMethodCalls = true
+ ))
def newErgoContext(height: Int, boxToSpend: ErgoBox, extension: Map[Byte, EvaluatedValue[SType]] = Map()): ErgoLikeContext = {
val tx1 = new ErgoLikeTransaction(IndexedSeq(), IndexedSeq(), IndexedSeq(boxToSpend))
@@ -182,7 +186,7 @@ trait ErgoScriptTestkit extends ContractsTestkit with LangTests
// check cost
val costCtx = ergoCtx.get.toSigmaContext(isCost = true)
- val estimatedCost = IR.checkCost(costCtx, tree, costF, CostTable.ScriptLimit)
+ IR.checkCost(costCtx, tree, costF, SigmaConstants.ScriptCostLimit.value)
// check size
{
diff --git a/sigmastate/src/test/scala/sigmastate/helpers/NegativeTesting.scala b/sigmastate/src/test/scala/sigmastate/helpers/NegativeTesting.scala
index a4bda66032..6748826b35 100644
--- a/sigmastate/src/test/scala/sigmastate/helpers/NegativeTesting.scala
+++ b/sigmastate/src/test/scala/sigmastate/helpers/NegativeTesting.scala
@@ -3,6 +3,8 @@ package sigmastate.helpers
import org.scalatest.Matchers
import scala.annotation.tailrec
+import scala.util.{Failure, Success, Try}
+import spire.syntax.all.cfor
import scala.reflect.ClassTag
trait NegativeTesting extends Matchers {
@@ -34,6 +36,12 @@ trait NegativeTesting extends Matchers {
if (t.getCause == null) t
else rootCause(t.getCause)
+ /** If error then maps it to the root cause, otherwise returns the original value. */
+ final def rootCause[A](x: Try[A]): Try[A] = x match {
+ case s: Success[_] => s
+ case Failure(t) => Failure(rootCause(t))
+ }
+
/** Creates an assertion which checks the given type and message contents.
*
* @tparam E expected type of exception
@@ -41,9 +49,56 @@ trait NegativeTesting extends Matchers {
* @return the assertion which can be used in assertExceptionThrown method
*/
def exceptionLike[E <: Throwable : ClassTag]
- (msgParts: String*): Throwable => Boolean = {
+ (msgParts: String*): Throwable => Boolean = {
case t: E => msgParts.forall(t.getMessage.contains(_))
case _ => false
}
+ /** Checks that both computations either succeed with the same value or fail with the same
+ * error. If this is not true, exception is thrown.
+ *
+ * @param f first computation
+ * @param g second computation
+ * @return result of the second computation `g`
+ */
+ def sameResultOrError[B](f: => B, g: => B): Try[B] = {
+ val b1 = Try(f); val b2 = Try(g)
+ (b1, b2) match {
+ case (Success(b1), res @ Success(b2)) =>
+ assert(b1 == b2)
+ res
+ case (Failure(t1), res @ Failure(t2)) =>
+ val c1 = rootCause(t1).getClass
+ val c2 = rootCause(t2).getClass
+ c1 shouldBe c2
+ res
+ case _ =>
+ val cause = if (b1.isFailure)
+ rootCause(b1.asInstanceOf[Failure[_]].exception)
+ else
+ rootCause(b2.asInstanceOf[Failure[_]].exception)
+
+ sys.error(
+ s"""Should succeed with the same value or fail with the same exception, but was:
+ |First result: $b1
+ |Second result: $b2
+ |Root cause: $cause
+ |""".stripMargin)
+ }
+ }
+
+ /** Repeat the given `block` computation `nIters` times.
+ *
+ * @param nIters number of iterations to repeat the computation
+ * @param block the computation to execute on each iteration
+ * @return the result of the last iteration
+ */
+ def repeatAndReturnLast[A](nIters: Int)(block: => A): A = {
+ require(nIters > 0)
+ var res = block
+ cfor(1)(_ < nIters, _ + 1) { i =>
+ res = block
+ }
+ res
+ }
}
diff --git a/sigmastate/src/test/scala/sigmastate/lang/SigmaCompilerTest.scala b/sigmastate/src/test/scala/sigmastate/lang/SigmaCompilerTest.scala
index 9f76b96108..e9f1557ab9 100644
--- a/sigmastate/src/test/scala/sigmastate/lang/SigmaCompilerTest.scala
+++ b/sigmastate/src/test/scala/sigmastate/lang/SigmaCompilerTest.scala
@@ -222,36 +222,6 @@ class SigmaCompilerTest extends SigmaTestingCommons with LangTests with ObjectGe
testMissingCosting("1 >>> 2", mkBitShiftRightZeroed(IntConstant(1), IntConstant(2)))
}
- // TODO soft-fork: related to https://github.com/ScorexFoundation/sigmastate-interpreter/issues/418
- ignore("Collection.BitShiftLeft") {
- comp("Coll(1,2) << 2") shouldBe
- mkMethodCall(
- ConcreteCollection.fromItems(IntConstant(1), IntConstant(2)),
- SCollection.BitShiftLeftMethod.withConcreteTypes(Map(SCollection.tIV -> SInt)),
- Vector(IntConstant(2)), Map())
- }
-
- // TODO soft-fork: related to https://github.com/ScorexFoundation/sigmastate-interpreter/issues/418
- ignore("Collection.BitShiftRight") {
- testMissingCosting("Coll(1,2) >> 2",
- mkMethodCall(
- ConcreteCollection.fromItems(IntConstant(1), IntConstant(2)),
- SCollection.BitShiftRightMethod,
- Vector(IntConstant(2)),
- Map(SCollection.tIV -> SInt))
- )
- }
-
- // TODO soft-fork: 1) implement method for special.collection.Coll 2) add rule to CollCoster
- ignore("Collection.BitShiftRightZeroed") {
- comp("Coll(true, false) >>> 2") shouldBe
- mkMethodCall(
- ConcreteCollection.fromItems(TrueLeaf, FalseLeaf),
- SCollection.BitShiftRightZeroedMethod,
- Vector(IntConstant(2))
- )
- }
-
property("Collection.indices") {
comp("Coll(true, false).indices") shouldBe
mkMethodCall(
@@ -261,45 +231,11 @@ class SigmaCompilerTest extends SigmaTestingCommons with LangTests with ObjectGe
)
}
- // TODO soft-fork: enable after such lambda is implemented in CollCoster.flatMap
- ignore("SCollection.flatMap") {
- comp("OUTPUTS.flatMap({ (out: Box) => Coll(out.value >= 1L) })") shouldBe
- mkMethodCall(Outputs,
- SCollection.FlatMapMethod.withConcreteTypes(Map(SCollection.tIV -> SBox, SCollection.tOV -> SBoolean)),
- Vector(FuncValue(1,SBox,
- ConcreteCollection(Array(GE(ExtractAmount(ValUse(1, SBox)), LongConstant(1))), SBoolean))), Map())
- }
-
- // TODO soft-fork: related to https://github.com/ScorexFoundation/sigmastate-interpreter/issues/486
- ignore("SNumeric.toBytes") {
- comp("4.toBytes") shouldBe
- mkMethodCall(IntConstant(4), SInt.method("toBytes").get, IndexedSeq())
- }
-
- // TODO soft-fork: related to https://github.com/ScorexFoundation/sigmastate-interpreter/issues/486
- ignore("SNumeric.toBits") {
- comp("4.toBits") shouldBe
- mkMethodCall(IntConstant(4), SInt.method("toBits").get, IndexedSeq())
- }
-
- // TODO soft-fork: related to https://github.com/ScorexFoundation/sigmastate-interpreter/issues/327
- ignore("SBigInt.multModQ") {
- comp("1.toBigInt.multModQ(2.toBigInt)") shouldBe
- mkMethodCall(BigIntConstant(1), SBigInt.MultModQMethod, IndexedSeq(BigIntConstant(2)))
- }
-
property("SBox.tokens") {
comp("SELF.tokens") shouldBe
mkMethodCall(Self, SBox.tokensMethod, IndexedSeq())
}
-//TODO soft-fork: related to https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479
-// property("SOption.toColl") {
-// comp("getVar[Int](1).toColl") shouldBe
-// mkMethodCall(GetVarInt(1),
-// SOption.ToCollMethod.withConcreteTypes(Map(SOption.tT -> SInt)), IndexedSeq(), Map())
-// }
-
property("SContext.dataInputs") {
comp("CONTEXT.dataInputs") shouldBe
mkMethodCall(Context, SContext.dataInputsMethod, IndexedSeq())
@@ -336,18 +272,6 @@ class SigmaCompilerTest extends SigmaTestingCommons with LangTests with ObjectGe
)
}
-// TODO soft-fork: related to https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479
-// property("SOption.flatMap") {
-// comp("getVar[Int](1).flatMap({(i: Int) => getVar[Int](2)})") shouldBe
-// mkMethodCall(GetVarInt(1),
-// SOption.FlatMapMethod.withConcreteTypes(Map(SOption.tT -> SInt, SOption.tR -> SInt)),
-// IndexedSeq(Terms.Lambda(
-// Vector(("i", SInt)),
-// SOption(SInt),
-// Some(GetVarInt(2)))),
-// Map())
-// }
-
property("SCollection.patch") {
comp("Coll(1, 2).patch(1, Coll(3), 1)") shouldBe
mkMethodCall(
@@ -375,50 +299,6 @@ class SigmaCompilerTest extends SigmaTestingCommons with LangTests with ObjectGe
Map())
}
- // TODO soft-fork: related to https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479
- ignore("SCollection.unionSets") {
- comp("Coll(1, 2).unionSets(Coll(1))") shouldBe
- mkMethodCall(
- ConcreteCollection.fromItems(IntConstant(1), IntConstant(2)),
- SCollection.UnionSetsMethod.withConcreteTypes(Map(SCollection.tIV -> SInt)),
- Vector(ConcreteCollection.fromItems(IntConstant(1))),
- Map())
- }
-
- // TODO soft-fork: related to https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479
- ignore("SCollection.diff") {
- comp("Coll(1, 2).diff(Coll(1))") shouldBe
- mkMethodCall(
- ConcreteCollection.fromItems(IntConstant(1), IntConstant(2)),
- SCollection.DiffMethod.withConcreteTypes(Map(SCollection.tIV -> SInt)),
- Vector(ConcreteCollection.fromItems(IntConstant(1))),
- Map())
- }
-
- // TODO soft-fork: related to https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479
- ignore("SCollection.intersect") {
- comp("Coll(1, 2).intersect(Coll(1))") shouldBe
- mkMethodCall(
- ConcreteCollection.fromItems(IntConstant(1), IntConstant(2)),
- SCollection.IntersectMethod.withConcreteTypes(Map(SCollection.tIV -> SInt)),
- Vector(ConcreteCollection.fromItems(IntConstant(1))),
- Map())
- }
-
- // TODO soft-fork: related to https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479
- ignore("SCollection.prefixLength") {
- comp("OUTPUTS.prefixLength({ (out: Box) => out.value >= 1L })") shouldBe
- mkMethodCall(Outputs,
- SCollection.PrefixLengthMethod.withConcreteTypes(Map(SCollection.tIV -> SBox)),
- Vector(
- Terms.Lambda(
- Vector(("out",SBox)),
- SBoolean,
- Some(GE(ExtractAmount(Ident("out",SBox).asBox),LongConstant(1))))
- ),
- Map())
- }
-
property("SCollection.indexOf") {
comp("Coll(1, 2).indexOf(1, 0)") shouldBe
mkMethodCall(
@@ -428,60 +308,6 @@ class SigmaCompilerTest extends SigmaTestingCommons with LangTests with ObjectGe
Map())
}
- // TODO soft-fork: related to https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479
- ignore("SCollection.lastIndexOf") {
- comp("Coll(1, 2).lastIndexOf(1, 0)") shouldBe
- mkMethodCall(
- ConcreteCollection.fromItems(IntConstant(1), IntConstant(2)),
- SCollection.LastIndexOfMethod.withConcreteTypes(Map(SCollection.tIV -> SInt)),
- Vector(IntConstant(1), IntConstant(0)),
- Map())
- }
-
- // TODO soft-fork: related to https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479
- ignore("SCollection.find") {
- comp("OUTPUTS.find({ (out: Box) => out.value >= 1L })") shouldBe
- mkMethodCall(Outputs,
- SCollection.FindMethod.withConcreteTypes(Map(SCollection.tIV -> SBox)),
- Vector(
- Terms.Lambda(
- Vector(("out",SBox)),
- SBoolean,
- Some(GE(ExtractAmount(Ident("out",SBox).asBox),LongConstant(1))))
- ),
- Map())
- }
-
- // TODO soft-fork: related to https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479
- ignore("Collection.distinct") {
- comp("Coll(true, false).distinct") shouldBe
- mkMethodCall(
- ConcreteCollection.fromItems(TrueLeaf, FalseLeaf),
- SCollection.DistinctMethod.withConcreteTypes(Map(SCollection.tIV -> SBoolean)),
- Vector()
- )
- }
-
- // TODO soft-fork: related to https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479
- ignore("SCollection.startsWith") {
- comp("Coll(1, 2).startsWith(Coll(1), 1)") shouldBe
- mkMethodCall(
- ConcreteCollection.fromItems(IntConstant(1), IntConstant(2)),
- SCollection.StartsWithMethod.withConcreteTypes(Map(SCollection.tIV -> SInt)),
- Vector(ConcreteCollection.fromItems(IntConstant(1)), IntConstant(1)),
- Map())
- }
-
- // TODO soft-fork: related to https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479
- ignore("SCollection.endsWith") {
- comp("Coll(1, 2).endsWith(Coll(1))") shouldBe
- mkMethodCall(
- ConcreteCollection.fromItems(IntConstant(1), IntConstant(2)),
- SCollection.EndsWithMethod.withConcreteTypes(Map(SCollection.tIV -> SInt)),
- Vector(ConcreteCollection.fromItems(IntConstant(1))),
- Map())
- }
-
property("SCollection.zip") {
comp("Coll(1, 2).zip(Coll(1, 1))") shouldBe
mkMethodCall(
@@ -491,34 +317,6 @@ class SigmaCompilerTest extends SigmaTestingCommons with LangTests with ObjectGe
)
}
- // TODO soft-fork: related to https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479
- ignore("SCollection.mapReduce") {
- comp(
- "Coll(1, 2).mapReduce({ (i: Int) => (i > 0, i.toLong) }, { (tl: (Long, Long)) => tl._1 + tl._2 })") shouldBe
- mkMethodCall(
- ConcreteCollection.fromItems(IntConstant(1), IntConstant(2)),
- SCollection.MapReduceMethod.withConcreteTypes(Map(SCollection.tIV -> SInt, SType.tK -> SBoolean, SType.tV -> SLong)),
- Vector(
- Lambda(List(),
- Vector(("i", SInt)),
- STuple(SBoolean, SLong),
- Some(Tuple(Vector(
- GT(Ident("i", SInt).asIntValue, IntConstant(0)),
- Upcast(Ident("i", SInt).asIntValue, SLong)
- )))
- ),
- Lambda(List(),
- Vector(("tl", STuple(SLong, SLong))),
- SLong,
- Some(Plus(
- SelectField(Ident("tl", STuple(SLong, SLong)).asValue[STuple], 1).asInstanceOf[Value[SLong.type]],
- SelectField(Ident("tl", STuple(SLong, SLong)).asValue[STuple], 2).asInstanceOf[Value[SLong.type]])
- )
- )
- ),
- Map())
- }
-
property("SCollection.filter") {
comp("OUTPUTS.filter({ (out: Box) => out.value >= 1L })") shouldBe
mkFilter(Outputs,
diff --git a/sigmastate/src/test/scala/sigmastate/lang/SigmaParserTest.scala b/sigmastate/src/test/scala/sigmastate/lang/SigmaParserTest.scala
index 0eab9f2550..45e87b3cb8 100644
--- a/sigmastate/src/test/scala/sigmastate/lang/SigmaParserTest.scala
+++ b/sigmastate/src/test/scala/sigmastate/lang/SigmaParserTest.scala
@@ -36,7 +36,10 @@ class SigmaParserTest extends PropSpec with PropertyChecks with Matchers with La
}
def fail(x: String, expectedLine: Int, expectedCol: Int): Unit = {
- val compiler = SigmaCompiler(ErgoAddressEncoder.TestnetNetworkPrefix)
+ val compiler = SigmaCompiler(CompilerSettings(
+ ErgoAddressEncoder.TestnetNetworkPrefix,
+ TransformingSigmaBuilder,
+ lowerMethodCalls = true))
val exception = the[ParserException] thrownBy compiler.parse(x)
withClue(s"Exception: $exception, is missing source context:") { exception.source shouldBe defined }
val sourceContext = exception.source.get
@@ -728,10 +731,6 @@ class SigmaParserTest extends PropSpec with PropertyChecks with Matchers with La
parse("1L.toBits") shouldBe Select(LongConstant(1), "toBits")
}
- property("SNumeric.abs") {
- parse("1.abs") shouldBe Select(IntConstant(1), "abs")
- }
-
property("SNumeric.compare") {
parse("1.compare(2)") shouldBe Apply(Select(IntConstant(1), "compare", None), Vector(IntConstant(2)))
}
diff --git a/sigmastate/src/test/scala/sigmastate/lang/SigmaSpecializerTest.scala b/sigmastate/src/test/scala/sigmastate/lang/SigmaSpecializerTest.scala
index a2ceefd828..b661ae5924 100644
--- a/sigmastate/src/test/scala/sigmastate/lang/SigmaSpecializerTest.scala
+++ b/sigmastate/src/test/scala/sigmastate/lang/SigmaSpecializerTest.scala
@@ -37,7 +37,7 @@ class SigmaSpecializerTest extends PropSpec
val predefinedFuncRegistry = new PredefinedFuncRegistry(builder)
val binder = new SigmaBinder(env, builder, TestnetNetworkPrefix, predefinedFuncRegistry)
val bound = binder.bind(parsed)
- val typer = new SigmaTyper(builder, predefinedFuncRegistry)
+ val typer = new SigmaTyper(builder, predefinedFuncRegistry, lowerMethodCalls = true)
val typed = typer.typecheck(bound)
typed
}
diff --git a/sigmastate/src/test/scala/sigmastate/lang/SigmaTyperTest.scala b/sigmastate/src/test/scala/sigmastate/lang/SigmaTyperTest.scala
index 4926824bb1..e29977cd09 100644
--- a/sigmastate/src/test/scala/sigmastate/lang/SigmaTyperTest.scala
+++ b/sigmastate/src/test/scala/sigmastate/lang/SigmaTyperTest.scala
@@ -31,7 +31,7 @@ class SigmaTyperTest extends PropSpec with PropertyChecks with Matchers with Lan
val predefinedFuncRegistry = new PredefinedFuncRegistry(builder)
val binder = new SigmaBinder(env, builder, TestnetNetworkPrefix, predefinedFuncRegistry)
val bound = binder.bind(parsed)
- val typer = new SigmaTyper(builder, predefinedFuncRegistry)
+ val typer = new SigmaTyper(builder, predefinedFuncRegistry, lowerMethodCalls = true)
val typed = typer.typecheck(bound)
assertSrcCtxForAllNodes(typed)
if (expected != null) typed shouldBe expected
@@ -48,7 +48,7 @@ class SigmaTyperTest extends PropSpec with PropertyChecks with Matchers with Lan
val predefinedFuncRegistry = new PredefinedFuncRegistry(builder)
val binder = new SigmaBinder(env, builder, TestnetNetworkPrefix, predefinedFuncRegistry)
val bound = binder.bind(parsed)
- val typer = new SigmaTyper(builder, predefinedFuncRegistry)
+ val typer = new SigmaTyper(builder, predefinedFuncRegistry, lowerMethodCalls = true)
typer.typecheck(bound)
}, {
case te: TyperException =>
@@ -614,30 +614,6 @@ class SigmaTyperTest extends PropSpec with PropertyChecks with Matchers with Lan
typefail(env, "true >>> false", 1, 1)
}
- // TODO soft-fork: https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479
- ignore("Collection.BitShiftLeft") {
- typecheck(env, "Coll(1,2) << 2") shouldBe SCollection(SInt)
- an [TyperException] should be thrownBy typecheck(env, "Coll(1,2) << true")
- an [TyperException] should be thrownBy typecheck(env, "Coll(1,2) << 2L")
- an [TyperException] should be thrownBy typecheck(env, "Coll(1,2) << (2L, 3)")
- }
-
- // TODO soft-fork: https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479
- ignore("Collection.BitShiftRight") {
- typecheck(env, "Coll(1,2) >> 2") shouldBe SCollection(SInt)
- an [TyperException] should be thrownBy typecheck(env, "Coll(1,2) >> 2L")
- an [TyperException] should be thrownBy typecheck(env, "Coll(1,2) >> true")
- an [TyperException] should be thrownBy typecheck(env, "Coll(1,2) >> (2L, 3)")
- }
-
- // TODO soft-fork: https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479
- ignore("Collection.BitShiftRightZeroed") {
- typecheck(env, "Coll(true, false) >>> 2") shouldBe SCollection(SBoolean)
- an [TyperException] should be thrownBy typecheck(env, "Coll(1,2) >>> 2")
- an [TyperException] should be thrownBy typecheck(env, "Coll(true, false) >>> true")
- an [TyperException] should be thrownBy typecheck(env, "Coll(true, false) >>> (2L, 3)")
- }
-
property("SCollection.indices") {
typecheck(env, "Coll(1).indices") shouldBe SCollection(SInt)
typecheck(env, "INPUTS.indices") shouldBe SCollection(SInt)
diff --git a/sigmastate/src/test/scala/sigmastate/serialization/SelectFieldSerializerSpecification.scala b/sigmastate/src/test/scala/sigmastate/serialization/SelectFieldSerializerSpecification.scala
index 960ac4bddb..678ff1a241 100644
--- a/sigmastate/src/test/scala/sigmastate/serialization/SelectFieldSerializerSpecification.scala
+++ b/sigmastate/src/test/scala/sigmastate/serialization/SelectFieldSerializerSpecification.scala
@@ -9,6 +9,7 @@ class SelectFieldSerializerSpecification extends TableSerializationSpecification
property("SelectField: Serializer round trip ") {
forAll(tupleGen(2, 10)) { tuple: Tuple =>
+ // TODO refactor: avoid usage of extension method `length`
val index = Gen.chooseNum(1, tuple.length - 1).sample.get
roundTripTest(SelectField(tuple, index.toByte))
}
diff --git a/sigmastate/src/test/scala/sigmastate/serialization/SigSerializerSpecification.scala b/sigmastate/src/test/scala/sigmastate/serialization/SigSerializerSpecification.scala
index 9cb3f8ee26..97c94c73a3 100644
--- a/sigmastate/src/test/scala/sigmastate/serialization/SigSerializerSpecification.scala
+++ b/sigmastate/src/test/scala/sigmastate/serialization/SigSerializerSpecification.scala
@@ -517,12 +517,19 @@ class SigSerializerSpecification extends SigmaTestingCommons
property("Invalid signature parsing") {
forAll { bytes: Array[Byte] =>
val r = SigmaSerializer.startReader(bytes)
- val readBytes = r.getBytesUnsafe(bytes.length + 1) // request more than present
+ val nRequested = bytes.length + 1 // request more than present
+ val readBytes = r.getBytesUnsafe(nRequested)
readBytes shouldBe bytes
// we now at the limit position of reader, and still can get all buffer bytes
val allBytes = r.getAllBufferBytes
allBytes shouldBe bytes
+
+ r.position = 0
+ var reported = false
+ val res = SigSerializer.readBytesChecked(r, nRequested, msg => reported = true)
+ res shouldBe bytes
+ reported shouldBe true
}
}
}
diff --git a/sigmastate/src/test/scala/sigmastate/utxo/BasicOpsSpecification.scala b/sigmastate/src/test/scala/sigmastate/utxo/BasicOpsSpecification.scala
index a08959d40b..9baade2639 100644
--- a/sigmastate/src/test/scala/sigmastate/utxo/BasicOpsSpecification.scala
+++ b/sigmastate/src/test/scala/sigmastate/utxo/BasicOpsSpecification.scala
@@ -15,6 +15,7 @@ import sigmastate.interpreter.Interpreter._
import sigmastate.lang.Terms._
import special.sigma.InvalidType
import SType.AnyOps
+import sigmastate.interpreter.ContextExtension.VarBinding
import sigmastate.interpreter.CryptoConstants
import sigmastate.utils.Helpers._
@@ -40,7 +41,7 @@ class BasicOpsSpecification extends SigmaTestingCommons
val propVar2 = 11.toByte
val lastExtVar = propVar2
- val ext: Seq[(Byte, EvaluatedValue[_ <: SType])] = Seq(
+ val ext: Seq[VarBinding] = Seq(
(intVar1, IntConstant(1)), (intVar2, IntConstant(2)),
(byteVar1, ByteConstant(1)), (byteVar2, ByteConstant(2)),
(bigIntVar1, BigIntConstant(BigInt(10).underlying())), (bigIntVar2, BigIntConstant(BigInt(20).underlying())),
@@ -55,7 +56,7 @@ class BasicOpsSpecification extends SigmaTestingCommons
)
def test(name: String, env: ScriptEnv,
- ext: Seq[(Byte, EvaluatedValue[_ <: SType])],
+ ext: Seq[VarBinding],
script: String, propExp: SValue,
onlyPositive: Boolean = true) = {
val prover = new ContextEnrichingTestProvingInterpreter() {
@@ -354,22 +355,6 @@ class BasicOpsSpecification extends SigmaTestingCommons
rootCause(_).isInstanceOf[InvalidType])
}
- // TODO related to https://github.com/ScorexFoundation/sigmastate-interpreter/issues/416
- ignore("Box.getReg") {
- test("Extract1", env, ext,
- "{ SELF.getReg[Int]( (getVar[Int](intVar1).get + 4)).get == 1}",
- BoolToSigmaProp(
- EQ(
- MethodCall(Self, SBox.getRegMethod,
- IndexedSeq(Plus(GetVarInt(1).get, IntConstant(4))), Map(SType.tT -> SInt)
- ).asInstanceOf[Value[SOption[SType]]].get,
- IntConstant(1)
- )
- ),
- true
- )
- }
-
property("OptionGet success (SomeValue)") {
test("Opt1", env, ext,
"{ getVar[Int](intVar2).get == 2 }",
@@ -524,12 +509,6 @@ class BasicOpsSpecification extends SigmaTestingCommons
// println(CostTableStat.costTableString)
}
- //TODO soft-fork: related to https://github.com/ScorexFoundation/sigmastate-interpreter/issues/236
- ignore("ZKProof") {
- test("zk1", env, ext, "ZKProof { sigmaProp(HEIGHT >= 0) }",
- ZKProofBlock(BoolToSigmaProp(GE(Height, LongConstant(0)))), true)
- }
-
property("numeric cast") {
test("downcast", env, ext,
"{ getVar[Int](intVar2).get.toByte == 2.toByte }",
diff --git a/sigmastate/src/test/scala/sigmastate/utxo/ErgoLikeInterpreterSpecification.scala b/sigmastate/src/test/scala/sigmastate/utxo/ErgoLikeInterpreterSpecification.scala
index 39d82cf304..5c304f6411 100644
--- a/sigmastate/src/test/scala/sigmastate/utxo/ErgoLikeInterpreterSpecification.scala
+++ b/sigmastate/src/test/scala/sigmastate/utxo/ErgoLikeInterpreterSpecification.scala
@@ -15,6 +15,7 @@ import sigmastate.basics.DLogProtocol.ProveDlog
import sigmastate.basics.ProveDHTuple
import sigmastate.helpers._
import sigmastate.helpers.TestingHelpers._
+import sigmastate.interpreter.ContextExtension.VarBinding
import sigmastate.interpreter.{ContextExtension, CostedProverResult}
import sigmastate.lang.Terms._
import sigmastate.serialization.{ValueSerializer, SerializationSpecification}
@@ -718,7 +719,7 @@ class ErgoLikeInterpreterSpecification extends SigmaTestingCommons
}
property("DeserializeContext can return expression of non-Boolean/SigmaProp type") {
- def prove(ergoTree: ErgoTree, script: (Byte, EvaluatedValue[_ <: SType])): CostedProverResult = {
+ def prove(ergoTree: ErgoTree, script: VarBinding): CostedProverResult = {
val boxToSpend = testBox(10, ergoTree, creationHeight = 5)
val ctx = ErgoLikeContextTesting.dummy(boxToSpend, activatedVersionInTests)
.withExtension(
diff --git a/sigmastate/src/test/scala/sigmastate/utxo/SerializationRoundTripSpec.scala b/sigmastate/src/test/scala/sigmastate/utxo/SerializationRoundTripSpec.scala
index c4d32cfd39..e1d61f3591 100644
--- a/sigmastate/src/test/scala/sigmastate/utxo/SerializationRoundTripSpec.scala
+++ b/sigmastate/src/test/scala/sigmastate/utxo/SerializationRoundTripSpec.scala
@@ -7,7 +7,9 @@ import scalan.util.BenchmarkUtil
import sigmastate.helpers.SigmaTestingCommons
import sigmastate.interpreter.{ProverResult, ContextExtension}
import sigmastate.serialization.generators.ObjectGenerators
+import sigmastate.serialization.ValueSerializer._
import debox.{Buffer => DBuffer}
+import sigmastate.lang.exceptions.SerializerException
import spire.algebra._
import spire.std.int._
@@ -21,6 +23,18 @@ class SerializationRoundTripSpec extends PropSpec
implicit val orderRun = Order.by((r: Run) => r.size)
+ property("ValueSerializer.newArray") {
+ newArray[Int](0).length shouldBe 0
+ newArray[Int](MaxArrayLength).length shouldBe MaxArrayLength
+
+ // test vector to catch constant changes
+ MaxArrayLength shouldBe 100000
+
+ assertExceptionThrown(
+ newArray[Int](MaxArrayLength + 1),
+ exceptionLike[SerializerException]("Cannot allocate array of Int"))
+ }
+
property("ErgoBoxCandidate: Serializer round trip benchmark") {
val runs = DBuffer.empty[Run]
forAll(MinSuccessful(20)) { t: ErgoBoxCandidate =>
diff --git a/sigmastate/src/test/scala/sigmastate/utxo/SigmaContract.scala b/sigmastate/src/test/scala/sigmastate/utxo/SigmaContract.scala
deleted file mode 100644
index 0d2ef4529a..0000000000
--- a/sigmastate/src/test/scala/sigmastate/utxo/SigmaContract.scala
+++ /dev/null
@@ -1,8 +0,0 @@
-package sigmastate.utxo
-
-import org.ergoplatform.ErgoAddressEncoder.TestnetNetworkPrefix
-import sigmastate.lang.{SigmaCompiler, TransformingSigmaBuilder}
-
-abstract class SigmaContract {
- val compiler = new SigmaCompiler(TestnetNetworkPrefix, TransformingSigmaBuilder)
-}
diff --git a/sigmastate/src/test/scala/sigmastate/utxo/examples/CoinEmissionSpecification.scala b/sigmastate/src/test/scala/sigmastate/utxo/examples/CoinEmissionSpecification.scala
index c3cff93dd8..6d40489759 100644
--- a/sigmastate/src/test/scala/sigmastate/utxo/examples/CoinEmissionSpecification.scala
+++ b/sigmastate/src/test/scala/sigmastate/utxo/examples/CoinEmissionSpecification.scala
@@ -130,8 +130,8 @@ block 1600 in 1622 ms, 30000000000 coins remain, defs: 61661
| val heightIncreased = HEIGHT > SELF.R4[Int].get
| val heightCorrect = out.R4[Int].get == HEIGHT
| val lastCoins = SELF.value <= oneEpochReduction
- | allOf(Coll(heightCorrect, heightIncreased, sameScriptRule, correctCoinsConsumed)) || (heightIncreased && lastCoins)
- |}""".stripMargin).asBoolValue.toSigmaProp
+ | sigmaProp(allOf(Coll(heightCorrect, heightIncreased, sameScriptRule, correctCoinsConsumed)) || (heightIncreased && lastCoins))
+ |}""".stripMargin).asSigmaProp
prop1 shouldEqual prop
diff --git a/sigmastate/src/test/scala/sigmastate/utxo/examples/OracleExamplesSpecification.scala b/sigmastate/src/test/scala/sigmastate/utxo/examples/OracleExamplesSpecification.scala
index c5fd41b6ae..7aaa2ef3a1 100644
--- a/sigmastate/src/test/scala/sigmastate/utxo/examples/OracleExamplesSpecification.scala
+++ b/sigmastate/src/test/scala/sigmastate/utxo/examples/OracleExamplesSpecification.scala
@@ -126,14 +126,14 @@ class OracleExamplesSpecification extends SigmaTestingCommons
def withinTimeframe(sinceHeight: Int,
timeoutHeight: Int,
- fallback: Value[SBoolean.type])(script: Value[SBoolean.type]) =
- OR(AND(GE(Height, IntConstant(sinceHeight)), LT(Height, IntConstant(timeoutHeight)), script),
- AND(GE(Height, IntConstant(timeoutHeight)), fallback))
+ fallback: SigmaPropValue)(script: SigmaPropValue) =
+ SigmaOr(SigmaAnd(GE(Height, IntConstant(sinceHeight)), LT(Height, IntConstant(timeoutHeight)), script),
+ SigmaAnd(GE(Height, IntConstant(timeoutHeight)), fallback))
- val contractLogic = OR(AND(GT(extract[SLong.type](reg1), LongConstant(15)), alicePubKey.isProven),
- AND(LE(extract[SLong.type](reg1), LongConstant(15)), bobPubKey.isProven))
+ val contractLogic = SigmaOr(SigmaAnd(GT(extract[SLong.type](reg1), LongConstant(15)), alicePubKey),
+ SigmaAnd(LE(extract[SLong.type](reg1), LongConstant(15)), bobPubKey))
- val oracleProp = AND(
+ val oracleProp = SigmaAnd(
OptionIsDefined(IR.builder.mkMethodCall(
LastBlockUtxoRootHash, SAvlTree.getMethod,
IndexedSeq(ExtractId(GetVarBox(22: Byte).get), GetVarByteArray(23: Byte).get)).asOption[SByteArray]),
@@ -164,7 +164,7 @@ class OracleExamplesSpecification extends SigmaTestingCommons
val timeout = 60
val propAlice = mkTestErgoTree(
- withinTimeframe(sinceHeight, timeout, alicePubKey.isProven)(oracleProp).toSigmaProp)
+ withinTimeframe(sinceHeight, timeout, alicePubKey)(oracleProp))
val sAlice = testBox(10, propAlice, 0, Seq(), Map(), boxIndex = 3)
@@ -173,7 +173,7 @@ class OracleExamplesSpecification extends SigmaTestingCommons
EQ(SizeOf(Inputs), IntConstant(2)),
EQ(ExtractId(ByIndex(Inputs, 0)), ByteArrayConstant(sAlice.id)))
val propBob = mkTestErgoTree(
- withinTimeframe(sinceHeight, timeout, bobPubKey.isProven)(propAlong).toSigmaProp)
+ withinTimeframe(sinceHeight, timeout, bobPubKey)(propAlong))
val sBob = testBox(10, propBob, 0, Seq(), Map(), boxIndex = 4)
val ctx = ErgoLikeContextTesting(
@@ -236,26 +236,18 @@ class OracleExamplesSpecification extends SigmaTestingCommons
additionalRegisters = Map(reg1 -> LongConstant(temperature))
)
- val contractLogic = OR(
- AND(
- GT(
- ExtractRegisterAs[SLong.type](ByIndex(Inputs, 0), reg1).get,
- LongConstant(15)),
- alicePubKey.isProven),
- AND(
- LE(
- ExtractRegisterAs[SLong.type](ByIndex(Inputs, 0), reg1).get,
- LongConstant(15)),
- bobPubKey.isProven)
+ val contractLogic = SigmaOr(
+ SigmaAnd(GT(ExtractRegisterAs[SLong.type](ByIndex(Inputs, 0), reg1).get, LongConstant(15)), alicePubKey),
+ SigmaAnd(LE(ExtractRegisterAs[SLong.type](ByIndex(Inputs, 0), reg1).get, LongConstant(15)), bobPubKey)
)
- val prop = mkTestErgoTree(AND(
+ val prop = SigmaOr(
EQ(SizeOf(Inputs), IntConstant(3)),
EQ(
ExtractScriptBytes(ByIndex(Inputs, 0)),
ByteArrayConstant(mkTestErgoTree(oraclePubKey).bytes)),
contractLogic
- ).toSigmaProp)
+ )
val sOracle = oracleBox
val sAlice = testBox(10, prop, 0, Seq(), Map())
diff --git a/sigmastate/src/test/scala/special/sigma/SigmaDslSpecification.scala b/sigmastate/src/test/scala/special/sigma/SigmaDslSpecification.scala
index 383f062371..400818ed8e 100644
--- a/sigmastate/src/test/scala/special/sigma/SigmaDslSpecification.scala
+++ b/sigmastate/src/test/scala/special/sigma/SigmaDslSpecification.scala
@@ -6,7 +6,7 @@ import java.math.BigInteger
import org.ergoplatform._
import org.ergoplatform.settings.ErgoAlgos
import org.scalacheck.{Arbitrary, Gen}
-import scalan.{ExactOrdering, ExactNumeric, RType}
+import scalan.{ExactOrdering, ExactNumeric, RType, ExactIntegral}
import scorex.crypto.authds.avltree.batch._
import scorex.crypto.authds.{ADDigest, ADKey, ADValue}
import scorex.crypto.hash.{Digest32, Blake2b256}
@@ -492,7 +492,7 @@ class SigmaDslSpecification extends SigmaDslTesting with CrossVersionProps { sui
(x: Byte) => x.toBigInt, "{ (x: Byte) => x.toBigInt }",
FuncValue(Vector((1, SByte)), Upcast(ValUse(1, SByte), SBigInt))))
- val n = ExactNumeric.ByteIsExactNumeric
+ val n = ExactIntegral.ByteIsExactIntegral
verifyCases(
{
def success[T](v: (T, (T, (T, (T, T))))) = Expected(Success(v), 39654)
@@ -833,7 +833,7 @@ class SigmaDslSpecification extends SigmaDslTesting with CrossVersionProps { sui
"{ (x: Short) => x.toBigInt }",
FuncValue(Vector((1, SShort)), Upcast(ValUse(1, SShort), SBigInt))))
- val n = ExactNumeric.ShortIsExactNumeric
+ val n = ExactIntegral.ShortIsExactIntegral
verifyCases(
{
def success[T](v: T) = Expected(Success(v), 39654)
@@ -1667,7 +1667,7 @@ class SigmaDslSpecification extends SigmaDslTesting with CrossVersionProps { sui
"{ (x: BigInt) => x.toBigInt }",
FuncValue(Vector((1, SBigInt)), ValUse(1, SBigInt))))
- val n = NumericOps.BigIntIsExactNumeric
+ val n = NumericOps.BigIntIsExactIntegral
verifyCases(
{
def success(v: (BigInt, (BigInt, (BigInt, (BigInt, BigInt))))) = Expected(Success(v), 39774)