diff --git a/src/main/scala/org/ergoplatform/ErgoBox.scala b/src/main/scala/org/ergoplatform/ErgoBox.scala index cbb61758ac..9336e74a9b 100644 --- a/src/main/scala/org/ergoplatform/ErgoBox.scala +++ b/src/main/scala/org/ergoplatform/ErgoBox.scala @@ -99,7 +99,7 @@ object ErgoBox { val size: Short = 32 } - val MaxBoxSize: Int = 64 * 1024 + val MaxBoxSize: Int = ErgoConstants.MaxBoxSize.get val STokenType = STuple(SByteArray, SLong) val STokensRegType = SCollection(STokenType) @@ -133,9 +133,9 @@ object ErgoBox { val TokensRegId: MandatoryRegisterId = R2 val ReferenceRegId: MandatoryRegisterId = R3 - val MaxTokens: Byte = 4 + val MaxTokens: Byte = ErgoConstants.MaxTokens.get - val maxRegisters = 10 + val maxRegisters: Int = ErgoConstants.MaxRegisters.get val mandatoryRegisters: Vector[MandatoryRegisterId] = Vector(R0, R1, R2, R3) val nonMandatoryRegisters: Vector[NonMandatoryRegisterId] = Vector(R4, R5, R6, R7, R8, R9) val startingNonMandatoryIndex: Byte = nonMandatoryRegisters.head.number diff --git a/src/main/scala/org/ergoplatform/ErgoConstants.scala b/src/main/scala/org/ergoplatform/ErgoConstants.scala new file mode 100644 index 0000000000..8bdcdd0551 --- /dev/null +++ b/src/main/scala/org/ergoplatform/ErgoConstants.scala @@ -0,0 +1,66 @@ +package org.ergoplatform + +import sigmastate.{AtLeast, SBigInt, SPrimType} + +case class SizeConstant[T: Numeric](value: T, id: Short, description: String) { + def get: T = value +} + +/** + * Fundamental constants that are used in sigma's logic and checks + */ +object ErgoConstants { + + object MaxInputSize extends SizeConstant[Int](1024 * 1024 * 1, 1, + "Input size should not be greater then provided value") { + } + + object MaxTreeDepth extends SizeConstant[Int](110, 2, + "Max tree depth should not be greater then provided value") { + } + + object MaxByteArrayLength extends SizeConstant[Int](10000, 3, + "Max bytearray length") { + } + + object MaxTokens extends SizeConstant[Byte](4, 6, + "Tokens count should not be greater than provided value") { + } + + object MaxRegisters extends SizeConstant[Int](10, 7, + "Registers count should not be greater than provided value") { + } + + object MaxBoxSize extends SizeConstant[Int](64 * 1024, 8, + "Box size should not be greater than provided value") { + } + + object MaxBigIntSizeInBytes extends SizeConstant[Long](32L, 9, + "BigInt size in bytes should not be greater than provided value") { + } + + object MaxTupleLength extends SizeConstant[Int](255, 10, + "Tuple length should not be greater than provided value") { + } + + object MaxHeaders extends SizeConstant[Int](10, 11, + "Headers count should not be greater than provided value") { + } + + object MaxChildrenCountForAtLeastOp extends SizeConstant[Int](255, 12, + "Max children count should not be greater than provided value") { + } + + val ConstTable: Seq[SizeConstant[_]] = Seq( + MaxInputSize, + MaxTreeDepth, + MaxByteArrayLength, + MaxTokens, + MaxRegisters, + MaxBoxSize, + MaxBigIntSizeInBytes, + MaxTupleLength, + MaxHeaders, + MaxChildrenCountForAtLeastOp + ) +} diff --git a/src/main/scala/org/ergoplatform/ErgoLikeContext.scala b/src/main/scala/org/ergoplatform/ErgoLikeContext.scala index e6df37ccb9..abd15e913f 100644 --- a/src/main/scala/org/ergoplatform/ErgoLikeContext.scala +++ b/src/main/scala/org/ergoplatform/ErgoLikeContext.scala @@ -108,7 +108,7 @@ object ErgoLikeContext { val dummyPreHeader: PreHeader = null /** Maximimum number of headers in `headers` collection of the context. */ - val MaxHeaders = 10 + val MaxHeaders = ErgoConstants.MaxHeaders.get def apply(currentHeight: Height, lastBlockUtxoRoot: AvlTreeData, diff --git a/src/main/scala/sigmastate/interpreter/Interpreter.scala b/src/main/scala/sigmastate/interpreter/Interpreter.scala index 29fa032b50..aab62c3310 100644 --- a/src/main/scala/sigmastate/interpreter/Interpreter.scala +++ b/src/main/scala/sigmastate/interpreter/Interpreter.scala @@ -2,17 +2,18 @@ package sigmastate.interpreter import java.util -import org.bitbucket.inkytonik.kiama.rewriting.Rewriter.{strategy, rule, everywherebu} +import org.bitbucket.inkytonik.kiama.rewriting.Rewriter.{everywherebu, rule, strategy} import org.bitbucket.inkytonik.kiama.rewriting.Strategy -import sigmastate.basics.DLogProtocol.{FirstDLogProverMessage, DLogInteractiveProver} +import org.ergoplatform.ErgoConstants +import sigmastate.basics.DLogProtocol.{DLogInteractiveProver, FirstDLogProverMessage} import scorex.util.ScorexLogging import sigmastate.SCollection.SByteArray import sigmastate.Values._ import sigmastate.eval.{IRContext, Sized} import sigmastate.lang.Terms.ValueOps import sigmastate.basics._ -import sigmastate.interpreter.Interpreter.{VerificationResult, ScriptEnv} -import sigmastate.lang.exceptions.InterpreterException +import sigmastate.interpreter.Interpreter.{ScriptEnv, VerificationResult} +import sigmastate.lang.exceptions.{CosterException, InterpreterException} import sigmastate.serialization.ValueSerializer import sigmastate.utxo.DeserializeContext import sigmastate.{SType, _} @@ -28,7 +29,7 @@ trait Interpreter extends ScorexLogging { type ProofT = UncheckedTree - final val MaxByteArrayLength = 10000 + final val MaxByteArrayLength = ErgoConstants.MaxByteArrayLength.get /** * Max cost of a script interpreter can accept diff --git a/src/main/scala/sigmastate/serialization/SigmaSerializer.scala b/src/main/scala/sigmastate/serialization/SigmaSerializer.scala index edf2240b68..13771035f7 100644 --- a/src/main/scala/sigmastate/serialization/SigmaSerializer.scala +++ b/src/main/scala/sigmastate/serialization/SigmaSerializer.scala @@ -2,6 +2,7 @@ package sigmastate.serialization import java.nio.ByteBuffer +import org.ergoplatform.ErgoConstants import org.ergoplatform.validation.SigmaValidationSettings import scorex.util.ByteArrayBuilder import sigmastate.lang.exceptions.SerializerException @@ -12,8 +13,8 @@ object SigmaSerializer { type Position = Int type Consumed = Int - val MaxInputSize: Int = 1024 * 1024 * 1 - val MaxTreeDepth: Int = 110 + val MaxInputSize: Int = ErgoConstants.MaxInputSize.get + val MaxTreeDepth: Int = ErgoConstants.MaxTreeDepth.get /** Helper function to be use in serializers. * Starting position is marked and then used to compute number of consumed bytes. diff --git a/src/main/scala/sigmastate/trees.scala b/src/main/scala/sigmastate/trees.scala index a9d957eadb..baf952f85f 100644 --- a/src/main/scala/sigmastate/trees.scala +++ b/src/main/scala/sigmastate/trees.scala @@ -1,12 +1,13 @@ package sigmastate +import org.ergoplatform.ErgoConstants import org.ergoplatform.validation.SigmaValidationSettings import scorex.crypto.hash.{Sha256, Blake2b256, CryptographicHash32} import sigmastate.Operations._ import sigmastate.SCollection.{SIntArray, SByteArray} import sigmastate.SOption.SIntOption import sigmastate.Values._ -import sigmastate.basics.{SigmaProtocol, SigmaProtocolPrivateInput, SigmaProtocolCommonInput} +import sigmastate.basics.{SigmaProtocol, SigmaProtocolCommonInput, SigmaProtocolPrivateInput} import sigmastate.serialization.OpCodes._ import sigmastate.serialization._ import sigmastate.utxo.{Transformer, SimpleTransformerCompanion} @@ -268,7 +269,7 @@ case class AtLeast(bound: Value[SInt.type], input: Value[SCollection[SSigmaProp. object AtLeast extends ValueCompanion { override def opCode: OpCode = AtLeastCode - val MaxChildrenCount = 255 + val MaxChildrenCount: Int = ErgoConstants.MaxChildrenCountForAtLeastOp.get def apply(bound: Value[SInt.type], children: Seq[SigmaPropValue]): AtLeast = AtLeast(bound, ConcreteCollection(children.toIndexedSeq)) diff --git a/src/main/scala/sigmastate/types.scala b/src/main/scala/sigmastate/types.scala index 42881ef229..44eb51df31 100644 --- a/src/main/scala/sigmastate/types.scala +++ b/src/main/scala/sigmastate/types.scala @@ -560,10 +560,13 @@ case object SBoolean extends SPrimType with SEmbeddable with SLogical with SProd override def typeId = typeCode override def ancestors: Seq[SType] = Nil val ToByte = "toByte" - protected override def getMethods() = super.getMethods() ++ Seq( + protected override def getMethods() = super.getMethods() + /* TODO soft-fork: https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479 + ++ Seq( SMethod(this, ToByte, SFunc(this, SByte), 1) .withInfo(PropertyCall, "Convert true to 1 and false to 0"), ) + */ override def mkConstant(v: Boolean): Value[SBoolean.type] = BooleanConstant(v) override def dataSize(v: SType#WrappedType): Long = 1 override def isConstantSize = true @@ -665,7 +668,7 @@ case object SBigInt extends SPrimType with SEmbeddable with SNumericType with SM val RelationOpType = SFunc(Vector(SBigInt, SBigInt), SBoolean) /** The maximum size of BigInteger value in byte array representation. */ - val MaxSizeInBytes: Long = 32L + val MaxSizeInBytes: Long = ErgoConstants.MaxBigIntSizeInBytes.get override def dataSize(v: SType#WrappedType): Long = MaxSizeInBytes @@ -709,7 +712,8 @@ case object SBigInt extends SPrimType with SEmbeddable with SNumericType with SM ModQMethod, PlusModQMethod, MinusModQMethod, - MultModQMethod, + // TODO soft-fork: https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479 + // MultModQMethod, ) } @@ -731,8 +735,10 @@ case object SGroupElement extends SProduct with SPrimType with SEmbeddable with override def typeId = typeCode override def coster: Option[CosterFactory] = Some(Coster(_.GroupElementCoster)) protected override def getMethods(): Seq[SMethod] = super.getMethods() ++ Seq( + /* TODO soft-fork: https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479 SMethod(this, "isIdentity", SFunc(this, SBoolean), 1) .withInfo(PropertyCall, "Checks if this value is identity element of the eliptic curve group."), + */ SMethod(this, "getEncoded", SFunc(IndexedSeq(this), SByteArray), 2) .withIRInfo(MethodCallIrBuilder) .withInfo(PropertyCall, "Get an encoding of the point value."), @@ -906,7 +912,9 @@ object SOption extends STypeCompanion { IsDefinedMethod, GetMethod, GetOrElseMethod, + /* TODO soft-fork: https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479 FoldMethod, + */ MapMethod, FilterMethod, ) @@ -1025,9 +1033,12 @@ object SCollection extends STypeCompanion with MethodByNameUnapply { ArgInfo("p", "the predicate used to test elements.")) val AppendMethod = SMethod(this, "append", SFunc(IndexedSeq(ThisType, ThisType), ThisType, Seq(paramIV)), 9) + .withIRInfo({ + case (builder, obj, _, Seq(xs), _) => + builder.mkAppend(obj.asCollection[SType], xs.asCollection[SType]) + }) .withInfo(Append, "Puts the elements of other collection after the elements of this collection (concatenation of 2 collections)", ArgInfo("other", "the collection to append at the end of this")) - val ApplyMethod = SMethod(this, "apply", SFunc(IndexedSeq(ThisType, SInt), tIV, Seq(tIV)), 10) .withInfo(ByIndex, """The element at given index. @@ -1138,26 +1149,34 @@ object SCollection extends STypeCompanion with MethodByNameUnapply { FilterMethod, AppendMethod, ApplyMethod, + /* TODO soft-fork: https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479 BitShiftLeftMethod, BitShiftRightMethod, BitShiftRightZeroedMethod, + */ IndicesMethod, FlatMapMethod, PatchMethod, UpdatedMethod, UpdateManyMethod, + /*TODO soft-fork: https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479 UnionSetsMethod, DiffMethod, IntersectMethod, PrefixLengthMethod, + */ IndexOfMethod, + /* TODO soft-fork: https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479 LastIndexOfMethod, FindMethod, + */ ZipMethod, + /* TODO soft-fork: https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479 DistinctMethod, StartsWithMethod, EndsWithMethod, MapReduceMethod, + */ ) def apply[T <: SType](elemType: T): SCollection[T] = SCollectionType(elemType) def apply[T <: SType](implicit elemType: T, ov: Overload1): SCollection[T] = SCollectionType(elemType) @@ -1255,7 +1274,9 @@ object STuple extends STypeCompanion { lazy val colMethods = { val subst = Map(SCollection.tIV -> SAny) - SCollection.methods.map { m => + // TODO: implement other + val activeMethods = Set(1.toByte, 10.toByte) + SCollection.methods.filter(m => activeMethods.contains(m.methodId)).map { m => m.copy(stype = SigmaTyper.applySubst(m.stype, subst).asFunc) } } @@ -1263,7 +1284,7 @@ object STuple extends STypeCompanion { def methods: Seq[SMethod] = sys.error(s"Shouldn't be called.") def apply(items: SType*): STuple = STuple(items.toIndexedSeq) - val MaxTupleLength = 255 + val MaxTupleLength: Int = ErgoConstants.MaxTupleLength.get private val componentNames = Array.tabulate(MaxTupleLength){ i => s"_${i + 1}" } def componentNameByIndex(i: Int): String = try componentNames(i) diff --git a/src/test/scala/sigmastate/helpers/SigmaTestingCommons.scala b/src/test/scala/sigmastate/helpers/SigmaTestingCommons.scala index d22399964f..c848dbb4f3 100644 --- a/src/test/scala/sigmastate/helpers/SigmaTestingCommons.scala +++ b/src/test/scala/sigmastate/helpers/SigmaTestingCommons.scala @@ -104,7 +104,7 @@ trait SigmaTestingCommons extends PropSpec } } - def func[A: RType, B: RType](func: String)(implicit IR: IRContext): A => B = { + def func[A: RType, B: RType](func: String, bindings: (Byte, EvaluatedValue[_ <: SType])*)(implicit IR: IRContext): A => B = { import IR._ import IR.Context._; val tA = RType[A] @@ -130,8 +130,9 @@ trait SigmaTestingCommons extends PropSpec (in: A) => { implicit val cA = tA.classTag val x = fromPrimView(in) - val context = ErgoLikeContext.dummy(createBox(0, TrueProp)) - .withBindings(1.toByte -> Constant[SType](x.asInstanceOf[SType#WrappedType], tpeA)) + val context = + ErgoLikeContext.dummy(createBox(0, TrueProp)) + .withBindings(1.toByte -> Constant[SType](x.asInstanceOf[SType#WrappedType], tpeA)).withBindings(bindings: _*) val calcCtx = context.toSigmaContext(IR, isCost = false) val (res, _) = valueFun(calcCtx) res.asInstanceOf[B] diff --git a/src/test/scala/sigmastate/lang/SigmaTyperTest.scala b/src/test/scala/sigmastate/lang/SigmaTyperTest.scala index 4422de6d34..0f0846d564 100644 --- a/src/test/scala/sigmastate/lang/SigmaTyperTest.scala +++ b/src/test/scala/sigmastate/lang/SigmaTyperTest.scala @@ -159,9 +159,13 @@ class SigmaTyperTest extends PropSpec with PropertyChecks with Matchers with Lan typecheck(env, "(1, 2L).size") shouldBe SInt typecheck(env, "(1, 2L)(0)") shouldBe SInt typecheck(env, "(1, 2L)(1)") shouldBe SLong + typecheck(env, "{ (a: Int) => (1, 2L)(a) }") shouldBe SFunc(IndexedSeq(SInt), SAny) + } + + // TODO soft-fork: https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479 + ignore("tuple advanced operations") { typecheck(env, "(1, 2L).getOrElse(2, 3)") shouldBe SAny typecheck(env, "(1, 2L).slice(0, 2)") shouldBe SCollection(SAny) - typecheck(env, "{ (a: Int) => (1, 2L)(a) }") shouldBe SFunc(IndexedSeq(SInt), SAny) } property("types") { @@ -240,8 +244,10 @@ class SigmaTyperTest extends PropSpec with PropertyChecks with Matchers with Lan typecheck(env, "{ (a: Int) => { val b = a + 1; b } }") shouldBe SFunc(IndexedSeq(SInt), SInt) typecheck(env, "{ (a: Int, box: Box) => a + box.value }") shouldBe SFunc(IndexedSeq(SInt, SBox), SLong) + /* TODO soft-fork: https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479 typecheck(env, "{ (p: (Int, GroupElement), box: Box) => p._1 > box.value && p._2.isIdentity }") shouldBe SFunc(IndexedSeq(STuple(SInt, SGroupElement), SBox), SBoolean) + */ typecheck(env, "{ (p: (Int, SigmaProp), box: Box) => p._1 > box.value && p._2.isProven }") shouldBe SFunc(IndexedSeq(STuple(SInt, SSigmaProp), SBox), SBoolean) @@ -585,21 +591,24 @@ class SigmaTyperTest extends PropSpec with PropertyChecks with Matchers with Lan typefail(env, "true >>> false", 1, 1) } - property("Collection.BitShiftLeft") { + // TODO soft-fork: https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479 + ignore("Collection.BitShiftLeft") { typecheck(env, "Coll(1,2) << 2") shouldBe SCollection(SInt) an [TyperException] should be thrownBy typecheck(env, "Coll(1,2) << true") an [TyperException] should be thrownBy typecheck(env, "Coll(1,2) << 2L") an [TyperException] should be thrownBy typecheck(env, "Coll(1,2) << (2L, 3)") } - property("Collection.BitShiftRight") { + // TODO soft-fork: https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479 + ignore("Collection.BitShiftRight") { typecheck(env, "Coll(1,2) >> 2") shouldBe SCollection(SInt) an [TyperException] should be thrownBy typecheck(env, "Coll(1,2) >> 2L") an [TyperException] should be thrownBy typecheck(env, "Coll(1,2) >> true") an [TyperException] should be thrownBy typecheck(env, "Coll(1,2) >> (2L, 3)") } - property("Collection.BitShiftRightZeroed") { + // TODO soft-fork: https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479 + ignore("Collection.BitShiftRightZeroed") { typecheck(env, "Coll(true, false) >>> 2") shouldBe SCollection(SBoolean) an [TyperException] should be thrownBy typecheck(env, "Coll(1,2) >>> 2") an [TyperException] should be thrownBy typecheck(env, "Coll(true, false) >>> true") diff --git a/src/test/scala/special/sigma/SigmaDslTest.scala b/src/test/scala/special/sigma/SigmaDslTest.scala index 88f0f2b1b5..21b96900e7 100644 --- a/src/test/scala/special/sigma/SigmaDslTest.scala +++ b/src/test/scala/special/sigma/SigmaDslTest.scala @@ -2,25 +2,28 @@ package special.sigma import java.math.BigInteger +import org.ergoplatform.ErgoLikeContext.dummyPubkey +import org.ergoplatform.ErgoScriptPredef.TrueProp import org.ergoplatform.dsl.{SigmaContractSyntax, TestContractSpec} -import org.ergoplatform.{ErgoLikeContext, ErgoLikeTransaction, ErgoBox} +import org.ergoplatform._ import org.scalacheck.Gen.containerOfN import org.scalacheck.{Arbitrary, Gen} import org.scalatest.prop.PropertyChecks -import org.scalatest.{PropSpec, Matchers} +import org.scalatest.{Matchers, PropSpec} import scalan.RType import scorex.crypto.authds.avltree.batch._ import scorex.crypto.authds.{ADKey, ADValue} -import scorex.crypto.hash.{Digest32, Blake2b256} +import scorex.crypto.hash.{Blake2b256, Digest32} import sigma.util.Extensions._ -import sigmastate.Values.{BooleanConstant, IntConstant} +import sigmastate.Values.{BooleanConstant, EvaluatedValue, IntConstant} import sigmastate._ +import sigmastate.Values._ import sigmastate.eval.Extensions._ import sigmastate.eval._ -import sigmastate.helpers.SigmaTestingCommons +import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, ErgoLikeTestInterpreter, SigmaTestingCommons} import sigmastate.interpreter.ContextExtension -import sigmastate.interpreter.Interpreter.ScriptEnv -import special.collection.{Coll, Builder} +import sigmastate.interpreter.Interpreter.{ScriptEnv, ScriptNameProp} +import special.collection.{Builder, Coll} /** This suite tests every method of every SigmaDsl type to be equivalent to @@ -50,20 +53,44 @@ class SigmaDslTest extends PropSpec assert(r1 == r2) } + def getRandomIndex(size: Int): Int = { + val r = scala.util.Random + if (size > 1) r.nextInt(size) else 0 + } + + def makeSlicePair(size: Int): (Int, Int) = { + val r = scala.util.Random + val rBorder = getRandomIndex(size) + val lBorder = getRandomIndex(rBorder) + (lBorder, rBorder) + } + + // TODO: make more effective + def generateIndexColl(maxSize: Int): Coll[Int] = { + var ret: Coll[Int] = Colls.emptyColl + var index = getRandomIndex(maxSize) + while (index > 0) { + ret = ret.append(Colls.fromArray(Array(index))) + index = getRandomIndex(index) + } + ret + } + case class EqualityChecker[T: RType](obj: T) { def apply[R: RType](dslFunc: T => R)(script: String) = checkEq(func[T, R](script))(dslFunc)(obj) } - property("Boolean methods equivalence") { - lazy val toByte = checkEq(func[Boolean,Byte]("{ (x: Boolean) => x.toByte }"))(x => x.toByte) + ignore("Boolean methods equivalence") { + lazy val toByte = checkEq(func[Boolean,Byte]("{ (x: Boolean) => x.toByte }"))((x: Boolean) => x.toByte) forAll { x: Boolean => //TODO soft-fork: for new operation below - //toByte(x) + Seq(toByte).foreach(_(x)) } } property("Byte methods equivalence") { + val toByte = checkEq(func[Byte, Byte]("{ (x: Byte) => x.toByte }"))(x => x.toByte) val toShort = checkEq(func[Byte,Short]("{ (x: Byte) => x.toShort }"))(x => x.toShort) val toInt = checkEq(func[Byte,Int]("{ (x: Byte) => x.toInt }"))(x => x.toInt) val toLong = checkEq(func[Byte,Long]("{ (x: Byte) => x.toLong }"))(x => x.toLong) @@ -76,11 +103,10 @@ class SigmaDslTest extends PropSpec lazy val compareTo = checkEq(func[(Byte, Byte), Int]("{ (x: (Byte, Byte)) => x._1.compareTo(x._2) }"))({ (x: (Byte, Byte)) => x._1.compareTo(x._2) }) forAll { x: Byte => - Seq(toShort, toInt, toLong, toBigInt).foreach(_(x)) + Seq(toByte, toShort, toInt, toLong, toBigInt).foreach(_(x)) } } - property("Int methods equivalence") { val toByte = checkEq(func[Int,Byte]("{ (x: Int) => x.toByte }"))(x => x.toByte) val toShort = checkEq(func[Int,Short]("{ (x: Int) => x.toShort }"))(x => x.toShort) @@ -106,7 +132,105 @@ class SigmaDslTest extends PropSpec //TODO soft-fork: compareTo(x) } } - // TODO soft-fork: add tests for Short, Long, BigInt operations + + property("Short methods equivalence") { + val toByte = checkEq(func[Short,Byte]("{ (x: Short) => x.toByte }"))(x => x.toByte) + val toShort = checkEq(func[Short,Short]("{ (x: Short) => x.toShort }"))(x => x.toShort) + val toInt = checkEq(func[Short,Int]("{ (x: Short) => x.toInt }"))(x => x.toInt) + val toLong = checkEq(func[Short,Long]("{ (x: Short) => x.toLong }"))(x => x.toLong) + val toBigInt = checkEq(func[Short,BigInt]("{ (x: Short) => x.toBigInt }"))(x => x.toBigInt) + lazy val toBytes = checkEq(func[Short,Coll[Byte]]("{ (x: Short) => x.toBytes }"))(x => x.toBytes) + lazy val toBits = checkEq(func[Short,Coll[Boolean]]("{ (x: Short) => x.toBits }"))(x => x.toBits) + // TODO: Implement Short.toAbs + lazy val toAbs = checkEq(func[Short,Short]("{ (x: Short) => x.toAbs }"))((x: Short) => if (x >= 0.toShort) x else (-x).toShort) + lazy val compareTo = checkEq(func[(Short, Short), Int]("{ (x: (Short, Short)) => x._1.compareTo(x._2) }"))(x => x._1.compareTo(x._2)) + + forAll { x: Short => + whenever(Byte.MinValue <= x && x <= scala.Byte.MaxValue) { + toByte(x) + } + Seq(toShort, toInt, toLong, toBigInt).foreach(_(x)) + //TODO soft-fork: toBytes, toBits, toAbs + } + forAll { x: (Short, Short) => + //TODO soft-fork: compareTo(x) + } + } + + property("Long methods equivalence") { + val toByte = checkEq(func[Long,Byte]("{ (x: Long) => x.toByte }"))(x => x.toByte) + val toShort = checkEq(func[Long,Short]("{ (x: Long) => x.toShort }"))(x => x.toShort) + val toInt = checkEq(func[Long,Int]("{ (x: Long) => x.toInt }"))(x => x.toInt) + val toLong = checkEq(func[Long,Long]("{ (x: Long) => x.toLong }"))(x => x.toLong) + val toBigInt = checkEq(func[Long,BigInt]("{ (x: Long) => x.toBigInt }"))(x => BigInt(x).bigInteger) + /* + lazy val toBytes = checkEq(func[Long,Coll[Byte]]("{ (x: Long) => x.toBytes }"))(x => x.toBytes) + lazy val toBits = checkEq(func[Long,Coll[Boolean]]("{ (x: Long) => x.toBits }"))(x => x.toBits) + lazy val toAbs = checkEq(func[Long,Long]("{ (x: Long) => x.toAbs }"))(x => x.toAbs) + */ + lazy val compareTo = checkEq(func[(Long, Long), Int]("{ (x: (Long, Long)) => x._1.compareTo(x._2) }"))(x => x._1.compareTo(x._2)) + + forAll { x: Long => + whenever(Byte.MinValue <= x && x <= scala.Byte.MaxValue) { + toByte(x) + } + whenever(Short.MinValue <= x && x <= Short.MaxValue) { + toShort(x) + } + whenever(Int.MinValue <= x && x <= Int.MaxValue) { + toInt(x) + } + Seq(toLong, toBigInt).foreach(_(x)) + //TODO soft-fork: toBytes, toBits, toAbs + } + forAll { x: (Long, Long) => + //TODO soft-fork: compareTo(x) + } + } + property("BigInt methods equivalence") { + val toByte = checkEq(func[(Byte, BigInt),Boolean]("{ (x: (Byte, BigInt)) => x._2.toByte == x._1 }")) { x => + x._2.toByte == x._1 + } + val toShort = checkEq(func[(Short, BigInt),Boolean]("{ (x: (Short, BigInt)) => x._2.toShort == x._1 }")) { x => + x._2.toShort == x._1 + } + val toInt = checkEq(func[(Int, BigInt),Boolean]("{ (x: (Int, BigInt)) => x._2.toInt == x._1 }")) { x => + x._2.toInt == x._1 + } + val toLong = checkEq(func[(Long, BigInt),Boolean]("{ (x: (Long, BigInt)) => x._2.toLong == x._1 }")) { x => + x._2.toLong == x._1 + } + val toBigInt = checkEq(func[(BigInt, BigInt),Boolean]("{ (x: (BigInt, BigInt)) => x._2.toBigInt == x._1 }")) { x => + x._2 == x._1 + } + + lazy val toBytes = checkEq(func[BigInt,Coll[Byte]]("{ (x: BigInt) => x.toBytes }"))(x => x.toBytes) + lazy val toBits = checkEq(func[BigInt,Coll[Boolean]]("{ (x: BigInt) => x.toBits }"))(x => x.toBits) + lazy val toAbs = checkEq(func[BigInt,BigInt]("{ (x: BigInt) => x.toAbs }"))(x => x.toAbs) + lazy val compareTo = checkEq(func[(BigInt, BigInt), Int]("{ (x: (BigInt, BigInt)) => x._1.compareTo(x._2) }"))(x => x._1.compareTo(x._2)) + + /* + forAll { x: Byte => + toByte((x, x.toBigInt)) + } + forAll { x: Short => + toShort((x, x.toBigInt)) + } + forAll { x: Int => + toInt((x, x.toBigInt)) + } + forAll { x: Long => + toLong((x, BigInt(x).bigInteger)) + } + */ + forAll { x: BigInt => + Seq(toBigInt).foreach(_((x, x))) + //TODO soft-fork: toBytes, toBits, toAbs + } + forAll { x: (BigInt, BigInt) => + //TODO soft-fork: compareTo(x) + } + } property("GroupElement operations equivalence") { val ge = SigmaDsl.groupGenerator @@ -144,15 +268,18 @@ class SigmaDslTest extends PropSpec val updateAllowed = checkEq(func[AvlTree, Boolean]("{ (t: AvlTree) => t.isUpdateAllowed }")) { (t: AvlTree) => t.isUpdateAllowed } val removeAllowed = checkEq(func[AvlTree, Boolean]("{ (t: AvlTree) => t.isRemoveAllowed }")) { (t: AvlTree) => t.isRemoveAllowed } - val tree = sampleAvlTree - - doDigest(tree) - doEnabledOps(tree) - doKeyLength(tree) - doValueLength(tree) - insertAllowed(tree) - updateAllowed(tree) - removeAllowed(tree) + val newTree = sampleAvlTree.updateOperations(1.toByte) + val trees = Array(sampleAvlTree, newTree) + + for (tree <- trees) { + doDigest(tree) + doEnabledOps(tree) + doKeyLength(tree) + doValueLength(tree) + insertAllowed(tree) + updateAllowed(tree) + removeAllowed(tree) + } } property("AvlTree.{contains, get, getMany} equivalence") { @@ -235,6 +362,17 @@ class SigmaDslTest extends PropSpec forAll { x: Long => eq(x) } } + property("byteArrayToBigInt equivalence") { + val eq = checkEq(func[Coll[Byte], BigInt]("{ (x: Coll[Byte]) => byteArrayToBigInt(x) }")){ x => + byteArrayToBigInt(x) + } + forAll { x: Array[Byte] => + whenever(x.length <= ErgoConstants.MaxBigIntSizeInBytes.get) { + eq(Builder.DefaultCollBuilder.fromArray(x)) + } + } + } + property("byteArrayToLong equivalence") { val eq = checkEq(func[Coll[Byte],Long]("{ (x: Coll[Byte]) => byteArrayToLong(x) }")){ x => byteArrayToLong(x) @@ -269,6 +407,33 @@ class SigmaDslTest extends PropSpec eq({ (x: Box) => x.tokens })("{ (x: Box) => x.tokens }") } + property("Advanced Box test") { + val avlProver = new BatchAVLProver[Digest32, Blake2b256.type](keyLength = 32, None) + avlProver.generateProof() + + val digest = avlProver.digest + val treeData = SigmaDsl.avlTree(new AvlTreeData(digest, AvlTreeFlags.ReadOnly, 32, None)) + val box = ctx.dataInputs(0) + + val s = ErgoBox(20, TrueProp, 0, Seq(),Map( + ErgoBox.nonMandatoryRegisters(0) -> ByteConstant(1.toByte), + ErgoBox.nonMandatoryRegisters(1) -> ShortConstant(1024.toShort), + ErgoBox.nonMandatoryRegisters(2) -> IntConstant(1024 * 1024), + ErgoBox.nonMandatoryRegisters(3) -> LongConstant(1024.toLong), + ErgoBox.nonMandatoryRegisters(4) -> BigIntConstant(222L), + ErgoBox.nonMandatoryRegisters(5) -> AvlTreeConstant(treeData) + )) + lazy val byteCheck = checkEq(func[Box,Byte]("{ (x: Box) => x.R4[Byte].get }"))((x: Box) => x.R4[Byte].get) + lazy val shortCheck = checkEq(func[Box,Short]("{ (x: Box) => x.R5[Short].get }"))((x: Box) => x.R5[Short].get) + lazy val intCheck = checkEq(func[Box,Int]("{ (x: Box) => x.R6[Int].get }"))((x: Box) => x.R6[Int].get) + lazy val longCheck = checkEq(func[Box,Long]("{ (x: Box) => x.R7[Long].get }"))((x: Box) => x.R7[Long].get) + lazy val BigIntCheck = checkEq(func[Box,BigInt]("{ (x: Box) => x.R8[BigInt].get }"))((x: Box) => x.R8[BigInt].get) + byteCheck(s) + shortCheck(s) + intCheck(s) + longCheck(s) + BigIntCheck(s) + } property("PreHeader properties equivalence") { val h = ctx.preHeader @@ -314,6 +479,9 @@ class SigmaDslTest extends PropSpec eq({ (x: Context) => x.HEIGHT })("{ (x: Context) => x.HEIGHT }") eq({ (x: Context) => x.SELF })("{ (x: Context) => x.SELF }") eq({ (x: Context) => x.INPUTS.map { (b: Box) => b.value } })("{ (x: Context) => x.INPUTS.map { (b: Box) => b.value } }") + eq({ (x: Context) => x.selfBoxIndex })("{ (x: Context) => x.selfBoxIndex }") + eq({ (x: Context) => x.LastBlockUtxoRootHash.isUpdateAllowed })("{ (x: Context) => x.LastBlockUtxoRootHash.isUpdateAllowed }") + eq({ (x: Context) => x.minerPubKey })("{ (x: Context) => x.minerPubKey }") eq({ (x: Context) => x.INPUTS.map { (b: Box) => (b.value, b.value) } })( @@ -338,6 +506,13 @@ class SigmaDslTest extends PropSpec |}""".stripMargin) } + property("getVar equivalence") { + val eq = checkEq(func[Int,Int]("{ (x: Int) => getVar[Int](2).get }", 2.toByte -> IntConstant(10))) { x => + 10 + } + eq(1) + } + property("xorOf equivalence") { val eq = checkEq(func[Coll[Boolean], Boolean]("{ (x: Coll[Boolean]) => xorOf(x) }")) { x => xorOf(x) @@ -371,7 +546,6 @@ class SigmaDslTest extends PropSpec forAll { x: BigInt => negBigInteger(x) } } - //TODO: related to https://github.com/ScorexFoundation/sigmastate-interpreter/issues/424 property("BinXor(logical XOR) equivalence") { val eq = checkEq(func[(Boolean, Boolean), Boolean]("{ (x: (Boolean, Boolean)) => x._1 ^ x._2 }")) { x => x._1 ^ x._2 @@ -415,7 +589,6 @@ class SigmaDslTest extends PropSpec eq(Builder.DefaultCollBuilder.fromArray(l), Builder.DefaultCollBuilder.fromArray(r)) } } - } property("Coll methods equivalence") { @@ -424,6 +597,192 @@ class SigmaDslTest extends PropSpec eq({ (x: Coll[Box]) => x.filter({ (b: Box) => b.value > 1 }) })("{ (x: Coll[Box]) => x.filter({(b: Box) => b.value > 1 }) }") eq({ (x: Coll[Box]) => x.flatMap({ (b: Box) => b.propositionBytes }) })("{ (x: Coll[Box]) => x.flatMap({(b: Box) => b.propositionBytes }) }") eq({ (x: Coll[Box]) => x.zip(x) })("{ (x: Coll[Box]) => x.zip(x) }") + eq({ (x: Coll[Box]) => x.size })("{ (x: Coll[Box]) => x.size }") + eq({ (x: Coll[Box]) => x.indices })("{ (x: Coll[Box]) => x.indices }") + eq({ (x: Coll[Box]) => x.forall({ (b: Box) => b.value > 1 }) })("{ (x: Coll[Box]) => x.forall({(b: Box) => b.value > 1 }) }") + eq({ (x: Coll[Box]) => x.exists({ (b: Box) => b.value > 1 }) })("{ (x: Coll[Box]) => x.exists({(b: Box) => b.value > 1 }) }") + } + + property("Coll size method equivalnce") { + val eq = checkEq(func[Coll[Int],Int]("{ (x: Coll[Int]) => x.size }")){ x => + x.size + } + forAll { x: Array[Int] => + eq(Builder.DefaultCollBuilder.fromArray(x)) + } + } + + property("Coll patch method equivalnce") { + val eq = checkEq(func[(Coll[Int], (Int, Int)),Coll[Int]]("{ (x: (Coll[Int], (Int, Int))) => x._1.patch(x._2._1, x._1, x._2._2) }")){ x => + x._1.patch(x._2._1, x._1, x._2._2) + } + forAll { x: Array[Int] => + whenever (x.size > 1) { + eq(Builder.DefaultCollBuilder.fromArray(x), makeSlicePair(x.size)) + } + } + } + + property("Coll updated method equivalnce") { + val eq = checkEq(func[(Coll[Int], (Int, Int)),Coll[Int]]("{ (x: (Coll[Int], (Int, Int))) => x._1.updated(x._2._1, x._2._2) }")){ x => + x._1.updated(x._2._1, x._2._2) + } + forAll { x: (Array[Int], Int) => + val size = x._1.size + whenever (size > 1) { + val index = getRandomIndex(size) + eq(Builder.DefaultCollBuilder.fromArray(x._1), (index, x._2)) + } + } + } + + property("Coll updateMany method equivalnce") { + val eq = checkEq(func[(Coll[Int], (Coll[Int], Coll[Int])),Coll[Int]]("{ (x: (Coll[Int], (Coll[Int], Coll[Int]))) => x._1.updateMany(x._2._1, x._2._2) }")){ x => + x._1.updateMany(x._2._1, x._2._2) + } + forAll { x: (Array[Int], Int) => + val size = x._1.size + whenever (size > 1) { + val fromColl = Builder.DefaultCollBuilder.fromArray(x._1) + val indexColl = generateIndexColl(size) + eq(fromColl, (indexColl, fromColl.reverse.slice(0, indexColl.size))) + } + } + } + + // TODO soft-fork: https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479 + ignore("Coll find method equivalnce") { + val eq = checkEq(func[Coll[Int],Option[Int]]("{ (x: Coll[Int]) => x.find({(v: Int) => v > 0})}")){ x => + x.find(v => v > 0) + } + forAll { x: Array[Int] => + eq(Builder.DefaultCollBuilder.fromArray(x)) + } + } + + // https://github.com/ScorexFoundation/sigmastate-interpreter/issues/418 + ignore("Coll bitwise methods equivalnce") { + val eq = checkEq(func[Coll[Boolean],Coll[Boolean]]("{ (x: Coll[Boolean]) => x >> 2 }")){ x => + if (x.size > 2) x.slice(0, x.size - 2) else Colls.emptyColl + } + forAll { x: Array[Boolean] => + eq(Builder.DefaultCollBuilder.fromArray(x)) + } + } + + // TODO soft-fork: https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479 + ignore("Coll diff methods equivalnce") { + val eq = checkEq(func[Coll[Int],Coll[Int]]("{ (x: Coll[Int]) => x.diff(x) }")){ x => + x.diff(x) + } + forAll { x: Array[Int] => + eq(Builder.DefaultCollBuilder.fromArray(x)) + } + } + + property("Coll fold method equivalnce") { + val monoid = Builder.DefaultCollBuilder.Monoids.intPlusMonoid + val eq = checkEq(func[(Coll[Int], Int),Int]("{ (x: (Coll[Int], Int)) => x._1.fold(x._2, { (i1: Int, i2: Int) => i1 + i2 }) }")) + { x => + x._1.sum(monoid) + x._2 + } + val eqIndexOf = checkEq(func[(Coll[Int], Int),Int]("{ (x: (Coll[Int], Int)) => x._1.indexOf(x._2, 0) }")) + { x => + x._1.indexOf(x._2, 0) + } + forAll { x: (Array[Int], Int) => + eq(Builder.DefaultCollBuilder.fromArray(x._1), x._2) + eqIndexOf(Builder.DefaultCollBuilder.fromArray(x._1), x._2) + } + } + + property("Coll indexOf method equivalnce") { + val eqIndexOf = checkEq(func[(Coll[Int], (Int, Int)),Int]("{ (x: (Coll[Int], (Int, Int))) => x._1.indexOf(x._2._1, x._2._2) }")) + { x => + x._1.indexOf(x._2._1, x._2._2) + } + forAll { x: (Array[Int], Int) => + eqIndexOf(Builder.DefaultCollBuilder.fromArray(x._1), (getRandomIndex(x._1.size), x._2)) + } + } + + property("Coll apply method equivalnce") { + val eqApply = checkEq(func[(Coll[Int], Int),Int]("{ (x: (Coll[Int], Int)) => x._1(x._2) }")) + { x => + x._1(x._2) + } + forAll { x: Array[Int] => + whenever (0 < x.size) { + eqApply(Builder.DefaultCollBuilder.fromArray(x), getRandomIndex(x.size)) + } + } + } + + property("Coll getOrElse method equivalnce") { + val eqGetOrElse = checkEq(func[(Coll[Int], (Int, Int)),Int]("{ (x: (Coll[Int], (Int, Int))) => x._1.getOrElse(x._2._1, x._2._2) }")) + { x => + x._1.getOrElse(x._2._1, x._2._2) + } + forAll { x: (Array[Int], (Int, Int)) => + eqGetOrElse(Builder.DefaultCollBuilder.fromArray(x._1), x._2) + } + } + + property("Tuple size method equivalence") { + val eq = checkEq(func[(Int, Int),Int]("{ (x: (Int, Int)) => x.size }")) { x => 2 } + eq((-1, 1)) + } + + property("Tuple apply method equivalence") { + val eq1 = checkEq(func[(Int, Int),Int]("{ (x: (Int, Int)) => x(0) }")) { x => -1 } + val eq2 = checkEq(func[(Int, Int),Int]("{ (x: (Int, Int)) => x(1) }")) { x => 1 } + eq1((-1, 1)) + eq2((-1, 1)) + } + + property("Coll map method equivalnce") { + val eq = checkEq(func[Coll[Int],Coll[Int]]("{ (x: Coll[Int]) => x.map({ (v: Int) => v + 1 }) }")) + { x => + x.map(v => v + 1) + } + forAll { x: Array[Int] => + eq(Builder.DefaultCollBuilder.fromArray(x)) + } + } + + property("Coll slice method equivalnce") { + val eq = checkEq(func[(Coll[Int], (Int, Int)),Coll[Int]]("{ (x: (Coll[Int], (Int, Int))) => x._1.slice(x._2._1, x._2._2) }")) + { x => + x._1.slice(x._2._1, x._2._2) + } + forAll { x: Array[Int] => + val size = x.size + whenever (size > 0) { + eq(Builder.DefaultCollBuilder.fromArray(x), makeSlicePair(size)) + } + } + val arr = Array[Int](1, 2, 3, 4, 5) + eq(Builder.DefaultCollBuilder.fromArray(arr), (0, 2)) + } + + property("Coll append method equivalence") { + val eq = checkEq(func[(Coll[Int], (Int, Int)),Coll[Int]]( + """{ (x: (Coll[Int], (Int, Int))) => + |val sliced: Coll[Int] = x._1.slice(x._2._1, x._2._2) + |val toAppend: Coll[Int] = x._1 + |sliced.append(toAppend) + |}""".stripMargin)) + { x => + val sliced: Coll[Int] = x._1.slice(x._2._1, x._2._2) + val toAppend: Coll[Int] = x._1 + sliced.append(toAppend) + } + forAll { x: Array[Int] => + val size = x.size + whenever (size > 0) { + eq(Builder.DefaultCollBuilder.fromArray(x), makeSlicePair(size)) + } + } } property("Option methods equivalence") { @@ -437,4 +796,21 @@ class SigmaDslTest extends PropSpec eq({ (x: Option[Long]) => x.filter({ (v: Long) => v == 1} ) })("{ (x: Option[Long]) => x.filter({ (v: Long) => v == 1 }) }") eq({ (x: Option[Long]) => x.map( (v: Long) => v + 1 ) })("{ (x: Option[Long]) => x.map({ (v: Long) => v + 1 }) }") } + + // TODO implement Option.fold + ignore("Option fold method") { + val opt: Option[Long] = ctx.dataInputs(0).R0[Long] + val eq = EqualityChecker(opt) + eq({ (x: Option[Long]) => x.fold(5.toLong)( (v: Long) => v + 1 ) })("{ (x: Option[Long]) => x.fold(5, { (v: Long) => v + 1 }) }") + } + + property("Option fold workaround method") { + val opt: Option[Long] = ctx.dataInputs(0).R0[Long] + val eq = EqualityChecker(opt) + eq({ (x: Option[Long]) => x.fold(5.toLong)( (v: Long) => v + 1 ) })( + """{(x: Option[Long]) => + | def f(opt: Long): Long = opt + 1 + | if (x.isDefined) f(x.get) else 5L + |}""".stripMargin) + } }