diff --git a/build.sbt b/build.sbt index d0156af6e4..0cad69bd90 100644 --- a/build.sbt +++ b/build.sbt @@ -78,8 +78,8 @@ version in ThisBuild := { git.gitUncommittedChanges in ThisBuild := true val bouncycastleBcprov = "org.bouncycastle" % "bcprov-jdk15on" % "1.64" -val scrypto = "org.scorexfoundation" %% "scrypto" % "2.1.9" -val scorexUtil = "org.scorexfoundation" %% "scorex-util" % "0.1.6" +val scrypto = "org.scorexfoundation" %% "scrypto" % "2.1.10" +val scorexUtil = "org.scorexfoundation" %% "scorex-util" % "0.1.8" val macroCompat = "org.typelevel" %% "macro-compat" % "1.1.1" val paradise = "org.scalamacros" %% "paradise" % "2.1.0" cross CrossVersion.full val debox = "org.spire-math" %% "debox" % "0.8.0" @@ -118,12 +118,19 @@ libraryDependencies ++= Seq( kiama, fastparse, debox ) ++ testingDependencies -val circeVersion = "0.10.0" -val circeCore = "io.circe" %% "circe-core" % circeVersion -val circeGeneric = "io.circe" %% "circe-generic" % circeVersion -val circeParser = "io.circe" %% "circe-parser" % circeVersion +lazy val circeCore211 = "io.circe" %% "circe-core" % "0.10.0" +lazy val circeGeneric211 = "io.circe" %% "circe-generic" % "0.10.0" +lazy val circeParser211 = "io.circe" %% "circe-parser" % "0.10.0" -libraryDependencies ++= Seq( circeCore, circeGeneric, circeParser ) +lazy val circeCore = "io.circe" %% "circe-core" % "0.13.0" +lazy val circeGeneric = "io.circe" %% "circe-generic" % "0.13.0" +lazy val circeParser = "io.circe" %% "circe-parser" % "0.13.0" + +libraryDependencies ++= Seq( + if (scalaVersion.value == scala211) circeCore211 else circeCore, + if (scalaVersion.value == scala211) circeGeneric211 else circeGeneric, + if (scalaVersion.value == scala211) circeParser211 else circeParser + ) scalacOptions ++= Seq("-feature", "-deprecation") @@ -274,7 +281,11 @@ lazy val sigmastate = (project in file("sigmastate")) .dependsOn(sigmaimpl % allConfigDependency, sigmalibrary % allConfigDependency) .settings(libraryDefSettings) .settings(libraryDependencies ++= Seq( - scorexUtil, kiama, fastparse, circeCore, circeGeneric, circeParser)) + scorexUtil, kiama, fastparse, + if (scalaVersion.value == scala211) circeCore211 else circeCore, + if (scalaVersion.value == scala211) circeGeneric211 else circeGeneric, + if (scalaVersion.value == scala211) circeParser211 else circeParser + )) .settings(publish / skip := true) lazy val sigma = (project in file(".")) diff --git a/common/src/main/scala/scalan/util/Extensions.scala b/common/src/main/scala/scalan/util/Extensions.scala index 072a9226bb..40852127fe 100644 --- a/common/src/main/scala/scalan/util/Extensions.scala +++ b/common/src/main/scala/scalan/util/Extensions.scala @@ -195,6 +195,8 @@ object Extensions { res } def getBytes(size: Int): Array[Byte] = { + if (size > buf.remaining) + throw new IllegalArgumentException(s"Not enough bytes in the ByteBuffer: $size") val res = new Array[Byte](size) buf.get(res) res diff --git a/library-impl/src/main/scala/special/collection/CollsOverArrays.scala b/library-impl/src/main/scala/special/collection/CollsOverArrays.scala index dce17ef130..ad78776d4f 100644 --- a/library-impl/src/main/scala/special/collection/CollsOverArrays.scala +++ b/library-impl/src/main/scala/special/collection/CollsOverArrays.scala @@ -211,7 +211,7 @@ class CollOverArrayBuilder extends CollBuilder { override def Monoids: MonoidBuilder = new MonoidBuilderInst @inline override def pairColl[@specialized A, @specialized B](as: Coll[A], bs: Coll[B]): PairColl[A, B] = { - // TODO HF: use minimal length and slice longer collection + // TODO HF (2h): use minimal length and slice longer collection // The current implementation doesn't check the case when `as` and `bs` have different lengths. // in which case the implementation of `PairOfCols` has inconsistent semantics of `map`, `exists` etc methods. // To fix the problem, the longer collection have to be truncated (which is consistent diff --git a/library/src/test/scala/special/collections/BasicBenchmarks.scala b/library/src/test/scala/special/collections/BasicBenchmarks.scala index a3db44d76a..ccef90488f 100644 --- a/library/src/test/scala/special/collections/BasicBenchmarks.scala +++ b/library/src/test/scala/special/collections/BasicBenchmarks.scala @@ -4,6 +4,22 @@ import org.scalameter.api._ import spire.syntax.all.cfor trait BasicBenchmarkCases extends BenchmarkGens { suite: Bench[Double] => + + performance of "allocation" in { + measure method "new" in { + using(sizes) in { case n => + val arr = new Array[Integer](n) + cfor(0)(_ < n, _ + 1) { i => + arr(i) = Integer.valueOf(i) + } + var s = 0 + cfor(0)(_ < n, _ + 1) { i => + s += arr(i).intValue() + } + } + } + } + performance of "Seq" in { var res: Seq[Int] = null measure method "Nil" in { diff --git a/library/src/test/scala/special/collections/BenchmarkGens.scala b/library/src/test/scala/special/collections/BenchmarkGens.scala index 7207b07b59..7047207a55 100644 --- a/library/src/test/scala/special/collections/BenchmarkGens.scala +++ b/library/src/test/scala/special/collections/BenchmarkGens.scala @@ -4,7 +4,7 @@ import org.scalameter.KeyValue import org.scalameter.api.{Gen, Bench, _} trait BenchmarkGens extends CollGens { suite: Bench[Double] => - val maxSize = 100000 + def maxSize = 100000 val sizes = Gen.exponential("size")(10, maxSize, 10) diff --git a/sigma-impl/src/main/scala/special/sigma/SigmaDslOverArrays.scala b/sigma-impl/src/main/scala/special/sigma/SigmaDslOverArrays.scala index b1bf83ca6d..abd72e5236 100644 --- a/sigma-impl/src/main/scala/special/sigma/SigmaDslOverArrays.scala +++ b/sigma-impl/src/main/scala/special/sigma/SigmaDslOverArrays.scala @@ -40,7 +40,7 @@ class TestSigmaDslBuilder extends SigmaDslBuilder { @NeverInline override def xorOf(conditions: Coll[Boolean]): Boolean = { - // TODO HF: see https://github.com/ScorexFoundation/sigmastate-interpreter/issues/640 + // TODO HF (2h): see https://github.com/ScorexFoundation/sigmastate-interpreter/issues/640 conditions.toArray.distinct.length == 2 } diff --git a/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeInterpreter.scala b/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeInterpreter.scala index aa060e892e..01efc82022 100644 --- a/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeInterpreter.scala +++ b/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeInterpreter.scala @@ -24,11 +24,11 @@ class ErgoLikeInterpreter(implicit val IR: IRContext) extends Interpreter { else Some(outVal) case _ => - // TODO HF: this case is not possible because `ErgoBox.get` + // TODO HF (1h): this case is not possible because `ErgoBox.get` // returns lookups values from `additionalRegisters` Map with values // of type EvaluatedValue, which are always Constant nodes in practice. // Also, this branch is never executed so can be safely removed - // (better as part of as part of HF) + // (better as part of the HF) None } }.orElse(d.default) diff --git a/sigmastate/src/main/scala/org/ergoplatform/validation/ValidationRules.scala b/sigmastate/src/main/scala/org/ergoplatform/validation/ValidationRules.scala index 33f17b3add..478e1983b4 100644 --- a/sigmastate/src/main/scala/org/ergoplatform/validation/ValidationRules.scala +++ b/sigmastate/src/main/scala/org/ergoplatform/validation/ValidationRules.scala @@ -353,6 +353,18 @@ object ValidationRules { map }) + /** Executes the given `block` catching [[ValidationException]] and checking possible + * soft-fork condition in the context of the given [[SigmaValidationSettings]]. + * If soft-fork condition is recognized the `whenSoftFork` is executed and its result + * is returned. + * + * @param whenSoftFork executed when soft-fork condition is detected + * @param block block of code, which may throw [[ValidationException]] + * @param vs set of [[SigmaValidationSettings]] which can be used to recognize soft-fork conditions. + * @return result of `block` if no ValidationException was thrown, or the result of + * `whenSoftFork` if soft-fork condition is detected. + * @throws ValidationException if soft-fork condition is not recognized by the given `vs` + */ def trySoftForkable[T](whenSoftFork: => T)(block: => T)(implicit vs: SigmaValidationSettings): T = { try block catch { diff --git a/sigmastate/src/main/scala/sigmastate/UnprovenTree.scala b/sigmastate/src/main/scala/sigmastate/UnprovenTree.scala index d5651f66f1..0cf1887ca1 100644 --- a/sigmastate/src/main/scala/sigmastate/UnprovenTree.scala +++ b/sigmastate/src/main/scala/sigmastate/UnprovenTree.scala @@ -207,7 +207,7 @@ case class UnprovenDiffieHellmanTuple(override val proposition: ProveDHTuple, * and should not contain challenges, responses, or the real/simulated flag for any node. * */ -// TODO coverage: write a test that restores the tree from this string and check that the result is equal, +// TODO coverage (8h): write a test that restores the tree from this string and check that the result is equal, // in order to make sure this conversion is unambiguous object FiatShamirTree { val internalNodePrefix = 0: Byte diff --git a/sigmastate/src/main/scala/sigmastate/Values.scala b/sigmastate/src/main/scala/sigmastate/Values.scala index b2a0409761..09a5974289 100644 --- a/sigmastate/src/main/scala/sigmastate/Values.scala +++ b/sigmastate/src/main/scala/sigmastate/Values.scala @@ -660,7 +660,7 @@ object Values { trait OptionValue[T <: SType] extends Value[SOption[T]] { } - // TODO HF: SomeValue and NoneValue are not used in ErgoTree and can be + // TODO HF (4h): SomeValue and NoneValue are not used in ErgoTree and can be // either removed or implemented in v4.x case class SomeValue[T <: SType](x: Value[T]) extends OptionValue[T] { override def companion = SomeValue diff --git a/sigmastate/src/main/scala/sigmastate/eval/CostingDataContext.scala b/sigmastate/src/main/scala/sigmastate/eval/CostingDataContext.scala index 9163367e22..2ff25a714c 100644 --- a/sigmastate/src/main/scala/sigmastate/eval/CostingDataContext.scala +++ b/sigmastate/src/main/scala/sigmastate/eval/CostingDataContext.scala @@ -242,7 +242,7 @@ class EvalSizeBox( val foundSize = varSize.asInstanceOf[SizeOption[AnyValue]].sizeOpt val regSize = foundSize match { case Some(varSize: SizeAnyValue) => - assert(varSize.tVal == tT, s"Unexpected register type found at register #$id: ${varSize.tVal}, expected $tT") + require(varSize.tVal == tT, s"Unexpected register type found at register #$id: ${varSize.tVal}, expected $tT") val regSize = varSize.valueSize.asInstanceOf[Size[T]] regSize case _ => diff --git a/sigmastate/src/main/scala/sigmastate/eval/Evaluation.scala b/sigmastate/src/main/scala/sigmastate/eval/Evaluation.scala index 0ce67b66ff..1241571810 100644 --- a/sigmastate/src/main/scala/sigmastate/eval/Evaluation.scala +++ b/sigmastate/src/main/scala/sigmastate/eval/Evaluation.scala @@ -472,9 +472,6 @@ trait Evaluation extends RuntimeCosting { IR: IRContext => val loopCost = if (_loopStack.isEmpty) 0 else _loopStack.head.accumulatedCost val accumulatedCost = java.lang.Math.addExact(cost, loopCost) if (accumulatedCost > limit) { -// if (cost < limit) -// println(s"FAIL FAST in loop: $accumulatedCost > $limit") - // TODO cover with tests throw new CostLimitException(accumulatedCost, Evaluation.msgCostLimitError(accumulatedCost, limit), None) } } diff --git a/sigmastate/src/main/scala/sigmastate/eval/IRContext.scala b/sigmastate/src/main/scala/sigmastate/eval/IRContext.scala index 9d24faec88..3f5a6e41e5 100644 --- a/sigmastate/src/main/scala/sigmastate/eval/IRContext.scala +++ b/sigmastate/src/main/scala/sigmastate/eval/IRContext.scala @@ -92,7 +92,6 @@ trait IRContext extends Evaluation with TreeBuilding { val costFun = compile[SSize[SContext], Int, Size[Context], Int](getDataEnv, costF, Some(maxCost)) val (_, estimatedCost) = costFun(Sized.sizeOf(ctx)) if (estimatedCost > maxCost) { - // TODO cover with tests throw new CostLimitException(estimatedCost, s"Estimated execution cost $estimatedCost exceeds the limit $maxCost in $exp") } estimatedCost @@ -130,7 +129,6 @@ trait IRContext extends Evaluation with TreeBuilding { val scaledCost = JMath.multiplyExact(estimatedCost.toLong, CostTable.costFactorIncrease.toLong) / CostTable.costFactorDecrease val totalCost = JMath.addExact(initCost, scaledCost) if (totalCost > maxCost) { - // TODO cover with tests throw new CostLimitException(totalCost, Evaluation.msgCostLimitError(totalCost, maxCost), None) } totalCost.toInt diff --git a/sigmastate/src/main/scala/sigmastate/eval/RuntimeCosting.scala b/sigmastate/src/main/scala/sigmastate/eval/RuntimeCosting.scala index 02124bac5d..ffbb87e8fa 100644 --- a/sigmastate/src/main/scala/sigmastate/eval/RuntimeCosting.scala +++ b/sigmastate/src/main/scala/sigmastate/eval/RuntimeCosting.scala @@ -989,11 +989,11 @@ trait RuntimeCosting extends CostingRules { IR: IRContext => implicit val tA = ct.tItem implicit val sizedA = Sized.typeToSized(tA) liftConst(Sized.sizeOf(x.asInstanceOf[special.collection.Coll[a]])) - case ct: OptionType[a] => // TODO cover with tests + case ct: OptionType[a] => // TODO cover with tests (1h) implicit val tA = ct.tA implicit val sizedA = Sized.typeToSized(tA) liftConst(Sized.sizeOf(x.asInstanceOf[Option[a]])) - case ct: PairType[a, b] => // TODO cover with tests + case ct: PairType[a, b] => // TODO cover with tests (1h) implicit val tA = ct.tFst implicit val tB = ct.tSnd implicit val sizedA = Sized.typeToSized(tA) diff --git a/sigmastate/src/main/scala/sigmastate/eval/Zero.scala b/sigmastate/src/main/scala/sigmastate/eval/Zero.scala index a2ca688d23..2fade5bd0a 100644 --- a/sigmastate/src/main/scala/sigmastate/eval/Zero.scala +++ b/sigmastate/src/main/scala/sigmastate/eval/Zero.scala @@ -54,8 +54,8 @@ object Zero extends ZeroLowPriority { case AvlTreeRType => Zero[AvlTree] case SigmaPropRType => sigmaPropIsZero case ct: CollType[a] => collIsZero(typeToZero(ct.tItem), ct.tItem) - case ct: OptionType[a] => optionIsZero(typeToZero(ct.tA)) // TODO cover with tests - case ct: PairType[a, b] => pairIsZero(typeToZero(ct.tFst), typeToZero(ct.tSnd)) // TODO cover with tests + case ct: OptionType[a] => optionIsZero(typeToZero(ct.tA)) // TODO cover with tests (2h) + case ct: PairType[a, b] => pairIsZero(typeToZero(ct.tFst), typeToZero(ct.tSnd)) // TODO cover with tests (1h) case _ => sys.error(s"Don't know how to compute Zero for type $t") }).asInstanceOf[Zero[T]] diff --git a/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala b/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala index 619505720a..58bb2a2dd6 100644 --- a/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala +++ b/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala @@ -52,9 +52,9 @@ trait Interpreter extends ScorexLogging { val currCost = JMath.addExact(context.initCost, scriptComplexity) val remainingLimit = context.costLimit - currCost - if (remainingLimit <= 0) - throw new CostLimitException(currCost, Evaluation.msgCostLimitError(currCost, context.costLimit), None) // TODO cover with tests - + if (remainingLimit <= 0) { + throw new CostLimitException(currCost, Evaluation.msgCostLimitError(currCost, context.costLimit), None) + } val ctx1 = context.withInitCost(currCost).asInstanceOf[CTX] (ctx1, script) } @@ -71,17 +71,20 @@ trait Interpreter extends ScorexLogging { CheckDeserializedScriptType(d, script) Some(script) - case _ => None // TODO cover with tests + case _ => + None } else - None // TODO cover with tests + None case _ => None } def toValidScriptType(exp: SValue): BoolValue = exp match { case v: Value[SBoolean.type]@unchecked if v.tpe == SBoolean => v case p: SValue if p.tpe == SSigmaProp => p.asSigmaProp.isProven - case x => // TODO cover with tests + case x => + // This case is not possible, due to exp is always of Boolean/SigmaProp type. + // In case it will ever change, leave it here to throw an explaining message. throw new Error(s"Context-dependent pre-processing should produce tree of type Boolean or SigmaProp but was $x") } @@ -96,7 +99,7 @@ trait Interpreter extends ScorexLogging { ergoTree.toProposition(ergoTree.isConstantSegregation) case Left(UnparsedErgoTree(_, error)) if validationSettings.isSoftFork(error) => TrueSigmaProp - case Left(UnparsedErgoTree(_, error)) => // TODO cover with tests + 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)) } @@ -231,9 +234,10 @@ trait Interpreter extends ScorexLogging { val initCost = JMath.addExact(ergoTree.complexity.toLong, context.initCost) val remainingLimit = context.costLimit - initCost - if (remainingLimit <= 0) - throw new CostLimitException(initCost, Evaluation.msgCostLimitError(initCost, context.costLimit), None) // TODO cover with tests - + if (remainingLimit <= 0) { + // TODO cover with tests (2h) + throw new CostLimitException(initCost, Evaluation.msgCostLimitError(initCost, context.costLimit), None) + } val contextWithCost = context.withInitCost(initCost).asInstanceOf[CTX] val (cProp, cost) = fullReduction(ergoTree, contextWithCost, env) diff --git a/sigmastate/src/main/scala/sigmastate/lang/Terms.scala b/sigmastate/src/main/scala/sigmastate/lang/Terms.scala index 2b4bc61f11..162f92f3f9 100644 --- a/sigmastate/src/main/scala/sigmastate/lang/Terms.scala +++ b/sigmastate/src/main/scala/sigmastate/lang/Terms.scala @@ -108,7 +108,7 @@ object Terms { def apply(name: String): Ident = Ident(name, NoType) } - // TODO HF: move to sigmastate.Values + // TODO refactor: move to sigmastate.Values /** ErgoTree node which represents application of function `func` to the given arguments. * @param func expression which evaluates to a function * @param args arguments of the function application diff --git a/sigmastate/src/main/scala/sigmastate/serialization/ByteBufferSerializer.scala b/sigmastate/src/main/scala/sigmastate/serialization/ByteBufferSerializer.scala deleted file mode 100644 index 7a80c0c9eb..0000000000 --- a/sigmastate/src/main/scala/sigmastate/serialization/ByteBufferSerializer.scala +++ /dev/null @@ -1,9 +0,0 @@ -package sigmastate.serialization - -import sigmastate.utils.{SigmaByteWriter, SigmaByteReader} - -/** Interface of serializers which use ByteWriter to serialize and ByteReader to deserialize. */ -trait ByteBufferSerializer[T] { - def serialize(value: T, w: SigmaByteWriter): Unit - def deserialize(r: SigmaByteReader): T -} diff --git a/sigmastate/src/main/scala/sigmastate/serialization/ConcreteCollectionBooleanConstantSerializer.scala b/sigmastate/src/main/scala/sigmastate/serialization/ConcreteCollectionBooleanConstantSerializer.scala index 500056251a..e2d5895e9b 100644 --- a/sigmastate/src/main/scala/sigmastate/serialization/ConcreteCollectionBooleanConstantSerializer.scala +++ b/sigmastate/src/main/scala/sigmastate/serialization/ConcreteCollectionBooleanConstantSerializer.scala @@ -19,8 +19,10 @@ case class ConcreteCollectionBooleanConstantSerializer(cons: (IndexedSeq[Value[S val bits = new Array[Boolean](len) cfor(0)(_ < len, _ + 1) { i => bits(i) = items(i) match { - case v: BooleanConstant => v.value - case v => error(s"Expected collection of BooleanConstant values, got: $v") // TODO cover with tests + case v: BooleanConstant if v.tpe == SBoolean => + v.value + case v => + error(s"Expected collection of BooleanConstant values, got: $v") } } w.putBits(bits, bitsInfo) diff --git a/sigmastate/src/main/scala/sigmastate/serialization/ConstantPlaceholderSerializer.scala b/sigmastate/src/main/scala/sigmastate/serialization/ConstantPlaceholderSerializer.scala index 3f4765a16c..e9c19ca9e5 100644 --- a/sigmastate/src/main/scala/sigmastate/serialization/ConstantPlaceholderSerializer.scala +++ b/sigmastate/src/main/scala/sigmastate/serialization/ConstantPlaceholderSerializer.scala @@ -14,7 +14,7 @@ case class ConstantPlaceholderSerializer(cons: (Int, SType) => Value[SType]) override def parse(r: SigmaByteReader): Value[SType] = { val id = r.getUInt().toInt - val constant = r.constantStore.get(id) // TODO HF move this under if branch + val constant = r.constantStore.get(id) if (r.resolvePlaceholdersToConstants) constant else diff --git a/sigmastate/src/main/scala/sigmastate/serialization/ConstantSerializer.scala b/sigmastate/src/main/scala/sigmastate/serialization/ConstantSerializer.scala index 33fa31cd1f..8e97117467 100644 --- a/sigmastate/src/main/scala/sigmastate/serialization/ConstantSerializer.scala +++ b/sigmastate/src/main/scala/sigmastate/serialization/ConstantSerializer.scala @@ -7,7 +7,7 @@ import sigmastate.utils.{SigmaByteReader, SigmaByteWriter} /** This works in tandem with DataSerializer, if you change one make sure to check the other.*/ case class ConstantSerializer(builder: SigmaBuilder) - extends ByteBufferSerializer[Constant[SType]] with ValueSerializer[Constant[SType]] { + extends ValueSerializer[Constant[SType]] { override def opDesc = Constant override def parse(r: SigmaByteReader): Value[SType] = deserialize(r) @@ -17,7 +17,7 @@ case class ConstantSerializer(builder: SigmaBuilder) DataSerializer.serialize(c.value, c.tpe, w) } - override def deserialize(r: SigmaByteReader): Constant[SType] = { + def deserialize(r: SigmaByteReader): Constant[SType] = { val tpe = r.getType() val obj = DataSerializer.deserialize(tpe, r) builder.mkConstant(obj, tpe) diff --git a/sigmastate/src/main/scala/sigmastate/serialization/DataJsonEncoder.scala b/sigmastate/src/main/scala/sigmastate/serialization/DataJsonEncoder.scala index 9a44051927..07d05ab174 100644 --- a/sigmastate/src/main/scala/sigmastate/serialization/DataJsonEncoder.scala +++ b/sigmastate/src/main/scala/sigmastate/serialization/DataJsonEncoder.scala @@ -46,7 +46,7 @@ object DataJsonEncoder { } private def encodeData[T <: SType](v: T#WrappedType, tpe: T): Json = tpe match { - case SUnit => Json.Null + case SUnit => Json.fromFields(mutable.WrappedArray.empty) case SBoolean => v.asInstanceOf[Boolean].asJson case SByte => v.asInstanceOf[Byte].asJson case SShort => v.asInstanceOf[Short].asJson @@ -62,7 +62,6 @@ object DataJsonEncoder { case tup: STuple => val tArr = tup.items.toArray if (tArr.length != 2) { - // TODO cover with tests throw new SerializerException("Tuples with length not equal to 2 are not supported") } val rtypeArr = tArr.map(x => Evaluation.stypeToRType(x)) @@ -95,7 +94,9 @@ object DataJsonEncoder { case tOpt: SOption[a] => val opt = v.asInstanceOf[tOpt.WrappedType] if (opt.isDefined) { - encodeData(opt.get, tOpt.elemType) + // save the single value as an array with one item + val valueJson = encodeData(opt.get, tOpt.elemType) + Json.fromValues(Array(valueJson)) } else { Json.Null } @@ -152,7 +153,7 @@ object DataJsonEncoder { private def decodeData[T <: SType](json: Json, tpe: T): (T#WrappedType) = { val res = (tpe match { - case SUnit => json.asNull.get + case SUnit => () case SBoolean => json.asBoolean.get case SByte => json.asNumber.get.toByte.get case SShort => json.asNumber.get.toShort.get @@ -169,12 +170,13 @@ object DataJsonEncoder { if (json == Json.Null) { None } else { - Some(decodeData(json, tOpt.elemType)) + // read the array with single value + val items = decodeColl(json, tOpt.elemType) + Some(items(0)) } case t: STuple => val tArr = t.items.toArray if (tArr.length != 2) { - // TODO cover with tests throw new SerializerException("Tuples with length not equal to 2 are not supported") } val collSource = mutable.ArrayBuilder.make[Any]() @@ -225,7 +227,7 @@ object DataJsonEncoder { implicit val tItem = (tpe match { case tTup: STuple if tTup.items.length == 2 => Evaluation.stypeToRType(tpe) - case _: STuple => // TODO cover with tests + case _: STuple => throw new SerializerException("Tuples with length not equal to 2 are not supported") case _ => Evaluation.stypeToRType(tpe) @@ -265,7 +267,6 @@ object DataJsonEncoder { data } - // TODO cover with tests def decodeAnyValue(json: Json): AnyValue = { val tpe = SigmaParser.parseType(json.hcursor.downField("type").focus.get.asString.get) val value = json.hcursor.downField("value").focus.get diff --git a/sigmastate/src/main/scala/sigmastate/serialization/DataSerializer.scala b/sigmastate/src/main/scala/sigmastate/serialization/DataSerializer.scala index de5d47f4c7..00629cef4c 100644 --- a/sigmastate/src/main/scala/sigmastate/serialization/DataSerializer.scala +++ b/sigmastate/src/main/scala/sigmastate/serialization/DataSerializer.scala @@ -69,13 +69,14 @@ object DataSerializer { val len = arr.length assert(arr.length == t.items.length, s"Type $t doesn't correspond to value $arr") if (len > 0xFFFF) - sys.error(s"Length of tuple $arr exceeds ${0xFFFF} limit.") // TODO cover with tests + sys.error(s"Length of tuple ${arr.length} exceeds ${0xFFFF} limit.") var i = 0 while (i < arr.length) { serialize[SType](arr(i), t.items(i), w) i += 1 } + // TODO HF (3h): support Option[T] (see https://github.com/ScorexFoundation/sigmastate-interpreter/issues/659) case _ => sys.error(s"Don't know how to serialize ($v, $tpe)") } @@ -97,9 +98,8 @@ object DataSerializer { new String(bytes, StandardCharsets.UTF_8) case SBigInt => val size: Short = r.getUShort().toShort - // TODO HF: replace with validation rule to enable soft-forkability + // TODO HF (2h): replace with validation rule to enable soft-forkability if (size > SBigInt.MaxSizeInBytes) { - // TODO cover consensus with tests throw new SerializerException(s"BigInt value doesn't not fit into ${SBigInt.MaxSizeInBytes} bytes: $size") } val valueBytes = r.getBytes(size) @@ -114,10 +114,7 @@ object DataSerializer { SigmaDsl.avlTree(AvlTreeData.serializer.parse(r)) case tColl: SCollectionType[a] => val len = r.getUShort() - if (tColl.elemType == SByte) - Colls.fromArray(r.getBytes(len)) - else - deserializeColl(len, tColl.elemType, r) + deserializeColl(len, tColl.elemType, r) case tuple: STuple => val arr = tuple.items.map { t => deserialize(t, r) @@ -137,13 +134,12 @@ object DataSerializer { case SBoolean => Colls.fromArray(r.getBits(len)).asInstanceOf[Coll[T#WrappedType]] case SByte => - // TODO cover with tests Colls.fromArray(r.getBytes(len)).asInstanceOf[Coll[T#WrappedType]] case _ => implicit val tItem = (tpeElem match { case tTup: STuple if tTup.items.length == 2 => Evaluation.stypeToRType(tpeElem) - case tTup: STuple => + case _: STuple => collRType(RType.AnyType) case _ => Evaluation.stypeToRType(tpeElem) diff --git a/sigmastate/src/main/scala/sigmastate/serialization/ErgoTreeSerializer.scala b/sigmastate/src/main/scala/sigmastate/serialization/ErgoTreeSerializer.scala index 279f0866b0..ad4401dd7f 100644 --- a/sigmastate/src/main/scala/sigmastate/serialization/ErgoTreeSerializer.scala +++ b/sigmastate/src/main/scala/sigmastate/serialization/ErgoTreeSerializer.scala @@ -247,7 +247,7 @@ class ErgoTreeSerializer { val (header, _, constants, treeBytes) = deserializeHeaderWithTreeBytes(r) val w = SigmaSerializer.startWriter() w.put(header) - w.putUInt(constants.length) // TODO HF: this should not be serialized when segregation is off + w.putUInt(constants.length) // TODO HF (3h): this should not be serialized when segregation is off val constantSerializer = ConstantSerializer(DeserializationSigmaBuilder) constants.zipWithIndex.foreach { @@ -260,7 +260,7 @@ class ErgoTreeSerializer { val newConsts = constantStore.getAll assert(newConsts.length == 1) val newConst = newConsts.head - // TODO HF: replace assert with require + // TODO HF (1h): replace assert with require assert(c.tpe == newConst.tpe, s"expected new constant to have the same ${c.tpe} tpe, got ${newConst.tpe}") constantSerializer.serialize(newConst, w) case (c, _) => diff --git a/sigmastate/src/main/scala/sigmastate/serialization/ModQArithOpSerializer.scala b/sigmastate/src/main/scala/sigmastate/serialization/ModQArithOpSerializer.scala index 3e0bb9119d..26d70281f3 100644 --- a/sigmastate/src/main/scala/sigmastate/serialization/ModQArithOpSerializer.scala +++ b/sigmastate/src/main/scala/sigmastate/serialization/ModQArithOpSerializer.scala @@ -6,7 +6,7 @@ import sigmastate.utils.SigmaByteWriter.DataInfo import sigmastate.utils.{SigmaByteReader, SigmaByteWriter} import sigmastate.{ModQArithOpCompanion, SType, ModQArithOp} -// TODO HF: make sure it is covered with tests +// TODO HF (2h): make sure it is covered with tests case class ModQArithOpSerializer(override val opDesc: ModQArithOpCompanion, cons: (BigIntValue, BigIntValue) => BigIntValue) extends ValueSerializer[ModQArithOp] { val leftInfo: DataInfo[SValue] = opDesc.argInfos(0) diff --git a/sigmastate/src/main/scala/sigmastate/serialization/ModQSerializer.scala b/sigmastate/src/main/scala/sigmastate/serialization/ModQSerializer.scala index 26be575e41..dee613490c 100644 --- a/sigmastate/src/main/scala/sigmastate/serialization/ModQSerializer.scala +++ b/sigmastate/src/main/scala/sigmastate/serialization/ModQSerializer.scala @@ -5,7 +5,7 @@ import sigmastate.lang.Terms._ import sigmastate.utils.{SigmaByteReader, SigmaByteWriter} import sigmastate.{ModQ, SType} -// TODO HF: make sure it is covered with tests +// TODO HF (2h): make sure it is covered with tests object ModQSerializer extends ValueSerializer[ModQ] { override def opDesc = ModQ diff --git a/sigmastate/src/main/scala/sigmastate/serialization/OperationSerializer.scala b/sigmastate/src/main/scala/sigmastate/serialization/OperationSerializer.scala deleted file mode 100644 index 630c78c728..0000000000 --- a/sigmastate/src/main/scala/sigmastate/serialization/OperationSerializer.scala +++ /dev/null @@ -1,71 +0,0 @@ -package sigmastate.serialization - -import scorex.crypto.authds.avltree.batch._ -import scorex.crypto.authds.{ADKey, ADValue} -import sigmastate.utils.{SigmaByteReader, SigmaByteWriter} - -import scala.annotation.tailrec - -class OperationSerializer(keyLength: Int, valueLengthOpt: Option[Int]) extends SigmaSerializer[Operation, Operation] { - - def parseSeq(r: SigmaByteReader): Seq[Operation] = { - @tailrec - def parseOps(r: SigmaByteReader, acc: Seq[Operation]): Seq[Operation] = if (r.remaining > 0) { - val op = parse(r) - parseOps(r, op +: acc) - } else { - acc.reverse - } - - parseOps(r, Seq()) - } - - def serializeSeq(ops: Seq[Operation]): Array[Byte] = { - val w = SigmaSerializer.startWriter() - ops.foreach(o => serialize(o, w)) - w.toBytes - } - - override def parse(r: SigmaByteReader): Operation = { - def parseValue(): ADValue = { - val vl: Int = valueLengthOpt.getOrElse(r.getShort()) - ADValue @@ r.getBytes(vl) - } - - r.getByte() match { - case 1 => Lookup(ADKey @@ r.getBytes(keyLength)) - case 2 => Remove(ADKey @@ r.getBytes(keyLength)) - case 3 => RemoveIfExists(ADKey @@ r.getBytes(keyLength)) - case 4 => Insert(ADKey @@ r.getBytes(keyLength), parseValue()) - case 5 => Update(ADKey @@ r.getBytes(keyLength), parseValue()) - case 6 => InsertOrUpdate(ADKey @@ r.getBytes(keyLength), parseValue()) - case _ => throw new Exception("Unknown operation") - } - } - - override def serialize(o: Operation, w: SigmaByteWriter): Unit = { - def serializeKey(tp: Byte, key: Array[Byte]): Unit = { - w.put(tp) - w.putBytes(key) - } - - def serializeKeyValue(tp: Byte, key: Array[Byte], value: Array[Byte]): Unit = { - serializeKey(tp, key) - if (valueLengthOpt.isEmpty) { - w.putShort(value.length.toShort) - } - w.putBytes(value) - } - - o match { - case Lookup(key) => serializeKey(1: Byte, key) - case Remove(key) => serializeKey(2: Byte, key) - case RemoveIfExists(key) => serializeKey(3: Byte, key) - case Insert(key, value) => serializeKeyValue(4: Byte, key, value) - case Update(key, value) => serializeKeyValue(5: Byte, key, value) - case InsertOrUpdate(key, value) => serializeKeyValue(6: Byte, key, value) - case _ => w.put(0: Byte) // TODO cover with tests - } - } - -} \ No newline at end of file diff --git a/sigmastate/src/main/scala/sigmastate/serialization/SigmaSerializer.scala b/sigmastate/src/main/scala/sigmastate/serialization/SigmaSerializer.scala index cdb61cd85b..6e9dc86f4a 100644 --- a/sigmastate/src/main/scala/sigmastate/serialization/SigmaSerializer.scala +++ b/sigmastate/src/main/scala/sigmastate/serialization/SigmaSerializer.scala @@ -63,7 +63,7 @@ object SigmaSerializer { } } -trait SigmaSerializer[TFamily, T <: TFamily] extends Serializer[TFamily, T, SigmaByteReader, SigmaByteWriter] { +abstract class SigmaSerializer[TFamily, T <: TFamily] extends Serializer[TFamily, T, SigmaByteReader, SigmaByteWriter] { /** Wraps the given writer in SigmaByteWriter and delegates to [[serialize]]. * NOTE: it is used in spam tests. diff --git a/sigmastate/src/main/scala/sigmastate/serialization/TypeSerializer.scala b/sigmastate/src/main/scala/sigmastate/serialization/TypeSerializer.scala index 3c6659d056..fb51333d51 100644 --- a/sigmastate/src/main/scala/sigmastate/serialization/TypeSerializer.scala +++ b/sigmastate/src/main/scala/sigmastate/serialization/TypeSerializer.scala @@ -9,7 +9,7 @@ import sigmastate.utils.{SigmaByteReader, SigmaByteWriter} import spire.syntax.all.cfor /** Serialization of types according to specification in TypeSerialization.md. */ -object TypeSerializer extends ByteBufferSerializer[SType] { +object TypeSerializer { import sigmastate.SCollectionType._ @@ -23,7 +23,7 @@ object TypeSerializer extends ByteBufferSerializer[SType] { embeddableIdToType(code) } - override def serialize(tpe: SType, w: SigmaByteWriter) = tpe match { + def serialize(tpe: SType, w: SigmaByteWriter): Unit = tpe match { case p: SEmbeddable => w.put(p.typeCode) case SString => w.put(SString.typeCode) case SAny => w.put(SAny.typeCode) @@ -90,7 +90,7 @@ object TypeSerializer extends ByteBufferSerializer[SType] { serialize(t2, w) } case STuple(items) if items.length < 2 => - sys.error(s"Invalid Tuple type with less than 2 items $items") // TODO cover with tests + sys.error(s"Invalid Tuple type with less than 2 items $items") case tup: STuple => tup.items.length match { case 3 => // Triple of types @@ -114,7 +114,7 @@ object TypeSerializer extends ByteBufferSerializer[SType] { } } - override def deserialize(r: SigmaByteReader): SType = deserialize(r, 0) + def deserialize(r: SigmaByteReader): SType = deserialize(r, 0) private def deserialize(r: SigmaByteReader, depth: Int): SType = { val c = r.getUByte() diff --git a/sigmastate/src/main/scala/sigmastate/serialization/ValueSerializer.scala b/sigmastate/src/main/scala/sigmastate/serialization/ValueSerializer.scala index 2b5d8ffc77..bc826dbf71 100644 --- a/sigmastate/src/main/scala/sigmastate/serialization/ValueSerializer.scala +++ b/sigmastate/src/main/scala/sigmastate/serialization/ValueSerializer.scala @@ -17,7 +17,7 @@ import sigmastate.utxo._ import scala.collection.mutable -trait ValueSerializer[V <: Value[SType]] extends SigmaSerializer[Value[SType], V] { +abstract class ValueSerializer[V <: Value[SType]] extends SigmaSerializer[Value[SType], V] { import scala.language.implicitConversions val companion = ValueSerializer diff --git a/sigmastate/src/main/scala/sigmastate/trees.scala b/sigmastate/src/main/scala/sigmastate/trees.scala index 117d0308bd..1ce7966d4d 100644 --- a/sigmastate/src/main/scala/sigmastate/trees.scala +++ b/sigmastate/src/main/scala/sigmastate/trees.scala @@ -642,7 +642,7 @@ object BitOp { } } -// TODO HF: implement modular operations +// TODO HF (24h): implement modular operations case class ModQ(input: Value[SBigInt.type]) extends NotReadyValue[SBigInt.type] { override def companion = ModQ diff --git a/sigmastate/src/main/scala/sigmastate/types.scala b/sigmastate/src/main/scala/sigmastate/types.scala index 1bc2017caf..ac25257a17 100644 --- a/sigmastate/src/main/scala/sigmastate/types.scala +++ b/sigmastate/src/main/scala/sigmastate/types.scala @@ -1,7 +1,6 @@ package sigmastate import java.math.BigInteger -import java.util import org.ergoplatform._ import org.ergoplatform.validation._ @@ -11,7 +10,6 @@ import sigmastate.SType.{TypeCode, AnyOps} import sigmastate.interpreter.CryptoConstants import sigmastate.utils.Overloading.Overload1 import scalan.util.Extensions._ -import sigmastate.SBigInt.MaxSizeInBytes import sigmastate.Values._ import sigmastate.lang.Terms._ import sigmastate.lang.{SigmaBuilder, SigmaTyper} @@ -32,7 +30,7 @@ import special.sigma.{Header, Box, SigmaProp, AvlTree, SigmaDslBuilder, PreHeade import sigmastate.lang.SigmaTyper.STypeSubst import sigmastate.eval.Evaluation.stypeToRType import sigmastate.eval._ -import sigmastate.lang.exceptions.SerializerException +import spire.syntax.all.cfor /** Base type for all AST nodes of sigma lang. */ trait SigmaNode extends Product @@ -72,7 +70,10 @@ sealed trait SType extends SigmaNode { def isEmbeddable: Boolean = false /** Returns true if dataSize doesn't depend on data value. - * This is useful for optimizations of calculating sizes of collections. */ + * This is useful for optimizations of calculating sizes of collections. + * The method should have O(1) amortized complexity over n invocations to avoid + * over-cost attacks on ErgoTree interpretation. + */ def isConstantSize: Boolean /** Elvis operator for types. See https://en.wikipedia.org/wiki/Elvis_operator*/ @@ -166,6 +167,9 @@ object SType { /** A mapping of object types supporting MethodCall operations. For each serialized typeId this map contains * a companion object which can be used to access the list of corresponding methods. * NOTE: in the current implementation only monomorphic methods are supported (without type parameters)*/ + // TODO HF (h4): should contain all numeric types (including also SNumericType) + // to support method calls like 10.toByte which encoded as MethodCall with typeId = 4, methodId = 1 + // see https://github.com/ScorexFoundation/sigmastate-interpreter/issues/667 val types: Map[Byte, STypeCompanion] = Seq( SBoolean, SNumericType, SString, STuple, SGroupElement, SSigmaProp, SContext, SGlobal, SHeader, SPreHeader, SAvlTree, SBox, SOption, SCollection, SBigInt @@ -489,6 +493,8 @@ trait SNumericType extends SProduct { } object SNumericType extends STypeCompanion { final val allNumericTypes = Array(SByte, SShort, SInt, SLong, SBigInt) + // TODO HF (4h): this typeId is now shadowed by SGlobal.typeId + // see https://github.com/ScorexFoundation/sigmastate-interpreter/issues/667 def typeId: TypeCode = 106: Byte val tNum = STypeVar("TNum") @@ -1095,7 +1101,7 @@ object SCollection extends STypeCompanion with MethodByNameUnapply { SFunc(IndexedSeq(ThisType, tIV, SInt), SInt, Seq(paramIV)), 27) .withIRInfo(MethodCallIrBuilder).withInfo(MethodCall, "") - // TODO HF: related to https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479 + // TODO HF (1h): related to https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479 lazy val FindMethod = SMethod(this, "find", SFunc(IndexedSeq(ThisType, tPredicate), SOption(tIV), Seq(paramIV)), 28) .withIRInfo(MethodCallIrBuilder).withInfo(MethodCall, "") @@ -1134,7 +1140,7 @@ object SCollection extends STypeCompanion with MethodByNameUnapply { FilterMethod, AppendMethod, ApplyMethod, - /* TODO soft-fork: https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479 + /* TODO HF (1h): https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479 BitShiftLeftMethod, BitShiftRightMethod, BitShiftRightZeroedMethod, @@ -1144,19 +1150,19 @@ object SCollection extends STypeCompanion with MethodByNameUnapply { PatchMethod, UpdatedMethod, UpdateManyMethod, - /*TODO soft-fork: https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479 + /*TODO HF (1h): https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479 UnionSetsMethod, DiffMethod, IntersectMethod, PrefixLengthMethod, */ IndexOfMethod, - /* TODO soft-fork: https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479 + /* TODO HF (1h): https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479 LastIndexOfMethod, FindMethod, */ ZipMethod - /* TODO soft-fork: https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479 + /* TODO HF (1h): https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479 DistinctMethod, StartsWithMethod, EndsWithMethod, @@ -1194,8 +1200,30 @@ case class STuple(items: IndexedSeq[SType]) extends SCollection[SAny.type] { import STuple._ override val typeCode = STuple.TupleTypeCode + /** Lazily computed value representing true | false | none. + * 0 - none, 1 - false, 2 - true + */ + @volatile + private var _isConstantSizeCode: Byte = 0.toByte + + /** use lazy pattern to support O(1) amortized complexity over n invocations. */ override def isConstantSize: Boolean = { - items.forall(t => t.isConstantSize) + val code = _isConstantSizeCode + if (code == 0) { + val len = items.length + var isConst: Boolean = true + cfor(0)(_ < len && isConst, _ + 1) { i => + val t = items(i) + isConst = t.isConstantSize + } + if (isConst) { + _isConstantSizeCode = 2 + } else { + _isConstantSizeCode = 1 + } + return isConst + } + code == 2.toByte } override def dataSize(v: SType#WrappedType) = { diff --git a/sigmastate/src/main/scala/sigmastate/utils/SigmaByteWriter.scala b/sigmastate/src/main/scala/sigmastate/utils/SigmaByteWriter.scala index a212b1be31..733b4c348b 100644 --- a/sigmastate/src/main/scala/sigmastate/utils/SigmaByteWriter.scala +++ b/sigmastate/src/main/scala/sigmastate/utils/SigmaByteWriter.scala @@ -76,6 +76,11 @@ class SigmaByteWriter(val w: Writer, w.putULong(x); this } + override def putBytes(xs: Array[Byte], + offset: Int, + length: Int): this.type = { + w.putBytes(xs, offset, length); this + } @inline def putBytes(xs: Array[Byte]): this.type = { w.putBytes(xs); this } @inline def putBytes(xs: Array[Byte], info: DataInfo[Array[Byte]]): this.type = { ValueSerializer.addArgInfo(info) diff --git a/sigmastate/src/test/scala/org/ergoplatform/ErgoAddressSpecification.scala b/sigmastate/src/test/scala/org/ergoplatform/ErgoAddressSpecification.scala index 4358b35f20..bc4d2e733f 100644 --- a/sigmastate/src/test/scala/org/ergoplatform/ErgoAddressSpecification.scala +++ b/sigmastate/src/test/scala/org/ergoplatform/ErgoAddressSpecification.scala @@ -3,6 +3,7 @@ package org.ergoplatform import java.math.BigInteger import org.ergoplatform.ErgoAddressEncoder.{hash256, MainnetNetworkPrefix, TestnetNetworkPrefix} +import org.ergoplatform.SigmaConstants.ScriptCostLimit import org.ergoplatform.validation.{ValidationException, ValidationRules} import org.scalatest.{Assertion, TryValues} import sigmastate.basics.DLogProtocol @@ -11,13 +12,14 @@ import sigmastate.serialization.ErgoTreeSerializer.DefaultSerializer import sigmastate.serialization.ValueSerializer import scorex.util.encode.Base58 import sigmastate.{SigmaAnd, SType} -import sigmastate.Values.{UnparsedErgoTree, Constant, ByteArrayConstant, IntConstant, ErgoTree} +import sigmastate.Values.{UnparsedErgoTree, Constant, EvaluatedValue, ByteArrayConstant, IntConstant, ErgoTree} import sigmastate.eval.IRContext import sigmastate.helpers._ import sigmastate.helpers.TestingHelpers._ -import sigmastate.interpreter.ContextExtension +import sigmastate.interpreter.{ContextExtension, CostedProverResult} import sigmastate.interpreter.Interpreter.{ScriptNameProp, ScriptEnv} import sigmastate.lang.Terms.ValueOps +import sigmastate.lang.exceptions.{CosterException, CostLimitException} import sigmastate.utils.Helpers._ import special.sigma.SigmaDslTesting @@ -209,4 +211,101 @@ class ErgoAddressSpecification extends SigmaDslTesting with TryValues { val tree = ErgoTree.fromProposition(prop) testPay2SHAddress(Pay2SHAddress(tree), scriptBytes) } + + property("negative cases: deserialized script + costing exceptions") { + implicit lazy val IR = new TestingIRContext + + def testPay2SHAddress(address: Pay2SHAddress, script: (Byte, EvaluatedValue[_ <: SType]), costLimit: Int = ScriptCostLimit.value): CostedProverResult = { + val boxToSpend = testBox(10, address.script, creationHeight = 5) + val ctx = copyContext(ErgoLikeContextTesting.dummy(boxToSpend) + .withExtension(ContextExtension(Seq( + script // provide script bytes in context variable + ).toMap)))(costLimit = costLimit) + + val prover = new ErgoLikeTestProvingInterpreter() + prover.prove(address.script, ctx, fakeMessage).getOrThrow + } + + val scriptVarId = 1.toByte + val script = "{ 1 < 2 }" + val prop = compile(Map.empty, script).asBoolValue.toSigmaProp + val scriptBytes = ValueSerializer.serialize(prop) + val addr = Pay2SHAddress(prop) + + // when everything is ok + testPay2SHAddress(addr, script = scriptVarId -> ByteArrayConstant(scriptBytes)) + + // when limit is low + { + // choose limit less than total cost: + // totalCost(2671) = addr.script.complexity(2277) + prop complexity(164) + scaledCost(230) + val deliberatelySmallLimit = 2600 + + assertExceptionThrown( + { + testPay2SHAddress(addr, + script = scriptVarId -> ByteArrayConstant(scriptBytes), + costLimit = deliberatelySmallLimit) + }, + { t => + t.isInstanceOf[CostLimitException] && + t.getMessage.contains( + s"Estimated execution cost 2671 exceeds the limit $deliberatelySmallLimit")} + ) + } + + // when limit is low + { + // choose limit less than addr.script.complexity == 2277 + script complexity == 164 + val deliberatelySmallLimit = 2300 + + assertExceptionThrown( + { + testPay2SHAddress(addr, + script = scriptVarId -> ByteArrayConstant(scriptBytes), + costLimit = deliberatelySmallLimit) + }, + { t => + t.isInstanceOf[CostLimitException] && + t.getMessage.contains( + s"Estimated execution cost 2441 exceeds the limit $deliberatelySmallLimit")} + ) + } + + // when limit is even lower than tree complexity + { + // choose limit less than addr.script.complexity == 2277 + val deliberatelySmallLimit = 2000 + + assertExceptionThrown( + { + testPay2SHAddress(addr, + script = scriptVarId -> ByteArrayConstant(scriptBytes), + costLimit = deliberatelySmallLimit) + }, + { t => + t.isInstanceOf[CostLimitException] && + t.getMessage.contains( + s"Estimated execution cost 2277 exceeds the limit $deliberatelySmallLimit")} + ) + } + + // when script var have invalid type + assertExceptionThrown( + testPay2SHAddress(addr, script = scriptVarId -> IntConstant(10)), + { t => + t.isInstanceOf[CosterException] && + t.getMessage.contains(s"Don't know how to evalNode(DeserializeContext(")} + ) + + // when script var have invalid id + val invalidId: Byte = 2 + assertExceptionThrown( + testPay2SHAddress(addr, script = invalidId -> IntConstant(10)), + { t => + t.isInstanceOf[CosterException] && + t.getMessage.contains(s"Don't know how to evalNode(DeserializeContext(")} + ) + } + } \ No newline at end of file diff --git a/sigmastate/src/test/scala/org/ergoplatform/ErgoLikeTransactionSpec.scala b/sigmastate/src/test/scala/org/ergoplatform/ErgoLikeTransactionSpec.scala index 5b4f0e7f26..bd7a01cccc 100644 --- a/sigmastate/src/test/scala/org/ergoplatform/ErgoLikeTransactionSpec.scala +++ b/sigmastate/src/test/scala/org/ergoplatform/ErgoLikeTransactionSpec.scala @@ -94,7 +94,7 @@ class ErgoLikeTransactionSpec extends SigmaDslTesting { ErgoAlgos.decodeUnsafe(token1).toColl -> 10000000L, ErgoAlgos.decodeUnsafe(token2).toColl -> 500L ).map(identity).toConstant - // TODO HF: fix collections equality and remove map(identity) + // TODO HF (16h): fix collections equality and remove map(identity) // (PairOfColl should be equal CollOverArray but now it is not) res shouldBe exp } diff --git a/sigmastate/src/test/scala/sigmastate/CostingSpecification.scala b/sigmastate/src/test/scala/sigmastate/CostingSpecification.scala index aa944390cc..4a3da0ad2b 100644 --- a/sigmastate/src/test/scala/sigmastate/CostingSpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/CostingSpecification.scala @@ -171,9 +171,6 @@ class CostingSpecification extends SigmaTestingData { cost("{ SELF.tokens.size > 0 }")(accessBox + extractCost + LengthGTConstCost) cost("{ SELF.creationInfo._1 > 0 }")(accessBox + accessRegister + selectField + GTConstCost) cost("{ SELF.R5[Int].get > 0 }")(accessBox + RegisterAccess + GTConstCost) - - // TODO coverage: related to https://github.com/ScorexFoundation/sigmastate-interpreter/issues/416 - // cost("{ SELF.getReg[Long](0.toByte).get > 0 }")(accessBox + RegisterAccess + GTConstCost) } lazy val OutputsCost = selectField + accessBox * tx.outputs.length diff --git a/sigmastate/src/test/scala/sigmastate/ErgoTreeBenchmarks.scala b/sigmastate/src/test/scala/sigmastate/ErgoTreeBenchmarks.scala new file mode 100644 index 0000000000..5155e1d117 --- /dev/null +++ b/sigmastate/src/test/scala/sigmastate/ErgoTreeBenchmarks.scala @@ -0,0 +1,46 @@ +package sigmastate + +import special.collections.BenchmarkGens +import org.scalameter.api.Bench +import sigmastate.Values.{SValue, IntConstant} +import sigmastate.serialization.OpCodes.PlusCode +import spire.syntax.all.cfor + +object ErgoTreeBenchmarks extends Bench.LocalTime with BenchmarkGens { suite: Bench[Double] => + + override def maxSize: Int = 10000 + + /** Expected approximate results: + * ::Benchmark allocation of sigmastate.Values.ArithOp, EQ, IntConstant:: + * name: OpenJDK 64-Bit Server VM + * osArch: x86_64 + * osName: Mac OS X + * vendor: AdoptOpenJDK + * version: 25.232-b09 + * Parameters(size -> 10): 0.007652 ms + * Parameters(size -> 100): 0.070323 ms + * Parameters(size -> 1000): 0.696851 ms + * Parameters(size -> 10000): 5.687967 ms + */ + performance of "allocation of sigmastate.Values" in { + measure method "ArithOp, EQ, IntConstant" in { + using(sizes) in { size => + val arr = new Array[SValue](size) + cfor(0)(_ < size, _ + 1) { i => + val expr = + ArithOp( + ArithOp( + ArithOp( + ArithOp( + ArithOp( + IntConstant(10), 20, PlusCode), + 30, PlusCode), + 40, PlusCode), + 50, PlusCode), + 60, PlusCode) + arr(i) = EQ(expr, 100) + } + } + } + } +} diff --git a/sigmastate/src/test/scala/sigmastate/ErgoTreeSpecification.scala b/sigmastate/src/test/scala/sigmastate/ErgoTreeSpecification.scala index 50158c74b2..892b88ab79 100644 --- a/sigmastate/src/test/scala/sigmastate/ErgoTreeSpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/ErgoTreeSpecification.scala @@ -160,6 +160,26 @@ class ErgoTreeSpecification extends SigmaTestingData { } } + property("Tuple Types") { + val constSizeTuple = STuple(SByte, SInt, SBigInt) + val dynSizeTuple = STuple(SByte, SInt, SBox, SBigInt) + forAll(Table(("type", "isConstantSize"), + (STuple(SByte), true), + (STuple(SByte, SInt), true), + (STuple(SByte, SInt, SAvlTree), true), + (STuple(SBox), false), + (STuple(SByte, SBox), false), + (STuple(SByte, SInt, SBox), false), + (STuple(SBox, SByte, SInt), false), + (constSizeTuple, true), + (constSizeTuple, true), // should avoid re-computation + (dynSizeTuple, false), + (dynSizeTuple, false) // should avoid re-computation + )) { (t, isConst) => + t.isConstantSize shouldBe isConst + } + } + /** Expected parameters of resolved method (see `methods` table below). * * @param isResolvableFromIds if true, them SMethod.fromIds must resolve, otherwise @@ -210,7 +230,7 @@ class ErgoTreeSpecification extends SigmaTestingData { { import SSigmaProp._ (SSigmaProp.typeId, Seq( MInfo(1, PropBytesMethod), - MInfo(2, IsProvenMethod) // TODO HF: this method must be removed + MInfo(2, IsProvenMethod) // TODO HF (3h): this method must be removed ), true) }, { import SBox._ diff --git a/sigmastate/src/test/scala/sigmastate/SoftForkabilitySpecification.scala b/sigmastate/src/test/scala/sigmastate/SoftForkabilitySpecification.scala index 2cfeaa45fa..5cfbf67957 100644 --- a/sigmastate/src/test/scala/sigmastate/SoftForkabilitySpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/SoftForkabilitySpecification.scala @@ -7,12 +7,12 @@ import sigmastate.SPrimType.MaxPrimTypeCode import sigmastate.Values.ErgoTree.EmptyConstants import sigmastate.Values.{UnparsedErgoTree, NotReadyValueInt, ByteArrayConstant, Tuple, IntConstant, ErgoTree, ValueCompanion} import sigmastate.eval.Colls -import sigmastate.helpers.{ErgoLikeContextTesting, ErgoLikeTestInterpreter, ErgoLikeTestProvingInterpreter} +import sigmastate.helpers.{ErgoLikeContextTesting, ErgoLikeTestProvingInterpreter, ErgoLikeTestInterpreter} import sigmastate.helpers.TestingHelpers._ import sigmastate.interpreter.Interpreter.{ScriptNameProp, emptyEnv} import sigmastate.interpreter.{ProverResult, ContextExtension} import sigmastate.lang.Terms._ -import sigmastate.lang.exceptions.{SerializerException, SigmaException, CosterException} +import sigmastate.lang.exceptions.{SerializerException, SigmaException, CosterException, InterpreterException} import sigmastate.serialization.OpCodes.{OpCodeExtra, LastConstantCode, OpCode} import sigmastate.serialization._ import sigmastate.utxo.{DeserializeContext, SelectField} @@ -26,14 +26,19 @@ class SoftForkabilitySpecification extends SigmaTestingData { lazy val verifier = new ErgoLikeTestInterpreter val deadline = 100 val boxAmt = 100L - lazy val invalidPropV1 = compile(emptyEnv + ("deadline" -> deadline), + + lazy val booleanPropV1 = compile(emptyEnv + ("deadline" -> deadline), """{ | HEIGHT > deadline && OUTPUTS.size == 1 |}""".stripMargin).asBoolValue - lazy val invalidTxV1 = createTransaction(createBox(boxAmt, invalidPropV1.asSigmaProp, 1)) + + // cast Boolean typed prop to SigmaProp (which is invalid) + lazy val invalidPropV1 = ErgoTree.fromProposition(booleanPropV1.asSigmaProp) + + lazy val invalidTxV1 = createTransaction(createBox(boxAmt, invalidPropV1, 1)) lazy val invalidTxV1bytes = invalidTxV1.messageToSign - lazy val propV1 = invalidPropV1.toSigmaProp + lazy val propV1 = booleanPropV1.toSigmaProp lazy val txV1 = createTransaction(createBox(boxAmt, propV1, 1)) lazy val txV1bytes = txV1.messageToSign @@ -108,11 +113,12 @@ class SoftForkabilitySpecification extends SigmaTestingData { res } - lazy val prop = GT(Height2, IntConstant(deadline)) - lazy val invalidTxV2 = createTransaction(createBox(boxAmt, prop.asSigmaProp, 1)) + lazy val booleanPropV2 = GT(Height2, IntConstant(deadline)) + lazy val invalidPropV2 = ErgoTree.fromProposition(booleanPropV2.asSigmaProp) + lazy val invalidTxV2 = createTransaction(createBox(boxAmt, invalidPropV2, 1)) - lazy val propV2 = prop.toSigmaProp + lazy val propV2 = booleanPropV2.toSigmaProp // prepare bytes using special serialization WITH `size flag` in the header lazy val propV2tree = ErgoTree.withSegregation(ErgoTree.SizeFlag, propV2) lazy val propV2treeBytes = runOnV2Node { @@ -177,6 +183,15 @@ class SoftForkabilitySpecification extends SigmaTestingData { val tx = ErgoLikeTransaction.serializer.parse(SigmaSerializer.startReader(txV2bytes)) proveAndVerifyTx("propV2", tx, v2vs) + // and with v1 settings + assertExceptionThrown( + proveAndVerifyTx("propV2", tx, vs), + { t => + t.isInstanceOf[InterpreterException] && + t.getMessage.contains("Script has not been recognized due to ValidationException, and it cannot be accepted as soft-fork.") + } + ) + // also check that transaction prop was trivialized due to soft-fork tx.outputs(0).ergoTree.root.left.get.bytes.array shouldBe treeBytes tx.outputs(0).ergoTree.root.left.get.isInstanceOf[UnparsedErgoTree] shouldBe true @@ -221,11 +236,11 @@ class SoftForkabilitySpecification extends SigmaTestingData { }) } } - + property("our node v1, was soft-fork up to v2, received v1 script, DeserializeContext of v2 script") { // script bytes for context variable containing v2 operation val propBytes = runOnV2Node { - ValueSerializer.serialize(prop) + ValueSerializer.serialize(booleanPropV2) } // v1 main script which deserializes from context v2 script diff --git a/sigmastate/src/test/scala/sigmastate/TestingInterpreterSpecification.scala b/sigmastate/src/test/scala/sigmastate/TestingInterpreterSpecification.scala index 0a14afc166..ba1c3e3abb 100644 --- a/sigmastate/src/test/scala/sigmastate/TestingInterpreterSpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/TestingInterpreterSpecification.scala @@ -195,14 +195,6 @@ class TestingInterpreterSpecification extends SigmaTestingCommons { |}""".stripMargin) } -// TODO coverage: implement it as negative test -// property("Evaluate sigma in lambdas") { -// testeval("""{ -// | val arr = Array(dk1, dk2) -// | allOf(arr.map(fun (d: Boolean) = d && true)) -// |}""".stripMargin) -// } - property("Evaluate numeric casting ops") { def testWithCasting(castSuffix: String): Unit = { testEval(s"OUTPUTS.size.toByte.$castSuffix == 0.$castSuffix") diff --git a/sigmastate/src/test/scala/sigmastate/helpers/SigmaTestingCommons.scala b/sigmastate/src/test/scala/sigmastate/helpers/SigmaTestingCommons.scala index 8826339ad4..d2d7deea37 100644 --- a/sigmastate/src/test/scala/sigmastate/helpers/SigmaTestingCommons.scala +++ b/sigmastate/src/test/scala/sigmastate/helpers/SigmaTestingCommons.scala @@ -167,7 +167,8 @@ trait SigmaTestingCommons extends PropSpec val tree = IR.buildTree(calcF) // sanity check that buildTree is reverse to buildGraph (see doCostingEx) - tree shouldBe compiledTree + if (tA != special.sigma.ContextRType) + tree shouldBe compiledTree val lA = Liftables.asLiftable[SContext, IR.Context](calcF.elem.eDom.liftable) val lB = Liftables.asLiftable[Any, Any](calcF.elem.eRange.liftable) diff --git a/sigmastate/src/test/scala/sigmastate/helpers/TestingHelpers.scala b/sigmastate/src/test/scala/sigmastate/helpers/TestingHelpers.scala index f6dd44919a..bd520accd3 100644 --- a/sigmastate/src/test/scala/sigmastate/helpers/TestingHelpers.scala +++ b/sigmastate/src/test/scala/sigmastate/helpers/TestingHelpers.scala @@ -3,11 +3,17 @@ package sigmastate.helpers import scorex.crypto.hash.Digest32 import special.collection.Coll import scorex.util.ModifierId -import org.ergoplatform.{DataInput, ErgoLikeTransaction, ErgoBox, ErgoBoxCandidate} +import org.ergoplatform.{ErgoLikeTransactionTemplate, ErgoLikeTransaction, ErgoLikeContext, UnsignedInput, ErgoBox, DataInput, ErgoBoxCandidate} import sigmastate.Values.ErgoTree import org.ergoplatform.ErgoBox.{AdditionalRegisters, allZerosModifierId, TokenId} +import org.ergoplatform.validation.SigmaValidationSettings +import sigmastate.AvlTreeData import sigmastate.eval.CostingSigmaDslBuilder import sigmastate.eval._ +import sigmastate.interpreter.ContextExtension +import special.sigma.{Header, PreHeader} + +import scala.collection.mutable.WrappedArray // TODO refactor: unification is required between two hierarchies of tests // and as part of it, more methods can be moved to TestingHelpers @@ -18,7 +24,7 @@ object TestingHelpers { def testBox(value: Long, ergoTree: ErgoTree, creationHeight: Int, - additionalTokens: Seq[(TokenId, Long)] = Nil, + additionalTokens: Seq[(TokenId, Long)] = WrappedArray.empty, additionalRegisters: AdditionalRegisters = Map.empty, transactionId: ModifierId = allZerosModifierId, boxIndex: Short = 0): ErgoBox = @@ -29,14 +35,14 @@ object TestingHelpers { def createBox(value: Long, proposition: ErgoTree, - additionalTokens: Seq[(Digest32, Long)] = Seq(), - additionalRegisters: AdditionalRegisters = Map()) + additionalTokens: Seq[(Digest32, Long)] = WrappedArray.empty, + additionalRegisters: AdditionalRegisters = Map.empty) = testBox(value, proposition, 0, additionalTokens, additionalRegisters) def createBox(value: Long, proposition: ErgoTree, creationHeight: Int) - = testBox(value, proposition, creationHeight, Seq(), Map(), ErgoBox.allZerosModifierId) + = testBox(value, proposition, creationHeight, WrappedArray.empty, Map.empty, ErgoBox.allZerosModifierId) /** Copies the given box allowing also to update fields. */ def copyBox(box: ErgoBox)( @@ -50,6 +56,24 @@ object TestingHelpers { new ErgoBox(value, ergoTree, additionalTokens, additionalRegisters, transactionId, index, creationHeight) } + /** Copies the given context allowing also to update fields. */ + def copyContext(ctx: ErgoLikeContext)( + lastBlockUtxoRoot: AvlTreeData = ctx.lastBlockUtxoRoot, + headers: Coll[Header] = ctx.headers, + preHeader: PreHeader = ctx.preHeader, + dataBoxes: IndexedSeq[ErgoBox] = ctx.dataBoxes, + boxesToSpend: IndexedSeq[ErgoBox] = ctx.boxesToSpend, + spendingTransaction: ErgoLikeTransactionTemplate[_ <: UnsignedInput] = ctx.spendingTransaction, + selfIndex: Int = ctx.selfIndex, + extension: ContextExtension = ctx.extension, + validationSettings: SigmaValidationSettings = ctx.validationSettings, + costLimit: Long = ctx.costLimit, + initCost: Long = ctx.initCost): ErgoLikeContext = { + new ErgoLikeContext( + lastBlockUtxoRoot, headers, preHeader, dataBoxes, boxesToSpend, + spendingTransaction, selfIndex, extension, validationSettings, costLimit, initCost) + } + /** Creates a new box by updating some of the additional registers with the given new bindings. * @param newBindings a map of the registers to be updated with new values */ @@ -64,13 +88,13 @@ object TestingHelpers { * in our test cases */ def createTransaction(outputCandidates: IndexedSeq[ErgoBoxCandidate]): ErgoLikeTransaction = { - new ErgoLikeTransaction(IndexedSeq(), IndexedSeq(), outputCandidates) + new ErgoLikeTransaction(WrappedArray.empty, WrappedArray.empty, outputCandidates) } - def createTransaction(box: ErgoBoxCandidate): ErgoLikeTransaction = createTransaction(IndexedSeq(box)) + def createTransaction(box: ErgoBoxCandidate): ErgoLikeTransaction = createTransaction(Array(box)) def createTransaction(dataInputs: IndexedSeq[ErgoBox], outputCandidates: IndexedSeq[ErgoBoxCandidate]): ErgoLikeTransaction = - new ErgoLikeTransaction(IndexedSeq(), dataInputs.map(b => DataInput(b.id)), outputCandidates) + new ErgoLikeTransaction(WrappedArray.empty, dataInputs.map(b => DataInput(b.id)), outputCandidates) } diff --git a/sigmastate/src/test/scala/sigmastate/lang/SigmaBinderTest.scala b/sigmastate/src/test/scala/sigmastate/lang/SigmaBinderTest.scala index 3b84820964..a283cbb07d 100644 --- a/sigmastate/src/test/scala/sigmastate/lang/SigmaBinderTest.scala +++ b/sigmastate/src/test/scala/sigmastate/lang/SigmaBinderTest.scala @@ -142,7 +142,7 @@ class SigmaBinderTest extends PropSpec with PropertyChecks with Matchers with La If(EQ(IntConstant(10), IntConstant(11)), IntConstant(2), IntConstant(3))) } - // TODO HF: SomeValue and NoneValue are not used in ErgoTree and can be + // TODO HF (4h): SomeValue and NoneValue are not used in ErgoTree and can be // either removed or implemented in v4.x property("Option constructors") { bind(env, "None") shouldBe NoneValue(NoType) diff --git a/sigmastate/src/test/scala/sigmastate/lang/SigmaCompilerTest.scala b/sigmastate/src/test/scala/sigmastate/lang/SigmaCompilerTest.scala index 786c31eeef..c4a2511eb9 100644 --- a/sigmastate/src/test/scala/sigmastate/lang/SigmaCompilerTest.scala +++ b/sigmastate/src/test/scala/sigmastate/lang/SigmaCompilerTest.scala @@ -439,7 +439,7 @@ class SigmaCompilerTest extends SigmaTestingCommons with LangTests with ObjectGe Map()) } - // TODO HF: related to https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479 + // TODO soft-fork: related to https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479 ignore("SCollection.find") { comp("OUTPUTS.find({ (out: Box) => out.value >= 1L })") shouldBe mkMethodCall(Outputs, diff --git a/sigmastate/src/test/scala/sigmastate/serialization/ConstantSerializerSpecification.scala b/sigmastate/src/test/scala/sigmastate/serialization/ConstantSerializerSpecification.scala index 50aab09279..648de1bb22 100644 --- a/sigmastate/src/test/scala/sigmastate/serialization/ConstantSerializerSpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/serialization/ConstantSerializerSpecification.scala @@ -15,6 +15,10 @@ import sigmastate.Values._ import sigmastate.eval.Evaluation import special.sigma.AvlTree import SType.AnyOps +import scorex.util.encode.Base16 +import sigmastate.lang.DeserializationSigmaBuilder +import sigmastate.lang.exceptions.SerializerException +import sigmastate.utils.Helpers class ConstantSerializerSpecification extends TableSerializationSpecification { @@ -112,4 +116,23 @@ class ConstantSerializerSpecification extends TableSerializationSpecification { tableRoundTripTest("Specific objects serializer round trip") tablePredefinedBytesTest("Specific objects deserialize from predefined bytes") + property("Element type checked in collection of Boolean") { + val builder = DeserializationSigmaBuilder + val ser = ConcreteCollectionBooleanConstantSerializer(builder.mkConcreteCollection) + + // successfull case + ser.toBytes(ConcreteCollection(Array(TrueLeaf), SBoolean)) shouldBe Base16.decode("0101").get + + assertExceptionThrown( { + val coll = ConcreteCollection( + Array(TrueLeaf, ByteConstant(0).asInstanceOf[BoolValue]), + SBoolean) + ser.toBytes(coll) + }, + { + case e: SerializerException => + e.getMessage.contains("Expected collection of BooleanConstant values") + case _ => false + }) + } } diff --git a/sigmastate/src/test/scala/sigmastate/serialization/DataJsonEncoderSpecification.scala b/sigmastate/src/test/scala/sigmastate/serialization/DataJsonEncoderSpecification.scala index 15e49ccd3e..f55037f772 100644 --- a/sigmastate/src/test/scala/sigmastate/serialization/DataJsonEncoderSpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/serialization/DataJsonEncoderSpecification.scala @@ -3,6 +3,7 @@ package sigmastate.serialization import java.math.BigInteger +import org.ergoplatform.JsonCodecs import org.scalacheck.Arbitrary._ import scalan.RType import sigmastate.SCollection.SByteArray @@ -13,9 +14,11 @@ import sigmastate.eval.Extensions._ import sigmastate.eval.{Evaluation, _} import sigmastate.interpreter.CryptoConstants.EcPointType import sigmastate.lang.exceptions.SerializerException -import special.sigma.{AvlTree, Box} +import special.sigma.{TestValue, Box, AvlTree} class DataJsonEncoderSpecification extends SerializationSpecification { + object JsonCodecs extends JsonCodecs + def roundtrip[T <: SType](obj: T#WrappedType, tpe: T) = { val json = DataJsonEncoder.encode(obj, tpe) val res = DataJsonEncoder.decode(json) @@ -56,6 +59,31 @@ class DataJsonEncoderSpecification extends SerializationSpecification { } } + def testAnyValue[T <: SType](tpe: T) = { + implicit val wWrapped = wrappedTypeGen(tpe) + implicit val tag = tpe.classTag[T#WrappedType] + implicit val tT = Evaluation.stypeToRType(tpe) + implicit val tAny = RType.AnyType + forAll { in: T#WrappedType => + val x = TestValue(in, tT) + val json = JsonCodecs.anyValueEncoder(x) + val y = JsonCodecs.anyValueDecoder.decodeJson(json).right.get + x shouldBe y + + val tTup = Evaluation.stypeToRType(STuple(tpe, tpe)).asInstanceOf[RType[(T#WrappedType, T#WrappedType)]] + val xTup = TestValue((in, in), tTup) + val jsonTup = JsonCodecs.anyValueEncoder(xTup) + val yTup = JsonCodecs.anyValueDecoder.decodeJson(jsonTup).right.get + xTup shouldBe yTup + + val tColl = Evaluation.stypeToRType(SCollection(tpe)) + val xColl = TestValue(SigmaDsl.Colls.fromItems(in, in), tColl) + val jsonColl = JsonCodecs.anyValueEncoder(xColl) + val yColl = JsonCodecs.anyValueDecoder.decodeJson(jsonColl).right.get + xColl shouldBe yColl + } + } + property("Data Json serialization round trip") { forAll { x: Byte => roundtrip[SByte.type](x, SByte) } forAll { x: Boolean => roundtrip[SBoolean.type](x, SBoolean) } @@ -77,6 +105,30 @@ class DataJsonEncoderSpecification extends SerializationSpecification { property("Example test") { def toUnifiedString(from: String): String = from.replaceAll("[\n ]", "") + toUnifiedString(DataJsonEncoder.encode(().asWrappedType, SUnit).toString()) shouldBe + toUnifiedString( + """ + |{ "type": "Unit", + | "value": {} + |}""".stripMargin) + toUnifiedString(DataJsonEncoder.encode(Some(10).asWrappedType, SOption(SInt)).toString()) shouldBe + toUnifiedString( + """ + |{ "type": "Option[Int]", + | "value": [10] + |}""".stripMargin) + toUnifiedString(DataJsonEncoder.encode(None.asWrappedType, SOption(SInt)).toString()) shouldBe + toUnifiedString( + """ + |{ "type": "Option[Int]", + | "value": null + |}""".stripMargin) + toUnifiedString(DataJsonEncoder.encode(Some(None).asWrappedType, SOption(SOption(SInt))).toString()) shouldBe + toUnifiedString( + """ + |{ "type": "Option[Option[Int]]", + | "value": [null] + |}""".stripMargin) toUnifiedString(DataJsonEncoder.encode((10, 20).asWrappedType, STuple(SInt, SInt)).toString()) shouldBe toUnifiedString( """ @@ -132,12 +184,78 @@ class DataJsonEncoderSpecification extends SerializationSpecification { implicit val tag = tpe.classTag[T#WrappedType] implicit val tAny = RType.AnyType forAll { x: T#WrappedType => - an[SerializerException] should be thrownBy + an[SerializerException] should be thrownBy { DataJsonEncoder.encode(TupleColl(x, x, x).asWrappedType, STuple(tpe, tpe, tpe)) + } + + // supported case + DataJsonEncoder.encode(SigmaDsl.Colls.fromItems(TupleColl(x, x)).asWrappedType, SCollection(STuple(tpe, tpe))) + + // not supported case + an[SerializerException] should be thrownBy { + DataJsonEncoder.encode(SigmaDsl.Colls.fromItems(TupleColl(x, x, x)).asWrappedType, SCollection(STuple(tpe, tpe, tpe))) + } + } + } + + property("AnyValue") { + forAll { t: SPredefType => + testAnyValue(t) + testAnyValue(SOption(t)) } } property("Tuples with > 2 items are not supported") { - forAll { t: SPredefType => testEncodeError(t) } + forAll { t: SPredefType => + testEncodeError(t) + } + } + + property("Reject decoding Tuple with more than 2 items") { + val pair = io.circe.parser.parse( + """ + |{ "type": "(Int, Int)", + | "value": { + | "_1": 10, + | "_2": 20 + | } + |}""".stripMargin).right.get + + DataJsonEncoder.decode(pair) shouldBe (10, 20) + + val triple = io.circe.parser.parse( + """ + |{ "type": "(Int, Int, Int)", + | "value": { + | "_1": 10, + | "_2": 20, + | "_3": 30 + | } + |}""".stripMargin).right.get + + assertExceptionThrown( + DataJsonEncoder.decode(triple), + { t => + t.isInstanceOf[SerializerException] && + t.getMessage.contains("Tuples with length not equal to 2 are not supported") + } + ) + + val tripleColl = io.circe.parser.parse( + """ + |{ "type": "Coll[(Int, Int, Int)]", + | "value": { + | "_1": [1, 2, 3], + | "_2": [10, 20, 30] + | } + |}""".stripMargin).right.get + + assertExceptionThrown( + DataJsonEncoder.decode(tripleColl), + { t => + t.isInstanceOf[SerializerException] && + t.getMessage.contains("Tuples with length not equal to 2 are not supported") + } + ) } } diff --git a/sigmastate/src/test/scala/sigmastate/serialization/DataSerializerSpecification.scala b/sigmastate/src/test/scala/sigmastate/serialization/DataSerializerSpecification.scala index 4747cef86c..68a4ebef38 100644 --- a/sigmastate/src/test/scala/sigmastate/serialization/DataSerializerSpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/serialization/DataSerializerSpecification.scala @@ -14,6 +14,8 @@ import sigmastate.eval.Extensions._ import sigmastate.interpreter.CryptoConstants.EcPointType import special.sigma.AvlTree import SType.AnyOps +import sigmastate.lang.exceptions.SerializerException +import sigmastate.utils.Helpers class DataSerializerSpecification extends SerializationSpecification { @@ -88,4 +90,31 @@ class DataSerializerSpecification extends SerializationSpecification { forAll { t: SPredefType => testTuples(t) } } + property("Should check limits and fail") { + val len = 0xFFFF + 1 + val tup = SigmaDsl.Colls.replicate(len, 1.asInstanceOf[Any])(RType.AnyType) + assertExceptionThrown({ + val w = SigmaSerializer.startWriter() + DataSerializer.serialize(tup.asWrappedType, STuple(Array.fill(len)(SInt)), w) + }, + { t => + t.isInstanceOf[RuntimeException] && + t.getMessage.contains(s"Length of tuple $len exceeds ${0xFFFF} limit.") + }) + + val tooBig = SigmaDsl.BigInt(new BigInteger(Helpers.decodeBytes( + "80e0ff7f02807fff72807f0a00ff7fb7c57f75c11ba2802970fd250052807fc37f6480ffff007fff18eeba44").toArray)) + + assertExceptionThrown({ + val w = SigmaSerializer.startWriter() + DataSerializer.serialize(tooBig.asWrappedType, SBigInt, w) + val r = SigmaSerializer.startReader(w.toBytes) + DataSerializer.deserialize(SBigInt, r) + }, + { t => + t.isInstanceOf[SerializerException] && + t.getMessage.contains(s"BigInt value doesn't not fit into ${SBigInt.MaxSizeInBytes} bytes") + }) + + } } diff --git a/sigmastate/src/test/scala/sigmastate/serialization/OperationSerializerSpecification.scala b/sigmastate/src/test/scala/sigmastate/serialization/OperationSerializerSpecification.scala deleted file mode 100644 index 4eee6528a1..0000000000 --- a/sigmastate/src/test/scala/sigmastate/serialization/OperationSerializerSpecification.scala +++ /dev/null @@ -1,55 +0,0 @@ -package sigmastate.serialization - -import org.scalacheck.{Arbitrary, Gen} -import scorex.crypto.authds.avltree.batch._ -import scorex.crypto.authds.{ADKey, ADValue} - -class OperationSerializerSpecification extends SerializationSpecification { - - val keyLength: Int = 32 - - property("operation seq serialization") { - forAll(Gen.nonEmptyListOf(operationGen)) { ops => - val serializer = new OperationSerializer(keyLength, None) - val bytes = serializer.serializeSeq(ops) - val parsed = serializer.parseSeq(SigmaSerializer.startReader(bytes, 0)) - val bytes2 = serializer.serializeSeq(parsed) - bytes2 shouldEqual bytes - } - } - - property("operation serialization") { - def roundTrip(op: Operation, serializer: OperationSerializer) = { - val randValueBytes = serializer.toBytes(op) - val randValueRecovered = serializer.parse(SigmaSerializer.startReader(randValueBytes, 0)) - serializer.toBytes(randValueRecovered) shouldEqual randValueBytes - } - - forAll(operationGen) { op => - val valueLength = op match { - case Insert(k, v) => v.length - case Update(k, v) => v.length - case InsertOrUpdate(k, v) => v.length - case _ => 0 - } - roundTrip(op, new OperationSerializer(keyLength, None)) - roundTrip(op, new OperationSerializer(keyLength, Some(valueLength))) - } - } - - lazy val operationGen: Gen[Operation] = for { - tp <- Gen.choose(1, 6) - key <- Gen.listOfN(keyLength, Arbitrary.arbitrary[Byte]).map(_.toArray).map(k => ADKey @@ k) - value <- Arbitrary.arbitrary[Array[Byte]].map(k => ADValue @@ k) - } yield { - tp match { - case 1 => Lookup(key) - case 2 => Remove(key) - case 3 => RemoveIfExists(key) - case 4 => Insert(key, value) - case 5 => Update(key, value) - case 6 => InsertOrUpdate(key, value) - case _ => ??? - } - } -} diff --git a/sigmastate/src/test/scala/sigmastate/serialization/SerializationSpecification.scala b/sigmastate/src/test/scala/sigmastate/serialization/SerializationSpecification.scala index 6ffcde7d77..5ffc800191 100644 --- a/sigmastate/src/test/scala/sigmastate/serialization/SerializationSpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/serialization/SerializationSpecification.scala @@ -7,6 +7,7 @@ import org.scalatest.{PropSpec, Assertion, Matchers} import org.scalacheck.Arbitrary._ import sigmastate.Values._ import sigmastate.SType +import sigmastate.helpers.NegativeTesting import sigmastate.serialization.generators._ trait SerializationSpecification extends PropSpec @@ -19,7 +20,8 @@ trait SerializationSpecification extends PropSpec with OpcodesGen with TransformerGenerators with RelationGenerators - with ValidationSpecification { + with ValidationSpecification + with NegativeTesting { protected def roundTripTest[V <: Value[_ <: SType]](v: V): Assertion = { val bytes = ValueSerializer.serialize(v) diff --git a/sigmastate/src/test/scala/sigmastate/serialization/TypeSerializerSpecification.scala b/sigmastate/src/test/scala/sigmastate/serialization/TypeSerializerSpecification.scala index 0cc2ef75b7..ed4cda54ce 100644 --- a/sigmastate/src/test/scala/sigmastate/serialization/TypeSerializerSpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/serialization/TypeSerializerSpecification.scala @@ -96,4 +96,13 @@ class TypeSerializerSpecification extends SerializationSpecification { roundtrip(ti) } } + + property("negative cases") { + assertExceptionThrown( + SigmaSerializer.startWriter().putType(STuple(SInt)), + { t => + t.isInstanceOf[RuntimeException] && + t.getMessage.contains("Invalid Tuple type with less than 2 items") + }) + } } diff --git a/sigmastate/src/test/scala/sigmastate/utxo/AVLTreeScriptsSpecification.scala b/sigmastate/src/test/scala/sigmastate/utxo/AVLTreeScriptsSpecification.scala index 6b2593b2b7..f230460d73 100644 --- a/sigmastate/src/test/scala/sigmastate/utxo/AVLTreeScriptsSpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/utxo/AVLTreeScriptsSpecification.scala @@ -5,8 +5,8 @@ import org.ergoplatform.ErgoScriptPredef.TrueProp import org.ergoplatform._ import org.ergoplatform.dsl.{ContractSpec, SigmaContractSyntax, TestContractSpec} import scorex.crypto.authds.avltree.batch._ -import scorex.crypto.authds.{ADKey, ADValue} -import scorex.crypto.hash.{Blake2b256, Digest32} +import scorex.crypto.authds.{ADKey, ADValue, SerializedAdProof} +import scorex.crypto.hash.{Digest32, Blake2b256} import sigmastate.SCollection.SByteArray import sigmastate.Values._ import sigmastate._ @@ -16,6 +16,7 @@ import sigmastate.eval.Extensions._ import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, ErgoLikeContextTesting, ErgoLikeTestInterpreter, SigmaTestingCommons} import sigmastate.helpers.TestingHelpers._ import sigmastate.interpreter.Interpreter.ScriptNameProp +import sigmastate.interpreter.ProverResult import sigmastate.lang.Terms._ import special.collection.Coll import special.sigma.{AvlTree, Context} @@ -267,7 +268,16 @@ class AVLTreeScriptsSpecification extends SigmaTestingCommons { suite => .withContextExtender(elementId, LongConstant(elements.head)) val proof = prover.prove(prop, ctx, fakeMessage).get - (new ErgoLikeTestInterpreter).verify(prop, ctx, proof, fakeMessage).get._1 shouldBe true + val verifier = new ErgoLikeTestInterpreter + verifier.verify(prop, ctx, proof, fakeMessage).get._1 shouldBe true + + // check that verifier returns false for incorrect proofs? + val invalidProof = SerializedAdProof @@ Array[Byte](1, 2, 3) + val invalidProofResult = new ProverResult( + proof = proof.proof, + extension = proof.extension.add(proofId -> ByteArrayConstant(invalidProof)) + ) + verifier.verify(prop, ctx, invalidProofResult, fakeMessage).get._1 shouldBe false avlProver.performOneOperation(Lookup(treeElements.last._1)) val smallLeafTreeProof = avlProver.generateProof() @@ -275,7 +285,6 @@ class AVLTreeScriptsSpecification extends SigmaTestingCommons { suite => .withContextExtender(proofId, ByteArrayConstant(smallLeafTreeProof)) .withContextExtender(elementId, LongConstant(elements.head)) smallProver.prove(prop, ctx, fakeMessage).isSuccess shouldBe false - // TODO coverage: check that verifier return false for incorrect proofs? } property("avl tree - prover provides proof") { diff --git a/sigmastate/src/test/scala/sigmastate/utxo/CollectionOperationsSpecification.scala b/sigmastate/src/test/scala/sigmastate/utxo/CollectionOperationsSpecification.scala index 25980f9f11..470db0f676 100644 --- a/sigmastate/src/test/scala/sigmastate/utxo/CollectionOperationsSpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/utxo/CollectionOperationsSpecification.scala @@ -1,6 +1,5 @@ package sigmastate.utxo -import org.ergoplatform import org.ergoplatform.ErgoScriptPredef.TrueProp import sigmastate.Values._ import sigmastate._ @@ -95,22 +94,37 @@ class CollectionOperationsSpecification extends SigmaTestingCommons { val newBox1 = testBox(16, pubkey, 0) val newBox2 = testBox(15, pubkey, 0) - val newBoxes = IndexedSeq(newBox1, newBox2) - val spendingTransaction = createTransaction(newBoxes) + val spendingTransaction = createTransaction(Array(newBox1, newBox2)) val ctx = ErgoLikeContextTesting( currentHeight = 50, lastBlockUtxoRoot = AvlTreeData.dummy, minerPubkey = ErgoLikeContextTesting.dummyPubkey, - boxesToSpend = IndexedSeq(fakeSelf), + boxesToSpend = Array(fakeSelf), spendingTransaction, self = fakeSelf) - val pr = prover.prove(prop, ctx, fakeMessage).get - verifier.verify(prop, ctx, pr, fakeMessage).get._1 shouldBe true + { + val pr = prover.prove(prop, ctx, fakeMessage).get + verifier.verify(prop, ctx, pr, fakeMessage).get._1 shouldBe true + } + + // negative case for `exists` + { + val newBox1 = testBox(1, pubkey, 0) + val newBox2 = testBox(5, pubkey, 0) + val tx2 = createTransaction(Array(newBox1, newBox2)) + val ctx2 = ErgoLikeContextTesting( + currentHeight = ctx.preHeader.height, + lastBlockUtxoRoot = AvlTreeData.dummy, + minerPubkey = ErgoLikeContextTesting.dummyPubkey, + boxesToSpend = Array(fakeSelf), + spendingTransaction = tx2, + self = fakeSelf) - //TODO coverage: add negative case for `exists` + prover.prove(prop, ctx2, fakeMessage).isFailure shouldBe true + } } property("forall") { diff --git a/sigmastate/src/test/scala/sigmastate/utxo/ErgoLikeInterpreterSpecification.scala b/sigmastate/src/test/scala/sigmastate/utxo/ErgoLikeInterpreterSpecification.scala index da779b4668..9c2d2c439a 100644 --- a/sigmastate/src/test/scala/sigmastate/utxo/ErgoLikeInterpreterSpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/utxo/ErgoLikeInterpreterSpecification.scala @@ -14,10 +14,11 @@ import sigmastate.eval._ import sigmastate.interpreter.Interpreter._ import sigmastate.basics.DLogProtocol.ProveDlog import sigmastate.basics.ProveDHTuple -import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, ErgoLikeContextTesting, ErgoLikeTestInterpreter, ErgoLikeTransactionTesting, SigmaTestingCommons} +import sigmastate.helpers.{ErgoLikeTransactionTesting, ErgoLikeContextTesting, ErgoLikeTestInterpreter, ErgoLikeTestProvingInterpreter, SigmaTestingCommons, ContextEnrichingTestProvingInterpreter} import sigmastate.helpers.TestingHelpers._ +import sigmastate.interpreter.{ContextExtension, CostedProverResult} import sigmastate.lang.Terms._ -import sigmastate.serialization.{SerializationSpecification, ValueSerializer} +import sigmastate.serialization.{ValueSerializer, SerializationSpecification} import sigmastate.utils.Helpers._ class ErgoLikeInterpreterSpecification extends SigmaTestingCommons @@ -613,7 +614,7 @@ class ErgoLikeInterpreterSpecification extends SigmaTestingCommons val spendingTransaction = createTransaction(output) - val ctx = ErgoLikeContextTesting( + val ctx1 = ErgoLikeContextTesting( currentHeight = 50, lastBlockUtxoRoot = AvlTreeData.dummy, minerPubkey = ErgoLikeContextTesting.dummyPubkey, @@ -621,10 +622,19 @@ class ErgoLikeInterpreterSpecification extends SigmaTestingCommons spendingTransaction, self = input3) - val pr = prover.prove(emptyEnv + (ScriptNameProp -> "prove"), prop, ctx, fakeMessage).get - verifier.verify(emptyEnv + (ScriptNameProp -> "verify"), prop, ctx, pr, fakeMessage).get._1 shouldBe true + val pr = prover.prove(emptyEnv + (ScriptNameProp -> "prove"), prop, ctx1, fakeMessage).get + verifier.verify(emptyEnv + (ScriptNameProp -> "verify"), prop, ctx1, pr, fakeMessage).get._1 shouldBe true - //TODO coverage: check failing branches + val ctx2 = ErgoLikeContextTesting( + currentHeight = 50, + lastBlockUtxoRoot = AvlTreeData.dummy, + minerPubkey = ErgoLikeContextTesting.dummyPubkey, + boxesToSpend = IndexedSeq( + copyBox(input0)(value = 20), // to go through `then` branch of `if` in the script + input1, input2, input3), + spendingTransaction, + self = input3) + prover.prove(prop, ctx2, fakeMessage).isFailure shouldBe true } property("DeserializeRegister value type mismatch") { @@ -680,7 +690,25 @@ class ErgoLikeInterpreterSpecification extends SigmaTestingCommons // make sure prover fails as well on deserializing context with mismatched type an[ValidationException] should be thrownBy prover.prove(prop1, ctx, fakeMessage).get } - } + } + + property("DeserializeContext can return expression of non-Boolean/SigmaProp type") { + def prove(ergoTree: ErgoTree, script: (Byte, EvaluatedValue[_ <: SType])): CostedProverResult = { + val boxToSpend = testBox(10, ergoTree, creationHeight = 5) + val ctx = ErgoLikeContextTesting.dummy(boxToSpend) + .withExtension( + ContextExtension(Seq(script).toMap)) // provide script bytes in context variable + + val prover = new ErgoLikeTestProvingInterpreter() + prover.prove(ergoTree, ctx, fakeMessage).getOrThrow + } + + val script = "{ 1 + 2 }" + val scriptProp = compile(Map.empty, script) // of Int type + val scriptBytes = ValueSerializer.serialize(scriptProp) + val tree = ErgoTree.fromProposition(EQ(DeserializeContext(1, SInt), IntConstant(3)).toSigmaProp) + prove(tree, script = 1.toByte -> ByteArrayConstant(scriptBytes)) + } property("non-const ProveDHT") { import sigmastate.interpreter.CryptoConstants.dlogGroup diff --git a/sigmastate/src/test/scala/special/sigma/SigmaDslSpecification.scala b/sigmastate/src/test/scala/special/sigma/SigmaDslSpecification.scala index 71e16fb564..6e4c20feab 100644 --- a/sigmastate/src/test/scala/special/sigma/SigmaDslSpecification.scala +++ b/sigmastate/src/test/scala/special/sigma/SigmaDslSpecification.scala @@ -6,9 +6,7 @@ import java.math.BigInteger import org.ergoplatform.ErgoScriptPredef.TrueProp import org.ergoplatform._ import org.ergoplatform.settings.ErgoAlgos -import org.scalacheck.{Arbitrary, Gen} -import org.scalatest.prop.{PropertyChecks, TableFor2} -import org.scalatest.{PropSpec, Matchers, Tag} +import org.scalacheck.Gen import scalan.{ExactNumeric, RType} import scorex.crypto.authds.avltree.batch._ import scorex.crypto.authds.{ADDigest, ADKey, ADValue} @@ -31,14 +29,14 @@ import sigmastate.utils.Helpers import sigmastate.utils.Helpers._ import sigmastate.helpers.TestingHelpers._ -import scala.reflect.ClassTag -import scala.util.{DynamicVariable, Success, Failure, Try} +import scala.util.{Success, Failure} import OrderingOps._ +import org.ergoplatform.ErgoBox.AdditionalRegisters import scorex.util.ModifierId -import sigmastate.basics.{ProveDHTuple, DLogProtocol} -import sigmastate.helpers.SigmaPPrint +import sigmastate.basics.ProveDHTuple + +import scala.collection.mutable -import scala.math.Ordering /** This suite tests every method of every SigmaDsl type to be equivalent to * the evaluation of the corresponding ErgoScript operation */ @@ -74,10 +72,10 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => ) )) val cases = Seq( - (true, true) -> Success(Expected(false, 36518)), - (true, false) -> Success(Expected(true, 36518)), - (false, false) -> Success(Expected(false, 36518)), - (false, true) -> Success(Expected(true, 36518)) + (true, true) -> Expected(Success(false), 36518), + (true, false) -> Expected(Success(true), 36518), + (false, false) -> Expected(Success(false), 36518), + (false, true) -> Expected(Success(true), 36518) ) verifyCases(cases, binXor) } @@ -96,12 +94,12 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => ) )) val cases = Seq( - (1095564593, true) -> Success(Expected(true, 36865)), - (-901834021, true) -> Success(Expected(true, 36865)), - (595045530, false) -> Success(Expected(false, 36865)), - (-1157998227, false) -> Success(Expected(false, 36865)), - (0, true) -> Success(Expected(false, 36865)), - (0, false) -> Success(Expected(true, 36865)) + (1095564593, true) -> Expected(Success(true), 36865), + (-901834021, true) -> Expected(Success(true), 36865), + (595045530, false) -> Expected(Success(false), 36865), + (-1157998227, false) -> Expected(Success(false), 36865), + (0, true) -> Expected(Success(false), 36865), + (0, false) -> Expected(Success(true), 36865) ) verifyCases(cases, xor) } @@ -117,10 +115,10 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => ) )) val cases = Seq( - (false, true) -> Success(Expected(false, 38241)), - (false, false) -> Success(Expected(false, 38241)), - (true, true) -> Success(Expected(true, 38241)), - (true, false) -> Success(Expected(false, 38241)) + (false, true) -> Expected(Success(false), 38241), + (false, false) -> Expected(Success(false), 38241), + (true, true) -> Expected(Success(true), 38241), + (true, false) -> Expected(Success(false), 38241) ) verifyCases(cases, eq) } @@ -136,10 +134,10 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => ) )) val cases = Seq( - (true, false) -> Success(Expected(true, 38241)), - (true, true) -> Success(Expected(true, 38241)), - (false, false) -> Success(Expected(false, 38241)), - (false, true) -> Success(Expected(true, 38241)) + (true, false) -> Expected(Success(true), 38241), + (true, true) -> Expected(Success(true), 38241), + (false, false) -> Expected(Success(false), 38241), + (false, true) -> Expected(Success(true), 38241) ) verifyCases(cases, eq) } @@ -147,8 +145,8 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => property("lazy || and && boolean equivalence") { verifyCases( Seq( - (true, Success(Expected(true, 38467))), - (false, Failure(new ArithmeticException("/ by zero"))) + (true, Expected(Success(true), 38467)), + (false, Expected(new ArithmeticException("/ by zero"))) ), existingFeature((x: Boolean) => x || (1 / 0 == 1), "{ (x: Boolean) => x || (1 / 0 == 1) }", @@ -162,8 +160,8 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( Seq( - (true, Failure(new ArithmeticException("/ by zero"))), - (false, Success(Expected(false, 38467))) + (true, Expected(new ArithmeticException("/ by zero"))), + (false, Expected(Success(false), 38467)) ), existingFeature((x: Boolean) => x && (1 / 0 == 1), "{ (x: Boolean) => x && (1 / 0 == 1) }", @@ -177,8 +175,8 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( Seq( - (false, Success(Expected(false, 40480))), - (true, Success(Expected(true, 40480))) + (false, Expected(Success(false), 40480)), + (true, Expected(Success(true), 40480)) ), existingFeature((x: Boolean) => x && (x || (1 / 0 == 1)), "{ (x: Boolean) => x && (x || (1 / 0 == 1)) }", @@ -195,8 +193,8 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( Seq( - (false, Success(Expected(false, 42493))), - (true, Success(Expected(true, 42493))) + (false, Expected(Success(false), 42493)), + (true, Expected(Success(true), 42493)) ), existingFeature((x: Boolean) => x && (x && (x || (1 / 0 == 1))), "{ (x: Boolean) => x && (x && (x || (1 / 0 == 1))) }", @@ -216,8 +214,8 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( Seq( - (false, Success(Expected(false, 44506))), - (true, Success(Expected(true, 44506))) + (false, Expected(Success(false), 44506)), + (true, Expected(Success(true), 44506)) ), existingFeature((x: Boolean) => x && (x && (x && (x || (1 / 0 == 1)))), "{ (x: Boolean) => x && (x && (x && (x || (1 / 0 == 1)))) }", @@ -240,8 +238,8 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( Seq( - (false, Failure(new ArithmeticException("/ by zero"))), - (true, Success(Expected(true, 43281))) + (false, Expected(new ArithmeticException("/ by zero"))), + (true, Expected(Success(true), 43281)) ), existingFeature((x: Boolean) => !(!x && (1 / 0 == 1)) && (x || (1 / 0 == 1)), "{ (x: Boolean) => !(!x && (1 / 0 == 1)) && (x || (1 / 0 == 1)) }", @@ -263,8 +261,8 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( Seq( - (true, Success(Expected(true, 40480))), - (false, Failure(new ArithmeticException("/ by zero"))) + (true, Expected(Success(true), 40480)), + (false, Expected(new ArithmeticException("/ by zero"))) ), existingFeature((x: Boolean) => (x || (1 / 0 == 1)) && x, "{ (x: Boolean) => (x || (1 / 0 == 1)) && x }", @@ -281,8 +279,8 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( Seq( - (true, Success(Expected(true, 43149))), - (false, Failure(new ArithmeticException("/ by zero"))) + (true, Expected(Success(true), 43149)), + (false, Expected(new ArithmeticException("/ by zero"))) ), existingFeature((x: Boolean) => (x || (1 / 0 == 1)) && (x || (1 / 0 == 1)), "{ (x: Boolean) => (x || (1 / 0 == 1)) && (x || (1 / 0 == 1)) }", @@ -302,8 +300,8 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( Seq( - (true, Success(Expected(true, 45950))), - (false, Failure(new ArithmeticException("/ by zero"))) + (true, Expected(Success(true), 45950)), + (false, Expected(new ArithmeticException("/ by zero"))) ), existingFeature( (x: Boolean) => (!(!x && (1 / 0 == 1)) || (1 / 0 == 0)) && (x || (1 / 0 == 1)), @@ -329,8 +327,8 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( Seq( - (false, Failure(new ArithmeticException("/ by zero"))), - (true, Success(Expected(true, 48862))) + (false, Expected(new ArithmeticException("/ by zero"))), + (true, Expected(Success(true), 48862)) ), existingFeature( (x: Boolean) => (!(!x && (1 / 0 == 1)) || (1 / 0 == 0)) && (!(!x && (1 / 0 == 1)) || (1 / 0 == 1)), @@ -369,7 +367,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( { - def expect(v: Byte) = Success(Expected(v, 35798)) + def expect(v: Byte) = Expected(Success(v), 35798) Seq( (0.toByte, expect(0.toByte)), (1.toByte, expect(1.toByte)), @@ -386,7 +384,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( { - def expected(v: Short) = Success(Expected(v, 35902)) + def expected(v: Short) = Expected(Success(v), 35902) Seq( (0.toByte, expected(0.toShort)), (1.toByte, expected(1.toShort)), @@ -403,7 +401,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( { - def expected(v: Int) = Success(Expected(v, 35902)) + def expected(v: Int) = Expected(Success(v), 35902) Seq( (0.toByte, expected(0)), (1.toByte, expected(1)), @@ -420,7 +418,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( { - def expected(v: Long) = Success(Expected(v, 35902)) + def expected(v: Long) = Expected(Success(v), 35902) Seq( (0.toByte, expected(0L)), (1.toByte, expected(1L)), @@ -437,7 +435,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( { - def expected(v: BigInt) = Success(Expected(v, 35932)) + def expected(v: BigInt) = Expected(Success(v), 35932) Seq( (0.toByte, expected(CBigInt(new BigInteger("0", 16)))), (1.toByte, expected(CBigInt(new BigInteger("1", 16)))), @@ -455,38 +453,38 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => val n = ExactNumeric.ByteIsExactNumeric verifyCases( { - def success[T](v: (T, (T, (T, (T, T))))) = Success(Expected(v, 39654)) + def success[T](v: (T, (T, (T, (T, T))))) = Expected(Success(v), 39654) Seq( - ((-128.toByte, -128.toByte), Failure(new ArithmeticException("Byte overflow"))), - ((-128.toByte, 0.toByte), Failure(new ArithmeticException("/ by zero"))), - ((-128.toByte, 17.toByte), Failure(new ArithmeticException("Byte overflow"))), - ((-128.toByte, 127.toByte), Failure(new ArithmeticException("Byte overflow"))), - ((-120.toByte, 82.toByte), Failure(new ArithmeticException("Byte overflow"))), + ((-128.toByte, -128.toByte), Expected(new ArithmeticException("Byte overflow"))), + ((-128.toByte, 0.toByte), Expected(new ArithmeticException("/ by zero"))), + ((-128.toByte, 17.toByte), Expected(new ArithmeticException("Byte overflow"))), + ((-128.toByte, 127.toByte), Expected(new ArithmeticException("Byte overflow"))), + ((-120.toByte, 82.toByte), Expected(new ArithmeticException("Byte overflow"))), ((-103.toByte, 1.toByte), success((-102.toByte, (-104.toByte, (-103.toByte, (-103.toByte, 0.toByte)))))), - ((-90.toByte, 37.toByte), Failure(new ArithmeticException("Byte overflow"))), - ((-78.toByte, -111.toByte), Failure(new ArithmeticException("Byte overflow"))), - ((-71.toByte, -44.toByte), Failure(new ArithmeticException("Byte overflow"))), - ((-53.toByte, 0.toByte), Failure(new ArithmeticException("/ by zero"))), - ((-34.toByte, 8.toByte), Failure(new ArithmeticException("Byte overflow"))), - ((-24.toByte, 127.toByte), Failure(new ArithmeticException("Byte overflow"))), + ((-90.toByte, 37.toByte), Expected(new ArithmeticException("Byte overflow"))), + ((-78.toByte, -111.toByte), Expected(new ArithmeticException("Byte overflow"))), + ((-71.toByte, -44.toByte), Expected(new ArithmeticException("Byte overflow"))), + ((-53.toByte, 0.toByte), Expected(new ArithmeticException("/ by zero"))), + ((-34.toByte, 8.toByte), Expected(new ArithmeticException("Byte overflow"))), + ((-24.toByte, 127.toByte), Expected(new ArithmeticException("Byte overflow"))), ((-1.toByte, -1.toByte), success((-2.toByte, (0.toByte, (1.toByte, (1.toByte, 0.toByte)))))), ((-1.toByte, 23.toByte), success((22.toByte, (-24.toByte, (-23.toByte, (0.toByte, -1.toByte)))))), - ((0.toByte, -128.toByte), Failure(new ArithmeticException("Byte overflow"))), + ((0.toByte, -128.toByte), Expected(new ArithmeticException("Byte overflow"))), ((0.toByte, -23.toByte), success((-23.toByte, (23.toByte, (0.toByte, (0.toByte, 0.toByte)))))), ((0.toByte, -1.toByte), success((-1.toByte, (1.toByte, (0.toByte, (0.toByte, 0.toByte)))))), - ((0.toByte, 0.toByte), Failure(new ArithmeticException("/ by zero"))), + ((0.toByte, 0.toByte), Expected(new ArithmeticException("/ by zero"))), ((0.toByte, 1.toByte), success((1.toByte, (-1.toByte, (0.toByte, (0.toByte, 0.toByte)))))), ((0.toByte, 60.toByte), success((60.toByte, (-60.toByte, (0.toByte, (0.toByte, 0.toByte)))))), ((0.toByte, 127.toByte), success((127.toByte, (-127.toByte, (0.toByte, (0.toByte, 0.toByte)))))), ((1.toByte, -1.toByte), success((0.toByte, (2.toByte, (-1.toByte, (-1.toByte, 0.toByte)))))), - ((1.toByte, 0.toByte), Failure(new ArithmeticException("/ by zero"))), + ((1.toByte, 0.toByte), Expected(new ArithmeticException("/ by zero"))), ((1.toByte, 26.toByte), success((27.toByte, (-25.toByte, (26.toByte, (0.toByte, 1.toByte)))))), - ((7.toByte, -32.toByte), Failure(new ArithmeticException("Byte overflow"))), + ((7.toByte, -32.toByte), Expected(new ArithmeticException("Byte overflow"))), ((33.toByte, 1.toByte), success((34.toByte, (32.toByte, (33.toByte, (33.toByte, 0.toByte)))))), - ((90.toByte, 0.toByte), Failure(new ArithmeticException("/ by zero"))), - ((127.toByte, -128.toByte), Failure(new ArithmeticException("Byte overflow"))), - ((127.toByte, -47.toByte), Failure(new ArithmeticException("Byte overflow"))), - ((127.toByte, 127.toByte), Failure(new ArithmeticException("Byte overflow"))) + ((90.toByte, 0.toByte), Expected(new ArithmeticException("/ by zero"))), + ((127.toByte, -128.toByte), Expected(new ArithmeticException("Byte overflow"))), + ((127.toByte, -47.toByte), Expected(new ArithmeticException("Byte overflow"))), + ((127.toByte, 127.toByte), Expected(new ArithmeticException("Byte overflow"))) ) }, existingFeature( @@ -580,17 +578,17 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( { - def success[T](v: T) = Success(Expected(v, 35976)) + def success[T](v: T) = Expected(Success(v), 35976) Seq( - (Short.MinValue, Failure(new ArithmeticException("Byte overflow"))), - (-21626.toShort, Failure(new ArithmeticException("Byte overflow"))), + (Short.MinValue, Expected(new ArithmeticException("Byte overflow"))), + (-21626.toShort, Expected(new ArithmeticException("Byte overflow"))), (Byte.MinValue.toShort, success(Byte.MinValue)), (-1.toShort, success(-1.toByte)), (0.toShort, success(0.toByte)), (1.toShort, success(1.toByte)), (Byte.MaxValue.toShort, success(Byte.MaxValue)), - (11768.toShort, Failure(new ArithmeticException("Byte overflow"))), - (Short.MaxValue, Failure(new ArithmeticException("Byte overflow"))) + (11768.toShort, Expected(new ArithmeticException("Byte overflow"))), + (Short.MaxValue, Expected(new ArithmeticException("Byte overflow"))) ) }, existingFeature((x: Short) => x.toByteExact, @@ -599,7 +597,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( { - def success[T](v: T) = Success(Expected(v, 35798)) + def success[T](v: T) = Expected(Success(v), 35798) Seq( (-32768.toShort, success(-32768.toShort)), (-27798.toShort, success(-27798.toShort)), @@ -616,7 +614,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( { - def success[T](v: T) = Success(Expected(v, 35902)) + def success[T](v: T) = Expected(Success(v), 35902) Seq( (-32768.toShort, success(-32768)), (-21064.toShort, success(-21064)), @@ -633,7 +631,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( { - def success[T](v: T) = Success(Expected(v, 35902)) + def success[T](v: T) = Expected(Success(v), 35902) Seq( (-32768.toShort, success(-32768L)), (-23408.toShort, success(-23408L)), @@ -650,7 +648,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( { - def success(v: BigInt) = Success(Expected(v, 35932)) + def success(v: BigInt) = Expected(Success(v), 35932) Seq( (-32768.toShort, success(CBigInt(new BigInteger("-8000", 16)))), (-26248.toShort, success(CBigInt(new BigInteger("-6688", 16)))), @@ -668,34 +666,34 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => val n = ExactNumeric.ShortIsExactNumeric verifyCases( { - def success[T](v: T) = Success(Expected(v, 39654)) + def success[T](v: T) = Expected(Success(v), 39654) Seq( - ((-32768.toShort, 1.toShort), Failure(new ArithmeticException("Short overflow"))), - ((-32768.toShort, 4006.toShort), Failure(new ArithmeticException("Short overflow"))), - ((-21384.toShort, 0.toShort), Failure(new ArithmeticException("/ by zero"))), - ((-19027.toShort, 6073.toShort), Failure(new ArithmeticException("Short overflow"))), - ((-16800.toShort, 32767.toShort), Failure(new ArithmeticException("Short overflow"))), + ((-32768.toShort, 1.toShort), Expected(new ArithmeticException("Short overflow"))), + ((-32768.toShort, 4006.toShort), Expected(new ArithmeticException("Short overflow"))), + ((-21384.toShort, 0.toShort), Expected(new ArithmeticException("/ by zero"))), + ((-19027.toShort, 6073.toShort), Expected(new ArithmeticException("Short overflow"))), + ((-16800.toShort, 32767.toShort), Expected(new ArithmeticException("Short overflow"))), ((-1.toShort, -30005.toShort), success((-30006.toShort, (30004.toShort, (30005.toShort, (0.toShort, -1.toShort)))))), - ((-1.toShort, 0.toShort), Failure(new ArithmeticException("/ by zero"))), + ((-1.toShort, 0.toShort), Expected(new ArithmeticException("/ by zero"))), ((0.toShort, -1.toShort), success((-1.toShort, (1.toShort, (0.toShort, (0.toShort, 0.toShort)))))), - ((0.toShort, 0.toShort), Failure(new ArithmeticException("/ by zero"))), + ((0.toShort, 0.toShort), Expected(new ArithmeticException("/ by zero"))), ((0.toShort, 1.toShort), success((1.toShort, (-1.toShort, (0.toShort, (0.toShort, 0.toShort)))))), ((0.toShort, 25105.toShort), success((25105.toShort, (-25105.toShort, (0.toShort, (0.toShort, 0.toShort)))))), - ((1.toShort, -32768.toShort), Failure(new ArithmeticException("Short overflow"))), + ((1.toShort, -32768.toShort), Expected(new ArithmeticException("Short overflow"))), ((1.toShort, -1.toShort), success((0.toShort, (2.toShort, (-1.toShort, (-1.toShort, 0.toShort)))))), - ((1.toShort, 0.toShort), Failure(new ArithmeticException("/ by zero"))), - ((605.toShort, 7698.toShort), Failure(new ArithmeticException("Short overflow"))), - ((5094.toShort, -32768.toShort), Failure(new ArithmeticException("Short overflow"))), + ((1.toShort, 0.toShort), Expected(new ArithmeticException("/ by zero"))), + ((605.toShort, 7698.toShort), Expected(new ArithmeticException("Short overflow"))), + ((5094.toShort, -32768.toShort), Expected(new ArithmeticException("Short overflow"))), ((5350.toShort, -1.toShort), success((5349.toShort, (5351.toShort, (-5350.toShort, (-5350.toShort, 0.toShort)))))), - ((8115.toShort, -32768.toShort), Failure(new ArithmeticException("Short overflow"))), - ((14217.toShort, 32767.toShort), Failure(new ArithmeticException("Short overflow"))), - ((16223.toShort, -11686.toShort), Failure(new ArithmeticException("Short overflow"))), + ((8115.toShort, -32768.toShort), Expected(new ArithmeticException("Short overflow"))), + ((14217.toShort, 32767.toShort), Expected(new ArithmeticException("Short overflow"))), + ((16223.toShort, -11686.toShort), Expected(new ArithmeticException("Short overflow"))), ((16989.toShort, 1.toShort), success((16990.toShort, (16988.toShort, (16989.toShort, (16989.toShort, 0.toShort)))))), - ((20397.toShort, -4450.toShort), Failure(new ArithmeticException("Short overflow"))), + ((20397.toShort, -4450.toShort), Expected(new ArithmeticException("Short overflow"))), ((20488.toShort, 1.toShort), success((20489.toShort, (20487.toShort, (20488.toShort, (20488.toShort, 0.toShort)))))), - ((32767.toShort, -32768.toShort), Failure(new ArithmeticException("Short overflow"))), - ((32767.toShort, -13423.toShort), Failure(new ArithmeticException("Short overflow"))), - ((32767.toShort, 32767.toShort), Failure(new ArithmeticException("Short overflow"))) + ((32767.toShort, -32768.toShort), Expected(new ArithmeticException("Short overflow"))), + ((32767.toShort, -13423.toShort), Expected(new ArithmeticException("Short overflow"))), + ((32767.toShort, 32767.toShort), Expected(new ArithmeticException("Short overflow"))) ) }, existingFeature( @@ -788,17 +786,17 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( { - def success[T](v: T) = Success(Expected(v, 35976)) + def success[T](v: T) = Expected(Success(v), 35976) Seq( - (Int.MinValue, Failure(new ArithmeticException("Byte overflow"))), - (-2014394379, Failure(new ArithmeticException("Byte overflow"))), + (Int.MinValue, Expected(new ArithmeticException("Byte overflow"))), + (-2014394379, Expected(new ArithmeticException("Byte overflow"))), (Byte.MinValue.toInt, success(Byte.MinValue)), (-1, success(-1.toByte)), (0, success(0.toByte)), (1, success(1.toByte)), (Byte.MaxValue.toInt, success(Byte.MaxValue)), - (181686429, Failure(new ArithmeticException("Byte overflow"))), - (Int.MaxValue, Failure(new ArithmeticException("Byte overflow"))) + (181686429, Expected(new ArithmeticException("Byte overflow"))), + (Int.MaxValue, Expected(new ArithmeticException("Byte overflow"))) ) }, existingFeature((x: Int) => x.toByteExact, @@ -807,17 +805,17 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( { - def success[T](v: T) = Success(Expected(v, 35976)) + def success[T](v: T) = Expected(Success(v), 35976) Seq( - (Int.MinValue, Failure(new ArithmeticException("Short overflow"))), - (Short.MinValue - 1, Failure(new ArithmeticException("Short overflow"))), + (Int.MinValue, Expected(new ArithmeticException("Short overflow"))), + (Short.MinValue - 1, Expected(new ArithmeticException("Short overflow"))), (Short.MinValue.toInt, success(Short.MinValue)), (-1, success(-1.toShort)), (0, success(0.toShort)), (1, success(1.toShort)), (Short.MaxValue.toInt, success(Short.MaxValue)), - (Short.MaxValue + 1, Failure(new ArithmeticException("Short overflow"))), - (Int.MaxValue, Failure(new ArithmeticException("Short overflow"))) + (Short.MaxValue + 1, Expected(new ArithmeticException("Short overflow"))), + (Int.MaxValue, Expected(new ArithmeticException("Short overflow"))) ) }, existingFeature((x: Int) => x.toShortExact, @@ -826,7 +824,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( { - def success[T](v: T) = Success(Expected(v, 35798)) + def success[T](v: T) = Expected(Success(v), 35798) Seq( (Int.MinValue, success(Int.MinValue)), (-1, success(-1)), @@ -841,7 +839,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( { - def success[T](v: T) = Success(Expected(v, 35902)) + def success[T](v: T) = Expected(Success(v), 35902) Seq( (Int.MinValue, success(Int.MinValue.toLong)), (-1, success(-1L)), @@ -856,7 +854,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( { - def success(v: BigInt) = Success(Expected(v, 35932)) + def success(v: BigInt) = Expected(Success(v), 35932) Seq( (Int.MinValue, success(CBigInt(new BigInteger("-80000000", 16)))), (-1937187314, success(CBigInt(new BigInteger("-737721f2", 16)))), @@ -874,35 +872,35 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => val n = ExactNumeric.IntIsExactNumeric verifyCases( { - def success[T](v: T) = Success(Expected(v, 39654)) + def success[T](v: T) = Expected(Success(v), 39654) Seq( - ((Int.MinValue, 449583993), Failure(new ArithmeticException("integer overflow"))), - ((-1589633733, 2147483647), Failure(new ArithmeticException("integer overflow"))), + ((Int.MinValue, 449583993), Expected(new ArithmeticException("integer overflow"))), + ((-1589633733, 2147483647), Expected(new ArithmeticException("integer overflow"))), ((-1585471506, -1), success((-1585471507, (-1585471505, (1585471506, (1585471506, 0)))))), - ((-1569005179, 1230236634), Failure(new ArithmeticException("integer overflow"))), - ((-1493733356, -1319619597), Failure(new ArithmeticException("integer overflow"))), - ((-1100263120, -880052091), Failure(new ArithmeticException("integer overflow"))), - ((-1055955857, 309147303), Failure(new ArithmeticException("integer overflow"))), - ((-569807371, 0), Failure(new ArithmeticException("/ by zero"))), - ((-522264843, 2147483647), Failure(new ArithmeticException("integer overflow"))), - ((-109552389, 0), Failure(new ArithmeticException("/ by zero"))), - ((-1, -2147483648), Failure(new ArithmeticException("integer overflow"))), + ((-1569005179, 1230236634), Expected(new ArithmeticException("integer overflow"))), + ((-1493733356, -1319619597), Expected(new ArithmeticException("integer overflow"))), + ((-1100263120, -880052091), Expected(new ArithmeticException("integer overflow"))), + ((-1055955857, 309147303), Expected(new ArithmeticException("integer overflow"))), + ((-569807371, 0), Expected(new ArithmeticException("/ by zero"))), + ((-522264843, 2147483647), Expected(new ArithmeticException("integer overflow"))), + ((-109552389, 0), Expected(new ArithmeticException("/ by zero"))), + ((-1, -2147483648), Expected(new ArithmeticException("integer overflow"))), ((-1, -1), success((-2, (0, (1, (1, 0)))))), - ((-1, 0), Failure(new ArithmeticException("/ by zero"))), - ((0, -2147483648), Failure(new ArithmeticException("integer overflow"))), + ((-1, 0), Expected(new ArithmeticException("/ by zero"))), + ((0, -2147483648), Expected(new ArithmeticException("integer overflow"))), ((1, -1525049432), success((-1525049431, (1525049433, (-1525049432, (0, 1)))))), - ((1, 0), Failure(new ArithmeticException("/ by zero"))), + ((1, 0), Expected(new ArithmeticException("/ by zero"))), ((1, 805353746), success((805353747, (-805353745, (805353746, (0, 1)))))), - ((1, 2147483647), Failure(new ArithmeticException("integer overflow"))), - ((475797978, 0), Failure(new ArithmeticException("/ by zero"))), - ((782343922, -1448560539), Failure(new ArithmeticException("integer overflow"))), - ((928769361, 542647292), Failure(new ArithmeticException("integer overflow"))), - ((1568062151, 0), Failure(new ArithmeticException("/ by zero"))), + ((1, 2147483647), Expected(new ArithmeticException("integer overflow"))), + ((475797978, 0), Expected(new ArithmeticException("/ by zero"))), + ((782343922, -1448560539), Expected(new ArithmeticException("integer overflow"))), + ((928769361, 542647292), Expected(new ArithmeticException("integer overflow"))), + ((1568062151, 0), Expected(new ArithmeticException("/ by zero"))), ((1698252401, -1), success((1698252400, (1698252402, (-1698252401, (-1698252401, 0)))))), - ((1949795740, -1575667037), Failure(new ArithmeticException("integer overflow"))), - ((Int.MaxValue, -1), Failure(new ArithmeticException("integer overflow"))), - ((Int.MaxValue, 1), Failure(new ArithmeticException("integer overflow"))), - ((Int.MaxValue, 1738276576), Failure(new ArithmeticException("integer overflow"))) + ((1949795740, -1575667037), Expected(new ArithmeticException("integer overflow"))), + ((Int.MaxValue, -1), Expected(new ArithmeticException("integer overflow"))), + ((Int.MaxValue, 1), Expected(new ArithmeticException("integer overflow"))), + ((Int.MaxValue, 1738276576), Expected(new ArithmeticException("integer overflow"))) ) }, existingFeature( @@ -993,17 +991,17 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( { - def success[T](v: T) = Success(Expected(v, 35976)) + def success[T](v: T) = Expected(Success(v), 35976) Seq( - (Long.MinValue, Failure(new ArithmeticException("Byte overflow"))), - (Byte.MinValue.toLong - 1, Failure(new ArithmeticException("Byte overflow"))), + (Long.MinValue, Expected(new ArithmeticException("Byte overflow"))), + (Byte.MinValue.toLong - 1, Expected(new ArithmeticException("Byte overflow"))), (Byte.MinValue.toLong, success(Byte.MinValue)), (-1L, success(-1.toByte)), (0L, success(0.toByte)), (1L, success(1.toByte)), (Byte.MaxValue.toLong, success(Byte.MaxValue)), - (Byte.MaxValue.toLong + 1, Failure(new ArithmeticException("Byte overflow"))), - (Long.MinValue, Failure(new ArithmeticException("Byte overflow"))) + (Byte.MaxValue.toLong + 1, Expected(new ArithmeticException("Byte overflow"))), + (Long.MinValue, Expected(new ArithmeticException("Byte overflow"))) ) }, existingFeature((x: Long) => x.toByteExact, @@ -1012,17 +1010,17 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( { - def success[T](v: T) = Success(Expected(v, 35976)) + def success[T](v: T) = Expected(Success(v), 35976) Seq( - (Long.MinValue, Failure(new ArithmeticException("Short overflow"))), - (Short.MinValue.toLong - 1, Failure(new ArithmeticException("Short overflow"))), + (Long.MinValue, Expected(new ArithmeticException("Short overflow"))), + (Short.MinValue.toLong - 1, Expected(new ArithmeticException("Short overflow"))), (Short.MinValue.toLong, success(Short.MinValue)), (-1L, success(-1.toShort)), (0L, success(0.toShort)), (1L, success(1.toShort)), (Short.MaxValue.toLong, success(Short.MaxValue)), - (Short.MaxValue.toLong + 1, Failure(new ArithmeticException("Short overflow"))), - (Long.MinValue, Failure(new ArithmeticException("Short overflow"))) + (Short.MaxValue.toLong + 1, Expected(new ArithmeticException("Short overflow"))), + (Long.MinValue, Expected(new ArithmeticException("Short overflow"))) ) }, existingFeature((x: Long) => x.toShortExact, @@ -1031,17 +1029,17 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( { - def success[T](v: T) = Success(Expected(v, 35976)) + def success[T](v: T) = Expected(Success(v), 35976) Seq( - (Long.MinValue, Failure(new ArithmeticException("Int overflow"))), - (Int.MinValue.toLong - 1, Failure(new ArithmeticException("Int overflow"))), + (Long.MinValue, Expected(new ArithmeticException("Int overflow"))), + (Int.MinValue.toLong - 1, Expected(new ArithmeticException("Int overflow"))), (Int.MinValue.toLong, success(Int.MinValue)), (-1L, success(-1.toInt)), (0L, success(0.toInt)), (1L, success(1.toInt)), (Int.MaxValue.toLong, success(Int.MaxValue)), - (Int.MaxValue.toLong + 1, Failure(new ArithmeticException("Int overflow"))), - (Long.MinValue, Failure(new ArithmeticException("Int overflow"))) + (Int.MaxValue.toLong + 1, Expected(new ArithmeticException("Int overflow"))), + (Long.MinValue, Expected(new ArithmeticException("Int overflow"))) ) }, existingFeature((x: Long) => x.toIntExact, @@ -1050,7 +1048,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( { - def success[T](v: T) = Success(Expected(v, 35798)) + def success[T](v: T) = Expected(Success(v), 35798) Seq( (Long.MinValue, success(Long.MinValue)), (-1L, success(-1L)), @@ -1065,7 +1063,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( { - def success(v: BigInt) = Success(Expected(v, 35932)) + def success(v: BigInt) = Expected(Success(v), 35932) Seq( (Long.MinValue, success(CBigInt(new BigInteger("-8000000000000000", 16)))), (-1074651039980347209L, success(CBigInt(new BigInteger("-ee9ed6d57885f49", 16)))), @@ -1083,33 +1081,33 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => val n = ExactNumeric.LongIsExactNumeric verifyCases( { - def success[T](v: T) = Success(Expected(v, 39654)) + def success[T](v: T) = Expected(Success(v), 39654) Seq( - ((Long.MinValue, -4677100190307931395L), Failure(new ArithmeticException("long overflow"))), - ((Long.MinValue, -1L), Failure(new ArithmeticException("long overflow"))), - ((Long.MinValue, 1L), Failure(new ArithmeticException("long overflow"))), - ((-9223372036854775808L, 0L), Failure(new ArithmeticException("/ by zero"))), - ((-5828066432064138816L, 9105034716270510411L), Failure(new ArithmeticException("long overflow"))), + ((Long.MinValue, -4677100190307931395L), Expected(new ArithmeticException("long overflow"))), + ((Long.MinValue, -1L), Expected(new ArithmeticException("long overflow"))), + ((Long.MinValue, 1L), Expected(new ArithmeticException("long overflow"))), + ((-9223372036854775808L, 0L), Expected(new ArithmeticException("/ by zero"))), + ((-5828066432064138816L, 9105034716270510411L), Expected(new ArithmeticException("long overflow"))), ((-4564956247298949325L, -1L), success( (-4564956247298949326L, (-4564956247298949324L, (4564956247298949325L, (4564956247298949325L, 0L)))) )), - ((-1499553565058783253L, -3237683216870282569L), Failure(new ArithmeticException("long overflow"))), - ((-1368457031689886112L, 9223372036854775807L), Failure(new ArithmeticException("long overflow"))), + ((-1499553565058783253L, -3237683216870282569L), Expected(new ArithmeticException("long overflow"))), + ((-1368457031689886112L, 9223372036854775807L), Expected(new ArithmeticException("long overflow"))), ((-1L, -4354407074688367443L), success((-4354407074688367444L, (4354407074688367442L, (4354407074688367443L, (0L, -1L)))))), ((-1L, -1L), success((-2L, (0L, (1L, (1L, 0L)))))), ((-1L, 5665019549505434695L), success((5665019549505434694L, (-5665019549505434696L, (-5665019549505434695L, (0L, -1L)))))), ((0L, -1L), success((-1L, (1L, (0L, (0L, 0L)))))), - ((0L, 0L), Failure(new ArithmeticException("/ by zero"))), + ((0L, 0L), Expected(new ArithmeticException("/ by zero"))), ((0L, 2112386634269044172L), success((2112386634269044172L, (-2112386634269044172L, (0L, (0L, 0L)))))), - ((2254604056782701370L, -5878231674026236574L), Failure(new ArithmeticException("long overflow"))), + ((2254604056782701370L, -5878231674026236574L), Expected(new ArithmeticException("long overflow"))), ((2903872550238813643L, 1L), success( (2903872550238813644L, (2903872550238813642L, (2903872550238813643L, (2903872550238813643L, 0L)))) )), - ((5091129735284641762L, -427673944382373638L), Failure(new ArithmeticException("long overflow"))), - ((6029085020194630780L, 2261786144956037939L), Failure(new ArithmeticException("long overflow"))), - ((8126382074515995418L, -4746652047588907829L), Failure(new ArithmeticException("long overflow"))), - ((Long.MaxValue, 1L), Failure(new ArithmeticException("long overflow"))), - ((Long.MaxValue, -1L), Failure(new ArithmeticException("long overflow"))) + ((5091129735284641762L, -427673944382373638L), Expected(new ArithmeticException("long overflow"))), + ((6029085020194630780L, 2261786144956037939L), Expected(new ArithmeticException("long overflow"))), + ((8126382074515995418L, -4746652047588907829L), Expected(new ArithmeticException("long overflow"))), + ((Long.MaxValue, 1L), Expected(new ArithmeticException("long overflow"))), + ((Long.MaxValue, -1L), Expected(new ArithmeticException("long overflow"))) ) }, existingFeature( @@ -1198,7 +1196,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => property("BigInt methods equivalence") { verifyCases( { - def success(v: BigInt) = Success(Expected(v, 35798)) + def success(v: BigInt) = Expected(Success(v), 35798) Seq( (CBigInt(new BigInteger("-85102d7f884ca0e8f56193b46133acaf7e4681e1757d03f191ae4f445c8e0", 16)), success( CBigInt(new BigInteger("-85102d7f884ca0e8f56193b46133acaf7e4681e1757d03f191ae4f445c8e0", 16)) @@ -1220,15 +1218,15 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => val n = NumericOps.BigIntIsExactNumeric verifyCases( { - def success(v: (BigInt, (BigInt, (BigInt, (BigInt, BigInt))))) = Success(Expected(v, 39774)) + def success(v: (BigInt, (BigInt, (BigInt, (BigInt, BigInt))))) = Expected(Success(v), 39774) Seq( ((CBigInt(new BigInteger("-8683d1cd99d5fcf0e6eff6295c285c36526190e13dbde008c49e5ae6fddc1c", 16)), CBigInt(new BigInteger("-2ef55db3f245feddacf0182e299dd", 16))), - Failure(new ArithmeticException("BigInteger out of 256 bit range"))), + Expected(new ArithmeticException("BigInteger out of 256 bit range"))), ((CBigInt(new BigInteger("-68e1136872f98fb0245ec5aa4bef46e16273e860746c892", 16)), CBigInt(new BigInteger("-352aaa769b41a327", 16))), - Failure(new ArithmeticException("BigInteger: modulus not positive"))), + Expected(new ArithmeticException("BigInteger: modulus not positive"))), ((CBigInt(new BigInteger("-39fc00ebf09080cbd8408dd38c4b7490bea533447047140", 16)), CBigInt(new BigInteger("31de9e96177dbd39", 16))), @@ -1247,23 +1245,23 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => (CBigInt(new BigInteger("-1", 16)), CBigInt(new BigInteger("0", 16)))))) )), ((CBigInt(new BigInteger("-47dede8d3e4804bb", 16)), CBigInt(new BigInteger("-388828eb6dfce683", 16))), - Failure(new ArithmeticException("BigInteger: modulus not positive"))), + Expected(new ArithmeticException("BigInteger: modulus not positive"))), ((CBigInt(new BigInteger("-4fde491150ea00d", 16)), CBigInt(new BigInteger("-80000001", 16))), - Failure(new ArithmeticException("BigInteger: modulus not positive"))), + Expected(new ArithmeticException("BigInteger: modulus not positive"))), ((CBigInt(new BigInteger("-80000001", 16)), CBigInt(new BigInteger("-80000001", 16))), - Failure(new ArithmeticException("BigInteger: modulus not positive"))), + Expected(new ArithmeticException("BigInteger: modulus not positive"))), ((CBigInt(new BigInteger("0", 16)), CBigInt(new BigInteger("-8000000000000000", 16))), - Failure(new ArithmeticException("BigInteger: modulus not positive"))), + Expected(new ArithmeticException("BigInteger: modulus not positive"))), ((CBigInt(new BigInteger("0", 16)), CBigInt(new BigInteger("0", 16))), - Failure(new ArithmeticException("BigInteger divide by zero"))), + Expected(new ArithmeticException("BigInteger divide by zero"))), ((CBigInt(new BigInteger("1", 16)), CBigInt(new BigInteger("-86063f66e06d6d535c95862cd506309a95d10102422fee", 16))), - Failure(new ArithmeticException("BigInteger: modulus not positive"))), + Expected(new ArithmeticException("BigInteger: modulus not positive"))), ((CBigInt(new BigInteger("80000000", 16)), CBigInt(new BigInteger("4e592ce5b544b8f7a91f97ec9ea2f2c3660111360297a4", 16))), success(( @@ -1274,11 +1272,11 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => ((CBigInt(new BigInteger("3d31398dc4783303", 16)), CBigInt(new BigInteger("-37b381db4e6e927e202a2a421d5a09ca", 16))), - Failure(new ArithmeticException("BigInteger: modulus not positive"))), + Expected(new ArithmeticException("BigInteger: modulus not positive"))), ((CBigInt(new BigInteger("5524814a26357cb71488b6fb26af2d3", 16)), CBigInt(new BigInteger("c413b7d975a9972427f46996299fe57cfe79479ac954a7", 16))), - Failure(new ArithmeticException("BigInteger out of 256 bit range"))) + Expected(new ArithmeticException("BigInteger out of 256 bit range"))) ) }, existingFeature( @@ -1342,7 +1340,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => } property("BigInt methods equivalence (new features)") { - // TODO HF: the behavior of `upcast` for BigInt is different from all other Numeric types + // TODO HF (2h): the behavior of `upcast` for BigInt is different from all other Numeric types // The `Upcast(bigInt, SBigInt)` node is never produced by ErgoScript compiler, but is still valid ErgoTree. // It makes sense to fix this inconsistency as part of HF assertExceptionThrown( @@ -1350,7 +1348,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => _.getMessage.contains("Cannot upcast value") ) - // TODO HF: the behavior of `downcast` for BigInt is different from all other Numeric types + // TODO HF (2h): the behavior of `downcast` for BigInt is different from all other Numeric types // The `Downcast(bigInt, SBigInt)` node is never produced by ErgoScript compiler, but is still valid ErgoTree. // It makes sense to fix this inconsistency as part of HF assertExceptionThrown( @@ -1402,7 +1400,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( { - def success[T](v: T) = Success(Expected(v, 37905)) + def success[T](v: T) = Expected(Success(v), 37905) Seq( (Helpers.decodeGroupElement(ge1), success(Helpers.decodeBytes(ge1))), (Helpers.decodeGroupElement(ge2), success(Helpers.decodeBytes(ge2))), @@ -1422,7 +1420,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( { - def success[T](v: T) = Success(Expected(v, 38340)) + def success[T](v: T) = Expected(Success(v), 38340) Seq( (Helpers.decodeGroupElement(ge1), success(true)), (Helpers.decodeGroupElement(ge2), success(true)), @@ -1450,7 +1448,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( { - def success[T](v: T) = Success(Expected(v, 36292)) + def success[T](v: T) = Expected(Success(v), 36292) Seq( (Helpers.decodeGroupElement(ge1), success(Helpers.decodeGroupElement("02358d53f01276211f92d0aefbd278805121d4ff6eb534b777af1ee8abae5b2056"))), (Helpers.decodeGroupElement(ge2), success(Helpers.decodeGroupElement("03dba7b94b111f3894e2f9120b577da595ec7d58d488485adf73bf4e153af63575"))), @@ -1466,13 +1464,13 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => MethodCall(ValUse(1, SGroupElement), SGroupElement.getMethodByName("negate"), Vector(), Map()) ))) - //TODO HF: related to https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479 + // TODO HF (3h): related to https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479 // val isIdentity = existingFeature({ (x: GroupElement) => x.isIdentity }, // "{ (x: GroupElement) => x.isIdentity }") verifyCases( { - def success[T](v: T) = Success(Expected(v, 41484)) + def success[T](v: T) = Expected(Success(v), 41484) Seq( ((Helpers.decodeGroupElement(ge1), CBigInt(new BigInteger("-25c80b560dd7844e2efd10f80f7ee57d", 16))), success(Helpers.decodeGroupElement("023a850181b7b73f92a5bbfa0bfc78f5bbb6ff00645ddde501037017e1a2251e2e"))), @@ -1502,7 +1500,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( { - def success[T](v: T) = Success(Expected(v, 36457)) + def success[T](v: T) = Expected(Success(v), 36457) Seq( ((Helpers.decodeGroupElement(ge1), Helpers.decodeGroupElement("03e132ca090614bd6c9f811e91f6daae61f16968a1e6c694ed65aacd1b1092320e")), success(Helpers.decodeGroupElement("02bc48937b4a66f249a32dfb4d2efd0743dc88d46d770b8c5d39fd03325ba211df"))), @@ -1570,7 +1568,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( { - def success[T](v: T) = Success(Expected(v, 36182)) + def success[T](v: T) = Expected(Success(v), 36182) Seq( (t1, success(Helpers.decodeBytes("000183807f66b301530120ff7fc6bd6601ff01ff7f7d2bedbbffff00187fe89094"))), (t2, success(Helpers.decodeBytes("ff000d937f80ffd731ed802d24358001ff8080ff71007f00ad37e0a7ae43fff95b"))), @@ -1583,7 +1581,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( { - def success[T](v: T) = Success(Expected(v, 36260)) + def success[T](v: T) = Expected(Success(v), 36260) Seq( (t1, success(6.toByte)), (t2, success(0.toByte)), @@ -1596,7 +1594,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( { - def success[T](v: T) = Success(Expected(v, 36136)) + def success[T](v: T) = Expected(Success(v), 36136) Seq( (t1, success(1)), (t2, success(32)), @@ -1609,7 +1607,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( { - def success[T](v: T) = Success(Expected(v, 37151)) + def success[T](v: T) = Expected(Success(v), 37151) Seq( (t1, success(Some(1))), (t2, success(Some(64))), @@ -1622,7 +1620,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( { - def success[T](v: T) = Success(Expected(v, 36479)) + def success[T](v: T) = Expected(Success(v), 36479) Seq( (t1, success(false)), (t2, success(false)), @@ -1635,7 +1633,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( { - def success[T](v: T) = Success(Expected(v, 36096)) + def success[T](v: T) = Expected(Success(v), 36096) Seq( (t1, success(true)), (t2, success(false)), @@ -1648,7 +1646,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( { - def success[T](v: T) = Success(Expected(v, 36502)) + def success[T](v: T) = Expected(Success(v), 36502) Seq( (t1, success(true)), (t2, success(false)), @@ -1851,7 +1849,13 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => val (key, value, _, avlProver) = sampleAvlProver val otherKey = key.map(x => (-x).toByte) // any other different from key - val table = Table(("key", "contains", "valueOpt"), (key, true, Some(value)), (otherKey, false, None)) + val table = Table(("key", "contains", "valueOpt"), + (key, true, Some(value)), + (otherKey, false, None) + ) + + def success[T](v: T) = Expected(Success(v), 0) + forAll(table) { (key, okContains, valueOpt) => avlProver.performOneOperation(Lookup(ADKey @@ key.toArray)) val proof = avlProver.generateProof().toColl @@ -1861,11 +1865,11 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => // positive test { val input = (tree, (key, proof)) - contains.checkExpected(input, okContains) - get.checkExpected(input, valueOpt) + contains.checkExpected(input, success(okContains)) + get.checkExpected(input, success(valueOpt)) - contains.checkVerify(input, expectedRes = okContains, expectedCost = 37850) - get.checkVerify(input, expectedRes = valueOpt, expectedCost = 38372) + contains.checkVerify(input, Expected(value = Success(okContains), cost = 37850)) + get.checkVerify(input, Expected(value = Success(valueOpt), cost = 38372)) } val keys = Colls.fromItems(key) @@ -1873,15 +1877,15 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => { val input = (tree, (keys, proof)) - getMany.checkExpected(input, expRes) - getMany.checkVerify(input, expectedRes = expRes, expectedCost = 38991) + getMany.checkExpected(input, success(expRes)) + getMany.checkVerify(input, Expected(value = Success(expRes), cost = 38991)) } { val input = (tree, digest) val (res, _) = updateDigest.checkEquality(input).getOrThrow res.digest shouldBe digest - updateDigest.checkVerify(input, expectedRes = res, expectedCost = 36341) + updateDigest.checkVerify(input, Expected(value = Success(res), cost = 36341)) } val newOps = 1.toByte @@ -1890,7 +1894,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => val input = (tree, newOps) val (res,_) = updateOperations.checkEquality(input).getOrThrow res.enabledOperations shouldBe newOps - updateOperations.checkVerify(input, expectedRes = res, expectedCost = 36341) + updateOperations.checkVerify(input, Expected(value = Success(res), cost = 36341)) } // negative tests: invalid proof @@ -1900,7 +1904,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => val input = (tree, (key, invalidProof)) val (res, _) = contains.checkEquality(input).getOrThrow res shouldBe false - contains.checkVerify(input, expectedRes = res, expectedCost = 37850) + contains.checkVerify(input, Expected(value = Success(res), cost = 37850)) } { @@ -2023,7 +2027,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => val input = (preInsertTree, (kvs, insertProof)) val (res, _) = insert.checkEquality(input).getOrThrow res.isDefined shouldBe true - insert.checkVerify(input, expectedRes = res, expectedCost = 38501) + insert.checkVerify(input, Expected(value = Success(res), cost = 38501)) } { // negative: readonly tree @@ -2031,7 +2035,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => val input = (readonlyTree, (kvs, insertProof)) val (res, _) = insert.checkEquality(input).getOrThrow res.isDefined shouldBe false - insert.checkVerify(input, expectedRes = res, expectedCost = 38501) + insert.checkVerify(input, Expected(value = Success(res), cost = 38501)) } { // negative: invalid key @@ -2041,7 +2045,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => val input = (tree, (invalidKvs, insertProof)) val (res, _) = insert.checkEquality(input).getOrThrow res.isDefined shouldBe true // TODO HF: should it really be true? (looks like a bug) - insert.checkVerify(input, expectedRes = res, expectedCost = 38501) + insert.checkVerify(input, Expected(value = Success(res), cost = 38501)) } { // negative: invalid proof @@ -2123,6 +2127,8 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => )) val cost = 40952 + def success[T](v: T) = Expected(Success(v), 0) + forAll(keyCollGen, bytesCollGen) { (key, value) => val (_, avlProver) = createAvlTreeAndProver(key -> value) val preUpdateDigest = avlProver.digest.toColl @@ -2136,8 +2142,8 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => val endTree = preUpdateTree.updateDigest(endDigest) val input = (preUpdateTree, (kvs, updateProof)) val res = Some(endTree) - update.checkExpected(input, res) - update.checkVerify(input, expectedRes = res, expectedCost = cost) + update.checkExpected(input, success(res)) + update.checkVerify(input, Expected(value = Success(res), cost = cost)) } { // positive: update to the same value (identity operation) @@ -2145,15 +2151,15 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => val keys = Colls.fromItems((key -> value)) val input = (tree, (keys, updateProof)) val res = Some(tree) - update.checkExpected(input, res) - update.checkVerify(input, expectedRes = res, expectedCost = cost) + update.checkExpected(input, success(res)) + update.checkVerify(input, Expected(value = Success(res), cost = cost)) } { // negative: readonly tree val readonlyTree = createTree(preUpdateDigest) val input = (readonlyTree, (kvs, updateProof)) - update.checkExpected(input, None) - update.checkVerify(input, expectedRes = None, expectedCost = cost) + update.checkExpected(input, success(None)) + update.checkVerify(input, Expected(value = Success(None), cost = cost)) } { // negative: invalid key @@ -2161,8 +2167,8 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => val invalidKey = key.map(x => (-x).toByte) // any other different from key val invalidKvs = Colls.fromItems((invalidKey -> newValue)) val input = (tree, (invalidKvs, updateProof)) - update.checkExpected(input, None) - update.checkVerify(input, expectedRes = None, expectedCost = cost) + update.checkExpected(input, success(None)) + update.checkVerify(input, Expected(value = Success(None), cost = cost)) } { // negative: invalid value (different from the value in the proof) @@ -2172,15 +2178,15 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => val input = (tree, (invalidKvs, updateProof)) val (res, _) = update.checkEquality(input).getOrThrow res.isDefined shouldBe true // TODO HF: should it really be true? (looks like a bug) - update.checkVerify(input, expectedRes = res, expectedCost = cost) + update.checkVerify(input, Expected(value = Success(res), cost = cost)) } { // negative: invalid proof val tree = createTree(preUpdateDigest, updateAllowed = true) val invalidProof = updateProof.map(x => (-x).toByte) // any other different from proof val input = (tree, (kvs, invalidProof)) - update.checkExpected(input, None) - update.checkVerify(input, expectedRes = None, expectedCost = cost) + update.checkExpected(input, success(None)) + update.checkVerify(input, Expected(value = Success(None), cost = cost)) } } } @@ -2222,6 +2228,8 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => ) )) + def success[T](v: T) = Expected(Success(v), 0) + forAll(keyCollGen, bytesCollGen) { (key, value) => val (_, avlProver) = createAvlTreeAndProver(key -> value) val preRemoveDigest = avlProver.digest.toColl @@ -2235,15 +2243,15 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => val endTree = preRemoveTree.updateDigest(endDigest) val input = (preRemoveTree, (keys, removeProof)) val res = Some(endTree) - remove.checkExpected(input, res) - remove.checkVerify(input, expectedRes = res, expectedCost = cost) + remove.checkExpected(input, success(res)) + remove.checkVerify(input, Expected(value = Success(res), cost = cost)) } { // negative: readonly tree val readonlyTree = createTree(preRemoveDigest) val input = (readonlyTree, (keys, removeProof)) - remove.checkExpected(input, None) - remove.checkVerify(input, expectedRes = None, expectedCost = cost) + remove.checkExpected(input, success(None)) + remove.checkVerify(input, Expected(value = Success(None), cost = cost)) } { // negative: invalid key @@ -2251,16 +2259,16 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => val invalidKey = key.map(x => (-x).toByte) // any other different from `key` val invalidKeys = Colls.fromItems(invalidKey) val input = (tree, (invalidKeys, removeProof)) - remove.checkExpected(input, None) - remove.checkVerify(input, expectedRes = None, expectedCost = cost) + remove.checkExpected(input, success(None)) + remove.checkVerify(input, Expected(value = Success(None), cost = cost)) } { // negative: invalid proof val tree = createTree(preRemoveDigest, removeAllowed = true) val invalidProof = removeProof.map(x => (-x).toByte) // any other different from `removeProof` val input = (tree, (keys, invalidProof)) - remove.checkExpected(input, None) - remove.checkVerify(input, expectedRes = None, expectedCost = cost) + remove.checkExpected(input, success(None)) + remove.checkVerify(input, Expected(value = Success(None), cost = cost)) } } } @@ -2268,7 +2276,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => property("longToByteArray equivalence") { verifyCases( { - def success[T](v: T) = Success(Expected(v, 36007)) + def success[T](v: T) = Expected(Success(v), 36007) Seq( (-9223372036854775808L, success(Helpers.decodeBytes("8000000000000000"))), (-1148502660425090565L, success(Helpers.decodeBytes("f00fb2ea55c579fb"))), @@ -2287,10 +2295,10 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => property("byteArrayToBigInt equivalence") { verifyCases( { - def success[T](v: T) = Success(Expected(v, 36536)) + def success[T](v: T) = Expected(Success(v), 36536) Seq( (Helpers.decodeBytes(""), - Failure(new NumberFormatException("Zero length BigInteger"))), + Expected(new NumberFormatException("Zero length BigInteger"))), (Helpers.decodeBytes("00"), success(CBigInt(new BigInteger("0", 16)))), (Helpers.decodeBytes("01"), @@ -2298,12 +2306,12 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => (Helpers.decodeBytes("ff"), success(CBigInt(new BigInteger("-1", 16)))), (Helpers.decodeBytes("80d6c201"), - Success(Expected(CBigInt(new BigInteger("-7f293dff", 16)), 36539))), + Expected(Success(CBigInt(new BigInteger("-7f293dff", 16))), 36539)), (Helpers.decodeBytes("70d6c20170d6c201"), - Success(Expected(CBigInt(new BigInteger("70d6c20170d6c201", 16)), 36543))), + Expected(Success(CBigInt(new BigInteger("70d6c20170d6c201", 16))), 36543)), (Helpers.decodeBytes( "80e0ff7f02807fff72807f0a00ff7fb7c57f75c11ba2802970fd250052807fc37f6480ffff007fff18eeba44" - ), Failure(new ArithmeticException("BigInteger out of 256 bit range"))) + ), Expected(new ArithmeticException("BigInteger out of 256 bit range"))) ) }, existingFeature((x: Coll[Byte]) => SigmaDsl.byteArrayToBigInt(x), @@ -2314,11 +2322,11 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => property("byteArrayToLong equivalence") { verifyCases( { - def success[T](v: T) = Success(Expected(v, 36092)) + def success[T](v: T) = Expected(Success(v), 36092) Seq( - (Helpers.decodeBytes(""), Failure(new IllegalArgumentException("array too small: 0 < 8"))), - (Helpers.decodeBytes("81"), Failure(new IllegalArgumentException("array too small: 1 < 8"))), - (Helpers.decodeBytes("812d7f00ff807f"), Failure(new IllegalArgumentException("array too small: 7 < 8"))), + (Helpers.decodeBytes(""), Expected(new IllegalArgumentException("array too small: 0 < 8"))), + (Helpers.decodeBytes("81"), Expected(new IllegalArgumentException("array too small: 1 < 8"))), + (Helpers.decodeBytes("812d7f00ff807f"), Expected(new IllegalArgumentException("array too small: 7 < 8"))), (Helpers.decodeBytes("812d7f00ff807f7f"), success(-9138508426601529473L)), (Helpers.decodeBytes("ffffffffffffffff"), success(-1L)), (Helpers.decodeBytes("0000000000000000"), success(0L)), @@ -2421,7 +2429,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( { - def success[T](v: T) = Success(Expected(v, 35984)) + def success[T](v: T) = Expected(Success(v), 35984) Seq( (b1, success(Helpers.decodeBytes("5ee78f30ae4e770e44900a46854e9fecb6b12e8112556ef1cd19aef633b4421e"))), (b2, success(Helpers.decodeBytes("3a0089be265460e29ca47d26e5b55a6f3e3ffaf5b4aed941410a2437913848ad"))) @@ -2433,7 +2441,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( { - def success[T](v: T) = Success(Expected(v, 35882)) + def success[T](v: T) = Expected(Success(v), 35882) Seq( (b1, success(9223372036854775807L)), (b2, success(12345L)) @@ -2445,7 +2453,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( { - def success[T](v: T) = Success(Expected(v, 35938)) + def success[T](v: T) = Expected(Success(v), 35938) Seq( (b1, success(Helpers.decodeBytes( "100108cd0297c44a12f4eb99a85d298fa3ba829b5b42b9f63798c980ece801cc663cc5fc9e7300" @@ -2459,7 +2467,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( { - def success[T](v: T) = Success(Expected(v, 36012)) + def success[T](v: T) = Expected(Success(v), 36012) Seq( (b1, success(Helpers.decodeBytes( "ffffffffffffffff7f100108cd0297c44a12f4eb99a85d298fa3ba829b5b42b9f63798c980ece801cc663cc5fc9e73009fac29026e789ab7b2fffff12280a6cd01557f6fb22b7f80ff7aff8e1f7f15973d7f000180ade204a3ff007f00057600808001ff8f8000019000ffdb806fff7cc0b6015eb37fa600f4030201000e067fc87f7f01ff218301ae8000018008637f0021fb9e00018055486f0b514121016a00ff718080bcb001" @@ -2475,7 +2483,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( { - def success[T](v: T) = Success(Expected(v, 35954)) + def success[T](v: T) = Expected(Success(v), 35954) Seq( (b1, success(Helpers.decodeBytes( "ffffffffffffffff7f100108cd0297c44a12f4eb99a85d298fa3ba829b5b42b9f63798c980ece801cc663cc5fc9e73009fac29026e789ab7b2fffff12280a6cd01557f6fb22b7f80ff7aff8e1f7f15973d7f000180ade204a3ff007f00057600808001ff8f8000019000ffdb806fff7cc0b6015eb37fa600f4030201000e067fc87f7f01ff" @@ -2491,7 +2499,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( { - def success[T](v: T) = Success(Expected(v, 36074)) + def success[T](v: T) = Expected(Success(v), 36074) Seq( (b1, success(( 677407, @@ -2507,15 +2515,15 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => "{ (x: Box) => x.creationInfo }", FuncValue(Vector((1, SBox)), ExtractCreationInfo(ValUse(1, SBox))))) - // TODO HF: fix collections equality and remove map(identity) + // TODO HF (2h): fix collections equality and remove map(identity) // (PairOfColl should be equal CollOverArray) verifyCases( Seq( - b1 -> Success(Expected(Coll[(Coll[Byte], Long)]( + b1 -> Expected(Success(Coll[(Coll[Byte], Long)]( (Helpers.decodeBytes("6e789ab7b2fffff12280a6cd01557f6fb22b7f80ff7aff8e1f7f15973d7f0001"), 10000000L), (Helpers.decodeBytes("a3ff007f00057600808001ff8f8000019000ffdb806fff7cc0b6015eb37fa600"), 500L) - ).map(identity), 36167)), - b2 -> Success(Expected(Coll[(Coll[Byte], Long)]().map(identity), 36157)) + ).map(identity)), 36167), + b2 -> Expected(Success(Coll[(Coll[Byte], Long)]().map(identity)), 36157) ), existingFeature({ (x: Box) => x.tokens }, "{ (x: Box) => x.tokens }", @@ -2531,7 +2539,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => } property("Box properties equivalence (new features)") { - // TODO HF: related to https://github.com/ScorexFoundation/sigmastate-interpreter/issues/416 + // TODO HF (4h): related to https://github.com/ScorexFoundation/sigmastate-interpreter/issues/416 val getReg = newFeature((x: Box) => x.getReg[Int](1).get, "{ (x: Box) => x.getReg[Int](1).get }") @@ -2540,6 +2548,128 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => } } + property("Conditional access to registers") { + def boxWithRegisters(regs: AdditionalRegisters): Box = { + SigmaDsl.Box(testBox(20, TrueProp, 0, Seq(), regs)) + } + val box1 = boxWithRegisters(Map( + ErgoBox.R4 -> ByteConstant(0.toByte), + ErgoBox.R5 -> ShortConstant(1024.toShort) + )) + val box2 = boxWithRegisters(Map( + ErgoBox.R4 -> ByteConstant(1.toByte), + ErgoBox.R5 -> IntConstant(1024 * 1024) + )) + val box3 = boxWithRegisters(Map( + ErgoBox.R4 -> ByteConstant(2.toByte) + )) + val box4 = boxWithRegisters(Map.empty) + + verifyCases( + Seq( + (box1, Expected(Success(1024.toShort), cost = 37190)), + (box2, Expected( + new InvalidType("Cannot getReg[Short](5): invalid type of value TestValue(1048576) at id=5") + )), + (box3, Expected(Success(0.toShort), cost = 37190)) + ), + existingFeature( + { (x: Box) => + val tagOpt = x.R5[Short] + if (tagOpt.isDefined) { + tagOpt.get + } else { + 0.toShort + } + }, + """{ (x: Box) => + | val tagOpt = x.R5[Short] + | if (tagOpt.isDefined) { + | tagOpt.get + | } else { + | 0.toShort + | } + |}""".stripMargin, + FuncValue( + Array((1, SBox)), + BlockValue( + Array(ValDef(3, List(), ExtractRegisterAs(ValUse(1, SBox), ErgoBox.R5, SOption(SShort)))), + If( + OptionIsDefined(ValUse(3, SOption(SShort))), + OptionGet(ValUse(3, SOption(SShort))), + ShortConstant(0.toShort) + ) + ) + ))) + + verifyCases( + Seq( + (box1, Expected(Success(1024), cost = 39782)), + (box2, Expected(Success(1024 * 1024), cost = 39782)), + (box3, Expected(Success(0), cost = 39782)), + (box4, Expected(Success(-1), cost = 39782)) + ), + existingFeature( + { (x: Box) => + val tagOpt = x.R4[Byte] + if (tagOpt.isDefined) { + val tag = tagOpt.get + if (tag == 0.toByte) { + val short = x.R5[Short].get // access Short in the register + short.toInt + } else { + if (tag == 1.toByte) { + x.R5[Int].get // access Int in the register + } + else 0 + } + } else { + -1 + } + }, + """{ + | (x: Box) => + | val tagOpt = x.R4[Byte] + | if (tagOpt.isDefined) { + | val tag = tagOpt.get + | if (tag == 0.toByte) { + | val short = x.R5[Short].get // access Short in the register + | short.toInt + | } else { + | if (tag == 1.toByte) { + | x.R5[Int].get // access Int in the register + | } + | else 0 + | } + | } else { + | -1 + | } + |}""".stripMargin, + FuncValue( + Array((1, SBox)), + BlockValue( + Array(ValDef(3, List(), ExtractRegisterAs(ValUse(1, SBox), ErgoBox.R4, SOption(SByte)))), + If( + OptionIsDefined(ValUse(3, SOption(SByte))), + BlockValue( + Array(ValDef(4, List(), OptionGet(ValUse(3, SOption(SByte))))), + If( + EQ(ValUse(4, SByte), ByteConstant(0.toByte)), + Upcast(OptionGet(ExtractRegisterAs(ValUse(1, SBox), ErgoBox.R5, SOption(SShort))), SInt), + If( + EQ(ValUse(4, SByte), ByteConstant(1.toByte)), + OptionGet(ExtractRegisterAs(ValUse(1, SBox), ErgoBox.R5, SOption(SInt))), + IntConstant(0) + ) + ) + ), + IntConstant(-1) + ) + ) + ) + )) + } + property("Advanced Box test") { val (tree, _) = createAvlTreeAndProver() @@ -2558,8 +2688,8 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( Seq( - (box1, Success(Expected(1.toByte, cost = 36253))), - (box2, Failure(new InvalidType("Cannot getReg[Byte](4): invalid type of value Value(Coll(1)) at id=4"))) + (box1, Expected(Success(1.toByte), cost = 36253)), + (box2, Expected(new InvalidType("Cannot getReg[Byte](4): invalid type of value Value(Coll(1)) at id=4"))) ), existingFeature((x: Box) => x.R4[Byte].get, "{ (x: Box) => x.R4[Byte].get }", @@ -2570,8 +2700,8 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( Seq( - (box1, Success(Expected(1024.toShort, cost = 36253))), - (box2, Failure(new NoSuchElementException("None.get"))) + (box1, Expected(Success(1024.toShort), cost = 36253)), + (box2, Expected(new NoSuchElementException("None.get"))) ), existingFeature((x: Box) => x.R5[Short].get, "{ (x: Box) => x.R5[Short].get }", @@ -2582,7 +2712,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( Seq( - (box1, Success(Expected(1024 * 1024, cost = 36253))) + (box1, Expected(Success(1024 * 1024), cost = 36253)) ), existingFeature((x: Box) => x.R6[Int].get, "{ (x: Box) => x.R6[Int].get }", @@ -2593,7 +2723,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( Seq( - (box1, Success(Expected(1024.toLong, cost = 36253))) + (box1, Expected(Success(1024.toLong), cost = 36253)) ), existingFeature((x: Box) => x.R7[Long].get, "{ (x: Box) => x.R7[Long].get }", @@ -2604,7 +2734,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( Seq( - (box1, Success(Expected(CBigInt(BigInteger.valueOf(222L)), cost = 36253))) + (box1, Expected(Success(CBigInt(BigInteger.valueOf(222L))), cost = 36253)) ), existingFeature((x: Box) => x.R8[BigInt].get, "{ (x: Box) => x.R8[BigInt].get }", @@ -2615,7 +2745,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( Seq( - (box1, Success(Expected(CAvlTree( + (box1, Expected(Success(CAvlTree( AvlTreeData( ADDigest @@ ( ErgoAlgos.decodeUnsafe("4ec61f485b98eb87153f7c57db4f5ecd75556fddbc403b41acf8441fde8e160900") @@ -2624,8 +2754,8 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => 32, None ) - ), cost = 36253))) - ), + )), cost = 36253) + )), existingFeature((x: Box) => x.R9[AvlTree].get, "{ (x: Box) => x.R9[AvlTree].get }", FuncValue( @@ -2658,35 +2788,35 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => ) verifyCases( - Seq((h1, Success(Expected(0.toByte, cost = 37022)))), + Seq((h1, Expected(Success(0.toByte), cost = 37022))), existingPropTest("version", { (x: PreHeader) => x.version })) verifyCases( - Seq((h1, Success(Expected( - Helpers.decodeBytes("7fff7fdd6f62018bae0001006d9ca888ff7f56ff8006573700a167f17f2c9f40"), - cost = 36121)))), + Seq((h1, Expected(Success( + Helpers.decodeBytes("7fff7fdd6f62018bae0001006d9ca888ff7f56ff8006573700a167f17f2c9f40")), + cost = 36121))), existingPropTest("parentId", { (x: PreHeader) => x.parentId })) verifyCases( - Seq((h1, Success(Expected(6306290372572472443L, cost = 36147)))), + Seq((h1, Expected(Success(6306290372572472443L), cost = 36147))), existingPropTest("timestamp", { (x: PreHeader) => x.timestamp })) verifyCases( - Seq((h1, Success(Expected(-3683306095029417063L, cost = 36127)))), + Seq((h1, Expected(Success(-3683306095029417063L), cost = 36127))), existingPropTest("nBits", { (x: PreHeader) => x.nBits })) verifyCases( - Seq((h1, Success(Expected(1, cost = 36136)))), + Seq((h1, Expected(Success(1), cost = 36136))), existingPropTest("height", { (x: PreHeader) => x.height })) verifyCases( - Seq((h1, Success(Expected( - Helpers.decodeGroupElement("026930cb9972e01534918a6f6d6b8e35bc398f57140d13eb3623ea31fbd069939b"), - cost = 36255)))), + Seq((h1, Expected(Success( + Helpers.decodeGroupElement("026930cb9972e01534918a6f6d6b8e35bc398f57140d13eb3623ea31fbd069939b")), + cost = 36255))), existingPropTest("minerPk", { (x: PreHeader) => x.minerPk })) verifyCases( - Seq((h1, Success(Expected(Helpers.decodeBytes("ff8087"), cost = 36249)))), + Seq((h1, Expected(Success(Helpers.decodeBytes("ff8087")), cost = 36249))), existingPropTest("votes", { (x: PreHeader) => x.votes })) } @@ -2718,89 +2848,87 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => ) verifyCases( - Seq((h1, Success(Expected( - Helpers.decodeBytes("957f008001808080ffe4ffffc8f3802401df40006aa05e017fa8d3f6004c804a"), - cost = 36955)))), + Seq((h1, Expected(Success( + Helpers.decodeBytes("957f008001808080ffe4ffffc8f3802401df40006aa05e017fa8d3f6004c804a")), + cost = 36955))), existingPropTest("id", { (x: Header) => x.id })) verifyCases( - Seq((h1, Success(Expected(0.toByte, cost = 36124)))), + Seq((h1, Expected(Success(0.toByte), cost = 36124))), existingPropTest("version", { (x: Header) => x.version })) verifyCases( - Seq((h1, Success(Expected( - Helpers.decodeBytes("0180dd805b0000ff5400b997fd7f0b9b00de00fb03c47e37806a8186b94f07ff"), - cost = 36097)))), + Seq((h1, Expected(Success( + Helpers.decodeBytes("0180dd805b0000ff5400b997fd7f0b9b00de00fb03c47e37806a8186b94f07ff")), + cost = 36097))), existingPropTest("parentId", { (x: Header) => x.parentId })) verifyCases( - Seq((h1, Success(Expected( - Helpers.decodeBytes("01f07f60d100ffb970c3007f60ff7f24d4070bb8fffa7fca7f34c10001ffe39d"), - cost = 36111)))), + Seq((h1, Expected(Success( + Helpers.decodeBytes("01f07f60d100ffb970c3007f60ff7f24d4070bb8fffa7fca7f34c10001ffe39d")), + cost = 36111))), existingPropTest("ADProofsRoot", { (x: Header) => x.ADProofsRoot})) verifyCases( - Seq((h1, Success(Expected(CAvlTree(treeData), cost = 36092)))), + Seq((h1, Expected(Success(CAvlTree(treeData)), cost = 36092))), existingPropTest("stateRoot", { (x: Header) => x.stateRoot })) verifyCases( - Seq((h1, Success(Expected( - Helpers.decodeBytes("804101ff01000080a3ffbd006ac080098df132a7017f00649311ec0e00000100"), - cost = 36094)))), + Seq((h1, Expected(Success( + Helpers.decodeBytes("804101ff01000080a3ffbd006ac080098df132a7017f00649311ec0e00000100")), + cost = 36094))), existingPropTest("transactionsRoot", { (x: Header) => x.transactionsRoot })) verifyCases( - Seq((h1, Success(Expected(1L, cost = 36151)))), + Seq((h1, Expected(Success(1L), cost = 36151))), existingPropTest("timestamp", { (x: Header) => x.timestamp })) verifyCases( - Seq((h1, Success(Expected(-1L, cost = 36125)))), + Seq((h1, Expected(Success(-1L), cost = 36125))), existingPropTest("nBits", { (x: Header) => x.nBits })) verifyCases( - Seq((h1, Success(Expected(1, cost = 36134)))), + Seq((h1, Expected(Success(1), cost = 36134))), existingPropTest("height", { (x: Header) => x.height })) verifyCases( - Seq((h1, Success(Expected( - Helpers.decodeBytes("e57f80885601b8ff348e01808000bcfc767f2dd37f0d01015030ec018080bc62"), - cost = 36133)))), + Seq((h1, Expected(Success( + Helpers.decodeBytes("e57f80885601b8ff348e01808000bcfc767f2dd37f0d01015030ec018080bc62")), + cost = 36133))), existingPropTest("extensionRoot", { (x: Header) => x.extensionRoot })) verifyCases( - Seq((h1, Success(Expected( - Helpers.decodeGroupElement("039bdbfa0b49cc6bef58297a85feff45f7bbeb500a9d2283004c74fcedd4bd2904"), - cost = 36111)))), + Seq((h1, Expected(Success( + Helpers.decodeGroupElement("039bdbfa0b49cc6bef58297a85feff45f7bbeb500a9d2283004c74fcedd4bd2904")), + cost = 36111))), existingPropTest("minerPk", { (x: Header) => x.minerPk })) verifyCases( - Seq((h1, Success(Expected( - Helpers.decodeGroupElement("0361299207fa392231e23666f6945ae3e867b978e021d8d702872bde454e9abe9c"), - cost = 36111)))), + Seq((h1, Expected(Success( + Helpers.decodeGroupElement("0361299207fa392231e23666f6945ae3e867b978e021d8d702872bde454e9abe9c")), + cost = 36111))), existingPropTest("powOnetimePk", { (x: Header) => x.powOnetimePk })) verifyCases( - Seq((h1, Success(Expected( - Helpers.decodeBytes("7f4f09012a807f01"), - cost = 36176)))), + Seq((h1, Expected(Success( + Helpers.decodeBytes("7f4f09012a807f01")), + cost = 36176))), existingPropTest("powNonce", { (x: Header) => x.powNonce })) verifyCases( - Seq((h1, Success(Expected( - CBigInt(new BigInteger("-e24990c47e15ed4d0178c44f1790cc72155d516c43c3e8684e75db3800a288", 16)), - cost = 36220)))), + Seq((h1, Expected(Success( + CBigInt(new BigInteger("-e24990c47e15ed4d0178c44f1790cc72155d516c43c3e8684e75db3800a288", 16))), + cost = 36220))), existingPropTest("powDistance", { (x: Header) => x.powDistance })) verifyCases( - Seq((h1, Success(Expected( - Helpers.decodeBytes("7f0180"), - cost = 36100)))), + Seq((h1, Expected(Success( + Helpers.decodeBytes("7f0180")), + cost = 36100))), existingPropTest("votes", { (x: Header) => x.votes })) } - property("Context properties equivalence") { - val samples = genSamples[Context](MinSuccessful(5)) - + def contextData() = { val input = CostingBox( false, new ErgoBox( @@ -2955,7 +3083,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => ) ), height = 11, - selfBox = input.copy(), // TODO HF: in 3.x implementation selfBox is never the same instance as input (see toSigmaContext) + selfBox = input.copy(), // TODO HF (2h): in 3.x implementation selfBox is never the same instance as input (see toSigmaContext) lastBlockUtxoRootHash = CAvlTree( AvlTreeData( ADDigest @@ (ErgoAlgos.decodeUnsafe("54d23dd080006bdb56800100356080935a80ffb77e90b800057f00661601807f17")), @@ -2966,21 +3094,46 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => ), _minerPubKey = Helpers.decodeBytes("0227a58e9b2537103338c237c52c1213bf44bdb344fa07d9df8ab826cca26ca08f"), vars = Colls - .replicate[AnyValue](10, null) // reserve 10 vars - .append(Coll[AnyValue]( - TestValue(Helpers.decodeBytes("00"), CollType(RType.ByteType)), - TestValue(true, RType.BooleanType))), + .replicate[AnyValue](10, null) // reserve 10 vars + .append(Coll[AnyValue]( + TestValue(Helpers.decodeBytes("00"), CollType(RType.ByteType)), + TestValue(true, RType.BooleanType))), false ) val ctx2 = ctx.copy(vars = Coll[AnyValue](null, null, null)) val ctx3 = ctx.copy(vars = Coll[AnyValue]()) + (input, dataBox, header, ctx, ctx2, ctx3) + } + + def ctxWithRegsInOutput(ctx: CostingDataContext, regs: AdditionalRegisters) = { + ctx.copy( + outputs = Coll({ + val box = ctx.outputs(0).asInstanceOf[CostingBox] + box.copy(ebox = copyBox(box.ebox)(additionalRegisters = regs)) + }) + ) + } + + def ctxWithRegsInDataInput(ctx: CostingDataContext, regs: AdditionalRegisters) = { + ctx.copy( + _dataInputs = Coll({ + val box = ctx.dataInputs(0).asInstanceOf[CostingBox] + box.copy(ebox = copyBox(box.ebox)(additionalRegisters = regs)) + }) + ) + } + + property("Context properties equivalence") { + val samples = genSamples[Context](MinSuccessful(5)) + val (input, dataBox, header, ctx, ctx2, ctx3) = contextData() + test(samples, existingPropTest("dataInputs", { (x: Context) => x.dataInputs })) verifyCases( Seq( - (ctx, Success(Expected(dataBox, cost = 37087))), - (ctx.copy(_dataInputs = Coll()), Failure(new ArrayIndexOutOfBoundsException("0"))) + (ctx, Expected(Success(dataBox), cost = 37087)), + (ctx.copy(_dataInputs = Coll()), Expected(new ArrayIndexOutOfBoundsException("0"))) ), existingFeature({ (x: Context) => x.dataInputs(0) }, "{ (x: Context) => x.dataInputs(0) }", @@ -3001,9 +3154,9 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( Seq( - (ctx, Success(Expected( - Helpers.decodeBytes("7da4b55971f19a78d007638464580f91a020ab468c0dbe608deb1f619e245bc3"), - cost = 37193))) + (ctx, Expected(Success( + Helpers.decodeBytes("7da4b55971f19a78d007638464580f91a020ab468c0dbe608deb1f619e245bc3")), + cost = 37193)) ), existingFeature({ (x: Context) => x.dataInputs(0).id }, "{ (x: Context) => x.dataInputs(0).id }", @@ -3024,8 +3177,8 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => )), preGeneratedSamples = Some(samples)) - // NOTE: testCases2 is not used below because PreHeader/Header cannot be put in - // registers and context vars + // NOTE: verifyCases is not used below because PreHeader/Header instances cannot be put in + // registers and context vars (which are used in `checkVerify` method) testCases( Seq(ctx -> Success(ctx.preHeader)), existingPropTest("preHeader", { (x: Context) => x.preHeader }), @@ -3053,7 +3206,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => "{ (x: Context) => x.SELF }", FuncValue(Vector((1, SContext)), Self))) verifyCases( - Seq(ctx -> Success(Expected(ctx.HEIGHT, cost = 35885))), + Seq(ctx -> Expected(Success(ctx.HEIGHT), cost = 35885)), existingFeature( { (x: Context) => x.HEIGHT }, "{ (x: Context) => x.HEIGHT }", @@ -3061,7 +3214,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => preGeneratedSamples = Some(samples)) verifyCases( - Seq((ctx, Success(Expected(Coll[Long](80946L), cost = 39152)))), + Seq((ctx, Expected(Success(Coll[Long](80946L)), cost = 39152))), existingFeature( { (x: Context) => x.INPUTS.map { (b: Box) => b.value } }, "{ (x: Context) => x.INPUTS.map { (b: Box) => b.value } }", @@ -3072,7 +3225,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => preGeneratedSamples = Some(samples)) verifyCases( - Seq((ctx, Success(Expected(Coll((80946L, 80946L)), cost = 39959)))), + Seq((ctx, Expected(Success(Coll((80946L, 80946L))), cost = 39959))), existingFeature( { (x: Context) => x.INPUTS.map { (b: Box) => (b.value, b.value) } }, """{ (x: Context) => @@ -3094,7 +3247,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => preGeneratedSamples = Some(samples)) verifyCases( - Seq((ctx, Failure(new InvalidType("Cannot getReg[Int](4): invalid type of value Value(Coll(52)) at id=4")))), + Seq((ctx, Expected(new InvalidType("Cannot getReg[Int](4): invalid type of value Value(Coll(52)) at id=4")))), existingFeature( { (x: Context) => x.INPUTS.map { (b: Box) => @@ -3128,7 +3281,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => preGeneratedSamples = Some(samples)) verifyCases( - Seq((ctx, Success(Expected(-1, cost = 36318)))), + Seq((ctx, Expected(Success(-1), cost = 36318))), existingFeature({ (x: Context) => x.selfBoxIndex }, "{ (x: Context) => x.selfBoxIndex }", FuncValue( @@ -3142,18 +3295,18 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => )), preGeneratedSamples = Some(samples)) - // TODO HF: see https://github.com/ScorexFoundation/sigmastate-interpreter/issues/603 + // TODO HF (2h): see https://github.com/ScorexFoundation/sigmastate-interpreter/issues/603 samples.foreach { c => ctx.selfBoxIndex shouldBe -1 } verifyCases( - Seq(ctx -> Success(Expected(ctx.LastBlockUtxoRootHash, cost = 35990))), + Seq(ctx -> Expected(Success(ctx.LastBlockUtxoRootHash), cost = 35990)), existingPropTest("LastBlockUtxoRootHash", { (x: Context) => x.LastBlockUtxoRootHash }), preGeneratedSamples = Some(samples)) verifyCases( - Seq(ctx -> Success(Expected(ctx.LastBlockUtxoRootHash.isUpdateAllowed, cost = 36288))), + Seq(ctx -> Expected(Success(ctx.LastBlockUtxoRootHash.isUpdateAllowed), cost = 36288)), existingFeature( { (x: Context) => x.LastBlockUtxoRootHash.isUpdateAllowed }, "{ (x: Context) => x.LastBlockUtxoRootHash.isUpdateAllowed }", @@ -3174,17 +3327,17 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => preGeneratedSamples = Some(samples)) verifyCases( - Seq(ctx -> Success(Expected(ctx.minerPubKey, cost = 36047))), + Seq(ctx -> Expected(Success(ctx.minerPubKey), cost = 36047)), existingPropTest("minerPubKey", { (x: Context) => x.minerPubKey }), preGeneratedSamples = Some(samples)) -// TODO HF: implement support of Option[T] in DataSerializer +// TODO HF (2h): implement support of Option[T] in DataSerializer // this will allow passing optional values in registers and also in constants // testCases2( // Seq( -// ctx -> Success(Expected(Some(true), cost = 0)), -// ctx2 -> Success(Expected(None, cost = 0)), -// ctx3 -> Success(Expected(None, cost = 0)) +// ctx -> Expected(Success(Some(true), cost = 0)), +// ctx2 -> Expected(Success(None, cost = 0)), +// ctx3 -> Expected(Success(None, cost = 0)) // ), testCases( Seq( @@ -3198,25 +3351,549 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => preGeneratedSamples = Some(samples)) verifyCases( - Seq((ctx, Failure(new InvalidType("Cannot getVar[Int](11): invalid type of value Value(true) at id=2")))), + Seq((ctx, Expected(new InvalidType("Cannot getVar[Int](11): invalid type of value Value(true) at id=2")))), existingFeature((x: Context) => x.getVar[Int](11).get, "{ (x: Context) => getVar[Int](11).get }", FuncValue(Vector((1, SContext)), OptionGet(GetVar(11.toByte, SOption(SInt))))), preGeneratedSamples = Some(samples)) verifyCases( - Seq((ctx, Success(Expected(true, cost = 36750)))), + Seq((ctx, Expected(Success(true), cost = 36750))), existingFeature((x: Context) => x.getVar[Boolean](11).get, "{ (x: Context) => getVar[Boolean](11).get }", FuncValue(Vector((1, SContext)), OptionGet(GetVar(11.toByte, SOption(SBoolean))))), preGeneratedSamples = Some(samples)) } + property("Conditional access to data box register using isDefined") { + val (_, _, _, ctx, _, _) = contextData() + + verifyCases( + Seq( + ctx -> Expected(Success(-135729055492651903L), 38399) + ), + existingFeature( + { (x: Context) => + val tagOpt = x.dataInputs(0).R5[Long] + if (tagOpt.isDefined) { + tagOpt.get + } else { + 0L + } + }, + """{ (x: Context) => + | val tagOpt = x.dataInputs(0).R5[Long] + | if (tagOpt.isDefined) { + | tagOpt.get + | } else { + | 0L + | } + |}""".stripMargin, + FuncValue( + Array((1, SContext)), + BlockValue( + Array( + ValDef( + 3, + List(), + ExtractRegisterAs( + ByIndex( + MethodCall.typed[Value[SCollection[SBox.type]]]( + ValUse(1, SContext), + SContext.getMethodByName("dataInputs"), + Vector(), + Map() + ), + IntConstant(0), + None + ), + ErgoBox.R5, + SOption(SLong) + ) + ) + ), + If( + OptionIsDefined(ValUse(3, SOption(SLong))), + OptionGet(ValUse(3, SOption(SLong))), + LongConstant(0L) + ) + ) + ) + ), + preGeneratedSamples = Some(mutable.WrappedArray.empty)) + } + + property("Conditional access (data box register)") { + val (_, _, _, ctx, _, _) = contextData() + + val expectedError = new IllegalArgumentException("assertion failed: Unexpected register type found at register #4") + + verifyCases( + Seq( + ctx -> Expected(expectedError) + ), + changedFeature( + scalaFunc = { (x: Context) => + // this error is expected in v3.x + throw expectedError + }, + scalaFuncNew = { (x: Context) => + // TODO HF: this is expected in v5.0 + val dataBox = x.dataInputs(0) + val ok = if (x.OUTPUTS(0).R5[Long].get == 1L) { + dataBox.R4[Long].get <= x.SELF.value + } else { + dataBox.R4[Coll[Byte]].get != x.SELF.propositionBytes + } + ok + }, + s"""{ (x: Context) => + | val dataBox = x.dataInputs(0) + | val ok = if (x.OUTPUTS(0).R5[Long].get == 1L) { + | dataBox.R4[Long].get <= x.SELF.value + | } else { + | dataBox.R4[Coll[Byte]].get != x.SELF.propositionBytes + | } + | ok + |} + |""".stripMargin, + FuncValue( + Array((1, SContext)), + BlockValue( + Array( + ValDef( + 3, + List(), + ByIndex( + MethodCall.typed[Value[SCollection[SBox.type]]]( + ValUse(1, SContext), + SContext.getMethodByName("dataInputs"), + Vector(), + Map() + ), + IntConstant(0), + None + ) + ) + ), + If( + EQ( + OptionGet( + ExtractRegisterAs(ByIndex(Outputs, IntConstant(0), None), ErgoBox.R5, SOption(SLong)) + ), + LongConstant(1L) + ), + LE( + OptionGet(ExtractRegisterAs(ValUse(3, SBox), ErgoBox.R4, SOption(SLong))), + ExtractAmount(Self) + ), + NEQ( + OptionGet(ExtractRegisterAs(ValUse(3, SBox), ErgoBox.R4, SOption(SByteArray))), + ExtractScriptBytes(Self) + ) + ) + ) + ) + ), + preGeneratedSamples = Some(mutable.WrappedArray.empty)) + } + + property("Conditional access OUTPUTS(0).R4 using tag in R5") { + val (_, _, _, ctx, _, _) = contextData() + + verifyCases( + Seq( + ctx -> Expected(Success(5008366408131208436L), 40406), + ctxWithRegsInOutput(ctx, Map( + ErgoBox.R5 -> LongConstant(0L), + ErgoBox.R4 -> ShortConstant(10))) -> Expected(Success(10L), 40396), + ctxWithRegsInOutput(ctx, Map( + ErgoBox.R4 -> ShortConstant(10))) -> Expected(Success(-1L), 40396) + ), + existingFeature( + { (x: Context) => + val tagOpt = x.OUTPUTS(0).R5[Long] + val res = if (tagOpt.isDefined) { + val tag = tagOpt.get + if (tag == 0L) { + val short = x.OUTPUTS(0).R4[Short].get // access Short in the register + short.toLong + } else { + if (tag == 1L) { + val long = x.OUTPUTS(0).R4[Long].get // access Long in the register + long + } + else 0L + } + } else { + -1L + } + res + }, + """{ + |(x: Context) => + | val tagOpt = x.OUTPUTS(0).R5[Long] + | val res = if (tagOpt.isDefined) { + | val tag = tagOpt.get + | if (tag == 0L) { + | val short = x.OUTPUTS(0).R4[Short].get // access Short in the register + | short.toLong + | } else { + | if (tag == 1L) { + | val long = x.OUTPUTS(0).R4[Long].get // access Long in the register + | long + | } + | else 0L + | } + | } else { + | -1L + | } + | res + |}""".stripMargin, + FuncValue( + Array((1, SContext)), + BlockValue( + Array( + ValDef(3, List(), ByIndex(Outputs, IntConstant(0), None)), + ValDef(4, List(), ExtractRegisterAs(ValUse(3, SBox), ErgoBox.R5, SOption(SLong))) + ), + If( + OptionIsDefined(ValUse(4, SOption(SLong))), + BlockValue( + Array(ValDef(5, List(), OptionGet(ValUse(4, SOption(SLong))))), + If( + EQ(ValUse(5, SLong), LongConstant(0L)), + Upcast(OptionGet(ExtractRegisterAs(ValUse(3, SBox), ErgoBox.R4, SOption(SShort))), SLong), + If( + EQ(ValUse(5, SLong), LongConstant(1L)), + OptionGet(ExtractRegisterAs(ValUse(3, SBox), ErgoBox.R4, SOption(SLong))), + LongConstant(0L) + ) + ) + ), + LongConstant(-1L) + ) + ) + ) + ), + preGeneratedSamples = Some(mutable.WrappedArray.empty)) + } + + property("Conditional access OUTPUTS(0).R4 using tag in R5 (plus action)") { + val (_, _, _, ctx, _, _) = contextData() + verifyCases( + Seq( + // case 1L + ctx -> Expected(Success(5008366408131289382L), 41016), + // case 0L + ctxWithRegsInOutput(ctx, Map( + ErgoBox.R5 -> LongConstant(0L), + ErgoBox.R4 -> ShortConstant(10))) -> Expected(Success(80956L), 41006), + + // case returning 0L + ctxWithRegsInOutput(ctx, Map( + ErgoBox.R5 -> LongConstant(2L), + // note R4 is required to avoid + // "RuntimeException: Set of non-mandatory indexes is not densely packed" + ErgoBox.R4 -> ShortConstant(10))) -> Expected(Success(0L), 41006), + + // case returning -1L + ctxWithRegsInOutput(ctx, Map( + ErgoBox.R4 -> ShortConstant(10))) -> Expected(Success(-1L), 41006) + ), + existingFeature( + { (x: Context) => + val tagOpt = x.OUTPUTS(0).R5[Long] + val res = if (tagOpt.isDefined) { + val tag = tagOpt.get + if (tag == 0L) { + val short = x.OUTPUTS(0).R4[Short].get // access Short in the register + short.toLong + x.SELF.value + } else { + if (tag == 1L) { + val long = x.OUTPUTS(0).R4[Long].get // access Long in the register + long + x.SELF.value + } + else 0L + } + } else { + -1L + } + res + }, + """{ + |(x: Context) => + | val tagOpt = x.OUTPUTS(0).R5[Long] + | val res = if (tagOpt.isDefined) { + | val tag = tagOpt.get + | if (tag == 0L) { + | val short = x.OUTPUTS(0).R4[Short].get // access Short in the register + | short.toLong + x.SELF.value + | } else { + | if (tag == 1L) { + | val long = x.OUTPUTS(0).R4[Long].get // access Long in the register + | long + x.SELF.value + | } + | else 0L + | } + | } else { + | -1L + | } + | res + |}""".stripMargin, + FuncValue( + Array((1, SContext)), + BlockValue( + Array( + ValDef(3, List(), ByIndex(Outputs, IntConstant(0), None)), + ValDef(4, List(), ExtractRegisterAs(ValUse(3, SBox), ErgoBox.R5, SOption(SLong))) + ), + If( + OptionIsDefined(ValUse(4, SOption(SLong))), + BlockValue( + Array(ValDef(5, List(), OptionGet(ValUse(4, SOption(SLong))))), + If( + EQ(ValUse(5, SLong), LongConstant(0L)), + ArithOp( + Upcast( + OptionGet(ExtractRegisterAs(ValUse(3, SBox), ErgoBox.R4, SOption(SShort))), + SLong + ), + ExtractAmount(Self), + OpCode @@ (-102.toByte) + ), + If( + EQ(ValUse(5, SLong), LongConstant(1L)), + ArithOp( + OptionGet(ExtractRegisterAs(ValUse(3, SBox), ErgoBox.R4, SOption(SLong))), + ExtractAmount(Self), + OpCode @@ (-102.toByte) + ), + LongConstant(0L) + ) + ) + ), + LongConstant(-1L) + ) + ) + ) + ), + preGeneratedSamples = Some(mutable.WrappedArray.empty)) + } + + property("Conditional access dataInputs(0).R4 using tag in R5") { + val (_, _, _, ctx, _, _) = contextData() + verifyCases( + Seq( + ctxWithRegsInDataInput(ctx, Map( + ErgoBox.R5 -> LongConstant(1L), + ErgoBox.R4 -> LongConstant(10))) -> Expected(Success(10L), 41084), + ctxWithRegsInDataInput(ctx, Map( + ErgoBox.R5 -> LongConstant(0L), + ErgoBox.R4 -> ShortConstant(10))) -> Expected(Success(10L), 41084), + ctxWithRegsInDataInput(ctx, Map( + ErgoBox.R5 -> LongConstant(2L), + ErgoBox.R4 -> ShortConstant(10))) -> Expected(Success(0L), 41084), + ctxWithRegsInDataInput(ctx, Map( + ErgoBox.R4 -> ShortConstant(10))) -> Expected(Success(-1L), 41084) + ), + existingFeature( + { (x: Context) => + val tagOpt = x.dataInputs(0).R5[Long] + val res = if (tagOpt.isDefined) { + val tag = tagOpt.get + if (tag == 0L) { + val short = x.dataInputs(0).R4[Short].get // access Short in the register + short.toLong + } else { + if (tag == 1L) { + val long = x.dataInputs(0).R4[Long].get // access Long in the register + long + } + else 0L + } + } else { + -1L + } + res + }, + """{ + |(x: Context) => + | val tagOpt = x.dataInputs(0).R5[Long] + | val res = if (tagOpt.isDefined) { + | val tag = tagOpt.get + | if (tag == 0L) { + | val short = x.dataInputs(0).R4[Short].get // access Short in the register + | short.toLong + | } else { + | if (tag == 1L) { + | val long = x.dataInputs(0).R4[Long].get // access Long in the register + | long + | } + | else 0L + | } + | } else { + | -1L + | } + | res + |}""".stripMargin, + FuncValue( + Array((1, SContext)), + BlockValue( + Array( + ValDef( + 3, + List(), + ByIndex( + MethodCall.typed[Value[SCollection[SBox.type]]]( + ValUse(1, SContext), + SContext.getMethodByName("dataInputs"), + Vector(), + Map() + ), + IntConstant(0), + None + ) + ), + ValDef(4, List(), ExtractRegisterAs(ValUse(3, SBox), ErgoBox.R5, SOption(SLong))) + ), + If( + OptionIsDefined(ValUse(4, SOption(SLong))), + BlockValue( + Array(ValDef(5, List(), OptionGet(ValUse(4, SOption(SLong))))), + If( + EQ(ValUse(5, SLong), LongConstant(0L)), + Upcast(OptionGet(ExtractRegisterAs(ValUse(3, SBox), ErgoBox.R4, SOption(SShort))), SLong), + If( + EQ(ValUse(5, SLong), LongConstant(1L)), + OptionGet(ExtractRegisterAs(ValUse(3, SBox), ErgoBox.R4, SOption(SLong))), + LongConstant(0L) + ) + ) + ), + LongConstant(-1L) + ) + ) + ) + ), + preGeneratedSamples = Some(mutable.WrappedArray.empty)) + } + + property("Conditional access dataInputs(0).R4 using tag in R5 (plus action)") { + val (_, _, _, ctx, _, _) = contextData() + verifyCases( + Seq( + ctxWithRegsInDataInput(ctx, Map( + ErgoBox.R5 -> LongConstant(1L), + ErgoBox.R4 -> LongConstant(10))) -> Expected(Success(80956L), 41694), + ctxWithRegsInDataInput(ctx, Map( + ErgoBox.R5 -> LongConstant(0L), + ErgoBox.R4 -> ShortConstant(10))) -> Expected(Success(80956L), 41694), + ctxWithRegsInDataInput(ctx, Map( + ErgoBox.R5 -> LongConstant(2L), + ErgoBox.R4 -> ShortConstant(10))) -> Expected(Success(0L), 41694), + ctxWithRegsInDataInput(ctx, Map( + ErgoBox.R4 -> ShortConstant(10))) -> Expected(Success(-1L), 41694) + ), + existingFeature( + { (x: Context) => + val tagOpt = x.dataInputs(0).R5[Long] + val res = if (tagOpt.isDefined) { + val tag = tagOpt.get + if (tag == 0L) { + val short = x.dataInputs(0).R4[Short].get // access Short in the register + short.toLong + x.SELF.value + } else { + if (tag == 1L) { + val long = x.dataInputs(0).R4[Long].get // access Long in the register + long + x.SELF.value + } + else 0L + } + } else { + -1L + } + res + }, + """{ + |(x: Context) => + | val tagOpt = x.dataInputs(0).R5[Long] + | val res = if (tagOpt.isDefined) { + | val tag = tagOpt.get + | if (tag == 0L) { + | val short = x.dataInputs(0).R4[Short].get // access Short in the register + | short.toLong + x.SELF.value + | } else { + | if (tag == 1L) { + | val long = x.dataInputs(0).R4[Long].get // access Long in the register + | long + x.SELF.value + | } + | else 0L + | } + | } else { + | -1L + | } + | res + |}""".stripMargin, + FuncValue( + Array((1, SContext)), + BlockValue( + Array( + ValDef( + 3, + List(), + ByIndex( + MethodCall.typed[Value[SCollection[SBox.type]]]( + ValUse(1, SContext), + SContext.getMethodByName("dataInputs"), + Vector(), + Map() + ), + IntConstant(0), + None + ) + ), + ValDef(4, List(), ExtractRegisterAs(ValUse(3, SBox), ErgoBox.R5, SOption(SLong))) + ), + If( + OptionIsDefined(ValUse(4, SOption(SLong))), + BlockValue( + Array(ValDef(5, List(), OptionGet(ValUse(4, SOption(SLong))))), + If( + EQ(ValUse(5, SLong), LongConstant(0L)), + ArithOp( + Upcast( + OptionGet(ExtractRegisterAs(ValUse(3, SBox), ErgoBox.R4, SOption(SShort))), + SLong + ), + ExtractAmount(Self), + OpCode @@ (-102.toByte) + ), + If( + EQ(ValUse(5, SLong), LongConstant(1L)), + ArithOp( + OptionGet(ExtractRegisterAs(ValUse(3, SBox), ErgoBox.R4, SOption(SLong))), + ExtractAmount(Self), + OpCode @@ (-102.toByte) + ), + LongConstant(0L) + ) + ) + ), + LongConstant(-1L) + ) + ) + ) + ), + preGeneratedSamples = Some(mutable.WrappedArray.empty)) + + } + property("xorOf equivalence") { - // TODO HF: see https://github.com/ScorexFoundation/sigmastate-interpreter/issues/640 + // TODO HF (3h): see https://github.com/ScorexFoundation/sigmastate-interpreter/issues/640 verifyCases( { - def success[T](v: T, c: Int) = Success(Expected(v, c)) + def success[T](v: T, c: Int) = Expected(Success(v), c) Seq( (Coll[Boolean](false), success(false, 37071)), (Coll[Boolean](true), success(false, 37071)), @@ -3246,8 +3923,8 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => property("LogicalNot equivalence") { verifyCases( Seq( - (true, Success(Expected(false, 35864))), - (false, Success(Expected(true, 35864)))), + (true, Expected(Success(false), 35864)), + (false, Expected(Success(true), 35864))), existingFeature((x: Boolean) => !x, "{ (x: Boolean) => !x }", FuncValue(Vector((1, SBoolean)), LogicalNot(ValUse(1, SBoolean))))) @@ -3256,7 +3933,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => property("Numeric Negation equivalence") { verifyCases( { - def success[T](v: T) = Success(Expected(v, 36136)) + def success[T](v: T) = Expected(Success(v), 36136) Seq( (Byte.MinValue, success(Byte.MinValue)), // !!! (-40.toByte, success(40.toByte)), @@ -3272,7 +3949,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( { - def success[T](v: T) = Success(Expected(v, 36136)) + def success[T](v: T) = Expected(Success(v), 36136) Seq( (Short.MinValue, success(Short.MinValue)), // special case! ((Short.MinValue + 1).toShort, success(32767.toShort)), @@ -3289,7 +3966,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( { - def success[T](v: T) = Success(Expected(v, 36136)) + def success[T](v: T) = Expected(Success(v), 36136) Seq( (Int.MinValue, success(Int.MinValue)), // special case! (Int.MinValue + 1, success(2147483647)), @@ -3306,7 +3983,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( { - def success[T](v: T) = Success(Expected(v, 36136)) + def success[T](v: T) = Expected(Success(v), 36136) Seq( (Long.MinValue, success(Long.MinValue)), // special case! (Long.MinValue + 1, success(9223372036854775807L)), @@ -3323,7 +4000,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( { - def success[T](v: T) = Success(Expected(v, 36136)) + def success[T](v: T) = Expected(Success(v), 36136) Seq( (CBigInt(new BigInteger("-1655a05845a6ad363ac88ea21e88b97e436a1f02c548537e12e2d9667bf0680", 16)), success(CBigInt(new BigInteger("1655a05845a6ad363ac88ea21e88b97e436a1f02c548537e12e2d9667bf0680", 16)))), (CBigInt(new BigInteger("-1b24ba8badba8abf347cce054d9b9f14f229321507245b8", 16)), success(CBigInt(new BigInteger("1b24ba8badba8abf347cce054d9b9f14f229321507245b8", 16)))), @@ -3350,7 +4027,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( { - def success[T](v: T) = Success(Expected(v, 35981)) + def success[T](v: T) = Expected(Success(v), 35981) Seq( (-1, success(Helpers.decodeGroupElement("0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"))), (1, success(Helpers.decodeGroupElement("0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798")))) @@ -3369,7 +4046,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( { - def success[T](v: T) = Success(Expected(v, 35981)) + def success[T](v: T) = Expected(Success(v), 35981) Seq( (-1, success(Helpers.decodeGroupElement("0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"))), (1, success(Helpers.decodeGroupElement("0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798")))) @@ -3388,7 +4065,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( { - def success[T](v: T) = Success(Expected(v, 41237)) + def success[T](v: T) = Expected(Success(v), 41237) Seq( (CBigInt(new BigInteger("-e5c1a54694c85d644fa30a6fc5f3aa209ed304d57f72683a0ebf21038b6a9d", 16)), success(Helpers.decodeGroupElement("023395bcba3d7cf21d73c50f8af79d09a8c404c15ce9d04f067d672823bae91a54"))), (CBigInt(new BigInteger("-bc2d08f935259e0eebf272c66c6e1dbd484c6706390215", 16)), success(Helpers.decodeGroupElement("02ddcf4c48105faf3c16f7399b5dbedd82ab0bb50ae292d8f88f49a3f86e78974e"))), @@ -3419,16 +4096,16 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => ) ))) - // TODO HF: fix semantics when the left collection is longer + // TODO HF (2h): fix semantics when the left collection is longer verifyCases( { - def success[T](v: T) = Success(Expected(v, 36903)) + def success[T](v: T) = Expected(Success(v), 36903) Seq( ((Helpers.decodeBytes(""), Helpers.decodeBytes("")), success(Helpers.decodeBytes(""))), ((Helpers.decodeBytes("01"), Helpers.decodeBytes("01")), success(Helpers.decodeBytes("00"))), ((Helpers.decodeBytes("0100"), Helpers.decodeBytes("0101")), success(Helpers.decodeBytes("0001"))), ((Helpers.decodeBytes("01"), Helpers.decodeBytes("0101")), success(Helpers.decodeBytes("00"))), - ((Helpers.decodeBytes("0100"), Helpers.decodeBytes("01")), Failure(new ArrayIndexOutOfBoundsException("1"))), + ((Helpers.decodeBytes("0100"), Helpers.decodeBytes("01")), Expected(new ArrayIndexOutOfBoundsException("1"))), ((Helpers.decodeBytes("800136fe89afff802acea67128a0ff007fffe3498c8001806080012b"), Helpers.decodeBytes("648018010a5d5800f5b400a580e7b4809b0cd273ff1230bfa800017f7fdb002749b3ac2b86ff")), success(Helpers.decodeBytes("e4812eff83f2a780df7aa6d4a8474b80e4f3313a7392313fc8800054"))) @@ -3508,7 +4185,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( { - def success[T](v: T, c: Int) = Success(Expected(v, c)) + def success[T](v: T, c: Int) = Expected(Success(v), c) Seq( (Coll[Box](), success(Coll[Box](), 37297)), (Coll[Box](b1), success(Coll[Box](), 37397)), @@ -3528,7 +4205,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( { - def success[T](v: T, c: Int) = Success(Expected(v, c)) + def success[T](v: T, c: Int) = Expected(Success(v), c) Seq( (Coll[Box](), success(Coll[Byte](), 38126)), (Coll[Box](b1), success(Helpers.decodeBytes( @@ -3556,7 +4233,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( { - def success[T](v: T, c: Int) = Success(Expected(v, c)) + def success[T](v: T, c: Int) = Expected(Success(v), c) Seq( (Coll[Box](), success(Coll[(Box, Box)](), 37399)), (Coll[Box](b1), success(Coll[(Box, Box)]((b1, b1)), 37559)), @@ -3580,7 +4257,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( { - def success[T](v: T) = Success(Expected(v, 35954)) + def success[T](v: T) = Expected(Success(v), 35954) Seq( (Coll[Box](), success(0)), (Coll[Box](b1), success(1)), @@ -3594,7 +4271,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( { - def success[T](v: T) = Success(Expected(v, 36036)) + def success[T](v: T) = Expected(Success(v), 36036) Seq( (Coll[Box](), success(Coll[Int]())), (Coll[Box](b1), success(Coll[Int](0))), @@ -3616,7 +4293,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( { - def success[T](v: T, c: Int) = Success(Expected(v, c)) + def success[T](v: T, c: Int) = Expected(Success(v), c) Seq( (Coll[Box](), success(true, 37909)), (Coll[Box](b1), success(false, 37969)), @@ -3636,7 +4313,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( { - def success[T](v: T, c: Int) = Success(Expected(v, c)) + def success[T](v: T, c: Int) = Expected(Success(v), c) Seq( (Coll[Box](), success(false, 38455)), (Coll[Box](b1), success(false, 38515)), @@ -3663,7 +4340,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => property("Coll flatMap method equivalence") { verifyCases( { - def success[T](v: T, c: Int) = Success(Expected(v, c)) + def success[T](v: T, c: Int) = Expected(Success(v), c) Seq( Coll[GroupElement]() -> success(Coll[Byte](), 40133), Coll[GroupElement]( @@ -3722,7 +4399,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => val samples = genSamples(collWithRangeGen, MinSuccessful(50)) verifyCases( { - def success[T](v: T) = Success(Expected(v, 37514)) + def success[T](v: T) = Expected(Success(v), 37514) Seq( ((Coll[Int](), (0, 0)), success(Coll[Int]())), ((Coll[Int](1), (0, 0)), success(Coll[Int](1, 1))), @@ -3790,16 +4467,16 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( // (coll, (index, elem)) { - def success[T](v: T) = Success(Expected(v, 37180)) + def success[T](v: T) = Expected(Success(v), 37180) Seq( - ((Coll[Int](), (0, 0)), Failure(new IndexOutOfBoundsException("0"))), + ((Coll[Int](), (0, 0)), Expected(new IndexOutOfBoundsException("0"))), ((Coll[Int](1), (0, 0)), success(Coll[Int](0))), ((Coll[Int](1, 2), (0, 0)), success(Coll[Int](0, 2))), ((Coll[Int](1, 2), (1, 0)), success(Coll[Int](1, 0))), ((Coll[Int](1, 2, 3), (2, 0)), success(Coll[Int](1, 2, 0))), - ((Coll[Int](1, 2), (2, 0)), Failure(new IndexOutOfBoundsException("2"))), - ((Coll[Int](1, 2), (3, 0)), Failure(new IndexOutOfBoundsException("3"))), - ((Coll[Int](1, 2), (-1, 0)), Failure(new IndexOutOfBoundsException("-1"))) + ((Coll[Int](1, 2), (2, 0)), Expected(new IndexOutOfBoundsException("2"))), + ((Coll[Int](1, 2), (3, 0)), Expected(new IndexOutOfBoundsException("3"))), + ((Coll[Int](1, 2), (-1, 0)), Expected(new IndexOutOfBoundsException("-1"))) ) }, existingFeature( @@ -3846,21 +4523,21 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( // (coll, (indexes, values)) { - def success[T](v: T) = Success(Expected(v, 37817)) + def success[T](v: T) = Expected(Success(v), 37817) Seq( - ((Coll[Int](), (Coll(0), Coll(0))), Failure(new IndexOutOfBoundsException("0"))), - ((Coll[Int](), (Coll(0, 1), Coll(0, 0))), Failure(new IndexOutOfBoundsException("0"))), - ((Coll[Int](), (Coll(0, 1), Coll(0))), Failure(new IllegalArgumentException("requirement failed: Collections should have same length but was 2 and 1:\n xs=Coll(0,1);\n ys=Coll(0)"))), + ((Coll[Int](), (Coll(0), Coll(0))), Expected(new IndexOutOfBoundsException("0"))), + ((Coll[Int](), (Coll(0, 1), Coll(0, 0))), Expected(new IndexOutOfBoundsException("0"))), + ((Coll[Int](), (Coll(0, 1), Coll(0))), Expected(new IllegalArgumentException("requirement failed: Collections should have same length but was 2 and 1:\n xs=Coll(0,1);\n ys=Coll(0)"))), ((Coll[Int](1), (Coll(0), Coll(0))), success(Coll[Int](0))), - ((Coll[Int](1), (Coll(0, 1), Coll(0, 0))), Failure(new IndexOutOfBoundsException("1"))), + ((Coll[Int](1), (Coll(0, 1), Coll(0, 0))), Expected(new IndexOutOfBoundsException("1"))), ((Coll[Int](1, 2), (Coll(0), Coll(0))), success(Coll[Int](0, 2))), ((Coll[Int](1, 2), (Coll(0, 1), Coll(0, 0))), success(Coll[Int](0, 0))), - ((Coll[Int](1, 2), (Coll(0, 1, 2), Coll(0, 0, 0))), Failure(new IndexOutOfBoundsException("2"))), + ((Coll[Int](1, 2), (Coll(0, 1, 2), Coll(0, 0, 0))), Expected(new IndexOutOfBoundsException("2"))), ((Coll[Int](1, 2), (Coll(1), Coll(0))), success(Coll[Int](1, 0))), ((Coll[Int](1, 2, 3), (Coll(2), Coll(0))), success(Coll[Int](1, 2, 0))), - ((Coll[Int](1, 2), (Coll(2), Coll(0))), Failure(new IndexOutOfBoundsException("2"))), - ((Coll[Int](1, 2), (Coll(3), Coll(0))), Failure(new IndexOutOfBoundsException("3"))), - ((Coll[Int](1, 2), (Coll(-1), Coll(0))), Failure(new IndexOutOfBoundsException("-1"))), + ((Coll[Int](1, 2), (Coll(2), Coll(0))), Expected(new IndexOutOfBoundsException("2"))), + ((Coll[Int](1, 2), (Coll(3), Coll(0))), Expected(new IndexOutOfBoundsException("3"))), + ((Coll[Int](1, 2), (Coll(-1), Coll(0))), Expected(new IndexOutOfBoundsException("-1"))), ((Coll[Int](10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140), (Coll[Int](12, 12, 4, 11, 1, 8, 0, 1), Coll[Int](-10, -20, -30, -40, -50, -60, -70, -80))), success(Coll[Int](-70, -80, 30, 40, -30, 60, 70, 80, -60, 100, 110, -40, -20, 140))) @@ -3908,7 +4585,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => preGeneratedSamples = Some(samples)) } - // TODO HF: https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479 + // TODO HF (3h): https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479 property("Coll find method equivalence") { val find = newFeature((x: Coll[Int]) => x.find({ (v: Int) => v > 0 }), "{ (x: Coll[Int]) => x.find({ (v: Int) => v > 0} ) }") @@ -3917,7 +4594,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => } } - // TODO HF: https://github.com/ScorexFoundation/sigmastate-interpreter/issues/418 + // TODO HF (3h): https://github.com/ScorexFoundation/sigmastate-interpreter/issues/418 property("Coll bitwise methods equivalence") { val shiftRight = newFeature( { (x: Coll[Boolean]) => @@ -3929,7 +4606,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => } } - // TODO HF: https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479 + // TODO HF (3h): https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479 property("Coll diff methods equivalence") { val diff = newFeature((x: (Coll[Int], Coll[Int])) => x._1.diff(x._2), "{ (x: (Coll[Int], Coll[Int])) => x._1.diff(x._2) }") @@ -3943,14 +4620,14 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( // (coll, initState) { - def success[T](v: T, c: Int) = Success(Expected(v, c)) + def success[T](v: T, c: Int) = Expected(Success(v), c) Seq( ((Coll[Byte](), 0), success(0, 41266)), ((Coll[Byte](), Int.MaxValue), success(Int.MaxValue, 41266)), ((Coll[Byte](1), Int.MaxValue - 1), success(Int.MaxValue, 41396)), - ((Coll[Byte](1), Int.MaxValue), Failure(new ArithmeticException("integer overflow"))), + ((Coll[Byte](1), Int.MaxValue), Expected(new ArithmeticException("integer overflow"))), ((Coll[Byte](-1), Int.MinValue + 1), success(Int.MinValue, 41396)), - ((Coll[Byte](-1), Int.MinValue), Failure(new ArithmeticException("integer overflow"))), + ((Coll[Byte](-1), Int.MinValue), Expected(new ArithmeticException("integer overflow"))), ((Coll[Byte](1, 2), 0), success(3, 41526)), ((Coll[Byte](1, -1), 0), success(0, 41526)), ((Coll[Byte](1, -1, 1), 0), success(1, 41656)) @@ -3980,7 +4657,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( // (coll, (elem: Byte, from: Int)) { - def success[T](v: T) = Success(Expected(v, 37649)) + def success[T](v: T) = Expected(Success(v), 37649) Seq( ((Coll[Byte](), (0.toByte, 0)), success(-1)), ((Coll[Byte](), (0.toByte, -1)), success(-1)), @@ -4034,16 +4711,16 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => property("Coll apply method equivalence") { verifyCases( { - def success[T](v: T) = Success(Expected(v, 36410)) + def success[T](v: T) = Expected(Success(v), 36410) Seq( - ((Coll[Int](), 0), Failure(new ArrayIndexOutOfBoundsException("0"))), - ((Coll[Int](), -1), Failure(new ArrayIndexOutOfBoundsException("-1"))), + ((Coll[Int](), 0), Expected(new ArrayIndexOutOfBoundsException("0"))), + ((Coll[Int](), -1), Expected(new ArrayIndexOutOfBoundsException("-1"))), ((Coll[Int](1), 0), success(1)), - ((Coll[Int](1), 1), Failure(new ArrayIndexOutOfBoundsException("1"))), - ((Coll[Int](1), -1), Failure(new ArrayIndexOutOfBoundsException("-1"))), + ((Coll[Int](1), 1), Expected(new ArrayIndexOutOfBoundsException("1"))), + ((Coll[Int](1), -1), Expected(new ArrayIndexOutOfBoundsException("-1"))), ((Coll[Int](1, 2), 1), success(2)), ((Coll[Int](1, 2), 1), success(2)), - ((Coll[Int](1, 2), 2), Failure(new ArrayIndexOutOfBoundsException("2"))) + ((Coll[Int](1, 2), 2), Expected(new ArrayIndexOutOfBoundsException("2"))) ) }, existingFeature((x: (Coll[Int], Int)) => x._1(x._2), @@ -4066,7 +4743,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( // (coll, (index, default)) { - def success[T](v: T) = Success(Expected(v, 37020)) + def success[T](v: T) = Expected(Success(v), 37020) Seq( ((Coll[Int](), (0, default)), success(default)), ((Coll[Int](), (-1, default)), success(default)), @@ -4109,7 +4786,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => property("Tuple size method equivalence") { verifyCases( { - def success[T](v: T) = Success(Expected(v, 35905)) + def success[T](v: T) = Expected(Success(v), 35905) Seq( ((0, 0), success(2)), ((1, 2), success(2)) @@ -4123,7 +4800,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => property("Tuple apply method equivalence") { val samples = genSamples[(Int, Int)](DefaultMinSuccessful) verifyCases( - Seq(((1, 2), Success(Expected(1, cost = 36013)))), + Seq(((1, 2), Expected(Success(1), cost = 36013))), existingFeature((x: (Int, Int)) => x._1, "{ (x: (Int, Int)) => x(0) }", FuncValue( @@ -4132,7 +4809,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => )), preGeneratedSamples = Some(samples)) verifyCases( - Seq(((1, 2), Success(Expected(2, cost = 36013)))), + Seq(((1, 2), Expected(Success(2), cost = 36013))), existingFeature((x: (Int, Int)) => x._2, "{ (x: (Int, Int)) => x(1) }", FuncValue( @@ -4146,12 +4823,12 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => val n = ExactNumeric.IntIsExactNumeric verifyCases( { - def success[T](v: T, c: Int) = Success(Expected(v, c)) + def success[T](v: T, c: Int) = Expected(Success(v), c) Seq( (Coll[Int](), success(Coll[Int](), 38886)), (Coll[Int](1), success(Coll[Int](2), 38936)), (Coll[Int](1, 2), success(Coll[Int](2, 3), 38986)), - (Coll[Int](1, 2, Int.MaxValue), Failure(new ArithmeticException("integer overflow"))) + (Coll[Int](1, 2, Int.MaxValue), Expected(new ArithmeticException("integer overflow"))) ) }, existingFeature((x: Coll[Int]) => x.map({ (v: Int) => n.plus(v, 1) }), @@ -4170,7 +4847,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( // (coll, (from, until)) { - def success[T](v: T) = Success(Expected(v, 36964)) + def success[T](v: T) = Expected(Success(v), 36964) Seq( ((Coll[Int](), (-1, 0)), success(Coll[Int]())), ((Coll[Int](), (0, 0)), success(Coll[Int]())), @@ -4215,7 +4892,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => property("Coll append method equivalence") { verifyCases( { - def success[T](v: T) = Success(Expected(v, 37765)) + def success[T](v: T) = Expected(Success(v), 37765) Seq( (Coll[Int](), Coll[Int]()) -> success(Coll[Int]()), (Coll[Int](), Coll[Int](1)) -> success(Coll[Int](1)), @@ -4245,11 +4922,11 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => } property("Option methods equivalence") { - def success[T](v: T, c: Int) = Success(Expected(v, c)) + def success[T](v: T, c: Int) = Expected(Success(v), c) verifyCases( Seq( - (None -> Failure(new NoSuchElementException("None.get"))), + (None -> Expected(new NoSuchElementException("None.get"))), (Some(10L) -> success(10L, 36046))), existingFeature({ (x: Option[Long]) => x.get }, "{ (x: Option[Long]) => x.get }", @@ -4293,7 +4970,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => Seq( (None -> success(None, 38575)), (Some(10L) -> success(Some(11L), 38575)), - (Some(Long.MaxValue) -> Failure(new ArithmeticException("long overflow")))), + (Some(Long.MaxValue) -> Expected(new ArithmeticException("long overflow")))), existingFeature({ (x: Option[Long]) => x.map( (v: Long) => n.plus(v, 1) ) }, "{ (x: Option[Long]) => x.map({ (v: Long) => v + 1 }) }", FuncValue( @@ -4314,7 +4991,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => ))) } - // TODO HF: implement Option.fold + // TODO HF (3h): implement Option.fold property("Option new methods") { val isEmpty = newFeature({ (x: Option[Long]) => x.isEmpty }, "{ (x: Option[Long]) => x.isEmpty }") @@ -4332,11 +5009,25 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => val n = ExactNumeric.LongIsExactNumeric verifyCases( Seq( - (None -> Failure(new NoSuchElementException("None.get"))), - (Some(0L) -> Success(Expected(1L, 39012))), - (Some(Long.MaxValue) -> Failure(new ArithmeticException("long overflow"))) + (None -> Expected( + Failure(new NoSuchElementException("None.get")), 0, + expectedNewValue = Success(5L), expectedNewCost = 39012)), + (Some(0L) -> Expected(Success(1L), 39012)), + (Some(Long.MaxValue) -> Expected(new ArithmeticException("long overflow"))) ), - existingFeature({ (x: Option[Long]) => x.fold(throw new NoSuchElementException("None.get"))( (v: Long) => n.plus(v, 1) ) }, + changedFeature( + scalaFunc = { (x: Option[Long]) => + def f(opt: Long): Long = n.plus(opt, 1) + if (x.isDefined) f(x.get) + else { + f(x.get); // simulate non-lazy 'if': f is called in both branches + 5L + } + }, + scalaFuncNew = { (x: Option[Long]) => + def f(opt: Long): Long = n.plus(opt, 1) + if (x.isDefined) f(x.get) else 5L + }, """{(x: Option[Long]) => | def f(opt: Long): Long = opt + 1 | if (x.isDefined) f(x.get) else 5L @@ -4358,7 +5049,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => } property("blake2b256, sha256 equivalence") { - def success[T](v: T, c: Int) = Success(Expected(v, c)) + def success[T](v: T, c: Int) = Expected(Success(v), c) verifyCases( Seq( @@ -4398,15 +5089,15 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => property("sigmaProp equivalence") { verifyCases( Seq( - (false, Success(Expected(CSigmaProp(TrivialProp.FalseProp), 35892))), - (true, Success(Expected(CSigmaProp(TrivialProp.TrueProp), 35892)))), + (false, Expected(Success(CSigmaProp(TrivialProp.FalseProp)), 35892)), + (true, Expected(Success(CSigmaProp(TrivialProp.TrueProp)), 35892))), existingFeature((x: Boolean) => sigmaProp(x), "{ (x: Boolean) => sigmaProp(x) }", FuncValue(Vector((1, SBoolean)), BoolToSigmaProp(ValUse(1, SBoolean))))) } property("atLeast equivalence") { - def success[T](v: T) = Success(Expected(v, 36462)) + def success[T](v: T) = Expected(Success(v), 36462) verifyCases( Seq( @@ -4448,7 +5139,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => ) ), Colls.replicate[SigmaProp](AtLeast.MaxChildrenCount + 1, CSigmaProp(TrivialProp.TrueProp)) -> - Failure(new IllegalArgumentException("Expected input elements count should not exceed 255, actual: 256")) + Expected(new IllegalArgumentException("Expected input elements count should not exceed 255, actual: 256")) ), existingFeature((x: Coll[SigmaProp]) => SigmaDsl.atLeast(x.size - 1, x), "{ (x: Coll[SigmaProp]) => atLeast(x.size - 1, x) }", @@ -4465,7 +5156,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( { - def success[T](v: T) = Success(Expected(v, 36428)) + def success[T](v: T) = Expected(Success(v), 36428) Seq( (CSigmaProp(ProveDlog(Helpers.decodeECPoint("02ea9bf6da7f512386c6ca509d40f8c5e7e0ffb3eea5dc3c398443ea17f4510798"))), CSigmaProp(ProveDlog(Helpers.decodeECPoint("03a426a66fc1af2792b35d9583904c3fb877b49ae5cea45b7a2aa105ffa4c68606")))) -> @@ -4508,7 +5199,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( { - def success[T](v: T) = Success(Expected(v, 36522)) + def success[T](v: T) = Expected(Success(v), 36522) Seq( (CSigmaProp(ProveDlog(Helpers.decodeECPoint("03a426a66fc1af2792b35d9583904c3fb877b49ae5cea45b7a2aa105ffa4c68606"))), true) -> success(CSigmaProp(ProveDlog(Helpers.decodeECPoint("03a426a66fc1af2792b35d9583904c3fb877b49ae5cea45b7a2aa105ffa4c68606")))), @@ -4535,7 +5226,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => property("|| sigma equivalence") { verifyCases( { - def success[T](v: T) = Success(Expected(v, 36494)) + def success[T](v: T) = Expected(Success(v), 36494) Seq( (CSigmaProp(ProveDlog(Helpers.decodeECPoint("02ea9bf6da7f512386c6ca509d40f8c5e7e0ffb3eea5dc3c398443ea17f4510798"))), CSigmaProp(ProveDlog(Helpers.decodeECPoint("03a426a66fc1af2792b35d9583904c3fb877b49ae5cea45b7a2aa105ffa4c68606")))) -> @@ -4578,7 +5269,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( { - def success[T](v: T) = Success(Expected(v, 36588)) + def success[T](v: T) = Expected(Success(v), 36588) Seq( (CSigmaProp(ProveDlog(Helpers.decodeECPoint("03a426a66fc1af2792b35d9583904c3fb877b49ae5cea45b7a2aa105ffa4c68606"))), false) -> success(CSigmaProp(ProveDlog(Helpers.decodeECPoint("03a426a66fc1af2792b35d9583904c3fb877b49ae5cea45b7a2aa105ffa4c68606")))), @@ -4606,16 +5297,16 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( Seq( CSigmaProp(ProveDlog(Helpers.decodeECPoint("039d0b1e46c21540d033143440d2fb7dd5d650cf89981c99ee53c6e0374d2b1b6f"))) - -> Success(Expected( - Helpers.decodeBytes("0008cd039d0b1e46c21540d033143440d2fb7dd5d650cf89981c99ee53c6e0374d2b1b6f"), - cost = 35902)) + -> Expected(Success( + Helpers.decodeBytes("0008cd039d0b1e46c21540d033143440d2fb7dd5d650cf89981c99ee53c6e0374d2b1b6f")), + cost = 35902) ), existingFeature((x: SigmaProp) => x.propBytes, "{ (x: SigmaProp) => x.propBytes }", FuncValue(Vector((1, SSigmaProp)), SigmaPropBytes(ValUse(1, SSigmaProp))))) } - // TODO HF: implement allZK func https://github.com/ScorexFoundation/sigmastate-interpreter/issues/543 + // TODO HF (3h): implement allZK func https://github.com/ScorexFoundation/sigmastate-interpreter/issues/543 property("allZK equivalence") { lazy val allZK = newFeature((x: Coll[SigmaProp]) => SigmaDsl.allZK(x), "{ (x: Coll[SigmaProp]) => allZK(x) }") @@ -4624,7 +5315,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => } } - // TODO HF: implement anyZK func https://github.com/ScorexFoundation/sigmastate-interpreter/issues/543 + // TODO HF (3h): implement anyZK func https://github.com/ScorexFoundation/sigmastate-interpreter/issues/543 property("anyZK equivalence") { lazy val anyZK = newFeature((x: Coll[SigmaProp]) => SigmaDsl.anyZK(x), "{ (x: Coll[SigmaProp]) => anyZK(x) }") @@ -4634,7 +5325,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => } property("allOf equivalence") { - def success[T](v: T, c: Int) = Success(Expected(v, c)) + def success[T](v: T, c: Int) = Expected(Success(v), c) verifyCases( Seq( @@ -4656,7 +5347,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => } property("anyOf equivalence") { - def success[T](v: T, c: Int) = Success(Expected(v, c)) + def success[T](v: T, c: Int) = Expected(Success(v), c) verifyCases( Seq( @@ -4681,9 +5372,9 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( Seq( (Helpers.decodeGroupElement("02288f0e55610c3355c89ed6c5de43cf20da145b8c54f03a29f481e540d94e9a69") - -> Success(Expected( - CSigmaProp(ProveDlog(Helpers.decodeECPoint("02288f0e55610c3355c89ed6c5de43cf20da145b8c54f03a29f481e540d94e9a69"))), - cost = 45935))) + -> Expected(Success( + CSigmaProp(ProveDlog(Helpers.decodeECPoint("02288f0e55610c3355c89ed6c5de43cf20da145b8c54f03a29f481e540d94e9a69")))), + cost = 45935)) ), existingFeature({ (x: GroupElement) => SigmaDsl.proveDlog(x) }, "{ (x: GroupElement) => proveDlog(x) }", @@ -4694,7 +5385,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( Seq( (Helpers.decodeGroupElement("039c15221a318d27c186eba84fa8d986c1f63bbd9f8060380c9bfc2ef455d8346a") - -> Success(Expected( + -> Expected(Success( CSigmaProp( ProveDHTuple( Helpers.decodeECPoint("039c15221a318d27c186eba84fa8d986c1f63bbd9f8060380c9bfc2ef455d8346a"), @@ -4702,9 +5393,9 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => Helpers.decodeECPoint("039c15221a318d27c186eba84fa8d986c1f63bbd9f8060380c9bfc2ef455d8346a"), Helpers.decodeECPoint("039c15221a318d27c186eba84fa8d986c1f63bbd9f8060380c9bfc2ef455d8346a") ) - ), + )), cost = 76215 - ))) + )) ), existingFeature({ (x: GroupElement) => SigmaDsl.proveDHTuple(x, x, x, x) }, "{ (x: GroupElement) => proveDHTuple(x, x, x, x) }", @@ -4734,11 +5425,11 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => verifyCases( { - def success[T](v: T) = Success(Expected(v, 37694)) + def success[T](v: T) = Expected(Success(v), 37694) Seq( - (Helpers.decodeBytes(""), 0) -> Failure(new java.nio.BufferUnderflowException()), + (Helpers.decodeBytes(""), 0) -> Expected(new java.nio.BufferUnderflowException()), - // TODO HF: fix for trees without segregation flag + // TODO HF (2h): fix for trees without segregation flag // NOTE: constants count is serialized erroneously in the following 2 cases (Coll(t1.bytes:_*), 0) -> success(Helpers.decodeBytes("000008d3")), (Helpers.decodeBytes("000008d3"), 0) -> success(Helpers.decodeBytes("00000008d3")), @@ -4749,7 +5440,7 @@ class SigmaDslSpecification extends SigmaDslTesting { suite => (Coll(t3.bytes:_*), 0) -> success(Helpers.decodeBytes("100108d27300")), (Helpers.decodeBytes("100108d37300"), 0) -> success(Helpers.decodeBytes("100108d27300")), (Coll(t3.bytes:_*), 1) -> success(Helpers.decodeBytes("100108d37300")), - (Coll(t4.bytes:_*), 0) -> Failure(new AssertionError("assertion failed: expected new constant to have the same SInt$ tpe, got SSigmaProp")) + (Coll(t4.bytes:_*), 0) -> Expected(new AssertionError("assertion failed: expected new constant to have the same SInt$ tpe, got SSigmaProp")) ) }, existingFeature( diff --git a/sigmastate/src/test/scala/special/sigma/SigmaDslTesting.scala b/sigmastate/src/test/scala/special/sigma/SigmaDslTesting.scala index ce066c9f26..3c9265cb3c 100644 --- a/sigmastate/src/test/scala/special/sigma/SigmaDslTesting.scala +++ b/sigmastate/src/test/scala/special/sigma/SigmaDslTesting.scala @@ -115,30 +115,32 @@ class SigmaDslTesting extends PropSpec .collect { case in: DLogProverInput => in.publicImage } } - /** Type of the language feature to be tested. */ - sealed trait FeatureType - case object ExistingFeature extends FeatureType - case object AddedFeature extends FeatureType - val LogScriptDefault: Boolean = false - /** Test case descriptor of the language feature. - * - * @param featureType type of the language feature - * @param scalaFunc feature semantic given by scala code - * @param expectedExpr expression which represents the test case code - * @param oldImpl function that executes the feature using v3 interpreter implementation - * @param newImpl function that executes the feature using v4 interpreter implementation - */ - case class FeatureTest[A, B](featureType: FeatureType, - script: String, - scalaFunc: A => B, - expectedExpr: Option[SValue], - oldImpl: () => CompiledFunc[A, B], - newImpl: () => CompiledFunc[A, B], - printExpectedExpr: Boolean = true, - logScript: Boolean = LogScriptDefault - ) { + /** Descriptor of the language feature. */ + trait Feature[A, B] { + + /** Script containing this feature. */ + def script: String + + /** Semantics of this feature in ErgoTree v1 given by scala code */ + def scalaFunc: A => B + + /** Semantics of this feature in ErgoTree v2 given by scala code */ + def scalaFuncNew: A => B = scalaFunc + + /** Expression which represents the test case code. */ + def expectedExpr: Option[SValue] + + /** Function that executes the feature using v3 interpreter implementation. */ + def oldImpl: () => CompiledFunc[A, B] + + /** Function that executes the feature using v4 interpreter implementation. */ + def newImpl: () => CompiledFunc[A, B] + + def printExpectedExpr: Boolean + def logScript: Boolean + /** Called to print test case expression (when it is not given). * Can be used to create regression test cases. */ def printSuggestion(cf: CompiledFunc[_,_]): Unit = { @@ -165,7 +167,7 @@ class SigmaDslTesting extends PropSpec true } - /** v3 implementation*/ + /** v3 and v4 implementation*/ private var _oldF: CompiledFunc[A, B] = _ def oldF: CompiledFunc[A, B] = { if (_oldF == null) { @@ -175,7 +177,7 @@ class SigmaDslTesting extends PropSpec _oldF } - /** v4 implementation*/ + /** v5 implementation*/ private var _newF: CompiledFunc[A, B] = _ def newF: CompiledFunc[A, B] = { if (_newF == null) { @@ -185,55 +187,35 @@ class SigmaDslTesting extends PropSpec _newF } - /** Depending on the featureType compares the old and new implementations against + /** Compares the old and new implementations against * semantic function (scalaFunc) on the given input. - * @param input data which is used to execute feature + * + * @param input data which is used to execute feature + * @param logInputOutput if true, then pretty-print input and output values * @return result of feature execution */ - def checkEquality(input: A, logInputOutput: Boolean = false): Try[(B, Int)] = featureType match { - case ExistingFeature => - // check both implementations with Scala semantic - val oldRes = checkEq(scalaFunc)(oldF)(input) - - if (!(newImpl eq oldImpl)) { - val newRes = checkEq(scalaFunc)(newF)(input) - newRes shouldBe oldRes - } - if (logInputOutput) - println(s"(${SigmaPPrint(input, height = 550, width = 150)}, ${SigmaPPrint(oldRes, height = 550, width = 150)}),${if (logScript) " // " + script else ""}") - oldRes - case AddedFeature => - val oldRes = Try(oldF(input)) - oldRes.isFailure shouldBe true - if (!(newImpl eq oldImpl)) { - val newRes = checkEq(scalaFunc)(newF)(input) - newRes shouldBe oldRes - } - oldRes - } + def checkEquality(input: A, logInputOutput: Boolean = false): Try[(B, Int)] /** Depending on the featureType compares the old and new implementations against * semantic function (scalaFunc) on the given input, also checking the given expected result. */ - def checkExpected(input: A, expectedResult: B): Unit = { - featureType match { - case ExistingFeature => - // check both implementations with Scala semantic - val (oldRes, _) = checkEq(scalaFunc)(oldF)(input).get - oldRes shouldBe expectedResult - - if (!(newImpl eq oldImpl)) { - val (newRes, _) = checkEq(scalaFunc)(newF)(input).get - newRes shouldBe expectedResult - } + def checkExpected(input: A, expectedResult: Expected[B]): Unit - case AddedFeature => - Try(oldF(input)).isFailure shouldBe true - if (!(newImpl eq oldImpl)) { - val (newRes, _) = checkEq(scalaFunc)(newF)(input).get - newRes shouldBe expectedResult - } - } - } + /** Tests this feature on the given input. + * @param input data value + * @param expectedResult the result which is expected + */ + def testCase(input: A, expectedResult: Try[B], + printTestCases: Boolean = PrintTestCasesDefault, + failOnTestVectors: Boolean = FailOnTestVectorsDefault): Unit + + /** Tests this feature by embedding it in the verification script. + * @param input data value + * @param expectedResult the result values which are expected + * @see checkVerify + */ + def verifyCase(input: A, expectedResult: Expected[B], + printTestCases: Boolean = PrintTestCasesDefault, + failOnTestVectors: Boolean = FailOnTestVectorsDefault): Unit /** Creates a new ErgoLikeContext using given [[CostingDataContext]] as template. * Copies most of the data from ctx and the missing data is taken from the args. @@ -269,14 +251,14 @@ class SigmaDslTesting extends PropSpec /** Executes the default feature verification wrapper script using: * 1) the given input - * 2) the given expected intermediate result - * 3) the total expected execution cost of the verification + * 2) the given expected results (values and costs) */ - def checkVerify(input: A, expectedRes: B, expectedCost: Int): Unit = { + def checkVerify(input: A, expected: Expected[B]): Unit = { val tpeA = Evaluation.rtypeToSType(oldF.tA) val tpeB = Evaluation.rtypeToSType(oldF.tB) val prover = new FeatureProvingInterpreter() + val verifier = new ErgoLikeInterpreter()(createIR()) { type CTX = ErgoLikeContext } // Create synthetic ErgoTree which uses all main capabilities of evaluation machinery. // 1) first-class functions (lambdas); 2) Context variables; 3) Registers; 4) Equality @@ -315,7 +297,7 @@ class SigmaDslTesting extends PropSpec val pkBobBytes = ValueSerializer.serialize(prover.pubKeys(1).toSigmaProp) val pkCarolBytes = ValueSerializer.serialize(prover.pubKeys(2).toSigmaProp) val newRegisters = Map( - ErgoBox.R4 -> Constant[SType](expectedRes.asInstanceOf[SType#WrappedType], tpeB), + ErgoBox.R4 -> Constant[SType](expected.value.get.asInstanceOf[SType#WrappedType], tpeB), ErgoBox.R5 -> ByteArrayConstant(pkBobBytes) ) @@ -359,10 +341,6 @@ class SigmaDslTesting extends PropSpec val pr = prover.prove(compiledTree, ergoCtx, fakeMessage).getOrThrow - implicit val IR: IRContext = createIR() - - val verifier = new ErgoLikeInterpreter() { type CTX = ErgoLikeContext } - val verificationCtx = ergoCtx.withExtension(pr.extension) val vres = verifier.verify(compiledTree, verificationCtx, pr, fakeMessage) @@ -370,23 +348,200 @@ class SigmaDslTesting extends PropSpec case Success((ok, cost)) => ok shouldBe true val verificationCost = cost.toIntExact -// NOTE: you can uncomment this line and comment the assertion in order to -// simplify adding new test vectors for cost estimation -// if (expectedCost != verificationCost) { -// println(s"Script: $script") -// println(s"Cost: $verificationCost\n") -// } - assertResult(expectedCost, - s"Actual verify() cost $cost != expected $expectedCost")(verificationCost) + // NOTE: you can uncomment this line and comment the assertion in order to + // simplify adding new test vectors for cost estimation + // if (expectedCost != verificationCost) { + // println(s"Script: $script") + // println(s"Cost: $verificationCost\n") + // } + assertResult(expected.cost, + s"Actual verify() cost $cost != expected ${expected.cost}")(verificationCost) case Failure(t) => throw t } } } - object FeatureTest { - /** Cost of the feature verify script. - * @see checkVerify() */ - val VerifyScriptCost = 6317 + + case class ExistingFeature[A: RType, B: RType]( + script: String, + scalaFunc: A => B, + expectedExpr: Option[SValue], + printExpectedExpr: Boolean = true, + logScript: Boolean = LogScriptDefault + )(implicit IR: IRContext) extends Feature[A, B] { + + val oldImpl = () => func[A, B](script) + val newImpl = oldImpl // TODO HF (16h): use actual new implementation here + + def checkEquality(input: A, logInputOutput: Boolean = false): Try[(B, Int)] = { + // check the old implementation against Scala semantic function + val oldRes = checkEq(scalaFunc)(oldF)(input) + + if (!(newImpl eq oldImpl)) { + // check the new implementation against Scala semantic function + val newRes = checkEq(scalaFunc)(newF)(input) + newRes shouldBe oldRes + } + if (logInputOutput) + println(s"(${SigmaPPrint(input, height = 550, width = 150)}, ${SigmaPPrint(oldRes, height = 550, width = 150)}),${if (logScript) " // " + script else ""}") + oldRes + } + + /** Depending on the featureType compares the old and new implementations against + * semantic function (scalaFunc) on the given input, also checking the given expected result. + */ + override def checkExpected(input: A, expected: Expected[B]): Unit = { + // check the old implementation with Scala semantic + val (oldRes, _) = checkEq(scalaFunc)(oldF)(input).get + oldRes shouldBe expected.value.get + + if (!(newImpl eq oldImpl)) { + // check the new implementation with Scala semantic + val (newRes, _) = checkEq(scalaFunc)(newF)(input).get + newRes shouldBe expected.value.get + } + } + + override def testCase(input: A, + expectedResult: Try[B], + printTestCases: Boolean, + failOnTestVectors: Boolean): Unit = { + val res = checkEquality(input, printTestCases).map(_._1) + checkResult(res, expectedResult, failOnTestVectors) + } + + override def verifyCase(input: A, + expected: Expected[B], + printTestCases: Boolean, + failOnTestVectors: Boolean): Unit = { + val funcRes = checkEquality(input, printTestCases) + + checkResult(funcRes.map(_._1), expected.value, failOnTestVectors) + + expected.value match { + case Success(y) => + checkVerify(input, expected) + case _ => + } + } + } + + case class ChangedFeature[A: RType, B: RType]( + script: String, + scalaFunc: A => B, + override val scalaFuncNew: A => B, + expectedExpr: Option[SValue], + printExpectedExpr: Boolean = true, + logScript: Boolean = LogScriptDefault + )(implicit IR: IRContext) extends Feature[A, B] { + + val oldImpl = () => func[A, B](script) + val newImpl = oldImpl // TODO HF (16h): use actual new implementation here + + def checkEquality(input: A, logInputOutput: Boolean = false): Try[(B, Int)] = { + // check the old implementation against Scala semantic function + val oldRes = checkEq(scalaFunc)(oldF)(input) + + if (!(newImpl eq oldImpl)) { + // check the new implementation against Scala semantic function + val newRes = checkEq(scalaFuncNew)(newF)(input) + } + if (logInputOutput) { + val inputStr = SigmaPPrint(input, height = 550, width = 150) + val oldResStr = SigmaPPrint(oldRes, height = 550, width = 150) + val scriptComment = if (logScript) " // " + script else "" + println(s"($inputStr, $oldResStr),$scriptComment") + } + oldRes + } + + /** Depending on the featureType compares the old and new implementations against + * semantic function (scalaFunc) on the given input, also checking the given expected result. + */ + override def checkExpected(input: A, expected: Expected[B]): Unit = { + // check the old implementation with Scala semantic + val (oldRes, _) = checkEq(scalaFunc)(oldF)(input).get + oldRes shouldBe expected.value.get + + if (!(newImpl eq oldImpl)) { + // check the new implementation with Scala semantic + val (newRes, _) = checkEq(scalaFuncNew)(newF)(input).get + newRes shouldBe expected.newValue.get + } + } + + override def testCase(input: A, + expectedResult: Try[B], + printTestCases: Boolean, + failOnTestVectors: Boolean): Unit = { + val res = checkEquality(input, printTestCases).map(_._1) + checkResult(res, expectedResult, failOnTestVectors) + } + + override def verifyCase(input: A, + expected: Expected[B], + printTestCases: Boolean, + failOnTestVectors: Boolean): Unit = { + val funcRes = checkEquality(input, printTestCases) + + checkResult(funcRes.map(_._1), expected.value, failOnTestVectors) + + expected.value match { + case Success(y) => + checkVerify(input, expected) + case _ => + } + } + } + + case class NewFeature[A: RType, B: RType]( + script: String, + override val scalaFuncNew: A => B, + expectedExpr: Option[SValue], + printExpectedExpr: Boolean = true, + logScript: Boolean = LogScriptDefault + )(implicit IR: IRContext) extends Feature[A, B] { + override def scalaFunc: A => B = { x => + sys.error(s"Semantic Scala function is not defined for old implementation: $this") + } + + val oldImpl = () => func[A, B](script) + val newImpl = oldImpl // TODO HF (16h): use actual new implementation here + + override def checkEquality(input: A, logInputOutput: Boolean = false): Try[(B, Int)] = { + val oldRes = Try(oldF(input)) + oldRes.isFailure shouldBe true + if (!(newImpl eq oldImpl)) { + val newRes = checkEq(scalaFuncNew)(newF)(input) + } + oldRes + } + + override def checkExpected(input: A, expected: Expected[B]): Unit = { + Try(oldF(input)).isFailure shouldBe true + if (!(newImpl eq oldImpl)) { + val (newRes, _) = checkEq(scalaFuncNew)(newF)(input).get + newRes shouldBe expected.newValue.get + } + } + + override def testCase(input: A, + expectedResult: Try[B], + printTestCases: Boolean, + failOnTestVectors: Boolean): Unit = { + val res = checkEquality(input, printTestCases).map(_._1) + res.isFailure shouldBe true + Try(scalaFuncNew(input)) shouldBe expectedResult + } + + override def verifyCase(input: A, + expected: Expected[B], + printTestCases: Boolean, + failOnTestVectors: Boolean): Unit = { + val funcRes = checkEquality(input, printTestCases) + funcRes.isFailure shouldBe true + Try(scalaFunc(input)) shouldBe expected.value + } } /** Represents expected result of successful feature test exectuion. @@ -394,12 +549,27 @@ class SigmaDslTesting extends PropSpec * @param cost expected cost value of the verification execution * @see [[testCases]] */ - case class Expected[+A](value: A, cost: Int) + case class Expected[+A](value: Try[A], cost: Int) { + def newCost: Int = cost + def newValue: Try[A] = value + } + + object Expected { + def apply[A](error: Throwable) = new Expected[A](Failure(error), 0) + def apply[A](value: Try[A], cost: Int, expectedNewCost: Int) = new Expected(value, cost) { + override val newCost = expectedNewCost + } + def apply[A](value: Try[A], cost: Int, expectedNewValue: Try[A], expectedNewCost: Int) = new Expected(value, cost) { + override val newCost = expectedNewCost + override val newValue = expectedNewValue + } + } - /** Describes existing language feature which should be equally supported in both v3 and - * v4 of the language. + /** Describes existing language feature which should be equally supported in both + * Script v1 (v3.x and v4.x releases) and Script v2 (v5.x) versions of the language. + * A behavior of the given `script` is tested against semantic function. * - * @param scalaFunc semantic function which defines expected behavior of the given script + * @param scalaFunc semantic function for both v1 and v2 script interpretations * @param script the script to be tested against semantic function * @param expectedExpr expected ErgoTree expression which corresponds to the given script * @return feature test descriptor object which can be used to execute this test case in @@ -407,14 +577,29 @@ class SigmaDslTesting extends PropSpec */ def existingFeature[A: RType, B: RType] (scalaFunc: A => B, script: String, expectedExpr: SValue = null) - (implicit IR: IRContext): FeatureTest[A, B] = { - val oldImpl = () => func[A, B](script) - val newImpl = oldImpl // TODO HF: use actual new implementation here - FeatureTest(ExistingFeature, script, scalaFunc, Option(expectedExpr), oldImpl, newImpl) + (implicit IR: IRContext): Feature[A, B] = { + ExistingFeature(script, scalaFunc, Option(expectedExpr)) + } + + /** Describes existing language feature which should be differently supported in both + * Script v1 (v3.x and v4.x releases) and Script v2 (v5.x) versions of the language. + * The behavior of the given `script` is tested against the given semantic functions. + * + * @param scalaFunc semantic function of v1 language version + * @param scalaFuncNew semantic function of v2 language version + * @param script the script to be tested against semantic functions + * @param expectedExpr expected ErgoTree expression which corresponds to the given script + * @return feature test descriptor object which can be used to execute this test case in + * various ways + */ + def changedFeature[A: RType, B: RType] + (scalaFunc: A => B, scalaFuncNew: A => B, script: String, expectedExpr: SValue = null) + (implicit IR: IRContext): Feature[A, B] = { + ChangedFeature(script, scalaFunc, scalaFuncNew, Option(expectedExpr)) } - /** Describes a NEW language feature which must NOT be supported in v3 and - * must BE supported in v4 of the language. + /** Describes a NEW language feature which must NOT be supported in v4 and + * must BE supported in v5 of the language. * * @param scalaFunc semantic function which defines expected behavior of the given script * @param script the script to be tested against semantic function @@ -424,10 +609,8 @@ class SigmaDslTesting extends PropSpec */ def newFeature[A: RType, B: RType] (scalaFunc: A => B, script: String, expectedExpr: SValue = null) - (implicit IR: IRContext): FeatureTest[A, B] = { - val oldImpl = () => func[A, B](script) - val newImpl = oldImpl // TODO HF: use actual new implementation here - FeatureTest(AddedFeature, script, scalaFunc, Option(expectedExpr), oldImpl, newImpl) + (implicit IR: IRContext): Feature[A, B] = { + NewFeature(script, scalaFunc, Option(expectedExpr)) } val contextGen: Gen[Context] = ergoLikeContextGen.map(c => c.toSigmaContext(isCost = false)) @@ -445,7 +628,8 @@ class SigmaDslTesting extends PropSpec rootCause(exception).getClass shouldBe expectedException.getClass case _ => if (failOnTestVectors) { - assertResult(expectedRes, s"Actual: ${SigmaPPrint(res, height = 150).plainText}")(res) + val actual = res.fold(t => Failure(rootCause(t)), Success(_)) + assertResult(expectedRes, s"Actual: ${SigmaPPrint(actual, height = 150).plainText}")(actual) } else { if (expectedRes != res) { @@ -463,23 +647,14 @@ class SigmaDslTesting extends PropSpec */ def testCases[A: Ordering : Arbitrary : ClassTag, B] (cases: Seq[(A, Try[B])], - f: FeatureTest[A, B], + f: Feature[A, B], printTestCases: Boolean = PrintTestCasesDefault, failOnTestVectors: Boolean = FailOnTestVectorsDefault, preGeneratedSamples: Option[Seq[A]] = None): Unit = { System.gc() // force GC to avoid occasional OOM exception val table = Table(("x", "y"), cases:_*) forAll(table) { (x: A, expectedRes: Try[B]) => - val res = f.checkEquality(x, printTestCases).map(_._1) - - // TODO HF: remove this `if` once newImpl is implemented - f.featureType match { - case ExistingFeature => - checkResult(res, expectedRes, failOnTestVectors) - case AddedFeature => - res.isFailure shouldBe true - Try(f.scalaFunc(x)) shouldBe expectedRes - } + f.testCase(x, expectedRes, printTestCases, failOnTestVectors) } test(preGeneratedSamples, f, printTestCases) } @@ -495,31 +670,15 @@ class SigmaDslTesting extends PropSpec * if None, then the given Arbitrary is used to generate samples */ def verifyCases[A: Ordering : Arbitrary : ClassTag, B] - (cases: Seq[(A, Try[Expected[B]])], - f: FeatureTest[A, B], + (cases: Seq[(A, Expected[B])], + f: Feature[A, B], printTestCases: Boolean = PrintTestCasesDefault, failOnTestVectors: Boolean = FailOnTestVectorsDefault, preGeneratedSamples: Option[Seq[A]] = None): Unit = { val table = Table(("x", "y"), cases:_*) - forAll(table) { (x: A, expectedRes: Try[Expected[B]]) => - val funcRes = f.checkEquality(x, printTestCases) - - val expectedResValue = expectedRes.map(_.value) - // TODO HF: remove this `match` once newImpl is implemented - f.featureType match { - case ExistingFeature => - checkResult(funcRes.map(_._1), expectedResValue, failOnTestVectors) - - (funcRes, expectedRes) match { - case (Success((y, _)), Success(Expected(_, expectedCost))) => - f.checkVerify(x, y, expectedCost) - case _ => - } - case AddedFeature => - funcRes.isFailure shouldBe true - Try(f.scalaFunc(x)) shouldBe expectedResValue - } + forAll(table) { (x: A, expectedRes: Expected[B]) => + f.verifyCase(x, expectedRes, printTestCases, failOnTestVectors) } test(preGeneratedSamples, f, printTestCases) } @@ -551,7 +710,7 @@ class SigmaDslTesting extends PropSpec */ def test[A: Arbitrary: Ordering : ClassTag, B] (preGeneratedSamples: Option[Seq[A]], - f: FeatureTest[A, B], + f: Feature[A, B], printTestCases: Boolean): Unit = { // either get provides or generate new samples (in sorted order) val samples = preGeneratedSamples.getOrElse(genSamples[A](DefaultMinSuccessful)) @@ -562,12 +721,12 @@ class SigmaDslTesting extends PropSpec } } - def test[A: Arbitrary : Ordering : ClassTag, B](samples: Seq[A], f: FeatureTest[A, B]): Unit = { + def test[A: Arbitrary : Ordering : ClassTag, B](samples: Seq[A], f: Feature[A, B]): Unit = { test(Some(samples), f, PrintTestCasesDefault) } def test[A: Arbitrary : Ordering : ClassTag, B] - (f: FeatureTest[A, B], + (f: Feature[A, B], printTestCases: Boolean = PrintTestCasesDefault): Unit = { test(None, f, printTestCases) }