diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 51761a5ecc..5de0781332 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -66,3 +66,55 @@ jobs: env: SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} + + buildJs: + name: JS - Test and publish a snapshot + env: + HAS_SECRETS: ${{ secrets.SONATYPE_PASSWORD != '' }} + strategy: + matrix: + os: [ubuntu-latest] + scala: [2.12.10] + java: [adopt@1.8] + node-version: [16.x] + runs-on: ${{ matrix.os }} + steps: + - name: Checkout current branch (full) + uses: actions/checkout@v2 + with: + fetch-depth: 0 + + - name: Setup NPM + uses: pnpm/action-setup@v2 + with: + version: 7.21.0 + node-version: ${{ matrix.node-version }} + cache: "pnpm" + - run: pnpm install --prefix sigma-js + + - name: Setup Java and Scala + uses: olafurpg/setup-scala@v10 + with: + java-version: ${{ matrix.java }} + + - name: Cache sbt + uses: actions/cache@v2 + with: + path: | + ~/.sbt + ~/.ivy2/cache + ~/.coursier/cache/v1 + ~/.cache/coursier/v1 + ~/AppData/Local/Coursier/Cache/v1 + ~/Library/Caches/Coursier/v1 + key: ${{ runner.os }}-sbt-cache-v2-${{ hashFiles('**/*.sbt') }}-${{ hashFiles('project/build.properties') }} + + - name: Runs tests and collect coverage + run: sbt -jvm-opts ci/ci.jvmopts ++${{ matrix.scala }} commonJS/test corelibJS/test interpreterJS/test + + - name: Publish a snapshot ${{ github.ref }} + if: env.HAS_SECRETS == 'true' + run: sbt ++${{ matrix.scala }} sdkJS/publish + env: + SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} + SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} diff --git a/.gitignore b/.gitignore index d621df577a..a3d1c520e6 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,11 @@ docs/spec/out/ test-out/ flamegraphs/ +node_modules/ +package-lock.json +sigma-js/.npmrc +sigma-js/dist/ + # sbt specific .cache .lib/ diff --git a/build.sbt b/build.sbt index cc1faf43cd..269ad14997 100644 --- a/build.sbt +++ b/build.sbt @@ -13,8 +13,6 @@ lazy val allConfigDependency = "compile->compile;test->test" lazy val commonSettings = Seq( organization := "org.scorexfoundation", - crossScalaVersions := Seq(scala213, scala212, scala211), - scalaVersion := scala212, scalacOptions ++= { CrossVersion.partialVersion(scalaVersion.value) match { case Some((2, 13)) => Seq("-Ywarn-unused:_,imports", "-Ywarn-unused:imports", "-release", "8") @@ -53,7 +51,16 @@ lazy val commonSettings = Seq( url("https://github.com/ScorexFoundation/sigmastate-interpreter"), "scm:git@github.com:ScorexFoundation/sigmastate-interpreter.git" ) - ), + ) +) + +lazy val crossScalaSettings = Seq( + crossScalaVersions := Seq(scala213, scala212, scala211), + scalaVersion := scala213 +) +lazy val crossScalaSettingsJS = Seq( + crossScalaVersions := Seq(scala213, scala212), + scalaVersion := scala213 ) def javacReleaseOption = { @@ -70,12 +77,25 @@ dynverSonatypeSnapshots in ThisBuild := true dynverSeparator in ThisBuild := "-" val bouncycastleBcprov = "org.bouncycastle" % "bcprov-jdk15on" % "1.66" + val scrypto = "org.scorexfoundation" %% "scrypto" % "2.3.0" +val scryptoDependency = + libraryDependencies += "org.scorexfoundation" %%% "scrypto" % "2.3.0" + val scorexUtil = "org.scorexfoundation" %% "scorex-util" % "0.2.0" +val scorexUtilDependency = + libraryDependencies += "org.scorexfoundation" %%% "scorex-util" % "0.2.0" + val debox = "org.scorexfoundation" %% "debox" % "0.10.0" val spireMacros = "org.typelevel" %% "spire-macros" % "0.17.0-M1" + val fastparse = "com.lihaoyi" %% "fastparse" % "2.3.3" +val fastparseDependency = + libraryDependencies += "com.lihaoyi" %%% "fastparse" % "2.3.3" + val scalaCompat = "org.scala-lang.modules" %% "scala-collection-compat" % "2.7.0" +lazy val scodecBitsDependency = + libraryDependencies += "org.scodec" %%% "scodec-bits" % "1.1.34" lazy val circeCore211 = "io.circe" %% "circe-core" % "0.10.0" lazy val circeGeneric211 = "io.circe" %% "circe-generic" % "0.10.0" @@ -90,32 +110,55 @@ def circeDeps(scalaVersion: String) = if (scalaVersion == scala211) else Seq(circeCore, circeGeneric, circeParser) +def circeDependency = { + libraryDependencies ++= { + val version = scalaVersion.value + val deps211 = Seq( + "io.circe" %%% "circe-core" % "0.10.0", + "io.circe" %%% "circe-generic" % "0.10.0", + "io.circe" %%% "circe-parser" % "0.10.0") + val deps212 = Seq( + "io.circe" %%% "circe-core" % "0.13.0", + "io.circe" %%% "circe-generic" % "0.13.0", + "io.circe" %%% "circe-parser" % "0.13.0") + if (version == scala211) deps211 else deps212 + } +} -val testingDependencies = Seq( +lazy val testingDependencies = Seq( "org.scalatest" %% "scalatest" % "3.2.14" % Test, "org.scalactic" %% "scalactic" % "3.2.14" % Test, "org.scalacheck" %% "scalacheck" % "1.15.2" % Test, // last supporting Scala 2.11 "org.scalatestplus" %% "scalacheck-1-15" % "3.2.3.0" % Test, // last supporting Scala 2.11 "com.lihaoyi" %% "pprint" % "0.6.3" % Test, - "com.storm-enroute" %% "scalameter" % "0.19" % Test, - "junit" % "junit" % "4.12" % Test + "com.storm-enroute" %% "scalameter" % "0.19" % Test ) +lazy val testingDependencies2 = + libraryDependencies ++= Seq( + "org.scalatest" %%% "scalatest" % "3.2.14" % Test, + "org.scalactic" %%% "scalactic" % "3.2.14" % Test, + "org.scalacheck" %%% "scalacheck" % "1.15.2" % Test, // last supporting Scala 2.11 + "org.scalatestplus" %%% "scalacheck-1-15" % "3.2.3.0" % Test, // last supporting Scala 2.11 + "com.lihaoyi" %%% "pprint" % "0.6.3" % Test + ) + lazy val testSettings = Seq( libraryDependencies ++= testingDependencies, - parallelExecution in Test := false, - baseDirectory in Test := file("."), - publishArtifact in Test := true, + Test / parallelExecution := false, + Test / baseDirectory := file("."), + Test / publishArtifact := true, publishArtifact in(Test, packageSrc) := true, publishArtifact in(Test, packageDoc) := false, - test in assembly := {}) + assembly / test := {}) -libraryDependencies ++= Seq( - scrypto, - scorexUtil, - "org.bouncycastle" % "bcprov-jdk15on" % "1.+", - fastparse, debox, spireMacros, scalaCompat -) ++ testingDependencies ++ circeDeps(scalaVersion.value) +lazy val testSettings2 = Seq( + Test / parallelExecution := true, + Test / baseDirectory := file("."), + Test / publishArtifact := true, + publishArtifact in(Test, packageSrc) := true, + publishArtifact in(Test, packageDoc) := false, + assembly / test := {}) scalacOptions ++= Seq("-feature", "-deprecation") @@ -124,80 +167,172 @@ publishArtifact in Test := true pomIncludeRepository := { _ => false } -val credentialFile = Path.userHome / ".sbt" / ".sigma-sonatype-credentials" -credentials ++= (for { - file <- if (credentialFile.exists) Some(credentialFile) else None -} yield Credentials(file)).toSeq +def libraryDefSettings = commonSettings ++ crossScalaSettings ++ testSettings -credentials ++= (for { - username <- Option(System.getenv().get("SONATYPE_USERNAME")) - password <- Option(System.getenv().get("SONATYPE_PASSWORD")) -} yield Credentials("Sonatype Nexus Repository Manager", "oss.sonatype.org", username, password)).toSeq - - -// PGP key for signing a release build published to sonatype -// signing is done by sbt-pgp plugin -// how to generate a key - https://central.sonatype.org/pages/working-with-pgp-signatures.html -// how to export a key and use it with Travis - https://docs.scala-lang.org/overviews/contributors/index.html#export-your-pgp-key-pair -pgpPublicRing := file("ci/pubring.asc") -pgpSecretRing := file("ci/secring.asc") -pgpPassphrase := sys.env.get("PGP_PASSPHRASE").map(_.toArray) -usePgpKeyHex("C1FD62B4D44BDF702CDF2B726FF59DA944B150DD") +lazy val commonDependenies2 = libraryDependencies ++= Seq( + "org.scala-lang" % "scala-reflect" % scalaVersion.value, + "org.scorexfoundation" %%% "debox" % "0.10.0", + "org.scala-lang.modules" %%% "scala-collection-compat" % "2.7.0" +) -def libraryDefSettings = commonSettings ++ testSettings +val sigmajsCryptoFacadeVersion = "0.0.5" -lazy val common = Project("common", file("common")) - .settings(libraryDefSettings, +lazy val common = crossProject(JVMPlatform, JSPlatform) + .in(file("common")) + .settings(commonSettings ++ testSettings2, + commonDependenies2, + testingDependencies2, + publish / skip := true + ) + .jvmSettings( crossScalaSettings ) + .jsSettings( + crossScalaSettingsJS, libraryDependencies ++= Seq( - "org.scala-lang" % "scala-reflect" % scalaVersion.value, - debox, scalaCompat - )) - .settings(publish / skip := true) + "org.scala-js" %%% "scala-js-macrotask-executor" % "1.0.0" + ), + useYarn := true + ) +lazy val commonJS = common.js + .enablePlugins(ScalaJSBundlerPlugin) -lazy val corelib = Project("core-lib", file("core-lib")) +lazy val corelib = crossProject(JVMPlatform, JSPlatform) + .in(file("core-lib")) .dependsOn(common % allConfigDependency) - .settings(libraryDefSettings, - libraryDependencies ++= Seq( debox, scrypto )) - .settings(publish / skip := true) + .settings(commonSettings ++ testSettings2, + commonDependenies2, + testingDependencies2, + crossScalaSettings, + scryptoDependency, + publish / skip := true + ) + .jvmSettings( + crossScalaSettings + ) + .jsSettings( + crossScalaSettingsJS, + libraryDependencies ++= Seq( + "org.scala-js" %%% "scala-js-macrotask-executor" % "1.0.0" + ), + useYarn := true + ) +lazy val corelibJS = corelib.js + .enablePlugins(ScalaJSBundlerPlugin) lazy val graphir = Project("graph-ir", file("graph-ir")) - .dependsOn(common % allConfigDependency, corelib) + .dependsOn(common.jvm % allConfigDependency, corelib.jvm % allConfigDependency) .settings( libraryDefSettings, libraryDependencies ++= Seq( debox, scrypto, bouncycastleBcprov )) .settings(publish / skip := true) -lazy val interpreter = (project in file("interpreter")) - .dependsOn(graphir % allConfigDependency) - .settings(libraryDefSettings) - .settings(libraryDependencies ++= - Seq(scorexUtil, fastparse) ++ circeDeps(scalaVersion.value) +lazy val interpreter = crossProject(JVMPlatform, JSPlatform) + .in(file("interpreter")) + .dependsOn(corelib % allConfigDependency) + .settings(commonSettings ++ testSettings2, + commonDependenies2, + testingDependencies2, + scorexUtilDependency, fastparseDependency, circeDependency, + publish / skip := true ) - .settings(publish / skip := true) + .jvmSettings( crossScalaSettings ) + .jsSettings( + crossScalaSettingsJS, + libraryDependencies ++= Seq ( + "org.scala-js" %%% "scala-js-macrotask-executor" % "1.0.0" + ), + useYarn := true + ) +lazy val interpreterJS = interpreter.js + .enablePlugins(ScalaJSBundlerPlugin) + .enablePlugins(ScalablyTypedConverterExternalNpmPlugin) + .settings( + // how to setup ScalablyTyped https://youtu.be/hWUAVrNj65c?t=1397 + externalNpm := {println(s"baseDirectory: ${baseDirectory.value}"); file(s"${baseDirectory.value}/../../sigma-js") }, + stIgnore ++= List("bouncycastle-js"), + scalaJSLinkerConfig ~= { conf => + conf.withSourceMap(false) + }, + Compile / npmDependencies ++= Seq( + "sigmajs-crypto-facade" -> sigmajsCryptoFacadeVersion + ) + ) + lazy val sc = (project in file("sc")) - .dependsOn(graphir % allConfigDependency, interpreter % allConfigDependency) + .dependsOn(graphir % allConfigDependency, interpreter.jvm % allConfigDependency) .settings(libraryDefSettings) .settings(libraryDependencies ++= Seq(scorexUtil, fastparse) ++ circeDeps(scalaVersion.value) ) .settings(publish / skip := true) +lazy val sdk = crossProject(JVMPlatform, JSPlatform) + .in(file("sdk")) + .dependsOn(corelib % allConfigDependency, interpreter % allConfigDependency) + .settings(commonSettings ++ testSettings2, + commonDependenies2, + testingDependencies2, + scodecBitsDependency, + publish / skip := true + ) + .jvmSettings( + crossScalaSettings + ) + .jsSettings( + crossScalaSettingsJS, + libraryDependencies ++= Seq( + "org.scala-js" %%% "scala-js-macrotask-executor" % "1.0.0" + ), + useYarn := true + ) +lazy val sdkJS = sdk.js + .enablePlugins(ScalaJSBundlerPlugin) + .settings( + scalaJSLinkerConfig ~= { conf => + conf.withSourceMap(false) + .withModuleKind(ModuleKind.CommonJSModule) + }, + Compile / npmDependencies ++= Seq( + "sigmajs-crypto-facade" -> sigmajsCryptoFacadeVersion + ) + ) + lazy val sigma = (project in file(".")) - .aggregate(common, corelib, graphir, interpreter, sc) + .aggregate(common.jvm, corelib.jvm, graphir, interpreter.jvm, sc, sdk.jvm) .settings(libraryDefSettings, rootSettings) .settings(publish / aggregate := false) .settings(publishLocal / aggregate := false) lazy val aggregateCompile = ScopeFilter( - inProjects(common, corelib, graphir, interpreter, sc), + inProjects(common.jvm, corelib.jvm, graphir, interpreter.jvm, sc, sdk.jvm), inConfigurations(Compile)) lazy val rootSettings = Seq( - sources in Compile := sources.all(aggregateCompile).value.flatten, - sourceDirectories in Compile := sourceDirectories.all(aggregateCompile).value.flatten, + Compile / sources := sources.all(aggregateCompile).value.flatten, + Compile / sourceDirectories := sourceDirectories.all(aggregateCompile).value.flatten, libraryDependencies := libraryDependencies.all(aggregateCompile).value.flatten, mappings in (Compile, packageSrc) ++= (mappings in(Compile, packageSrc)).all(aggregateCompile).value.flatten, mappings in (Test, packageBin) ++= (mappings in(Test, packageBin)).all(aggregateCompile).value.flatten, mappings in(Test, packageSrc) ++= (mappings in(Test, packageSrc)).all(aggregateCompile).value.flatten ) + +val credentialFile = Path.userHome / ".sbt" / ".sigma-sonatype-credentials" +credentials ++= (for { + file <- if (credentialFile.exists) Some(credentialFile) else None +} yield Credentials(file)).toSeq + +credentials ++= (for { + username <- Option(System.getenv().get("SONATYPE_USERNAME")) + password <- Option(System.getenv().get("SONATYPE_PASSWORD")) +} yield Credentials("Sonatype Nexus Repository Manager", "oss.sonatype.org", username, password)).toSeq + + +// PGP key for signing a release build published to sonatype +// signing is done by sbt-pgp plugin +// how to generate a key - https://central.sonatype.org/pages/working-with-pgp-signatures.html +// how to export a key and use it with Travis - https://docs.scala-lang.org/overviews/contributors/index.html#export-your-pgp-key-pair +pgpPublicRing := file("ci/pubring.asc") +pgpSecretRing := file("ci/secring.asc") +pgpPassphrase := sys.env.get("PGP_PASSPHRASE").map(_.toArray) +usePgpKeyHex("C1FD62B4D44BDF702CDF2B726FF59DA944B150DD") + diff --git a/common/js/src/main/scala/scalan/reflection/Platform.scala b/common/js/src/main/scala/scalan/reflection/Platform.scala new file mode 100644 index 0000000000..5005f13741 --- /dev/null +++ b/common/js/src/main/scala/scalan/reflection/Platform.scala @@ -0,0 +1,50 @@ +package scalan.reflection + +import scala.collection.mutable + +/** JS Platform dependent implementation of reflection methods. */ +object Platform { + /** Returns an RClass instance for the given class. + * + * @param clazz The class for which to retrieve an RClass instance. + * @tparam T The type of the class. + * @return An RClass instance for the given class. + * @throws java.lang.RuntimeException if RClass metadata for the given class cannot be + * found. + */ + def resolveClass[T](clazz: Class[T]): RClass[T] = { + val res = CommonReflection.classes.get(clazz) match { + case Some(c) => + assert(c.clazz == clazz) + c + case _ => + sys.error(s"Cannot find RClass data for $clazz") + // Uncomment the following line to collect missing reflection data and generate Scala code for it + // memoize(classes)(clazz, new JRClass[T](clazz)) + } + res.asInstanceOf[RClass[T]] + } + + /** A cache that stores key-value pairs using HashMap. + * This class is thread-safe using the synchronized access to the underlying HashMap + * instance. + * + * @tparam K the type of keys used in the cache + * @tparam V the type of values stored in the cache + */ + class Cache[K, V] { + private val map = mutable.HashMap.empty[K, V] + + /** Retrieves the value associated with the given key from the cache or + * computes and stores the value if the key is not present in the cache. + * This method is thread-safe using the synchronized block. + * + * @param key the key to look up or store in the cache + * @param value a by-name parameter that computes the value to be stored if the key is not present + * @return the value associated with the key, either retrieved or computed + */ + def getOrElseUpdate(key: K, value: => V): V = synchronized { + map.getOrElseUpdate(key, value) + } + } +} diff --git a/common/src/main/scala/scalan/reflection/JavaImpl.scala b/common/jvm/src/main/scala/scalan/reflection/JavaImpl.scala similarity index 99% rename from common/src/main/scala/scalan/reflection/JavaImpl.scala rename to common/jvm/src/main/scala/scalan/reflection/JavaImpl.scala index bf8033c070..d5629ec4d1 100644 --- a/common/src/main/scala/scalan/reflection/JavaImpl.scala +++ b/common/jvm/src/main/scala/scalan/reflection/JavaImpl.scala @@ -2,7 +2,7 @@ package scalan.reflection import debox.cfor -import java.lang.reflect.{Field, Constructor, Method} +import java.lang.reflect.{Constructor, Field, Method} import scala.collection.concurrent.TrieMap import scala.collection.mutable diff --git a/common/src/main/scala/scalan/reflection/Platform.scala b/common/jvm/src/main/scala/scalan/reflection/Platform.scala similarity index 67% rename from common/src/main/scala/scalan/reflection/Platform.scala rename to common/jvm/src/main/scala/scalan/reflection/Platform.scala index bf2ee9c6cc..6030ad11a7 100644 --- a/common/src/main/scala/scalan/reflection/Platform.scala +++ b/common/jvm/src/main/scala/scalan/reflection/Platform.scala @@ -43,4 +43,23 @@ object Platform { // } cls } + + /** A thread-safe cache class that stores key-value pairs. + * + * @tparam K the type of keys used in the cache + * @tparam V the type of values stored in the cache + */ + class Cache[K, V] { + /** A concurrent TrieMap for storing key-value pairs. */ + private val map: TrieMap[K, V] = TrieMap.empty[K, V] + + /** Retrieves the value associated with the given key from the cache or + * computes and stores the value if the key is not present in the cache. + * + * @param key the key to look up or store in the cache + * @param value a by-name parameter that computes the value to be stored if the key is not present + * @return the value associated with the key, either retrieved or computed + */ + def getOrElseUpdate(key: K, value: => V): V = map.getOrElseUpdate(key, value) + } } diff --git a/common/src/test/scala/scalan/util/FileUtil.scala b/common/jvm/src/test/scala/scalan/util/FileUtil.scala similarity index 79% rename from common/src/test/scala/scalan/util/FileUtil.scala rename to common/jvm/src/test/scala/scalan/util/FileUtil.scala index 89a596ed28..c4a8d15595 100644 --- a/common/src/test/scala/scalan/util/FileUtil.scala +++ b/common/jvm/src/test/scala/scalan/util/FileUtil.scala @@ -1,15 +1,42 @@ package scalan.util +import scalan.util.CollectionUtil.AnyOps +import scalan.util.StringUtil.cleanFileName + import java.io._ import java.net.{JarURLConnection, URL} import java.nio.file._ import java.nio.file.attribute.BasicFileAttributes import scala.Console import scala.collection.JavaConverters._ -import scalan.util.StringUtil.StringUtilExtensions -import scalan.util.CollectionUtil.AnyOps object FileUtil { + + implicit class StringUtilExtensions(val str: String) extends AnyVal { + /** The last component of the string after the given separator. + * + * @param sep the separator character + */ + def lastComponent(sep: Char): String = { + str.substring(str.lastIndexOf(sep) + 1) + } + + /** The prefix of the string before the specified substring. + * + * @param substr the substring to find in the input string + */ + def prefixBefore(substr: String): String = { + val pos = str.indexOf(substr) + val res = if (pos == -1) str else str.substring(0, pos) + res + } + } + + /** Executes a function with a PrintWriter for the specified file. + * + * @param file the target file + * @param f a function that takes a PrintWriter + */ def withFile(file: File)(f: PrintWriter => Unit): Unit = { if (file.isDirectory && !file.delete()) { throw new RuntimeException(s"File $file is a non-empty directory") @@ -24,8 +51,18 @@ object FileUtil { } } + /** Writes the provided text to the specified file. + * + * @param file the target file + * @param text the text to write + */ def write(file: File, text: String): Unit = withFile(file) { _.print(text) } + /** Executes a function with the provided PrintStream as standard output and error streams. + * + * @param out the PrintStream to use as standard output and error streams + * @param func the function to execute + */ def withStdOutAndErr(out: PrintStream)(func: => Unit): Unit = { val oldStdOut = System.out val oldStdErr = System.err @@ -40,6 +77,11 @@ object FileUtil { } } + /** Captures the standard output and error streams produced by the given function. + * + * @param func the function to execute + * @return the captured output as a string + */ def captureStdOutAndErr(func: => Unit): String = { val out = new ByteArrayOutputStream val ps = new PrintStream(out) @@ -48,6 +90,11 @@ object FileUtil { out.toString } + /** Returns the last modified timestamp of the specified resource on the classpath. + * + * @param source the resource to find + * @param classLoader the class loader to use for searching the resource + */ def classPathLastModified(source: String, classLoader: ClassLoader = getClass.getClassLoader) = { def urlLastModified(url: URL): Long = { url.getProtocol match { @@ -182,12 +229,6 @@ object FileUtil { def urlToFile(url: URL) = Paths.get(url.toURI).toFile - /** Accepts an arbitrary (printable) string and returns a similar string - * which can be used as a file name. For convenience, replaces spaces with hyphens. - */ - def cleanFileName(string: String) = string. - replaceAll("""[ /\\:;<>|?*^]""", "_"). - replaceAll("""['"]""", "") def isBadFileName(string: String) = cleanFileName(string) != string diff --git a/common/src/test/scala/scalan/util/FileUtilTests.scala b/common/jvm/src/test/scala/scalan/util/FileUtilTests.scala similarity index 68% rename from common/src/test/scala/scalan/util/FileUtilTests.scala rename to common/jvm/src/test/scala/scalan/util/FileUtilTests.scala index 6a265b1367..2edb4289ce 100644 --- a/common/src/test/scala/scalan/util/FileUtilTests.scala +++ b/common/jvm/src/test/scala/scalan/util/FileUtilTests.scala @@ -4,8 +4,24 @@ import scalan.BaseNestedTests class FileUtilTests extends BaseNestedTests { import FileUtil._ + + describe("StringExtension methods") { + it("lastComponent") { + "a/b/c".lastComponent('/') shouldBe ("c") + "a/b/".lastComponent('/') shouldBe ("") + "a".lastComponent('/') shouldBe ("a") + "".lastComponent('/') shouldBe ("") + } + it("prefixBefore") { + "a/b/c".prefixBefore("/b") shouldBe ("a") + "a/b/c".prefixBefore("/c") shouldBe ("a/b") + "a/b/c".prefixBefore("a/b/c") shouldBe ("") + "a/b/c".prefixBefore("") shouldBe ("") + } + } + describe("File traversals") { - val root = file("common/src/test/resources/root") + val root = file("common/shared/src/test/resources/root") val subdir = file(root, "subdir") val subsubdir = file(subdir, "subsubdir") val empty = { val dir = file(root, "empty"); dir.mkdir(); dir } diff --git a/common/src/test/scala/scalan/util/ProcessUtil.scala b/common/jvm/src/test/scala/scalan/util/ProcessUtil.scala similarity index 100% rename from common/src/test/scala/scalan/util/ProcessUtil.scala rename to common/jvm/src/test/scala/scalan/util/ProcessUtil.scala diff --git a/common/src/main/scala-2.11/sigmastate/kiama/util/Collections.scala b/common/shared/src/main/scala-2.11/sigmastate/kiama/util/Collections.scala similarity index 100% rename from common/src/main/scala-2.11/sigmastate/kiama/util/Collections.scala rename to common/shared/src/main/scala-2.11/sigmastate/kiama/util/Collections.scala diff --git a/common/src/main/scala-2.12/sigmastate/kiama/util/Collections.scala b/common/shared/src/main/scala-2.12/sigmastate/kiama/util/Collections.scala similarity index 100% rename from common/src/main/scala-2.12/sigmastate/kiama/util/Collections.scala rename to common/shared/src/main/scala-2.12/sigmastate/kiama/util/Collections.scala diff --git a/common/src/main/scala-2.13/sigmastate/kiama/util/Collections.scala b/common/shared/src/main/scala-2.13/sigmastate/kiama/util/Collections.scala similarity index 100% rename from common/src/main/scala-2.13/sigmastate/kiama/util/Collections.scala rename to common/shared/src/main/scala-2.13/sigmastate/kiama/util/Collections.scala diff --git a/common/src/main/scala/java7/compat/Math.scala b/common/shared/src/main/scala/java7/compat/Math.scala similarity index 100% rename from common/src/main/scala/java7/compat/Math.scala rename to common/shared/src/main/scala/java7/compat/Math.scala diff --git a/common/src/main/scala/scalan/AnyVals.scala b/common/shared/src/main/scala/scalan/AnyVals.scala similarity index 92% rename from common/src/main/scala/scalan/AnyVals.scala rename to common/shared/src/main/scala/scalan/AnyVals.scala index e1d5235d42..b77aa943de 100644 --- a/common/src/main/scala/scalan/AnyVals.scala +++ b/common/shared/src/main/scala/scalan/AnyVals.scala @@ -32,14 +32,6 @@ class AVHashMap[K,V](val hashMap: HashMap[K,V]) extends AnyVal { @inline final def clear(): Unit = { hashMap.clear() } - final def getOrElseUpdate(key: K, op: => V): V = { - var v = hashMap.get(key) - if (v == null) { - v = op - hashMap.put(key, v) - } - v - } @inline final def keySet: java.util.Set[K] = hashMap.keySet() } object AVHashMap { diff --git a/common/src/main/scala/scalan/DFunc.scala b/common/shared/src/main/scala/scalan/DFunc.scala similarity index 52% rename from common/src/main/scala/scalan/DFunc.scala rename to common/shared/src/main/scala/scalan/DFunc.scala index 4083792132..23f4766fa8 100644 --- a/common/src/main/scala/scalan/DFunc.scala +++ b/common/shared/src/main/scala/scalan/DFunc.scala @@ -4,8 +4,3 @@ package scalan abstract class DFunc[@specialized(Int) A, B] { def apply(x: A): B } - -/** Convenient but SLOW adapter to be used in tests. */ -class DFuncAdapter[A,B](f: A => B) extends DFunc[A, B] { - override def apply(x: A): B = f(x) -} diff --git a/common/src/main/scala/scalan/ExactIntegral.scala b/common/shared/src/main/scala/scalan/ExactIntegral.scala similarity index 100% rename from common/src/main/scala/scalan/ExactIntegral.scala rename to common/shared/src/main/scala/scalan/ExactIntegral.scala diff --git a/common/src/main/scala/scalan/ExactNumeric.scala b/common/shared/src/main/scala/scalan/ExactNumeric.scala similarity index 100% rename from common/src/main/scala/scalan/ExactNumeric.scala rename to common/shared/src/main/scala/scalan/ExactNumeric.scala diff --git a/common/src/main/scala/scalan/ExactOrdering.scala b/common/shared/src/main/scala/scalan/ExactOrdering.scala similarity index 100% rename from common/src/main/scala/scalan/ExactOrdering.scala rename to common/shared/src/main/scala/scalan/ExactOrdering.scala diff --git a/common/src/main/scala/scalan/Lazy.scala b/common/shared/src/main/scala/scalan/Lazy.scala similarity index 100% rename from common/src/main/scala/scalan/Lazy.scala rename to common/shared/src/main/scala/scalan/Lazy.scala diff --git a/common/src/main/scala/scalan/OverloadHack.scala b/common/shared/src/main/scala/scalan/OverloadHack.scala similarity index 61% rename from common/src/main/scala/scalan/OverloadHack.scala rename to common/shared/src/main/scala/scalan/OverloadHack.scala index 685946979e..7905b9604e 100644 --- a/common/src/main/scala/scalan/OverloadHack.scala +++ b/common/shared/src/main/scala/scalan/OverloadHack.scala @@ -12,15 +12,7 @@ object OverloadHack { class Overloaded1 extends Overloaded { override def toString = "O1"} class Overloaded2 extends Overloaded { override def toString = "O2"} class Overloaded3 extends Overloaded { override def toString = "O3"} - class Overloaded4 extends Overloaded { override def toString = "O4"} - class Overloaded5 extends Overloaded { override def toString = "O5"} - class Overloaded6 extends Overloaded { override def toString = "O6"} - class Overloaded7 extends Overloaded { override def toString = "O7"} implicit val overloaded1 = new Overloaded1 implicit val overloaded2 = new Overloaded2 implicit val overloaded3 = new Overloaded3 - implicit val overloaded4 = new Overloaded4 - implicit val overloaded5 = new Overloaded5 - implicit val overloaded6 = new Overloaded6 - implicit val overloaded7 = new Overloaded7 } \ No newline at end of file diff --git a/common/src/main/scala/scalan/TypeDesc.scala b/common/shared/src/main/scala/scalan/TypeDesc.scala similarity index 97% rename from common/src/main/scala/scalan/TypeDesc.scala rename to common/shared/src/main/scala/scalan/TypeDesc.scala index 5b42fbfa4c..7020cd40ae 100644 --- a/common/src/main/scala/scalan/TypeDesc.scala +++ b/common/shared/src/main/scala/scalan/TypeDesc.scala @@ -66,14 +66,13 @@ object RType { } val AnyType : RType[Any] = GeneralType[Any] (ClassTag.Any) - val AnyRefType : RType[AnyRef] = GeneralType[AnyRef] (ClassTag.AnyRef) - val NothingType : RType[Nothing] = GeneralType[Nothing] (ClassTag.Nothing) implicit val BooleanType : RType[Boolean] = PrimitiveType[Boolean] (ClassTag.Boolean, Array.emptyBooleanArray) implicit val ByteType : RType[Byte] = PrimitiveType[Byte] (ClassTag.Byte, Array.emptyByteArray) implicit val ShortType : RType[Short] = PrimitiveType[Short] (ClassTag.Short, Array.emptyShortArray) implicit val IntType : RType[Int] = PrimitiveType[Int] (ClassTag.Int, Array.emptyIntArray) implicit val LongType : RType[Long] = PrimitiveType[Long] (ClassTag.Long, Array.emptyLongArray) + // TODO v5.x: optimize: remove Char, Float, Double types, they are not supported and will never be implicit val CharType : RType[Char] = PrimitiveType[Char] (ClassTag.Char, Array.emptyCharArray) implicit val FloatType : RType[Float] = PrimitiveType[Float] (ClassTag.Float, Array.emptyFloatArray) implicit val DoubleType : RType[Double] = PrimitiveType[Double] (ClassTag.Double, Array.emptyDoubleArray) diff --git a/common/src/main/scala/scalan/WrapSpec.scala b/common/shared/src/main/scala/scalan/WrapSpec.scala similarity index 100% rename from common/src/main/scala/scalan/WrapSpec.scala rename to common/shared/src/main/scala/scalan/WrapSpec.scala diff --git a/common/src/main/scala/scalan/package.scala b/common/shared/src/main/scala/scalan/package.scala similarity index 86% rename from common/src/main/scala/scalan/package.scala rename to common/shared/src/main/scala/scalan/package.scala index 87845d14c5..1a25931fd6 100644 --- a/common/src/main/scala/scalan/package.scala +++ b/common/shared/src/main/scala/scalan/package.scala @@ -18,6 +18,8 @@ package object scalan { /** Create a new empty buffer around pre-allocated empty array. * This method is preferred, rather that creating empty debox.Buffer directly * because it allows to avoid allocation of the empty array. + * Note, this method allocates a new Buffer, but the underlying empty array is shared. + * This is safe because empty arrays are immutable. */ def emptyDBufferOfInt: debox.Buffer[Int] = debox.Buffer.unsafe(EmptyArrayOfInt) diff --git a/common/src/main/scala/scalan/reflection/CommonReflection.scala b/common/shared/src/main/scala/scalan/reflection/CommonReflection.scala similarity index 100% rename from common/src/main/scala/scalan/reflection/CommonReflection.scala rename to common/shared/src/main/scala/scalan/reflection/CommonReflection.scala diff --git a/common/src/main/scala/scalan/reflection/RClass.scala b/common/shared/src/main/scala/scalan/reflection/RClass.scala similarity index 100% rename from common/src/main/scala/scalan/reflection/RClass.scala rename to common/shared/src/main/scala/scalan/reflection/RClass.scala diff --git a/common/src/main/scala/scalan/reflection/StaticImpl.scala b/common/shared/src/main/scala/scalan/reflection/StaticImpl.scala similarity index 100% rename from common/src/main/scala/scalan/reflection/StaticImpl.scala rename to common/shared/src/main/scala/scalan/reflection/StaticImpl.scala diff --git a/common/src/main/scala/scalan/reflection/package.scala b/common/shared/src/main/scala/scalan/reflection/package.scala similarity index 100% rename from common/src/main/scala/scalan/reflection/package.scala rename to common/shared/src/main/scala/scalan/reflection/package.scala diff --git a/common/shared/src/main/scala/scalan/util/CollectionUtil.scala b/common/shared/src/main/scala/scalan/util/CollectionUtil.scala new file mode 100644 index 0000000000..10042b5b78 --- /dev/null +++ b/common/shared/src/main/scala/scalan/util/CollectionUtil.scala @@ -0,0 +1,224 @@ +package scalan.util + +import java.util + +import scala.collection.{Seq, mutable, GenIterable} +import scala.collection.mutable.{HashMap, ArrayBuffer} +import scala.reflect.ClassTag +import scala.collection.compat._ + +object CollectionUtil { + + /** @deprecated shouldn't be used other than for backwards compatibility with v3.x, v4.x. */ + def concatArrays[T](xs: Array[T], ys: Array[T]): Array[T] = { + val len = xs.length + ys.length + val result = (xs match { + case arr: Array[AnyRef] => new Array[AnyRef](len) // creates an array with invalid type descriptor (i.e. when T == Tuple2) + case arr: Array[Byte] => new Array[Byte](len) + case arr: Array[Short] => new Array[Short](len) + case arr: Array[Int] => new Array[Int](len) + case arr: Array[Long] => new Array[Long](len) + case arr: Array[Char] => new Array[Char](len) + case arr: Array[Float] => new Array[Float](len) + case arr: Array[Double] => new Array[Double](len) + case arr: Array[Boolean] => new Array[Boolean](len) + }).asInstanceOf[Array[T]] + Array.copy(xs, 0, result, 0, xs.length) + Array.copy(ys, 0, result, xs.length, ys.length) + result + } + + /** Concatenates two arrays into a new resulting array. + * All items of both arrays are copied to the result using System.arraycopy. + * This method takes ClassTag to create proper resulting array. + * Can be used in v5.0 and above. + */ + def concatArrays_v5[T:ClassTag](arr1: Array[T], arr2: Array[T]): Array[T] = { + val l1 = arr1.length + val l2 = arr2.length + val length: Int = l1 + l2 + val result: Array[T] = new Array[T](length) + System.arraycopy(arr1, 0, result, 0, l1) + System.arraycopy(arr2, 0, result, l1, l2) + result + } + + /** Computes the deep hash code for the given array. + * + * This method calculates the hash code based on the array's elements and type, taking nested arrays + * into account. + * + * @tparam T the type of the elements in the array + * @param arr the input array for which the deep hash code is to be calculated + */ + def deepHashCode[T](arr: Array[T]): Int = arr match { + case arr: Array[AnyRef] => util.Arrays.deepHashCode(arr) + case arr: Array[Byte] => util.Arrays.hashCode(arr) + case arr: Array[Short] => util.Arrays.hashCode(arr) + case arr: Array[Int] => util.Arrays.hashCode(arr) + case arr: Array[Long] => util.Arrays.hashCode(arr) + case arr: Array[Char] => util.Arrays.hashCode(arr) + case arr: Array[Float] => util.Arrays.hashCode(arr) + case arr: Array[Double] => util.Arrays.hashCode(arr) + case arr: Array[Boolean] => util.Arrays.hashCode(arr) + } + + /** Group the given sequence of pairs by first values as keys. + * @param kvs sequence of values which is traversed once + * @return a multimap with ArrayBuffer of values for each key. + */ + def createMultiMap[K,V](kvs: GenIterable[(K,V)]): Map[K, ArrayBuffer[V]] = { + val res = HashMap.empty[K, ArrayBuffer[V]] + kvs.foreach { case (k, v) => + if (res.contains(k)) + res(k) += v + else + res += k -> ArrayBuffer(v) + () + } + res.toMap + } + + /** Perform relational inner join of two sequences using the given key projections. */ + def joinSeqs[O, I, K](outer: Iterable[O], inner: Iterable[I])(outKey: O=>K, inKey: I=>K): Iterable[(O,I)] = { + val kvs = createMultiMap(inner.map(i => (inKey(i), i))) + val res = outer.flatMap(o => { + val ko = outKey(o) + kvs.get(ko) match { + case Some(inners) => + inners.map(i => (o,i)) + case None => + Nil + } + }) + res + } + + /** Performs an outer join on two sequences using specified key projection functions and + * result projection functions. + * + * @param outer the outer sequence for the join operation + * @param inner the inner sequence for the join operation + * @param outKey projects an outer element to its join key + * @param inKey projects an inner element to its join key + * @param projO projects a non-matching outer element and its key to a result element + * @param projI projects a non-matching inner element and its key to a result element + * @param proj projects a matching pair of outer and inner elements and their key to a result element + */ + def outerJoinSeqs[O, I, K, R] + (outer: Seq[O], inner: Seq[I]) + (outKey: O=>K, inKey: I=>K) + (projO: (K,O) => R, projI: (K,I) => R, proj:(K,O,I) => R): Seq[(K,R)] = { + val res = ArrayBuffer.empty[(K,R)] + val kis = inner.map(i => (inKey(i), i)) + val kvs = createMultiMap(kis) + val outerKeys = mutable.Set.empty[K] + for (o <- outer) { + val ko = outKey(o) + outerKeys += ko + if (!kvs.contains(ko)) + res += ((ko, projO(ko, o))) + else + for (i <- kvs(ko)) + res += ((ko, proj(ko, o, i))) + } + for ((k,i) <- kis if !outerKeys.contains(k)) + res += ((k, projI(k, i))) + res + } + + implicit class AnyOps[A](val x: A) extends AnyVal { + /** Traverses the tree structure in a depth-first manner using the provided function to generate child nodes. + * + * @param f a function that takes a node of type A and returns a list of its children + * @tparam A the type of the node elements in the tree structure + * @return a list of traversed nodes in depth-first order + */ + def traverseDepthFirst(f: A => List[A]): List[A] = { + var all: List[A] = Nil + var stack = List(x) + while (stack.nonEmpty) { + val h = stack.head + stack = stack.tail + + var next = f(h).reverse + while (next.nonEmpty) { + stack = next.head :: stack + next = next.tail + } + all = h :: all + } + all.reverse + } + } + + implicit class TraversableOps[A, Source[X] <: GenIterable[X]](val xs: Source[A]) extends AnyVal { + + /** Returns a copy of this collection where elements at `items(i)._1` are replaced + * with `items(i)._2` for each i. + */ + def updateMany(items: Seq[(Int, A)])(implicit tA: ClassTag[A]): Seq[A] = { + val res = xs.toArray + val nItems = items.length + var i = 0 + while (i < nItems) { + val item = items(i) + // this explicit check is necessary for Scala.js to be equivalent with JVM + if (!res.isDefinedAt(item._1)) { + throw new IndexOutOfBoundsException("Index out of range: " + item._1) + } + res(item._1) = item._2 + i += 1 + } + res + } + + /** Traverses the `xs` collection and checks that each item is of type `B`. + * @return original collection `xs` casted to Source[B] + * @throws java.lang.AssertionError if at least one item cannot be cast to `B` + */ + def cast[B:ClassTag](implicit cbf: BuildFrom[Source[A], B, Source[B]]): Source[B] = { + for (x <- xs) { + assert(x match { case _: B => true case _ => false}, s"Value $x doesn't conform to type ${reflect.classTag[B]}") + } + xs.asInstanceOf[Source[B]] + } + + /** This methods is for compatibility with Scala 2.11. */ + def distinctBy[K](key: A => K) + (implicit cbf: BuildFrom[Source[A], A, Source[A]]): Source[A] = { + val keys = mutable.Set[K]() + val b = cbf.newBuilder(xs) + for ( x <- xs ) { + val k = key(x) + if (!keys.contains(k)) { + b += x + keys += k + } + } + b.result() + } + + private def flattenIter(i: Iterator[_]): Iterator[_] = i.flatMap(x => x match { + case nested: GenIterable[_] => nested + case arr: Array[_] => arr.iterator + case _ => Iterator.single(x) + }) + + /** Determines if two nested structures have the same elements in the same order. + * Supports structures containing `GenIterable` and `Array` elements. + * + * @param that the other nested structure to compare with + * @tparam A the type of the elements in the current structure + * @tparam B the type of the elements in the other structure, a supertype of A + * @return true if the two nested structures have the same elements in the same order, false otherwise + */ + def sameElementsNested[B >: A](that: GenIterable[B]): Boolean = { + val i1: Iterator[Any] = flattenIter(xs.iterator) + val i2: Iterator[Any] = flattenIter(that.iterator) + i1.sameElements(i2) + } + } +} + + diff --git a/common/src/main/scala/scalan/util/Extensions.scala b/common/shared/src/main/scala/scalan/util/Extensions.scala similarity index 65% rename from common/src/main/scala/scalan/util/Extensions.scala rename to common/shared/src/main/scala/scalan/util/Extensions.scala index 5b0c70987a..00ee2f10dd 100644 --- a/common/src/main/scala/scalan/util/Extensions.scala +++ b/common/shared/src/main/scala/scalan/util/Extensions.scala @@ -6,9 +6,7 @@ import scala.language.higherKinds object Extensions { implicit class BooleanOps(val b: Boolean) extends AnyVal { - /** Convert true to 1 and false to 0 - * @since 2.0 - */ + /** Convert true to 1 and false to 0 */ def toByte: Byte = if (b) 1 else 0 } @@ -39,13 +37,7 @@ object Extensions { r.toByte } - def toShort: Short = b.toShort - def toInt: Int = b.toInt - def toLong: Long = b.toLong - - /** Absolute value of this numeric value. - * @since 2.0 - */ + /** Absolute value of this numeric value. */ def toAbs: Byte = if (b < 0) (-b).toByte else b } @@ -77,9 +69,7 @@ object Extensions { r.toShort } - /** Absolute value of this numeric value. - * @since 2.0 - */ + /** Absolute value of this numeric value. */ def toAbs: Short = if (x < 0) (-x).toShort else x } @@ -96,9 +86,7 @@ object Extensions { x.toShort } - /** Absolute value of this numeric value. - * @since 2.0 - */ + /** Absolute value of this numeric value. */ def toAbs: Int = if (x < 0) -x else x } @@ -121,37 +109,87 @@ object Extensions { x.toInt } - /** Absolute value of this numeric value. - * @since 2.0 - */ + /** Absolute value of this numeric value. */ def toAbs: Long = if (x < 0) -x else x } implicit class BigIntegerOps(val x: BigInteger) extends AnyVal { - /** Returns this `mod` Q, i.e. remainder of division by Q, where Q is an order of the cryprographic group. - * @since 2.0 - */ - def modQ: BigInt = ??? - /** Adds this number with `other` by module Q. - * @since 2.0 + /** + * Converts this {@code BigInteger} to a {@code long}, checking + * for lost information. If the value of this {@code BigInteger} + * is out of the range of the {@code long} type, then an + * {@code ArithmeticException} is thrown. + * + * @return this {@code BigInteger} converted to a {@code long}. + * @throws ArithmeticException if the value of {@code this} will + * not exactly fit in a {@code long}. + * @see BigInteger#longValue + * @since 1.8 */ - def plusModQ(other: BigInt): BigInt = ??? + def toLongExact: Long = { + if (x.bitLength <= 63) x.longValue + else + throw new ArithmeticException("BigInteger out of long range") + } - /** Subracts this number with `other` by module Q. - * @since 2.0 + /** + * Converts this {@code BigInteger} to an {@code int}, checking + * for lost information. If the value of this {@code BigInteger} + * is out of the range of the {@code int} type, then an + * {@code ArithmeticException} is thrown. + * + * @return this {@code BigInteger} converted to an {@code int}. + * @throws ArithmeticException if the value of {@code this} will + * not exactly fit in an {@code int}. + * @see BigInteger#intValue */ - def minusModQ(other: BigInt): BigInt = ??? + def toIntExact: Int = { + if (x.bitLength <= 31) x.intValue + else + throw new ArithmeticException("BigInteger out of int range") + } - /** Multiply this number with `other` by module Q. - * @since 2.0 + /** + * Converts this {@code BigInteger} to a {@code short}, checking + * for lost information. If the value of this {@code BigInteger} + * is out of the range of the {@code short} type, then an + * {@code ArithmeticException} is thrown. + * + * @return this {@code BigInteger} converted to a {@code short}. + * @throws ArithmeticException if the value of {@code this} will + * not exactly fit in a {@code short}. + * @see BigInteger#shortValue */ - def multModQ(other: BigInt): BigInt = ??? + def toShortExact: Short = { + if (x.bitLength <= 31) { + val value = x.intValue + if (value >= Short.MinValue && value <= Short.MaxValue) + return x.shortValue + } + throw new ArithmeticException("BigInteger out of short range") + } - /** Multiply this number with `other` by module Q. - * @since Mainnet + /** + * Converts this {@code BigInteger} to a {@code byte}, checking + * for lost information. If the value of this {@code BigInteger} + * is out of the range of the {@code byte} type, then an + * {@code ArithmeticException} is thrown. + * + * @return this {@code BigInteger} converted to a {@code byte}. + * @throws ArithmeticException if the value of {@code this} will + * not exactly fit in a {@code byte}. + * @see BigInteger#byteValue + * @since 1.8 */ - def multInverseModQ: BigInt = ??? + def toByteExact: Byte = { + if (x.bitLength <= 31) { + val value = x.intValue + if (value >= Byte.MinValue && value <= Byte.MaxValue) + return x.byteValue + } + throw new ArithmeticException("BigInteger out of byte range") + } /** Checks this {@code BigInteger} can be cust to 256 bit two's-compliment representation, * checking for lost information. If the value of this {@code BigInteger} diff --git a/common/src/main/scala/scalan/util/GraphUtil.scala b/common/shared/src/main/scala/scalan/util/GraphUtil.scala similarity index 100% rename from common/src/main/scala/scalan/util/GraphUtil.scala rename to common/shared/src/main/scala/scalan/util/GraphUtil.scala diff --git a/common/src/main/scala/scalan/util/MemoizedFunc.scala b/common/shared/src/main/scala/scalan/util/MemoizedFunc.scala similarity index 100% rename from common/src/main/scala/scalan/util/MemoizedFunc.scala rename to common/shared/src/main/scala/scalan/util/MemoizedFunc.scala diff --git a/common/src/main/scala/scalan/util/ReflectionUtil.scala b/common/shared/src/main/scala/scalan/util/ReflectionUtil.scala similarity index 100% rename from common/src/main/scala/scalan/util/ReflectionUtil.scala rename to common/shared/src/main/scala/scalan/util/ReflectionUtil.scala diff --git a/common/src/main/scala/scalan/util/StringUtil.scala b/common/shared/src/main/scala/scalan/util/StringUtil.scala similarity index 56% rename from common/src/main/scala/scalan/util/StringUtil.scala rename to common/shared/src/main/scala/scalan/util/StringUtil.scala index d62c728e19..9dcf42daab 100644 --- a/common/src/main/scala/scalan/util/StringUtil.scala +++ b/common/shared/src/main/scala/scalan/util/StringUtil.scala @@ -5,13 +5,6 @@ import debox.cfor object StringUtil { final def quote(x: Any) = "\"" + x + "\"" - /** Uppercase the first character. */ - final def lowerCaseFirst(s: String) = if (s.isEmpty) { - s - } else { - s.substring(0, 1).toLowerCase + s.substring(1) - } - /** Emit string representation of `x` into given builder `sb`, * recursively descending into Array structure of `x`. */ @@ -35,29 +28,22 @@ object StringUtil { } } + /** Accepts an arbitrary (printable) string and returns a similar string + * which can be used as a file name. For convenience, replaces spaces with hyphens. + */ + def cleanFileName(string: String) = string. + replaceAll("""[ /\\:;<>|?*^]""", "_"). + replaceAll("""['"]""", "") + + /** Compose file name from components. */ + def fileName(file: String, pathComponents: String*): String = { + val path = pathComponents.mkString("/") + s"$file/$path" + } + implicit class StringUtilExtensions(val str: String) extends AnyVal { def isNullOrEmpty = str == null || str.isEmpty - def lastComponent(sep: Char): String = { - str.substring(str.lastIndexOf(sep) + 1) - } - - def prefixBefore(substr: String): String = { - val pos = str.indexOf(substr) - val res = if (pos == -1) str else str.substring(0, pos) - res - } - - def replaceSuffix(suffix: String, newSuffix: String) = { - if (str.isNullOrEmpty || suffix.isNullOrEmpty) str - else { - val stripped = str.stripSuffix(suffix) - if (stripped.length == str.length) str - else - stripped + (if (newSuffix == null) "" else newSuffix) - } - } - def opt(show: String => String = _.toString, default: String = ""): String = if (str.nonEmpty) show(str) else default } diff --git a/common/src/main/scala/sigmastate/VersionContext.scala b/common/shared/src/main/scala/sigmastate/VersionContext.scala similarity index 100% rename from common/src/main/scala/sigmastate/VersionContext.scala rename to common/shared/src/main/scala/sigmastate/VersionContext.scala diff --git a/common/src/main/scala/sigmastate/kiama/kiama.scala b/common/shared/src/main/scala/sigmastate/kiama/kiama.scala similarity index 100% rename from common/src/main/scala/sigmastate/kiama/kiama.scala rename to common/shared/src/main/scala/sigmastate/kiama/kiama.scala diff --git a/common/src/main/scala/sigmastate/kiama/rewriting/CallbackRewriter.scala b/common/shared/src/main/scala/sigmastate/kiama/rewriting/CallbackRewriter.scala similarity index 100% rename from common/src/main/scala/sigmastate/kiama/rewriting/CallbackRewriter.scala rename to common/shared/src/main/scala/sigmastate/kiama/rewriting/CallbackRewriter.scala diff --git a/common/src/main/scala/sigmastate/kiama/rewriting/PlusStrategy.scala b/common/shared/src/main/scala/sigmastate/kiama/rewriting/PlusStrategy.scala similarity index 100% rename from common/src/main/scala/sigmastate/kiama/rewriting/PlusStrategy.scala rename to common/shared/src/main/scala/sigmastate/kiama/rewriting/PlusStrategy.scala diff --git a/common/src/main/scala/sigmastate/kiama/rewriting/Rewriter.scala b/common/shared/src/main/scala/sigmastate/kiama/rewriting/Rewriter.scala similarity index 97% rename from common/src/main/scala/sigmastate/kiama/rewriting/Rewriter.scala rename to common/shared/src/main/scala/sigmastate/kiama/rewriting/Rewriter.scala index 922a1368d6..c20347a3c9 100644 --- a/common/src/main/scala/sigmastate/kiama/rewriting/Rewriter.scala +++ b/common/shared/src/main/scala/sigmastate/kiama/rewriting/Rewriter.scala @@ -11,7 +11,7 @@ package sigmastate.kiama package rewriting -import scalan.reflection.{RClass, RConstructor} +import scalan.reflection.{Platform, RClass, RConstructor} import scala.collection.concurrent.TrieMap @@ -243,8 +243,7 @@ trait Rewriter { // are trying to duplicate one of these then we want to return the same // singleton so we use an identity duper. clazz.getField("MODULE$") - (t : Any, children : Array[AnyRef]) => - t + (t : Any, children : Array[AnyRef]) => t } catch { // Otherwise, this is a normal class, so we try to make a // duper that uses the first constructor. @@ -296,19 +295,11 @@ trait Rewriter { } /** All memoized duppers. */ - private val dupers = TrieMap.empty[RClass[_], Duper] + private val dupers = new Platform.Cache[RClass[_], Duper] - /** Obtains a duper for the given class lazily. and memoize it in the `cache` map. - * This is the simplest solution, but not the most efficient for concurrent access. - */ - def getDuper(clazz: RClass[_]): Duper = synchronized { // TODO optimize: avoid global sync (if this really is a bottleneck) - val duper = dupers.get(clazz) match { - case Some(d) => d - case None => - val d = MakeDuper(clazz) - dupers.put(clazz, d) - d - } + /** Obtains a duper for the given class and memoizes it in the `dupers` cache. */ + def getDuper(clazz: RClass[_]): Duper = { + val duper = dupers.getOrElseUpdate(clazz, MakeDuper(clazz)) duper } diff --git a/common/src/main/scala/sigmastate/kiama/rewriting/Strategy.scala b/common/shared/src/main/scala/sigmastate/kiama/rewriting/Strategy.scala similarity index 100% rename from common/src/main/scala/sigmastate/kiama/rewriting/Strategy.scala rename to common/shared/src/main/scala/sigmastate/kiama/rewriting/Strategy.scala diff --git a/common/src/main/scala/sigmastate/kiama/util/Comparison.scala b/common/shared/src/main/scala/sigmastate/kiama/util/Comparison.scala similarity index 100% rename from common/src/main/scala/sigmastate/kiama/util/Comparison.scala rename to common/shared/src/main/scala/sigmastate/kiama/util/Comparison.scala diff --git a/common/src/main/scala/sigmastate/util.scala b/common/shared/src/main/scala/sigmastate/util.scala similarity index 100% rename from common/src/main/scala/sigmastate/util.scala rename to common/shared/src/main/scala/sigmastate/util.scala diff --git a/common/src/test/resources/root/A.txt b/common/shared/src/test/resources/root/A.txt similarity index 100% rename from common/src/test/resources/root/A.txt rename to common/shared/src/test/resources/root/A.txt diff --git a/common/src/test/resources/root/B.txt b/common/shared/src/test/resources/root/B.txt similarity index 100% rename from common/src/test/resources/root/B.txt rename to common/shared/src/test/resources/root/B.txt diff --git a/common/src/test/resources/root/subdir/C.txt b/common/shared/src/test/resources/root/subdir/C.txt similarity index 100% rename from common/src/test/resources/root/subdir/C.txt rename to common/shared/src/test/resources/root/subdir/C.txt diff --git a/common/src/test/resources/root/subdir/subsubdir/D.txt b/common/shared/src/test/resources/root/subdir/subsubdir/D.txt similarity index 100% rename from common/src/test/resources/root/subdir/subsubdir/D.txt rename to common/shared/src/test/resources/root/subdir/subsubdir/D.txt diff --git a/common/src/test/scala/scalan/BaseTests.scala b/common/shared/src/test/scala/scalan/BaseTests.scala similarity index 100% rename from common/src/test/scala/scalan/BaseTests.scala rename to common/shared/src/test/scala/scalan/BaseTests.scala diff --git a/common/src/test/scala/scalan/TestUtils.scala b/common/shared/src/test/scala/scalan/TestUtils.scala similarity index 80% rename from common/src/test/scala/scalan/TestUtils.scala rename to common/shared/src/test/scala/scalan/TestUtils.scala index a583f590af..a7471b3a62 100644 --- a/common/src/test/scala/scalan/TestUtils.scala +++ b/common/shared/src/test/scala/scalan/TestUtils.scala @@ -1,10 +1,10 @@ package scalan -import scalan.util.FileUtil +import scalan.util.StringUtil import org.scalactic.TripleEquals import org.scalatest.matchers.should.Matchers -import org.scalatest.{Inside, TestSuite} -import scalan.util.StringUtil.StringUtilExtensions +import org.scalatest.{TestSuite, Inside} +import scalan.util.StringUtil.{cleanFileName, StringUtilExtensions} /** * Created by slesarenko on 11/10/2017. @@ -17,7 +17,7 @@ trait TestUtils extends TestSuite with Matchers with Inside with TripleEquals { lazy val prefix = { val suiteName = testSuffixes.foldLeft(getClass.getName)(_.stripSuffix(_)) val pathComponents = suiteName.split('.') - FileUtil.file(testOutDir, pathComponents: _*) + StringUtil.fileName(testOutDir, pathComponents: _*) } /* if runs in continuous integration environment */ @@ -41,5 +41,5 @@ trait TestUtils extends TestSuite with Matchers with Inside with TripleEquals { else testName } - protected def currentTestNameAsFileName: String = FileUtil.cleanFileName(currentTestName) + protected def currentTestNameAsFileName: String = cleanFileName(currentTestName) } diff --git a/common/src/test/scala/scalan/util/BenchmarkUtil.scala b/common/shared/src/test/scala/scalan/util/BenchmarkUtil.scala similarity index 100% rename from common/src/test/scala/scalan/util/BenchmarkUtil.scala rename to common/shared/src/test/scala/scalan/util/BenchmarkUtil.scala diff --git a/common/src/test/scala/scalan/util/CollectionUtilTests.scala b/common/shared/src/test/scala/scalan/util/CollectionUtilTests.scala similarity index 61% rename from common/src/test/scala/scalan/util/CollectionUtilTests.scala rename to common/shared/src/test/scala/scalan/util/CollectionUtilTests.scala index 1ceb8639f4..457bef4436 100644 --- a/common/src/test/scala/scalan/util/CollectionUtilTests.scala +++ b/common/shared/src/test/scala/scalan/util/CollectionUtilTests.scala @@ -27,7 +27,7 @@ class CollectionUtilTests extends BaseTests { val pairs = xs.zip(ys) // this reproduces the problem which takes place in v3.x, v4.x (ErgoTree v0, v1) - an[ClassCastException] should be thrownBy(concatArrays(pairs, pairs)) + an[Throwable] should be thrownBy(concatArrays(pairs, pairs)) // and this is the fix in v5.0 concatArrays_v5(pairs, pairs) shouldBe Array((1, 4), (2, 5), (3, 6), (1, 4), (2, 5), (3, 6)) @@ -36,8 +36,6 @@ class CollectionUtilTests extends BaseTests { concatArrays_v5(xOpts, xOpts) shouldBe Array(Some(1), Some(2), Some(3), Some(1), Some(2), Some(3)) } - def join(l: Map[Int,Int], r: Map[Int,Int]) = - outerJoin(l, r)((_,l) => l, (_,r) => r, (k,l,r) => l + r) def joinSeqs(l: Seq[Int], r: Seq[Int]) = outerJoinSeqs(l, r)(l => l, r => r)((_,l) => l, (_,r) => r, (k,l,r) => l + r).map(_._2) def joinPairs(l: Seq[(String,Int)], r: Seq[(String,Int)]) = @@ -67,16 +65,6 @@ class CollectionUtilTests extends BaseTests { } } - test("outerJoin maps") { - val left = Map(1 -> 1, 2 -> 2, 3 -> 3) - val right = Map(2 -> 2, 3 -> 3, 4 -> 4) - - assertResult(Map(1 -> 1, 2 -> 4, 3 -> 6, 4 -> 4))(join(left,right)) - assertResult(Map(1 -> 1, 2 -> 2, 3 -> 3))(join(left,Map())) - assertResult(Map(2 -> 2, 3 -> 3, 4 -> 4))(join(Map(), right)) - assertResult(Map(2 -> 4, 3 -> 6, 4 -> 8))(join(right, right)) - } - test("outerJoinSeqs") { val left = Seq(1, 2, 3) val right = Seq(2, 3, 4) @@ -95,34 +83,6 @@ class CollectionUtilTests extends BaseTests { assertResult(Seq("b" -> 4, "c" -> 6, "d" -> 8))(joinPairs(outer, outer)) } - test("filterMap") { - val xs = List(1, 2, 3) - xs.filterMap(x => if (x <= 2) Some(s"x = $x") else None) should be(List("x = 1", "x = 2")) - } - - test("mapUnzip") { - val xs = Seq(1, 2, 3) - - { - val (ints, strings, plus1s) = xs.mapUnzip((x: Int) => (x, x.toString, x + 1)) - ints shouldBe Seq(1, 2, 3) - strings shouldBe Seq("1", "2", "3") - plus1s shouldBe Seq(2, 3, 4) - } - - { - val (ints, strings) = xs.mapUnzip((x: Int) => (x, x.toString)) - ints shouldBe Seq(1, 2, 3) - strings shouldBe Seq("1", "2", "3") - } - } - - test("mapFirst") { - val xs = List(1, 2, 3) - xs.findMap(x => if (x > 2) Some(s"x = $x") else None) should be(Some("x = 3")) - xs.findMap(x => if (x > 3) Some(x) else None) should be(None) - } - val items: Iterable[(Int, String)] = Array((1, "a"), (2, "b"), (1, "c")) test("distinctBy") { @@ -130,40 +90,6 @@ class CollectionUtilTests extends BaseTests { assertResult(Array((1, "a"), (2, "b")))(res) } - test("mapReduce") { - val res = items.mapReduce(p => (p._1, p._2))((v1, v2) => v1 + v2) - assertResult(List((1, "ac"), (2, "b")))(res) - } - - test("mergeWith") { - type V = (Int, String) - def key(p: V) = p._1 - def merge(v1: V, v2: V) = (v1._1, v1._2 + v2._2) - - { - val res = List().mergeWith(List(), key, merge) - assertResult(List())(res) - } - { - val res = List((1, "a"), (2, "b"), (1, "c")).mergeWith(List(), key, merge) - assertResult(List((1, "ac"), (2, "b")))(res) - } - { - val res = List().mergeWith(List((1, "a"), (2, "b"), (1, "c")), key, merge) - assertResult(List((1, "ac"), (2, "b")))(res) - } - { - val ys = List((2, "c"), (3, "d")) - val res = List((1, "a"), (2, "b"), (1, "c")).mergeWith(ys, key, merge) - assertResult(List((1, "ac"), (2, "bc"), (3, "d")))(res) - } - } - - test("zipWithExpandedBy") { - assertResult(Array((2, 0), (2, 1)))(2.zipWithExpandedBy(x => List.range(0,x))) - assertResult(Array((3, 0), (3, 1), (3, 2)))(3.zipWithExpandedBy(x => List.range(0,x))) - } - def treeStep(tree: Array[List[Int]]): Int => List[Int] = i => tree(i) test("traverseDepthFirst") { @@ -198,31 +124,16 @@ class CollectionUtilTests extends BaseTests { } } - test("mapConserve") { - class A(val x: Int) - val x = new A(10) - val opt = Option(x) - opt.mapConserve(a => a) shouldBe theSameInstanceAs(opt) - opt.mapConserve(a => new A(a.x)) should not be theSameInstanceAs(opt) - } - - test("transformConserve") { - class A(val x: Int) - val x = new A(10) - x.transformConserve(a => a) shouldBe theSameInstanceAs(x) - x.transformConserve(a => new A(a.x)) should not be theSameInstanceAs(x) - } - test("sameElements2") { - Seq(1, 2).sameElements2(List(1, 2)) shouldBe true - new mutable.WrappedArray.ofInt(Array(1, 2)).sameElements2(Vector(1, 2)) shouldBe true - Seq(new mutable.WrappedArray.ofInt(Array(1, 2)), 3).sameElements2(Array(Vector(1, 2), 3)) shouldBe true - Seq(Array(1, 2), 3).sameElements2(Array(Vector(1, 2), 3)) shouldBe true - Seq(Array(1, 2), Option(3)).sameElements2(Array(Vector(1, 2), List(3))) shouldBe false - - Seq(1, 2).sameElements2(List(1, 2, 3)) shouldBe false - new mutable.WrappedArray.ofInt(Array(1, 2, 3)).sameElements2(Vector(1, 2)) shouldBe false - Seq(new mutable.WrappedArray.ofInt(Array(1, 2, 3)), 3).sameElements2(Array(Vector(1, 2), 3)) shouldBe false + Seq(1, 2).sameElementsNested(List(1, 2)) shouldBe true + new mutable.WrappedArray.ofInt(Array(1, 2)).sameElementsNested(Vector(1, 2)) shouldBe true + Seq(new mutable.WrappedArray.ofInt(Array(1, 2)), 3).sameElementsNested(Array(Vector(1, 2), 3)) shouldBe true + Seq(Array(1, 2), 3).sameElementsNested(Array(Vector(1, 2), 3)) shouldBe true + Seq(Array(1, 2), Option(3)).sameElementsNested(Array(Vector(1, 2), List(3))) shouldBe false + + Seq(1, 2).sameElementsNested(List(1, 2, 3)) shouldBe false + new mutable.WrappedArray.ofInt(Array(1, 2, 3)).sameElementsNested(Vector(1, 2)) shouldBe false + Seq(new mutable.WrappedArray.ofInt(Array(1, 2, 3)), 3).sameElementsNested(Array(Vector(1, 2), 3)) shouldBe false } diff --git a/common/src/test/scala/scalan/util/GraphUtilTests.scala b/common/shared/src/test/scala/scalan/util/GraphUtilTests.scala similarity index 100% rename from common/src/test/scala/scalan/util/GraphUtilTests.scala rename to common/shared/src/test/scala/scalan/util/GraphUtilTests.scala diff --git a/common/src/test/scala/scalan/util/PrintExtensions.scala b/common/shared/src/test/scala/scalan/util/PrintExtensions.scala similarity index 100% rename from common/src/test/scala/scalan/util/PrintExtensions.scala rename to common/shared/src/test/scala/scalan/util/PrintExtensions.scala diff --git a/common/shared/src/test/scala/scalan/util/StringUtilTests.scala b/common/shared/src/test/scala/scalan/util/StringUtilTests.scala new file mode 100644 index 0000000000..4a2fc3b14f --- /dev/null +++ b/common/shared/src/test/scala/scalan/util/StringUtilTests.scala @@ -0,0 +1,13 @@ +package scalan.util + +import scalan.BaseNestedTests + +class StringUtilTests extends BaseNestedTests { + import StringUtil._ + + describe("StringUtil methods") { + it("fileName") { + fileName("dir", "a", "b") shouldBe "dir/a/b" + } + } +} diff --git a/common/src/test/scala/scalan/util/StronglyConnectedComponentsTests.scala b/common/shared/src/test/scala/scalan/util/StronglyConnectedComponentsTests.scala similarity index 100% rename from common/src/test/scala/scalan/util/StronglyConnectedComponentsTests.scala rename to common/shared/src/test/scala/scalan/util/StronglyConnectedComponentsTests.scala diff --git a/common/src/test/scala/sigmastate/VersionTesting.scala b/common/shared/src/test/scala/sigmastate/VersionTesting.scala similarity index 100% rename from common/src/test/scala/sigmastate/VersionTesting.scala rename to common/shared/src/test/scala/sigmastate/VersionTesting.scala diff --git a/common/src/test/scala/sigmastate/VersionTestingProperty.scala b/common/shared/src/test/scala/sigmastate/VersionTestingProperty.scala similarity index 100% rename from common/src/test/scala/sigmastate/VersionTestingProperty.scala rename to common/shared/src/test/scala/sigmastate/VersionTestingProperty.scala diff --git a/common/src/main/scala/scalan/util/CollectionUtil.scala b/common/src/main/scala/scalan/util/CollectionUtil.scala deleted file mode 100644 index 414d3b9cea..0000000000 --- a/common/src/main/scala/scalan/util/CollectionUtil.scala +++ /dev/null @@ -1,361 +0,0 @@ -package scalan.util - -import scala.language.higherKinds -import java.util -import java.util.Objects - -import scala.collection.{Seq, mutable, GenIterable} -import scala.collection.mutable.{HashMap, ArrayBuffer} -import scala.reflect.ClassTag -import scala.collection.compat._ - -object CollectionUtil { - - /** @deprecated shouldn't be used other than for backwards compatibility with v3.x, v4.x. */ - def concatArrays[T](xs: Array[T], ys: Array[T]): Array[T] = { - val len = xs.length + ys.length - val result = (xs match { - case arr: Array[AnyRef] => new Array[AnyRef](len) // creates an array with invalid type descriptor (i.e. when T == Tuple2) - case arr: Array[Byte] => new Array[Byte](len) - case arr: Array[Short] => new Array[Short](len) - case arr: Array[Int] => new Array[Int](len) - case arr: Array[Long] => new Array[Long](len) - case arr: Array[Char] => new Array[Char](len) - case arr: Array[Float] => new Array[Float](len) - case arr: Array[Double] => new Array[Double](len) - case arr: Array[Boolean] => new Array[Boolean](len) - }).asInstanceOf[Array[T]] - Array.copy(xs, 0, result, 0, xs.length) - Array.copy(ys, 0, result, xs.length, ys.length) - result - } - - /** Concatenates two arrays into a new resulting array. - * All items of both arrays are copied to the result using System.arraycopy. - * This method takes ClassTag to create proper resulting array. - * Can be used in v5.0 and above. - */ - def concatArrays_v5[T:ClassTag](arr1: Array[T], arr2: Array[T]): Array[T] = { - val l1 = arr1.length - val l2 = arr2.length - val length: Int = l1 + l2 - val result: Array[T] = new Array[T](length) - System.arraycopy(arr1, 0, result, 0, l1) - System.arraycopy(arr2, 0, result, l1, l2) - result - } - - def deepHashCode[T](arr: Array[T]): Int = arr match { - case arr: Array[AnyRef] => util.Arrays.deepHashCode(arr) - case arr: Array[Byte] => util.Arrays.hashCode(arr) - case arr: Array[Short] => util.Arrays.hashCode(arr) - case arr: Array[Int] => util.Arrays.hashCode(arr) - case arr: Array[Long] => util.Arrays.hashCode(arr) - case arr: Array[Char] => util.Arrays.hashCode(arr) - case arr: Array[Float] => util.Arrays.hashCode(arr) - case arr: Array[Double] => util.Arrays.hashCode(arr) - case arr: Array[Boolean] => util.Arrays.hashCode(arr) - } - - /** Group the given sequence of pairs by first values as keys. - * @param kvs sequence of values which is traversed once - * @return a multimap with ArrayBuffer of values for each key. - */ - def createMultiMap[K,V](kvs: GenIterable[(K,V)]): Map[K, ArrayBuffer[V]] = { - val res = HashMap.empty[K, ArrayBuffer[V]] - kvs.foreach { case (k, v) => - if (res.contains(k)) - res(k) += v - else - res += k -> ArrayBuffer(v) - () - } - res.toMap - } - - /** Perform relational inner join of two sequences using the given key projections. */ - def joinSeqs[O, I, K](outer: Iterable[O], inner: Iterable[I])(outKey: O=>K, inKey: I=>K): Iterable[(O,I)] = { - val kvs = createMultiMap(inner.map(i => (inKey(i), i))) - val res = outer.flatMap(o => { - val ko = outKey(o) - kvs.get(ko) match { - case Some(inners) => - inners.map(i => (o,i)) - case None => - Nil - } - }) - res - } - - def outerJoinSeqs[O, I, K, R] - (outer: Seq[O], inner: Seq[I]) - (outKey: O=>K, inKey: I=>K) - (projO: (K,O) => R, projI: (K,I) => R, proj:(K,O,I) => R): Seq[(K,R)] = { - val res = ArrayBuffer.empty[(K,R)] - val kis = inner.map(i => (inKey(i), i)) - val kvs = createMultiMap(kis) - val outerKeys = mutable.Set.empty[K] - for (o <- outer) { - val ko = outKey(o) - outerKeys += ko - if (!kvs.contains(ko)) - res += ((ko, projO(ko, o))) - else - for (i <- kvs(ko)) - res += ((ko, proj(ko, o, i))) - } - for ((k,i) <- kis if !outerKeys.contains(k)) - res += ((k, projI(k, i))) - res - } - - def outerJoin[K, L, R, O] - (left: Map[K, L], right: Map[K, R]) - (l: (K,L) => O, r: (K,R) => O, inner: (K,L,R) => O): Map[K,O] = { - val res = HashMap.empty[K, O] - val lks = left.keySet - val rks = right.keySet - val leftOnly = lks diff rks - val rightOnly = rks diff lks - val both = lks intersect rks - for (lk <- leftOnly) - res += lk -> l(lk, left(lk)) - for (rk <- rightOnly) - res += rk -> r(rk, right(rk)) - for (k <- both) - res += k -> inner(k, left(k), right(k)) - res.toMap - } - - def join[K,V,R](ks: List[K], kv: Map[K,V])(f: (K,V) => R): List[R] = { - val vs = ks.map(k => kv.get(k) match { - case Some(v) => v - case None => sys.error(s"Cannot find value for key $k") - }) - (ks zip vs).map(f.tupled) - } - - implicit class AnyOps[A](val x: A) extends AnyVal { - def zipWithExpandedBy[B](f: A => List[B]): List[(A,B)] = { - val ys = f(x) - List.fill(ys.length)(x) zip ys - } - def traverseDepthFirst(f: A => List[A]): List[A] = { - var all: List[A] = Nil - var stack = List(x) - while (stack.nonEmpty) { - val h = stack.head - stack = stack.tail - - var next = f(h).reverse - while (next.nonEmpty) { - stack = next.head :: stack - next = next.tail - } - all = h :: all - } - all.reverse - } - } - - implicit class AnyRefOps[A <: AnyRef](val x: A) extends AnyVal { - def transformConserve(f: A => A) = { - val newX = f(x) - if (newX eq x) x else newX - } - } - - implicit class OptionOps[A](val source: Option[A]) extends AnyVal { - def mergeWith[K](other: Option[A], merge: (A,A) => A): Option[A] = (source, other) match { - case (_, None) => source - case (None, Some(_)) => other - case (Some(x), Some(y)) => Some(merge(x, y)) - } - } - implicit class OptionOfAnyRefOps[A <: AnyRef](val source: Option[A]) extends AnyVal { - def mapConserve[B <: AnyRef](f: A => B): Option[B] = source match { - case Some(a) => - val b = f(a) - if (b.eq(a)) source.asInstanceOf[Option[B]] - else Some(b) - case None => None - } - } - - implicit class HashMapOps[K,V](val source: java.util.HashMap[K,V]) extends AnyVal { - def toImmutableMap: Map[K,V] = { - var res = Map[K,V]() - // don't beautify: at least this code should compile under java 1.7 - val entries = source.entrySet().iterator() - while (entries.hasNext) { - val e = entries.next() - res = res + (e.getKey -> e.getValue) - } - res - } - } - - implicit class TraversableOps[A, Source[X] <: GenIterable[X]](val xs: Source[A]) extends AnyVal { - - /** Returns a copy of this collection where elements at `items(i)._1` are replaced - * with `items(i)._2` for each i. - */ - def updateMany(items: Seq[(Int, A)])(implicit tA: ClassTag[A]): Seq[A] = { - val res = xs.toArray - val nItems = items.length - var i = 0 - while (i < nItems) { - val item = items(i) - res(item._1) = item._2 - i += 1 - } - res - } - - def filterCast[B:ClassTag](implicit cbf: BuildFrom[Source[A], B, Source[B]]): Source[B] = { - val b = cbf.newBuilder(xs) - for (x <- xs) { - x match { - case y: B => - b += y - case _ => - } - } - b.result() - } - - def cast[B:ClassTag](implicit cbf: BuildFrom[Source[A], B, Source[B]]): Source[B] = { - for (x <- xs) { - assert(x match { case _: B => true case _ => false}, s"Value $x doesn't conform to type ${reflect.classTag[B]}") - } - xs.asInstanceOf[Source[B]] - } - - /** Applies 'f' to elements of 'xs' until 'f' returns Some(b), - * which is immediately returned as result of this method. - * If not such element found, returns None as result. */ - def findMap[B](f: A => Option[B]): Option[B] = { - for (x <- xs) { - val y = f(x) - if (y.isDefined) return y - } - return None - } - - def filterMap[B](f: A => Option[B])(implicit cbf: BuildFrom[Source[A], B, Source[B]]): Source[B] = { - val b = cbf.newBuilder(xs) - for (x <- xs) { - f(x) match { - case Some(y) => - b += y - case None => - } - } - b.result() - } - - def mapUnzip[B1, B2](f: A => (B1,B2)) - (implicit cbf1: BuildFrom[Source[A], B1, Source[B1]], - cbf2: BuildFrom[Source[A], B2, Source[B2]]): (Source[B1], Source[B2]) = - { - val b1 = cbf1.newBuilder(xs) - val b2 = cbf2.newBuilder(xs) - for (x <- xs) { - val (y1, y2) = f(x) - b1 += y1; b2 += y2 - } - (b1.result(), b2.result()) - } - - def mapUnzip[B1, B2, B3](f: A => (B1,B2,B3)) - (implicit cbf1: BuildFrom[Source[A], B1, Source[B1]], - cbf2: BuildFrom[Source[A], B2, Source[B2]], - cbf3: BuildFrom[Source[A], B3, Source[B3]]): (Source[B1], Source[B2], Source[B3]) = - { - val b1 = cbf1.newBuilder(xs) - val b2 = cbf2.newBuilder(xs) - val b3 = cbf3.newBuilder(xs) - for (x <- xs) { - val (y1, y2, y3) = f(x) - b1 += y1; b2 += y2; b3 += y3 - } - (b1.result(), b2.result(), b3.result()) - } - - def distinctBy[K](key: A => K)(implicit cbf: BuildFrom[Source[A], A, Source[A]]): Source[A] = { - val keys = mutable.Set[K]() - val b = cbf.newBuilder(xs) - for (x <- xs) { - val k = key(x) - if (!keys.contains(k)) { - b += x - keys += k - } - } - b.result() - } - - /** Apply m for each element of this collection, group by key and reduce each group using r. - * @returns one item for each group in a new collection of (K,V) pairs. */ - def mapReduce[K, V](map: A => (K, V))(reduce: (V, V) => V) - (implicit cbf: BuildFrom[Source[A], (K,V), Source[(K,V)]]): Source[(K, V)] = { - val result = scala.collection.mutable.LinkedHashMap.empty[K, V] - xs.foldLeft(result)((r, x) => { - val (key, value) = map(x) - result.update(key, if (result.contains(key)) reduce(result(key), value) else value) - result - }) - val b = cbf.newBuilder(xs) - for (kv <- result) b += kv - b.result() - } - - def mergeWith[K] - (ys: Source[A], key: A => K, merge: (A,A) => A) - (implicit cbf: BuildFrom[Source[A], A, Source[A]]): Source[A] = { - val b = cbf.newBuilder(xs) - for (v <- (xs ++ ys).mapReduce(x => (key(x), x))(merge)) - b += v._2 - b.result() - } - - private def flattenIter(i: Iterator[_]): Iterator[_] = i.flatMap(x => x match { - case nested: GenIterable[_] => nested - case arr: Array[_] => arr.iterator - case _ => Iterator.single(x) - }) - - def sameElements2[B >: A](that: GenIterable[B]): Boolean = { - val i1: Iterator[Any] = flattenIter(xs.iterator) - val i2: Iterator[Any] = flattenIter(that.iterator) - i1.sameElements(i2) - } - - def deepHashCode: Int = { - var result: Int = 1 - for (x <- xs) { - val elementHash = x match { - case arr: Array[_] => CollectionUtil.deepHashCode(arr) - case _ => Objects.hashCode(x) - } - result = 31 * result + elementHash; - } - result - } - } - - private def sameLengthErrorMsg[A,B](xs: Seq[A], ys: Seq[B]) = { - s"Collections should have same length but was ${xs.length} and ${ys.length}:\n xs=$xs;\n ys=$ys" - } - - def assertSameLength[A,B](xs: Seq[A], ys: Seq[B]) = { - assert(xs.length == ys.length, sameLengthErrorMsg(xs, ys)) - } - - def requireSameLength[A,B](xs: Seq[A], ys: Seq[B]) = { - require(xs.length == ys.length, sameLengthErrorMsg(xs, ys)) - } -} - - diff --git a/common/src/test/scala/scalan/util/StringUtilTests.scala b/common/src/test/scala/scalan/util/StringUtilTests.scala deleted file mode 100644 index 9e465b7953..0000000000 --- a/common/src/test/scala/scalan/util/StringUtilTests.scala +++ /dev/null @@ -1,31 +0,0 @@ -package scalan.util - -import scalan.BaseNestedTests - -class StringUtilTests extends BaseNestedTests { - import StringUtil._ - - describe("StringExtension methods") { - it("lastComponent") { - "a/b/c".lastComponent('/') shouldBe("c") - "a/b/".lastComponent('/') shouldBe("") - "a".lastComponent('/') shouldBe("a") - "".lastComponent('/') shouldBe("") - } - it("prefixBefore") { - "a/b/c".prefixBefore("/b") shouldBe("a") - "a/b/c".prefixBefore("/c") shouldBe("a/b") - "a/b/c".prefixBefore("a/b/c") shouldBe("") - "a/b/c".prefixBefore("") shouldBe("") - } - it("replaceSuffix") { - "a.old".replaceSuffix(null, ".new") shouldBe("a.old") - (null: String).replaceSuffix(".old", ".new") shouldBe(null) - "a.old".replaceSuffix("", ".new") shouldBe("a.old") - "a.".replaceSuffix(".old", ".new") shouldBe("a.") - "a.old".replaceSuffix(".old", ".new") shouldBe("a.new") - "a.old".replaceSuffix(".old", "") shouldBe("a") - "a.old".replaceSuffix(".old", null) shouldBe("a") - } - } -} diff --git a/core-lib/src/main/scala/special/CoreLibReflection.scala b/core-lib/shared/src/main/scala/special/CoreLibReflection.scala similarity index 100% rename from core-lib/src/main/scala/special/CoreLibReflection.scala rename to core-lib/shared/src/main/scala/special/CoreLibReflection.scala diff --git a/core-lib/src/main/scala/special/Types.scala b/core-lib/shared/src/main/scala/special/Types.scala similarity index 100% rename from core-lib/src/main/scala/special/Types.scala rename to core-lib/shared/src/main/scala/special/Types.scala diff --git a/core-lib/src/main/scala/special/collection/Colls.scala b/core-lib/shared/src/main/scala/special/collection/Colls.scala similarity index 100% rename from core-lib/src/main/scala/special/collection/Colls.scala rename to core-lib/shared/src/main/scala/special/collection/Colls.scala diff --git a/core-lib/src/main/scala/special/collection/CollsOverArrays.scala b/core-lib/shared/src/main/scala/special/collection/CollsOverArrays.scala similarity index 100% rename from core-lib/src/main/scala/special/collection/CollsOverArrays.scala rename to core-lib/shared/src/main/scala/special/collection/CollsOverArrays.scala diff --git a/core-lib/shared/src/main/scala/special/collection/Extensions.scala b/core-lib/shared/src/main/scala/special/collection/Extensions.scala new file mode 100644 index 0000000000..4256d91336 --- /dev/null +++ b/core-lib/shared/src/main/scala/special/collection/Extensions.scala @@ -0,0 +1,46 @@ +package special.collection + +import debox.cfor +import scorex.util.encode.Base16 +import scorex.util.{ModifierId, bytesToId} + +object Extensions { + /** Extension methods for `Coll[T]`. */ + implicit class CollOps[T](val source: Coll[T]) extends AnyVal { + /** Applies a function `f` to each element of the `source` collection. */ + def foreach(f: T => Unit) = { + val limit = source.length + cfor(0)(_ < limit, _ + 1) { i => + f(source(i)) + } + } + } + + /** Extension methods for `Coll[Byte]` not available for generic `Coll[T]`. */ + implicit class CollBytesOps(val source: Coll[Byte]) extends AnyVal { + /** Returns a representation of this collection of bytes as a modifier id. */ + def toModifierId: ModifierId = { + val bytes = source.toArray + bytesToId(bytes) + } + + /** Encodes this collection of bytes as a hex string. */ + def toHex: String = Base16.encode(source.toArray) + } + + /** Extension methods for `Coll[(A,B)]`. + * Has priority over the extensions in [[CollOps]]. + */ + implicit class PairCollOps[A,B](val source: Coll[(A,B)]) extends AnyVal { + /** Applies a function `f` to each pair of elements in the `source` collection. */ + def foreach(f: (A, B) => Unit) = { + val b = source.builder + val (as, bs) = b.unzip(source) + val limit = source.length + cfor(0)(_ < limit, _ + 1) { i => + f(as(i), bs(i)) + } + } + } + +} diff --git a/core-lib/src/main/scala/special/collection/Helpers.scala b/core-lib/shared/src/main/scala/special/collection/Helpers.scala similarity index 100% rename from core-lib/src/main/scala/special/collection/Helpers.scala rename to core-lib/shared/src/main/scala/special/collection/Helpers.scala diff --git a/core-lib/src/main/scala/special/collection/package.scala b/core-lib/shared/src/main/scala/special/collection/package.scala similarity index 100% rename from core-lib/src/main/scala/special/collection/package.scala rename to core-lib/shared/src/main/scala/special/collection/package.scala diff --git a/core-lib/src/main/scala/special/sigma/SigmaDsl.scala b/core-lib/shared/src/main/scala/special/sigma/SigmaDsl.scala similarity index 99% rename from core-lib/src/main/scala/special/sigma/SigmaDsl.scala rename to core-lib/shared/src/main/scala/special/sigma/SigmaDsl.scala index a6864a3302..186aa59bd7 100644 --- a/core-lib/src/main/scala/special/sigma/SigmaDsl.scala +++ b/core-lib/shared/src/main/scala/special/sigma/SigmaDsl.scala @@ -716,7 +716,11 @@ trait Context { def currentErgoTreeVersion: Byte } +/** Shortcut declarations for methods of global object (aka [[SigmaDslBuilder]]). + * Every methods delegates to the corresponding method of `builder`, look there for details. + */ trait SigmaContract { + /** Returns Global instance (aka Builder) which implements all global operations. */ def builder: SigmaDslBuilder def Collection[T](items: T*)(implicit cT: RType[T]): Coll[T] = this.builder.Colls.fromItems[T](items:_*) diff --git a/core-lib/src/main/scala/special/sigma/package.scala b/core-lib/shared/src/main/scala/special/sigma/package.scala similarity index 100% rename from core-lib/src/main/scala/special/sigma/package.scala rename to core-lib/shared/src/main/scala/special/sigma/package.scala diff --git a/core-lib/src/main/scala/special/wrappers/WrappersSpec.scala b/core-lib/shared/src/main/scala/special/wrappers/WrappersSpec.scala similarity index 100% rename from core-lib/src/main/scala/special/wrappers/WrappersSpec.scala rename to core-lib/shared/src/main/scala/special/wrappers/WrappersSpec.scala diff --git a/core-lib/src/test/scala/special/TypesTests.scala b/core-lib/shared/src/test/scala/special/TypesTests.scala similarity index 100% rename from core-lib/src/test/scala/special/TypesTests.scala rename to core-lib/shared/src/test/scala/special/TypesTests.scala diff --git a/graph-ir/src/test/scala/special/collections/CollGens.scala b/core-lib/shared/src/test/scala/special/collections/CollGens.scala similarity index 70% rename from graph-ir/src/test/scala/special/collections/CollGens.scala rename to core-lib/shared/src/test/scala/special/collections/CollGens.scala index c12bf5bac8..5c962cf2b9 100644 --- a/graph-ir/src/test/scala/special/collections/CollGens.scala +++ b/core-lib/shared/src/test/scala/special/collections/CollGens.scala @@ -3,22 +3,21 @@ package special.collections import org.scalacheck.util.Buildable import org.scalacheck.{Arbitrary, Gen} import scalan._ +import scalan.RType import special.collection.{Coll, CollBuilder, CollOverArrayBuilder, PairColl} import spire.scalacompat.BuilderCompat - import scala.collection.mutable import scala.collection.mutable.ArrayBuffer -import scala.language.{existentials, implicitConversions} trait CollGens { testSuite => import Gen._ + val builder: CollBuilder = new CollOverArrayBuilder val valGen = choose(-100, 100) val byteGen = choose[Byte](-100, 100) val indexGen = choose(0, 100) val replacedGen = choose(0, 100) val lenGen = choose(0, 100) - val shortGen = choose[Short](Short.MinValue, Short.MaxValue) val intGen = choose[Int](Int.MinValue, Int.MaxValue) val longGen = choose[Long](Long.MinValue, Long.MaxValue) @@ -27,7 +26,8 @@ trait CollGens { testSuite => val doubleGen = choose[Double](Double.MinValue, Double.MaxValue) def getArrayGen[T](valGen: Gen[T], count: Int = 100) - (implicit evb: Buildable[T,Array[T]], evt: Array[T] => Traversable[T]): Gen[Array[T]] = { + (implicit evb: Buildable[T, Array[T]], + evt: Array[T] => Traversable[T]): Gen[Array[T]] = { containerOfN[Array, T](count, valGen) } @@ -35,28 +35,41 @@ trait CollGens { testSuite => containerOfN[Array, T](count, valGen).map(builder.fromArray(_)) } - def getCollReplGen[T: RType](lenGen: Gen[Int], valGen: Gen[T], count: Int = 100): Gen[Coll[T]] = { - for { l <- lenGen; v <- valGen } yield builder.replicate(l, v) + def getCollReplGen[T: RType]( + lenGen: Gen[Int], + valGen: Gen[T], + count: Int = 100): Gen[Coll[T]] = { + for {l <- lenGen; v <- valGen} yield builder.replicate(l, v) } - def getCollPairGenFinal[A: RType, B: RType](collGenLeft: Gen[Coll[A]], collGenRight: Gen[Coll[B]]): Gen[PairColl[A, B]] = { - for { left <- collGenLeft; right <- collGenRight } yield builder.pairColl(left, right) + def getCollPairGenFinal[A: RType, B: RType]( + collGenLeft: Gen[Coll[A]], + collGenRight: Gen[Coll[B]]): Gen[PairColl[A, B]] = { + for {left <- collGenLeft; right <- collGenRight} yield builder.pairColl(left, right) } - def getCollPairGenRight[A: RType, B: RType, C: RType](collGenLeft: Gen[Coll[A]], collGenRight: Gen[PairColl[B, C]]): Gen[PairColl[A, (B, C)]] = { - for { left <- collGenLeft; right <- collGenRight } yield builder.pairColl(left, right) + def getCollPairGenRight[A: RType, B: RType, C: RType]( + collGenLeft: Gen[Coll[A]], + collGenRight: Gen[PairColl[B, C]]): Gen[PairColl[A, (B, C)]] = { + for {left <- collGenLeft; right <- collGenRight} yield builder.pairColl(left, right) } - def getCollPairGenLeft[A: RType, B: RType, C: RType](collGenLeft: Gen[PairColl[A, B]], collGenRight: Gen[Coll[C]]): Gen[PairColl[(A, B), C]] = { - for { left <- collGenLeft; right <- collGenRight } yield builder.pairColl(left, right) + def getCollPairGenLeft[A: RType, B: RType, C: RType]( + collGenLeft: Gen[PairColl[A, B]], + collGenRight: Gen[Coll[C]]): Gen[PairColl[(A, B), C]] = { + for {left <- collGenLeft; right <- collGenRight} yield builder.pairColl(left, right) } - def getCollPairGenBoth[A: RType, B: RType, C: RType, D: RType](collGenLeft: Gen[PairColl[A, B]], collGenRight: Gen[PairColl[C, D]]): Gen[PairColl[(A, B), (C, D)]] = { - for { left <- collGenLeft; right <- collGenRight } yield builder.pairColl(left, right) + def getCollPairGenBoth[A: RType, B: RType, C: RType, D: RType]( + collGenLeft: Gen[PairColl[A, B]], + collGenRight: Gen[PairColl[C, D]]): Gen[PairColl[(A, B), (C, D)]] = { + for {left <- collGenLeft; right <- collGenRight} yield builder.pairColl(left, right) } // TODO: there's a need in generator that produces collections with different elements and the same type scheme - def getSuperGen[T: RType](length: Int = 1, collGen: Gen[Coll[T]]): Gen[PairColl[_, _]] = { + def getSuperGen[T: RType]( + length: Int = 1, + collGen: Gen[Coll[T]]): Gen[PairColl[_, _]] = { length match { case 0 => { Gen.oneOf(getCollPairGenFinal(collGen, collGen), @@ -87,35 +100,34 @@ trait CollGens { testSuite => val bytesArrayGen: Gen[Array[Byte]] = getArrayGen[Byte](byteGen) //containerOfN[Array, Byte](100, byteGen) val arrayGen: Gen[Array[Int]] = getArrayGen[Int](valGen) //containerOfN[Array, Int](100, valGen) val indexesGen = containerOfN[Array, Int](10, indexGen).map(arr => builder.fromArray(arr.distinct.sorted)) - val collOverArrayGen = getCollOverArrayGen(valGen) //arrayGen.map(builder.fromArray(_)) - val bytesOverArrayGen = getCollOverArrayGen(byteGen) val replCollGen = getCollReplGen(lenGen, valGen) val replBytesCollGen = getCollReplGen(lenGen, byteGen) def easyFunction(arg: Int): Int = arg * 20 + 300 + def inverseEasyFunction(arg: Int): Int = (arg - 300) / 20 val collGen = Gen.oneOf(collOverArrayGen, replCollGen) val bytesGen = Gen.oneOf(bytesOverArrayGen, replBytesCollGen) - val innerGen = Gen.oneOf(collOverArrayGen, replCollGen) - val superGenInt = getSuperGen(1, Gen.oneOf(collOverArrayGen, replCollGen)) val superGenByte = getSuperGen(1, Gen.oneOf(bytesOverArrayGen, replBytesCollGen)) val superGen = Gen.oneOf(superGenInt, superGenByte) - val allGen = Gen.oneOf(superGen, collGen) - implicit val arbColl = Arbitrary(collGen) implicit val arbBytes = Arbitrary(bytesGen) def eq0(x: Int) = x == 0 + def lt0(x: Int) = x < 0 + def plus(acc: Int, x: Int): Int = acc + x - val plusF = (p: (Int,Int)) => plus(p._1, p._2) - val predF = (p: (Int,Int)) => plus(p._1, p._2) > 0 + + val plusF = (p: (Int, Int)) => plus(p._1, p._2) + val predF = (p: (Int, Int)) => plus(p._1, p._2) > 0 + def inc(x: Int) = x + 1 def complexFunction(arg: Int): Int = { @@ -128,18 +140,20 @@ trait CollGens { testSuite => res } - implicit def buildableColl[T:RType] = new Buildable[T,Coll[T]] { - def builder = new BuilderCompat[T,Coll[T]] { + implicit def buildableColl[T: RType] = new Buildable[T, Coll[T]] { + def builder = new BuilderCompat[T, Coll[T]] { val al = new ArrayBuffer[T] + def addOne(x: T) = { al += x this } + def clear() = al.clear() + def result() = testSuite.builder.fromArray(al.toArray) } } - implicit def traversableColl[T](coll: Coll[T]): mutable.Traversable[T] = coll.toArray - + def traversableColl[T](coll: Coll[T]): mutable.Traversable[T] = coll.toArray } diff --git a/graph-ir/src/test/scala/special/collections/CollsTests.scala b/core-lib/shared/src/test/scala/special/collections/CollsTests.scala similarity index 97% rename from graph-ir/src/test/scala/special/collections/CollsTests.scala rename to core-lib/shared/src/test/scala/special/collections/CollsTests.scala index b8a8339276..0f75c0c27b 100644 --- a/graph-ir/src/test/scala/special/collections/CollsTests.scala +++ b/core-lib/shared/src/test/scala/special/collections/CollsTests.scala @@ -13,7 +13,7 @@ import scala.language.{existentials, implicitConversions} class CollsTests extends AnyPropSpec with ScalaCheckPropertyChecks with Matchers with CollGens with VersionTestingProperty { testSuite => import Gen._ - import special.collection.ExtensionMethods._ + import special.collection.Extensions._ def squared[A](f: A => A): ((A, A)) => (A, A) = (p: (A, A)) => (f(p._1), f(p._2)) @@ -62,7 +62,7 @@ class CollsTests extends AnyPropSpec with ScalaCheckPropertyChecks with Matchers equalLength(pairs) if (xs.nonEmpty && !VersionContext.current.isJitActivated) { - an[ClassCastException] should be thrownBy { + an[Throwable] should be thrownBy { equalLengthMapped(pairs, squared(inc)) // due to problem with append } } @@ -74,7 +74,7 @@ class CollsTests extends AnyPropSpec with ScalaCheckPropertyChecks with Matchers equalLength(pairs.append(pairs)) if (xs.nonEmpty && !VersionContext.current.isJitActivated) { - an[ClassCastException] should be thrownBy { + an[Throwable] should be thrownBy { equalLengthMapped(pairs.append(pairs), squared(inc)) // due to problem with append } } @@ -86,6 +86,7 @@ class CollsTests extends AnyPropSpec with ScalaCheckPropertyChecks with Matchers } property("Coll.flatMap") { + implicit val t: Coll[Int] => Traversable[Int] = traversableColl forAll(containerOfN[Coll, Int](3, valGen), collGen) { (zs, col) => val matrix = zs.map(_ => col) val res = zs.zip(matrix).flatMap(_._2) @@ -249,7 +250,7 @@ class CollsTests extends AnyPropSpec with ScalaCheckPropertyChecks with Matchers ys.append(ys).toArray shouldBe ys.toArray ++ ys.toArray } else { // due to the problem with CollectionUtil.concatArrays - an[ClassCastException] should be thrownBy (ys.append(ys)) + an[Throwable] should be thrownBy (ys.append(ys)) } } @@ -493,17 +494,6 @@ class CollsTests extends AnyPropSpec with ScalaCheckPropertyChecks with Matchers } } - - property("PairColl.mapFirst") { - val minSuccess = minSuccessful(30) - - forAll(collGen, minSuccess) { col => - val pairs = col.zip(col) - pairs.mapFirst(inc).toArray shouldBe pairs.toArray.map { case (x, y) => (inc(x), y) } - pairs.mapSecond(inc).toArray shouldBe pairs.toArray.map { case (x, y) => (x, inc(y)) } - } - } - property("Coll.unionSet") { forAll(collGen, collGen) { (col1, col2) => val res = col1.unionSet(col2) diff --git a/core-lib/src/main/scala/special/collection/ExtensionMethods.scala b/core-lib/src/main/scala/special/collection/ExtensionMethods.scala deleted file mode 100644 index fb2152510f..0000000000 --- a/core-lib/src/main/scala/special/collection/ExtensionMethods.scala +++ /dev/null @@ -1,12 +0,0 @@ -package special.collection - -import scalan.RType - -object ExtensionMethods { - - implicit class PairCollOps[A,B](val source: Coll[(A,B)]) extends AnyVal { - @inline def mapFirst[A1: RType](f: A => A1): Coll[(A1, B)] = source.asInstanceOf[PairColl[A,B]].mapFirst(f) - @inline def mapSecond[B1: RType](f: B => B1): Coll[(A, B1)] = source.asInstanceOf[PairColl[A,B]].mapSecond(f) - } - -} diff --git a/graph-ir/src/main/scala/scalan/MethodCalls.scala b/graph-ir/src/main/scala/scalan/MethodCalls.scala index b1bf67ebe6..d300d0765a 100644 --- a/graph-ir/src/main/scala/scalan/MethodCalls.scala +++ b/graph-ir/src/main/scala/scalan/MethodCalls.scala @@ -72,7 +72,7 @@ trait MethodCalls extends Base { self: Scalan => neverInvoke == other.neverInvoke && isAdapterCall == other.isAdapterCall && args.length == other.args.length && - args.sameElements2(other.args) // this is required in case method have T* arguments + args.sameElementsNested(other.args) // this is required in case method have T* arguments case _ => false } } diff --git a/graph-ir/src/main/scala/scalan/TypeDescs.scala b/graph-ir/src/main/scala/scalan/TypeDescs.scala index fd028f15bb..a3d7c889af 100644 --- a/graph-ir/src/main/scala/scalan/TypeDescs.scala +++ b/graph-ir/src/main/scala/scalan/TypeDescs.scala @@ -364,16 +364,6 @@ abstract class TypeDescs extends Base { self: Scalan => /** Predefined Lazy value saved here to be used in hotspot code. */ val LazyAnyElement = Lazy(AnyElement) - /** Type descriptor for `AnyRef`, cannot be used implicitly. */ - val AnyRefElement: Elem[AnyRef] = new BaseElemLiftable[AnyRef](null, AnyRefType) - - // somewhat ugly casts, but they completely disappear after type erasure - // (so not present in Java bytecode) - val NothingElement: Elem[Nothing] = - asElem[Nothing](new BaseElemLiftable[Null]( - null, NothingType.asInstanceOf[RType[Null]] - )) - implicit val BooleanElement: Elem[Boolean] = new BaseElemLiftable(false, BooleanType) implicit val ByteElement: Elem[Byte] = new BaseElemLiftable(0.toByte, ByteType) implicit val ShortElement: Elem[Short] = new BaseElemLiftable(0.toShort, ShortType) diff --git a/graph-ir/src/main/scala/special/collection/CollsUnit.scala b/graph-ir/src/main/scala/special/collection/CollsUnit.scala index 747ab25e68..217341830d 100644 --- a/graph-ir/src/main/scala/special/collection/CollsUnit.scala +++ b/graph-ir/src/main/scala/special/collection/CollsUnit.scala @@ -1,6 +1,13 @@ package special.collection { import scalan._ + /** Staged version of collection interfaces which is used in graph-based IR to represent + * methods of Coll and CollBuilder. + * Each method of Coll and CollBuilder in Colls corresponds to a method of the original + * non-staged class [[special.collection.Coll]] and [[special.collection.CollBuilder]]. + * The semantics of each method is the same as in the original class, please look there + * for details. + */ trait Colls extends Base { self: Library => import Coll._; import CollBuilder._; diff --git a/graph-ir/src/main/scala/special/collection/impl/CollsImpl.scala b/graph-ir/src/main/scala/special/collection/impl/CollsImpl.scala index 63203f2f90..1e9f948238 100644 --- a/graph-ir/src/main/scala/special/collection/impl/CollsImpl.scala +++ b/graph-ir/src/main/scala/special/collection/impl/CollsImpl.scala @@ -13,6 +13,9 @@ package impl { // Abs ----------------------------------- trait CollsDefs extends scalan.Scalan with Colls { self: Library => + + registerModule(CollsModule) + import Coll._ import CollBuilder._ import WOption._ @@ -637,7 +640,6 @@ object CollBuilder extends EntityObject("CollBuilder") { super.resetContext() } - registerModule(CollsModule) } object CollsModule extends scalan.ModuleInfo("special.collection", "Colls") { diff --git a/graph-ir/src/main/scala/special/sigma/impl/SigmaDslImpl.scala b/graph-ir/src/main/scala/special/sigma/impl/SigmaDslImpl.scala index c82b88c94e..e5b6b37b8a 100644 --- a/graph-ir/src/main/scala/special/sigma/impl/SigmaDslImpl.scala +++ b/graph-ir/src/main/scala/special/sigma/impl/SigmaDslImpl.scala @@ -14,6 +14,9 @@ package impl { // Abs ----------------------------------- trait SigmaDslDefs extends scalan.Scalan with SigmaDsl { self: SigmaLibrary => + + registerModule(SigmaDslModule) + import AvlTree._ import BigInt._ import Box._ @@ -2335,11 +2338,11 @@ object SigmaDslBuilder extends EntityObject("SigmaDslBuilder") { } } // of object SigmaDslBuilder registerEntityObject("SigmaDslBuilder", SigmaDslBuilder) - - registerModule(SigmaDslModule) } -object SigmaDslModule extends scalan.ModuleInfo("special.sigma", "SigmaDsl") +object SigmaDslModule extends scalan.ModuleInfo("special.sigma", "SigmaDsl") { + val reflection = GraphIRReflection +} } trait SigmaDslModule extends special.sigma.impl.SigmaDslDefs {self: SigmaLibrary =>} diff --git a/graph-ir/src/main/scala/wrappers/scala/impl/WOptionsImpl.scala b/graph-ir/src/main/scala/wrappers/scala/impl/WOptionsImpl.scala index 9b5cdb595b..4b69cfe957 100644 --- a/graph-ir/src/main/scala/wrappers/scala/impl/WOptionsImpl.scala +++ b/graph-ir/src/main/scala/wrappers/scala/impl/WOptionsImpl.scala @@ -226,8 +226,11 @@ class WOptionCls extends EntityObject("WOption") { } } } // of object WOption -object WOption extends WOptionCls + registerModule(WOptionsModule) + + object WOption extends WOptionCls + registerEntityObject("WOption", WOption) } diff --git a/graph-ir/src/main/scala/wrappers/scalan/impl/WRTypesImpl.scala b/graph-ir/src/main/scala/wrappers/scalan/impl/WRTypesImpl.scala index 4a85b46661..dfbd0f3111 100644 --- a/graph-ir/src/main/scala/wrappers/scalan/impl/WRTypesImpl.scala +++ b/graph-ir/src/main/scala/wrappers/scalan/impl/WRTypesImpl.scala @@ -7,12 +7,15 @@ import special.wrappers.RTypeWrapSpec import scala.collection.compat.immutable.ArraySeq package impl { + import scalan.GraphIRReflection import scalan.reflection.{RClass, RMethod} // Abs ----------------------------------- trait WRTypesDefs extends scalan.Scalan with WRTypes { self: WrappersModule => + registerModule(WRTypesModule) + /** IR implementation for RType methods. * Prefix `W` means that this is implementation of IR wrapper over RType. * @see [[EntityObject]], [[WRType]] @@ -106,10 +109,11 @@ class WRTypeCls extends EntityObject("WRType") { object WRType extends WRTypeCls registerEntityObject("WRType", WRType) -registerModule(WRTypesModule) } -object WRTypesModule extends scalan.ModuleInfo("wrappers.scalan", "WRTypes") +object WRTypesModule extends scalan.ModuleInfo("wrappers.scalan", "WRTypes") { + val reflection = GraphIRReflection +} } trait WRTypesModule extends wrappers.scalan.impl.WRTypesDefs {self: WrappersModule =>} diff --git a/graph-ir/src/main/scala/wrappers/special/impl/WSpecialPredefsImpl.scala b/graph-ir/src/main/scala/wrappers/special/impl/WSpecialPredefsImpl.scala index 8803bc332e..72b673553f 100644 --- a/graph-ir/src/main/scala/wrappers/special/impl/WSpecialPredefsImpl.scala +++ b/graph-ir/src/main/scala/wrappers/special/impl/WSpecialPredefsImpl.scala @@ -10,6 +10,9 @@ package impl { // Abs ----------------------------------- trait WSpecialPredefsDefs extends scalan.Scalan with WSpecialPredefs { self: WrappersModule => + + registerModule(WSpecialPredefsModule) + import WOption._ import WSpecialPredef._ @@ -55,10 +58,11 @@ object WSpecialPredef extends EntityObject("WSpecialPredef") { RWSpecialPredef.reset() } - registerModule(WSpecialPredefsModule) } -object WSpecialPredefsModule extends scalan.ModuleInfo("wrappers.special", "WSpecialPredefs") +object WSpecialPredefsModule extends scalan.ModuleInfo("wrappers.special", "WSpecialPredefs") { + val reflection = GraphIRReflection +} } trait WSpecialPredefsModule extends wrappers.special.impl.WSpecialPredefsDefs {self: WrappersModule =>} diff --git a/graph-ir/src/test/scala/special/collections/CollBenchmark.scala b/graph-ir/src/test/scala/special/collections/CollBenchmark.scala index c2df0f6c37..c123778137 100644 --- a/graph-ir/src/test/scala/special/collections/CollBenchmark.scala +++ b/graph-ir/src/test/scala/special/collections/CollBenchmark.scala @@ -4,7 +4,7 @@ import org.scalameter.{execution, Executor} import org.scalameter.api._ import org.scalameter.picklers.Implicits._ import special.collection.Coll -import special.collection.ExtensionMethods._ +import special.collection.Extensions._ import debox.cfor @@ -119,13 +119,6 @@ trait CollBenchmarkCases extends BenchmarkGens { suite: Bench[Double] => } } } - measure method "of PairColl" in { - using(colls) in { case (c, n) => - cfor(0)(_ < n, _ + 1) { _ => - c.zip(c).mapFirst(inc) - } - } - } } performance of "unionSet" in { measure method "of PairArray" in { diff --git a/graph-ir/src/test/scala/special/collections/CollsStagingTests.scala b/graph-ir/src/test/scala/special/collections/CollsStagingTests.scala index 9c160c6093..cf56428d71 100644 --- a/graph-ir/src/test/scala/special/collections/CollsStagingTests.scala +++ b/graph-ir/src/test/scala/special/collections/CollsStagingTests.scala @@ -26,140 +26,6 @@ class CollsStagingTests extends WrappersTests { ctx.emit("t3", ctx.t3) } - test("measure: build graph and resetContext") { - val ctx = new Ctx { - useAlphaEquality = false - } - import ctx._ - import Coll._ - import CollBuilder._ - - var res: Sym = null - val nIters = 10 - measure(nIters, okShowIterTime = printDebugInfo, okShowTotalTime = printDebugInfo) { i => - for (j <- 0 until 1000) { - val col = colBuilder.replicate(i*j, 0) - res = col.map(fun {x => x + 1}) - } - if (printDebugInfo) println(s"Defs: ${ctx.defCount}") - if (i == nIters - 1) emit("res", res) - ctx.resetContext() - } - } - - test("measure: build graph with new context") { - measure(10, okShowIterTime = printDebugInfo, okShowTotalTime = printDebugInfo) { i => - var sum: Int = 0 - for (j <- 0 until 1000) { - val ctx = new Ctx { - useAlphaEquality = false - } - import ctx._ - import Coll._ - import CollBuilder._ - val col = colBuilder.replicate(i*j, 0) - val res = col.map(fun {x => x + 1}) - sum += ctx.defCount - } - if (printDebugInfo) println(s"Defs: ${sum}") - } - } - - def runMeasure(nRepeats: Int, name: String, alphaEq: Boolean, keepOrig: Boolean, unfoldWithOrig: Boolean) = { - if (printDebugInfo) - println(s"runMeasure($name, alphaEq = $alphaEq, keepOrig = $keepOrig, unfoldWithOrig = $unfoldWithOrig)") - val nIters = 10 - def warmUp(i: Int) = { - val ctx = new Ctx { - useAlphaEquality = alphaEq - keepOriginalFunc = keepOrig - unfoldWithOriginalFunc = unfoldWithOrig - } - import ctx._ - import Coll._ - import CollBuilder._ - var outGraph: Sym = null - for (j <- 0 until nRepeats) { - val f = fun { in: Ref[(CollBuilder, Int)] => - val Pair(colBuilder, delta) = in - val col = colBuilder.replicate(i*j, 0) - val res = col.map(fun {x => x + delta}) - res - } - outGraph = Pair(f, f(Pair(colBuilder, 1))) - } - } - def measureUp(i: Int) = { - val ctx = new Ctx { - useAlphaEquality = alphaEq - keepOriginalFunc = keepOrig - unfoldWithOriginalFunc = unfoldWithOrig - } - import ctx._ - import Coll._ - import CollBuilder._ - var outGraph: Sym = null - for (j <- 0 until nRepeats) { - val f = fun { in: Ref[(CollBuilder, Int)] => - val Pair(colBuilder, delta) = in - val col = colBuilder.replicate(i*j, delta) - val col2 = colBuilder.replicate(j+i, delta) - val res = col.map(fun {x => x + delta}).zip(col2) - res - } - outGraph = Pair(f, f(Pair(colBuilder, 1))) - } - if (printDebugInfo) println(s"Defs: ${ctx.defCount}") - - if (i == nIters - 1) - emit(name, outGraph) - } - measure(nIters, okShowIterTime = printDebugInfo, okShowTotalTime = printDebugInfo)(warmUp) - System.gc() - measure(nIters, okShowIterTime = printDebugInfo, okShowTotalTime = printDebugInfo)(measureUp) - } - - test("measure: unfoldLambda") { - val dummyCtx = new Ctx // to force class loading - runMeasure(100, "default", true, true, true) - runMeasure(1000, "noAlpha", false, true, true) - runMeasure(1000, "noAlpha_noKeepOrig", false, false, true) - } -/* -runMeasure(noAlpha_noKeepOrig, alphaEq = false, keepOrig = false, unfoldWithOrig = true) -Iter 0: 1222 ms -Iter 1: 653 ms -Iter 2: 588 ms -Iter 3: 300 ms -Iter 4: 327 ms -Iter 5: 469 ms -Iter 6: 346 ms -Iter 7: 372 ms -Iter 8: 350 ms -Iter 9: 220 ms -Total time: 4847 ms -Defs: 220004 -Iter 0: 845 ms -Defs: 220005 -Iter 1: 580 ms -Defs: 225005 -Iter 2: 491 ms -Defs: 226671 -Iter 3: 620 ms -Defs: 227505 -Iter 4: 405 ms -Defs: 228005 -Iter 5: 558 ms -Defs: 228338 -Iter 6: 530 ms -Defs: 228576 -Iter 7: 418 ms -Defs: 228755 -Iter 8: 426 ms -Defs: 228893 -Iter 9: 533 ms -Total time: 5406 ms -*/ test("invokeTransformedAdapterMethodCall") { val ctx = new Ctx { useAlphaEquality = true @@ -190,28 +56,13 @@ Total time: 5406 ms val inc = (x: Int) => x + 1 check(col, { env: EnvRep[Coll[Int]] => for { xs <- env; incL <- lifted(inc) } yield xs.map(incL) }, col.map(inc)) - -// check(Cols, { env: EnvRep[CollBuilder] => for { b <- env; arrL <- lifted(arr) } yield b.fromArray(arrL) }, Cols.fromArray(arr)) - - measure(10, okShowIterTime = printDebugInfo, okShowTotalTime = printDebugInfo) { i => - (1 to 100).foreach { j => - check(Cols, - {env: EnvRep[CollBuilder] => for { - b <- env; x1 <- lifted(1); x2 <- lifted(j); x3 <- lifted(i) - } yield b.fromItems(x1, x2, x3) }, - Cols.fromItems(1, j, i)) - } - if (printDebugInfo) println(s"Defs: ${ctx.defCount}") - } } test("invokeUnlifted for method of Ctor") { val ctx = new WrappersCtx with Library import ctx._ - import Liftables._ import Coll._ import CollBuilder._ - import EnvRep._ val Cols: SCollBuilder = new special.collection.CollOverArrayBuilder val colData = Cols.replicate(10, 10) diff --git a/interpreter/js/src/main/scala/sigmastate/crypto/Platform.scala b/interpreter/js/src/main/scala/sigmastate/crypto/Platform.scala new file mode 100644 index 0000000000..780b74f9a0 --- /dev/null +++ b/interpreter/js/src/main/scala/sigmastate/crypto/Platform.scala @@ -0,0 +1,256 @@ +package sigmastate.crypto + +import scalan.RType +import scorex.util.encode.Base16 +import sigmastate._ +import special.collection.Coll +import special.sigma._ + +import java.math.BigInteger +import scala.scalajs.js +import scala.scalajs.js.UnicodeNormalizationForm +import scala.scalajs.js.typedarray.Uint8Array +import scala.util.Random + +/** JVM specific implementation of crypto methods (NOT yet implemented). */ +object Platform { + /** Description of elliptic curve of point `p` which belongs to the curve. + * + * @param p the elliptic curve point + */ + def getCurve(p: Ecp): Curve = ??? + + /** Returns the x-coordinate. + * + * Caution: depending on the curve's coordinate system, this may not be the same value as in an + * affine coordinate system; use normalizePoint() to get a point where the coordinates have their + * affine values, or use getAffineXCoord() if you expect the point to already have been + * normalized. + */ + def getXCoord(p: Ecp): ECFieldElem = new ECFieldElem(CryptoFacadeJs.getXCoord(p.point)) + + /** Returns the y-coordinate. + * + * Caution: depending on the curve's coordinate system, this may not be the same value as in an + * affine coordinate system; use normalizePoint() to get a point where the coordinates have their + * affine values, or use getAffineYCoord() if you expect the point to already have been + * normalized. + */ + def getYCoord(p: Ecp): ECFieldElem = new ECFieldElem(CryptoFacadeJs.getYCoord(p.point)) + + /** Returns the affine x-coordinate after checking that this point is normalized. */ + def getAffineXCoord(p: Ecp): ECFieldElem = new ECFieldElem(CryptoFacadeJs.getAffineXCoord(p.point)) + + /** Returns the affine y-coordinate after checking that this point is normalized. */ + def getAffineYCoord(p: Ecp): ECFieldElem = new ECFieldElem(CryptoFacadeJs.getAffineYCoord(p.point)) + + /** Converts JS representation of bytes array to Scala's equivalent. */ + def Uint8ArrayToBytes(jsShorts: Uint8Array): Array[Byte] = { + jsShorts.toArray[Short].map(x => x.toByte) + } + + /** Converts Scala's representation of bytes array to JS array of Shorts. */ + def bytesToJsShorts(bytes: Array[Byte]): js.Array[Short] = { + js.Array(bytes.map(x => (x & 0xFF).toShort): _*) + } + + /** Converts JS array of Short values to Scala's bytes array by dropping most + * significant byte of each Short. + */ + def jsShortsToBytes(jsShorts: js.Array[Short]): Array[Byte] = { + jsShorts.toArray[Short].map(x => x.toByte) + } + + /** Returns byte representation of the given field element. */ + def encodeFieldElem(p: ECFieldElem): Array[Byte] = { + Uint8ArrayToBytes(CryptoFacadeJs.getEncodedOfFieldElem(p.elem)) + } + + /** Byte representation of the given point. + * + * @param p point to encode + * @param compressed if true, generates a compressed point encoding + * @see [[CryptoFacade.getASN1Encoding]] + */ + def getASN1Encoding(p: Ecp, compressed: Boolean): Array[Byte] = { + val hex = if (isInfinityPoint(p)) "00" // to ensure equality with Java implementation + else p.point.toHex(compressed) + Base16.decode(hex).get + } + + /** Returns the sign of the field element. */ + def signOf(p: ECFieldElem): Boolean = CryptoFacadeJs.testBitZeroOfFieldElem(p.elem) + + /** Normalization ensures that any projective coordinate is 1, and therefore that the x, y + * coordinates reflect those of the equivalent point in an affine coordinate system. + * + * @return a new ECPoint instance representing the same point, but with normalized coordinates + */ + def normalizePoint(p: Ecp): Ecp = new Ecp(CryptoFacadeJs.normalizePoint(p.point)) + + /** Return simplified string representation of the point (used only for debugging) */ + def showPoint(p: Ecp): String = CryptoFacadeJs.showPoint(p.point) + + /** Multiply two points. + * + * @param p1 first point + * @param p2 second point + * @return group multiplication (p1 * p2) + */ + def multiplyPoints(p1: Ecp, p2: Ecp): Ecp = new Ecp(CryptoFacadeJs.addPoint(p1.point, p2.point)) + + /** Exponentiate a point p. + * + * @param p point to exponentiate + * @param n exponent + * @return p to the power of n (`p^n`) i.e. `p + p + ... + p` (n times) + */ + def exponentiatePoint(p: Ecp, n: BigInteger): Ecp = { + val scalar = Convert.bigIntegerToBigInt(n) + new Ecp(CryptoFacadeJs.multiplyPoint(p.point, scalar)) + } + + /** Check if a point is infinity. */ + def isInfinityPoint(p: Ecp): Boolean = CryptoFacadeJs.isInfinityPoint(p.point) + + /** Negates the given point by negating its y coordinate. */ + def negatePoint(p: Ecp): Ecp = new Ecp(CryptoFacadeJs.negatePoint(p.point)) + + /** JS implementation of Elliptic Curve. */ + class Curve + + // TODO JS: Use JS library for secure source of randomness + type SecureRandom = Random + + /** Opaque point type. */ + @js.native + trait Point extends js.Object { + /** coordinate x of this point. */ + def x: js.BigInt = js.native + + /** coordinate y of this point. */ + def y: js.BigInt = js.native + + /** Returns hex prepresentation of this point. */ + def toHex(b: Boolean): String = js.native + } + + /** JS implementation of EC point. */ + class Ecp(val point: Point) { + lazy val hex = point.toHex(true) + override def hashCode(): Int = hex.hashCode + + override def equals(obj: Any): Boolean = (this eq obj.asInstanceOf[AnyRef]) || (obj match { + case that: Ecp => this.hex == that.hex + case _ => false + }) + } + + /** JS implementation of field element. */ + class ECFieldElem(val elem: js.BigInt) { + private lazy val digits: String = elem.toString(10) + + override def hashCode(): Int = digits.hashCode + + override def equals(obj: Any): Boolean = (this eq obj.asInstanceOf[AnyRef]) || (obj match { + case that: ECFieldElem => this.digits == that.digits + case _ => false + }) + } + + /** Helper converters. */ + object Convert { + /** Converts a JavaScript BigInt to a Java BigInteger. */ + def bigIntToBigInteger(jsValue: js.BigInt): BigInteger = { + new BigInteger(jsValue.toString(10), 10) + } + + /** Converts a Java BigInteger to a JavaScript BigInt. */ + def bigIntegerToBigInt(value: BigInteger): js.BigInt = { + js.BigInt(value.toString(10)) + } + } + + /** Create a new context for cryptographic operations. */ + def createContext(): CryptoContext = new CryptoContext { + private val ctx = new CryptoContextJs + + /** The underlying elliptic curve descriptor. */ + override def curve: crypto.Curve = ??? + + override def fieldCharacteristic: BigInteger = Convert.bigIntToBigInteger(ctx.getModulus()) + + override def order: BigInteger = Convert.bigIntToBigInteger(ctx.getOrder()) + + override def validatePoint(x: BigInteger, y: BigInteger): crypto.Ecp = { + val point = ctx.validatePoint(Convert.bigIntegerToBigInt(x), Convert.bigIntegerToBigInt(y)) + new Ecp(point) + } + + override def infinity(): crypto.Ecp = + new Ecp(ctx.getInfinity()) + + override def decodePoint(encoded: Array[Byte]): crypto.Ecp = + new Ecp(ctx.decodePoint(Base16.encode(encoded))) + + override def generator: crypto.Ecp = + new Ecp(ctx.getGenerator()) + } + + /** Create JS specific source of secure randomness. */ + def createSecureRandom(): Random = new Random() + + /** Computes HMAC-SHA512 hash of the given data using the specified key. + * + * @param key the secret key used for hashing + * @param data the input data to be hashed + * @return a HMAC-SHA512 hash of the input data + */ + def hashHmacSHA512(key: Array[Byte], data: Array[Byte]): Array[Byte] = { + val keyArg = Uint8Array.from(bytesToJsShorts(key)) + val dataArg = Uint8Array.from(bytesToJsShorts(data)) + val hash = CryptoFacadeJs.hashHmacSHA512(keyArg, dataArg) + Uint8ArrayToBytes(hash) + } + + /** Generates PBKDF2 key from a mnemonic and passphrase using SHA512 digest. */ + def generatePbkdf2Key(normalizedMnemonic: String, normalizedPass: String): Array[Byte] = { + val res = CryptoFacadeJs.generatePbkdf2Key(normalizedMnemonic, normalizedPass) + Uint8ArrayToBytes(res) + } + + /** Normalize a sequence of char values using NFKD normalization form. */ + def normalizeChars(chars: Array[Char]): String = { + import js.JSStringOps._ + String.valueOf(chars).normalize(UnicodeNormalizationForm.NFKD) + } + + /** Checks that the type of the value corresponds to the descriptor `tpe`. + * If the value has complex structure only root type constructor is checked. + * NOTE, this is surface check with possible false positives, but it is ok + * when used in assertions, like `assert(isCorrestType(...))`, see `ConstantNode`. + */ + def isCorrectType[T <: SType](value: Any, tpe: T): Boolean = value match { + case c: Coll[_] => tpe match { + case STuple(items) => c.tItem == RType.AnyType && c.length == items.length + case tpeColl: SCollection[_] => true + case _ => sys.error(s"Collection value $c has unexpected type $tpe") + } + case _: Option[_] => tpe.isOption + case _: Tuple2[_, _] => tpe.isTuple && tpe.asTuple.items.length == 2 + case _: Boolean => tpe == SBoolean + case _: Byte | _: Short | _: Int | _: Long => tpe.isInstanceOf[SNumericType] + case _: BigInt => tpe == SBigInt + case _: String => tpe == SString + case _: GroupElement => tpe.isGroupElement + case _: SigmaProp => tpe.isSigmaProp + case _: AvlTree => tpe.isAvlTree + case _: Box => tpe.isBox + case _: PreHeader => tpe == SPreHeader + case _: Header => tpe == SHeader + case _: Context => tpe == SContext + case _: Function1[_, _] => tpe.isFunc + case _: Unit => tpe == SUnit + case _ => false + } +} diff --git a/interpreter/js/src/main/scala/sigmastate/crypto/SigmaJsCryptoFacade.scala b/interpreter/js/src/main/scala/sigmastate/crypto/SigmaJsCryptoFacade.scala new file mode 100644 index 0000000000..3b19095b6a --- /dev/null +++ b/interpreter/js/src/main/scala/sigmastate/crypto/SigmaJsCryptoFacade.scala @@ -0,0 +1,128 @@ +package sigmastate.crypto + +import scala.scalajs.js +import scala.scalajs.js.annotation.JSImport +import scala.scalajs.js.typedarray.Uint8Array + +/** Represents imported CryptoContext class from `sigmajs-crypto-facade` JS libarary. */ +@js.native +@JSImport("sigmajs-crypto-facade", "CryptoContext") +class CryptoContextJs() extends js.Object { + /** The characteristics (modulus) of the underlying finite field. */ + def getModulus(): js.BigInt = js.native + + /** The order of the underlying group. */ + def getOrder(): js.BigInt = js.native + + /** Validates a point. + * + * @param x the x-coordinate of the point + * @param y the y-coordinate of the point + * @return the point if it is valid + */ + def validatePoint(x: js.BigInt, y: js.BigInt): Platform.Point = js.native + + /** The point at infinity. */ + def getInfinity(): Platform.Point = js.native + + /** Decodes a point from its hex string representation. */ + def decodePoint(encoded: String): Platform.Point = js.native + + /** The generator of the underlying group. */ + def getGenerator(): Platform.Point = js.native +} + +/** Represents imported CryptoFacade object from `sigmajs-crypto-facade` JS libarary. */ +@js.native +@JSImport("sigmajs-crypto-facade", "CryptoFacade") +object CryptoFacadeJs extends js.Object { + /** Normalization ensures that any projective coordinate is 1, and therefore that the x, y + * coordinates reflect those of the equivalent point in an affine coordinate system. + * + * @return a new ECPoint instance representing the same point, but with normalized coordinates + */ + def normalizePoint(point: Platform.Point): Platform.Point = js.native + + /** Creates a new context for cryptographic operations. */ + def createCryptoContext(): CryptoContextJs = js.native + + /** Negates the given point by negating its y coordinate. */ + def negatePoint(point: Platform.Point): Platform.Point = js.native + + /** Check if a point is infinity. */ + def isInfinityPoint(point: Platform.Point): Boolean = js.native + + /** Multiplies the [[ECPoint]] `p`` by `k`, i.e. `p` is added `k` times to itself. + * + * @param p The [[ECPoint]] to be multiplied. + * @param k The factor by which `p` is multiplied. + */ + def multiplyPoint(point: Platform.Point, scalar: js.BigInt): Platform.Point = js.native + + /** Adds two EC points. */ + def addPoint(point1: Platform.Point, point2: Platform.Point): Platform.Point = js.native + + /** Return simplified string representation of the point (used only for debugging) */ + def showPoint(point: Platform.Point): String = js.native + + // TODO refactor: rename to signOf to be consistent with CryptoFacade.signOf + /** Returns the sign of the field element. */ + def testBitZeroOfFieldElem(element: js.BigInt): Boolean = js.native + + // TODO refactor: raname to encodeFieldElem to be consistent with CryptoFacade.encodeFieldElem + /** Returns byte representation of the given field element. */ + def getEncodedOfFieldElem(element: js.BigInt): Uint8Array = js.native + + /** Returns the x-coordinate. + * + * Caution: depending on the curve's coordinate system, this may not be the same value as in an + * affine coordinate system; use normalizePoint() to get a point where the coordinates have their + * affine values, or use getAffineXCoord() if you expect the point to already have been + * normalized. + */ + def getXCoord(point: Platform.Point): js.BigInt = js.native + + /** Returns the y-coordinate. + * + * Caution: depending on the curve's coordinate system, this may not be the same value as in an + * affine coordinate system; use normalizePoint() to get a point where the coordinates have their + * affine values, or use getAffineYCoord() if you expect the point to already have been + * normalized. + */ + def getYCoord(point: Platform.Point): js.BigInt = js.native + + /** Returns the affine x-coordinate after checking that this point is normalized. */ + def getAffineXCoord(point: Platform.Point): js.BigInt = js.native + + /** Returns the affine y-coordinate after checking that this point is normalized. */ + def getAffineYCoord(point: Platform.Point): js.BigInt = js.native + + /** Computes HMAC-SHA512 hash of the given data using the specified key. + * + * @param key the secret key used for hashing + * @param data the input data to be hashed + * @return a HMAC-SHA512 hash of the input data + */ + def hashHmacSHA512(key: Uint8Array, data: Uint8Array): Uint8Array = js.native + + /** Generates PBKDF2 key from a mnemonic and passphrase. */ + def generatePbkdf2Key( + normalizedMnemonic: String, + normalizedPass: String): Uint8Array = js.native +} + +/** Represents imported Point class from `sigmajs-crypto-facade` JS libarary. */ +@js.native +@JSImport("sigmajs-crypto-facade", "Point") +object Point extends js.Any { + def fromHex(hex: String): Platform.Point = js.native + def ZERO: Platform.Point = js.native +} + +/** Represents imported `utils` module from `sigmajs-crypto-facade` JS libarary. */ +@js.native +@JSImport("sigmajs-crypto-facade", "utils") +object utils extends js.Any { + def bytesToHex(bytes: Uint8Array): String = js.native + def hexToBytes(hex: String): Uint8Array = js.native +} \ No newline at end of file diff --git a/interpreter/js/src/test/scala/sigmastate/crypto/CryptoContextJsSpec.scala b/interpreter/js/src/test/scala/sigmastate/crypto/CryptoContextJsSpec.scala new file mode 100644 index 0000000000..e6d8c93009 --- /dev/null +++ b/interpreter/js/src/test/scala/sigmastate/crypto/CryptoContextJsSpec.scala @@ -0,0 +1,42 @@ +package sigmastate.crypto + +import org.scalatest.matchers.should.Matchers +import org.scalatest.propspec.AnyPropSpec + +import scala.scalajs.js + +class CryptoContextJsSpec extends AnyPropSpec with Matchers with CryptoTesting { + val p1_x = js.BigInt("58696697963552658225209022604545906839720992139040109583461478932841590007980") + val p1_y = js.BigInt("99221890655421281687240657051313379827030356718751695543666346070432252019955") + val pointHex = "0381c5275b1d50c39a0c36c4561c3a37bff1d87e37a9ad69eab029e426c0b1a8ac" + val generatorPointHex = "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" + val ctx = new CryptoContextJs + + property("CryptoContext.getModulus") { + ctx.getModulus() shouldBe modulus + } + + property("CryptoContext.getOrder") { + ctx.getOrder() shouldBe order + } + + property("CryptoContext.validatePoint") { + val p1 = ctx.validatePoint(p1_x, p1_y) + p1.x shouldBe p1_x + p1.y shouldBe p1_y + } + + property("CryptoContext.getInfinity") { + ctx.getInfinity().toHex(true) should not be(empty) + } + + property("CryptoContext.decodePoint") { + val p1_decoded = ctx.decodePoint(pointHex) + p1_decoded.x shouldBe p1_x + p1_decoded.y shouldBe p1_y + } + + property("CryptoContext.getGenerator") { + ctx.getGenerator().toHex(true) shouldBe generatorPointHex + } +} diff --git a/interpreter/js/src/test/scala/sigmastate/crypto/CryptoFacadeJsSpec.scala b/interpreter/js/src/test/scala/sigmastate/crypto/CryptoFacadeJsSpec.scala new file mode 100644 index 0000000000..9fc4e43d86 --- /dev/null +++ b/interpreter/js/src/test/scala/sigmastate/crypto/CryptoFacadeJsSpec.scala @@ -0,0 +1,101 @@ +package sigmastate.crypto + +import org.scalatest.matchers.should.Matchers +import org.scalatest.propspec.AnyPropSpec +import scorex.util.encode.Base16 + +import scala.scalajs.js +import scala.scalajs.js.typedarray.Uint8Array + +class CryptoFacadeJsSpec extends AnyPropSpec with Matchers with CryptoTesting { + val p1 = Point.fromHex("0381c5275b1d50c39a0c36c4561c3a37bff1d87e37a9ad69eab029e426c0b1a8ac") + val p2 = Point.fromHex("02198064ec24024bb8b300e20dd18e33cc1fccb0fea73940bd9a1d3d9d6c3ddd8f") + val infinity = Point.ZERO + val ctx = CryptoFacadeJs.createCryptoContext() + + property("CryptoFacade.createCryptoContext") { + CryptoFacadeJs.createCryptoContext().getModulus() shouldBe modulus + } + + property("CryptoFacade.normalizePoint") { + CryptoFacade.normalizePoint(new Ecp(p1)) shouldBe new Ecp(p1) + } + + property("CryptoFacade.negatePoint") { + CryptoFacadeJs.negatePoint(p1).toHex(true) shouldBe + "0281c5275b1d50c39a0c36c4561c3a37bff1d87e37a9ad69eab029e426c0b1a8ac" + } + + property("CryptoFacade.isInfinityPoint") { + CryptoFacadeJs.isInfinityPoint(infinity) shouldBe true + CryptoFacadeJs.isInfinityPoint(p1) shouldBe false + } + + property("CryptoFacade.multiplyPoint") { + CryptoFacadeJs.multiplyPoint(p1, ctx.getOrder() - js.BigInt(1)).toHex(true) shouldBe + "0281c5275b1d50c39a0c36c4561c3a37bff1d87e37a9ad69eab029e426c0b1a8ac" + } + + property("CryptoFacade.addPoint") { + CryptoFacadeJs.addPoint(p1, p2).toHex(true) shouldBe + "03de5e9c2806c05cd45a57d18c469743f42a0d2c84370b6662eb39d8a2990abed8" + } + + property("CryptoFacade.showPoint") { + CryptoFacadeJs.showPoint(p1) shouldBe "ECPoint(81c527,db5d99,...)" + CryptoFacadeJs.showPoint(p2) shouldBe "ECPoint(198064,811477,...)" + } + + property("CryptoFacade.testBitZeroOfFieldElem") { + case class Item(value: js.BigInt, expected: Boolean) + val testVectors = Array( + Item( + value = js.BigInt("58696697963552658225209022604545906839720992139040109583461478932841590007980"), + expected = false + ), + Item(js.BigInt("99221890655421281687240657051313379827030356718751695543666346070432252019955"), true), + Item(js.BigInt("11534674179847572339525410967292119666111600146005056703881161120062101118351"), true), + Item(js.BigInt("58384518810610603488166176247797257725226278383152129497027289229952751610898"), false), + Item(js.BigInt("53248006962404494469764696243319434838764093672738035895911945007004750906420"), false), + Item(js.BigInt("101865731467179904889950494818132658695802833332454014925098273981492319479977"), true), + ) + for ( tv <- testVectors ) { + CryptoFacadeJs.testBitZeroOfFieldElem(tv.value) shouldBe tv.expected + } + } + + def bytesToJsShorts(bytes: Array[Byte]): js.Array[Short] = { + js.Array(bytes.map(x => (x & 0xFF).toShort): _*) + } + + property("CryptoFacade.getEncodedOfFieldElem") { + utils.bytesToHex(CryptoFacadeJs.getEncodedOfFieldElem(p1.x)) shouldEqual + "81c5275b1d50c39a0c36c4561c3a37bff1d87e37a9ad69eab029e426c0b1a8ac" + utils.bytesToHex(CryptoFacadeJs.getEncodedOfFieldElem(p1.y)) shouldBe + "db5d999704ec84b62962f3e35889901d04a619cd6d81d251c69d0f625c2dc4f3" + } + + property("CryptoFacade should get coordinates") { + CryptoFacadeJs.getXCoord(p1) shouldBe p1.x + CryptoFacadeJs.getYCoord(p1) shouldBe p1.y + } + + property("CryptoFacade should get affine coordinates") { + CryptoFacadeJs.getAffineXCoord(p1) shouldBe p1.x + CryptoFacadeJs.getAffineYCoord(p1) shouldBe p1.y + } + + property("CryptoFacade.hashHmacSHA512") { + val key = Uint8Array.from(bytesToJsShorts(CryptoFacade.BitcoinSeed)) + val data = Uint8Array.from(bytesToJsShorts("abc".getBytes(CryptoFacade.Encoding))) + val res = utils.bytesToHex(CryptoFacadeJs.hashHmacSHA512(key, data)) + res shouldBe "2c15e87cde0f876fd8f060993748330cbe5f37c8bb3355e8ef44cea57890ec1d9b3274ef2b67bbe046cf8a012fba69796ec7803b1cc227521b9f5191e80a7da2" + } + + property("CryptoFacade.generatePbkdf2Key") { + val mnemonic = "slow silly start wash bundle suffer bulb ancient height spin express remind today effort helmet" + val password = "pwd" + val res = utils.bytesToHex(CryptoFacadeJs.generatePbkdf2Key(mnemonic, password)) + res shouldBe "0a8ea2ea0c4c12a9df88b005bda00c4de51ff36834b5fcd6a83667c371ad1da94bca1798690d87f2603b8f51d5ae025209e31f6cf81e12b84e4c543d236e58d0" + } +} diff --git a/interpreter/js/src/test/scala/sigmastate/crypto/CryptoTesting.scala b/interpreter/js/src/test/scala/sigmastate/crypto/CryptoTesting.scala new file mode 100644 index 0000000000..d09b64dcff --- /dev/null +++ b/interpreter/js/src/test/scala/sigmastate/crypto/CryptoTesting.scala @@ -0,0 +1,8 @@ +package sigmastate.crypto + +import scala.scalajs.js + +trait CryptoTesting { + val modulus = js.BigInt("115792089237316195423570985008687907853269984665640564039457584007908834671663") + val order = js.BigInt("115792089237316195423570985008687907852837564279074904382605163141518161494337") +} diff --git a/interpreter/src/main/scala/sigmastate/crypto/CryptoContextJvm.scala b/interpreter/jvm/src/main/scala/sigmastate/crypto/CryptoContextJvm.scala similarity index 100% rename from interpreter/src/main/scala/sigmastate/crypto/CryptoContextJvm.scala rename to interpreter/jvm/src/main/scala/sigmastate/crypto/CryptoContextJvm.scala diff --git a/interpreter/jvm/src/main/scala/sigmastate/crypto/HmacSHA512.scala b/interpreter/jvm/src/main/scala/sigmastate/crypto/HmacSHA512.scala new file mode 100644 index 0000000000..237ca9e176 --- /dev/null +++ b/interpreter/jvm/src/main/scala/sigmastate/crypto/HmacSHA512.scala @@ -0,0 +1,23 @@ +package sigmastate.crypto + +import javax.crypto.Mac +import javax.crypto.spec.SecretKeySpec + +/** A utility object to compute HMAC-SHA512 hashes. */ +object HmacSHA512 { + private val HashAlgo = "HmacSHA512" + + /** Computes HMAC-SHA512 hash of the given data using the specified key. + * + * @param key the secret key used for hashing + * @param data the input data to be hashed + */ + def hash(key: Array[Byte], data: Array[Byte]): Array[Byte] = initialize(key).doFinal(data) + + private def initialize(byteKey: Array[Byte]) = { + val hmacSha512 = Mac.getInstance(HashAlgo) + val keySpec = new SecretKeySpec(byteKey, HashAlgo) + hmacSha512.init(keySpec) + hmacSha512 + } +} diff --git a/interpreter/jvm/src/main/scala/sigmastate/crypto/Platform.scala b/interpreter/jvm/src/main/scala/sigmastate/crypto/Platform.scala new file mode 100644 index 0000000000..bdca5949ba --- /dev/null +++ b/interpreter/jvm/src/main/scala/sigmastate/crypto/Platform.scala @@ -0,0 +1,215 @@ +package sigmastate.crypto + +import org.bouncycastle.crypto.digests.SHA512Digest +import org.bouncycastle.crypto.ec.CustomNamedCurves +import org.bouncycastle.crypto.generators.PKCS5S2ParametersGenerator +import org.bouncycastle.crypto.params.KeyParameter +import org.bouncycastle.math.ec.{ECCurve, ECFieldElement, ECPoint} +import scalan.RType + +import java.math.BigInteger +import sigmastate._ +import sigmastate.basics.BcDlogGroup +import special.collection.Coll +import special.sigma._ + +import java.text.Normalizer.Form.NFKD +import java.text.Normalizer + +/** JVM specific implementation of crypto methods*/ +object Platform { + /** Description of elliptic curve of point `p` which belongs to the curve. + * @param p the elliptic curve point + */ + def getCurve(p: Ecp): Curve = Curve(p.value.getCurve) + + /** Returns the x-coordinate. + * + * Caution: depending on the curve's coordinate system, this may not be the same value as in an + * affine coordinate system; use normalize() to get a point where the coordinates have their + * affine values, or use getAffineXCoord() if you expect the point to already have been + * normalized. + * + * @return the x-coordinate of this point + */ + def getXCoord(p: Ecp): ECFieldElem = ECFieldElem(p.value.getXCoord) + + /** Returns the y-coordinate. + * + * Caution: depending on the curve's coordinate system, this may not be the same value as in an + * affine coordinate system; use normalize() to get a point where the coordinates have their + * affine values, or use getAffineYCoord() if you expect the point to already have been + * normalized. + * + * @return the y-coordinate of this point + */ + def getYCoord(p: Ecp): ECFieldElem = ECFieldElem(p.value.getYCoord) + + /** Returns the affine x-coordinate after checking that this point is normalized. + * + * @return The affine x-coordinate of this point + * @throws IllegalStateException if the point is not normalized + */ + def getAffineXCoord(p: Ecp): ECFieldElem = ECFieldElem(p.value.getAffineXCoord) + + /** Returns the affine y-coordinate after checking that this point is normalized + * + * @return The affine y-coordinate of this point + * @throws IllegalStateException if the point is not normalized + */ + def getAffineYCoord(p: Ecp): ECFieldElem = ECFieldElem(p.value.getAffineYCoord) + + /** Returns byte representation of the given field element. */ + def encodeFieldElem(p: ECFieldElem): Array[Byte] = p.value.getEncoded + + /** Byte representation of the given point. + * @param p point to encode + * @param compressed if true, generates a compressed point encoding + * @see [[CryptoFacade.getASN1Encoding]] + */ + def getASN1Encoding(p: Ecp, compressed: Boolean): Array[Byte] = p.value.getEncoded(compressed) + + /** Returns the value of bit 0 in BigInteger representation of this point. */ + def signOf(p: ECFieldElem): Boolean = p.value.testBitZero() + + /** Normalization ensures that any projective coordinate is 1, and therefore that the x, y + * coordinates reflect those of the equivalent point in an affine coordinate system. + * + * @return a new ECPoint instance representing the same point, but with normalized coordinates + */ + def normalizePoint(p: Ecp): Ecp = Ecp(p.value.normalize()) + + /** Return simplified string representation of the point (used only for debugging) */ + def showPoint(p: Ecp): String = { + val rawX = p.value.getRawXCoord.toString.substring(0, 6) + val rawY = p.value.getRawYCoord.toString.substring(0, 6) + s"ECPoint($rawX,$rawY,...)" + } + + /** Multiply two points. + * + * @param p1 first point + * @param p2 second point + * @return group multiplication (p1 * p2) + */ + def multiplyPoints(p1: Ecp, p2: Ecp): Ecp = { + /* + * BC treats EC as additive group while we treat that as multiplicative group. + */ + Ecp(p1.value.add(p2.value)) + } + + /** Exponentiate a point. + * @param p point to exponentiate + * @param n exponent + * @return p to the power of n (`p^n`) i.e. `p + p + ... + p` (n times) + */ + def exponentiatePoint(p: Ecp, n: BigInteger): Ecp = { + /* + * BC treats EC as additive group while we treat that as multiplicative group. + * Therefore, exponentiate point is multiply. + */ + Ecp(p.value.multiply(n)) + } + + /** Check if a point is infinity. */ + def isInfinityPoint(p: Ecp): Boolean = p.value.isInfinity + + /** Negates the given point by negating its y coordinate. */ + def negatePoint(p: Ecp): Ecp = Ecp(p.value.negate()) + + /** Wrapper for curve descriptor. Serves as the concrete implementation of the + * [[sigmastate.crypto.Curve]] type in JVM. + */ + case class Curve(private[crypto] val value: ECCurve) + + /** Wrapper for point type. */ + case class Ecp(private[crypto] val value: ECPoint) + + /** Wrapper for field element type. */ + case class ECFieldElem(value: ECFieldElement) + + /** Secure source of randomness on JVM. */ + type SecureRandom = java.security.SecureRandom + + /** Create a new context for cryptographic operations. */ + def createContext(): CryptoContext = new CryptoContextJvm(CustomNamedCurves.getByName("secp256k1")) + + /** Create JVM specific source of secure randomness. */ + def createSecureRandom(): SecureRandom = new SecureRandom() + + /** Computes HMAC-SHA512 hash of the given data using the specified key. + * + * @param key the secret key used for hashing + * @param data the input data to be hashed + * @return a HMAC-SHA512 hash of the input data + */ + def hashHmacSHA512(key: Array[Byte], data: Array[Byte]): Array[Byte] = HmacSHA512.hash(key, data) + + /** Generates PBKDF2 key from a mnemonic and passphrase using SHA512 digest. + * Seed generation based on bouncycastle implementation. + * See https://github.com/ergoplatform/ergo-appkit/issues/82 + */ + def generatePbkdf2Key(normalizedMnemonic: String, normalizedPass: String): Array[Byte] = { + val gen = new PKCS5S2ParametersGenerator(new SHA512Digest) + gen.init( + normalizedMnemonic.getBytes(CryptoFacade.Encoding), + normalizedPass.getBytes(CryptoFacade.Encoding), + CryptoFacade.Pbkdf2Iterations) + val dk = gen.generateDerivedParameters(CryptoFacade.Pbkdf2KeyLength).asInstanceOf[KeyParameter].getKey + dk + } + + /** Normalize a sequence of char values. + * The sequence will be normalized according to the NFKD normalization form. + * Implementation that uses [[java.text.Normalizer]]. + * See https://www.unicode.org/reports/tr15/ */ + def normalizeChars(chars: Array[Char]): String = { + Normalizer.normalize(ArrayCharSequence(chars), NFKD) + } + + /** Checks that the type of the value corresponds to the descriptor `tpe`. + * If the value has complex structure only root type constructor is checked. + * NOTE, this is surface check with possible false positives, but it is ok + * when used in assertions, like `assert(isCorrestType(...))`, see `ConstantNode`. + */ + def isCorrectType[T <: SType](value: Any, tpe: T): Boolean = value match { + case c: Coll[_] => tpe match { + case STuple(items) => c.tItem == RType.AnyType && c.length == items.length + case tpeColl: SCollection[_] => true + case _ => sys.error(s"Collection value $c has unexpected type $tpe") + } + case _: Option[_] => tpe.isOption + case _: Tuple2[_, _] => tpe.isTuple && tpe.asTuple.items.length == 2 + case _: Boolean => tpe == SBoolean + case _: Byte => tpe == SByte + case _: Short => tpe == SShort + case _: Int => tpe == SInt + case _: Long => tpe == SLong + case _: BigInt => tpe == SBigInt + case _: String => tpe == SString + case _: GroupElement => tpe.isGroupElement + case _: SigmaProp => tpe.isSigmaProp + case _: AvlTree => tpe.isAvlTree + case _: Box => tpe.isBox + case _: PreHeader => tpe == SPreHeader + case _: Header => tpe == SHeader + case _: Context => tpe == SContext + case _: Function1[_, _] => tpe.isFunc + case _: Unit => tpe == SUnit + case _ => false + } + + /** This JVM specific methods are used in Ergo node which won't be JS cross-compiled. */ + implicit class EcpOps(val p: Ecp) extends AnyVal { + def getCurve: ECCurve = p.value.getCurve + def isInfinity: Boolean = CryptoFacade.isInfinityPoint(p) + def add(p2: Ecp): Ecp = CryptoFacade.multiplyPoints(p, p2) + def multiply(n: BigInteger): Ecp = CryptoFacade.exponentiatePoint(p, n) + } + + /** This JVM specific methods are used in Ergo node which won't be JS cross-compiled. */ + implicit class BcDlogGroupOps(val group: BcDlogGroup) extends AnyVal { + def curve: Curve = group.ctx.asInstanceOf[CryptoContextJvm].curve + } +} diff --git a/interpreter/src/main/scala/org/ergoplatform/ErgoAddress.scala b/interpreter/shared/src/main/scala/org/ergoplatform/ErgoAddress.scala similarity index 99% rename from interpreter/src/main/scala/org/ergoplatform/ErgoAddress.scala rename to interpreter/shared/src/main/scala/org/ergoplatform/ErgoAddress.scala index 251df04e1f..b847ae7776 100644 --- a/interpreter/src/main/scala/org/ergoplatform/ErgoAddress.scala +++ b/interpreter/shared/src/main/scala/org/ergoplatform/ErgoAddress.scala @@ -9,7 +9,7 @@ import scorex.util.encode.Base58 import sigmastate.Values._ import sigmastate._ import sigmastate.basics.DLogProtocol.{ProveDlogProp, ProveDlog} -import sigmastate.lang.exceptions.SigmaException +import sigmastate.exceptions.SigmaException import sigmastate.serialization._ import sigmastate.utxo.{DeserializeContext, Slice} import special.collection.Coll diff --git a/interpreter/src/main/scala/org/ergoplatform/ErgoBox.scala b/interpreter/shared/src/main/scala/org/ergoplatform/ErgoBox.scala similarity index 96% rename from interpreter/src/main/scala/org/ergoplatform/ErgoBox.scala rename to interpreter/shared/src/main/scala/org/ergoplatform/ErgoBox.scala index 51b6434253..37a6e96193 100644 --- a/interpreter/src/main/scala/org/ergoplatform/ErgoBox.scala +++ b/interpreter/shared/src/main/scala/org/ergoplatform/ErgoBox.scala @@ -1,7 +1,7 @@ package org.ergoplatform import scorex.utils.{Ints, Shorts} -import org.ergoplatform.ErgoBox.{NonMandatoryRegisterId, TokenId} +import org.ergoplatform.ErgoBox.{NonMandatoryRegisterId, Token} import org.ergoplatform.settings.ErgoAlgos import scorex.crypto.authds.ADKey import scorex.crypto.hash.{Blake2b256, Digest32} @@ -51,7 +51,7 @@ import scala.runtime.ScalaRunTime class ErgoBox( override val value: Long, override val ergoTree: ErgoTree, - override val additionalTokens: Coll[(TokenId, Long)] = Colls.emptyColl[(TokenId, Long)], + override val additionalTokens: Coll[Token] = Colls.emptyColl[Token], override val additionalRegisters: Map[NonMandatoryRegisterId, _ <: EvaluatedValue[_ <: SType]] = Map.empty, val transactionId: ModifierId, val index: Short, @@ -103,10 +103,13 @@ object ErgoBox { val size: Short = 32 } - type TokenId = Digest32 + /** Token id is tagged collection of bytes. */ + type TokenId = Digest32Coll object TokenId { val size: Short = 32 } + /** Helper synonym for a token with a value attached. */ + type Token = (Digest32Coll, Long) val MaxBoxSize: Int = SigmaConstants.MaxBoxSize.value diff --git a/interpreter/src/main/scala/org/ergoplatform/ErgoBoxAssets.scala b/interpreter/shared/src/main/scala/org/ergoplatform/ErgoBoxAssets.scala similarity index 100% rename from interpreter/src/main/scala/org/ergoplatform/ErgoBoxAssets.scala rename to interpreter/shared/src/main/scala/org/ergoplatform/ErgoBoxAssets.scala diff --git a/interpreter/src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala b/interpreter/shared/src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala similarity index 93% rename from interpreter/src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala rename to interpreter/shared/src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala index dbdc46296b..8d5765caf5 100644 --- a/interpreter/src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala +++ b/interpreter/shared/src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala @@ -15,6 +15,7 @@ import sigmastate.eval.Extensions._ import sigmastate.serialization.ErgoTreeSerializer.DefaultSerializer import sigmastate.util.safeNewArray import debox.cfor +import special.collection.Extensions.CollOps import scala.collection.{immutable, mutable} import scala.runtime.ScalaRunTime @@ -36,7 +37,7 @@ import scala.runtime.ScalaRunTime class ErgoBoxCandidate(val value: Long, val ergoTree: ErgoTree, val creationHeight: Int, - val additionalTokens: Coll[(TokenId, Long)] = Colls.emptyColl, + val additionalTokens: Coll[Token] = Colls.emptyColl, val additionalRegisters: Map[NonMandatoryRegisterId, _ <: EvaluatedValue[_ <: SType]] = Map()) extends ErgoBoxAssets { @@ -73,7 +74,7 @@ class ErgoBoxCandidate(val value: Long, case TokensRegId => // TODO optimize using mapFirst // However this requires fixing Coll equality (see property("ErgoBox test vectors")) - Some(Constant(additionalTokens.map { case (id, v) => (id.toColl, v) }.asWrappedType, STokensRegType)) + Some(Constant(additionalTokens.map(identity).asWrappedType, STokensRegType)) case ReferenceRegId => val tupleVal = (creationHeight, ErgoBoxCandidate.UndefinedBoxRef) Some(Constant(tupleVal.asWrappedType, SReferenceRegType)) @@ -106,7 +107,7 @@ class ErgoBoxCandidate(val value: Long, lazy val tokens: Map[ModifierId, Long] = { val merged = new mutable.HashMap[ModifierId, Long] additionalTokens.foreach { case (id, amount) => - val mId = bytesToId(id) + val mId = bytesToId(id.toArray) merged.put(mId, java7.compat.Math.addExact(merged.getOrElse(mId, 0L), amount)) } merged.toMap @@ -136,7 +137,7 @@ object ErgoBoxCandidate { * @param w writer of serialized bytes */ def serializeBodyWithIndexedDigests(box: ErgoBoxCandidate, - tokensInTx: Option[Coll[TokenId]], + tokensInTx: Option[Coll[Digest32Coll]], w: SigmaByteWriter): Unit = { w.putULong(box.value) w.putBytes(DefaultSerializer.serializeErgoTree(box.ergoTree)) @@ -151,11 +152,11 @@ object ErgoBoxCandidate { val id = ids(i) val amount = amounts(i) if (tokensInTx.isDefined) { - val tokenIndex = tokensInTx.get.indexWhere(v => util.Arrays.equals(v, id), 0) + val tokenIndex = tokensInTx.get.indexWhere(_ == id, 0) // using equality on Coll if (tokenIndex == -1) sys.error(s"failed to find token id ($id) in tx's digest index") w.putUInt(tokenIndex) } else { - w.putBytes(id) + w.putBytes(id.toArray) } w.putULong(amount) } @@ -187,7 +188,7 @@ object ErgoBoxCandidate { /** Helper method to parse [[ErgoBoxCandidate]] previously serialized by * [[serializeBodyWithIndexedDigests()]]. */ - def parseBodyWithIndexedDigests(digestsInTx: Array[Array[Byte]], r: SigmaByteReader): ErgoBoxCandidate = { + def parseBodyWithIndexedDigests(digestsInTx: Array[TokenId], r: SigmaByteReader): ErgoBoxCandidate = { val previousPositionLimit = r.positionLimit r.positionLimit = r.position + ErgoBox.MaxBoxSize val value = r.getULong() // READ @@ -198,7 +199,7 @@ object ErgoBoxCandidate { // and ErgoBoxCandidate with negative creation height is created, which is then invalidated // during transaction validation. See validation rule # 122 in the Ergo node (ValidationRules.scala) val nTokens = r.getUByte() // READ - val tokenIds = safeNewArray[Array[Byte]](nTokens) + val tokenIds = safeNewArray[TokenId](nTokens) val tokenAmounts = safeNewArray[Long](nTokens) if (digestsInTx != null) { val nDigests = digestsInTx.length @@ -216,8 +217,8 @@ object ErgoBoxCandidate { } else { val tokenIdSize = TokenId.size // optimization: access the value once cfor(0)(_ < nTokens, _ + 1) { i => - tokenIds(i) = r.getBytes(tokenIdSize) // READ - tokenAmounts(i) = r.getULong() // READ + tokenIds(i) = Digest32Coll @@@ Colls.fromArray(r.getBytes(tokenIdSize)) // READ + tokenAmounts(i) = r.getULong() // READ } } val tokens = Colls.pairCollFromArrays(tokenIds, tokenAmounts) @@ -234,7 +235,7 @@ object ErgoBoxCandidate { r.positionLimit = previousPositionLimit new ErgoBoxCandidate( value, tree, creationHeight, - tokens.asInstanceOf[Coll[(TokenId, Long)]], b.result()) + tokens, b.result()) } override def parse(r: SigmaByteReader): ErgoBoxCandidate = { diff --git a/interpreter/src/main/scala/org/ergoplatform/ErgoLikeContext.scala b/interpreter/shared/src/main/scala/org/ergoplatform/ErgoLikeContext.scala similarity index 99% rename from interpreter/src/main/scala/org/ergoplatform/ErgoLikeContext.scala rename to interpreter/shared/src/main/scala/org/ergoplatform/ErgoLikeContext.scala index bacc5c9cd9..17ed46449d 100644 --- a/interpreter/src/main/scala/org/ergoplatform/ErgoLikeContext.scala +++ b/interpreter/shared/src/main/scala/org/ergoplatform/ErgoLikeContext.scala @@ -7,8 +7,8 @@ import sigmastate._ import sigmastate.eval.Extensions._ import sigmastate.eval._ import sigmastate.interpreter.ErgoTreeEvaluator.DataEnv -import sigmastate.interpreter.{Interpreter, InterpreterContext, ErgoTreeEvaluator, ContextExtension} -import sigmastate.lang.exceptions.InterpreterException +import sigmastate.interpreter.{ContextExtension, ErgoTreeEvaluator, Interpreter, InterpreterContext} +import sigmastate.exceptions.InterpreterException import sigmastate.serialization.OpCodes import sigmastate.serialization.OpCodes.OpCode import special.collection.Coll diff --git a/interpreter/src/main/scala/org/ergoplatform/ErgoLikeInterpreter.scala b/interpreter/shared/src/main/scala/org/ergoplatform/ErgoLikeInterpreter.scala similarity index 100% rename from interpreter/src/main/scala/org/ergoplatform/ErgoLikeInterpreter.scala rename to interpreter/shared/src/main/scala/org/ergoplatform/ErgoLikeInterpreter.scala diff --git a/interpreter/src/main/scala/org/ergoplatform/ErgoLikeTransaction.scala b/interpreter/shared/src/main/scala/org/ergoplatform/ErgoLikeTransaction.scala similarity index 96% rename from interpreter/src/main/scala/org/ergoplatform/ErgoLikeTransaction.scala rename to interpreter/shared/src/main/scala/org/ergoplatform/ErgoLikeTransaction.scala index 5117485d21..5ad3e08207 100644 --- a/interpreter/src/main/scala/org/ergoplatform/ErgoLikeTransaction.scala +++ b/interpreter/shared/src/main/scala/org/ergoplatform/ErgoLikeTransaction.scala @@ -124,12 +124,12 @@ object ErgoLikeTransactionSerializer extends SigmaSerializer[ErgoLikeTransaction val tokenIds = tx.outputCandidates.toColl .flatMap(box => box.additionalTokens.map(t => t._1)) - val distinctTokenIds = tokenIds.map(_.toColl).distinct.map(_.toArray.asInstanceOf[TokenId]) + val distinctTokenIds = tokenIds.distinct // rely on equality of Coll w.putUInt(distinctTokenIds.length) cfor(0)(_ < distinctTokenIds.length, _ + 1) { i => val tokenId = distinctTokenIds(i) - w.putBytes(tokenId) + w.putBytes(tokenId.toArray) } // serialize outputs val outs = tx.outputCandidates @@ -161,9 +161,9 @@ object ErgoLikeTransactionSerializer extends SigmaSerializer[ErgoLikeTransaction // NO-FORK: in v5.x getUIntExact may throw Int overflow exception // in v4.x r.getUInt().toInt is used and may return negative Int instead of the overflow // in which case the array allocation will throw NegativeArraySizeException - val tokens = safeNewArray[Array[Byte]](tokensCount) + val tokens = safeNewArray[TokenId](tokensCount) cfor(0)(_ < tokensCount, _ + 1) { i => - tokens(i) = r.getBytes(TokenId.size) + tokens(i) = Digest32Coll @@@ Colls.fromArray(r.getBytes(TokenId.size)) } // parse outputs diff --git a/interpreter/src/main/scala/org/ergoplatform/ErgoScriptPredef.scala b/interpreter/shared/src/main/scala/org/ergoplatform/ErgoTreePredef.scala similarity index 76% rename from interpreter/src/main/scala/org/ergoplatform/ErgoScriptPredef.scala rename to interpreter/shared/src/main/scala/org/ergoplatform/ErgoTreePredef.scala index 8a6c2fe06f..d8fbf16b8f 100644 --- a/interpreter/src/main/scala/org/ergoplatform/ErgoScriptPredef.scala +++ b/interpreter/shared/src/main/scala/org/ergoplatform/ErgoTreePredef.scala @@ -1,36 +1,25 @@ package org.ergoplatform -import org.ergoplatform.ErgoAddressEncoder.NetworkPrefix import org.ergoplatform.settings.MonetarySettings -import sigmastate.SCollection.SByteArray -import sigmastate.Values._ import sigmastate.basics.DLogProtocol.ProveDlog -import sigmastate.eval.IRContext -import sigmastate.interpreter.CryptoConstants -import sigmastate.lang.Terms.ValueOps -import sigmastate._ -import sigmastate.lang.SigmaCompiler +import sigmastate.basics.CryptoConstants import sigmastate.serialization.ErgoTreeSerializer.DefaultSerializer +import sigmastate.SCollection.SByteArray +import sigmastate.Values.{LongConstant, SigmaPropConstant, IntArrayConstant, TrueSigmaProp, Value, FalseSigmaProp, SigmaPropValue, IntConstant, ErgoTree} import sigmastate.utxo._ +import sigmastate._ +import sigmastate.lang.Terms.ValueOps -object ErgoScriptPredef { - - import sigmastate.interpreter.Interpreter._ - - /** Compiles the given ErgoScript `code` into ErgoTree expression. */ - def compileWithCosting(env: ScriptEnv, code: String, networkPrefix: NetworkPrefix)(implicit IR: IRContext): Value[SType] = { - val compiler = new SigmaCompiler(networkPrefix) - val res = compiler.compile(env, code) - res.buildTree - } - +object ErgoTreePredef { /** Create ErgoTree with `false` proposition, which is never true. + * * @param headerFlags ErgoTree header flags to be combined with default header * @see ErgoTree.headerWithVersion() */ def FalseProp(headerFlags: Byte): ErgoTree = ErgoTree.withoutSegregation(headerFlags, FalseSigmaProp) /** Create ErgoTree with `true` proposition, which is always true. + * * @param headerFlags ErgoTree header flags to be combined with default header * @see ErgoTree.headerWithVersion() */ @@ -40,10 +29,12 @@ object ErgoScriptPredef { * Byte array value of the serialized reward output script proposition with pk being substituted * with given pk * - * @param delta - number of blocks for which miner should hold this box before spending it + * @param delta - number of blocks for which miner should hold this box before spending it * @param minerPkBytesVal - byte array val for pk to substitute in the reward script */ - def expectedMinerOutScriptBytesVal(delta: Int, minerPkBytesVal: Value[SByteArray]): Value[SByteArray] = { + def expectedMinerOutScriptBytesVal( + delta: Int, + minerPkBytesVal: Value[SByteArray]): Value[SByteArray] = { val genericPk = ProveDlog(CryptoConstants.dlogGroup.generator) val genericMinerProp = rewardOutputScript(delta, genericPk) val genericMinerPropBytes = DefaultSerializer.serializeErgoTree(genericMinerProp) @@ -83,7 +74,6 @@ object ErgoScriptPredef { def emissionBoxProp(s: MonetarySettings): ErgoTree = { val rewardOut = ByIndex(Outputs, IntConstant(0)) val minerOut = ByIndex(Outputs, IntConstant(1)) - val minersReward = s.fixedRate - s.foundersInitialReward val minersFixedRatePeriod = s.fixedRatePeriod + 2 * s.epochLength val epoch = Plus(IntConstant(1), Divide(Minus(Height, IntConstant(s.fixedRatePeriod)), IntConstant(s.epochLength))) @@ -97,7 +87,6 @@ object ErgoScriptPredef { val correctCoinsConsumed = EQ(coinsToIssue, Minus(ExtractAmount(Self), ExtractAmount(rewardOut))) val lastCoins = LE(ExtractAmount(Self), s.oneEpochReduction) val outputsNum = EQ(SizeOf(Outputs), 2) - val correctMinerOutput = AND( EQ(ExtractScriptBytes(minerOut), expectedMinerOutScriptBytesVal(s.minerRewardDelay, MinerPubkey)), EQ(Height, boxCreationHeight(minerOut)) @@ -133,7 +122,6 @@ object ErgoScriptPredef { val full15reward = (s.foundersInitialReward - 2 * s.oneEpochReduction) * s.epochLength val full45reward = (s.foundersInitialReward - s.oneEpochReduction) * s.epochLength val fixedRatePeriodMinus1: Int = s.fixedRatePeriod - 1 - If(LT(Height, IntConstant(s.fixedRatePeriod)), Plus( LongConstant(full15reward + full45reward), @@ -172,34 +160,4 @@ object ErgoScriptPredef { */ def boxCreationHeight(box: Value[SBox.type]): Value[SInt.type] = SelectField(ExtractCreationInfo(box), 1).asIntValue - - /** - * Proposition of the box that may be spent by a transaction - * which inputs contains at least `thresholdAmount` of token with id `tokenId`. - * The logic of this script is following - * (v1) INPUTS.flatMap(box => box.tokens.filter(t => t._1 == tokenId).map(t => t._2)).sum >= thresholdAmount - * (v2) INPUTS.flatMap(box => box.tokens).filter(t => t._1 == tokenId).sum >= thresholdAmount - * (v3) INPUTS.map(box => box.tokens.find(t => t._1 == tokenId).map(t => t._2).getOrElse(0)).sum >= thresholdAmount - */ - def tokenThresholdScript(tokenId: Array[Byte], thresholdAmount: Long, networkPrefix: NetworkPrefix) - (implicit IR: IRContext): SigmaPropValue = { - val env = emptyEnv + - ("tokenId" -> tokenId, "thresholdAmount" -> thresholdAmount) - val res = compileWithCosting(env, - """{ - | val sumValues = { (xs: Coll[Long]) => xs.fold(0L, { (acc: Long, amt: Long) => acc + amt }) } - | - | val tokenAmounts = INPUTS.map({ (box: Box) => - | sumValues(box.tokens.map { (tokenPair: (Coll[Byte], Long)) => - | val ourTokenAmount = if (tokenPair._1 == tokenId) tokenPair._2 else 0L - | ourTokenAmount - | }) - | }) - | val total = sumValues(tokenAmounts) - | sigmaProp(total >= thresholdAmount) - |} - """.stripMargin, networkPrefix) - res.asSigmaProp - } - } diff --git a/interpreter/src/main/scala/org/ergoplatform/Input.scala b/interpreter/shared/src/main/scala/org/ergoplatform/Input.scala similarity index 100% rename from interpreter/src/main/scala/org/ergoplatform/Input.scala rename to interpreter/shared/src/main/scala/org/ergoplatform/Input.scala diff --git a/interpreter/src/main/scala/org/ergoplatform/SigmaConstants.scala b/interpreter/shared/src/main/scala/org/ergoplatform/SigmaConstants.scala similarity index 96% rename from interpreter/src/main/scala/org/ergoplatform/SigmaConstants.scala rename to interpreter/shared/src/main/scala/org/ergoplatform/SigmaConstants.scala index 87de4ba08c..ff1251ae10 100644 --- a/interpreter/src/main/scala/org/ergoplatform/SigmaConstants.scala +++ b/interpreter/shared/src/main/scala/org/ergoplatform/SigmaConstants.scala @@ -1,7 +1,7 @@ package org.ergoplatform -import scalan.util.CollectionUtil._ -import sigmastate.interpreter.CryptoConstants +import scalan.util.CollectionUtil.TraversableOps // used in Scala 2.11 +import sigmastate.basics.CryptoConstants /** Descriptor of a constant which represents some size value. * @tparam T type of the constant value diff --git a/interpreter/src/main/scala/org/ergoplatform/dsl/AvlTreeHelpers.scala b/interpreter/shared/src/main/scala/org/ergoplatform/dsl/AvlTreeHelpers.scala similarity index 100% rename from interpreter/src/main/scala/org/ergoplatform/dsl/AvlTreeHelpers.scala rename to interpreter/shared/src/main/scala/org/ergoplatform/dsl/AvlTreeHelpers.scala diff --git a/interpreter/src/main/scala/org/ergoplatform/mining/emission/EmissionRules.scala b/interpreter/shared/src/main/scala/org/ergoplatform/mining/emission/EmissionRules.scala similarity index 100% rename from interpreter/src/main/scala/org/ergoplatform/mining/emission/EmissionRules.scala rename to interpreter/shared/src/main/scala/org/ergoplatform/mining/emission/EmissionRules.scala diff --git a/interpreter/src/main/scala/org/ergoplatform/settings/ErgoAlgos.scala b/interpreter/shared/src/main/scala/org/ergoplatform/settings/ErgoAlgos.scala similarity index 100% rename from interpreter/src/main/scala/org/ergoplatform/settings/ErgoAlgos.scala rename to interpreter/shared/src/main/scala/org/ergoplatform/settings/ErgoAlgos.scala diff --git a/interpreter/shared/src/main/scala/org/ergoplatform/settings/MonetarySettings.scala b/interpreter/shared/src/main/scala/org/ergoplatform/settings/MonetarySettings.scala new file mode 100644 index 0000000000..15f7b9edbb --- /dev/null +++ b/interpreter/shared/src/main/scala/org/ergoplatform/settings/MonetarySettings.scala @@ -0,0 +1,26 @@ +package org.ergoplatform.settings + +import sigmastate.Values.ErgoTree +import org.ergoplatform.ErgoTreePredef +import org.ergoplatform.mining.emission.EmissionRules + +/** + * Configuration file for monetary settings of Ergo chain + * + * @see src/main/resources/application.conf for parameters description + */ +case class MonetarySettings( + fixedRatePeriod: Int = 30 * 2 * 24 * 365, + epochLength: Int = 90 * 24 * 30, + fixedRate: Long = 75L * EmissionRules.CoinsInOneErgo, + oneEpochReduction: Long = 3L * EmissionRules.CoinsInOneErgo, + minerRewardDelay: Int = 720, + foundersInitialReward: Long = 75L * EmissionRules.CoinsInOneErgo / 10) { + val feeProposition: ErgoTree = ErgoTreePredef.feeProposition(minerRewardDelay) + + val feePropositionBytes: Array[Byte] = feeProposition.bytes + + val emissionBoxProposition: ErgoTree = ErgoTreePredef.emissionBoxProp(this) + + val foundersBoxProposition: ErgoTree = ErgoTreePredef.foundationScript(this) +} diff --git a/interpreter/src/main/scala/org/ergoplatform/validation/RuleStatus.scala b/interpreter/shared/src/main/scala/org/ergoplatform/validation/RuleStatus.scala similarity index 100% rename from interpreter/src/main/scala/org/ergoplatform/validation/RuleStatus.scala rename to interpreter/shared/src/main/scala/org/ergoplatform/validation/RuleStatus.scala diff --git a/interpreter/src/main/scala/org/ergoplatform/validation/RuleStatusSerializer.scala b/interpreter/shared/src/main/scala/org/ergoplatform/validation/RuleStatusSerializer.scala similarity index 100% rename from interpreter/src/main/scala/org/ergoplatform/validation/RuleStatusSerializer.scala rename to interpreter/shared/src/main/scala/org/ergoplatform/validation/RuleStatusSerializer.scala diff --git a/interpreter/src/main/scala/org/ergoplatform/validation/SigmaValidationSettings.scala b/interpreter/shared/src/main/scala/org/ergoplatform/validation/SigmaValidationSettings.scala similarity index 100% rename from interpreter/src/main/scala/org/ergoplatform/validation/SigmaValidationSettings.scala rename to interpreter/shared/src/main/scala/org/ergoplatform/validation/SigmaValidationSettings.scala diff --git a/interpreter/src/main/scala/org/ergoplatform/validation/SigmaValidationSettingsSerializer.scala b/interpreter/shared/src/main/scala/org/ergoplatform/validation/SigmaValidationSettingsSerializer.scala similarity index 100% rename from interpreter/src/main/scala/org/ergoplatform/validation/SigmaValidationSettingsSerializer.scala rename to interpreter/shared/src/main/scala/org/ergoplatform/validation/SigmaValidationSettingsSerializer.scala diff --git a/interpreter/src/main/scala/org/ergoplatform/validation/SoftForkChecker.scala b/interpreter/shared/src/main/scala/org/ergoplatform/validation/SoftForkChecker.scala similarity index 100% rename from interpreter/src/main/scala/org/ergoplatform/validation/SoftForkChecker.scala rename to interpreter/shared/src/main/scala/org/ergoplatform/validation/SoftForkChecker.scala diff --git a/interpreter/src/main/scala/org/ergoplatform/validation/ValidationRules.scala b/interpreter/shared/src/main/scala/org/ergoplatform/validation/ValidationRules.scala similarity index 94% rename from interpreter/src/main/scala/org/ergoplatform/validation/ValidationRules.scala rename to interpreter/shared/src/main/scala/org/ergoplatform/validation/ValidationRules.scala index b87cf8107d..1110916e03 100644 --- a/interpreter/src/main/scala/org/ergoplatform/validation/ValidationRules.scala +++ b/interpreter/shared/src/main/scala/org/ergoplatform/validation/ValidationRules.scala @@ -1,10 +1,9 @@ package org.ergoplatform.validation import scalan.util.Extensions.toUByte -import sigmastate.Values.{ErgoTree, SValue} +import sigmastate.Values.{SValue, ErgoTree} import sigmastate._ -import sigmastate.eval.IRContext -import sigmastate.lang.exceptions._ +import sigmastate.exceptions.{InvalidOpCode, SerializerException, ReaderPositionLimitExceeded, SigmaException, InterpreterException} import sigmastate.serialization.OpCodes.OpCode import sigmastate.serialization.TypeSerializer.embeddableIdToType import sigmastate.serialization.{OpCodes, ValueSerializer} @@ -93,6 +92,7 @@ object ValidationRules { object CheckDeserializedScriptIsSigmaProp extends ValidationRule(1001, "Deserialized script should have SigmaProp type") { + /** @param root candidate node before it is added as a root of ErgoTree */ final def apply[T](root: SValue): Unit = { checkRule() if (!root.tpe.isSigmaProp) { @@ -127,18 +127,21 @@ object ValidationRules { object CheckCalcFunc extends ValidationRule(1005, "If SigmaProp.isProven method calls exists in the given function,\n then it is the last operation") + /** This rule is not use in v5.x, keep the commented code below as a precise + * documentation of its semantics. + */ object CheckTupleType extends ValidationRule(1006, "Supported tuple type.") with SoftForkWhenReplaced { - final def apply[Ctx <: IRContext, T](ctx: Ctx)(e: ctx.Elem[_]): Unit = { - checkRule() - val condition = e match { - case _: ctx.PairElem[_,_] => true - case _ => false - } - if (!condition) { - throwValidationException(new SigmaException(s"Invalid tuple type $e"), Array[ctx.Elem[_]](e)) - } - } +// final def apply[Ctx <: IRContext, T](ctx: Ctx)(e: ctx.Elem[_]): Unit = { +// checkRule() +// val condition = e match { +// case _: ctx.PairElem[_,_] => true +// case _ => false +// } +// if (!condition) { +// throwValidationException(new SigmaException(s"Invalid tuple type $e"), Array[ctx.Elem[_]](e)) +// } +// } } object CheckPrimitiveTypeCode extends ValidationRule(1007, diff --git a/interpreter/src/main/scala/sigmastate/AvlTreeData.scala b/interpreter/shared/src/main/scala/sigmastate/AvlTreeData.scala similarity index 92% rename from interpreter/src/main/scala/sigmastate/AvlTreeData.scala rename to interpreter/shared/src/main/scala/sigmastate/AvlTreeData.scala index 81aafa1a19..2f07156c0e 100644 --- a/interpreter/src/main/scala/sigmastate/AvlTreeData.scala +++ b/interpreter/shared/src/main/scala/sigmastate/AvlTreeData.scala @@ -1,9 +1,8 @@ package sigmastate import java.util.{Arrays, Objects} - import scorex.crypto.authds.ADDigest -import sigmastate.interpreter.CryptoConstants +import sigmastate.basics.CryptoConstants import sigmastate.serialization.SigmaSerializer import sigmastate.utils.{SigmaByteReader, SigmaByteWriter} @@ -79,6 +78,12 @@ object AvlTreeData { AvlTreeFlags.AllOperationsAllowed, keyLength = 32) + /** Create [[AvlTreeData]] with the given digest and all operations enabled. */ + def avlTreeFromDigest(digest: ADDigest): AvlTreeData = { + val flags = AvlTreeFlags(insertAllowed = true, updateAllowed = true, removeAllowed = true) + AvlTreeData(digest, flags, CryptoConstants.hashLength) + } + object serializer extends SigmaSerializer[AvlTreeData, AvlTreeData] { override def serialize(data: AvlTreeData, w: SigmaByteWriter): Unit = { diff --git a/interpreter/src/main/scala/sigmastate/CostKind.scala b/interpreter/shared/src/main/scala/sigmastate/CostKind.scala similarity index 100% rename from interpreter/src/main/scala/sigmastate/CostKind.scala rename to interpreter/shared/src/main/scala/sigmastate/CostKind.scala diff --git a/interpreter/src/main/scala/sigmastate/DataValueComparer.scala b/interpreter/shared/src/main/scala/sigmastate/DataValueComparer.scala similarity index 99% rename from interpreter/src/main/scala/sigmastate/DataValueComparer.scala rename to interpreter/shared/src/main/scala/sigmastate/DataValueComparer.scala index b1c9eb4202..eab9c461b8 100644 --- a/interpreter/src/main/scala/sigmastate/DataValueComparer.scala +++ b/interpreter/shared/src/main/scala/sigmastate/DataValueComparer.scala @@ -7,7 +7,7 @@ import sigmastate.Values.SigmaBoolean import sigmastate.basics.DLogProtocol.ProveDlog import sigmastate.basics.ProveDHTuple import sigmastate.eval.SigmaDsl -import sigmastate.interpreter.CryptoConstants.EcPointType +import sigmastate.basics.CryptoConstants.EcPointType 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} diff --git a/interpreter/src/main/scala/sigmastate/InterpreterReflection.scala b/interpreter/shared/src/main/scala/sigmastate/InterpreterReflection.scala similarity index 99% rename from interpreter/src/main/scala/sigmastate/InterpreterReflection.scala rename to interpreter/shared/src/main/scala/sigmastate/InterpreterReflection.scala index 6691c10766..cce29d51da 100644 --- a/interpreter/src/main/scala/sigmastate/InterpreterReflection.scala +++ b/interpreter/shared/src/main/scala/sigmastate/InterpreterReflection.scala @@ -1,7 +1,6 @@ package sigmastate import org.ergoplatform.ErgoBox.RegisterId -import scalan.GraphIRReflection import scalan.reflection.CommonReflection.registerClassEntry import scalan.reflection.{SRConstructor, mkConstructor, mkMethod, SRMethod} import sigmastate.SAvlTree.KeyValueColl @@ -24,7 +23,7 @@ import special.sigma.{SigmaDslBuilder, AvlTree} * Only information that is needed at runtime is registered. */ object InterpreterReflection { - val reflection = (GraphIRReflection, CoreLibReflection) + val reflection = CoreLibReflection registerClassEntry(classOf[AND], constructors = Array( diff --git a/interpreter/shared/src/main/scala/sigmastate/JitCost.scala b/interpreter/shared/src/main/scala/sigmastate/JitCost.scala new file mode 100644 index 0000000000..dd6d57fe9f --- /dev/null +++ b/interpreter/shared/src/main/scala/sigmastate/JitCost.scala @@ -0,0 +1,36 @@ +package sigmastate + +/** Represents cost estimation computed by JITC interpreter. + * The JITC costs use 10x more accurate scale comparing to block cost values. + * + * @see toBlockCost + */ +case class JitCost private[sigmastate](private[sigmastate] val value: Int) extends AnyVal { + /** Adds two cost values. */ + def +(y: JitCost): JitCost = + new JitCost(java7.compat.Math.addExact(value, y.value)) + + /** Multiplies this cost to the given integer. */ + def *(n: Int): JitCost = + new JitCost(java7.compat.Math.multiplyExact(value, n)) + + /** Divides this cost by the given integer. */ + def /(n: Int): JitCost = + new JitCost(value / n) + + /** Return true if this value > y.value in the normal Int ordering. */ + def >(y: JitCost): Boolean = value > y.value + + /** Return true if this value >= y.value in the normal Int ordering. */ + def >=(y: JitCost): Boolean = value >= y.value + + /** Scales JitCost back to block cost value. This is inverse to JitCost.fromBlockCost. */ + def toBlockCost: Int = value / 10 +} + +object JitCost { + /** Scales the given block cost to the JitCost scale. This is inverse to toBlockCost */ + def fromBlockCost(blockCost: Int): JitCost = + new JitCost(java7.compat.Math.multiplyExact(blockCost, 10)) +} + diff --git a/interpreter/src/main/scala/sigmastate/Operations.scala b/interpreter/shared/src/main/scala/sigmastate/Operations.scala similarity index 100% rename from interpreter/src/main/scala/sigmastate/Operations.scala rename to interpreter/shared/src/main/scala/sigmastate/Operations.scala diff --git a/interpreter/src/main/scala/sigmastate/SigSerializer.scala b/interpreter/shared/src/main/scala/sigmastate/SigSerializer.scala similarity index 96% rename from interpreter/src/main/scala/sigmastate/SigSerializer.scala rename to interpreter/shared/src/main/scala/sigmastate/SigSerializer.scala index 92c18463dc..07ff33c5bc 100644 --- a/interpreter/src/main/scala/sigmastate/SigSerializer.scala +++ b/interpreter/shared/src/main/scala/sigmastate/SigSerializer.scala @@ -1,26 +1,26 @@ package sigmastate -import com.typesafe.scalalogging.LazyLogging import sigmastate.crypto.{BigIntegers, GF2_192_Poly} import scorex.util.encode.Base16 import sigmastate.Values.SigmaBoolean import sigmastate.basics.DLogProtocol.{ProveDlog, SecondDLogProverMessage} import sigmastate.basics.VerifierMessage.Challenge -import sigmastate.basics.{ProveDHTuple, SecondDiffieHellmanTupleProverMessage} +import sigmastate.basics.{SecondDiffieHellmanTupleProverMessage, ProveDHTuple, CryptoConstants} import sigmastate.interpreter.ErgoTreeEvaluator.{fixedCostOp, perItemCostOp} -import sigmastate.interpreter.{CryptoConstants, ErgoTreeEvaluator, NamedDesc, OperationCostInfo} -import sigmastate.lang.exceptions.SerializerException +import sigmastate.interpreter.{ErgoTreeEvaluator, NamedDesc, OperationCostInfo} import sigmastate.serialization.SigmaSerializer import sigmastate.util.safeNewArray import sigmastate.utils.{Helpers, SigmaByteReader, SigmaByteWriter} import debox.cfor +import sigmastate.exceptions.SerializerException /** Contains implementation of signature (aka proof) serialization. + * * @see toProofBytes, parseAndComputeChallenges */ -object SigSerializer extends LazyLogging { +class SigSerializer { /** Log warning message using this class's logger. */ - def warn(msg: String) = logger.warn(msg) + def warn(msg: String) = println(msg) /** A size of challenge in Sigma protocols, in bits. */ val hashSize = CryptoConstants.soundnessBits / 8 @@ -263,4 +263,6 @@ object SigSerializer extends LazyLogging { } } -} \ No newline at end of file +} + +object SigSerializer extends SigSerializer \ No newline at end of file diff --git a/interpreter/src/main/scala/sigmastate/UncheckedTree.scala b/interpreter/shared/src/main/scala/sigmastate/UncheckedTree.scala similarity index 100% rename from interpreter/src/main/scala/sigmastate/UncheckedTree.scala rename to interpreter/shared/src/main/scala/sigmastate/UncheckedTree.scala diff --git a/interpreter/src/main/scala/sigmastate/UnprovenTree.scala b/interpreter/shared/src/main/scala/sigmastate/UnprovenTree.scala similarity index 100% rename from interpreter/src/main/scala/sigmastate/UnprovenTree.scala rename to interpreter/shared/src/main/scala/sigmastate/UnprovenTree.scala diff --git a/interpreter/src/main/scala/sigmastate/Values.scala b/interpreter/shared/src/main/scala/sigmastate/Values.scala similarity index 97% rename from interpreter/src/main/scala/sigmastate/Values.scala rename to interpreter/shared/src/main/scala/sigmastate/Values.scala index f7eb2b10cd..62543daa51 100644 --- a/interpreter/src/main/scala/sigmastate/Values.scala +++ b/interpreter/shared/src/main/scala/sigmastate/Values.scala @@ -8,14 +8,14 @@ import org.ergoplatform.validation.ValidationException import scalan.{Nullable, RType} import scalan.util.CollectionUtil._ import sigmastate.SCollection.{SByteArray, SIntArray} -import sigmastate.interpreter.CryptoConstants.EcPointType -import sigmastate.interpreter.{CompanionDesc, CryptoConstants, ErgoTreeEvaluator, Interpreter, NamedDesc} -import sigmastate.serialization.{ConstantStore, OpCodes, _} +import sigmastate.basics.CryptoConstants.EcPointType +import sigmastate.interpreter.{CompanionDesc, ErgoTreeEvaluator, Interpreter, NamedDesc} +import sigmastate.serialization._ import sigmastate.serialization.OpCodes._ import sigmastate.TrivialProp.{FalseProp, TrueProp} import sigmastate.Values.ErgoTree.substConstants import sigmastate.basics.DLogProtocol.ProveDlog -import sigmastate.basics.ProveDHTuple +import sigmastate.basics.{ProveDHTuple, CryptoConstants} import sigmastate.lang.Terms._ import sigmastate.utxo._ import sigmastate.eval._ @@ -23,7 +23,7 @@ import sigmastate.eval.Extensions._ import scalan.util.Extensions.ByteOps import sigmastate.interpreter.ErgoTreeEvaluator._ import debox.cfor - +import sigmastate.exceptions.InterpreterException import scala.language.implicitConversions import scala.reflect.ClassTag import sigmastate.lang.CheckingSigmaBuilder._ @@ -32,8 +32,8 @@ import sigmastate.serialization.transformers.ProveDHTupleSerializer import sigmastate.utils.{SigmaByteReader, SigmaByteWriter} import special.sigma.{AvlTree, Header, PreHeader, _} import sigmastate.lang.SourceContext -import sigmastate.lang.exceptions.InterpreterException import sigmastate.util.safeNewArray +import sigmastate.crypto.Platform import special.collection.Coll import scala.collection.compat.immutable.ArraySeq @@ -324,7 +324,7 @@ object Values { * @see Constant */ 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") + require(Platform.isCorrectType(value, tpe), s"Invalid type of constant value $value, expected type $tpe") override def companion: ValueCompanion = Constant override def opCode: OpCode = companion.opCode override def opName: String = s"Const" @@ -374,37 +374,6 @@ object Values { case _ => None } - /** Checks that the type of the value corresponds to the descriptor `tpe`. - * If the value has complex structure only root type constructor is checked. - * NOTE, this is surface check with possible false positives, but it is ok - * when used in assertions, like `assert(isCorrestType(...))`, see `ConstantNode`. - */ - def isCorrectType[T <: SType](value: Any, tpe: T): Boolean = value match { - case c: Coll[_] => tpe match { - case STuple(items) => c.tItem == RType.AnyType && c.length == items.length - case tpeColl: SCollection[_] => true - case _ => sys.error(s"Collection value $c has unexpected type $tpe") - } - case _: Option[_] => tpe.isOption - case _: Tuple2[_,_] => tpe.isTuple && tpe.asTuple.items.length == 2 - case _: Boolean => tpe == SBoolean - case _: Byte => tpe == SByte - case _: Short => tpe == SShort - case _: Int => tpe == SInt - case _: Long => tpe == SLong - case _: BigInt => tpe == SBigInt - case _: String => tpe == SString - case _: GroupElement => tpe.isGroupElement - case _: SigmaProp => tpe.isSigmaProp - case _: AvlTree => tpe.isAvlTree - case _: Box => tpe.isBox - case _: PreHeader => tpe == SPreHeader - case _: Header => tpe == SHeader - case _: Context => tpe == SContext - case _: Function1[_,_] => tpe.isFunc - case _: Unit => tpe == SUnit - case _ => false - } } /** Placeholder for a constant in ErgoTree. Zero based index in ErgoTree.constants array. */ @@ -743,6 +712,7 @@ object Values { override def companion = this override def opCode: OpCode = TrueCode override def costKind: FixedCost = Constant.costKind + override def toString: String = "TrueLeaf" } /** ErgoTree node which represents `false` literal. */ @@ -750,6 +720,7 @@ object Values { override def companion = this override def opCode: OpCode = FalseCode override def costKind: FixedCost = Constant.costKind + override def toString: String = "FalseLeaf" } trait NotReadyValueBoolean extends NotReadyValue[SBoolean.type] { diff --git a/interpreter/src/main/scala/sigmastate/basics/BcDlogGroup.scala b/interpreter/shared/src/main/scala/sigmastate/basics/BcDlogGroup.scala similarity index 100% rename from interpreter/src/main/scala/sigmastate/basics/BcDlogGroup.scala rename to interpreter/shared/src/main/scala/sigmastate/basics/BcDlogGroup.scala diff --git a/interpreter/src/main/scala/sigmastate/interpreter/CryptoConstants.scala b/interpreter/shared/src/main/scala/sigmastate/basics/CryptoConstants.scala similarity index 91% rename from interpreter/src/main/scala/sigmastate/interpreter/CryptoConstants.scala rename to interpreter/shared/src/main/scala/sigmastate/basics/CryptoConstants.scala index 1d4a3a2d28..b82892291c 100644 --- a/interpreter/src/main/scala/sigmastate/interpreter/CryptoConstants.scala +++ b/interpreter/shared/src/main/scala/sigmastate/basics/CryptoConstants.scala @@ -1,8 +1,6 @@ -package sigmastate.interpreter +package sigmastate.basics import java.math.BigInteger -import java.security.SecureRandom -import sigmastate.basics.{BcDlogGroup, SecP256K1Group} import sigmastate.crypto.Ecp /** Constants used in crypto operations implementation. */ @@ -17,7 +15,7 @@ object CryptoConstants { val dlogGroup: BcDlogGroup = SecP256K1Group /** Secure random generator used in the signature scheme. */ - val secureRandom: SecureRandom = dlogGroup.secureRandom + val secureRandom: sigmastate.crypto.SecureRandom = dlogGroup.secureRandom /** Size of the binary representation of any group element (2 ^ groupSizeBits == ) */ val groupSizeBits: Int = 256 @@ -51,5 +49,4 @@ object CryptoConstants { secureRandom.nextBytes(bytes) bytes } - } diff --git a/interpreter/src/main/scala/sigmastate/interpreter/CryptoFunctions.scala b/interpreter/shared/src/main/scala/sigmastate/basics/CryptoFunctions.scala similarity index 95% rename from interpreter/src/main/scala/sigmastate/interpreter/CryptoFunctions.scala rename to interpreter/shared/src/main/scala/sigmastate/basics/CryptoFunctions.scala index 44cbfa73e3..58eaa70598 100644 --- a/interpreter/src/main/scala/sigmastate/interpreter/CryptoFunctions.scala +++ b/interpreter/shared/src/main/scala/sigmastate/basics/CryptoFunctions.scala @@ -1,4 +1,4 @@ -package sigmastate.interpreter +package sigmastate.basics import scorex.crypto.hash.Blake2b256 @@ -16,5 +16,4 @@ object CryptoFunctions { Array.copy(h, 0, res, 0, soundnessBytes) res } - } diff --git a/interpreter/src/main/scala/sigmastate/basics/DLogProtocol.scala b/interpreter/shared/src/main/scala/sigmastate/basics/DLogProtocol.scala similarity index 97% rename from interpreter/src/main/scala/sigmastate/basics/DLogProtocol.scala rename to interpreter/shared/src/main/scala/sigmastate/basics/DLogProtocol.scala index 38cfe1d8cb..492b054b0e 100644 --- a/interpreter/src/main/scala/sigmastate/basics/DLogProtocol.scala +++ b/interpreter/shared/src/main/scala/sigmastate/basics/DLogProtocol.scala @@ -9,8 +9,7 @@ import scorex.util.encode.Base16 import sigmastate._ import sigmastate.eval._ import sigmastate.basics.VerifierMessage.Challenge -import sigmastate.interpreter.CryptoConstants.{EcPointType, dlogGroup} -import sigmastate.interpreter.CryptoConstants +import CryptoConstants.{EcPointType, dlogGroup} import sigmastate.serialization.{OpCodes, GroupElementSerializer} import sigmastate.serialization.OpCodes.OpCode import special.sigma.SigmaProp diff --git a/interpreter/src/main/scala/sigmastate/basics/DiffieHellmanTupleProtocol.scala b/interpreter/shared/src/main/scala/sigmastate/basics/DiffieHellmanTupleProtocol.scala similarity index 96% rename from interpreter/src/main/scala/sigmastate/basics/DiffieHellmanTupleProtocol.scala rename to interpreter/shared/src/main/scala/sigmastate/basics/DiffieHellmanTupleProtocol.scala index 9122cc78cd..08e9dd2ab7 100644 --- a/interpreter/src/main/scala/sigmastate/basics/DiffieHellmanTupleProtocol.scala +++ b/interpreter/shared/src/main/scala/sigmastate/basics/DiffieHellmanTupleProtocol.scala @@ -7,8 +7,7 @@ import sigmastate.Values.Value.PropositionCode import sigmastate._ import sigmastate.basics.VerifierMessage.Challenge import sigmastate.eval.SigmaDsl -import sigmastate.interpreter.CryptoConstants.EcPointType -import sigmastate.interpreter.CryptoConstants +import CryptoConstants.EcPointType import sigmastate.serialization.{OpCodes, GroupElementSerializer} import sigmastate.serialization.OpCodes.OpCode import special.sigma.SigmaProp @@ -27,7 +26,7 @@ case class DiffieHellmanTupleProverInput(w: BigInteger, commonInput: ProveDHTupl object DiffieHellmanTupleProverInput { - import sigmastate.interpreter.CryptoConstants.dlogGroup + import CryptoConstants.dlogGroup def random(): DiffieHellmanTupleProverInput = { val g = dlogGroup.generator @@ -86,7 +85,7 @@ object ProveDHTupleProp { object DiffieHellmanTupleInteractiveProver { - import sigmastate.interpreter.CryptoConstants.dlogGroup + import CryptoConstants.dlogGroup def firstMessage(publicInput: ProveDHTuple): (BigInteger, FirstDiffieHellmanTupleProverMessage) = { val qMinusOne = dlogGroup.order.subtract(BigInteger.ONE) diff --git a/interpreter/src/main/scala/sigmastate/basics/DlogGroup.scala b/interpreter/shared/src/main/scala/sigmastate/basics/DlogGroup.scala similarity index 76% rename from interpreter/src/main/scala/sigmastate/basics/DlogGroup.scala rename to interpreter/shared/src/main/scala/sigmastate/basics/DlogGroup.scala index f7df1c8e3c..07e989d119 100644 --- a/interpreter/src/main/scala/sigmastate/basics/DlogGroup.scala +++ b/interpreter/shared/src/main/scala/sigmastate/basics/DlogGroup.scala @@ -1,12 +1,9 @@ package sigmastate.basics import java.math.BigInteger -import java.security.SecureRandom -import sigmastate.crypto.{CryptoFacade, Ecp} +import sigmastate.crypto.{Ecp, CryptoFacade, SecureRandom} - -/** - * This is the general interface for the discrete logarithm prime-order group. +/** This is the general interface for the discrete logarithm prime-order group. * Every class in the DlogGroup family implements this interface. * * @@ -17,52 +14,44 @@ import sigmastate.crypto.{CryptoFacade, Ecp} * In cryptography, we are interested in groups for which the discrete logarithm problem * (Dlog for short) is assumed to be hard. The most known groups of that kind are some Elliptic curve groups. * - * @tparam ElemType is concrete type */ trait DlogGroup { /** The type of the elements of this Dlog group */ type ElemType = Ecp - val secureRandom = new SecureRandom() + /** Source of secure randomness. This instance is used in all operations of this + * [[DlogGroup]]. + */ + val secureRandom: SecureRandom = CryptoFacade.createSecureRandom() - /** - * The generator g of the group is an element of the group such that, when written multiplicatively, every element - * of the group is a power of g. + /** The generator g of the group is an element of the group such that, when written + * multiplicatively, every element of the group is a power of g. * @return the generator of this Dlog group */ def generator: ElemType - /** - * - * @return the order of this Dlog group - */ + /** The order of this Dlog group */ def order: BigInteger - /** - * - * @return the identity element of this Dlog group - */ + /** The identity element of this Dlog group */ def identity: ElemType - /** - * Checks if the order of this group is greater than `2^numBits` + /** Checks if the order of this group is greater than `2^numBits` * @param numBits * @return true if the order is greater than `2^numBits`;

* false otherwise. */ def orderGreaterThan(numBits: Int): Boolean - /** - * Calculates the inverse of the given GroupElement. + /** Calculates the inverse of the given GroupElement. * @param groupElement to invert * @return the inverse element of the given GroupElement * @throws IllegalArgumentException - **/ + */ def inverseOf(groupElement: ElemType): ElemType - /** - * Raises the base GroupElement to the exponent. The result is another GroupElement. + /** Raises the base GroupElement to the exponent. The result is another GroupElement. * @param exponent * @param base * @return the result of the exponentiation @@ -70,8 +59,7 @@ trait DlogGroup { */ def exponentiate(base: ElemType, exponent: BigInteger): ElemType - /** - * Multiplies two GroupElements + /** Multiplies two GroupElements * @param groupElement1 * @param groupElement2 * @return the multiplication result @@ -79,14 +67,12 @@ trait DlogGroup { */ def multiplyGroupElements(groupElement1: ElemType, groupElement2: ElemType): ElemType - /** - * Creates a random member of this Dlog group + /** Creates a random member of this Dlog group * @return the random element */ def createRandomElement(): ElemType - /** - * Creates a random generator of this Dlog group + /** Creates a random generator of this Dlog group * * @return the random generator */ @@ -103,8 +89,7 @@ trait DlogGroup { randGen } - /** - * Computes the product of several exponentiations of the same base + /** Computes the product of several exponentiations of the same base * and distinct exponents. * An optimization is used to compute it more quickly by keeping in memory * the result of h1, h2, h4,h8,... and using it in the calculation.

@@ -116,16 +101,14 @@ trait DlogGroup { */ def exponentiateWithPreComputedValues(base: ElemType, exponent: BigInteger): ElemType - /** - * This function cleans up any resources used by exponentiateWithPreComputedValues for the requested base. + /** This function cleans up any resources used by exponentiateWithPreComputedValues for the requested base. * It is recommended to call it whenever an application does not need to continue calculating exponentiations for this specific base. * * @param base */ def endExponentiateWithPreComputedValues(base: ElemType) - /** - * This function returns the value k which is the maximum length of a string to be encoded to a Group Element of this group.

+ /** This function returns the value k which is the maximum length of a string to be encoded to a Group Element of this group.

* Any string of length k has a numeric value that is less than (p-1)/2 - 1. * k is the maximum length a binary string is allowed to be in order to encode the said binary string to a group element and vice-versa.

* If a string exceeds the k length it cannot be encoded. diff --git a/interpreter/src/main/scala/sigmastate/basics/SigmaProtocolFunctions.scala b/interpreter/shared/src/main/scala/sigmastate/basics/SigmaProtocolFunctions.scala similarity index 100% rename from interpreter/src/main/scala/sigmastate/basics/SigmaProtocolFunctions.scala rename to interpreter/shared/src/main/scala/sigmastate/basics/SigmaProtocolFunctions.scala diff --git a/interpreter/src/main/scala/sigmastate/crypto/BigIntegers.scala b/interpreter/shared/src/main/scala/sigmastate/crypto/BigIntegers.scala similarity index 87% rename from interpreter/src/main/scala/sigmastate/crypto/BigIntegers.scala rename to interpreter/shared/src/main/scala/sigmastate/crypto/BigIntegers.scala index 0e55f12532..7bab41df79 100644 --- a/interpreter/src/main/scala/sigmastate/crypto/BigIntegers.scala +++ b/interpreter/shared/src/main/scala/sigmastate/crypto/BigIntegers.scala @@ -1,7 +1,7 @@ package sigmastate.crypto import java.math.BigInteger -import java.security.SecureRandom +import sigmastate.crypto.SecureRandom /** Re-implementation in Scala of select set of utility methods from * org.bouncycastle.util.BigIntegers. @@ -101,4 +101,20 @@ object BigIntegers { * @return the resulting positive BigInteger */ def fromUnsignedByteArray(buf: Array[Byte]) = new BigInteger(1, buf) + + /** + * Return the passed in value as an unsigned byte array. + * + * @param value the value to be converted. + * @return a byte array without a leading zero byte if present in the signed encoding. + */ + def asUnsignedByteArray(value: BigInteger): Array[Byte] = { + val bytes = value.toByteArray + if (bytes(0) == 0) { + val tmp = new Array[Byte](bytes.length - 1) + System.arraycopy(bytes, 1, tmp, 0, tmp.length) + return tmp + } + bytes + } } diff --git a/interpreter/src/main/scala/sigmastate/crypto/CryptoContext.scala b/interpreter/shared/src/main/scala/sigmastate/crypto/CryptoContext.scala similarity index 100% rename from interpreter/src/main/scala/sigmastate/crypto/CryptoContext.scala rename to interpreter/shared/src/main/scala/sigmastate/crypto/CryptoContext.scala diff --git a/interpreter/src/main/scala/sigmastate/crypto/CryptoFacade.scala b/interpreter/shared/src/main/scala/sigmastate/crypto/CryptoFacade.scala similarity index 57% rename from interpreter/src/main/scala/sigmastate/crypto/CryptoFacade.scala rename to interpreter/shared/src/main/scala/sigmastate/crypto/CryptoFacade.scala index 3a53bdfa29..3ce337d695 100644 --- a/interpreter/src/main/scala/sigmastate/crypto/CryptoFacade.scala +++ b/interpreter/shared/src/main/scala/sigmastate/crypto/CryptoFacade.scala @@ -7,17 +7,32 @@ import java.math.BigInteger * Cross-platform code should use this facade instead of the Platform object directly. */ object CryptoFacade { + /** Default encoding used for Strings. */ + val Encoding = "UTF-8" + + /** part of the protocol, do not change */ + val SecretKeyLength = 32 + + /** Used as the key parameter of hashHmacSHA512 */ + val BitcoinSeed: Array[Byte] = "Bitcoin seed".getBytes(Encoding) + + /** Number of iteration specified in BIP39 standard. */ + val Pbkdf2Iterations = 2048 + + /** The size of the key in bits. */ + val Pbkdf2KeyLength = 512 + /** Create a new context for cryptographic operations. */ def createCryptoContext(): CryptoContext = Platform.createContext() - /** * Normalization ensures that any projective coordinate is 1, and therefore that the x, y + /** Normalization ensures that any projective coordinate is 1, and therefore that the x, y * coordinates reflect those of the equivalent point in an affine coordinate system. * * @return a new ECPoint instance representing the same point, but with normalized coordinates */ def normalizePoint(p: Ecp): Ecp = Platform.normalizePoint(p) - /** Negate a point. */ + /** Negates the given point by negating its y coordinate. */ def negatePoint(p: Ecp): Ecp = Platform.negatePoint(p) /** Check if a point is infinity. */ @@ -27,7 +42,7 @@ object CryptoFacade { * * @param p point to exponentiate * @param n exponent - * @return p to the power of n (`p^n`) + * @return p to the power of n (`p^n`) i.e. `p + p + ... + p` (n times) */ def exponentiatePoint(p: Ecp, n: BigInteger): Ecp = Platform.exponentiatePoint(p, n) @@ -49,11 +64,29 @@ object CryptoFacade { def encodeFieldElem(p: ECFieldElem): Array[Byte] = Platform.encodeFieldElem(p) /** Byte representation of the given point. + * + * ASN.1, short for Abstract Syntax Notation One, is a standard and notation that + * describes data structures for representing, encoding, transmitting, and decoding + * data. + * + * The ASN.1 encoding of EC point according to this standard can be one of two forms: + * + * Compressed form: This is a shorter form where only the x coordinate and a bit of + * information about the y coordinate are stored. The full y coordinate can be + * recalculated from this information. The compressed form begins with a byte value of + * 02 or 03 (depending on the y coordinate), followed by the x coordinate. + * + * Uncompressed form: This is a longer form where both the x and y coordinates are + * stored. The uncompressed form begins with a byte value of 04, followed by the x + * coordinate, and then the y coordinate. + * + * NOTE, this encoding is not used in ErgoTree and not part of consensus, it is used for + * extended keys (in the wallet) to ensure BIP32 compatibility. * * @param p point to encode * @param compressed if true, generates a compressed point encoding */ - def encodePoint(p: Ecp, compressed: Boolean): Array[Byte] = Platform.encodePoint(p, compressed) + def getASN1Encoding(p: Ecp, compressed: Boolean): Array[Byte] = Platform.getASN1Encoding(p, compressed) /** A [[Curve]] instance describing the elliptic curve of the point p * @@ -96,4 +129,23 @@ object CryptoFacade { * @throws IllegalStateException if the point is not normalized */ def getAffineYCoord(p: Ecp): ECFieldElem = Platform.getAffineYCoord(p) + + /** Create source of secure randomness. */ + def createSecureRandom(): SecureRandom = Platform.createSecureRandom() + + /** Computes HMAC-SHA512 hash of the given data using the specified key. + * + * @param key the secret key used for hashing + * @param data the input data to be hashed + * @return a HMAC-SHA512 hash of the input data + */ + def hashHmacSHA512(key: Array[Byte], data: Array[Byte]): Array[Byte] = + Platform.hashHmacSHA512(key, data) + + /** Generates PBKDF2 key from a mnemonic and passphrase using SHA512 digest. */ + def generatePbkdf2Key(normalizedMnemonic: String, normalizedPass: String): Array[Byte] = + Platform.generatePbkdf2Key(normalizedMnemonic, normalizedPass) + + /** Normalize a sequence of char values using NFKD normalization form. */ + def normalizeChars(chars: Array[Char]): String = Platform.normalizeChars(chars) } diff --git a/interpreter/src/main/scala/sigmastate/crypto/GF2_192.scala b/interpreter/shared/src/main/scala/sigmastate/crypto/GF2_192.scala similarity index 100% rename from interpreter/src/main/scala/sigmastate/crypto/GF2_192.scala rename to interpreter/shared/src/main/scala/sigmastate/crypto/GF2_192.scala diff --git a/interpreter/src/main/scala/sigmastate/crypto/GF2_192_Poly.scala b/interpreter/shared/src/main/scala/sigmastate/crypto/GF2_192_Poly.scala similarity index 100% rename from interpreter/src/main/scala/sigmastate/crypto/GF2_192_Poly.scala rename to interpreter/shared/src/main/scala/sigmastate/crypto/GF2_192_Poly.scala diff --git a/interpreter/src/main/scala/sigmastate/crypto/package.scala b/interpreter/shared/src/main/scala/sigmastate/crypto/package.scala similarity index 73% rename from interpreter/src/main/scala/sigmastate/crypto/package.scala rename to interpreter/shared/src/main/scala/sigmastate/crypto/package.scala index a0feb011f9..c7ef439d87 100644 --- a/interpreter/src/main/scala/sigmastate/crypto/package.scala +++ b/interpreter/shared/src/main/scala/sigmastate/crypto/package.scala @@ -9,4 +9,7 @@ package object crypto { /** Instance of Elliptic Curve field element. */ type ECFieldElem = Platform.ECFieldElem + + /** A cryptographically strong random number generator. */ + type SecureRandom = Platform.SecureRandom } diff --git a/interpreter/src/main/scala/sigmastate/eval/BigIntegerOps.scala b/interpreter/shared/src/main/scala/sigmastate/eval/BigIntegerOps.scala similarity index 100% rename from interpreter/src/main/scala/sigmastate/eval/BigIntegerOps.scala rename to interpreter/shared/src/main/scala/sigmastate/eval/BigIntegerOps.scala diff --git a/interpreter/src/main/scala/sigmastate/eval/CostingDataContext.scala b/interpreter/shared/src/main/scala/sigmastate/eval/CostingDataContext.scala similarity index 98% rename from interpreter/src/main/scala/sigmastate/eval/CostingDataContext.scala rename to interpreter/shared/src/main/scala/sigmastate/eval/CostingDataContext.scala index 76bafd2fa2..82e7510c00 100644 --- a/interpreter/src/main/scala/sigmastate/eval/CostingDataContext.scala +++ b/interpreter/shared/src/main/scala/sigmastate/eval/CostingDataContext.scala @@ -10,10 +10,10 @@ import scalan.OverloadHack.Overloaded1 import scorex.crypto.authds.avltree.batch._ import scorex.crypto.authds.{ADDigest, ADKey, ADValue, SerializedAdProof} import sigmastate.SCollection.SByteArray -import sigmastate.{TrivialProp, _} +import sigmastate._ import sigmastate.Values.{ConstantNode, ErgoTree, EvaluatedValue, SValue, SigmaBoolean} -import sigmastate.interpreter.CryptoConstants.EcPointType -import sigmastate.interpreter.{CryptoConstants, Interpreter} +import sigmastate.basics.CryptoConstants.EcPointType +import sigmastate.interpreter.Interpreter import special.collection._ import special.sigma._ import sigmastate.eval.Extensions._ @@ -25,7 +25,7 @@ import scalan.{Nullable, RType} import scorex.crypto.hash.{Blake2b256, Digest32, Sha256} import sigmastate.Values.ErgoTree.EmptyConstants import sigmastate.basics.DLogProtocol.ProveDlog -import sigmastate.basics.ProveDHTuple +import sigmastate.basics.{ProveDHTuple, CryptoConstants} import sigmastate.crypto.{CryptoFacade, Ecp} import sigmastate.lang.TransformingSigmaBuilder import sigmastate.serialization.ErgoTreeSerializer.DefaultSerializer @@ -44,10 +44,10 @@ trait WrapperOf[T] { */ case class CBigInt(override val wrappedValue: BigInteger) extends BigInt with WrapperOf[BigInteger] { val dsl = CostingSigmaDslBuilder - override def toByte : Byte = wrappedValue.byteValueExact() - override def toShort: Short = wrappedValue.shortValueExact() - override def toInt : Int = wrappedValue.intValueExact() - override def toLong : Long = wrappedValue.longValueExact() + override def toByte : Byte = wrappedValue.toByteExact + override def toShort: Short = wrappedValue.toShortExact + override def toInt : Int = wrappedValue.toIntExact + override def toLong : Long = wrappedValue.toLongExact override def toBytes: Coll[Byte] = dsl.Colls.fromArray(wrappedValue.toByteArray) @@ -99,7 +99,7 @@ case class CBigInt(override val wrappedValue: BigInteger) extends BigInt with Wr case class CGroupElement(override val wrappedValue: Ecp) extends GroupElement with WrapperOf[Ecp] { val dsl = CostingSigmaDslBuilder - override def toString: String = s"GroupElement(${Extensions.showECPoint(wrappedValue)})" + override def toString: String = s"GroupElement(${sigmastate.eval.Extensions.showECPoint(wrappedValue)})" override def getEncoded: Coll[Byte] = dsl.Colls.fromArray(GroupElementSerializer.toBytes(wrappedValue)) diff --git a/interpreter/src/main/scala/sigmastate/eval/Evaluation.scala b/interpreter/shared/src/main/scala/sigmastate/eval/Evaluation.scala similarity index 90% rename from interpreter/src/main/scala/sigmastate/eval/Evaluation.scala rename to interpreter/shared/src/main/scala/sigmastate/eval/Evaluation.scala index d54c2e8f7a..818e604dd2 100644 --- a/interpreter/src/main/scala/sigmastate/eval/Evaluation.scala +++ b/interpreter/shared/src/main/scala/sigmastate/eval/Evaluation.scala @@ -6,10 +6,9 @@ import scalan.RType._ import sigmastate.SType._ import sigmastate.Values.SigmaBoolean import sigmastate._ -import sigmastate.lang.exceptions.CostLimitException import special.Types._ import debox.cfor - +import sigmastate.exceptions.CostLimitException import java.math.BigInteger import scala.reflect.ClassTag import scala.util.Try @@ -24,18 +23,24 @@ object Evaluation { def msgCostLimitError(cost: Long, limit: Long) = s"Estimated execution cost $cost exceeds the limit $limit" /** Helper method to accumulate cost while checking limit. - * @param current current cost value - * @param more additional cost to add to the current value - * @param limit total cost limit + * + * @param current current cost value + * @param delta additional cost to add to the current value + * @param limit total cost limit + * @param msgSuffix use case-specific error message suffix * @return new increased cost when it doesn't exceed the limit * @throws CostLimitException */ - def addCostChecked(current: Long, more: Long, limit: Long): Long = { - val newCost = java7.compat.Math.addExact(current, more) + def addCostChecked(current: Long, delta: Long, limit: Long, msgSuffix: => String = ""): Long = { + val newCost = java7.compat.Math.addExact(current, delta) if (newCost > limit) { throw new CostLimitException( estimatedCost = newCost, - message = msgCostLimitError(newCost, limit), cause = None) + message = { + val suffix = if (msgSuffix.isEmpty) "" else s": $msgSuffix" + msgCostLimitError(newCost, limit) + suffix + }, + cause = None) } newCost } diff --git a/interpreter/src/main/scala/sigmastate/eval/Exceptions.scala b/interpreter/shared/src/main/scala/sigmastate/eval/Exceptions.scala similarity index 100% rename from interpreter/src/main/scala/sigmastate/eval/Exceptions.scala rename to interpreter/shared/src/main/scala/sigmastate/eval/Exceptions.scala diff --git a/interpreter/src/main/scala/sigmastate/eval/Extensions.scala b/interpreter/shared/src/main/scala/sigmastate/eval/Extensions.scala similarity index 84% rename from interpreter/src/main/scala/sigmastate/eval/Extensions.scala rename to interpreter/shared/src/main/scala/sigmastate/eval/Extensions.scala index a5d93ba25c..9938129f5e 100644 --- a/interpreter/src/main/scala/sigmastate/eval/Extensions.scala +++ b/interpreter/shared/src/main/scala/sigmastate/eval/Extensions.scala @@ -11,6 +11,7 @@ import sigmastate.SType.AnyOps import org.ergoplatform.ErgoBox import debox.{Buffer => DBuffer} import debox.cfor +import org.ergoplatform.ErgoBox.TokenId import sigmastate.crypto.{CryptoFacade, Ecp} object Extensions { @@ -31,21 +32,21 @@ object Extensions { } implicit class ArrayOps[T: RType](arr: Array[T]) { + /** Wraps array into Coll instance. The source array in not cloned. */ @inline def toColl: Coll[T] = Colls.fromArray(arr) } + /** Extension methods for `Coll[Byte]` not available for generic `Array[T]`. */ + implicit class ArrayByteOps(val arr: Array[Byte]) extends AnyVal { + /** Wraps array into TokenId instance. The source array in not cloned. */ + @inline def toTokenId: TokenId = Digest32Coll @@ Colls.fromArray(arr) + } + implicit class EvalIterableOps[T: RType](seq: Iterable[T]) { @inline def toColl: Coll[T] = Colls.fromArray[T](seq.toArray(RType[T].classTag)) } implicit class EvalCollOps[T](val coll: Coll[T]) extends AnyVal { - def foreach(f: T => Unit) = { - val limit = coll.length - cfor(0)(_ < limit, _ + 1) { i => - f(coll(i)) - } - } - /** Helper type synonym. */ type ElemTpe = SType { type WrappedType = T} @@ -56,17 +57,6 @@ object Extensions { } } - // NOTE: it cannot extend AnyVal because of compiler error: type parameter of value class may not be specialized - implicit class PairCollOps[@specialized A, @specialized B](val coll: Coll[(A,B)]) { - def foreach(f: (A, B) => Unit) = { - val (as, bs) = Colls.unzip(coll) - val limit = coll.length - cfor(0)(_ < limit, _ + 1) { i => - f(as(i), bs(i)) - } - } - } - implicit class DslDataOps[A](data: A)(implicit tA: RType[A]) { def toTreeData: Constant[SType] = { CheckingSigmaBuilder.mkConstant(data.asWrappedType, Evaluation.rtypeToSType(tA)) @@ -97,6 +87,11 @@ object Extensions { } } + implicit class EcpOps(val source: Ecp) extends AnyVal { + /** Extracts [[GroupElement]] from the Ecp instance. */ + def toGroupElement: GroupElement = SigmaDsl.GroupElement(source) + } + implicit class GroupElementOps(val source: GroupElement) extends AnyVal { /** Shortened String representation of `source` GroupElement. */ def showToString: String = showECPoint(source.asInstanceOf[CGroupElement].wrappedValue) diff --git a/interpreter/src/main/scala/sigmastate/eval/Profiler.scala b/interpreter/shared/src/main/scala/sigmastate/eval/Profiler.scala similarity index 100% rename from interpreter/src/main/scala/sigmastate/eval/Profiler.scala rename to interpreter/shared/src/main/scala/sigmastate/eval/Profiler.scala diff --git a/interpreter/src/main/scala/sigmastate/eval/package.scala b/interpreter/shared/src/main/scala/sigmastate/eval/package.scala similarity index 96% rename from interpreter/src/main/scala/sigmastate/eval/package.scala rename to interpreter/shared/src/main/scala/sigmastate/eval/package.scala index d5abcedf35..cb4ebb9c05 100644 --- a/interpreter/src/main/scala/sigmastate/eval/package.scala +++ b/interpreter/shared/src/main/scala/sigmastate/eval/package.scala @@ -6,7 +6,7 @@ import org.ergoplatform.ErgoBox import scalan.RType import scorex.crypto.hash.Digest32 import sigmastate.Values.SigmaBoolean -import sigmastate.interpreter.CryptoConstants.EcPointType +import sigmastate.basics.CryptoConstants.EcPointType import special.collection.{Coll, CollBuilder} import special.sigma._ import supertagged.TaggedType @@ -35,8 +35,6 @@ package object eval { trait BaseDigestColl extends TaggedType[Coll[Byte]] - type DigestColl = BaseDigestColl#Type - object Digest32Coll extends BaseDigestColl type Digest32Coll = Digest32Coll.Type diff --git a/interpreter/shared/src/main/scala/sigmastate/exceptions/CompilerExceptions.scala b/interpreter/shared/src/main/scala/sigmastate/exceptions/CompilerExceptions.scala new file mode 100644 index 0000000000..b652a4091d --- /dev/null +++ b/interpreter/shared/src/main/scala/sigmastate/exceptions/CompilerExceptions.scala @@ -0,0 +1,53 @@ +package sigmastate.exceptions + +import sigmastate.lang.SourceContext + +/** Base class for exceptions thrown by the compiler. + * + * @param message the error message + * @param source an optional source context with location information + * @param cause an optional cause for the exception + */ +class CompilerException(message: String, val source: Option[SourceContext] = None, cause: Option[Throwable] = None) + extends SigmaException(message, cause) { + + override def getMessage: String = source.map { srcCtx => + val lineNumberStrPrefix = s"line ${srcCtx.line}: " + "\n" + lineNumberStrPrefix + + s"${srcCtx.sourceLine}\n${" " * (lineNumberStrPrefix.length + srcCtx.column - 1)}^\n" + message + }.getOrElse(message) +} + +/** Exception thrown during the binding phase of the compiler. + * + * @param message the error message + * @param source an optional source context with location information + */ +class BinderException(message: String, source: Option[SourceContext] = None) + extends CompilerException(message, source) + +/** Exception thrown during the type checking phase of the compiler. + * + * @param message the error message + * @param source an optional source context with location information + */ +class TyperException(message: String, source: Option[SourceContext] = None) + extends CompilerException(message, source) + +/** Exception thrown during the building phase of the compiler. + * + * @param message the error message + * @param source an optional source context with location information + */ +class BuilderException(message: String, source: Option[SourceContext] = None) + extends CompilerException(message, source) + +// TODO v5.x: remove this exception +class CosterException(message: String, source: Option[SourceContext], cause: Option[Throwable] = None) + extends CompilerException(message, source, cause) + + + + + + diff --git a/interpreter/src/main/scala/sigmastate/lang/exceptions/ConstraintFailed.scala b/interpreter/shared/src/main/scala/sigmastate/exceptions/ConstraintFailed.scala similarity index 82% rename from interpreter/src/main/scala/sigmastate/lang/exceptions/ConstraintFailed.scala rename to interpreter/shared/src/main/scala/sigmastate/exceptions/ConstraintFailed.scala index 8073b624e2..bddd2a6951 100644 --- a/interpreter/src/main/scala/sigmastate/lang/exceptions/ConstraintFailed.scala +++ b/interpreter/shared/src/main/scala/sigmastate/exceptions/ConstraintFailed.scala @@ -1,4 +1,4 @@ -package sigmastate.lang.exceptions +package sigmastate.exceptions import sigmastate.lang.SourceContext diff --git a/interpreter/src/main/scala/sigmastate/lang/exceptions/InvalidArguments.scala b/interpreter/shared/src/main/scala/sigmastate/exceptions/InvalidArguments.scala similarity index 82% rename from interpreter/src/main/scala/sigmastate/lang/exceptions/InvalidArguments.scala rename to interpreter/shared/src/main/scala/sigmastate/exceptions/InvalidArguments.scala index c6bf0324ef..b154fdb6d4 100644 --- a/interpreter/src/main/scala/sigmastate/lang/exceptions/InvalidArguments.scala +++ b/interpreter/shared/src/main/scala/sigmastate/exceptions/InvalidArguments.scala @@ -1,4 +1,4 @@ -package sigmastate.lang.exceptions +package sigmastate.exceptions import sigmastate.lang.SourceContext diff --git a/interpreter/shared/src/main/scala/sigmastate/exceptions/SigmaExceptions.scala b/interpreter/shared/src/main/scala/sigmastate/exceptions/SigmaExceptions.scala new file mode 100644 index 0000000000..8014fccda9 --- /dev/null +++ b/interpreter/shared/src/main/scala/sigmastate/exceptions/SigmaExceptions.scala @@ -0,0 +1,52 @@ +package sigmastate.exceptions + +import sigmastate.JitCost + +/** Base class for Sigma-related exceptions. + * + * @param message the error message + * @param cause an optional cause for the exception + */ +class SigmaException(val message: String, val cause: Option[Throwable] = None) + extends Exception(message, cause.orNull) + +/** Exception thrown during serialization. + * + * @param message the error message + * @param cause an optional cause for the exception + */ +case class SerializerException( + override val message: String, + override val cause: Option[Throwable] = None) + extends SigmaException(message, cause) + +/** Exception thrown by [[sigmastate.interpreter.Interpreter]]. + * + * @param message the error message + * @param cause an optional cause for the exception + */ +class InterpreterException(message: String, cause: Option[Throwable] = None) + extends SigmaException(message, cause) + +/** Exception thrown when the estimated cost exceeds the allowed cost limit. + * + * @param estimatedCost the estimated cost of execution + * @param message the error message + * @param cause an optional cause for the exception + */ +class CostLimitException( + val estimatedCost: Long, + message: String, + cause: Option[Throwable] = None) + extends SigmaException(message, cause) + +object CostLimitException { + /** Generates a cost limit error message. + * + * @param cost the estimated cost of execution + * @param limit the allowed cost limit + */ + def msgCostLimitError( + cost: JitCost, + limit: JitCost) = s"Estimated execution cost $cost exceeds the limit $limit" +} \ No newline at end of file diff --git a/interpreter/shared/src/main/scala/sigmastate/exceptions/SigmaSerializerExceptions.scala b/interpreter/shared/src/main/scala/sigmastate/exceptions/SigmaSerializerExceptions.scala new file mode 100644 index 0000000000..ee49cb433a --- /dev/null +++ b/interpreter/shared/src/main/scala/sigmastate/exceptions/SigmaSerializerExceptions.scala @@ -0,0 +1,23 @@ +package sigmastate.exceptions + +/** Thrown by TypeSerializer when type prefix <= 0. */ +final class InvalidTypePrefix(message: String, cause: Option[Throwable] = None) + extends SerializerException(message, cause) + +/** Thrown when the current reader position > positionLimit which is set in the Reader. + * @see [[org.ergoplatform.validation.ValidationRules.CheckPositionLimit]] + */ +final class ReaderPositionLimitExceeded( + message: String, + val position: Int, + val positionLimit: Int, + cause: Option[Throwable] = None) + extends SerializerException(message, cause) + +/** Thrown when the current depth level > maxDepthLevel which is set in the Reader. */ +final class DeserializeCallDepthExceeded(message: String, cause: Option[Throwable] = None) + extends SerializerException(message, cause) + +/** Thrown by [[org.ergoplatform.validation.ValidationRules.CheckValidOpCode]] validation rule. */ +final class InvalidOpCode(message: String, cause: Option[Throwable] = None) + extends SerializerException(message, cause) diff --git a/interpreter/src/main/scala/sigmastate/lang/exceptions/SigmaTyperExceptions.scala b/interpreter/shared/src/main/scala/sigmastate/exceptions/SigmaTyperExceptions.scala similarity index 94% rename from interpreter/src/main/scala/sigmastate/lang/exceptions/SigmaTyperExceptions.scala rename to interpreter/shared/src/main/scala/sigmastate/exceptions/SigmaTyperExceptions.scala index 5073268613..b5802e39ca 100644 --- a/interpreter/src/main/scala/sigmastate/lang/exceptions/SigmaTyperExceptions.scala +++ b/interpreter/shared/src/main/scala/sigmastate/exceptions/SigmaTyperExceptions.scala @@ -1,4 +1,4 @@ -package sigmastate.lang.exceptions +package sigmastate.exceptions import sigmastate.lang.SourceContext diff --git a/interpreter/src/main/scala/sigmastate/interpreter/CostAccumulator.scala b/interpreter/shared/src/main/scala/sigmastate/interpreter/CostAccumulator.scala similarity index 98% rename from interpreter/src/main/scala/sigmastate/interpreter/CostAccumulator.scala rename to interpreter/shared/src/main/scala/sigmastate/interpreter/CostAccumulator.scala index b40aec83ca..fe86acf9a4 100644 --- a/interpreter/src/main/scala/sigmastate/interpreter/CostAccumulator.scala +++ b/interpreter/shared/src/main/scala/sigmastate/interpreter/CostAccumulator.scala @@ -1,7 +1,7 @@ package sigmastate.interpreter import sigmastate.JitCost -import sigmastate.lang.exceptions.CostLimitException +import sigmastate.exceptions.CostLimitException /** Encapsulate simple monotonic (add only) counter with reset. */ class CostCounter(val initialCost: JitCost) { diff --git a/interpreter/src/main/scala/sigmastate/interpreter/CostDetails.scala b/interpreter/shared/src/main/scala/sigmastate/interpreter/CostDetails.scala similarity index 100% rename from interpreter/src/main/scala/sigmastate/interpreter/CostDetails.scala rename to interpreter/shared/src/main/scala/sigmastate/interpreter/CostDetails.scala diff --git a/interpreter/src/main/scala/sigmastate/interpreter/CostItem.scala b/interpreter/shared/src/main/scala/sigmastate/interpreter/CostItem.scala similarity index 100% rename from interpreter/src/main/scala/sigmastate/interpreter/CostItem.scala rename to interpreter/shared/src/main/scala/sigmastate/interpreter/CostItem.scala diff --git a/interpreter/src/main/scala/sigmastate/interpreter/ErgoTreeEvaluator.scala b/interpreter/shared/src/main/scala/sigmastate/interpreter/ErgoTreeEvaluator.scala similarity index 100% rename from interpreter/src/main/scala/sigmastate/interpreter/ErgoTreeEvaluator.scala rename to interpreter/shared/src/main/scala/sigmastate/interpreter/ErgoTreeEvaluator.scala diff --git a/interpreter/src/main/scala/sigmastate/interpreter/Hint.scala b/interpreter/shared/src/main/scala/sigmastate/interpreter/Hint.scala similarity index 100% rename from interpreter/src/main/scala/sigmastate/interpreter/Hint.scala rename to interpreter/shared/src/main/scala/sigmastate/interpreter/Hint.scala diff --git a/interpreter/src/main/scala/sigmastate/interpreter/Interpreter.scala b/interpreter/shared/src/main/scala/sigmastate/interpreter/Interpreter.scala similarity index 95% rename from interpreter/src/main/scala/sigmastate/interpreter/Interpreter.scala rename to interpreter/shared/src/main/scala/sigmastate/interpreter/Interpreter.scala index 5366a4b2e0..1559fcc775 100644 --- a/interpreter/src/main/scala/sigmastate/interpreter/Interpreter.scala +++ b/interpreter/shared/src/main/scala/sigmastate/interpreter/Interpreter.scala @@ -12,7 +12,6 @@ import sigmastate.Values._ import sigmastate.basics.DLogProtocol.{ProveDlog, DLogInteractiveProver, FirstDLogProverMessage} import sigmastate.basics._ import sigmastate.interpreter.Interpreter._ -import sigmastate.lang.exceptions.InterpreterException import sigmastate.serialization.{SigmaSerializer, ValueSerializer} import sigmastate.utxo.DeserializeContext import sigmastate.{SType, _} @@ -24,8 +23,8 @@ import sigmastate.interpreter.ErgoTreeEvaluator.fixedCostOp import sigmastate.utils.Helpers._ import sigmastate.lang.Terms.ValueOps import debox.cfor - -import scala.util.{Try, Success} +import sigmastate.exceptions.{CostLimitException, InterpreterException} +import scala.util.{Success, Try} /** Base (verifying) interpreter of ErgoTrees. * Can perform: @@ -46,6 +45,7 @@ import scala.util.{Try, Success} */ trait Interpreter { + /** Type of context data used by this interpreter to access blockchain and transaction data. */ type CTX <: InterpreterContext type ProofT = UncheckedTree @@ -130,7 +130,7 @@ trait Interpreter { TrueSigmaProp case Left(UnparsedErgoTree(_, error)) => throw new InterpreterException( - "Script has not been recognized due to ValidationException, and it cannot be accepted as soft-fork.", None, Some(error)) + "Script has not been recognized due to ValidationException, and it cannot be accepted as soft-fork.", Some(error)) } prop } @@ -257,16 +257,18 @@ trait Interpreter { * This is AOT part of JITC-based interpreter, it predicts the cost of crypto * verification, which is asymptotically much faster and protects from spam scripts. * - * @param jitRes result of JIT-based reduction - * @param costLimit total cost limit to check and raise exception if exceeded - * @return computed jitRes.cost + crypto verification cost + * @param reductionRes result of JIT-based reduction + * @param baseCost base cost of verification prior to calling this method + * @param costLimit total cost limit to check and raise exception if exceeded + * @return computed baseCost + crypto verification cost + * @throws CostLimitException if cost limit is exceeded */ - protected def addCryptoCost(jitRes: ReductionResult, costLimit: Long) = { - val cryptoCost = estimateCryptoVerifyCost(jitRes.value).toBlockCost // scale JitCost to tx cost + protected def addCryptoCost(reductionRes: SigmaBoolean, baseCost: Long, costLimit: Long): Long = { + val cryptoCost = estimateCryptoVerifyCost(reductionRes).toBlockCost // scale JitCost to tx cost - // Note, jitRes.cost is already scaled in fullReduction - val fullJitCost = addCostChecked(jitRes.cost, cryptoCost, costLimit) - fullJitCost + // Note, baseCost should be already scaled + val fullCost = addCostChecked(baseCost, cryptoCost, costLimit) + fullCost } /** Checks the possible soft-fork condition. @@ -354,7 +356,7 @@ trait Interpreter { case TrivialProp.TrueProp => (true, reduced.cost) case TrivialProp.FalseProp => (false, reduced.cost) case _ => - val fullCost = addCryptoCost(reduced, context.costLimit) + val fullCost = addCryptoCost(reduced.value, reduced.cost, context.costLimit) val ok = if (evalSettings.isMeasureOperationTime) { val E = ErgoTreeEvaluator.forProfiling(verifySignatureProfiler, evalSettings) @@ -493,6 +495,11 @@ object Interpreter { /** Property name used to store script name. */ val ScriptNameProp = "ScriptName" + /** Initial cost of instantiating an interpreter and creating ErgoLikeContext. + * Added once per transaction. + */ + val interpreterInitCost = 10000 + /** The result of script reduction when soft-fork condition is detected by the old node, * in which case the script is reduced to the trivial true proposition and takes up 0 cost. */ diff --git a/interpreter/src/main/scala/sigmastate/interpreter/InterpreterContext.scala b/interpreter/shared/src/main/scala/sigmastate/interpreter/InterpreterContext.scala similarity index 100% rename from interpreter/src/main/scala/sigmastate/interpreter/InterpreterContext.scala rename to interpreter/shared/src/main/scala/sigmastate/interpreter/InterpreterContext.scala diff --git a/interpreter/src/main/scala/sigmastate/interpreter/OperationDesc.scala b/interpreter/shared/src/main/scala/sigmastate/interpreter/OperationDesc.scala similarity index 100% rename from interpreter/src/main/scala/sigmastate/interpreter/OperationDesc.scala rename to interpreter/shared/src/main/scala/sigmastate/interpreter/OperationDesc.scala diff --git a/interpreter/src/main/scala/sigmastate/interpreter/ProverInterpreter.scala b/interpreter/shared/src/main/scala/sigmastate/interpreter/ProverInterpreter.scala similarity index 99% rename from interpreter/src/main/scala/sigmastate/interpreter/ProverInterpreter.scala rename to interpreter/shared/src/main/scala/sigmastate/interpreter/ProverInterpreter.scala index d49cba76f3..ff91db6285 100644 --- a/interpreter/src/main/scala/sigmastate/interpreter/ProverInterpreter.scala +++ b/interpreter/shared/src/main/scala/sigmastate/interpreter/ProverInterpreter.scala @@ -11,7 +11,7 @@ import sigmastate.basics.DLogProtocol._ import sigmastate.basics.VerifierMessage.Challenge import sigmastate.basics._ import sigmastate.crypto.{GF2_192, GF2_192_Poly} -import sigmastate.lang.exceptions.InterpreterException +import sigmastate.exceptions.InterpreterException import sigmastate.utils.Helpers import java.math.BigInteger @@ -28,6 +28,7 @@ trait ProverInterpreter extends Interpreter with ProverUtils { override type ProofT = UncheckedTree + /** All secrets available for this prover. */ def secrets: Seq[SigmaProtocolPrivateInput[_, _]] /** @@ -128,7 +129,7 @@ trait ProverInterpreter extends Interpreter with ProverUtils { VersionContext.withVersions(context.activatedScriptVersion, ergoTree.version) { val (resValue, resCost) = { val reduced = fullReduction(ergoTree, context, env) - val fullCost = addCryptoCost(reduced, context.costLimit) + val fullCost = addCryptoCost(reduced.value, reduced.cost, context.costLimit) (reduced.value, fullCost) } diff --git a/interpreter/src/main/scala/sigmastate/interpreter/ProverResult.scala b/interpreter/shared/src/main/scala/sigmastate/interpreter/ProverResult.scala similarity index 100% rename from interpreter/src/main/scala/sigmastate/interpreter/ProverResult.scala rename to interpreter/shared/src/main/scala/sigmastate/interpreter/ProverResult.scala diff --git a/interpreter/src/main/scala/sigmastate/interpreter/ProverUtils.scala b/interpreter/shared/src/main/scala/sigmastate/interpreter/ProverUtils.scala similarity index 100% rename from interpreter/src/main/scala/sigmastate/interpreter/ProverUtils.scala rename to interpreter/shared/src/main/scala/sigmastate/interpreter/ProverUtils.scala diff --git a/interpreter/src/main/scala/sigmastate/lang/SigmaBuilder.scala b/interpreter/shared/src/main/scala/sigmastate/lang/SigmaBuilder.scala similarity index 99% rename from interpreter/src/main/scala/sigmastate/lang/SigmaBuilder.scala rename to interpreter/shared/src/main/scala/sigmastate/lang/SigmaBuilder.scala index 3a90b1efad..a38b6f74f1 100644 --- a/interpreter/src/main/scala/sigmastate/lang/SigmaBuilder.scala +++ b/interpreter/shared/src/main/scala/sigmastate/lang/SigmaBuilder.scala @@ -9,15 +9,15 @@ import sigmastate.Values._ import sigmastate._ import sigmastate.lang.Constraints._ import sigmastate.lang.Terms._ -import sigmastate.lang.exceptions.ConstraintFailed +import sigmastate.exceptions.ConstraintFailed import sigmastate.serialization.OpCodes import sigmastate.utxo._ import scalan.Nullable import sigmastate.SOption.SIntOption -import sigmastate.eval.{Evaluation, _} -import sigmastate.interpreter.CryptoConstants.EcPointType +import sigmastate.eval._ +import sigmastate.basics.CryptoConstants.EcPointType import special.collection.Coll -import sigmastate.lang.SigmaTyper.STypeSubst +import sigmastate.lang.Terms.STypeSubst import sigmastate.serialization.OpCodes.OpCode import special.sigma.{AvlTree, SigmaProp, GroupElement} import debox.cfor @@ -254,7 +254,6 @@ abstract class SigmaBuilder { case v: BigInteger => Nullable(mkConstant[SBigInt.type](SigmaDsl.BigInt(v), SBigInt)) case n: special.sigma.BigInt => Nullable(mkConstant[SBigInt.type](n, SBigInt)) - case v: EcPointType => Nullable(mkConstant[SGroupElement.type](SigmaDsl.GroupElement(v), SGroupElement)) case ge: GroupElement => Nullable(mkConstant[SGroupElement.type](ge, SGroupElement)) case b: Boolean => Nullable(if(b) TrueLeaf else FalseLeaf) diff --git a/interpreter/src/main/scala/sigmastate/lang/SigmaPredef.scala b/interpreter/shared/src/main/scala/sigmastate/lang/SigmaPredef.scala similarity index 99% rename from interpreter/src/main/scala/sigmastate/lang/SigmaPredef.scala rename to interpreter/shared/src/main/scala/sigmastate/lang/SigmaPredef.scala index ed15cd783c..7c958918e5 100644 --- a/interpreter/src/main/scala/sigmastate/lang/SigmaPredef.scala +++ b/interpreter/shared/src/main/scala/sigmastate/lang/SigmaPredef.scala @@ -9,7 +9,7 @@ import sigmastate.SOption._ import sigmastate.Values.{StringConstant, SValue, IntValue, SigmaPropConstant, Value, ByteArrayConstant, SigmaPropValue, ValueCompanion, Constant, EvaluatedValue, ConstantPlaceholder, BoolValue} import sigmastate._ import sigmastate.lang.Terms._ -import sigmastate.lang.exceptions.InvalidArguments +import sigmastate.exceptions.InvalidArguments import sigmastate.serialization.ValueSerializer import sigmastate.utxo.{DeserializeRegister, SelectField, DeserializeContext, GetVar} diff --git a/interpreter/src/main/scala/sigmastate/lang/SourceContext.scala b/interpreter/shared/src/main/scala/sigmastate/lang/SourceContext.scala similarity index 100% rename from interpreter/src/main/scala/sigmastate/lang/SourceContext.scala rename to interpreter/shared/src/main/scala/sigmastate/lang/SourceContext.scala diff --git a/interpreter/src/main/scala/sigmastate/lang/Terms.scala b/interpreter/shared/src/main/scala/sigmastate/lang/Terms.scala similarity index 80% rename from interpreter/src/main/scala/sigmastate/lang/Terms.scala rename to interpreter/shared/src/main/scala/sigmastate/lang/Terms.scala index 97eb26e850..4cc8dea0d6 100644 --- a/interpreter/src/main/scala/sigmastate/lang/Terms.scala +++ b/interpreter/shared/src/main/scala/sigmastate/lang/Terms.scala @@ -163,7 +163,7 @@ object Terms { override lazy val tpe: SType = input.tpe match { case funcType: SFunc => val subst = funcType.tpeParams.map(_.ident).zip(tpeArgs).toMap - SigmaTyper.applySubst(input.tpe, subst) + Terms.applySubst(input.tpe, subst) case _ => input.tpe } /** This is not used as operation, but rather to form a program structure */ @@ -363,4 +363,104 @@ object Terms { } } + + /** Type alias for a substitution of type variables with their corresponding types. */ + type STypeSubst = Map[STypeVar, SType] + + /** Immutable and sharable empty substitution. */ + val EmptySubst = Map.empty[STypeVar, SType] + + /** Performs pairwise type unification making sure each type variable is equally + * substituted in all items. */ + def unifyTypeLists(items1: Seq[SType], items2: Seq[SType]): Option[Terms.STypeSubst] = { + // unify items pairwise independently + val itemsUni = (items1, items2).zipped.map((t1, t2) => unifyTypes(t1, t2)) + if (itemsUni.forall(_.isDefined)) { + // merge substitutions making sure the same id is equally substituted in all items + val merged = itemsUni.foldLeft(EmptySubst)((acc, subst) => { + var res = acc + for ( (id, t) <- subst.get ) { + if (res.contains(id) && res(id) != t) return None + res = res + (id -> t) + } + res + }) + Some(merged) + } else + None + } + + private val unifiedWithoutSubst = Some(EmptySubst) + + /** Finds a substitution `subst` of type variables such that unifyTypes(applySubst(t1, subst), t2) shouldBe Some(emptySubst) */ + def unifyTypes(t1: SType, t2: SType): Option[Terms.STypeSubst] = (t1, t2) match { + case (_ @ STypeVar(n1), _ @ STypeVar(n2)) => + if (n1 == n2) unifiedWithoutSubst else None + case (id1 @ STypeVar(_), _) => + Some(Map(id1 -> t2)) + case (e1: SCollectionType[_], e2: SCollectionType[_]) => + unifyTypes(e1.elemType, e2.elemType) + case (e1: SCollectionType[_], _: STuple) => + unifyTypes(e1.elemType, SAny) + case (e1: SOption[_], e2: SOption[_]) => + unifyTypes(e1.elemType, e2.elemType) + case (e1: STuple, e2: STuple) if e1.items.length == e2.items.length => + unifyTypeLists(e1.items, e2.items) + case (e1: SFunc, e2: SFunc) if e1.tDom.length == e2.tDom.length => + unifyTypeLists(e1.tDom :+ e1.tRange, e2.tDom :+ e2.tRange) + case (STypeApply(name1, args1), STypeApply(name2, args2)) + if name1 == name2 && args1.length == args2.length => + unifyTypeLists(args1, args2) + case (SBoolean, SSigmaProp) => // it is necessary for implicit conversion in Coll(bool, prop, bool) + unifiedWithoutSubst + case (SPrimType(e1), SPrimType(e2)) if e1 == e2 => + unifiedWithoutSubst + case (SAny, _) => + unifiedWithoutSubst + case _ => None + } + + /** Applies a type substitution to a given type. + * + * @param tpe the type to apply the substitution to + * @param subst the type substitution to apply + * @return the type after applying the substitution + */ + def applySubst(tpe: SType, subst: Terms.STypeSubst): SType = tpe match { + case SFunc(args, res, tparams) => + val remainingVars = tparams.filterNot { p => subst.contains(p.ident) } + SFunc(args.map(applySubst(_, subst)), applySubst(res, subst), remainingVars) + case _ => + val substRule = rule[Any] { + case id: STypeVar if subst.contains(id) => subst(id) + } + rewrite(everywherebu(substRule))(tpe) + } + + /** Computes the most general type given two types. + * + * @param t1 the first type + * @param t2 the second type + * @return the most general type if it exists, otherwise None + */ + def msgType(t1: SType, t2: SType): Option[SType] = unifyTypes(t1, t2) match { + case Some(_) => Some(t1) + case None => unifyTypes(t2, t1).map(_ => t2) + } + + /** Most Specific Generalized (MSG) type of ts. + * Currently just the type of the first element as long as all the elements have the same type. */ + def msgTypeOf(ts: Seq[SType]): Option[SType] = { + if (ts.isEmpty) None + else { + var res: SType = ts.head + for ( t <- ts.iterator.drop(1) ) { + msgType(t, res) match { + case Some(msg) => res = msg //assign new + case None => return None + } + } + Some(res) + } + } } diff --git a/interpreter/src/main/scala/sigmastate/serialization/ApplySerializer.scala b/interpreter/shared/src/main/scala/sigmastate/serialization/ApplySerializer.scala similarity index 100% rename from interpreter/src/main/scala/sigmastate/serialization/ApplySerializer.scala rename to interpreter/shared/src/main/scala/sigmastate/serialization/ApplySerializer.scala diff --git a/interpreter/src/main/scala/sigmastate/serialization/BlockValueSerializer.scala b/interpreter/shared/src/main/scala/sigmastate/serialization/BlockValueSerializer.scala similarity index 100% rename from interpreter/src/main/scala/sigmastate/serialization/BlockValueSerializer.scala rename to interpreter/shared/src/main/scala/sigmastate/serialization/BlockValueSerializer.scala diff --git a/interpreter/src/main/scala/sigmastate/serialization/BoolToSigmaPropSerializer.scala b/interpreter/shared/src/main/scala/sigmastate/serialization/BoolToSigmaPropSerializer.scala similarity index 100% rename from interpreter/src/main/scala/sigmastate/serialization/BoolToSigmaPropSerializer.scala rename to interpreter/shared/src/main/scala/sigmastate/serialization/BoolToSigmaPropSerializer.scala diff --git a/interpreter/src/main/scala/sigmastate/serialization/CaseObjectSerialization.scala b/interpreter/shared/src/main/scala/sigmastate/serialization/CaseObjectSerialization.scala similarity index 100% rename from interpreter/src/main/scala/sigmastate/serialization/CaseObjectSerialization.scala rename to interpreter/shared/src/main/scala/sigmastate/serialization/CaseObjectSerialization.scala diff --git a/interpreter/src/main/scala/sigmastate/serialization/ConcreteCollectionBooleanConstantSerializer.scala b/interpreter/shared/src/main/scala/sigmastate/serialization/ConcreteCollectionBooleanConstantSerializer.scala similarity index 100% rename from interpreter/src/main/scala/sigmastate/serialization/ConcreteCollectionBooleanConstantSerializer.scala rename to interpreter/shared/src/main/scala/sigmastate/serialization/ConcreteCollectionBooleanConstantSerializer.scala diff --git a/interpreter/src/main/scala/sigmastate/serialization/ConcreteCollectionSerializer.scala b/interpreter/shared/src/main/scala/sigmastate/serialization/ConcreteCollectionSerializer.scala similarity index 100% rename from interpreter/src/main/scala/sigmastate/serialization/ConcreteCollectionSerializer.scala rename to interpreter/shared/src/main/scala/sigmastate/serialization/ConcreteCollectionSerializer.scala diff --git a/interpreter/src/main/scala/sigmastate/serialization/ConstantPlaceholderSerializer.scala b/interpreter/shared/src/main/scala/sigmastate/serialization/ConstantPlaceholderSerializer.scala similarity index 100% rename from interpreter/src/main/scala/sigmastate/serialization/ConstantPlaceholderSerializer.scala rename to interpreter/shared/src/main/scala/sigmastate/serialization/ConstantPlaceholderSerializer.scala diff --git a/interpreter/src/main/scala/sigmastate/serialization/ConstantSerializer.scala b/interpreter/shared/src/main/scala/sigmastate/serialization/ConstantSerializer.scala similarity index 100% rename from interpreter/src/main/scala/sigmastate/serialization/ConstantSerializer.scala rename to interpreter/shared/src/main/scala/sigmastate/serialization/ConstantSerializer.scala diff --git a/interpreter/src/main/scala/sigmastate/serialization/ConstantStore.scala b/interpreter/shared/src/main/scala/sigmastate/serialization/ConstantStore.scala similarity index 100% rename from interpreter/src/main/scala/sigmastate/serialization/ConstantStore.scala rename to interpreter/shared/src/main/scala/sigmastate/serialization/ConstantStore.scala diff --git a/interpreter/src/main/scala/sigmastate/serialization/CreateAvlTreeSerializer.scala b/interpreter/shared/src/main/scala/sigmastate/serialization/CreateAvlTreeSerializer.scala similarity index 100% rename from interpreter/src/main/scala/sigmastate/serialization/CreateAvlTreeSerializer.scala rename to interpreter/shared/src/main/scala/sigmastate/serialization/CreateAvlTreeSerializer.scala diff --git a/interpreter/src/main/scala/sigmastate/serialization/DataSerializer.scala b/interpreter/shared/src/main/scala/sigmastate/serialization/DataSerializer.scala similarity index 99% rename from interpreter/src/main/scala/sigmastate/serialization/DataSerializer.scala rename to interpreter/shared/src/main/scala/sigmastate/serialization/DataSerializer.scala index dc1ee2f230..d659dbf81b 100644 --- a/interpreter/src/main/scala/sigmastate/serialization/DataSerializer.scala +++ b/interpreter/shared/src/main/scala/sigmastate/serialization/DataSerializer.scala @@ -10,10 +10,10 @@ import sigmastate.Values.SigmaBoolean import sigmastate.utils.{SigmaByteReader, SigmaByteWriter} import sigmastate._ import sigmastate.eval.{Evaluation, _} -import sigmastate.lang.exceptions.SerializerException import special.collection._ import special.sigma._ import debox.cfor +import sigmastate.exceptions.SerializerException import scala.collection.mutable /** This works in tandem with ConstantSerializer, if you change one make sure to check the other.*/ diff --git a/interpreter/src/main/scala/sigmastate/serialization/ErgoTreeSerializer.scala b/interpreter/shared/src/main/scala/sigmastate/serialization/ErgoTreeSerializer.scala similarity index 99% rename from interpreter/src/main/scala/sigmastate/serialization/ErgoTreeSerializer.scala rename to interpreter/shared/src/main/scala/sigmastate/serialization/ErgoTreeSerializer.scala index c48b47be9d..dd3c9f7d13 100644 --- a/interpreter/src/main/scala/sigmastate/serialization/ErgoTreeSerializer.scala +++ b/interpreter/shared/src/main/scala/sigmastate/serialization/ErgoTreeSerializer.scala @@ -6,13 +6,12 @@ import sigmastate.{SType, VersionContext} import sigmastate.Values.{Constant, ErgoTree, UnparsedErgoTree} import sigmastate.lang.DeserializationSigmaBuilder import sigmastate.lang.Terms.ValueOps -import sigmastate.lang.exceptions.{SerializerException, ReaderPositionLimitExceeded} import sigmastate.utils.{SigmaByteReader, SigmaByteWriter} import sigmastate.Values.ErgoTree.EmptyConstants import sigmastate.util.safeNewArray import sigmastate.utxo.ComplexityTable import debox.cfor - +import sigmastate.exceptions.{SerializerException, ReaderPositionLimitExceeded} import java.util /** @@ -191,7 +190,7 @@ class ErgoTreeSerializer { new ErgoTree(ErgoTree.DefaultHeader, EmptyConstants, Left(UnparsedErgoTree(bytes, ve)), complexity, bytes, None) case None => throw new SerializerException( - s"Cannot handle ValidationException, ErgoTree serialized without size bit.", None, Some(ve)) + s"Cannot handle ValidationException, ErgoTree serialized without size bit.", Some(ve)) } } finally { diff --git a/interpreter/src/main/scala/sigmastate/serialization/FuncValueSerializer.scala b/interpreter/shared/src/main/scala/sigmastate/serialization/FuncValueSerializer.scala similarity index 100% rename from interpreter/src/main/scala/sigmastate/serialization/FuncValueSerializer.scala rename to interpreter/shared/src/main/scala/sigmastate/serialization/FuncValueSerializer.scala diff --git a/interpreter/src/main/scala/sigmastate/serialization/GetVarSerializer.scala b/interpreter/shared/src/main/scala/sigmastate/serialization/GetVarSerializer.scala similarity index 100% rename from interpreter/src/main/scala/sigmastate/serialization/GetVarSerializer.scala rename to interpreter/shared/src/main/scala/sigmastate/serialization/GetVarSerializer.scala diff --git a/interpreter/src/main/scala/sigmastate/serialization/GroupElementSerializer.scala b/interpreter/shared/src/main/scala/sigmastate/serialization/GroupElementSerializer.scala similarity index 94% rename from interpreter/src/main/scala/sigmastate/serialization/GroupElementSerializer.scala rename to interpreter/shared/src/main/scala/sigmastate/serialization/GroupElementSerializer.scala index b8523cb430..f54606082f 100644 --- a/interpreter/src/main/scala/sigmastate/serialization/GroupElementSerializer.scala +++ b/interpreter/shared/src/main/scala/sigmastate/serialization/GroupElementSerializer.scala @@ -1,8 +1,8 @@ package sigmastate.serialization +import sigmastate.basics.CryptoConstants import sigmastate.crypto.CryptoFacade -import sigmastate.interpreter.CryptoConstants -import sigmastate.interpreter.CryptoConstants.EcPointType +import CryptoConstants.EcPointType import sigmastate.util.safeNewArray import sigmastate.utils.{SigmaByteReader, SigmaByteWriter} diff --git a/interpreter/src/main/scala/sigmastate/serialization/LogicalNotSerializer.scala b/interpreter/shared/src/main/scala/sigmastate/serialization/LogicalNotSerializer.scala similarity index 100% rename from interpreter/src/main/scala/sigmastate/serialization/LogicalNotSerializer.scala rename to interpreter/shared/src/main/scala/sigmastate/serialization/LogicalNotSerializer.scala diff --git a/interpreter/src/main/scala/sigmastate/serialization/MethodCallSerializer.scala b/interpreter/shared/src/main/scala/sigmastate/serialization/MethodCallSerializer.scala similarity index 98% rename from interpreter/src/main/scala/sigmastate/serialization/MethodCallSerializer.scala rename to interpreter/shared/src/main/scala/sigmastate/serialization/MethodCallSerializer.scala index a9abacb02a..8167790492 100644 --- a/interpreter/src/main/scala/sigmastate/serialization/MethodCallSerializer.scala +++ b/interpreter/shared/src/main/scala/sigmastate/serialization/MethodCallSerializer.scala @@ -2,7 +2,7 @@ package sigmastate.serialization import sigmastate.Values._ import sigmastate._ -import sigmastate.lang.SigmaTyper.STypeSubst +import sigmastate.lang.Terms.STypeSubst import sigmastate.lang.Terms.MethodCall import sigmastate.util.safeNewArray import sigmastate.utils.SigmaByteWriter.{DataInfo, valuesItemInfo} diff --git a/interpreter/src/main/scala/sigmastate/serialization/ModQArithOpSerializer.scala b/interpreter/shared/src/main/scala/sigmastate/serialization/ModQArithOpSerializer.scala similarity index 100% rename from interpreter/src/main/scala/sigmastate/serialization/ModQArithOpSerializer.scala rename to interpreter/shared/src/main/scala/sigmastate/serialization/ModQArithOpSerializer.scala diff --git a/interpreter/src/main/scala/sigmastate/serialization/ModQSerializer.scala b/interpreter/shared/src/main/scala/sigmastate/serialization/ModQSerializer.scala similarity index 100% rename from interpreter/src/main/scala/sigmastate/serialization/ModQSerializer.scala rename to interpreter/shared/src/main/scala/sigmastate/serialization/ModQSerializer.scala diff --git a/interpreter/src/main/scala/sigmastate/serialization/OneArgumentOperationSerializer.scala b/interpreter/shared/src/main/scala/sigmastate/serialization/OneArgumentOperationSerializer.scala similarity index 100% rename from interpreter/src/main/scala/sigmastate/serialization/OneArgumentOperationSerializer.scala rename to interpreter/shared/src/main/scala/sigmastate/serialization/OneArgumentOperationSerializer.scala diff --git a/interpreter/src/main/scala/sigmastate/serialization/OpCodes.scala b/interpreter/shared/src/main/scala/sigmastate/serialization/OpCodes.scala similarity index 100% rename from interpreter/src/main/scala/sigmastate/serialization/OpCodes.scala rename to interpreter/shared/src/main/scala/sigmastate/serialization/OpCodes.scala diff --git a/interpreter/src/main/scala/sigmastate/serialization/OptionGetOrElseSerializer.scala b/interpreter/shared/src/main/scala/sigmastate/serialization/OptionGetOrElseSerializer.scala similarity index 100% rename from interpreter/src/main/scala/sigmastate/serialization/OptionGetOrElseSerializer.scala rename to interpreter/shared/src/main/scala/sigmastate/serialization/OptionGetOrElseSerializer.scala diff --git a/interpreter/src/main/scala/sigmastate/serialization/PropertyCallSerializer.scala b/interpreter/shared/src/main/scala/sigmastate/serialization/PropertyCallSerializer.scala similarity index 84% rename from interpreter/src/main/scala/sigmastate/serialization/PropertyCallSerializer.scala rename to interpreter/shared/src/main/scala/sigmastate/serialization/PropertyCallSerializer.scala index 3a2ae84002..0c4b15aaf5 100644 --- a/interpreter/src/main/scala/sigmastate/serialization/PropertyCallSerializer.scala +++ b/interpreter/shared/src/main/scala/sigmastate/serialization/PropertyCallSerializer.scala @@ -2,11 +2,11 @@ package sigmastate.serialization import sigmastate.Values._ import sigmastate._ -import sigmastate.lang.SigmaTyper -import sigmastate.lang.SigmaTyper.STypeSubst -import sigmastate.lang.Terms.{PropertyCall, MethodCall} +import sigmastate.lang.Terms +import sigmastate.lang.Terms.STypeSubst +import sigmastate.lang.Terms.{MethodCall, PropertyCall} import sigmastate.utils.SigmaByteWriter.DataInfo -import sigmastate.utils.{SigmaByteReader, SigmaByteWriter} +import sigmastate.utils.{SigmaByteWriter, SigmaByteReader} import sigmastate.utxo.ComplexityTable case class PropertyCallSerializer(cons: (Value[SType], SMethod, IndexedSeq[Value[SType]], STypeSubst) => Value[SType]) @@ -31,6 +31,6 @@ case class PropertyCallSerializer(cons: (Value[SType], SMethod, IndexedSeq[Value val complexity = ComplexityTable.MethodCallComplexity.getOrElse((typeId, methodId), ComplexityTable.MinimalComplexity) r.addComplexity(complexity) val specMethod = method.specializeFor(obj.tpe, SType.EmptySeq) - cons(obj, specMethod, Value.EmptySeq, SigmaTyper.EmptySubst) + cons(obj, specMethod, Value.EmptySeq, Terms.EmptySubst) } } diff --git a/interpreter/src/main/scala/sigmastate/serialization/ProveDlogSerializer.scala b/interpreter/shared/src/main/scala/sigmastate/serialization/ProveDlogSerializer.scala similarity index 95% rename from interpreter/src/main/scala/sigmastate/serialization/ProveDlogSerializer.scala rename to interpreter/shared/src/main/scala/sigmastate/serialization/ProveDlogSerializer.scala index d94d602c65..edf7b175f4 100644 --- a/interpreter/src/main/scala/sigmastate/serialization/ProveDlogSerializer.scala +++ b/interpreter/shared/src/main/scala/sigmastate/serialization/ProveDlogSerializer.scala @@ -3,7 +3,7 @@ package sigmastate.serialization import sigmastate.basics.DLogProtocol.ProveDlog import sigmastate.{SGroupElement, CreateProveDlog} import sigmastate.Values.{Value, SValue, SigmaPropValue} -import sigmastate.interpreter.CryptoConstants.EcPointType +import sigmastate.basics.CryptoConstants.EcPointType import sigmastate.lang.Terms._ import sigmastate.utils.SigmaByteWriter.DataInfo import sigmastate.utils.{SigmaByteReader, SigmaByteWriter} diff --git a/interpreter/src/main/scala/sigmastate/serialization/SelectFieldSerializer.scala b/interpreter/shared/src/main/scala/sigmastate/serialization/SelectFieldSerializer.scala similarity index 100% rename from interpreter/src/main/scala/sigmastate/serialization/SelectFieldSerializer.scala rename to interpreter/shared/src/main/scala/sigmastate/serialization/SelectFieldSerializer.scala diff --git a/interpreter/src/main/scala/sigmastate/serialization/SigmaPropBytesSerializer.scala b/interpreter/shared/src/main/scala/sigmastate/serialization/SigmaPropBytesSerializer.scala similarity index 100% rename from interpreter/src/main/scala/sigmastate/serialization/SigmaPropBytesSerializer.scala rename to interpreter/shared/src/main/scala/sigmastate/serialization/SigmaPropBytesSerializer.scala diff --git a/interpreter/src/main/scala/sigmastate/serialization/SigmaPropIsProvenSerializer.scala b/interpreter/shared/src/main/scala/sigmastate/serialization/SigmaPropIsProvenSerializer.scala similarity index 100% rename from interpreter/src/main/scala/sigmastate/serialization/SigmaPropIsProvenSerializer.scala rename to interpreter/shared/src/main/scala/sigmastate/serialization/SigmaPropIsProvenSerializer.scala diff --git a/interpreter/src/main/scala/sigmastate/serialization/SigmaSerializer.scala b/interpreter/shared/src/main/scala/sigmastate/serialization/SigmaSerializer.scala similarity index 98% rename from interpreter/src/main/scala/sigmastate/serialization/SigmaSerializer.scala rename to interpreter/shared/src/main/scala/sigmastate/serialization/SigmaSerializer.scala index 38c96f8636..eea880521a 100644 --- a/interpreter/src/main/scala/sigmastate/serialization/SigmaSerializer.scala +++ b/interpreter/shared/src/main/scala/sigmastate/serialization/SigmaSerializer.scala @@ -5,9 +5,9 @@ import java.nio.ByteBuffer import org.ergoplatform.SigmaConstants import org.ergoplatform.validation.SigmaValidationSettings import scorex.util.ByteArrayBuilder -import sigmastate.lang.exceptions.SerializerException import sigmastate.utils._ import scorex.util.serialization._ +import sigmastate.exceptions.SerializerException import sigmastate.serialization.OpCodes.OpCode object SigmaSerializer { diff --git a/interpreter/src/main/scala/sigmastate/serialization/SubstConstantsSerializer.scala b/interpreter/shared/src/main/scala/sigmastate/serialization/SubstConstantsSerializer.scala similarity index 100% rename from interpreter/src/main/scala/sigmastate/serialization/SubstConstantsSerializer.scala rename to interpreter/shared/src/main/scala/sigmastate/serialization/SubstConstantsSerializer.scala diff --git a/interpreter/src/main/scala/sigmastate/serialization/TaggedVariableSerializer.scala b/interpreter/shared/src/main/scala/sigmastate/serialization/TaggedVariableSerializer.scala similarity index 100% rename from interpreter/src/main/scala/sigmastate/serialization/TaggedVariableSerializer.scala rename to interpreter/shared/src/main/scala/sigmastate/serialization/TaggedVariableSerializer.scala diff --git a/interpreter/src/main/scala/sigmastate/serialization/TupleSerializer.scala b/interpreter/shared/src/main/scala/sigmastate/serialization/TupleSerializer.scala similarity index 100% rename from interpreter/src/main/scala/sigmastate/serialization/TupleSerializer.scala rename to interpreter/shared/src/main/scala/sigmastate/serialization/TupleSerializer.scala diff --git a/interpreter/src/main/scala/sigmastate/serialization/TwoArgumentsSerializer.scala b/interpreter/shared/src/main/scala/sigmastate/serialization/TwoArgumentsSerializer.scala similarity index 100% rename from interpreter/src/main/scala/sigmastate/serialization/TwoArgumentsSerializer.scala rename to interpreter/shared/src/main/scala/sigmastate/serialization/TwoArgumentsSerializer.scala diff --git a/interpreter/src/main/scala/sigmastate/serialization/TypeSerializer.scala b/interpreter/shared/src/main/scala/sigmastate/serialization/TypeSerializer.scala similarity index 99% rename from interpreter/src/main/scala/sigmastate/serialization/TypeSerializer.scala rename to interpreter/shared/src/main/scala/sigmastate/serialization/TypeSerializer.scala index f8b9f3de44..3d0f44007e 100644 --- a/interpreter/src/main/scala/sigmastate/serialization/TypeSerializer.scala +++ b/interpreter/shared/src/main/scala/sigmastate/serialization/TypeSerializer.scala @@ -3,10 +3,10 @@ package sigmastate.serialization import java.nio.charset.StandardCharsets import org.ergoplatform.validation.ValidationRules.{CheckPrimitiveTypeCode, CheckTypeCode} import sigmastate._ -import sigmastate.lang.exceptions.InvalidTypePrefix import sigmastate.util.safeNewArray import sigmastate.utils.{SigmaByteReader, SigmaByteWriter} import debox.cfor +import sigmastate.exceptions.InvalidTypePrefix /** Serialization of types according to specification in TypeSerialization.md. */ object TypeSerializer { diff --git a/interpreter/src/main/scala/sigmastate/serialization/ValDefSerializer.scala b/interpreter/shared/src/main/scala/sigmastate/serialization/ValDefSerializer.scala similarity index 100% rename from interpreter/src/main/scala/sigmastate/serialization/ValDefSerializer.scala rename to interpreter/shared/src/main/scala/sigmastate/serialization/ValDefSerializer.scala diff --git a/interpreter/src/main/scala/sigmastate/serialization/ValDefTypeStore.scala b/interpreter/shared/src/main/scala/sigmastate/serialization/ValDefTypeStore.scala similarity index 100% rename from interpreter/src/main/scala/sigmastate/serialization/ValDefTypeStore.scala rename to interpreter/shared/src/main/scala/sigmastate/serialization/ValDefTypeStore.scala diff --git a/interpreter/src/main/scala/sigmastate/serialization/ValUseSerializer.scala b/interpreter/shared/src/main/scala/sigmastate/serialization/ValUseSerializer.scala similarity index 100% rename from interpreter/src/main/scala/sigmastate/serialization/ValUseSerializer.scala rename to interpreter/shared/src/main/scala/sigmastate/serialization/ValUseSerializer.scala diff --git a/interpreter/src/main/scala/sigmastate/serialization/ValueSerializer.scala b/interpreter/shared/src/main/scala/sigmastate/serialization/ValueSerializer.scala similarity index 100% rename from interpreter/src/main/scala/sigmastate/serialization/ValueSerializer.scala rename to interpreter/shared/src/main/scala/sigmastate/serialization/ValueSerializer.scala diff --git a/interpreter/src/main/scala/sigmastate/serialization/transformers/AppendSerializer.scala b/interpreter/shared/src/main/scala/sigmastate/serialization/transformers/AppendSerializer.scala similarity index 100% rename from interpreter/src/main/scala/sigmastate/serialization/transformers/AppendSerializer.scala rename to interpreter/shared/src/main/scala/sigmastate/serialization/transformers/AppendSerializer.scala diff --git a/interpreter/src/main/scala/sigmastate/serialization/transformers/AtLeastSerializer.scala b/interpreter/shared/src/main/scala/sigmastate/serialization/transformers/AtLeastSerializer.scala similarity index 100% rename from interpreter/src/main/scala/sigmastate/serialization/transformers/AtLeastSerializer.scala rename to interpreter/shared/src/main/scala/sigmastate/serialization/transformers/AtLeastSerializer.scala diff --git a/interpreter/src/main/scala/sigmastate/serialization/transformers/BooleanTransformerSerializer.scala b/interpreter/shared/src/main/scala/sigmastate/serialization/transformers/BooleanTransformerSerializer.scala similarity index 100% rename from interpreter/src/main/scala/sigmastate/serialization/transformers/BooleanTransformerSerializer.scala rename to interpreter/shared/src/main/scala/sigmastate/serialization/transformers/BooleanTransformerSerializer.scala diff --git a/interpreter/src/main/scala/sigmastate/serialization/transformers/ByIndexSerializer.scala b/interpreter/shared/src/main/scala/sigmastate/serialization/transformers/ByIndexSerializer.scala similarity index 100% rename from interpreter/src/main/scala/sigmastate/serialization/transformers/ByIndexSerializer.scala rename to interpreter/shared/src/main/scala/sigmastate/serialization/transformers/ByIndexSerializer.scala diff --git a/interpreter/src/main/scala/sigmastate/serialization/transformers/DeserializeContextSerializer.scala b/interpreter/shared/src/main/scala/sigmastate/serialization/transformers/DeserializeContextSerializer.scala similarity index 100% rename from interpreter/src/main/scala/sigmastate/serialization/transformers/DeserializeContextSerializer.scala rename to interpreter/shared/src/main/scala/sigmastate/serialization/transformers/DeserializeContextSerializer.scala diff --git a/interpreter/src/main/scala/sigmastate/serialization/transformers/DeserializeRegisterSerializer.scala b/interpreter/shared/src/main/scala/sigmastate/serialization/transformers/DeserializeRegisterSerializer.scala similarity index 100% rename from interpreter/src/main/scala/sigmastate/serialization/transformers/DeserializeRegisterSerializer.scala rename to interpreter/shared/src/main/scala/sigmastate/serialization/transformers/DeserializeRegisterSerializer.scala diff --git a/interpreter/src/main/scala/sigmastate/serialization/transformers/ExtractRegisterAsSerializer.scala b/interpreter/shared/src/main/scala/sigmastate/serialization/transformers/ExtractRegisterAsSerializer.scala similarity index 100% rename from interpreter/src/main/scala/sigmastate/serialization/transformers/ExtractRegisterAsSerializer.scala rename to interpreter/shared/src/main/scala/sigmastate/serialization/transformers/ExtractRegisterAsSerializer.scala diff --git a/interpreter/src/main/scala/sigmastate/serialization/transformers/FilterSerializer.scala b/interpreter/shared/src/main/scala/sigmastate/serialization/transformers/FilterSerializer.scala similarity index 100% rename from interpreter/src/main/scala/sigmastate/serialization/transformers/FilterSerializer.scala rename to interpreter/shared/src/main/scala/sigmastate/serialization/transformers/FilterSerializer.scala diff --git a/interpreter/src/main/scala/sigmastate/serialization/transformers/FoldSerializer.scala b/interpreter/shared/src/main/scala/sigmastate/serialization/transformers/FoldSerializer.scala similarity index 100% rename from interpreter/src/main/scala/sigmastate/serialization/transformers/FoldSerializer.scala rename to interpreter/shared/src/main/scala/sigmastate/serialization/transformers/FoldSerializer.scala diff --git a/interpreter/src/main/scala/sigmastate/serialization/transformers/LogicalTransformerSerializer.scala b/interpreter/shared/src/main/scala/sigmastate/serialization/transformers/LogicalTransformerSerializer.scala similarity index 100% rename from interpreter/src/main/scala/sigmastate/serialization/transformers/LogicalTransformerSerializer.scala rename to interpreter/shared/src/main/scala/sigmastate/serialization/transformers/LogicalTransformerSerializer.scala diff --git a/interpreter/src/main/scala/sigmastate/serialization/transformers/MapCollectionSerializer.scala b/interpreter/shared/src/main/scala/sigmastate/serialization/transformers/MapCollectionSerializer.scala similarity index 100% rename from interpreter/src/main/scala/sigmastate/serialization/transformers/MapCollectionSerializer.scala rename to interpreter/shared/src/main/scala/sigmastate/serialization/transformers/MapCollectionSerializer.scala diff --git a/interpreter/src/main/scala/sigmastate/serialization/transformers/NumericCastSerializer.scala b/interpreter/shared/src/main/scala/sigmastate/serialization/transformers/NumericCastSerializer.scala similarity index 100% rename from interpreter/src/main/scala/sigmastate/serialization/transformers/NumericCastSerializer.scala rename to interpreter/shared/src/main/scala/sigmastate/serialization/transformers/NumericCastSerializer.scala diff --git a/interpreter/src/main/scala/sigmastate/serialization/transformers/ProveDHTupleSerializer.scala b/interpreter/shared/src/main/scala/sigmastate/serialization/transformers/ProveDHTupleSerializer.scala similarity index 96% rename from interpreter/src/main/scala/sigmastate/serialization/transformers/ProveDHTupleSerializer.scala rename to interpreter/shared/src/main/scala/sigmastate/serialization/transformers/ProveDHTupleSerializer.scala index 823055d101..b25d13d8de 100644 --- a/interpreter/src/main/scala/sigmastate/serialization/transformers/ProveDHTupleSerializer.scala +++ b/interpreter/shared/src/main/scala/sigmastate/serialization/transformers/ProveDHTupleSerializer.scala @@ -3,7 +3,7 @@ package sigmastate.serialization.transformers import sigmastate.{SGroupElement, CreateProveDHTuple} import sigmastate.Values.{Value, SigmaPropValue} import sigmastate.basics.ProveDHTuple -import sigmastate.interpreter.CryptoConstants.EcPointType +import sigmastate.basics.CryptoConstants.EcPointType import sigmastate.lang.Terms._ import sigmastate.utils.{SigmaByteReader, SigmaByteWriter} import sigmastate.serialization._ diff --git a/interpreter/src/main/scala/sigmastate/serialization/transformers/SigmaTransformerSerializer.scala b/interpreter/shared/src/main/scala/sigmastate/serialization/transformers/SigmaTransformerSerializer.scala similarity index 100% rename from interpreter/src/main/scala/sigmastate/serialization/transformers/SigmaTransformerSerializer.scala rename to interpreter/shared/src/main/scala/sigmastate/serialization/transformers/SigmaTransformerSerializer.scala diff --git a/interpreter/src/main/scala/sigmastate/serialization/transformers/SimpleTransformerSerializer.scala b/interpreter/shared/src/main/scala/sigmastate/serialization/transformers/SimpleTransformerSerializer.scala similarity index 100% rename from interpreter/src/main/scala/sigmastate/serialization/transformers/SimpleTransformerSerializer.scala rename to interpreter/shared/src/main/scala/sigmastate/serialization/transformers/SimpleTransformerSerializer.scala diff --git a/interpreter/src/main/scala/sigmastate/serialization/transformers/SliceSerializer.scala b/interpreter/shared/src/main/scala/sigmastate/serialization/transformers/SliceSerializer.scala similarity index 100% rename from interpreter/src/main/scala/sigmastate/serialization/transformers/SliceSerializer.scala rename to interpreter/shared/src/main/scala/sigmastate/serialization/transformers/SliceSerializer.scala diff --git a/interpreter/src/main/scala/sigmastate/serialization/trees/QuadrupleSerializer.scala b/interpreter/shared/src/main/scala/sigmastate/serialization/trees/QuadrupleSerializer.scala similarity index 100% rename from interpreter/src/main/scala/sigmastate/serialization/trees/QuadrupleSerializer.scala rename to interpreter/shared/src/main/scala/sigmastate/serialization/trees/QuadrupleSerializer.scala diff --git a/interpreter/src/main/scala/sigmastate/serialization/trees/Relation2Serializer.scala b/interpreter/shared/src/main/scala/sigmastate/serialization/trees/Relation2Serializer.scala similarity index 100% rename from interpreter/src/main/scala/sigmastate/serialization/trees/Relation2Serializer.scala rename to interpreter/shared/src/main/scala/sigmastate/serialization/trees/Relation2Serializer.scala diff --git a/interpreter/shared/src/main/scala/sigmastate/sigmastate.scala b/interpreter/shared/src/main/scala/sigmastate/sigmastate.scala new file mode 100644 index 0000000000..96d11e8a2e --- /dev/null +++ b/interpreter/shared/src/main/scala/sigmastate/sigmastate.scala @@ -0,0 +1,35 @@ +import sigmastate.Values._ +import sigmastate.lang.CheckingSigmaBuilder + +package object sigmastate { + import CheckingSigmaBuilder._ + + /** Helper method to create "+" operation node. */ + def Plus[T <: SNumericType](left: Value[T], right: Value[T]): Value[T] = + mkPlus(left, right) + + /** Helper method to create "-" operation node. */ + def Minus[T <: SNumericType](left: Value[T], right: Value[T]): Value[T] = + mkMinus(left, right) + + /** Helper method to create "*" operation node. */ + def Multiply[T <: SNumericType](left: Value[T], right: Value[T]): Value[T] = + mkMultiply(left, right) + + /** Helper method to create "/" operation node. */ + def Divide[T <: SNumericType](left: Value[T], right: Value[T]): Value[T] = + mkDivide(left, right) + + /** Helper method to create "%" operation node. */ + def Modulo[T <: SNumericType](left: Value[T], right: Value[T]): Value[T] = + mkModulo(left, right) + + /** Helper method to create "min" operation node. */ + def Min[T <: SNumericType](left: Value[T], right: Value[T]): Value[T] = + mkMin(left, right) + + /** Helper method to create "max" operation node. */ + def Max[T <: SNumericType](left: Value[T], right: Value[T]): Value[T] = + mkMax(left, right) + +} diff --git a/interpreter/src/main/scala/sigmastate/trees.scala b/interpreter/shared/src/main/scala/sigmastate/trees.scala similarity index 100% rename from interpreter/src/main/scala/sigmastate/trees.scala rename to interpreter/shared/src/main/scala/sigmastate/trees.scala diff --git a/interpreter/src/main/scala/sigmastate/types.scala b/interpreter/shared/src/main/scala/sigmastate/types.scala similarity index 99% rename from interpreter/src/main/scala/sigmastate/types.scala rename to interpreter/shared/src/main/scala/sigmastate/types.scala index 384f1b3b89..fe7630eb3e 100644 --- a/interpreter/src/main/scala/sigmastate/types.scala +++ b/interpreter/shared/src/main/scala/sigmastate/types.scala @@ -3,39 +3,39 @@ package sigmastate import java.math.BigInteger import org.ergoplatform._ import org.ergoplatform.validation._ -import scalan.{Nullable, RType} +import scalan.{RType, Nullable} import scalan.RType.GeneralType -import sigmastate.SType.{AnyOps, TypeCode} +import sigmastate.SType.{TypeCode, AnyOps} 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.{Insert, Lookup, Remove, Update} +import scorex.crypto.authds.{ADValue, ADKey} +import scorex.crypto.authds.avltree.batch.{Lookup, Remove, Insert, Update} import scorex.crypto.hash.Blake2b256 import sigmastate.Values._ import sigmastate.lang.Terms._ -import sigmastate.lang.{SigmaBuilder, SigmaTyper} +import sigmastate.lang.{SigmaBuilder, Terms} import sigmastate.SCollection._ -import sigmastate.interpreter.CryptoConstants.{EcPointType, hashLength} +import sigmastate.basics.CryptoConstants.{hashLength, EcPointType} import sigmastate.serialization.OpCodes import special.collection.Coll import special.sigma._ import scala.language.implicitConversions -import scala.reflect.{ClassTag, classTag} +import scala.reflect.{classTag, ClassTag} import scala.collection.compat.immutable.ArraySeq -import sigmastate.SMethod.{InvokeDescBuilder, MethodCallIrBuilder, MethodCostFunc, givenCost, javaMethodOf} +import sigmastate.SMethod.{InvokeDescBuilder, MethodCostFunc, givenCost, javaMethodOf, MethodCallIrBuilder} import sigmastate.utxo._ -import sigmastate.lang.SigmaTyper.STypeSubst +import sigmastate.lang.Terms.STypeSubst import sigmastate.eval.Evaluation.stypeToRType import sigmastate.eval._ -import sigmastate.lang.exceptions.MethodNotFound +import sigmastate.exceptions.MethodNotFound import debox.cfor -import scalan.reflection.{RClass, RMethod, CommonReflection} +import scalan.reflection.{CommonReflection, RClass, RMethod} import scala.collection.mutable -import scala.util.{Failure, Success} +import scala.util.{Success, Failure} /** Base type for all AST nodes of sigma lang. */ trait SigmaNode extends Product @@ -78,10 +78,15 @@ sealed trait SType extends SigmaNode { /** Elvis operator for types. See https://en.wikipedia.org/wiki/Elvis_operator*/ def ?:(whenNoType: => SType): SType = if (this == NoType) whenNoType else this + /** Applies a type substitution to this type. + * + * @param subst the type substitution to apply + * @return the type after applying the substitution + */ def withSubstTypes(subst: Map[STypeVar, SType]): SType = if (subst.isEmpty) this else - SigmaTyper.applySubst(this, subst) + Terms.applySubst(this, subst) /** Returns parsable type term string of the type described by this type descriptor. * For every type it should be inverse to SigmaTyper.parseType. @@ -560,7 +565,7 @@ case class SMethod( * @consensus */ def specializeFor(objTpe: SType, args: Seq[SType]): SMethod = { - SigmaTyper.unifyTypeLists(stype.tDom, objTpe +: args) match { + Terms.unifyTypeLists(stype.tDom, objTpe +: args) match { case Some(subst) if subst.nonEmpty => withConcreteTypes(subst) case _ => this @@ -747,7 +752,7 @@ trait SNumericType extends SProduct { import SNumericType._ protected override def getMethods(): Seq[SMethod] = { super.getMethods() ++ SNumericType.methods.map { - m => m.copy(stype = SigmaTyper.applySubst(m.stype, Map(tNum -> this)).asFunc) + m => m.copy(stype = Terms.applySubst(m.stype, Map(tNum -> this)).asFunc) } } @@ -1838,7 +1843,7 @@ object STuple extends STypeCompanion { // 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) + m.copy(stype = Terms.applySubst(m.stype, subst).asFunc) } } diff --git a/interpreter/src/main/scala/sigmastate/utils/Extensions.scala b/interpreter/shared/src/main/scala/sigmastate/utils/Extensions.scala similarity index 79% rename from interpreter/src/main/scala/sigmastate/utils/Extensions.scala rename to interpreter/shared/src/main/scala/sigmastate/utils/Extensions.scala index 24ee381108..5cdb9f70fc 100644 --- a/interpreter/src/main/scala/sigmastate/utils/Extensions.scala +++ b/interpreter/shared/src/main/scala/sigmastate/utils/Extensions.scala @@ -1,7 +1,9 @@ package sigmastate.utils +import org.ergoplatform.ErgoBox.TokenId +import scorex.util.{ModifierId, idToBytes} import scorex.utils.{Ints, Longs, Shorts} -import sigmastate.eval.SigmaDsl +import sigmastate.eval.{Digest32Coll, SigmaDsl} import special.collection.Coll object Extensions { @@ -58,4 +60,16 @@ object Extensions { */ def toBits: Coll[Boolean] = ??? } + + /** Provides extension methods for `ModifierId` instances. + * + * @param id the `ModifierId` instance + */ + implicit class ModifierIdOps(val id: ModifierId) extends AnyVal { + /** @return a `Coll[Byte]` representation of the `ModifierId` (decodes using Base16). */ + def toColl: Coll[Byte] = SigmaDsl.Colls.fromArray(idToBytes(id)) + + /** Converts this modifier id to to token id. */ + def toTokenId: TokenId = Digest32Coll @@ toColl + } } diff --git a/interpreter/src/main/scala/sigmastate/utils/Helpers.scala b/interpreter/shared/src/main/scala/sigmastate/utils/Helpers.scala similarity index 92% rename from interpreter/src/main/scala/sigmastate/utils/Helpers.scala rename to interpreter/shared/src/main/scala/sigmastate/utils/Helpers.scala index b3b23907c2..f3e6eddda1 100644 --- a/interpreter/src/main/scala/sigmastate/utils/Helpers.scala +++ b/interpreter/shared/src/main/scala/sigmastate/utils/Helpers.scala @@ -2,8 +2,9 @@ package sigmastate.utils import io.circe.Decoder import org.ergoplatform.settings.ErgoAlgos +import scorex.utils.Ints import sigmastate.eval.{Colls, SigmaDsl} -import sigmastate.interpreter.CryptoConstants.EcPointType +import sigmastate.basics.CryptoConstants.EcPointType import special.collection.Coll import special.sigma.GroupElement @@ -75,6 +76,14 @@ object Helpers { case arr: Array[Boolean] => util.Arrays.hashCode(arr) } + /** Optimized hashCode for array of bytes when it represents some hash thus it have + * enough randomness and we can use only first 4 bytes. + * @param id result of some hash function + */ + @inline final def safeIdHashCode(id: Array[Byte]): Int = + if (id != null && id.length >= 4) Ints.fromBytes(id(0), id(1), id(2), id(3)) + else util.Arrays.hashCode(id) + implicit class TryOps[+A](val source: Try[A]) extends AnyVal { def fold[B](onError: Throwable => B, onSuccess: A => B) = source match { case Success(value) => onSuccess(value) diff --git a/interpreter/src/main/scala/sigmastate/utils/SigmaByteReader.scala b/interpreter/shared/src/main/scala/sigmastate/utils/SigmaByteReader.scala similarity index 99% rename from interpreter/src/main/scala/sigmastate/utils/SigmaByteReader.scala rename to interpreter/shared/src/main/scala/sigmastate/utils/SigmaByteReader.scala index de0c971de3..2faae121d8 100644 --- a/interpreter/src/main/scala/sigmastate/utils/SigmaByteReader.scala +++ b/interpreter/shared/src/main/scala/sigmastate/utils/SigmaByteReader.scala @@ -5,10 +5,10 @@ import scorex.util.Extensions._ import scorex.util.serialization.Reader import sigmastate.SType import sigmastate.Values.{SValue, Value} -import sigmastate.lang.exceptions.DeserializeCallDepthExceeded import sigmastate.serialization._ import sigmastate.util.safeNewArray import debox.cfor +import sigmastate.exceptions.DeserializeCallDepthExceeded /** Reader used in the concrete implementations of [[SigmaSerializer]]. * It decorates the given reader, delegates most of the methods to it, but also adds new diff --git a/interpreter/src/main/scala/sigmastate/utils/SigmaByteWriter.scala b/interpreter/shared/src/main/scala/sigmastate/utils/SigmaByteWriter.scala similarity index 100% rename from interpreter/src/main/scala/sigmastate/utils/SigmaByteWriter.scala rename to interpreter/shared/src/main/scala/sigmastate/utils/SigmaByteWriter.scala diff --git a/interpreter/src/main/scala/sigmastate/utils/SparseArrayContainer.scala b/interpreter/shared/src/main/scala/sigmastate/utils/SparseArrayContainer.scala similarity index 100% rename from interpreter/src/main/scala/sigmastate/utils/SparseArrayContainer.scala rename to interpreter/shared/src/main/scala/sigmastate/utils/SparseArrayContainer.scala diff --git a/interpreter/src/main/scala/sigmastate/utxo/ComplexityTable.scala b/interpreter/shared/src/main/scala/sigmastate/utxo/ComplexityTable.scala similarity index 100% rename from interpreter/src/main/scala/sigmastate/utxo/ComplexityTable.scala rename to interpreter/shared/src/main/scala/sigmastate/utxo/ComplexityTable.scala diff --git a/interpreter/src/main/scala/sigmastate/utxo/ComplexityTableStat.scala b/interpreter/shared/src/main/scala/sigmastate/utxo/ComplexityTableStat.scala similarity index 100% rename from interpreter/src/main/scala/sigmastate/utxo/ComplexityTableStat.scala rename to interpreter/shared/src/main/scala/sigmastate/utxo/ComplexityTableStat.scala diff --git a/interpreter/src/main/scala/sigmastate/utxo/transformers.scala b/interpreter/shared/src/main/scala/sigmastate/utxo/transformers.scala similarity index 99% rename from interpreter/src/main/scala/sigmastate/utxo/transformers.scala rename to interpreter/shared/src/main/scala/sigmastate/utxo/transformers.scala index 5fd36c30d9..f78552197e 100644 --- a/interpreter/src/main/scala/sigmastate/utxo/transformers.scala +++ b/interpreter/shared/src/main/scala/sigmastate/utxo/transformers.scala @@ -10,9 +10,9 @@ import org.ergoplatform.ErgoBox.RegisterId import scalan.RType import sigmastate.Operations._ import sigmastate.eval.{Evaluation, SigmaDsl} +import sigmastate.exceptions.InterpreterException import sigmastate.interpreter.ErgoTreeEvaluator import sigmastate.interpreter.ErgoTreeEvaluator.{DataEnv, error} -import sigmastate.lang.exceptions.InterpreterException import special.collection.Coll import special.sigma.{Box, SigmaProp} diff --git a/interpreter/src/test/scala/org/ergoplatform/validation/ValidationSpecification.scala b/interpreter/shared/src/test/scala/org/ergoplatform/validation/ValidationSpecification.scala similarity index 100% rename from interpreter/src/test/scala/org/ergoplatform/validation/ValidationSpecification.scala rename to interpreter/shared/src/test/scala/org/ergoplatform/validation/ValidationSpecification.scala diff --git a/interpreter/src/test/scala/sigmastate/CalcSha256Specification.scala b/interpreter/shared/src/test/scala/sigmastate/CalcSha256Specification.scala similarity index 92% rename from interpreter/src/test/scala/sigmastate/CalcSha256Specification.scala rename to interpreter/shared/src/test/scala/sigmastate/CalcSha256Specification.scala index 44d3ac803d..9fc8333784 100644 --- a/interpreter/src/test/scala/sigmastate/CalcSha256Specification.scala +++ b/interpreter/shared/src/test/scala/sigmastate/CalcSha256Specification.scala @@ -3,11 +3,10 @@ package sigmastate import org.scalatest.prop.TableFor2 import scorex.util.encode.Base16 import sigmastate.Values.{ByteArrayConstant, CollectionConstant} -import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, ErgoLikeContextTesting, SigmaTestingCommons} +import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, ErgoLikeContextTesting, TestingCommons} -class CalcSha256Specification extends SigmaTestingCommons +class CalcSha256Specification extends TestingCommons with CrossVersionProps { - implicit lazy val IR = new TestingIRContext def stringToByteConstant(in: String): CollectionConstant[SByte.type] = ByteArrayConstant(in.getBytes("UTF-8")) def decodeString(in: String): CollectionConstant[SByte.type] = ByteArrayConstant(Base16.decode(in).get) diff --git a/interpreter/src/test/scala/sigmastate/CrossVersionProps.scala b/interpreter/shared/src/test/scala/sigmastate/CrossVersionProps.scala similarity index 73% rename from interpreter/src/test/scala/sigmastate/CrossVersionProps.scala rename to interpreter/shared/src/test/scala/sigmastate/CrossVersionProps.scala index 3b46ba4371..0c4056dcee 100644 --- a/interpreter/src/test/scala/sigmastate/CrossVersionProps.scala +++ b/interpreter/shared/src/test/scala/sigmastate/CrossVersionProps.scala @@ -1,25 +1,24 @@ package sigmastate -import org.scalatest.Tag +import debox.cfor import org.scalactic.source.Position +import scala.util.DynamicVariable +import org.scalatest.Tag import sigmastate.eval.Profiler -import debox.cfor import org.scalatest.propspec.AnyPropSpecLike -import scala.util.DynamicVariable - trait CrossVersionProps extends AnyPropSpecLike with TestsBase { - /** 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 override protected def property(testName: String, testTags: Tag*) - (testFun: => Any) - (implicit pos: Position): Unit = { - super.property(testName, testTags:_*) { + (testFun: => Any) + (implicit pos: Position): Unit = { + super.property(testName, testTags: _*) { // do warmup if necessary if (perTestWarmUpIters > 0) { _warmupProfiler.withValue(Some(new Profiler)) { @@ -28,18 +27,17 @@ trait CrossVersionProps extends AnyPropSpecLike with TestsBase { } } System.gc() - Thread.sleep(100) // give it some time to finish warm-up } - forEachScriptAndErgoTreeVersion(activatedVersions, ergoTreeVersions) { testFun_Run(testName, testFun) } - - if (okRunTestsWithoutMCLowering) { - _lowerMethodCalls.withValue(false) { - testFun_Run(testName, testFun) - } - } } } + + /** This methods is used to bypass profiling and forEachScriptAndErgoTreeVersion */ + protected def property2(testName: String, testTags: Tag*) + (testFun: => Any) + (implicit pos: Position): Unit = { + super.property(testName, testTags: _*)(testFun) + } } diff --git a/interpreter/shared/src/test/scala/sigmastate/CryptoFacadeSpecification.scala b/interpreter/shared/src/test/scala/sigmastate/CryptoFacadeSpecification.scala new file mode 100644 index 0000000000..9d5a8c116f --- /dev/null +++ b/interpreter/shared/src/test/scala/sigmastate/CryptoFacadeSpecification.scala @@ -0,0 +1,74 @@ +package sigmastate + +import org.ergoplatform.settings.ErgoAlgos +import org.scalatest.matchers.should.Matchers +import org.scalatest.propspec.AnyPropSpec +import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks +import scorex.util.encode.Base16 +import sigmastate.crypto.CryptoFacade + +import java.math.BigInteger + +class CryptoFacadeSpecification extends AnyPropSpec with Matchers with ScalaCheckPropertyChecks { + + property("CryptoFacade.HashHmacSHA512") { + val cases = Table( + ("string", "hash"), + ("abc", "2c15e87cde0f876fd8f060993748330cbe5f37c8bb3355e8ef44cea57890ec1d9b3274ef2b67bbe046cf8a012fba69796ec7803b1cc227521b9f5191e80a7da2"), + ("", "300b155f751964276c0536230bd9b16fe7a86533c3cbaa7575e8d0431dbedf23f9945bb8b052bd0b0802c10c7c852e7765b69b61ce7233d9fe5a35ab108ca3b6"), + ("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", "888ead7cb2ff330420333cac103f1062a6a443170108f6f74e2cdf39468015ae792c4a822664ce5d865424d2569d67bec03abd2df2a924977d635d06a0b550a3"), + ("abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu", "57edbc19570001de233edcb104237ea81439e59c5b0000d4db7bd991d228453827428d3cf30ecb4cdb17f00de3444579fa771f8933d2a7d9430b56dd989e55d9"), + ("a" * 1000, "2b53959bef11414f0592f8d6e834857734c7b9fcfbbfcafd84db989a85f7dfa76990a832fd24d8a3f804801898e59ac11a4dcd3e23a9121adc40a5498dcf4bc5") + ) + forAll(cases) { (in, expectedRes) => + val inBytes = in.getBytes(CryptoFacade.Encoding) + val res = CryptoFacade.hashHmacSHA512(CryptoFacade.BitcoinSeed, inBytes) + Base16.encode(res) shouldBe expectedRes + } + } + + property("CryptoFacade.normalizeChars") { + val cases = Table( + ("chars", "keyHex"), + ("slow silly start wash bundle suffer bulb ancient height spin express remind today effort helmet".toCharArray, + "slow silly start wash bundle suffer bulb ancient height spin express remind today effort helmet"), + ("pwd".toCharArray, "pwd") + ) + + forAll(cases) { (chars, expected) => + val res = CryptoFacade.normalizeChars(chars) + res shouldBe expected + } + } + + property("CryptoFacade.generatePbkdf2Key") { + val cases = Table( + ("mnemonic", "password", "keyHex"), + ("slow silly start wash bundle suffer bulb ancient height spin express remind today effort helmet", "", "e97efb594affe44261494fb366f6f3e8f506265b2865d3a5d173c5127d67c5d3fcb5bf7fe1b05cdad344df43ab87796810d545dbcba24f596275d8fceb846c98"), + ("slow silly start wash bundle suffer bulb ancient height spin express remind today effort helmet", "pwd", "0a8ea2ea0c4c12a9df88b005bda00c4de51ff36834b5fcd6a83667c371ad1da94bca1798690d87f2603b8f51d5ae025209e31f6cf81e12b84e4c543d236e58d0") + ) + + forAll(cases) { (mnemonic, password, keyHex) => + val res = CryptoFacade.generatePbkdf2Key(mnemonic, password) + Base16.encode(res) shouldBe keyHex + } + } + + property("CryptoFacade.encodePoint") { + val ctx = CryptoFacade.createCryptoContext() + val G = ctx.generator + val Q = ctx.order + val vectors = Table( + ("point", "expectedHex"), + (ctx.infinity(), "00"), + (CryptoFacade.exponentiatePoint(G, Q), "00"), + (G, "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"), + (CryptoFacade.exponentiatePoint(G, BigInteger.ONE), "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"), + (CryptoFacade.exponentiatePoint(G, Q.subtract(BigInteger.ONE)), "0379be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798") + ) + forAll (vectors) { (point, expectedHex) => + val res = ErgoAlgos.encode(CryptoFacade.getASN1Encoding(point, true)) + res shouldBe expectedHex + } + } +} \ No newline at end of file diff --git a/interpreter/src/test/scala/sigmastate/JitCostSpecification.scala b/interpreter/shared/src/test/scala/sigmastate/JitCostSpecification.scala similarity index 91% rename from interpreter/src/test/scala/sigmastate/JitCostSpecification.scala rename to interpreter/shared/src/test/scala/sigmastate/JitCostSpecification.scala index 3cf46f1075..4a434624a8 100644 --- a/interpreter/src/test/scala/sigmastate/JitCostSpecification.scala +++ b/interpreter/shared/src/test/scala/sigmastate/JitCostSpecification.scala @@ -1,10 +1,10 @@ package sigmastate -import special.sigma.SigmaTestingData +import sigmastate.helpers.TestingCommons -import scala.util.{Try, Failure} +import scala.util.{Failure, Try} -class JitCostSpecification extends SigmaTestingData { +class JitCostSpecification extends TestingCommons { type BinOp = (Int, Int) => Int type JitBinOp = (JitCost, JitCost) => JitCost diff --git a/interpreter/src/test/scala/sigmastate/SigmaProtocolSpecification.scala b/interpreter/shared/src/test/scala/sigmastate/SigmaProtocolSpecification.scala similarity index 100% rename from interpreter/src/test/scala/sigmastate/SigmaProtocolSpecification.scala rename to interpreter/shared/src/test/scala/sigmastate/SigmaProtocolSpecification.scala diff --git a/interpreter/shared/src/test/scala/sigmastate/TestsBase.scala b/interpreter/shared/src/test/scala/sigmastate/TestsBase.scala new file mode 100644 index 0000000000..2ac254da5b --- /dev/null +++ b/interpreter/shared/src/test/scala/sigmastate/TestsBase.scala @@ -0,0 +1,34 @@ +package sigmastate + +import org.scalatest.matchers.should.Matchers +import sigmastate.Values.{SigmaPropValue, SigmaBoolean, ErgoTree} +import org.ergoplatform.ErgoTreePredef + +trait TestsBase extends Matchers with VersionTesting { + /** Set this to true to enable debug console output in tests */ + val printDebugInfo: Boolean = false + + /** Print debug message if printDebugInfo is true */ + def printDebug(msg: Any): Unit = if (printDebugInfo) println(msg) + + /** Current ErgoTree header flags assigned dynamically using [[CrossVersionProps]] and + * ergoTreeVersionInTests. + */ + def ergoTreeHeaderInTests: Byte = ErgoTree.headerWithVersion(ergoTreeVersionInTests) + + /** Obtains [[ErgoTree]] which corresponds to True proposition using current + * ergoTreeHeaderInTests. */ + def TrueTree: ErgoTree = ErgoTreePredef.TrueProp(ergoTreeHeaderInTests) + + /** Obtains [[ErgoTree]] which corresponds to False proposition using current + * ergoTreeHeaderInTests. */ + def FalseTree: ErgoTree = ErgoTreePredef.FalseProp(ergoTreeHeaderInTests) + + /** Transform proposition into [[ErgoTree]] using current ergoTreeHeaderInTests. */ + def mkTestErgoTree(prop: SigmaPropValue): ErgoTree = + ErgoTree.fromProposition(ergoTreeHeaderInTests, prop) + + /** Transform sigma proposition into [[ErgoTree]] using current ergoTreeHeaderInTests. */ + def mkTestErgoTree(prop: SigmaBoolean): ErgoTree = + ErgoTree.fromSigmaBoolean(ergoTreeHeaderInTests, prop) +} diff --git a/interpreter/src/test/scala/sigmastate/crypto/GF2_192_Specification.scala b/interpreter/shared/src/test/scala/sigmastate/crypto/GF2_192_Specification.scala similarity index 76% rename from interpreter/src/test/scala/sigmastate/crypto/GF2_192_Specification.scala rename to interpreter/shared/src/test/scala/sigmastate/crypto/GF2_192_Specification.scala index b415fc99bd..952aa08375 100644 --- a/interpreter/src/test/scala/sigmastate/crypto/GF2_192_Specification.scala +++ b/interpreter/shared/src/test/scala/sigmastate/crypto/GF2_192_Specification.scala @@ -1,6 +1,5 @@ package sigmastate.crypto -import org.junit.Assert.assertFalse import org.scalatest.matchers.should.Matchers import org.scalatest.propspec.AnyPropSpec import org.scalatestplus.scalacheck.ScalaCheckDrivenPropertyChecks @@ -11,7 +10,15 @@ import java.util.{Arrays, Random} class GF2_192_Specification extends AnyPropSpec with ScalaCheckDrivenPropertyChecks with Matchers { - + + def assumeTrue(message: String, condition: Boolean) = { + assume(condition, message) + } + + def assumeFalse(message: String, condition: Boolean) = { + assume(!condition, message) + } + private object GF2t_slow { def mulBits(ret: GF2t_slow, a: Array[Long], b: Array[Long]): Unit = { val c = new Array[Long](a.length + b.length) @@ -187,19 +194,19 @@ class GF2_192_Specification extends AnyPropSpec property("constructorAndEqualityTest") { var t = new GF2_192 var r = t.toLongArray - assertFalse("Fail: empty constructor.", !t.isZero || r.length != 3 || r(0) != 0L || r(1) != 0L || r(2) != 0L) + assumeFalse("Fail: empty constructor.", !t.isZero || r.length != 3 || r(0) != 0L || r(1) != 0L || r(2) != 0L) t = new GF2_192(0) r = t.toLongArray - assertFalse("Fail: constructor on 0 int", !t.isZero || r.length != 3 || r(0) != 0L || r(1) != 0L || r(2) != 0L) + assumeFalse("Fail: constructor on 0 int", !t.isZero || r.length != 3 || r(0) != 0L || r(1) != 0L || r(2) != 0L) t = new GF2_192(1) r = t.toLongArray - assertFalse("Fail: constructor on 1 int", !t.isOne || r.length != 3 || r(0) != 1L || r(1) != 0L || r(2) != 0L) + assumeFalse("Fail: constructor on 1 int", !t.isOne || r.length != 3 || r(0) != 1L || r(1) != 0L || r(2) != 0L) t = new GF2_192(-1) r = t.toLongArray - assertFalse("Fail: constructor on 0xFFFFFFFF int " + t, r(0) != 0xFFFFFFFFL || r(1) != 0L || r(2) != 0L) + assumeFalse("Fail: constructor on 0xFFFFFFFF int " + t, r(0) != 0xFFFFFFFFL || r(1) != 0L || r(2) != 0L) var s = new Array[Long](3) @@ -211,10 +218,10 @@ class GF2_192_Specification extends AnyPropSpec var t1 = new GF2_192(t) r = t.toLongArray - assertFalse("Fail: constructor on long array", r(0) != s(0) || r(1) != s(1) || r(2) != s(2)) + assumeFalse("Fail: constructor on long array", r(0) != s(0) || r(1) != s(1) || r(2) != s(2)) r = t1.toLongArray - assertFalse("Fail: copy constructor", r(0) != s(0) || r(1) != s(1) || r(2) != s(2)) + assumeFalse("Fail: copy constructor", r(0) != s(0) || r(1) != s(1) || r(2) != s(2)) val b = new Array[Byte](24) for ( i <- 0 until 8 ) { @@ -229,11 +236,11 @@ class GF2_192_Specification extends AnyPropSpec t = new GF2_192(b) s = t.toLongArray - assertFalse("Fail: constructor on byte array", + assumeFalse("Fail: constructor on byte array", r(0) != s(0) || r(1) != s(1) || r(2) != s(2)) val c = t.toByteArray - assertFalse("Fail: toByteArray", !Arrays.equals(b, c)) + assumeFalse("Fail: toByteArray", !Arrays.equals(b, c)) var b2 = new Array[Byte](30) for ( i <- 0 until 24 ) { @@ -242,16 +249,16 @@ class GF2_192_Specification extends AnyPropSpec t = new GF2_192(b2, 6) s = t.toLongArray - assertFalse("Fail: constructor on byte array with offset", + assumeFalse("Fail: constructor on byte array with offset", r(0) != s(0) || r(1) != s(1) || r(2) != s(2)) var b1 = t.toByteArray - assertFalse("Fail: toByteArray", !Arrays.equals(b, b1)) + assumeFalse("Fail: toByteArray", !Arrays.equals(b, b1)) var b3 = new Array[Byte](40) t.toByteArray(b3, 10) for ( i <- 0 until b.length ) { - assertFalse("Fail: toByteArray with offset", + assumeFalse("Fail: toByteArray with offset", b3(i + 10) != b(i)) } @@ -262,11 +269,11 @@ class GF2_192_Specification extends AnyPropSpec t1 = new GF2_192(t) r = t.toLongArray - assertFalse("Fail: constructor on long array of all 1s", + assumeFalse("Fail: constructor on long array of all 1s", r(0) != s(0) || r(1) != s(1) || r(2) != s(2)) r = t1.toLongArray - assertFalse("Fail: copy constructor", + assumeFalse("Fail: copy constructor", r(0) != s(0) || r(1) != s(1) || r(2) != s(2)) for ( i <- 0 until 8 ) { @@ -283,11 +290,11 @@ class GF2_192_Specification extends AnyPropSpec t = new GF2_192(b) s = t.toLongArray - assertFalse("Fail: constructor on byte array of all 1s", + assumeFalse("Fail: constructor on byte array of all 1s", r(0) != s(0) || r(1) != s(1) || r(2) != s(2)) b1 = t.toByteArray - assertFalse("Fail: toByteArray all 1s", !Arrays.equals(b, b1)) + assumeFalse("Fail: toByteArray all 1s", !Arrays.equals(b, b1)) b2 = new Array[Byte](30) for ( i <- 0 until 24 ) { @@ -295,16 +302,16 @@ class GF2_192_Specification extends AnyPropSpec } t = new GF2_192(b2, 6) s = t.toLongArray - assertFalse("Fail: constructor on byte array with offset of all 1s", + assumeFalse("Fail: constructor on byte array with offset of all 1s", r(0) != s(0) || r(1) != s(1) || r(2) != s(2)) b1 = t.toByteArray - assertFalse("Fail: toByteArray all 1s", !Arrays.equals(b, b1)) + assumeFalse("Fail: toByteArray all 1s", !Arrays.equals(b, b1)) b3 = new Array[Byte](40) t.toByteArray(b3, 10) for ( i <- 0 until b.length ) { - assertFalse("Fail: toByteArray all 1s with offset", b3(i + 10) != b(i)) + assumeFalse("Fail: toByteArray all 1s with offset", b3(i + 10) != b(i)) } } @@ -315,33 +322,33 @@ class GF2_192_Specification extends AnyPropSpec val maxK = 15 for ( k <- 0 until maxK ) { GF2_192.power2To2ToK(res, zero, k) - assertFalse("Fail: power2To2ToK of 0 for k=" + k, !res.isZero) + assumeFalse("Fail: power2To2ToK of 0 for k=" + k, !res.isZero) z = new GF2_192(zero) GF2_192.power2To2ToK(z, z, k) - assertFalse("Fail: power2To2ToK of 0 in place for k=" + k, !z.isZero) + assumeFalse("Fail: power2To2ToK of 0 in place for k=" + k, !z.isZero) GF2_192.power2To2ToK(res, one, k) - assertFalse("Fail: power2To2ToK of 1 for k=" + k, !res.isOne) + assumeFalse("Fail: power2To2ToK of 1 for k=" + k, !res.isOne) z = new GF2_192(one) GF2_192.power2To2ToK(z, z, k) - assertFalse("Fail: power2To2ToK of 1 in place for k=" + k, !z.isOne) + assumeFalse("Fail: power2To2ToK of 1 in place for k=" + k, !z.isOne) } GF2_192.sqr(res, zero) - assertFalse("Fail: sqr of 0", !res.isZero) + assumeFalse("Fail: sqr of 0", !res.isZero) z = new GF2_192(zero) GF2_192.sqr(z, z) - assertFalse("Fail: sqr of 0 in place", !z.isZero) + assumeFalse("Fail: sqr of 0 in place", !z.isZero) GF2_192.sqr(res, one) - assertFalse("Fail: sqr of 1", !res.isOne) + assumeFalse("Fail: sqr of 1", !res.isOne) z = new GF2_192(one) GF2_192.sqr(z, z) - assertFalse("Fail: sqr of 1 in place", !z.isOne) + assumeFalse("Fail: sqr of 1 in place", !z.isOne) val res1 = new GF2_192 val res2 = new GF2_192 @@ -352,23 +359,23 @@ class GF2_192_Specification extends AnyPropSpec if (k == 0) { // Ground truth for squaring: self-multiply GF2_192.mul(res1, z, z) // sqr should equal power2To2ToK with k = 0 - assertFalse("Fail: power2To2To1 " + z, !(res == res1)) + assumeFalse("Fail: power2To2To1 " + z, !(res == res1)) GF2_192.sqr(res2, z) // sqr should equal self-multiply with k = 0 - assertFalse("Fail: sqr for k = " + k + " value = " + z, !(res == res2)) + assumeFalse("Fail: sqr for k = " + k + " value = " + z, !(res == res2)) } else { // res1 is the ground truth, computed using smaller values of k than is currently being tested GF2_192.power2To2ToK(res1, res1, k - 1) - assertFalse("Fail: power2To2ToK for k = " + k + " value = " + z, !(res == res1)) + assumeFalse("Fail: power2To2ToK for k = " + k + " value = " + z, !(res == res1)) } // Input location = output location tests GF2_192.power2To2ToK(z, z, k) // power2To2ToK into same location - assertFalse("Fail: power2To2ToK in place for k = " + k + " value = " + new GF2_192(p), !(res == z)) + assumeFalse("Fail: power2To2ToK in place for k = " + k + " value = " + new GF2_192(p), !(res == z)) if (k == 0) { z = new GF2_192(p) GF2_192.sqr(z, z) // sqr into same location - assertFalse("Fail: sqr in place " + new GF2_192(p), !(res == z)) + assumeFalse("Fail: sqr in place " + new GF2_192(p), !(res == z)) } } } @@ -385,23 +392,23 @@ class GF2_192_Specification extends AnyPropSpec for ( p <- testValues ) { var p1 = new GF2_192(p) GF2_192.mul(res, p1, zero) - assertFalse("Fail: " + p1 + " * 0", !res.isZero) + assumeFalse("Fail: " + p1 + " * 0", !res.isZero) GF2_192.mul(p1, p1, zero) - assertFalse("Fail: " + p1 + " * 0" + " in place ", !p1.isZero) + assumeFalse("Fail: " + p1 + " * 0" + " in place ", !p1.isZero) p1 = new GF2_192(p) GF2_192.mul(res, zero, p1) - assertFalse("Fail: 0 * " + p1, !res.isZero) + assumeFalse("Fail: 0 * " + p1, !res.isZero) GF2_192.mul(p1, zero, p1) - assertFalse("Fail: 0 * " + p1 + " in place ", !p1.isZero) + assumeFalse("Fail: 0 * " + p1 + " in place ", !p1.isZero) p1 = new GF2_192(p) GF2_192.mul(res, p1, one) - assertFalse("Fail: " + p1 + " * 1", !(res == p1)) + assumeFalse("Fail: " + p1 + " * 1", !(res == p1)) GF2_192.mul(p1, p1, one) - assertFalse("Fail: " + p1 + " * 1 in place", !(res == p1)) + assumeFalse("Fail: " + p1 + " * 1 in place", !(res == p1)) GF2_192.mul(res, one, p1) - assertFalse("Fail: 1 * " + p1, !(res == p1)) + assumeFalse("Fail: 1 * " + p1, !(res == p1)) GF2_192.mul(p1, one, p1) - assertFalse("Fail: 1 * " + p1 + " in place", !(res == p1)) + assumeFalse("Fail: 1 * " + p1 + " in place", !(res == p1)) } // Run everything times 0 @@ -410,13 +417,13 @@ class GF2_192_Specification extends AnyPropSpec for ( p <- testValues ) { val p1 = new GF2_192(p) GF2_192.mul(res, p1, 1.toByte) - assertFalse("Fail: " + p1 + " * 1 byte ", !(res == p1)) + assumeFalse("Fail: " + p1 + " * 1 byte ", !(res == p1)) GF2_192.mul(p1, p1, 1.toByte) - assertFalse("Fail: " + p1 + " * 1 byte in place", !(res == p1)) + assumeFalse("Fail: " + p1 + " * 1 byte in place", !(res == p1)) GF2_192.mul(res, p1, 0.toByte) - assertFalse("Fail: " + p1 + " * 0 byte", !res.isZero) + assumeFalse("Fail: " + p1 + " * 0 byte", !res.isZero) GF2_192.mul(p1, p1, 0.toByte) - assertFalse("Fail: " + p1 + " * 0 byte in place", !p1.isZero) + assumeFalse("Fail: " + p1 + " * 0 byte in place", !p1.isZero) } // Run everything times every byte @@ -428,9 +435,9 @@ class GF2_192_Specification extends AnyPropSpec GF2_192.mul(res, p1, i.toByte) GF2t_slow.mulBits(res1, p, temp) GF2t_slow.modReduce(res1, m) - assertFalse("Fail: " + p1 + " * " + i + " byte", !res1.equals(res.toLongArray)) + assumeFalse("Fail: " + p1 + " * " + i + " byte", !res1.equals(res.toLongArray)) GF2_192.mul(p1, p1, i.toByte) - assertFalse("Fail: " + p1 + " * " + i + " byte in place", !(res == p1)) + assumeFalse("Fail: " + p1 + " * " + i + " byte in place", !(res == p1)) } } } @@ -443,13 +450,13 @@ class GF2_192_Specification extends AnyPropSpec for ( p <- testValues ) { val p1 = new GF2_192(p) GF2_192.add(res, p1, zero) - assertFalse(s"Fail: $p1 + 0", !(res == p1)) + assumeFalse(s"Fail: $p1 + 0", !(res == p1)) GF2_192.add(p1, p1, zero) - assertFalse(s"Fail: $p1 + 0 in place", !(res == p1)) + assumeFalse(s"Fail: $p1 + 0 in place", !(res == p1)) GF2_192.add(res, zero, p1) - assertFalse(s"Fail: 0 + $p1", !(res == p1)) + assumeFalse(s"Fail: 0 + $p1", !(res == p1)) GF2_192.add(p1, zero, p1) - assertFalse(s"Fail: $p1 + 0 in place", !(res == p1)) + assumeFalse(s"Fail: $p1 + 0 in place", !(res == p1)) } } @@ -467,15 +474,15 @@ class GF2_192_Specification extends AnyPropSpec res1.x(0) = p(0) ^ q(0) res1.x(1) = p(1) ^ q(1) res1.x(2) = p(2) ^ q(2) - assertFalse(s"Fail: $p1 + $q1 = $res not $res1", + assumeFalse(s"Fail: $p1 + $q1 = $res not $res1", !res1.equals(res.toLongArray)) GF2_192.add(p1, p1, q1) - assertFalse(s"Fail: $p1 + $q1 in place 1 ", !(res == p1)) + assumeFalse(s"Fail: $p1 + $q1 in place 1 ", !(res == p1)) p1 = new GF2_192(p) GF2_192.add(q1, p1, q1) - assertFalse(s"Fail: $p1 + $q1 in place 2 ", !(res == q1)) + assumeFalse(s"Fail: $p1 + $q1 in place 2 ", !(res == q1)) } } @@ -483,10 +490,10 @@ class GF2_192_Specification extends AnyPropSpec for ( p <- testValues ) { val p1 = new GF2_192(p) GF2_192.add(res, p1, p1) - assertFalse(s"Fail: $p1 + self", !res.isZero) + assumeFalse(s"Fail: $p1 + self", !res.isZero) GF2_192.add(p1, p1, p1) - assertFalse(s"Fail: $p1 self in place", !p1.isZero) + assumeFalse(s"Fail: $p1 self in place", !p1.isZero) } } @@ -503,14 +510,14 @@ class GF2_192_Specification extends AnyPropSpec GF2_192.mul(res, p1, q1) GF2t_slow.mulBits(res1, p, q) GF2t_slow.modReduce(res1, m) - assertFalse(s"Fail: $p1 * $q1", !res1.equals(res.toLongArray)) + assumeFalse(s"Fail: $p1 * $q1", !res1.equals(res.toLongArray)) GF2_192.mul(p1, p1, q1) - assertFalse(s"Fail: $p1 * $q1 in place 1 ", !(res == p1)) + assumeFalse(s"Fail: $p1 * $q1 in place 1 ", !(res == p1)) p1 = new GF2_192(p) GF2_192.mul(q1, p1, q1) - assertFalse(s"Fail: $p1 * $q1 in place 2 ", !(res == q1)) + assumeFalse(s"Fail: $p1 * $q1 in place 2 ", !(res == q1)) } } @@ -519,7 +526,7 @@ class GF2_192_Specification extends AnyPropSpec val p1 = new GF2_192(p) GF2_192.sqr(res, p1) GF2_192.mul(p1, p1, p1) - assertFalse(s"Fail: $p1 * self in place", !(res == p1)) + assumeFalse(s"Fail: $p1 * self in place", !(res == p1)) } } @@ -530,7 +537,7 @@ class GF2_192_Specification extends AnyPropSpec // Test inversion of 1 GF2_192.invert(res, one) - assertFalse("Fail: inversion of 1", !res.isOne) + assumeFalse("Fail: inversion of 1", !res.isOne) // Test inversion of everything for ( p <- testValues ) { @@ -538,14 +545,14 @@ class GF2_192_Specification extends AnyPropSpec if (!p1.isZero) { GF2_192.invert(res, p1) GF2_192.mul(res2, p1, res) - assertFalse(s"Fail: inversion of $p1 self-test ", !res2.isOne) + assumeFalse(s"Fail: inversion of $p1 self-test ", !res2.isOne) GF2t_slow.mulBits(res1, res.toLongArray, p) GF2t_slow.modReduce(res1, m) - assertFalse(s"Fail: inversion of $p1 GF2t_slow-test", !res1.isOne) + assumeFalse(s"Fail: inversion of $p1 GF2t_slow-test", !res1.isOne) GF2_192.invert(p1, p1) - assertFalse(s"Fail: inversion of $p1 in place ", !(p1 == res)) + assumeFalse(s"Fail: inversion of $p1 in place ", !(p1 == res)) } } } @@ -562,13 +569,13 @@ class GF2_192_Specification extends AnyPropSpec // Try with arrays of length 0 var p = GF2_192_Poly.interpolate( new Array[Byte](0), new Array[GF2_192](0), new GF2_192(0)) - assertFalse("Zero polynomial should be 0 at 0", !p.evaluate(0.toByte).isZero) - assertFalse("Zero polynomial should be 0 at 5", !p.evaluate(5.toByte).isZero) + assumeFalse("Zero polynomial should be 0 at 0", !p.evaluate(0.toByte).isZero) + assumeFalse("Zero polynomial should be 0 at 5", !p.evaluate(5.toByte).isZero) val val17 = new GF2_192(17) p = GF2_192_Poly.interpolate(new Array[Byte](0), new Array[GF2_192](0), val17) - assertFalse("Constant 17 polynomial should be 17 at 0", !(p.evaluate(0.toByte) == val17)) - assertFalse("Constant 17 polynomial should be 17 at 5", !(p.evaluate(5.toByte) == val17)) + assumeFalse("Constant 17 polynomial should be 17 at 0", !(p.evaluate(0.toByte) == val17)) + assumeFalse("Constant 17 polynomial should be 17 at 5", !(p.evaluate(5.toByte) == val17)) for ( len <- 1 until 100 ) { val points = new Array[Byte](len) @@ -603,33 +610,33 @@ class GF2_192_Specification extends AnyPropSpec res = GF2_192_Poly.interpolate(points, values, null) for ( i <- 0 until len ) { val t = res.evaluate(points(i)) - assertFalse(s"Interpolation error on length = $len at input point number $i", !(t == values(i))) + assumeFalse(s"Interpolation error on length = $len at input point number $i", !(t == values(i))) } rand.nextBytes(temp) val valueAt0 = new GF2_192(temp) res = GF2_192_Poly.interpolate(points, values, valueAt0) for ( i <- 0 until len ) { val t = res.evaluate(points(i)) - assertFalse(s"Interpolation error on length = $len at input point number $i(with optional 0)", !(t == values(i))) + assumeFalse(s"Interpolation error on length = $len at input point number $i(with optional 0)", !(t == values(i))) } val t = res.evaluate(0.toByte) - assertFalse(s"Interpolation error on length = $len at input optional 0", !(t == valueAt0)) + assumeFalse(s"Interpolation error on length = $len at input optional 0", !(t == valueAt0)) val b = res.toByteArray(false) val t1 = GF2_192_Poly.fromByteArray(valueAt0.toByteArray, b) val b1 = t1.toByteArray(false) - assertFalse( + assumeFalse( s"To byte array round trip error ${util.Arrays.toString(b)} ${util.Arrays.toString(b1)}", !Arrays.equals(b, b1)) val b2 = t1.toByteArray(true) - assertFalse("To byte array round trip error at coeff0", + assumeFalse("To byte array round trip error at coeff0", !Arrays.equals(valueAt0.toByteArray, Arrays.copyOfRange(b2, 0, 24))) - assertFalse("To byte array round trip error with coeff0 at later coeff", + assumeFalse("To byte array round trip error with coeff0 at later coeff", !Arrays.equals(b1, Arrays.copyOfRange(b2, 24, b2.length))) val b3 = t1.coeff0Bytes - assertFalse("To byte array round trip error on coeff0", + assumeFalse("To byte array round trip error on coeff0", !Arrays.equals(b3, valueAt0.toByteArray)) } @@ -667,27 +674,27 @@ class GF2_192_Specification extends AnyPropSpec res = GF2_192_Poly.interpolate(points, values, null) for ( i <- 0 until len ) { val t = res.evaluate(points(i)) - assertFalse(s"Interpolation error on length = $len $i(with 0 allowed but not additional)", !(t == values(i))) + assumeFalse(s"Interpolation error on length = $len $i(with 0 allowed but not additional)", !(t == values(i))) } for ( opt <- optArray ) { res = GF2_192_Poly.interpolate(null, values, opt) - assertFalse("Fail: interpolate should output null on points = null", res != null) + assumeFalse("Fail: interpolate should output null on points = null", res != null) res = GF2_192_Poly.interpolate(points, null, opt) - assertFalse("Fail: interpolate should output null on values = null", res != null) + assumeFalse("Fail: interpolate should output null on values = null", res != null) res = GF2_192_Poly.interpolate(points, new Array[GF2_192](0), opt) - assertFalse("Fail: interpolate should output null on values of length 0", res != null) + assumeFalse("Fail: interpolate should output null on values of length 0", res != null) res = GF2_192_Poly.interpolate(new Array[Byte](0), values, opt) - assertFalse("Fail: interpolate should output null on points of length 0", res != null) + assumeFalse("Fail: interpolate should output null on points of length 0", res != null) res = GF2_192_Poly.interpolate(new Array[Byte](len - 1), values, opt) - assertFalse("Fail: interpolate should output null on not enough points", res != null) + assumeFalse("Fail: interpolate should output null on not enough points", res != null) res = GF2_192_Poly.interpolate(new Array[Byte](len + 1), values, opt) - assertFalse("Fail: interpolate should output null on too many points", res != null) + assumeFalse("Fail: interpolate should output null on too many points", res != null) } } for ( opt <- optArray ) { res = GF2_192_Poly.interpolate(null, null, opt) - assertFalse("Fail: interpolate should output null on both points and values = null", res != null) + assumeFalse("Fail: interpolate should output null on both points and values = null", res != null) } } } diff --git a/interpreter/src/test/scala/sigmastate/crypto/GroupLawsSpecification.scala b/interpreter/shared/src/test/scala/sigmastate/crypto/GroupLawsSpecification.scala similarity index 77% rename from interpreter/src/test/scala/sigmastate/crypto/GroupLawsSpecification.scala rename to interpreter/shared/src/test/scala/sigmastate/crypto/GroupLawsSpecification.scala index 7b998a22ac..45863a18a6 100644 --- a/interpreter/src/test/scala/sigmastate/crypto/GroupLawsSpecification.scala +++ b/interpreter/shared/src/test/scala/sigmastate/crypto/GroupLawsSpecification.scala @@ -2,14 +2,16 @@ package sigmastate.crypto import java.math.BigInteger import org.scalacheck.Gen -import sigmastate.helpers.{SigmaPPrint, SigmaTestingCommons} -import sigmastate.interpreter.CryptoConstants -import sigmastate.interpreter.CryptoConstants.EcPointType +import sigmastate.basics.CryptoConstants +import CryptoConstants.EcPointType +import org.scalatest.propspec.AnyPropSpec +import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks +import sigmastate.TestsBase import sigmastate.utils.Helpers import scala.util.Random -class GroupLawsSpecification extends SigmaTestingCommons { +class GroupLawsSpecification extends AnyPropSpec with ScalaCheckPropertyChecks with TestsBase { private val group = CryptoConstants.dlogGroup val groupElementGen: Gen[EcPointType] = Gen.const(group.createRandomElement()) @@ -54,13 +56,12 @@ class GroupLawsSpecification extends SigmaTestingCommons { } } - private def printPoints(points: Seq[(String, Any)]) = { - points.foreach { case (name, p) => - printDebug(s"val $name = ${SigmaPPrint.apply(p).plainText}") - } - } - // uncommment to generate new test vectors +//def printPoints(points: Seq[(String, Any)]) = { +// points.foreach { case (name, p) => +// println(s"val $name = ${SigmaPPrint.apply(p).plainText}") +// } +//} // // property("generate initial points") { // printPoints(Seq( @@ -77,17 +78,17 @@ class GroupLawsSpecification extends SigmaTestingCommons { val p3 = Helpers.decodeECPoint("02e135f5f905fb843698d48959c6c792b2c6f9605b90be2280d53b4b69ef23e8a9") // uncommment to generate new test vectors - property("generate op results") { - printPoints(Seq( - "p1" -> p1, - "p2" -> p2, - "p3" -> p3, - "p1.add(p2)" -> CryptoFacade.multiplyPoints(p1, p2), - "p1.multiply(order)" -> CryptoFacade.exponentiatePoint(p1, group.order), - "p1.multiply(order + 1)" -> CryptoFacade.exponentiatePoint(p1, group.order.add(BigInteger.ONE)), - "p1.inverse" -> CryptoFacade.negatePoint(p1) - )) - } +// property("generate op results") { +// printPoints(Seq( +// "p1" -> p1, +// "p2" -> p2, +// "p3" -> p3, +// "p1.add(p2)" -> CryptoFacade.multiplyPoints(p1, p2), +// "p1.multiply(order)" -> CryptoFacade.exponentiatePoint(p1, group.order), +// "p1.multiply(order + 1)" -> CryptoFacade.exponentiatePoint(p1, group.order.add(BigInteger.ONE)), +// "p1.inverse" -> CryptoFacade.negatePoint(p1) +// )) +// } property("check test vectors") { CryptoFacade.multiplyPoints(p1, p2) shouldBe diff --git a/interpreter/src/test/scala/sigmastate/crypto/SigningSpecification.scala b/interpreter/shared/src/test/scala/sigmastate/crypto/SigningSpecification.scala similarity index 97% rename from interpreter/src/test/scala/sigmastate/crypto/SigningSpecification.scala rename to interpreter/shared/src/test/scala/sigmastate/crypto/SigningSpecification.scala index 7a49eeeae5..4ac2f7c8da 100644 --- a/interpreter/src/test/scala/sigmastate/crypto/SigningSpecification.scala +++ b/interpreter/shared/src/test/scala/sigmastate/crypto/SigningSpecification.scala @@ -2,18 +2,15 @@ package sigmastate.crypto import org.scalacheck.Gen import scorex.util.encode.Base16 -import sigmastate.{AtLeast, CAND, COR} +import sigmastate.{AtLeast, COR, CAND} 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.helpers.{ErgoLikeTestInterpreter, ErgoLikeTestProvingInterpreter, TestingCommons} +import sigmastate.interpreter.{HintsBag, ContextExtension, ProverResult} import sigmastate.serialization.transformers.ProveDHTupleSerializer -import sigmastate.lang.StdSigmaBuilder import sigmastate.basics.ProveDHTuple -class SigningSpecification extends SigmaTestingCommons { - implicit lazy val IR: TestingIRContext = new TestingIRContext +class SigningSpecification extends TestingCommons { property("simple signature test vector") { diff --git a/interpreter/src/test/scala/sigmastate/eval/BasicOpsTests.scala b/interpreter/shared/src/test/scala/sigmastate/eval/BasicOpsTests.scala similarity index 100% rename from interpreter/src/test/scala/sigmastate/eval/BasicOpsTests.scala rename to interpreter/shared/src/test/scala/sigmastate/eval/BasicOpsTests.scala diff --git a/interpreter/src/test/scala/sigmastate/helpers/ContextEnrichingProverInterpreter.scala b/interpreter/shared/src/test/scala/sigmastate/helpers/ContextEnrichingProverInterpreter.scala similarity index 100% rename from interpreter/src/test/scala/sigmastate/helpers/ContextEnrichingProverInterpreter.scala rename to interpreter/shared/src/test/scala/sigmastate/helpers/ContextEnrichingProverInterpreter.scala diff --git a/interpreter/src/test/scala/sigmastate/helpers/ContextEnrichingTestProvingInterpreter.scala b/interpreter/shared/src/test/scala/sigmastate/helpers/ContextEnrichingTestProvingInterpreter.scala similarity index 100% rename from interpreter/src/test/scala/sigmastate/helpers/ContextEnrichingTestProvingInterpreter.scala rename to interpreter/shared/src/test/scala/sigmastate/helpers/ContextEnrichingTestProvingInterpreter.scala diff --git a/interpreter/src/test/scala/sigmastate/helpers/ErgoLikeContextTesting.scala b/interpreter/shared/src/test/scala/sigmastate/helpers/ErgoLikeContextTesting.scala similarity index 97% rename from interpreter/src/test/scala/sigmastate/helpers/ErgoLikeContextTesting.scala rename to interpreter/shared/src/test/scala/sigmastate/helpers/ErgoLikeContextTesting.scala index 2d734663fc..2945192700 100644 --- a/interpreter/src/test/scala/sigmastate/helpers/ErgoLikeContextTesting.scala +++ b/interpreter/shared/src/test/scala/sigmastate/helpers/ErgoLikeContextTesting.scala @@ -5,8 +5,9 @@ import org.ergoplatform.ErgoLikeContext.Height import org.ergoplatform._ import org.ergoplatform.validation.{ValidationRules, SigmaValidationSettings} import sigmastate.AvlTreeData +import sigmastate.basics.CryptoConstants import sigmastate.eval._ -import sigmastate.interpreter.{ContextExtension, CryptoConstants} +import sigmastate.interpreter.ContextExtension import sigmastate.serialization.{SigmaSerializer, GroupElementSerializer} import special.collection.Coll import special.sigma.{Box, PreHeader, Header} diff --git a/interpreter/src/test/scala/sigmastate/helpers/ErgoLikeTestProvingInterpreter.scala b/interpreter/shared/src/test/scala/sigmastate/helpers/ErgoLikeTestProvingInterpreter.scala similarity index 100% rename from interpreter/src/test/scala/sigmastate/helpers/ErgoLikeTestProvingInterpreter.scala rename to interpreter/shared/src/test/scala/sigmastate/helpers/ErgoLikeTestProvingInterpreter.scala diff --git a/interpreter/src/test/scala/sigmastate/helpers/ErgoLikeTransactionTesting.scala b/interpreter/shared/src/test/scala/sigmastate/helpers/ErgoLikeTransactionTesting.scala similarity index 100% rename from interpreter/src/test/scala/sigmastate/helpers/ErgoLikeTransactionTesting.scala rename to interpreter/shared/src/test/scala/sigmastate/helpers/ErgoLikeTransactionTesting.scala diff --git a/interpreter/src/test/scala/sigmastate/helpers/ErgoTransactionValidator.scala b/interpreter/shared/src/test/scala/sigmastate/helpers/ErgoTransactionValidator.scala similarity index 98% rename from interpreter/src/test/scala/sigmastate/helpers/ErgoTransactionValidator.scala rename to interpreter/shared/src/test/scala/sigmastate/helpers/ErgoTransactionValidator.scala index 1c2a837539..3203dfc13f 100644 --- a/interpreter/src/test/scala/sigmastate/helpers/ErgoTransactionValidator.scala +++ b/interpreter/shared/src/test/scala/sigmastate/helpers/ErgoTransactionValidator.scala @@ -1,7 +1,6 @@ package sigmastate.helpers import org.ergoplatform._ -import sigmastate.eval.IRContext import sigmastate.interpreter.ErgoTreeEvaluator.DefaultEvalSettings import sigmastate.interpreter.EvalSettings import sigmastate.interpreter.Interpreter.{ScriptNameProp, emptyEnv} diff --git a/interpreter/src/test/scala/sigmastate/helpers/NegativeTesting.scala b/interpreter/shared/src/test/scala/sigmastate/helpers/NegativeTesting.scala similarity index 88% rename from interpreter/src/test/scala/sigmastate/helpers/NegativeTesting.scala rename to interpreter/shared/src/test/scala/sigmastate/helpers/NegativeTesting.scala index 25d7a9e384..158dc2b1bf 100644 --- a/interpreter/src/test/scala/sigmastate/helpers/NegativeTesting.scala +++ b/interpreter/shared/src/test/scala/sigmastate/helpers/NegativeTesting.scala @@ -1,21 +1,23 @@ package sigmastate.helpers - -import scala.annotation.tailrec -import scala.util.{Failure, Success, Try} import debox.cfor import org.scalatest.matchers.should.Matchers - +import scala.util.{Try, Success, Failure} import scala.reflect.ClassTag +import scala.annotation.tailrec +/** Contains helpers to test expected exceptions and their causes. */ trait NegativeTesting extends Matchers { - /** Checks that a [[Throwable]] is thrown and satisfies the given predicate. - * @param fun block of code to execute - * @param assertion expected assertion on the thrown exception - * @param clue added to the error message + * + * @param fun block of code to execute + * @param assertion expected assertion on the thrown exception + * @param clue added to the error message */ - def assertExceptionThrown(fun: => Any, assertion: Throwable => Boolean, clue: => String = ""): Unit = { + def assertExceptionThrown( + fun: => Any, + assertion: Throwable => Boolean, + clue: => String = ""): Unit = { try { fun fail("exception is expected but hasn't been thrown") @@ -78,7 +80,8 @@ trait NegativeTesting extends Matchers { * @return result of the second computation `g` */ def sameResultOrError[B](f: => B, g: => B): Try[B] = { - val b1 = Try(f); val b2 = Try(g) + val b1 = Try(f) + val b2 = Try(g) (b1, b2) match { case (Success(b1), res @ Success(b2)) => assert(b1 == b2) @@ -93,7 +96,6 @@ trait NegativeTesting extends Matchers { 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 diff --git a/interpreter/shared/src/test/scala/sigmastate/helpers/TestingCommons.scala b/interpreter/shared/src/test/scala/sigmastate/helpers/TestingCommons.scala new file mode 100644 index 0000000000..9c61e52aed --- /dev/null +++ b/interpreter/shared/src/test/scala/sigmastate/helpers/TestingCommons.scala @@ -0,0 +1,31 @@ +package sigmastate.helpers + +import sigmastate.Values.GroupElementConstant +import org.scalatest.propspec.AnyPropSpec +import sigmastate.TestsBase +import sigmastate.helpers.TestingHelpers.createBox +import org.scalatestplus.scalacheck.{ScalaCheckPropertyChecks, ScalaCheckDrivenPropertyChecks} +import sigmastate.eval.SigmaDsl +import scorex.crypto.hash.Blake2b256 +import org.ergoplatform.{ErgoBox, ErgoLikeContext} +import org.scalatest.matchers.should.Matchers +import sigmastate.basics.CryptoConstants.EcPointType + +trait TestingCommons extends AnyPropSpec + with ScalaCheckPropertyChecks + with ScalaCheckDrivenPropertyChecks + with Matchers + with NegativeTesting + with TestsBase { + def fakeSelf: ErgoBox = createBox(0, TrueTree) + + def fakeContext: ErgoLikeContext = + ErgoLikeContextTesting.dummy(fakeSelf, activatedVersionInTests) + .withErgoTreeVersion(ergoTreeVersionInTests) + + //fake message, in a real-life a message is to be derived from a spending transaction + val fakeMessage = Blake2b256("Hello World") + + implicit def grElemConvert(leafConstant: GroupElementConstant): EcPointType = + SigmaDsl.toECPoint(leafConstant.value).asInstanceOf[EcPointType] +} diff --git a/interpreter/src/test/scala/sigmastate/helpers/TestingHelpers.scala b/interpreter/shared/src/test/scala/sigmastate/helpers/TestingHelpers.scala similarity index 94% rename from interpreter/src/test/scala/sigmastate/helpers/TestingHelpers.scala rename to interpreter/shared/src/test/scala/sigmastate/helpers/TestingHelpers.scala index bf38d357fa..0ebac010ae 100644 --- a/interpreter/src/test/scala/sigmastate/helpers/TestingHelpers.scala +++ b/interpreter/shared/src/test/scala/sigmastate/helpers/TestingHelpers.scala @@ -5,7 +5,7 @@ import special.collection.{Coll, CollOverArray, PairOfCols} import scorex.util.ModifierId import org.ergoplatform.{DataInput, ErgoBox, ErgoBoxCandidate, ErgoLikeContext, ErgoLikeTransaction, ErgoLikeTransactionTemplate, Input, UnsignedInput} import sigmastate.Values.ErgoTree -import org.ergoplatform.ErgoBox.{AdditionalRegisters, TokenId, allZerosModifierId} +import org.ergoplatform.ErgoBox.{AdditionalRegisters, Token, allZerosModifierId} import org.ergoplatform.validation.SigmaValidationSettings import sigmastate.AvlTreeData import sigmastate.eval.CostingSigmaDslBuilder @@ -25,18 +25,18 @@ object TestingHelpers { def testBox(value: Long, ergoTree: ErgoTree, creationHeight: Int, - additionalTokens: Seq[(TokenId, Long)] = ArraySeq.empty, + additionalTokens: Seq[Token] = ArraySeq.empty, additionalRegisters: AdditionalRegisters = Map.empty, transactionId: ModifierId = allZerosModifierId, boxIndex: Short = 0): ErgoBox = new ErgoBox(value, ergoTree, - CostingSigmaDslBuilder.Colls.fromArray(additionalTokens.toArray[(TokenId, Long)]), + CostingSigmaDslBuilder.Colls.fromArray(additionalTokens.toArray[Token]), additionalRegisters, transactionId, boxIndex, creationHeight) def createBox(value: Long, proposition: ErgoTree, - additionalTokens: Seq[(Digest32, Long)] = ArraySeq.empty, + additionalTokens: Seq[Token] = ArraySeq.empty, additionalRegisters: AdditionalRegisters = Map.empty) = testBox(value, proposition, 0, additionalTokens, additionalRegisters) @@ -60,7 +60,7 @@ object TestingHelpers { def copyBox(box: ErgoBox)( value: Long = box.value, ergoTree: ErgoTree = box.ergoTree, - additionalTokens: Coll[(TokenId, Long)] = box.additionalTokens, + additionalTokens: Coll[Token] = box.additionalTokens, additionalRegisters: AdditionalRegisters = box.additionalRegisters, transactionId: ModifierId = box.transactionId, index: Short = box.index, diff --git a/interpreter/src/test/scala/sigmastate/serialization/AndSerializerSpecification.scala b/interpreter/shared/src/test/scala/sigmastate/serialization/AndSerializerSpecification.scala similarity index 100% rename from interpreter/src/test/scala/sigmastate/serialization/AndSerializerSpecification.scala rename to interpreter/shared/src/test/scala/sigmastate/serialization/AndSerializerSpecification.scala diff --git a/interpreter/src/test/scala/sigmastate/serialization/AvlTreeSpecification.scala b/interpreter/shared/src/test/scala/sigmastate/serialization/AvlTreeSpecification.scala similarity index 100% rename from interpreter/src/test/scala/sigmastate/serialization/AvlTreeSpecification.scala rename to interpreter/shared/src/test/scala/sigmastate/serialization/AvlTreeSpecification.scala diff --git a/interpreter/src/test/scala/sigmastate/serialization/BlockSerializerSpecification.scala b/interpreter/shared/src/test/scala/sigmastate/serialization/BlockSerializerSpecification.scala similarity index 100% rename from interpreter/src/test/scala/sigmastate/serialization/BlockSerializerSpecification.scala rename to interpreter/shared/src/test/scala/sigmastate/serialization/BlockSerializerSpecification.scala diff --git a/interpreter/src/test/scala/sigmastate/serialization/ConcreteCollectionSerializerSpecification.scala b/interpreter/shared/src/test/scala/sigmastate/serialization/ConcreteCollectionSerializerSpecification.scala similarity index 100% rename from interpreter/src/test/scala/sigmastate/serialization/ConcreteCollectionSerializerSpecification.scala rename to interpreter/shared/src/test/scala/sigmastate/serialization/ConcreteCollectionSerializerSpecification.scala diff --git a/interpreter/src/test/scala/sigmastate/serialization/ConstantSerializerSpecification.scala b/interpreter/shared/src/test/scala/sigmastate/serialization/ConstantSerializerSpecification.scala similarity index 98% rename from interpreter/src/test/scala/sigmastate/serialization/ConstantSerializerSpecification.scala rename to interpreter/shared/src/test/scala/sigmastate/serialization/ConstantSerializerSpecification.scala index 5156028b81..8b261df880 100644 --- a/interpreter/src/test/scala/sigmastate/serialization/ConstantSerializerSpecification.scala +++ b/interpreter/shared/src/test/scala/sigmastate/serialization/ConstantSerializerSpecification.scala @@ -7,7 +7,7 @@ import org.scalacheck.Arbitrary._ import scalan.RType import sigmastate.SCollection.SByteArray import sigmastate.Values.{LongConstant, FalseLeaf, Constant, SValue, TrueLeaf, BigIntConstant, GroupGenerator, ByteArrayConstant} -import sigmastate.interpreter.CryptoConstants.EcPointType +import sigmastate.basics.CryptoConstants.EcPointType import sigmastate._ import sigmastate.eval._ import sigmastate.eval.Extensions._ @@ -16,8 +16,8 @@ import sigmastate.eval.Evaluation import special.sigma.AvlTree import SType.AnyOps import scorex.util.encode.Base16 +import sigmastate.exceptions.SerializerException import sigmastate.lang.DeserializationSigmaBuilder -import sigmastate.lang.exceptions.SerializerException class ConstantSerializerSpecification extends TableSerializationSpecification { diff --git a/interpreter/src/test/scala/sigmastate/serialization/ConstantStoreSpecification.scala b/interpreter/shared/src/test/scala/sigmastate/serialization/ConstantStoreSpecification.scala similarity index 91% rename from interpreter/src/test/scala/sigmastate/serialization/ConstantStoreSpecification.scala rename to interpreter/shared/src/test/scala/sigmastate/serialization/ConstantStoreSpecification.scala index f2ceec7014..70764b5e1f 100644 --- a/interpreter/src/test/scala/sigmastate/serialization/ConstantStoreSpecification.scala +++ b/interpreter/shared/src/test/scala/sigmastate/serialization/ConstantStoreSpecification.scala @@ -2,12 +2,10 @@ package sigmastate.serialization import sigmastate.Values.{Constant, IntConstant} import sigmastate._ -import sigmastate.helpers.SigmaTestingCommons import sigmastate.lang.{SigmaBuilder, DeserializationSigmaBuilder} -class ConstantStoreSpecification extends SerializationSpecification with SigmaTestingCommons { +class ConstantStoreSpecification extends SerializationSpecification { - implicit lazy val IR: TestingIRContext = new TestingIRContext implicit val builder: SigmaBuilder = DeserializationSigmaBuilder property("empty store should have no constants") { diff --git a/interpreter/src/test/scala/sigmastate/serialization/DataSerializerSpecification.scala b/interpreter/shared/src/test/scala/sigmastate/serialization/DataSerializerSpecification.scala similarity index 97% rename from interpreter/src/test/scala/sigmastate/serialization/DataSerializerSpecification.scala rename to interpreter/shared/src/test/scala/sigmastate/serialization/DataSerializerSpecification.scala index 311e957dfd..61f6fc85e3 100644 --- a/interpreter/src/test/scala/sigmastate/serialization/DataSerializerSpecification.scala +++ b/interpreter/shared/src/test/scala/sigmastate/serialization/DataSerializerSpecification.scala @@ -11,13 +11,13 @@ import sigmastate._ import sigmastate.eval.Evaluation import sigmastate.eval._ import sigmastate.eval.Extensions._ -import sigmastate.interpreter.CryptoConstants.EcPointType +import sigmastate.basics.CryptoConstants.EcPointType import special.sigma.AvlTree import SType.AnyOps import org.ergoplatform.SigmaConstants.ScriptCostLimit +import sigmastate.exceptions.SerializerException import sigmastate.interpreter.{CostAccumulator, ErgoTreeEvaluator} import sigmastate.interpreter.ErgoTreeEvaluator.DefaultProfiler -import sigmastate.lang.exceptions.SerializerException import sigmastate.utils.Helpers class DataSerializerSpecification extends SerializationSpecification { diff --git a/interpreter/src/test/scala/sigmastate/serialization/GroupElementSerializerSpecification.scala b/interpreter/shared/src/test/scala/sigmastate/serialization/GroupElementSerializerSpecification.scala similarity index 96% rename from interpreter/src/test/scala/sigmastate/serialization/GroupElementSerializerSpecification.scala rename to interpreter/shared/src/test/scala/sigmastate/serialization/GroupElementSerializerSpecification.scala index 5be6cc5ff9..a0a5c8c2c1 100644 --- a/interpreter/src/test/scala/sigmastate/serialization/GroupElementSerializerSpecification.scala +++ b/interpreter/shared/src/test/scala/sigmastate/serialization/GroupElementSerializerSpecification.scala @@ -1,7 +1,7 @@ package sigmastate.serialization +import sigmastate.basics.CryptoConstants import sigmastate.crypto.CryptoFacade -import sigmastate.interpreter.CryptoConstants import sigmastate.eval._ class GroupElementSerializerSpecification extends SerializationSpecification { diff --git a/interpreter/src/test/scala/sigmastate/serialization/MethodCallSerializerSpecification.scala b/interpreter/shared/src/test/scala/sigmastate/serialization/MethodCallSerializerSpecification.scala similarity index 100% rename from interpreter/src/test/scala/sigmastate/serialization/MethodCallSerializerSpecification.scala rename to interpreter/shared/src/test/scala/sigmastate/serialization/MethodCallSerializerSpecification.scala diff --git a/interpreter/shared/src/test/scala/sigmastate/serialization/ModQSerializerSpecification.scala b/interpreter/shared/src/test/scala/sigmastate/serialization/ModQSerializerSpecification.scala new file mode 100644 index 0000000000..cfeb881fc6 --- /dev/null +++ b/interpreter/shared/src/test/scala/sigmastate/serialization/ModQSerializerSpecification.scala @@ -0,0 +1,15 @@ +package sigmastate.serialization + +import sigmastate.Values.BigIntConstant +import sigmastate._ + +class ModQSerializerSpecification extends SerializationSpecification { + + // TODO https://github.com/ScorexFoundation/sigmastate-interpreter/issues/327 + ignore("ModQ: Serializer round trip") { + forAll(bigIntConstGen) { x: BigIntConstant => + roundTripTest(ModQ(x)) + } + } + +} diff --git a/interpreter/src/test/scala/sigmastate/serialization/OrSerializerSpecification.scala b/interpreter/shared/src/test/scala/sigmastate/serialization/OrSerializerSpecification.scala similarity index 100% rename from interpreter/src/test/scala/sigmastate/serialization/OrSerializerSpecification.scala rename to interpreter/shared/src/test/scala/sigmastate/serialization/OrSerializerSpecification.scala diff --git a/interpreter/src/test/scala/sigmastate/serialization/PDHTSerializerSpecification.scala b/interpreter/shared/src/test/scala/sigmastate/serialization/PDHTSerializerSpecification.scala similarity index 100% rename from interpreter/src/test/scala/sigmastate/serialization/PDHTSerializerSpecification.scala rename to interpreter/shared/src/test/scala/sigmastate/serialization/PDHTSerializerSpecification.scala diff --git a/interpreter/src/test/scala/sigmastate/serialization/ProveDlogSerializerSpec.scala b/interpreter/shared/src/test/scala/sigmastate/serialization/ProveDlogSerializerSpec.scala similarity index 100% rename from interpreter/src/test/scala/sigmastate/serialization/ProveDlogSerializerSpec.scala rename to interpreter/shared/src/test/scala/sigmastate/serialization/ProveDlogSerializerSpec.scala diff --git a/interpreter/src/test/scala/sigmastate/serialization/RelationsSpecification.scala b/interpreter/shared/src/test/scala/sigmastate/serialization/RelationsSpecification.scala similarity index 100% rename from interpreter/src/test/scala/sigmastate/serialization/RelationsSpecification.scala rename to interpreter/shared/src/test/scala/sigmastate/serialization/RelationsSpecification.scala diff --git a/interpreter/src/test/scala/sigmastate/serialization/SelectFieldSerializerSpecification.scala b/interpreter/shared/src/test/scala/sigmastate/serialization/SelectFieldSerializerSpecification.scala similarity index 100% rename from interpreter/src/test/scala/sigmastate/serialization/SelectFieldSerializerSpecification.scala rename to interpreter/shared/src/test/scala/sigmastate/serialization/SelectFieldSerializerSpecification.scala diff --git a/interpreter/src/test/scala/sigmastate/serialization/SerializationSpecification.scala b/interpreter/shared/src/test/scala/sigmastate/serialization/SerializationSpecification.scala similarity index 100% rename from interpreter/src/test/scala/sigmastate/serialization/SerializationSpecification.scala rename to interpreter/shared/src/test/scala/sigmastate/serialization/SerializationSpecification.scala diff --git a/interpreter/src/test/scala/sigmastate/serialization/SigSerializerSpecification.scala b/interpreter/shared/src/test/scala/sigmastate/serialization/SigSerializerSpecification.scala similarity index 98% rename from interpreter/src/test/scala/sigmastate/serialization/SigSerializerSpecification.scala rename to interpreter/shared/src/test/scala/sigmastate/serialization/SigSerializerSpecification.scala index dbaa64f57e..6df3cc89d0 100644 --- a/interpreter/src/test/scala/sigmastate/serialization/SigSerializerSpecification.scala +++ b/interpreter/shared/src/test/scala/sigmastate/serialization/SigSerializerSpecification.scala @@ -3,24 +3,23 @@ package sigmastate.serialization import java.math.BigInteger import java.util import org.ergoplatform.settings.ErgoAlgos -import org.scalacheck.{Arbitrary, Gen} +import org.scalacheck.{Gen, Arbitrary} import org.scalatest.Assertion import sigmastate.Values.SigmaBoolean import sigmastate._ import sigmastate.basics.DLogProtocol.{ProveDlog, SecondDLogProverMessage} import sigmastate.basics.VerifierMessage.Challenge -import sigmastate.basics.{ProveDHTuple, SecondDiffieHellmanTupleProverMessage} +import sigmastate.basics.{SecondDiffieHellmanTupleProverMessage, ProveDHTuple} import sigmastate.crypto.GF2_192_Poly -import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, ErgoLikeContextTesting, ErgoLikeTransactionTesting, SigmaTestingCommons} +import sigmastate.helpers.{ErgoLikeTransactionTesting, ErgoLikeContextTesting, ContextEnrichingTestProvingInterpreter, TestingCommons} import sigmastate.interpreter.Interpreter import sigmastate.serialization.generators.ObjectGenerators import sigmastate.utils.Helpers import scala.util.Random -class SigSerializerSpecification extends SigmaTestingCommons +class SigSerializerSpecification extends TestingCommons with ObjectGenerators with CrossVersionProps { - implicit lazy val IR = new TestingIRContext private lazy implicit val arbExprGen: Arbitrary[SigmaBoolean] = Arbitrary(exprTreeGen) private lazy val prover = new ContextEnrichingTestProvingInterpreter() @@ -501,12 +500,12 @@ class SigSerializerSpecification extends SigmaTestingCommons val hex = getFiatShamirHex(c.uncheckedTree) if (c.fiatShamirHex.isEmpty) { - // output test vector - val vector = sigmastate.helpers.SigmaPPrint(hex, width = 150, height = 150) - println( - s"""case $iCase: ------------------------- - |hex: $vector - |""".stripMargin) + // uncomment to run on JVM only and print output test vector +// val vector = sigmastate.helpers.SigmaPPrint(hex, width = 150, height = 150) +// println( +// s"""case $iCase: ------------------------- +// |hex: $vector +// |""".stripMargin) } hex shouldBe c.fiatShamirHex diff --git a/interpreter/src/test/scala/sigmastate/serialization/SubstConstantsSerializerSpecification.scala b/interpreter/shared/src/test/scala/sigmastate/serialization/SubstConstantsSerializerSpecification.scala similarity index 80% rename from interpreter/src/test/scala/sigmastate/serialization/SubstConstantsSerializerSpecification.scala rename to interpreter/shared/src/test/scala/sigmastate/serialization/SubstConstantsSerializerSpecification.scala index da33a5f85c..0a6e73dac1 100644 --- a/interpreter/src/test/scala/sigmastate/serialization/SubstConstantsSerializerSpecification.scala +++ b/interpreter/shared/src/test/scala/sigmastate/serialization/SubstConstantsSerializerSpecification.scala @@ -1,8 +1,8 @@ package sigmastate.serialization -import sigmastate.Values.{ConcreteCollection, IntArrayConstant, IntConstant, IntValue} +import sigmastate.Values.{ConcreteCollection, IntConstant, IntArrayConstant, IntValue} import sigmastate.serialization.ErgoTreeSerializer.DefaultSerializer -import sigmastate.{CrossVersionProps, EQ, SInt, SubstConstants} +import sigmastate.{CrossVersionProps, SInt, EQ, SubstConstants} class SubstConstantsSerializerSpecification extends SerializationSpecification with CrossVersionProps { diff --git a/interpreter/src/test/scala/sigmastate/serialization/TableSerializationSpecification.scala b/interpreter/shared/src/test/scala/sigmastate/serialization/TableSerializationSpecification.scala similarity index 100% rename from interpreter/src/test/scala/sigmastate/serialization/TableSerializationSpecification.scala rename to interpreter/shared/src/test/scala/sigmastate/serialization/TableSerializationSpecification.scala diff --git a/interpreter/src/test/scala/sigmastate/serialization/TaggedVariableSerializerSpecification.scala b/interpreter/shared/src/test/scala/sigmastate/serialization/TaggedVariableSerializerSpecification.scala similarity index 100% rename from interpreter/src/test/scala/sigmastate/serialization/TaggedVariableSerializerSpecification.scala rename to interpreter/shared/src/test/scala/sigmastate/serialization/TaggedVariableSerializerSpecification.scala diff --git a/interpreter/src/test/scala/sigmastate/serialization/TransformersSerializationSpec.scala b/interpreter/shared/src/test/scala/sigmastate/serialization/TransformersSerializationSpec.scala similarity index 100% rename from interpreter/src/test/scala/sigmastate/serialization/TransformersSerializationSpec.scala rename to interpreter/shared/src/test/scala/sigmastate/serialization/TransformersSerializationSpec.scala diff --git a/interpreter/src/test/scala/sigmastate/serialization/TupleSerializerSpecification.scala b/interpreter/shared/src/test/scala/sigmastate/serialization/TupleSerializerSpecification.scala similarity index 100% rename from interpreter/src/test/scala/sigmastate/serialization/TupleSerializerSpecification.scala rename to interpreter/shared/src/test/scala/sigmastate/serialization/TupleSerializerSpecification.scala diff --git a/interpreter/src/test/scala/sigmastate/serialization/TwoArgumentSerializerSpecification.scala b/interpreter/shared/src/test/scala/sigmastate/serialization/TwoArgumentSerializerSpecification.scala similarity index 100% rename from interpreter/src/test/scala/sigmastate/serialization/TwoArgumentSerializerSpecification.scala rename to interpreter/shared/src/test/scala/sigmastate/serialization/TwoArgumentSerializerSpecification.scala diff --git a/interpreter/src/test/scala/sigmastate/serialization/TypeSerializerSpecification.scala b/interpreter/shared/src/test/scala/sigmastate/serialization/TypeSerializerSpecification.scala similarity index 100% rename from interpreter/src/test/scala/sigmastate/serialization/TypeSerializerSpecification.scala rename to interpreter/shared/src/test/scala/sigmastate/serialization/TypeSerializerSpecification.scala diff --git a/interpreter/src/test/scala/sigmastate/serialization/UpcastOnDeserializationSpecification.scala b/interpreter/shared/src/test/scala/sigmastate/serialization/UpcastOnDeserializationSpecification.scala similarity index 100% rename from interpreter/src/test/scala/sigmastate/serialization/UpcastOnDeserializationSpecification.scala rename to interpreter/shared/src/test/scala/sigmastate/serialization/UpcastOnDeserializationSpecification.scala diff --git a/interpreter/src/test/scala/sigmastate/serialization/generators/ConcreteCollectionGenerators.scala b/interpreter/shared/src/test/scala/sigmastate/serialization/generators/ConcreteCollectionGenerators.scala similarity index 93% rename from interpreter/src/test/scala/sigmastate/serialization/generators/ConcreteCollectionGenerators.scala rename to interpreter/shared/src/test/scala/sigmastate/serialization/generators/ConcreteCollectionGenerators.scala index 537cbf66b6..08ae08bd3f 100644 --- a/interpreter/src/test/scala/sigmastate/serialization/generators/ConcreteCollectionGenerators.scala +++ b/interpreter/shared/src/test/scala/sigmastate/serialization/generators/ConcreteCollectionGenerators.scala @@ -15,7 +15,7 @@ trait ConcreteCollectionGenerators { self: ObjectGenerators => listOfConsts <- Gen.listOfN(size, constGen) } yield ConcreteCollection(listOfConsts.toArray[Value[T]], c.tpe) - val intConstCollectionGen: Gen[ConcreteCollection[SInt.type]] = for { + lazy val intConstCollectionGen: Gen[ConcreteCollection[SInt.type]] = for { size <- Gen.chooseNum(minCollLength, maxCollLength) listOfConstInts <- Gen.listOfN(size, intConstGen) } yield ConcreteCollection.fromSeq(listOfConstInts.toArray[IntConstant]) diff --git a/interpreter/src/test/scala/sigmastate/serialization/generators/ObjectGenerators.scala b/interpreter/shared/src/test/scala/sigmastate/serialization/generators/ObjectGenerators.scala similarity index 73% rename from interpreter/src/test/scala/sigmastate/serialization/generators/ObjectGenerators.scala rename to interpreter/shared/src/test/scala/sigmastate/serialization/generators/ObjectGenerators.scala index b776bd8682..a4feda1ed7 100644 --- a/interpreter/src/test/scala/sigmastate/serialization/generators/ObjectGenerators.scala +++ b/interpreter/shared/src/test/scala/sigmastate/serialization/generators/ObjectGenerators.scala @@ -10,23 +10,22 @@ import org.scalacheck.util.Buildable import org.scalacheck.{Arbitrary, Gen} import scalan.RType import scorex.crypto.authds.{ADDigest, ADKey} -import scorex.crypto.hash.Digest32 import scorex.util.encode.{Base58, Base64} -import scorex.util.{ModifierId, Random, bytesToId} +import scorex.util.{ModifierId, bytesToId} import sigmastate.Values._ import sigmastate.basics.DLogProtocol.ProveDlog -import sigmastate.basics.ProveDHTuple +import sigmastate.basics.{CryptoConstants, ProveDHTuple} import sigmastate.eval.Extensions._ import sigmastate.eval.{CostingBox, SigmaDsl, _} -import sigmastate.interpreter.CryptoConstants.EcPointType -import sigmastate.interpreter.{ContextExtension, CryptoConstants, Interpreter, ProverResult} +import sigmastate.basics.CryptoConstants.EcPointType +import sigmastate.interpreter.{ContextExtension, ProverResult} import sigmastate.lang.TransformingSigmaBuilder._ import sigmastate._ import sigmastate.utxo._ import special.collection.Coll import special.sigma._ -import scala.collection.JavaConverters._ +import scala.collection.compat.immutable.ArraySeq import scala.collection.mutable.ListBuffer import scala.reflect.ClassTag @@ -92,48 +91,83 @@ trait ObjectGenerators extends TypeGenerators implicit lazy val arbContextExtension = Arbitrary(contextExtensionGen) implicit lazy val arbSerializedProverResult = Arbitrary(serializedProverResultGen) implicit lazy val arbUnsignedInput = Arbitrary(unsignedInputGen) + implicit lazy val arbDataInput = Arbitrary(dataInputGen) implicit lazy val arbInput = Arbitrary(inputGen) + implicit lazy val arbUnsignedErgoLikeTransaction = Arbitrary(unsignedErgoLikeTransactionGen) - val byteConstGen: Gen[ByteConstant] = + def arrayOfN[T](n: Int, g: Gen[T]) + (implicit evb: Buildable[T, Array[T]]): Gen[Array[T]] = { + Gen.containerOfN[Array, T](n, g) + } + + def arrayOfRange[T](minLength: Int, maxLength: Int, g: Gen[T]) + (implicit evb: Buildable[T, Array[T]]): Gen[Array[T]] = for { + length <- Gen.chooseNum(minLength, maxLength) + bytes <- arrayOfN(length, g) + } yield bytes + + implicit def arrayGen[T: Arbitrary : ClassTag]: Gen[Array[T]] = + arrayOfRange(1, 100, arbitrary[T]) + + def collOfN[T: RType](n: Int, g: Gen[T]) + (implicit b: Buildable[T, Array[T]]): Gen[Coll[T]] = { + arrayOfN[T](n, g).map(Colls.fromArray(_)) + } + + def collOfRange[T: RType](minLength: Int, maxLength: Int, g: Gen[T]) + (implicit evb: Buildable[T, Array[T]]): Gen[Coll[T]] = + arrayOfRange(minLength, maxLength, g).map(_.toColl) + + implicit def collGen[T: Arbitrary : RType]: Gen[Coll[T]] = { + implicit val cT = RType[T].classTag + arrayGen[T].map(Colls.fromArray[T](_)) + } + + lazy val byteConstGen: Gen[ByteConstant] = arbByte.arbitrary.map { v => mkConstant[SByte.type](v, SByte) } - val booleanConstGen: Gen[Value[SBoolean.type]] = Gen.oneOf(TrueLeaf, FalseLeaf) - val shortConstGen: Gen[ShortConstant] = + lazy val booleanConstGen: Gen[Value[SBoolean.type]] = Gen.oneOf(TrueLeaf, FalseLeaf) + lazy val shortConstGen: Gen[ShortConstant] = arbShort.arbitrary.map { v => mkConstant[SShort.type](v, SShort) } - val intConstGen: Gen[IntConstant] = + lazy val intConstGen: Gen[IntConstant] = arbInt.arbitrary.map { v => mkConstant[SInt.type](v, SInt) } - val longConstGen: Gen[LongConstant] = + lazy val longConstGen: Gen[LongConstant] = arbLong.arbitrary.map { v => mkConstant[SLong.type](v, SLong) } - val stringConstGen: Gen[StringConstant] = + lazy val stringConstGen: Gen[StringConstant] = arbString.arbitrary.map { v => mkConstant[SString.type](v, SString) } - val bigIntConstGen: Gen[BigIntConstant] = + lazy val bigIntConstGen: Gen[BigIntConstant] = arbBigInt.arbitrary.map { v => mkConstant[SBigInt.type](v, SBigInt) } - val byteArrayConstGen: Gen[CollectionConstant[SByte.type]] = for { - length <- Gen.chooseNum(1, 100) - bytes <- Gen.listOfN(length, arbByte.arbitrary) - } yield mkCollectionConstant[SByte.type](bytes.toArray, SByte) - val intArrayConstGen: Gen[CollectionConstant[SInt.type]] = for { - length <- Gen.chooseNum(1, 100) - ints <- Gen.listOfN(length, arbInt.arbitrary) - } yield mkCollectionConstant[SInt.type](ints.toArray, SInt) - - val heightGen: Gen[Int] = Gen.chooseNum(0, 1000000) - - val groupElementGen: Gen[EcPointType] = for { + + lazy val byteArrayConstGen: Gen[CollectionConstant[SByte.type]] = for { + bytes <- arrayOfRange(1, 100, arbByte.arbitrary) + } yield mkCollectionConstant[SByte.type](bytes, SByte) + + lazy val intArrayConstGen: Gen[CollectionConstant[SInt.type]] = for { + ints <- arrayOfRange(1, 100, arbInt.arbitrary) + } yield mkCollectionConstant[SInt.type](ints, SInt) + + lazy val heightGen: Gen[Int] = Gen.chooseNum(0, 1000000) + + lazy val groupElementGen: Gen[EcPointType] = for { _ <- Gen.const(1) } yield CryptoConstants.dlogGroup.createRandomElement() - val groupElementConstGen: Gen[GroupElementConstant] = for { + lazy val groupElementConstGen: Gen[GroupElementConstant] = for { p <- groupElementGen } yield mkConstant[SGroupElement.type](p, SGroupElement) + lazy val constantGen: Gen[Constant[SType]] = + Gen.oneOf(booleanConstGen, byteConstGen, + shortConstGen, intConstGen, longConstGen, bigIntConstGen, byteArrayConstGen, + intArrayConstGen, groupElementConstGen).asInstanceOf[Gen[Constant[SType]]] + def taggedVar[T <: SType](implicit aT: Arbitrary[T]): Gen[TaggedVariable[T]] = for { t <- aT.arbitrary id <- arbByte.arbitrary } yield mkTaggedVariable(id, t) - val proveDlogGen: Gen[ProveDlog] = for {v <- groupElementGen} yield ProveDlog(v) - val proveDHTGen: Gen[ProveDHTuple] = for { + lazy val proveDlogGen: Gen[ProveDlog] = for {v <- groupElementGen} yield ProveDlog(v) + lazy val proveDHTGen: Gen[ProveDHTuple] = for { gv <- groupElementGen hv <- groupElementGen uv <- groupElementGen @@ -143,9 +177,9 @@ trait ObjectGenerators extends TypeGenerators lazy val sigmaTreeNodeGen: Gen[SigmaBoolean] = for { itemsNum <- Gen.choose(2, ThresholdLimit) items <- if (itemsNum <= 2) { - Gen.listOfN(itemsNum, sigmaBooleanGen) + arrayOfN(itemsNum, sigmaBooleanGen) } else { - Gen.listOfN(itemsNum, nonRecursiveSigmaBoolean) + arrayOfN(itemsNum, nonRecursiveSigmaBoolean) } threshold <- Gen.choose(1, itemsNum) node <- Gen.oneOf( @@ -155,23 +189,23 @@ trait ObjectGenerators extends TypeGenerators ) } yield node - val nonRecursiveSigmaBoolean: Gen[SigmaBoolean] = Gen.oneOf(proveDlogGen, proveDHTGen) + lazy val nonRecursiveSigmaBoolean: Gen[SigmaBoolean] = Gen.oneOf(proveDlogGen, proveDHTGen) - val sigmaBooleanGen: Gen[SigmaBoolean] = Gen.oneOf( + lazy val sigmaBooleanGen: Gen[SigmaBoolean] = Gen.oneOf( nonRecursiveSigmaBoolean, Gen.delay(sigmaTreeNodeGen) ) - val sigmaPropGen: Gen[SigmaProp] = sigmaBooleanGen.map(SigmaDsl.SigmaProp) - val sigmaPropValueGen: Gen[SigmaPropValue] = + lazy val sigmaPropGen: Gen[SigmaProp] = sigmaBooleanGen.map(SigmaDsl.SigmaProp) + lazy val sigmaPropValueGen: Gen[SigmaPropValue] = Gen.oneOf(proveDlogGen.map(SigmaPropConstant(_)), proveDHTGen.map(SigmaPropConstant(_))) - val registerIdentifierGen: Gen[RegisterId] = Gen.oneOf(R0, R1, R2, R3, R4, R5, R6, R7, R8, R9) + lazy val registerIdentifierGen: Gen[RegisterId] = Gen.oneOf(R0, R1, R2, R3, R4, R5, R6, R7, R8, R9) - val taggedAvlTreeGen: Gen[TaggedAvlTree] = + lazy val taggedAvlTreeGen: Gen[TaggedAvlTree] = arbByte.arbitrary.map { v => TaggedAvlTree(v).asInstanceOf[TaggedAvlTree] } - val evaluatedValueGen: Gen[EvaluatedValue[SType]] = + lazy val evaluatedValueGen: Gen[EvaluatedValue[SType]] = Gen.oneOf(booleanConstGen.asInstanceOf[Gen[EvaluatedValue[SType]]], byteArrayConstGen, longConstGen) def additionalRegistersGen(cnt: Byte): Seq[Gen[(NonMandatoryRegisterId, EvaluatedValue[SType])]] = { @@ -185,12 +219,12 @@ trait ObjectGenerators extends TypeGenerators } } - def additionalTokensGen(cnt: Int): Seq[Gen[(Digest32, Long)]] = + def additionalTokensGen(cnt: Int): Seq[Gen[(Digest32Coll, Long)]] = (0 until cnt).map { _ => for { - id <- Digest32 @@@ boxIdGen + id <- boxIdGen amt <- Gen.oneOf(1, 500, 20000, 10000000, Long.MaxValue) - } yield id -> amt + } yield (Digest32Coll @@@ id.toColl) -> amt } val smallIntGen: Gen[Int] = Gen.chooseNum(2, 16) @@ -201,30 +235,29 @@ trait ObjectGenerators extends TypeGenerators val unsignedIntGen: Gen[Int] = Gen.chooseNum(0, Int.MaxValue) val unsignedShortGen: Gen[Short] = Gen.chooseNum(0, Short.MaxValue).map(_.toShort) - val contextExtensionGen: Gen[ContextExtension] = for { + lazy val contextExtensionGen: Gen[ContextExtension] = for { values <- Gen.sequence(contextExtensionValuesGen(0, 5))(Buildable.buildableSeq) } yield ContextExtension(values.toMap) - val serializedProverResultGen: Gen[ProverResult] = for { - length <- Gen.chooseNum(1, 100) - bytes <- Gen.listOfN(length, arbByte.arbitrary) + lazy val serializedProverResultGen: Gen[ProverResult] = for { + bytes <- arrayOfRange(1, 100, arbByte.arbitrary) contextExt <- contextExtensionGen - } yield ProverResult(bytes.toArray, contextExt) + } yield ProverResult(bytes, contextExt) val boxIdGen: Gen[BoxId] = for { - bytes <- Gen.listOfN(BoxId.size, arbByte.arbitrary) - } yield ADKey @@ bytes.toArray + bytes <- arrayOfN(BoxId.size, arbByte.arbitrary) + } yield ADKey @@ bytes - val unsignedInputGen: Gen[UnsignedInput] = for { + lazy val unsignedInputGen: Gen[UnsignedInput] = for { boxId <- boxIdGen contextExt <- contextExtensionGen } yield new UnsignedInput(boxId, contextExt) - val dataInputGen: Gen[DataInput] = for { + lazy val dataInputGen: Gen[DataInput] = for { boxId <- boxIdGen } yield DataInput(boxId) - val inputGen: Gen[Input] = for { + lazy val inputGen: Gen[Input] = for { boxId <- boxIdGen proof <- serializedProverResultGen } yield Input(boxId, proof) @@ -245,7 +278,8 @@ trait ObjectGenerators extends TypeGenerators remove <- arbBool.arbitrary } yield AvlTreeFlags(insert, update, remove) - val aDDigestGen: Gen[ADDigest] = Gen.listOfN(AvlTreeData.DigestSize, arbByte.arbitrary).map(ADDigest @@ _.toArray) + lazy val aDDigestGen: Gen[ADDigest] = + arrayOfN(AvlTreeData.DigestSize, arbByte.arbitrary).map(ADDigest @@ _) def avlTreeDataGen: Gen[AvlTreeData] = for { digest <- aDDigestGen @@ -258,16 +292,6 @@ trait ObjectGenerators extends TypeGenerators def avlTreeConstantGen: Gen[AvlTreeConstant] = avlTreeGen.map { v => AvlTreeConstant(v) } - implicit def arrayGen[T: Arbitrary : ClassTag]: Gen[Array[T]] = for { - length <- Gen.chooseNum(1, 100) - bytes <- Gen.listOfN(length, arbitrary[T]) - } yield bytes.toArray - - implicit def collGen[T: Arbitrary : RType]: Gen[Coll[T]] = { - implicit val cT = RType[T].classTag - arrayGen[T].map(Colls.fromArray[T](_)) - } - def wrappedTypeGen[T <: SType](tpe: T): Gen[T#WrappedType] = (tpe match { case SBoolean => arbBool case SByte => arbByte @@ -286,8 +310,7 @@ trait ObjectGenerators extends TypeGenerators }).asInstanceOf[Arbitrary[T#WrappedType]].arbitrary def tupleGen(min: Int, max: Int): Gen[Tuple] = for { - length <- Gen.chooseNum(min, max) - values <- Gen.listOfN(length, Gen.oneOf( + values <- arrayOfRange(min, max, Gen.oneOf( byteArrayConstGen, byteConstGen, shortConstGen, @@ -303,15 +326,16 @@ trait ObjectGenerators extends TypeGenerators )) } yield mkTuple(values).asInstanceOf[Tuple] - lazy val modifierIdGen: Gen[ModifierId] = Gen.listOfN(32, arbByte.arbitrary) - .map(id => bytesToId(id.toArray)) + lazy val modifierIdGen: Gen[ModifierId] = + arrayOfN(CryptoConstants.hashLength, arbByte.arbitrary) + .map(bytesToId) - lazy val modifierIdBytesGen: Gen[Coll[Byte]] = Gen.listOfN(32, arbByte.arbitrary) - .map(id => bytesToId(id.toArray).toBytes.toColl) + lazy val modifierIdBytesGen: Gen[Coll[Byte]] = + collOfN(CryptoConstants.hashLength, arbByte.arbitrary) val MaxTokens = 10 - val ergoBoxGen: Gen[ErgoBox] = for { + lazy val ergoBoxGen: Gen[ErgoBox] = for { tId <- modifierIdGen boxId <- unsignedShortGen tokensCount <- Gen.chooseNum[Int](0, MaxTokens) @@ -319,48 +343,46 @@ trait ObjectGenerators extends TypeGenerators candidate <- ergoBoxCandidateGen(tokens.toSeq) } yield candidate.toBox(tId, boxId) - val additionalRegistersGen: Gen[Map[NonMandatoryRegisterId, EvaluatedValue[SType]]] = for { + lazy val additionalRegistersGen: Gen[AdditionalRegisters] = for { regNum <- Gen.chooseNum[Byte](0, ErgoBox.nonMandatoryRegistersCount) regs <- Gen.sequence(additionalRegistersGen(regNum))(Buildable.buildableSeq) } yield regs.toMap - def arrayOfN[T](n: Int, g: Gen[T])(implicit evb: Buildable[T,Array[T]]): Gen[Array[T]] = { - Gen.containerOfN[Array, T](n, g) - } - - def ergoBoxCandidateGen(availableTokens: Seq[Digest32]): Gen[ErgoBoxCandidate] = for { - l <- arbLong.arbitrary - b <- ergoTreeGen.filter(t => t.bytes.length < MaxPropositionBytes.value) - ar <- additionalRegistersGen + def ergoBoxTokens(availableTokens: Seq[TokenId]): Gen[Coll[Token]] = for { tokens <- if(availableTokens.nonEmpty) { for { tokensCount <- Gen.chooseNum[Int](0, MaxTokens) - ts <- arrayOfN(tokensCount, Gen.oneOf(availableTokens).map(_.toColl)) - } yield ts.distinct.map(coll => Digest32 @@ coll.toArray) + ts <- arrayOfN(tokensCount, Gen.oneOf(availableTokens)) + } yield ts.distinct } else { - Gen.oneOf(Seq(Array[Digest32]())) + Gen.const(Array.empty[TokenId]) } tokenAmounts <- arrayOfN(tokens.length, Gen.oneOf(1, 500, 20000, 10000000, Long.MaxValue)) + } yield tokens.toColl.zip(tokenAmounts.toColl) + + def ergoBoxCandidateGen(availableTokens: Seq[TokenId]): Gen[ErgoBoxCandidate] = for { + l <- arbLong.arbitrary + b <- ergoTreeGen.filter(t => t.bytes.length < MaxPropositionBytes.value) + ar <- additionalRegistersGen + tokens <- ergoBoxTokens(availableTokens) creationHeight <- heightGen - } yield new ErgoBoxCandidate(l, b, creationHeight, tokens.toColl.zip(tokenAmounts.toColl), ar) + } yield new ErgoBoxCandidate(l, b, creationHeight, tokens, ar) - val boxConstantGen: Gen[BoxConstant] = ergoBoxGen.map { v => BoxConstant(CostingBox(v)) } + lazy val boxConstantGen: Gen[BoxConstant] = ergoBoxGen.map { v => BoxConstant(CostingBox(v)) } - val digest32Gen: Gen[Digest32] = for { - bytes <- Gen.listOfN(TokenId.size, arbByte.arbitrary).map(_.toArray) - } yield Digest32 @@ bytes + lazy val digest32Gen: Gen[TokenId] = for { + bytes <- collOfN(TokenId.size, arbByte.arbitrary) + } yield Digest32Coll @@@ bytes - val tokenIdGen: Gen[Digest32] = digest32Gen + lazy val tokenIdGen: Gen[TokenId] = digest32Gen - val tokensGen: Gen[Seq[Digest32]] = for { + lazy val tokensGen: Gen[Seq[TokenId]] = for { count <- Gen.chooseNum(1, MaxTokens) tokens <- arrayOfN(count, tokenIdGen) } yield tokens - val digest32CollGen: Gen[Digest32Coll] = digest32Gen.map(Digest32Coll @@ _.toColl) - - val ergoTransactionGen: Gen[ErgoLikeTransaction] = for { + lazy val ergoTransactionGen: Gen[ErgoLikeTransaction] = for { inputBoxIds <- Gen.nonEmptyListOf(boxIdGen) dataInputBoxIds <- Gen.listOf(boxIdGen) tx <- ergoLikeTransactionGen(inputBoxIds, dataInputBoxIds) @@ -387,22 +409,9 @@ trait ObjectGenerators extends TypeGenerators } } - def byteArrayGen(length: Int): Gen[Array[Byte]] = for { - bytes <- Gen.listOfN(length, arbByte.arbitrary) - } yield bytes.toArray - - def byteArrayGen(minLength: Int, maxLength: Int): Gen[Array[Byte]] = for { - length <- Gen.chooseNum(minLength, maxLength) - bytes <- Gen.listOfN(length, arbByte.arbitrary) - } yield bytes.toArray - - def byteCollGen(length: Int): Gen[Coll[Byte]] = byteArrayGen(length).map(_.toColl) - - def byteCollGen(minLength: Int, maxLength: Int): Gen[Coll[Byte]] = byteArrayGen(minLength, maxLength).map(_.toColl) - - val minerVotesGen: Gen[Coll[Byte]] = byteCollGen(CHeader.VotesSize) + lazy val minerVotesGen: Gen[Coll[Byte]] = collOfN(CHeader.VotesSize, arbByte.arbitrary) - val nonceBytesGen: Gen[Coll[Byte]] = byteCollGen(CHeader.NonceSize) + lazy val nonceBytesGen: Gen[Coll[Byte]] = collOfN(CHeader.NonceSize, arbByte.arbitrary) import ValidationRules._ @@ -415,79 +424,79 @@ trait ObjectGenerators extends TypeGenerators val statusGen: Gen[RuleStatus] = Gen.oneOf( Gen.oneOf(EnabledRule, DisabledRule), replacedRuleIdGen.map(id => ReplacedRule(id)), - byteArrayGen(1, 10).map(xs => ChangedRule(xs)) + arrayOfRange(1, 10, arbitrary[Byte]).map(xs => ChangedRule(xs)) ) - val mapCollectionGen: Gen[MapCollection[SInt.type, SInt.type]] = for { + lazy val mapCollectionGen: Gen[MapCollection[SInt.type, SInt.type]] = for { input <- arbCCOfIntConstant.arbitrary mapper <- funcValueGen } yield mkMapCollection(input, mapper).asInstanceOf[MapCollection[SInt.type, SInt.type]] - val existsGen: Gen[Exists[SInt.type]] = for { + lazy val existsGen: Gen[Exists[SInt.type]] = for { input <- arbCCOfIntConstant.arbitrary condition <- funcValueGen } yield mkExists(input, condition).asInstanceOf[Exists[SInt.type]] - val forAllGen: Gen[ForAll[SInt.type]] = for { + lazy val forAllGen: Gen[ForAll[SInt.type]] = for { input <- arbCCOfIntConstant.arbitrary condition <- funcValueGen } yield mkForAll(input, condition).asInstanceOf[ForAll[SInt.type]] - val foldGen: Gen[Fold[SInt.type, SBoolean.type]] = for { + lazy val foldGen: Gen[Fold[SInt.type, SBoolean.type]] = for { input <- arbCCOfIntConstant.arbitrary foldOp <- funcValueGen } yield mkFold(input, TrueLeaf, foldOp).asInstanceOf[Fold[SInt.type, SBoolean.type]] - val sliceGen: Gen[Slice[SInt.type]] = for { + lazy val sliceGen: Gen[Slice[SInt.type]] = for { col1 <- arbCCOfIntConstant.arbitrary from <- intConstGen until <- intConstGen } yield mkSlice(col1, from, until).asInstanceOf[Slice[SInt.type]] - val atLeastGen: Gen[AtLeast] = for { + lazy val atLeastGen: Gen[AtLeast] = for { bound <- intConstGen input <- arbCCOfSigmaPropConstant.arbitrary } yield mkAtLeast(bound, input).asInstanceOf[AtLeast] - val filterGen: Gen[Filter[SInt.type]] = for { + lazy val filterGen: Gen[Filter[SInt.type]] = for { col1 <- arbCCOfIntConstant.arbitrary condition <- funcValueGen } yield mkFilter(col1, condition).asInstanceOf[Filter[SInt.type]] - val appendGen: Gen[Append[SInt.type]] = for { + lazy val appendGen: Gen[Append[SInt.type]] = for { col1 <- arbCCOfIntConstant.arbitrary col2 <- arbCCOfIntConstant.arbitrary } yield mkAppend(col1, col2).asInstanceOf[Append[SInt.type]] - val sizeOfGen: Gen[SizeOf[SInt.type]] = for { + lazy val sizeOfGen: Gen[SizeOf[SInt.type]] = for { input <- arbCCOfIntConstant.arbitrary } yield mkSizeOf(input).asInstanceOf[SizeOf[SInt.type]] - val extractAmountGen: Gen[ExtractAmount] = + lazy val extractAmountGen: Gen[ExtractAmount] = arbTaggedBox.arbitrary.map { b => mkExtractAmount(b).asInstanceOf[ExtractAmount] } - val extractScriptBytesGen: Gen[ExtractScriptBytes] = + lazy val extractScriptBytesGen: Gen[ExtractScriptBytes] = arbTaggedBox.arbitrary.map { b => mkExtractScriptBytes(b).asInstanceOf[ExtractScriptBytes] } - val extractBytesGen: Gen[ExtractBytes] = + lazy val extractBytesGen: Gen[ExtractBytes] = arbTaggedBox.arbitrary.map { b => mkExtractBytes(b).asInstanceOf[ExtractBytes] } - val extractBytesWithNoRefGen: Gen[ExtractBytesWithNoRef] = + lazy val extractBytesWithNoRefGen: Gen[ExtractBytesWithNoRef] = arbTaggedBox.arbitrary.map { b => mkExtractBytesWithNoRef(b).asInstanceOf[ExtractBytesWithNoRef] } - val extractIdGen: Gen[ExtractId] = + lazy val extractIdGen: Gen[ExtractId] = arbTaggedBox.arbitrary.map { b => mkExtractId(b).asInstanceOf[ExtractId] } - val extractRegisterAsGen: Gen[ExtractRegisterAs[SInt.type]] = for { + lazy val extractRegisterAsGen: Gen[ExtractRegisterAs[SInt.type]] = for { input <- arbTaggedBox.arbitrary r <- arbRegisterIdentifier.arbitrary } yield ExtractRegisterAs(input, r)(SInt) - val extractCreationInfoGen: Gen[ExtractCreationInfo] = + lazy val extractCreationInfoGen: Gen[ExtractCreationInfo] = arbTaggedBox.arbitrary.map { b => mkExtractCreationInfo(b).asInstanceOf[ExtractCreationInfo] } - val deserializeContextGen: Gen[DeserializeContext[SBoolean.type]] = + lazy val deserializeContextGen: Gen[DeserializeContext[SBoolean.type]] = Arbitrary.arbitrary[Byte].map(b => mkDeserializeContext(b, SBoolean).asInstanceOf[DeserializeContext[SBoolean.type]]) - val deserializeRegisterGen: Gen[DeserializeRegister[SBoolean.type]] = for { + lazy val deserializeRegisterGen: Gen[DeserializeRegister[SBoolean.type]] = for { r <- arbRegisterIdentifier.arbitrary default <- booleanConstGen isDefined <- Arbitrary.arbitrary[Boolean] @@ -495,21 +504,21 @@ trait ObjectGenerators extends TypeGenerators } yield mkDeserializeRegister(r, SBoolean, defaultOpt) .asInstanceOf[DeserializeRegister[SBoolean.type]] - val longToByteArrayGen: Gen[LongToByteArray] = arbLongConstants.arbitrary.map { v => LongToByteArray(v) } - val byteArrayToBigIntGen: Gen[ByteArrayToBigInt] = + lazy val longToByteArrayGen: Gen[LongToByteArray] = arbLongConstants.arbitrary.map { v => LongToByteArray(v) } + lazy val byteArrayToBigIntGen: Gen[ByteArrayToBigInt] = arbByteArrayConstant.arbitrary.map { v => mkByteArrayToBigInt(v).asInstanceOf[ByteArrayToBigInt] } - val calcBlake2b256Gen: Gen[CalcBlake2b256] = arbByteArrayConstant.arbitrary.map { v => CalcBlake2b256(v) } - val calcSha256Gen: Gen[CalcSha256] = arbByteArrayConstant.arbitrary.map { v => CalcSha256(v) } + lazy val calcBlake2b256Gen: Gen[CalcBlake2b256] = arbByteArrayConstant.arbitrary.map { v => CalcBlake2b256(v) } + lazy val calcSha256Gen: Gen[CalcSha256] = arbByteArrayConstant.arbitrary.map { v => CalcSha256(v) } - val byIndexGen: Gen[ByIndex[SInt.type]] = for { + lazy val byIndexGen: Gen[ByIndex[SInt.type]] = for { input <- Gen.oneOf(intConstCollectionGen, intArrayConstGen) index <- arbInt.arbitrary defaultValue <- arbOption(arbIntConstants).arbitrary } yield ByIndex(input, index, defaultValue) - val booleanExprGen: Gen[Value[SBoolean.type]] = + lazy val booleanExprGen: Gen[Value[SBoolean.type]] = Gen.oneOf( Gen.oneOf( EQ(IntConstant(1), IntConstant(1)), // true @@ -564,15 +573,15 @@ trait ObjectGenerators extends TypeGenerators ) } yield node - val downcastGen: Gen[Downcast[SNumericType, SNumericType]] = for { + lazy val downcastGen: Gen[Downcast[SNumericType, SNumericType]] = for { numVal <- Gen.oneOf(numExprTreeNodeGen, shortConstGen, intConstGen, longConstGen) } yield mkDowncast(numVal, SByte).asInstanceOf[Downcast[SNumericType, SNumericType]] - val base58StringGen: Gen[String] = for { + lazy val base58StringGen: Gen[String] = for { s <- Gen.someOf(Base58.Alphabet).suchThat(_.nonEmpty) } yield s.toString - val base64StringGen: Gen[String] = for { + lazy val base64StringGen: Gen[String] = for { s <- Gen.someOf(Base64.Alphabet).suchThat(_.length > 1) } yield s.toString @@ -580,92 +589,90 @@ trait ObjectGenerators extends TypeGenerators pd <- proveDlogGen } yield P2PKAddress(pd)(new ErgoAddressEncoder(networkPrefix)) - val getVarIntGen: Gen[GetVar[SInt.type]] = for { + lazy val getVarIntGen: Gen[GetVar[SInt.type]] = for { varId <- arbByte.arbitrary } yield GetVarInt(varId) - val optionGetGen: Gen[OptionGet[SInt.type]] = for { + lazy val optionGetGen: Gen[OptionGet[SInt.type]] = for { getVar <- getVarIntGen } yield OptionGet(getVar) - val optionGetOrElseGen: Gen[OptionGetOrElse[SInt.type]] = for { + lazy val optionGetOrElseGen: Gen[OptionGetOrElse[SInt.type]] = for { getVar <- getVarIntGen } yield OptionGetOrElse(getVar, IntConstant(1)) - val optionIsDefinedGen: Gen[OptionIsDefined[SInt.type]] = for { + lazy val optionIsDefinedGen: Gen[OptionIsDefined[SInt.type]] = for { getVar <- getVarIntGen } yield OptionIsDefined(getVar) - val valDefGen: Gen[ValDef] = for { + lazy val valDefGen: Gen[ValDef] = for { id <- unsignedIntGen rhs <- booleanExprGen } yield ValDef(id, Nil, rhs) - val funDefGen: Gen[ValDef] = for { + lazy val funDefGen: Gen[ValDef] = for { id <- unsignedIntGen tpeArgs <- Gen.nonEmptyListOf(sTypeIdentGen) rhs <- booleanExprGen } yield ValDef(id, tpeArgs, rhs) - val valOrFunDefGen: Gen[ValDef] = for { + lazy val valOrFunDefGen: Gen[ValDef] = for { v <- Gen.oneOf(valDefGen, funDefGen) } yield v - val valUseGen: Gen[ValUse[SType]] = for { + lazy val valUseGen: Gen[ValUse[SType]] = for { id <- unsignedIntGen tpe <- predefTypeGen } yield ValUse(id, tpe) - val blockValueGen: Gen[BlockValue] = for { + lazy val blockValueGen: Gen[BlockValue] = for { items <- Gen.listOf(valDefGen) } yield BlockValue(items.toIndexedSeq, EQ( SizeOf(Tuple(items.toIndexedSeq.map(valDef => ValUse(valDef.id, valDef.tpe)))), IntConstant(items.length))) - val constantPlaceholderGen: Gen[ConstantPlaceholder[SType]] = for { + lazy val constantPlaceholderGen: Gen[ConstantPlaceholder[SType]] = for { id <- unsignedIntGen tpe <- predefTypeGen } yield ConstantPlaceholder(id, tpe) - val funcValueArgsGen: Gen[IndexedSeq[(Int, SType)]] = for { + lazy val funcValueArgsGen: Gen[IndexedSeq[(Int, SType)]] = for { num <- Gen.chooseNum(1, 10) - indices <- Gen.listOfN(num, unsignedIntGen) - tpes <- Gen.listOfN(num, predefTypeGen) + indices <- arrayOfN(num, unsignedIntGen) + tpes <- arrayOfN(num, predefTypeGen) } yield indices.zip(tpes).toIndexedSeq - val funcValueGen: Gen[FuncValue] = for { + lazy val funcValueGen: Gen[FuncValue] = for { args <- funcValueArgsGen - body <- logicalExprTreeNodeGen(Seq(AND.apply)) + body <- logicalExprTreeNodeGen(ArraySeq(AND.apply)) } yield FuncValue(args, body) - val sigmaAndGen: Gen[SigmaAnd] = for { - num <- Gen.chooseNum(1, ThresholdLimit) - items <- Gen.listOfN(num, sigmaPropValueGen) + lazy val sigmaAndGen: Gen[SigmaAnd] = for { + items <- arrayOfRange(1, ThresholdLimit, sigmaPropValueGen) } yield mkSigmaAnd(items).asInstanceOf[SigmaAnd] - val sigmaOrGen: Gen[SigmaOr] = for { - num <- Gen.chooseNum(1, ThresholdLimit) - items <- Gen.listOfN(num, sigmaPropValueGen) + lazy val sigmaOrGen: Gen[SigmaOr] = for { + items <- arrayOfRange(1, ThresholdLimit, sigmaPropValueGen) } yield mkSigmaOr(items).asInstanceOf[SigmaOr] - val sigmaThresholdGen: Gen[CTHRESHOLD] = for { + lazy val sigmaThresholdGen: Gen[CTHRESHOLD] = for { num <- Gen.chooseNum(1, ThresholdLimit) threshold <- Gen.choose(0, num) - items: Seq[SigmaBoolean] <- Gen.listOfN(num, sigmaBooleanGen).map(_.toSeq) + items: Seq[SigmaBoolean] <- arrayOfN(num, sigmaBooleanGen).map(_.toSeq) } yield CTHRESHOLD(threshold, items) - val boolToSigmaPropGen: Gen[BoolToSigmaProp] = for { + lazy val boolToSigmaPropGen: Gen[BoolToSigmaProp] = for { b <- booleanConstGen } yield mkBoolToSigmaProp(b).asInstanceOf[BoolToSigmaProp] - val byteArrayToLongGen: Gen[ByteArrayToLong] = + lazy val byteArrayToLongGen: Gen[ByteArrayToLong] = arbByteArrayConstant.arbitrary.map { v => mkByteArrayToLong(v).asInstanceOf[ByteArrayToLong] } - val ergoTreeGen: Gen[ErgoTree] = for { + lazy val ergoTreeGen: Gen[ErgoTree] = for { sigmaBoolean <- Gen.delay(sigmaBooleanGen) propWithConstants <- Gen.delay(logicalExprTreeNodeGen(Seq(AND.apply, OR.apply, XorOf.apply)).map(_.toSigmaProp)) prop <- Gen.oneOf(propWithConstants, sigmaBoolean.toSigmaProp) @@ -676,12 +683,12 @@ trait ObjectGenerators extends TypeGenerators def headerGen(stateRoot: AvlTree, parentId: Coll[Byte]): Gen[Header] = for { id <- modifierIdBytesGen version <- arbByte.arbitrary - adProofsRoot <- digest32CollGen - transactionRoot <- digest32CollGen + adProofsRoot <- digest32Gen + transactionRoot <- digest32Gen timestamp <- arbLong.arbitrary nBits <- arbLong.arbitrary height <- heightGen - extensionRoot <- digest32CollGen + extensionRoot <- digest32Gen minerPk <- groupElementGen powOnetimePk <- groupElementGen powNonce <- nonceBytesGen @@ -690,22 +697,26 @@ trait ObjectGenerators extends TypeGenerators } yield CHeader(id, version, parentId, adProofsRoot, stateRoot, transactionRoot, timestamp, nBits, height, extensionRoot, minerPk, powOnetimePk, powNonce, powDistance, votes) - val headerGen: Gen[Header] = for { + lazy val headerGen: Gen[Header] = for { stateRoot <- avlTreeGen parentId <- modifierIdBytesGen header <- headerGen(stateRoot, parentId) } yield header - implicit val arbHeader = Arbitrary(headerGen) + implicit lazy val arbHeader = Arbitrary(headerGen) val MaxHeaders = 2 def headersGen(stateRoot: AvlTree): Gen[Seq[Header]] = for { size <- Gen.chooseNum(0, MaxHeaders) - } yield if (size == 0) Seq() else - (0 to size) - .foldLeft(List[Header](headerGen(stateRoot, modifierIdBytesGen.sample.get).sample.get)) { (h, _) => - h :+ headerGen(stateRoot, h.last.id).sample.get - }.reverse + } yield + if (size == 0) Seq() + else { + val h = headerGen(stateRoot, modifierIdBytesGen.sample.get).sample.get + (0 to size) + .foldLeft(List[Header](h)) { (hs, _) => + headerGen(stateRoot, hs.head.id).sample.get :: hs + } + } def preHeaderGen(parentId: Coll[Byte]): Gen[PreHeader] = for { version <- arbByte.arbitrary @@ -716,14 +727,14 @@ trait ObjectGenerators extends TypeGenerators votes <- minerVotesGen } yield CPreHeader(version, parentId, timestamp, nBits, height, minerPk, votes) - val preHeaderGen: Gen[PreHeader] = for { + lazy val preHeaderGen: Gen[PreHeader] = for { parentId <- modifierIdBytesGen preHeader <- preHeaderGen(parentId) } yield preHeader - implicit val arbPreHeader = Arbitrary(preHeaderGen) + implicit lazy val arbPreHeader = Arbitrary(preHeaderGen) - val ergoLikeTransactionGen: Gen[ErgoLikeTransaction] = for { + lazy val ergoLikeTransactionGen: Gen[ErgoLikeTransaction] = for { inputBoxesIds <- Gen.nonEmptyListOf(boxIdGen) dataInputBoxIds <- Gen.listOf(boxIdGen) tx <- ergoLikeTransactionGen(inputBoxesIds, dataInputBoxIds) @@ -740,7 +751,7 @@ trait ObjectGenerators extends TypeGenerators outputCandidates = outputCandidates.toIndexedSeq ) - val unsignedErgoLikeTransactionGen: Gen[UnsignedErgoLikeTransaction] = for { + lazy val unsignedErgoLikeTransactionGen: Gen[UnsignedErgoLikeTransaction] = for { inputBoxesIds <- Gen.nonEmptyListOf(boxIdGen) dataInputBoxIds <- Gen.listOf(boxIdGen) tx <- unsignedErgoLikeTransactionGen(inputBoxesIds, dataInputBoxIds) @@ -757,14 +768,14 @@ trait ObjectGenerators extends TypeGenerators outputCandidates = outputCandidates.toIndexedSeq ) - val ergoLikeTransactionTemplateGen: Gen[ErgoLikeTransactionTemplate[_ <: UnsignedInput]] = + lazy val ergoLikeTransactionTemplateGen: Gen[ErgoLikeTransactionTemplate[_ <: UnsignedInput]] = Gen.oneOf(unsignedErgoLikeTransactionGen, ergoLikeTransactionGen) val MaxDataBoxes = 5 val MaxInputBoxes = 5 val MaxOutputBoxes = 100 - val ergoLikeContextGen: Gen[ErgoLikeContext] = for { + lazy val ergoLikeContextGen: Gen[ErgoLikeContext] = for { stateRoot <- avlTreeGen headers <- headersGen(stateRoot) preHeader <- preHeaderGen(headers.headOption.map(_.id).getOrElse(modifierIdBytesGen.sample.get)) diff --git a/interpreter/src/test/scala/sigmastate/serialization/generators/OpcodesGen.scala b/interpreter/shared/src/test/scala/sigmastate/serialization/generators/OpcodesGen.scala similarity index 100% rename from interpreter/src/test/scala/sigmastate/serialization/generators/OpcodesGen.scala rename to interpreter/shared/src/test/scala/sigmastate/serialization/generators/OpcodesGen.scala diff --git a/interpreter/src/test/scala/sigmastate/serialization/generators/RelationGenerators.scala b/interpreter/shared/src/test/scala/sigmastate/serialization/generators/RelationGenerators.scala similarity index 100% rename from interpreter/src/test/scala/sigmastate/serialization/generators/RelationGenerators.scala rename to interpreter/shared/src/test/scala/sigmastate/serialization/generators/RelationGenerators.scala diff --git a/interpreter/src/test/scala/sigmastate/serialization/generators/TransformerGenerators.scala b/interpreter/shared/src/test/scala/sigmastate/serialization/generators/TransformerGenerators.scala similarity index 100% rename from interpreter/src/test/scala/sigmastate/serialization/generators/TransformerGenerators.scala rename to interpreter/shared/src/test/scala/sigmastate/serialization/generators/TransformerGenerators.scala diff --git a/interpreter/src/test/scala/sigmastate/serialization/generators/TypeGenerators.scala b/interpreter/shared/src/test/scala/sigmastate/serialization/generators/TypeGenerators.scala similarity index 89% rename from interpreter/src/test/scala/sigmastate/serialization/generators/TypeGenerators.scala rename to interpreter/shared/src/test/scala/sigmastate/serialization/generators/TypeGenerators.scala index 8521703809..c4c964cc74 100644 --- a/interpreter/src/test/scala/sigmastate/serialization/generators/TypeGenerators.scala +++ b/interpreter/shared/src/test/scala/sigmastate/serialization/generators/TypeGenerators.scala @@ -1,8 +1,8 @@ package sigmastate.serialization.generators -import org.scalacheck.{Arbitrary, Gen} +import org.scalacheck.{Gen, Arbitrary} +import org.scalacheck.Arbitrary.arbString import sigmastate._ -import org.scalacheck.Arbitrary._ trait TypeGenerators { implicit val booleanTypeGen = Gen.const(SBoolean) @@ -15,17 +15,11 @@ trait TypeGenerators { implicit val sigmaPropTypeGen = Gen.const(SSigmaProp) implicit val boxTypeGen = Gen.const(SBox) implicit val avlTreeTypeGen = Gen.const(SAvlTree) - implicit val optionSigmaPropTypeGen = Gen.const(SOption(SSigmaProp)) -// Gen[SOption[SSigmaProp.type]] = for { -// tpe <- Gen.const(SSigmaProp) -// } yield SOption(tpe) - implicit val primTypeGen: Gen[SPrimType] = Gen.oneOf[SPrimType](SBoolean, SByte, SShort, SInt, SLong, SBigInt, SGroupElement, SSigmaProp, SUnit) implicit val arbPrimType = Arbitrary(primTypeGen) - implicit val predefTypeGen: Gen[SPredefType] = Gen.oneOf[SPredefType](SBoolean, SByte, SShort, SInt, SLong, SBigInt, SGroupElement, SSigmaProp, SUnit, SBox, SAvlTree) implicit val arbPredefType = Arbitrary(predefTypeGen) diff --git a/interpreter/src/test/scala/sigmastate/utils/HelpersTests.scala b/interpreter/shared/src/test/scala/sigmastate/utils/HelpersTests.scala similarity index 100% rename from interpreter/src/test/scala/sigmastate/utils/HelpersTests.scala rename to interpreter/shared/src/test/scala/sigmastate/utils/HelpersTests.scala diff --git a/interpreter/src/test/scala/sigmastate/utils/SparseArrayContainerSpecification.scala b/interpreter/shared/src/test/scala/sigmastate/utils/SparseArrayContainerSpecification.scala similarity index 100% rename from interpreter/src/test/scala/sigmastate/utils/SparseArrayContainerSpecification.scala rename to interpreter/shared/src/test/scala/sigmastate/utils/SparseArrayContainerSpecification.scala diff --git a/interpreter/src/test/scala/sigmastate/utxo/ProverSpecification.scala b/interpreter/shared/src/test/scala/sigmastate/utxo/ProverSpecification.scala similarity index 96% rename from interpreter/src/test/scala/sigmastate/utxo/ProverSpecification.scala rename to interpreter/shared/src/test/scala/sigmastate/utxo/ProverSpecification.scala index 88b29bac4f..1ca5bb96a2 100644 --- a/interpreter/src/test/scala/sigmastate/utxo/ProverSpecification.scala +++ b/interpreter/shared/src/test/scala/sigmastate/utxo/ProverSpecification.scala @@ -6,13 +6,11 @@ import sigmastate.Values.SigmaBoolean import sigmastate._ import sigmastate.basics.DLogProtocol.FirstDLogProverMessage import sigmastate.basics.{FirstDiffieHellmanTupleProverMessage, SecP256K1Group} -import sigmastate.helpers.{ErgoLikeTestProvingInterpreter, SigmaTestingCommons} +import sigmastate.exceptions.InterpreterException +import sigmastate.helpers.{ErgoLikeTestProvingInterpreter, TestingCommons} import sigmastate.interpreter.{HintsBag, ProverInterpreter} -import sigmastate.lang.exceptions.InterpreterException -class ProverSpecification extends SigmaTestingCommons { - - implicit lazy val IR: TestingIRContext = new TestingIRContext +class ProverSpecification extends TestingCommons { property("generateCommitments") { diff --git a/interpreter/src/test/scala/special/sigma/ContractsTestkit.scala b/interpreter/shared/src/test/scala/special/sigma/ContractsTestkit.scala similarity index 76% rename from interpreter/src/test/scala/special/sigma/ContractsTestkit.scala rename to interpreter/shared/src/test/scala/special/sigma/ContractsTestkit.scala index d0e8e62e34..4c073d3dc9 100644 --- a/interpreter/src/test/scala/special/sigma/ContractsTestkit.scala +++ b/interpreter/shared/src/test/scala/special/sigma/ContractsTestkit.scala @@ -20,8 +20,6 @@ trait ContractsTestkit { val R7 = 7.toByte; val R8 = 8.toByte; val R9 = 9.toByte; - - val Colls = new CollOverArrayBuilder val SigmaDsl: SigmaDslBuilder = CostingSigmaDslBuilder val noRegisters = collection[AnyValue]() @@ -34,21 +32,24 @@ trait ContractsTestkit { val noHeaders = CostingSigmaDslBuilder.Colls.emptyColl[Header] val dummyPreHeader: PreHeader = null - def collection[T:RType](items: T*) = Colls.fromArray(items.toArray) + /** Create collection from array of items */ + def collection[T: RType](items: T*) = Colls.fromArray(items.toArray) + /** Converts a map of registers to collection of registers. */ def regs(m: Map[Byte, AnyValue]): Coll[AnyValue] = { val res = new Array[AnyValue](10) - for ((id, v) <- m) { + for ( (id, v) <- m ) { assert(res(id) == null, s"register $id is defined more then once") res(id) = v } Colls.fromArray(res) } + /** Converts a map of context vars to collection of context vars. */ def contextVars(m: Map[Byte, AnyValue]): Coll[AnyValue] = { - val maxKey = if (m.keys.isEmpty) 0 else m.keys.max // TODO optimize: max takes 90% of this method + 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) - for ((id, v) <- m) { + for ( (id, v) <- m ) { val i = id - 1 assert(res(i) == null, s"register $id is defined more then once") res(i) = v @@ -57,6 +58,7 @@ trait ContractsTestkit { } val AliceId = Array[Byte](1) // 0x0001 + def newAliceBox(id: Byte, value: Long): Box = { val ergoBox = testBox(value, ErgoTree.fromProposition(Values.TrueSigmaProp), @@ -64,16 +66,21 @@ trait ContractsTestkit { new CostingBox(ergoBox) } - - def testContext(inputs: Array[Box], outputs: Array[Box], height: Int, self: Box, - tree: AvlTree, minerPk: Array[Byte], activatedScriptVersion: Byte, - currErgoTreeVersion: Byte, vars: Array[AnyValue]) = + def testContext( + inputs: Array[Box], outputs: Array[Box], height: Int, self: Box, + tree: AvlTree, minerPk: Array[Byte], activatedScriptVersion: Byte, + currErgoTreeVersion: Byte, vars: Array[AnyValue]) = new CostingDataContext( noInputs.toColl, noHeaders, dummyPreHeader, inputs.toColl, outputs.toColl, height, self, inputs.indexOf(self), tree, minerPk.toColl, vars.toColl, activatedScriptVersion, currErgoTreeVersion) - def newContext(height: Int, self: Box, activatedScriptVersion: Byte, currErgoTreeVersion: Byte, vars: AnyValue*): CostingDataContext = { + def newContext( + height: Int, + self: Box, + activatedScriptVersion: Byte, + currErgoTreeVersion: Byte, + vars: AnyValue*): CostingDataContext = { testContext( noInputs, noOutputs, height, self, emptyAvlTree, dummyPubkey, activatedScriptVersion, currErgoTreeVersion, vars.toArray) @@ -81,9 +88,10 @@ trait ContractsTestkit { implicit class TestContextOps(ctx: CostingDataContext) { def withInputs(inputs: Box*) = ctx.copy(inputs = inputs.toArray.toColl) + def withOutputs(outputs: Box*) = ctx.copy(outputs = outputs.toArray.toColl) + def withVariables(vars: Map[Int, AnyValue]) = ctx.copy(vars = contextVars(vars.map { case (k, v) => (k.toByte, v) })) } - } diff --git a/interpreter/src/test/scala/special/sigma/SigmaTestingData.scala b/interpreter/shared/src/test/scala/special/sigma/SigmaTestingData.scala similarity index 91% rename from interpreter/src/test/scala/special/sigma/SigmaTestingData.scala rename to interpreter/shared/src/test/scala/special/sigma/SigmaTestingData.scala index c9af4a92d1..193282929f 100644 --- a/interpreter/src/test/scala/special/sigma/SigmaTestingData.scala +++ b/interpreter/shared/src/test/scala/special/sigma/SigmaTestingData.scala @@ -1,36 +1,40 @@ package special.sigma -import java.math.BigInteger - import org.ergoplatform.ErgoBox import org.ergoplatform.settings.ErgoAlgos +import org.scalacheck.Arbitrary.arbitrary import org.scalacheck.Gen.containerOfN -import sigmastate._ -import org.scalacheck.{Arbitrary, Gen} -import sigmastate.helpers.SigmaTestingCommons -import sigmastate.eval.{Colls, _} -import sigmastate.eval.Extensions._ import org.scalacheck.util.Buildable +import org.scalacheck.{Arbitrary, Gen} import scalan.RType -import scorex.crypto.hash.{Digest32, Blake2b256} import scorex.crypto.authds.{ADDigest, ADKey, ADValue} +import scorex.crypto.hash.{Blake2b256, Digest32} import scorex.util.ModifierId -import sigmastate.Values._ +import sigmastate.Values.{ByteArrayConstant, ConcreteCollection, ConstantPlaceholder, ErgoTree, FalseLeaf, IntConstant, LongConstant, SigmaPropConstant, TrueLeaf} +import sigmastate.basics.CryptoConstants.EcPointType import sigmastate.basics.DLogProtocol.ProveDlog import sigmastate.basics.ProveDHTuple -import sigmastate.interpreter.CryptoConstants.EcPointType -import sigmastate.Values.ErgoTree +import sigmastate.eval.{Colls, _} +import sigmastate.eval.Extensions._ +import sigmastate.eval.{CAvlTree, CBigInt, CHeader, CPreHeader, CSigmaProp, CostingBox, CostingSigmaDslBuilder, SigmaDsl} +import sigmastate.helpers.TestingCommons import sigmastate.serialization.ErgoTreeSerializer import sigmastate.serialization.generators.ObjectGenerators import sigmastate.utils.Helpers +import sigmastate._ import special.collection.Coll +import java.math.BigInteger import scala.reflect.ClassTag -trait SigmaTestingData extends SigmaTestingCommons with ObjectGenerators { - def Coll[T](items: T*)(implicit cT: RType[T]) = CostingSigmaDslBuilder.Colls.fromItems(items:_*) +trait SigmaTestingData extends TestingCommons with ObjectGenerators { + /** Creates a [[special.collection.Coll]] with the given `items`. */ + def Coll[T](items: T*)(implicit cT: RType[T]): Coll[T] = + CostingSigmaDslBuilder.Colls.fromItems(items: _*) - def collOfN[T: RType: Arbitrary](n: Int)(implicit b: Buildable[T, Array[T]]): Gen[Coll[T]] = { + /** Generator of random collection with `n` elements. */ + def collOfN[T: RType : Arbitrary](n: Int) + (implicit b: Buildable[T, Array[T]]): Gen[Coll[T]] = { implicit val g: Gen[T] = Arbitrary.arbitrary[T] containerOfN[Array, T](n, g).map(Colls.fromArray(_)) } @@ -39,27 +43,23 @@ trait SigmaTestingData extends SigmaTestingCommons with ObjectGenerators { len <- Gen.choose(0, 100) arr <- containerOfN[Array, Byte](len, Arbitrary.arbByte.arbitrary) } yield arr - val bytesCollGen = bytesGen.map(Colls.fromArray(_)) val intsCollGen = arrayGen[Int].map(Colls.fromArray(_)) - implicit val arbBytes = Arbitrary(bytesCollGen) implicit val arbInts = Arbitrary(intsCollGen) - - val keyCollGen = collOfN[Byte](32) - + val keyCollGen = collOfN[Byte](32, arbitrary[Byte]) import org.ergoplatform.dsl.AvlTreeHelpers._ def createAvlTreeAndProver(entries: (Coll[Byte], Coll[Byte])*) = { - val kvs = entries.map { case (k,v) => ADKey @@ k.toArray -> ADValue @@ v.toArray} - val res = createAvlTree(AvlTreeFlags.AllOperationsAllowed, kvs:_*) + val kvs = entries.map { case (k, v) => ADKey @@ k.toArray -> ADValue @@ v.toArray } + val res = createAvlTree(AvlTreeFlags.AllOperationsAllowed, kvs: _*) res } protected def sampleAvlProver = { - val keys = arrayOfN(100, keyCollGen).sample.get + val keys = arrayOfN(100, keyCollGen).sample.get val values = arrayOfN(100, bytesCollGen).sample.get - val (tree, prover) = createAvlTreeAndProver(keys.zip(values):_*) + val (tree, prover) = createAvlTreeAndProver(keys.zip(values): _*) (keys, values, tree, prover) } @@ -72,7 +72,6 @@ trait SigmaTestingData extends SigmaTestingCommons with ObjectGenerators { val tokenId1: Digest32 = Blake2b256("id1") val tokenId2: Digest32 = Blake2b256("id2") - val header1: Header = CHeader(Blake2b256("Header.id").toColl, 0, Blake2b256("Header.parentId").toColl, @@ -103,7 +102,7 @@ trait SigmaTestingData extends SigmaTestingCommons with ObjectGenerators { powOnetimePk = SigmaDsl.groupGenerator, powNonce = Colls.fromArray(Array.fill(0.toByte)(8)), powDistance = SigmaDsl.BigInt(BigInt("19306206489815517413186395405558417825367537880571815686937307203793939").bigInteger), - votes = Colls.fromArray(Array[Byte](0, 1, 0)) + votes = Colls.fromArray(Array[Byte](0, 1, 0)) ) val headers = Colls.fromItems(header2, header1) val preHeader: PreHeader = CPreHeader(0, @@ -117,12 +116,17 @@ trait SigmaTestingData extends SigmaTestingCommons with ObjectGenerators { object TestData { val BigIntZero: BigInt = CBigInt(new BigInteger("0", 16)) + val BigIntOne: BigInt = CBigInt(new BigInteger("1", 16)) + val BigIntMinusOne: BigInt = CBigInt(new BigInteger("-1", 16)) + val BigInt10: BigInt = CBigInt(new BigInteger("a", 16)) + val BigInt11: BigInt = CBigInt(new BigInteger("b", 16)) val BigIntMaxValueStr = "7F" + "ff" * 31 + val BigIntMaxValue_instances = new CloneSet(1000, CBigInt(new BigInteger(BigIntMaxValueStr, 16))) @@ -130,17 +134,22 @@ trait SigmaTestingData extends SigmaTestingCommons with ObjectGenerators { // TODO v6.0: this values have bitCount == 255 (see to256BitValueExact) val BigIntMinValue = CBigInt(new BigInteger("-7F" + "ff" * 31, 16)) + val BigIntMaxValue = createBigIntMaxValue() + val BigIntOverlimit = CBigInt(new BigInteger("7F" + "ff" * 33, 16)) val ge1str = "03358d53f01276211f92d0aefbd278805121d4ff6eb534b777af1ee8abae5b2056" + val ge2str = "02dba7b94b111f3894e2f9120b577da595ec7d58d488485adf73bf4e153af63575" + val ge3str = "0290449814f5671172dd696a61b8aa49aaa4c87013f56165e27d49944e98bc414d" val ge1_bytes = ErgoAlgos.decodeUnsafe(ge1str) class CloneSet[T: ClassTag](val size: Int, generator: => T) { val instances = Array.fill(size)(generator) + var currentInst: Int = 0 /** Selects next instance (round-robin). */ @@ -159,8 +168,11 @@ trait SigmaTestingData extends SigmaTestingCommons with ObjectGenerators { val ge1 = create_ge1() val ge2_bytes = ErgoAlgos.decodeUnsafe(ge2str) + val ge2_instances = new CloneSet(1000, SigmaDsl.decodePoint(Colls.fromArray(ge2_bytes))) + def create_ge2(): GroupElement = ge2_instances.getNext + val ge2 = create_ge2() val ge3 = Helpers.decodeGroupElement(ge3str) @@ -177,6 +189,7 @@ trait SigmaTestingData extends SigmaTestingCommons with ObjectGenerators { def create_t1(): AvlTree = t1_instances.getNext val t1: AvlTree = create_t1() + val t2: AvlTree = CAvlTree( AvlTreeData( ADDigest @@ ErgoAlgos.decodeUnsafe("ff000d937f80ffd731ed802d24358001ff8080ff71007f00ad37e0a7ae43fff95b"), @@ -185,6 +198,7 @@ trait SigmaTestingData extends SigmaTestingCommons with ObjectGenerators { Some(64) ) ) + val t3: AvlTree = CAvlTree( AvlTreeData( ADDigest @@ ErgoAlgos.decodeUnsafe("3100d2e101ff01fc047c7f6f00ff80129df69a5090012f01ffca99f5bfff0c8036"), @@ -213,9 +227,9 @@ trait SigmaTestingData extends SigmaTestingCommons with ObjectGenerators { Right(ConstantPlaceholder(0, SSigmaProp)) ), Coll( - (Digest32 @@ (ErgoAlgos.decodeUnsafe("6e789ab7b2fffff12280a6cd01557f6fb22b7f80ff7aff8e1f7f15973d7f0001")), + (Digest32Coll @@@ Colls.fromArray(ErgoAlgos.decodeUnsafe("6e789ab7b2fffff12280a6cd01557f6fb22b7f80ff7aff8e1f7f15973d7f0001")), 10000000L), - (Digest32 @@ (ErgoAlgos.decodeUnsafe("a3ff007f00057600808001ff8f8000019000ffdb806fff7cc0b6015eb37fa600")), + (Digest32Coll @@@ Colls.fromArray(ErgoAlgos.decodeUnsafe("a3ff007f00057600808001ff8f8000019000ffdb806fff7cc0b6015eb37fa600")), 500L) ), Map( diff --git a/interpreter/src/main/scala/org/ergoplatform/settings/MonetarySettings.scala b/interpreter/src/main/scala/org/ergoplatform/settings/MonetarySettings.scala deleted file mode 100644 index 911e1c1c31..0000000000 --- a/interpreter/src/main/scala/org/ergoplatform/settings/MonetarySettings.scala +++ /dev/null @@ -1,24 +0,0 @@ -package org.ergoplatform.settings - -import org.ergoplatform.ErgoScriptPredef -import org.ergoplatform.mining.emission.EmissionRules -import sigmastate.Values.ErgoTree - -/** - * Configuration file for monetary settings of Ergo chain - * - * @see src/main/resources/application.conf for parameters description - */ -case class MonetarySettings(fixedRatePeriod: Int = 30 * 2 * 24 * 365, - epochLength: Int = 90 * 24 * 30, - fixedRate: Long = 75L * EmissionRules.CoinsInOneErgo, - oneEpochReduction: Long = 3L * EmissionRules.CoinsInOneErgo, - minerRewardDelay: Int = 720, - foundersInitialReward: Long = 75L * EmissionRules.CoinsInOneErgo / 10) { - - val feeProposition: ErgoTree = ErgoScriptPredef.feeProposition(minerRewardDelay) - val feePropositionBytes: Array[Byte] = feeProposition.bytes - val emissionBoxProposition: ErgoTree = ErgoScriptPredef.emissionBoxProp(this) - val foundersBoxProposition: ErgoTree = ErgoScriptPredef.foundationScript(this) - -} diff --git a/interpreter/src/main/scala/sigmastate/crypto/Platform.scala b/interpreter/src/main/scala/sigmastate/crypto/Platform.scala deleted file mode 100644 index 55bc179e54..0000000000 --- a/interpreter/src/main/scala/sigmastate/crypto/Platform.scala +++ /dev/null @@ -1,122 +0,0 @@ -package sigmastate.crypto - -import org.bouncycastle.crypto.ec.CustomNamedCurves -import org.bouncycastle.math.ec.{ECPoint, ECFieldElement, ECCurve} - -import java.math.BigInteger - -/** JVM specific implementation of crypto methods*/ -object Platform { - /** Description of elliptic curve of point `p` which belongs to the curve. - * @param p the elliptic curve point - */ - def getCurve(p: Ecp): Curve = Curve(p.value.getCurve) - - /** Returns the x-coordinate. - * - * Caution: depending on the curve's coordinate system, this may not be the same value as in an - * affine coordinate system; use normalize() to get a point where the coordinates have their - * affine values, or use getAffineXCoord() if you expect the point to already have been - * normalized. - * - * @return the x-coordinate of this point - */ - def getXCoord(p: Ecp): ECFieldElem = ECFieldElem(p.value.getXCoord) - - /** Returns the y-coordinate. - * - * Caution: depending on the curve's coordinate system, this may not be the same value as in an - * affine coordinate system; use normalize() to get a point where the coordinates have their - * affine values, or use getAffineYCoord() if you expect the point to already have been - * normalized. - * - * @return the y-coordinate of this point - */ - def getYCoord(p: Ecp): ECFieldElem = ECFieldElem(p.value.getYCoord) - - /** Returns the affine x-coordinate after checking that this point is normalized. - * - * @return The affine x-coordinate of this point - * @throws IllegalStateException if the point is not normalized - */ - def getAffineXCoord(p: Ecp): ECFieldElem = ECFieldElem(p.value.getAffineXCoord) - - /** Returns the affine y-coordinate after checking that this point is normalized - * - * @return The affine y-coordinate of this point - * @throws IllegalStateException if the point is not normalized - */ - def getAffineYCoord(p: Ecp): ECFieldElem = ECFieldElem(p.value.getAffineYCoord) - - /** Returns byte representation of the given field element. */ - def encodeFieldElem(p: ECFieldElem): Array[Byte] = p.value.getEncoded - - /** Byte representation of the given point. - * @param p point to encode - * @param compressed if true, generates a compressed point encoding - */ - def encodePoint(p: Ecp, compressed: Boolean): Array[Byte] = p.value.getEncoded(compressed) - - /** Returns the value of bit 0 in BigInteger representation of this point. */ - def signOf(p: ECFieldElem): Boolean = p.value.testBitZero() - - /** * Normalization ensures that any projective coordinate is 1, and therefore that the x, y - * coordinates reflect those of the equivalent point in an affine coordinate system. - * - * @return a new ECPoint instance representing the same point, but with normalized coordinates - */ - def normalizePoint(p: Ecp): Ecp = Ecp(p.value.normalize()) - - /** Return simplified string representation of the point (used only for debugging) */ - def showPoint(p: Ecp): String = { - val rawX = p.value.getRawXCoord.toString.substring(0, 6) - val rawY = p.value.getRawYCoord.toString.substring(0, 6) - s"ECPoint($rawX,$rawY,...)" - } - - /** Multiply two points. - * @param p1 first point - * @param p2 second point - * @return group multiplication (p1 * p2) - */ - def multiplyPoints(p1: Ecp, p2: Ecp): Ecp = { - /* - * BC treats EC as additive group while we treat that as multiplicative group. - */ - Ecp(p1.value.add(p2.value)) - } - - /** Exponentiate a point. - * @param p point to exponentiate - * @param n exponent - * @return p to the power of n (p^n) - */ - def exponentiatePoint(p: Ecp, n: BigInteger): Ecp = { - /* - * BC treats EC as additive group while we treat that as multiplicative group. - * Therefore, exponentiate point is multiply. - */ - Ecp(p.value.multiply(n)) - } - - /** Check if a point is infinity. */ - def isInfinityPoint(p: Ecp): Boolean = p.value.isInfinity - - /** Negate a point. */ - def negatePoint(p: Ecp): Ecp = Ecp(p.value.negate()) - - /** Wrapper for curve descriptor. Serves as the concrete implementation of the - * [[sigmastate.crypto.Curve]] type in JVM. - */ - case class Curve(private[crypto] val value: ECCurve) - - /** Wrapper for point type. */ - case class Ecp(private[crypto] val value: ECPoint) - - /** Wrapper for field element type. */ - case class ECFieldElem(value: ECFieldElement) - - /** Create a new context for cryptographic operations. */ - def createContext(): CryptoContext = new CryptoContextJvm(CustomNamedCurves.getByName("secp256k1")) - -} diff --git a/interpreter/src/main/scala/sigmastate/lang/exceptions/SigmaSerializerExceptions.scala b/interpreter/src/main/scala/sigmastate/lang/exceptions/SigmaSerializerExceptions.scala deleted file mode 100644 index 69daa59a10..0000000000 --- a/interpreter/src/main/scala/sigmastate/lang/exceptions/SigmaSerializerExceptions.scala +++ /dev/null @@ -1,26 +0,0 @@ -package sigmastate.lang.exceptions - -import sigmastate.lang.SourceContext - -/** Thrown by TypeSerializer when type prefix <= 0. */ -final class InvalidTypePrefix(message: String, source: Option[SourceContext] = None, cause: Option[Throwable] = None) - extends SerializerException(message, source, cause) - -/** Thrown when the current reader position > positionLimit which is set in the Reader. - * @see [[org.ergoplatform.validation.ValidationRules.CheckPositionLimit]] - */ -final class ReaderPositionLimitExceeded( - message: String, - val position: Int, - val positionLimit: Int, - source: Option[SourceContext] = None, - cause: Option[Throwable] = None) - extends SerializerException(message, source, cause) - -/** Thrown when the current depth level > maxDepthLevel which is set in the Reader. */ -final class DeserializeCallDepthExceeded(message: String, source: Option[SourceContext] = None, cause: Option[Throwable] = None) - extends SerializerException(message, source, cause) - -/** Thrown by [[org.ergoplatform.validation.ValidationRules.CheckValidOpCode]] validation rule. */ -final class InvalidOpCode(message: String, source: Option[SourceContext] = None, cause: Option[Throwable] = None) - extends SerializerException(message, source, cause) diff --git a/interpreter/src/main/scala/sigmastate/sigmastate.scala b/interpreter/src/main/scala/sigmastate/sigmastate.scala deleted file mode 100644 index a8c2bb1a3e..0000000000 --- a/interpreter/src/main/scala/sigmastate/sigmastate.scala +++ /dev/null @@ -1,81 +0,0 @@ -import sigmastate.Values._ -import sigmastate.lang.CheckingSigmaBuilder - -package object sigmastate { - import CheckingSigmaBuilder._ - - /** Represents cost estimation computed by JITC interpreter. - * The JITC costs use 10x more accurate scale comparing to block cost values. - * @see toBlockCost - */ - case class JitCost private[sigmastate] (private[sigmastate] val value: Int) extends AnyVal { - /** Adds two cost values. */ - def + (y: JitCost): JitCost = - new JitCost(java7.compat.Math.addExact(value, y.value)) - - /** Multiplies this cost to the given integer. */ - def * (n: Int): JitCost = - new JitCost(java7.compat.Math.multiplyExact(value, n)) - - /** Divides this cost by the given integer. */ - def / (n: Int): JitCost = - new JitCost(value / n) - - /** Return true if this value > y.value in the normal Int ordering. */ - def > (y: JitCost): Boolean = value > y.value - - /** Return true if this value >= y.value in the normal Int ordering. */ - def >= (y: JitCost): Boolean = value >= y.value - - /** Scales JitCost back to block cost value. This is inverse to JitCost.fromBlockCost. */ - def toBlockCost: Int = value / 10 - } - object JitCost { - /** Scales the given block cost to the JitCost scale. This is inverse to toBlockCost*/ - def fromBlockCost(blockCost: Int): JitCost = - new JitCost(java7.compat.Math.multiplyExact(blockCost, 10)) - } - - /** - * SInt addition - */ - def Plus[T <: SNumericType](left: Value[T], right: Value[T]): Value[T] = - mkPlus(left, right) - - /** - * SInt subtraction - */ - def Minus[T <: SNumericType](left: Value[T], right: Value[T]): Value[T] = - mkMinus(left, right) - - /** - * SInt multiplication - */ - def Multiply[T <: SNumericType](left: Value[T], right: Value[T]): Value[T] = - mkMultiply(left, right) - - /** - * SInt division - */ - def Divide[T <: SNumericType](left: Value[T], right: Value[T]): Value[T] = - mkDivide(left, right) - - /** - * SInt modulo - */ - def Modulo[T <: SNumericType](left: Value[T], right: Value[T]): Value[T] = - mkModulo(left, right) - - def Min[T <: SNumericType](left: Value[T], right: Value[T]): Value[T] = - mkMin(left, right) - - def Max[T <: SNumericType](left: Value[T], right: Value[T]): Value[T] = - mkMax(left, right) - - def PlusModQ(left: Value[SBigInt.type], right: Value[SBigInt.type]): Value[SBigInt.type] = - mkPlusModQ(left, right) - - def MinusModQ(left: Value[SBigInt.type], right: Value[SBigInt.type]): Value[SBigInt.type] = - mkMinusModQ(left, right) - -} diff --git a/interpreter/src/test/scala/sigmastate/serialization/ModQSerializerSpecification.scala b/interpreter/src/test/scala/sigmastate/serialization/ModQSerializerSpecification.scala deleted file mode 100644 index d7d0100c2d..0000000000 --- a/interpreter/src/test/scala/sigmastate/serialization/ModQSerializerSpecification.scala +++ /dev/null @@ -1,28 +0,0 @@ -package sigmastate.serialization - -import sigmastate.Values.BigIntConstant -import sigmastate._ - -class ModQSerializerSpecification extends SerializationSpecification { - - // TODO https://github.com/ScorexFoundation/sigmastate-interpreter/issues/327 - ignore("ModQ: Serializer round trip") { - forAll(bigIntConstGen) { x: BigIntConstant => - roundTripTest(ModQ(x)) - } - } - - // TODO https://github.com/ScorexFoundation/sigmastate-interpreter/issues/327 - ignore("PlusModQ: Serializer round trip") { - forAll(bigIntConstGen, bigIntConstGen) { (x1: BigIntConstant, x2: BigIntConstant) => - roundTripTest(PlusModQ(x1, x2)) - } - } - - // TODO https://github.com/ScorexFoundation/sigmastate-interpreter/issues/327 - ignore("MinusModQ: Serializer round trip") { - forAll(bigIntConstGen, bigIntConstGen) { (x1: BigIntConstant, x2: BigIntConstant) => - roundTripTest(MinusModQ(x1, x2)) - } - } -} diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 0000000000..9a77235bd5 --- /dev/null +++ b/jest.config.js @@ -0,0 +1,6 @@ +module.exports = { + collectCoverage: false, + coverageProvider: "v8", + moduleDirectories: ["node_modules"], + testMatch: ["**/__tests__/**/*.[jt]s?(x)", "**/?(*.)+(spec|test).[tj]s?(x)"], +}; diff --git a/project/build.properties b/project/build.properties index c06db1bb2e..9edb75b77c 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.4.5 +sbt.version=1.5.4 diff --git a/project/plugins.sbt b/project/plugins.sbt index f9112ba7f2..bf82a32cbb 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -7,4 +7,9 @@ addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.6.1") addSbtPlugin("org.scoverage" % "sbt-coveralls" % "1.2.7") addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "3.9.17") addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.2.1") -addSbtPlugin("com.dwijnand" % "sbt-dynver" % "4.1.1") \ No newline at end of file +addSbtPlugin("com.dwijnand" % "sbt-dynver" % "4.1.1") + +addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.2.0") +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.11.0") +addSbtPlugin("ch.epfl.scala" % "sbt-scalajs-bundler" % "0.20.0") +addSbtPlugin("org.scalablytyped.converter" % "sbt-converter" % "1.0.0-beta37") \ No newline at end of file diff --git a/sc/src/main/scala/org/ergoplatform/ErgoScriptPredef.scala b/sc/src/main/scala/org/ergoplatform/ErgoScriptPredef.scala new file mode 100644 index 0000000000..b8c0db379a --- /dev/null +++ b/sc/src/main/scala/org/ergoplatform/ErgoScriptPredef.scala @@ -0,0 +1,51 @@ +package org.ergoplatform + +import sigmastate.SType +import sigmastate.lang.SigmaCompiler +import sigmastate.eval.IRContext +import org.ergoplatform.ErgoAddressEncoder.NetworkPrefix +import sigmastate.Values.{SigmaPropValue, Value} +import sigmastate.lang.Terms.ValueOps + +object ErgoScriptPredef { + import sigmastate.interpreter.Interpreter._ + + /** Compiles the given ErgoScript `code` into ErgoTree expression. */ + def compileWithCosting(env: ScriptEnv, code: String, networkPrefix: NetworkPrefix)(implicit IR: IRContext): Value[SType] = { + val compiler = new SigmaCompiler(networkPrefix) + val res = compiler.compile(env, code) + res.buildTree + } + + /** + * Proposition of the box that may be spent by a transaction + * which inputs contains at least `thresholdAmount` of token with id `tokenId`. + * The logic of this script is following + * (v1) INPUTS.flatMap(box => box.tokens.filter(t => t._1 == tokenId).map(t => t._2)).sum >= thresholdAmount + * (v2) INPUTS.flatMap(box => box.tokens).filter(t => t._1 == tokenId).sum >= thresholdAmount + * (v3) INPUTS.map(box => box.tokens.find(t => t._1 == tokenId).map(t => t._2).getOrElse(0)).sum >= thresholdAmount + */ + def tokenThresholdScript( + tokenId: Array[Byte], + thresholdAmount: Long, + networkPrefix: NetworkPrefix) + (implicit IR: IRContext): SigmaPropValue = { + val env = emptyEnv + + ("tokenId" -> tokenId, "thresholdAmount" -> thresholdAmount) + val res = compileWithCosting(env, + """{ + | val sumValues = { (xs: Coll[Long]) => xs.fold(0L, { (acc: Long, amt: Long) => acc + amt }) } + | + | val tokenAmounts = INPUTS.map({ (box: Box) => + | sumValues(box.tokens.map { (tokenPair: (Coll[Byte], Long)) => + | val ourTokenAmount = if (tokenPair._1 == tokenId) tokenPair._2 else 0L + | ourTokenAmount + | }) + | }) + | val total = sumValues(tokenAmounts) + | sigmaProp(total >= thresholdAmount) + |} + """.stripMargin, networkPrefix) + res.asSigmaProp + } +} diff --git a/interpreter/src/main/scala/org/ergoplatform/JsonCodecs.scala b/sc/src/main/scala/org/ergoplatform/JsonCodecs.scala similarity index 97% rename from interpreter/src/main/scala/org/ergoplatform/JsonCodecs.scala rename to sc/src/main/scala/org/ergoplatform/JsonCodecs.scala index a8684f1568..ea239af46f 100644 --- a/interpreter/src/main/scala/org/ergoplatform/JsonCodecs.scala +++ b/sc/src/main/scala/org/ergoplatform/JsonCodecs.scala @@ -5,7 +5,7 @@ import java.math.BigInteger import cats.syntax.either._ import io.circe._ import io.circe.syntax._ -import org.ergoplatform.ErgoBox.{BoxId, NonMandatoryRegisterId, TokenId} +import org.ergoplatform.ErgoBox.{BoxId, NonMandatoryRegisterId, Token, TokenId} import org.ergoplatform.settings.ErgoAlgos import org.ergoplatform.validation.{SigmaValidationSettings, SigmaValidationSettingsSerializer} import scorex.crypto.authds.{ADDigest, ADKey} @@ -14,8 +14,8 @@ import scorex.util.ModifierId import sigmastate.Values.{ErgoTree, EvaluatedValue} import sigmastate.eval.Extensions._ import sigmastate.eval.{CPreHeader, WrapperOf, _} +import sigmastate.exceptions.SigmaException import sigmastate.interpreter.{ContextExtension, ProverResult} -import sigmastate.lang.exceptions.SigmaException import sigmastate.serialization.{DataJsonEncoder, ErgoTreeSerializer, ValueSerializer} import sigmastate.{AvlTreeData, AvlTreeFlags, SType} import special.collection.Coll @@ -76,14 +76,17 @@ trait JsonCodecs { implicit val digest32Encoder: Encoder[Digest32] = Encoder.instance(_.array.asJson) implicit val digest32Decoder: Decoder[Digest32] = bytesDecoder(Digest32 @@ _) - implicit val assetEncoder: Encoder[(TokenId, Long)] = Encoder.instance({ asset => + implicit val digest32CollEncoder: Encoder[Digest32Coll] = Encoder.instance(d => ErgoAlgos.encode(d).asJson) + implicit val digest32CollDecoder: Decoder[Digest32Coll] = bytesDecoder(bytes => Digest32Coll @@ bytes.toColl) + + implicit val assetEncoder: Encoder[Token] = Encoder.instance({ asset => Json.obj( "tokenId" -> asset._1.asJson, "amount" -> asset._2.asJson ) }) - implicit val assetDecoder: Decoder[(TokenId, Long)] = Decoder.instance({ cursor => + implicit val assetDecoder: Decoder[Token] = Decoder.instance({ cursor => for { tokenId <- cursor.downField("tokenId").as[TokenId] amount <- cursor.downField("amount").as[Long] diff --git a/interpreter/src/main/scala/org/ergoplatform/dsl/ContractSpec.scala b/sc/src/main/scala/org/ergoplatform/dsl/ContractSpec.scala similarity index 94% rename from interpreter/src/main/scala/org/ergoplatform/dsl/ContractSpec.scala rename to sc/src/main/scala/org/ergoplatform/dsl/ContractSpec.scala index c8563a5ae4..bba4d81b3e 100644 --- a/interpreter/src/main/scala/org/ergoplatform/dsl/ContractSpec.scala +++ b/sc/src/main/scala/org/ergoplatform/dsl/ContractSpec.scala @@ -1,7 +1,7 @@ package org.ergoplatform.dsl -import org.ergoplatform.ErgoBox.{NonMandatoryRegisterId, BoxId} -import sigmastate.interpreter.{ProverResult, CostedProverResult} +import org.ergoplatform.ErgoBox.{BoxId, NonMandatoryRegisterId, TokenId} +import sigmastate.interpreter.{CostedProverResult, ProverResult} import scalan.RType import org.ergoplatform.{ErgoLikeContext, ErgoBox} import special.sigma.{SigmaDslBuilder, AnyValue, SigmaProp} @@ -9,7 +9,8 @@ import sigmastate.Values.ErgoTree import sigmastate.eval.{IRContext, CostingSigmaDslBuilder} import scala.util.Try -import org.ergoplatform.dsl.ContractSyntax.{Token, TokenId, ErgoScript, Proposition} +import org.ergoplatform.dsl.ContractSyntax.{ErgoScript, Proposition, Token} + import scala.language.implicitConversions trait ContractSpec { diff --git a/interpreter/src/main/scala/org/ergoplatform/dsl/ContractSyntax.scala b/sc/src/main/scala/org/ergoplatform/dsl/ContractSyntax.scala similarity index 61% rename from interpreter/src/main/scala/org/ergoplatform/dsl/ContractSyntax.scala rename to sc/src/main/scala/org/ergoplatform/dsl/ContractSyntax.scala index 9e3b8160bc..d792663669 100644 --- a/interpreter/src/main/scala/org/ergoplatform/dsl/ContractSyntax.scala +++ b/sc/src/main/scala/org/ergoplatform/dsl/ContractSyntax.scala @@ -1,26 +1,45 @@ package org.ergoplatform.dsl +import org.ergoplatform.ErgoBox.TokenId import scalan.RType import sigmastate.SType import sigmastate.SType.AnyOps import org.ergoplatform.dsl.ContractSyntax.{ErgoScript, Proposition} import sigmastate.eval.{CostingSigmaDslBuilder, Evaluation} import sigmastate.interpreter.Interpreter.ScriptEnv -import special.collection.Coll import special.sigma.{SigmaProp, SigmaContract, Context, SigmaDslBuilder} import scala.language.implicitConversions +/** Defines methods to be used in contract implementations based on [[SigmaContract]]. */ trait ContractSyntax { contract: SigmaContract => override def builder: SigmaDslBuilder = CostingSigmaDslBuilder + + /** Instance of contract specification DSL, which can be imported in the body of + * [[SigmaContract]] implementations. */ val spec: ContractSpec + + /** A contract environment which defines named constants used in the contract. + * Should be defined in [[SigmaContract]] implementations. + */ def contractEnv: ScriptEnv /** The default verifier which represents miner's role in verification of transactions. * It can be overriden in derived classes. */ lazy val verifier: spec.VerifyingParty = spec.VerifyingParty("Miner") + /** Helper method to support Scala <-> ErgoScript equivalence. */ def Coll[T](items: T*)(implicit cT: RType[T]) = builder.Colls.fromItems(items:_*) + /** Call this function in [[SigmaContract]] implementations to define propositions. + * + * @param name name of the proposition (aka contract name) + * @param dslSpec Scala lambda of type [[Proposition]] which defines contract semantics + * and can be executed directly. + * @param scriptCode ErgoScript representation of the contract. + * @param scriptVersion optional script version to be used in ErgoTree. + * If None then ergoTreeVersionInTests is used. + * @return proposition specification with compiled ErgoTree. + */ def proposition(name: String, dslSpec: Proposition, scriptCode: String, @@ -33,12 +52,15 @@ trait ContractSyntax { contract: SigmaContract => spec.mkPropositionSpec(name, dslSpec, ErgoScript(env, scriptCode, scriptVersion)) } + /** Creates new environment with the given named constants. */ def Env(entries: (String, Any)*): ScriptEnv = Map(entries:_*) } object ContractSyntax { + /** Type of proposition as Scala lambda. */ type Proposition = Context => SigmaProp - type TokenId = Coll[Byte] + /** Represents ErgoScript contract to be compiled. */ case class ErgoScript(env: ScriptEnv, code: String, scriptVersion: Option[Byte]) + /** Typed representation of token id and amount. */ case class Token(id: TokenId, value: Long) } diff --git a/interpreter/src/main/scala/org/ergoplatform/dsl/ErgoContractSpec.scala b/sc/src/main/scala/org/ergoplatform/dsl/ErgoContractSpec.scala similarity index 89% rename from interpreter/src/main/scala/org/ergoplatform/dsl/ErgoContractSpec.scala rename to sc/src/main/scala/org/ergoplatform/dsl/ErgoContractSpec.scala index 116a769a87..abc025acf9 100644 --- a/interpreter/src/main/scala/org/ergoplatform/dsl/ErgoContractSpec.scala +++ b/sc/src/main/scala/org/ergoplatform/dsl/ErgoContractSpec.scala @@ -3,8 +3,8 @@ package org.ergoplatform.dsl import special.collection.Coll import sigmastate.interpreter.CostedProverResult import sigmastate.eval.IRContext -import org.ergoplatform.dsl.ContractSyntax.{Token, TokenId, ErgoScript, Proposition} -import org.ergoplatform.ErgoBox.{NonMandatoryRegisterId, BoxId} +import org.ergoplatform.dsl.ContractSyntax.{ErgoScript, Proposition, Token} +import org.ergoplatform.ErgoBox.{BoxId, NonMandatoryRegisterId, TokenId} class ErgoContractSpec(implicit val IR: IRContext) extends ContractSpec { diff --git a/interpreter/src/main/scala/org/ergoplatform/dsl/StdContracts.scala b/sc/src/main/scala/org/ergoplatform/dsl/StdContracts.scala similarity index 100% rename from interpreter/src/main/scala/org/ergoplatform/dsl/StdContracts.scala rename to sc/src/main/scala/org/ergoplatform/dsl/StdContracts.scala diff --git a/interpreter/src/main/scala/sigmastate/eval/GraphBuilding.scala b/sc/src/main/scala/sigmastate/eval/GraphBuilding.scala similarity index 98% rename from interpreter/src/main/scala/sigmastate/eval/GraphBuilding.scala rename to sc/src/main/scala/sigmastate/eval/GraphBuilding.scala index 8afa48e0d2..cae5eb7110 100644 --- a/interpreter/src/main/scala/sigmastate/eval/GraphBuilding.scala +++ b/sc/src/main/scala/sigmastate/eval/GraphBuilding.scala @@ -14,9 +14,8 @@ import sigmastate.lang.Terms.{Ident, Select, Val, ValueOps} import sigmastate.serialization.OpCodes import sigmastate.utxo._ import sigmastate._ -import sigmastate.interpreter.CryptoConstants.EcPointType -import sigmastate.lang.exceptions.CosterException - +import sigmastate.basics.CryptoConstants.EcPointType +import sigmastate.exceptions.{SigmaException, CosterException} import scala.collection.mutable.ArrayBuffer /** Perform translation of typed expression given by [[Value]] to a graph in IRContext. @@ -70,6 +69,20 @@ trait GraphBuilding extends SigmaLibrary { IR: IRContext => /** To enable specific configuration uncomment one of the lines above and use it in the beginPass below. */ // beginPass(costPass) + /** Check the tuple type is valid. + * In v5.x this code is taken from CheckTupleType validation rule which is no longer + * part of consensus. + */ + def checkTupleType[Ctx <: IRContext, T](ctx: Ctx)(e: ctx.Elem[_]): Unit = { + val condition = e match { + case _: ctx.PairElem[_, _] => true + case _ => false + } + if (!condition) { + throw new SigmaException(s"Invalid tuple type $e") + } + } + type RColl[T] = Ref[Coll[T]] type ROption[T] = Ref[WOption[T]] @@ -612,7 +625,7 @@ trait GraphBuilding extends SigmaLibrary { IR: IRContext => // tup._1 or tup._2 case SelectField(In(tup), fieldIndex) => val eTuple = tup.elem.asInstanceOf[Elem[_]] - CheckTupleType(IR)(eTuple) + checkTupleType(IR)(eTuple) eTuple match { case pe: PairElem[a,b] => assert(fieldIndex == 1 || fieldIndex == 2, s"Invalid field index $fieldIndex of the pair ${tup}: $pe") diff --git a/interpreter/src/main/scala/sigmastate/eval/IRContext.scala b/sc/src/main/scala/sigmastate/eval/IRContext.scala similarity index 100% rename from interpreter/src/main/scala/sigmastate/eval/IRContext.scala rename to sc/src/main/scala/sigmastate/eval/IRContext.scala diff --git a/interpreter/src/main/scala/sigmastate/eval/TreeBuilding.scala b/sc/src/main/scala/sigmastate/eval/TreeBuilding.scala similarity index 99% rename from interpreter/src/main/scala/sigmastate/eval/TreeBuilding.scala rename to sc/src/main/scala/sigmastate/eval/TreeBuilding.scala index f8f477dca3..444cd3bb9e 100644 --- a/interpreter/src/main/scala/sigmastate/eval/TreeBuilding.scala +++ b/sc/src/main/scala/sigmastate/eval/TreeBuilding.scala @@ -13,7 +13,7 @@ import SType._ import scalan.SigmaLibrary import sigmastate.basics.DLogProtocol.ProveDlog import sigmastate.basics.ProveDHTuple -import sigmastate.lang.SigmaTyper +import sigmastate.lang.Terms /** Implementation of IR-graph to ErgoTree expression translation. * This, in a sense, is inverse to [[GraphBuilding]], however roundtrip identity is not @@ -282,7 +282,7 @@ trait TreeBuilding extends SigmaLibrary { IR: IRContext => case (mth @ SCollection.ZipMethod, Seq(coll)) => val typeSubst = Map(SCollection.tOV -> coll.asCollection[SType].tpe.elemType) typeSubst - case (mth, _) => SigmaTyper.EmptySubst + case (mth, _) => Terms.EmptySubst } val specMethod = method.withConcreteTypes(typeSubst + (SCollection.tIV -> colTpe.elemType)) builder.mkMethodCall(col, specMethod, args.toIndexedSeq, Map()) diff --git a/interpreter/src/main/scala/sigmastate/lang/SigmaBinder.scala b/sc/src/main/scala/sigmastate/lang/SigmaBinder.scala similarity index 98% rename from interpreter/src/main/scala/sigmastate/lang/SigmaBinder.scala rename to sc/src/main/scala/sigmastate/lang/SigmaBinder.scala index 74db3ce7e7..7ca4f4db73 100644 --- a/interpreter/src/main/scala/sigmastate/lang/SigmaBinder.scala +++ b/sc/src/main/scala/sigmastate/lang/SigmaBinder.scala @@ -9,7 +9,7 @@ import sigmastate._ import sigmastate.interpreter.Interpreter.ScriptEnv import sigmastate.lang.SigmaPredef.PredefinedFuncRegistry import sigmastate.lang.Terms._ -import sigmastate.lang.exceptions.{BinderException, InvalidArguments} +import sigmastate.exceptions.{BinderException, InvalidArguments} object SrcCtxCallbackRewriter extends CallbackRewriter { override def rewriting[T](oldTerm: T, newTerm: T): T = (oldTerm, newTerm) match { diff --git a/interpreter/src/main/scala/sigmastate/lang/SigmaCompiler.scala b/sc/src/main/scala/sigmastate/lang/SigmaCompiler.scala similarity index 99% rename from interpreter/src/main/scala/sigmastate/lang/SigmaCompiler.scala rename to sc/src/main/scala/sigmastate/lang/SigmaCompiler.scala index ebe3118fb0..b3575bcd8c 100644 --- a/interpreter/src/main/scala/sigmastate/lang/SigmaCompiler.scala +++ b/sc/src/main/scala/sigmastate/lang/SigmaCompiler.scala @@ -60,7 +60,7 @@ class SigmaCompiler(settings: CompilerSettings) { /** Parses the given ErgoScript source code and produces expression tree. */ def parse(x: String): SValue = { - SigmaParser(x, builder) match { + SigmaParser(x) match { case Success(v, _) => v case f: Parsed.Failure => throw new ParserException(s"Syntax error: $f", Some(SourceContext.fromParserFailure(f))) diff --git a/interpreter/src/main/scala/sigmastate/lang/SigmaParser.scala b/sc/src/main/scala/sigmastate/lang/SigmaParser.scala similarity index 66% rename from interpreter/src/main/scala/sigmastate/lang/SigmaParser.scala rename to sc/src/main/scala/sigmastate/lang/SigmaParser.scala index 7ce05f2ad7..0ef4651cf5 100644 --- a/interpreter/src/main/scala/sigmastate/lang/SigmaParser.scala +++ b/sc/src/main/scala/sigmastate/lang/SigmaParser.scala @@ -11,11 +11,12 @@ import sigmastate.lang.syntax.{Core, Exprs} import scala.collection.mutable import scala.util.DynamicVariable +/** Main facade to ErgoScript parser implementation. */ object SigmaParser extends Exprs with Types with Core { import fastparse._; import ScalaWhitespace._ import builder._ - val currentInput = new DynamicVariable[String]("") + private val currentInput = new DynamicVariable[String]("") override def atSrcPos[A](parserIndex: Int)(thunk: => A): A = builder.currentSrcCtx.withValue(Nullable(srcCtx(parserIndex))) { thunk } @@ -23,40 +24,21 @@ object SigmaParser extends Exprs with Types with Core { override def srcCtx(parserIndex: Int): SourceContext = SourceContext.fromParserIndex(parserIndex, currentInput.value) - def TmplBodyPrelude[_:P] = P( (Annot ~ OneNLMax).rep ) - def TmplBodyStat[_:P] = P( TmplBodyPrelude ~ BlockDef | StatCtx.Expr ) - - def TmplBody[_:P] = { - P( "{" ~/ BlockLambda.? ~ Semis.? ~ TmplBodyStat.repX(sep = Semis) ~ Semis.? ~ `}` ) - } - -// val FunDef = { -// P( (Id | `this`).! ~ LambdaDef ).map { case (name, lam) => builder.mkVal(name, NoType, lam) } -// } - - def ValVarDef[_:P] = P( Index ~ BindPattern/*.rep(1, ",".~/)*/ ~ (`:` ~/ Type).? ~ (`=` ~/ FreeCtx.Expr) ).map { + override def ValVarDef[_:P] = P( Index ~ BindPattern ~ (`:` ~/ Type).? ~ (`=` ~/ FreeCtx.Expr) ).map { case (index, Ident(n,_), t, body) => atSrcPos(index) { mkVal(n, t.getOrElse(NoType), body) } - case (index, pat,_,_) => error(s"Only single name patterns supported but was $pat", Some(srcCtx(index))) + case (index, pat,_,_) => + error(s"Only single name patterns supported but was $pat", Some(srcCtx(index))) } - def BlockDef[_:P] = P( Dcl ) - - def Constr[_:P] = P( AnnotType ~~ (NotNewline ~ ParenArgList ).repX ) - def Constrs[_:P] = P( (WL ~ Constr).rep(1, `with`) ) //fix `with`.~/ - def EarlyDefTmpl[_:P] = P( TmplBody ~ (`with` ~/ Constr).rep ~ TmplBody.? ) - def NamedTmpl[_:P] = P( Constrs ~ TmplBody.? ) - - def AnonTmpl[_:P] = P( EarlyDefTmpl | NamedTmpl | TmplBody ).ignore - def DefTmpl[_:P] = P( (`extends` | `<:`) ~ AnonTmpl | TmplBody ) - + override def BlockDef[_:P] = P( Dcl ) - val logged = mutable.Buffer.empty[String] - implicit val logger = Logger(m => this.synchronized { logged.append(m) }) + private val logged = mutable.Buffer.empty[String] + implicit val logger: Logger = Logger(m => this.synchronized { logged.append(m) }) - def mkUnaryOp(opName: String, arg: Value[SType]) = + override def mkUnaryOp(opName: String, arg: Value[SType]) = builder.currentSrcCtx.withValue(arg.sourceContext) { opName match { case "-" if arg.isInstanceOf[Constant[_]] && arg.tpe.isNumType => @@ -87,9 +69,9 @@ object SigmaParser extends Exprs with Types with Core { } } - val parseAsMethods = Set("*", "++", "||", "&&", "+", "^", "<<", ">>", ">>>") + private val parseAsMethods = Set("*", "++", "||", "&&", "+", "^", "<<", ">>", ">>>") - def mkBinaryOp(l: Value[SType], opName: String, r: Value[SType]): Value[SType] = + override def mkBinaryOp(l: Value[SType], opName: String, r: Value[SType]): Value[SType] = builder.currentSrcCtx.withValue(l.sourceContext) { opName match { case "==" => mkEQ(l, r) @@ -119,14 +101,18 @@ object SigmaParser extends Exprs with Types with Core { } } - def parsedType(str: String): Parsed[SType] = parse(str, implicit p => Type ~ End) + private def parsedType(str: String): Parsed[SType] = parse(str, implicit p => Type ~ End) + /** Parse `str` into SType. + * @param str string representation of type in ErgoScript syntax + */ def parseType(str: String): SType = { val res = parsedType(str).get.value res } - def apply(script: String, sigmaBuilder: SigmaBuilder): Parsed[Value[_ <: SType]] = + /** Parse `script` into ErgoTree expression. */ + def apply(script: String): Parsed[Value[_ <: SType]] = currentInput.withValue(script) { parse(script, implicit p => (StatCtx.Expr ~ End)) } diff --git a/interpreter/src/main/scala/sigmastate/lang/SigmaTyper.scala b/sc/src/main/scala/sigmastate/lang/SigmaTyper.scala similarity index 89% rename from interpreter/src/main/scala/sigmastate/lang/SigmaTyper.scala rename to sc/src/main/scala/sigmastate/lang/SigmaTyper.scala index 06928adb28..b8d23051dd 100644 --- a/interpreter/src/main/scala/sigmastate/lang/SigmaTyper.scala +++ b/sc/src/main/scala/sigmastate/lang/SigmaTyper.scala @@ -1,6 +1,5 @@ package sigmastate.lang -import sigmastate.kiama.rewriting.Rewriter._ import org.ergoplatform._ import sigmastate.SCollection._ import sigmastate.Values._ @@ -9,7 +8,7 @@ import SCollection.SBooleanArray import scalan.Nullable import scalan.util.Extensions.Ensuring import sigmastate.lang.Terms._ -import sigmastate.lang.exceptions._ +import sigmastate.exceptions._ import sigmastate.lang.SigmaPredef._ import sigmastate.serialization.OpCodes import sigmastate.utxo._ @@ -616,91 +615,5 @@ class SigmaTyper(val builder: SigmaBuilder, } object SigmaTyper { - - type STypeSubst = Map[STypeVar, SType] - val EmptySubst = Map.empty[STypeVar, SType] - - /** Performs pairwise type unification making sure each type variable is equally - * substituted in all items. */ - def unifyTypeLists(items1: Seq[SType], items2: Seq[SType]): Option[STypeSubst] = { - // unify items pairwise independently - val itemsUni = (items1, items2).zipped.map((t1, t2) => unifyTypes(t1,t2)) - if (itemsUni.forall(_.isDefined)) { - // merge substitutions making sure the same id is equally substituted in all items - val merged = itemsUni.foldLeft(EmptySubst)((acc, subst) => { - var res = acc - for ((id, t) <- subst.get) { - if (res.contains(id) && res(id) != t) return None - res = res + (id -> t) - } - res - }) - Some(merged) - } else - None - } - - private val unifiedWithoutSubst = Some(EmptySubst) - - /** Finds a substitution `subst` of type variables such that unifyTypes(applySubst(t1, subst), t2) shouldBe Some(emptySubst) */ - def unifyTypes(t1: SType, t2: SType): Option[STypeSubst] = (t1, t2) match { - case (_ @ STypeVar(n1), _ @ STypeVar(n2)) => - if (n1 == n2) unifiedWithoutSubst else None - case (id1 @ STypeVar(_), _) => - Some(Map(id1 -> t2)) - case (e1: SCollectionType[_], e2: SCollectionType[_]) => - unifyTypes(e1.elemType, e2.elemType) - case (e1: SCollectionType[_], _: STuple) => - unifyTypes(e1.elemType, SAny) - case (e1: SOption[_], e2: SOption[_]) => - unifyTypes(e1.elemType, e2.elemType) - case (e1: STuple, e2: STuple) if e1.items.length == e2.items.length => - unifyTypeLists(e1.items, e2.items) - case (e1: SFunc, e2: SFunc) if e1.tDom.length == e2.tDom.length => - unifyTypeLists(e1.tDom :+ e1.tRange, e2.tDom :+ e2.tRange) - case (STypeApply(name1, args1), STypeApply(name2, args2)) - if name1 == name2 && args1.length == args2.length => - unifyTypeLists(args1, args2) - case (SBoolean, SSigmaProp) => // it is necessary for implicit conversion in Coll(bool, prop, bool) - unifiedWithoutSubst - case (SPrimType(e1), SPrimType(e2)) if e1 == e2 => - unifiedWithoutSubst - case (SAny, _) => - unifiedWithoutSubst - case _ => None - } - - def applySubst(tpe: SType, subst: STypeSubst): SType = tpe match { - case SFunc(args, res, tparams) => - val remainingVars = tparams.filterNot { p => subst.contains(p.ident) } - SFunc(args.map(applySubst(_, subst)), applySubst(res, subst), remainingVars) - case _ => - val substRule = rule[Any] { - case id: STypeVar if subst.contains(id) => subst(id) - } - rewrite(everywherebu(substRule))(tpe) - } - - def msgType(t1: SType, t2: SType): Option[SType] = unifyTypes(t1, t2) match { - case Some(_) => Some(t1) - case None => unifyTypes(t2, t1).map(_ => t2) - } - - /** Most Specific Generalized (MSG) type of ts. - * Currently just the type of the first element as long as all the elements have the same type. */ - def msgTypeOf(ts: Seq[SType]): Option[SType] = { - if (ts.isEmpty) None - else { - var res: SType = ts.head - for (t <- ts.iterator.drop(1)) { - msgType(t, res) match { - case Some(msg) => res = msg //assign new - case None => return None - } - } - Some(res) - } - } - def error(msg: String, srcCtx: Nullable[SourceContext]) = throw new TyperException(msg, srcCtx.toOption) } diff --git a/interpreter/src/main/scala/sigmastate/lang/Types.scala b/sc/src/main/scala/sigmastate/lang/Types.scala similarity index 71% rename from interpreter/src/main/scala/sigmastate/lang/Types.scala rename to sc/src/main/scala/sigmastate/lang/Types.scala index 31529c38a2..8a45d4d66b 100644 --- a/interpreter/src/main/scala/sigmastate/lang/Types.scala +++ b/sc/src/main/scala/sigmastate/lang/Types.scala @@ -10,30 +10,50 @@ import sigmastate.lang.syntax.Core import syntax.Basic.error //noinspection ForwardReference +/** Parsers of type terms. Can produce values of SType. */ trait Types extends Core { + /** Parser of typed expressions. + * @return expression of ErgoTree IR + */ def TypeExpr[_:P]: P[Value[SType]] + + /** Parser of `name = expr` syntax. + * @return an instance of ValNode + */ def ValVarDef[_:P]: P[Value[SType]] -// def FunDef: P[Value[SType]] - def Dcl[_:P] = { - P( `val` ~/ ValVarDef /*| /* `fun` ~/ */ FunDef */ ) + /** Parser of `val name = expr` syntax. + * @return an instance of ValNode + */ + def Dcl[_:P]: P[Value[SType]] = { + P( `val` ~/ ValVarDef ) } - /** This map should be in sync with SType.allPredefTypes*/ + /** This map should be in sync with SType.allPredefTypes */ val predefTypes = Map( - "Boolean" -> SBoolean, "Byte" -> SByte, "Short" -> SShort, "Int" -> SInt,"Long" -> SLong, "BigInt" -> SBigInt, - "ByteArray" -> SByteArray, - "AvlTree" -> SAvlTree, "Context" -> SContext, "GroupElement" -> SGroupElement, "SigmaProp" -> SSigmaProp, - "SigmaDslBuilder" -> SGlobal, + "Boolean" -> SBoolean, + "Byte" -> SByte, + "Short" -> SShort, + "Int" -> SInt, + "Long" -> SLong, + "BigInt" -> SBigInt, + "AvlTree" -> SAvlTree, + "Context" -> SContext, + "GroupElement" -> SGroupElement, + "SigmaProp" -> SSigmaProp, + "Global" -> SGlobal, "Header" -> SHeader, "PreHeader" -> SPreHeader, "String" -> SString, - "Box" -> SBox, "Unit" -> SUnit, "Any" -> SAny + "Box" -> SBox, + "Unit" -> SUnit, + "Any" -> SAny ) - def typeFromName(tn: String): Option[SType] = predefTypes.get(tn) + /** Lookup pre-defined type by name. */ + private def typeFromName(tn: String): Option[SType] = predefTypes.get(tn) - def PostfixType[_:P] = P( InfixType ~ (`=>` ~/ Type ).? ).map { + def PostfixType[_:P]: P[SType] = P( InfixType ~ (`=>` ~/ Type ).? ).map { case (t, None) => t case (d, Some(r)) => d match { case STuple(items) => @@ -49,7 +69,7 @@ trait Types extends Core { // we may need to backtrack and settle for the `*`-postfix rather than // an infix type // See http://www.scala-lang.org/files/archive/spec/2.12/03-types.html - def InfixType[_:P] = { + def InfixType[_:P]: P[SType] = { val RightAssoc = 1; val LeftAssoc = -1 /** All operators op1,…,opn must have the same associativity */ def checkAssoc(ops: Seq[String], index: Int): Int = { @@ -76,15 +96,15 @@ trait Types extends Core { } } - def CompoundType[_:P] = { -// val Refinement[_:P] = P( OneNLMax ~ `{` ~/ Dcl.repX(sep=Semis) ~ `}` ) + def CompoundType[_:P]: P[SType] = { def NamedType = P( (Pass ~ AnnotType).rep(1, `with`./) ) - P( Index ~ NamedType /*~~ Refinement.? | Refinement*/ ).map { + P( Index ~ NamedType ).map { case (_, Seq(t)) => t case (index, ts) => error(s"Compound types are not supported: $ts", Some(srcCtx(index))) } } - def NLAnnot[_:P] = P( NotNewline ~ Annot ) + + private def NLAnnot[_:P] = P( NotNewline ~ Annot ) def AnnotType[_:P] = P(SimpleType ~~ NLAnnot.repX ) def TypeId[_:P] = P( StableId ).map { @@ -114,6 +134,7 @@ trait Types extends Core { } } + /** Parses [T1,T2](a1: T, a2: S) */ def FunSig[_:P] = { def FunArg = P( Annot.rep ~ Id.! ~ (`:` ~/ Type).? ).map { case (n, Some(t)) => (n, t) @@ -125,22 +146,26 @@ trait Types extends Core { P( FunTypeArgs.? ~~ FunArgs.rep ) } + // TODO refactor: extensions syntax is not fully implemented, so this probably can be removed // extension method subject (type that being extended) // see dotty extension method http://dotty.epfl.ch/blog/2019/01/21/12th-dotty-milestone-release.html def DottyExtMethodSubj[_:P] = P( "(" ~/ Id.! ~ `:` ~/ Type ~ ")" ) - def TypeBounds[_:P]: P0 = P( (`>:` ~/ Type).? ~ (`<:` ~/ Type).? ).ignore - def TypeArg[_:P]: P0 = { + private def TypeBounds[_:P]: P0 = P( (`>:` ~/ Type).? ~ (`<:` ~/ Type).? ).ignore + private def TypeArg[_:P]: P0 = { def CtxBounds = P((`:` ~/ Type).rep) P((Id | `_`) ~ TypeArgList.? ~ TypeBounds ~ CtxBounds).ignore } + /** Annotation with optional arguments, result is ignored. */ def Annot[_:P]: P0 = P( `@` ~/ SimpleType ~ ("(" ~/ (Exprs ~ (`:` ~/ `_*`).?).? ~ TrailingComma ~ ")").rep ).ignore - def TypeArgVariant[_:P]: P0 = P( Annot.rep ~ ("+" | "-").? ~ TypeArg ) + private def TypeArgVariant[_:P]: P0 = P( Annot.rep ~ ("+" | "-").? ~ TypeArg ) - def TypeArgList[_:P]: P0 = { + private def TypeArgList[_:P]: P0 = { P( "[" ~/ TypeArgVariant.rep(1, ",") ~ TrailingComma ~ "]" ) // fix } - def Exprs[_:P] = P( TypeExpr.rep(1, ",") ) + + /** Sequence of comma separated expressions. */ + def Exprs[_:P]: P[Seq[Value[SType]]] = P( TypeExpr.rep(1, ",") ) } diff --git a/interpreter/src/main/scala/sigmastate/lang/syntax/Basic.scala b/sc/src/main/scala/sigmastate/lang/syntax/Basic.scala similarity index 73% rename from interpreter/src/main/scala/sigmastate/lang/syntax/Basic.scala rename to sc/src/main/scala/sigmastate/lang/syntax/Basic.scala index 7cf4a3b055..e9e91c8197 100644 --- a/interpreter/src/main/scala/sigmastate/lang/syntax/Basic.scala +++ b/sc/src/main/scala/sigmastate/lang/syntax/Basic.scala @@ -1,47 +1,69 @@ package sigmastate.lang.syntax -import fastparse._; import NoWhitespace._ +import fastparse._ +import NoWhitespace._ import fastparse.CharPredicates._ import scalan.Nullable import sigmastate.lang.SourceContext -import sigmastate.lang.exceptions.SigmaException +import sigmastate.exceptions.CompilerException +/** Basic lexical parsers for ErgoScript. */ object Basic { val digits = "0123456789" - def Digit[_:P]: P[Unit] = P( CharPred(digits.contains(_)) ) val hexDigits: String = digits + "abcdefABCDEF" + + /** Matches a decimal digit. */ + def Digit[_:P]: P[Unit] = P( CharPred(digits.contains(_)) ) + /** Matches a hexadecimal digit. */ def HexDigit[_:P]: P[Unit] = P( CharPred(hexDigits.contains(_)) ) + def UnicodeEscape[_:P]: P[Unit] = P( "u" ~ HexDigit ~ HexDigit ~ HexDigit ~ HexDigit ) //Numbers and digits + /** Matches a positive hexadecimal number. */ def HexNum[_:P]: P[Unit] = P( "0x" ~ CharsWhile(hexDigits.contains(_), 1) ) + /** Matches a positive decimal number. */ def DecNum[_:P]: P[Unit] = P( CharsWhile(digits.contains(_), 1) ) + /** Matches a exponent part of floating point number. */ def Exp[_:P]: P[Unit] = P( CharPred("Ee".contains(_)) ~ CharPred("+-".contains(_)).? ~ DecNum ) + def FloatType[_:P]: P[Unit] = P( CharIn("fFdD") ) + /** Matches a sequience of whitespace character. */ def WSChars[_:P]: P[Unit] = P( CharsWhileIn("\u0020\u0009") ) def Newline[_:P]: P[Unit] = P( StringIn("\r\n", "\n") ) def Semi[_:P]: P[Unit] = P( ";" | Newline.rep(1) ) + + /** Matches a single operation character. */ def OpChar[_:P]: P[Unit] = P ( CharPred(isOpChar) ) + /** Characters allowed in as operations. */ def isOpChar(c: Char): Boolean = c match{ case '!' | '#' | '%' | '&' | '*' | '+' | '-' | '/' | ':' | '<' | '=' | '>' | '?' | '@' | '\\' | '^' | '|' | '~' => true case _ => isOtherSymbol(c) || isMathSymbol(c) } - def Letter[_:P]: P[Unit] = P( CharPred(c => isLetter(c) | isDigit(c) | c == '$' | c == '_' ) ) + + /** Character allowed in identificators. */ def LetterDigitDollarUnderscore[_:P]: P[Unit] = P( CharPred(c => isLetter(c) | isDigit(c) | c == '$' | c == '_' ) ) + /** Lower-case letters. */ def Lower[_:P]: P[Unit] = P( CharPred(c => isLower(c) || c == '$' | c == '_') ) + /** Upper-case letters. */ def Upper[_:P]: P[Unit] = P( CharPred(isUpper) ) def error(msg: String, srcCtx: Option[SourceContext]) = throw new ParserException(msg, srcCtx) def error(msg: String, srcCtx: Nullable[SourceContext]) = throw new ParserException(msg, srcCtx.toOption) } +/** Exception thrown during the parsing phase of the compiler. + * + * @param message the error message + * @param source an optional source context with location information + */ class ParserException(message: String, source: Option[SourceContext]) - extends SigmaException(message, source) + extends CompilerException(message, source) /** * Most keywords don't just require the correct characters to match, diff --git a/interpreter/src/main/scala/sigmastate/lang/syntax/Core.scala b/sc/src/main/scala/sigmastate/lang/syntax/Core.scala similarity index 56% rename from interpreter/src/main/scala/sigmastate/lang/syntax/Core.scala rename to sc/src/main/scala/sigmastate/lang/syntax/Core.scala index a598fdc8cd..2a6c407693 100644 --- a/interpreter/src/main/scala/sigmastate/lang/syntax/Core.scala +++ b/sc/src/main/scala/sigmastate/lang/syntax/Core.scala @@ -5,11 +5,14 @@ import sigmastate._ import sigmastate.Values._ import sigmastate.lang.syntax +/** Keywords and identifiers used in expressions. */ trait Core extends syntax.Literals { import fastparse._ import ScalaWhitespace._ + /** Constructor of ErgoTree unary operation. */ def mkUnaryOp(opName: String, arg: Value[SType]): Value[SType] + /** Constructor of ErgoTree binary operation. */ def mkBinaryOp(l: Value[SType], opName: String, r: Value[SType]): Value[SType] // Aliases for common things. These things are used in almost every parser @@ -19,7 +22,6 @@ trait Core extends syntax.Literals { // Keywords that match themselves and nothing else def `=>`[_:P] = O("=>") | O("⇒") -// val `<-`[_:P] = O("<-") | O("←") def `:`[_:P] = O(":") def `=`[_:P] = O("=") def `@`[_:P] = O("@") @@ -28,46 +30,18 @@ trait Core extends syntax.Literals { def `val`[_:P] = W("val") def `def`[_:P] = W("def") def `case`[_:P] = W("case") - def `then`[_:P] = W("then") def `else`[_:P] = W("else") - def `#`[_:P] = O("#") - def `return`[_:P] = W("return") def `if`[_:P] = W("if") def `match`[_:P] = W("match") def `this`[_:P] = W("this") def `super`[_:P] = W("super") - // val `var`[_:P] = W("var") - // val `def`[_:P] = W("def") - def `with`[_:P] = W("with") - // val `package`[_:P] = W("package") - // val `object`[_:P] = W("object") - // val `class`[_:P] = W("class") - // val `trait`[_:P] = W("trait") - def `extends`[_:P] = W("extends") - def `implicit`[_:P] = W("implicit") - // val `try`[_:P] = W("try") - def `new`[_:P] = W("new") - // val `macro`[_:P] = W("macro") - // val `import`[_:P] = W("import") -// val `catch`[_:P] = W("catch") -// val `finally`[_:P] = W("finally") -// val `do`[_:P] = W("do") -// val `yield`[_:P] = W("yield") -// val `while`[_:P] = W("while") -// val `<%`[_:P] = O("<%") -// val `override`[_:P] = W("override") -// val `forSome`[_:P] = W("forSome") -// val `for`[_:P] = W("for") -// val `abstract`[_:P] = W("abstract") -// val `throw`[_:P] = W("throw") + def `with`[_:P] = W("with") + def `extends`[_:P] = W("extends") + def `implicit`[_:P] = W("implicit") + def `new`[_:P] = W("new") def `lazy`[_:P] = W("lazy") def `>:`[_:P] = O(">:") def `<:`[_:P] = O("<:") -// val `final` = W("final") -// val `sealed`[_:P] = W("sealed") -// val `private`[_:P] = W("private") -// val `protected`[_:P] = W("protected") - // kinda-sorta keywords that are common patterns even if not // really-truly keywords @@ -80,10 +54,6 @@ trait Core extends syntax.Literals { def VarId[_:P] = P( WL ~ Identifiers.VarId ) def BacktickId[_:P] = P( WL ~ Identifiers.BacktickId ) def ExprLiteral[_:P] = P( WL ~ Literals.Expr.Literal ) - def PatLiteral[_:P] = P( WL ~ Literals.Pat.Literal ) - - def QualId[_:P] = P( WL ~ Id.rep(1, sep = ".") ) - def Ids[_:P] = P( Id.rep(1, sep = ",") ) /** * Sketchy way to whitelist a few suffixes that come after a . select; @@ -91,14 +61,11 @@ trait Core extends syntax.Literals { */ def PostDotCheck[_:P]: P0 = P( WL ~ !(`super` | `this` | "{" | `_` | `type`) ) def StableId[_:P] = { -// val ClassQualifier[_:P] = P( "[" ~ Id ~ "]" ) -// val ThisSuper[_:P] = P( `this` | `super` ~ ClassQualifier.? ) -// val ThisPath: P0[_:P] = P( ThisSuper ~ ("." ~ PostDotCheck ~/ Id).rep ) - def IdPath = P( Index ~ Id.! ~ ("." ~ PostDotCheck ~/ Index ~ (`this`.! | Id.!)).rep /*~ ("." ~ ThisPath).?*/ ).map { + def IdPath = P( Index ~ Id.! ~ ("." ~ PostDotCheck ~/ Index ~ (`this`.! | Id.!)).rep ).map { case (hi, hs, t) => t.foldLeft[SValue](atSrcPos(hi){builder.mkIdent(hs, NoType)}){ case (obj, (i, s)) => atSrcPos(i) { builder.mkSelect(obj, s) } } } - P( /*ThisPath |*/ IdPath ) + P( IdPath ) } } diff --git a/interpreter/src/main/scala/sigmastate/lang/syntax/Exprs.scala b/sc/src/main/scala/sigmastate/lang/syntax/Exprs.scala similarity index 62% rename from interpreter/src/main/scala/sigmastate/lang/syntax/Exprs.scala rename to sc/src/main/scala/sigmastate/lang/syntax/Exprs.scala index 4444f29485..f2b8c68b12 100644 --- a/interpreter/src/main/scala/sigmastate/lang/syntax/Exprs.scala +++ b/sc/src/main/scala/sigmastate/lang/syntax/Exprs.scala @@ -13,10 +13,11 @@ import scala.annotation.tailrec import scala.collection.compat.immutable.ArraySeq //noinspection ForwardReference,TypeAnnotation +/** Parsers of ErgoScript expressions. */ trait Exprs extends Core with Types { import builder._ - def AnonTmpl[_:P]: P0 + /** Parses a definition in a block `val name = expr` */ def BlockDef[_:P]: P[Value[SType]] // Depending on where an expression is located, subtle behavior around @@ -30,94 +31,98 @@ trait Exprs extends Core with Types { // Expressions directly within a `val x = ...` or `def x = ...` object FreeCtx extends WsCtx(semiInference=true, arrowTypeAscriptions=true) - def TypeExpr[_:P] = ExprCtx.Expr + override def TypeExpr[_:P]: P[Value[SType]] = ExprCtx.Expr private val predefFuncRegistry = new PredefinedFuncRegistry(builder) import predefFuncRegistry._ //noinspection TypeAnnotation,ForwardReference - class WsCtx(semiInference: Boolean, arrowTypeAscriptions: Boolean){ + /** Parsing context of expressions (see derived classes). */ + class WsCtx(semiInference: Boolean, arrowTypeAscriptions: Boolean) { - def OneSemiMax[_:P] = if (semiInference) OneNLMax else Pass - def NoSemis[_:P] = if (semiInference) NotNewline else Pass + private def OneSemiMax[_:P]: P[Unit] = if (semiInference) OneNLMax else Pass + private def NoSemis[_:P]: P[Unit] = if (semiInference) NotNewline else Pass + /** Parses ErgoScript expressions. See nested methods for subexpressions. */ def Expr[_:P]: P[Value[SType]] = { - def If = { - def Else = P( Semi.? ~ `else` ~/ Expr ) + def If: P[Value[SType]] = { + def Else: P[Value[SType]] = P( Semi.? ~ `else` ~/ Expr ) P( Index ~ `if` ~/ "(" ~ ExprCtx.Expr ~ ")" ~ Expr ~ Else ).map { case (i, c, t, e) => atSrcPos(i) { mkIf(c.asValue[SBoolean.type], t, e) } } } - def Fun = P(`def` ~ FunDef) - - def LambdaRhs = if (semiInference) P( (Index ~ BlockChunk).map { - case (index, (_ , b)) => atSrcPos(index) { block(b) } - } ) - else P( Expr ) -// val ParenedLambda = P( Parened ~~ (WL ~ `=>` ~ LambdaRhs.? /*| ExprSuffix ~~ PostfixSuffix ~ SuperPostfixSuffix*/) ).map { -// case (args, None) => mkLambda(args, UnitConstant) -// case (args, Some(body)) => mkLambda(args, body) -// } - def PostfixLambda = P( Index ~ PostfixExpr ~ (`=>` ~ LambdaRhs.? | SuperPostfixSuffix).? ).map { + + /** Note, `def` declarations parsed to ValNode with function type. */ + def Fun: P[Val] = P(`def` ~ FunDef) + + def LambdaRhs: P[Value[SType]] = + if (semiInference) + P( (Index ~ BlockChunk).map { + case (index, (_ , b)) => atSrcPos(index) { block(b) } + } ) + else + P( Expr ) + + def PostfixLambda: P[Value[SType]] = P( Index ~ PostfixExpr ~ (`=>` ~ LambdaRhs.? | SuperPostfixSuffix).? ).map { case (_, e, None) => e case (_, e, Some(None)) => e case (i, Tuple(args), Some(Some(body))) => atSrcPos(i) { lambda(args, body) } case (i, e, Some(body)) => error(s"Invalid declaration of lambda $e => $body", Some(srcCtx(i))) } - def SmallerExprOrLambda = P( /*ParenedLambda |*/ PostfixLambda ) -// val Arg = (Id.! ~ `:` ~/ Type).map { case (n, t) => mkIdent(IndexedSeq(n), t)} + + def SmallerExprOrLambda = P( PostfixLambda ) + P( If | Fun | SmallerExprOrLambda ) } - def SuperPostfixSuffix[_:P] = P( (`=` ~/ Expr).? /*~ MatchAscriptionSuffix.?*/ ) - def AscriptionType[_:P] = (if (arrowTypeAscriptions) P( Type ) else P( InfixType )).ignore - def Ascription[_:P] = P( `:` ~/ (`_*` | AscriptionType | Annot.rep(1)) ) - def MatchAscriptionSuffix[_:P] = P(`match` ~/ "{" ~ CaseClauses | Ascription) - def ExprPrefix[_:P] = P( WL ~ CharPred("-+!~".contains(_)).! ~~ !syntax.Basic.OpChar ~ WS) - def ExprSuffix[_:P] = P( + private def SuperPostfixSuffix[_:P] = P( (`=` ~/ Expr).? ) + private def ExprPrefix[_:P] = P( WL ~ CharPred("-+!~".contains(_)).! ~~ !syntax.Basic.OpChar ~ WS) + private def ExprSuffix[_:P] = P( (WL ~ "." ~/ (Index ~ Id.!).map{ case (i, s) => atSrcPos(i) { mkIdent(s, NoType)} } | WL ~ TypeArgs.map(items => STypeApply("", items.toIndexedSeq)) - | NoSemis ~ ArgList ).repX /* ~~ (NoSemis ~ `_`).? */ + | NoSemis ~ ArgList ).repX ) - def PrefixExpr[_:P] = P( ExprPrefix.? ~ SimpleExpr ).map { + private def PrefixExpr[_:P] = P( ExprPrefix.? ~ SimpleExpr ).map { case (Some(op), e) => mkUnaryOp(op, e) case (None, e) => e } // Intermediate `WL` needs to always be non-cutting, because you need to // backtrack out of `InfixSuffix` into `PostFixSuffix` if it doesn't work out - def InfixSuffix[_:P] = P( NoSemis ~~ WL ~~ Id.! /*~ TypeArgs.?*/ ~~ OneSemiMax ~ PrefixExpr ~~ ExprSuffix).map { - case (op, f, args) => - val rhs = applySuffix(f, args) - (op, rhs) - } - def PostFix[_:P] = P( NoSemis ~~ WL ~~ (Index ~ Id.!) ~ Newline.? ) - .map{ case (i, s) => + private def InfixSuffix[_:P]: P[(String, Value[SType])] = + P( NoSemis ~~ WL ~~ Id.! ~~ OneSemiMax ~ PrefixExpr ~~ ExprSuffix).map { + case (op, f, args) => + val rhs = applySuffix(f, args) + (op, rhs) + } + + private def PostFix[_:P]: P[Value[SType]] = + P( NoSemis ~~ WL ~~ (Index ~ Id.!) ~ Newline.? ).map { case (i, s) => atSrcPos(i) { mkIdent(s, NoType)} } - def PostfixSuffix[_:P] = P( InfixSuffix.repX ~~ PostFix.?) - - def PostfixExpr[_:P] = P( PrefixExpr ~~ ExprSuffix ~~ PostfixSuffix ).map { - case (prefix, suffix, (infixOps, postfix)) => - val lhs = applySuffix(prefix, suffix) - val obj = mkInfixTree(lhs, infixOps) - postfix.fold(obj) { - case Ident(name, _) => - builder.currentSrcCtx.withValue(obj.sourceContext) { - mkMethodCallLike(obj, name, IndexedSeq.empty) - } - } - } + private def PostfixSuffix[_:P] = P( InfixSuffix.repX ~~ PostFix.?) + + private def PostfixExpr[_:P]: P[SValue] = + P( PrefixExpr ~~ ExprSuffix ~~ PostfixSuffix ).map { + case (prefix, suffix, (infixOps, postfix)) => + val lhs = applySuffix(prefix, suffix) + val obj = mkInfixTree(lhs, infixOps) + postfix.fold(obj) { + case Ident(name, _) => + builder.currentSrcCtx.withValue(obj.sourceContext) { + mkMethodCallLike(obj, name, IndexedSeq.empty) + } + } + } - def Parened[_:P] = P ( "(" ~/ TypeExpr.rep(0, ",") ~ TrailingComma ~ ")" ) - def SimpleExpr[_:P] = { -// val New = P( `new` ~/ AnonTmpl ) + private def Parened[_:P] = P ( "(" ~/ TypeExpr.rep(0, ",") ~ TrailingComma ~ ")" ) + private def SimpleExpr[_:P] = { - P( /*New | */ BlockExpr + P( BlockExpr | ExprLiteral - | StableId //.map { case Ident(ps, t) => Ident(ps, t) } + | StableId | (Index ~ `_`.!).map { case (i, lit) => atSrcPos(i) { mkIdent(lit, NoType) } } | (Index ~ Parened).map { case (index, Seq()) => atSrcPos(index) { mkUnitConstant } @@ -126,9 +131,9 @@ trait Exprs extends Core with Types { } ) } - def Guard[_:P]: P0 = P( `if` ~/ PostfixExpr ).ignore } + /** Constructor of lambda nodes */ protected def lambda(args: Seq[Value[SType]], body: Value[SType]): Value[SType] = { val names = args.map { case Ident(n, t) => (n, t) } mkLambda(names.toIndexedSeq, NoType, Some(body)) @@ -137,7 +142,7 @@ trait Exprs extends Core with Types { /** The precedence of an infix operator is determined by the operator's first character. * Characters are listed below in increasing order of precedence, with characters on the same line * having the same precedence. */ - val priorityList = Seq( + private val priorityList = Seq( // all letters have lowerst precedence 0 Seq('|'), Seq('^'), @@ -149,14 +154,17 @@ trait Exprs extends Core with Types { Seq('*', '/', '%') ) - val priorityMap = (for { + private val priorityMap: Map[Char, Int] = (for { (xs, p) <- priorityList.zipWithIndex.map { case (xs, i) => (xs, i + 1) } x <- xs } yield (x, p)).toMap - @inline def precedenceOf(ch: Char): Int = if (priorityMap.contains(ch)) priorityMap(ch) else 0 - @inline def precedenceOf(op: String): Int = precedenceOf(op(0)) + @inline private def precedenceOf(ch: Char): Int = if (priorityMap.contains(ch)) priorityMap(ch) else 0 + @inline private def precedenceOf(op: String): Int = precedenceOf(op(0)) + /** Build expression tree from a list of operations respecting precedence. + * Example: a + b * c => a + (b * c) + */ protected[lang] def mkInfixTree(lhs: SValue, rhss: Seq[(String, SValue)]): SValue = { @tailrec def build(wait: List[(SValue, String)], x: SValue, rest: List[(String, SValue)]): SValue = (wait, rest) match { case ((l, op1) :: stack, (op2, r) :: tail) => @@ -181,9 +189,7 @@ trait Exprs extends Core with Types { build(Nil, lhs, rhss.toList) } - - - protected def applySuffix(f: Value[SType], args: Seq[SigmaNode]): Value[SType] = { + private def applySuffix(f: Value[SType], args: Seq[SigmaNode]): Value[SType] = { builder.currentSrcCtx.withValue(f.sourceContext) { val rhs = args.foldLeft(f)((acc, arg) => arg match { case Ident(name, _) => mkSelect(acc, name) @@ -205,8 +211,10 @@ trait Exprs extends Core with Types { } } - def FunDef[_:P] = { + /** Parses `name[T1, ..., Tn](a1, ..., aM): R = expr` */ + def FunDef[_:P]: P[Val] = { def Body = P( WL ~ `=` ~/ FreeCtx.Expr ) + P(Index ~ DottyExtMethodSubj.? ~ Id.! ~ FunSig ~ (`:` ~/ Type).? ~~ Body ).map { case (index, None, n, args, resType, body) => atSrcPos(index) { @@ -224,17 +232,17 @@ trait Exprs extends Core with Types { } } - def SimplePattern[_:P] = { + private def SimplePattern[_:P] = { def TupleEx = P( "(" ~/ Pattern.rep(0, ",") ~ TrailingComma ~ ")" ) - def Extractor = P( StableId /* ~ TypeArgs.?*/ ~ TupleEx.? ) -// val Thingy = P( `_` ~ (`:` ~/ TypePat).? ~ !("*" ~~ !syntax.Basic.OpChar) ) - P( /*Thingy | PatLiteral |*/ TupleEx | Extractor + def Extractor = P( StableId ~ TupleEx.? ) + P( TupleEx + | Extractor | (Index ~ VarId.!).map { case (i, lit) => atSrcPos(i) { mkIdent(lit, NoType) } }) } - def BlockExpr[_:P] = P( "{" ~/ (/*CaseClauses |*/ Block ~ "}") ) + private def BlockExpr[_:P] = P( "{" ~/ ( Block ~ "}" ) ) - def BlockLambdaHead[_:P] = { + private def BlockLambdaHead[_:P] = { def Arg = P( Annot.rep ~ Id.! ~ (`:` ~/ Type).? ).map { case (n, Some(t)) => (n, t) case (n, None) => (n, NoType) @@ -243,15 +251,16 @@ trait Exprs extends Core with Types { P( OneNLMax ~ "(" ~ Args.? ~ ")" ).map(_.toSeq.flatten) } + def BlockLambda[_:P] = P( BlockLambdaHead ~ `=>` ) - def BlockChunk[_:P] = { + private def BlockChunk[_:P] = { def Prelude = P( Annot.rep ~ `lazy`.? ) def BlockStat = P( Prelude ~ BlockDef | StatCtx.Expr ) P( BlockLambda.rep ~ BlockStat.rep(sep = Semis) ) } - def extractBlockStats(stats: Seq[SValue]): (Seq[Val], SValue) = { + private def extractBlockStats(stats: Seq[SValue]): (Seq[Val], SValue) = { if (stats.nonEmpty) { val lets = stats.iterator.take(stats.size - 1).map { case l: Val => l @@ -269,7 +278,7 @@ trait Exprs extends Core with Types { mkBlock(lets, body) } - def BaseBlock[_:P](end: P0)(implicit name: sourcecode.Name): P[Value[SType]] = { + private def BaseBlock[_:P](end: P0)(implicit name: sourcecode.Name): P[Value[SType]] = { def BlockEnd = P( Semis.? ~ &(end) ) def Body = P( BlockChunk.repX(sep = Semis) ) P( Index ~ Semis.? ~ BlockLambda.? ~ Body ~/ BlockEnd ).map { @@ -290,30 +299,24 @@ trait Exprs extends Core with Types { } } } - def Block[_:P] = BaseBlock("}") - def CaseBlock[_:P] = BaseBlock("}" | `case`) - - def Patterns[_:P]: P0 = P( Pattern.rep(1, sep = ","./) ) - def Pattern[_:P]: P0 = P( (WL ~ TypeOrBindPattern).rep(1, sep = "|"./) ) - def TypePattern[_:P] = P( (`_` | BacktickId | VarId) ~ `:` ~ TypePat ) - def TypeOrBindPattern[_:P]: P0 = P( TypePattern | BindPattern ).ignore - def BindPattern[_:P] = { - def InfixPattern = P( SimplePattern /*~ (Id ~/ SimplePattern).rep | `_*`*/ ) -// val Binding = P( (Id | `_`) ~ `@` ) - P( /*Binding ~ InfixPattern | */ InfixPattern /*| VarId*/ ) - } - def TypePat[_:P] = P( CompoundType ) - def ParenArgList[_:P] = P( "(" ~/ Index ~ Exprs /*~ (`:` ~/ `_*`).?*/.? ~ TrailingComma ~ ")" ).map { - case (index, Some(exprs)) => atSrcPos(index) { mkTuple(exprs) } - case (index, None) => atSrcPos(index) { mkUnitConstant } - } - def ArgList[_:P] = P( ParenArgList | OneNLMax ~ BlockExpr ) + override def Block[_:P] = BaseBlock("}") + + override def Pattern[_:P]: P0 = P( (WL ~ TypeOrBindPattern).rep(1, sep = "|"./) ) + + private def TypePattern[_:P] = P( (`_` | BacktickId | VarId) ~ `:` ~ TypePat ) + private def TypeOrBindPattern[_:P]: P0 = P( TypePattern | BindPattern ).ignore - def CaseClauses[_:P]: P0 = { - // Need to lookahead for `class` and `object` because - // the block { case object X } is not a case clause! - val CaseClause: P0 = P( `case` ~/ Pattern ~ ExprCtx.Guard.? ~ `=>` ~ CaseBlock ).ignore - P( CaseClause.rep(1) ~ "}" ) + def BindPattern[_:P]: P[Any] = { + def InfixPattern = P( SimplePattern ) + P( InfixPattern ) } + + private def TypePat[_:P]: P[SType] = P( CompoundType ) + def ParenArgList[_:P]: P[Value[SType]] = + P( "(" ~/ Index ~ Exprs.? ~ TrailingComma ~ ")" ).map { + case (index, Some(exprs)) => atSrcPos(index) { mkTuple(exprs) } + case (index, None) => atSrcPos(index) { mkUnitConstant } + } + private def ArgList[_:P] = P( ParenArgList | OneNLMax ~ BlockExpr ) } diff --git a/interpreter/src/main/scala/sigmastate/lang/syntax/Identifiers.scala b/sc/src/main/scala/sigmastate/lang/syntax/Identifiers.scala similarity index 57% rename from interpreter/src/main/scala/sigmastate/lang/syntax/Identifiers.scala rename to sc/src/main/scala/sigmastate/lang/syntax/Identifiers.scala index a06af44c3d..65acacc765 100644 --- a/interpreter/src/main/scala/sigmastate/lang/syntax/Identifiers.scala +++ b/sc/src/main/scala/sigmastate/lang/syntax/Identifiers.scala @@ -6,24 +6,29 @@ import NoWhitespace._ import sigmastate.lang.syntax.Basic._ //noinspection ForwardReference +/** Identifiers and keywords */ object Identifiers { - case class NamedFunction(f: Char => Boolean) + /** Helper wrapper to capture the name of the use site. */ + private case class NamedFunction(f: Char => Boolean) (implicit name: sourcecode.Name) extends (Char => Boolean){ - def apply(t: Char) = f(t) + def apply(t: Char): Boolean = f(t) override def toString: String = name.value } - val OpCharNotSlash = NamedFunction(x => isOpChar(x) && x != '/') - val NotBackTick = NamedFunction(_ != '`') - def Operator[_:P]: P[Unit] = P( + private val OpCharNotSlash = NamedFunction(x => isOpChar(x) && x != '/') + + private val NotBackTick = NamedFunction(_ != '`') + + private def Operator[_:P]: P[Unit] = P( !Keywords ~ (!("/*" | "//") ~ (CharsWhile(OpCharNotSlash) | "/")).rep(1) ) def VarId[_:P]: P[Unit] = VarId0(true) - def VarId0[_:P](dollar: Boolean): P[Unit] = P( !Keywords ~ Lower ~ IdRest(dollar) ) + private def VarId0[_:P](dollar: Boolean): P[Unit] = P( !Keywords ~ Lower ~ IdRest(dollar) ) + + private def UppercaseId[_: P](dollar: Boolean): P[Unit] = P( !Keywords ~ Upper ~ IdRest(dollar) ) - def UppercaseId[_: P](dollar: Boolean) = P( !Keywords ~ Upper ~ IdRest(dollar) ) def PlainId[_:P]: P[Unit] = P( UppercaseId(true) | VarId | Operator ~ (!OpChar | &("/*" | "//")) ) .opaque("plain-id") @@ -31,33 +36,25 @@ object Identifiers { def BacktickId[_:P]: P[Unit] = P( "`" ~ CharsWhile(NotBackTick) ~ "`" ) def Id[_:P]: P0 = P( BacktickId | PlainId ) - def IdRest[_:P](allowDollar: Boolean): P[Unit] = { + private def IdRest[_:P](allowDollar: Boolean): P[Unit] = { - def IdCharacter = + def IdCharacter: NamedFunction = if(allowDollar) NamedFunction(c => c == '$' || isLetter(c) || isDigit(c)) else NamedFunction(c => isLetter(c) || isDigit(c)) - def IdUnderscoreChunk = P( CharsWhileIn("_", 0) ~ CharsWhile(IdCharacter) ) + def IdUnderscoreChunk: P[Unit] = P( CharsWhileIn("_", 0) ~ CharsWhile(IdCharacter) ) + P( IdUnderscoreChunk.rep ~ (CharsWhileIn("_") ~ CharsWhile(isOpChar, 0)).? ) } - final val alphaKeywords = Seq( - "case", "else", "false", "function", "if", "match", "return", "then", "true" - ) - def AlphabetKeywords[_:P]: P[Unit] = P { + private def AlphabetKeywords[_:P]: P[Unit] = P { StringIn("case", "else", "false", "function", "if", "match", "return", "then", "true") ~ -// ("case" | "else" | "false" | "function" | "if" | "match" | "return" | "then" | "true") ~ !Basic.LetterDigitDollarUnderscore } - val symbolKeywords = Seq( - ":", ";", "=>", "=", "#", "@" - ) - def SymbolicKeywords[_:P]: P[Unit] = P{ + private def SymbolicKeywords[_:P]: P[Unit] = P{ (":" | ";" | "=>" | "=" | "#" | "@") ~ !OpChar } -// val keywords: Seq[String] = alphaKeywords ++ symbolKeywords - def Keywords[_:P]: P[Unit] = P( AlphabetKeywords | SymbolicKeywords ) } diff --git a/interpreter/src/main/scala/sigmastate/lang/syntax/Literals.scala b/sc/src/main/scala/sigmastate/lang/syntax/Literals.scala similarity index 82% rename from interpreter/src/main/scala/sigmastate/lang/syntax/Literals.scala rename to sc/src/main/scala/sigmastate/lang/syntax/Literals.scala index 9fd541c8f4..007e4057b5 100644 --- a/interpreter/src/main/scala/sigmastate/lang/syntax/Literals.scala +++ b/sc/src/main/scala/sigmastate/lang/syntax/Literals.scala @@ -9,16 +9,26 @@ import java.lang.Integer.parseInt import sigmastate.lang.{SigmaBuilder, SourceContext, StdSigmaBuilder} +/** Parsers of literal expressions. */ trait Literals { l => + /** A builder instance used by the parsers to create ErgoTree expressions. */ val builder: SigmaBuilder = StdSigmaBuilder import builder._ + + /** Set the current source position (dynamic variable) and execute the given thunk. */ def atSrcPos[A](parserIndex: Int)(thunk: => A): A + + /** Create SourceContext using current input string and the given index. */ def srcCtx(parserIndex: Int): SourceContext - + + /** Parses simple blocks `{ ... }` */ def Block[_:P]: P[Value[SType]] + + /** Parses pattern, like in the expression `val = expr` */ def Pattern[_:P]: P0 implicit class ParserOps[+T](p: P[T]) { + /** Ignores the result produced by `p`. */ def ignore: P[Unit] = p.map(_ => ()) } @@ -42,30 +52,29 @@ trait Literals { l => def Semis[_:P]: P[Unit] = P( Semi.rep(1) ~ WS ) def Newline[_:P]: P[Unit] = P( WL ~ Basic.Newline ) + /** Look ahead whitespaces, but not new line. */ def NotNewline[_:P]: P0 = P( &( WS ~ !Basic.Newline ) ) + def OneNLMax[_:P]: P0 = { def ConsumeComments = P( (Basic.WSChars.? ~ Literals.Comment ~ Basic.WSChars.? ~ Basic.Newline).rep ) P( NoCut( WS ~ Basic.Newline.? ~ ConsumeComments ~ NotNewline) ) } + + /** Parses optional trailing comma. */ def TrailingComma[_:P]: P0 = P( ("," ~ WS ~ Basic.Newline).? ) //noinspection ForwardReference object Literals{ import Basic._ - def Float[_:P]: P[Unit] = { - def Thing = P( DecNum ~ Exp.? ~ FloatType.? ) - def Thing2 = P( "." ~ Thing | Exp ~ FloatType.? | Exp.? ~ FloatType ) - P( "." ~ Thing | DecNum ~ Thing2 ) - } + /** Decimal or hex integers or longs. */ def Int[_:P]: P[Unit] = P( (HexNum | DecNum) ~ CharIn("Ll").? ) def Bool[_:P]: P[BooleanConstant] = - P( (Index ~ (Key.W("true").! | Key.W("false").!)).map { - case (i, lit) => - atSrcPos(i) { - mkConstant[SBoolean.type](if (lit == "true") true else false, SBoolean) - } + P( (Index ~ (Key.W("true").! | Key.W("false").!)).map { case (i, lit) => + atSrcPos(i) { + mkConstant[SBoolean.type](if (lit == "true") true else false, SBoolean) + } }) // Comments cannot have cuts in them, because they appear before every @@ -116,23 +125,23 @@ trait Literals { l => } }) - def Interp[_:P]: P[Unit] = interp match{ + private def Interp[_:P]: P[Unit] = interp match{ case None => P ( Fail ) case Some(p) => P( "$" ~ Identifiers.PlainIdNoDollar | ("${" ~ p() ~ WL ~ "}") | "$$" ) } + private def TQ[_:P]: P[Unit] = P( "\"\"\"" ) - def TQ[_:P]: P[Unit] = P( "\"\"\"" ) /** * Helper to quickly gobble up large chunks of un-interesting * characters. We break out conservatively, even if we don't know * it's a "real" escape sequence: worst come to worst it turns out * to be a dud and we go back into a CharsChunk next rep */ - def StringChars[_:P]: P[Unit] = P( CharsWhile(c => c != '\n' && c != '"' && c != '\\' && c != '$') ) - def NonTripleQuoteChar[_:P]: P[Unit] = P( "\"" ~ "\"".? ~ !"\"" | CharIn("\\$\n") ) + private def StringChars[_:P]: P[Unit] = P( CharsWhile(c => c != '\n' && c != '"' && c != '\\' && c != '$') ) + private def NonTripleQuoteChar[_:P]: P[Unit] = P( "\"" ~ "\"".? ~ !"\"" | CharIn("\\$\n") ) def TripleChars[_:P]: P[Unit] = P( (StringChars | Interp | NonTripleQuoteChar).rep ) - def TripleTail[_:P]: P[Unit] = P( TQ ~ "\"".rep ) + private def TripleTail[_:P]: P[Unit] = P( TQ ~ "\"".rep ) def SingleChars[_:P](allowSlash: Boolean): P[Unit] = { def LiteralSlash = P( if(allowSlash) "\\" else Fail ) def NonStringEnd = P( !CharIn("\n\"") ~ AnyChar ) @@ -149,7 +158,6 @@ trait Literals { l => } object NoInterp extends InterpCtx(None) - def Pat[_:P] = new InterpCtx(Some(() => l.Pattern)) def Expr[_:P] = new InterpCtx(Some(() => Block)) } } diff --git a/interpreter/src/main/scala/sigmastate/serialization/DataJsonEncoder.scala b/sc/src/main/scala/sigmastate/serialization/DataJsonEncoder.scala similarity index 93% rename from interpreter/src/main/scala/sigmastate/serialization/DataJsonEncoder.scala rename to sc/src/main/scala/sigmastate/serialization/DataJsonEncoder.scala index d64c8cd470..fa30825aae 100644 --- a/interpreter/src/main/scala/sigmastate/serialization/DataJsonEncoder.scala +++ b/sc/src/main/scala/sigmastate/serialization/DataJsonEncoder.scala @@ -4,20 +4,18 @@ import java.math.BigInteger import io.circe._ import io.circe.syntax._ import org.ergoplatform.ErgoBox -import org.ergoplatform.ErgoBox.NonMandatoryRegisterId +import org.ergoplatform.ErgoBox.{NonMandatoryRegisterId, Token} import org.ergoplatform.settings.ErgoAlgos import scalan.RType -import scorex.crypto.hash.Digest32 import scorex.util._ -import sigmastate.Values.{EvaluatedValue, Constant} +import sigmastate.Values.{Constant, EvaluatedValue} import sigmastate._ import sigmastate.eval._ import sigmastate.lang.SigmaParser -import sigmastate.lang.exceptions.SerializerException -import special.collection.{collRType, Coll} +import special.collection.Coll import special.sigma._ import debox.cfor - +import sigmastate.exceptions.SerializerException import scala.collection.compat.immutable.ArraySeq import scala.collection.mutable @@ -135,9 +133,10 @@ object DataJsonEncoder { val obj = mutable.ArrayBuffer.empty[(String, Json)] obj += ("value" -> encodeData(ergoBox.value.asInstanceOf[SType#WrappedType], SLong)) obj += ("ergoTree" -> encodeBytes(ErgoTreeSerializer.DefaultSerializer.serializeErgoTree(ergoBox.ergoTree))) - obj += "tokens" -> encodeData(ergoBox.additionalTokens.map { case (id, amount) => - (Colls.fromArray(id), amount) - }.asInstanceOf[SType#WrappedType], SCollectionType(STuple(SCollectionType(SByte), SLong))) + obj += "tokens" -> encodeData( + ergoBox.additionalTokens.asInstanceOf[SType#WrappedType], + SCollectionType(STuple(SCollectionType(SByte), SLong)) + ) ergoBox.additionalRegisters.foreach { case (id, value) => obj += (s"r${id.number}" -> encode[SType](value.value, value.tpe)) } @@ -205,11 +204,9 @@ object DataJsonEncoder { case SBox => val value = decodeData(json.hcursor.downField(s"value").focus.get, SLong) val tree = ErgoTreeSerializer.DefaultSerializer.deserializeErgoTree(decodeBytes(json.hcursor.downField(s"ergoTree").focus.get)) - val tokens = decodeData(json.hcursor.downField(s"tokens").focus.get, SCollectionType(STuple(SCollectionType(SByte), SLong))).asInstanceOf[Coll[(Coll[Byte], Long)]].map { - v => - val tup = v.asInstanceOf[(Coll[Byte], Long)] - (tup._1.toArray.asInstanceOf[Digest32], tup._2) - } + val tokens = decodeData( + json.hcursor.downField(s"tokens").focus.get, + SCollectionType(STuple(SCollectionType(SByte), SLong))).asInstanceOf[Coll[Token]] val txId = decodeBytes(json.hcursor.downField(s"txId").focus.get).toModifierId val index = decodeData(json.hcursor.downField(s"index").focus.get, SShort) val creationHeight = decodeData(json.hcursor.downField(s"creationHeight").focus.get, SInt) diff --git a/interpreter/src/test/scala/org/ergoplatform/EmissionSpec.scala b/sc/src/test/scala/org/ergoplatform/EmissionSpec.scala similarity index 95% rename from interpreter/src/test/scala/org/ergoplatform/EmissionSpec.scala rename to sc/src/test/scala/org/ergoplatform/EmissionSpec.scala index 224e4b7b95..5f13f79a8d 100644 --- a/interpreter/src/test/scala/org/ergoplatform/EmissionSpec.scala +++ b/sc/src/test/scala/org/ergoplatform/EmissionSpec.scala @@ -3,9 +3,9 @@ package org.ergoplatform import org.ergoplatform.mining.emission.EmissionRules import org.ergoplatform.settings.MonetarySettings import org.scalacheck.Gen -import sigmastate.helpers.SigmaTestingCommons +import sigmastate.helpers.CompilerTestingCommons -class EmissionSpec extends SigmaTestingCommons { +class EmissionSpec extends CompilerTestingCommons { private val settings = MonetarySettings(30 * 2 * 24 * 365, 90 * 24 * 30, 75L * EmissionRules.CoinsInOneErgo, 3L * EmissionRules.CoinsInOneErgo, 720, 75L * EmissionRules.CoinsInOneErgo / 10) diff --git a/interpreter/src/test/scala/org/ergoplatform/ErgoAddressSpecification.scala b/sc/src/test/scala/org/ergoplatform/ErgoAddressSpecification.scala similarity index 98% rename from interpreter/src/test/scala/org/ergoplatform/ErgoAddressSpecification.scala rename to sc/src/test/scala/org/ergoplatform/ErgoAddressSpecification.scala index c48fe4050f..6d96582306 100644 --- a/interpreter/src/test/scala/org/ergoplatform/ErgoAddressSpecification.scala +++ b/sc/src/test/scala/org/ergoplatform/ErgoAddressSpecification.scala @@ -13,21 +13,21 @@ import sigmastate.eval.{IRContext, InvalidType} import sigmastate.helpers.TestingHelpers._ import sigmastate.helpers._ import sigmastate.interpreter.ContextExtension.VarBinding -import sigmastate.interpreter.CryptoConstants.dlogGroup +import sigmastate.basics.CryptoConstants.dlogGroup +import sigmastate.exceptions.CostLimitException import sigmastate.interpreter.Interpreter.{ScriptEnv, ScriptNameProp} import sigmastate.interpreter.{ContextExtension, CostedProverResult} import sigmastate.lang.Terms.ValueOps -import sigmastate.lang.exceptions.CostLimitException import sigmastate.serialization.ErgoTreeSerializer.DefaultSerializer import sigmastate.serialization.{GroupElementSerializer, ValueSerializer} import sigmastate.utils.Helpers._ -import sigmastate.{CrossVersionProps, SType, SigmaAnd} +import sigmastate.{CompilerCrossVersionProps, SType, SigmaAnd} import special.sigma.SigmaDslTesting import java.math.BigInteger class ErgoAddressSpecification extends SigmaDslTesting - with TryValues with CrossVersionProps { + with TryValues with CompilerCrossVersionProps { private implicit val ergoAddressEncoder: ErgoAddressEncoder = new ErgoAddressEncoder(TestnetNetworkPrefix) diff --git a/interpreter/src/test/scala/org/ergoplatform/ErgoLikeTransactionSpec.scala b/sc/src/test/scala/org/ergoplatform/ErgoLikeTransactionSpec.scala similarity index 92% rename from interpreter/src/test/scala/org/ergoplatform/ErgoLikeTransactionSpec.scala rename to sc/src/test/scala/org/ergoplatform/ErgoLikeTransactionSpec.scala index 5de774def8..1ec9bd74db 100644 --- a/interpreter/src/test/scala/org/ergoplatform/ErgoLikeTransactionSpec.scala +++ b/sc/src/test/scala/org/ergoplatform/ErgoLikeTransactionSpec.scala @@ -16,6 +16,7 @@ import sigmastate.SType._ import sigmastate.helpers.TestingHelpers.copyTransaction import sigmastate.utils.Helpers import special.sigma.SigmaDslTesting +import special.collection.Extensions._ class ErgoLikeTransactionSpec extends SigmaDslTesting { @@ -27,8 +28,8 @@ class ErgoLikeTransactionSpec extends SigmaDslTesting { ErgoTree(ErgoTree.DefaultHeader, Vector(), TrueSigmaProp), 100, Coll( - Digest32 @@ (ErgoAlgos.decodeUnsafe(token1)) -> 10000000L, - Digest32 @@ (ErgoAlgos.decodeUnsafe(token2)) -> 500L + (Digest32Coll @@@ (ErgoAlgos.decodeUnsafe(token1).toColl)) -> 10000000L, + (Digest32Coll @@@ (ErgoAlgos.decodeUnsafe(token2).toColl)) -> 500L ) ) val b1_clone = new ErgoBoxCandidate( @@ -36,8 +37,8 @@ class ErgoLikeTransactionSpec extends SigmaDslTesting { ErgoTree(ErgoTree.DefaultHeader, Vector(), TrueSigmaProp), 100, Coll( - Digest32 @@ (ErgoAlgos.decodeUnsafe(token1)) -> 10000000L, - Digest32 @@ (ErgoAlgos.decodeUnsafe(token2)) -> 500L + Digest32Coll @@ (ErgoAlgos.decodeUnsafe(token1).toColl) -> 10000000L, + Digest32Coll @@ (ErgoAlgos.decodeUnsafe(token2).toColl) -> 500L ) ) val b2 = new ErgoBoxCandidate( @@ -45,17 +46,17 @@ class ErgoLikeTransactionSpec extends SigmaDslTesting { ErgoTree(ErgoTree.ConstantSegregationHeader, Vector(TrueSigmaProp), ConstantPlaceholder(0, SSigmaProp)), 100, Coll( - Digest32 @@ (ErgoAlgos.decodeUnsafe(token1)) -> 10000000L, - Digest32 @@ (ErgoAlgos.decodeUnsafe(token1)) -> 500L, - Digest32 @@ (ErgoAlgos.decodeUnsafe(token2)) -> 500L + Digest32Coll @@ (ErgoAlgos.decodeUnsafe(token1).toColl) -> 10000000L, + Digest32Coll @@ (ErgoAlgos.decodeUnsafe(token1).toColl) -> 500L, + Digest32Coll @@ (ErgoAlgos.decodeUnsafe(token2).toColl) -> 500L ) ) val b3 = new ErgoBox( 10L, ErgoTree(ErgoTree.DefaultHeader, Vector(), TrueSigmaProp), Coll( - Digest32 @@ (ErgoAlgos.decodeUnsafe(token1)) -> 10000000L, - Digest32 @@ (ErgoAlgos.decodeUnsafe(token2)) -> 500L + Digest32Coll @@ (ErgoAlgos.decodeUnsafe(token1).toColl) -> 10000000L, + Digest32Coll @@ (ErgoAlgos.decodeUnsafe(token2).toColl) -> 500L ), Map( ErgoBox.R5 -> ByteArrayConstant(Helpers.decodeBytes("7fc87f7f01ff")), @@ -95,8 +96,8 @@ class ErgoLikeTransactionSpec extends SigmaDslTesting { { // test case for R2 val res = b1.get(ErgoBox.R2).get val exp = Coll( - ErgoAlgos.decodeUnsafe(token1).toColl -> 10000000L, - ErgoAlgos.decodeUnsafe(token2).toColl -> 500L + (Digest32Coll @@ ErgoAlgos.decodeUnsafe(token1).toColl) -> 10000000L, + (Digest32Coll @@ ErgoAlgos.decodeUnsafe(token2).toColl) -> 500L ).map(identity).toConstant // TODO v6.0 (16h): fix collections equality and remove map(identity) // (PairOfColl should be equal CollOverArray but now it is not) @@ -132,7 +133,7 @@ class ErgoLikeTransactionSpec extends SigmaDslTesting { whenever(txIn.outputCandidates.head.additionalTokens.nonEmpty) { val out = txIn.outputCandidates.head // clone tokenIds so that same id have different references - val tokens = out.additionalTokens.map(v => (v._1.clone().asInstanceOf[TokenId], v._2)) + val tokens = out.additionalTokens.map(v => (v._1.toArray.clone().toColl.asInstanceOf[TokenId], v._2)) val outputs = (0 until 10).map { i => new ErgoBoxCandidate(out.value, out.ergoTree, i, tokens, out.additionalRegisters) } @@ -145,8 +146,8 @@ class ErgoLikeTransactionSpec extends SigmaDslTesting { val bytes = w.toBytes tx.outputCandidates.toColl.flatMap(_.additionalTokens).foreach { (tokenId, _) => - bytes.indexOfSlice(tokenId) should not be -1 - bytes.indexOfSlice(tokenId) shouldBe bytes.lastIndexOfSlice(tokenId) + bytes.indexOfSlice(tokenId.toArray) should not be -1 + bytes.indexOfSlice(tokenId.toArray) shouldBe bytes.lastIndexOfSlice(tokenId.toArray) } } } diff --git a/interpreter/src/test/scala/org/ergoplatform/ErgoScriptPredefSpec.scala b/sc/src/test/scala/org/ergoplatform/ErgoTreePredefSpec.scala similarity index 92% rename from interpreter/src/test/scala/org/ergoplatform/ErgoScriptPredefSpec.scala rename to sc/src/test/scala/org/ergoplatform/ErgoTreePredefSpec.scala index 5548a88df4..81e6f1954c 100644 --- a/interpreter/src/test/scala/org/ergoplatform/ErgoScriptPredefSpec.scala +++ b/sc/src/test/scala/org/ergoplatform/ErgoTreePredefSpec.scala @@ -10,7 +10,7 @@ import scorex.util.Random import sigmastate.Values.{SigmaPropConstant, CollectionConstant, ByteArrayConstant, IntConstant, ErgoTree} import sigmastate._ import sigmastate.basics.DLogProtocol.{ProveDlog, DLogProverInput} -import sigmastate.helpers.{ErgoLikeContextTesting, ErgoLikeTestInterpreter, SigmaTestingCommons, ContextEnrichingTestProvingInterpreter} +import sigmastate.helpers.{ErgoLikeContextTesting, ErgoLikeTestInterpreter, CompilerTestingCommons, ContextEnrichingTestProvingInterpreter} import sigmastate.helpers.TestingHelpers._ import sigmastate.interpreter.Interpreter.{ScriptNameProp, emptyEnv} import sigmastate.interpreter.{ProverResult, ContextExtension} @@ -18,11 +18,12 @@ import sigmastate.lang.Terms.ValueOps import sigmastate.serialization.ValueSerializer import sigmastate.utxo.{ExtractCreationInfo, ByIndex, SelectField} import scalan.util.BenchmarkUtil._ +import sigmastate.eval.{Colls, Digest32Coll} import sigmastate.utils.Helpers._ import scala.util.Try -class ErgoScriptPredefSpec extends SigmaTestingCommons with CrossVersionProps { +class ErgoTreePredefSpec extends CompilerTestingCommons with CompilerCrossVersionProps { private implicit lazy val IR: TestingIRContext = new TestingIRContext { } @@ -38,7 +39,7 @@ class ErgoScriptPredefSpec extends SigmaTestingCommons with CrossVersionProps { val pk = minerProp.pkBytes val nextHeight = 1 - val prop = EQ(Height, ErgoScriptPredef.boxCreationHeight(ByIndex(Outputs, IntConstant(0)))).toSigmaProp + val prop = EQ(Height, ErgoTreePredef.boxCreationHeight(ByIndex(Outputs, IntConstant(0)))).toSigmaProp val propInlined = EQ(Height, SelectField(ExtractCreationInfo(ByIndex(Outputs, IntConstant(0))), 1).asIntValue).toSigmaProp prop shouldBe propInlined val propTree = mkTestErgoTree(prop) @@ -64,7 +65,7 @@ class ErgoScriptPredefSpec extends SigmaTestingCommons with CrossVersionProps { def remaining(h: Int) = emission.remainingFoundationRewardAtHeight(h) val prover = new ContextEnrichingTestProvingInterpreter - val prop = ErgoScriptPredef.foundationScript(settings) + val prop = ErgoTreePredef.foundationScript(settings) def R4Prop(ableToProve: Boolean): CollectionConstant[SByte.type] = if (ableToProve) { val pks = (DLogProverInput.random() +: prover.dlogSecrets.take(2)).map(s => SigmaPropConstant(s.publicImage)) @@ -122,7 +123,7 @@ class ErgoScriptPredefSpec extends SigmaTestingCommons with CrossVersionProps { property("collect coins from rewardOutputScript") { val prover = new ContextEnrichingTestProvingInterpreter val minerPk = prover.dlogSecrets.head.publicImage - val prop = ErgoScriptPredef.rewardOutputScript(settings.minerRewardDelay, minerPk) + val prop = ErgoTreePredef.rewardOutputScript(settings.minerRewardDelay, minerPk) val verifier = new ErgoLikeTestInterpreter val inputBoxes = IndexedSeq(testBox(20, prop, 0, Seq(), Map())) val inputs = inputBoxes.map(b => Input(b.id, emptyProverResult)) @@ -156,9 +157,9 @@ class ErgoScriptPredefSpec extends SigmaTestingCommons with CrossVersionProps { property("create transaction collecting the emission box") { val prover = new ContextEnrichingTestProvingInterpreter val minerPk = prover.dlogSecrets.head.publicImage - val prop = ErgoScriptPredef.emissionBoxProp(settings) + val prop = ErgoTreePredef.emissionBoxProp(settings) val emissionBox = testBox(emission.coinsTotal, prop, 0, Seq(), Map()) - val minerProp = ErgoScriptPredef.rewardOutputScript(settings.minerRewardDelay, minerPk) + val minerProp = ErgoTreePredef.rewardOutputScript(settings.minerRewardDelay, minerPk) // collect coins during the fixed rate period forAll(Gen.choose(1, settings.fixedRatePeriod)) { height => @@ -180,9 +181,9 @@ class ErgoScriptPredefSpec extends SigmaTestingCommons with CrossVersionProps { forAll(Gen.choose(1, emission.blocksTotal - 1)) { height => val currentRate = emission.minersRewardAtHeight(height) val pk2 = prover.dlogSecrets(1).publicImage - val correctProp = ErgoScriptPredef.rewardOutputScript(settings.minerRewardDelay, minerPk) - val incorrectDelay = ErgoScriptPredef.rewardOutputScript(settings.minerRewardDelay + 1, minerPk) - val incorrectPk = ErgoScriptPredef.rewardOutputScript(settings.minerRewardDelay, pk2) + val correctProp = ErgoTreePredef.rewardOutputScript(settings.minerRewardDelay, minerPk) + val incorrectDelay = ErgoTreePredef.rewardOutputScript(settings.minerRewardDelay + 1, minerPk) + val incorrectPk = ErgoTreePredef.rewardOutputScript(settings.minerRewardDelay, pk2) createRewardTx(currentRate, height, correctProp) shouldBe 'success createRewardTx(currentRate, height, incorrectDelay) shouldBe 'failure createRewardTx(currentRate, height, incorrectPk) shouldBe 'failure @@ -206,12 +207,12 @@ class ErgoScriptPredefSpec extends SigmaTestingCommons with CrossVersionProps { val pubkey = prover.dlogSecrets.head.publicImage val pubkeyTree = mkTestErgoTree(pubkey) - val tokenId: Digest32 = Blake2b256("id") - val wrongId: Digest32 = Blake2b256(tokenId) - val wrongId2: Digest32 = Blake2b256(wrongId) + val tokenId = Digest32Coll @@@ Colls.fromArray(Blake2b256("id")) + val wrongId = Digest32Coll @@@ Colls.fromArray(Blake2b256(tokenId.toArray)) + val wrongId2 = Digest32Coll @@@ Colls.fromArray(Blake2b256(wrongId.toArray)) val tokenAmount: Int = 50 - val prop = mkTestErgoTree(ErgoScriptPredef.tokenThresholdScript(tokenId, tokenAmount, TestnetNetworkPrefix)) + val prop = mkTestErgoTree(ErgoScriptPredef.tokenThresholdScript(tokenId.toArray, tokenAmount, TestnetNetworkPrefix)) def check(inputBoxes: IndexedSeq[ErgoBox]): Try[Unit] = Try { val inputs = inputBoxes.map(b => Input(b.id, emptyProverResult)) diff --git a/interpreter/src/test/scala/org/ergoplatform/JsonSerializationSpec.scala b/sc/src/test/scala/org/ergoplatform/JsonSerializationSpec.scala similarity index 86% rename from interpreter/src/test/scala/org/ergoplatform/JsonSerializationSpec.scala rename to sc/src/test/scala/org/ergoplatform/JsonSerializationSpec.scala index 8fbd80987f..fec85d4095 100644 --- a/interpreter/src/test/scala/org/ergoplatform/JsonSerializationSpec.scala +++ b/sc/src/test/scala/org/ergoplatform/JsonSerializationSpec.scala @@ -4,21 +4,23 @@ import io.circe._ import io.circe.syntax._ import org.ergoplatform.ErgoBox._ import org.ergoplatform.validation.ValidationRules +import org.scalacheck.Arbitrary.arbitrary import scorex.crypto.authds.{ADDigest, ADKey} -import scorex.crypto.hash.Digest32 import scorex.util.ModifierId import scorex.util.encode.Base16 import sigmastate.{AvlTreeData, SType} -import sigmastate.Values.{EvaluatedValue, SigmaPropConstant, ByteArrayConstant, IntConstant, ErgoTree, ByteConstant, LongArrayConstant} +import sigmastate.Values.{ByteArrayConstant, ByteConstant, ErgoTree, EvaluatedValue, IntConstant, LongArrayConstant, SigmaPropConstant} +import sigmastate.basics.CryptoConstants import sigmastate.basics.DLogProtocol.ProveDlog -import sigmastate.helpers.SigmaTestingCommons -import sigmastate.interpreter.{ProverResult, ContextExtension, CryptoConstants} +import sigmastate.eval.Digest32Coll +import sigmastate.helpers.CompilerTestingCommons +import sigmastate.interpreter.{ContextExtension, ProverResult} import sigmastate.serialization.SerializationSpecification -import sigmastate.utils.Helpers._ // required for Scala 2.11 +import sigmastate.utils.Helpers._ import special.collection.Coll -import special.sigma.{PreHeader, Header} +import special.sigma.{Header, PreHeader} -class JsonSerializationSpec extends SigmaTestingCommons with SerializationSpecification with JsonCodecs { +class JsonSerializationSpec extends CompilerTestingCommons with SerializationSpecification with JsonCodecs { def jsonRoundTrip[T](v: T)(implicit encoder: Encoder[T], decoder: Decoder[T]): Unit = { val json = v.asJson @@ -34,11 +36,11 @@ class JsonSerializationSpec extends SigmaTestingCommons with SerializationSpecif } property("byte array should be encoded into JSON and decoded back correctly") { - forAll(byteArrayGen(0, 1000)) { v: Array[Byte] => jsonRoundTrip(v) } + forAll(arrayOfRange(0, 1000, arbitrary[Byte])) { v: Array[Byte] => jsonRoundTrip(v) } } property("byte coll should be encoded into JSON and decoded back correctly") { - forAll(byteCollGen(0, 1000)) { v: Coll[Byte] => jsonRoundTrip(v) } + forAll(collOfRange(0, 1000, arbitrary[Byte])) { v: Coll[Byte] => jsonRoundTrip(v) } } property("ADKey should be encoded into JSON and decoded back correctly") { @@ -50,7 +52,7 @@ class JsonSerializationSpec extends SigmaTestingCommons with SerializationSpecif } property("Digest32 should be encoded into JSON and decoded back correctly") { - forAll(digest32Gen) { v: Digest32 => jsonRoundTrip(v) } + forAll(digest32Gen) { v: Digest32Coll => jsonRoundTrip(v) } } property("ModifierId should be encoded into JSON and decoded back correctly") { diff --git a/interpreter/src/test/scala/org/ergoplatform/dsl/TestContractSpec.scala b/sc/src/test/scala/org/ergoplatform/dsl/TestContractSpec.scala similarity index 94% rename from interpreter/src/test/scala/org/ergoplatform/dsl/TestContractSpec.scala rename to sc/src/test/scala/org/ergoplatform/dsl/TestContractSpec.scala index 2f491d88af..996db0e184 100644 --- a/interpreter/src/test/scala/org/ergoplatform/dsl/TestContractSpec.scala +++ b/sc/src/test/scala/org/ergoplatform/dsl/TestContractSpec.scala @@ -6,22 +6,21 @@ import scala.collection.mutable import sigmastate.interpreter.{CostedProverResult, ProverResult} import scala.collection.mutable.ArrayBuffer -import org.ergoplatform.ErgoBox.NonMandatoryRegisterId +import org.ergoplatform.ErgoBox.{NonMandatoryRegisterId, TokenId} import scalan.Nullable -import scorex.crypto.hash.Digest32 import scala.util.Try import org.ergoplatform.{ErgoBox, ErgoLikeContext} -import org.ergoplatform.dsl.ContractSyntax.{ErgoScript, Proposition, Token, TokenId} +import org.ergoplatform.dsl.ContractSyntax.{ErgoScript, Proposition, Token} import sigmastate.{AvlTreeData, SType} import sigmastate.Values.{ErgoTree, EvaluatedValue} import sigmastate.eval.{CSigmaProp, Evaluation, IRContext, CAnyValue} -import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, ErgoLikeContextTesting, ErgoLikeTestInterpreter, SigmaTestingCommons} +import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, ErgoLikeContextTesting, ErgoLikeTestInterpreter, CompilerTestingCommons} import sigmastate.helpers.TestingHelpers._ import sigmastate.lang.Terms.ValueOps import special.sigma.{AnyValue, SigmaProp} -case class TestContractSpec(testSuite: SigmaTestingCommons)(implicit val IR: IRContext) extends ContractSpec { +case class TestContractSpec(testSuite: CompilerTestingCommons)(implicit val IR: IRContext) extends ContractSpec { case class TestPropositionSpec(name: String, dslSpec: Proposition, scriptSpec: ErgoScript) extends PropositionSpec { lazy val ergoTree: ErgoTree = { @@ -123,7 +122,7 @@ case class TestContractSpec(testSuite: SigmaTestingCommons)(implicit val IR: IRC } private[dsl] lazy val ergoBox: ErgoBox = { - val tokens = _tokens.map { t => (Digest32 @@ t.id.toArray, t.value) } + val tokens = _tokens.map(t => (t.id, t.value)) testBox(value, propSpec.ergoTree, tx.block.height, tokens, _regs) } def id = ergoBox.id diff --git a/interpreter/src/test/scala/org/ergoplatform/validation/RuleStatusSerializerSpec.scala b/sc/src/test/scala/org/ergoplatform/validation/RuleStatusSerializerSpec.scala similarity index 91% rename from interpreter/src/test/scala/org/ergoplatform/validation/RuleStatusSerializerSpec.scala rename to sc/src/test/scala/org/ergoplatform/validation/RuleStatusSerializerSpec.scala index 37dce4830a..711827fe1e 100644 --- a/interpreter/src/test/scala/org/ergoplatform/validation/RuleStatusSerializerSpec.scala +++ b/sc/src/test/scala/org/ergoplatform/validation/RuleStatusSerializerSpec.scala @@ -1,10 +1,10 @@ package org.ergoplatform.validation import org.scalatest.Assertion -import sigmastate.helpers.SigmaTestingCommons +import sigmastate.helpers.CompilerTestingCommons import sigmastate.serialization.{SigmaSerializer, SerializationSpecification} -class RuleStatusSerializerSpec extends SerializationSpecification with SigmaTestingCommons { +class RuleStatusSerializerSpec extends SerializationSpecification with CompilerTestingCommons { private def roundtrip(status: RuleStatus): Assertion = { implicit val ser = RuleStatusSerializer diff --git a/interpreter/src/test/scala/org/ergoplatform/validation/SigmaValidationSettingsSerializerSpec.scala b/sc/src/test/scala/org/ergoplatform/validation/SigmaValidationSettingsSerializerSpec.scala similarity index 88% rename from interpreter/src/test/scala/org/ergoplatform/validation/SigmaValidationSettingsSerializerSpec.scala rename to sc/src/test/scala/org/ergoplatform/validation/SigmaValidationSettingsSerializerSpec.scala index 2024274ae3..b74d4014f2 100644 --- a/interpreter/src/test/scala/org/ergoplatform/validation/SigmaValidationSettingsSerializerSpec.scala +++ b/sc/src/test/scala/org/ergoplatform/validation/SigmaValidationSettingsSerializerSpec.scala @@ -1,10 +1,10 @@ package org.ergoplatform.validation import org.scalatest.Assertion -import sigmastate.helpers.SigmaTestingCommons +import sigmastate.helpers.CompilerTestingCommons import sigmastate.serialization.SerializationSpecification -class SigmaValidationSettingsSerializerSpec extends SerializationSpecification with SigmaTestingCommons { +class SigmaValidationSettingsSerializerSpec extends SerializationSpecification with CompilerTestingCommons { private def roundtrip(settings: SigmaValidationSettings): Assertion = { implicit val set = SigmaValidationSettingsSerializer diff --git a/sc/src/test/scala/sigmastate/CompilerCrossVersionProps.scala b/sc/src/test/scala/sigmastate/CompilerCrossVersionProps.scala new file mode 100644 index 0000000000..89d15dd4df --- /dev/null +++ b/sc/src/test/scala/sigmastate/CompilerCrossVersionProps.scala @@ -0,0 +1,24 @@ +package sigmastate + +import org.scalatest.Tag +import org.scalactic.source.Position + + +/** Redefines `property` for cross-version testing of ErgoScript compiler. */ +trait CompilerCrossVersionProps extends CrossVersionProps with CompilerTestsBase { + + override protected def property(testName: String, testTags: Tag*) + (testFun: => Any) + (implicit pos: Position): Unit = { + super.property(testName, testTags:_*)(testFun) + + val testName2 = s"${testName}_MCLowering" + super.property2(testName2, testTags:_*) { + if (okRunTestsWithoutMCLowering) { + _lowerMethodCalls.withValue(false) { + testFun_Run(testName2, testFun) + } + } + } + } +} diff --git a/interpreter/src/test/scala/sigmastate/TestsBase.scala b/sc/src/test/scala/sigmastate/CompilerTestsBase.scala similarity index 57% rename from interpreter/src/test/scala/sigmastate/TestsBase.scala rename to sc/src/test/scala/sigmastate/CompilerTestsBase.scala index 5d2f28e074..67abf7d0a9 100644 --- a/interpreter/src/test/scala/sigmastate/TestsBase.scala +++ b/sc/src/test/scala/sigmastate/CompilerTestsBase.scala @@ -1,45 +1,15 @@ package sigmastate +import scala.util.DynamicVariable +import sigmastate.lang.{TransformingSigmaBuilder, CompilerResult, CompilerSettings, SigmaCompiler} +import sigmastate.interpreter.Interpreter.ScriptEnv +import sigmastate.Values.{SigmaPropValue, SValue, Value, ErgoTree} import org.ergoplatform.ErgoAddressEncoder.TestnetNetworkPrefix -import org.ergoplatform.ErgoScriptPredef -import org.scalatest.matchers.should.Matchers -import sigmastate.Values.{ErgoTree, SValue, SigmaBoolean, SigmaPropValue, Value} +import sigmastate.serialization.ValueSerializer import sigmastate.eval.IRContext -import sigmastate.helpers.SigmaPPrint -import sigmastate.interpreter.Interpreter.ScriptEnv import sigmastate.lang.Terms.ValueOps -import sigmastate.lang.{CompilerResult, CompilerSettings, SigmaCompiler, TransformingSigmaBuilder} -import sigmastate.serialization.ValueSerializer - -import scala.util.DynamicVariable - -trait TestsBase extends Matchers with VersionTesting { - /** Set this to true to enable debug console output in tests */ - val printDebugInfo: Boolean = false - - /** Print debug message if printDebugInfo is true */ - def printDebug(msg: Any): Unit = if (printDebugInfo) println(msg) - - /** Current ErgoTree header flags assigned dynamically using [[CrossVersionProps]] and - * ergoTreeVersionInTests. - */ - def ergoTreeHeaderInTests: Byte = ErgoTree.headerWithVersion(ergoTreeVersionInTests) - /** Obtains [[ErgoTree]] which corresponds to True proposition using current - * ergoTreeHeaderInTests. */ - def TrueTree: ErgoTree = ErgoScriptPredef.TrueProp(ergoTreeHeaderInTests) - - /** Obtains [[ErgoTree]] which corresponds to False proposition using current - * ergoTreeHeaderInTests. */ - def FalseTree: ErgoTree = ErgoScriptPredef.FalseProp(ergoTreeHeaderInTests) - - /** Transform proposition into [[ErgoTree]] using current ergoTreeHeaderInTests. */ - def mkTestErgoTree(prop: SigmaPropValue): ErgoTree = - ErgoTree.fromProposition(ergoTreeHeaderInTests, prop) - - /** Transform sigma proposition into [[ErgoTree]] using current ergoTreeHeaderInTests. */ - def mkTestErgoTree(prop: SigmaBoolean): ErgoTree = - ErgoTree.fromSigmaBoolean(ergoTreeHeaderInTests, prop) +trait CompilerTestsBase extends TestsBase { protected val _lowerMethodCalls = new DynamicVariable[Boolean](true) /** Returns true if MethodCall nodes should be lowered by TypeChecker to the @@ -53,6 +23,7 @@ trait TestsBase extends Matchers with VersionTesting { */ val okRunTestsWithoutMCLowering: Boolean = false + /** Compiler settings used in tests. */ val defaultCompilerSettings: CompilerSettings = CompilerSettings( TestnetNetworkPrefix, TransformingSigmaBuilder, lowerMethodCalls = true @@ -85,7 +56,7 @@ trait TestsBase extends Matchers with VersionTesting { /** Compiles the given code and checks the resulting `prop` against `expected`. */ def compileAndCheck(env: ScriptEnv, code: String, expected: SValue) - (implicit IR: IRContext): (ErgoTree, SigmaPropValue) = { + (implicit IR: IRContext): (ErgoTree, SigmaPropValue) = { val prop = compile(env, code).asSigmaProp prop shouldBe expected val tree = mkTestErgoTree(prop) diff --git a/interpreter/src/test/scala/sigmastate/ErgoTreeBenchmarks.scala b/sc/src/test/scala/sigmastate/ErgoTreeBenchmarks.scala similarity index 91% rename from interpreter/src/test/scala/sigmastate/ErgoTreeBenchmarks.scala rename to sc/src/test/scala/sigmastate/ErgoTreeBenchmarks.scala index 4fa897a727..c7587712f2 100644 --- a/interpreter/src/test/scala/sigmastate/ErgoTreeBenchmarks.scala +++ b/sc/src/test/scala/sigmastate/ErgoTreeBenchmarks.scala @@ -2,9 +2,10 @@ package sigmastate import special.collections.BenchmarkGens import org.scalameter.api.Bench -import sigmastate.Values.{SValue, IntConstant, Constant} +import sigmastate.Values.{SValue, Constant, IntConstant} import sigmastate.serialization.OpCodes.PlusCode import debox.cfor +import sigmastate.crypto.Platform object ErgoTreeBenchmarks extends Bench.LocalTime with BenchmarkGens { suite: Bench[Double] => @@ -57,7 +58,7 @@ object ErgoTreeBenchmarks extends Bench.LocalTime with BenchmarkGens { suite: Be measure method "isCorrectType" in { using(sizes) in { size => cfor(0)(_ < size, _ + 1) { i => - Constant.isCorrectType(i, SType.allPredefTypes(i % 10)) + Platform.isCorrectType(i, SType.allPredefTypes(i % 10)) } } } diff --git a/interpreter/src/test/scala/sigmastate/ErgoTreeSpecification.scala b/sc/src/test/scala/sigmastate/ErgoTreeSpecification.scala similarity index 96% rename from interpreter/src/test/scala/sigmastate/ErgoTreeSpecification.scala rename to sc/src/test/scala/sigmastate/ErgoTreeSpecification.scala index 5d7bdad31b..5dabf9974d 100644 --- a/interpreter/src/test/scala/sigmastate/ErgoTreeSpecification.scala +++ b/sc/src/test/scala/sigmastate/ErgoTreeSpecification.scala @@ -9,12 +9,12 @@ import sigmastate.SCollection.{SByteArray, checkValidFlatmap} import sigmastate.Values._ import sigmastate.VersionContext._ import sigmastate.eval.{CostingBox, Evaluation, Profiler} +import sigmastate.exceptions.{CostLimitException, InterpreterException} import sigmastate.helpers.{ErgoLikeContextTesting, SigmaPPrint} import sigmastate.interpreter.ErgoTreeEvaluator import sigmastate.interpreter.Interpreter.ReductionResult import sigmastate.lang.SourceContext import sigmastate.lang.Terms._ -import sigmastate.lang.exceptions.{CostLimitException, InterpreterException} import sigmastate.serialization.ErgoTreeSerializer.DefaultSerializer import sigmastate.utils.Helpers.TryOps import sigmastate.utxo._ @@ -169,25 +169,25 @@ class ErgoTreeSpecification extends SigmaDslTesting with ContractsTestkit { // Expected meta-parameters of predefined types (see Predefined Types section in docs/spec/spec.pdf) val types = Table( - ("type", "Code", "IsConst", "IsPrim", "IsEmbed", "IsNum"), - (SBoolean, 1, true, true, true, false), - (SByte, 2, true, true, true, true), - (SShort, 3, true, true, true, true), - (SInt, 4, true, true, true, true), - (SLong, 5, true, true, true, true), - (SBigInt, 6, true, true, true, true), - (SGroupElement, 7, true, true, true, false), - (SSigmaProp, 8, true, true, true, false), - (SBox, 99, false, false, false, false), - (SAvlTree, 100, true, false, false, false), - (SContext, 101, false, false, false, false), - (SHeader, 104, true, false, false, false), - (SPreHeader, 105, true, false, false, false), - (SGlobal, 106, true, false, false, false) + ("type", "Code", "IsPrim", "IsEmbed", "IsNum"), + (SBoolean, 1, true, true, false), + (SByte, 2, true, true, true), + (SShort, 3, true, true, true), + (SInt, 4, true, true, true), + (SLong, 5, true, true, true), + (SBigInt, 6, true, true, true), + (SGroupElement, 7, true, true, false), + (SSigmaProp, 8, true, true, false), + (SBox, 99, false, false, false), + (SAvlTree, 100, false, false, false), + (SContext, 101, false, false, false), + (SHeader, 104, false, false, false), + (SPreHeader, 105, false, false, false), + (SGlobal, 106, false, false, false) ) property("Predefined Types") { - forAll(types) { (t, code, isConst, isPrim, isEmbed, isNum) => + forAll(types) { (t, code, isPrim, isEmbed, isNum) => t.typeCode shouldBe code t.typeId shouldBe code t.isInstanceOf[SPrimType] shouldBe isPrim @@ -432,7 +432,7 @@ class ErgoTreeSpecification extends SigmaDslTesting with ContractsTestkit { assertExceptionThrown( { val x = 100 // any value which is not used anyway - val (y, _) = VersionContext.withVersions(activatedVersionInTests, ergoTreeVersionInTests) { + val _ = VersionContext.withVersions(activatedVersionInTests, ergoTreeVersionInTests) { newF.apply(x) } }, @@ -474,7 +474,7 @@ class ErgoTreeSpecification extends SigmaDslTesting with ContractsTestkit { val newF = funcJitFromExpr[(Int, Int), Int](script, expr) assertExceptionThrown( { - val (y, _) = VersionContext.withVersions(activatedVersionInTests, ergoTreeVersionInTests) { + val _ = VersionContext.withVersions(activatedVersionInTests, ergoTreeVersionInTests) { newF.apply((1, 1)) } }, @@ -587,7 +587,6 @@ class ErgoTreeSpecification extends SigmaDslTesting with ContractsTestkit { { // depth 60 val cf = mkCompiledFunc(60) - val expected = (xs: Coll[Byte]) => xs.map(_ => xs.map(_ => xs)) exprCostForSize(1, cf, None) shouldBe JitCost(2194) assertExceptionThrown( diff --git a/interpreter/src/test/scala/sigmastate/FailingToProveSpec.scala b/sc/src/test/scala/sigmastate/FailingToProveSpec.scala similarity index 95% rename from interpreter/src/test/scala/sigmastate/FailingToProveSpec.scala rename to sc/src/test/scala/sigmastate/FailingToProveSpec.scala index 6153b7f7a7..0d2e68e54f 100644 --- a/interpreter/src/test/scala/sigmastate/FailingToProveSpec.scala +++ b/sc/src/test/scala/sigmastate/FailingToProveSpec.scala @@ -1,13 +1,13 @@ package sigmastate -import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, ErgoLikeContextTesting, ErgoLikeTestInterpreter, SigmaTestingCommons} +import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, ErgoLikeContextTesting, ErgoLikeTestInterpreter, CompilerTestingCommons} import sigmastate.helpers.TestingHelpers._ import sigmastate.lang.Terms._ import org.scalatest.TryValues._ import sigmastate.interpreter.Interpreter.{ScriptNameProp, emptyEnv} -class FailingToProveSpec extends SigmaTestingCommons - with CrossVersionProps { +class FailingToProveSpec extends CompilerTestingCommons + with CompilerCrossVersionProps { implicit lazy val IR = new TestingIRContext /** * Both properties should work fine. diff --git a/interpreter/src/test/scala/sigmastate/InterpreterReflectionGeneratorTests.scala b/sc/src/test/scala/sigmastate/InterpreterReflectionGeneratorTests.scala similarity index 94% rename from interpreter/src/test/scala/sigmastate/InterpreterReflectionGeneratorTests.scala rename to sc/src/test/scala/sigmastate/InterpreterReflectionGeneratorTests.scala index 0487b7c324..45d8888e14 100644 --- a/interpreter/src/test/scala/sigmastate/InterpreterReflectionGeneratorTests.scala +++ b/sc/src/test/scala/sigmastate/InterpreterReflectionGeneratorTests.scala @@ -30,9 +30,8 @@ class InterpreterReflectionGeneratorTests extends AnyPropSpec with Matchers { test("A$B$", "A#B$") test("scala.A$B$", "scala.A#B$") test("sigmastate.ObjA$A", "sigmastate.ObjA.A"); - classOf[ObjA.A] + val _ = (classOf[ObjA.A], classOf[InterpreterReflectionGeneratorTests#ObjB#B]) test("sigmastate.ReflectionGeneratorTests$ObjB$B", "sigmastate.ReflectionGeneratorTests#ObjB#B"); - classOf[InterpreterReflectionGeneratorTests#ObjB#B] Class.forName("sigmastate.ObjA$") shouldNot be (null) Class.forName("sigmastate.ObjA$ObjB$") shouldNot be (null) } diff --git a/interpreter/src/test/scala/sigmastate/ReflectionGenerator.scala b/sc/src/test/scala/sigmastate/ReflectionGenerator.scala similarity index 99% rename from interpreter/src/test/scala/sigmastate/ReflectionGenerator.scala rename to sc/src/test/scala/sigmastate/ReflectionGenerator.scala index 18a4de53e0..f3301a2f0c 100644 --- a/interpreter/src/test/scala/sigmastate/ReflectionGenerator.scala +++ b/sc/src/test/scala/sigmastate/ReflectionGenerator.scala @@ -19,7 +19,7 @@ object ReflectionGenerator { Class.forName(prefix) '.' // prefix is object } catch { - case t => + case t: Throwable => '#' // prefix is not object } } diff --git a/interpreter/src/test/scala/sigmastate/ScriptVersionSwitchSpecification.scala b/sc/src/test/scala/sigmastate/ScriptVersionSwitchSpecification.scala similarity index 99% rename from interpreter/src/test/scala/sigmastate/ScriptVersionSwitchSpecification.scala rename to sc/src/test/scala/sigmastate/ScriptVersionSwitchSpecification.scala index 42ca379ef2..fe57c33f16 100644 --- a/interpreter/src/test/scala/sigmastate/ScriptVersionSwitchSpecification.scala +++ b/sc/src/test/scala/sigmastate/ScriptVersionSwitchSpecification.scala @@ -7,13 +7,13 @@ import sigmastate.Values.ErgoTree.{DefaultHeader, updateVersionBits} import sigmastate.Values._ import sigmastate.VersionContext.MaxSupportedScriptVersion import sigmastate.eval._ +import sigmastate.exceptions.InterpreterException import sigmastate.helpers.{ErgoLikeContextTesting, ErgoLikeTestInterpreter} import sigmastate.helpers.TestingHelpers.createBox import sigmastate.interpreter.ErgoTreeEvaluator.DefaultEvalSettings import sigmastate.interpreter.EvalSettings.EvaluationMode import sigmastate.interpreter.{CostedProverResult, ErgoTreeEvaluator, EvalSettings, Interpreter, ProverResult} import sigmastate.lang.Terms.ValueOps -import sigmastate.lang.exceptions.InterpreterException import sigmastate.utils.Helpers._ import special.sigma.{Box, SigmaDslTesting} diff --git a/interpreter/src/test/scala/sigmastate/SoftForkabilitySpecification.scala b/sc/src/test/scala/sigmastate/SoftForkabilitySpecification.scala similarity index 94% rename from interpreter/src/test/scala/sigmastate/SoftForkabilitySpecification.scala rename to sc/src/test/scala/sigmastate/SoftForkabilitySpecification.scala index dc00e39f9b..574f02328d 100644 --- a/interpreter/src/test/scala/sigmastate/SoftForkabilitySpecification.scala +++ b/sc/src/test/scala/sigmastate/SoftForkabilitySpecification.scala @@ -8,13 +8,13 @@ import sigmastate.SPrimType.MaxPrimTypeCode import sigmastate.Values.ErgoTree.EmptyConstants import sigmastate.Values.{ByteArrayConstant, ErgoTree, IntConstant, NotReadyValueInt, Tuple, UnparsedErgoTree, ValueCompanion} import sigmastate.eval.Colls +import sigmastate.exceptions.{SerializerException, SigmaException, InterpreterException} import sigmastate.helpers.TestingHelpers._ -import sigmastate.helpers.{ErgoLikeContextTesting, ErgoLikeTestInterpreter, ErgoLikeTestProvingInterpreter} +import sigmastate.helpers.{ErgoLikeTestInterpreter, ErgoLikeContextTesting, ErgoLikeTestProvingInterpreter, CompilerTestingCommons} import sigmastate.interpreter.ErgoTreeEvaluator.DataEnv import sigmastate.interpreter.Interpreter.{ScriptNameProp, emptyEnv} import sigmastate.interpreter.{ContextExtension, ErgoTreeEvaluator, ProverResult} import sigmastate.lang.Terms._ -import sigmastate.lang.exceptions.{InterpreterException, SerializerException, SigmaException} import sigmastate.serialization.OpCodes.{LastConstantCode, OpCode} import sigmastate.serialization.SigmaSerializer.startReader import sigmastate.serialization._ @@ -22,7 +22,9 @@ import sigmastate.utils.Helpers._ import sigmastate.utxo.{DeserializeContext, SelectField} import special.sigma.SigmaTestingData -class SoftForkabilitySpecification extends SigmaTestingData with BeforeAndAfterAll { +class SoftForkabilitySpecification extends SigmaTestingData + with CompilerTestingCommons + with BeforeAndAfterAll { implicit lazy val IR = new TestingIRContext lazy val prover = new ErgoLikeTestProvingInterpreter() @@ -297,16 +299,6 @@ class SoftForkabilitySpecification extends SigmaTestingData with BeforeAndAfterA res shouldBe false } - property("CheckTupleType rule") { - val tuple = Tuple(IntConstant(1), IntConstant(2), IntConstant(3)) - val exp = SelectField(tuple, 3) - val v2vs = vs.updated(CheckTupleType.id, ReplacedRule(0)) - checkRule(CheckTupleType, v2vs, { - // simulate throwing of exception in - CheckTupleType.throwValidationException(new SigmaException(s"Invalid tuple type"), Array[IR.Elem[_]]()) - }) - } - property("CheckPrimitiveTypeCode rule") { val typeBytes = Array[Byte](MaxPrimTypeCode) val v2vs = vs.updated(CheckPrimitiveTypeCode.id, ChangedRule(Array[Byte](MaxPrimTypeCode))) diff --git a/interpreter/src/test/scala/sigmastate/TestingInterpreterSpecification.scala b/sc/src/test/scala/sigmastate/TestingInterpreterSpecification.scala similarity index 97% rename from interpreter/src/test/scala/sigmastate/TestingInterpreterSpecification.scala rename to sc/src/test/scala/sigmastate/TestingInterpreterSpecification.scala index 1f594ed06c..76b4b63e50 100644 --- a/interpreter/src/test/scala/sigmastate/TestingInterpreterSpecification.scala +++ b/sc/src/test/scala/sigmastate/TestingInterpreterSpecification.scala @@ -10,15 +10,16 @@ import sigmastate.lang.Terms._ import org.ergoplatform._ import org.scalatest.BeforeAndAfterAll import scorex.util.encode.Base58 -import sigmastate.helpers.{ErgoLikeContextTesting, ErgoLikeTestProvingInterpreter, SigmaTestingCommons, ErgoLikeTestInterpreter} +import sigmastate.basics.CryptoConstants +import sigmastate.helpers.{ErgoLikeTestInterpreter, CompilerTestingCommons, ErgoLikeContextTesting, ErgoLikeTestProvingInterpreter} import sigmastate.helpers.TestingHelpers._ import sigmastate.serialization.ValueSerializer import sigmastate.utils.Helpers._ import scala.util.Random -class TestingInterpreterSpecification extends SigmaTestingCommons - with CrossVersionProps with BeforeAndAfterAll { +class TestingInterpreterSpecification extends CompilerTestingCommons + with CompilerCrossVersionProps with BeforeAndAfterAll { implicit lazy val IR = new TestingIRContext lazy val prover = new ErgoLikeTestProvingInterpreter() { diff --git a/interpreter/src/test/scala/sigmastate/TypesSpecification.scala b/sc/src/test/scala/sigmastate/TypesSpecification.scala similarity index 97% rename from interpreter/src/test/scala/sigmastate/TypesSpecification.scala rename to sc/src/test/scala/sigmastate/TypesSpecification.scala index 47cb360e8f..5270ce8344 100644 --- a/interpreter/src/test/scala/sigmastate/TypesSpecification.scala +++ b/sc/src/test/scala/sigmastate/TypesSpecification.scala @@ -6,8 +6,7 @@ import org.ergoplatform.ErgoBox import org.ergoplatform.settings.ErgoAlgos import scalan.RType import scorex.crypto.hash.Digest32 -import sigmastate.basics.DLogProtocol -import sigmastate.interpreter.CryptoConstants +import sigmastate.basics.{DLogProtocol, CryptoConstants} import special.sigma.SigmaTestingData import sigmastate.SType.{isValueOfType, AnyOps} import sigmastate.Values.{ShortConstant, LongConstant, BigIntConstant, AvlTreeConstant, IntConstant, ByteConstant} diff --git a/interpreter/src/test/scala/sigmastate/eval/ErgoScriptTestkit.scala b/sc/src/test/scala/sigmastate/eval/ErgoScriptTestkit.scala similarity index 94% rename from interpreter/src/test/scala/sigmastate/eval/ErgoScriptTestkit.scala rename to sc/src/test/scala/sigmastate/eval/ErgoScriptTestkit.scala index d1d580324f..1407be9dc3 100644 --- a/interpreter/src/test/scala/sigmastate/eval/ErgoScriptTestkit.scala +++ b/sc/src/test/scala/sigmastate/eval/ErgoScriptTestkit.scala @@ -4,14 +4,14 @@ import org.ergoplatform.ErgoAddressEncoder.TestnetNetworkPrefix import org.ergoplatform.validation.ValidationSpecification import scala.util.Success -import sigmastate.{AvlTreeData, SType, TestsBase, VersionContext} -import sigmastate.Values.{BigIntArrayConstant, EvaluatedValue, SValue, SigmaPropConstant, Value} +import sigmastate.{VersionContext, CompilerTestsBase, SType, TestsBase, AvlTreeData} +import sigmastate.Values.{EvaluatedValue, SValue, SigmaPropConstant, Value, BigIntArrayConstant} import org.ergoplatform.{Context => _, _} import scalan.BaseCtxTests -import sigmastate.lang.{CompilerResult, CompilerSettings, LangTests, SigmaCompiler} +import sigmastate.lang.{LangTests, CompilerResult, CompilerSettings, SigmaCompiler} import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, ErgoLikeContextTesting} import sigmastate.helpers.TestingHelpers._ -import sigmastate.interpreter.{ContextExtension, ErgoTreeEvaluator} +import sigmastate.interpreter.{ErgoTreeEvaluator, ContextExtension} import sigmastate.interpreter.Interpreter.ScriptEnv import sigmastate.lang.Terms.ValueOps import special.sigma.{ContractsTestkit, Context => DContext} @@ -20,7 +20,7 @@ import sigmastate.serialization.ErgoTreeSerializer.DefaultSerializer import scala.language.implicitConversions trait ErgoScriptTestkit extends ContractsTestkit with LangTests - with ValidationSpecification with TestsBase { self: BaseCtxTests => + with ValidationSpecification with CompilerTestsBase { self: BaseCtxTests => implicit lazy val IR: TestContext with IRContext = new TestContext with IRContext diff --git a/interpreter/src/test/scala/sigmastate/eval/ErgoTreeBuildingTest.scala b/sc/src/test/scala/sigmastate/eval/ErgoTreeBuildingTest.scala similarity index 100% rename from interpreter/src/test/scala/sigmastate/eval/ErgoTreeBuildingTest.scala rename to sc/src/test/scala/sigmastate/eval/ErgoTreeBuildingTest.scala diff --git a/interpreter/src/test/scala/sigmastate/eval/EvaluationTest.scala b/sc/src/test/scala/sigmastate/eval/EvaluationTest.scala similarity index 100% rename from interpreter/src/test/scala/sigmastate/eval/EvaluationTest.scala rename to sc/src/test/scala/sigmastate/eval/EvaluationTest.scala diff --git a/interpreter/src/test/scala/sigmastate/eval/ExampleContracts.scala b/sc/src/test/scala/sigmastate/eval/ExampleContracts.scala similarity index 100% rename from interpreter/src/test/scala/sigmastate/eval/ExampleContracts.scala rename to sc/src/test/scala/sigmastate/eval/ExampleContracts.scala diff --git a/interpreter/src/test/scala/sigmastate/eval/MeasureIRContext.scala b/sc/src/test/scala/sigmastate/eval/MeasureIRContext.scala similarity index 100% rename from interpreter/src/test/scala/sigmastate/eval/MeasureIRContext.scala rename to sc/src/test/scala/sigmastate/eval/MeasureIRContext.scala diff --git a/interpreter/src/test/scala/sigmastate/helpers/SigmaTestingCommons.scala b/sc/src/test/scala/sigmastate/helpers/CompilerTestingCommons.scala similarity index 86% rename from interpreter/src/test/scala/sigmastate/helpers/SigmaTestingCommons.scala rename to sc/src/test/scala/sigmastate/helpers/CompilerTestingCommons.scala index 69ed1a9c78..8981ace5df 100644 --- a/interpreter/src/test/scala/sigmastate/helpers/SigmaTestingCommons.scala +++ b/sc/src/test/scala/sigmastate/helpers/CompilerTestingCommons.scala @@ -7,46 +7,26 @@ import org.ergoplatform.validation.{ValidationException, ValidationSpecification import org.scalacheck.Arbitrary.arbByte import org.scalacheck.Gen import org.scalatest.Assertion -import org.scalatest.matchers.should.Matchers -import org.scalatest.propspec.AnyPropSpec -import org.scalatestplus.scalacheck.{ScalaCheckDrivenPropertyChecks, ScalaCheckPropertyChecks} import scalan.util.BenchmarkUtil import scalan.{RType, TestContexts, TestUtils} -import scorex.crypto.hash.Blake2b256 -import sigmastate.Values.{Constant, ErgoTree, GroupElementConstant, SValue, SigmaBoolean, SigmaPropValue} -import sigmastate.eval.{Evaluation, IRContext, _} +import sigmastate.Values.{Constant, ErgoTree, SValue, SigmaBoolean, SigmaPropValue} +import sigmastate.eval._ import sigmastate.helpers.TestingHelpers._ import sigmastate.interpreter.ContextExtension.VarBinding -import sigmastate.interpreter.CryptoConstants.EcPointType import sigmastate.interpreter.ErgoTreeEvaluator.DefaultProfiler import sigmastate.interpreter.Interpreter.ScriptEnv import sigmastate.interpreter._ import sigmastate.lang.{CompilerSettings, SigmaCompiler, Terms} import sigmastate.serialization.SigmaSerializer -import sigmastate.{JitCost, SOption, SType, TestsBase} +import sigmastate.{CompilerTestsBase, JitCost, SOption, SType} import scala.language.implicitConversions import scala.reflect.ClassTag import scala.util.DynamicVariable -trait SigmaTestingCommons extends AnyPropSpec - with ScalaCheckPropertyChecks - with ScalaCheckDrivenPropertyChecks - with Matchers with TestUtils with TestContexts with ValidationSpecification - with NegativeTesting - with TestsBase { - - def fakeSelf: ErgoBox = createBox(0, TrueTree) - - def fakeContext: ErgoLikeContext = - ErgoLikeContextTesting.dummy(fakeSelf, activatedVersionInTests) - .withErgoTreeVersion(ergoTreeVersionInTests) - - //fake message, in a real-life a message is to be derived from a spending transaction - val fakeMessage = Blake2b256("Hello World") - - implicit def grElemConvert(leafConstant: GroupElementConstant): EcPointType = - SigmaDsl.toECPoint(leafConstant.value).asInstanceOf[EcPointType] +trait CompilerTestingCommons extends TestingCommons + with TestUtils with TestContexts with ValidationSpecification + with CompilerTestsBase { class TestingIRContext extends TestContext with IRContext { } diff --git a/interpreter/src/test/scala/sigmastate/helpers/SigmaPPrint.scala b/sc/src/test/scala/sigmastate/helpers/SigmaPPrint.scala similarity index 97% rename from interpreter/src/test/scala/sigmastate/helpers/SigmaPPrint.scala rename to sc/src/test/scala/sigmastate/helpers/SigmaPPrint.scala index 3122e2f5f0..38ae6c6740 100644 --- a/interpreter/src/test/scala/sigmastate/helpers/SigmaPPrint.scala +++ b/sc/src/test/scala/sigmastate/helpers/SigmaPPrint.scala @@ -11,8 +11,8 @@ import sigmastate.SCollection._ import sigmastate.Values.{ConstantNode, ErgoTree, FuncValue, ValueCompanion} import sigmastate._ import sigmastate.crypto.GF2_192_Poly -import sigmastate.interpreter.CryptoConstants.EcPointType -import sigmastate.lang.SigmaTyper +import sigmastate.basics.CryptoConstants.EcPointType +import sigmastate.lang.Terms import sigmastate.lang.Terms.MethodCall import sigmastate.serialization.GroupElementSerializer import sigmastate.utxo.SelectField @@ -249,7 +249,7 @@ object SigmaPPrint extends PPrinter { case mc @ MethodCall(obj, method, args, typeSubst) => val objType = apply(method.objType).plainText val methodTemplate = method.objType.getMethodByName(method.name) - val methodT = SigmaTyper.unifyTypeLists(methodTemplate.stype.tDom, obj.tpe +: args.map(_.tpe)) match { + val methodT = Terms.unifyTypeLists(methodTemplate.stype.tDom, obj.tpe +: args.map(_.tpe)) match { case Some(subst) if subst.nonEmpty => val getMethod = s"""$objType.getMethodByName("${method.name}").withConcreteTypes""" Tree.Apply(getMethod, treeifySeq(Seq(subst))) diff --git a/interpreter/src/test/scala/sigmastate/helpers/SigmaPPrintSpec.scala b/sc/src/test/scala/sigmastate/helpers/SigmaPPrintSpec.scala similarity index 95% rename from interpreter/src/test/scala/sigmastate/helpers/SigmaPPrintSpec.scala rename to sc/src/test/scala/sigmastate/helpers/SigmaPPrintSpec.scala index c166b4ee5d..f732fb7cf2 100644 --- a/interpreter/src/test/scala/sigmastate/helpers/SigmaPPrintSpec.scala +++ b/sc/src/test/scala/sigmastate/helpers/SigmaPPrintSpec.scala @@ -6,13 +6,13 @@ import org.ergoplatform.settings.ErgoAlgos import org.ergoplatform.{Outputs, ErgoBox} import scalan.RType import scorex.crypto.authds.ADDigest -import scorex.crypto.hash.Digest32 import scorex.util.ModifierId import sigmastate.Values._ import sigmastate.lang.Terms.MethodCall import sigmastate.serialization.OpCodes import sigmastate.utxo.SelectField import sigmastate._ +import sigmastate.eval.Extensions.ArrayOps import sigmastate.eval._ import sigmastate.utils.Helpers import special.collection.CollType @@ -106,7 +106,7 @@ class SigmaPPrintSpec extends SigmaDslTesting { 9223372036854775807L, new ErgoTree(0.toByte, Vector(), Right(BoolToSigmaProp(FalseLeaf))), Coll( - (Digest32 @@ (ErgoAlgos.decodeUnsafe("6e789ab7b2fffff12280a6cd01557f6fb22b7f80ff7aff8e1f7f15973d7f0001")), 10000000L) + (Digest32Coll @@ (ErgoAlgos.decodeUnsafe("6e789ab7b2fffff12280a6cd01557f6fb22b7f80ff7aff8e1f7f15973d7f0001").toColl), 10000000L) ), Map(), ModifierId @@ ("bc80ffc00100d60101ffd3d3ab7f73800aff80487fff7fffbb010080ff7f0837"), @@ -121,7 +121,7 @@ class SigmaPPrintSpec extends SigmaDslTesting { | Coll( | ( | Digest32 @@ ( - | ErgoAlgos.decodeUnsafe("6e789ab7b2fffff12280a6cd01557f6fb22b7f80ff7aff8e1f7f15973d7f0001") + | Helpers.decodeBytes("6e789ab7b2fffff12280a6cd01557f6fb22b7f80ff7aff8e1f7f15973d7f0001") | ), | 10000000L | ) diff --git a/interpreter/src/test/scala/sigmastate/lang/LangTests.scala b/sc/src/test/scala/sigmastate/lang/LangTests.scala similarity index 97% rename from interpreter/src/test/scala/sigmastate/lang/LangTests.scala rename to sc/src/test/scala/sigmastate/lang/LangTests.scala index a512b25de9..8cf6b69d28 100644 --- a/interpreter/src/test/scala/sigmastate/lang/LangTests.scala +++ b/sc/src/test/scala/sigmastate/lang/LangTests.scala @@ -8,8 +8,7 @@ import sigmastate._ import java.math.BigInteger import sigmastate.basics.DLogProtocol.ProveDlog import sigmastate.SCollection.SByteArray -import sigmastate.basics.ProveDHTuple -import sigmastate.interpreter.CryptoConstants +import sigmastate.basics.{ProveDHTuple, CryptoConstants} import sigmastate.interpreter.Interpreter.ScriptEnv import special.sigma._ import sigmastate.eval._ diff --git a/interpreter/src/test/scala/sigmastate/lang/SigmaBinderTest.scala b/sc/src/test/scala/sigmastate/lang/SigmaBinderTest.scala similarity index 98% rename from interpreter/src/test/scala/sigmastate/lang/SigmaBinderTest.scala rename to sc/src/test/scala/sigmastate/lang/SigmaBinderTest.scala index a9404048ed..de52c93fc3 100644 --- a/interpreter/src/test/scala/sigmastate/lang/SigmaBinderTest.scala +++ b/sc/src/test/scala/sigmastate/lang/SigmaBinderTest.scala @@ -10,7 +10,7 @@ import sigmastate._ import sigmastate.interpreter.Interpreter.ScriptEnv import sigmastate.lang.SigmaPredef.PredefinedFuncRegistry import sigmastate.lang.Terms._ -import sigmastate.lang.exceptions.BinderException +import sigmastate.exceptions.BinderException import sigmastate.eval._ class SigmaBinderTest extends AnyPropSpec with ScalaCheckPropertyChecks with Matchers with LangTests { @@ -18,7 +18,7 @@ class SigmaBinderTest extends AnyPropSpec with ScalaCheckPropertyChecks with Mat def bind(env: ScriptEnv, x: String): SValue = { val builder = TransformingSigmaBuilder - val ast = SigmaParser(x, builder).get.value + val ast = SigmaParser(x).get.value val binder = new SigmaBinder(env, builder, TestnetNetworkPrefix, new PredefinedFuncRegistry(builder)) val res = binder.bind(ast) @@ -29,7 +29,7 @@ class SigmaBinderTest extends AnyPropSpec with ScalaCheckPropertyChecks with Mat private def fail(env: ScriptEnv, x: String, expectedLine: Int, expectedCol: Int): Unit = { val builder = TransformingSigmaBuilder - val ast = SigmaParser(x, builder).get.value + val ast = SigmaParser(x).get.value val binder = new SigmaBinder(env, builder, TestnetNetworkPrefix, new PredefinedFuncRegistry(builder)) val exception = the[BinderException] thrownBy binder.bind(ast) diff --git a/interpreter/src/test/scala/sigmastate/lang/SigmaBuilderTest.scala b/sc/src/test/scala/sigmastate/lang/SigmaBuilderTest.scala similarity index 98% rename from interpreter/src/test/scala/sigmastate/lang/SigmaBuilderTest.scala rename to sc/src/test/scala/sigmastate/lang/SigmaBuilderTest.scala index 3e25700816..832a5bb11d 100644 --- a/interpreter/src/test/scala/sigmastate/lang/SigmaBuilderTest.scala +++ b/sc/src/test/scala/sigmastate/lang/SigmaBuilderTest.scala @@ -5,7 +5,7 @@ import org.scalatest.propspec.AnyPropSpec import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks import sigmastate.Values._ import sigmastate._ -import sigmastate.lang.exceptions.ConstraintFailed +import sigmastate.exceptions.ConstraintFailed import sigmastate.serialization.OpCodes class SigmaBuilderTest extends AnyPropSpec with ScalaCheckPropertyChecks with Matchers with LangTests { diff --git a/interpreter/src/test/scala/sigmastate/lang/SigmaCompilerTest.scala b/sc/src/test/scala/sigmastate/lang/SigmaCompilerTest.scala similarity index 97% rename from interpreter/src/test/scala/sigmastate/lang/SigmaCompilerTest.scala rename to sc/src/test/scala/sigmastate/lang/SigmaCompilerTest.scala index 605082437f..96a430a51d 100644 --- a/interpreter/src/test/scala/sigmastate/lang/SigmaCompilerTest.scala +++ b/sc/src/test/scala/sigmastate/lang/SigmaCompilerTest.scala @@ -5,15 +5,15 @@ import org.ergoplatform._ import scorex.util.encode.Base58 import sigmastate.Values._ import sigmastate._ -import sigmastate.helpers.SigmaTestingCommons +import sigmastate.helpers.CompilerTestingCommons import sigmastate.interpreter.Interpreter.ScriptEnv import sigmastate.lang.Terms.{Apply, Ident, Lambda, MethodCall, ZKProofBlock} -import sigmastate.lang.exceptions.{CosterException, InvalidArguments, TyperException} +import sigmastate.exceptions.{CosterException, InvalidArguments, TyperException} import sigmastate.serialization.ValueSerializer import sigmastate.serialization.generators.ObjectGenerators import sigmastate.utxo.{ByIndex, ExtractAmount, GetVar, SelectField} -class SigmaCompilerTest extends SigmaTestingCommons with LangTests with ObjectGenerators { +class SigmaCompilerTest extends CompilerTestingCommons with LangTests with ObjectGenerators { import CheckingSigmaBuilder._ implicit lazy val IR: TestingIRContext = new TestingIRContext { beginPass(noConstPropagationPass) @@ -78,8 +78,8 @@ class SigmaCompilerTest extends SigmaTestingCommons with LangTests with ObjectGe } property("global methods") { - comp(env, "{ groupGenerator }") shouldBe MethodCall(Global, SGlobal.groupGeneratorMethod, IndexedSeq(), SigmaTyper.EmptySubst) - comp(env, "{ Global.groupGenerator }") shouldBe MethodCall(Global, SGlobal.groupGeneratorMethod, IndexedSeq(), SigmaTyper.EmptySubst) + comp(env, "{ groupGenerator }") shouldBe MethodCall(Global, SGlobal.groupGeneratorMethod, IndexedSeq(), Terms.EmptySubst) + comp(env, "{ Global.groupGenerator }") shouldBe MethodCall(Global, SGlobal.groupGeneratorMethod, IndexedSeq(), Terms.EmptySubst) comp(env, "{ Global.xor(arr1, arr2) }") shouldBe Xor(ByteArrayConstant(arr1), ByteArrayConstant(arr2)) comp(env, "{ xor(arr1, arr2) }") shouldBe Xor(ByteArrayConstant(arr1), ByteArrayConstant(arr2)) } diff --git a/interpreter/src/test/scala/sigmastate/lang/SigmaParserTest.scala b/sc/src/test/scala/sigmastate/lang/SigmaParserTest.scala similarity index 99% rename from interpreter/src/test/scala/sigmastate/lang/SigmaParserTest.scala rename to sc/src/test/scala/sigmastate/lang/SigmaParserTest.scala index 4b5680af0a..3bc64cf0cf 100644 --- a/interpreter/src/test/scala/sigmastate/lang/SigmaParserTest.scala +++ b/sc/src/test/scala/sigmastate/lang/SigmaParserTest.scala @@ -20,7 +20,7 @@ class SigmaParserTest extends AnyPropSpec with ScalaCheckPropertyChecks with Mat import predefFuncRegistry._ def parse(x: String): SValue = { - SigmaParser(x, TransformingSigmaBuilder) match { + SigmaParser(x) match { case Parsed.Success(v, _) => v.sourceContext.isDefined shouldBe true assertSrcCtxForAllNodes(v) diff --git a/interpreter/src/test/scala/sigmastate/lang/SigmaTyperTest.scala b/sc/src/test/scala/sigmastate/lang/SigmaTyperTest.scala similarity index 98% rename from interpreter/src/test/scala/sigmastate/lang/SigmaTyperTest.scala rename to sc/src/test/scala/sigmastate/lang/SigmaTyperTest.scala index 3153344bbe..dc87d2dc2f 100644 --- a/interpreter/src/test/scala/sigmastate/lang/SigmaTyperTest.scala +++ b/sc/src/test/scala/sigmastate/lang/SigmaTyperTest.scala @@ -8,13 +8,13 @@ import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks import sigmastate.SCollection._ import sigmastate.Values._ import sigmastate._ +import sigmastate.basics.CryptoConstants import sigmastate.basics.DLogProtocol.{DLogProverInput, ProveDlog} import sigmastate.eval.Colls -import sigmastate.interpreter.CryptoConstants +import sigmastate.exceptions.TyperException import sigmastate.interpreter.Interpreter.ScriptEnv import sigmastate.lang.SigmaPredef._ -import sigmastate.lang.Terms.Select -import sigmastate.lang.exceptions.TyperException +import sigmastate.lang.Terms._ import sigmastate.lang.syntax.ParserException import sigmastate.serialization.ErgoTreeSerializer import sigmastate.serialization.generators.ObjectGenerators @@ -29,7 +29,7 @@ class SigmaTyperTest extends AnyPropSpec def typecheck(env: ScriptEnv, x: String, expected: SValue = null): SType = { try { val builder = TransformingSigmaBuilder - val parsed = SigmaParser(x, builder).get.value + val parsed = SigmaParser(x).get.value val predefinedFuncRegistry = new PredefinedFuncRegistry(builder) val binder = new SigmaBinder(env, builder, TestnetNetworkPrefix, predefinedFuncRegistry) val bound = binder.bind(parsed) @@ -46,7 +46,7 @@ class SigmaTyperTest extends AnyPropSpec def typefail(env: ScriptEnv, x: String, expectedLine: Int, expectedCol: Int): Unit = { val builder = TransformingSigmaBuilder assertExceptionThrown({ - val parsed = SigmaParser(x, builder).get.value + val parsed = SigmaParser(x).get.value val predefinedFuncRegistry = new PredefinedFuncRegistry(builder) val binder = new SigmaBinder(env, builder, TestnetNetworkPrefix, predefinedFuncRegistry) val bound = binder.bind(parsed) @@ -313,7 +313,6 @@ class SigmaTyperTest extends AnyPropSpec } property("compute unifying type substitution: prim types") { - import SigmaTyper._ forAll { t: SPredefType => unifyTypes(t, t) shouldBe Some(EmptySubst) unifyTypes(SAny, t) shouldBe Some(EmptySubst) @@ -325,7 +324,6 @@ class SigmaTyperTest extends AnyPropSpec } property("compute unifying type substitution") { - import SigmaTyper._ def checkTypes(t1: SType, t2: SType, exp: Option[STypeSubst]): Unit = { unifyTypes(t1, t2) shouldBe exp exp match { @@ -426,7 +424,6 @@ class SigmaTyperTest extends AnyPropSpec } property("most specific general (MSG) type") { - import SigmaTyper._ def checkTypes(t1: SType, t2: SType, exp: Option[SType]): Unit = { msgType(t1, t2) shouldBe exp } diff --git a/interpreter/src/test/scala/sigmastate/serialization/DataJsonEncoderSpecification.scala b/sc/src/test/scala/sigmastate/serialization/DataJsonEncoderSpecification.scala similarity index 98% rename from interpreter/src/test/scala/sigmastate/serialization/DataJsonEncoderSpecification.scala rename to sc/src/test/scala/sigmastate/serialization/DataJsonEncoderSpecification.scala index e0a595dc60..a5465ad366 100644 --- a/interpreter/src/test/scala/sigmastate/serialization/DataJsonEncoderSpecification.scala +++ b/sc/src/test/scala/sigmastate/serialization/DataJsonEncoderSpecification.scala @@ -12,8 +12,8 @@ import sigmastate.Values.SigmaBoolean import sigmastate._ import sigmastate.eval.Extensions._ import sigmastate.eval.{Evaluation, _} -import sigmastate.interpreter.CryptoConstants.EcPointType -import sigmastate.lang.exceptions.SerializerException +import sigmastate.basics.CryptoConstants.EcPointType +import sigmastate.exceptions.SerializerException import special.sigma.{Box, AvlTree} class DataJsonEncoderSpecification extends SerializationSpecification { diff --git a/interpreter/src/test/scala/sigmastate/serialization/DeserializationResilience.scala b/sc/src/test/scala/sigmastate/serialization/DeserializationResilience.scala similarity index 97% rename from interpreter/src/test/scala/sigmastate/serialization/DeserializationResilience.scala rename to sc/src/test/scala/sigmastate/serialization/DeserializationResilience.scala index 22be862da2..9bc698127f 100644 --- a/interpreter/src/test/scala/sigmastate/serialization/DeserializationResilience.scala +++ b/sc/src/test/scala/sigmastate/serialization/DeserializationResilience.scala @@ -12,11 +12,12 @@ import scorex.crypto.hash.{Blake2b256, Digest32} import scorex.util.serialization.{Reader, VLQByteBufferReader} import sigmastate.Values.{SValue, BlockValue, GetVarInt, SigmaBoolean, ValDef, ValUse, SigmaPropValue, Tuple, IntConstant} import sigmastate._ +import sigmastate.basics.CryptoConstants import sigmastate.eval.Extensions._ import sigmastate.eval._ -import sigmastate.helpers.{ErgoLikeTestInterpreter, SigmaTestingCommons, ErgoLikeContextTesting} -import sigmastate.interpreter.{CryptoConstants, CostedProverResult, ContextExtension} -import sigmastate.lang.exceptions.{ReaderPositionLimitExceeded, InvalidTypePrefix, DeserializeCallDepthExceeded, SerializerException} +import sigmastate.exceptions.{ReaderPositionLimitExceeded, InvalidTypePrefix, SerializerException, DeserializeCallDepthExceeded} +import sigmastate.helpers.{ErgoLikeContextTesting, ErgoLikeTestInterpreter, CompilerTestingCommons} +import sigmastate.interpreter.{ContextExtension, CostedProverResult} import sigmastate.serialization.OpCodes._ import sigmastate.util.safeNewArray import sigmastate.utils.SigmaByteReader @@ -27,7 +28,7 @@ import scala.collection.mutable import scala.util.{Try, Success, Failure} class DeserializationResilience extends SerializationSpecification - with SigmaTestingCommons with CrossVersionProps { + with CompilerTestingCommons with CompilerCrossVersionProps { implicit lazy val IR: TestingIRContext = new TestingIRContext { // substFromCostTable = false @@ -67,7 +68,7 @@ class DeserializationResilience extends SerializationSpecification assertExceptionThrown( ErgoBoxCandidate.serializer.parse(SigmaSerializer.startReader(w.toBytes)), { - case SerializerException(_, _, + case SerializerException(_, Some(ValidationException(_,CheckPositionLimit,_, Some(_: ReaderPositionLimitExceeded)))) => true case _ => false diff --git a/interpreter/src/test/scala/sigmastate/serialization/ErgoTreeSerializerSpecification.scala b/sc/src/test/scala/sigmastate/serialization/ErgoTreeSerializerSpecification.scala similarity index 97% rename from interpreter/src/test/scala/sigmastate/serialization/ErgoTreeSerializerSpecification.scala rename to sc/src/test/scala/sigmastate/serialization/ErgoTreeSerializerSpecification.scala index d31cd5202d..c4d8ebf13d 100644 --- a/interpreter/src/test/scala/sigmastate/serialization/ErgoTreeSerializerSpecification.scala +++ b/sc/src/test/scala/sigmastate/serialization/ErgoTreeSerializerSpecification.scala @@ -7,13 +7,13 @@ import org.ergoplatform.validation.ValidationRules.CheckDeserializedScriptIsSigm import sigmastate.Values.{BigIntConstant, ByteConstant, ConstantPlaceholder, ErgoTree, IntConstant, ShortConstant, SigmaPropValue, UnparsedErgoTree} import sigmastate._ import sigmastate.eval.{CBigInt, IRContext} -import sigmastate.helpers.SigmaTestingCommons -import sigmastate.lang.exceptions.{ReaderPositionLimitExceeded, SerializerException} +import sigmastate.exceptions.{SerializerException, ReaderPositionLimitExceeded} +import sigmastate.helpers.CompilerTestingCommons import sigmastate.serialization.ErgoTreeSerializer.DefaultSerializer import sigmastate.utxo.{DeserializeContext, DeserializeRegister} class ErgoTreeSerializerSpecification extends SerializationSpecification - with SigmaTestingCommons with CrossVersionProps { + with CompilerTestingCommons with CompilerCrossVersionProps { implicit lazy val IR: TestingIRContext = new TestingIRContext { beginPass(noConstPropagationPass) diff --git a/interpreter/src/test/scala/sigmastate/utils/GenInfoObjects.scala b/sc/src/test/scala/sigmastate/utils/GenInfoObjects.scala similarity index 100% rename from interpreter/src/test/scala/sigmastate/utils/GenInfoObjects.scala rename to sc/src/test/scala/sigmastate/utils/GenInfoObjects.scala diff --git a/interpreter/src/test/scala/sigmastate/utils/GenPredefFuncsApp.scala b/sc/src/test/scala/sigmastate/utils/GenPredefFuncsApp.scala similarity index 100% rename from interpreter/src/test/scala/sigmastate/utils/GenPredefFuncsApp.scala rename to sc/src/test/scala/sigmastate/utils/GenPredefFuncsApp.scala diff --git a/interpreter/src/test/scala/sigmastate/utils/GenPredefTypesApp.scala b/sc/src/test/scala/sigmastate/utils/GenPredefTypesApp.scala similarity index 100% rename from interpreter/src/test/scala/sigmastate/utils/GenPredefTypesApp.scala rename to sc/src/test/scala/sigmastate/utils/GenPredefTypesApp.scala diff --git a/interpreter/src/test/scala/sigmastate/utils/GenSerializers.scala b/sc/src/test/scala/sigmastate/utils/GenSerializers.scala similarity index 100% rename from interpreter/src/test/scala/sigmastate/utils/GenSerializers.scala rename to sc/src/test/scala/sigmastate/utils/GenSerializers.scala diff --git a/interpreter/src/test/scala/sigmastate/utils/SpecGen.scala b/sc/src/test/scala/sigmastate/utils/SpecGen.scala similarity index 100% rename from interpreter/src/test/scala/sigmastate/utils/SpecGen.scala rename to sc/src/test/scala/sigmastate/utils/SpecGen.scala diff --git a/interpreter/src/test/scala/sigmastate/utxo/AVLTreeScriptsSpecification.scala b/sc/src/test/scala/sigmastate/utxo/AVLTreeScriptsSpecification.scala similarity index 98% rename from interpreter/src/test/scala/sigmastate/utxo/AVLTreeScriptsSpecification.scala rename to sc/src/test/scala/sigmastate/utxo/AVLTreeScriptsSpecification.scala index 8d4ac696be..320877f1e8 100644 --- a/interpreter/src/test/scala/sigmastate/utxo/AVLTreeScriptsSpecification.scala +++ b/sc/src/test/scala/sigmastate/utxo/AVLTreeScriptsSpecification.scala @@ -12,7 +12,7 @@ import sigmastate._ import sigmastate.eval.{CSigmaProp, IRContext} import sigmastate.eval._ import sigmastate.eval.Extensions._ -import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, ErgoLikeContextTesting, ErgoLikeTestInterpreter, SigmaTestingCommons} +import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, ErgoLikeContextTesting, ErgoLikeTestInterpreter, CompilerTestingCommons} import sigmastate.helpers.TestingHelpers._ import sigmastate.interpreter.Interpreter.ScriptNameProp import sigmastate.interpreter.ProverResult @@ -21,8 +21,8 @@ import special.collection.Coll import special.sigma.{AvlTree, Context} -class AVLTreeScriptsSpecification extends SigmaTestingCommons - with CrossVersionProps { suite => +class AVLTreeScriptsSpecification extends CompilerTestingCommons + with CompilerCrossVersionProps { suite => import org.ergoplatform.dsl.AvlTreeHelpers._ lazy val spec = TestContractSpec(suite)(new TestingIRContext) lazy val prover = spec.ProvingParty("Alice") diff --git a/interpreter/src/test/scala/sigmastate/utxo/BasicOpsSpecification.scala b/sc/src/test/scala/sigmastate/utxo/BasicOpsSpecification.scala similarity index 98% rename from interpreter/src/test/scala/sigmastate/utxo/BasicOpsSpecification.scala rename to sc/src/test/scala/sigmastate/utxo/BasicOpsSpecification.scala index f090ad1857..ace7abb082 100644 --- a/interpreter/src/test/scala/sigmastate/utxo/BasicOpsSpecification.scala +++ b/sc/src/test/scala/sigmastate/utxo/BasicOpsSpecification.scala @@ -8,22 +8,21 @@ import sigmastate.SCollection.SByteArray import sigmastate.Values._ import sigmastate._ import sigmastate.eval.Extensions._ -import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, ErgoLikeContextTesting, ErgoLikeTestInterpreter, SigmaTestingCommons} +import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, ErgoLikeContextTesting, ErgoLikeTestInterpreter, CompilerTestingCommons} import sigmastate.helpers.TestingHelpers._ import sigmastate.interpreter.Interpreter._ import sigmastate.lang.Terms._ import SType.AnyOps +import sigmastate.basics.CryptoConstants import sigmastate.eval.InvalidType import sigmastate.interpreter.ContextExtension.VarBinding import sigmastate.interpreter.ErgoTreeEvaluator.DefaultEvalSettings -import sigmastate.interpreter.{CryptoConstants, EvalSettings} +import sigmastate.interpreter.EvalSettings import sigmastate.utils.Helpers._ import scalan.util.StringUtil._ -import sigmastate.basics.DLogProtocol.DLogProverInput -import sigmastate.lang.exceptions.CosterException -class BasicOpsSpecification extends SigmaTestingCommons - with CrossVersionProps { +class BasicOpsSpecification extends CompilerTestingCommons + with CompilerCrossVersionProps { override val printVersions: Boolean = false implicit lazy val IR = new TestingIRContext { } diff --git a/interpreter/src/test/scala/sigmastate/utxo/BlockchainSimulationSpecification.scala b/sc/src/test/scala/sigmastate/utxo/BlockchainSimulationSpecification.scala similarity index 95% rename from interpreter/src/test/scala/sigmastate/utxo/BlockchainSimulationSpecification.scala rename to sc/src/test/scala/sigmastate/utxo/BlockchainSimulationSpecification.scala index 8807986713..d112ce45c3 100644 --- a/interpreter/src/test/scala/sigmastate/utxo/BlockchainSimulationSpecification.scala +++ b/sc/src/test/scala/sigmastate/utxo/BlockchainSimulationSpecification.scala @@ -3,7 +3,7 @@ package sigmastate.utxo import java.io.{FileWriter, File} import org.ergoplatform -import org.ergoplatform.ErgoBox.TokenId +import org.ergoplatform.ErgoBox.Token import org.ergoplatform._ import org.scalacheck.Gen import scorex.crypto.authds.avltree.batch.{Remove, BatchAVLProver, Insert} @@ -11,12 +11,12 @@ import scorex.crypto.authds.{ADDigest, ADKey, ADValue} import scorex.crypto.hash.{Digest32, Blake2b256} import scorex.util._ import sigmastate.Values.{LongConstant, IntConstant, ErgoTree} -import sigmastate.helpers.{ErgoTransactionValidator, ErgoLikeContextTesting, ErgoLikeTestProvingInterpreter, SigmaTestingCommons, BlockchainState} +import sigmastate.helpers.{ErgoTransactionValidator, ErgoLikeContextTesting, ErgoLikeTestProvingInterpreter, CompilerTestingCommons, BlockchainState} import sigmastate.helpers.TestingHelpers._ import sigmastate.interpreter.ContextExtension import sigmastate.eval._ import sigmastate.interpreter.Interpreter.{ScriptNameProp, emptyEnv} -import sigmastate.{GE, AvlTreeData, CrossVersionProps, AvlTreeFlags} +import sigmastate.{GE, AvlTreeData, CompilerCrossVersionProps, AvlTreeFlags} import scala.annotation.tailrec import scala.collection.concurrent.TrieMap @@ -24,8 +24,8 @@ import scala.collection.mutable import scala.util.Try -class BlockchainSimulationSpecification extends SigmaTestingCommons - with CrossVersionProps { +class BlockchainSimulationSpecification extends CompilerTestingCommons + with CompilerCrossVersionProps { import BlockchainSimulationSpecification._ implicit lazy val IR = new TestingIRContext @@ -35,7 +35,7 @@ class BlockchainSimulationSpecification extends SigmaTestingCommons val txs = boxesToSpend.map { box => val newBoxCandidate = - new ErgoBoxCandidate(10, minerPubKey, height, Colls.emptyColl[(TokenId, Long)], Map(heightReg -> IntConstant(height + windowSize))) + new ErgoBoxCandidate(10, minerPubKey, height, Colls.emptyColl[Token], Map(heightReg -> IntConstant(height + windowSize))) val unsignedInput = new UnsignedInput(box.id) val tx = UnsignedErgoLikeTransaction(IndexedSeq(unsignedInput), IndexedSeq(newBoxCandidate)) val context = ErgoLikeContextTesting(height + 1, diff --git a/interpreter/src/test/scala/sigmastate/utxo/CollectionOperationsSpecification.scala b/sc/src/test/scala/sigmastate/utxo/CollectionOperationsSpecification.scala similarity index 99% rename from interpreter/src/test/scala/sigmastate/utxo/CollectionOperationsSpecification.scala rename to sc/src/test/scala/sigmastate/utxo/CollectionOperationsSpecification.scala index 64fb945aa1..54f0d1dc04 100644 --- a/interpreter/src/test/scala/sigmastate/utxo/CollectionOperationsSpecification.scala +++ b/sc/src/test/scala/sigmastate/utxo/CollectionOperationsSpecification.scala @@ -2,7 +2,7 @@ package sigmastate.utxo import sigmastate.Values._ import sigmastate._ -import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, ErgoLikeContextTesting, ErgoLikeTestInterpreter, SigmaTestingCommons} +import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, ErgoLikeContextTesting, ErgoLikeTestInterpreter, CompilerTestingCommons} import sigmastate.helpers.TestingHelpers._ import sigmastate.lang.Terms._ import org.ergoplatform._ @@ -11,8 +11,8 @@ import sigmastate.interpreter.Interpreter.{ScriptNameProp, emptyEnv} import sigmastate.serialization.OpCodes._ import sigmastate.utils.Helpers._ -class CollectionOperationsSpecification extends SigmaTestingCommons - with CrossVersionProps { +class CollectionOperationsSpecification extends CompilerTestingCommons + with CompilerCrossVersionProps { implicit lazy val IR: TestingIRContext = new TestingIRContext private val reg1 = ErgoBox.nonMandatoryRegisters.head diff --git a/interpreter/src/test/scala/sigmastate/utxo/ComplexSigSpecification.scala b/sc/src/test/scala/sigmastate/utxo/ComplexSigSpecification.scala similarity index 99% rename from interpreter/src/test/scala/sigmastate/utxo/ComplexSigSpecification.scala rename to sc/src/test/scala/sigmastate/utxo/ComplexSigSpecification.scala index 10aea07d97..e78f5156a2 100644 --- a/interpreter/src/test/scala/sigmastate/utxo/ComplexSigSpecification.scala +++ b/sc/src/test/scala/sigmastate/utxo/ComplexSigSpecification.scala @@ -5,12 +5,12 @@ import org.scalacheck.Gen import sigmastate.Values.IntConstant import sigmastate._ import sigmastate.lang.Terms._ -import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, ErgoLikeContextTesting, ErgoLikeTestInterpreter, ErgoLikeTransactionTesting, SigmaTestingCommons} +import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, ErgoLikeContextTesting, ErgoLikeTestInterpreter, ErgoLikeTransactionTesting, CompilerTestingCommons} import scala.util.Random -class ComplexSigSpecification extends SigmaTestingCommons - with CrossVersionProps { +class ComplexSigSpecification extends CompilerTestingCommons + with CompilerCrossVersionProps { implicit lazy val IR: TestingIRContext = new TestingIRContext private def proverGen: Gen[ContextEnrichingTestProvingInterpreter] = for { diff --git a/interpreter/src/test/scala/sigmastate/utxo/ContextEnrichingSpecification.scala b/sc/src/test/scala/sigmastate/utxo/ContextEnrichingSpecification.scala similarity index 97% rename from interpreter/src/test/scala/sigmastate/utxo/ContextEnrichingSpecification.scala rename to sc/src/test/scala/sigmastate/utxo/ContextEnrichingSpecification.scala index 8c2b7ddefc..c76606ed96 100644 --- a/interpreter/src/test/scala/sigmastate/utxo/ContextEnrichingSpecification.scala +++ b/sc/src/test/scala/sigmastate/utxo/ContextEnrichingSpecification.scala @@ -5,12 +5,12 @@ import scorex.crypto.hash.Blake2b256 import sigmastate.Values._ import sigmastate._ import sigmastate.lang.Terms._ -import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, ErgoLikeContextTesting, ErgoLikeTestInterpreter, SigmaTestingCommons} +import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, ErgoLikeContextTesting, ErgoLikeTestInterpreter, CompilerTestingCommons} import special.collection.Coll -class ContextEnrichingSpecification extends SigmaTestingCommons - with CrossVersionProps { +class ContextEnrichingSpecification extends CompilerTestingCommons + with CompilerCrossVersionProps { implicit lazy val IR: TestingIRContext = new TestingIRContext diff --git a/interpreter/src/test/scala/sigmastate/utxo/DistributedSigSpecification.scala b/sc/src/test/scala/sigmastate/utxo/DistributedSigSpecification.scala similarity index 99% rename from interpreter/src/test/scala/sigmastate/utxo/DistributedSigSpecification.scala rename to sc/src/test/scala/sigmastate/utxo/DistributedSigSpecification.scala index 51820e7ac5..9276413e1e 100644 --- a/interpreter/src/test/scala/sigmastate/utxo/DistributedSigSpecification.scala +++ b/sc/src/test/scala/sigmastate/utxo/DistributedSigSpecification.scala @@ -1,7 +1,7 @@ package sigmastate.utxo import sigmastate._ -import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, ErgoLikeTestProvingInterpreter, SigmaTestingCommons} +import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, ErgoLikeTestProvingInterpreter, CompilerTestingCommons} import sigmastate.interpreter._ import sigmastate.lang.Terms._ @@ -10,8 +10,8 @@ import sigmastate.lang.Terms._ * See EIP-11 for generic signing procedure. * In some simple generic procedure is simplified. */ -class DistributedSigSpecification extends SigmaTestingCommons - with CrossVersionProps { +class DistributedSigSpecification extends CompilerTestingCommons + with CompilerCrossVersionProps { implicit lazy val IR: TestingIRContext = new TestingIRContext diff --git a/interpreter/src/test/scala/sigmastate/utxo/ErgoLikeInterpreterSpecification.scala b/sc/src/test/scala/sigmastate/utxo/ErgoLikeInterpreterSpecification.scala similarity index 98% rename from interpreter/src/test/scala/sigmastate/utxo/ErgoLikeInterpreterSpecification.scala rename to sc/src/test/scala/sigmastate/utxo/ErgoLikeInterpreterSpecification.scala index d9b3453777..7f2cc3ac33 100644 --- a/interpreter/src/test/scala/sigmastate/utxo/ErgoLikeInterpreterSpecification.scala +++ b/sc/src/test/scala/sigmastate/utxo/ErgoLikeInterpreterSpecification.scala @@ -10,6 +10,7 @@ import sigmastate.SCollection.SByteArray import sigmastate.Values._ import sigmastate._ import sigmastate.eval._ +import sigmastate.eval.Extensions._ import sigmastate.interpreter.Interpreter._ import sigmastate.basics.DLogProtocol.ProveDlog import sigmastate.basics.ProveDHTuple @@ -21,9 +22,9 @@ import sigmastate.lang.Terms._ import sigmastate.serialization.{ValueSerializer, SerializationSpecification} import sigmastate.utils.Helpers._ -class ErgoLikeInterpreterSpecification extends SigmaTestingCommons +class ErgoLikeInterpreterSpecification extends CompilerTestingCommons with SerializationSpecification - with CrossVersionProps { + with CompilerCrossVersionProps { implicit lazy val IR: TestingIRContext = new TestingIRContext private val reg1 = ErgoBox.nonMandatoryRegisters.head @@ -74,7 +75,7 @@ class ErgoLikeInterpreterSpecification extends SigmaTestingCommons val wrongProp = SigmaPropConstant(ProveDHTuple(ci.g, ci.h, ci.u, ci.u)) - val env = Map("g" -> ci.g, "h" -> ci.h, "u" -> ci.u, "v" -> ci.v, "s" -> secret.publicImage) + val env = Map("g" -> ci.g.toGroupElement, "h" -> ci.h.toGroupElement, "u" -> ci.u.toGroupElement, "v" -> ci.v.toGroupElement, "s" -> secret.publicImage) val compiledProp1 = compile(env, "s").asSigmaProp val compiledProp2 = compile(env, "proveDHTuple(g, h, u, v)").asSigmaProp compiledProp1 shouldBe prop @@ -741,8 +742,8 @@ class ErgoLikeInterpreterSpecification extends SigmaTestingCommons } property("non-const ProveDHT") { - import sigmastate.interpreter.CryptoConstants.dlogGroup - compile(Map("gA" -> dlogGroup.generator), + import sigmastate.basics.CryptoConstants.dlogGroup + compile(Map("gA" -> dlogGroup.generator.toGroupElement), "proveDHTuple(gA, OUTPUTS(0).R4[GroupElement].get, gA, gA)" ).asInstanceOf[BlockValue].result shouldBe a [CreateProveDHTuple] } diff --git a/interpreter/src/test/scala/sigmastate/utxo/SerializationRoundTripSpec.scala b/sc/src/test/scala/sigmastate/utxo/SerializationRoundTripSpec.scala similarity index 97% rename from interpreter/src/test/scala/sigmastate/utxo/SerializationRoundTripSpec.scala rename to sc/src/test/scala/sigmastate/utxo/SerializationRoundTripSpec.scala index 4a653dabdb..54ee4f4857 100644 --- a/interpreter/src/test/scala/sigmastate/utxo/SerializationRoundTripSpec.scala +++ b/sc/src/test/scala/sigmastate/utxo/SerializationRoundTripSpec.scala @@ -2,7 +2,7 @@ package sigmastate.utxo import org.ergoplatform._ import scalan.util.BenchmarkUtil -import sigmastate.helpers.SigmaTestingCommons +import sigmastate.helpers.CompilerTestingCommons import sigmastate.interpreter.{ContextExtension, ProverResult} import sigmastate.serialization.generators.ObjectGenerators import debox.{Buffer => DBuffer} @@ -15,7 +15,7 @@ class SerializationRoundTripSpec extends AnyPropSpec with ScalaCheckDrivenPropertyChecks with Matchers with ObjectGenerators - with SigmaTestingCommons { + with CompilerTestingCommons { case class Run(size: Int, time: Long) diff --git a/interpreter/src/test/scala/sigmastate/utxo/SigmaCompilerSpecification.scala b/sc/src/test/scala/sigmastate/utxo/SigmaCompilerSpecification.scala similarity index 66% rename from interpreter/src/test/scala/sigmastate/utxo/SigmaCompilerSpecification.scala rename to sc/src/test/scala/sigmastate/utxo/SigmaCompilerSpecification.scala index 21dea7f121..280683cfcc 100644 --- a/interpreter/src/test/scala/sigmastate/utxo/SigmaCompilerSpecification.scala +++ b/sc/src/test/scala/sigmastate/utxo/SigmaCompilerSpecification.scala @@ -2,7 +2,7 @@ package sigmastate.utxo import sigmastate.{GE, ModQ, SType} import sigmastate.Values._ -import sigmastate.helpers.SigmaTestingCommons +import sigmastate.helpers.CompilerTestingCommons import sigmastate.interpreter.Interpreter.ScriptEnv import sigmastate.lang.Terms._ import sigmastate._ @@ -10,7 +10,7 @@ import sigmastate._ /** * Specification for compile function */ -class SigmaCompilerSpecification extends SigmaTestingCommons { +class SigmaCompilerSpecification extends CompilerTestingCommons { implicit lazy val IR: TestingIRContext = new TestingIRContext private def compile(code: String, env: ScriptEnv = Map()): Value[SType] = compile(env, code) @@ -31,11 +31,4 @@ class SigmaCompilerSpecification extends SigmaTestingCommons { compile("10.toBigInt.modQ") shouldEqual ModQ(BigIntConstant(10)) } - // TODO https://github.com/ScorexFoundation/sigmastate-interpreter/issues/327 - ignore("modular arithmetic ops: BinOps") { - compile("10.toBigInt.plusModQ(2.toBigInt)") shouldEqual - PlusModQ(BigIntConstant(10), BigIntConstant(2)) - compile("10.toBigInt.minusModQ(2.toBigInt)") shouldEqual - MinusModQ(BigIntConstant(10), BigIntConstant(2)) - } } diff --git a/interpreter/src/test/scala/sigmastate/utxo/ThresholdSpecification.scala b/sc/src/test/scala/sigmastate/utxo/ThresholdSpecification.scala similarity index 98% rename from interpreter/src/test/scala/sigmastate/utxo/ThresholdSpecification.scala rename to sc/src/test/scala/sigmastate/utxo/ThresholdSpecification.scala index bc90421943..f2f797a832 100644 --- a/interpreter/src/test/scala/sigmastate/utxo/ThresholdSpecification.scala +++ b/sc/src/test/scala/sigmastate/utxo/ThresholdSpecification.scala @@ -3,12 +3,11 @@ package sigmastate.utxo import sigmastate.basics.DLogProtocol.{DLogProverInput, ProveDlog} import sigmastate.Values.{ConcreteCollection, FalseLeaf, IntConstant, SigmaPropConstant, SigmaPropValue, TrueLeaf} import sigmastate._ -import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, ErgoLikeContextTesting, ErgoLikeTestInterpreter, ErgoLikeTransactionTesting, SigmaTestingCommons} -import sigmastate.interpreter.Interpreter -import sigmastate.lang.exceptions.CosterException +import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, ErgoLikeContextTesting, ErgoLikeTestInterpreter, ErgoLikeTransactionTesting, CompilerTestingCommons} +import sigmastate.exceptions.CosterException -class ThresholdSpecification extends SigmaTestingCommons - with CrossVersionProps { +class ThresholdSpecification extends CompilerTestingCommons + with CompilerCrossVersionProps { implicit lazy val IR = new TestingIRContext { } diff --git a/interpreter/src/test/scala/sigmastate/utxo/UsingContextPropertiesSpecification.scala b/sc/src/test/scala/sigmastate/utxo/UsingContextPropertiesSpecification.scala similarity index 92% rename from interpreter/src/test/scala/sigmastate/utxo/UsingContextPropertiesSpecification.scala rename to sc/src/test/scala/sigmastate/utxo/UsingContextPropertiesSpecification.scala index 185be9a713..dea73e67b4 100644 --- a/interpreter/src/test/scala/sigmastate/utxo/UsingContextPropertiesSpecification.scala +++ b/sc/src/test/scala/sigmastate/utxo/UsingContextPropertiesSpecification.scala @@ -1,16 +1,16 @@ package sigmastate.utxo -import sigmastate.{TrivialProp, CrossVersionProps} +import sigmastate.{TrivialProp, CompilerCrossVersionProps} import sigmastate.eval.{IRContext, CSigmaProp} import sigmastate.eval.Extensions._ import special.sigma.Context -import sigmastate.helpers.SigmaTestingCommons +import sigmastate.helpers.CompilerTestingCommons import org.ergoplatform.dsl.{SigmaContractSyntax, ContractSpec, TestContractSpec} import org.ergoplatform.ErgoBox import scorex.crypto.hash.Blake2b256 -class UsingContextPropertiesSpecification extends SigmaTestingCommons - with CrossVersionProps { suite => +class UsingContextPropertiesSpecification extends CompilerTestingCommons + with CompilerCrossVersionProps { suite => lazy val spec = TestContractSpec(suite)(new TestingIRContext) lazy val prover = spec.ProvingParty("Alice") private implicit lazy val IR: IRContext = spec.IR diff --git a/interpreter/src/test/scala/sigmastate/utxo/blockchain/BlockchainSimulationSpecification.scala b/sc/src/test/scala/sigmastate/utxo/blockchain/BlockchainSimulationSpecification.scala similarity index 97% rename from interpreter/src/test/scala/sigmastate/utxo/blockchain/BlockchainSimulationSpecification.scala rename to sc/src/test/scala/sigmastate/utxo/blockchain/BlockchainSimulationSpecification.scala index 913f2a9295..ddd5238819 100644 --- a/interpreter/src/test/scala/sigmastate/utxo/blockchain/BlockchainSimulationSpecification.scala +++ b/sc/src/test/scala/sigmastate/utxo/blockchain/BlockchainSimulationSpecification.scala @@ -2,7 +2,7 @@ package sigmastate.utxo.blockchain import java.io.{FileWriter, File} -import sigmastate.CrossVersionProps +import sigmastate.CompilerCrossVersionProps import sigmastate.Values.{TrueLeaf, GetVarBoolean} import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, ErgoLikeTestProvingInterpreter} import sigmastate.interpreter.ContextExtension @@ -12,7 +12,7 @@ import scala.collection.concurrent.TrieMap class BlockchainSimulationSpecification extends BlockchainSimulationTestingCommons - with CrossVersionProps { + with CompilerCrossVersionProps { implicit lazy val IR = new TestingIRContext diff --git a/interpreter/src/test/scala/sigmastate/utxo/blockchain/BlockchainSimulationTestingCommons.scala b/sc/src/test/scala/sigmastate/utxo/blockchain/BlockchainSimulationTestingCommons.scala similarity index 94% rename from interpreter/src/test/scala/sigmastate/utxo/blockchain/BlockchainSimulationTestingCommons.scala rename to sc/src/test/scala/sigmastate/utxo/blockchain/BlockchainSimulationTestingCommons.scala index c465496004..a70519112f 100644 --- a/interpreter/src/test/scala/sigmastate/utxo/blockchain/BlockchainSimulationTestingCommons.scala +++ b/sc/src/test/scala/sigmastate/utxo/blockchain/BlockchainSimulationTestingCommons.scala @@ -1,6 +1,6 @@ package sigmastate.utxo.blockchain -import org.ergoplatform.ErgoBox.TokenId +import org.ergoplatform.ErgoBox.Token import org.ergoplatform._ import scorex.crypto.authds.{ADDigest, ADKey, ADValue} import scorex.crypto.authds.avltree.batch.{BatchAVLProver, Insert, Remove} @@ -8,7 +8,7 @@ import scorex.crypto.hash.{Blake2b256, Digest32} import sigmastate.{AvlTreeData, AvlTreeFlags, Values} import sigmastate.Values.{ErgoTree, LongConstant} import sigmastate.eval._ -import sigmastate.helpers.{BlockchainState, ErgoLikeContextTesting, ErgoLikeTestProvingInterpreter, ErgoTransactionValidator, SigmaTestingCommons} +import sigmastate.helpers.{BlockchainState, ErgoLikeContextTesting, ErgoLikeTestProvingInterpreter, ErgoTransactionValidator, CompilerTestingCommons} import sigmastate.helpers.TestingHelpers._ import sigmastate.utils.Helpers._ import scala.collection.mutable @@ -20,7 +20,7 @@ import sigmastate.utxo.blockchain.BlockchainSimulationTestingCommons.{FullBlock, import scala.annotation.tailrec -trait BlockchainSimulationTestingCommons extends SigmaTestingCommons { +trait BlockchainSimulationTestingCommons extends CompilerTestingCommons { @tailrec protected final def checkState(state: ValidationState, @@ -50,7 +50,7 @@ trait BlockchainSimulationTestingCommons extends SigmaTestingCommons { val txs = boxesToSpend.map { box => val newBoxCandidate = - new ErgoBoxCandidate(10, prop, height, Colls.emptyColl[(TokenId, Long)], Map()) + new ErgoBoxCandidate(10, prop, height, Colls.emptyColl[Token], Map()) val unsignedInput = new UnsignedInput(box.id) val tx = UnsignedErgoLikeTransaction(IndexedSeq(unsignedInput), IndexedSeq(newBoxCandidate)) val context = ErgoLikeContextTesting(height + 1, @@ -75,7 +75,7 @@ trait BlockchainSimulationTestingCommons extends SigmaTestingCommons { } -object BlockchainSimulationTestingCommons extends SigmaTestingCommons { +object BlockchainSimulationTestingCommons extends CompilerTestingCommons { private val MaxBlockCost = 1000000 diff --git a/interpreter/src/test/scala/sigmastate/utxo/examples/AssetsAtomicExchange.scala b/sc/src/test/scala/sigmastate/utxo/examples/AssetsAtomicExchange.scala similarity index 97% rename from interpreter/src/test/scala/sigmastate/utxo/examples/AssetsAtomicExchange.scala rename to sc/src/test/scala/sigmastate/utxo/examples/AssetsAtomicExchange.scala index 17de1cd96f..c27dd1356d 100644 --- a/interpreter/src/test/scala/sigmastate/utxo/examples/AssetsAtomicExchange.scala +++ b/sc/src/test/scala/sigmastate/utxo/examples/AssetsAtomicExchange.scala @@ -2,7 +2,7 @@ package sigmastate.utxo.examples import org.ergoplatform.dsl.ContractSyntax.Token import special.sigma.Context -import org.ergoplatform.ErgoBox.R4 +import org.ergoplatform.ErgoBox.{R4, TokenId} import special.collection.Coll import org.ergoplatform.dsl.{SigmaContractSyntax, ContractSpec, StdContracts} @@ -13,7 +13,7 @@ import org.ergoplatform.dsl.{SigmaContractSyntax, ContractSpec, StdContracts} * @param tokenSeller The party, who wants to sell some amount of token with id == `tokenId`. * */ case class AssetsAtomicExchange[Spec <: ContractSpec] - (val deadline: Int, val tokenId: Coll[Byte], + (val deadline: Int, val tokenId: TokenId, val tokenBuyer: Spec#ProvingParty, val tokenSeller: Spec#ProvingParty) (implicit val spec: Spec) diff --git a/interpreter/src/test/scala/sigmastate/utxo/examples/AssetsAtomicExchangeErgoTests.scala b/sc/src/test/scala/sigmastate/utxo/examples/AssetsAtomicExchangeErgoTests.scala similarity index 87% rename from interpreter/src/test/scala/sigmastate/utxo/examples/AssetsAtomicExchangeErgoTests.scala rename to sc/src/test/scala/sigmastate/utxo/examples/AssetsAtomicExchangeErgoTests.scala index 67304c39b1..069b39e942 100644 --- a/interpreter/src/test/scala/sigmastate/utxo/examples/AssetsAtomicExchangeErgoTests.scala +++ b/sc/src/test/scala/sigmastate/utxo/examples/AssetsAtomicExchangeErgoTests.scala @@ -1,14 +1,15 @@ package sigmastate.utxo.examples -import sigmastate.helpers.SigmaTestingCommons +import sigmastate.helpers.CompilerTestingCommons import org.ergoplatform.dsl.ContractSyntax.Token import org.ergoplatform.dsl.ErgoContractSpec import special.collection.Coll import scorex.crypto.hash.Blake2b256 +import sigmastate.eval.Digest32Coll -class AssetsAtomicExchangeErgoTests extends SigmaTestingCommons { suite => +class AssetsAtomicExchangeErgoTests extends CompilerTestingCommons { suite => lazy val spec = new ErgoContractSpec()(new TestingIRContext) - private lazy val tokenId: Coll[Byte] = spec.Coll(Blake2b256("token1")) + private lazy val tokenId = Digest32Coll @@@ spec.Coll(Blake2b256("token1")) lazy val buyer = spec.ProvingParty("Alice") lazy val seller = spec.ProvingParty("Bob") val ergAmt = 100 diff --git a/interpreter/src/test/scala/sigmastate/utxo/examples/AssetsAtomicExchangeTests.scala b/sc/src/test/scala/sigmastate/utxo/examples/AssetsAtomicExchangeTests.scala similarity index 94% rename from interpreter/src/test/scala/sigmastate/utxo/examples/AssetsAtomicExchangeTests.scala rename to sc/src/test/scala/sigmastate/utxo/examples/AssetsAtomicExchangeTests.scala index 8557a92bf2..b8fe0a8410 100644 --- a/interpreter/src/test/scala/sigmastate/utxo/examples/AssetsAtomicExchangeTests.scala +++ b/sc/src/test/scala/sigmastate/utxo/examples/AssetsAtomicExchangeTests.scala @@ -2,18 +2,17 @@ package sigmastate.utxo.examples import org.ergoplatform.{Height, Outputs, ErgoBox, Self} import org.ergoplatform.ErgoBox.R4 -import sigmastate.helpers.SigmaTestingCommons +import sigmastate.helpers.CompilerTestingCommons import org.ergoplatform.dsl.ContractSyntax.Token import org.ergoplatform.dsl.TestContractSpec import scorex.crypto.hash.Blake2b256 import sigmastate.SCollection.SByteArray import sigmastate._ -import sigmastate.Values.{LongConstant, BlockValue, Value, ByteArrayConstant, ErgoTree, ValDef, ValUse} -import sigmastate.eval.CSigmaProp +import sigmastate.Values.{BlockValue, ByteArrayConstant, LongConstant, ValDef, ValUse, Value} +import sigmastate.eval.{CSigmaProp, Digest32Coll} import sigmastate.eval.Extensions._ import sigmastate.lang.Terms.ValueOps import sigmastate.utxo._ -import special.collection.Coll /** An example of an atomic ergo <=> asset exchange. * Let's assume that Alice is willing to buy 60 assets of type "token1" for 100 ergo coins, and Bob @@ -37,9 +36,9 @@ import special.collection.Coll * * //todo: make an example of multiple orders being matched */ -class AssetsAtomicExchangeTests extends SigmaTestingCommons { suite => +class AssetsAtomicExchangeTests extends CompilerTestingCommons { suite => lazy val spec = TestContractSpec(suite)(new TestingIRContext) - private lazy val tokenId: Coll[Byte] = spec.Coll(Blake2b256("token1")) + private lazy val tokenId = Digest32Coll @@@ spec.Coll(Blake2b256("token1")) lazy val buyer = spec.ProvingParty("Alice") lazy val seller = spec.ProvingParty("Bob") diff --git a/interpreter/src/test/scala/sigmastate/utxo/examples/AssetsPartialFilling.scala b/sc/src/test/scala/sigmastate/utxo/examples/AssetsPartialFilling.scala similarity index 98% rename from interpreter/src/test/scala/sigmastate/utxo/examples/AssetsPartialFilling.scala rename to sc/src/test/scala/sigmastate/utxo/examples/AssetsPartialFilling.scala index 448f9e4801..ad5126d626 100644 --- a/interpreter/src/test/scala/sigmastate/utxo/examples/AssetsPartialFilling.scala +++ b/sc/src/test/scala/sigmastate/utxo/examples/AssetsPartialFilling.scala @@ -1,5 +1,6 @@ package sigmastate.utxo.examples +import org.ergoplatform.ErgoBox.TokenId import org.ergoplatform.dsl.ContractSyntax.Token import special.sigma.Context import special.collection.Coll @@ -9,7 +10,7 @@ import org.ergoplatform.dsl.{SigmaContractSyntax, ContractSpec, StdContracts} * @param tokenBuyer The party, who wants to buy some amount of token with id == `tokenId`. * @param tokenSeller The party, who wants to sell some amount of token with id == `tokenId`. */ case class AssetsPartialFilling[Spec <: ContractSpec] - (deadline: Int, token1: Coll[Byte], + (deadline: Int, token1: TokenId, tokenBuyer: Spec#ProvingParty, tokenSeller: Spec#ProvingParty) (implicit val spec: Spec) extends SigmaContractSyntax with StdContracts diff --git a/interpreter/src/test/scala/sigmastate/utxo/examples/AtomicSwapExampleSpecification.scala b/sc/src/test/scala/sigmastate/utxo/examples/AtomicSwapExampleSpecification.scala similarity index 98% rename from interpreter/src/test/scala/sigmastate/utxo/examples/AtomicSwapExampleSpecification.scala rename to sc/src/test/scala/sigmastate/utxo/examples/AtomicSwapExampleSpecification.scala index efcade8731..ef004f602a 100644 --- a/interpreter/src/test/scala/sigmastate/utxo/examples/AtomicSwapExampleSpecification.scala +++ b/sc/src/test/scala/sigmastate/utxo/examples/AtomicSwapExampleSpecification.scala @@ -10,7 +10,7 @@ import sigmastate.helpers._ import sigmastate.lang.Terms._ import sigmastate.utxo.SizeOf -class AtomicSwapExampleSpecification extends SigmaTestingCommons with CrossVersionProps { +class AtomicSwapExampleSpecification extends CompilerTestingCommons with CompilerCrossVersionProps { private implicit lazy val IR: TestingIRContext = new TestingIRContext /** diff --git a/interpreter/src/test/scala/sigmastate/utxo/examples/CoinEmissionSpecification.scala b/sc/src/test/scala/sigmastate/utxo/examples/CoinEmissionSpecification.scala similarity index 98% rename from interpreter/src/test/scala/sigmastate/utxo/examples/CoinEmissionSpecification.scala rename to sc/src/test/scala/sigmastate/utxo/examples/CoinEmissionSpecification.scala index f2b898e21b..382962c8fc 100644 --- a/interpreter/src/test/scala/sigmastate/utxo/examples/CoinEmissionSpecification.scala +++ b/sc/src/test/scala/sigmastate/utxo/examples/CoinEmissionSpecification.scala @@ -3,7 +3,7 @@ package sigmastate.utxo.examples import org.ergoplatform._ import org.ergoplatform.settings.ErgoAlgos import sigmastate.Values.{BlockValue, ErgoTree, IntConstant, LongConstant, ValDef, ValUse} -import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, ErgoLikeContextTesting, SigmaPPrint, SigmaTestingCommons} +import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, ErgoLikeContextTesting, SigmaPPrint, CompilerTestingCommons} import sigmastate.helpers.TestingHelpers._ import sigmastate.interpreter.ContextExtension import sigmastate.interpreter.Interpreter.{ScriptNameProp, emptyEnv} @@ -20,8 +20,8 @@ import sigmastate.eval._ * This script is corresponding to the whitepaper. Please note that Ergo has different contract * defined in ErgoScriptPredef. */ -class CoinEmissionSpecification extends SigmaTestingCommons - with CrossVersionProps { +class CoinEmissionSpecification extends CompilerTestingCommons + with CompilerCrossVersionProps { // don't use TestingIRContext, this suite also serves the purpose of testing the RuntimeIRContext implicit lazy val IR: TestingIRContext = new TestingIRContext { // uncomment if you want to log script evaluation diff --git a/interpreter/src/test/scala/sigmastate/utxo/examples/ColdWalletAdvContractExampleSpecification.scala b/sc/src/test/scala/sigmastate/utxo/examples/ColdWalletAdvContractExampleSpecification.scala similarity index 96% rename from interpreter/src/test/scala/sigmastate/utxo/examples/ColdWalletAdvContractExampleSpecification.scala rename to sc/src/test/scala/sigmastate/utxo/examples/ColdWalletAdvContractExampleSpecification.scala index 2edfcb67e8..1230177437 100644 --- a/interpreter/src/test/scala/sigmastate/utxo/examples/ColdWalletAdvContractExampleSpecification.scala +++ b/sc/src/test/scala/sigmastate/utxo/examples/ColdWalletAdvContractExampleSpecification.scala @@ -2,16 +2,16 @@ package sigmastate.utxo.examples import org.ergoplatform.ErgoBox.{R6, R4, R5} import org.ergoplatform._ -import sigmastate.{AvlTreeData, CrossVersionProps} +import sigmastate.{AvlTreeData, CompilerCrossVersionProps} import sigmastate.Values.{LongConstant, IntConstant} -import sigmastate.helpers.{ErgoLikeContextTesting, ErgoLikeTestInterpreter, ErgoLikeTestProvingInterpreter, SigmaTestingCommons, ContextEnrichingTestProvingInterpreter} +import sigmastate.helpers.{ErgoLikeContextTesting, ErgoLikeTestInterpreter, ErgoLikeTestProvingInterpreter, CompilerTestingCommons, ContextEnrichingTestProvingInterpreter} import sigmastate.helpers.TestingHelpers._ import sigmastate.interpreter.Interpreter.ScriptNameProp import sigmastate.lang.Terms._ -class ColdWalletAdvContractExampleSpecification extends SigmaTestingCommons - with CrossVersionProps { +class ColdWalletAdvContractExampleSpecification extends CompilerTestingCommons + with CompilerCrossVersionProps { private implicit lazy val IR: TestingIRContext = new TestingIRContext import ErgoAddressEncoder._ diff --git a/interpreter/src/test/scala/sigmastate/utxo/examples/ColdWalletContractExampleSpecification.scala b/sc/src/test/scala/sigmastate/utxo/examples/ColdWalletContractExampleSpecification.scala similarity index 97% rename from interpreter/src/test/scala/sigmastate/utxo/examples/ColdWalletContractExampleSpecification.scala rename to sc/src/test/scala/sigmastate/utxo/examples/ColdWalletContractExampleSpecification.scala index 8a10c5745d..ffd4b17018 100644 --- a/interpreter/src/test/scala/sigmastate/utxo/examples/ColdWalletContractExampleSpecification.scala +++ b/sc/src/test/scala/sigmastate/utxo/examples/ColdWalletContractExampleSpecification.scala @@ -2,16 +2,16 @@ package sigmastate.utxo.examples import org.ergoplatform.ErgoBox.{R4, R5} import org.ergoplatform._ -import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, ErgoLikeContextTesting, SigmaTestingCommons, ErgoLikeTestInterpreter} +import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, ErgoLikeContextTesting, CompilerTestingCommons, ErgoLikeTestInterpreter} import sigmastate.helpers.TestingHelpers._ -import sigmastate.{AvlTreeData, CrossVersionProps} +import sigmastate.{AvlTreeData, CompilerCrossVersionProps} import sigmastate.Values.{LongConstant, IntConstant} import sigmastate.interpreter.Interpreter.ScriptNameProp import sigmastate.lang.Terms._ -class ColdWalletContractExampleSpecification extends SigmaTestingCommons - with CrossVersionProps { +class ColdWalletContractExampleSpecification extends CompilerTestingCommons + with CompilerCrossVersionProps { private implicit lazy val IR: TestingIRContext = new TestingIRContext import ErgoAddressEncoder._ diff --git a/interpreter/src/test/scala/sigmastate/utxo/examples/CoopExampleSpecification.scala b/sc/src/test/scala/sigmastate/utxo/examples/CoopExampleSpecification.scala similarity index 98% rename from interpreter/src/test/scala/sigmastate/utxo/examples/CoopExampleSpecification.scala rename to sc/src/test/scala/sigmastate/utxo/examples/CoopExampleSpecification.scala index 76d485f91a..6f1136e31a 100644 --- a/interpreter/src/test/scala/sigmastate/utxo/examples/CoopExampleSpecification.scala +++ b/sc/src/test/scala/sigmastate/utxo/examples/CoopExampleSpecification.scala @@ -6,13 +6,13 @@ import org.scalatest.TryValues._ import sigmastate.basics.DLogProtocol.{ProveDlog, DLogProverInput} import scorex.crypto.hash.Blake2b256 import sigmastate.Values.{ByteArrayConstant, ErgoTree, BooleanConstant} -import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, ErgoLikeContextTesting, SigmaTestingCommons, ErgoLikeTestInterpreter} +import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, ErgoLikeContextTesting, CompilerTestingCommons, ErgoLikeTestInterpreter} import sigmastate.helpers.TestingHelpers._ import sigmastate.lang.Terms._ -import sigmastate.{AvlTreeData, CrossVersionProps} +import sigmastate.{AvlTreeData, CompilerCrossVersionProps} -class CoopExampleSpecification extends SigmaTestingCommons - with CrossVersionProps { +class CoopExampleSpecification extends CompilerTestingCommons + with CompilerCrossVersionProps { implicit lazy val IR = new TestingIRContext def mkTxFromOutputs(ergoBox: ErgoBox*): ErgoLikeTransaction = { diff --git a/interpreter/src/test/scala/sigmastate/utxo/examples/CrowdFunding.scala b/sc/src/test/scala/sigmastate/utxo/examples/CrowdFunding.scala similarity index 100% rename from interpreter/src/test/scala/sigmastate/utxo/examples/CrowdFunding.scala rename to sc/src/test/scala/sigmastate/utxo/examples/CrowdFunding.scala diff --git a/interpreter/src/test/scala/sigmastate/utxo/examples/CrowdFundingTests.scala b/sc/src/test/scala/sigmastate/utxo/examples/CrowdFundingTests.scala similarity index 95% rename from interpreter/src/test/scala/sigmastate/utxo/examples/CrowdFundingTests.scala rename to sc/src/test/scala/sigmastate/utxo/examples/CrowdFundingTests.scala index 1b3bc924e7..173eade2a6 100644 --- a/interpreter/src/test/scala/sigmastate/utxo/examples/CrowdFundingTests.scala +++ b/sc/src/test/scala/sigmastate/utxo/examples/CrowdFundingTests.scala @@ -1,9 +1,9 @@ package sigmastate.utxo.examples -import sigmastate.helpers.SigmaTestingCommons +import sigmastate.helpers.CompilerTestingCommons import org.ergoplatform.dsl.TestContractSpec -class CrowdFundingTests extends SigmaTestingCommons { suite => +class CrowdFundingTests extends CompilerTestingCommons { suite => lazy val spec = TestContractSpec(suite)(new TestingIRContext) lazy val backer = spec.ProvingParty("Alice") lazy val project = spec.ProvingParty("Bob") diff --git a/interpreter/src/test/scala/sigmastate/utxo/examples/DHTupleExampleSpecification.scala b/sc/src/test/scala/sigmastate/utxo/examples/DHTupleExampleSpecification.scala similarity index 89% rename from interpreter/src/test/scala/sigmastate/utxo/examples/DHTupleExampleSpecification.scala rename to sc/src/test/scala/sigmastate/utxo/examples/DHTupleExampleSpecification.scala index 657beb98ab..e4984a25c1 100644 --- a/interpreter/src/test/scala/sigmastate/utxo/examples/DHTupleExampleSpecification.scala +++ b/sc/src/test/scala/sigmastate/utxo/examples/DHTupleExampleSpecification.scala @@ -4,18 +4,18 @@ package sigmastate.utxo.examples import java.math.BigInteger import org.ergoplatform.ErgoBox.{R4, R5} -import sigmastate.{AvlTreeData, CrossVersionProps} +import sigmastate.{AvlTreeData, CompilerCrossVersionProps} import sigmastate.Values.GroupElementConstant import sigmastate.basics.DLogProtocol.ProveDlog -import sigmastate.basics.{ProveDHTuple, DiffieHellmanTupleProverInput} -import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, ErgoLikeContextTesting, SigmaTestingCommons, ErgoLikeTestInterpreter} +import sigmastate.basics.{DiffieHellmanTupleProverInput, ProveDHTuple, CryptoConstants} +import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, ErgoLikeContextTesting, CompilerTestingCommons, ErgoLikeTestInterpreter} import sigmastate.helpers.TestingHelpers._ -import sigmastate.interpreter.CryptoConstants import sigmastate.interpreter.Interpreter._ import sigmastate.lang.Terms._ +import sigmastate.eval.Extensions._ -class DHTupleExampleSpecification extends SigmaTestingCommons - with CrossVersionProps { +class DHTupleExampleSpecification extends CompilerTestingCommons + with CompilerCrossVersionProps { private implicit lazy val IR = new TestingIRContext /** * let Alice's secret be x and Bob's be y @@ -39,8 +39,8 @@ class DHTupleExampleSpecification extends SigmaTestingCommons val env = Map( ScriptNameProp -> "env", - "g" -> g, - "g_x" -> g_x + "g" -> g.toGroupElement, + "g_x" -> g_x.toGroupElement ) val script = mkTestErgoTree(compile(env, diff --git a/interpreter/src/test/scala/sigmastate/utxo/examples/DemurrageExampleSpecification.scala b/sc/src/test/scala/sigmastate/utxo/examples/DemurrageExampleSpecification.scala similarity index 97% rename from interpreter/src/test/scala/sigmastate/utxo/examples/DemurrageExampleSpecification.scala rename to sc/src/test/scala/sigmastate/utxo/examples/DemurrageExampleSpecification.scala index 99f34e86c0..8eb5b3b04c 100644 --- a/interpreter/src/test/scala/sigmastate/utxo/examples/DemurrageExampleSpecification.scala +++ b/sc/src/test/scala/sigmastate/utxo/examples/DemurrageExampleSpecification.scala @@ -4,13 +4,13 @@ import sigmastate.interpreter.Interpreter._ import org.ergoplatform._ import sigmastate.Values.ShortConstant import sigmastate._ -import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, ErgoLikeContextTesting, ErgoLikeTestInterpreter, SigmaTestingCommons} +import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, ErgoLikeContextTesting, ErgoLikeTestInterpreter, CompilerTestingCommons} import sigmastate.helpers.TestingHelpers._ import sigmastate.interpreter.ContextExtension import sigmastate.lang.Terms._ -class DemurrageExampleSpecification extends SigmaTestingCommons - with CrossVersionProps { +class DemurrageExampleSpecification extends CompilerTestingCommons + with CompilerCrossVersionProps { override val printVersions: Boolean = true implicit lazy val IR = new TestingIRContext diff --git a/interpreter/src/test/scala/sigmastate/utxo/examples/DummyExamplesSpecification.scala b/sc/src/test/scala/sigmastate/utxo/examples/DummyExamplesSpecification.scala similarity index 97% rename from interpreter/src/test/scala/sigmastate/utxo/examples/DummyExamplesSpecification.scala rename to sc/src/test/scala/sigmastate/utxo/examples/DummyExamplesSpecification.scala index 8e1cf9936a..9206a6502c 100644 --- a/interpreter/src/test/scala/sigmastate/utxo/examples/DummyExamplesSpecification.scala +++ b/sc/src/test/scala/sigmastate/utxo/examples/DummyExamplesSpecification.scala @@ -3,12 +3,12 @@ package sigmastate.utxo.examples import org.ergoplatform.ErgoBox import org.ergoplatform.dsl.{ContractSpec, SigmaContractSyntax, StdContracts, TestContractSpec} import scorex.crypto.hash -import sigmastate.helpers.SigmaTestingCommons +import sigmastate.helpers.CompilerTestingCommons import special.collection.Coll import special.sigma.{Box, Context} import sigmastate.eval.Extensions -class DummyExamplesSpecification extends SigmaTestingCommons { suite => +class DummyExamplesSpecification extends CompilerTestingCommons { suite => implicit lazy val IR = new TestingIRContext private val reg1 = ErgoBox.nonMandatoryRegisters(0) diff --git a/interpreter/src/test/scala/sigmastate/utxo/examples/ExecuteFromExamplesSpecification.scala b/sc/src/test/scala/sigmastate/utxo/examples/ExecuteFromExamplesSpecification.scala similarity index 93% rename from interpreter/src/test/scala/sigmastate/utxo/examples/ExecuteFromExamplesSpecification.scala rename to sc/src/test/scala/sigmastate/utxo/examples/ExecuteFromExamplesSpecification.scala index b053d1bf8b..34fbc5e356 100644 --- a/interpreter/src/test/scala/sigmastate/utxo/examples/ExecuteFromExamplesSpecification.scala +++ b/sc/src/test/scala/sigmastate/utxo/examples/ExecuteFromExamplesSpecification.scala @@ -2,11 +2,11 @@ package sigmastate.utxo.examples import org.ergoplatform._ import org.ergoplatform.dsl.{ContractSpec, SigmaContractSyntax, StdContracts, TestContractSpec} -import sigmastate.helpers.SigmaTestingCommons +import sigmastate.helpers.CompilerTestingCommons import special.sigma.Context import sigmastate.eval.Extensions._ -class ExecuteFromExamplesSpecification extends SigmaTestingCommons { suite => +class ExecuteFromExamplesSpecification extends CompilerTestingCommons { suite => implicit lazy val IR = new TestingIRContext private val reg1 = ErgoBox.nonMandatoryRegisters(0) diff --git a/interpreter/src/test/scala/sigmastate/utxo/examples/FsmExampleSpecification.scala b/sc/src/test/scala/sigmastate/utxo/examples/FsmExampleSpecification.scala similarity index 98% rename from interpreter/src/test/scala/sigmastate/utxo/examples/FsmExampleSpecification.scala rename to sc/src/test/scala/sigmastate/utxo/examples/FsmExampleSpecification.scala index 11170795a7..9d11749d9b 100644 --- a/interpreter/src/test/scala/sigmastate/utxo/examples/FsmExampleSpecification.scala +++ b/sc/src/test/scala/sigmastate/utxo/examples/FsmExampleSpecification.scala @@ -10,15 +10,15 @@ import sigmastate.Values._ import sigmastate._ import sigmastate.eval._ import sigmastate.lang.Terms._ -import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, ErgoLikeContextTesting, ErgoLikeTestInterpreter, SigmaTestingCommons} +import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, ErgoLikeContextTesting, ErgoLikeTestInterpreter, CompilerTestingCommons} import sigmastate.helpers.TestingHelpers._ import sigmastate.interpreter.Interpreter.{ScriptNameProp, emptyEnv} import sigmastate.serialization.ValueSerializer import sigmastate.utxo._ -class FsmExampleSpecification extends SigmaTestingCommons - with CrossVersionProps { +class FsmExampleSpecification extends CompilerTestingCommons + with CompilerCrossVersionProps { private implicit lazy val IR: TestingIRContext = new TestingIRContext /** * Similarly to the MAST-like example (in the MASTExampleSpecification class), we can do more complex contracts, diff --git a/interpreter/src/test/scala/sigmastate/utxo/examples/IcoExample.scala b/sc/src/test/scala/sigmastate/utxo/examples/IcoExample.scala similarity index 97% rename from interpreter/src/test/scala/sigmastate/utxo/examples/IcoExample.scala rename to sc/src/test/scala/sigmastate/utxo/examples/IcoExample.scala index 6500b280b3..fa60cc20b9 100644 --- a/interpreter/src/test/scala/sigmastate/utxo/examples/IcoExample.scala +++ b/sc/src/test/scala/sigmastate/utxo/examples/IcoExample.scala @@ -10,11 +10,12 @@ import scorex.crypto.authds.{ADKey, ADValue} import scorex.crypto.hash.{Blake2b256, Digest32} import sigmastate.Values._ import sigmastate._ -import sigmastate.eval.{IRContext, _} +import sigmastate.basics.CryptoConstants +import sigmastate.eval._ import sigmastate.helpers.TestingHelpers._ -import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, ErgoLikeContextTesting, ErgoLikeTestProvingInterpreter, SigmaTestingCommons} +import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, ErgoLikeContextTesting, ErgoLikeTestProvingInterpreter, CompilerTestingCommons} import sigmastate.interpreter.Interpreter.ScriptNameProp -import sigmastate.interpreter.{CryptoConstants, Interpreter} +import sigmastate.interpreter.Interpreter import sigmastate.lang.Terms._ import sigmastate.serialization.ErgoTreeSerializer import sigmastate.serialization.ErgoTreeSerializer.DefaultSerializer @@ -231,8 +232,8 @@ by miners via storage rent mechanism, potentially for decades or even centuries. reasonable to have an additional input from the project with the value equal to the value of the fee output. And so on. */ -class IcoExample extends SigmaTestingCommons - with CrossVersionProps with BeforeAndAfterAll { suite => +class IcoExample extends CompilerTestingCommons + with CompilerCrossVersionProps with BeforeAndAfterAll { suite => // Not mixed with TestContext since it is not possible to call commpiler.compile outside tests if mixed implicit lazy val IR: IRContext = new IRContext {} @@ -241,7 +242,7 @@ class IcoExample extends SigmaTestingCommons lazy val project = new ErgoLikeTestProvingInterpreter() private val miningRewardsDelay = 720 - private val feeProp = ErgoScriptPredef.feeProposition(miningRewardsDelay) // create ErgoTree v0 + private val feeProp = ErgoTreePredef.feeProposition(miningRewardsDelay) // create ErgoTree v0 private val feeBytes = feeProp.bytes val env = Map( @@ -440,7 +441,7 @@ class IcoExample extends SigmaTestingCommons val projectBoxBeforeClosing = testBox(10, issuanceTree, 0, Seq(), Map(R4 -> ByteArrayConstant(Array.emptyByteArray), R5 -> AvlTreeConstant(openTreeData))) - val tokenId = Digest32 @@@ projectBoxBeforeClosing.id + val tokenId = Digest32Coll @@@ Colls.fromArray(projectBoxBeforeClosing.id) val closedTreeData = SigmaDsl.avlTree(new AvlTreeData(digest, AvlTreeFlags.RemoveOnly, 32, None)) val projectBoxAfterClosing = testBox(1, withdrawalTree, 0, @@ -503,7 +504,7 @@ class IcoExample extends SigmaTestingCommons val finalTree = new AvlTreeData(avlProver.digest, AvlTreeFlags.RemoveOnly, 32, None) - val tokenId = Digest32 @@ Array.fill(32)(Random.nextInt(100).toByte) + val tokenId = Digest32Coll @@ Colls.fromArray(Array.fill(32)(Random.nextInt(100).toByte)) val withdrawalAmounts = funderProps.take(withdrawalsCount).map { case (prop, v) => val tv = Longs.fromByteArray(v) diff --git a/interpreter/src/test/scala/sigmastate/utxo/examples/LetsSpecification.scala b/sc/src/test/scala/sigmastate/utxo/examples/LetsSpecification.scala similarity index 91% rename from interpreter/src/test/scala/sigmastate/utxo/examples/LetsSpecification.scala rename to sc/src/test/scala/sigmastate/utxo/examples/LetsSpecification.scala index aafaa92419..f2d409aaab 100644 --- a/interpreter/src/test/scala/sigmastate/utxo/examples/LetsSpecification.scala +++ b/sc/src/test/scala/sigmastate/utxo/examples/LetsSpecification.scala @@ -3,12 +3,12 @@ package sigmastate.utxo.examples import org.ergoplatform._ import org.ergoplatform.ErgoBox.{R4, R5} import scorex.crypto.authds.{ADKey, ADValue} -import scorex.crypto.authds.avltree.batch.{Lookup, BatchAVLProver, Insert} -import scorex.crypto.hash.{Digest32, Blake2b256} -import sigmastate.{AvlTreeData, AvlTreeFlags, TrivialProp, CrossVersionProps} -import sigmastate.Values.{ByteArrayConstant, AvlTreeConstant, SigmaPropConstant, LongConstant} -import sigmastate.eval.{IRContext, SigmaDsl} -import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, ErgoLikeContextTesting, ErgoLikeTestProvingInterpreter, SigmaTestingCommons} +import scorex.crypto.authds.avltree.batch.{BatchAVLProver, Insert, Lookup} +import scorex.crypto.hash.{Blake2b256, Digest32} +import sigmastate.{AvlTreeData, AvlTreeFlags, CompilerCrossVersionProps, TrivialProp} +import sigmastate.Values.{AvlTreeConstant, ByteArrayConstant, LongConstant, SigmaPropConstant} +import sigmastate.eval.{Colls, Digest32Coll, IRContext, SigmaDsl} +import sigmastate.helpers.{CompilerTestingCommons, ContextEnrichingTestProvingInterpreter, ErgoLikeContextTesting, ErgoLikeTestProvingInterpreter} import sigmastate.helpers.TestingHelpers._ import sigmastate.interpreter.Interpreter.ScriptNameProp import sigmastate.serialization.ErgoTreeSerializer @@ -166,18 +166,18 @@ import scala.util.Random some day this article will be continued! */ -class LetsSpecification extends SigmaTestingCommons with CrossVersionProps { suite => +class LetsSpecification extends CompilerTestingCommons with CompilerCrossVersionProps { suite => // Not mixed with TestContext since it is not possible to call compiler.compile outside tests if mixed implicit lazy val IR: IRContext = new TestingIRContext lazy val project = new ErgoLikeTestProvingInterpreter() - val letsTokenId = Digest32 @@ Array.fill(32)(Random.nextInt(100).toByte) + val letsTokenId = Digest32Coll @@ Colls.fromArray(Array.fill(32)(Random.nextInt(100).toByte)) val env = Map(ScriptNameProp -> "withdrawalScriptEnv", "letsToken" -> ByteArrayConstant(letsTokenId)) private val miningRewardsDelay = 720 - private val feeProp = ErgoScriptPredef.feeProposition(miningRewardsDelay) // create ErgoTree v0 + private val feeProp = ErgoTreePredef.feeProposition(miningRewardsDelay) // create ErgoTree v0 lazy val exchangeScript = compile(env, """{ @@ -291,9 +291,9 @@ class LetsSpecification extends SigmaTestingCommons with CrossVersionProps { sui R4 -> AvlTreeConstant(SigmaDsl.avlTree(initTreeData)), R5 -> SigmaPropConstant(TrivialProp.TrueProp))) - val userTokenId = Digest32 @@@ projectBoxBefore.id + val userTokenId = Digest32Coll @@@ Colls.fromArray(projectBoxBefore.id) - avlProver.performOneOperation(Insert(ADKey @@@ userTokenId, ADValue @@ Array.emptyByteArray)) + avlProver.performOneOperation(Insert(ADKey @@@ userTokenId.toArray, ADValue @@ Array.emptyByteArray)) val proof = avlProver.generateProof() val endTree = new AvlTreeData(avlProver.digest, AvlTreeFlags.InsertOnly, 32, None) @@ -326,18 +326,18 @@ class LetsSpecification extends SigmaTestingCommons with CrossVersionProps { sui property("exchange") { - val userTokenId0 = Digest32 @@ Array.fill(32)(Random.nextInt(100).toByte) - val userTokenId1 = Digest32 @@ Array.fill(32)(Random.nextInt(100).toByte) + val userTokenId0 = Digest32Coll @@ Colls.fromArray(Array.fill(32)(Random.nextInt(100).toByte)) + val userTokenId1 = Digest32Coll @@ Colls.fromArray(Array.fill(32)(Random.nextInt(100).toByte)) val avlProver = new BatchAVLProver[Digest32, Blake2b256.type](keyLength = 32, None) - avlProver.performOneOperation(Insert(ADKey @@@ userTokenId0, ADValue @@ Array.emptyByteArray)) - avlProver.performOneOperation(Insert(ADKey @@@ userTokenId1, ADValue @@ Array.emptyByteArray)) + avlProver.performOneOperation(Insert(ADKey @@@ userTokenId0.toArray, ADValue @@ Array.emptyByteArray)) + avlProver.performOneOperation(Insert(ADKey @@@ userTokenId1.toArray, ADValue @@ Array.emptyByteArray)) val digest = avlProver.digest avlProver.generateProof() val initTreeData = new AvlTreeData(digest, AvlTreeFlags.InsertOnly, 32, None) - avlProver.performOneOperation(Lookup(ADKey @@@ userTokenId0)) - avlProver.performOneOperation(Lookup(ADKey @@@ userTokenId1)) + avlProver.performOneOperation(Lookup(ADKey @@@ userTokenId0.toArray)) + avlProver.performOneOperation(Lookup(ADKey @@@ userTokenId1.toArray)) val proof = avlProver.generateProof() val managementTree = mkTestErgoTree(managementScript) diff --git a/interpreter/src/test/scala/sigmastate/utxo/examples/MASTExampleSpecification.scala b/sc/src/test/scala/sigmastate/utxo/examples/MASTExampleSpecification.scala similarity index 97% rename from interpreter/src/test/scala/sigmastate/utxo/examples/MASTExampleSpecification.scala rename to sc/src/test/scala/sigmastate/utxo/examples/MASTExampleSpecification.scala index 2de3395431..f1fbfc3765 100644 --- a/interpreter/src/test/scala/sigmastate/utxo/examples/MASTExampleSpecification.scala +++ b/sc/src/test/scala/sigmastate/utxo/examples/MASTExampleSpecification.scala @@ -7,7 +7,7 @@ import scorex.crypto.hash.{Blake2b256, Digest32} import sigmastate.SCollection.SByteArray import sigmastate.Values._ import sigmastate._ -import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, ErgoLikeContextTesting, ErgoLikeTestInterpreter, SigmaTestingCommons} +import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, ErgoLikeContextTesting, ErgoLikeTestInterpreter, CompilerTestingCommons} import sigmastate.helpers.TestingHelpers._ import sigmastate.lang.Terms._ import sigmastate.interpreter.Interpreter._ @@ -26,8 +26,8 @@ import scala.util.Random * remain unrevealed, providing more privacy and saving space in a blockchain. * See more at https://bitcointechtalk.com/what-is-a-bitcoin-merklized-abstract-syntax-tree-mast-33fdf2da5e2f */ -class MASTExampleSpecification extends SigmaTestingCommons - with CrossVersionProps { +class MASTExampleSpecification extends CompilerTestingCommons + with CompilerCrossVersionProps { implicit lazy val IR = new TestingIRContext private val reg1 = ErgoBox.nonMandatoryRegisters.head diff --git a/interpreter/src/test/scala/sigmastate/utxo/examples/MixExampleSpecification.scala b/sc/src/test/scala/sigmastate/utxo/examples/MixExampleSpecification.scala similarity index 95% rename from interpreter/src/test/scala/sigmastate/utxo/examples/MixExampleSpecification.scala rename to sc/src/test/scala/sigmastate/utxo/examples/MixExampleSpecification.scala index 6185145aa3..c0e9ca1d73 100644 --- a/interpreter/src/test/scala/sigmastate/utxo/examples/MixExampleSpecification.scala +++ b/sc/src/test/scala/sigmastate/utxo/examples/MixExampleSpecification.scala @@ -4,19 +4,19 @@ import java.math.BigInteger import org.ergoplatform.ErgoBox.{R4, R5} import scorex.crypto.hash.Blake2b256 -import sigmastate.{AvlTreeData, CrossVersionProps} +import sigmastate.{AvlTreeData, CompilerCrossVersionProps} import sigmastate.Values.GroupElementConstant import sigmastate.basics.DLogProtocol.ProveDlog -import sigmastate.basics.{ProveDHTuple, DiffieHellmanTupleProverInput} -import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, ErgoLikeContextTesting, SigmaTestingCommons, ErgoLikeTestInterpreter} +import sigmastate.basics.{DiffieHellmanTupleProverInput, ProveDHTuple, CryptoConstants} +import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, ErgoLikeContextTesting, CompilerTestingCommons, ErgoLikeTestInterpreter} import sigmastate.helpers.TestingHelpers._ -import sigmastate.interpreter.CryptoConstants import sigmastate.interpreter.Interpreter._ import sigmastate.lang.Terms._ import sigmastate.eval._ +import sigmastate.eval.Extensions._ -class MixExampleSpecification extends SigmaTestingCommons - with CrossVersionProps { +class MixExampleSpecification extends CompilerTestingCommons + with CompilerCrossVersionProps { private implicit lazy val IR: TestingIRContext = new TestingIRContext property("Evaluation - Mix Example") { @@ -41,8 +41,8 @@ class MixExampleSpecification extends SigmaTestingCommons val fullMixEnv = Map( ScriptNameProp -> "fullMixEnv", - "g" -> g, - "gX" -> gX + "g" -> g.toGroupElement, + "gX" -> gX.toGroupElement ) ProveDlog(gX) shouldBe alicePubKey @@ -60,8 +60,8 @@ class MixExampleSpecification extends SigmaTestingCommons val halfMixEnv = Map( ScriptNameProp -> "halfMixEnv", - "g" -> g, - "gX" -> gX, + "g" -> g.toGroupElement, + "gX" -> gX.toGroupElement, "fullMixScriptHash" -> Blake2b256(fullMixScript.bytes) ) diff --git a/interpreter/src/test/scala/sigmastate/utxo/examples/OracleDataInputsExamplesSpecification.scala b/sc/src/test/scala/sigmastate/utxo/examples/OracleDataInputsExamplesSpecification.scala similarity index 92% rename from interpreter/src/test/scala/sigmastate/utxo/examples/OracleDataInputsExamplesSpecification.scala rename to sc/src/test/scala/sigmastate/utxo/examples/OracleDataInputsExamplesSpecification.scala index bcf1284b9f..002d1836c9 100644 --- a/interpreter/src/test/scala/sigmastate/utxo/examples/OracleDataInputsExamplesSpecification.scala +++ b/sc/src/test/scala/sigmastate/utxo/examples/OracleDataInputsExamplesSpecification.scala @@ -4,17 +4,18 @@ import org.ergoplatform._ import org.ergoplatform.dsl.ContractSyntax.Token import org.ergoplatform.dsl.{ContractSpec, SigmaContractSyntax, StdContracts, TestContractSpec} import scorex.crypto.hash.Blake2b256 -import sigmastate.helpers.SigmaTestingCommons +import sigmastate.eval.Digest32Coll +import sigmastate.helpers.CompilerTestingCommons import special.collection.Coll import special.sigma.Context -class OracleDataInputsExamplesSpecification extends SigmaTestingCommons { suite => +class OracleDataInputsExamplesSpecification extends CompilerTestingCommons { suite => implicit lazy val IR: TestingIRContext = new TestingIRContext private val reg1 = ErgoBox.nonMandatoryRegisters(0) private val reg2 = ErgoBox.nonMandatoryRegisters(1) - private lazy val tokenId: Coll[Byte] = spec.Coll(Blake2b256("token1")) + private lazy val tokenId = Digest32Coll @@@ spec.Coll(Blake2b256("token1")) case class OracleContract[Spec <: ContractSpec] ( temperature: Long, diff --git a/interpreter/src/test/scala/sigmastate/utxo/examples/OracleExamplesSpecification.scala b/sc/src/test/scala/sigmastate/utxo/examples/OracleExamplesSpecification.scala similarity index 94% rename from interpreter/src/test/scala/sigmastate/utxo/examples/OracleExamplesSpecification.scala rename to sc/src/test/scala/sigmastate/utxo/examples/OracleExamplesSpecification.scala index 5ec589587f..aab0ac99e9 100644 --- a/interpreter/src/test/scala/sigmastate/utxo/examples/OracleExamplesSpecification.scala +++ b/sc/src/test/scala/sigmastate/utxo/examples/OracleExamplesSpecification.scala @@ -1,29 +1,28 @@ package sigmastate.utxo.examples -import java.security.SecureRandom - import scorex.utils.Longs import org.ergoplatform.ErgoBox.RegisterId -import scorex.crypto.authds.avltree.batch.{BatchAVLProver, Insert, Lookup} -import scorex.crypto.authds.{ADKey, ADValue} +import scorex.crypto.authds.avltree.batch.{BatchAVLProver, Lookup, Insert} +import scorex.crypto.authds.{ADValue, ADKey} import scorex.crypto.hash.{Blake2b256, Digest32} import sigmastate.SCollection.SByteArray import sigmastate.Values._ import sigmastate._ import sigmastate.eval._ import sigmastate.lang.Terms._ -import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, ErgoLikeContextTesting, ErgoLikeTestInterpreter, SigmaTestingCommons} +import sigmastate.helpers.{ErgoLikeTestInterpreter, ContextEnrichingTestProvingInterpreter, ErgoLikeContextTesting, CompilerTestingCommons} import sigmastate.helpers.TestingHelpers._ -import sigmastate.interpreter.CryptoConstants import org.ergoplatform._ -import org.ergoplatform.dsl.{ContractSpec, SigmaContractSyntax, StdContracts, TestContractSpec} -import sigmastate.interpreter.Interpreter.{ScriptNameProp, emptyEnv} +import org.ergoplatform.dsl.{StdContracts, ContractSpec, TestContractSpec, SigmaContractSyntax} +import sigmastate.basics.CryptoConstants +import sigmastate.crypto.CryptoFacade +import sigmastate.interpreter.Interpreter.{emptyEnv, ScriptNameProp} import sigmastate.utxo._ import special.sigma.Context import sigmastate.utils.Helpers._ -class OracleExamplesSpecification extends SigmaTestingCommons - with CrossVersionProps { suite => +class OracleExamplesSpecification extends CompilerTestingCommons + with CompilerCrossVersionProps { suite => implicit lazy val IR: TestingIRContext = new TestingIRContext private val reg1 = ErgoBox.nonMandatoryRegisters(0) @@ -89,7 +88,7 @@ class OracleExamplesSpecification extends SigmaTestingCommons val temperature: Long = 18 - val r = BigInt.apply(128, new SecureRandom()) //128 bits random number + val r = BigInt.apply(128, CryptoFacade.createSecureRandom()) //128 bits random number val a = group.exponentiate(group.generator, r.bigInteger) val ts = System.currentTimeMillis() diff --git a/interpreter/src/test/scala/sigmastate/utxo/examples/OracleTokenExamplesSpecification.scala b/sc/src/test/scala/sigmastate/utxo/examples/OracleTokenExamplesSpecification.scala similarity index 91% rename from interpreter/src/test/scala/sigmastate/utxo/examples/OracleTokenExamplesSpecification.scala rename to sc/src/test/scala/sigmastate/utxo/examples/OracleTokenExamplesSpecification.scala index 65493ef28e..7087854598 100644 --- a/interpreter/src/test/scala/sigmastate/utxo/examples/OracleTokenExamplesSpecification.scala +++ b/sc/src/test/scala/sigmastate/utxo/examples/OracleTokenExamplesSpecification.scala @@ -4,16 +4,17 @@ import org.ergoplatform._ import org.ergoplatform.dsl.ContractSyntax.Token import org.ergoplatform.dsl.{ContractSpec, SigmaContractSyntax, StdContracts, TestContractSpec} import scorex.crypto.hash.Blake2b256 -import sigmastate.helpers.SigmaTestingCommons +import sigmastate.eval.Digest32Coll +import sigmastate.helpers.CompilerTestingCommons import special.collection.Coll import special.sigma.Context -class OracleTokenExamplesSpecification extends SigmaTestingCommons { suite => +class OracleTokenExamplesSpecification extends CompilerTestingCommons { suite => implicit lazy val IR: TestingIRContext = new TestingIRContext private val reg1 = ErgoBox.nonMandatoryRegisters(0) - private lazy val tokenId: Coll[Byte] = spec.Coll(Blake2b256("token1")) + private lazy val tokenId = Digest32Coll @@@ spec.Coll(Blake2b256("token1")) case class OracleContract[Spec <: ContractSpec] ( temperature: Long, diff --git a/interpreter/src/test/scala/sigmastate/utxo/examples/RPSGameExampleSpecification.scala b/sc/src/test/scala/sigmastate/utxo/examples/RPSGameExampleSpecification.scala similarity index 98% rename from interpreter/src/test/scala/sigmastate/utxo/examples/RPSGameExampleSpecification.scala rename to sc/src/test/scala/sigmastate/utxo/examples/RPSGameExampleSpecification.scala index 736462013c..c65e63c95c 100644 --- a/interpreter/src/test/scala/sigmastate/utxo/examples/RPSGameExampleSpecification.scala +++ b/sc/src/test/scala/sigmastate/utxo/examples/RPSGameExampleSpecification.scala @@ -7,13 +7,13 @@ import scorex.utils.Random import sigmastate.Values.{ByteArrayConstant, ByteConstant, IntConstant, SigmaPropConstant} import sigmastate._ import sigmastate.basics.DLogProtocol.ProveDlog -import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, ErgoLikeContextTesting, ErgoLikeTestInterpreter, SigmaTestingCommons} +import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, ErgoLikeContextTesting, ErgoLikeTestInterpreter, CompilerTestingCommons} import sigmastate.helpers.TestingHelpers._ import sigmastate.interpreter.Interpreter._ import sigmastate.lang.Terms._ -class RPSGameExampleSpecification extends SigmaTestingCommons - with CrossVersionProps { +class RPSGameExampleSpecification extends CompilerTestingCommons + with CompilerCrossVersionProps { implicit lazy val IR = new TestingIRContext /** RPS game: diff --git a/interpreter/src/test/scala/sigmastate/utxo/examples/RevenueSharingExamplesSpecification.scala b/sc/src/test/scala/sigmastate/utxo/examples/RevenueSharingExamplesSpecification.scala similarity index 97% rename from interpreter/src/test/scala/sigmastate/utxo/examples/RevenueSharingExamplesSpecification.scala rename to sc/src/test/scala/sigmastate/utxo/examples/RevenueSharingExamplesSpecification.scala index 17440752e4..90cf1871a6 100644 --- a/interpreter/src/test/scala/sigmastate/utxo/examples/RevenueSharingExamplesSpecification.scala +++ b/sc/src/test/scala/sigmastate/utxo/examples/RevenueSharingExamplesSpecification.scala @@ -2,10 +2,10 @@ package sigmastate.utxo.examples import org.ergoplatform.dsl.{SigmaContractSyntax, ContractSpec, TestContractSpec, StdContracts} import sigmastate.eval.Extensions -import sigmastate.helpers.SigmaTestingCommons +import sigmastate.helpers.CompilerTestingCommons import special.sigma.Context -class RevenueSharingExamplesSpecification extends SigmaTestingCommons { suite => +class RevenueSharingExamplesSpecification extends CompilerTestingCommons { suite => implicit lazy val IR = new TestingIRContext case class RevenueContract[Spec <: ContractSpec] diff --git a/interpreter/src/test/scala/sigmastate/utxo/examples/ReversibleTxExampleSpecification.scala b/sc/src/test/scala/sigmastate/utxo/examples/ReversibleTxExampleSpecification.scala similarity index 97% rename from interpreter/src/test/scala/sigmastate/utxo/examples/ReversibleTxExampleSpecification.scala rename to sc/src/test/scala/sigmastate/utxo/examples/ReversibleTxExampleSpecification.scala index 2cdcb034a1..f68c97079b 100644 --- a/interpreter/src/test/scala/sigmastate/utxo/examples/ReversibleTxExampleSpecification.scala +++ b/sc/src/test/scala/sigmastate/utxo/examples/ReversibleTxExampleSpecification.scala @@ -5,14 +5,14 @@ import org.ergoplatform._ import scorex.crypto.hash.Blake2b256 import sigmastate.Values.{IntConstant, SigmaPropConstant} import sigmastate._ -import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, ErgoLikeContextTesting, ErgoLikeTestInterpreter, SigmaTestingCommons} +import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, ErgoLikeContextTesting, ErgoLikeTestInterpreter, CompilerTestingCommons} import sigmastate.helpers.TestingHelpers._ import sigmastate.interpreter.Interpreter.ScriptNameProp import sigmastate.lang.Terms._ -class ReversibleTxExampleSpecification extends SigmaTestingCommons - with CrossVersionProps { +class ReversibleTxExampleSpecification extends CompilerTestingCommons + with CompilerCrossVersionProps { private implicit lazy val IR: TestingIRContext = new TestingIRContext import ErgoAddressEncoder._ @@ -91,7 +91,7 @@ class ReversibleTxExampleSpecification extends SigmaTestingCommons |}""".stripMargin).asSigmaProp) val blocksIn24h = 500 - val feeProposition = ErgoScriptPredef.feeProposition() + val feeProposition = ErgoTreePredef.feeProposition() val depositEnv = Map( ScriptNameProp -> "depositEnv", "alice" -> alicePubKey, diff --git a/interpreter/src/test/scala/sigmastate/utxo/examples/Rule110Specification.scala b/sc/src/test/scala/sigmastate/utxo/examples/Rule110Specification.scala similarity index 99% rename from interpreter/src/test/scala/sigmastate/utxo/examples/Rule110Specification.scala rename to sc/src/test/scala/sigmastate/utxo/examples/Rule110Specification.scala index 401b332c66..62fbaf37a7 100644 --- a/interpreter/src/test/scala/sigmastate/utxo/examples/Rule110Specification.scala +++ b/sc/src/test/scala/sigmastate/utxo/examples/Rule110Specification.scala @@ -6,7 +6,7 @@ import scorex.util._ import sigmastate.Values._ import sigmastate._ import sigmastate.eval._ -import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, ErgoLikeContextTesting, SigmaTestingCommons, ErgoLikeTestInterpreter} +import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, ErgoLikeContextTesting, CompilerTestingCommons, ErgoLikeTestInterpreter} import sigmastate.helpers.TestingHelpers._ import sigmastate.interpreter.ContextExtension import sigmastate.lang.Terms._ @@ -18,8 +18,8 @@ import sigmastate.utxo.blockchain.BlockchainSimulationTestingCommons._ * Wolfram's Rule110 implementations * */ -class Rule110Specification extends SigmaTestingCommons - with CrossVersionProps { +class Rule110Specification extends CompilerTestingCommons + with CompilerCrossVersionProps { implicit lazy val IR = new TestingIRContext private val reg1 = ErgoBox.nonMandatoryRegisters.head private val reg2 = ErgoBox.nonMandatoryRegisters(1) diff --git a/interpreter/src/test/scala/sigmastate/utxo/examples/TimedPaymentExampleSpecification.scala b/sc/src/test/scala/sigmastate/utxo/examples/TimedPaymentExampleSpecification.scala similarity index 93% rename from interpreter/src/test/scala/sigmastate/utxo/examples/TimedPaymentExampleSpecification.scala rename to sc/src/test/scala/sigmastate/utxo/examples/TimedPaymentExampleSpecification.scala index 4345a0cd8a..04390272e8 100644 --- a/interpreter/src/test/scala/sigmastate/utxo/examples/TimedPaymentExampleSpecification.scala +++ b/sc/src/test/scala/sigmastate/utxo/examples/TimedPaymentExampleSpecification.scala @@ -3,15 +3,14 @@ package sigmastate.utxo.examples import org.ergoplatform._ import sigmastate.Values.IntConstant import sigmastate._ -import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, ErgoLikeContextTesting, SigmaTestingCommons, ErgoLikeTestInterpreter} +import sigmastate.exceptions.InterpreterException +import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, ErgoLikeContextTesting, CompilerTestingCommons, ErgoLikeTestInterpreter} import sigmastate.helpers.TestingHelpers._ import sigmastate.interpreter.Interpreter.ScriptNameProp import sigmastate.lang.Terms._ -import sigmastate.lang.exceptions.InterpreterException - -class TimedPaymentExampleSpecification extends SigmaTestingCommons - with CrossVersionProps { +class TimedPaymentExampleSpecification extends CompilerTestingCommons + with CompilerCrossVersionProps { private implicit lazy val IR: TestingIRContext = new TestingIRContext import ErgoAddressEncoder._ diff --git a/interpreter/src/test/scala/sigmastate/utxo/examples/TrustlessLETS.scala b/sc/src/test/scala/sigmastate/utxo/examples/TrustlessLETS.scala similarity index 98% rename from interpreter/src/test/scala/sigmastate/utxo/examples/TrustlessLETS.scala rename to sc/src/test/scala/sigmastate/utxo/examples/TrustlessLETS.scala index 347d3b74c9..ecaa4c0a14 100644 --- a/interpreter/src/test/scala/sigmastate/utxo/examples/TrustlessLETS.scala +++ b/sc/src/test/scala/sigmastate/utxo/examples/TrustlessLETS.scala @@ -2,12 +2,12 @@ package sigmastate.utxo.examples import scorex.crypto.hash.Blake2b256 -import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, SigmaTestingCommons} +import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, CompilerTestingCommons} import sigmastate.helpers.TestingHelpers._ import sigmastate.interpreter.Interpreter._ import sigmastate.lang.Terms._ -class TrustlessLETS1 extends SigmaTestingCommons { +class TrustlessLETS1 extends CompilerTestingCommons { private implicit lazy val IR: TestingIRContext = new TestingIRContext property("Evaluation - LETS1 Example") { @@ -114,7 +114,7 @@ class TrustlessLETS1 extends SigmaTestingCommons { } -class TrustlessLETS2 extends SigmaTestingCommons { +class TrustlessLETS2 extends CompilerTestingCommons { // LETS2 // Non-refundable ergs // Zero sum @@ -217,7 +217,7 @@ class TrustlessLETS2 extends SigmaTestingCommons { } -class TrustlessLETS3 extends SigmaTestingCommons { +class TrustlessLETS3 extends CompilerTestingCommons { // LETS3 // time-locked ergs // Positive sum @@ -332,7 +332,7 @@ class TrustlessLETS3 extends SigmaTestingCommons { } -class TrustlessLETS4 extends SigmaTestingCommons { +class TrustlessLETS4 extends CompilerTestingCommons { // LETS4 // Non-refundable ergs // Positive sum diff --git a/interpreter/src/test/scala/sigmastate/utxo/examples/XorGameExampleSpecification.scala b/sc/src/test/scala/sigmastate/utxo/examples/XorGameExampleSpecification.scala similarity index 98% rename from interpreter/src/test/scala/sigmastate/utxo/examples/XorGameExampleSpecification.scala rename to sc/src/test/scala/sigmastate/utxo/examples/XorGameExampleSpecification.scala index 5ae741af6b..c462e0396f 100644 --- a/interpreter/src/test/scala/sigmastate/utxo/examples/XorGameExampleSpecification.scala +++ b/sc/src/test/scala/sigmastate/utxo/examples/XorGameExampleSpecification.scala @@ -7,13 +7,13 @@ import scorex.utils.Random import sigmastate.Values.{ByteArrayConstant, ByteConstant, IntConstant, SigmaPropConstant} import sigmastate._ import sigmastate.basics.DLogProtocol.ProveDlog -import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, ErgoLikeContextTesting, ErgoLikeTestInterpreter, SigmaTestingCommons} +import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, ErgoLikeContextTesting, ErgoLikeTestInterpreter, CompilerTestingCommons} import sigmastate.helpers.TestingHelpers._ import sigmastate.interpreter.Interpreter._ import sigmastate.lang.Terms._ -class XorGameExampleSpecification extends SigmaTestingCommons - with CrossVersionProps { +class XorGameExampleSpecification extends CompilerTestingCommons + with CompilerCrossVersionProps { private implicit lazy val IR: TestingIRContext = new TestingIRContext /** XOR game: diff --git a/interpreter/src/test/scala/special/sigma/DataValueComparerSpecification.scala b/sc/src/test/scala/special/sigma/DataValueComparerSpecification.scala similarity index 100% rename from interpreter/src/test/scala/special/sigma/DataValueComparerSpecification.scala rename to sc/src/test/scala/special/sigma/DataValueComparerSpecification.scala diff --git a/interpreter/src/test/scala/special/sigma/LoopTests.scala b/sc/src/test/scala/special/sigma/LoopTests.scala similarity index 91% rename from interpreter/src/test/scala/special/sigma/LoopTests.scala rename to sc/src/test/scala/special/sigma/LoopTests.scala index 1495ef4758..37ff177c8e 100644 --- a/interpreter/src/test/scala/special/sigma/LoopTests.scala +++ b/sc/src/test/scala/special/sigma/LoopTests.scala @@ -1,8 +1,8 @@ package special.sigma -import sigmastate.helpers.SigmaTestingCommons +import sigmastate.helpers.CompilerTestingCommons -class LoopTests extends SigmaTestingCommons { suite => +class LoopTests extends CompilerTestingCommons { suite => implicit lazy val IR = new TestingIRContext import IR._ diff --git a/interpreter/src/test/scala/special/sigma/SigmaDslSpecification.scala b/sc/src/test/scala/special/sigma/SigmaDslSpecification.scala similarity index 99% rename from interpreter/src/test/scala/special/sigma/SigmaDslSpecification.scala rename to sc/src/test/scala/special/sigma/SigmaDslSpecification.scala index 2012febdb8..0ab8ca97bd 100644 --- a/interpreter/src/test/scala/special/sigma/SigmaDslSpecification.scala +++ b/sc/src/test/scala/special/sigma/SigmaDslSpecification.scala @@ -21,6 +21,7 @@ import sigmastate.eval._ import sigmastate.lang.Terms.{MethodCall, PropertyCall} import sigmastate.utxo._ import special.collection._ +import special.collection.Extensions._ import sigmastate.serialization.OpCodes.OpCode import sigmastate.utils.Helpers import sigmastate.utils.Helpers._ @@ -38,8 +39,7 @@ import sigmastate.basics.ProveDHTuple import sigmastate.interpreter._ import org.scalactic.source.Position import sigmastate.helpers.SigmaPPrint -import sigmastate.lang.exceptions.CosterException - +import sigmastate.exceptions.CosterException import scala.collection.compat.immutable.ArraySeq /** This suite tests every method of every SigmaDsl type to be equivalent to @@ -70,7 +70,7 @@ import scala.collection.compat.immutable.ArraySeq * nBenchmarkIters = 1 */ class SigmaDslSpecification extends SigmaDslTesting - with CrossVersionProps + with CompilerCrossVersionProps with BeforeAndAfterAll { suite => /** Use VersionContext so that each property in this suite runs under correct @@ -4652,7 +4652,7 @@ class SigmaDslSpecification extends SigmaDslTesting Vector(), Right(SigmaPropConstant(CSigmaProp(ProveDlog(Helpers.decodeECPoint("02af645874c3b53465a5e9d820eb207d6001258c3b708f0d31d7c2e342833dce64"))))) ), - Coll((Digest32 @@ (ErgoAlgos.decodeUnsafe("8f0000ff009e7fff012427ff7fffcc35dfe680017f004ef3be1280e57fc40101")), 500L)), + Coll((Digest32Coll @@ (ErgoAlgos.decodeUnsafe("8f0000ff009e7fff012427ff7fffcc35dfe680017f004ef3be1280e57fc40101").toColl), 500L)), Map( ErgoBox.R9 -> LongConstant(-6985752043373238161L), ErgoBox.R4 -> LongConstant(-7374898275229807247L), @@ -4740,7 +4740,7 @@ class SigmaDslSpecification extends SigmaDslTesting ), Right(ConstantPlaceholder(0, SSigmaProp)) ), - Coll((Digest32 @@ (ErgoAlgos.decodeUnsafe("6f070152007f00005a00893ea1e98045ffa28f72da01ff7f01ff2d48eb793fd6")), 20000L)), + Coll((Digest32Coll @@ (ErgoAlgos.decodeUnsafe("6f070152007f00005a00893ea1e98045ffa28f72da01ff7f01ff2d48eb793fd6").toColl), 20000L)), Map(ErgoBox.R5 -> LongConstant(1L), ErgoBox.R4 -> LongConstant(5008366408131208436L)), ModifierId @@ ("26485d14a94ef18ec36227a838b98e11e910087be4c7e634f51391e4ea4d16ff"), 0.toShort, @@ -4755,7 +4755,7 @@ class SigmaDslSpecification extends SigmaDslTesting Vector(), Right(SigmaPropConstant(CSigmaProp(ProveDlog(Helpers.decodeECPoint("02d13e1a8c31f32194761adc1cdcbaa746b3e049e682bba9308d8ee84576172991"))))) ), - Coll((Digest32 @@ (ErgoAlgos.decodeUnsafe("6f070152007f00005a00893ea1e98045ffa28f72da01ff7f01ff2d48eb793fd6")), 500L)), + Coll((Digest32Coll @@ (ErgoAlgos.decodeUnsafe("6f070152007f00005a00893ea1e98045ffa28f72da01ff7f01ff2d48eb793fd6").toColl), 500L)), Map(), ModifierId @@ ("26485d14a94ef18ec36227a838b98e11e910087be4c7e634f51391e4ea4d16ff"), 1.toShort, @@ -6577,7 +6577,7 @@ class SigmaDslSpecification extends SigmaDslTesting )) } - def sampleCollBoxes = genSamples[Coll[Box]](collOfN[Box](5), MinSuccessful(20)) + def sampleCollBoxes = genSamples[Coll[Box]](collOfN[Box](5, arbitrary[Box]), MinSuccessful(20)) def create_b1 = CostingBox( new ErgoBox( @@ -7566,7 +7566,7 @@ class SigmaDslSpecification extends SigmaDslTesting for { coll <- collGen[Int] is <- genIndices(coll.length) - vs <- collOfN[Int](is.length) + vs <- collOfN[Int](is.length, arbitrary[Int]) } yield (coll, (is.toColl, vs)), MinSuccessful(20)) diff --git a/interpreter/src/test/scala/special/sigma/SigmaDslStaginTests.scala b/sc/src/test/scala/special/sigma/SigmaDslStaginTests.scala similarity index 100% rename from interpreter/src/test/scala/special/sigma/SigmaDslStaginTests.scala rename to sc/src/test/scala/special/sigma/SigmaDslStaginTests.scala diff --git a/interpreter/src/test/scala/special/sigma/SigmaDslTesting.scala b/sc/src/test/scala/special/sigma/SigmaDslTesting.scala similarity index 99% rename from interpreter/src/test/scala/special/sigma/SigmaDslTesting.scala rename to sc/src/test/scala/special/sigma/SigmaDslTesting.scala index 281355ccd2..444adb337d 100644 --- a/interpreter/src/test/scala/special/sigma/SigmaDslTesting.scala +++ b/sc/src/test/scala/special/sigma/SigmaDslTesting.scala @@ -22,8 +22,8 @@ import sigmastate.basics.{SigmaProtocol, SigmaProtocolCommonInput, SigmaProtocol import sigmastate.eval.Extensions._ import sigmastate.eval.{CompiletimeIRContext, CostingBox, CostingDataContext, Evaluation, IRContext, SigmaDsl} import sigmastate.helpers.TestingHelpers._ -import sigmastate.helpers.{ErgoLikeContextTesting, ErgoLikeTestInterpreter, SigmaPPrint} -import sigmastate.interpreter.Interpreter.{ScriptEnv, VerificationResult} +import sigmastate.helpers.{ErgoLikeTestInterpreter, SigmaPPrint, ErgoLikeContextTesting, CompilerTestingCommons} +import sigmastate.interpreter.Interpreter.{VerificationResult, ScriptEnv} import sigmastate.interpreter._ import sigmastate.lang.Terms.{Apply, ValueOps} import sigmastate.serialization.ValueSerializer @@ -45,7 +45,7 @@ import scala.util.{Failure, Success, Try} class SigmaDslTesting extends AnyPropSpec with ScalaCheckPropertyChecks with Matchers - with SigmaTestingData with SigmaContractSyntax + with SigmaTestingData with SigmaContractSyntax with CompilerTestingCommons with ObjectGenerators { suite => override def Coll[T](items: T*)(implicit cT: RType[T]): Coll[T] = super.Coll(items:_*) diff --git a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/BlockchainParameters.scala b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/BlockchainParameters.scala new file mode 100644 index 0000000000..4f4cc87010 --- /dev/null +++ b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/BlockchainParameters.scala @@ -0,0 +1,34 @@ +package org.ergoplatform.sdk.js + +import org.ergoplatform.sdk.wallet.protocol.context.ErgoLikeParameters + +import scala.scalajs.js.UndefOr +import scala.scalajs.js.annotation.JSExportTopLevel + +@JSExportTopLevel("BlockchainParameters") +class BlockchainParameters( + val storageFeeFactor: Int, + val minValuePerByte: Int, + val maxBlockSize: Int, + val tokenAccessCost: Int, + val inputCost: Int, + val dataInputCost: Int, + val outputCost: Int, + val maxBlockCost: Int, + val _softForkStartingHeight: UndefOr[Int], + val _softForkVotesCollected: UndefOr[Int], + val blockVersion: Byte +) extends ErgoLikeParameters { + import org.ergoplatform.sdk.Iso._ + /** + * @return height when voting for a soft-fork had been started + */ + override def softForkStartingHeight: Option[Int] = + Isos.isoUndefOr[Int, Int](identityIso).to(_softForkStartingHeight) + + /** + * @return votes for soft-fork collected in previous epochs + */ + override def softForkVotesCollected: Option[Int] = + Isos.isoUndefOr[Int, Int](identityIso).to(_softForkVotesCollected) +} diff --git a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/BlockchainStateContext.scala b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/BlockchainStateContext.scala new file mode 100644 index 0000000000..0b91cbae57 --- /dev/null +++ b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/BlockchainStateContext.scala @@ -0,0 +1,12 @@ +package org.ergoplatform.sdk.js + +import scala.scalajs.js +import scala.scalajs.js.annotation.JSExportTopLevel + +/** Equivalent of [[org.ergoplatform.sdk.wallet.protocol.context.ErgoLikeStateContext]] available from JS. */ +@JSExportTopLevel("BlockchainStateContext") +class BlockchainStateContext( + val sigmaLastHeaders: js.Array[Header], + val previousStateDigest: String, + val sigmaPreHeader: PreHeader +) diff --git a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/ErgoTree.scala b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/ErgoTree.scala new file mode 100644 index 0000000000..0fbacabc95 --- /dev/null +++ b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/ErgoTree.scala @@ -0,0 +1,46 @@ +package org.ergoplatform.sdk.js + +import scorex.util.encode.Base16 +import sigmastate.Values +import sigmastate.serialization.ErgoTreeSerializer + +import scala.scalajs.js +import scala.scalajs.js.annotation.{JSExportTopLevel} + +/** An exported JavaScript class wrapping the Scala `Values.ErgoTree` type. */ +@JSExportTopLevel("ErgoTree") +class ErgoTree(tree: Values.ErgoTree) extends js.Object { + /** Serializes the ErgoTree instance to an array of bytes. */ + def toBytes(): Array[Byte] = { + val bytes = ErgoTreeSerializer.DefaultSerializer.serializeErgoTree(tree) + bytes + } + + /** Serializes the ErgoTree instance to a hexadecimal string. */ + def toHex(): String = { + Base16.encode(toBytes()) + } +} + +/** An exported JavaScript object providing utility methods for working with ErgoTree instances. */ +@JSExportTopLevel("ErgoTrees") +object ErgoTree extends js.Object { + + /** Deserializes an ErgoTree instance from a hexadecimal string. + * + * @param hex a hexadecimal string representing the serialized ErgoTree + */ + def fromHex(hex: String): ErgoTree = { + val bytes = Base16.decode(hex).get + fromBytes(bytes) + } + + /** Deserializes an ErgoTree instance from an array of bytes. + * + * @param bytes an array of bytes representing the serialized ErgoTree + */ + def fromBytes(bytes: Array[Byte]): ErgoTree = { + val tree = ErgoTreeSerializer.DefaultSerializer.deserializeErgoTree(bytes) + new ErgoTree(tree) + } +} diff --git a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Header.scala b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Header.scala new file mode 100644 index 0000000000..f1cf5d5dac --- /dev/null +++ b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Header.scala @@ -0,0 +1,41 @@ +package org.ergoplatform.sdk.js + +import scorex.crypto.authds.ADDigest +import sigmastate.AvlTreeFlags +import special.sigma + +import scala.scalajs.js +import scala.scalajs.js.UndefOr +import scala.scalajs.js.annotation.JSExportTopLevel + + +/** Equivalent of [[special.sigma.AvlTree]] available from JS. */ +@JSExportTopLevel("AvlTree") +class AvlTree( + val digest: String, + val insertAllowed: Boolean, + val updateAllowed: Boolean, + val removeAllowed: Boolean, + val keyLength: Int, + val valueLengthOpt: UndefOr[Int] +) extends js.Object + +/** Equivalent of [[special.sigma.Header]] available from JS. */ +@JSExportTopLevel("Header") +class Header( + val id: String, + val version: Byte, + val parentId: String, + val ADProofsRoot: String, + val stateRoot: AvlTree, + val transactionsRoot: String, + val timestamp: js.BigInt, + val nBits: js.BigInt, + val height: Int, + val extensionRoot: String, + val minerPk: String, + val powOnetimePk: String, + val powNonce: String, + val powDistance: js.BigInt, + val votes: String +) extends js.Object diff --git a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Isos.scala b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Isos.scala new file mode 100644 index 0000000000..8c1a85902c --- /dev/null +++ b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Isos.scala @@ -0,0 +1,440 @@ +package org.ergoplatform.sdk.js + +import org.ergoplatform.ErgoBox._ +import org.ergoplatform.{DataInput, ErgoBox, ErgoBoxCandidate, UnsignedErgoLikeTransaction, UnsignedInput} +import org.ergoplatform.sdk.{ExtendedInputBox, Iso} +import org.ergoplatform.sdk.JavaHelpers.UniversalConverter +import org.ergoplatform.sdk.wallet.protocol.context.{CErgoLikeStateContext, ErgoLikeStateContext} +import scalan.RType +import scorex.crypto.authds.{ADDigest, ADKey} +import scorex.util.ModifierId +import scorex.util.encode.Base16 +import sigmastate.{AvlTreeData, AvlTreeFlags, SType} +import sigmastate.Values.{Constant, GroupElementConstant} +import sigmastate.eval.Extensions.ArrayOps +import sigmastate.eval.{CAvlTree, CBigInt, CHeader, CPreHeader, Colls, Digest32Coll, Evaluation} +import sigmastate.interpreter.ContextExtension +import sigmastate.serialization.{ErgoTreeSerializer, ValueSerializer} +import special.collection.Coll +import special.collection.Extensions.CollBytesOps +import special.sigma +import special.sigma.GroupElement +import typings.fleetSdkCommon.boxesMod.Box +import typings.fleetSdkCommon.commonMod.HexString +import typings.fleetSdkCommon.registersMod.NonMandatoryRegisters +import typings.fleetSdkCommon.tokenMod.TokenAmount +import typings.fleetSdkCommon.{boxesMod, commonMod, contextExtensionMod, inputsMod, registersMod, tokenMod} +import typings.fleetSdkCommon.transactionsMod.UnsignedTransaction + +import java.math.BigInteger +import scala.collection.immutable.ListMap +import scala.reflect.ClassTag +import scala.scalajs.js +import scala.scalajs.js.JSConverters.JSRichOption +import scala.scalajs.js.Object + +/** Definitions of isomorphisms. */ +object Isos { + /** Conversion between `Value` and `Constant[SType]`. */ + implicit val isoValueToConstant: Iso[Value, Constant[SType]] = new Iso[Value, Constant[SType]] { + override def to(x: Value): Constant[SType] = + Constant(x.runtimeData.asInstanceOf[SType#WrappedType], Evaluation.rtypeToSType(x.tpe.rtype)) + + override def from(x: Constant[SType]): Value = { + val rtype = Evaluation.stypeToRType(x.tpe) + val jsvalue = Value.fromRuntimeData(x.value, rtype) + new Value(jsvalue, new Type(rtype)) + } + } + + val isoStringToArray: Iso[String, Array[Byte]] = new Iso[String, Array[Byte]] { + override def to(x: String): Array[Byte] = Base16.decode(x).get + override def from(x: Array[Byte]): String = Base16.encode(x) + } + + val isoStringToColl: Iso[String, Coll[Byte]] = new Iso[String, Coll[Byte]] { + override def to(x: String): Coll[Byte] = Colls.fromArray(Base16.decode(x).get) + override def from(x: Coll[Byte]): String = x.toHex + } + + val isoStringToGroupElement: Iso[String, GroupElement] = new Iso[String, GroupElement] { + override def to(x: String): GroupElement = { + val bytes = Base16.decode(x).get + ValueSerializer.deserialize(bytes).asInstanceOf[GroupElementConstant].value + } + override def from(x: GroupElement): String = { + val bytes = ValueSerializer.serialize(GroupElementConstant(x)) + Base16.encode(bytes) + } + } + + implicit val isoBoxId: Iso[boxesMod.BoxId, ErgoBox.BoxId] = new Iso[boxesMod.BoxId, ErgoBox.BoxId] { + override def to(x: boxesMod.BoxId): ErgoBox.BoxId = ADKey @@@ isoStringToArray.to(x) + + override def from(x: ErgoBox.BoxId): boxesMod.BoxId = isoStringToArray.from(x) + } + + implicit val isoHexStringToConstant: Iso[HexString, Constant[SType]] = new Iso[HexString, Constant[SType]] { + override def to(x: HexString): Constant[SType] = { + val bytes = isoStringToArray.to(x) + val value = ValueSerializer.deserialize(bytes) + value.asInstanceOf[Constant[SType]] + } + override def from(x: Constant[SType]): HexString = { + val bytes = ValueSerializer.serialize(x) + isoStringToArray.from(bytes) + } + } + + implicit val isoAvlTree: Iso[AvlTree, sigma.AvlTree] = new Iso[AvlTree, sigma.AvlTree] { + override def to(x: AvlTree): sigma.AvlTree = { + CAvlTree( + AvlTreeData( + digest = ADDigest @@ isoStringToArray.to(x.digest), + treeFlags = AvlTreeFlags(x.insertAllowed, x.updateAllowed, x.removeAllowed), + x.keyLength, + valueLengthOpt = isoUndefOr(Iso.identityIso[Int]).to(x.valueLengthOpt), + ), + ) + } + override def from(x: sigma.AvlTree): AvlTree = { + val tree = x.asInstanceOf[CAvlTree] + val data = tree.treeData + new AvlTree( + digest = isoStringToColl.from(tree.digest), + insertAllowed = data.treeFlags.insertAllowed, + updateAllowed = data.treeFlags.updateAllowed, + removeAllowed = data.treeFlags.removeAllowed, + keyLength = data.keyLength, + valueLengthOpt = isoUndefOr(Iso.identityIso[Int]).from(data.valueLengthOpt), + ) + } + } + + implicit val isoHeader: Iso[Header, special.sigma.Header] = new Iso[Header, special.sigma.Header] { + override def to(a: Header): sigma.Header = { + CHeader( + id = isoStringToColl.to(a.id), + version = a.version, + parentId = isoStringToColl.to(a.parentId), + ADProofsRoot = isoStringToColl.to(a.ADProofsRoot), + stateRoot = isoAvlTree.to(a.stateRoot), + transactionsRoot = isoStringToColl.to(a.transactionsRoot), + timestamp = isoBigIntToLong.to(a.timestamp), + nBits = isoBigIntToLong.to(a.nBits), + height = a.height, + extensionRoot = isoStringToColl.to(a.extensionRoot), + minerPk = isoStringToGroupElement.to(a.minerPk), + powOnetimePk = isoStringToGroupElement.to(a.powOnetimePk), + powNonce = isoStringToColl.to(a.powNonce), + powDistance = isoBigInt.to(a.powDistance), + votes = isoStringToColl.to(a.votes) + ) + } + override def from(b: sigma.Header): Header = { + val header = b.asInstanceOf[CHeader] + new Header( + id = isoStringToColl.from(header.id), + version = header.version, + parentId = isoStringToColl.from(header.parentId), + ADProofsRoot = isoStringToColl.from(header.ADProofsRoot), + stateRoot = isoAvlTree.from(header.stateRoot), + transactionsRoot = isoStringToColl.from(header.transactionsRoot), + timestamp = isoBigIntToLong.from(header.timestamp), + nBits = isoBigIntToLong.from(header.nBits), + height = header.height, + extensionRoot = isoStringToColl.from(header.extensionRoot), + minerPk = isoStringToGroupElement.from(header.minerPk), + powOnetimePk = isoStringToGroupElement.from(header.powOnetimePk), + powNonce = isoStringToColl.from(header.powNonce), + powDistance = isoBigInt.from(header.powDistance), + votes = isoStringToColl.from(header.votes) + ) + } + } + + implicit val isoPreHeader: Iso[PreHeader, special.sigma.PreHeader] = new Iso[PreHeader, special.sigma.PreHeader] { + override def to(a: PreHeader): sigma.PreHeader = { + CPreHeader( + version = a.version, + parentId = isoStringToColl.to(a.parentId), + timestamp = isoBigIntToLong.to(a.timestamp), + nBits = isoBigIntToLong.to(a.nBits), + height = a.height, + minerPk = isoStringToGroupElement.to(a.minerPk), + votes = isoStringToColl.to(a.votes) + ) + } + override def from(b: sigma.PreHeader): PreHeader = { + val header = b.asInstanceOf[CPreHeader] + new PreHeader( + version = header.version, + parentId = isoStringToColl.from(header.parentId), + timestamp = isoBigIntToLong.from(header.timestamp), + nBits = isoBigIntToLong.from(header.nBits), + height = header.height, + minerPk = isoStringToGroupElement.from(header.minerPk), + votes = isoStringToColl.from(header.votes) + ) + } + } + + implicit val isoBlockchainStateContext: Iso[BlockchainStateContext, ErgoLikeStateContext] = new Iso[BlockchainStateContext, ErgoLikeStateContext] { + override def to(a: BlockchainStateContext): ErgoLikeStateContext = { + CErgoLikeStateContext( + sigmaLastHeaders = isoArrayToColl(isoHeader).to(a.sigmaLastHeaders), + previousStateDigest = ADDigest @@ isoStringToColl.to(a.previousStateDigest).toArray, + sigmaPreHeader = isoPreHeader.to(a.sigmaPreHeader) + ) + } + + override def from(b: ErgoLikeStateContext): BlockchainStateContext = { + new BlockchainStateContext( + sigmaLastHeaders = isoArrayToColl(isoHeader).from(b.sigmaLastHeaders), + previousStateDigest = isoStringToColl.from(b.previousStateDigest.toColl), + sigmaPreHeader = isoPreHeader.from(b.sigmaPreHeader) + ) + } + } + + implicit val isoContextExtension: Iso[contextExtensionMod.ContextExtension, ContextExtension] = new Iso[contextExtensionMod.ContextExtension, ContextExtension] { + override def to(x: contextExtensionMod.ContextExtension): ContextExtension = { + var map = new ListMap[Byte, Constant[SType]]() + val keys = js.Object.keys(x) + for ( k <- keys ) { + val id = k.toInt.toByte + val c = isoHexStringToConstant.to(x.apply(id).get.get) + map = map + (id -> c) + } + ContextExtension(map) + } + + override def from(x: ContextExtension): contextExtensionMod.ContextExtension = { + val res = new Object().asInstanceOf[contextExtensionMod.ContextExtension] + x.values.foreach { case (k, v: Constant[_]) => + val hex = isoHexStringToConstant.from(v) + res.update(k, hex) + } + res + } + } + + implicit val isoUnsignedInput: Iso[inputsMod.UnsignedInput, UnsignedInput] = new Iso[inputsMod.UnsignedInput, UnsignedInput] { + override def to(x: inputsMod.UnsignedInput): UnsignedInput = + new UnsignedInput(x.boxId.convertTo[ErgoBox.BoxId], isoContextExtension.to(x.extension)) + + override def from(x: UnsignedInput): inputsMod.UnsignedInput = + inputsMod.UnsignedInput(x.boxId.convertTo[boxesMod.BoxId], isoContextExtension.from(x.extension)) + } + + implicit val isoDataInput: Iso[inputsMod.DataInput, DataInput] = new Iso[inputsMod.DataInput, DataInput] { + override def to(x: inputsMod.DataInput): DataInput = DataInput(x.boxId.convertTo[ErgoBox.BoxId]) + + override def from(x: DataInput): inputsMod.DataInput = inputsMod.DataInput(x.boxId.convertTo[boxesMod.BoxId]) + } + + implicit val isoBigInt: Iso[js.BigInt, special.sigma.BigInt] = new Iso[js.BigInt, special.sigma.BigInt] { + override def to(x: js.BigInt): sigma.BigInt = { + CBigInt(new BigInteger(x.toString(10))) + } + override def from(x: sigma.BigInt): js.BigInt = { + val bi = x.asInstanceOf[CBigInt].wrappedValue + val s = bi.toString(10) + js.BigInt(s) + } + } + + implicit val isoBigIntToLong: Iso[js.BigInt, Long] = new Iso[js.BigInt, Long] { + override def to(x: js.BigInt): Long = java.lang.Long.parseLong(x.toString(10)) + override def from(x: Long): js.BigInt = js.BigInt(x.toString) + } + + implicit val isoAmount: Iso[commonMod.Amount, Long] = new Iso[commonMod.Amount, Long] { + override def to(x: commonMod.Amount): Long = x.asInstanceOf[Any] match { + case s: String => BigInt(s).toLong + case _ => java.lang.Long.parseLong(x.asInstanceOf[js.BigInt].toString(10)) + } + override def from(x: Long): commonMod.Amount = x.toString + } + + implicit val isoToken: Iso[tokenMod.TokenAmount[commonMod.Amount], Token] = + new Iso[tokenMod.TokenAmount[commonMod.Amount], Token] { + override def to(x: tokenMod.TokenAmount[commonMod.Amount]): Token = + (Digest32Coll @@@ Colls.fromArray(Base16.decode(x.tokenId).get), isoAmount.to(x.amount)) + + override def from(x: Token): tokenMod.TokenAmount[commonMod.Amount] = + tokenMod.TokenAmount[commonMod.Amount](isoAmount.from(x._2), x._1.toHex) + } + + implicit def isoUndefOr[A, B](implicit iso: Iso[A, B]): Iso[js.UndefOr[A], Option[B]] = new Iso[js.UndefOr[A], Option[B]] { + override def to(x: js.UndefOr[A]): Option[B] = x.toOption.map(iso.to) + override def from(x: Option[B]): js.UndefOr[A] = x.map(iso.from).orUndefined + } + + implicit def isoArrayToColl[A, B](iso: Iso[A, B])(implicit ctA: ClassTag[A], tB: RType[B]): Iso[js.Array[A], Coll[B]] = new Iso[js.Array[A], Coll[B]] { + override def to(x: js.Array[A]): Coll[B] = Colls.fromArray(x.map(iso.to).toArray(tB.classTag)) + override def from(x: Coll[B]): js.Array[A] = js.Array(x.toArray.map(iso.from):_*) + } + + implicit def isoArrayToIndexed[A, B](iso: Iso[A, B])(implicit cB: ClassTag[B]): Iso[js.Array[A], IndexedSeq[B]] = new Iso[js.Array[A], IndexedSeq[B]] { + override def to(x: js.Array[A]): IndexedSeq[B] = x.map(iso.to).toArray(cB).toIndexedSeq + override def from(x: IndexedSeq[B]): js.Array[A] = js.Array(x.map(iso.from):_*) + } + + val isoTokenArray: Iso[js.Array[tokenMod.TokenAmount[commonMod.Amount]], Coll[Token]] = + new Iso[js.Array[tokenMod.TokenAmount[commonMod.Amount]], Coll[Token]] { + override def to(x: js.Array[tokenMod.TokenAmount[commonMod.Amount]]): Coll[Token] = { + isoArrayToColl(isoToken).to(x) + } + override def from(x: Coll[Token]): js.Array[tokenMod.TokenAmount[commonMod.Amount]] = { + isoArrayToColl(isoToken).from(x) + } + } + + val isoNonMandatoryRegisters: Iso[registersMod.NonMandatoryRegisters, AdditionalRegisters] = + new Iso[registersMod.NonMandatoryRegisters, AdditionalRegisters] { + override def to(x: registersMod.NonMandatoryRegisters): AdditionalRegisters = { + val regs = Seq( + x.R4 -> R4, + x.R5 -> R5, + x.R6 -> R6, + x.R7 -> R7, + x.R8 -> R8, + x.R9 -> R9 + ).collect { + case (regOpt, id) if regOpt.isDefined => id -> isoHexStringToConstant.to(regOpt.get) + } + Map(regs:_*) + } + override def from(regs: AdditionalRegisters): registersMod.NonMandatoryRegisters = { + def regHexOpt(t: NonMandatoryRegisterId): Option[HexString] = + regs.get(t).map(v => isoHexStringToConstant.from(v.asInstanceOf[Constant[SType]])) + + val resRegs = NonMandatoryRegisters() + regHexOpt(R4).foreach(resRegs.setR4(_)) + regHexOpt(R5).foreach(resRegs.setR5(_)) + regHexOpt(R6).foreach(resRegs.setR6(_)) + regHexOpt(R7).foreach(resRegs.setR7(_)) + regHexOpt(R8).foreach(resRegs.setR8(_)) + regHexOpt(R9).foreach(resRegs.setR9(_)) + resRegs + } + } + + implicit val isoBoxCandidate: Iso[boxesMod.BoxCandidate[commonMod.Amount], ErgoBoxCandidate] = new Iso[boxesMod.BoxCandidate[commonMod.Amount], ErgoBoxCandidate] { + override def to(x: boxesMod.BoxCandidate[commonMod.Amount]): ErgoBoxCandidate = { + val ergoBoxCandidate = new ErgoBoxCandidate( + value = isoAmount.to(x.value), + ergoTree = { + val bytes = Base16.decode(x.ergoTree).get + ErgoTreeSerializer.DefaultSerializer.deserializeErgoTree(bytes) + }, + x.creationHeight.toInt, + additionalTokens = isoTokenArray.to(x.assets), + additionalRegisters = isoNonMandatoryRegisters.to(x.additionalRegisters) + ) + ergoBoxCandidate + } + + override def from(x: ErgoBoxCandidate): boxesMod.BoxCandidate[commonMod.Amount] = { + val ergoTree = ErgoTreeSerializer.DefaultSerializer.serializeErgoTree(x.ergoTree) + val ergoTreeStr = Base16.encode(ergoTree) + val assets = isoTokenArray.from(x.additionalTokens) + boxesMod.BoxCandidate[commonMod.Amount]( + ergoTree = ergoTreeStr, + value = isoAmount.from(x.value), + assets = assets, + creationHeight = x.creationHeight, + additionalRegisters = isoNonMandatoryRegisters.from(x.additionalRegisters) + ) + } + } + + // Implements Iso between UnsignedTransaction and UnsignedErgoLikeTransaction + val isoUnsignedTransaction: Iso[UnsignedTransaction, UnsignedErgoLikeTransaction] = + new Iso[UnsignedTransaction, UnsignedErgoLikeTransaction] { + override def to(a: UnsignedTransaction): UnsignedErgoLikeTransaction = { + new UnsignedErgoLikeTransaction( + inputs = isoArrayToIndexed(isoUnsignedInput).to(a.inputs), + dataInputs = isoArrayToIndexed(isoDataInput).to(a.dataInputs), + outputCandidates = isoArrayToIndexed(isoBoxCandidate).to(a.outputs), + ) + } + override def from(b: UnsignedErgoLikeTransaction): UnsignedTransaction = { + UnsignedTransaction( + inputs = isoArrayToIndexed(isoUnsignedInput).from(b.inputs), + dataInputs = isoArrayToIndexed(isoDataInput).from(b.dataInputs), + outputs = isoArrayToIndexed(isoBoxCandidate).from(b.outputCandidates) + ) + } + } + + val isoBox: Iso[Box[commonMod.Amount], ErgoBox] = new Iso[Box[commonMod.Amount], ErgoBox] { + import sigmastate.eval._ + override def to(x: Box[commonMod.Amount]): ErgoBox = { + val ergoBox = new ErgoBox( + value = isoAmount.to(x.value), + ergoTree = { + val bytes = Base16.decode(x.ergoTree).get + ErgoTreeSerializer.DefaultSerializer.deserializeErgoTree(bytes) + }, + creationHeight = x.creationHeight.toInt, + additionalTokens = isoTokenArray.to(x.assets), + additionalRegisters = isoNonMandatoryRegisters.to(x.additionalRegisters), + transactionId = ModifierId @@ x.transactionId, + index = x.index.toShort + ) + ergoBox + } + + override def from(x: ErgoBox): Box[commonMod.Amount] = { + val ergoTree = ErgoTreeSerializer.DefaultSerializer.serializeErgoTree(x.ergoTree) + val ergoTreeStr = Base16.encode(ergoTree) + val assets = isoTokenArray.from(x.additionalTokens) + Box[commonMod.Amount]( + boxId = Base16.encode(x.id), + ergoTree = ergoTreeStr, + value = isoAmount.from(x.value), + assets = assets, + creationHeight = x.creationHeight, + additionalRegisters = isoNonMandatoryRegisters.from(x.additionalRegisters), + transactionId = x.transactionId, + index = x.index + ) + } + } + + val isoEIP12UnsignedInput: Iso[inputsMod.EIP12UnsignedInput, ExtendedInputBox] = + new Iso[inputsMod.EIP12UnsignedInput, ExtendedInputBox] { + override def to(x: inputsMod.EIP12UnsignedInput): ExtendedInputBox = { + val box = Box[commonMod.Amount]( + boxId = x.boxId, + ergoTree = x.ergoTree, + value = x.value, + assets = x.assets.asInstanceOf[js.Array[TokenAmount[commonMod.Amount]]], + creationHeight = x.creationHeight, + additionalRegisters = x.additionalRegisters, + transactionId = x.transactionId, + index = x.index + ) + val ergoBox = isoBox.to(box) + val extendedInputBox = ExtendedInputBox(ergoBox, isoContextExtension.to(x.extension)) + extendedInputBox + } + override def from(x: ExtendedInputBox): inputsMod.EIP12UnsignedInput = { + val box = isoBox.from(x.box) + val ext = isoContextExtension.from(x.extension) + inputsMod.EIP12UnsignedInput( + boxId = box.boxId, + ergoTree = box.ergoTree, + value = box.value.toString, + assets = box.assets.asInstanceOf[js.Array[TokenAmount[String]]], + creationHeight = box.creationHeight, + additionalRegisters = box.additionalRegisters, + transactionId = box.transactionId, + index = box.index, + extension = ext + ) + } + } +} \ No newline at end of file diff --git a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/PreHeader.scala b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/PreHeader.scala new file mode 100644 index 0000000000..7e38032446 --- /dev/null +++ b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/PreHeader.scala @@ -0,0 +1,16 @@ +package org.ergoplatform.sdk.js + +import scala.scalajs.js +import scala.scalajs.js.annotation.JSExportTopLevel + +/** Equivalent of [[special.sigma.PreHeader]] available from JS. */ +@JSExportTopLevel("PreHeader") +class PreHeader( + val version: Byte, + val parentId: String, + val timestamp: js.BigInt, + val nBits: js.BigInt, + val height: Int, + val minerPk: String, + val votes: String +) extends js.Object diff --git a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/ProverBuilder.scala b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/ProverBuilder.scala new file mode 100644 index 0000000000..4dfce27d44 --- /dev/null +++ b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/ProverBuilder.scala @@ -0,0 +1,92 @@ +package org.ergoplatform.sdk.js + +import org.ergoplatform.ErgoAddressEncoder.NetworkPrefix +import org.ergoplatform.sdk.wallet.protocol.context.ErgoLikeParameters +import org.ergoplatform.sdk +import org.ergoplatform.sdk.SecretString + +import scala.scalajs.js +import scala.scalajs.js.annotation.JSExportTopLevel +import Isos._ +import sigmastate.eval.SigmaDsl + +/** Equivalent of [[sdk.ProverBuilder]] available from JS. */ +@JSExportTopLevel("ProverBuilder") +class ProverBuilder(parameters: ErgoLikeParameters, networkPrefix: NetworkPrefix) extends js.Object { + val _builder = new sdk.ProverBuilder(parameters, networkPrefix) + + /** Configure this builder to use the given seed when building a new prover. + * + * @param mnemonicPhrase secret seed phrase to be used in prover for generating proofs. + * @param mnemonicPass password to protect secret seed phrase. + */ + def withMnemonic(mnemonicPhrase: String, mnemonicPass: String): ProverBuilder = { + _builder.withMnemonic( + SecretString.create(mnemonicPhrase), + SecretString.create(mnemonicPass), + usePre1627KeyDerivation = false + ) + this + } + + /** Configure this builder to derive the new EIP-3 secret key with the given index. + * The derivation uses master key derived from the mnemonic configured using + * [[ErgoProverBuilder.withMnemonic]]. + * + * @param index last index in the EIP-3 derivation path. + */ + def withEip3Secret(index: Int): ProverBuilder = { + _builder.withEip3Secret(index) + this + } + + /** Configures this builder to use group elements (g, h, u, v) and secret x for a + * ProveDHTuple statement when building a new prover. + * + * ProveDHTuple is a statement consisting of 4 group elements (g, h, u, v) and + * requires the prover to prove knowledge of secret integer x such that. + * + * u = g^x + * and + * y = h^x + * + * @param g [[GroupElement]] instance defining g + * @param h [[GroupElement]] instance defining h + * @param u [[GroupElement]] instance defining u + * @param v [[GroupElement]] instance defining v + * @param x [[BigInteger]] instance defining x + * @see + * example + * @see + * implementation + */ + def withDHTSecret(g: String, h: String, u: String, v: String, x: js.BigInt): ProverBuilder = { + _builder.withDHTData( + isoStringToGroupElement.to(g), + isoStringToGroupElement.to(h), + isoStringToGroupElement.to(u), + isoStringToGroupElement.to(v), + SigmaDsl.toBigInteger(isoBigInt.to(x)) + ) + this + } + + /** This allows adding additional secret for use in proveDlog, when the secret is not + * part of the wallet. + * + * Multiple secrets can be added by calling this method multiple times. + * + * Multiple secrets are necessary for statements that need multiple proveDlogs, such + * as proveDlog(a) && proveDlog(b), where a and b are two group elements. + */ + def withDLogSecret(x: js.BigInt): ProverBuilder = { + _builder.withDLogSecret(SigmaDsl.toBigInteger(isoBigInt.to(x))) + this + } + + /** Builds a new prover using provided configuration. */ + def build(): SigmaProver = { + val p =_builder.build() + new SigmaProver(p) + } +} diff --git a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/SigmaProver.scala b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/SigmaProver.scala new file mode 100644 index 0000000000..ac3dd72f42 --- /dev/null +++ b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/SigmaProver.scala @@ -0,0 +1,58 @@ +package org.ergoplatform.sdk.js + +import org.ergoplatform.sdk +import typings.fleetSdkCommon.boxesMod.Box +import typings.fleetSdkCommon.{commonMod, inputsMod, tokenMod, transactionsMod} + +import scala.scalajs.js +import scala.scalajs.js.annotation.JSExportTopLevel + +/** Equivalent of [[sdk.SigmaProver]] available from JS. */ +@JSExportTopLevel("SigmaProver") +class SigmaProver(_prover: sdk.SigmaProver) extends js.Object { + import Isos._ + + //TODO finish implementation + def reduce( + stateCtx: BlockchainStateContext, + unsignedTx: transactionsMod.UnsignedTransaction, + boxesToSpend: js.Array[inputsMod.EIP12UnsignedInput], + baseCost: Int): ReducedTransaction = { + val tx = sdk.UnreducedTransaction( + unsignedTx = isoUnsignedTransaction.to(unsignedTx), + boxesToSpend = isoArrayToIndexed(isoEIP12UnsignedInput).to(boxesToSpend), + dataInputs = IndexedSeq.empty, + tokensToBurn = IndexedSeq.empty + ) + _prover.reduce( + isoBlockchainStateContext.to(stateCtx), + tx, + baseCost + ) + new ReducedTransaction + } + + def reduceTransaction( + unsignedTx: transactionsMod.UnsignedTransaction, + boxesToSpend: js.Array[inputsMod.EIP12UnsignedInput], + dataBoxes: js.Array[Box[commonMod.Amount]], + stateDigest: String, + baseCost: Int, + tokensToBurn: js.Array[tokenMod.TokenAmount[commonMod.Amount]] + ): (ReducedTransaction, Int) = { + val tx = Isos.isoUnsignedTransaction.to(unsignedTx) +// val inputs: = boxesToSpend.map(isoEIP12UnsignedInput.to).toArray + + (new ReducedTransaction, 0) + } + +} + +//TODO finish implementation +@JSExportTopLevel("ReducedTransaction") +class ReducedTransaction + +//TODO finish implementation +@JSExportTopLevel("ProverFactory") +object SigmaProver { +} diff --git a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Type.scala b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Type.scala new file mode 100644 index 0000000000..61f4252e37 --- /dev/null +++ b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Type.scala @@ -0,0 +1,49 @@ +package org.ergoplatform.sdk.js + +import scalan.RType + +import scala.scalajs.js +import scala.scalajs.js.annotation.JSExportTopLevel + +/** + * Runtime representation of ErgoScript types. Type is a JS friendly + * wrapper around {@link RType} type descriptor. + */ +@JSExportTopLevel("Type") +class Type(private[js] final val rtype: RType[_]) extends js.Object { + /** Syntactically correct type name (type expression as String) */ + def name: String = rtype.name + + override def toString = s"Type($rtype)" +} + +@JSExportTopLevel("Types") +object Type extends js.Object { + /** Descriptor of ErgoScript type Byte. */ + val Byte = new Type(RType.ByteType) + + /** Descriptor of ErgoScript type Short. */ + val Short = new Type(RType.ShortType) + + /** Descriptor of ErgoScript type Int. */ + val Int = new Type(RType.IntType) + + /** Descriptor of ErgoScript type Long. */ + val Long = new Type(RType.LongType) + + /** Constructs a new descriptor of ErgoScript pair type (l, r). + * @param l first component of the pair + * @param r second component of the pair + */ + def pairType(l: Type, r: Type): Type = { + new Type(RType.pairRType(l.rtype, r.rtype)) + } + + /** Constructs a new descriptor of ErgoScript collection type `Coll[elemType]`. + * + * @param elemType type descriptor of collection elements + */ + def collType(elemType: Type): Type = { + new Type(special.collection.collRType(elemType.rtype)) + } +} \ No newline at end of file diff --git a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Value.scala b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Value.scala new file mode 100644 index 0000000000..318e3b08ee --- /dev/null +++ b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Value.scala @@ -0,0 +1,211 @@ +package org.ergoplatform.sdk.js + +import scalan.RType +import scalan.RType.PairType +import scorex.util.Extensions.{IntOps, LongOps} +import scorex.util.encode.Base16 +import sigmastate.eval.{Colls, Evaluation} +import sigmastate.serialization.{DataSerializer, SigmaSerializer} +import sigmastate.SType +import Value.toRuntimeData +import special.collection.{Coll, CollType} + +import scala.scalajs.js +import scala.scalajs.js.annotation.JSExportTopLevel + +/** + * This class is used to represent any valid value of ErgoScript language. + * Any such value comes equipped with {@link Type} descriptor. + * Note, there is a distinction between JS types and ErgoScript types. + * Each Value instance represents the concrete ErgoScript type given by `tpe`. + * The implementation is based on the pre-defined mapping between JS and ES types. + * This mapping is applied recursively and is given by the following: + * + * JS type | ErgoScript Type + * -------------------------------------- + * Number | Byte + * Number | Short + * Number | Int + * BigInt | Long + * BigInt | BigInt + * array [A, B] | (A, B) - pair + * array [a1, a2 ..] | Coll[A] - collection + * + * @param data JS value wrapped in this value + * @param tpe type descriptor of the ErgoScript type + */ +@JSExportTopLevel("Value") +class Value(val data: Any, val tpe: Type) extends js.Object { + + /** Get Sigma runtime value which can be passed to interpreter, saved in register and + * [[sigmastate.Values.Constant]] nodes. + */ + final private[js] def runtimeData: Any = toRuntimeData(data, tpe.rtype) + + /** + * Encode this value as Base16 hex string. + * 1) it transforms this value into {@link sigmastate.Values.ConstantNode} of sigma. + * 2) it serializes the constant into byte array using {@link sigmastate.serialization.ConstantSerializer} + * 3) the bytes are encoded using Base16 encoder into string + * + * @return hex string of serialized bytes + */ + def toHex(): String = { + // this can be implemented using ConstantSerializer and isoValueToConstant, but this + // will add dependence on Constant and Values, which we want to avoid facilitate + // module splitting + // TODO simplify if module splitting fails + val stype = Evaluation.rtypeToSType(tpe.rtype) + val value = runtimeData.asInstanceOf[SType#WrappedType] + val w = SigmaSerializer.startWriter() + w.putType(stype) + DataSerializer.serialize(value, stype, w) + Base16.encode(w.toBytes) + } +} + +@JSExportTopLevel("Values") +object Value extends js.Object { + /** Maximal positive value of ES type Long */ + val MaxLong = js.BigInt("0x7fffffffffffffff") + + /** Minimal negative value of ES type Long */ + val MinLong = -js.BigInt("0x8000000000000000") + + /** Helper method to get Sigma runtime value which can be passed to interpreter, saved + * in register and [[sigmastate.Values.Constant]] nodes. + */ + final private[js] def toRuntimeData(data: Any, rtype: RType[_]): Any = rtype match { + case RType.ByteType | RType.ShortType | RType.IntType => data + case RType.LongType => java.lang.Long.parseLong(data.asInstanceOf[js.BigInt].toString(10)) + case ct: CollType[a] => + val xs = data.asInstanceOf[js.Array[Any]] + implicit val cT = ct.tItem.classTag + val items = xs.map(x => toRuntimeData(x, ct.tItem).asInstanceOf[a]).toArray[a] + Colls.fromItems(items:_*)(ct.tItem) + case pt: PairType[a, b] => + val p = data.asInstanceOf[js.Array[Any]] + val x = toRuntimeData(p(0), pt.tFst).asInstanceOf[a] + val y = toRuntimeData(p(1), pt.tSnd).asInstanceOf[b] + (x, y) + case _ => + throw new IllegalArgumentException(s"Unsupported type $rtype") + } + + /** Helper method to extract JS data value from Sigma runtime value. + * This should be inverse to `toRuntimeData`. + * + * @param value runtime value of type given by `rtype` + * @param rtype type descriptor of Sigma runtime value + */ + final private[js] def fromRuntimeData(value: Any, rtype: RType[_]): Any = rtype match { + case RType.ByteType | RType.ShortType | RType.IntType => value + case RType.LongType => js.BigInt(value.asInstanceOf[Long].toString) + case ct: CollType[a] => + val arr = value.asInstanceOf[Coll[a]].toArray + js.Array(arr.map(x => fromRuntimeData(x, ct.tItem)):_*) + case pt: PairType[a, b] => + val p = value.asInstanceOf[(a, b)] + js.Array(fromRuntimeData(p._1, pt.tFst), fromRuntimeData(p._2, pt.tSnd)) + case _ => + throw new IllegalArgumentException(s"Unsupported type $rtype") + } + + /** Helper method to check validity of JS data value against the given runtime type. + * + * @param data js value + * @param rtype type descriptor of Sigma runtime value + */ + final private def checkJsData[T](data: T, rtype: RType[_]): Any = rtype match { + case RType.ByteType => data.asInstanceOf[Int].toByteExact + case RType.ShortType => data.asInstanceOf[Int].toShortExact + case RType.IntType => data.asInstanceOf[Int].toLong.toIntExact + case RType.LongType => + val n = data.asInstanceOf[js.BigInt] + if (n < MinLong || n > MaxLong) + throw new ArithmeticException(s"value $n is out of long range") + n + case PairType(l, r) => data match { + case arr: js.Array[Any] => + checkJsData(arr(0), l) + checkJsData(arr(1), r) + data + case _ => + throw new ArithmeticException(s"$data cannot represent pair value") + } + case CollType(elemType) => data match { + case arr: js.Array[Any] => + arr.foreach(x => checkJsData(x, elemType)) + data + case _ => + throw new ArithmeticException(s"$data cannot represent Coll value") + } + case _ => + throw new IllegalArgumentException(s"Unsupported type $rtype") + } + + /** Create Byte value from JS number. */ + def ofByte(n: Int): Value = { + checkJsData(n, Type.Byte.rtype) + new Value(n, Type.Byte) + } + + /** Create Short value from JS number. */ + def ofShort(n: Int): Value = { + checkJsData(n, Type.Short.rtype) + new Value(n, Type.Short) + } + + /** Create Int value from JS number. */ + def ofInt(n: Int): Value = { + checkJsData(n, Type.Int.rtype) + new Value(n, Type.Int) + } + + /** Create Long value from JS BigInt. */ + def ofLong(n: js.BigInt): Value = { + checkJsData(n, Type.Long.rtype) + new Value(n, Type.Long) + } + + /** Create Pair value from two values. */ + def pairOf(l: Value, r: Value): Value = { + val data = js.Array(l.data, r.data) // the l and r data have been validated + new Value(data, Type.pairType(l.tpe, r.tpe)) + } + + /** Create Coll value from array and element type descriptor. + * @param items collection elements which should be valid JS representation of `elemType` + * @param elemType descriptor of types for collection elements + */ + def collOf(items: js.Array[Any], elemType: Type): Value = { + val t = Type.collType(elemType) + checkJsData(items, t.rtype) + new Value(items, t) + } + + /** + * Creates Value from hex encoded serialized bytes of Constant values. + *

+ * In order to create Value you need to provide both value instance and + * Type descriptor. This is similar to how values are represented in sigma + * ConstantNode. Each ConstantNode also have value instance and `tpe: SType` + * descriptor. + * @param hex the string is obtained as hex encoding of serialized ConstantNode. + * (The bytes obtained by ConstantSerializer in sigma) + * @return new deserialized ErgoValue instance + */ + def fromHex(hex: String): Value = { + // this can be implemented using ConstantSerializer and isoValueToConstant, but this + // will add dependence on Constant and Values, which we want to avoid facilitate + // module splitting + // TODO simplify if module splitting fails + val bytes = Base16.decode(hex).fold(t => throw t, identity) + val r = SigmaSerializer.startReader(bytes) + val stype = r.getType() + val value = DataSerializer.deserialize(stype, r) + val rtype = Evaluation.stypeToRType(stype) + val jsvalue = fromRuntimeData(value, rtype) + new Value(jsvalue, new Type(rtype)) + } +} diff --git a/sdk/js/src/test/scala/org/ergoplatform/sdk/js/IsosSpec.scala b/sdk/js/src/test/scala/org/ergoplatform/sdk/js/IsosSpec.scala new file mode 100644 index 0000000000..597ee2fb3e --- /dev/null +++ b/sdk/js/src/test/scala/org/ergoplatform/sdk/js/IsosSpec.scala @@ -0,0 +1,186 @@ +package org.ergoplatform.sdk.js + +import org.ergoplatform.ErgoBox.{AdditionalRegisters, BoxId, TokenId} +import org.ergoplatform.sdk.{ExtendedInputBox, Iso} +import org.ergoplatform._ +import org.ergoplatform.sdk.wallet.protocol.context.{CErgoLikeStateContext, ErgoLikeStateContext} +import org.scalacheck.{Arbitrary, Gen} +import org.scalatest.matchers.should.Matchers +import org.scalatest.propspec.AnyPropSpec +import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks +import scorex.crypto.authds.ADDigest +import sigmastate.SType +import sigmastate.Values.Constant +import sigmastate.eval.Colls +import sigmastate.interpreter.ContextExtension +import sigmastate.serialization.generators.ObjectGenerators +import special.collection.Coll +import special.sigma +import special.sigma.GroupElement + +import scala.scalajs.js + +class IsosSpec extends AnyPropSpec with Matchers with ObjectGenerators with ScalaCheckPropertyChecks{ + + lazy val extendedInputBoxGen: Gen[ExtendedInputBox] = for { + box <- ergoBoxGen + extension <- contextExtensionGen + } yield ExtendedInputBox(box, extension) + + lazy val ergoLikeStateContextGen: Gen[ErgoLikeStateContext] = for { + stateRoot <- avlTreeGen + headers <- headersGen(stateRoot) + preHeader <- preHeaderGen(headers.headOption.map(_.id).getOrElse(modifierIdBytesGen.sample.get)) + } yield CErgoLikeStateContext( + sigmaLastHeaders = Colls.fromItems(headers:_*), + previousStateDigest = ADDigest @@ stateRoot.digest.toArray, + sigmaPreHeader = preHeader + ) + + def roundtrip[A,B](iso: Iso[A,B])(b: B): Unit = { + val invIso = iso.inverse + invIso.from(invIso.to(b)) shouldBe b + } + + override implicit val generatorDrivenConfig: PropertyCheckConfiguration = PropertyCheckConfiguration(minSuccessful = 30) + + property("Iso.isoStringToArray") { + forAll() { (bytes: Array[Byte]) => + roundtrip(Isos.isoStringToArray)(bytes) + } + } + + property("Iso.isoStringToColl") { + forAll() { (bytes: Coll[Byte]) => + roundtrip(Isos.isoStringToColl)(bytes) + } + } + + property("Iso.isoStringToGroupElement") { + forAll() { (bytes: GroupElement) => + roundtrip(Isos.isoStringToGroupElement)(bytes) + } + } + + property("Iso.isoBoxId") { + forAll(boxIdGen) { (id: BoxId) => + roundtrip(Isos.isoBoxId)(id) + } + } + + property("Iso.isoHexStringToConstant") { + forAll(constantGen, MinSuccessful(100)) { (c: Constant[SType]) => + roundtrip(Isos.isoHexStringToConstant)(c) + } + } + + property("Iso.avlTree") { + forAll { (c: sigma.AvlTree) => + roundtrip(Isos.isoAvlTree)(c) + } + } + + property("Iso.isoHeader") { + forAll { (c: sigma.Header) => + roundtrip(Isos.isoHeader)(c) + } + } + + property("Iso.isoPreHeader") { + forAll { (c: sigma.PreHeader) => + roundtrip(Isos.isoPreHeader)(c) + } + } + + property("Iso.isoBlockchainStateContext") { + forAll(ergoLikeStateContextGen) { (c: ErgoLikeStateContext) => + roundtrip(Isos.isoBlockchainStateContext)(c) + } + } + + property("Iso.isoContextExtension") { + forAll { (c: ContextExtension) => + roundtrip(Isos.isoContextExtension)(c) + } + } + + property("Iso.isoUnsignedInput") { + forAll { (c: UnsignedInput) => + roundtrip(Isos.isoUnsignedInput)(c) + } + } + + property("Iso.isoDataInput") { + forAll { (c: DataInput) => + roundtrip(Isos.isoDataInput)(c) + } + } + + property("Iso.isoBigInt") { + forAll { (c: sigma.BigInt) => + roundtrip(Isos.isoBigInt)(c) + } + } + + property("Iso.isoBigIntToLong") { + forAll { (c: Long) => + roundtrip(Isos.isoBigIntToLong)(c) + } + } + + property("Iso.isoAmount") { + forAll { (c: Long) => + roundtrip(Isos.isoAmount)(c) + Isos.isoAmount.to(js.BigInt(c.toString)) shouldBe c + } + } + + property("Iso.isoToken") { + forAll(tokenIdGen, Arbitrary.arbLong.arbitrary) { (id: TokenId, amount: Long) => + roundtrip(Isos.isoToken)((id, amount)) + } + } + + property("Iso.isoTokenArray") { + forAll(ergoBoxTokens(tokensGen.sample.get)) { tokens => + roundtrip(Isos.isoTokenArray)(tokens) + } + } + + property("Iso.isoUndefOr") { + forAll { opt: Option[Long] => + roundtrip(Isos.isoUndefOr(Iso.identityIso[Long]))(opt) + } + } + + property("Iso.isoNonMandatoryRegisters") { + forAll(additionalRegistersGen) { (x: AdditionalRegisters) => + roundtrip(Isos.isoNonMandatoryRegisters)(x) + } + } + + property("Iso.isoBoxCandidate") { + forAll { (box: ErgoBoxCandidate) => + roundtrip(Isos.isoBoxCandidate)(box) + } + } + + ignore("Iso.isoUnsignedTransaction") { + forAll { (tx: UnsignedErgoLikeTransaction) => + roundtrip(Isos.isoUnsignedTransaction)(tx) + } + } + + property("Iso.isoBox") { + forAll { (b: ErgoBox) => + roundtrip(Isos.isoBox)(b) + } + } + + property("Iso.isoEIP12UnsignedInput") { + forAll(extendedInputBoxGen) { (b: ExtendedInputBox) => + roundtrip(Isos.isoEIP12UnsignedInput)(b) + } + } + +} diff --git a/sdk/js/src/test/scala/org/ergoplatform/sdk/js/SigmaProverSpec.scala b/sdk/js/src/test/scala/org/ergoplatform/sdk/js/SigmaProverSpec.scala new file mode 100644 index 0000000000..3c19cbb7ec --- /dev/null +++ b/sdk/js/src/test/scala/org/ergoplatform/sdk/js/SigmaProverSpec.scala @@ -0,0 +1,10 @@ +package org.ergoplatform.sdk.js + +import org.scalatest.matchers.should.Matchers +import org.scalatest.propspec.AnyPropSpec + +class SigmaProverSpec extends AnyPropSpec with Matchers { + property("Prover.create") { + + } +} diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/AppkitProvingInterpreter.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/AppkitProvingInterpreter.scala new file mode 100644 index 0000000000..f114325ef9 --- /dev/null +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/AppkitProvingInterpreter.scala @@ -0,0 +1,350 @@ +package org.ergoplatform.sdk + +import debox.cfor +import org.ergoplatform._ +import org.ergoplatform.sdk.Extensions.{CollOps, PairCollOps} +import org.ergoplatform.sdk.JavaHelpers.{TokenColl, UniversalConverter} +import org.ergoplatform.sdk.utils.ArithUtils +import org.ergoplatform.sdk.wallet.protocol.context.{ErgoLikeParameters, ErgoLikeStateContext} +import org.ergoplatform.sdk.wallet.secrets.ExtendedSecretKey +import scalan.util.Extensions.LongOps +import sigmastate.Values.SigmaBoolean +import sigmastate.{AvlTreeData, VersionContext} +import sigmastate.basics.DLogProtocol.{DLogProverInput, ProveDlog} +import sigmastate.basics.{DiffieHellmanTupleProverInput, SigmaProtocolPrivateInput} +import sigmastate.interpreter.Interpreter.{ReductionResult, estimateCryptoVerifyCost} +import sigmastate.interpreter.{ContextExtension, CostedProverResult, HintsBag, Interpreter, ProverInterpreter, ProverResult} +import sigmastate.serialization.SigmaSerializer +import sigmastate.utils.{SigmaByteReader, SigmaByteWriter} +import org.ergoplatform.sdk.wallet.protocol.context.TransactionContext +import org.ergoplatform.validation.ValidationRules +import scorex.crypto.authds.ADDigest + +import java.util +import java.util.{Objects, List => JList} +import scala.collection.mutable +import scala.util.Try + +/** + * A class which holds secrets and can sign transactions (aka generate proofs). + * + * @param secretKeys secrets in extended form to be used by prover + * @param dLogInputs prover inputs containing secrets for generating proofs for [[ProveDlog]] nodes. + * @param dhtInputs prover inputs containing secrets for generating proofs for [[ProveDHTuple]] nodes. + * @param params ergo blockchain parameters + */ +class AppkitProvingInterpreter( + val secretKeys: IndexedSeq[ExtendedSecretKey], + val dLogInputs: IndexedSeq[DLogProverInput], + val dhtInputs: IndexedSeq[DiffieHellmanTupleProverInput], + params: ErgoLikeParameters) + extends ReducingInterpreter(params) with ProverInterpreter { + + override type CTX = ErgoLikeContext + import org.ergoplatform.sdk.Iso._ + + /** All secrets available to this interpreter including [[ExtendedSecretKey]], dlog and + * dht secrets. + */ + override val secrets: Seq[SigmaProtocolPrivateInput[_, _]] = { + val dlogs: IndexedSeq[DLogProverInput] = secretKeys.map(_.privateInput) + dlogs ++ dLogInputs ++ dhtInputs + } + + /** Public keys corresponding to dlog secrets (aka publicImage). */ + val pubKeys: Seq[ProveDlog] = secrets + .filter { case _: DLogProverInput => true case _ => false } + .map(_.asInstanceOf[DLogProverInput].publicImage) + + /** Helper method to accumulate cost while checking limit. + * + * @param currentCost current cost value + * @param delta additional cost to add to the current value + * @param limit total cost limit + * @param msgSuffix suffix added to the exception message + * @return new increased cost when it doesn't exceed the limit + * @throws Exception + */ + def addCostLimited(currentCost: Long, delta: Long, limit: Long, msgSuffix: => String): Long = { + val newCost = java7.compat.Math.addExact(currentCost, delta) + if (newCost > limit) + throw new Exception(s"Cost of transaction $newCost exceeds limit $limit: $msgSuffix") + newCost + } + + /** Reduces and signs the given transaction. + * + * @param unreducedTx unreduced transaction data to be reduced (contains unsigned transaction) + * @param stateContext state context of the blockchain in which the transaction should be signed + * @param baseCost the cost accumulated before this transaction + * @return a new signed transaction with all inputs signed and the cost of this transaction + * The returned cost doesn't include `baseCost`. + */ + def sign(unreducedTx: UnreducedTransaction, + stateContext: ErgoLikeStateContext, + baseCost: Int): Try[SignedTransaction] = Try { + val maxCost = params.maxBlockCost + var currentCost: Long = baseCost + + val reducedTx = reduceTransaction(unreducedTx, stateContext, baseCost) + currentCost = addCostLimited(currentCost, reducedTx.ergoTx.cost, maxCost, msgSuffix = reducedTx.toString()) + + val signedTx = signReduced(reducedTx, currentCost.toInt) + currentCost += signedTx.cost // this never overflows if signReduced is successful + + val reductionAndVerificationCost = (currentCost - baseCost).toIntExact + signedTx.copy(cost = reductionAndVerificationCost) + } + + /** Reduce inputs of the given unsigned transaction to provable sigma propositions using + * the given context. See [[ReducedErgoLikeTransaction]] for details. + * + * @note requires `unsignedTx` and `boxesToSpend` have the same boxIds in the same order. + * @param boxesToSpend input boxes of the transaction + * @param dataBoxes data inputs of the transaction + * @param stateContext state context of the blockchain in which the transaction should be signed + * @param baseCost the cost accumulated so far and before this operation + * @param tokensToBurn requested tokens to be burnt in the transaction, if empty no burning allowed + * @return a new reduced transaction with all inputs reduced and the cost of this transaction + * The returned cost doesn't include (so they need to be added back to get the total cost): + * 1) `baseCost` + * 2) reduction cost for each input. + */ + def reduceTransaction( + unsignedTx: UnsignedErgoLikeTransaction, + boxesToSpend: IndexedSeq[ExtendedInputBox], + dataBoxes: IndexedSeq[ErgoBox], + stateContext: ErgoLikeStateContext, + baseCost: Int, + tokensToBurn: IndexedSeq[ErgoToken]): ReducedErgoLikeTransaction = { + if (unsignedTx.inputs.length != boxesToSpend.length) throw new Exception("Not enough boxes to spend") + if (unsignedTx.dataInputs.length != dataBoxes.length) throw new Exception("Not enough data boxes") + + val inputTokens = boxesToSpend.flatMap(_.box.additionalTokens.toArray) + val outputTokens = unsignedTx.outputCandidates.flatMap(_.additionalTokens.toArray) + val tokenDiff = JavaHelpers.subtractTokens(outputTokens, inputTokens) + if (tokenDiff.nonEmpty) { + val (toBurn, toMint) = tokenDiff.partition(_._2 < 0) // those with negative diff are to be burnt + if (toBurn.nonEmpty) { + if (tokensToBurn.nonEmpty) { + val requestedToBurn = isoTokensListToTokenColl.to(tokensToBurn.convertTo[JList[ErgoToken]]) + val diff = JavaHelpers.subtractTokenColls( + reducedTokens = toBurn.mapSecond(v => -v), // make positive amounts + subtractedTokens = requestedToBurn + ) + if (diff.nonEmpty) { // empty diff would mean equality + throw TokenBalanceException( + "Transaction tries to burn tokens, but not how it was requested", diff) + } + } else { + throw TokenBalanceException( + "Transaction tries to burn tokens when no burning was requested", tokenDiff) + } + } + if (toMint.nonEmpty) { + if (toMint.length > 1) { + throw TokenBalanceException("Only one token can be minted in a transaction", toMint) + } + val isCorrectMintedTokenId = Objects.deepEquals(toMint(0)._1.toArray, boxesToSpend.head.box.id) + if (!isCorrectMintedTokenId) { + throw TokenBalanceException("Cannot mint a token with invalid id", toMint) + } + } + } + + // Cost of transaction initialization: we should read and parse all inputs and data inputs, + // and also iterate through all outputs to check rules + val initialCost = ArithUtils.addExact( + Interpreter.interpreterInitCost, + java7.compat.Math.multiplyExact(boxesToSpend.size, params.inputCost), + java7.compat.Math.multiplyExact(dataBoxes.size, params.dataInputCost), + java7.compat.Math.multiplyExact(unsignedTx.outputCandidates.size, params.outputCost) + ) + val maxCost = params.maxBlockCost + val startCost = addCostLimited(baseCost, initialCost, maxCost, msgSuffix = unsignedTx.toString()) + + val transactionContext = TransactionContext(boxesToSpend.map(_.box), dataBoxes, unsignedTx) + + val (outAssets, outAssetsNum) = JavaHelpers.extractAssets(unsignedTx.outputCandidates) + val (inAssets, inAssetsNum) = JavaHelpers.extractAssets(boxesToSpend.map(_.box)) + + val tokenAccessCost = params.tokenAccessCost + val totalAssetsAccessCost = + java7.compat.Math.addExact( + java7.compat.Math.multiplyExact(java7.compat.Math.addExact(outAssetsNum, inAssetsNum), tokenAccessCost), + java7.compat.Math.multiplyExact(java7.compat.Math.addExact(inAssets.size, outAssets.size), tokenAccessCost)) + + val txCost = addCostLimited(startCost, + delta = totalAssetsAccessCost, + limit = maxCost, msgSuffix = s"when adding assets cost of $totalAssetsAccessCost") + + var currentCost = txCost + val reducedInputs = mutable.ArrayBuilder.make[ReducedInputData] + + for ((inputBox, boxIdx) <- boxesToSpend.zipWithIndex) { + val unsignedInput = unsignedTx.inputs(boxIdx) + require(util.Arrays.equals(unsignedInput.boxId, inputBox.box.id)) + + val context = new ErgoLikeContext( + AvlTreeData.avlTreeFromDigest(stateContext.previousStateDigest), + stateContext.sigmaLastHeaders, + stateContext.sigmaPreHeader, + transactionContext.dataBoxes, + transactionContext.boxesToSpend, + transactionContext.spendingTransaction, + boxIdx.toShort, + inputBox.extension, + ValidationRules.currentSettings, + costLimit = maxCost, + initCost = currentCost, + activatedScriptVersion = (params.blockVersion - 1).toByte + ) + + val reducedInput = reduce(Interpreter.emptyEnv, inputBox.box.ergoTree, context) + currentCost = reducedInput.reductionResult.cost // Note, this value includes context.initCost + reducedInputs += reducedInput + } + val reducedTx = ReducedErgoLikeTransaction( + unsignedTx, reducedInputs.result(), cost = (currentCost - baseCost).toIntExact) + reducedTx + } + + /** Signs the given transaction (i.e. providing spending proofs) for each input so that + * the resulting transaction can be submitted to the blockchain. + * Note, this method doesn't require context to generate proofs (aka signatures). + * + * @param reducedTx unsigend transaction augmented with reduced + * @return a new signed transaction with all inputs signed and the cost of this transaction + * The returned cost includes: + * - the costs of obtaining reduced transaction + * - the cost of verification of each signed input + */ + def signReduced(reducedTx: ReducedTransaction, baseCost: Int): SignedTransaction = { + val provedInputs = mutable.ArrayBuilder.make[Input] + val unsignedTx = reducedTx.ergoTx.unsignedTx + + val maxCost = params.maxBlockCost + var currentCost: Long = baseCost + + for ((reducedInput, boxIdx) <- reducedTx.ergoTx.reducedInputs.zipWithIndex ) { + val unsignedInput = unsignedTx.inputs(boxIdx) + val proverResult = proveReduced(reducedInput, unsignedTx.messageToSign) + val signedInput = Input(unsignedInput.boxId, proverResult) + + val verificationCost = estimateCryptoVerifyCost(reducedInput.reductionResult.value).toBlockCost + currentCost = addCostLimited(currentCost, verificationCost, maxCost, msgSuffix = signedInput.toString()) + + provedInputs += signedInput + } + + val signedTx = new ErgoLikeTransaction( + provedInputs.result(), unsignedTx.dataInputs, unsignedTx.outputCandidates) + + // compute accumulated crypto verification cost of all inputs + val txVerificationCost = (currentCost - baseCost).toIntExact + SignedTransaction(signedTx, txVerificationCost) + } + + // TODO pull this method up to the base class and reuse in `prove` + /** Generates proof (aka signature) for the given message using secrets of this prover. + * All the necessary secrets should be configured in this prover to satisfy the given + * sigma proposition in the reducedInput. + */ + def proveReduced( + reducedInput: ReducedInputData, + message: Array[Byte], + hintsBag: HintsBag = HintsBag.empty): ProverResult = { + val proof = generateProof(reducedInput.reductionResult.value, message, hintsBag) + new ProverResult(proof, reducedInput.extension) + } + +} + +/** Thrown during transaction signing when inputs token are not balanced with output tokens. + * @param tokensDiff balance difference which caused the error + */ +case class TokenBalanceException( + message: String, + tokensDiff: TokenColl +) extends Exception(s"Input and output tokens are not balanced: $message") + +/** Represents data necessary to sign an input of an unsigned transaction. + * @param reductionResult result of reducing input script to a sigma proposition + * @param extension context extensions (aka context variables) used by script and which + * are also necessary to verify the transaction on-chain. Extensions are + * included in tx bytes, which are signed. + */ +case class ReducedInputData(reductionResult: ReductionResult, extension: ContextExtension) + +/** Represent `reduced` transaction, i.e. unsigned transaction where each unsigned input + * is augmented with [[ReducedInputData]] which contains a script reduction result. + * After an unsigned transaction is reduced it can be signed without context. + * Thus, it can be serialized and transferred for example to Cold Wallet and signed + * in an environment where secrets are known. + */ +case class ReducedErgoLikeTransaction( + unsignedTx: UnsignedErgoLikeTransaction, + reducedInputs: Seq[ReducedInputData], + cost: Int +) { + require(unsignedTx.inputs.length == reducedInputs.length) +} + +/** HOTSPOT: don't beautify the code */ +object ReducedErgoLikeTransactionSerializer extends SigmaSerializer[ReducedErgoLikeTransaction, ReducedErgoLikeTransaction] { + + override def serialize(tx: ReducedErgoLikeTransaction, w: SigmaByteWriter): Unit = { + val msg = tx.unsignedTx.messageToSign + w.putUInt(msg.length) // size of the tx bytes to restore tx reliably + w.putBytes(msg) + + // serialize sigma propositions for each input + val nInputs = tx.reducedInputs.length + // no need to save nInputs because it is known from unsignedTx.inputs + cfor(0)(_ < nInputs, _ + 1) { i => + val input = tx.reducedInputs(i) + SigmaBoolean.serializer.serialize(input.reductionResult.value, w) + w.putULong(input.reductionResult.cost) + // Note, we don't need to save `extension` field because it has already + // been saved in msg + } + w.putUInt(tx.cost) + } + + override def parse(r: SigmaByteReader): ReducedErgoLikeTransaction = { + val nBytes = r.getUInt() + val msg = r.getBytes(nBytes.toIntExact) + + // here we read ErgoLikeTransaction which is used below as raw data for + // the new UnsignedErgoLikeTransaction + val tx = ErgoLikeTransactionSerializer.parse(SigmaSerializer.startReader(msg)) + + // serialize sigma propositions for each input + val nInputs = tx.inputs.length + val reducedInputs = new Array[ReducedInputData](nInputs) + val unsignedInputs = new Array[UnsignedInput](nInputs) + cfor(0)(_ < nInputs, _ + 1) { i => + val sb = SigmaBoolean.serializer.parse(r) + val cost = r.getULong() + val input = tx.inputs(i) + val extension = input.extension + val reductionResult = ReductionResult(sb, cost) + reducedInputs(i) = ReducedInputData(reductionResult, extension) + unsignedInputs(i) = new UnsignedInput(input.boxId, extension) + } + val cost = r.getUIntExact + val unsignedTx = UnsignedErgoLikeTransaction(unsignedInputs, tx.dataInputs, tx.outputCandidates) + ReducedErgoLikeTransaction(unsignedTx, reducedInputs, cost) + } + + /** Parses the [[ReducedErgoLikeTransaction]] using the given blockVersion. + * @param blockVersion version of Ergo protocol to use during parsing. + */ + def parse(r: SigmaByteReader, blockVersion: Byte): ReducedErgoLikeTransaction = { + val scriptVersion = (blockVersion - 1).toByte + VersionContext.withVersions(scriptVersion, scriptVersion) { + parse(r) + } + } +} + diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/ErgoId.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/ErgoId.scala new file mode 100644 index 0000000000..088b16fe77 --- /dev/null +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/ErgoId.scala @@ -0,0 +1,36 @@ +package org.ergoplatform.sdk + +import scorex.utils.Ints + +import java.util + +object ErgoId { + /** Creates a new ErgoId decoding it from the given hex string. */ + def create(base16Str: String) = new ErgoId(JavaHelpers.decodeStringToBytes(base16Str)) +} + +/** + * Identifier of Ergo object which wraps byte array (usually 256 bit hash). + * ErgoId supports equality. + */ +class ErgoId(val _idBytes: Array[Byte]) { + /** Extracts underlying byte array with id bytes. */ + def getBytes = _idBytes + + override def hashCode = + if (_idBytes != null && _idBytes.length >= 4) Ints.fromByteArray(_idBytes) + else util.Arrays.hashCode(_idBytes) + + override def equals(obj: Any): Boolean = { + if (obj == null) return false + if (this eq obj.asInstanceOf[AnyRef]) return true + obj match { + case that: ErgoId => + util.Arrays.equals(this._idBytes, that._idBytes) + case _ => false + } + } + + /** String representation of id using Base16 encoding. */ + override def toString = JavaHelpers.Algos.encode(_idBytes) +} \ No newline at end of file diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/ErgoToken.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/ErgoToken.scala new file mode 100644 index 0000000000..0b8714125d --- /dev/null +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/ErgoToken.scala @@ -0,0 +1,21 @@ +package org.ergoplatform.sdk + +/** + * Represents ergo token (aka asset) paired with its value. + * Implements equality and can be used as keys for maps and sets. + */ +case class ErgoToken(id: ErgoId, value: Long) { + def this(idBytes: Array[Byte], value: Long) { + this(new ErgoId(idBytes), value) + } + + def this(id: String, value: Long) { + this(JavaHelpers.decodeStringToBytes(id), value) + } + + /** Java friendly id accessor method. */ + def getId: ErgoId = id + + /** Java friendly value accessor method. */ + def getValue: Long = value +} \ No newline at end of file diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/ExtendedInputBox.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/ExtendedInputBox.scala new file mode 100644 index 0000000000..311475c134 --- /dev/null +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/ExtendedInputBox.scala @@ -0,0 +1,19 @@ +package org.ergoplatform.sdk + +import org.ergoplatform.{ErgoBox, UnsignedInput} +import sigmastate.interpreter.ContextExtension + +/** Input ErgoBox paired with context variables (aka ContextExtensions). + * + * @param box an instance of ErgoBox which is used as an input of the transaction. + * @param extension a set of context variables necessary to satisfy the box's + * guarding proposition. + * This extension is also saved in the corresponding + * [[org.ergoplatform.Input]] instance of the signed transaction. + */ +case class ExtendedInputBox( + box: ErgoBox, + extension: ContextExtension +) { + def toUnsignedInput: UnsignedInput = new UnsignedInput(box.id, extension) +} diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/Extensions.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/Extensions.scala new file mode 100644 index 0000000000..840ec21576 --- /dev/null +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/Extensions.scala @@ -0,0 +1,199 @@ +package org.ergoplatform.sdk + +import debox.cfor +import scalan.RType +import scalan.rtypeToClassTag // actually required +import special.collection.{Coll, CollBuilder, PairColl} + +import scala.collection.compat.BuildFrom +import scala.collection.{GenIterable, immutable} +import scala.reflect.ClassTag + +object Extensions { + + implicit class GenIterableOps[A, Source[X] <: GenIterable[X]](val xs: Source[A]) extends AnyVal { + + /** Apply m for each element of this collection, group by key and reduce each group + * using r. + * Note, the ordering of the resulting keys is deterministic and the keys appear in + * the order they first produced by `map`. + * + * @returns one item for each group in a new collection of (K,V) pairs. */ + def mapReduce[K, V](map: A => (K, V))(reduce: (V, V) => V) + (implicit cbf: BuildFrom[Source[A], (K, V), Source[(K, V)]]): Source[(K, V)] = { + val result = scala.collection.mutable.LinkedHashMap.empty[K, V] + xs.foreach { x => + val (key, value) = map(x) + val reduced = if (result.contains(key)) reduce(result(key), value) else value + result.update(key, reduced) + } + + val b = cbf.newBuilder(xs) + for ( kv <- result ) b += kv + b.result() + } + } + + implicit class CollOps[A](val coll: Coll[A]) extends AnyVal { + + /** Partitions this $coll in two $colls according to a predicate. + * + * @param pred the predicate on which to partition. + * @return a pair of $colls: the first $coll consists of all elements that + * satisfy the predicate `p` and the second $coll consists of all elements + * that don't. The relative order of the elements in the resulting ${coll}s + * will BE preserved (this is different from Scala's version of this method). + */ + def partition(pred: A => Boolean): (Coll[A], Coll[A]) = { + val (ls, rs) = coll.toArray.partition(pred) + val b = coll.builder + implicit val tA: RType[A] = coll.tItem + (b.fromArray(ls), b.fromArray(rs)) + } + + def toMap[T, U](implicit ev: A <:< (T, U)): immutable.Map[T, U] = { + var b = immutable.Map.empty[T, U] + val len = coll.length + cfor(0)(_ < len, _ + 1) { i => + val kv = coll(i) + if (b.contains(kv._1)) + throw new IllegalArgumentException(s"Cannot transform collection $this to Map: duplicate key in entry $kv") + b = b + kv + } + b + } + + /** Sums elements of this collection using given Numeric. + * @return sum of elements or Numeric.zero if coll is empty + */ + def sum(implicit n: Numeric[A]): A = { + var sum = n.zero + val len = coll.length + cfor(0)(_ < len, _ + 1) { i => + sum = n.plus(sum, coll(i)) + } + sum + } + + /** Apply m for each element of this collection, group by key and reduce each group using r. + * + * @returns one item for each group in a new collection of (K,V) pairs. + */ + def mapReduce[K: RType, V: RType]( + m: A => (K, V), + r: ((V, V)) => V): Coll[(K, V)] = { + val b = coll.builder + val (keys, values) = Utils.mapReduce(coll.toArray, m, r) + b.pairCollFromArrays(keys, values) + } + + /** Partitions this collection into a map of collections according to some discriminator function. + * + * @param key the discriminator function. + * @tparam K the type of keys returned by the discriminator function. + * @return A map from keys to ${coll}s such that the following invariant holds: + * {{{ + * (xs groupBy key)(k) = xs filter (x => key(x) == k) + * }}} + * That is, every key `k` is bound to a $coll of those elements `x` + * for which `key(x)` equa `k`. + */ + def groupBy[K: RType](key: A => K): Coll[(K, Coll[A])] = { + val b = coll.builder + implicit val tA = coll.tItem + val res = coll.toArray.groupBy(key).mapValues(b.fromArray(_)) + b.fromMap(res.toMap) + } + + /** Partitions this collection into a map of collections according to some + * discriminator function. Additionally projecting each element to a new value. + * + * @param key the discriminator fu tion. + * @param proj projection function to produce new value for each element of this $coll + * @tparam K the type of keys returned by the discriminator function. + * @tparam V the type of values returned by the projection function. + * @return A map from keys to ${coll}s such that the following invariant holds: + * {{{ + * (xs groupByProjecting (key, proj))(k) = xs filter (x => key(x) == k).map(proj) + * }}} + * That is, every key `k` is bound to projections of those elements `x` + * for which `key(x)` eq ls `k`. + */ + def groupByProjecting[K: RType, V: RType](key: A => K, proj: A => V): Coll[(K, Coll[V])] = { + implicit val ctV: ClassTag[V] = RType[V].classTag + val b = coll.builder + val res = coll.toArray.groupBy(key).mapValues(arr => b.fromArray(arr.map(proj))) + b.fromMap(res.toMap) + } + + } + + implicit class PairCollOps[A,B](val source: Coll[(A,B)]) extends AnyVal { + implicit def tA = source.tItem.tFst + implicit def tB = source.tItem.tSnd + + /** Maps the first component of each pair in the collection. */ + @inline def mapFirst[A1: RType](f: A => A1): Coll[(A1, B)] = source.asInstanceOf[PairColl[A, B]].mapFirst(f) + + /** Maps the first component of each pair in the collection. */ + @inline def mapSecond[B1: RType](f: B => B1): Coll[(A, B1)] = source.asInstanceOf[PairColl[A, B]].mapSecond(f) + + /** Uses the first component of each pair in the collection as a key for + * grouping and reducing the corresponding values. + * + * @return collection with each found unique key as first component and the reduction + * result of the corresponding values. + */ + def reduceByKey(r: ((B, B)) => B): Coll[(A, B)] = { + source.mapReduce(identity, r) + } + + /** Uses the first component of each pair in the collection as a key for + * grouping and summing the corresponding values using the given Numeric. + * + * @return collection with each found unique key as first component and the summation + * result of the corresponding values. + */ + def sumByKey(implicit m: Numeric[B]): Coll[(A, B)] = + reduceByKey(r => m.plus(r._1, r._2)) + + /** Uses the first component of each pair in the collection as a key for + * grouping the corresponding values into a new collection. + * + * @return collection with each found unique key as first component and the + * collection of the corresponding values. + */ + def groupByKey: Coll[(A, Coll[B])] = { + source.groupByProjecting(_._1, _._2) + } + } + + implicit class CollBuilderOps(val builder: CollBuilder) extends AnyVal { + /** Performs outer join operation between left and right collections. + * + * @param l projection function executed for each element of `left` + * @param r projection function executed for each element of `right` + * @param inner projection function which is executed for matching items (K, L) and (K, R) with the same K + * @return collection of (K, O) pairs, where each key comes form either left or right + * collection and values are produced by projections + */ + def outerJoin[K: RType, L, R, O: RType] + (left: Coll[(K, L)], right: Coll[(K, R)]) + (l: ((K, L)) => O, + r: ((K, R)) => O, + inner: ((K, (L, R))) => O): Coll[(K, O)] = { + val res = Utils.outerJoin[K, L, R, O](left.toMap, right.toMap)( + (k, lv) => l((k, lv)), + (k, rv) => r((k, rv)), + (k, lv, rv) => inner((k, (lv, rv)))) + fromMap(res) + } + + /** Construct a collection of (K,V) pairs using PairColl representation, + * in which keys and values are stored as separate unboxed arrays. */ + def fromMap[K: RType, V: RType](m: Map[K, V]): Coll[(K, V)] = { + val (ks, vs) = Utils.mapToArrays(m) + builder.pairCollFromArrays(ks, vs) + } + } +} diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/JavaHelpers.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/JavaHelpers.scala new file mode 100644 index 0000000000..4a49e23283 --- /dev/null +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/JavaHelpers.scala @@ -0,0 +1,529 @@ +package org.ergoplatform.sdk + +import scalan.RType +import special.collection.Coll + +import scala.collection.{JavaConverters, mutable} +import org.ergoplatform._ +import org.ergoplatform.ErgoBox.{Token, TokenId} +import sigmastate.SType +import sigmastate.Values.{Constant, ErgoTree, EvaluatedValue, SValue, SigmaBoolean, SigmaPropConstant} +import sigmastate.serialization.{ErgoTreeSerializer, GroupElementSerializer, SigmaSerializer, ValueSerializer} +import scorex.crypto.authds.ADKey +import org.ergoplatform.settings.ErgoAlgos +import sigmastate.eval.{CPreHeader, Colls, CostingSigmaDslBuilder, Digest32Coll, Evaluation} +import special.sigma.{AnyValue, AvlTree, GroupElement, Header} +import sigmastate.utils.Helpers._ // don't remove, required for Scala 2.11 + +import java.util +import java.lang.{Boolean => JBoolean, Byte => JByte, Integer => JInt, Long => JLong, Short => JShort, String => JString} +import java.util.{List => JList, Map => JMap} +import org.ergoplatform.ErgoAddressEncoder.NetworkPrefix +import scorex.util.encode.Base16 +import sigmastate.basics.DLogProtocol.ProveDlog +import scorex.util.{ModifierId, bytesToId, idToBytes} +import org.ergoplatform.sdk.JavaHelpers.{TokenColl, TokenIdRType} +import org.ergoplatform.sdk.Extensions.{CollBuilderOps, PairCollOps} +import org.ergoplatform.sdk.wallet.{Constants, TokensMap} +import org.ergoplatform.sdk.wallet.secrets.{DerivationPath, ExtendedSecretKey} +import scalan.ExactIntegral.LongIsExactIntegral +import scalan.util.StringUtil.StringUtilExtensions +import sigmastate.basics.CryptoConstants.EcPointType +import sigmastate.basics.{DiffieHellmanTupleProverInput, ProveDHTuple} +import sigmastate.crypto.CryptoFacade + +import java.math.BigInteger + +/** Type-class of isomorphisms between types. + * Isomorphism between two types `A` and `B` essentially say that both types + * represents the same information (entity) but in a different way. + *

+ * The information is not lost so that both are true: + * 1) a == from(to(a)) + * 2) b == to(from(b)) + *

+ * It is used to define type-full conversions: + * - different conversions between Java and Scala data types. + * - conversion between Ergo representations and generated API representations + */ +abstract class Iso[A, B] { + def to(a: A): B + def from(b: B): A + def andThen[C](iso: Iso[B,C]): Iso[A,C] = ComposeIso(iso, this) + def inverse: Iso[B, A] = InverseIso(this) +} +final case class InverseIso[A,B](iso: Iso[A,B]) extends Iso[B,A] { + override def to(a: B): A = iso.from(a) + override def from(b: A): B = iso.to(b) +} +final case class ComposeIso[A, B, C](iso2: Iso[B, C], iso1: Iso[A, B]) extends Iso[A, C] { + def from(c: C): A = iso1.from(iso2.from(c)) + def to(a: A): C = iso2.to(iso1.to(a)) +} + +trait LowPriorityIsos { +} + +object Iso extends LowPriorityIsos { + implicit def identityIso[A]: Iso[A, A] = new Iso[A, A] { + override def to(a: A): A = a + + override def from(b: A): A = b + } + + implicit def inverseIso[A,B](implicit iso: Iso[A,B]): Iso[B,A] = InverseIso[A,B](iso) + + implicit val jbyteToByte: Iso[JByte, Byte] = new Iso[JByte, Byte] { + override def to(b: JByte): Byte = b + override def from(a: Byte): JByte = a + } + + implicit val jshortToShort: Iso[JShort, Short] = new Iso[JShort, Short] { + override def to(b: JShort): Short = b + override def from(a: Short): JShort = a + } + + implicit val jintToInt: Iso[JInt, Int] = new Iso[JInt, Int] { + override def to(b: JInt): Int = b + override def from(a: Int): JInt = a + } + + implicit val jlongToLong: Iso[JLong, Long] = new Iso[JLong, Long] { + override def to(b: JLong): Long = b + override def from(a: Long): JLong = a + } + + implicit val jboolToBool: Iso[JBoolean, Boolean] = new Iso[JBoolean, Boolean] { + override def to(b: JBoolean): Boolean = b + override def from(a: Boolean): JBoolean = a + } + + implicit def collToColl[A: RType, B: RType](implicit iso: Iso[A, B]): Iso[Coll[A], Coll[B]] = new Iso[Coll[A], Coll[B]] { + override def to(as: Coll[A]): Coll[B] = as.map(iso.to) + override def from(bs: Coll[B]): Coll[A] = bs.map(iso.from) + } + + implicit val isoErgoTokenToPair: Iso[ErgoToken, Token] = new Iso[ErgoToken, Token] { + override def to(a: ErgoToken): Token = + (Digest32Coll @@ Colls.fromArray(a.getId.getBytes), a.getValue) + override def from(t: Token): ErgoToken = new ErgoToken(t._1.toArray, t._2) + } + + implicit val isoJListErgoTokenToMapPair: Iso[JList[ErgoToken], mutable.LinkedHashMap[ModifierId, Long]] = + new Iso[JList[ErgoToken], mutable.LinkedHashMap[ModifierId, Long]] { + override def to(a: JList[ErgoToken]): mutable.LinkedHashMap[ModifierId, Long] = { + import JavaHelpers._ + val lhm = new mutable.LinkedHashMap[ModifierId, Long]() + a.convertTo[IndexedSeq[Token]] + .map(t => bytesToId(t._1.toArray) -> t._2) + .foldLeft(lhm)(_ += _) + } + + override def from(t: mutable.LinkedHashMap[ModifierId, Long]): JList[ErgoToken] = { + import JavaHelpers._ + val pairs: IndexedSeq[Token] = t.toIndexedSeq + .map(t => (Digest32Coll @@ Colls.fromArray(idToBytes(t._1))) -> t._2) + pairs.convertTo[JList[ErgoToken]] + } + } + + implicit def isoJMapToMap[K,V1,V2](iso: Iso[V1, V2]): Iso[JMap[K, V1], scala.collection.Map[K,V2]] = new Iso[JMap[K, V1], scala.collection.Map[K,V2]] { + import JavaConverters._ + override def to(a: JMap[K, V1]): scala.collection.Map[K, V2] = { + a.asScala.mapValues(iso.to).toMap + } + override def from(b: scala.collection.Map[K, V2]): JMap[K, V1] = { + b.mapValues(iso.from).toMap.asJava + } + } + + val isoEvaluatedValueToSConstant: Iso[EvaluatedValue[SType], Constant[SType]] = new Iso[EvaluatedValue[SType], Constant[SType]] { + override def to(x: EvaluatedValue[SType]): Constant[SType] = x.asInstanceOf[Constant[SType]] + override def from(x: Constant[SType]): EvaluatedValue[SType] = x + } + + val isoTokensListToPairsColl: Iso[JList[ErgoToken], Coll[Token]] = { + JListToColl(isoErgoTokenToPair, RType[Token]) + } + + val isoTokensListToTokenColl: Iso[JList[ErgoToken], TokenColl] = new Iso[JList[ErgoToken], TokenColl] { + override def to(ts: JList[ErgoToken]): TokenColl = + isoTokensListToPairsColl.to(ts) + + override def from(ts: TokenColl): JList[ErgoToken] = { + isoTokensListToPairsColl.from(ts) + } + } + + val isoSigmaBooleanToByteArray: Iso[SigmaBoolean, Array[Byte]] = new Iso[SigmaBoolean, Array[Byte]] { + override def to(a: SigmaBoolean): Array[Byte] = { + val w = SigmaSerializer.startWriter() + SigmaBoolean.serializer.serialize(a, w) + w.toBytes + } + override def from(b: Array[Byte]): SigmaBoolean ={ + val r = SigmaSerializer.startReader(b, 0) + SigmaBoolean.serializer.parse(r) + } + } + + implicit val jstringToOptionString: Iso[JString, Option[String]] = new Iso[JString, Option[String]] { + override def to(a: JString): Option[String] = if (a.isNullOrEmpty) None else Some(a) + override def from(b: Option[String]): JString = if (b.isEmpty) "" else b.get + } + + implicit val arrayCharToOptionString: Iso[SecretString, Option[String]] = new Iso[SecretString, Option[String]] { + override def to(ss: SecretString): Option[String] = { + if (ss == null || ss.isEmpty) None else Some(ss.toStringUnsecure) + } + override def from(b: Option[String]): SecretString = + if (b.isEmpty) SecretString.empty() else SecretString.create(b.get) + } + + implicit def JListToIndexedSeq[A, B](implicit itemIso: Iso[A, B]): Iso[JList[A], IndexedSeq[B]] = + new Iso[JList[A], IndexedSeq[B]] { + import JavaConverters._ + + override def to(as: JList[A]): IndexedSeq[B] = { + as.iterator().asScala.map(itemIso.to).toIndexedSeq + } + + override def from(bs: IndexedSeq[B]): JList[A] = { + val res = new util.ArrayList[A](bs.length) + for ( a <- bs.map(itemIso.from) ) res.add(a) + res + } + } + + implicit def JListToColl[A, B](implicit itemIso: Iso[A, B], tB: RType[B]): Iso[JList[A], Coll[B]] = + new Iso[JList[A], Coll[B]] { + import JavaConverters._ + + override def to(as: JList[A]): Coll[B] = { + val bsIter = as.iterator.asScala.map { a => + itemIso.to(a) + } + Colls.fromArray(bsIter.toArray(tB.classTag)) + } + + override def from(bs: Coll[B]): JList[A] = { + val res = new util.ArrayList[A](bs.length) + bs.toArray.foreach { b => + res.add(itemIso.from(b)) + } + res + } + } + +} + +object JavaHelpers { + implicit class UniversalConverter[A](val x: A) extends AnyVal { + def convertTo[B](implicit iso: Iso[A,B]): B = iso.to(x) + } + + implicit class StringExtensions(val base16: String) extends AnyVal { + /** Decodes this base16 string to byte array. */ + def toBytes: Array[Byte] = decodeStringToBytes(base16) + + /** Decodes this base16 string to byte array and wrap it as Coll[Byte]. */ + def toColl: Coll[Byte] = decodeStringToColl(base16) + + /** Decodes this base16 string to byte array and parse it using [[GroupElementSerializer]]. */ + def toGroupElement: GroupElement = decodeStringToGE(base16) + + /** Decodes this base16 string to byte array and parse it using + * [[ErgoTreeSerializer.deserializeErgoTree()]]. + */ + def toErgoTree: ErgoTree = decodeStringToErgoTree(base16) + } + + implicit val TokenIdRType: RType[TokenId] = collRType(RType.ByteType).asInstanceOf[RType[TokenId]] + implicit val JByteRType: RType[JByte] = RType.ByteType.asInstanceOf[RType[JByte]] + implicit val JShortRType: RType[JShort] = RType.ShortType.asInstanceOf[RType[JShort]] + implicit val JIntRType: RType[JInt] = RType.IntType.asInstanceOf[RType[JInt]] + implicit val JLongRType: RType[JLong] = RType.LongType.asInstanceOf[RType[JLong]] + implicit val JBooleanRType: RType[JBoolean] = RType.BooleanType.asInstanceOf[RType[JBoolean]] + + val HeaderRType: RType[Header] = special.sigma.HeaderRType + val PreHeaderRType: RType[special.sigma.PreHeader] = special.sigma.PreHeaderRType + + def Algos: ErgoAlgos = org.ergoplatform.settings.ErgoAlgos + + def deserializeValue[T <: SValue](bytes: Array[Byte]): T = { + ValueSerializer.deserialize(bytes).asInstanceOf[T] + } + + /** Decodes this base16 string to byte array. */ + def decodeStringToBytes(base16: String): Array[Byte] = { + val bytes = ErgoAlgos.decode(base16).fold(t => throw t, identity) + bytes + } + + /** Decodes this base16 string to byte array and wrap it as Coll[Byte]. */ + def decodeStringToColl(base16: String): Coll[Byte] = { + val bytes = ErgoAlgos.decode(base16).fold(t => throw t, identity) + Colls.fromArray(bytes) + } + + /** Decodes this base16 string to byte array and parse it using [[GroupElementSerializer]]. */ + def decodeStringToGE(base16: String): GroupElement = { + val bytes = ErgoAlgos.decode(base16).fold(t => throw t, identity) + val pe = GroupElementSerializer.parse(SigmaSerializer.startReader(bytes)) + SigmaDsl.GroupElement(pe) + } + + /** Decodes this base16 string to byte array and parse it using + * [[ErgoTreeSerializer.deserializeErgoTree()]]. + */ + def decodeStringToErgoTree(base16: String): ErgoTree = { + ErgoTreeSerializer.DefaultSerializer.deserializeErgoTree(Base16.decode(base16).get) + } + + def createP2PKAddress(pk: ProveDlog, networkPrefix: NetworkPrefix): P2PKAddress = { + implicit val ergoAddressEncoder: ErgoAddressEncoder = ErgoAddressEncoder(networkPrefix) + P2PKAddress(pk) + } + + def createP2SAddress(ergoTree: ErgoTree, networkPrefix: NetworkPrefix): Pay2SAddress = { + implicit val ergoAddressEncoder: ErgoAddressEncoder = ErgoAddressEncoder(networkPrefix) + Pay2SAddress(ergoTree) + } + + def hash(s: String): String = { + ErgoAlgos.encode(ErgoAlgos.hash(s)) + } + + def toPreHeader(h: Header): special.sigma.PreHeader = { + CPreHeader(h.version, h.parentId, h.timestamp, h.nBits, h.height, h.minerPk, h.votes) + } + + def toSigmaBoolean(ergoTree: ErgoTree): SigmaBoolean = { + val prop = ergoTree.toProposition(ergoTree.isConstantSegregation) + prop match { + case SigmaPropConstant(p) => SigmaDsl.toSigmaBoolean(p) + } + } + + def toErgoTree(sigmaBoolean: SigmaBoolean): ErgoTree = ErgoTree.fromSigmaBoolean(sigmaBoolean) + + def getStateDigest(tree: AvlTree): Array[Byte] = { + tree.digest.toArray + } + + def toIndexedSeq[T](xs: util.List[T]): IndexedSeq[T] = { + import JavaConverters._ + xs.iterator().asScala.toIndexedSeq + } + + private def anyValueToConstant(v: AnyValue): Constant[_ <: SType] = { + val tpe = Evaluation.rtypeToSType(v.tVal) + Constant(v.value.asInstanceOf[SType#WrappedType], tpe) + } + + /** Converts mnemonic phrase to seed it was derived from. + * This method should be equivalent to Mnemonic.toSeed(). + */ + def mnemonicToSeed(mnemonic: String, passOpt: Option[String] = None): Array[Byte] = { + val normalizedMnemonic = CryptoFacade.normalizeChars(mnemonic.toCharArray) + val normalizedPass = CryptoFacade.normalizeChars(s"mnemonic${passOpt.getOrElse("")}".toCharArray) + CryptoFacade.generatePbkdf2Key(normalizedMnemonic, normalizedPass) + } + + /** + * Create an extended secret key from mnemonic + * + * @param seedPhrase secret seed phrase to be used in prover for generating proofs. + * @param pass password to protect secret seed phrase. + * @param usePre1627KeyDerivation use incorrect(previous) BIP32 derivation, expected to be false for new + * wallets, and true for old pre-1627 wallets (see https://github.com/ergoplatform/ergo/issues/1627 for details) + */ + def seedToMasterKey(seedPhrase: SecretString, pass: SecretString = null, usePre1627KeyDerivation: java.lang.Boolean): ExtendedSecretKey = { + val passOpt = if (pass == null || pass.isEmpty()) None else Some(pass.toStringUnsecure) + val seed = mnemonicToSeed(seedPhrase.toStringUnsecure, passOpt) + val masterKey = ExtendedSecretKey.deriveMasterKey(seed, usePre1627KeyDerivation) + masterKey + } + + def createUnsignedInput(boxId: String): UnsignedInput = { + val idBytes = decodeStringToBytes(boxId) + createUnsignedInput(idBytes) + } + + def createUnsignedInput(boxIdBytes: Array[Byte]): UnsignedInput = { + new UnsignedInput(ADKey @@ boxIdBytes) + } + + def createDataInput(boxIdBytes: Array[Byte]): DataInput = { + DataInput(ADKey @@ boxIdBytes) + } + + def collRType[T](tItem: RType[T]): RType[Coll[T]] = special.collection.collRType(tItem) + + def BigIntRType: RType[special.sigma.BigInt] = special.sigma.BigIntRType + + def GroupElementRType: RType[special.sigma.GroupElement] = special.sigma.GroupElementRType + + def SigmaPropRType: RType[special.sigma.SigmaProp] = special.sigma.SigmaPropRType + + def AvlTreeRType: RType[special.sigma.AvlTree] = special.sigma.AvlTreeRType + + def BoxRType: RType[special.sigma.Box] = special.sigma.BoxRType + + def SigmaDsl: CostingSigmaDslBuilder = sigmastate.eval.SigmaDsl + + def collFrom(arr: Array[Byte]): Coll[Byte] = { + Colls.fromArray(arr) + } + + def collToByteArray(in: Coll[Byte]): Array[Byte] = { + in.toArray + } + + def collFrom(arr: Array[Long]): Coll[Long] = Colls.fromArray(arr) + + def collFrom(arr: Array[Int]): Coll[Int] = Colls.fromArray(arr) + + def collFrom(arr: Array[Boolean]): Coll[Boolean] = Colls.fromArray(arr) + + def collFrom(arr: Array[Short]): Coll[Short] = Colls.fromArray(arr) + + def ergoTreeTemplateBytes(ergoTree: ErgoTree): Array[Byte] = { + val r = SigmaSerializer.startReader(ergoTree.bytes) + ErgoTreeSerializer.DefaultSerializer.deserializeHeaderWithTreeBytes(r)._4 + } + + def createTokensMap(linkedMap: mutable.LinkedHashMap[ModifierId, Long]): TokensMap = { + linkedMap.toMap + } + + // TODO the method below is copied from ErgoTransaction. Both of them should be moved to ergo-wallet. + + /** + * Extracts a mapping (assets -> total amount) from a set of boxes passed as a parameter. + * That is, the method is checking amounts of assets in the boxes(i.e. that a box contains positive + * amount for an asset) and then summarize and group their corresponding amounts. + * + * @param boxes - boxes to check and extract assets from + * @return a mapping from asset id to to balance and total assets number + */ + def extractAssets(boxes: IndexedSeq[ErgoBoxCandidate]): (Map[Digest32Coll, Long], Int) = { + import special.collection.Extensions.CollOps + val map = mutable.Map[Digest32Coll, Long]() + val assetsNum = boxes.foldLeft(0) { case (acc, box) => + require(box.additionalTokens.length <= SigmaConstants.MaxTokens.value, "too many assets in one box") + box.additionalTokens.foreach { case (assetId, amount) => + val total = map.getOrElse(assetId, 0L) + map.put(assetId, java7.compat.Math.addExact(total, amount)) + } + acc + box.additionalTokens.size + } + map.toMap -> assetsNum + } + + /** Creates a new EIP-3 derivation path with the given last index. + * The resulting path corresponds to `m/44'/429'/0'/0/index` path. + */ + def eip3DerivationWithLastIndex(index: Int) = { + val firstPath = Constants.eip3DerivationPath + DerivationPath(firstPath.decodedPath.dropRight(1) :+ index, firstPath.publicBranch) + } + + /** Creates a new EIP-3 derivation parent path. + * The resulting path is the `m/44'/429'/0'/0` path. + */ + def eip3DerivationParent() = { + val firstPath = Constants.eip3DerivationPath + DerivationPath(firstPath.decodedPath.dropRight(1), firstPath.publicBranch) + } + + /** Type synonym for a collection of tokens represented using Coll */ + type TokenColl = Coll[Token] + + /** Ensures that all tokens have strictly positive value. + * @throws IllegalArgumentException when any token have value <= 0 + * @return the original `tokens` collection (passes the argument through to the result) + */ + def checkAllTokensPositive(tokens: TokenColl): TokenColl = { + val invalidTokens = tokens.filter(_._2 <= 0) + require(invalidTokens.isEmpty, s"All token values should be > 0: ") + tokens + } + + /** Compute the difference between `reducedTokens` collection and `subtractedTokens` collection. + * It can be thought as `reducedTokens - subtractedTokens` operation. + * + * Each collection can have many `(tokenId, amount)` pairs with the same `tokenId`. + * The method works by first combining all the pairs with the same tokenId and then + * computing the difference. + * The resulting collection contain a single pair for each tokenId and those token ids + * form a subset of tokens from the argument collections. + * + * One concrete use case to think of is `subtractTokenColls(outputTokens, inputTokens)`. + * In this case the resulting collection of (tokenId, amount) pairs can be interpreted as: + * - when `amount < 0` then it is to be burnt + * - when `amount > 0` then it is to be minted + * + * @param reducedTokens the tokens to be subracted from + * @param subtractedTokens the tokens which amounts will be subtracted from the + * corresponding tokens from `reducedTokens` + * @return the differences between token amounts (matched by token ids) + */ + def subtractTokenColls( + reducedTokens: TokenColl, + subtractedTokens: TokenColl + ): TokenColl = { + val exactNumeric: Numeric[Long] = new Utils.IntegralFromExactIntegral(LongIsExactIntegral) + val b = reducedTokens.builder // any Coll has builder, which is suitable + val reduced = checkAllTokensPositive(reducedTokens).sumByKey(exactNumeric) // summation with overflow checking + val subtracted = checkAllTokensPositive(subtractedTokens).sumByKey(exactNumeric) // summation with overflow checking + val tokensDiff = b.outerJoin(subtracted, reduced)( + { case (_, sV) => -sV }, // for each token missing in reduced: amount to burn + { case (_, rV) => rV }, // for each token missing in subtracted: amount to mint + { case (_, (sV, rV)) => rV - sV } // for tokens both in subtracted and reduced: balance change + ) + tokensDiff.filter(_._2 != 0) // return only unbalanced tokens + } + + /** Compute the difference between `reducedTokens` collection and `subtractedTokens` + * collection. + * @see subtractTokenColls for details + */ + def subtractTokens( + reducedTokens: IndexedSeq[Token], + subtractedTokens: IndexedSeq[Token] + ): TokenColl = { + subtractTokenColls( + reducedTokens = Colls.fromItems(reducedTokens:_*), + subtractedTokens = Colls.fromItems(subtractedTokens:_*) + ) + } + + def createDiffieHellmanTupleProverInput( + g: GroupElement, + h: GroupElement, + u: GroupElement, + v: GroupElement, + x: BigInteger): DiffieHellmanTupleProverInput = { + createDiffieHellmanTupleProverInput( + g = sdk.JavaHelpers.SigmaDsl.toECPoint(g), + h = sdk.JavaHelpers.SigmaDsl.toECPoint(h), + u = sdk.JavaHelpers.SigmaDsl.toECPoint(u), + v = sdk.JavaHelpers.SigmaDsl.toECPoint(v), + x + ) + } + + def createDiffieHellmanTupleProverInput( + g: EcPointType, + h: EcPointType, + u: EcPointType, + v: EcPointType, + x: BigInteger): DiffieHellmanTupleProverInput = { + val dht = ProveDHTuple(g, h, u, v) + DiffieHellmanTupleProverInput(x, dht) + } +} + + + diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/ProverBuilder.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/ProverBuilder.scala new file mode 100644 index 0000000000..2bdfe48fb1 --- /dev/null +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/ProverBuilder.scala @@ -0,0 +1,91 @@ +package org.ergoplatform.sdk + +import org.ergoplatform.ErgoAddressEncoder.NetworkPrefix +import org.ergoplatform.sdk.wallet.protocol.context.ErgoLikeParameters +import org.ergoplatform.sdk.wallet.secrets.ExtendedSecretKey +import sigmastate.basics.DLogProtocol.DLogProverInput +import sigmastate.basics.{DLogProtocol, DiffieHellmanTupleProverInput} +import special.sigma.GroupElement + +import java.math.BigInteger +import scala.collection.mutable.ArrayBuffer + +/** A builder class for constructing a `Prover` with specified secrets. */ +class ProverBuilder(parameters: ErgoLikeParameters, networkPrefix: NetworkPrefix) { + private var _masterKey: Option[ExtendedSecretKey] = None + + /** Generated EIP-3 secret keys paired with their derivation path index. */ + private val _eip2Keys = ArrayBuffer.empty[(Int, ExtendedSecretKey)] + + private val _dhtSecrets = ArrayBuffer.empty[DiffieHellmanTupleProverInput] + private val _dLogSecrets = ArrayBuffer.empty[DLogProverInput] + + /** Sets the mnemonic phrase and password for the prover. + * + * @param usePre1627KeyDerivation whether to use pre-1627 key derivation + */ + def withMnemonic( + mnemonicPhrase: SecretString, + mnemonicPass: SecretString, + usePre1627KeyDerivation: Boolean + ): ProverBuilder = { + _masterKey = Some(JavaHelpers.seedToMasterKey(mnemonicPhrase, mnemonicPass, usePre1627KeyDerivation)) + this + } + + /** Adds an EIP-3 secret key for the specified index to the prover. + * + * @param index the derivation path index + * @return an updated ProverBuilder + */ + def withEip3Secret(index: Int): ProverBuilder = { + require(_masterKey.isDefined, s"Mnemonic is not specified, use withMnemonic method.") + require(!_eip2Keys.exists(_._1 == index), + s"Secret key for derivation index $index has already been added.") + val path = JavaHelpers.eip3DerivationWithLastIndex(index) + val secretKey = _masterKey.get.derive(path) + _eip2Keys += (index -> secretKey) + this + } + + /** Adds a Diffie-Hellman Tuple secret to the prover. */ + def withDHTData( + g: GroupElement, + h: GroupElement, + u: GroupElement, + v: GroupElement, + x: BigInteger): ProverBuilder = { + val dht = org.ergoplatform.sdk.JavaHelpers.createDiffieHellmanTupleProverInput(g, h, u, v, x) + if (_dhtSecrets.contains(dht)) + throw new IllegalStateException("DHTuple secret already exists") + _dhtSecrets += dht + this + } + + /** Adds a DLog secret to the prover. + * + * @param x the x value of the DLog secret + * @return an updated ProverBuilder + */ + def withDLogSecret(x: BigInteger): ProverBuilder = { + val dLog = new DLogProtocol.DLogProverInput(x) + if (_dLogSecrets.contains(dLog)) + throw new IllegalStateException("Dlog secret already exists") + _dLogSecrets += dLog + this + } + + /** Constructs a `Prover` with the specified secrets. + * + * @return a new Prover instance + */ + def build(): SigmaProver = { + val secretKeys = _masterKey.toIndexedSeq ++ _eip2Keys.map(_._2) + val interpreter = new AppkitProvingInterpreter( + secretKeys, + _dLogSecrets.toIndexedSeq, + _dhtSecrets.toIndexedSeq, parameters) + new SigmaProver(interpreter, networkPrefix) + } +} + diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/ReducingInterpreter.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/ReducingInterpreter.scala new file mode 100644 index 0000000000..b7d8547e32 --- /dev/null +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/ReducingInterpreter.scala @@ -0,0 +1,158 @@ +package org.ergoplatform.sdk + +import org.ergoplatform.sdk.Extensions.{CollOps, PairCollOps} +import org.ergoplatform.sdk.JavaHelpers.UniversalConverter +import org.ergoplatform.sdk.utils.ArithUtils +import org.ergoplatform.sdk.wallet.protocol.context.{ErgoLikeParameters, ErgoLikeStateContext, TransactionContext} +import org.ergoplatform.validation.ValidationRules +import org.ergoplatform.{ErgoLikeContext, ErgoLikeInterpreter} +import scalan.util.Extensions.LongOps +import scorex.crypto.authds.ADDigest +import sigmastate.AvlTreeData +import sigmastate.Values.ErgoTree +import sigmastate.eval.Evaluation.addCostChecked +import sigmastate.exceptions.CostLimitException +import sigmastate.interpreter.Interpreter +import sigmastate.interpreter.Interpreter.ScriptEnv + +import java.util +import java.util.{Objects, List => JList} +import scala.collection.mutable + +/** Interpreter that can reduce transactions with given chain parameters. */ +class ReducingInterpreter(params: ErgoLikeParameters) extends ErgoLikeInterpreter { + override type CTX = ErgoLikeContext + import org.ergoplatform.sdk.Iso._ + + /** Reduces the given ErgoTree in the given context to the sigma proposition. + * + * @param env script environment (use Interpreter.emptyEnv as default) + * @param ergoTree input ErgoTree expression to reduce + * @param context context used in reduction + * @return data object containing enough data to sign a transaction without Context. + */ + def reduce(env: ScriptEnv, ergoTree: ErgoTree, context: CTX): ReducedInputData = { + val initCost = ergoTree.complexity + context.initCost + val remainingLimit = context.costLimit - initCost + if (remainingLimit <= 0) + throw new CostLimitException(initCost, + s"Estimated execution cost $initCost exceeds the limit ${context.costLimit}", None) + val ctxUpdInitCost = context.withInitCost(initCost) + val res = fullReduction(ergoTree, ctxUpdInitCost, env) + ReducedInputData(res, ctxUpdInitCost.extension) + } + + /** Reduce inputs of the given unsigned transaction to provable sigma propositions using + * the given context. See [[ReducedErgoLikeTransaction]] for details. + * + * @param unreducedTx unreduced transaction data to be reduced (holds unsigned transaction) + * @param stateContext state context of the blockchain in which the transaction should be signed + * @param baseCost the cost accumulated so far and before this operation + * @return a new reduced transaction with all inputs reduced and the cost of this transaction + * The returned cost include all the costs accumulated during the reduction: + * 1) `baseCost` + * 2) general costs of the transaction based on its data + * 3) reduction cost for each input. + */ + def reduceTransaction( + unreducedTx: UnreducedTransaction, + stateContext: ErgoLikeStateContext, + baseCost: Int + ): ReducedTransaction = { + val unsignedTx = unreducedTx.unsignedTx + val boxesToSpend = unreducedTx.boxesToSpend + val dataBoxes = unreducedTx.dataInputs + if (unsignedTx.inputs.length != boxesToSpend.length) throw new Exception("Not enough boxes to spend") + if (unsignedTx.dataInputs.length != dataBoxes.length) throw new Exception("Not enough data boxes") + + val tokensToBurn = unreducedTx.tokensToBurn + val inputTokens = boxesToSpend.flatMap(_.box.additionalTokens.toArray) + val outputTokens = unsignedTx.outputCandidates.flatMap(_.additionalTokens.toArray) + val tokenDiff = JavaHelpers.subtractTokens(outputTokens, inputTokens) + if (tokenDiff.nonEmpty) { + val (toBurn, toMint) = tokenDiff.partition(_._2 < 0) // those with negative diff are to be burnt + if (toBurn.nonEmpty) { + if (tokensToBurn.nonEmpty) { + val requestedToBurn = isoTokensListToTokenColl.to(tokensToBurn.convertTo[JList[ErgoToken]]) + val diff = JavaHelpers.subtractTokenColls( + reducedTokens = toBurn.mapSecond(v => -v), // make positive amounts + subtractedTokens = requestedToBurn + ) + if (diff.nonEmpty) { // empty diff would mean equality + throw TokenBalanceException( + "Transaction tries to burn tokens, but not how it was requested", diff) + } + } else { + throw TokenBalanceException( + "Transaction tries to burn tokens when no burning was requested", tokenDiff) + } + } + if (toMint.nonEmpty) { + if (toMint.length > 1) { + throw TokenBalanceException("Only one token can be minted in a transaction", toMint) + } + val isCorrectMintedTokenId = Objects.deepEquals(toMint(0)._1.toArray, boxesToSpend.head.box.id) + if (!isCorrectMintedTokenId) { + throw TokenBalanceException("Cannot mint a token with invalid id", toMint) + } + } + } + // Cost of transaction initialization: we should read and parse all inputs and data inputs, + // and also iterate through all outputs to check rules + val initialCost = ArithUtils.addExact( + Interpreter.interpreterInitCost, + java7.compat.Math.multiplyExact(boxesToSpend.size, params.inputCost), + java7.compat.Math.multiplyExact(dataBoxes.size, params.dataInputCost), + java7.compat.Math.multiplyExact(unsignedTx.outputCandidates.size, params.outputCost) + ) + val maxCost = params.maxBlockCost + val startCost = addCostChecked(baseCost, initialCost, maxCost, msgSuffix = unsignedTx.toString()) + + val transactionContext = TransactionContext(boxesToSpend.map(_.box), dataBoxes, unsignedTx) + + val (outAssets, outAssetsNum) = JavaHelpers.extractAssets(unsignedTx.outputCandidates) + val (inAssets, inAssetsNum) = JavaHelpers.extractAssets(boxesToSpend.map(_.box)) + + val tokenAccessCost = params.tokenAccessCost + val totalAssetsAccessCost = + java7.compat.Math.addExact( + java7.compat.Math.multiplyExact(java7.compat.Math.addExact(outAssetsNum, inAssetsNum), tokenAccessCost), + java7.compat.Math.multiplyExact(java7.compat.Math.addExact(inAssets.size, outAssets.size), tokenAccessCost)) + + val txCost = addCostChecked(startCost, + delta = totalAssetsAccessCost, + limit = maxCost, msgSuffix = s"when adding assets cost of $totalAssetsAccessCost") + + var currentCost = txCost + val reducedInputs = mutable.ArrayBuilder.make[ReducedInputData] + + for ((inputBox, boxIdx) <- boxesToSpend.zipWithIndex) { + val unsignedInput = unsignedTx.inputs(boxIdx) + require(util.Arrays.equals(unsignedInput.boxId, inputBox.box.id)) + + val context = new ErgoLikeContext( + AvlTreeData.avlTreeFromDigest(stateContext.previousStateDigest), + stateContext.sigmaLastHeaders, + stateContext.sigmaPreHeader, + transactionContext.dataBoxes, + transactionContext.boxesToSpend, + transactionContext.spendingTransaction, + boxIdx.toShort, + inputBox.extension, + ValidationRules.currentSettings, + costLimit = maxCost, + initCost = currentCost, + activatedScriptVersion = (params.blockVersion - 1).toByte + ) + + val reducedInput = reduce(Interpreter.emptyEnv, inputBox.box.ergoTree, context) + + currentCost = reducedInput.reductionResult.cost + reducedInputs += reducedInput + } + val reducedTx = ReducedErgoLikeTransaction( + unsignedTx, reducedInputs.result(), + cost = (currentCost - baseCost).toIntExact) + ReducedTransaction(reducedTx) + } +} diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/SecretString.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/SecretString.scala new file mode 100644 index 0000000000..4c25ef6891 --- /dev/null +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/SecretString.scala @@ -0,0 +1,109 @@ +package org.ergoplatform.sdk + +import debox.cfor + +import java.util + +/** + * Encapsulates secret array of characters (char[]) with proper equality. + * The secret data can be {@link SecretString# erase ( ) erased} in memory and not leaked to GC. + * Using this class is more secure and safe than using char[] directly. + */ +object SecretString { + /** + * Creates a new instance wrapping the given characters. The given array is not copied. + */ + def create(data: Array[Char]) = new SecretString(data) + + /** + * Creates a new instance by copying characters from the given String. + */ + def create(s: String) = new SecretString(s.toCharArray) + + /** + * Create a new instance with empty sequence of characters. + */ + def empty() = new SecretString(new Array[Char](0)) +} + +/** Encapsulates secret array of characters (char[]) with proper equality. + * The secret data can be erased in memory and not leaked to GC. + * Note that calling any methods after `erase()` will throw a runtime exception. + * Using this class is more secure and safe than using char[] directly. + * + * Secret data, should not be copied outside of this instance. + * Use static methods to construct new instances. + */ +final class SecretString private[sdk](val _data: Array[Char]) { + /** + * Erased flag, should not be copied outside of this instance. + */ + private var _erased = false + + /** + * Throws exception if SecretString is erased + */ + private def checkErased(): Unit = { + if (_erased) throw new RuntimeException("SecretString already erased") + } + + /** + * Returns true if the string doesn't have characters. + */ + def isEmpty(): Boolean = { + checkErased() + _data == null || _data.length == 0 + } + + /** + * Extracts secret characters as an array. + */ + def getData(): Array[Char] = { + checkErased() + _data + } + + /** + * Erases secret characters stored in this instance so that they are no longer reside in memory. + */ + def erase(): Unit = { + util.Arrays.fill(_data, ' ') + _erased = true + } + + override def hashCode(): Int = { + checkErased() + util.Arrays.hashCode(_data) + } + + /** this is adapted version of java.lang.String */ + override def equals(obj: Any): Boolean = { + checkErased() + if (this eq obj.asInstanceOf[AnyRef]) return true + if (obj == null) return false + obj match { + case anotherString: SecretString if _data.length == anotherString._data.length => + var n: Int = _data.length + val v1: Array[Char] = _data + val v2: Array[Char] = anotherString._data + cfor(0)(_ < n, _ + 1) { i => + if (v1(i) != v2(i)) return false + } + true + case _ => false + } + } + + /** + * Returns unsecured String with secret characters. + * The secret characters are copied to the new String instance and cannot be erased in memory. + * So they leak to GC and may remain in memory until overwritten by new data. + * Usage of this method is discouraged and the method is provided solely to interact with + * legacy code which keeps secret characters in String. + */ + def toStringUnsecure(): String = { + checkErased() + String.valueOf(_data) + } + +} diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/SigmaProver.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/SigmaProver.scala new file mode 100644 index 0000000000..255534e14b --- /dev/null +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/SigmaProver.scala @@ -0,0 +1,81 @@ +package org.ergoplatform.sdk + +import org.ergoplatform.ErgoAddressEncoder.NetworkPrefix +import org.ergoplatform._ +import org.ergoplatform.sdk.wallet.protocol.context.ErgoLikeStateContext +import sigmastate.eval.{CostingSigmaDslBuilder, SigmaDsl} +import sigmastate.interpreter.HintsBag +import sigmastate.utils.Helpers.TryOps +import special.sigma.{BigInt, SigmaProp} + +/** Represents a prover for signing Ergo transactions and messages. + * + * @param _prover an instance of interpreter and a prover combined + * @param networkPrefix the network prefix for Ergo addresses + */ +class SigmaProver(_prover: AppkitProvingInterpreter, networkPrefix: NetworkPrefix) { + implicit val ergoAddressEncoder: ErgoAddressEncoder = ErgoAddressEncoder(networkPrefix) + + /** Returns the Pay-to-Public-Key (P2PK) address associated with the prover's public key. */ + def getP2PKAddress: P2PKAddress = { + val pk = _prover.pubKeys(0) + P2PKAddress(pk) + } + + /** Returns the prover's secret key. */ + def getSecretKey: BigInt = + CostingSigmaDslBuilder.BigInt(_prover.secretKeys(0).privateInput.w) + + /** Returns a sequence of EIP-3 addresses associated with the prover's secret keys. */ + def getEip3Addresses: Seq[P2PKAddress] = { + val addresses = _prover.secretKeys + .drop(1) // the master address + .map { k => + val p2pkAddress = P2PKAddress(k.publicImage) + p2pkAddress + } + addresses + } + + /** Signs a given `UnreducedTransaction` using the prover's secret keys and the provided `ErgoLikeStateContext`. + * Uses baseCost == 0. + */ + def sign(stateCtx: ErgoLikeStateContext, tx: UnreducedTransaction): SignedTransaction = + sign(stateCtx, tx, baseCost = 0) + + /** Signs a given `UnreducedTransaction` using the prover's secret keys and the provided `ErgoLikeStateContext`. + * Uses the given baseCost. + */ + def sign(stateCtx: ErgoLikeStateContext, tx: UnreducedTransaction, baseCost: Int): SignedTransaction = { + val signed = _prover + .sign(tx, stateContext = stateCtx, baseCost = baseCost) + .getOrThrow + signed + } + + /** Sign arbitrary message under a key representing a statement provable via a sigma-protocol. + * + * @param sigmaProp - public key + * @param message - message to sign + * @param hintsBag - additional hints for a signer (useful for distributed signing) + * @return - signature bytes + */ + def signMessage(sigmaProp: SigmaProp, message: Array[Byte], hintsBag: HintsBag): Array[Byte] = { + _prover.signMessage(SigmaDsl.toSigmaBoolean(sigmaProp), message, hintsBag).getOrThrow + } + + /** Reduces a given `UnreducedTransaction` using the prover's secret keys and the + * provided `ErgoLikeStateContext` with a base cost. + */ + def reduce(stateCtx: ErgoLikeStateContext, tx: UnreducedTransaction, baseCost: Int): ReducedTransaction = { + val reduced = _prover.reduceTransaction( + unreducedTx = tx, stateContext = stateCtx, baseCost = baseCost) + reduced + } + + /** Signs a given ReducedTransaction using the prover's secret keys. */ + def signReduced(tx: ReducedTransaction): SignedTransaction = { + _prover.signReduced(tx, tx.ergoTx.cost) + } + +} diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/Transactions.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/Transactions.scala new file mode 100644 index 0000000000..8073b77c9e --- /dev/null +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/Transactions.scala @@ -0,0 +1,53 @@ +package org.ergoplatform.sdk + +import org.ergoplatform.{ErgoBox, ErgoLikeTransaction, UnsignedErgoLikeTransaction} + +import java.util + +/** Represents a transaction data chat can be reduced to [[ReducedTransaction]]. + * + * @note requires `unsignedTx` and `boxesToSpend` have the same boxIds in the same order. + * + * @param unsignedTx original unsigned transaction to be reduced (holds messageToSign) + * @param boxesToSpend input boxes of the transaction + * @param dataInputs data inputs of the transaction + * @param tokensToBurn requested tokens to be burnt in the transaction, if empty no burning allowed + */ +case class UnreducedTransaction( + unsignedTx: UnsignedErgoLikeTransaction, + boxesToSpend: IndexedSeq[ExtendedInputBox], + dataInputs: IndexedSeq[ErgoBox], + tokensToBurn: IndexedSeq[ErgoToken] +) { + require(unsignedTx.inputs.length == boxesToSpend.length, "Not enough boxes to spend") + require(unsignedTx.dataInputs.length == dataInputs.length, "Not enough data boxes") + checkSameIds( + "unsignedTx.inputs", unsignedTx.inputs.map(_.boxId), + "boxesToSpend", boxesToSpend.map(_.box.id), + "boxesToSpend should have the same box ids as unsignedTx.inputs") + checkSameIds( + "unsignedTx.dataInputs", unsignedTx.dataInputs.map(_.boxId), + "dataInputs", dataInputs.map(_.id), + "dataInputs should have the same box ids as unsignedTx.dataInputs") + + private def checkSameIds( + xsName: String, xs: Seq[Array[Byte]], + ysName: String, ys: Seq[Array[Byte]], msg: => String) = { + require(xs.zip(ys).forall { case (id1, id2) => util.Arrays.equals(id1, id2) }, { + val xsOnly = xs.diff(ys) + val ysOnly = ys.diff(xs) + s"""$msg: + | only in $xsName: $xsOnly + | only in $ysName: $ysOnly + |""".stripMargin + }) + } +} + +/** Represents results for transaction reduction by [[ReducingInterpreter]]. */ +case class ReducedTransaction(ergoTx: ReducedErgoLikeTransaction) + +/** Represents results for transaction signing by a prover like [[SigmaProver]]. */ +case class SignedTransaction(ergoTx: ErgoLikeTransaction, cost: Int) + + diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/Utils.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/Utils.scala new file mode 100644 index 0000000000..f706a09f96 --- /dev/null +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/Utils.scala @@ -0,0 +1,115 @@ +package org.ergoplatform.sdk + +import scalan.ExactIntegral + +import scala.collection.mutable +import scala.reflect.ClassTag + +object Utils { + + /** Performs outer join operation between left and right collections. + * Note, the ordering is not deterministic. + * + * @param l projection function executed for each element of `left` + * @param r projection function executed for each element of `right` + * @param inner projection function which is executed for matching items (K, L) and (K, R) with the same K + * @return map of (K, O) pairs, where each key comes form either left or right collection and values are produced by projections + */ + def outerJoin[K, L, R, O] + (left: Map[K, L], right: Map[K, R]) + (l: (K, L) => O, r: (K, R) => O, inner: (K, L, R) => O): Map[K, O] = { + val res = mutable.HashMap.empty[K, O] + val lks = left.keySet + val rks = right.keySet + val leftOnly = lks diff rks + val rightOnly = rks diff lks + val both = lks intersect rks + for ( lk <- leftOnly ) + res += lk -> l(lk, left(lk)) + for ( rk <- rightOnly ) + res += rk -> r(rk, right(rk)) + for ( k <- both ) + res += k -> inner(k, left(k), right(k)) + res.toMap + } + + /** Performance optimized deterministic mapReduce primitive. + * @param arr array to be mapped to (K, V) pairs + * @param m mapper function + * @param r value reduction function + * @return pair of arrays (keys, values), where keys appear in order of their first + * production by `m` and for each i => values(i) corresponds to keys(i) + */ + def mapReduce[A, K: ClassTag, V: ClassTag]( + arr: Array[A], + m: A => (K, V), + r: ((V, V)) => V): (Array[K], Array[V]) = { + val keyPositions = new java.util.HashMap[K, Int](32) + val keys = mutable.ArrayBuilder.make[K] + val values = new Array[V](arr.length) + var i = 0 + var nValues = 0 + while (i < arr.length) { + val (key, value) = m(arr(i)) + val pos = { + val p: Integer = keyPositions.get(key) + if (p == null) 0 else p.intValue() + } + if (pos == 0) { + keyPositions.put(key, nValues + 1) + keys += key + values(nValues) = value + nValues += 1 + } else { + values(pos - 1) = r((values(pos - 1), value)) + } + i += 1 + } + val resValues = new Array[V](nValues) + Array.copy(values, 0, resValues, 0, nValues) + (keys.result(), resValues) + } + + def mapToArrays[K: ClassTag, V: ClassTag](m: Map[K, V]): (Array[K], Array[V]) = { + val keys = mutable.ArrayBuilder.make[K] + val values = mutable.ArrayBuilder.make[V] + for ( (k, v) <- m ) { + keys += k + values += v + } + (keys.result, values.result) + } + + /** This class can adapt ExactIntegral instance to be used where Integral is required. + * See methods like sum, subByKey. + */ + class IntegralFromExactIntegral[A](val ei: ExactIntegral[A]) extends Integral[A] { + override def quot(x: A, y: A): A = ei.quot(x, y) + + override def rem(x: A, y: A): A = ei.divisionRemainder(x, y) + + override def plus(x: A, y: A): A = ei.plus(x, y) + + override def minus(x: A, y: A): A = ei.minus(x, y) + + override def times(x: A, y: A): A = ei.times(x, y) + + override def negate(x: A): A = ei.negate(x) + + override def fromInt(x: Int): A = ei.fromInt(x) + + override def toInt(x: A): Int = ei.toInt(x) + + override def toLong(x: A): Long = ei.toLong(x) + + override def toFloat(x: A): Float = notSupported + + override def toDouble(x: A): Double = notSupported + + override def compare(x: A, y: A): Int = notSupported + + private def notSupported = throw new NotImplementedError("operation is not supported") + + def parseString(str: String): Option[A] = throw new NotImplementedError("operation is not supported") + } +} diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/utils/ArithUtils.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/utils/ArithUtils.scala new file mode 100644 index 0000000000..474b2033c7 --- /dev/null +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/utils/ArithUtils.scala @@ -0,0 +1,30 @@ +package org.ergoplatform.sdk.utils + +/** Utils used in Ergo node (moved here from ergo-wallet). */ +object ArithUtils { + + /** Add longs, returning Long.Max value if there was any long overflow. + * This version of addExact is used in Ergo node. + */ + @inline def addExact(a: Long, b: Long): Long = { + val sum = a + b + if (((a ^ sum) & (b ^ sum)) < 0) Long.MaxValue else sum + } + + /** Add longs, returning Long.Max value if there was any long overflow. + * This version of addExact is used in Ergo node. + */ + def addExact(a: Long, b: Long, c: Long*): Long = c.foldLeft(addExact(a,b))((x, y) => addExact(x, y)) + + /** Multiply longs, returning Long.Max value if there was any long overflow. + * This version of multiplyExact is used in Ergo node. + */ + @inline def multiplyExact(e1: Long, e2: Long): Long = { + try { + java7.compat.Math.multiplyExact(e1, e2) + } catch { + case _: Throwable => Long.MaxValue + } + } + +} diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/wallet/AssetUtils.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/wallet/AssetUtils.scala new file mode 100644 index 0000000000..df61627805 --- /dev/null +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/wallet/AssetUtils.scala @@ -0,0 +1,55 @@ +package org.ergoplatform.sdk.wallet + +import scorex.util.ModifierId + +import scala.collection.mutable + +object AssetUtils { + + @inline + def mergeAssetsMut(into: mutable.Map[ModifierId, Long], from: TokensMap*): Unit = { + from.foreach(_.foreach { + case (id, amount) => + into.put(id, java7.compat.Math.addExact(into.getOrElse(id, 0L), amount)) + }) + } + + @inline + def mergeAssets(initialMap: TokensMap, maps: TokensMap*): TokensMap = { + maps.foldLeft(initialMap) { case (to, map) => + map.foldLeft(to) { case (acc, (id, amount)) => + acc.updated(id, java7.compat.Math.addExact(acc.getOrElse(id, 0L), amount)) + } + } + } + + @inline + def subtractAssets(initialMap: TokensMap, subtractor: TokensMap*): TokensMap = { + subtractor.foldLeft(initialMap){ case (from, mapToSubtract) => + mapToSubtract.foldLeft(from) { case (acc, (id, amount)) => + val currAmt = acc.getOrElse(id, + throw new IllegalArgumentException(s"Cannot subtract $amount of token $id: token not found in $acc")) + require(amount >= 0, s"Cannot subtract negative amount from token $id: $amount") + require(currAmt >= amount, s"Not enough amount of token $id -> $currAmt to subtract $amount") + if (currAmt == amount) { + acc - id + } else { + acc.updated(id, currAmt - amount) + } + } + } + } + + @inline + def subtractAssetsMut(from: mutable.Map[ModifierId, Long], subtractor: TokensMap): Unit = { + subtractor.foreach { case (id, subtractAmt) => + val fromAmt = from(id) + if (fromAmt == subtractAmt) { + from.remove(id) + } else { + from.put(id, fromAmt - subtractAmt) + } + } + } + +} diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/wallet/Constants.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/wallet/Constants.scala new file mode 100644 index 0000000000..3f5b4a83d5 --- /dev/null +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/wallet/Constants.scala @@ -0,0 +1,43 @@ +package org.ergoplatform.sdk.wallet + +import org.ergoplatform.sdk.wallet.secrets.DerivationPath + +object Constants { + + /** part of the protocol, do not change */ + val ModifierIdLength = 32 + + /** + * [See EIP-3 https://github.com/ergoplatform/eips/blob/master/eip-0003.md ] + * + * For coin type, we suggest consider "ergo" word in ASCII and calculate coin_type number as + * + * 101 + 114 + 103 + 111 = 429 + * + * Following this idea we should use next scheme + * + * m / 44' / 429' / account' / change / address_index + */ + val CoinType = 429 + + /** + * There needs to be reasonable amount of tokens in a box due to a byte size limit for ergo box + * */ + val MaxAssetsPerBox = 100 + + /** + * Pre - EIP3 derivation path + */ + val preEip3DerivationPath: DerivationPath = DerivationPath.fromEncoded("m/1").get + + /** + * Post - EIP3 derivation path + */ + val eip3DerivationPath: DerivationPath = DerivationPath.fromEncoded("m/44'/429'/0'/0/0").get + + val MnemonicSentenceSizes: Seq[Int] = Seq(12, 15, 18, 21, 24) + + val AllowedStrengths: Seq[Int] = Seq(128, 160, 192, 224, 256) + + val AllowedEntropyLengths: Seq[Int] = AllowedStrengths.map(_ / 8) +} diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/wallet/package.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/wallet/package.scala new file mode 100644 index 0000000000..3a26161fb7 --- /dev/null +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/wallet/package.scala @@ -0,0 +1,7 @@ +package org.ergoplatform.sdk + +import scorex.util.ModifierId + +package object wallet { + type TokensMap = Map[ModifierId, Long] +} diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/wallet/protocol/context/ErgoLikeParameters.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/wallet/protocol/context/ErgoLikeParameters.scala new file mode 100644 index 0000000000..b84387e2b3 --- /dev/null +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/wallet/protocol/context/ErgoLikeParameters.scala @@ -0,0 +1,63 @@ +package org.ergoplatform.sdk.wallet.protocol.context + +/** + * Blockchain parameters readjustable via miners voting and voting-related data. + * All these fields are included into extension section of a first block of a voting epoch. + */ +trait ErgoLikeParameters { + + /** + * @return cost of storing 1 byte in UTXO for four years, in nanoErgs + */ + def storageFeeFactor: Int + + /** + * @return cost of a transaction output, in computation unit + */ + def minValuePerByte: Int + + /** + * @return max block size, in bytes + */ + def maxBlockSize: Int + + /** + * @return cost of a token contained in a transaction, in computation unit + */ + def tokenAccessCost: Int + + /** + * @return cost of a transaction input, in computation unit + */ + def inputCost: Int + + /** + * @return cost of a transaction data input, in computation unit + */ + def dataInputCost: Int + + /** + * @return cost of a transaction output, in computation unit + */ + def outputCost: Int + + /** + * @return computation units limit per block + */ + def maxBlockCost: Int + + /** + * @return height when voting for a soft-fork had been started + */ + def softForkStartingHeight: Option[Int] + + /** + * @return votes for soft-fork collected in previous epochs + */ + def softForkVotesCollected: Option[Int] + + /** + * @return Protocol version + */ + def blockVersion: Byte +} diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/wallet/protocol/context/ErgoLikeStateContext.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/wallet/protocol/context/ErgoLikeStateContext.scala new file mode 100644 index 0000000000..327c3a3ddf --- /dev/null +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/wallet/protocol/context/ErgoLikeStateContext.scala @@ -0,0 +1,51 @@ +package org.ergoplatform.sdk.wallet.protocol.context + +import scorex.crypto.authds.ADDigest +import special.collection.Coll + +import java.util + +/** + * Blockchain context used in transaction validation. + */ +trait ErgoLikeStateContext { + + /** + * @return fixed number (10 in Ergo) of last block headers + */ + def sigmaLastHeaders: Coll[special.sigma.Header] + + // todo remove from ErgoLikeContext and from ErgoStateContext + /** + * @return UTXO set digest from a last header (of sigmaLastHeaders) + */ + def previousStateDigest: ADDigest + + /** + * @return returns pre-header (header without certain fields) of the current block + */ + def sigmaPreHeader: special.sigma.PreHeader +} + +/** Representis the Ergo-like state context for tx signing. + * + * @param sigmaLastHeaders the last headers of the Sigma blockchain + * @param previousStateDigest the bytes representing the previous state digest + * @param sigmaPreHeader the pre-header object + */ +case class CErgoLikeStateContext( + sigmaLastHeaders: Coll[special.sigma.Header], + previousStateDigest: ADDigest, + sigmaPreHeader: special.sigma.PreHeader +) extends ErgoLikeStateContext { + override def hashCode(): Int = + (sigmaLastHeaders.hashCode() * 41 + util.Arrays.hashCode(previousStateDigest)) * 41 + sigmaPreHeader.hashCode() + + override def equals(obj: Any): Boolean = (this eq obj.asInstanceOf[AnyRef]) || (obj match { + case c: CErgoLikeStateContext => + sigmaLastHeaders == c.sigmaLastHeaders && + util.Arrays.equals(previousStateDigest, c.previousStateDigest) && + sigmaPreHeader == c.sigmaPreHeader + case _ => false + }) +} diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/wallet/protocol/context/TransactionContext.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/wallet/protocol/context/TransactionContext.scala new file mode 100644 index 0000000000..3c57cceada --- /dev/null +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/wallet/protocol/context/TransactionContext.scala @@ -0,0 +1,17 @@ +package org.ergoplatform.sdk.wallet.protocol.context + +import org.ergoplatform.{ErgoBox, ErgoLikeTransactionTemplate, UnsignedInput} + +/** + * Part of the execution context in regards with spending transaction + * + * @param boxesToSpend - inputs of the transaction + * @param dataBoxes - data (read-only) inputs of the transaction + * @param spendingTransaction - spending transaction + */ + +// TODO: it seems spendingTransaction is needed only to get output candidates in ErgoLikeContext. +// After ErgoLikeContext refactoring in sigma, this class can be simplified. +case class TransactionContext(boxesToSpend: IndexedSeq[ErgoBox], + dataBoxes: IndexedSeq[ErgoBox], + spendingTransaction: ErgoLikeTransactionTemplate[_ <: UnsignedInput]) diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/wallet/secrets/DerivationPath.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/wallet/secrets/DerivationPath.scala new file mode 100644 index 0000000000..601a45e537 --- /dev/null +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/wallet/secrets/DerivationPath.scala @@ -0,0 +1,151 @@ +package org.ergoplatform.sdk.wallet.secrets + +import org.ergoplatform.sdk.wallet.Constants +import scorex.util.serialization.{Reader, Writer} +import sigmastate.serialization.SigmaSerializer +import sigmastate.utils.{SigmaByteReader, SigmaByteWriter} + +import scala.util.{Failure, Success, Try} + +/** + * HD key derivation path (see: https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki) + */ +final case class DerivationPath(decodedPath: Seq[Int], publicBranch: Boolean) { + + import DerivationPath._ + + def depth: Int = decodedPath.length + + /** + * @return last element of the derivation path, e.g. 2 for m/1/2 + */ + def index: Int = decodedPath.last + + def isMaster: Boolean = depth == 1 + + /** Encode this DerivationPath as a parsable string. */ + def encoded: String = { + val masterPrefix = if (publicBranch) s"$PublicBranchMasterId/" else s"$PrivateBranchMasterId/" + val tailPath = decodedPath.tail + .map(x => if (Index.isHardened(x)) s"${x - Index.HardRangeStart}'" else x.toString) + .mkString("/") + masterPrefix + tailPath + } + + def extended(idx: Int): DerivationPath = DerivationPath(decodedPath :+ idx, publicBranch) + + /** + * @return path with last element of the derivation path being increased, e.g. m/1/2 -> m/1/3 + */ + def increased: DerivationPath = DerivationPath(decodedPath.dropRight(1) :+ (index + 1), publicBranch) + + /** + * Convert the derivation path to public branch. See BIP-32 for details. + * @return derivation path from the public branch + */ + def toPublicBranch: DerivationPath = this.copy(publicBranch = true) + + /** + * Convert the derivation path to private branch. See BIP-32 for details. + * @return derivation path from the private branch + */ + def toPrivateBranch: DerivationPath = this.copy(publicBranch = false) + + /** + * @return whether derivation path corresponds to EIP-3 + */ + def isEip3: Boolean = { + decodedPath.tail.startsWith(Constants.eip3DerivationPath.decodedPath.tail.take(3)) + } + + override def toString: String = encoded + + def bytes: Array[Byte] = DerivationPathSerializer.toBytes(this) +} + +object DerivationPath { + + val PublicBranchMasterId = "M" + val PrivateBranchMasterId = "m" + + val MasterPath: DerivationPath = DerivationPath(List(0), publicBranch = false) + + def fromEncoded(path: String): Try[DerivationPath] = { + val split = path.split("/") + if (!split.headOption.exists(Seq(PublicBranchMasterId, PrivateBranchMasterId).contains)) { + Failure(new Exception("Wrong path format")) + } else { + val pathTry = split.tail.foldLeft(Try(List(0))) { case (accTry, sym) => + accTry.flatMap { acc => + Try(if (sym.endsWith("'")) Index.hardIndex(sym.dropRight(1).toInt) else sym.toInt) + .map(acc :+ _) + } + } + val isPublicBranch = split.head == PublicBranchMasterId + pathTry.map(DerivationPath(_, isPublicBranch)) + } + } + + /** + * Finds next available path index for a new key. + * @param secrets - secrets previously generated + * @param usePreEip3Derivation - whether to use pre-EIP3 derivation or not + */ + def nextPath(secrets: IndexedSeq[ExtendedSecretKey], + usePreEip3Derivation: Boolean): Try[DerivationPath] = { + + def pathSequence(secret: ExtendedSecretKey): Seq[Int] = secret.path.decodedPath.tail + + @scala.annotation.tailrec + def nextPath(accPath: List[Int], remaining: Seq[Seq[Int]]): Try[DerivationPath] = { + if (!remaining.forall(_.isEmpty)) { + val maxChildIdx = remaining.flatMap(_.headOption).max + if (!Index.isHardened(maxChildIdx)) { + Success(DerivationPath(0 +: (accPath :+ maxChildIdx + 1), publicBranch = false)) + } else { + nextPath(accPath :+ maxChildIdx, remaining.map(_.drop(1))) + } + } else { + val exc = new Exception("Out of non-hardened index space. Try to derive hardened key specifying path manually") + Failure(exc) + } + } + + if (secrets.isEmpty || (secrets.size == 1 && secrets.head.path.isMaster)) { + // If pre-EIP3 generation, the first key generated after master's would be m/1, otherwise m/44'/429'/0'/0/0 + val path = if(usePreEip3Derivation) { + Constants.preEip3DerivationPath + } else { + Constants.eip3DerivationPath + } + Success(path) + } else { + // If last secret corresponds to EIP-3 path, do EIP-3 derivation, otherwise, old derivation + // For EIP-3 derivation, we increase last segment, m/44'/429'/0'/0/0 -> m/44'/429'/0'/0/1 and so on + // For old derivation, we increase last non-hardened segment, m/1/1 -> m/2 + if (secrets.last.path.isEip3) { + Success(secrets.last.path.increased) + } else { + nextPath(List.empty, secrets.map(pathSequence)) + } + } + } + +} + +object DerivationPathSerializer extends SigmaSerializer[DerivationPath, DerivationPath] { + + override def serialize(obj: DerivationPath, w: SigmaByteWriter): Unit = { + w.put(if (obj.publicBranch) 0x01 else 0x00) + w.putInt(obj.depth) + obj.decodedPath.foreach(i => w.putBytes(Index.serializeIndex(i))) + } + + override def parse(r: SigmaByteReader): DerivationPath = { + val publicBranch = if (r.getByte() == 0x01) true else false + val depth = r.getInt() + val path = (0 until depth).map(_ => Index.parseIndex(r.getBytes(4))) + DerivationPath(path, publicBranch) + } + +} diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/wallet/secrets/ExtendedKey.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/wallet/secrets/ExtendedKey.scala new file mode 100644 index 0000000000..44db3b2f4f --- /dev/null +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/wallet/secrets/ExtendedKey.scala @@ -0,0 +1,43 @@ +package org.ergoplatform.sdk.wallet.secrets + +/** Description from https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki : + * + * We extend both private and public keys first with an extra 256 bits of entropy. + * This extension, called the chain code, is identical for corresponding private and public keys + * and consists of 32 bytes. + * We represent an extended private key as (k, c), with k the normal private key, + * and c the chain code. An extended public key is represented as (K, c), + * with K = point(k) and c the chain code. + * + * Each extended key has 2^31 normal child keys, and 2^31 hardened child keys. + * Each of these child keys has an index. The normal child keys use indices 0 through 2^31-1. + * The hardened child keys use indices 2^31 through `2^32-1`. + */ +trait ExtendedKey[T <: ExtendedKey[T]] { + + val path: DerivationPath + + /** Returns subtype reference. + */ + def selfReflection: T + + /** Given a parent extended key and an index `idx`, it is possible to compute the corresponding + * child extended key. The algorithm to do so depends on whether the child is a hardened key + * or not (or, equivalently, whether `idx ≥ 2^31`), and whether we're talking about private or + * public keys. + * + * @see implementations in derived classes + */ + def child(idx: Int): T + + def derive(upPath: DerivationPath): T = { + require( + upPath.depth >= path.depth && + upPath.decodedPath.take(path.depth).zip(path.decodedPath).forall { case (i1, i2) => i1 == i2 } && + upPath.publicBranch == path.publicBranch, + s"Incompatible paths: $upPath, $path" + ) + upPath.decodedPath.drop(path.depth).foldLeft(selfReflection)((parent, i) => parent.child(i)) + } + +} diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/wallet/secrets/ExtendedPublicKey.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/wallet/secrets/ExtendedPublicKey.scala new file mode 100644 index 0000000000..cdc7458b75 --- /dev/null +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/wallet/secrets/ExtendedPublicKey.scala @@ -0,0 +1,90 @@ +package org.ergoplatform.sdk.wallet.secrets + +import java.util +import sigmastate.basics.DLogProtocol.{DLogProverInput, ProveDlog} +import sigmastate.basics.CryptoConstants +import sigmastate.crypto.CryptoFacade +import sigmastate.crypto.BigIntegers +import sigmastate.serialization.SigmaSerializer +import sigmastate.utils.{SigmaByteReader, SigmaByteWriter} + +import scala.annotation.tailrec + +/** + * Public key, its chain code and path in key tree. + * (see: https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki) + */ +final class ExtendedPublicKey(/*private[secrets] */val keyBytes: Array[Byte], + /*private[secrets] */val chainCode: Array[Byte], + val path: DerivationPath) + extends ExtendedKey[ExtendedPublicKey] { + + def selfReflection: ExtendedPublicKey = this + + def key: ProveDlog = ProveDlog( + CryptoConstants.dlogGroup.ctx.decodePoint(keyBytes) + ) + + def child(idx: Int): ExtendedPublicKey = ExtendedPublicKey.deriveChildPublicKey(this, idx) + + override def equals(obj: Any): Boolean = (this eq obj.asInstanceOf[AnyRef]) || (obj match { + case that: ExtendedPublicKey => + util.Arrays.equals(that.keyBytes, this.keyBytes) && + util.Arrays.equals(that.chainCode, this.chainCode) && + that.path == this.path + case _ => false + }) + + override def hashCode(): Int = { + var h = util.Arrays.hashCode(keyBytes) + h = 31 * h + util.Arrays.hashCode(chainCode) + h = 31 * h + path.hashCode() + h + } + + override def toString: String = s"ExtendedPublicKey($path : $key)" +} + +object ExtendedPublicKey { + + @tailrec + def deriveChildPublicKey(parentKey: ExtendedPublicKey, idx: Int): ExtendedPublicKey = { + require(!Index.isHardened(idx), "Hardened public keys derivation is not supported") + val (childKeyProto, childChainCode) = CryptoFacade + .hashHmacSHA512(parentKey.chainCode, parentKey.keyBytes ++ Index.serializeIndex(idx)) + .splitAt(CryptoFacade.SecretKeyLength) + val childKeyProtoDecoded = BigIntegers.fromUnsignedByteArray(childKeyProto) + val childKey = CryptoFacade.multiplyPoints(DLogProverInput(childKeyProtoDecoded).publicImage.value, parentKey.key.value) + if (childKeyProtoDecoded.compareTo(CryptoConstants.groupOrder) >= 0 || CryptoFacade.isInfinityPoint(childKey)) { + deriveChildPublicKey(parentKey, idx + 1) + } else { + new ExtendedPublicKey(CryptoFacade.getASN1Encoding(childKey, true), childChainCode, parentKey.path.extended(idx)) + } + } + +} + +object ExtendedPublicKeySerializer extends SigmaSerializer[ExtendedPublicKey, ExtendedPublicKey] { + + import scorex.util.Extensions._ + + //ASN.1 encoding for secp256k1 points - 1 byte for sign + 32 bytes for x-coordinate of the point + val PublicKeyBytesSize: Int = CryptoFacade.SecretKeyLength + 1 + + override def serialize(obj: ExtendedPublicKey, w: SigmaByteWriter): Unit = { + w.putBytes(obj.keyBytes) + w.putBytes(obj.chainCode) + val pathBytes = DerivationPathSerializer.toBytes(obj.path) + w.putUInt(pathBytes.length) + w.putBytes(pathBytes) + } + + override def parse(r: SigmaByteReader): ExtendedPublicKey = { + val keyBytes = r.getBytes(PublicKeyBytesSize) + val chainCode = r.getBytes(CryptoFacade.SecretKeyLength) + val pathLen = r.getUInt().toIntExact + val path = DerivationPathSerializer.fromBytes(r.getBytes(pathLen)) + new ExtendedPublicKey(keyBytes, chainCode, path) + } + +} diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/wallet/secrets/ExtendedSecretKey.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/wallet/secrets/ExtendedSecretKey.scala new file mode 100644 index 0000000000..2842658e66 --- /dev/null +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/wallet/secrets/ExtendedSecretKey.scala @@ -0,0 +1,127 @@ +package org.ergoplatform.sdk.wallet.secrets + +import org.ergoplatform.sdk.wallet.Constants +import java.math.BigInteger +import java.util +import sigmastate.crypto.BigIntegers +import sigmastate.basics.DLogProtocol +import sigmastate.basics.DLogProtocol.DLogProverInput +import sigmastate.basics.CryptoConstants +import sigmastate.crypto.CryptoFacade +import sigmastate.serialization.SigmaSerializer +import sigmastate.utils.{SigmaByteReader, SigmaByteWriter} + +/** + * Secret, its chain code and path in key tree. + * (see: https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki) + */ +final class ExtendedSecretKey(/*private[secrets]*/ val keyBytes: Array[Byte], + /*private[secrets]*/ val chainCode: Array[Byte], + val usePre1627KeyDerivation: Boolean, + val path: DerivationPath) + extends ExtendedKey[ExtendedSecretKey] with SecretKey { + + def selfReflection: ExtendedSecretKey = this + + override def privateInput: DLogProverInput = DLogProverInput(BigIntegers.fromUnsignedByteArray(keyBytes)) + + def publicImage: DLogProtocol.ProveDlog = privateInput.publicImage + + def child(idx: Int): ExtendedSecretKey = ExtendedSecretKey.deriveChildSecretKey(this, idx) + + def publicKey: ExtendedPublicKey = + new ExtendedPublicKey(CryptoFacade.getASN1Encoding(privateInput.publicImage.value, true), chainCode, path.toPublicBranch) + + def isErased: Boolean = keyBytes.forall(_ == 0x00) + + def zeroSecret(): Unit = util.Arrays.fill(keyBytes, 0: Byte) + + override def equals(obj: Any): Boolean = (this eq obj.asInstanceOf[AnyRef]) || (obj match { + case that: ExtendedSecretKey => + util.Arrays.equals(that.keyBytes, this.keyBytes) && + util.Arrays.equals(that.chainCode, this.chainCode) && + that.path == this.path + case _ => false + }) + + override def hashCode(): Int = { + var h = util.Arrays.hashCode(keyBytes) + h = 31 * h + util.Arrays.hashCode(chainCode) + h = 31 * h + path.hashCode() + h + } + +} + +object ExtendedSecretKey { + + @scala.annotation.tailrec + def deriveChildSecretKey(parentKey: ExtendedSecretKey, idx: Int): ExtendedSecretKey = { + val keyCoded: Array[Byte] = + if (Index.isHardened(idx)) (0x00: Byte) +: parentKey.keyBytes + else CryptoFacade.getASN1Encoding(parentKey.privateInput.publicImage.value, true) + val (childKeyProto, childChainCode) = CryptoFacade + .hashHmacSHA512(parentKey.chainCode, keyCoded ++ Index.serializeIndex(idx)) + .splitAt(CryptoFacade.SecretKeyLength) + val childKeyProtoDecoded = BigIntegers.fromUnsignedByteArray(childKeyProto) + val childKey = childKeyProtoDecoded + .add(BigIntegers.fromUnsignedByteArray(parentKey.keyBytes)) + .mod(CryptoConstants.groupOrder) + if (childKeyProtoDecoded.compareTo(CryptoConstants.groupOrder) >= 0 || childKey.equals(BigInteger.ZERO)) + deriveChildSecretKey(parentKey, idx + 1) + else { + val keyBytes = if (parentKey.usePre1627KeyDerivation) { + // maybe less than 32 bytes if childKey is small enough while BIP32 requires 32 bytes. + // see https://github.com/ergoplatform/ergo/issues/1627 for details + BigIntegers.asUnsignedByteArray(childKey) + } else { + // padded with leading zeroes to 32 bytes + BigIntegers.asUnsignedByteArray(CryptoFacade.SecretKeyLength, childKey) + } + new ExtendedSecretKey(keyBytes, childChainCode, parentKey.usePre1627KeyDerivation, parentKey.path.extended(idx)) + } + } + + def deriveChildPublicKey(parentKey: ExtendedSecretKey, idx: Int): ExtendedPublicKey = { + val derivedSecret = deriveChildSecretKey(parentKey, idx) + val derivedPk = CryptoFacade.getASN1Encoding(derivedSecret.privateInput.publicImage.value, true) + val derivedPath = derivedSecret.path.copy(publicBranch = true) + new ExtendedPublicKey(derivedPk, derivedSecret.chainCode, derivedPath) + } + + + /** + * Derives master secret key from the seed + * @param seed - seed bytes + * @param usePre1627KeyDerivation - use incorrect(previous) BIP32 derivation, expected to be false for new wallets, and true for old pre-1627 wallets (see https://github.com/ergoplatform/ergo/issues/1627 for details) + */ + def deriveMasterKey(seed: Array[Byte], usePre1627KeyDerivation: Boolean): ExtendedSecretKey = { + val (masterKey, chainCode) = CryptoFacade + .hashHmacSHA512(CryptoFacade.BitcoinSeed, seed) + .splitAt(CryptoFacade.SecretKeyLength) + new ExtendedSecretKey(masterKey, chainCode, usePre1627KeyDerivation, DerivationPath.MasterPath) + } + +} + +object ExtendedSecretKeySerializer extends SigmaSerializer[ExtendedSecretKey, ExtendedSecretKey] { + + import scorex.util.Extensions._ + + override def serialize(obj: ExtendedSecretKey, w: SigmaByteWriter): Unit = { + w.putBytes(obj.keyBytes) + w.putBytes(obj.chainCode) + val pathBytes = DerivationPathSerializer.toBytes(obj.path) + w.putUInt(pathBytes.length) + w.putBytes(pathBytes) + } + + override def parse(r: SigmaByteReader): ExtendedSecretKey = { + val keyBytes = r.getBytes(CryptoFacade.SecretKeyLength) + val chainCode = r.getBytes(CryptoFacade.SecretKeyLength) + val pathLen = r.getUInt().toIntExact + val path = DerivationPathSerializer.fromBytes(r.getBytes(pathLen)) + new ExtendedSecretKey(keyBytes, chainCode, false, path) + } + +} diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/wallet/secrets/Index.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/wallet/secrets/Index.scala new file mode 100644 index 0000000000..c375e256ae --- /dev/null +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/wallet/secrets/Index.scala @@ -0,0 +1,17 @@ +package org.ergoplatform.sdk.wallet.secrets + +import scodec.bits.ByteVector + +object Index { + + val HardRangeStart = 0x80000000 + + def hardIndex(i: Int): Int = i | HardRangeStart + + def isHardened(i: Int): Boolean = (i & HardRangeStart) != 0 + + def serializeIndex(i: Int): Array[Byte] = ByteVector.fromInt(i).toArray + + def parseIndex(xs: Array[Byte]): Int = ByteVector(xs).toInt(signed = false) + +} diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/wallet/secrets/SecretKey.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/wallet/secrets/SecretKey.scala new file mode 100644 index 0000000000..0d6d54d719 --- /dev/null +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/wallet/secrets/SecretKey.scala @@ -0,0 +1,39 @@ +package org.ergoplatform.sdk.wallet.secrets + +import sigmastate.basics.DLogProtocol.DLogProverInput +import sigmastate.basics.{DiffieHellmanTupleProverInput, SigmaProtocolPrivateInput} + +/** + * Basic trait for secret data, encapsulating a corresponding private inputs for a Sigma protocol. + */ +trait SecretKey { + /** + * Private (secret) input of a sigma protocol + */ + def privateInput: SigmaProtocolPrivateInput[_, _] +} + +/** + * Basic trait for a secret which does not have a derivation scheme. + */ +sealed trait PrimitiveSecretKey extends SecretKey + +object PrimitiveSecretKey { + def apply(sigmaPrivateInput: SigmaProtocolPrivateInput[_, _]): PrimitiveSecretKey = sigmaPrivateInput match { + case dls: DLogProverInput => DlogSecretKey(dls) + case dhts: DiffieHellmanTupleProverInput => DhtSecretKey(dhts) + } +} + +/** + * Secret exponent of a group element, i.e. secret w such as h = g^^w, where g is group generator, h is a public key. + * @param privateInput - secret (in form of a sigma-protocol private input) + */ +case class DlogSecretKey(override val privateInput: DLogProverInput) extends PrimitiveSecretKey + +/** + * Secret exponent of a Diffie-Hellman tuple, i.e. secret w such as u = g^^w and v = h^^w, where g and h are group + * generators, (g,h,u,v) is a public input (public key). + * @param privateInput - secret (in form of a sigma-protocol private input) + */ +case class DhtSecretKey(override val privateInput: DiffieHellmanTupleProverInput) extends PrimitiveSecretKey diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/wallet/settings/EncryptionSettings.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/wallet/settings/EncryptionSettings.scala new file mode 100644 index 0000000000..00d5db9879 --- /dev/null +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/wallet/settings/EncryptionSettings.scala @@ -0,0 +1,40 @@ +package org.ergoplatform.sdk.wallet.settings + +import io.circe.{Json, Encoder, Decoder, HCursor} +import io.circe.syntax._ +import cats.syntax.either._ // don't remove, it is needed for scala 2.11 + +/** + * Encryption parameters + * @param prf - pseudo-random function with output of length `dkLen` (PBKDF2 param) + * @param c - number of PBKDF2 iterations (PBKDF2 param) + * @param dkLen - desired bit-length of the derived key (PBKDF2 param) + */ +final case class EncryptionSettings(prf: String, c: Int, dkLen: Int) + +object EncryptionSettings { + + implicit object EncryptionSettingsEncoder extends Encoder[EncryptionSettings] { + + def apply(s: EncryptionSettings): Json = { + Json.obj( + "prf" -> s.prf.asJson, + "c" -> s.c.asJson, + "dkLen" -> s.dkLen.asJson + ) + } + + } + + implicit object EncryptionSettingsDecoder extends Decoder[EncryptionSettings] { + + def apply(cursor: HCursor): Decoder.Result[EncryptionSettings] = { + for { + prf <- cursor.downField("prf").as[String] + c <- cursor.downField("c").as[Int] + dkLen <- cursor.downField("dkLen").as[Int] + } yield EncryptionSettings(prf, c, dkLen) + } + + } +} diff --git a/sdk/shared/src/test/scala/org/ergoplatform/sdk/ExtensionsSpec.scala b/sdk/shared/src/test/scala/org/ergoplatform/sdk/ExtensionsSpec.scala new file mode 100644 index 0000000000..3dfb5c4157 --- /dev/null +++ b/sdk/shared/src/test/scala/org/ergoplatform/sdk/ExtensionsSpec.scala @@ -0,0 +1,105 @@ +package org.ergoplatform.sdk + +import org.scalatest.matchers.should.Matchers +import org.scalatest.propspec.AnyPropSpec +import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks +import special.collection.Coll +import special.collections.CollGens +import org.ergoplatform.sdk.Extensions.{CollBuilderOps, CollOps, GenIterableOps, PairCollOps} +import scalan.RType +import sigmastate.eval.CostingSigmaDslBuilder + +class ExtensionsSpec extends AnyPropSpec with ScalaCheckPropertyChecks with Matchers with CollGens { + def Coll[T](items: T*)(implicit cT: RType[T]) = + CostingSigmaDslBuilder.Colls.fromItems(items: _*) + + val items: Iterable[(Int, String)] = Array((1, "a"), (2, "b"), (1, "c")) + + property("Traversable.mapReduce") { + val res = items.mapReduce(p => (p._1, p._2))((v1, v2) => v1 + v2) + assertResult(List((1, "ac"), (2, "b")))(res) + } + + property("Coll.partition") { + forAll(collGen) { col: Coll[Int] => + val (lsC, rsC) = col.partition(lt0) + val (ls, rs) = col.toArray.partition(lt0) + lsC.toArray shouldBe ls + rsC.toArray shouldBe rs + } + } + + property("Coll.mapReduce") { + def m(x: Int) = (math.abs(x) % 10, x) + + forAll(collGen) { col => + val res = col.mapReduce(m, plusF) + val (ks, vs) = builder.unzip(res) + vs.toArray.sum shouldBe col.toArray.sum + ks.length <= 10 shouldBe true + res.toArray shouldBe col.toArray.toIterable.mapReduce(m)(plus).toArray + } + } + + property("Coll.groupBy") { + def key(x: Int) = math.abs(x) % 10 + + forAll(collGen) { col => + val res = col.groupBy(key) + val (ks, vs) = builder.unzip(res) + vs.flatMap(identity).toArray.sum shouldBe col.toArray.sum + ks.length <= 10 shouldBe true + val pairs = col.map(x => (key(x), x)) + val res2 = pairs.groupByKey + val (ks2, vs2) = builder.unzip(res) + ks shouldBe ks2 + vs shouldBe vs2 + } + } + + property("PairColl.mapFirst") { + val minSuccess = minSuccessful(30) + forAll(collGen, minSuccess) { col => + val pairs = col.zip(col) + pairs.mapFirst(inc).toArray shouldBe pairs.toArray.map { case (x, y) => (inc(x), y) } + pairs.mapSecond(inc).toArray shouldBe pairs.toArray.map { case (x, y) => (x, inc(y)) } + } + } + + property("PairColl.sumByKey") { + val table = Table(("in", "out"), + (Coll[(String, Int)](), Coll[(String, Int)]()), + (Coll("a" -> 1), Coll("a" -> 1)), + (Coll("a" -> 1, "a" -> 1), Coll("a" -> 2)), + (Coll("a" -> 1, "b" -> 1, "a" -> 1), Coll("a" -> 2, "b" -> 1)), + (Coll("b" -> 1, "a" -> 1, "b" -> 1, "a" -> 1), Coll("b" -> 2, "a" -> 2)) + ) + forAll(table) { (in, out) => + in.sumByKey shouldBe out + } + } + + property("CollBuilder.outerJoin") { + def test(col: Coll[Int]) = { + val inner = col.indices + val rightOnly = inner.map(i => i + col.length) + val leftOnly = rightOnly.map(i => -i) + val leftKeys = inner.append(leftOnly) + val leftValues = col.append(col.map(x => x + 2)) + val rightKeys = inner.append(rightOnly) + val rightValues = col.append(col.map(x => x + 3)) + val left = builder.pairColl(leftKeys, leftValues) + val right = builder.pairColl(rightKeys, rightValues) + val res = builder.outerJoin(left, right)(l => l._2 - 2, r => r._2 - 3, i => i._2._1 + 5) + val (ks, vs) = builder.unzip(res) + vs.sum shouldBe (col.sum * 2 + col.map(_ + 5).sum) + } + // test(builder.fromItems(0)) + // val gen = containerOfN[Array, Int](100, choose(20, 100)) + // .map(xs => builder.fromArray(xs.distinct)) + forAll(collGen) { col => + test(col) + } + } + +} diff --git a/sdk/shared/src/test/scala/org/ergoplatform/sdk/wallet/secrets/DerivationPathSpec.scala b/sdk/shared/src/test/scala/org/ergoplatform/sdk/wallet/secrets/DerivationPathSpec.scala new file mode 100644 index 0000000000..ec524eebf6 --- /dev/null +++ b/sdk/shared/src/test/scala/org/ergoplatform/sdk/wallet/secrets/DerivationPathSpec.scala @@ -0,0 +1,131 @@ +package org.ergoplatform.sdk.wallet.secrets + +import org.ergoplatform.sdk.JavaHelpers +import org.ergoplatform.sdk.wallet.Constants +import org.ergoplatform.sdk.wallet.utils.Generators +import org.ergoplatform.{ErgoAddressEncoder, P2PKAddress} +import org.scalatest.matchers.should.Matchers +import org.scalatest.propspec.AnyPropSpec +import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks + +class DerivationPathSpec + extends AnyPropSpec + with Matchers + with ScalaCheckPropertyChecks + with Generators { + + property("derivation from encoded path") { + forAll(derivationPathGen) { path => + val decodeTry = DerivationPath.fromEncoded(path.encoded) + + decodeTry.isSuccess shouldBe true + decodeTry.get shouldEqual path + } + } + + property("nextPath - new default derivation") { + implicit val enc = new ErgoAddressEncoder(ErgoAddressEncoder.MainnetNetworkPrefix) + + // This testing pair is checked against CoinBarn and Yoroi + val mnemonic = "liar exercise solve delay betray sheriff method empower disease river recall vacuum" + val address = "9hAymcGaRfTX7bMADNdfWfk7CKzi2ZpvRBCmtEf6d92n8E26Ax7" + + val seed = JavaHelpers.mnemonicToSeed(mnemonic, None) + + val masterKey = ExtendedSecretKey.deriveMasterKey(seed, usePre1627KeyDerivation = false) + val dp = DerivationPath.nextPath(IndexedSeq(masterKey), usePreEip3Derivation = false).get + val sk = masterKey.derive(dp) + val pk = sk.publicKey.key + + // The first derived key corresponds to m/44'/429'/0'/0/0 and the same as the key in CoinBarn + // (and the first key in Yoroi) + P2PKAddress(pk).toString() shouldBe address + + val pk2 = masterKey.derive(DerivationPath.fromEncoded("m/44'/429'/0'/0/0").get).publicKey.key + P2PKAddress(pk2).toString() shouldBe address + + // next path should be m/44'/429'/0'/0/1 + val dp2 = DerivationPath.nextPath(IndexedSeq(masterKey, sk), usePreEip3Derivation = false).get + dp2 shouldBe DerivationPath.fromEncoded("m/44'/429'/0'/0/1").get + + // on top of old paths, derivation works as before EIP, m/1 -> m/2 + val sk2 = masterKey.derive(DerivationPath.fromEncoded("m/1").get) + val dp3 = DerivationPath.nextPath(IndexedSeq(masterKey, sk2), usePreEip3Derivation = false).get + dp3 shouldBe DerivationPath.fromEncoded("m/2").get + + // for (m/1, m/44'/429'/0'/0/0), next path would be m/44'/429'/0'/0/1 + val dp4 = DerivationPath.nextPath(IndexedSeq(masterKey, sk2, sk), usePreEip3Derivation = false).get + dp4 shouldBe DerivationPath.fromEncoded("m/44'/429'/0'/0/1").get + } + + + property("nextPath - old derivation") { + implicit val enc = new ErgoAddressEncoder(ErgoAddressEncoder.MainnetNetworkPrefix) + + // pre-EIP3 derivation testing pair got from the node + val mnemonic = "liar exercise solve delay betray sheriff method empower disease river recall vacuum" + val address = "9h7f11AC9RMHkhFbXg46XfYHq3HNnb1A9UtMmMYo6hAuQzWxVWu" + + val seed = JavaHelpers.mnemonicToSeed(mnemonic, None) + + val masterKey = ExtendedSecretKey.deriveMasterKey(seed, usePre1627KeyDerivation = false) + val dp = DerivationPath.nextPath(IndexedSeq(masterKey), usePreEip3Derivation = true).get + val sk = masterKey.derive(dp) + val pk = sk.publicKey.key + P2PKAddress(pk).toString() shouldBe address + + // Derivation procedure (post-EIP3) should get m/2 after m/1 (like pre-EIP3) + val dp2 = DerivationPath.nextPath(IndexedSeq(masterKey, sk), usePreEip3Derivation = false).get + dp2 shouldBe DerivationPath.fromEncoded("m/2").get + + val sk2 = masterKey.derive(DerivationPath.fromEncoded("m/1/1").get) + + // Derivation procedure (post-EIP3) should get m/2 after m/1/1 (like pre-EIP3) + val dp3 = DerivationPath.nextPath(IndexedSeq(masterKey, sk, sk2), usePreEip3Derivation = false).get + dp3 shouldBe DerivationPath.fromEncoded("m/2").get + + + // Once a user adds EIP-3 path (e.g. m/44'/429'/0'/0/0), post-EIP3 derivation applied + val sk3 = masterKey.derive(DerivationPath.fromEncoded("m/44'/429'/0'/0/0").get) + val dp4 = DerivationPath.nextPath(IndexedSeq(masterKey, sk, sk3), usePreEip3Derivation = false).get + dp4 shouldBe DerivationPath.fromEncoded("m/44'/429'/0'/0/1").get + } + + property("equality of old derivation") { + // Check that hardcoded path from old codebase corresponds to the new string form (Constants.usePreEip3Derivation) + DerivationPath(Array(0, 1), publicBranch = false) shouldBe Constants.preEip3DerivationPath + } + + property("master key derivation") { + implicit val enc = new ErgoAddressEncoder(ErgoAddressEncoder.MainnetNetworkPrefix) + val masterKeyDerivation: DerivationPath = DerivationPath.fromEncoded("m/").get + + val mnemonic = "liar exercise solve delay betray sheriff method empower disease river recall vacuum" + val address = "9hXkYAHd1hWDroNMA3w9t6st2QyS3aTVe5w6GwWPKK5q4SmpUDL" + + val seed = JavaHelpers.mnemonicToSeed(mnemonic, None) + + val masterKey = ExtendedSecretKey.deriveMasterKey(seed, usePre1627KeyDerivation = false) + P2PKAddress(masterKey.publicKey.key).toString() shouldBe address + + masterKey.path shouldBe masterKeyDerivation + + masterKeyDerivation.isMaster shouldBe true + + val sk = masterKey.derive(masterKeyDerivation) + val pk = sk.publicKey.key + sk.path.isMaster shouldBe true + P2PKAddress(pk).toString() shouldBe address + } + + property("isEip3 correctly distinguishing") { + Constants.eip3DerivationPath.isEip3 shouldBe true + Constants.eip3DerivationPath.toPublicBranch.isEip3 shouldBe true + DerivationPath.fromEncoded("m/44'/429'/0'/0/1").get.isEip3 shouldBe true + DerivationPath.fromEncoded("M/44'/429'/0'/0/1").get.isEip3 shouldBe true + DerivationPath.fromEncoded("m/44'/429'/0'/1/1").get.isEip3 shouldBe true + Constants.preEip3DerivationPath.isEip3 shouldBe false + DerivationPath.fromEncoded("m/44'/429'/1'/0/1").get.isEip3 shouldBe false + } + +} diff --git a/sdk/shared/src/test/scala/org/ergoplatform/sdk/wallet/utils/Generators.scala b/sdk/shared/src/test/scala/org/ergoplatform/sdk/wallet/utils/Generators.scala new file mode 100644 index 0000000000..e0672d4a3d --- /dev/null +++ b/sdk/shared/src/test/scala/org/ergoplatform/sdk/wallet/utils/Generators.scala @@ -0,0 +1,177 @@ +package org.ergoplatform.sdk.wallet.utils + +import org.ergoplatform.ErgoBox.{BoxId, NonMandatoryRegisterId, Token} +import org.ergoplatform.sdk.wallet.Constants +import org.ergoplatform.sdk.wallet.secrets.ExtendedPublicKey +import org.ergoplatform.sdk.wallet.secrets.{DerivationPath, ExtendedSecretKey, Index, SecretKey} +import org.ergoplatform.sdk.wallet.settings.EncryptionSettings +import org.scalacheck.Arbitrary.arbByte +import org.scalacheck.{Arbitrary, Gen} +import scorex.crypto.authds.ADKey +import sigmastate.Values.{ByteArrayConstant, CollectionConstant, ErgoTree, EvaluatedValue, FalseLeaf, TrueLeaf} +import sigmastate.basics.DLogProtocol.ProveDlog +import sigmastate.{SByte, SType} +import scorex.util._ +import org.ergoplatform.{ErgoBox, ErgoBoxCandidate, ErgoTreePredef, UnsignedErgoLikeTransaction, UnsignedInput} +import sigmastate.eval.Extensions._ +import scorex.util.{ModifierId, bytesToId} +import sigmastate.eval._ +import sigmastate.helpers.TestingHelpers._ +import scorex.crypto.hash.Digest32 +import sigmastate.crypto.CryptoFacade + +trait Generators { + + val MinValuePerByteIncreaseTest: Byte = 2 + val CoinsTotalTest = 9500000000000L + + val passwordGen: Gen[String] = Gen.nonEmptyListOf(Gen.alphaNumChar).map(_.toString) + val dataGen: Gen[Array[Byte]] = Gen.nonEmptyListOf(Gen.posNum[Byte]).map(_.toArray) + + val encryptionSettingsGen: Gen[EncryptionSettings] = for { + prf <- Gen.oneOf(Seq("HmacSHA1", "HmacSHA256", "HmacSHA512")) + c <- Gen.posNum[Int] + } yield EncryptionSettings(prf, c, 256) + + val entropyGen: Gen[Array[Byte]] = Gen.oneOf(Constants.AllowedEntropyLengths).map(scorex.utils.Random.randomBytes) + + val derivationPathGen: Gen[DerivationPath] = for { + isPublic <- Gen.oneOf(Seq(true, false)) + indices <- Gen.listOf(Gen.oneOf(Seq(true, false)) + .flatMap(x => Gen.posNum[Int].map(i => if (x) Index.hardIndex(i) else i))) + } yield DerivationPath(0 +: indices, isPublic) + + val heightGen: Gen[Int] = Gen.choose(0, Int.MaxValue / 2) + + val boxIndexGen: Gen[Short] = for { + v <- Gen.chooseNum(0, Short.MaxValue) + } yield v.toShort + + + def genLimitedSizedBytes(minSize: Int, maxSize: Int): Gen[Array[Byte]] = { + Gen.choose(minSize, maxSize) flatMap { sz => Gen.listOfN(sz, Arbitrary.arbitrary[Byte]).map(_.toArray) } + } + + def genExactSizeBytes(size: Int): Gen[Array[Byte]] = genLimitedSizedBytes(size, size) + + val boxIdGen: Gen[BoxId] = { + val x = ADKey @@ genExactSizeBytes(Constants.ModifierIdLength) + x + } + + val modIdGen: Gen[ModifierId] = genExactSizeBytes(Constants.ModifierIdLength).map(bytesToId) + + val assetGen: Gen[Token] = for { + id <- boxIdGen + amt <- Gen.oneOf(1, 500, 20000, 10000000, Long.MaxValue) + } yield Digest32Coll @@@ id.toColl -> amt + + def additionalTokensGen(cnt: Int): Gen[Seq[Token]] = Gen.listOfN(cnt, assetGen) + + def additionalTokensGen: Gen[Seq[Token]] = for { + cnt <- Gen.chooseNum[Int](0, 10) + assets <- additionalTokensGen(cnt) + } yield assets + + val byteArrayConstGen: Gen[CollectionConstant[SByte.type]] = for { + length <- Gen.chooseNum(1, 100) + bytes <- Gen.listOfN(length, arbByte.arbitrary) + } yield ByteArrayConstant(bytes.toArray) + + def evaluatedValueGen: Gen[EvaluatedValue[SType]] = for { + arr <- byteArrayConstGen + v <- Gen.oneOf(TrueLeaf, FalseLeaf, arr) + } yield v.asInstanceOf[EvaluatedValue[SType]] + + def additionalRegistersGen(cnt: Byte): Gen[Map[NonMandatoryRegisterId, EvaluatedValue[SType]]] = { + Gen.listOfN(cnt, evaluatedValueGen) map { values => + ErgoBox.nonMandatoryRegisters.take(cnt).zip(values).toMap + } + } + + def additionalRegistersGen: Gen[Map[NonMandatoryRegisterId, EvaluatedValue[SType]]] = for { + cnt <- Gen.choose(0: Byte, ErgoBox.nonMandatoryRegistersCount) + registers <- additionalRegistersGen(cnt) + } yield registers + + def validValueGen: Gen[Long] = { + //there are outputs in tests of 183 bytes, and maybe in some tests at least 2 outputs are required + //thus we put in an input a monetary value which is at least enough for storing 400 bytes of outputs + val minValue = MinValuePerByteIncreaseTest * 400 + Gen.choose(minValue, CoinsTotalTest / 1000) + } + + def ergoBoxGen(propGen: Gen[ErgoTree] = Gen.const(TrueLeaf.toSigmaProp), + tokensGen: Gen[Seq[Token]] = additionalTokensGen, + valueGenOpt: Option[Gen[Long]] = None, + heightGen: Gen[Int] = heightGen): Gen[ErgoBox] = for { + h <- heightGen + prop <- propGen + transactionId: Array[Byte] <- genExactSizeBytes(Constants.ModifierIdLength) + boxId: Short <- boxIndexGen + ar <- additionalRegistersGen + tokens <- tokensGen + value <- valueGenOpt.getOrElse(validValueGen) + } yield { + val box = testBox(value, prop, h, tokens, ar, transactionId.toModifierId, boxId) + if (box.bytes.length < ErgoBox.MaxBoxSize) { + box + } else { + // is size limit is reached, generate box without registers and tokens + testBox(value, prop, h, Seq(), Map(), transactionId.toModifierId, boxId) + } + } + + val ergoBoxGen: Gen[ErgoBox] = ergoBoxGen() + + def derivationPathGen(isPublic: Boolean, allowHardened: Boolean): Gen[DerivationPath] = for { + indices <- Gen.listOf(Gen.oneOf(Seq(true, false)) + .flatMap(x => Gen.posNum[Int].map(i => if (x && allowHardened) Index.hardIndex(i) else i))) + } yield DerivationPath(0 +: indices, isPublic) + + def extendedSecretGen: Gen[ExtendedSecretKey] = for { + seed <- Gen.const(CryptoFacade.SecretKeyLength).map(scorex.utils.Random.randomBytes) + } yield ExtendedSecretKey.deriveMasterKey(seed, usePre1627KeyDerivation = false) + + def extendedPubKeyGen: Gen[ExtendedPublicKey] = extendedSecretGen.map(_.publicKey) + + def extendedPubKeyListGen: Gen[Seq[ExtendedPublicKey]] = extendedSecretGen.flatMap { sk => + Gen.choose(1, 100).map { cnt => + (1 to cnt).foldLeft(IndexedSeq(sk)) { case (keys, _) => + val dp = DerivationPath.nextPath(keys, usePreEip3Derivation = false).get + val newSk = sk.derive(dp) + keys :+ newSk + }.map(_.publicKey) + } + } + + + + def unsignedTxGen(secret: SecretKey): Gen[(IndexedSeq[ErgoBox], UnsignedErgoLikeTransaction)] = { + val dlog: Gen[ErgoTree] = Gen.const(secret.privateInput.publicImage.asInstanceOf[ProveDlog].toSigmaProp) + + for { + ins <- Gen.listOfN(2, ergoBoxGen(dlog)) + value <- Gen.posNum[Long] + h <- Gen.posNum[Int] + out = new ErgoBoxCandidate( + value, + ErgoTreePredef.feeProposition(), + h, + Seq.empty[(ErgoBox.TokenId, Long)].toColl, + Map.empty + ) + unsignedInputs = ins + .map { box => + new UnsignedInput(box.id) + } + .toIndexedSeq + unsignedTx = new UnsignedErgoLikeTransaction( + unsignedInputs, + IndexedSeq(), + IndexedSeq(out) + ) + } yield (ins.toIndexedSeq, unsignedTx) + } + +} diff --git a/sigma-js/README.md b/sigma-js/README.md new file mode 100644 index 0000000000..8e598ffe51 --- /dev/null +++ b/sigma-js/README.md @@ -0,0 +1,23 @@ +[![CI](https://github.com/ScorexFoundation/sigmastate-interpreter/actions/workflows/ci.yml/badge.svg)](https://github.com/ScorexFoundation/sigmastate-interpreter/actions/workflows/ci.yml) +[![codecov](https://codecov.io/gh/ScorexFoundation/sigmastate-interpreter/branch/develop/graph/badge.svg?token=HNu2ZEOoV6)](https://codecov.io/gh/ScorexFoundation/sigmastate-interpreter) + +# ErgoScript compiler and ErgoTree interpreter + +This repository contains implementations of ErgoScript compiler and ErgoTree +Interpreter for a family of Sigma-protocol based authentication languages (or simply +Sigma language). + +This JS package is cross-compiled from [Scala +implementation](https://github.com/ScorexFoundation/sigmastate-interpreter) using Scala.js +compiler. + +The modules published here can be used directly from JavaScript. + +# Getting Started + +TODO + +# Examples + +TODO + diff --git a/sigma-js/package.json b/sigma-js/package.json new file mode 100644 index 0000000000..530117a8ba --- /dev/null +++ b/sigma-js/package.json @@ -0,0 +1,43 @@ +{ + "name": "sigmastate-js", + "version": "0.0.9", + "description": "Sigma.js library", + "main": "dist/main.js", + "files": [ + "dist/", + "README.md" + ], + "exports": { + "./internal-*": null, + "./*": "./dist/*.js" + }, + "license": "MIT", + "publishConfig": { + "access": "public" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/ScorexFoundation/sigmastate-interpreter.git" + }, + "author": "aslesarenko", + "bugs": { + "url": "https://github.com/ScorexFoundation/sigmastate-interpreter/issues" + }, + "homepage": "https://github.com/ScorexFoundation/sigmastate-interpreter/blob/v5.x-scala-js/sigma-js/README.md", + "scripts": { + "test": "node_modules/.bin/jest", + "clean": "shx rm -rf ./dist/*", + "copy-output": "shx mkdir -p ./dist/ && cp -r ../sdk/js/target/scala-2.12/sdk-fastopt/* ./dist/", + "prepublishOnly": "npm run clean && npm run copy-output" + }, + "dependencies": { + "@fleet-sdk/common": "0.1.0-alpha.14", + "bouncycastle-js": "0.1.8", + "sigmajs-crypto-facade": "0.0.3" + }, + "devDependencies": { + "jest": "^29.0.3", + "shx": "^0.3.4", + "typescript": "^4.9.4" + } +} diff --git a/sigma-js/tests/js/bindings.spec.js b/sigma-js/tests/js/bindings.spec.js new file mode 100644 index 0000000000..b2ea24fee0 --- /dev/null +++ b/sigma-js/tests/js/bindings.spec.js @@ -0,0 +1,105 @@ +const { + Types, Values, ErgoTree, ErgoTrees +} = require("../../../sdk/js/target/scala-2.12/sdk-fastopt/main.js"); + +describe("Smoke tests for API exporting", () => { + it("Should export ErgoTree object", () => { + expect(ErgoTree).not.toBeUndefined(); + }); +}); + +describe("Smoke tests for ErgoTree", () => { + it("Should create fromHex", () => { + var hex = "100604000e000400040005000500d803d601e30004d602e4c6a70408d603e4c6a7050595e67201d804d604b2a5e4720100d605b2db63087204730000d606db6308a7d60799c1a7c17204d1968302019683050193c27204c2a7938c720501730193e4c672040408720293e4c672040505720393e4c67204060ec5a796830201929c998c7205029591b1720673028cb272067303000273047203720792720773057202"; + var tree = ErgoTrees.fromHex(hex) + expect(tree.toString()).not.toBeUndefined(); + expect(tree.toHex()).toEqual(hex) + }); +}); + +describe("Smoke tests for Types", () => { + it("Should create primitive types", () => { + expect(Types.Byte.name).toEqual("Byte"); + expect(Types.Short.name).toEqual("Short"); + expect(Types.Int.name).toEqual("Int"); + expect(Types.Long.name).toEqual("Long"); + }); + it("Should create complex types", () => { + expect(Types.pairType(Types.Int, Types.Long).name).toEqual("(Int, Long)"); + expect(Types.collType(Types.Int).name).toEqual("Coll[Int]"); + expect(Types.collType(Types.pairType(Types.Int, Types.Long)).name) + .toEqual("Coll[(Int, Long)]"); + }); +}); + +function testRange(factory, min, max) { + expect(factory(max).data).toEqual(max); + expect(() => factory(max + 1).data).toThrow(); + expect(factory(-1).data).toEqual(-1); + expect(factory(min).data).toEqual(min); + expect(() => factory(min - 1).data).toThrow(); +} + +describe("Smoke tests for Values", () => { + it("Should create values of primitive types", () => { + expect(Values.ofByte(0).data).toEqual(0); + expect(Values.ofByte(0).tpe).toEqual(Types.Byte); + testRange(function(v) { return Values.ofByte(v); }, -128, 127); + testRange(function(v) { return Values.ofShort(v); }, -32768, 32767); + testRange(function(v) { return Values.ofInt(v); }, -0x7FFFFFFF - 1, 0x7FFFFFFF); + testRange(function(v) { return Values.ofLong(v); }, -0x8000000000000000n, 0x7fffffffffffffffn); + }); + + it("Should create values of complex types", () => { + let pair = Values.pairOf(Values.ofByte(10), Values.ofLong(20n)); + expect(pair.data).toEqual([10, 20n]); + expect(pair.tpe.name).toEqual("(Byte, Long)"); + + let coll = Values.collOf([-10, 0, 10], Types.Byte) + expect(coll.tpe.name).toEqual("Coll[Byte]"); + }); + + let longHex = "05e012"; + let collHex = "1a0203010203020a14"; + let pairHex = "3e050a28" + + it("Long Value.toHex", () => { + let v = Values.ofLong(1200n) + expect(v.toHex()).toEqual(longHex) + }); + + it("Coll Value.toHex", () => { + let arr = [ [1, 2, 3], [10, 20] ] + let t = Types.collType(Types.Byte) + let collV = Values.collOf(arr, t) + + expect(collV.tpe.name).toEqual("Coll[Coll[Byte]]"); + expect(collV.toHex()).toEqual(collHex) + }); + + it("Pair Value.toHex", () => { + let fst = Values.ofByte(10) + let snd = Values.ofLong(20) + let pair = Values.pairOf(fst, snd) + expect(pair.tpe.name).toEqual("(Byte, Long)"); + expect(pair.toHex()).toEqual(pairHex) + }); + + it("Long Value.fromHex", () => { + let v = Values.fromHex(longHex) + expect(v.data).toEqual(1200n) + expect(v.tpe.name).toEqual("Long") + }); + + it("Coll Value.fromHex", () => { + let coll = Values.fromHex(collHex) + expect(coll.tpe.name).toEqual("Coll[Coll[Byte]]"); + expect(coll.toHex()).toEqual(collHex) + }); + + it("Pair Value.fromHex", () => { + let p = Values.fromHex(pairHex) + expect(p.tpe.name).toEqual("(Byte, Long)"); + expect(p.toHex()).toEqual(pairHex) + }); +});