diff --git a/.gitignore b/.gitignore index 4dc1640004..3570923b1d 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ yarn.lock *.log +yarn.lock docs/spec/out/ test-out/ flamegraphs/ diff --git a/build.sbt b/build.sbt index 85c59512d6..ab9d931172 100644 --- a/build.sbt +++ b/build.sbt @@ -82,9 +82,9 @@ ThisBuild / dynverSeparator := "-" val bouncycastleBcprov = "org.bouncycastle" % "bcprov-jdk15on" % "1.66" -val scrypto = "org.scorexfoundation" %% "scrypto" % "2.3.0-4-a0bc6176-SNAPSHOT" +val scrypto = "org.scorexfoundation" %% "scrypto" % "3.0.0" val scryptoDependency = - libraryDependencies += "org.scorexfoundation" %%% "scrypto" % "2.3.0-4-a0bc6176-SNAPSHOT" + libraryDependencies += "org.scorexfoundation" %%% "scrypto" % "3.0.0" val scorexUtil = "org.scorexfoundation" %% "scorex-util" % "0.2.1" val scorexUtilDependency = diff --git a/core/shared/src/main/scala/sigma/SigmaDsl.scala b/core/shared/src/main/scala/sigma/SigmaDsl.scala index df2b419273..497273d2db 100644 --- a/core/shared/src/main/scala/sigma/SigmaDsl.scala +++ b/core/shared/src/main/scala/sigma/SigmaDsl.scala @@ -5,9 +5,7 @@ import java.math.BigInteger import sigma.data._ /** - * All `modQ` operations assume that Q is a global constant (an order of the only one cryptographically strong group - * which is used for all cryptographic operations). - * So it is globally and implicitly used in all methods. + * Functions defined for 256-bit signed integers * */ trait BigInt { /** Convert this BigInt value to Byte. @@ -154,6 +152,22 @@ trait BigInt { */ def or(that: BigInt): BigInt def |(that: BigInt): BigInt = or(that) + + /** + * @return a big integer whose value is `this xor that`. + * This method returns a negative BigInteger if and only if exactly one of this and val are negative. + */ + def xor(that: BigInt): BigInt + + /** + * @return a 256-bit signed integer whose value is (this << n). `n` should be in 0..255 range (inclusive). + */ + def shiftLeft(n: Int): BigInt + + /** + * @return a 256-bit signed integer whose value is (this >> n). `n` should be in 0..255 range (inclusive). + */ + def shiftRight(n: Int): BigInt } /** Base class for points on elliptic curves. */ @@ -459,6 +473,23 @@ trait Header { /** Miner votes for changing system parameters. */ def votes: Coll[Byte] //3 bytes + + /** Bytes which are coming from future versions of the protocol, so + * their meaning is not known to current version of Sigma, but they + * are stored to get the same id as future version users. + */ + def unparsedBytes: Coll[Byte] + + /** + * @return header bytes without proof of work, a PoW is generated over them + */ + def serializeWithoutPoW: Coll[Byte] + + /** + * @return result of header's proof-of-work validation + */ + def checkPow: Boolean + } /** Runtime representation of Context ErgoTree type. @@ -727,6 +758,9 @@ trait SigmaDslBuilder { /** Construct a new authenticated dictionary with given parameters and tree root digest. */ def avlTree(operationFlags: Byte, digest: Coll[Byte], keyLength: Int, valueLengthOpt: Option[Int]): AvlTree + /** Serializes the given `value` into bytes using the default serialization format. */ + def serialize[T](value: T)(implicit cT: RType[T]): Coll[Byte] + /** Returns a byte-wise XOR of the two collections of bytes. */ def xor(l: Coll[Byte], r: Coll[Byte]): Coll[Byte] } diff --git a/core/shared/src/main/scala/sigma/ast/SType.scala b/core/shared/src/main/scala/sigma/ast/SType.scala index 717439bcbb..d289f4067a 100644 --- a/core/shared/src/main/scala/sigma/ast/SType.scala +++ b/core/shared/src/main/scala/sigma/ast/SType.scala @@ -113,27 +113,48 @@ object SType { * 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) + * @note starting from v6.0 methods with type parameters are also supported. * - * NOTE2: in v3.x SNumericType.typeId is silently shadowed by SGlobal.typeId as part of - * `toMap` operation. As a result, the methods collected into SByte.methods cannot be + * @note on versioning: + * In v3.x-5.x SNumericType.typeId is silently shadowed by SGlobal.typeId as part of + * `toMap` operation. As a result, SNumericTypeMethods container cannot be resolved by + * typeId = 106, because SNumericType was being silently removed when `_types` map is + * constructed. See `property("SNumericType.typeId resolves to SGlobal")`. + * In addition, the methods associated with the concrete numeric types cannot be * resolved (using SMethod.fromIds()) for all numeric types (SByte, SShort, SInt, - * SLong, SBigInt). See the corresponding regression `property("MethodCall on numerics")`. + * SLong) because these types are not registered in the `_types` map. + * See the corresponding property("MethodCall on numerics")`. * However, this "shadowing" is not a problem since all casting methods are implemented - * via Downcast, Upcast opcodes and the remaining `toBytes`, `toBits` methods are not - * implemented at all. - * In order to allow MethodCalls on numeric types in future versions the SNumericType.typeId - * should be changed and SGlobal.typeId should be preserved. The regression tests in - * `property("MethodCall Codes")` should pass. + * via lowering to Downcast, Upcast opcodes and the remaining `toBytes`, `toBits` + * methods are not implemented at all. + * + * Starting from v6.0 the SNumericType.typeId is demoted as a receiver object of + * method calls and: + * 1) numeric type SByte, SShort, SInt, SLong are promoted as receivers and added to + * the _types map. + * 2) all methods from SNumericTypeMethods are copied to all the concrete numeric types + * (SByte, SShort, SInt, SLong, SBigInt) and the generic tNum type parameter is + * specialized accordingly. + * + * This difference in behaviour is tested by `property("MethodCall on numerics")`. + * + * The regression tests in `property("MethodCall Codes")` should pass. */ - // TODO v6.0: 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 - lazy val types: Map[Byte, STypeCompanion] = Seq( - SBoolean, SNumericType, SString, STuple, SGroupElement, SSigmaProp, SContext, SGlobal, SHeader, SPreHeader, + private val v5Types = Seq( + SBoolean, SString, STuple, SGroupElement, SSigmaProp, SContext, SGlobal, SHeader, SPreHeader, SAvlTree, SBox, SOption, SCollection, SBigInt - ).map { t => (t.typeId, t) }.toMap + ) + private val v6Types = v5Types ++ Seq(SByte, SShort, SInt, SLong) + + private val v5TypesMap = v5Types.map { t => (t.typeId, t) }.toMap + + private val v6TypesMap = v6Types.map { t => (t.typeId, t) }.toMap + + def types: Map[Byte, STypeCompanion] = if (VersionContext.current.isV6SoftForkActivated) { + v6TypesMap + } else { + v5TypesMap + } /** Checks that the type of the value corresponds to the descriptor `tpe`. * If the value has complex structure only root type constructor is checked. @@ -452,7 +473,7 @@ case object SLong extends SPrimType with SEmbeddable with SNumericType with SMon } } -/** Type of 256 bit integet values. Implemented using [[java.math.BigInteger]]. */ +/** Type of 256 bit integer values. Implemented using [[java.math.BigInteger]]. */ case object SBigInt extends SPrimType with SEmbeddable with SNumericType with SMonoType { override type WrappedType = BigInt override val typeCode: TypeCode = 6: Byte @@ -587,7 +608,7 @@ case class SFunc(tDom: IndexedSeq[SType], tRange: SType, tpeParams: Seq[STypePa } object SFunc { - final val FuncTypeCode: TypeCode = TypeCodes.FirstFuncType + final val FuncTypeCode: TypeCode = TypeCodes.FuncType def apply(tDom: SType, tRange: SType): SFunc = SFunc(Array(tDom), tRange) // HOTSPOT: val identity = { x: Any => x } } @@ -654,7 +675,6 @@ object SOption extends STypeCompanion { def apply[T <: SType](implicit elemType: T, ov: Overloaded1): SOption[T] = SOption(elemType) } - /** Base class for descriptors of `Coll[T]` ErgoTree type for some elemType T. */ trait SCollection[T <: SType] extends SProduct with SGenericType { def elemType: T diff --git a/core/shared/src/main/scala/sigma/ast/STypeParam.scala b/core/shared/src/main/scala/sigma/ast/STypeParam.scala index 56d89d01f8..08349ae024 100644 --- a/core/shared/src/main/scala/sigma/ast/STypeParam.scala +++ b/core/shared/src/main/scala/sigma/ast/STypeParam.scala @@ -2,18 +2,10 @@ package sigma.ast /** Represents a type parameter in a type system. * - * @param ident The identifier for this type parameter. - * @param upperBound The upper bound of this type parameter, if exists. - * @param lowerBound The lower bound of this type parameter, if exists. - * @note Type parameters with bounds are currently not supported. + * @param ident The identifier for this type parameter */ -case class STypeParam( - ident: STypeVar, - upperBound: Option[SType] = None, - lowerBound: Option[SType] = None) { - assert(upperBound.isEmpty && lowerBound.isEmpty, s"Type parameters with bounds are not supported, but found $this") - - override def toString = ident.toString + upperBound.fold("")(u => s" <: $u") + lowerBound.fold("")(l => s" >: $l") +case class STypeParam(ident: STypeVar) { + override def toString = ident.toString } object STypeParam { diff --git a/core/shared/src/main/scala/sigma/ast/TypeCodes.scala b/core/shared/src/main/scala/sigma/ast/TypeCodes.scala index 1b7b6121d6..68670449db 100644 --- a/core/shared/src/main/scala/sigma/ast/TypeCodes.scala +++ b/core/shared/src/main/scala/sigma/ast/TypeCodes.scala @@ -14,10 +14,8 @@ object TypeCodes { val LastDataType : TypeCode = TypeCode @@ 111.toByte - /** SFunc types occupy remaining space of byte values [FirstFuncType .. 255] */ - val FirstFuncType: TypeCode = TypeCode @@ (LastDataType + 1).toByte - - val LastFuncType : TypeCode = TypeCode @@ 255.toByte + /** SFunc type */ + val FuncType: TypeCode = TypeCode @@ (LastDataType + 1).toByte /** We use optimized encoding of constant values to save space in serialization. * Since Box registers are stored as Constant nodes we save 1 byte for each register. diff --git a/core/shared/src/main/scala/sigma/ast/package.scala b/core/shared/src/main/scala/sigma/ast/package.scala index 63a2cfbcba..ec51ca6e3a 100644 --- a/core/shared/src/main/scala/sigma/ast/package.scala +++ b/core/shared/src/main/scala/sigma/ast/package.scala @@ -137,6 +137,12 @@ package object ast { def asNumType: SNumericType = tpe.asInstanceOf[SNumericType] + /** Cast this type to numeric type or else throws the given error. */ + def asNumTypeOrElse(error: => Exception): SNumericType = tpe match { + case nt: SNumericType => nt + case _ => throw error + } + def asFunc: SFunc = tpe.asInstanceOf[SFunc] def asProduct: SProduct = tpe.asInstanceOf[SProduct] diff --git a/core/shared/src/main/scala/sigma/data/CBigInt.scala b/core/shared/src/main/scala/sigma/data/CBigInt.scala index bbf1a85e46..ea69174877 100644 --- a/core/shared/src/main/scala/sigma/data/CBigInt.scala +++ b/core/shared/src/main/scala/sigma/data/CBigInt.scala @@ -49,4 +49,10 @@ case class CBigInt(override val wrappedValue: BigInteger) extends BigInt with Wr override def and(that: BigInt): BigInt = CBigInt(wrappedValue.and(that.asInstanceOf[CBigInt].wrappedValue)) override def or(that: BigInt): BigInt = CBigInt(wrappedValue.or(that.asInstanceOf[CBigInt].wrappedValue)) + + override def xor(that: BigInt): BigInt = CBigInt(wrappedValue.xor(that.asInstanceOf[CBigInt].wrappedValue)) + + override def shiftLeft(n: Int): BigInt = CBigInt(wrappedValue.shiftLeft(n).to256BitValueExact) + + override def shiftRight(n: Int): BigInt = CBigInt(wrappedValue.shiftRight(n).to256BitValueExact) } diff --git a/core/shared/src/main/scala/sigma/reflection/ReflectionData.scala b/core/shared/src/main/scala/sigma/reflection/ReflectionData.scala index 028e68bf72..a2de9dd8c5 100644 --- a/core/shared/src/main/scala/sigma/reflection/ReflectionData.scala +++ b/core/shared/src/main/scala/sigma/reflection/ReflectionData.scala @@ -346,6 +346,9 @@ object ReflectionData { }, mkMethod(clazz, "powDistance", Array[Class[_]]()) { (obj, _) => obj.asInstanceOf[Header].powDistance + }, + mkMethod(clazz, "checkPow", Array[Class[_]]()) { (obj, _) => + obj.asInstanceOf[Header].checkPow } ) ) @@ -441,6 +444,10 @@ object ReflectionData { mkMethod(clazz, "sha256", Array[Class[_]](cColl)) { (obj, args) => obj.asInstanceOf[SigmaDslBuilder].sha256(args(0).asInstanceOf[Coll[Byte]]) }, + mkMethod(clazz, "serialize", Array[Class[_]](classOf[Object], classOf[RType[_]])) { (obj, args) => + obj.asInstanceOf[SigmaDslBuilder].serialize[Any]( + args(0).asInstanceOf[Any])(args(1).asInstanceOf[RType[Any]]) + }, mkMethod(clazz, "decodePoint", Array[Class[_]](cColl)) { (obj, args) => obj.asInstanceOf[SigmaDslBuilder].decodePoint(args(0).asInstanceOf[Coll[Byte]]) } diff --git a/core/shared/src/main/scala/sigma/serialization/CoreByteWriter.scala b/core/shared/src/main/scala/sigma/serialization/CoreByteWriter.scala index aa4255449c..92d85ed9d8 100644 --- a/core/shared/src/main/scala/sigma/serialization/CoreByteWriter.scala +++ b/core/shared/src/main/scala/sigma/serialization/CoreByteWriter.scala @@ -3,9 +3,10 @@ package sigma.serialization import scorex.util.serialization.Writer.Aux import scorex.util.serialization.{VLQByteBufferWriter, Writer} import sigma.ast.SType -import sigma.serialization.CoreByteWriter.{Bits, DataInfo, U, Vlq, ZigZag} +import sigma.serialization.CoreByteWriter._ /** Implementation of [[Writer]] provided by `sigma-core` module. + * * @param w destination [[Writer]] to which all the call got delegated. */ class CoreByteWriter(val w: Writer) extends Writer { @@ -15,11 +16,20 @@ class CoreByteWriter(val w: Writer) extends Writer { @inline override def newWriter(): Aux[CH] = w.newWriter() - @inline override def putChunk(chunk: CH): this.type = { w.putChunk(chunk); this } + @inline override def putChunk(chunk: CH): this.type = { + w.putChunk(chunk); this + } @inline override def result(): CH = w.result() - @inline def put(x: Byte): this.type = { w.put(x); this } + @inline override def put(x: Byte): this.type = { + w.put(x); this + } + + /** Put the given byte into the writer. + * @param x the byte to put into the writer + * @param info meta information about the data being put into the writer + */ @inline def put(x: Byte, info: DataInfo[Byte]): this.type = { w.put(x); this } @@ -27,41 +37,110 @@ class CoreByteWriter(val w: Writer) extends Writer { override def putUByte(x: Int): this.type = { super.putUByte(x) } + + /** Encode integer as an unsigned byte asserting the range check + * @param x integer value to encode (should be in the range of unsigned byte) + * @param info meta information about the data being put into the writer + * @return + * @throws AssertionError if x is outside of the unsigned byte range + */ def putUByte(x: Int, info: DataInfo[U[Byte]]): this.type = { super.putUByte(x) } - @inline def putBoolean(x: Boolean): this.type = { w.putBoolean(x); this } + @inline override def putBoolean(x: Boolean): this.type = { + w.putBoolean(x); this + } + + /** Encode boolean by delegating to the underlying writer. + * @param x boolean value to encode + * @param info meta information about the data being put into the writer + * @return + */ @inline def putBoolean(x: Boolean, info: DataInfo[Boolean]): this.type = { w.putBoolean(x); this } - @inline def putShort(x: Short): this.type = { w.putShort(x); this } + @inline override def putShort(x: Short): this.type = { + w.putShort(x); this + } + + /** Encode signed Short by delegating to the underlying writer. + * + * Use [[putUShort]] to encode values that are positive. + * @param x short value to encode + * @param info meta information about the data being put into the writer + */ @inline def putShort(x: Short, info: DataInfo[Short]): this.type = { w.putShort(x); this } - @inline def putUShort(x: Int): this.type = { w.putUShort(x); this } + @inline override def putUShort(x: Int): this.type = { + w.putUShort(x); this + } + + /** Encode Short that are positive by delegating to the underlying writer. + * + * Use [[putShort]] to encode values that might be negative. + * @param x unsigned short value (represented as Int) to encode + * @param info meta information about the data being put into the writer + */ @inline def putUShort(x: Int, info: DataInfo[Vlq[U[Short]]]): this.type = { w.putUShort(x); this } - @inline def putInt(x: Int): this.type = { w.putInt(x); this } + @inline override def putInt(x: Int): this.type = { + w.putInt(x); this + } + + /** Encode signed Int by delegating to the underlying writer. + * Use [[putUInt]] to encode values that are positive. + * + * @param x integer value to encode + * @param info meta information about the data being put into the writer + */ @inline def putInt(x: Int, info: DataInfo[Int]): this.type = { w.putInt(x); this } - @inline def putUInt(x: Long): this.type = { w.putUInt(x); this } + @inline override def putUInt(x: Long): this.type = { + w.putUInt(x); this + } + + /** Encode Int that are positive by delegating to the underlying writer. + * Use [[putInt]] to encode values that might be negative. + * + * @param x unsigned integer value (represented as Long) to encode + * @param info meta information about the data being put into the writer + */ @inline def putUInt(x: Long, info: DataInfo[Vlq[U[Int]]]): this.type = { w.putUInt(x); this } - @inline def putLong(x: Long): this.type = { w.putLong(x); this } + @inline override def putLong(x: Long): this.type = { + w.putLong(x); this + } + + /** Encode signed Long by delegating to the underlying writer. + * Use [[putULong]] to encode values that are positive. + * + * @param x long value to encode + * @param info meta information about the data being put into the writer + */ @inline def putLong(x: Long, info: DataInfo[Vlq[ZigZag[Long]]]): this.type = { w.putLong(x); this } - @inline def putULong(x: Long): this.type = { w.putULong(x); this } + @inline override def putULong(x: Long): this.type = { + w.putULong(x); this + } + + /** Encode Long that are positive by delegating to the underlying writer. + * Use [[putLong]] to encode values that might be negative. + * + * @param x unsigned long value to encode + * @param info meta information about the data being put into the writer + */ @inline def putULong(x: Long, info: DataInfo[Vlq[U[Long]]]): this.type = { w.putULong(x); this } @@ -71,7 +150,15 @@ class CoreByteWriter(val w: Writer) extends Writer { length: Int): this.type = { w.putBytes(xs, offset, length); this } - @inline def putBytes(xs: Array[Byte]): this.type = { w.putBytes(xs); this } + + @inline override def putBytes(xs: Array[Byte]): this.type = { + w.putBytes(xs); this + } + + /** Encode an array of bytes by delegating to the underlying writer. + * @param xs array of bytes to encode + * @param info meta information about the data being put into the writer + */ @inline def putBytes(xs: Array[Byte], info: DataInfo[Array[Byte]]): this.type = { w.putBytes(xs); this } @@ -84,29 +171,51 @@ class CoreByteWriter(val w: Writer) extends Writer { this } - @inline def putBits(xs: Array[Boolean]): this.type = { w.putBits(xs); this } + @inline override def putBits(xs: Array[Boolean]): this.type = { + w.putBits(xs); this + } + + /** Encode an array of boolean values as a bit array (packing bits into bytes) + * + * @param xs array of boolean values + * @param info meta information about the data being put into the writer + */ @inline def putBits(xs: Array[Boolean], info: DataInfo[Bits]): this.type = { - w.putBits(xs); - this + w.putBits(xs); this } - @inline def putOption[T](x: Option[T])(putValueC: (this.type, T) => Unit): this.type = { + @inline override def putOption[T](x: Option[T])(putValueC: (this.type, T) => Unit): this.type = { w.putOption(x) { (_, v) => putValueC(this, v) } this } - @inline def putShortString(s: String): this.type = { w.putShortString(s); this } + @inline override def putShortString(s: String): this.type = { + w.putShortString(s); + this + } // TODO refactor: move to Writer @inline def toBytes: Array[Byte] = w match { case wr: VLQByteBufferWriter => wr.toBytes } - @inline def putType[T <: SType](x: T): this.type = { TypeSerializer.serialize(x, this); this } + /** Serialize the given type into the writer using [[TypeSerializer]]. + * @param x the type to put into the writer + */ + @inline def putType[T <: SType](x: T): this.type = { + TypeSerializer.serialize(x, this) + this + } + + /** Serialize the given type into the writer using [[TypeSerializer]]. + * @param x the type to put into the writer + * @param info meta information about the data being put into the writer + */ @inline def putType[T <: SType](x: T, info: DataInfo[SType]): this.type = { - TypeSerializer.serialize(x, this); this + TypeSerializer.serialize(x, this) + this } } @@ -226,6 +335,11 @@ object CoreByteWriter { * @param description argument description. */ case class ArgInfo(name: String, description: String) + /** Represents meta information about serialized data. + * Passed as additional argument of serializer methods. + * Can be used to automatically generate format specifications based on + * the actual collected method invocations. + */ case class DataInfo[T](info: ArgInfo, format: FormatDescriptor[T]) object DataInfo { diff --git a/core/shared/src/main/scala/sigma/serialization/TypeSerializer.scala b/core/shared/src/main/scala/sigma/serialization/TypeSerializer.scala index 9c84de0944..1936bbcd9a 100644 --- a/core/shared/src/main/scala/sigma/serialization/TypeSerializer.scala +++ b/core/shared/src/main/scala/sigma/serialization/TypeSerializer.scala @@ -1,9 +1,9 @@ package sigma.serialization import debox.cfor +import sigma.VersionContext import sigma.ast.SCollectionType.{CollectionTypeCode, NestedCollectionTypeCode} import sigma.ast._ -import sigma.serialization.{CoreByteReader, CoreByteWriter, InvalidTypePrefix} import sigma.util.safeNewArray import sigma.validation.ValidationRules.{CheckPrimitiveTypeCode, CheckTypeCode} @@ -101,6 +101,17 @@ class TypeSerializer { // `Tuple` type with more than 4 items `(Int, Byte, Box, Boolean, Int)` serializeTuple(tup, w) } + case SFunc(tDom, tRange, tpeParams) => + w.put(SFunc.FuncTypeCode) + w.putUByte(tDom.length) + tDom.foreach { st => + serialize(st, w) + } + serialize(tRange, w) + w.putUByte(tpeParams.length) + tpeParams.foreach { tp => + serialize(tp.ident, w) + } case typeIdent: STypeVar => { w.put(typeIdent.typeCode) val bytes = typeIdent.name.getBytes(StandardCharsets.UTF_8) @@ -189,7 +200,23 @@ class TypeSerializer { case SHeader.typeCode => SHeader case SPreHeader.typeCode => SPreHeader case SGlobal.typeCode => SGlobal + case SFunc.FuncTypeCode if VersionContext.current.isV6SoftForkActivated => + val tdLength = r.getUByte() + + val tDom = (1 to tdLength).map { _ => + deserialize(r) + } + val tRange = deserialize(r) + val tpeParamsLength = r.getUByte() + val tpeParams = (1 to tpeParamsLength).map { _ => + val ident = deserialize(r) + require(ident.isInstanceOf[STypeVar]) + STypeParam(ident.asInstanceOf[STypeVar]) + } + SFunc(tDom, tRange, tpeParams) case _ => + // todo: 6.0: replace 1008 check with identical behavior but other opcode, to activate + // ReplacedRule(1008 -> new opcode) during 6.0 activation CheckTypeCode(c.toByte) NoType } diff --git a/core/shared/src/main/scala/sigma/util/NBitsUtils.scala b/core/shared/src/main/scala/sigma/util/NBitsUtils.scala new file mode 100644 index 0000000000..36d526d1d5 --- /dev/null +++ b/core/shared/src/main/scala/sigma/util/NBitsUtils.scala @@ -0,0 +1,84 @@ +package sigma.util + +import java.math.BigInteger + +object NBitsUtils { + + /** + *
The "compact" format is a representation of a whole number N using an unsigned 32 bit number similar to a + * floating point format. The most significant 8 bits are the unsigned exponent of base 256. This exponent can + * be thought of as "number of bytes of N". The lower 23 bits are the mantissa. Bit number 24 (0x800000) represents + * the sign of N. Therefore, N = (-1^sign) * mantissa * 256^(exponent-3).
+ * + *Satoshi's original implementation used BN_bn2mpi() and BN_mpi2bn(). MPI uses the most significant bit of the + * first byte as sign. Thus 0x1234560000 is compact 0x05123456 and 0xc0de000000 is compact 0x0600c0de. Compact + * 0x05c0de00 would be -0x40de000000.
+ * + *Bitcoin only uses this "compact" format for encoding difficulty targets, which are unsigned 256bit quantities. + * Thus, all the complexities of the sign bit and using base 256 are probably an implementation accident.
+ */ + def decodeCompactBits(compact: Long): BigInt = { + val size: Int = (compact >> 24).toInt & 0xFF + val bytes: Array[Byte] = new Array[Byte](4 + size) + bytes(3) = size.toByte + if (size >= 1) bytes(4) = ((compact >> 16) & 0xFF).toByte + if (size >= 2) bytes(5) = ((compact >> 8) & 0xFF).toByte + if (size >= 3) bytes(6) = (compact & 0xFF).toByte + decodeMPI(bytes) + } + + /** + * @see Utils#decodeCompactBits(long) + */ + def encodeCompactBits(requiredDifficulty: BigInt): Long = { + val value = requiredDifficulty.bigInteger + var result: Long = 0L + var size: Int = value.toByteArray.length + if (size <= 3) { + result = value.longValue << 8 * (3 - size) + } else { + result = value.shiftRight(8 * (size - 3)).longValue + } + // The 0x00800000 bit denotes the sign. + // Thus, if it is already set, divide the mantissa by 256 and increase the exponent. + if ((result & 0x00800000L) != 0) { + result >>= 8 + size += 1 + } + result |= size << 24 + val a: Int = if (value.signum == -1) 0x00800000 else 0 + result |= a + result + } + + + /** Parse 4 bytes from the byte array (starting at the offset) as unsigned 32-bit integer in big endian format. */ + def readUint32BE(bytes: Array[Byte]): Long = ((bytes(0) & 0xffL) << 24) | ((bytes(1) & 0xffL) << 16) | ((bytes(2) & 0xffL) << 8) | (bytes(3) & 0xffL) + + /** + * MPI encoded numbers are produced by the OpenSSL BN_bn2mpi function. They consist of + * a 4 byte big endian length field, followed by the stated number of bytes representing + * the number in big endian format (with a sign bit). + * + */ + private def decodeMPI(mpi: Array[Byte]): BigInteger = { + + val length: Int = readUint32BE(mpi).toInt + val buf = new Array[Byte](length) + System.arraycopy(mpi, 4, buf, 0, length) + + if (buf.length == 0) { + BigInteger.ZERO + } else { + val isNegative: Boolean = (buf(0) & 0x80) == 0x80 + if (isNegative) buf(0) = (buf(0) & 0x7f).toByte + val result: BigInteger = new BigInteger(buf) + if (isNegative) { + result.negate + } else { + result + } + } + } + +} diff --git a/data/shared/src/main/scala/org/ergoplatform/ErgoHeader.scala b/data/shared/src/main/scala/org/ergoplatform/ErgoHeader.scala new file mode 100644 index 0000000000..2c6a40d46d --- /dev/null +++ b/data/shared/src/main/scala/org/ergoplatform/ErgoHeader.scala @@ -0,0 +1,182 @@ +package org.ergoplatform + +import scorex.crypto.authds.ADDigest +import scorex.crypto.hash.{Blake2b256, Digest32} +import scorex.util.ModifierId +import sigma.Colls +import sigma.crypto.{BigIntegers, CryptoConstants, EcPointType} +import sigma.serialization.{GroupElementSerializer, SigmaByteReader, SigmaByteWriter, SigmaSerializer} + +import scala.runtime.ScalaRunTime +import scala.util.hashing.MurmurHash3 + + + +/** + * Solution for an Autolykos PoW puzzle. + * + * In Autolykos v.1 all the four fields are used, in Autolykos v.2 only pk and n fields are used. + * + * @param pk - miner public key. Should be used to collect block rewards + * @param w - one-time public key. Prevents revealing of miners secret + * @param n - nonce (8 bytes) + * @param d - distance between pseudo-random number, corresponding to nonce `n` and a secret, + * corresponding to `pk`. The lower `d` is, the harder it was to find this solution. + */ +class AutolykosSolution(val pk: EcPointType, + val w: EcPointType, + val n: Array[Byte], + val d: BigInt) { + + val encodedPk: Array[Byte] = GroupElementSerializer.toBytes(pk) + + override def hashCode(): Int = { + var h = pk.hashCode() + h = h * 31 + w.hashCode() + h = h * 31 + MurmurHash3.arrayHash(n) + h = h * 31 + d.hashCode() + h + } + + override def equals(obj: Any): Boolean = { + obj match { + case other: AutolykosSolution => + this.pk == other.pk && + this.n.sameElements(other.n) && + this.w == other.w && + this.d == other.d + + case _ => false + } + } +} + + +object AutolykosSolution { + // "pk", "w" and "d" values for Autolykos v2 solution, where they not passed from outside + val pkForV2: EcPointType = CryptoConstants.dlogGroup.identity + val wForV2: EcPointType = CryptoConstants.dlogGroup.generator + val dForV2: BigInt = 0 + + object sigmaSerializerV1 extends SigmaSerializer[AutolykosSolution, AutolykosSolution] { + override def serialize(s: AutolykosSolution, w: SigmaByteWriter): Unit = { + GroupElementSerializer.serialize(s.pk, w) + GroupElementSerializer.serialize(s.w, w) + require(s.n.length == 8) // non-consensus check on prover side + w.putBytes(s.n) + val dBytes = BigIntegers.asUnsignedByteArray(s.d.bigInteger) + w.putUByte(dBytes.length) + w.putBytes(dBytes) + } + + override def parse(r: SigmaByteReader): AutolykosSolution = { + val pk = GroupElementSerializer.parse(r) + val w = GroupElementSerializer.parse(r) + val nonce = r.getBytes(8) + val dBytesLength = r.getUByte() + val d = BigInt(BigIntegers.fromUnsignedByteArray(r.getBytes(dBytesLength))) + new AutolykosSolution(pk, w, nonce, d) + } + } + + object sigmaSerializerV2 extends SigmaSerializer[AutolykosSolution, AutolykosSolution] { + override def serialize(s: AutolykosSolution, w: SigmaByteWriter): Unit = { + GroupElementSerializer.serialize(s.pk, w) + require(s.n.length == 8) // non-consensus check on prover side + w.putBytes(s.n) + } + + override def parse(r: SigmaByteReader): AutolykosSolution = { + val pk = GroupElementSerializer.parse(r) + val nonce = r.getBytes(8) + new AutolykosSolution(pk, wForV2, nonce, dForV2) + } + } +} + +/** + * Header of a block. It authenticates link to a previous block, other block sections + * (transactions, UTXO set transformation proofs, extension), UTXO set, votes for parameters + * to be changed and proof-of-work related data. + * + * @param version - protocol version + * @param parentId - id of a parent block header + * @param ADProofsRoot - digest of UTXO set transformation proofs + * @param stateRoot - AVL+ tree digest of UTXO set (after the block) + * @param transactionsRoot - Merkle tree digest of transactions in the block (BlockTransactions section) + * @param timestamp - block generation time reported by a miner + * @param nBits - difficulty encoded + * @param height - height of the block (genesis block height == 1) + * @param extensionRoot - Merkle tree digest of the extension section of the block + * @param powSolution - solution for the proof-of-work puzzle + * @param votes - votes for changing system parameters + * @param unparsedBytes - bytes from future versions of the protocol our version can't parse + * @param _bytes - serialized bytes of the header when not `null` + */ +case class ErgoHeader(override val version: ErgoHeader.Version, + override val parentId: ModifierId, + override val ADProofsRoot: Digest32, + override val stateRoot: ADDigest, //33 bytes! extra byte with tree height here! + override val transactionsRoot: Digest32, + override val timestamp: ErgoHeader.Timestamp, + override val nBits: Long, //actually it is unsigned int + override val height: Int, + override val extensionRoot: Digest32, + powSolution: AutolykosSolution, + override val votes: Array[Byte], //3 bytes + override val unparsedBytes: Array[Byte], + _bytes: Array[Byte]) extends + HeaderWithoutPow(version, parentId, ADProofsRoot, stateRoot, transactionsRoot, timestamp, + nBits, height, extensionRoot, votes, unparsedBytes) { + + lazy val bytes = if(_bytes != null) { + _bytes + } else { + ErgoHeader.sigmaSerializer.toBytes(this) + } + + lazy val serializedId: Array[Byte] = Blake2b256.hash(bytes) + + lazy val id = Colls.fromArray(serializedId) + + override def hashCode(): Int = id.hashCode() + + override def equals(other: Any): Boolean = other match { + case h: ErgoHeader => h.id == this.id + case _ => false + } +} + + +object ErgoHeader { + + type Timestamp = Long + + type Version = Byte + + object sigmaSerializer extends SigmaSerializer[ErgoHeader, ErgoHeader] { + override def serialize(hdr: ErgoHeader, w: SigmaByteWriter): Unit = { + HeaderWithoutPowSerializer.serialize(hdr, w) + if (hdr.version == 1) { + AutolykosSolution.sigmaSerializerV1.serialize(hdr.powSolution, w) + } else { + AutolykosSolution.sigmaSerializerV2.serialize(hdr.powSolution, w) + } + } + + override def parse(r: SigmaByteReader): ErgoHeader = { + val start = r.position + val headerWithoutPow = HeaderWithoutPowSerializer.parse(r) + val powSolution = if (headerWithoutPow.version == 1) { + AutolykosSolution.sigmaSerializerV1.parse(r) + } else { + AutolykosSolution.sigmaSerializerV2.parse(r) + } + val end = r.position + val len = end - start + r.position = start + val headerBytes = r.getBytes(len) // also moves position back to end + headerWithoutPow.toHeader(powSolution, headerBytes) + } + } +} \ No newline at end of file diff --git a/data/shared/src/main/scala/org/ergoplatform/HeaderWithoutPow.scala b/data/shared/src/main/scala/org/ergoplatform/HeaderWithoutPow.scala new file mode 100644 index 0000000000..21a10a5036 --- /dev/null +++ b/data/shared/src/main/scala/org/ergoplatform/HeaderWithoutPow.scala @@ -0,0 +1,145 @@ +package org.ergoplatform + +import scorex.crypto.authds.ADDigest +import scorex.crypto.hash.Digest32 +import scorex.util.{ModifierId, bytesToId, idToBytes} +import sigma.serialization.{SigmaByteReader, SigmaByteWriter, SigmaSerializer} +import scorex.util.Extensions._ + +/** + * Header without proof-of-work puzzle solution, see Header class description for details. + */ +class HeaderWithoutPow(val version: Byte, // 1 byte + val parentId: ModifierId, // 32 bytes + val ADProofsRoot: Digest32, // 32 bytes + val stateRoot: ADDigest, //33 bytes! extra byte with tree height here! + val transactionsRoot: Digest32, // 32 bytes + val timestamp: Long, + val nBits: Long, //actually it is unsigned int + val height: Int, + val extensionRoot: Digest32, + val votes: Array[Byte], //3 bytes + val unparsedBytes: Array[Byte]) { + def toHeader(powSolution: AutolykosSolution, bytes: Array[Byte]): ErgoHeader = + ErgoHeader(version, parentId, ADProofsRoot, stateRoot, transactionsRoot, timestamp, + nBits, height, extensionRoot, powSolution, votes, unparsedBytes, bytes) + + override def toString: String = { + s"HeaderWithoutPow($version, $parentId, ${bytesToId(ADProofsRoot)}, ${bytesToId(stateRoot)}, " + + s"${bytesToId(transactionsRoot)}, $timestamp, $nBits, $height, ${bytesToId(extensionRoot)}, ${bytesToId(votes)}, " + + s"${bytesToId(unparsedBytes)} )" + } +} + +object HeaderWithoutPow { + + def apply(version: Byte, parentId: ModifierId, ADProofsRoot: Digest32, stateRoot: ADDigest, + transactionsRoot: Digest32, timestamp: Long, nBits: Long, height: Int, + extensionRoot: Digest32, votes: Array[Byte], unparsedBytes: Array[Byte]): HeaderWithoutPow = { + new HeaderWithoutPow(version, parentId, ADProofsRoot, stateRoot, transactionsRoot, timestamp, + nBits, height, extensionRoot, votes, unparsedBytes) + } + +} + +object HeaderWithoutPowSerializer extends SigmaSerializer[HeaderWithoutPow, HeaderWithoutPow] { + + override def serialize(h: HeaderWithoutPow, w: SigmaByteWriter): Unit = { + w.put(h.version) + w.putBytes(idToBytes(h.parentId)) + w.putBytes(h.ADProofsRoot) + w.putBytes(h.transactionsRoot) + w.putBytes(h.stateRoot) + w.putULong(h.timestamp) + w.putBytes(h.extensionRoot) + DifficultySerializer.serialize(h.nBits, w) + w.putUInt(h.height.toLong) + w.putBytes(h.votes) + + // For block version >= 2, this new byte encodes length of possible new fields. + // Set to 0 for now, so no new fields. + if (h.version > HeaderVersion.InitialVersion) { + w.putUByte(h.unparsedBytes.length) + w.putBytes(h.unparsedBytes) + } + } + + override def parse(r: SigmaByteReader): HeaderWithoutPow = { + val version = r.getByte() + val parentId = bytesToId(r.getBytes(32)) + val ADProofsRoot = Digest32 @@ r.getBytes(32) + val transactionsRoot = Digest32 @@ r.getBytes(32) + val stateRoot = ADDigest @@ r.getBytes(33) + val timestamp = r.getULong() + val extensionHash = Digest32 @@ r.getBytes(32) + val nBits = DifficultySerializer.parse(r) + val height = r.getUInt().toIntExact + val votes = r.getBytes(3) + + // For block version >= 2, a new byte encodes length of possible new fields. + // If this byte > 0, we read new fields but do nothing, as semantics of the fields is not known. + val unparsedBytes = if (version > HeaderVersion.InitialVersion) { + val newFieldsSize = r.getUByte() + if (newFieldsSize > 0 && version > HeaderVersion.Interpreter60Version) { + // new bytes could be added only for block version >= 5 + r.getBytes(newFieldsSize) + } else { + Array.emptyByteArray + } + } else { + Array.emptyByteArray + } + + HeaderWithoutPow(version, parentId, ADProofsRoot, stateRoot, transactionsRoot, timestamp, + nBits, height, extensionHash, votes, unparsedBytes) + } + +} + + +object DifficultySerializer extends SigmaSerializer[Long, Long] { + + /** Parse 4 bytes from the byte array (starting at the offset) as unsigned 32-bit integer in big endian format. */ + def readUint32BE(bytes: Array[Byte]): Long = ((bytes(0) & 0xffL) << 24) | ((bytes(1) & 0xffL) << 16) | ((bytes(2) & 0xffL) << 8) | (bytes(3) & 0xffL) + + def uint32ToByteArrayBE(value: Long): Array[Byte] = { + Array(0xFF & (value >> 24), 0xFF & (value >> 16), 0xFF & (value >> 8), 0xFF & value).map(_.toByte) + } + + override def serialize(obj: Long, w: SigmaByteWriter): Unit = { + w.putBytes(uint32ToByteArrayBE(obj)) + } + + override def parse(r: SigmaByteReader): Long = { + readUint32BE(r.getBytes(4)) + } + +} + +object HeaderVersion { + type Value = Byte + + /** + * Block version during mainnet launch + */ + val InitialVersion: Value = 1.toByte + + /** + * Block version after the Hardening hard-fork + * Autolykos v2 PoW, witnesses in transactions Merkle tree + */ + val HardeningVersion: Value = 2.toByte + + /** + * Block version after the 5.0 soft-fork + * 5.0 interpreter with JITC, monotonic height rule (EIP-39) + */ + val Interpreter50Version: Value = 3.toByte + + /** + * Block version after the 6.0 soft-fork + * 6.0 interpreter (EIP-50) + */ + val Interpreter60Version: Value = 4.toByte + +} diff --git a/data/shared/src/main/scala/sigma/SigmaDataReflection.scala b/data/shared/src/main/scala/sigma/SigmaDataReflection.scala index 48939b1460..c64bdee877 100644 --- a/data/shared/src/main/scala/sigma/SigmaDataReflection.scala +++ b/data/shared/src/main/scala/sigma/SigmaDataReflection.scala @@ -86,6 +86,14 @@ object SigmaDataReflection { ) ) + registerClassEntry(classOf[LongToByteArray], + constructors = Array( + mkConstructor(Array(classOf[Value[_]])) { args => + new LongToByteArray(args(0).asInstanceOf[Value[SLong.type]]) + } + ) + ) + registerClassEntry(classOf[CalcBlake2b256], constructors = Array( mkConstructor(Array(classOf[Value[_]])) { args => @@ -322,6 +330,11 @@ object SigmaDataReflection { args(1).asInstanceOf[SigmaDslBuilder], args(2).asInstanceOf[Coll[Byte]], args(3).asInstanceOf[Coll[Byte]])(args(4).asInstanceOf[ErgoTreeEvaluator]) + }, + mkMethod(clazz, "serialize_eval", Array[Class[_]](classOf[MethodCall], classOf[SigmaDslBuilder], classOf[Object], classOf[ErgoTreeEvaluator])) { (obj, args) => + obj.asInstanceOf[SGlobalMethods.type].serialize_eval(args(0).asInstanceOf[MethodCall], + args(1).asInstanceOf[SigmaDslBuilder], + args(2).asInstanceOf[SType#WrappedType])(args(3).asInstanceOf[ErgoTreeEvaluator]) } ) ) diff --git a/data/shared/src/main/scala/sigma/ast/ErgoTree.scala b/data/shared/src/main/scala/sigma/ast/ErgoTree.scala index 68d69abd91..8d731e1c67 100644 --- a/data/shared/src/main/scala/sigma/ast/ErgoTree.scala +++ b/data/shared/src/main/scala/sigma/ast/ErgoTree.scala @@ -381,7 +381,7 @@ object ErgoTree { * */ def withSegregation(header: HeaderType, prop: SigmaPropValue): ErgoTree = { val constantStore = new ConstantStore() - val w = SigmaSerializer.startWriter(constantStore) + val w = SigmaSerializer.startWriter(Some(constantStore)) // serialize value and segregate constants into constantStore ValueSerializer.serialize(prop, w) val extractedConstants = constantStore.getAll diff --git a/data/shared/src/main/scala/sigma/ast/SMethod.scala b/data/shared/src/main/scala/sigma/ast/SMethod.scala index 5a17038c54..11e656b1be 100644 --- a/data/shared/src/main/scala/sigma/ast/SMethod.scala +++ b/data/shared/src/main/scala/sigma/ast/SMethod.scala @@ -81,7 +81,9 @@ case class SMethod( /** Operation descriptor of this method. */ lazy val opDesc = MethodDesc(this) - /** Return true if this method has runtime type parameters */ + /** Return true if this method has explicit type parameters, which need to be serialized + * as part of [[MethodCall]]. + */ def hasExplicitTypeArgs: Boolean = explicitTypeArgs.nonEmpty /** Finds and keeps the [[RMethod]] instance which corresponds to this method descriptor. diff --git a/data/shared/src/main/scala/sigma/ast/SigmaPredef.scala b/data/shared/src/main/scala/sigma/ast/SigmaPredef.scala index 068d955541..8b89851938 100644 --- a/data/shared/src/main/scala/sigma/ast/SigmaPredef.scala +++ b/data/shared/src/main/scala/sigma/ast/SigmaPredef.scala @@ -402,6 +402,24 @@ object SigmaPredef { ArgInfo("default", "optional default value, if register is not available"))) ) + val SerializeFunc = PredefinedFunc("serialize", + Lambda(Seq(paramT), Array("value" -> tT), SByteArray, None), + irInfo = PredefFuncInfo( + irBuilder = { case (_, args @ Seq(value)) => + MethodCall.typed[Value[SCollection[SByte.type]]]( + Global, + SGlobalMethods.serializeMethod.withConcreteTypes(Map(tT -> value.tpe)), + args.toIndexedSeq, + Map() + ) + }), + docInfo = OperationInfo(MethodCall, + """Serializes the given `value` into bytes using the default serialization format. + """.stripMargin, + Seq(ArgInfo("value", "value to serialize")) + ) + ) + val globalFuncs: Map[String, PredefinedFunc] = Seq( AllOfFunc, AnyOfFunc, @@ -429,7 +447,8 @@ object SigmaPredef { AvlTreeFunc, SubstConstantsFunc, ExecuteFromVarFunc, - ExecuteFromSelfRegFunc + ExecuteFromSelfRegFunc, + SerializeFunc ).map(f => f.name -> f).toMap def comparisonOp(symbolName: String, opDesc: ValueCompanion, desc: String, args: Seq[ArgInfo]) = { diff --git a/data/shared/src/main/scala/sigma/ast/methods.scala b/data/shared/src/main/scala/sigma/ast/methods.scala index e4cf0007e0..77758320f1 100644 --- a/data/shared/src/main/scala/sigma/ast/methods.scala +++ b/data/shared/src/main/scala/sigma/ast/methods.scala @@ -5,13 +5,16 @@ import org.ergoplatform.validation._ import sigma._ import sigma.ast.SCollection.{SBooleanArray, SBoxArray, SByteArray, SByteArray2, SHeaderArray} import sigma.ast.SMethod.{MethodCallIrBuilder, MethodCostFunc, javaMethodOf} -import sigma.ast.SType.TypeCode +import sigma.ast.SType.{TypeCode, paramT, tT} import sigma.ast.syntax.{SValue, ValueOps} +import sigma.data.ExactIntegral.{ByteIsExactIntegral, IntIsExactIntegral, LongIsExactIntegral, ShortIsExactIntegral} +import sigma.data.NumericOps.BigIntIsExactIntegral import sigma.data.OverloadHack.Overloaded1 import sigma.data.{DataValueComparer, KeyValueColl, Nullable, RType, SigmaConstants} import sigma.eval.{CostDetails, ErgoTreeEvaluator, TracedCost} import sigma.reflection.RClass import sigma.serialization.CoreByteWriter.ArgInfo +import sigma.serialization.{DataSerializer, SigmaByteWriter, SigmaSerializer} import sigma.utils.SparseArrayContainer import scala.annotation.unused @@ -156,14 +159,34 @@ trait MonoTypeMethods extends MethodsContainer { trait SNumericTypeMethods extends MonoTypeMethods { import SNumericTypeMethods.tNum + + private val subst = Map(tNum -> this.ownerType) + + val v5Methods = { + SNumericTypeMethods.v5Methods.map { m => + m.copy(stype = applySubst(m.stype, subst).asFunc) + } + } + + val v6Methods = { + SNumericTypeMethods.v6Methods.map { m => + m.copy( + objType = this, // associate the method with the concrete numeric type + stype = applySubst(m.stype, subst).asFunc + )} + } + protected override def getMethods(): Seq[SMethod] = { - super.getMethods() ++ SNumericTypeMethods.methods.map { - m => m.copy(stype = applySubst(m.stype, Map(tNum -> this.ownerType)).asFunc) + if (VersionContext.current.isV6SoftForkActivated) { + super.getMethods() ++ v6Methods + } else { + super.getMethods() ++ v5Methods } } } object SNumericTypeMethods extends MethodsContainer { + /** Type for which this container defines methods. */ override def ownerType: STypeCompanion = SNumericType @@ -216,6 +239,15 @@ object SNumericTypeMethods extends MethodsContainer { val ToBytesMethod: SMethod = SMethod( this, "toBytes", SFunc(tNum, SByteArray), 6, ToBytes_CostKind) .withIRInfo(MethodCallIrBuilder) + .withUserDefinedInvoke({ (m: SMethod, obj: Any, _: Array[Any]) => + m.objType match { + case SByteMethods => ByteIsExactIntegral.toBigEndianBytes(obj.asInstanceOf[Byte]) + case SShortMethods => ShortIsExactIntegral.toBigEndianBytes(obj.asInstanceOf[Short]) + case SIntMethods => IntIsExactIntegral.toBigEndianBytes(obj.asInstanceOf[Int]) + case SLongMethods => LongIsExactIntegral.toBigEndianBytes(obj.asInstanceOf[Long]) + case SBigIntMethods => obj.asInstanceOf[BigInt].toBytes + } + }) .withInfo(PropertyCall, """ Returns a big-endian representation of this numeric value in a collection of bytes. | For example, the \lst{Int} value \lst{0x12131415} would yield the @@ -229,12 +261,124 @@ object SNumericTypeMethods extends MethodsContainer { val ToBitsMethod: SMethod = SMethod( this, "toBits", SFunc(tNum, SBooleanArray), 7, ToBits_CostKind) .withIRInfo(MethodCallIrBuilder) + .withUserDefinedInvoke({ (m: SMethod, obj: Any, _: Array[Any]) => + m.objType match { + case SByteMethods => ByteIsExactIntegral.toBits(obj.asInstanceOf[Byte]) + case SShortMethods => ShortIsExactIntegral.toBits(obj.asInstanceOf[Short]) + case SIntMethods => IntIsExactIntegral.toBits(obj.asInstanceOf[Int]) + case SLongMethods => LongIsExactIntegral.toBits(obj.asInstanceOf[Long]) + case SBigIntMethods => BigIntIsExactIntegral.toBits(obj.asInstanceOf[BigInt]) + } + }) .withInfo(PropertyCall, """ Returns a big-endian representation of this numeric in a collection of Booleans. | Each boolean corresponds to one bit. """.stripMargin) - protected override def getMethods(): Seq[SMethod] = Array( + /** Cost of inverting bits of a number. */ + val BitwiseOp_CostKind = FixedCost(JitCost(5)) + + val BitwiseInverseMethod: SMethod = SMethod( + this, "bitwiseInverse", SFunc(tNum, tNum), 8, BitwiseOp_CostKind) + .withIRInfo(MethodCallIrBuilder) + .withUserDefinedInvoke({ (m: SMethod, obj: Any, _: Array[Any]) => + m.objType match { + case SByteMethods => ByteIsExactIntegral.bitwiseInverse(obj.asInstanceOf[Byte]) + case SShortMethods => ShortIsExactIntegral.bitwiseInverse(obj.asInstanceOf[Short]) + case SIntMethods => IntIsExactIntegral.bitwiseInverse(obj.asInstanceOf[Int]) + case SLongMethods => LongIsExactIntegral.bitwiseInverse(obj.asInstanceOf[Long]) + case SBigIntMethods => BigIntIsExactIntegral.bitwiseInverse(obj.asInstanceOf[BigInt]) + } + }) + .withInfo(PropertyCall, desc = "Returns bitwise inverse of this numeric. ") + + val BitwiseOrMethod: SMethod = SMethod( + this, "bitwiseOr", SFunc(Array(tNum, tNum), tNum), 9, BitwiseOp_CostKind) + .withIRInfo(MethodCallIrBuilder) + .withUserDefinedInvoke({ (m: SMethod, obj: Any, other: Array[Any]) => + m.objType match { + case SByteMethods => ByteIsExactIntegral.bitwiseOr(obj.asInstanceOf[Byte], other.head.asInstanceOf[Byte]) + case SShortMethods => ShortIsExactIntegral.bitwiseOr(obj.asInstanceOf[Short], other.head.asInstanceOf[Short]) + case SIntMethods => IntIsExactIntegral.bitwiseOr(obj.asInstanceOf[Int], other.head.asInstanceOf[Int]) + case SLongMethods => LongIsExactIntegral.bitwiseOr(obj.asInstanceOf[Long], other.head.asInstanceOf[Long]) + case SBigIntMethods => BigIntIsExactIntegral.bitwiseOr(obj.asInstanceOf[BigInt], other.head.asInstanceOf[BigInt]) + } + }) + .withInfo(MethodCall, + """ Returns bitwise or of this numeric and provided one. """.stripMargin, + ArgInfo("that", "A numeric value to calculate or with.")) + + val BitwiseAndMethod: SMethod = SMethod( + this, "bitwiseAnd", SFunc(Array(tNum, tNum), tNum), 10, BitwiseOp_CostKind) + .withIRInfo(MethodCallIrBuilder) + .withUserDefinedInvoke({ (m: SMethod, obj: Any, other: Array[Any]) => + m.objType match { + case SByteMethods => ByteIsExactIntegral.bitwiseAnd(obj.asInstanceOf[Byte], other.head.asInstanceOf[Byte]) + case SShortMethods => ShortIsExactIntegral.bitwiseAnd(obj.asInstanceOf[Short], other.head.asInstanceOf[Short]) + case SIntMethods => IntIsExactIntegral.bitwiseAnd(obj.asInstanceOf[Int], other.head.asInstanceOf[Int]) + case SLongMethods => LongIsExactIntegral.bitwiseAnd(obj.asInstanceOf[Long], other.head.asInstanceOf[Long]) + case SBigIntMethods => BigIntIsExactIntegral.bitwiseAnd(obj.asInstanceOf[BigInt], other.head.asInstanceOf[BigInt]) + } + }) + .withInfo(MethodCall, + """ Returns bitwise and of this numeric and provided one. """.stripMargin, + ArgInfo("that", "A numeric value to calculate and with.")) + + val BitwiseXorMethod: SMethod = SMethod( + this, "bitwiseXor", SFunc(Array(tNum, tNum), tNum), 11, BitwiseOp_CostKind) + .withIRInfo(MethodCallIrBuilder) + .withUserDefinedInvoke({ (m: SMethod, obj: Any, other: Array[Any]) => + m.objType match { + case SByteMethods => ByteIsExactIntegral.bitwiseXor(obj.asInstanceOf[Byte], other.head.asInstanceOf[Byte]) + case SShortMethods => ShortIsExactIntegral.bitwiseXor(obj.asInstanceOf[Short], other.head.asInstanceOf[Short]) + case SIntMethods => IntIsExactIntegral.bitwiseXor(obj.asInstanceOf[Int], other.head.asInstanceOf[Int]) + case SLongMethods => LongIsExactIntegral.bitwiseXor(obj.asInstanceOf[Long], other.head.asInstanceOf[Long]) + case SBigIntMethods => BigIntIsExactIntegral.bitwiseXor(obj.asInstanceOf[BigInt], other.head.asInstanceOf[BigInt]) + } + }) + .withInfo(MethodCall, + """ Returns bitwise xor of this numeric and provided one. """.stripMargin, + ArgInfo("that", "A numeric value to calculate xor with.")) + + val ShiftLeftMethod: SMethod = SMethod( + this, "shiftLeft", SFunc(Array(tNum, SInt), tNum), 12, BitwiseOp_CostKind) + .withIRInfo(MethodCallIrBuilder) + .withUserDefinedInvoke({ (m: SMethod, obj: Any, other: Array[Any]) => + m.objType match { + case SByteMethods => ByteIsExactIntegral.shiftLeft(obj.asInstanceOf[Byte], other.head.asInstanceOf[Int]) + case SShortMethods => ShortIsExactIntegral.shiftLeft(obj.asInstanceOf[Short], other.head.asInstanceOf[Int]) + case SIntMethods => IntIsExactIntegral.shiftLeft(obj.asInstanceOf[Int], other.head.asInstanceOf[Int]) + case SLongMethods => LongIsExactIntegral.shiftLeft(obj.asInstanceOf[Long], other.head.asInstanceOf[Int]) + case SBigIntMethods => BigIntIsExactIntegral.shiftLeft(obj.asInstanceOf[BigInt], other.head.asInstanceOf[Int]) + } + }) + .withInfo(MethodCall, + """ Returns a big-endian representation of this numeric in a collection of Booleans. + | Each boolean corresponds to one bit. + """.stripMargin, + ArgInfo("bits", "Number of bit to shift to the left. Note, that bits value must be non-negative and less than " + + "the size of the number in bits (e.g. 64 for Long, 256 for BigInt)")) + + val ShiftRightMethod: SMethod = SMethod( + this, "shiftRight", SFunc(Array(tNum, SInt), tNum), 13, BitwiseOp_CostKind) + .withIRInfo(MethodCallIrBuilder) + .withUserDefinedInvoke({ (m: SMethod, obj: Any, other: Array[Any]) => + m.objType match { + case SByteMethods => ByteIsExactIntegral.shiftRight(obj.asInstanceOf[Byte], other.head.asInstanceOf[Int]) + case SShortMethods => ShortIsExactIntegral.shiftRight(obj.asInstanceOf[Short], other.head.asInstanceOf[Int]) + case SIntMethods => IntIsExactIntegral.shiftRight(obj.asInstanceOf[Int], other.head.asInstanceOf[Int]) + case SLongMethods => LongIsExactIntegral.shiftRight(obj.asInstanceOf[Long], other.head.asInstanceOf[Int]) + case SBigIntMethods => BigIntIsExactIntegral.shiftRight(obj.asInstanceOf[BigInt], other.head.asInstanceOf[Int]) + } + }) + .withInfo(MethodCall, + """ Returns a big-endian representation of this numeric in a collection of Booleans. + | Each boolean corresponds to one bit. + """.stripMargin, + ArgInfo("bits", "Number of bit to shift to the right. Note, that bits value must be non-negative and less than " + + "the size of the number in bits (e.g. 64 for Long, 256 for BigInt)")) + + lazy val v5Methods = Array( ToByteMethod, // see Downcast ToShortMethod, // see Downcast ToIntMethod, // see Downcast @@ -244,7 +388,21 @@ object SNumericTypeMethods extends MethodsContainer { ToBitsMethod ) + lazy val v6Methods = v5Methods ++ Array( + BitwiseInverseMethod, + BitwiseOrMethod, + BitwiseAndMethod, + BitwiseXorMethod, + ShiftLeftMethod, + ShiftRightMethod + ) + + protected override def getMethods(): Seq[SMethod] = { + throw new Exception("SNumericTypeMethods.getMethods shouldn't ever be called") + } + /** Collection of names of numeric casting methods (like `toByte`, `toInt`, etc). */ + // todo: add unsigned big int val castMethods: Array[String] = Array(ToByteMethod, ToShortMethod, ToIntMethod, ToLongMethod, ToBigIntMethod) .map(_.name) @@ -309,31 +467,9 @@ case object SBigIntMethods extends SNumericTypeMethods { /** Type for which this container defines methods. */ override def ownerType: SMonoType = SBigInt - final val ToNBitsCostInfo = OperationCostInfo( - FixedCost(JitCost(5)), NamedDesc("NBitsMethodCall")) - - //id = 8 to make it after toBits - val ToNBits = SMethod(this, "nbits", SFunc(this.ownerType, SLong), 8, ToNBitsCostInfo.costKind) - .withInfo(ModQ, "Encode this big integer value as NBits") - - /** The following `modQ` methods are not fully implemented in v4.x and this descriptors. - * This descritors are remain here in the code and are waiting for full implementation - * is upcoming soft-forks at which point the cost parameters should be calculated and - * changed. - */ - val ModQMethod = SMethod(this, "modQ", SFunc(this.ownerType, SBigInt), 1, FixedCost(JitCost(1))) - .withInfo(ModQ, "Returns this \\lst{mod} Q, i.e. remainder of division by Q, where Q is an order of the cryprographic group.") - val PlusModQMethod = SMethod(this, "plusModQ", SFunc(IndexedSeq(this.ownerType, SBigInt), SBigInt), 2, FixedCost(JitCost(1))) - .withInfo(ModQArithOp.PlusModQ, "Adds this number with \\lst{other} by module Q.", ArgInfo("other", "Number to add to this.")) - val MinusModQMethod = SMethod(this, "minusModQ", SFunc(IndexedSeq(this.ownerType, SBigInt), SBigInt), 3, FixedCost(JitCost(1))) - .withInfo(ModQArithOp.MinusModQ, "Subtracts \\lst{other} number from this by module Q.", ArgInfo("other", "Number to subtract from this.")) - val MultModQMethod = SMethod(this, "multModQ", SFunc(IndexedSeq(this.ownerType, SBigInt), SBigInt), 4, FixedCost(JitCost(1))) - .withIRInfo(MethodCallIrBuilder) - .withInfo(MethodCall, "Multiply this number with \\lst{other} by module Q.", ArgInfo("other", "Number to multiply with this.")) - protected override def getMethods(): Seq[SMethod] = { if (VersionContext.current.isV6SoftForkActivated) { - super.getMethods() ++ Seq(ToNBits) + super.getMethods() // ModQMethod, // PlusModQMethod, // MinusModQMethod, @@ -344,12 +480,6 @@ case object SBigIntMethods extends SNumericTypeMethods { } } - /** - * - */ - def nbits_eval(mc: MethodCall, bi: sigma.BigInt)(implicit E: ErgoTreeEvaluator): Long = { - ??? - } } @@ -1063,7 +1193,7 @@ case object SBoxMethods extends MonoTypeMethods { | identifier followed by box index in the transaction outputs. """.stripMargin ) // see ExtractCreationInfo - lazy val getRegMethod = SMethod(this, "getReg", + lazy val getRegMethodV5 = SMethod(this, "getReg", SFunc(Array(SBox, SInt), SOption(tT), Array(paramT)), 7, ExtractRegisterAs.costKind) .withInfo(ExtractRegisterAs, """ Extracts register by id and type. @@ -1072,23 +1202,49 @@ case object SBoxMethods extends MonoTypeMethods { """.stripMargin, ArgInfo("regId", "zero-based identifier of the register.")) + lazy val getRegMethodV6 = SMethod(this, "getReg", + SFunc(Array(SBox, SInt), SOption(tT), Array(paramT)), 7, ExtractRegisterAs.costKind, Seq(tT)) + .withIRInfo(MethodCallIrBuilder, + javaMethodOf[Box, Int, RType[_]]("getReg"), + { mtype => Array(mtype.tRange.asOption[SType].elemType) }) + .withInfo(MethodCall, """ Extracts register by id and type. + | Type param \lst{T} expected type of the register. + | Returns \lst{Some(value)} if the register is defined and has given type and \lst{None} otherwise + """.stripMargin, + ArgInfo("regId", "zero-based identifier of the register.")) + lazy val tokensMethod = SMethod( this, "tokens", SFunc(SBox, ErgoBox.STokensRegType), 8, FixedCost(JitCost(15))) .withIRInfo(MethodCallIrBuilder) .withInfo(PropertyCall, "Secondary tokens") - - // should be lazy to solve recursive initialization - protected override def getMethods() = super.getMethods() ++ Array( + lazy val commonBoxMethods = super.getMethods() ++ Array( ValueMethod, // see ExtractAmount PropositionBytesMethod, // see ExtractScriptBytes BytesMethod, // see ExtractBytes BytesWithoutRefMethod, // see ExtractBytesWithNoRef IdMethod, // see ExtractId creationInfoMethod, - getRegMethod, tokensMethod ) ++ registers(8) + + lazy val v5Methods = commonBoxMethods ++ Array( + getRegMethodV5 + ) + + lazy val v6Methods = commonBoxMethods ++ Array( + getRegMethodV6 + ) + + // should be lazy to solve recursive initialization + protected override def getMethods() = { + if (VersionContext.current.isV6SoftForkActivated) { + v6Methods + } else { + v5Methods + } + } + } /** Type descriptor of `AvlTree` type of ErgoTree. */ @@ -1456,11 +1612,27 @@ case object SHeaderMethods extends MonoTypeMethods { lazy val powDistanceMethod = propertyCall("powDistance", SBigInt, 14, FixedCost(JitCost(10))) lazy val votesMethod = propertyCall("votes", SByteArray, 15, FixedCost(JitCost(10))) - protected override def getMethods() = super.getMethods() ++ Seq( + // cost of checkPoW is 700 as about 2*32 hashes required, and 1 hash (id) over short data costs 10 + lazy val checkPowMethod = SMethod( + this, "checkPow", SFunc(Array(SHeader), SBoolean), 16, FixedCost(JitCost(700))) + .withIRInfo(MethodCallIrBuilder) + .withInfo(MethodCall, "Validate header's proof-of-work") + + private lazy val v5Methods = super.getMethods() ++ Seq( idMethod, versionMethod, parentIdMethod, ADProofsRootMethod, stateRootMethod, transactionsRootMethod, timestampMethod, nBitsMethod, heightMethod, extensionRootMethod, minerPkMethod, powOnetimePkMethod, - powNonceMethod, powDistanceMethod, votesMethod - ) + powNonceMethod, powDistanceMethod, votesMethod) + + // 6.0 : checkPow method added + private lazy val v6Methods = v5Methods ++ Seq(checkPowMethod) + + protected override def getMethods() = { + if (VersionContext.current.isV6SoftForkActivated) { + v6Methods + } else { + v5Methods + } + } } /** Type descriptor of `PreHeader` type of ErgoTree. */ @@ -1519,9 +1691,48 @@ case object SGlobalMethods extends MonoTypeMethods { Xor.xorWithCosting(ls, rs) } - protected override def getMethods() = super.getMethods() ++ Seq( - groupGeneratorMethod, - xorMethod - ) + lazy val serializeMethod = SMethod(this, "serialize", + SFunc(Array(SGlobal, tT), SByteArray, Array(paramT)), 3, DynamicCost) + .withIRInfo(MethodCallIrBuilder) + .withInfo(MethodCall, "Serializes the given `value` into bytes using the default serialization format.", + ArgInfo("value", "value to be serialized")) + + + /** Implements evaluation of Global.serialize method call ErgoTree node. + * Called via reflection based on naming convention. + * @see SMethod.evalMethod + */ + def serialize_eval(mc: MethodCall, G: SigmaDslBuilder, value: SType#WrappedType) + (implicit E: ErgoTreeEvaluator): Coll[Byte] = { + + E.addCost(SigmaByteWriter.StartWriterCost) + + val addFixedCostCallback = { (costInfo: OperationCostInfo[FixedCost]) => + E.addCost(costInfo) + } + val addPerItemCostCallback = { (info: OperationCostInfo[PerItemCost], nItems: Int) => + E.addSeqCostNoOp(info.costKind, nItems, info.opDesc) + } + val w = SigmaSerializer.startWriter(None, + Some(addFixedCostCallback), Some(addPerItemCostCallback)) + + DataSerializer.serialize(value, mc.args(0).tpe, w) + Colls.fromArray(w.toBytes) + } + + protected override def getMethods() = super.getMethods() ++ { + if (VersionContext.current.isV6SoftForkActivated) { + Seq( + groupGeneratorMethod, + xorMethod, + serializeMethod + ) + } else { + Seq( + groupGeneratorMethod, + xorMethod + ) + } + } } diff --git a/data/shared/src/main/scala/sigma/ast/values.scala b/data/shared/src/main/scala/sigma/ast/values.scala index 87c661a00a..ff5da32ec7 100644 --- a/data/shared/src/main/scala/sigma/ast/values.scala +++ b/data/shared/src/main/scala/sigma/ast/values.scala @@ -1298,6 +1298,10 @@ case class MethodCall( method: SMethod, args: IndexedSeq[Value[SType]], typeSubst: Map[STypeVar, SType]) extends Value[SType] { + + require(method.explicitTypeArgs.forall(tyArg => typeSubst.contains(tyArg)), + s"Generic method call should have concrete type for each explicit type parameter, but was: $this") + override def companion = if (args.isEmpty) PropertyCall else MethodCall override def opType: SFunc = SFunc(obj.tpe +: args.map(_.tpe), tpe) diff --git a/data/shared/src/main/scala/sigma/data/BigIntegerOps.scala b/data/shared/src/main/scala/sigma/data/BigIntegerOps.scala index 168b2f8266..8d272439f4 100644 --- a/data/shared/src/main/scala/sigma/data/BigIntegerOps.scala +++ b/data/shared/src/main/scala/sigma/data/BigIntegerOps.scala @@ -2,6 +2,7 @@ package sigma.data import sigma._ import sigma.eval.Extensions.IntExt +import sigma.util.Extensions.BigIntOps import scala.math.{Integral, Ordering} @@ -89,6 +90,32 @@ object NumericOps { * NOTE: This method should not be used in v4.x */ override def divisionRemainder(x: BigInt, y: BigInt): BigInt = x.mod(y) + + override def toBigEndianBytes(x: BigInt): Coll[Byte] = Colls.fromArray(x.toBigInteger.toByteArray) + + override def bitwiseInverse(x: BigInt): BigInt = CBigInt(x.toBigInteger.not()) + + override def bitwiseOr(x: BigInt, y: BigInt): BigInt = x.or(y) + + override def bitwiseAnd(x: BigInt, y: BigInt): BigInt = x.and(y) + + override def bitwiseXor(x: BigInt, y: BigInt): BigInt = x.xor(y) + + override def shiftLeft(x: BigInt, bits: Int): BigInt = { + if (bits < 0 || bits >= 256) { + throw new IllegalArgumentException(s"Wrong argument in BigInt.shiftRight: bits < 0 || bits >= 256 ($bits)") + } else { + x.shiftLeft(bits) + } + } + + override def shiftRight(x: BigInt, bits: Int): BigInt = { + if (bits < 0 || bits >= 256) { + throw new IllegalArgumentException(s"Wrong argument in BigInt.shiftRight: bits < 0 || bits >= 256 ($bits)") + } else { + x.shiftRight(bits) + } + } } /** The instance of [[scalan.ExactOrdering]] typeclass for [[BigInt]]. */ diff --git a/data/shared/src/main/scala/sigma/data/CHeader.scala b/data/shared/src/main/scala/sigma/data/CHeader.scala new file mode 100644 index 0000000000..aa5a5756d2 --- /dev/null +++ b/data/shared/src/main/scala/sigma/data/CHeader.scala @@ -0,0 +1,142 @@ +package sigma.data + +import org.ergoplatform.{AutolykosSolution, ErgoHeader, HeaderWithoutPow, HeaderWithoutPowSerializer} +import scorex.crypto.authds.ADDigest +import scorex.crypto.hash.Digest32 +import scorex.util.{bytesToId, idToBytes} +import sigma.pow.Autolykos2PowValidation +import sigma.{AvlTree, BigInt, Coll, Colls, GroupElement, Header} + +/** A default implementation of [[Header]] interface. + * + * @see [[Header]] for detailed descriptions + */ +class CHeader(val ergoHeader: ErgoHeader) extends Header with WrapperOf[ErgoHeader] { + + /** Bytes representation of ModifierId of this Header */ + override lazy val id: Coll[Byte] = ergoHeader.id + + /** Block version, to be increased on every soft and hardfork. */ + override def version: Byte = ergoHeader.version + + /** Bytes representation of ModifierId of the parent block */ + override def parentId: Coll[Byte] = Colls.fromArray(idToBytes(ergoHeader.parentId)) + + /** Hash of ADProofs for transactions in a block */ + override def ADProofsRoot: Coll[Byte] = Colls.fromArray(ergoHeader.ADProofsRoot) + + /** AvlTree of a state after block application */ + override def stateRoot: AvlTree = CAvlTree(AvlTreeData.avlTreeFromDigest(Colls.fromArray(ergoHeader.stateRoot))) + + /** Root hash (for a Merkle tree) of transactions in a block. */ + override def transactionsRoot: Coll[Byte] = Colls.fromArray(ergoHeader.transactionsRoot) + + /** Block timestamp (in milliseconds since beginning of Unix Epoch) */ + override def timestamp: Long = ergoHeader.timestamp + + /** Current difficulty in a compressed view. + * NOTE: actually it is unsigned Int */ + override def nBits: Long = ergoHeader.nBits + + /** Block height */ + override def height: Int = ergoHeader.height + + /** Root hash of extension section */ + override def extensionRoot: Coll[Byte] = Colls.fromArray(ergoHeader.extensionRoot) + + /** Miner public key. Should be used to collect block rewards. + * Part of Autolykos solution. */ + override def minerPk: GroupElement = CGroupElement(ergoHeader.powSolution.pk) + + /** One-time public key. Prevents revealing of miners secret. */ + override def powOnetimePk: GroupElement = CGroupElement(ergoHeader.powSolution.w) + + /** nonce */ + override def powNonce: Coll[Byte] = Colls.fromArray(ergoHeader.powSolution.n) + + /** Distance between pseudo-random number, corresponding to nonce `powNonce` and a secret, + * corresponding to `minerPk`. The lower `powDistance` is, the harder it was to find this solution. */ + override def powDistance: BigInt = CBigInt(ergoHeader.powSolution.d.bigInteger) + + /** Miner votes for changing system parameters. */ + override def votes: Coll[Byte] = Colls.fromArray(ergoHeader.votes) + + override def unparsedBytes: Coll[Byte] = Colls.fromArray(ergoHeader.unparsedBytes) + + /** The data value wrapped by this wrapper. */ + override def wrappedValue: ErgoHeader = ergoHeader + + override def serializeWithoutPoW: Coll[Byte] = { + Colls.fromArray(HeaderWithoutPowSerializer.toBytes(ergoHeader)) + } + + override def checkPow: Boolean = { + Autolykos2PowValidation.checkPoWForVersion2(this) + } + + override def toString: String = + s"""CHeader( + | id: ${id}, + | version: ${version}, + | tx proofs hash: ${ADProofsRoot}, + | state root: ${stateRoot.digest}, + | transactions root: ${transactionsRoot}, + | time: $timestamp, + | nbits: $nBits, + | extension root: $extensionRoot, + | miner pubkey: $minerPk, + | pow one time pubkey(from AL 1): $powOnetimePk, + | pow nonce: $powNonce, + | pow distance (from AL 1): $powDistance, + | votes: $votes, + | unparsed bytes: $unparsedBytes + |)""".stripMargin + + override def hashCode(): Int = id.hashCode() + + override def equals(other: Any): Boolean = other match { + case ch: CHeader => ch.id == this.id + case _ => false + } + + def copy(): CHeader = new CHeader(ergoHeader.copy()) // used in tests only +} + +object CHeader { + + def apply( version: Byte, + parentId: Coll[Byte], + ADProofsRoot: Coll[Byte], + stateRootDigest: Coll[Byte], + transactionsRoot: Coll[Byte], + timestamp: Long, + nBits: Long, + height: Int, + extensionRoot: Coll[Byte], + minerPk: GroupElement, + powOnetimePk: GroupElement, + powNonce: Coll[Byte], + powDistance: BigInt, + votes: Coll[Byte], + unparsedBytes: Coll[Byte]): CHeader = { + + val solution = new AutolykosSolution( + minerPk.asInstanceOf[CGroupElement].wrappedValue, + powOnetimePk.asInstanceOf[CGroupElement].wrappedValue, + powNonce.toArray, + powDistance.asInstanceOf[CBigInt].wrappedValue) + + val h = ErgoHeader(version, bytesToId(parentId.toArray), Digest32 @@ ADProofsRoot.toArray, + ADDigest @@ stateRootDigest.toArray, Digest32 @@ transactionsRoot.toArray, timestamp, nBits, height, + Digest32 @@ extensionRoot.toArray, solution, votes.toArray, unparsedBytes.toArray, null) + + new CHeader(h) + } + + /** Size of of Header.votes array. */ + val VotesSize: Int = SigmaConstants.VotesArraySize.value + + /** Size of nonce array from Autolykos POW solution in Header.powNonce array. */ + val NonceSize: Int = SigmaConstants.AutolykosPowSolutionNonceArraySize.value + +} diff --git a/data/shared/src/main/scala/sigma/data/CSigmaDslBuilder.scala b/data/shared/src/main/scala/sigma/data/CSigmaDslBuilder.scala index 3938feacd3..21b9900028 100644 --- a/data/shared/src/main/scala/sigma/data/CSigmaDslBuilder.scala +++ b/data/shared/src/main/scala/sigma/data/CSigmaDslBuilder.scala @@ -5,13 +5,13 @@ import org.ergoplatform.ErgoBox import org.ergoplatform.validation.ValidationRules import scorex.crypto.hash.{Blake2b256, Sha256} import scorex.utils.Longs -import sigma.ast.{AtLeast, SubstConstants} +import sigma.ast.{AtLeast, SType, SubstConstants} import sigma.crypto.{CryptoConstants, EcPointType, Ecp} import sigma.eval.Extensions.EvalCollOps -import sigma.serialization.{GroupElementSerializer, SigmaSerializer} +import sigma.serialization.{DataSerializer, GroupElementSerializer, SigmaSerializer} import sigma.util.Extensions.BigIntegerOps import sigma.validation.SigmaValidationSettings -import sigma.{AvlTree, BigInt, Box, Coll, CollBuilder, GroupElement, SigmaDslBuilder, SigmaProp, VersionContext} +import sigma.{AvlTree, BigInt, Box, Coll, CollBuilder, Evaluation, GroupElement, SigmaDslBuilder, SigmaProp, VersionContext} import java.math.BigInteger @@ -200,6 +200,14 @@ class CSigmaDslBuilder extends SigmaDslBuilder { dsl => val p = GroupElementSerializer.parse(r) this.GroupElement(p) } + + /** Serializes the given `value` into bytes using the default serialization format. */ + override def serialize[T](value: T)(implicit cT: RType[T]): Coll[Byte] = { + val tpe = Evaluation.rtypeToSType(cT) + val w = SigmaSerializer.startWriter() + DataSerializer.serialize(value.asInstanceOf[SType#WrappedType], tpe, w) + Colls.fromArray(w.toBytes) + } } /** Default singleton instance of Global object, which implements global ErgoTree functions. */ diff --git a/data/shared/src/main/scala/sigma/data/ExactIntegral.scala b/data/shared/src/main/scala/sigma/data/ExactIntegral.scala index 34e2f47f63..86a9bfffce 100644 --- a/data/shared/src/main/scala/sigma/data/ExactIntegral.scala +++ b/data/shared/src/main/scala/sigma/data/ExactIntegral.scala @@ -1,5 +1,6 @@ package sigma.data +import sigma.{Coll, Colls} import sigma.util.Extensions.{ByteOps, ShortOps} /** Type-class which defines the operations on Integral types (Byte, Short, Int, Long, BigInt) @@ -37,6 +38,25 @@ object ExactIntegral { override def plus(x: Byte, y: Byte): Byte = x.addExact(y) override def minus(x: Byte, y: Byte): Byte = x.subtractExact(y) override def times(x: Byte, y: Byte): Byte = x.multiplyExact(y) + override def toBigEndianBytes(x: Byte): Coll[Byte] = Colls.fromItems(x) + override def bitwiseInverse(x: Byte): Byte = (~x).toByte + override def bitwiseOr(x: Byte, y: Byte): Byte = (x | y).toByte + override def bitwiseAnd(x: Byte, y: Byte): Byte = (x & y).toByte + override def bitwiseXor(x: Byte, y: Byte): Byte = (x ^ y).toByte + override def shiftLeft(x: Byte, bits: Int): Byte = { + if (bits < 0 || bits >= 8) { + throw new IllegalArgumentException(s"Wrong argument in Byte.shiftRight: bits < 0 || bits >= 8 ($bits)") + } else { + (x << bits).toByte + } + } + override def shiftRight(x: Byte, bits: Int): Byte = { + if (bits < 0 || bits >= 8) { + throw new IllegalArgumentException(s"Wrong argument in Byte.shiftRight: bits < 0 || bits >= 8 ($bits)") + } else { + (x >> bits).toByte + } + } } implicit object ShortIsExactIntegral extends ExactIntegral[Short] { @@ -44,6 +64,25 @@ object ExactIntegral { override def plus(x: Short, y: Short): Short = x.addExact(y) override def minus(x: Short, y: Short): Short = x.subtractExact(y) override def times(x: Short, y: Short): Short = x.multiplyExact(y) + override def toBigEndianBytes(x: Short): Coll[Byte] = Colls.fromItems((x >> 8).toByte, x.toByte) + override def bitwiseInverse(x: Short): Short = (~x).toShort + override def bitwiseOr(x: Short, y: Short): Short = (x | y).toShort + override def bitwiseAnd(x: Short, y: Short): Short = (x & y).toShort + override def bitwiseXor(x: Short, y: Short): Short = (x ^ y).toShort + override def shiftLeft(x: Short, bits: Int): Short = { + if (bits < 0 || bits >= 16) { + throw new IllegalArgumentException(s"Wrong argument in Short.shiftRight: bits < 0 || bits >= 16 ($bits)") + } else { + (x << bits).toShort + } + } + override def shiftRight(x: Short, bits: Int): Short = { + if (bits < 0 || bits >= 16){ + throw new IllegalArgumentException(s"Wrong argument in Short.shiftRight: bits < 0 || bits >= 16 ($bits)") + } else { + (x >> bits).toShort + } + } } implicit object IntIsExactIntegral extends ExactIntegral[Int] { @@ -51,6 +90,28 @@ object ExactIntegral { override def plus(x: Int, y: Int): Int = java7.compat.Math.addExact(x, y) override def minus(x: Int, y: Int): Int = java7.compat.Math.subtractExact(x, y) override def times(x: Int, y: Int): Int = java7.compat.Math.multiplyExact(x, y) + override def toBigEndianBytes(x: Int): Coll[Byte] = + Colls.fromItems((x >> 24).toByte, (x >> 16).toByte, (x >> 8).toByte, x.toByte) + override def bitwiseInverse(x: Int): Int = ~x + override def bitwiseOr(x: Int, y: Int): Int = x | y + override def bitwiseAnd(x: Int, y: Int): Int = x & y + override def bitwiseXor(x: Int, y: Int): Int = x ^ y + + override def shiftLeft(x: Int, bits: Int): Int = { + if (bits < 0 || bits >= 32) { + throw new IllegalArgumentException(s"Wrong argument in Byte.shiftRight: bits < 0 || bits >= 32 ($bits)") + } else { + x << bits + } + } + + override def shiftRight(x: Int, bits: Int): Int = { + if (bits < 0 || bits >= 32) { + throw new IllegalArgumentException(s"Wrong argument in Int.shiftRight: bits < 0 || bits >= 32 ($bits)") + } else { + x >> bits + } + } } implicit object LongIsExactIntegral extends ExactIntegral[Long] { @@ -58,5 +119,27 @@ object ExactIntegral { override def plus(x: Long, y: Long): Long = java7.compat.Math.addExact(x, y) override def minus(x: Long, y: Long): Long = java7.compat.Math.subtractExact(x, y) override def times(x: Long, y: Long): Long = java7.compat.Math.multiplyExact(x, y) + override def toBigEndianBytes(x: Long): Coll[Byte] = + Colls.fromItems((x >> 56).toByte, (x >> 48).toByte, (x >> 40).toByte, (x >> 32).toByte, (x >> 24).toByte, (x >> 16).toByte, (x >> 8).toByte, x.toByte) + override def bitwiseInverse(x: Long): Long = ~x + override def bitwiseOr(x: Long, y: Long): Long = x | y + override def bitwiseAnd(x: Long, y: Long): Long = x & y + override def bitwiseXor(x: Long, y: Long): Long = x ^ y + + override def shiftLeft(x: Long, bits: Int): Long = { + if (bits < 0 || bits >= 64) { + throw new IllegalArgumentException(s"Wrong argument in Long.shiftRight: bits < 0 || bits >= 64 ($bits)") + } else { + x << bits + } + } + + override def shiftRight(x: Long, bits: Int): Long = { + if (bits < 0 || bits >= 64) { + throw new IllegalArgumentException(s"Wrong argument in Long.shiftRight: bits < 0 || bits >= 64 ($bits)") + } else { + x >> bits + } + } } } diff --git a/data/shared/src/main/scala/sigma/data/ExactNumeric.scala b/data/shared/src/main/scala/sigma/data/ExactNumeric.scala index 2e9b799a61..244cc6b7b7 100644 --- a/data/shared/src/main/scala/sigma/data/ExactNumeric.scala +++ b/data/shared/src/main/scala/sigma/data/ExactNumeric.scala @@ -1,5 +1,7 @@ package sigma.data +import debox.cfor +import sigma.{Coll, Colls} import sigma.data.ExactIntegral._ /** Numeric operations with overflow checks. @@ -30,6 +32,63 @@ trait ExactNumeric[T] { def toInt(x: T): Int = n.toInt(x) def toLong(x: T): Long = n.toLong(x) + /** Returns a big-endian representation of this value in a collection of bytes. + * For example, the `Int` value `0x12131415` would yield the + * collection of bytes [0x12, 0x13, 0x14, 0x15] + */ + def toBigEndianBytes(x: T): Coll[Byte] + + /** + * Returns a big-endian binary representation of this value as boolean array. + */ + def toBits(x: T): Coll[Boolean] = { + + def isBitSet(byte: Byte)(bit: Int): Boolean = ((byte >> bit) & 1) == 1 + + val bytes = toBigEndianBytes(x) + val l = bytes.length + val res = new Array[Boolean](l * 8) + cfor(0)(_ < l, _ + 1) { i => + val b = bytes(i) + cfor(0)(_ < 8, _ + 1) { bitIdx => + res(i * 8 + (7 - bitIdx)) = isBitSet(b)(bitIdx) + } + } + Colls.fromArray(res) + } + + /** + * @return a numeric value which is inverse of `x` (every bit, including sign, is flipped) + */ + def bitwiseInverse(x: T): T + + /** + * @return a numeric value which is `this | that` + */ + def bitwiseOr(x: T, y: T): T + + /** + * @return a numeric value which is `this && that` + */ + def bitwiseAnd(x: T, y: T): T + + /** + * @return a numeric value which is `this xor that` + */ + def bitwiseXor(x: T, y: T): T + + /** + * @return a value which is (this << n). The shift distance, n, may be negative, + * in which case this method performs a right shift. (Computes floor(this * 2n).) + */ + def shiftLeft(x: T, bits: Int): T + + /** + * @return a value which is (this >> n). Sign extension is performed. The shift distance, n, + * may be negative, in which case this method performs a left shift. (Computes floor(this / 2n).) + */ + def shiftRight(x: T, bits: Int): T + /** A value of type T which corresponds to integer 0. */ lazy val zero: T = fromInt(0) diff --git a/data/shared/src/main/scala/sigma/eval/ErgoTreeEvaluator.scala b/data/shared/src/main/scala/sigma/eval/ErgoTreeEvaluator.scala index 52f839354c..6df82125df 100644 --- a/data/shared/src/main/scala/sigma/eval/ErgoTreeEvaluator.scala +++ b/data/shared/src/main/scala/sigma/eval/ErgoTreeEvaluator.scala @@ -1,6 +1,6 @@ package sigma.eval -import sigma.{AvlTree, Coll, Context} +import sigma.{AvlTree, Coll, Context, Header} import sigma.ast.{Constant, FixedCost, MethodCall, OperationCostInfo, OperationDesc, PerItemCost, SType, TypeBasedCost} import sigma.data.KeyValueColl diff --git a/data/shared/src/main/scala/sigma/pow/Autolykos2PowValidation.scala b/data/shared/src/main/scala/sigma/pow/Autolykos2PowValidation.scala new file mode 100644 index 0000000000..23cf722194 --- /dev/null +++ b/data/shared/src/main/scala/sigma/pow/Autolykos2PowValidation.scala @@ -0,0 +1,176 @@ +package sigma.pow + + +import scorex.crypto.hash.Blake2b256 +import scorex.utils.{Bytes, Ints, Longs} +import sigma.Header +import sigma.crypto.{BcDlogGroup, BigIntegers, CryptoConstants} +import sigma.util.NBitsUtils + +/** + * Functions used to validate Autolykos2 Proof-of-Work. + */ +object Autolykos2PowValidation { + + type Height = Int + + /** + * k value for k-sum problem Autolykos is based on (find k numbers in table on N size) + */ + private val k = 32 + + /** + * Initial size of N value for k-sum problem Autolykos is based on (find k numbers in table on N size). + * It grows from it since predefined block height in Autolykos 2. + */ + private val NStart = 26 + + /** + * Group order, used in Autolykos V.1 for non-outsourceability, + * and also to obtain target in both Autolykos v1 and v2 + */ + private val q: BigInt = CryptoConstants.dlogGroup.order + + /** + * Number of elements in a table to find k-sum problem solution on top of + */ + val NBase: Int = Math.pow(2, NStart.toDouble).toInt + + /** + * Initial height since which table (`N` value) starting to increase by 5% per `IncreasePeriodForN` blocks + */ + val IncreaseStart: Height = 600 * 1024 + + /** + * Table size (`N`) increased every 50 * 1024 blocks + */ + val IncreasePeriodForN: Height = 50 * 1024 + + /** + * On this height, the table (`N` value) will stop to grow. + * Max N on and after this height would be 2,143,944,600 which is still less than 2^^31. + */ + val NIncreasementHeightMax: Height = 4198400 + + /** + * Blake2b256 hash function invocation + * @param in - input bit-string + * @return - 256 bits (32 bytes) array + */ + def hash(in: Array[Byte]): Array[Byte] = Blake2b256.hash(in) + + /** + * Convert byte array to unsigned integer + * @param in - byte array + * @return - unsigned integer + */ + def toBigInt(in: Array[Byte]): BigInt = BigInt(BigIntegers.fromUnsignedByteArray(in)) + + /** + * Constant data to be added to hash function to increase its calculation time + */ + val M: Array[Byte] = (0 until 1024).toArray.flatMap(i => Longs.toByteArray(i.toLong)) + + /** + * Calculates table size (N value) for a given height (moment of time) + * + * @see papers/yellow/pow/ErgoPow.tex for full description and test vectors + * @param headerHeight - height of a header to mine + * @return - N value + */ + def calcN(headerHeight: Height): Int = { + val height = Math.min(NIncreasementHeightMax, headerHeight) + if (height < IncreaseStart) { + NBase + } else { + val itersNumber = (height - IncreaseStart) / IncreasePeriodForN + 1 + (1 to itersNumber).foldLeft(NBase) { case (step, _) => + step / 100 * 105 + } + } + } + + def calcN(header: Header): Int = calcN(header.height) + + /** + * Hash function that takes `m` and `nonceBytes` and returns a list of size `k` with numbers in + * [0,`N`) + */ + private def genIndexes(k: Int, seed: Array[Byte], N: Int): Seq[Int] = { + val hash = Blake2b256(seed) + val extendedHash = Bytes.concat(hash, hash.take(3)) + (0 until k).map { i => + BigInt(1, extendedHash.slice(i, i + 4)).mod(N).toInt + } + }.ensuring(_.length == k) + + /** + * Generate element of Autolykos equation. + */ + private def genElementV2(indexBytes: Array[Byte], heightBytes: => Array[Byte]): BigInt = { + // Autolykos v. 2: H(j|h|M) (line 5 from the Algo 2 of the spec) + toBigInt(hash(Bytes.concat(indexBytes, heightBytes, M)).drop(1)) + } + + def hitForVersion2ForMessage(k: Int, msg: Array[Byte], nonce: Array[Byte], h: Array[Byte], N: Int): BigInt = { + + val prei8 = BigIntegers.fromUnsignedByteArray(hash(Bytes.concat(msg, nonce)).takeRight(8)) + val i = BigIntegers.asUnsignedByteArray(4, prei8.mod(BigInt(N).underlying())) + val f = Blake2b256(Bytes.concat(i, h, M)).drop(1) // .drop(1) is the same as takeRight(31) + val seed = Bytes.concat(f, msg, nonce) // Autolykos v1, Alg. 2, line4: + + val indexes = genIndexes(k, seed, N) + //pk and w not used in v2 + val elems = indexes.map(idx => genElementV2(Ints.toByteArray(idx), h)) + val f2 = elems.sum + + // sum as byte array is always about 32 bytes + val array: Array[Byte] = BigIntegers.asUnsignedByteArray(32, f2.underlying()) + val ha = hash(array) + toBigInt(ha) + } + + /** + * Header digest ("message" for default GPU miners) a miner is working on + */ + def msgByHeader(h: Header): Array[Byte] = Blake2b256(h.serializeWithoutPoW.toArray) + + /** + * Get hit for Autolykos v2 header (to test it then against PoW target) + * + * @param header - header to check PoW for + * @return PoW hit + */ + def hitForVersion2(header: Header): BigInt = { + + val msg = msgByHeader(header) + val nonce = header.powNonce + + val h = Ints.toByteArray(header.height) // used in AL v.2 only + + val N = calcN(header) + + hitForVersion2ForMessage(k, msg, nonce.toArray, h, N) + } + + /** + * Get target `b` from encoded difficulty `nBits` + */ + def getB(nBits: Long): BigInt = { + q / NBitsUtils.decodeCompactBits(nBits) + } + + /** + * Check PoW for Autolykos v2 header + * + * @param header - header to check PoW for + * @return whether PoW is valid or not + */ + def checkPoWForVersion2(header: Header): Boolean = { + val b = getB(header.nBits) + // for version 2, we're calculating hit and compare it with target + val hit = hitForVersion2(header) + hit < b + } + +} diff --git a/data/shared/src/main/scala/sigma/serialization/DataSerializer.scala b/data/shared/src/main/scala/sigma/serialization/DataSerializer.scala index 5f554e96a1..92a54f9aa4 100644 --- a/data/shared/src/main/scala/sigma/serialization/DataSerializer.scala +++ b/data/shared/src/main/scala/sigma/serialization/DataSerializer.scala @@ -1,8 +1,9 @@ package sigma.serialization -import org.ergoplatform.ErgoBox +import org.ergoplatform.{ErgoBox, ErgoHeader} +import sigma.VersionContext import sigma.ast._ -import sigma.data.CBox +import sigma.data.{CBox, CHeader} /** This works in tandem with ConstantSerializer, if you change one make sure to check the other.*/ object DataSerializer extends CoreDataSerializer { @@ -15,6 +16,9 @@ object DataSerializer extends CoreDataSerializer { case SBox => val b = v.asInstanceOf[CBox] ErgoBox.sigmaSerializer.serialize(b.ebox, w.asInstanceOf[SigmaByteWriter]) + case SHeader if VersionContext.current.isV6SoftForkActivated => + val h = v.asInstanceOf[CHeader] + ErgoHeader.sigmaSerializer.serialize(h.ergoHeader, w.asInstanceOf[SigmaByteWriter]) case _ => super.serialize(v, tpe, w) } @@ -32,6 +36,12 @@ object DataSerializer extends CoreDataSerializer { val res = CBox(ErgoBox.sigmaSerializer.parse(r.asInstanceOf[SigmaByteReader])) r.level = r.level - 1 res + case SHeader if VersionContext.current.isV6SoftForkActivated => + val depth = r.level + r.level = depth + 1 + val res = new CHeader(ErgoHeader.sigmaSerializer.parse(r.asInstanceOf[SigmaByteReader])) + r.level = r.level - 1 + res case t => super.deserialize(t, r) }).asInstanceOf[T#WrappedType] diff --git a/data/shared/src/main/scala/sigma/serialization/ErgoTreeSerializer.scala b/data/shared/src/main/scala/sigma/serialization/ErgoTreeSerializer.scala index e7bb46429a..5122ee940c 100644 --- a/data/shared/src/main/scala/sigma/serialization/ErgoTreeSerializer.scala +++ b/data/shared/src/main/scala/sigma/serialization/ErgoTreeSerializer.scala @@ -373,7 +373,7 @@ class ErgoTreeSerializer { val newVal = newVals(positions.indexOf(i)) // we need to get newVal's serialized constant value (see ProveDlogSerializer for example) val constantStore = new ConstantStore() - val valW = SigmaSerializer.startWriter(constantStore) + val valW = SigmaSerializer.startWriter(Some(constantStore)) valW.putValue(newVal) val newConsts = constantStore.getAll require(newConsts.length == 1) diff --git a/data/shared/src/main/scala/sigma/serialization/SigmaByteWriter.scala b/data/shared/src/main/scala/sigma/serialization/SigmaByteWriter.scala index 35d5e0c9b9..db9312240f 100644 --- a/data/shared/src/main/scala/sigma/serialization/SigmaByteWriter.scala +++ b/data/shared/src/main/scala/sigma/serialization/SigmaByteWriter.scala @@ -4,15 +4,52 @@ import scorex.util.serialization.Writer import sigma.ast.syntax._ import sigma.ast._ import sigma.serialization.CoreByteWriter.{ArgInfo, DataInfo, FormatDescriptor, SeqFmt} +import SigmaByteWriter._ -class SigmaByteWriter(override val w: Writer, - val constantExtractionStore: Option[ConstantStore]) - extends CoreByteWriter(w) { +/** Implementation of [[Writer]] provided by `sigma-data` module. + * + * @param w destination [[Writer]] to which all the call got delegated. + * @param constantExtractionStore optional store to segregate constants to while + * replacing them with placeholders. + * @param addFixedCostCallbackOpt optional callback to accumulate fixed costs. + * @param addPerItemCostCallbackOpt optional callback to accumulate per-item costs. + */ +class SigmaByteWriter( + override val w: Writer, + val constantExtractionStore: Option[ConstantStore], + val addFixedCostCallbackOpt: Option[FixedCostCallback], + val addPerItemCostCallbackOpt: Option[PerItemCostCallback] +) extends CoreByteWriter(w) { import CoreByteWriter._ import ValueSerializer._ + /** Adds the given cost to the callback if it is defined. */ + @inline private def addFixedCost(cost: OperationCostInfo[FixedCost]): Unit = { + if (addFixedCostCallbackOpt.isDefined) + addFixedCostCallbackOpt.get(cost) + } + + /** Adds the given cost to the callback if it is defined. */ + @inline private def addPerItemCost(cost: OperationCostInfo[PerItemCost], nItems: Int): Unit = { + if (addPerItemCostCallbackOpt.isDefined) + addPerItemCostCallbackOpt.get(cost, nItems) + } + + override def putChunk(chunk: w.CH): SigmaByteWriter.this.type = { + val start = length() + super.putChunk(chunk) + addPerItemCost(PutChunkCost, length() - start) + this + } + + override def put(x: Byte): this.type = { + addFixedCost(PutByteCost) + super.put(x) + } + override def put(x: Byte, info: DataInfo[Byte]): this.type = { ValueSerializer.addArgInfo(info) + addFixedCost(PutByteCost) w.put(x); this } @@ -21,68 +58,160 @@ class SigmaByteWriter(override val w: Writer, super.putUByte(x) } - @inline override def putBoolean(x: Boolean, info: DataInfo[Boolean]): this.type = { + override def putBoolean(x: Boolean): this.type = { + addFixedCost(PutByteCost) + super.putBoolean(x) + } + + override def putBoolean(x: Boolean, info: DataInfo[Boolean]): this.type = { ValueSerializer.addArgInfo(info) + addFixedCost(PutByteCost) w.putBoolean(x); this } - @inline override def putShort(x: Short, info: DataInfo[Short]): this.type = { + override def putShort(x: Short): this.type = { + addFixedCost(PutSignedNumericCost) + super.putShort(x) + } + + override def putShort(x: Short, info: DataInfo[Short]): this.type = { ValueSerializer.addArgInfo(info) + addFixedCost(PutSignedNumericCost) w.putShort(x); this } - @inline override def putUShort(x: Int, info: DataInfo[Vlq[U[Short]]]): this.type = { + override def putUShort(x: Int): this.type = { + addFixedCost(PutUnsignedNumericCost) + super.putUShort(x) + } + + override def putUShort(x: Int, info: DataInfo[Vlq[U[Short]]]): this.type = { ValueSerializer.addArgInfo(info) + addFixedCost(PutUnsignedNumericCost) w.putUShort(x); this } - @inline override def putInt(x: Int, info: DataInfo[Int]): this.type = { + override def putInt(x: Int): this.type = { + addFixedCost(PutSignedNumericCost) + super.putInt(x) + } + + override def putInt(x: Int, info: DataInfo[Int]): this.type = { ValueSerializer.addArgInfo(info) + addFixedCost(PutSignedNumericCost) w.putInt(x); this } - @inline override def putUInt(x: Long, info: DataInfo[Vlq[U[Int]]]): this.type = { + override def putUInt(x: Long): SigmaByteWriter.this.type = { + super.putUInt(x) + } + + override def putUInt(x: Long, info: DataInfo[Vlq[U[Int]]]): this.type = { ValueSerializer.addArgInfo(info) + addFixedCost(PutUnsignedNumericCost) w.putUInt(x); this } - @inline override def putLong(x: Long, info: DataInfo[Vlq[ZigZag[Long]]]): this.type = { + override def putLong(x: Long): SigmaByteWriter.this.type = { + addFixedCost(PutSignedNumericCost) + super.putLong(x) + } + + override def putLong(x: Long, info: DataInfo[Vlq[ZigZag[Long]]]): this.type = { ValueSerializer.addArgInfo(info) + addFixedCost(PutSignedNumericCost) w.putLong(x); this } - @inline override def putULong(x: Long, info: DataInfo[Vlq[U[Long]]]): this.type = { + override def putULong(x: Long): SigmaByteWriter.this.type = { + addFixedCost(PutUnsignedNumericCost) + super.putULong(x) + } + + override def putULong(x: Long, info: DataInfo[Vlq[U[Long]]]): this.type = { ValueSerializer.addArgInfo(info) + addFixedCost(PutUnsignedNumericCost) w.putULong(x); this } - @inline override def putBytes(xs: Array[Byte], info: DataInfo[Array[Byte]]): this.type = { + override def putBytes(xs: Array[Byte], offset: Int, length: Int): this.type = { + addPerItemCost(PutChunkCost, length) + super.putBytes(xs, offset, length) + } + + override def putBytes(xs: Array[Byte]): SigmaByteWriter.this.type = { + addPerItemCost(PutChunkCost, xs.length) + super.putBytes(xs) + } + + override def putBytes(xs: Array[Byte], info: DataInfo[Array[Byte]]): this.type = { ValueSerializer.addArgInfo(info) + addPerItemCost(PutChunkCost, xs.length) w.putBytes(xs); this } - @inline override def putBits(xs: Array[Boolean], info: DataInfo[Bits]): this.type = { + /** Put the two bytes of the big-endian representation of the Short value into the + * writer. */ + override def putShortBytes(value: Short): SigmaByteWriter.this.type = { + addPerItemCost(PutChunkCost, 2) + super.putShortBytes(value) + } + + override def putBits(xs: Array[Boolean]): SigmaByteWriter.this.type = { + addPerItemCost(PutChunkCost, xs.length) // number of bits + super.putBits(xs) + } + + override def putBits(xs: Array[Boolean], info: DataInfo[Bits]): this.type = { ValueSerializer.addArgInfo(info) - w.putBits(xs); - this + addPerItemCost(PutChunkCost, xs.length) // number of bits + w.putBits(xs); this } - @inline override def putType[T <: SType](x: T, info: DataInfo[SType]): this.type = { + override def putOption[T](x: Option[T])(putValueC: (this.type, T) => Unit): this.type = { + addFixedCost(PutByteCost) // cost of option tag byte + super.putOption(x)(putValueC) + } + + override def putShortString(s: String): SigmaByteWriter.this.type = { + addPerItemCost(PutChunkCost, s.length) + super.putShortString(s) + } + + override def putType[T <: SType](x: T, info: DataInfo[SType]): this.type = { ValueSerializer.addArgInfo(info) - TypeSerializer.serialize(x, this); this + TypeSerializer.serialize(x, this); // the cost is added in TypeSerializer + this } - @inline def putValue[T <: SType](x: Value[T]): this.type = { ValueSerializer.serialize(x, this); this } - @inline def putValue[T <: SType](x: Value[T], info: DataInfo[SValue]): this.type = { + /** Serializes the given expression using [[ValueSerializer]]. */ + def putValue[T <: SType](x: Value[T]): this.type = { + ValueSerializer.serialize(x, this) // the cost is added in ValueSerializer + this + } + + /** Serializes the given expression using [[ValueSerializer]]. + * @param x the ErgoTree expression to serialize + * @param info meta information about the data being serialized + */ + def putValue[T <: SType](x: Value[T], info: DataInfo[SValue]): this.type = { ValueSerializer.addArgInfo(info) - ValueSerializer.serialize(x, this); this + ValueSerializer.serialize(x, this); // the cost is added in ValueSerializer + this } - @inline def putValues[T <: SType](xs: Seq[Value[T]]): this.type = { + + /** Serializes the given sequence of expressions using [[ValueSerializer]]. */ + def putValues[T <: SType](xs: Seq[Value[T]]): this.type = { putUInt(xs.length) xs.foreach(putValue(_)) this } - @inline def putValues[T <: SType](xs: Seq[Value[T]], info: DataInfo[Seq[SValue]], itemInfo: DataInfo[SValue]): this.type = { + + /** Serializes the given sequence of expressions using [[ValueSerializer]]. + * @param xs the sequence of ErgoTree expressions to serialize + * @param info additional information about the data being serialized + */ + def putValues[T <: SType](xs: Seq[Value[T]], info: DataInfo[Seq[SValue]], itemInfo: DataInfo[SValue]): this.type = { putUInt(xs.length, valuesLengthInfo) foreach("\\#items", xs) { x => putValue(x, itemInfo) @@ -92,6 +221,46 @@ class SigmaByteWriter(override val w: Writer, } object SigmaByteWriter { + + /** Callback to accumulate fixed costs. */ + type FixedCostCallback = OperationCostInfo[FixedCost] => Unit + + /** Callback to accumulate per-item costs (chunked cost). */ + type PerItemCostCallback = (OperationCostInfo[PerItemCost], Int) => Unit + + /** Cost of instantiating a new serializer. + * This also include overhead of method calls. + * This is the minimal possible JitCost value + */ + val StartWriterCost = OperationCostInfo(FixedCost(JitCost(10)), NamedDesc("SigmaByteWriter.startWriter")) + + /** Cost of writing single byte without any encoding. + * This also include overhead of method calls. + * This is the minimal possible JitCost value + */ + val PutByteCost = OperationCostInfo(FixedCost(JitCost(1)), NamedDesc("SigmaByteWriter.put")) + + /** Cost of writing a signed numeric including: + * 1) allocation of VLQ buffer array (see putULong in [[scorex.util.serialization.VLQWriter]]) + * 2) VLQ encoding + * 3) overhead of method calls. + */ + val PutUnsignedNumericCost = OperationCostInfo(FixedCost(JitCost(3)), NamedDesc("SigmaByteWriter.putUNumeric")) + + /** Cost of writing a signed numeric including: + * 1) ZigZag encoding. + * 2) allocation of VLQ buffer array (see putULong in [[scorex.util.serialization.VLQWriter]]) + * 3) VLQ encoding + * 4) overhead of method calls. + */ + val PutSignedNumericCost = OperationCostInfo(FixedCost(JitCost(3)), NamedDesc("SigmaByteWriter.putNumeric")) + + /** Cost of writing a chunk of bytes: + * 1) method call overhead + * 2) 1 cost unit per byte + */ + val PutChunkCost = OperationCostInfo(PerItemCost(JitCost(3), JitCost(1), 1), NamedDesc("SigmaByteWriter.putChunk")) + implicit case object ValueFmt extends FormatDescriptor[SValue] { override def size: String = "[1, *]" override def toString: String = "Expr" diff --git a/data/shared/src/main/scala/sigma/serialization/SigmaSerializer.scala b/data/shared/src/main/scala/sigma/serialization/SigmaSerializer.scala index 3765adb029..7da7ec1606 100644 --- a/data/shared/src/main/scala/sigma/serialization/SigmaSerializer.scala +++ b/data/shared/src/main/scala/sigma/serialization/SigmaSerializer.scala @@ -4,6 +4,7 @@ import java.nio.ByteBuffer import scorex.util.ByteArrayBuilder import scorex.util.serialization._ import sigma.data.SigmaConstants +import sigma.serialization.SigmaByteWriter.{FixedCostCallback, PerItemCostCallback} import sigma.serialization.ValueCodes.OpCode object SigmaSerializer { @@ -51,14 +52,18 @@ object SigmaSerializer { def startWriter(): SigmaByteWriter = { val b = new ByteArrayBuilder() val wi = new VLQByteBufferWriter(b) - val w = new SigmaByteWriter(wi, constantExtractionStore = None) + val w = new SigmaByteWriter(wi, constantExtractionStore = None, addFixedCostCallbackOpt = None, addPerItemCostCallbackOpt = None) w } - def startWriter(constantExtractionStore: ConstantStore): SigmaByteWriter = { + def startWriter( + constantExtractionStore: Option[ConstantStore], + addFixedCostCallback: Option[FixedCostCallback] = None, + addPerItemCostCallback: Option[PerItemCostCallback] = None + ): SigmaByteWriter = { val b = new ByteArrayBuilder() val wi = new VLQByteBufferWriter(b) - val w = new SigmaByteWriter(wi, constantExtractionStore = Some(constantExtractionStore)) + val w = new SigmaByteWriter(wi, constantExtractionStore = constantExtractionStore, addFixedCostCallback, addPerItemCostCallback) w } } diff --git a/docs/LangSpec.md b/docs/LangSpec.md index ba66748f08..5b72a8659d 100644 --- a/docs/LangSpec.md +++ b/docs/LangSpec.md @@ -68,7 +68,7 @@ The following sections describe ErgoScript and its operations. #### Operations and constructs overview - Binary operations: `>, <, >=, <=, +, -, &&, ||, ==, !=, |, &, *, /, %, ^, ++` -- predefined primitives: `blake2b256`, `byteArrayToBigInt`, `proveDlog` etc. +- predefined primitives: `serialize`, `blake2b256`, `byteArrayToBigInt`, `proveDlog` etc. - val declarations: `val h = blake2b256(pubkey)` - if-then-else clause: `if (x > 0) 1 else 0` - collection literals: `Coll(1, 2, 3, 4)` @@ -249,6 +249,10 @@ class Context { /** Represents data of the block headers available in scripts. */ class Header { + + /** Validate header's proof-of-work */ + def checkPow: Boolean + /** Bytes representation of ModifierId of this Header */ def id: Coll[Byte] diff --git a/interpreter/shared/src/main/scala/sigmastate/eval/CHeader.scala b/interpreter/shared/src/main/scala/sigmastate/eval/CHeader.scala deleted file mode 100644 index 7062fa0f0e..0000000000 --- a/interpreter/shared/src/main/scala/sigmastate/eval/CHeader.scala +++ /dev/null @@ -1,34 +0,0 @@ -package sigmastate.eval - -import sigma.data.SigmaConstants -import sigma.{AvlTree, BigInt, Coll, GroupElement, Header} - -/** A default implementation of [[Header]] interface. - * - * @see [[Header]] for detailed descriptions - */ -case class CHeader( - id: Coll[Byte], - version: Byte, - parentId: Coll[Byte], - ADProofsRoot: Coll[Byte], - stateRoot: AvlTree, - transactionsRoot: Coll[Byte], - timestamp: Long, - nBits: Long, - height: Int, - extensionRoot: Coll[Byte], - minerPk: GroupElement, - powOnetimePk: GroupElement, - powNonce: Coll[Byte], - powDistance: BigInt, - votes: Coll[Byte] -) extends Header - -object CHeader { - /** Size of of Header.votes array. */ - val VotesSize: Int = SigmaConstants.VotesArraySize.value - - /** Size of nonce array from Autolykos POW solution in Header.powNonce array. */ - val NonceSize: Int = SigmaConstants.AutolykosPowSolutionNonceArraySize.value -} \ No newline at end of file diff --git a/interpreter/shared/src/main/scala/sigmastate/interpreter/CErgoTreeEvaluator.scala b/interpreter/shared/src/main/scala/sigmastate/interpreter/CErgoTreeEvaluator.scala index de8aa6b620..a72510f641 100644 --- a/interpreter/shared/src/main/scala/sigmastate/interpreter/CErgoTreeEvaluator.scala +++ b/interpreter/shared/src/main/scala/sigmastate/interpreter/CErgoTreeEvaluator.scala @@ -5,15 +5,17 @@ import sigma.ast._ import sigma.ast.syntax._ import sigmastate.eval.{CAvlTreeVerifier, CProfiler} import sigmastate.interpreter.Interpreter.ReductionResult -import sigma.{AvlTree, Coll, Colls, Context, VersionContext} +import sigma.{AvlTree, Coll, Colls, Context, Header, VersionContext} import sigma.util.Extensions._ import debox.{cfor, Buffer => DBuffer} import scorex.crypto.authds.ADKey import sigma.ast.SAvlTreeMethods._ +import sigma.ast.SHeaderMethods.checkPowMethod import sigma.ast.SType import sigma.data.{CSigmaProp, KeyValueColl, SigmaBoolean} import sigma.eval.{AvlTreeVerifier, ErgoTreeEvaluator, EvalSettings, Profiler} import sigma.eval.ErgoTreeEvaluator.DataEnv +import sigmastate.interpreter.CErgoTreeEvaluator.fixedCostOp import scala.collection.compat.immutable.ArraySeq import scala.util.{DynamicVariable, Failure, Success} @@ -449,7 +451,7 @@ object CErgoTreeEvaluator { * HOTSPOT: don't beautify the code * Note, `null` is used instead of Option to avoid allocations. */ - def fixedCostOp[R <: AnyRef](costInfo: OperationCostInfo[FixedCost]) + def fixedCostOp[R](costInfo: OperationCostInfo[FixedCost]) (block: => R)(implicit E: ErgoTreeEvaluator): R = { if (E != null) { var res: R = null.asInstanceOf[R] diff --git a/interpreter/shared/src/main/scala/sigmastate/utils/Extensions.scala b/interpreter/shared/src/main/scala/sigmastate/utils/Extensions.scala index 54abc40f4e..2e66eb6b19 100644 --- a/interpreter/shared/src/main/scala/sigmastate/utils/Extensions.scala +++ b/interpreter/shared/src/main/scala/sigmastate/utils/Extensions.scala @@ -13,7 +13,7 @@ object Extensions { * For example, the Byte value {@code 0x12} would yield the * byte array {@code {0x12}}. */ - def toBytes: Coll[Byte] = SigmaDsl.Colls.fromItems(b) + def toBigEndianBytes: Coll[Byte] = SigmaDsl.Colls.fromItems(b) } @@ -22,7 +22,7 @@ object Extensions { * For example, the Short value {@code 0x1213} would yield the * byte array {@code {0x12, 0x13}}. */ - def toBytes: Coll[Byte] = Colls.fromArray(Shorts.toByteArray(x)) + def toBigEndianBytes: Coll[Byte] = Colls.fromArray(Shorts.toByteArray(x)) } implicit class IntOpsForSigma(val x: Int) extends AnyVal { @@ -30,7 +30,7 @@ object Extensions { * For example, the Int value {@code 0x12131415} would yield the * byte array {@code {0x12, 0x13, 0x14, 0x15}}. */ - def toBytes: Coll[Byte] = Colls.fromArray(Ints.toByteArray(x)) + def toBigEndianBytes: Coll[Byte] = Colls.fromArray(Ints.toByteArray(x)) } implicit class LongOpsForSigma(val x: Long) extends AnyVal { @@ -38,7 +38,7 @@ object Extensions { * For example, the Long value {@code 0x1213141516171819} would yield the * byte array {@code {0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19}}. */ - def toBytes: Coll[Byte] = Colls.fromArray(Longs.toByteArray(x)) + def toBigEndianBytes: Coll[Byte] = Colls.fromArray(Longs.toByteArray(x)) } /** Provides extension methods for `ModifierId` instances. diff --git a/interpreter/shared/src/test/scala/sigma/serialization/ConstantSerializerSpecification.scala b/interpreter/shared/src/test/scala/sigma/serialization/ConstantSerializerSpecification.scala index 43e9cf9e5d..c9478c8356 100644 --- a/interpreter/shared/src/test/scala/sigma/serialization/ConstantSerializerSpecification.scala +++ b/interpreter/shared/src/test/scala/sigma/serialization/ConstantSerializerSpecification.scala @@ -9,7 +9,7 @@ import sigma.ast.{BigIntConstant, ByteArrayConstant, Constant, DeserializationSi import sigmastate.eval._ import sigma.Extensions.ArrayOps import sigma.ast._ -import sigma.{AvlTree, Colls, Evaluation} +import sigma.{AvlTree, Colls, Evaluation, Header, VersionContext} import sigma.ast.SType.AnyOps import scorex.util.encode.Base16 import sigma.ast.BoolArrayConstant.BoolArrayTypeCode @@ -17,6 +17,7 @@ import sigma.ast.ByteArrayConstant.ByteArrayTypeCode import sigma.ast.syntax.{BoolValue, SValue} import sigma.crypto.EcPointType import sigma.util.Extensions.{BigIntegerOps, EcpOps, SigmaBooleanOps} + import scala.annotation.nowarn class ConstantSerializerSpecification extends TableSerializationSpecification { @@ -25,22 +26,29 @@ class ConstantSerializerSpecification extends TableSerializationSpecification { implicit val wWrapped = wrappedTypeGen(tpe) implicit val tT = Evaluation.stypeToRType(tpe) implicit val tag = tT.classTag + + val withVersion = if (tpe == SHeader) { + Some(VersionContext.V6SoftForkVersion) + } else { + None + } + forAll { xs: Array[T#WrappedType] => implicit val tAny = sigma.AnyType - roundTripTest(Constant[SCollection[T]](xs.toColl, SCollection(tpe))) - roundTripTest(Constant[SType](xs.toColl.map(x => (x, x)).asWrappedType, SCollection(STuple(tpe, tpe)))) // pairs are special case + roundTripTest(Constant[SCollection[T]](xs.toColl, SCollection(tpe)), withVersion) + roundTripTest(Constant[SType](xs.toColl.map(x => (x, x)).asWrappedType, SCollection(STuple(tpe, tpe))), withVersion) // pairs are special case val triples = xs.toColl.map(x => TupleColl(x, x, x)).asWrappedType - roundTripTest(Constant[SType](triples, SCollection(STuple(tpe, tpe, tpe)))) + roundTripTest(Constant[SType](triples, SCollection(STuple(tpe, tpe, tpe))), withVersion) val quartets = xs.toColl.map(x => TupleColl(x, x, x, x)).asWrappedType - roundTripTest(Constant[SType](quartets, SCollection(STuple(tpe, tpe, tpe, tpe)))) - roundTripTest(Constant[SCollection[SCollection[T]]](xs.toColl.map(x => Colls.fromItems(x, x)), SCollection(SCollection(tpe)))) + roundTripTest(Constant[SType](quartets, SCollection(STuple(tpe, tpe, tpe, tpe))), withVersion) + roundTripTest(Constant[SCollection[SCollection[T]]](xs.toColl.map(x => Colls.fromItems(x, x)), SCollection(SCollection(tpe))), withVersion) roundTripTest(Constant[SType]( xs.toColl.map { x => val arr = Colls.fromItems(x, x) (arr, arr) }.asWrappedType, SCollection(STuple(SCollection(tpe), SCollection(tpe))) - )) + ), withVersion) } } @@ -49,14 +57,19 @@ class ConstantSerializerSpecification extends TableSerializationSpecification { implicit val tT = Evaluation.stypeToRType(tpe) @nowarn implicit val tag = tT.classTag implicit val tAny: RType[Any] = sigma.AnyType + val withVersion = if (tpe == SHeader) { + Some(VersionContext.V6SoftForkVersion) + } else { + None + } forAll { in: (T#WrappedType, T#WrappedType) => val (x,y) = (in._1, in._2) - roundTripTest(Constant[SType]((x, y).asWrappedType, STuple(tpe, tpe))) - roundTripTest(Constant[SType](TupleColl(x, y, x).asWrappedType, STuple(tpe, tpe, tpe))) - roundTripTest(Constant[SType](TupleColl(x, y, x, y).asWrappedType, STuple(tpe, tpe, tpe, tpe))) - roundTripTest(Constant[STuple](Colls.fromItems[Any](x, y, (x, y)), STuple(tpe, tpe, STuple(tpe, tpe)))) - roundTripTest(Constant[STuple](Colls.fromItems[Any](x, y, TupleColl(x, y, x)), STuple(tpe, tpe, STuple(tpe, tpe, tpe)))) - roundTripTest(Constant[STuple](Colls.fromItems[Any](x, y, TupleColl(x, y, (x, y))), STuple(tpe, tpe, STuple(tpe, tpe, STuple(tpe, tpe))))) + roundTripTest(Constant[SType]((x, y).asWrappedType, STuple(tpe, tpe)), withVersion) + roundTripTest(Constant[SType](TupleColl(x, y, x).asWrappedType, STuple(tpe, tpe, tpe)), withVersion) + roundTripTest(Constant[SType](TupleColl(x, y, x, y).asWrappedType, STuple(tpe, tpe, tpe, tpe)), withVersion) + roundTripTest(Constant[STuple](Colls.fromItems[Any](x, y, (x, y)), STuple(tpe, tpe, STuple(tpe, tpe))), withVersion) + roundTripTest(Constant[STuple](Colls.fromItems[Any](x, y, TupleColl(x, y, x)), STuple(tpe, tpe, STuple(tpe, tpe, tpe))), withVersion) + roundTripTest(Constant[STuple](Colls.fromItems[Any](x, y, TupleColl(x, y, (x, y))), STuple(tpe, tpe, STuple(tpe, tpe, STuple(tpe, tpe)))), withVersion) } } @@ -71,6 +84,7 @@ class ConstantSerializerSpecification extends TableSerializationSpecification { forAll { x: SigmaBoolean => roundTripTest(Constant[SSigmaProp.type](x.toSigmaProp, SSigmaProp)) } forAll { x: ErgoBox => roundTripTest(Constant[SBox.type](x, SBox)) } forAll { x: AvlTree => roundTripTest(Constant[SAvlTree.type](x, SAvlTree)) } + forAll { x: Header => roundTripTest(Constant[SHeader.type](x, SHeader), Some(VersionContext.V6SoftForkVersion)) } forAll { x: Array[Byte] => roundTripTest(Constant[SByteArray](x.toColl, SByteArray)) } forAll { t: SPredefType => testCollection(t) } forAll { t: SPredefType => testTuples(t) } @@ -88,6 +102,7 @@ class ConstantSerializerSpecification extends TableSerializationSpecification { testCollection(SUnit) testCollection(SBox) testCollection(SAvlTree) + testCollection(SHeader) } private def caseObjectValue(v: SValue) = (v, Array[Byte](v.opCode)) diff --git a/interpreter/shared/src/test/scala/sigma/serialization/DataSerializerSpecification.scala b/interpreter/shared/src/test/scala/sigma/serialization/DataSerializerSpecification.scala index 2d9da3a87e..fe6f62dbe0 100644 --- a/interpreter/shared/src/test/scala/sigma/serialization/DataSerializerSpecification.scala +++ b/interpreter/shared/src/test/scala/sigma/serialization/DataSerializerSpecification.scala @@ -3,10 +3,10 @@ package sigma.serialization import java.math.BigInteger import org.ergoplatform.ErgoBox import org.scalacheck.Arbitrary._ -import sigma.data.{DataValueComparer, OptionType, RType, SigmaBoolean, TupleColl} +import sigma.data.{CBigInt, CHeader, DataValueComparer, OptionType, RType, SigmaBoolean, TupleColl} import sigma.ast.SCollection.SByteArray import sigmastate.eval._ -import sigma.{AvlTree, Colls, Evaluation, VersionContext} +import sigma.{AvlTree, Colls, Evaluation, Header, VersionContext} import sigma.ast.SType.AnyOps import sigma.ast._ import org.scalacheck.Gen @@ -23,29 +23,41 @@ import scala.reflect.ClassTag class DataSerializerSpecification extends SerializationSpecification { - def roundtrip[T <: SType](obj: T#WrappedType, tpe: T) = { - val w = SigmaSerializer.startWriter() - DataSerializer.serialize(obj, tpe, w) - val bytes = w.toBytes - val r = SigmaSerializer.startReader(bytes) - val res = DataSerializer.deserialize(tpe, r) - res shouldBe obj - - val es = CErgoTreeEvaluator.DefaultEvalSettings - val accumulator = new CostAccumulator( - initialCost = JitCost(0), - costLimit = Some(JitCost.fromBlockCost(es.scriptCostLimitInEvaluator))) - val evaluator = new CErgoTreeEvaluator( - context = null, - constants = ErgoTree.EmptyConstants, - coster = accumulator, DefaultProfiler, es) - val ok = DataValueComparer.equalDataValues(res, obj)(evaluator) - ok shouldBe true - - val randomPrefix = arrayGen[Byte].sample.get - val r2 = SigmaSerializer.startReader(randomPrefix ++ bytes, randomPrefix.length) - val res2 = DataSerializer.deserialize(tpe, r2) - res2 shouldBe obj + def roundtrip[T <: SType](obj: T#WrappedType, tpe: T, withVersion: Option[Byte] = None) = { + + def test() = { + val w = SigmaSerializer.startWriter() + DataSerializer.serialize(obj, tpe, w) + val bytes = w.toBytes + val r = SigmaSerializer.startReader(bytes) + val res = DataSerializer.deserialize(tpe, r) + res shouldBe obj + + val es = CErgoTreeEvaluator.DefaultEvalSettings + val accumulator = new CostAccumulator( + initialCost = JitCost(0), + costLimit = Some(JitCost.fromBlockCost(es.scriptCostLimitInEvaluator))) + val evaluator = new CErgoTreeEvaluator( + context = null, + constants = ErgoTree.EmptyConstants, + coster = accumulator, DefaultProfiler, es) + val ok = DataValueComparer.equalDataValues(res, obj)(evaluator) + ok shouldBe true + + val randomPrefix = arrayGen[Byte].sample.get + val r2 = SigmaSerializer.startReader(randomPrefix ++ bytes, randomPrefix.length) + val res2 = DataSerializer.deserialize(tpe, r2) + res2 shouldBe obj + } + + withVersion match { + case Some(ver) => + VersionContext.withVersions(ver, 1) { + test() + } + case None => + test() + } } def testCollection[T <: SType](tpe: T) = { @@ -53,25 +65,32 @@ class DataSerializerSpecification extends SerializationSpecification { implicit val tT = Evaluation.stypeToRType(tpe) implicit val tagT = tT.classTag implicit val tAny = sigma.AnyType + + val withVersion = if (tpe == SHeader) { + Some(VersionContext.V6SoftForkVersion) + } else { + None + } forAll { xs: Array[T#WrappedType] => - roundtrip[SCollection[T]](xs.toColl, SCollection(tpe)) - roundtrip[SType](xs.toColl.map(x => (x, x)).asWrappedType, SCollection(STuple(tpe, tpe))) + roundtrip[SCollection[T]](xs.toColl, SCollection(tpe), withVersion) + roundtrip[SType](xs.toColl.map(x => (x, x)).asWrappedType, SCollection(STuple(tpe, tpe)), withVersion) val triples = xs.toColl.map(x => TupleColl(x, x, x)).asWrappedType - roundtrip(triples, SCollection(STuple(tpe, tpe, tpe))) + roundtrip(triples, SCollection(STuple(tpe, tpe, tpe)), withVersion) val quartets = xs.toColl.map(x => TupleColl(x, x, x, x)).asWrappedType - roundtrip(quartets, SCollection(STuple(tpe, tpe, tpe, tpe))) + roundtrip(quartets, SCollection(STuple(tpe, tpe, tpe, tpe)), withVersion) val nested = xs.toColl.map(x => Colls.fromItems[T#WrappedType](x, x)) - roundtrip[SCollection[SCollection[T]]](nested, SCollection(SCollection(tpe))) + roundtrip[SCollection[SCollection[T]]](nested, SCollection(SCollection(tpe)), withVersion) roundtrip[SType]( xs.toColl.map { x => val arr = Colls.fromItems[T#WrappedType](x, x) (arr, arr) }.asWrappedType, - SCollection(STuple(SCollection(tpe), SCollection(tpe))) + SCollection(STuple(SCollection(tpe), SCollection(tpe))), + withVersion ) } } @@ -81,14 +100,19 @@ class DataSerializerSpecification extends SerializationSpecification { val tT = Evaluation.stypeToRType(tpe) @nowarn implicit val tag: ClassTag[T#WrappedType] = tT.classTag implicit val tAny : RType[Any] = sigma.AnyType + val withVersion = if (tpe == SHeader) { + Some(VersionContext.V6SoftForkVersion) + } else { + None + } forAll { in: (T#WrappedType, T#WrappedType) => val (x,y) = (in._1, in._2) - roundtrip[SType]((x, y).asWrappedType, STuple(tpe, tpe)) - roundtrip[SType](TupleColl(x, y, x).asWrappedType, STuple(tpe, tpe, tpe)) - roundtrip[SType](TupleColl(x, y, x, y).asWrappedType, STuple(tpe, tpe, tpe, tpe)) - roundtrip[STuple](Colls.fromItems[Any](x, y, (x, y)), STuple(tpe, tpe, STuple(tpe, tpe))) - roundtrip[STuple](Colls.fromItems[Any](x, y, TupleColl(x, y, x)), STuple(tpe, tpe, STuple(tpe, tpe, tpe))) - roundtrip[STuple](Colls.fromItems[Any](x, y, TupleColl(x, y, (x, y))), STuple(tpe, tpe, STuple(tpe, tpe, STuple(tpe, tpe)))) + roundtrip[SType]((x, y).asWrappedType, STuple(tpe, tpe), withVersion) + roundtrip[SType](TupleColl(x, y, x).asWrappedType, STuple(tpe, tpe, tpe), withVersion) + roundtrip[SType](TupleColl(x, y, x, y).asWrappedType, STuple(tpe, tpe, tpe, tpe), withVersion) + roundtrip[STuple](Colls.fromItems[Any](x, y, (x, y)), STuple(tpe, tpe, STuple(tpe, tpe)), withVersion) + roundtrip[STuple](Colls.fromItems[Any](x, y, TupleColl(x, y, x)), STuple(tpe, tpe, STuple(tpe, tpe, tpe)), withVersion) + roundtrip[STuple](Colls.fromItems[Any](x, y, TupleColl(x, y, (x, y))), STuple(tpe, tpe, STuple(tpe, tpe, STuple(tpe, tpe))), withVersion) } } @@ -129,6 +153,7 @@ class DataSerializerSpecification extends SerializationSpecification { forAll { x: ErgoBox => roundtrip[SBox.type](x, SBox) } forAll { x: AvlTree => roundtrip[SAvlTree.type](x, SAvlTree) } forAll { x: Array[Byte] => roundtrip[SByteArray](x.toColl, SByteArray) } + forAll { x: Header => roundtrip[SHeader.type](x, SHeader, Some(VersionContext.V6SoftForkVersion)) } forAll { t: SPredefType => testCollection(t) } forAll { t: SPredefType => testTuples(t) } forAll { t: SPredefType => testOption(t) } @@ -159,6 +184,42 @@ class DataSerializerSpecification extends SerializationSpecification { t.isInstanceOf[SerializerException] && t.getMessage.contains(s"BigInt value doesn't not fit into ${SBigInt.MaxSizeInBytes} bytes") }) + } + property("nuanced versioned test for header roundtrip") { + VersionContext.withVersions(VersionContext.V6SoftForkVersion, 1) { + forAll { x: Header => roundtrip[SHeader.type](x, SHeader) } + } + + an[SerializerException] should be thrownBy ( + VersionContext.withVersions((VersionContext.V6SoftForkVersion - 1).toByte, 1) { + val h = headerGen.sample.get + roundtrip[SHeader.type](h, SHeader) + }) + } + + property("header vector") { + val header = CHeader( + 0.toByte, + Helpers.decodeBytes("7a7fe5347f09017818010062000001807f86808000ff7f66ffb07f7ad27f3362"), + Helpers.decodeBytes("c1d70ad9b1ffc1fb9a715fff19807f2401017fcd8b73db017f1cff77727fff08"), + Helpers.decodeBytes("54d23dd080006bdb56800100356080935a80ffb77e90b800057f00661601807f17"), + Helpers.decodeBytes("5e7f1164ccd0990080c501fc0e0181cb387fc17f00ff00c7d5ff767f91ff5e68"), + -7421721754642387858L, + -4826493284887861030L, + 10, + Helpers.decodeBytes("e580c88001ff6fc89c5501017f80e001ff0101fe48c153ff7f00666b80d780ab"), + Helpers.decodeGroupElement("03e7f2875298fddd933c2e0a38968fe85bdeeb70dd8b389559a1d36e2ff1b58fc5"), + Helpers.decodeGroupElement("034e2d3b5f9e409e3ae8a2e768340760362ca33764eda5855f7a43487f14883300"), + Helpers.decodeBytes("974651c9efff7f00"), + CBigInt(new BigInteger("478e827dfa1e4b57", 16)), + Helpers.decodeBytes("01ff13"), + Colls.emptyColl + ) + + VersionContext.withVersions(VersionContext.V6SoftForkVersion, 1) { + roundtrip[SHeader.type](header, SHeader) + } } + } diff --git a/interpreter/shared/src/test/scala/sigma/serialization/MethodCallSerializerSpecification.scala b/interpreter/shared/src/test/scala/sigma/serialization/MethodCallSerializerSpecification.scala index 1db166c685..793d3df959 100644 --- a/interpreter/shared/src/test/scala/sigma/serialization/MethodCallSerializerSpecification.scala +++ b/interpreter/shared/src/test/scala/sigma/serialization/MethodCallSerializerSpecification.scala @@ -1,6 +1,7 @@ package sigma.serialization import sigma.VersionContext +import sigma.ast.SCollection.SByteArray import sigma.ast._ import sigma.validation.ValidationException @@ -24,11 +25,11 @@ class MethodCallSerializerSpecification extends SerializationSpecification { roundTripTest(expr) } - property("MethodCall deserialization round trip for BigInt.nbits") { + property("MethodCall deserialization round trip for Header.checkPow") { def code = { - val bi = BigIntConstant(5) + val bi = HeaderConstant(headerGen.sample.get) val expr = MethodCall(bi, - SBigIntMethods.ToNBits, + SHeaderMethods.checkPowMethod, Vector(), Map() ) @@ -39,10 +40,32 @@ class MethodCallSerializerSpecification extends SerializationSpecification { code } - an[ValidationException] should be thrownBy ( + an[Exception] should be thrownBy ( VersionContext.withVersions((VersionContext.V6SoftForkVersion - 1).toByte, 1) { code } ) } + + property("MethodCall deserialization round trip for Global.serialize") { + def code = { + val b = ByteArrayConstant(Array(1.toByte, 2.toByte, 3.toByte)) + val expr = MethodCall(Global, + SGlobalMethods.serializeMethod.withConcreteTypes(Map(STypeVar("T") -> SByteArray)), + Vector(b), + Map() + ) + roundTripTest(expr) + } + + VersionContext.withVersions(VersionContext.V6SoftForkVersion, 1) { + code + } + + an[Exception] should be thrownBy ( + VersionContext.withVersions((VersionContext.V6SoftForkVersion - 1).toByte, 1) { + code + }) + } + } diff --git a/interpreter/shared/src/test/scala/sigma/serialization/SerializationSpecification.scala b/interpreter/shared/src/test/scala/sigma/serialization/SerializationSpecification.scala index 30ae6af19b..aa7a8722ba 100644 --- a/interpreter/shared/src/test/scala/sigma/serialization/SerializationSpecification.scala +++ b/interpreter/shared/src/test/scala/sigma/serialization/SerializationSpecification.scala @@ -8,6 +8,7 @@ import org.scalacheck.Arbitrary._ import org.scalatest.matchers.should.Matchers import org.scalatest.propspec.AnyPropSpec import org.scalatestplus.scalacheck.{ScalaCheckDrivenPropertyChecks, ScalaCheckPropertyChecks} +import sigma.VersionContext import sigma.ast.SType import sigma.ast._ import sigmastate.helpers.NegativeTesting @@ -26,10 +27,20 @@ trait SerializationSpecification extends AnyPropSpec with ValidationSpecification with NegativeTesting { - protected def roundTripTest[V <: Value[_ <: SType]](v: V): Assertion = { - val bytes = ValueSerializer.serialize(v) - predefinedBytesTest(v, bytes) - predefinedBytesTestNotFomZeroElement(bytes, v) + protected def roundTripTest[V <: Value[_ <: SType]](v: V, withVersion: Option[Byte] = None): Assertion = { + def test() = { + val bytes = ValueSerializer.serialize(v) + predefinedBytesTest(v, bytes) + predefinedBytesTestNotFomZeroElement(bytes, v) + } + withVersion match { + case Some(ver) => + VersionContext.withVersions(ver, 0) { + test() + } + case None => + test() + } } protected def predefinedBytesTest[V <: Value[_ <: SType]](v: V, bytes: Array[Byte]): Assertion = { @@ -41,7 +52,7 @@ trait SerializationSpecification extends AnyPropSpec r.positionLimit shouldBe positionLimitBefore } - //check that pos and consumed are being implented correctly + //check that pos and consumed are being implemented correctly protected def predefinedBytesTestNotFomZeroElement[V <: Value[_ <: SType]](bytes: Array[Byte], v: V): Assertion = { val randomInt = Gen.chooseNum(1, 20).sample.get val randomBytes = Gen.listOfN(randomInt, arbByte.arbitrary).sample.get.toArray diff --git a/interpreter/shared/src/test/scala/sigma/serialization/generators/ObjectGenerators.scala b/interpreter/shared/src/test/scala/sigma/serialization/generators/ObjectGenerators.scala index d4971f88c2..db6cd87330 100644 --- a/interpreter/shared/src/test/scala/sigma/serialization/generators/ObjectGenerators.scala +++ b/interpreter/shared/src/test/scala/sigma/serialization/generators/ObjectGenerators.scala @@ -8,7 +8,6 @@ import org.scalacheck.Arbitrary._ import org.scalacheck.Gen.{choose, frequency} import org.scalacheck.util.Buildable import org.scalacheck.{Arbitrary, Gen} -import sigma.data._ import scorex.crypto.authds.{ADDigest, ADKey} import scorex.util.encode.{Base58, Base64} import scorex.util.{ModifierId, bytesToId} @@ -27,6 +26,7 @@ import sigma.util.Extensions.EcpOps import sigma.validation.{ChangedRule, DisabledRule, EnabledRule, ReplacedRule, RuleStatus} import sigma.validation.ValidationRules.FirstRuleId import ErgoTree.ZeroHeader +import sigma.data.{AvlTreeData, AvlTreeFlags, CAND, CBox, CHeader, COR, CTHRESHOLD, Digest32Coll, ProveDHTuple, ProveDlog, RType, SigmaBoolean} import sigma.eval.Extensions.{EvalIterableOps, SigmaBooleanOps} import sigma.eval.SigmaDsl import sigma.interpreter.{ContextExtension, ProverResult} @@ -311,6 +311,7 @@ trait ObjectGenerators extends TypeGenerators case SAvlTree => arbAvlTree case SAny => arbAnyVal case SUnit => arbUnit + case SHeader => arbHeader case opt: SOption[a] => Arbitrary(frequency((5, None), (5, for (x <- wrappedTypeGen(opt.elemType)) yield Some(x)))) }).asInstanceOf[Arbitrary[T#WrappedType]].arbitrary @@ -694,7 +695,6 @@ trait ObjectGenerators extends TypeGenerators } yield ErgoTree.withSegregation(ZeroHeader, prop) def headerGen(stateRoot: AvlTree, parentId: Coll[Byte]): Gen[Header] = for { - id <- modifierIdBytesGen version <- arbByte.arbitrary adProofsRoot <- digest32Gen transactionRoot <- digest32Gen @@ -707,8 +707,10 @@ trait ObjectGenerators extends TypeGenerators powNonce <- nonceBytesGen powDistance <- arbBigInt.arbitrary votes <- minerVotesGen - } yield CHeader(id, version, parentId, adProofsRoot, stateRoot, transactionRoot, timestamp, nBits, - height, extensionRoot, minerPk.toGroupElement, powOnetimePk.toGroupElement, powNonce, powDistance, votes) + unparsedBytes <- collOfRange(0, 32, arbByte.arbitrary) + } yield CHeader(version, parentId, adProofsRoot, stateRoot.digest, transactionRoot, timestamp, nBits, + height, extensionRoot, minerPk.toGroupElement, powOnetimePk.toGroupElement, powNonce, powDistance, votes, + if(version > HeaderVersion.Interpreter60Version){ unparsedBytes } else {Colls.emptyColl[Byte]}) lazy val headerGen: Gen[Header] = for { stateRoot <- avlTreeGen diff --git a/interpreter/shared/src/test/scala/sigma/serialization/generators/TypeGenerators.scala b/interpreter/shared/src/test/scala/sigma/serialization/generators/TypeGenerators.scala index 81073c4849..70a215e831 100644 --- a/interpreter/shared/src/test/scala/sigma/serialization/generators/TypeGenerators.scala +++ b/interpreter/shared/src/test/scala/sigma/serialization/generators/TypeGenerators.scala @@ -16,12 +16,13 @@ trait TypeGenerators { implicit val boxTypeGen: Gen[SBox.type] = Gen.const(SBox) implicit val avlTreeTypeGen: Gen[SAvlTree.type] = Gen.const(SAvlTree) implicit val optionSigmaPropTypeGen: Gen[SOption[SSigmaProp.type]] = Gen.const(SOption(SSigmaProp)) + implicit val headerTypeGen: Gen[SHeader.type] = Gen.const(SHeader) implicit val primTypeGen: Gen[SPrimType] = Gen.oneOf[SPrimType](SBoolean, SByte, SShort, SInt, SLong, SBigInt, SGroupElement, SSigmaProp, SUnit) implicit val arbPrimType: Arbitrary[SPrimType] = Arbitrary(primTypeGen) implicit val predefTypeGen: Gen[SPredefType] = - Gen.oneOf[SPredefType](SBoolean, SByte, SShort, SInt, SLong, SBigInt, SGroupElement, SSigmaProp, SUnit, SBox, SAvlTree) + Gen.oneOf[SPredefType](SBoolean, SByte, SShort, SInt, SLong, SBigInt, SGroupElement, SSigmaProp, SUnit, SBox, SAvlTree, SHeader) implicit val arbPredefType: Arbitrary[SPredefType] = Arbitrary(predefTypeGen) implicit def genToArbitrary[T: Gen]: Arbitrary[T] = Arbitrary(implicitly[Gen[T]]) diff --git a/interpreter/shared/src/test/scala/special/sigma/SigmaTestingData.scala b/interpreter/shared/src/test/scala/special/sigma/SigmaTestingData.scala index f113a484ef..b8bf76cacf 100644 --- a/interpreter/shared/src/test/scala/special/sigma/SigmaTestingData.scala +++ b/interpreter/shared/src/test/scala/special/sigma/SigmaTestingData.scala @@ -12,7 +12,7 @@ import scorex.crypto.hash.{Blake2b256, Digest32} import scorex.util.ModifierId import sigma.ast._ import sigma.Extensions.ArrayOps -import sigmastate.eval.{CHeader, CPreHeader} +import sigmastate.eval.CPreHeader import sigmastate.helpers.TestingCommons import sigma.serialization.ErgoTreeSerializer import sigma.serialization.generators.ObjectGenerators @@ -240,17 +240,16 @@ trait SigmaTestingData extends TestingCommons with ObjectGenerators { def createAvlTreeData() = AvlTreeData( ErgoAlgos.decodeUnsafe("010180017f7f7b7f720c00007f7f7f0f01e857a626f37f1483d06af8077a008080").toColl, - AvlTreeFlags(false, true, false), - 728138553, - Some(2147483647) + AvlTreeFlags(true, true, true), + 32, + None ) val h1_instances = new CloneSet(1000, CHeader( - Helpers.decodeBytes("957f008001808080ffe4ffffc8f3802401df40006aa05e017fa8d3f6004c804a"), 0.toByte, Helpers.decodeBytes("0180dd805b0000ff5400b997fd7f0b9b00de00fb03c47e37806a8186b94f07ff"), Helpers.decodeBytes("01f07f60d100ffb970c3007f60ff7f24d4070bb8fffa7fca7f34c10001ffe39d"), - CAvlTree(createAvlTreeData()), + CAvlTree(createAvlTreeData()).digest, Helpers.decodeBytes("804101ff01000080a3ffbd006ac080098df132a7017f00649311ec0e00000100"), 1L, -1L, @@ -260,14 +259,15 @@ trait SigmaTestingData extends TestingCommons with ObjectGenerators { Helpers.decodeGroupElement("0361299207fa392231e23666f6945ae3e867b978e021d8d702872bde454e9abe9c"), Helpers.decodeBytes("7f4f09012a807f01"), CBigInt(new BigInteger("-e24990c47e15ed4d0178c44f1790cc72155d516c43c3e8684e75db3800a288", 16)), - Helpers.decodeBytes("7f0180") + Helpers.decodeBytes("7f0180"), + Colls.emptyColl[Byte] )) def create_h1(): Header = h1_instances.getNext val h1: Header = create_h1() - val h2: Header = create_h1().asInstanceOf[CHeader].copy(height = 2) + val h2: Header = new CHeader(h1.asInstanceOf[CHeader].wrappedValue.copy(height = 2)) val dlog_instances = new CloneSet(1000, ProveDlog( SigmaDsl.toECPoint(create_ge1()).asInstanceOf[EcPointType] diff --git a/parsers/shared/src/test/scala/sigmastate/helpers/SigmaPPrint.scala b/parsers/shared/src/test/scala/sigmastate/helpers/SigmaPPrint.scala index 24aaeddefd..11381e1ac3 100644 --- a/parsers/shared/src/test/scala/sigmastate/helpers/SigmaPPrint.scala +++ b/parsers/shared/src/test/scala/sigmastate/helpers/SigmaPPrint.scala @@ -52,6 +52,8 @@ object SigmaPPrint extends PPrinter { s"SOption[${typeName(ot.elemType)}]" case _: STuple => "STuple" + case _: SFunc => + s"SFunc" case _ => sys.error(s"Cannot get typeName($tpe)") } diff --git a/parsers/shared/src/test/scala/sigmastate/lang/SigmaParserTest.scala b/parsers/shared/src/test/scala/sigmastate/lang/SigmaParserTest.scala index dc63330f95..5a0f2b3465 100644 --- a/parsers/shared/src/test/scala/sigmastate/lang/SigmaParserTest.scala +++ b/parsers/shared/src/test/scala/sigmastate/lang/SigmaParserTest.scala @@ -906,6 +906,17 @@ class SigmaParserTest extends AnyPropSpec with ScalaCheckPropertyChecks with Mat ) } + property("serialize") { + checkParsed("serialize(1)", Apply(Ident("serialize", NoType), Array(IntConstant(1)))) + checkParsed("serialize((1, 2L))", + Apply(Ident("serialize", NoType), Array(Tuple(Vector(IntConstant(1), LongConstant(2L)))))) + checkParsed("serialize(Coll(1, 2, 3))", + Apply( + Ident("serialize", NoType), + Array(Apply(Ident("Coll", NoType), Array(IntConstant(1), IntConstant(2), IntConstant(3)))) + )) + } + property("single name pattern fail") { fail("{val (a,b) = (1,2)}", 1, 6) } diff --git a/sc/shared/src/main/scala/sigma/compiler/ir/GraphBuilding.scala b/sc/shared/src/main/scala/sigma/compiler/ir/GraphBuilding.scala index 7c7b80d39a..d4b2abed89 100644 --- a/sc/shared/src/main/scala/sigma/compiler/ir/GraphBuilding.scala +++ b/sc/shared/src/main/scala/sigma/compiler/ir/GraphBuilding.scala @@ -1,6 +1,8 @@ package sigma.compiler.ir import org.ergoplatform._ +import sigma.Evaluation.stypeToRType +import sigma.ast.SType.tT import sigma.ast.TypeCodes.LastConstantCode import sigma.ast.Value.Typed import sigma.ast.syntax.{SValue, ValueOps} @@ -13,7 +15,7 @@ import sigma.data.{CSigmaDslBuilder, ExactIntegral, ExactNumeric, ExactOrdering, import sigma.exceptions.GraphBuildingException import sigma.serialization.OpCodes import sigma.util.Extensions.ByteOps -import sigma.{SigmaException, ast} +import sigma.{SigmaException, VersionContext, ast} import sigmastate.interpreter.Interpreter.ScriptEnv import scala.collection.mutable.ArrayBuffer @@ -437,8 +439,8 @@ trait GraphBuilding extends Base with DefRewriting { IR: IRContext => } Nullable(res) }} - def throwError = - error(s"Don't know how to buildNode($node)", node.sourceContext.toOption) + def throwError(clue: String = "") = + error((if (clue.nonEmpty) clue + ": " else "") + s"Don't know how to buildNode($node)", node.sourceContext.toOption) val res: Ref[Any] = node match { case Constant(v, tpe) => v match { @@ -928,7 +930,7 @@ trait GraphBuilding extends Base with DefRewriting { IR: IRContext => sigmaDslBuilder.decodePoint(bytes) // fallback rule for MethodCall, should be the last case in the list - case sigma.ast.MethodCall(obj, method, args, _) => + case sigma.ast.MethodCall(obj, method, args, typeSubst) => val objV = eval(obj) val argsV = args.map(eval) (objV, method.objType) match { @@ -985,7 +987,7 @@ trait GraphBuilding extends Base with DefRewriting { IR: IRContext => val i = asRep[Int](argsV(0)) val d = asRep[t](argsV(1)) xs.getOrElse(i, d) - case _ => throwError + case _ => throwError() } case (opt: ROption[t]@unchecked, SOptionMethods) => method.name match { case SOptionMethods.GetMethod.name => @@ -999,7 +1001,7 @@ trait GraphBuilding extends Base with DefRewriting { IR: IRContext => opt.map(asRep[t => Any](argsV(0))) case SOptionMethods.FilterMethod.name => opt.filter(asRep[t => Boolean](argsV(0))) - case _ => throwError + case _ => throwError() } case (ge: Ref[GroupElement]@unchecked, SGroupElementMethods) => method.name match { case SGroupElementMethods.GetEncodedMethod.name => @@ -1012,12 +1014,16 @@ trait GraphBuilding extends Base with DefRewriting { IR: IRContext => case SGroupElementMethods.ExponentiateMethod.name => val k = asRep[BigInt](argsV(0)) ge.exp(k) - case _ => throwError + case _ => throwError() } case (box: Ref[Box]@unchecked, SBoxMethods) => method.name match { case SBoxMethods.tokensMethod.name => box.tokens - case _ => throwError + case SBoxMethods.getRegMethodV6.name if VersionContext.current.isV6SoftForkActivated => + val c1 = asRep[Int](argsV(0)) + val c2 = stypeToElem(typeSubst.apply(tT)) + box.getReg(c1)(c2) + case _ => throwError() } case (ctx: Ref[Context]@unchecked, SContextMethods) => method.name match { case SContextMethods.dataInputsMethod.name => @@ -1040,7 +1046,7 @@ trait GraphBuilding extends Base with DefRewriting { IR: IRContext => ctx.LastBlockUtxoRootHash case SContextMethods.minerPubKeyMethod.name => ctx.minerPubKey - case _ => throwError + case _ => throwError() } case (tree: Ref[AvlTree]@unchecked, SAvlTreeMethods) => method.name match { case SAvlTreeMethods.digestMethod.name => @@ -1087,7 +1093,7 @@ trait GraphBuilding extends Base with DefRewriting { IR: IRContext => val operations = asRep[Coll[(Coll[Byte], Coll[Byte])]](argsV(0)) val proof = asRep[Coll[Byte]](argsV(1)) tree.update(operations, proof) - case _ => throwError + case _ => throwError() } case (ph: Ref[PreHeader]@unchecked, SPreHeaderMethods) => method.name match { case SPreHeaderMethods.versionMethod.name => @@ -1104,7 +1110,7 @@ trait GraphBuilding extends Base with DefRewriting { IR: IRContext => ph.minerPk case SPreHeaderMethods.votesMethod.name => ph.votes - case _ => throwError + case _ => throwError() } case (h: Ref[Header]@unchecked, SHeaderMethods) => method.name match { case SHeaderMethods.idMethod.name => @@ -1137,7 +1143,9 @@ trait GraphBuilding extends Base with DefRewriting { IR: IRContext => h.powDistance case SHeaderMethods.votesMethod.name => h.votes - case _ => throwError + case SHeaderMethods.checkPowMethod.name if VersionContext.current.isV6SoftForkActivated => + h.checkPow + case _ => throwError() } case (g: Ref[SigmaDslBuilder]@unchecked, SGlobalMethods) => method.name match { case SGlobalMethods.groupGeneratorMethod.name => @@ -1146,13 +1154,48 @@ trait GraphBuilding extends Base with DefRewriting { IR: IRContext => val c1 = asRep[Coll[Byte]](argsV(0)) val c2 = asRep[Coll[Byte]](argsV(1)) g.xor(c1, c2) - case _ => throwError + case SGlobalMethods.serializeMethod.name => + val value = asRep[Any](argsV(0)) + g.serialize(value) + case _ => throwError() } - case _ => throwError + case (x: Ref[tNum], _: SNumericTypeMethods) => method.name match { + case SNumericTypeMethods.ToBytesMethod.name => + val op = NumericToBigEndianBytes(elemToExactNumeric(x.elem)) + ApplyUnOp(op, x) + case SNumericTypeMethods.ToBitsMethod.name => + val op = NumericToBits(elemToExactNumeric(x.elem)) + ApplyUnOp(op, x) + case SNumericTypeMethods.BitwiseInverseMethod.name => + val op = NumericBitwiseInverse(elemToExactNumeric(x.elem))(x.elem) + ApplyUnOp(op, x) + case SNumericTypeMethods.BitwiseOrMethod.name => + val y = asRep[tNum](argsV(0)) + val op = NumericBitwiseOr(elemToExactNumeric(x.elem))(x.elem) + ApplyBinOp(op, x, y) + case SNumericTypeMethods.BitwiseAndMethod.name => + val y = asRep[tNum](argsV(0)) + val op = NumericBitwiseAnd(elemToExactNumeric(x.elem))(x.elem) + ApplyBinOp(op, x, y) + case SNumericTypeMethods.BitwiseXorMethod.name => + val y = asRep[tNum](argsV(0)) + val op = NumericBitwiseXor(elemToExactNumeric(x.elem))(x.elem) + ApplyBinOp(op, x, y) + case SNumericTypeMethods.ShiftLeftMethod.name => + val y = asRep[Int](argsV(0)) + val op = NumericShiftLeft(elemToExactNumeric(x.elem))(x.elem) + ApplyBinOpDiffArgs(op, x, y) + case SNumericTypeMethods.ShiftRightMethod.name => + val y = asRep[Int](argsV(0)) + val op = NumericShiftRight(elemToExactNumeric(x.elem))(x.elem) + ApplyBinOpDiffArgs(op, x, y) + case _ => throwError() + } + case _ => throwError(s"Type ${stypeToRType(obj.tpe).name} doesn't have methods") } case _ => - throwError + throwError() } val resC = asRep[T#WrappedType](res) resC diff --git a/sc/shared/src/main/scala/sigma/compiler/ir/GraphIRReflection.scala b/sc/shared/src/main/scala/sigma/compiler/ir/GraphIRReflection.scala index 69736a0224..dcdce623ac 100644 --- a/sc/shared/src/main/scala/sigma/compiler/ir/GraphIRReflection.scala +++ b/sc/shared/src/main/scala/sigma/compiler/ir/GraphIRReflection.scala @@ -407,6 +407,9 @@ object GraphIRReflection { }, mkMethod(clazz, "powDistance", Array[Class[_]]()) { (obj, args) => obj.asInstanceOf[ctx.Header].powDistance + }, + mkMethod(clazz, "checkPow", Array[Class[_]]()) { (obj, args) => + obj.asInstanceOf[ctx.Header].checkPow } ) ) @@ -504,6 +507,9 @@ object GraphIRReflection { }, mkMethod(clazz, "decodePoint", Array[Class[_]](classOf[Base#Ref[_]])) { (obj, args) => obj.asInstanceOf[ctx.SigmaDslBuilder].decodePoint(args(0).asInstanceOf[ctx.Ref[ctx.Coll[Byte]]]) + }, + mkMethod(clazz, "serialize", Array[Class[_]](classOf[Base#Ref[_]])) { (obj, args) => + obj.asInstanceOf[ctx.SigmaDslBuilder].serialize(args(0).asInstanceOf[ctx.Ref[Any]]) } ) ) diff --git a/sc/shared/src/main/scala/sigma/compiler/ir/IRContext.scala b/sc/shared/src/main/scala/sigma/compiler/ir/IRContext.scala index c60bc0882f..a22962f987 100644 --- a/sc/shared/src/main/scala/sigma/compiler/ir/IRContext.scala +++ b/sc/shared/src/main/scala/sigma/compiler/ir/IRContext.scala @@ -153,7 +153,7 @@ trait IRContext override def invokeUnlifted(e: Elem[_], mc: MethodCall, dataEnv: DataEnv): Any = e match { case _: CollElem[_,_] => mc match { case CollMethods.map(_, f) => - val newMC = mc.copy(args = mc.args :+ f.elem.eRange)(mc.resultType, mc.isAdapterCall) + val newMC = mc.copy(args = mc.args :+ f.elem.eRange)(mc.resultType, mc.isAdapterCall, mc.typeSubst) super.invokeUnlifted(e, newMC, dataEnv) case _ => super.invokeUnlifted(e, mc, dataEnv) diff --git a/sc/shared/src/main/scala/sigma/compiler/ir/MethodCalls.scala b/sc/shared/src/main/scala/sigma/compiler/ir/MethodCalls.scala index 876f0e9d7e..089b76cae4 100644 --- a/sc/shared/src/main/scala/sigma/compiler/ir/MethodCalls.scala +++ b/sc/shared/src/main/scala/sigma/compiler/ir/MethodCalls.scala @@ -1,6 +1,7 @@ package sigma.compiler.ir import debox.{cfor, Buffer => DBuffer} +import sigma.ast.{SType, STypeVar} import sigma.compiler.DelayInvokeException import sigma.reflection.RMethod import sigma.util.CollectionUtil.TraversableOps @@ -26,7 +27,9 @@ trait MethodCalls extends Base { self: IRContext => * given `method`. */ case class MethodCall private[MethodCalls](receiver: Sym, method: RMethod, args: Seq[AnyRef], neverInvoke: Boolean) - (val resultType: Elem[Any], val isAdapterCall: Boolean = false) extends Def[Any] { + (val resultType: Elem[Any], + val isAdapterCall: Boolean = false, + val typeSubst: Map[STypeVar, SType]) extends Def[Any] { override def mirror(t: Transformer): Ref[Any] = { val len = args.length @@ -99,9 +102,14 @@ trait MethodCalls extends Base { self: IRContext => } /** Creates new MethodCall node and returns its node ref. */ - def mkMethodCall(receiver: Sym, method: RMethod, args: Seq[AnyRef], - neverInvoke: Boolean, isAdapterCall: Boolean, resultElem: Elem[_]): Sym = { - reifyObject(MethodCall(receiver, method, args, neverInvoke)(asElem[Any](resultElem), isAdapterCall)) + def mkMethodCall(receiver: Sym, + method: RMethod, + args: Seq[AnyRef], + neverInvoke: Boolean, + isAdapterCall: Boolean, + resultElem: Elem[_], + typeSubst: Map[STypeVar, SType] = Map.empty): Sym = { + reifyObject(MethodCall(receiver, method, args, neverInvoke)(asElem[Any](resultElem), isAdapterCall, typeSubst)) } @tailrec diff --git a/sc/shared/src/main/scala/sigma/compiler/ir/TreeBuilding.scala b/sc/shared/src/main/scala/sigma/compiler/ir/TreeBuilding.scala index 725e3b1d19..aed197843c 100644 --- a/sc/shared/src/main/scala/sigma/compiler/ir/TreeBuilding.scala +++ b/sc/shared/src/main/scala/sigma/compiler/ir/TreeBuilding.scala @@ -106,6 +106,27 @@ trait TreeBuilding extends Base { IR: IRContext => object IsNumericUnOp { def unapply(op: UnOp[_,_]): Option[SValue => SValue] = op match { case NumericNegate(_) => Some({ v: SValue => builder.mkNegation(v.asNumValue) }) + case _: NumericToBigEndianBytes[_] => + val mkNode = { v: SValue => + val receiverType = v.tpe.asNumTypeOrElse(error(s"Expected numeric type, got: ${v.tpe}")) + val m = SMethod.fromIds(receiverType.typeId, SNumericTypeMethods.ToBytesMethod.methodId) + builder.mkMethodCall(v.asNumValue, m, IndexedSeq.empty) + } + Some(mkNode) + case _: NumericToBits[_] => + val mkNode = { v: SValue => + val receiverType = v.tpe.asNumTypeOrElse(error(s"Expected numeric type, got: ${v.tpe}")) + val m = SMethod.fromIds(receiverType.typeId, SNumericTypeMethods.ToBitsMethod.methodId) + builder.mkMethodCall(v.asNumValue, m, IndexedSeq.empty) + } + Some(mkNode) + case _: NumericBitwiseInverse[_] => + val mkNode = { v: SValue => + val receiverType = v.tpe.asNumTypeOrElse(error(s"Expected numeric type, got: ${v.tpe}")) + val m = SMethod.fromIds(receiverType.typeId, SNumericTypeMethods.BitwiseInverseMethod.methodId) + builder.mkMethodCall(v.asNumValue, m, IndexedSeq.empty) + } + Some(mkNode) case _ => None } } @@ -179,7 +200,11 @@ trait TreeBuilding extends Base { IR: IRContext => .asInstanceOf[ConstantNode[SType]] s.put(constant)(builder) case None => - mkConstant[tpe.type](x.asInstanceOf[tpe.WrappedType], tpe) + if(x.isInstanceOf[CollConst[_, _]]) { // hack used to process NumericToBigEndianBytes only + mkConstant[tpe.type](x.asInstanceOf[CollConst[_, _]].constValue.asInstanceOf[tpe.WrappedType], tpe) + } else { + mkConstant[tpe.type](x.asInstanceOf[tpe.WrappedType], tpe) + } } case Def(IR.ConstantPlaceholder(id, elem)) => val tpe = elemToSType(elem) @@ -192,6 +217,37 @@ trait TreeBuilding extends Base { IR: IRContext => case Def(IsContextProperty(v)) => v case s if s == sigmaDslBuilder => Global + case Def(ApplyBinOp(op, xSym, ySym)) if op.isInstanceOf[NumericBitwiseOr[_]] => + val Seq(x, y) = Seq(xSym, ySym).map(recurse) + val receiverType = x.asNumValue.tpe.asNumTypeOrElse(error(s"Expected numeric type, got: ${x.tpe}")) + val m = SMethod.fromIds(receiverType.typeId, SNumericTypeMethods.BitwiseOrMethod.methodId) + builder.mkMethodCall(x.asNumValue, m, IndexedSeq(y)) + + case Def(ApplyBinOp(op, xSym, ySym)) if op.isInstanceOf[NumericBitwiseAnd[_]] => + val Seq(x, y) = Seq(xSym, ySym).map(recurse) + val receiverType = x.asNumValue.tpe.asNumTypeOrElse(error(s"Expected numeric type, got: ${x.tpe}")) + val m = SMethod.fromIds(receiverType.typeId, SNumericTypeMethods.BitwiseAndMethod.methodId) + builder.mkMethodCall(x.asNumValue, m, IndexedSeq(y)) + + case Def(ApplyBinOp(op, xSym, ySym)) if op.isInstanceOf[NumericBitwiseXor[_]] => + val Seq(x, y) = Seq(xSym, ySym).map(recurse) + val receiverType = x.asNumValue.tpe.asNumTypeOrElse(error(s"Expected numeric type, got: ${x.tpe}")) + val m = SMethod.fromIds(receiverType.typeId, SNumericTypeMethods.BitwiseXorMethod.methodId) + builder.mkMethodCall(x.asNumValue, m, IndexedSeq(y)) + + case Def(ApplyBinOpDiffArgs(op, xSym, ySym)) if op.isInstanceOf[NumericShiftLeft[_]] => + val Seq(x, y) = Seq(xSym, ySym).map(recurse) + val receiverType = x.asNumValue.tpe.asNumTypeOrElse(error(s"Expected numeric type, got: ${x.tpe}")) + val m = SMethod.fromIds(receiverType.typeId, SNumericTypeMethods.ShiftLeftMethod.methodId) + builder.mkMethodCall(x.asNumValue, m, IndexedSeq(y)) + + case Def(ApplyBinOpDiffArgs(op, xSym, ySym)) if op.isInstanceOf[NumericShiftRight[_]] => + val Seq(x, y) = Seq(xSym, ySym).map(recurse) + val receiverType = x.asNumValue.tpe.asNumTypeOrElse(error(s"Expected numeric type, got: ${x.tpe}")) + val m = SMethod.fromIds(receiverType.typeId, SNumericTypeMethods.ShiftRightMethod.methodId) + builder.mkMethodCall(x.asNumValue, m, IndexedSeq(y)) + + case Def(ApplyBinOp(IsArithOp(opCode), xSym, ySym)) => val Seq(x, y) = Seq(xSym, ySym).map(recurse) mkArith(x.asNumValue, y.asNumValue, opCode) @@ -290,13 +346,10 @@ trait TreeBuilding extends Base { IR: IRContext => mkExtractAmount(box.asBox) case BoxM.propositionBytes(In(box)) => mkExtractScriptBytes(box.asBox) - case BoxM.getReg(In(box), regId, _) => + case BoxM.getReg(In(box), regId, _) if regId.isConst => val tpe = elemToSType(s.elem).asOption - if (regId.isConst) - mkExtractRegisterAs(box.asBox, ErgoBox.allRegisters(valueFromRep(regId)), tpe) - else - error(s"Non constant expressions (${regId.node}) are not supported in getReg") - case BoxM.creationInfo(In(box)) => + mkExtractRegisterAs(box.asBox, ErgoBox.allRegisters(valueFromRep(regId)), tpe) + case BoxM.creationInfo(In(box)) => mkExtractCreationInfo(box.asBox) case BoxM.id(In(box)) => mkExtractId(box.asBox) @@ -399,13 +452,14 @@ trait TreeBuilding extends Base { IR: IRContext => mkMultiplyGroup(obj.asGroupElement, arg.asGroupElement) // Fallback MethodCall rule: should be the last in this list of cases - case Def(MethodCall(objSym, m, argSyms, _)) => + case Def(mc @ MethodCall(objSym, m, argSyms, _)) => val obj = recurse[SType](objSym) val args = argSyms.collect { case argSym: Sym => recurse[SType](argSym) } MethodsContainer.getMethod(obj.tpe, m.getName) match { case Some(method) => - val specMethod = method.specializeFor(obj.tpe, args.map(_.tpe)) - builder.mkMethodCall(obj, specMethod, args.toIndexedSeq, Map()) + val typeSubst = mc.typeSubst + val specMethod = method.specializeFor(obj.tpe, args.map(_.tpe)).withConcreteTypes(typeSubst) + builder.mkMethodCall(obj, specMethod, args.toIndexedSeq, typeSubst) case None => error(s"Cannot find method ${m.getName} in object $obj") } diff --git a/sc/shared/src/main/scala/sigma/compiler/ir/primitives/NumericOps.scala b/sc/shared/src/main/scala/sigma/compiler/ir/primitives/NumericOps.scala index 4e732bbb5f..5b858d8586 100644 --- a/sc/shared/src/main/scala/sigma/compiler/ir/primitives/NumericOps.scala +++ b/sc/shared/src/main/scala/sigma/compiler/ir/primitives/NumericOps.scala @@ -14,6 +14,7 @@ trait NumericOps extends Base { self: IRContext => def unary_- : Ref[T] = NumericNegate(n)(x.elem).apply(x) def toInt: Ref[Int] = NumericToInt(n).apply(x) def toLong: Ref[Long] = NumericToLong(n).apply(x) + def toBigEndianBytes: Ref[Coll[Byte]] = NumericToBigEndianBytes(n).apply(x) } /** Extension methods over `Ref[T]` where T is instance of ExactIntegral type-class. */ @@ -46,6 +47,27 @@ trait NumericOps extends Base { self: IRContext => override def applySeq(x: T, y: T): T = n.times(x, y) } + /** Descriptor of unary `ToBits` conversion operation. */ + case class NumericBitwiseOr[T: Elem](n: ExactNumeric[T]) extends EndoBinOp[T]("|") { + override def applySeq(x: T, y: T): T = n.bitwiseOr(x, y) + } + + case class NumericBitwiseAnd[T: Elem](n: ExactNumeric[T]) extends EndoBinOp[T]("&") { + override def applySeq(x: T, y: T): T = n.bitwiseAnd(x, y) + } + + case class NumericBitwiseXor[T: Elem](n: ExactNumeric[T]) extends EndoBinOp[T]("^") { + override def applySeq(x: T, y: T): T = n.bitwiseXor(x, y) + } + + case class NumericShiftLeft[T: Elem](n: ExactNumeric[T]) extends BinDiffArgsOp[T, Int]("<<") { + override def applySeq(x: T, y: Int): T = n.shiftLeft(x, y) + } + + case class NumericShiftRight[T: Elem](n: ExactNumeric[T]) extends BinDiffArgsOp[T, Int](">>") { + override def applySeq(x: T, y: Int): T = n.shiftRight(x, y) + } + /** Base class for descriptors of binary division operations. */ abstract class DivOp[T: Elem](opName: String, n: ExactIntegral[T]) extends EndoBinOp[T](opName) { override def shouldPropagate(lhs: T, rhs: T) = rhs != n.zero @@ -66,6 +88,28 @@ trait NumericOps extends Base { self: IRContext => override def applySeq(x: T): Long = n.toLong(x) } + import Coll._ + /** Descriptor of unary `ToBigEndianBytes` conversion operation. */ + case class NumericToBigEndianBytes[T](n: ExactNumeric[T]) + extends UnOp[T, Coll[Byte]]("ToBigEndianBytes")(element[Coll[Byte]]) { + override def applySeq(x: T): Coll[Byte] = { + liftableColl(Liftables.ByteIsLiftable).lift(n.toBigEndianBytes(x)) + } + } + + /** Descriptor of unary `ToBits` conversion operation. */ + case class NumericToBits[T](n: ExactNumeric[T]) + extends UnOp[T, Coll[Boolean]]("ToBits")(element[Coll[Boolean]]) { + override def applySeq(x: T): Coll[Boolean] = { + liftableColl(Liftables.BooleanIsLiftable).lift(n.toBits(x)) + } + } + + /** Descriptor of unary `BitwiseInverse` conversion operation. */ + case class NumericBitwiseInverse[T: Elem](n: ExactNumeric[T]) extends UnOp[T, T]("~") { + override def applySeq(x: T): T = n.bitwiseInverse(x) + } + /** Descriptor of binary `/` operation (integral division). */ case class IntegralDivide[T](i: ExactIntegral[T])(implicit elem: Elem[T]) extends DivOp[T]("/", i) { override def applySeq(x: T, y: T): T = i.quot(x, y) diff --git a/sc/shared/src/main/scala/sigma/compiler/ir/primitives/UnBinOps.scala b/sc/shared/src/main/scala/sigma/compiler/ir/primitives/UnBinOps.scala index 23f8bc3800..b55596052e 100644 --- a/sc/shared/src/main/scala/sigma/compiler/ir/primitives/UnBinOps.scala +++ b/sc/shared/src/main/scala/sigma/compiler/ir/primitives/UnBinOps.scala @@ -45,6 +45,30 @@ trait UnBinOps extends Base { self: IRContext => def shouldPropagate(lhs: A, rhs: A) = true } + /** Base class for descriptors of binary operations where arguments are of different types. */ + abstract class BinDiffArgsOp[A, B](val opName: String)(implicit val eResult: Elem[A]) { + override def toString = opName + + /** Called as part of graph interpretation to execute the given binary operation. + * @param x operation argument + * @param y operation argument + * @return result of applying this operation to (x, y) + */ + def applySeq(x: A, y: B): A + + /** Builds a new graph node by applying this operation to the given arguments. */ + def apply(lhs: Ref[A], rhs: Ref[B]) = ApplyBinOpDiffArgs(this, lhs, rhs) + + /** Builds a new graph node by applying this operation to the given arguments. + * This is a short-cuting (aka lazy) version of the operation, where the lazyness is + * represented by Thunk. + */ + def applyLazy(lhs: Ref[A], rhs: Ref[Thunk[B]]) = ApplyBinOpDiffArgsLazy(this, lhs, rhs) + + /** Whether the constants should be propagated through this operations by rewriting. */ + def shouldPropagate(lhs: A, rhs: B) = true + } + type EndoUnOp[A] = UnOp[A, A] type EndoBinOp[A] = BinOp[A, A] @@ -68,6 +92,19 @@ trait UnBinOps extends Base { self: IRContext => override def transform(t: Transformer): Def[R] = ApplyBinOpLazy[A,R](op, t(lhs), t(rhs)) } + /** Graph node which represents application of the given binary operation to the given arguments of different types + * where the second argument is lazy. */ + case class ApplyBinOpDiffArgsLazy[A, B](op: BinDiffArgsOp[A, B], lhs: Ref[A], rhs: Ref[Thunk[B]]) extends BaseDef[A]()(op.eResult) { + override def toString = s"$lhs $op { $rhs }" + override def transform(t: Transformer): Def[A] = ApplyBinOpDiffArgsLazy[A, B](op, t(lhs), t(rhs)) + } + + /** Graph node which represents application of the given binary operation to the given arguments of different types. */ + case class ApplyBinOpDiffArgs[A, B](op: BinDiffArgsOp[A, B], lhs: Ref[A], rhs: Ref[B]) extends BaseDef[A]()(op.eResult) { + override def toString = s"$op($lhs, $rhs)" + override def transform(t: Transformer): Def[A] = ApplyBinOpDiffArgs[A, B](op, t(lhs), t(rhs)) + } + /** Overridable constructor of an unary operation node. */ def applyUnOp[A, R](op: UnOp[A, R], arg: Ref[A]): Ref[R] = ApplyUnOp(op, arg) diff --git a/sc/shared/src/main/scala/sigma/compiler/ir/wrappers/sigma/SigmaDslUnit.scala b/sc/shared/src/main/scala/sigma/compiler/ir/wrappers/sigma/SigmaDslUnit.scala index 2a6a341686..9a68197520 100644 --- a/sc/shared/src/main/scala/sigma/compiler/ir/wrappers/sigma/SigmaDslUnit.scala +++ b/sc/shared/src/main/scala/sigma/compiler/ir/wrappers/sigma/SigmaDslUnit.scala @@ -78,6 +78,7 @@ import scalan._ def powNonce: Ref[Coll[Byte]]; def powDistance: Ref[BigInt]; def votes: Ref[Coll[Byte]] + def checkPow: Ref[Boolean] }; trait Context extends Def[Context] { def OUTPUTS: Ref[Coll[Box]]; @@ -114,6 +115,7 @@ import scalan._ /** This method will be used in v6.0 to handle CreateAvlTree operation in GraphBuilding */ def avlTree(operationFlags: Ref[Byte], digest: Ref[Coll[Byte]], keyLength: Ref[Int], valueLengthOpt: Ref[WOption[Int]]): Ref[AvlTree]; def xor(l: Ref[Coll[Byte]], r: Ref[Coll[Byte]]): Ref[Coll[Byte]] + def serialize[T](value: Ref[T]): Ref[Coll[Byte]] }; trait CostModelCompanion; trait BigIntCompanion; diff --git a/sc/shared/src/main/scala/sigma/compiler/ir/wrappers/sigma/impl/SigmaDslImpl.scala b/sc/shared/src/main/scala/sigma/compiler/ir/wrappers/sigma/impl/SigmaDslImpl.scala index c113cb7de3..85448f4512 100644 --- a/sc/shared/src/main/scala/sigma/compiler/ir/wrappers/sigma/impl/SigmaDslImpl.scala +++ b/sc/shared/src/main/scala/sigma/compiler/ir/wrappers/sigma/impl/SigmaDslImpl.scala @@ -8,6 +8,8 @@ import sigma.compiler.ir.wrappers.sigma.impl.SigmaDslDefs import scala.collection.compat.immutable.ArraySeq package impl { + import sigma.Evaluation + import sigma.ast.SType.tT import sigma.compiler.ir.meta.ModuleInfo import sigma.compiler.ir.wrappers.sigma.SigmaDsl import sigma.compiler.ir.{Base, GraphIRReflection, IRContext} @@ -620,10 +622,11 @@ object Box extends EntityObject("Box") { } override def getReg[T](i: Ref[Int])(implicit cT: Elem[T]): Ref[WOption[T]] = { + val st = Evaluation.rtypeToSType(cT.sourceType) asRep[WOption[T]](mkMethodCall(self, BoxClass.getMethod("getReg", classOf[Sym], classOf[Elem[_]]), Array[AnyRef](i, cT), - true, false, element[WOption[T]])) + true, false, element[WOption[T]], Map(tT -> st) )) } override def tokens: Ref[Coll[(Coll[Byte], Long)]] = { @@ -695,10 +698,11 @@ object Box extends EntityObject("Box") { } def getReg[T](i: Ref[Int])(implicit cT: Elem[T]): Ref[WOption[T]] = { + val st = Evaluation.rtypeToSType(cT.sourceType) asRep[WOption[T]](mkMethodCall(source, BoxClass.getMethod("getReg", classOf[Sym], classOf[Elem[_]]), Array[AnyRef](i, cT), - true, true, element[WOption[T]])) + true, true, element[WOption[T]], Map(tT -> st))) } def tokens: Ref[Coll[(Coll[Byte], Long)]] = { @@ -1368,6 +1372,13 @@ object Header extends EntityObject("Header") { ArraySeq.empty, true, false, element[Coll[Byte]])) } + + override def checkPow: Ref[Boolean] = { + asRep[Boolean](mkMethodCall(self, + HeaderClass.getMethod("checkPow"), + ArraySeq.empty, + true, false, element[Boolean])) + } } implicit object LiftableHeader @@ -1492,6 +1503,13 @@ object Header extends EntityObject("Header") { ArraySeq.empty, true, true, element[Coll[Byte]])) } + + def checkPow: Ref[Boolean] = { + asRep[Boolean](mkMethodCall(source, + HeaderClass.getMethod("checkPow"), + ArraySeq.empty, + true, true, element[Boolean])) + } } // entityUnref: single unref method for each type family @@ -1509,7 +1527,7 @@ object Header extends EntityObject("Header") { override protected def collectMethods: Map[RMethod, MethodDesc] = { super.collectMethods ++ Elem.declaredMethods(RClass(classOf[Header]), RClass(classOf[SHeader]), Set( - "id", "version", "parentId", "ADProofsRoot", "stateRoot", "transactionsRoot", "timestamp", "nBits", "height", "extensionRoot", "minerPk", "powOnetimePk", "powNonce", "powDistance", "votes" + "id", "version", "parentId", "ADProofsRoot", "stateRoot", "transactionsRoot", "timestamp", "nBits", "height", "extensionRoot", "minerPk", "powOnetimePk", "powNonce", "powDistance", "votes", "checkPow" )) } } @@ -1945,6 +1963,14 @@ object SigmaDslBuilder extends EntityObject("SigmaDslBuilder") { Array[AnyRef](l, r), true, false, element[Coll[Byte]])) } + + def serialize[T](value: Ref[T]): Ref[Coll[Byte]] = { + asRep[Coll[Byte]](mkMethodCall(self, + SigmaDslBuilderClass.getMethod("serialize", classOf[Sym]), + Array[AnyRef](value), + true, false, element[Coll[Byte]])) + } + } implicit object LiftableSigmaDslBuilder @@ -2104,6 +2130,13 @@ object SigmaDslBuilder extends EntityObject("SigmaDslBuilder") { Array[AnyRef](l, r), true, true, element[Coll[Byte]])) } + + def serialize[T](value: Ref[T]): Ref[Coll[Byte]] = { + asRep[Coll[Byte]](mkMethodCall(source, + SigmaDslBuilderClass.getMethod("serialize", classOf[Sym]), + Array[AnyRef](value), + true, true, element[Coll[Byte]])) + } } // entityUnref: single unref method for each type family diff --git a/sc/shared/src/main/scala/sigma/compiler/phases/SigmaBinder.scala b/sc/shared/src/main/scala/sigma/compiler/phases/SigmaBinder.scala index af5be938be..d4943ef892 100644 --- a/sc/shared/src/main/scala/sigma/compiler/phases/SigmaBinder.scala +++ b/sc/shared/src/main/scala/sigma/compiler/phases/SigmaBinder.scala @@ -105,6 +105,9 @@ class SigmaBinder(env: ScriptEnv, builder: SigmaBuilder, case a @ Apply(PKFunc.symNoType, args) => Some(PKFunc.irInfo.irBuilder(PKFunc.sym, args).withPropagatedSrcCtx(a.sourceContext)) + case a @ Apply(predefFuncRegistry.SerializeFunc.symNoType, args) => + Some(predefFuncRegistry.SerializeFunc.irInfo.irBuilder(PKFunc.sym, args).withPropagatedSrcCtx(a.sourceContext)) + case sel @ Select(obj, "isEmpty", _) => Some(mkLogicalNot(mkSelect(obj, "isDefined").asBoolValue).withPropagatedSrcCtx(sel.sourceContext)) diff --git a/sc/shared/src/main/scala/sigma/compiler/phases/SigmaTyper.scala b/sc/shared/src/main/scala/sigma/compiler/phases/SigmaTyper.scala index ac30a6cd0a..5f5e430e47 100644 --- a/sc/shared/src/main/scala/sigma/compiler/phases/SigmaTyper.scala +++ b/sc/shared/src/main/scala/sigma/compiler/phases/SigmaTyper.scala @@ -136,7 +136,7 @@ class SigmaTyper(val builder: SigmaBuilder, case Apply(ApplyTypes(sel @ Select(obj, n, _), Seq(rangeTpe)), args) => val newObj = assignType(env, obj) val newArgs = args.map(assignType(env, _)) - obj.tpe match { + newObj.tpe match { case p: SProduct => MethodsContainer.getMethod(p, n) match { case Some(method: SMethod) => @@ -160,10 +160,10 @@ class SigmaTyper(val builder: SigmaBuilder, case Some(method) => error(s"Don't know how to handle method $method in obj $p", sel.sourceContext) case None => - throw new MethodNotFound(s"Cannot find method '$n' in in the object $obj of Product type $p", obj.sourceContext.toOption) + throw new MethodNotFound(s"Cannot find method '$n' in in the object $newObj of Product type $p", newObj.sourceContext.toOption) } case _ => - error(s"Cannot get field '$n' in in the object $obj of non-product type ${obj.tpe}", sel.sourceContext) + error(s"Cannot get field '$n' in in the object $newObj of non-product type ${newObj.tpe}", sel.sourceContext) } case app @ Apply(sel @ Select(obj, n, _), args) => diff --git a/sc/shared/src/test/scala/sigma/LanguageSpecificationV5.scala b/sc/shared/src/test/scala/sigma/LanguageSpecificationV5.scala index af4f93d861..bd86feb82b 100644 --- a/sc/shared/src/test/scala/sigma/LanguageSpecificationV5.scala +++ b/sc/shared/src/test/scala/sigma/LanguageSpecificationV5.scala @@ -11,6 +11,7 @@ import scorex.crypto.hash.{Blake2b256, Digest32} import scorex.util.ModifierId import sigma.Extensions.{ArrayOps, CollOps} import sigma.VersionContext.V6SoftForkVersion +import sigma.VersionContext.JitActivationVersion import sigma.ast.ErgoTree.{HeaderType, ZeroHeader} import sigma.ast.SCollection._ import sigma.ast.syntax._ @@ -956,6 +957,7 @@ class LanguageSpecificationV5 extends LanguageSpecificationBase { suite => swapArgs(LE_cases, cost = 1768, newCostDetails = binaryRelationCostDetails(GE, SByte), expectedV3Costs = Seq.fill(4)(2012)), ">=", GE.apply)(_ >= _) } + property("Short methods equivalence") { SShort.upcast(0.toShort) shouldBe 0.toShort // boundary test case SShort.downcast(0.toShort) shouldBe 0.toShort // boundary test case @@ -4292,7 +4294,7 @@ class LanguageSpecificationV5 extends LanguageSpecificationBase { suite => property("Header properties equivalence") { verifyCases( Seq((h1, Expected(Success( - Helpers.decodeBytes("957f008001808080ffe4ffffc8f3802401df40006aa05e017fa8d3f6004c804a")), + Helpers.decodeBytes("cea31f0e0a794b103f65f8296a22ac8ff214e1bc75442186b90df4844c978e81")), cost = 1766, methodCostDetails(SHeaderMethods.idMethod, 10), 1766, Seq.fill(4)(2002)))), existingPropTest("id", { (x: Header) => x.id })) @@ -4430,18 +4432,10 @@ class LanguageSpecificationV5 extends LanguageSpecificationBase { suite => ) val header = CHeader( - Helpers.decodeBytes("1c597f88969600d2fffffdc47f00d8ffc555a9e85001000001c505ff80ff8f7f"), 0.toByte, Helpers.decodeBytes("7a7fe5347f09017818010062000001807f86808000ff7f66ffb07f7ad27f3362"), Helpers.decodeBytes("c1d70ad9b1ffc1fb9a715fff19807f2401017fcd8b73db017f1cff77727fff08"), - CAvlTree( - AvlTreeData( - ErgoAlgos.decodeUnsafe("54d23dd080006bdb56800100356080935a80ffb77e90b800057f00661601807f17").toColl, - AvlTreeFlags(true, true, false), - 2147483647, - None - ) - ), + Helpers.decodeBytes("54d23dd080006bdb56800100356080935a80ffb77e90b800057f00661601807f17"), Helpers.decodeBytes("5e7f1164ccd0990080c501fc0e0181cb387fc17f00ff00c7d5ff767f91ff5e68"), -7421721754642387858L, -4826493284887861030L, @@ -4451,7 +4445,8 @@ class LanguageSpecificationV5 extends LanguageSpecificationBase { suite => Helpers.decodeGroupElement("034e2d3b5f9e409e3ae8a2e768340760362ca33764eda5855f7a43487f14883300"), Helpers.decodeBytes("974651c9efff7f00"), CBigInt(new BigInteger("478e827dfa1e4b57", 16)), - Helpers.decodeBytes("01ff13") + Helpers.decodeBytes("01ff13"), + Colls.emptyColl ) val ctx = CContext( @@ -4459,7 +4454,7 @@ class LanguageSpecificationV5 extends LanguageSpecificationBase { suite => headers = Coll[Header](header), preHeader = CPreHeader( 0.toByte, - Helpers.decodeBytes("1c597f88969600d2fffffdc47f00d8ffc555a9e85001000001c505ff80ff8f7f"), + header.id, -755484979487531112L, 9223372036854775807L, 11, @@ -9827,30 +9822,6 @@ class LanguageSpecificationV5 extends LanguageSpecificationBase { suite => } } - property("higher order lambdas") { - val f = existingFeature( - { (xs: Coll[Int]) => - val inc = { (x: Int) => x + 1 } - - def apply(in: (Int => Int, Int)) = in._1(in._2) - - xs.map { (x: Int) => apply((inc, x)) } - }, - """{(xs: Coll[Int]) => - | val inc = { (x: Int) => x + 1 } - | def apply(in: (Int => Int, Int)) = in._1(in._2) - | xs.map { (x: Int) => apply((inc, x)) } - | } - |""".stripMargin - ) - - // TODO v6.0: Add support of SFunc in TypeSerializer (see https://github.com/ScorexFoundation/sigmastate-interpreter/issues/847) - assertExceptionThrown( - f.verifyCase(Coll[Int](), Expected(Success(Coll[Int]()), 0)), - exceptionLike[MatchError]("(SInt$) => SInt$ (of class sigma.ast.SFunc)") - ) - } - override protected def afterAll(): Unit = { printDebug(CErgoTreeEvaluator.DefaultProfiler.generateReport) printDebug("==========================================================") diff --git a/sc/shared/src/test/scala/sigma/LanguageSpecificationV6.scala b/sc/shared/src/test/scala/sigma/LanguageSpecificationV6.scala index 382c47403c..7605043cea 100644 --- a/sc/shared/src/test/scala/sigma/LanguageSpecificationV6.scala +++ b/sc/shared/src/test/scala/sigma/LanguageSpecificationV6.scala @@ -1,17 +1,27 @@ package sigma +import org.ergoplatform.ErgoHeader +import scorex.util.encode.Base16 +import sigma.VersionContext.V6SoftForkVersion +import org.ergoplatform.ErgoBox +import org.ergoplatform.ErgoBox.Token +import scorex.util.ModifierId +import scorex.utils.{Ints, Longs, Shorts} import sigma.ast.ErgoTree.ZeroHeader import sigma.ast.SCollection.SByteArray import sigma.ast.syntax.TrueSigmaProp -import sigma.ast._ -import sigma.data.{CBigInt, ExactNumeric} +import sigma.ast.{SInt, _} +import sigma.data.{CBigInt, CBox, CHeader, ExactNumeric} import sigma.eval.{CostDetails, SigmaDsl, TracedCost} +import sigma.serialization.ValueCodes.OpCode +import sigma.data.{RType} import sigma.util.Extensions.{BooleanOps, ByteOps, IntOps, LongOps} import sigmastate.exceptions.MethodNotFound +import sigmastate.utils.Extensions.ByteOpsForSigma import sigmastate.utils.Helpers import java.math.BigInteger -import scala.util.Success +import scala.util.{Failure, Success} /** This suite tests all operations for v6.0 version of the language. * The base classes establish the infrastructure for the tests. @@ -21,9 +31,82 @@ import scala.util.Success class LanguageSpecificationV6 extends LanguageSpecificationBase { suite => override def languageVersion: Byte = VersionContext.V6SoftForkVersion + implicit override def evalSettings = super.evalSettings.copy(printTestVectors = true) + + def mkSerializeFeature[A: RType]: Feature[A, Coll[Byte]] = { + val tA = RType[A] + val tpe = Evaluation.rtypeToSType(tA) + newFeature( + (x: A) => SigmaDsl.serialize(x), + s"{ (x: ${tA.name}) => serialize(x) }", + expectedExpr = FuncValue( + Array((1, tpe)), + MethodCall( + Global, + SGlobalMethods.serializeMethod.withConcreteTypes(Map(STypeVar("T") -> tpe)), + Array(ValUse(1, tpe)), + Map() + ) + ), + sinceVersion = VersionContext.V6SoftForkVersion) + } + + val baseTrace = Array( + FixedCostItem(Apply), + FixedCostItem(FuncValue), + FixedCostItem(GetVar), + FixedCostItem(OptionGet), + FixedCostItem(FuncValue.AddToEnvironmentDesc, FixedCost(JitCost(5))) + ) + + property("Global.serialize[Byte]") { + lazy val serializeByte = mkSerializeFeature[Byte] + val expectedCostTrace = TracedCost( + baseTrace ++ Array( + FixedCostItem(Global), + FixedCostItem(MethodCall), + FixedCostItem(ValUse), + FixedCostItem(NamedDesc("SigmaByteWriter.startWriter"), FixedCost(JitCost(10))), + FixedCostItem(NamedDesc("SigmaByteWriter.put"), FixedCost(JitCost(1))) + ) + ) + val cases = Seq( + (-128.toByte, Expected(Success(Coll(-128.toByte)), expectedCostTrace)), + (-1.toByte, Expected(Success(Coll(-1.toByte)), expectedCostTrace)), + (0.toByte, Expected(Success(Coll(0.toByte)), expectedCostTrace)), + (1.toByte, Expected(Success(Coll(1.toByte)), expectedCostTrace)), + (127.toByte, Expected(Success(Coll(127.toByte)), expectedCostTrace)) + ) + verifyCases(cases, serializeByte, preGeneratedSamples = None) + } + + property("Global.serialize[Short]") { + lazy val serializeShort = mkSerializeFeature[Short] + val expectedCostTrace = TracedCost( + baseTrace ++ Array( + FixedCostItem(Global), + FixedCostItem(MethodCall), + FixedCostItem(ValUse), + FixedCostItem(NamedDesc("SigmaByteWriter.startWriter"), FixedCost(JitCost(10))), + FixedCostItem(NamedDesc("SigmaByteWriter.putNumeric"), FixedCost(JitCost(3))) + ) + ) + val cases = Seq( + (Short.MinValue, Expected(Success(Coll[Byte](0xFF.toByte, 0xFF.toByte, 0x03.toByte)), expectedCostTrace)), + (-1.toShort, Expected(Success(Coll(1.toByte)), expectedCostTrace)), + (0.toShort, Expected(Success(Coll(0.toByte)), expectedCostTrace)), + (1.toShort, Expected(Success(Coll(2.toByte)), expectedCostTrace)), + (Short.MaxValue, Expected(Success(Coll(-2.toByte, -1.toByte, 3.toByte)), expectedCostTrace)) + ) + verifyCases(cases, serializeShort, preGeneratedSamples = None) + } + + // TODO v6.0: implement serialization roundtrip tests after merge with deserializeTo + + property("Boolean.toByte") { val toByte = newFeature((x: Boolean) => x.toByte, "{ (x: Boolean) => x.toByte }", - sinceVersion = VersionContext.V6SoftForkVersion + sinceVersion = V6SoftForkVersion ) val cases = Seq( @@ -42,129 +125,803 @@ class LanguageSpecificationV6 extends LanguageSpecificationBase { suite => testCases(cases, toByte) } - property("Byte methods equivalence (new features)") { - // TODO v6.0: implement as part of https://github.com/ScorexFoundation/sigmastate-interpreter/issues/474 - if (activatedVersionInTests < VersionContext.V6SoftForkVersion) { - // NOTE, for such versions the new features are not supported - // which is checked below + property("Byte methods - 6.0 features") { - lazy val toAbs = newFeature((x: Byte) => x.toAbs, "{ (x: Byte) => x.toAbs }", - sinceVersion = VersionContext.V6SoftForkVersion) + lazy val bitOr = newFeature( + { (x: (Byte, Byte)) => (x._1 | x._2).toByteExact }, + "{ (x: (Byte, Byte)) => x._1.bitwiseOr(x._2) }", + FuncValue( + Array((1, SPair(SByte, SByte))), + MethodCall.typed[Value[SByte.type]]( + SelectField.typed[Value[SByte.type]](ValUse(1, SPair(SByte, SByte)), 1.toByte), + SByteMethods.v6Methods.find(_.name == "bitwiseOr").get, + Vector(SelectField.typed[Value[SByte.type]](ValUse(1, SPair(SByte, SByte)),2.toByte)), + Map() + ) + ), + sinceVersion = V6SoftForkVersion) - lazy val compareTo = newFeature( - (x: (Byte, Byte)) => x._1.compareTo(x._2), - "{ (x: (Byte, Byte)) => x._1.compareTo(x._2) }", - sinceVersion = VersionContext.V6SoftForkVersion) + verifyCases( + Seq( + (1.toByte, 2.toByte) -> new Expected(ExpectedResult(Success(3.toByte), None)) + ), + bitOr + ) - lazy val bitOr = newFeature( - { (x: (Byte, Byte)) => (x._1 | x._2).toByteExact }, - "{ (x: (Byte, Byte)) => (x._1 | x._2) }", - sinceVersion = VersionContext.V6SoftForkVersion) + lazy val bitNot = newFeature( + { (x: Byte) => (~x).toByteExact }, + "{ (x: Byte) => x.bitwiseInverse }", + FuncValue( + Array((1, SByte)), + MethodCall.typed[Value[SByte.type]]( + ValUse(1, SByte), + SByteMethods.v6Methods.find(_.name == "bitwiseInverse").get, + Vector(), + Map() + ) + ), + sinceVersion = V6SoftForkVersion) - lazy val bitAnd = newFeature( - { (x: (Byte, Byte)) => (x._1 & x._2).toByteExact }, - "{ (x: (Byte, Byte)) => (x._1 & x._2) }", - sinceVersion = VersionContext.V6SoftForkVersion) + verifyCases( + Seq( + 1.toByte -> new Expected(ExpectedResult(Success((-2).toByte), None)) + ), + bitNot + ) - forAll { x: Byte => - Seq(toAbs).foreach(f => f.checkEquality(x)) - } + lazy val bitAnd = newFeature( + { (x: (Byte, Byte)) => (x._1 & x._2).toByteExact }, + "{ (x: (Byte, Byte)) => x._1.bitwiseAnd(x._2) }", + FuncValue( + Array((1, SPair(SByte, SByte))), + MethodCall.typed[Value[SByte.type]]( + SelectField.typed[Value[SByte.type]](ValUse(1, SPair(SByte, SByte)), 1.toByte), + SByteMethods.v6Methods.find(_.name == "bitwiseAnd").get, + Vector(SelectField.typed[Value[SByte.type]](ValUse(1, SPair(SByte, SByte)),2.toByte)), + Map() + ) + ), + sinceVersion = V6SoftForkVersion) - forAll { x: (Byte, Byte) => - Seq(compareTo, bitOr, bitAnd).foreach(_.checkEquality(x)) - } - } - } + verifyCases( + Seq( + (3.toByte, 5.toByte) -> new Expected(ExpectedResult(Success(1.toByte), None)) + ), + bitAnd + ) - // TODO v6.0: enable as part of https://github.com/ScorexFoundation/sigmastate-interpreter/issues/474 - property("Short methods equivalence (new features)") { - if (activatedVersionInTests < VersionContext.V6SoftForkVersion) { - // NOTE, for such versions the new features are not supported - // which is checked below + lazy val bitXor = newFeature( + { (x: (Byte, Byte)) => (x._1 ^ x._2).toByteExact }, + "{ (x: (Byte, Byte)) => x._1.bitwiseXor(x._2) }", + FuncValue( + Array((1, SPair(SByte, SByte))), + MethodCall.typed[Value[SByte.type]]( + SelectField.typed[Value[SByte.type]](ValUse(1, SPair(SByte, SByte)), 1.toByte), + SByteMethods.v6Methods.find(_.name == "bitwiseXor").get, + Vector(SelectField.typed[Value[SByte.type]](ValUse(1, SPair(SByte, SByte)),2.toByte)), + Map() + ) + ), + sinceVersion = V6SoftForkVersion) - lazy val toAbs = newFeature((x: Short) => x.toAbs, "{ (x: Short) => x.toAbs }", - sinceVersion = VersionContext.V6SoftForkVersion) + verifyCases( + Seq( + (3.toByte, 5.toByte) -> new Expected(ExpectedResult(Success(6.toByte), None)) + ), + bitXor + ) + + lazy val toBigEndianBytes = newFeature( + { x: Byte => Coll(x) }, + "{ (x: Byte) => x.toBytes }", + FuncValue( + Array((1, SByte)), + MethodCall.typed[Value[SCollection[SByte.type]]]( + ValUse(1, SByte), + SByteMethods.getMethodByName("toBytes"), + Vector(), + Map() + ) + ), + sinceVersion = V6SoftForkVersion) + + verifyCases( + Seq( + 127.toByte -> new Expected(ExpectedResult(Success(Coll(127.toByte)), None)) + ), + toBigEndianBytes + ) + + def byte2Bools(b: Byte): Seq[Boolean] = + (0 to 7 map isBitSet(b)).reverse + + def isBitSet(byte: Byte)(bit: Int): Boolean = + ((byte >> bit) & 1) == 1 + + lazy val toBits = newFeature[Byte, Coll[Boolean]]( + { x: Byte => Colls.fromArray(byte2Bools(x).toArray) }, + "{ (x: Byte) => x.toBits }", + FuncValue( + Array((1, SByte)), + MethodCall.typed[Value[SCollection[SByte.type]]]( + ValUse(1, SByte), + SByteMethods.getMethodByName("toBits"), + Vector(), + Map() + ) + ), + sinceVersion = V6SoftForkVersion) + + verifyCases( + Seq( + 83.toByte -> new Expected(ExpectedResult(Success(Coll(false, true, false, true, false, false, true, true)), None)), + -55.toByte -> new Expected(ExpectedResult(Success(Coll(true, true, false, false, true, false, false, true)), None)), + -1.toByte -> new Expected(ExpectedResult(Success(Coll(true, true, true, true, true, true, true, true)), None)) + ), + toBits + ) + + lazy val shiftLeft = newFeature( + { (x: (Byte, Int)) => if(x._2 < 0 || x._2 >= 8) throw new IllegalArgumentException() else (x._1 << x._2).toByte }, + "{ (x: (Byte, Int)) => x._1.shiftLeft(x._2) }", + FuncValue( + Array((1, SPair(SByte, SInt))), + MethodCall.typed[Value[SByte.type]]( + SelectField.typed[Value[SByte.type]](ValUse(1, SPair(SByte, SInt)), 1.toByte), + SByteMethods.v6Methods.find(_.name == "shiftLeft").get, + Vector(SelectField.typed[Value[SInt.type]](ValUse(1, SPair(SByte, SInt)), 2.toByte)), + Map() + ) + ), + sinceVersion = V6SoftForkVersion) + + verifyCases( + Seq( + (3.toByte, 3) -> new Expected(ExpectedResult(Success(24.toByte), None)), + (3.toByte, 0) -> new Expected(ExpectedResult(Success(3.toByte), None)), + (3.toByte, -1) -> new Expected(ExpectedResult(Failure(new IllegalArgumentException()), None)), + (3.toByte, 8) -> new Expected(ExpectedResult(Failure(new IllegalArgumentException()), None)) + ), + shiftLeft, + preGeneratedSamples = Some(Seq()) + ) - lazy val compareTo = newFeature((x: (Short, Short)) => x._1.compareTo(x._2), - "{ (x: (Short, Short)) => x._1.compareTo(x._2) }", - sinceVersion = VersionContext.V6SoftForkVersion) + lazy val shiftRight = newFeature( + { (x: (Byte, Int)) => if(x._2 < 0 || x._2 >= 8) throw new IllegalArgumentException() else (x._1 >> x._2).toByte }, + "{ (x: (Byte, Int)) => x._1.shiftRight(x._2) }", + FuncValue( + Array((1, SPair(SByte, SInt))), + MethodCall.typed[Value[SByte.type]]( + SelectField.typed[Value[SByte.type]](ValUse(1, SPair(SByte, SInt)), 1.toByte), + SByteMethods.v6Methods.find(_.name == "shiftRight").get, + Vector(SelectField.typed[Value[SInt.type]](ValUse(1, SPair(SByte, SInt)), 2.toByte)), + Map() + ) + ), + sinceVersion = V6SoftForkVersion) + + verifyCases( + Seq( + (24.toByte, 3) -> new Expected(ExpectedResult(Success(3.toByte), None)), + (24.toByte, 0) -> new Expected(ExpectedResult(Success(24.toByte), None)), + (24.toByte, -1) -> new Expected(ExpectedResult(Failure(new IllegalArgumentException()), None)), + (24.toByte, 8) -> new Expected(ExpectedResult(Failure(new IllegalArgumentException()), None)) + ), + shiftRight, + preGeneratedSamples = Some(Seq()) + ) + } + + property("Short - 6.0 methods") { - lazy val bitOr = newFeature( + lazy val bitOr = newFeature( { (x: (Short, Short)) => (x._1 | x._2).toShortExact }, - "{ (x: (Short, Short)) => x._1 | x._2 }", - sinceVersion = VersionContext.V6SoftForkVersion) + "{ (x: (Short, Short)) => x._1.bitwiseOr(x._2) }", + FuncValue( + Array((1, SPair(SShort, SShort))), + MethodCall.typed[Value[SShort.type]]( + SelectField.typed[Value[SShort.type]](ValUse(1,SPair(SShort, SShort)), 1.toByte), + SShortMethods.v6Methods.find(_.name == "bitwiseOr").get, + Vector(SelectField.typed[Value[SShort.type]](ValUse(1, SPair(SShort, SShort)),2.toByte)), + Map() + ) + ), + sinceVersion = V6SoftForkVersion) - lazy val bitAnd = newFeature( + verifyCases( + Seq( + (1.toShort, 2.toShort) -> new Expected(ExpectedResult(Success(3.toShort), None)), + (1001.toShort, 2002.toShort) -> new Expected(ExpectedResult(Success(2043.toShort), None)) + ), + bitOr + ) + + lazy val bitNot = newFeature( + { (x: Short) => (~x).toShortExact }, + "{ (x: Short) => x.bitwiseInverse }", + FuncValue( + Array((1, SShort)), + MethodCall.typed[Value[SShort.type]]( + ValUse(1, SShort), + SShortMethods.v6Methods.find(_.name == "bitwiseInverse").get, + Vector(), + Map() + ) + ), + sinceVersion = V6SoftForkVersion) + + verifyCases( + Seq( + 1.toShort -> new Expected(ExpectedResult(Success((-2).toShort), None)), + 10001.toShort -> new Expected(ExpectedResult(Success((-10002).toShort), None)) + ), + bitNot + ) + + lazy val bitAnd = newFeature( { (x: (Short, Short)) => (x._1 & x._2).toShortExact }, - "{ (x: (Short, Short)) => x._1 & x._2 }", - sinceVersion = VersionContext.V6SoftForkVersion) + "{ (x: (Short, Short)) => x._1.bitwiseAnd(x._2) }", + FuncValue( + Array((1, SPair(SShort, SShort))), + MethodCall.typed[Value[SShort.type]]( + SelectField.typed[Value[SShort.type]](ValUse(1, SPair(SShort, SShort)), 1.toByte), + SShortMethods.v6Methods.find(_.name == "bitwiseAnd").get, + Vector(SelectField.typed[Value[SShort.type]](ValUse(1, SPair(SShort, SShort)),2.toByte)), + Map() + ) + ), + sinceVersion = V6SoftForkVersion) - forAll { x: Short => - Seq(toAbs).foreach(_.checkEquality(x)) - } - forAll { x: (Short, Short) => - Seq(compareTo, bitOr, bitAnd).foreach(_.checkEquality(x)) - } - } + verifyCases( + Seq( + (3.toShort, 5.toShort) -> new Expected(ExpectedResult(Success(1.toShort), None)), + (10001.toShort, 2202.toShort) -> new Expected(ExpectedResult(Success(16.toShort), None)) + ), + bitAnd + ) + + lazy val bitXor = newFeature( + { (x: (Short, Short)) => (x._1 ^ x._2).toShortExact }, + "{ (x: (Short, Short)) => x._1.bitwiseXor(x._2) }", + FuncValue( + Array((1, SPair(SShort, SShort))), + MethodCall.typed[Value[SShort.type]]( + SelectField.typed[Value[SShort.type]](ValUse(1, SPair(SShort, SShort)), 1.toByte), + SShortMethods.v6Methods.find(_.name == "bitwiseXor").get, + Vector(SelectField.typed[Value[SShort.type]](ValUse(1, SPair(SShort, SShort)),2.toByte)), + Map() + ) + ), + sinceVersion = V6SoftForkVersion) + + verifyCases( + Seq( + (3.toShort, 5.toShort) -> new Expected(ExpectedResult(Success(6.toShort), None)), + (10001.toShort, 2202.toShort) -> new Expected(ExpectedResult(Success(12171.toShort), None)) + ), + bitXor + ) + + lazy val toBigEndianBytes = newFeature[Short, Coll[Byte]]( + { x: Short => Colls.fromArray(Shorts.toByteArray(x)) }, + "{ (x: Short) => x.toBytes }", + FuncValue( + Array((1, SShort)), + MethodCall.typed[Value[SCollection[SShort.type]]]( + ValUse(1, SShort), + SShortMethods.getMethodByName("toBytes"), + Vector(), + Map() + ) + ), + sinceVersion = V6SoftForkVersion) + + verifyCases( + Seq( + 127.toShort -> new Expected(ExpectedResult(Success(Coll(0.toByte, 127.toByte)), None)), + Short.MaxValue -> new Expected(ExpectedResult(Success(Coll(127.toByte, (-1).toByte)), None)), + Short.MinValue -> new Expected(ExpectedResult(Success(Coll((-128).toByte, 0.toByte)), None)) + ), + toBigEndianBytes + ) + + def byte2Bools(b: Byte): Seq[Boolean] = + (0 to 7 map isBitSet(b)).reverse + + def isBitSet(byte: Byte)(bit: Int): Boolean = + ((byte >> bit) & 1) == 1 + + lazy val toBits = newFeature[Short, Coll[Boolean]]( + { x: Short => Colls.fromArray(Shorts.toByteArray(x)).flatMap(b => Colls.fromArray(byte2Bools(b).toArray)) }, + "{ (x: Short) => x.toBits }", + FuncValue( + Array((1, SShort)), + MethodCall.typed[Value[SCollection[SShort.type]]]( + ValUse(1, SShort), + SShortMethods.getMethodByName("toBits"), + Vector(), + Map() + ) + ), + sinceVersion = V6SoftForkVersion) + + verifyCases( + Seq( + 83.toShort -> new Expected(ExpectedResult(Success(Coll(false, false, false, false, false, false, false, false, false, true, false, true, false, false, true, true)), None)), + -55.toShort -> new Expected(ExpectedResult(Success(Coll(true, true, true, true, true, true, true, true, true, true, false, false, true, false, false, true)), None)), + -1.toShort-> new Expected(ExpectedResult(Success(Coll(true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true)), None)), + -10001.toShort-> new Expected(ExpectedResult(Success(Coll(true, true, false, true, true, false, false, false, true, true, true, false, true, true, true, true)), None)) + ), + toBits + ) + + lazy val shiftLeft = newFeature( + { (x: (Short, Int)) => if(x._2 < 0 || x._2 >= 16) throw new IllegalArgumentException() else (x._1 << x._2).toShort }, + "{ (x: (Short, Int)) => x._1.shiftLeft(x._2) }", + FuncValue( + Array((1, SPair(SShort, SInt))), + MethodCall.typed[Value[SShort.type]]( + SelectField.typed[Value[SShort.type]](ValUse(1, SPair(SShort, SInt)), 1.toByte), + SShortMethods.v6Methods.find(_.name == "shiftLeft").get, + Vector(SelectField.typed[Value[SInt.type]](ValUse(1, SPair(SShort, SInt)), 2.toByte)), + Map() + ) + ), + sinceVersion = V6SoftForkVersion) + + verifyCases( + Seq( + (3.toShort, 3) -> new Expected(ExpectedResult(Success(24.toShort), None)), + (3.toShort, 8) -> new Expected(ExpectedResult(Success(768.toShort), None)), + ((-2).toShort, 10) -> new Expected(ExpectedResult(Success((-2048).toShort), None)), + ((-2).toShort, 20) -> new Expected(ExpectedResult(Failure(new IllegalArgumentException()), None)), + (3.toShort, -1) -> new Expected(ExpectedResult(Failure(new IllegalArgumentException()), None)) + ), + shiftLeft, + preGeneratedSamples = Some(Seq()) + ) + + lazy val shiftRight = newFeature( + { (x: (Short, Int)) => if(x._2 < 0 || x._2 >= 16) throw new IllegalArgumentException() else (x._1 >> x._2).toShort }, + "{ (x: (Short, Int)) => x._1.shiftRight(x._2) }", + FuncValue( + Array((1, SPair(SShort, SInt))), + MethodCall.typed[Value[SShort.type]]( + SelectField.typed[Value[SShort.type]](ValUse(1, SPair(SShort, SInt)), 1.toByte), + SShortMethods.v6Methods.find(_.name == "shiftRight").get, + Vector(SelectField.typed[Value[SInt.type]](ValUse(1, SPair(SShort, SInt)), 2.toByte)), + Map() + ) + ), + sinceVersion = V6SoftForkVersion) + + verifyCases( + Seq( + (24.toShort, 3) -> new Expected(ExpectedResult(Success(3.toShort), None)), + (1600.toShort, 8) -> new Expected(ExpectedResult(Success(6.toShort), None)), + ((-3200).toShort, 8) -> new Expected(ExpectedResult(Success((-13).toShort), None)), + (3.toShort, -1) -> new Expected(ExpectedResult(Failure(new IllegalArgumentException()), None)), + (3.toShort, 16) -> new Expected(ExpectedResult(Failure(new IllegalArgumentException()), None)) + ), + shiftRight, + preGeneratedSamples = Some(Seq()) + ) } - property("Int methods equivalence (new features)") { - if (activatedVersionInTests < VersionContext.V6SoftForkVersion) { - // NOTE, for such versions the new features are not supported - // which is checked below - lazy val toAbs = newFeature((x: Int) => x.toAbs, "{ (x: Int) => x.toAbs }", - sinceVersion = VersionContext.V6SoftForkVersion) - lazy val compareTo = newFeature((x: (Int, Int)) => x._1.compareTo(x._2), - "{ (x: (Int, Int)) => x._1.compareTo(x._2) }", - sinceVersion = VersionContext.V6SoftForkVersion) - lazy val bitOr = newFeature( - { (x: (Int, Int)) => x._1 | x._2 }, - "{ (x: (Int, Int)) => x._1 | x._2 }", - sinceVersion = VersionContext.V6SoftForkVersion) - lazy val bitAnd = newFeature( + property("Int - 6.0 methods") { + + lazy val bitOr = newFeature( + { (x: (Int, Int)) => (x._1 | x._2)}, + "{ (x: (Int, Int)) => x._1.bitwiseOr(x._2) }", + FuncValue( + Array((1, SPair(SInt, SInt))), + MethodCall.typed[Value[SInt.type]]( + SelectField.typed[Value[SInt.type]](ValUse(1,SPair(SInt, SInt)), 1.toByte), + SIntMethods.v6Methods.find(_.name == "bitwiseOr").get, + Vector(SelectField.typed[Value[SInt.type]](ValUse(1, SPair(SInt, SInt)),2.toByte)), + Map() + ) + ), + sinceVersion = V6SoftForkVersion) + + verifyCases( + Seq( + (1, 2) -> new Expected(ExpectedResult(Success(3), None)), + (1001, 2002) -> new Expected(ExpectedResult(Success(2043), None)), + (100001, 20002) -> new Expected(ExpectedResult(Success(118435), None)) + ), + bitOr + ) + + lazy val bitNot = newFeature( + { (x: Int) => ~x }, + "{ (x: Int) => x.bitwiseInverse }", + FuncValue( + Array((1, SInt)), + MethodCall.typed[Value[SInt.type]]( + ValUse(1, SInt), + SIntMethods.v6Methods.find(_.name == "bitwiseInverse").get, + Vector(), + Map() + ) + ), + sinceVersion = V6SoftForkVersion) + + verifyCases( + Seq( + 1 -> new Expected(ExpectedResult(Success(-2), None)), + 10001 -> new Expected(ExpectedResult(Success(-10002), None)), + Int.MinValue -> new Expected(ExpectedResult(Success(Int.MaxValue), None)) + ), + bitNot + ) + + lazy val bitAnd = newFeature( { (x: (Int, Int)) => x._1 & x._2 }, - "{ (x: (Int, Int)) => x._1 & x._2 }", - sinceVersion = VersionContext.V6SoftForkVersion) - forAll { x: Int => - Seq(toAbs).foreach(_.checkEquality(x)) - } - forAll { x: (Int, Int) => - Seq(compareTo, bitOr, bitAnd).foreach(_.checkEquality(x)) - } - } + "{ (x: (Int, Int)) => x._1.bitwiseAnd(x._2) }", + FuncValue( + Array((1, SPair(SInt, SInt))), + MethodCall.typed[Value[SInt.type]]( + SelectField.typed[Value[SInt.type]](ValUse(1, SPair(SInt, SInt)), 1.toByte), + SIntMethods.v6Methods.find(_.name == "bitwiseAnd").get, + Vector(SelectField.typed[Value[SInt.type]](ValUse(1, SPair(SInt, SInt)),2.toByte)), + Map() + ) + ), + sinceVersion = V6SoftForkVersion) + + verifyCases( + Seq( + (3, 5) -> new Expected(ExpectedResult(Success(1), None)), + (10001, 2202) -> new Expected(ExpectedResult(Success(16), None)), + (-10001, 200202) -> new Expected(ExpectedResult(Success(198666), None)) + ), + bitAnd + ) + + lazy val bitXor = newFeature( + { (x: (Int, Int)) => (x._1 ^ x._2) }, + "{ (x: (Int, Int)) => x._1.bitwiseXor(x._2) }", + FuncValue( + Array((1, SPair(SInt, SInt))), + MethodCall.typed[Value[SInt.type]]( + SelectField.typed[Value[SInt.type]](ValUse(1, SPair(SInt, SInt)), 1.toByte), + SIntMethods.v6Methods.find(_.name == "bitwiseXor").get, + Vector(SelectField.typed[Value[SInt.type]](ValUse(1, SPair(SInt, SInt)),2.toByte)), + Map() + ) + ), + sinceVersion = V6SoftForkVersion) + + verifyCases( + Seq( + (3, 5) -> new Expected(ExpectedResult(Success(6), None)), + (10001, 2202) -> new Expected(ExpectedResult(Success(12171), None)), + (-10001, 200202) -> new Expected(ExpectedResult(Success(-207131), None)) + ), + bitXor + ) + + lazy val toBigEndianBytes = newFeature[Int, Coll[Byte]]( + { x: Int => Colls.fromArray(Ints.toByteArray(x)) }, + "{ (x: Int) => x.toBytes }", + FuncValue( + Array((1, SInt)), + MethodCall.typed[Value[SCollection[SInt.type]]]( + ValUse(1, SInt), + SIntMethods.getMethodByName("toBytes"), + Vector(), + Map() + ) + ), + sinceVersion = V6SoftForkVersion) + + verifyCases( + Seq( + 127 -> new Expected(ExpectedResult(Success(Coll(0.toByte, 0.toByte, 0.toByte, 127.toByte)), None)), + Short.MaxValue.toInt -> new Expected(ExpectedResult(Success(Coll(0.toByte, 0.toByte, 127.toByte, (-1).toByte)), None)), + Short.MinValue.toInt -> new Expected(ExpectedResult(Success(Coll((-1).toByte, (-1).toByte, (-128).toByte, 0.toByte)), None)), + Int.MaxValue.toInt -> new Expected(ExpectedResult(Success(Coll(127.toByte, (-1).toByte, (-1).toByte, (-1).toByte)), None)) + ), + toBigEndianBytes + ) + + def byte2Bools(b: Byte): Seq[Boolean] = + (0 to 7 map isBitSet(b)).reverse + + def isBitSet(byte: Byte)(bit: Int): Boolean = + ((byte >> bit) & 1) == 1 + + lazy val toBits = newFeature[Int, Coll[Boolean]]( + { x: Int => Colls.fromArray(Ints.toByteArray(x)).flatMap(b => Colls.fromArray(byte2Bools(b).toArray)) }, + "{ (x: Int) => x.toBits }", + FuncValue( + Array((1, SInt)), + MethodCall.typed[Value[SCollection[SInt.type]]]( + ValUse(1, SInt), + SIntMethods.getMethodByName("toBits"), + Vector(), + Map() + ) + ), + sinceVersion = V6SoftForkVersion) + + verifyCases( + Seq( + 83 -> new Expected(ExpectedResult(Success(Coll(false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, true, false, true, false, false, true, true)), None)), + -55 -> new Expected(ExpectedResult(Success(Coll(true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, false, false, true, false, false, true)), None)), + -1 -> new Expected(ExpectedResult(Success(Coll(true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true)), None)), + -10001 -> new Expected(ExpectedResult(Success(Coll(true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, false, true, true, false, false, false, true, true, true, false, true, true, true, true)), None)) + ), + toBits + ) + + lazy val shiftLeft = newFeature( + { (x: (Int, Int)) => if(x._2 < 0 || x._2 >= 32) throw new IllegalArgumentException() else (x._1 << x._2) }, + "{ (x: (Int, Int)) => x._1.shiftLeft(x._2) }", + FuncValue( + Array((1, SPair(SInt, SInt))), + MethodCall.typed[Value[SInt.type]]( + SelectField.typed[Value[SInt.type]](ValUse(1, SPair(SInt, SInt)), 1.toByte), + SIntMethods.v6Methods.find(_.name == "shiftLeft").get, + Vector(SelectField.typed[Value[SInt.type]](ValUse(1, SPair(SInt, SInt)), 2.toByte)), + Map() + ) + ), + sinceVersion = V6SoftForkVersion) + + verifyCases( + Seq( + (3, 3) -> new Expected(ExpectedResult(Success(24), None)), + (3, 8) -> new Expected(ExpectedResult(Success(768), None)), + (-2, 10) -> new Expected(ExpectedResult(Success(-2048), None)), + (-222, 10) -> new Expected(ExpectedResult(Success(-227328), None)), + (-222, 32) -> new Expected(ExpectedResult(Failure(new IllegalArgumentException()), None)), + (-222, -1) -> new Expected(ExpectedResult(Failure(new IllegalArgumentException()), None)) + ), + shiftLeft, + preGeneratedSamples = Some(Seq()) + ) + + lazy val shiftRight = newFeature( + { (x: (Int, Int)) => if(x._2 < 0 || x._2 >= 32) throw new IllegalArgumentException() else (x._1 >> x._2) }, + "{ (x: (Int, Int)) => x._1.shiftRight(x._2) }", + FuncValue( + Array((1, SPair(SInt, SInt))), + MethodCall.typed[Value[SInt.type]]( + SelectField.typed[Value[SInt.type]](ValUse(1, SPair(SInt, SInt)), 1.toByte), + SIntMethods.v6Methods.find(_.name == "shiftRight").get, + Vector(SelectField.typed[Value[SInt.type]](ValUse(1, SPair(SInt, SInt)), 2.toByte)), + Map() + ) + ), + sinceVersion = V6SoftForkVersion) + + verifyCases( + Seq( + (24, 3) -> new Expected(ExpectedResult(Success(3), None)), + (1600, 8) -> new Expected(ExpectedResult(Success(6), None)), + (-3200, 8) -> new Expected(ExpectedResult(Success(-13), None)), + (-320019, 18) -> new Expected(ExpectedResult(Success(-2), None)), + (-320019, 32) -> new Expected(ExpectedResult(Failure(new IllegalArgumentException()), None)), + (-320019, -1) -> new Expected(ExpectedResult(Failure(new IllegalArgumentException()), None)) + ), + shiftRight, + preGeneratedSamples = Some(Seq()) + ) } - property("Long methods equivalence (new features)") { - if (activatedVersionInTests < VersionContext.V6SoftForkVersion) { - // NOTE, for such versions the new features are not supported - // which is checked below - lazy val toAbs = newFeature((x: Long) => x.toAbs, "{ (x: Long) => x.toAbs }", - sinceVersion = VersionContext.V6SoftForkVersion) - lazy val compareTo = newFeature((x: (Long, Long)) => x._1.compareTo(x._2), - "{ (x: (Long, Long)) => x._1.compareTo(x._2) }", - sinceVersion = VersionContext.V6SoftForkVersion) - - lazy val bitOr = newFeature( - { (x: (Long, Long)) => x._1 | x._2 }, - "{ (x: (Long, Long)) => x._1 | x._2 }", - sinceVersion = VersionContext.V6SoftForkVersion) - - lazy val bitAnd = newFeature( - { (x: (Long, Long)) => x._1 & x._2 }, - "{ (x: (Long, Long)) => x._1 & x._2 }", - sinceVersion = VersionContext.V6SoftForkVersion) + property("Long - 6.0 methods") { - forAll { x: Long => - Seq(toAbs).foreach(_.checkEquality(x)) - } - forAll { x: (Long, Long) => - Seq(compareTo, bitOr, bitAnd).foreach(_.checkEquality(x)) - } - } + lazy val bitOr = newFeature( + { (x: (Long, Long)) => (x._1 | x._2)}, + "{ (x: (Long, Long)) => x._1.bitwiseOr(x._2) }", + FuncValue( + Array((1, SPair(SLong, SLong))), + MethodCall.typed[Value[SLong.type]]( + SelectField.typed[Value[SLong.type]](ValUse(1,SPair(SLong, SLong)), 1.toByte), + SLongMethods.v6Methods.find(_.name == "bitwiseOr").get, + Vector(SelectField.typed[Value[SLong.type]](ValUse(1, SPair(SLong, SLong)),2.toByte)), + Map() + ) + ), + sinceVersion = V6SoftForkVersion) + verifyCases( + Seq( + (1L, 2L) -> new Expected(ExpectedResult(Success(3L), None)), + (1001L, 2002L) -> new Expected(ExpectedResult(Success(2043L), None)), + (100001L, 20002L) -> new Expected(ExpectedResult(Success(118435L), None)), + (1000010111L, -22L) -> new Expected(ExpectedResult(Success(-1L), None)) + ), + bitOr + ) + + lazy val bitNot = newFeature( + { (x: Long) => ~x }, + "{ (x: Long) => x.bitwiseInverse }", + FuncValue( + Array((1, SLong)), + MethodCall.typed[Value[SLong.type]]( + ValUse(1, SLong), + SLongMethods.v6Methods.find(_.name == "bitwiseInverse").get, + Vector(), + Map() + ) + ), + sinceVersion = V6SoftForkVersion) + + verifyCases( + Seq( + 1L -> new Expected(ExpectedResult(Success(-2L), None)), + 10001L -> new Expected(ExpectedResult(Success(-10002L), None)), + Int.MinValue.toLong -> new Expected(ExpectedResult(Success(Int.MaxValue.toLong), None)), + Long.MinValue -> new Expected(ExpectedResult(Success(Long.MaxValue), None)), + Long.MaxValue -> new Expected(ExpectedResult(Success(Long.MinValue), None)) + ), + bitNot + ) + + lazy val bitAnd = newFeature( + { (x: (Long, Long)) => x._1 & x._2 }, + "{ (x: (Long, Long)) => x._1.bitwiseAnd(x._2) }", + FuncValue( + Array((1, SPair(SLong, SLong))), + MethodCall.typed[Value[SLong.type]]( + SelectField.typed[Value[SLong.type]](ValUse(1, SPair(SLong, SLong)), 1.toByte), + SLongMethods.v6Methods.find(_.name == "bitwiseAnd").get, + Vector(SelectField.typed[Value[SLong.type]](ValUse(1, SPair(SLong, SLong)),2.toByte)), + Map() + ) + ), + sinceVersion = V6SoftForkVersion) + + verifyCases( + Seq( + (3L, 5L) -> new Expected(ExpectedResult(Success(1L), None)), + (10001L, 2202L) -> new Expected(ExpectedResult(Success(16L), None)), + (-10001L, 200202L) -> new Expected(ExpectedResult(Success(198666L), None)), + (1000010111L, -22L) -> new Expected(ExpectedResult(Success(1000010090L), None)) + ), + bitAnd + ) + + lazy val bitXor = newFeature( + { (x: (Long, Long)) => (x._1 ^ x._2) }, + "{ (x: (Long, Long)) => x._1.bitwiseXor(x._2) }", + FuncValue( + Array((1, SPair(SLong, SLong))), + MethodCall.typed[Value[SLong.type]]( + SelectField.typed[Value[SLong.type]](ValUse(1, SPair(SLong, SLong)), 1.toByte), + SLongMethods.v6Methods.find(_.name == "bitwiseXor").get, + Vector(SelectField.typed[Value[SLong.type]](ValUse(1, SPair(SLong, SLong)),2.toByte)), + Map() + ) + ), + sinceVersion = V6SoftForkVersion) + + verifyCases( + Seq( + (3L, 5L) -> new Expected(ExpectedResult(Success(6L), None)), + (10001L, 2202L) -> new Expected(ExpectedResult(Success(12171L), None)), + (-10001L, 200202L) -> new Expected(ExpectedResult(Success(-207131L), None)), + (1000010111L, -22L) -> new Expected(ExpectedResult(Success(-1000010091L), None)) + ), + bitXor + ) + + lazy val toBigEndianBytes = newFeature[Long, Coll[Byte]]( + { x: Long => Colls.fromArray(Longs.toByteArray(x)) }, + "{ (x: Long) => x.toBytes }", + FuncValue( + Array((1, SLong)), + MethodCall.typed[Value[SCollection[SLong.type]]]( + ValUse(1, SLong), + SLongMethods.getMethodByName("toBytes"), + Vector(), + Map() + ) + ), + sinceVersion = V6SoftForkVersion) + + verifyCases( + Seq( + 127L -> new Expected(ExpectedResult(Success(Coll(0.toByte, 0.toByte, 0.toByte, 0.toByte, 0.toByte, 0.toByte, 0.toByte, 127.toByte)), None)), + Short.MaxValue.toLong -> new Expected(ExpectedResult(Success(Coll(0.toByte, 0.toByte, 0.toByte, 0.toByte, 0.toByte, 0.toByte, 127.toByte, (-1).toByte)), None)), + Short.MinValue.toLong -> new Expected(ExpectedResult(Success(Coll((-1).toByte, (-1).toByte, (-1).toByte, (-1).toByte, (-1).toByte, (-1).toByte, (-128).toByte, 0.toByte)), None)), + Int.MaxValue.toLong -> new Expected(ExpectedResult(Success(Coll(0.toByte, 0.toByte, 0.toByte, 0.toByte, 127.toByte, (-1).toByte, (-1).toByte, (-1).toByte)), None)) + ), + toBigEndianBytes + ) + + def byte2Bools(b: Byte): Seq[Boolean] = + (0 to 7 map isBitSet(b)).reverse + + def isBitSet(byte: Byte)(bit: Int): Boolean = + ((byte >> bit) & 1) == 1 + + lazy val toBits = newFeature[Long, Coll[Boolean]]( + { x: Long => Colls.fromArray(Longs.toByteArray(x)).flatMap(b => Colls.fromArray(byte2Bools(b).toArray)) }, + "{ (x: Long) => x.toBits }", + FuncValue( + Array((1, SLong)), + MethodCall.typed[Value[SCollection[SLong.type]]]( + ValUse(1, SLong), + SLongMethods.getMethodByName("toBits"), + Vector(), + Map() + ) + ), + sinceVersion = V6SoftForkVersion) + + verifyCases( + Seq( + 83L -> new Expected(ExpectedResult(Success(Colls.fromArray(Array.fill(57)(false)).append(Coll(true, false, true, false, false, true, true))), None)), + -55L -> new Expected(ExpectedResult(Success(Colls.fromArray(Array.fill(58)(true)).append(Coll(false, false, true, false, false, true))), None)), + -1L -> new Expected(ExpectedResult(Success(Colls.fromArray(Array.fill(64)(true))), None)), + -10001L -> new Expected(ExpectedResult(Success(Colls.fromArray(Array.fill(50)(true)).append(Coll( false, true, true, false, false, false, true, true, true, false, true, true, true, true))), None)) + ), + toBits + ) + + lazy val shiftLeft = newFeature( + { (x: (Long, Int)) => if(x._2 < 0 || x._2 >= 32) throw new IllegalArgumentException() else (x._1 << x._2) }, + "{ (x: (Long, Int)) => x._1.shiftLeft(x._2) }", + FuncValue( + Array((1, SPair(SLong, SInt))), + MethodCall.typed[Value[SLong.type]]( + SelectField.typed[Value[SLong.type]](ValUse(1, SPair(SLong, SInt)), 1.toByte), + SLongMethods.v6Methods.find(_.name == "shiftLeft").get, + Vector(SelectField.typed[Value[SInt.type]](ValUse(1, SPair(SLong, SInt)), 2.toByte)), + Map() + ) + ), + sinceVersion = V6SoftForkVersion) + + verifyCases( + Seq( + (3L, 3) -> new Expected(ExpectedResult(Success(24L), None)), + (3L, 8) -> new Expected(ExpectedResult(Success(768L), None)), + (-2L, 10) -> new Expected(ExpectedResult(Success(-2048L), None)), + (-222L, 10) -> new Expected(ExpectedResult(Success(-227328L), None)), + (-222L, -1) -> new Expected(ExpectedResult(Failure(new IllegalArgumentException()), None)), + (-222L, 64) -> new Expected(ExpectedResult(Failure(new IllegalArgumentException()), None)) + ), + shiftLeft, + preGeneratedSamples = Some(Seq()) + ) + + lazy val shiftRight = newFeature( + { (x: (Long, Int)) => if(x._2 < 0 || x._2 >= 64) throw new IllegalArgumentException() else (x._1 >> x._2) }, + "{ (x: (Long, Int)) => x._1.shiftRight(x._2) }", + FuncValue( + Array((1, SPair(SLong, SInt))), + MethodCall.typed[Value[SLong.type]]( + SelectField.typed[Value[SLong.type]](ValUse(1, SPair(SLong, SInt)), 1.toByte), + SLongMethods.v6Methods.find(_.name == "shiftRight").get, + Vector(SelectField.typed[Value[SInt.type]](ValUse(1, SPair(SLong, SInt)), 2.toByte)), + Map() + ) + ), + sinceVersion = V6SoftForkVersion) + + verifyCases( + Seq( + (24L, 3) -> new Expected(ExpectedResult(Success(3L), None)), + (1600L, 8) -> new Expected(ExpectedResult(Success(6L), None)), + (-3200L, 8) -> new Expected(ExpectedResult(Success(-13L), None)), + (-320019L, 18) -> new Expected(ExpectedResult(Success(-2L), None)), + (-320019L, 63) -> new Expected(ExpectedResult(Success(-1L), None)), + (24L, -1) -> new Expected(ExpectedResult(Failure(new IllegalArgumentException()), None)) + ), + shiftRight, + preGeneratedSamples = Some(Seq()) + ) } - property("BigInt methods equivalence (new features)") { + property("BigInt - 6.0 features") { + import sigma.data.OrderingOps.BigIntOrdering + if (activatedVersionInTests < VersionContext.V6SoftForkVersion) { // The `Upcast(bigInt, SBigInt)` node is never produced by ErgoScript compiler, but is still valid ErgoTree. // Fixed in 6.0 @@ -179,50 +936,6 @@ class LanguageSpecificationV6 extends LanguageSpecificationBase { suite => SBigInt.downcast(CBigInt(new BigInteger("0", 16)).asInstanceOf[AnyVal]), _.getMessage.contains("Cannot downcast value") ) - } else { - forAll { x: BigInteger => - SBigInt.upcast(CBigInt(x).asInstanceOf[AnyVal]) shouldBe CBigInt(x) - SBigInt.downcast(CBigInt(x).asInstanceOf[AnyVal]) shouldBe CBigInt(x) - } - } - - if (activatedVersionInTests < VersionContext.V6SoftForkVersion) { - // NOTE, for such versions the new features are not supported - // which is checked below - val toByte = newFeature((x: BigInt) => x.toByte, - "{ (x: BigInt) => x.toByte }", - FuncValue(Vector((1, SBigInt)), Downcast(ValUse(1, SBigInt), SByte)), - sinceVersion = VersionContext.V6SoftForkVersion) - val toShort = newFeature((x: BigInt) => x.toShort, - "{ (x: BigInt) => x.toShort }", - FuncValue(Vector((1, SBigInt)), Downcast(ValUse(1, SBigInt), SShort)), - sinceVersion = VersionContext.V6SoftForkVersion) - val toInt = newFeature((x: BigInt) => x.toInt, - "{ (x: BigInt) => x.toInt }", - FuncValue(Vector((1, SBigInt)), Downcast(ValUse(1, SBigInt), SInt)), - sinceVersion = VersionContext.V6SoftForkVersion) - val toLong = newFeature((x: BigInt) => x.toLong, - "{ (x: BigInt) => x.toLong }", - FuncValue(Vector((1, SBigInt)), Downcast(ValUse(1, SBigInt), SLong)), - sinceVersion = VersionContext.V6SoftForkVersion) - lazy val toAbs = newFeature((x: BigInt) => x.toAbs, "{ (x: BigInt) => x.toAbs }", - sinceVersion = VersionContext.V6SoftForkVersion) - lazy val compareTo = newFeature((x: (BigInt, BigInt)) => x._1.compareTo(x._2), - "{ (x: (BigInt, BigInt)) => x._1.compareTo(x._2) }", - sinceVersion = VersionContext.V6SoftForkVersion) - lazy val bitOr = newFeature({ (x: (BigInt, BigInt)) => x._1 | x._2 }, - "{ (x: (BigInt, BigInt)) => x._1 | x._2 }", - sinceVersion = VersionContext.V6SoftForkVersion) - lazy val bitAnd = newFeature({ (x: (BigInt, BigInt)) => x._1 & x._2 }, - "{ (x: (BigInt, BigInt)) => x._1 & x._2 }", - sinceVersion = VersionContext.V6SoftForkVersion) - - forAll { x: BigInt => - Seq(toByte, toShort, toInt, toLong, toAbs).foreach(_.checkEquality(x)) - } - forAll { x: (BigInt, BigInt) => - Seq(compareTo, bitOr, bitAnd).foreach(_.checkEquality(x)) - } forAll { x: Long => assertExceptionThrown( @@ -249,6 +962,10 @@ class LanguageSpecificationV6 extends LanguageSpecificationBase { suite => ) } } else { + forAll { x: BigInteger => + SBigInt.upcast(CBigInt(x).asInstanceOf[AnyVal]) shouldBe CBigInt(x) + SBigInt.downcast(CBigInt(x).asInstanceOf[AnyVal]) shouldBe CBigInt(x) + } forAll { x: Long => SLong.downcast(CBigInt(new BigInteger(x.toString)).asInstanceOf[AnyVal]) shouldBe x } @@ -262,21 +979,241 @@ class LanguageSpecificationV6 extends LanguageSpecificationBase { suite => SShort.downcast(CBigInt(new BigInteger(x.toString)).asInstanceOf[AnyVal]) shouldBe x } } + + lazy val bitOr = newFeature[(BigInt, BigInt), BigInt]( + { (x: (BigInt, BigInt)) => (x._1 | x._2)}, + "{ (x: (BigInt, BigInt)) => x._1.bitwiseOr(x._2) }", + FuncValue( + Array((1, SPair(SBigInt, SBigInt))), + MethodCall.typed[Value[SBigInt.type]]( + SelectField.typed[Value[SBigInt.type]](ValUse(1,SPair(SBigInt, SBigInt)), 1.toByte), + SBigIntMethods.v6Methods.find(_.name == "bitwiseOr").get, + Vector(SelectField.typed[Value[SBigInt.type]](ValUse(1, SPair(SBigInt, SBigInt)),2.toByte)), + Map() + ) + ), + sinceVersion = V6SoftForkVersion) + + verifyCases( + Seq( + (CBigInt(BigInteger.valueOf(1)), CBigInt(BigInteger.valueOf(2))) -> new Expected(ExpectedResult(Success(CBigInt(BigInteger.valueOf(3))), None)), + (CBigInt(BigInteger.valueOf(1001)), CBigInt(BigInteger.valueOf(2002))) -> new Expected(ExpectedResult(Success(CBigInt(BigInteger.valueOf(2043))), None)), + (CBigInt(BigInteger.valueOf(100001)), CBigInt(BigInteger.valueOf(20002))) -> new Expected(ExpectedResult(Success(CBigInt(BigInteger.valueOf(118435))), None)), + (CBigInt(BigInteger.valueOf(1000010111)), CBigInt(BigInteger.valueOf(-22))) -> new Expected(ExpectedResult(Success(CBigInt(BigInteger.valueOf(-1))), None)) + ), + bitOr + ) + + lazy val bitNot = newFeature[BigInt, BigInt]( + { (x: BigInt) => CBigInt(x.asInstanceOf[CBigInt].wrappedValue.not()) }, + "{ (x: BigInt) => x.bitwiseInverse }", + FuncValue( + Array((1, SBigInt)), + MethodCall.typed[Value[SBigInt.type]]( + ValUse(1, SBigInt), + SBigIntMethods.v6Methods.find(_.name == "bitwiseInverse").get, + Vector(), + Map() + ) + ), + sinceVersion = V6SoftForkVersion) + + verifyCases( + Seq( + CBigInt(BigInteger.valueOf(1)) -> new Expected(ExpectedResult(Success(CBigInt(BigInteger.valueOf(-2))), None)), + CBigInt(BigInteger.valueOf(10001)) -> new Expected(ExpectedResult(Success(CBigInt(BigInteger.valueOf(-10002))), None)), + CBigInt(BigInteger.valueOf(Int.MinValue)) -> new Expected(ExpectedResult(Success(CBigInt(BigInteger.valueOf(Int.MaxValue))), None)), + CBigInt(BigInteger.valueOf(Long.MinValue)) -> new Expected(ExpectedResult(Success(CBigInt(BigInteger.valueOf(Long.MaxValue))), None)), + CBigInt(BigInteger.valueOf(Long.MaxValue)) -> new Expected(ExpectedResult(Success(CBigInt(BigInteger.valueOf(Long.MinValue))), None)) + ), + bitNot + ) + + lazy val bitAnd = newFeature( + { (x: (BigInt, BigInt)) => x._1.asInstanceOf[CBigInt].and(x._2.asInstanceOf[CBigInt]) }, + "{ (x: (BigInt, BigInt)) => x._1.bitwiseAnd(x._2) }", + FuncValue( + Array((1, SPair(SBigInt, SBigInt))), + MethodCall.typed[Value[SBigInt.type]]( + SelectField.typed[Value[SBigInt.type]](ValUse(1, SPair(SBigInt, SBigInt)), 1.toByte), + SBigIntMethods.v6Methods.find(_.name == "bitwiseAnd").get, + Vector(SelectField.typed[Value[SBigInt.type]](ValUse(1, SPair(SBigInt, SBigInt)), 2.toByte)), + Map() + ) + ), + sinceVersion = V6SoftForkVersion) + + verifyCases( + Seq( + (CBigInt(BigInteger.valueOf(3)), CBigInt(BigInteger.valueOf(5))) -> new Expected(ExpectedResult(Success(CBigInt(BigInteger.valueOf(1))), None)), + (CBigInt(BigInteger.valueOf(10001)), CBigInt(BigInteger.valueOf(2202))) -> new Expected(ExpectedResult(Success(CBigInt(BigInteger.valueOf(16))), None)), + (CBigInt(BigInteger.valueOf(-10001)), CBigInt(BigInteger.valueOf(200202))) -> new Expected(ExpectedResult(Success(CBigInt(BigInteger.valueOf(198666))), None)), + (CBigInt(BigInteger.valueOf(1000010111)), CBigInt(BigInteger.valueOf(-22))) -> new Expected(ExpectedResult(Success(CBigInt(BigInteger.valueOf(1000010090))), None)) + ), + bitAnd + ) + + lazy val bitXor = newFeature( + { (x: (BigInt, BigInt)) => x._1.asInstanceOf[CBigInt].xor(x._2.asInstanceOf[CBigInt]) }, + "{ (x: (BigInt, BigInt)) => x._1.bitwiseXor(x._2) }", + FuncValue( + Array((1, SPair(SBigInt, SBigInt))), + MethodCall.typed[Value[SBigInt.type]]( + SelectField.typed[Value[SBigInt.type]](ValUse(1, SPair(SBigInt, SBigInt)), 1.toByte), + SBigIntMethods.v6Methods.find(_.name == "bitwiseXor").get, + Vector(SelectField.typed[Value[SBigInt.type]](ValUse(1, SPair(SBigInt, SBigInt)),2.toByte)), + Map() + ) + ), + sinceVersion = V6SoftForkVersion) + + verifyCases( + Seq( + (CBigInt(BigInteger.valueOf(3)), CBigInt(BigInteger.valueOf(5))) -> new Expected(ExpectedResult(Success(CBigInt(BigInteger.valueOf(6))), None)), + (CBigInt(BigInteger.valueOf(10001)), CBigInt(BigInteger.valueOf(2202))) -> new Expected(ExpectedResult(Success(CBigInt(BigInteger.valueOf(12171))), None)), + (CBigInt(BigInteger.valueOf(-10001)), CBigInt(BigInteger.valueOf(200202))) -> new Expected(ExpectedResult(Success(CBigInt(BigInteger.valueOf(-207131))), None)), + (CBigInt(BigInteger.valueOf(1000010111)), CBigInt(BigInteger.valueOf(-22))) -> new Expected(ExpectedResult(Success(CBigInt(BigInteger.valueOf(-1000010091))), None)) + ), + bitXor + ) + + lazy val toBigEndianBytes = newFeature[BigInt, Coll[Byte]]( + { x: BigInt => x.toBytes }, + "{ (x: BigInt) => x.toBytes }", + FuncValue( + Array((1, SBigInt)), + MethodCall.typed[Value[SCollection[SBigInt.type]]]( + ValUse(1, SBigInt), + SBigIntMethods.getMethodByName("toBytes"), + Vector(), + Map() + ) + ), + sinceVersion = V6SoftForkVersion) + + verifyCases( + Seq( + CBigInt(BigInteger.valueOf(127)) -> new Expected(ExpectedResult(Success(Coll(127.toByte)), None)), + CBigInt(BigInteger.valueOf(Short.MaxValue)) -> new Expected(ExpectedResult(Success(Coll(127.toByte, (-1).toByte)), None)), + CBigInt(BigInteger.valueOf(Short.MinValue)) -> new Expected(ExpectedResult(Success(Coll((-128).toByte, 0.toByte)), None)), + CBigInt(BigInteger.valueOf(Int.MaxValue)) -> new Expected(ExpectedResult(Success(Coll(127.toByte, (-1).toByte, (-1).toByte, (-1).toByte)), None)) + ), + toBigEndianBytes + ) + + def byte2Bools(b: Byte): Seq[Boolean] = + (0 to 7 map isBitSet(b)).reverse + + def isBitSet(byte: Byte)(bit: Int): Boolean = + ((byte >> bit) & 1) == 1 + + lazy val toBits = newFeature[BigInt, Coll[Boolean]]( + { x: BigInt => x.toBytes.flatMap(b => Colls.fromArray(byte2Bools(b).toArray)) }, + "{ (x: BigInt) => x.toBits }", + FuncValue( + Array((1, SBigInt)), + MethodCall.typed[Value[SCollection[SBigInt.type]]]( + ValUse(1, SBigInt), + SBigIntMethods.getMethodByName("toBits"), + Vector(), + Map() + ) + ), + sinceVersion = V6SoftForkVersion) + + verifyCases( + Seq( + CBigInt(BigInteger.valueOf(83)) -> new Expected(ExpectedResult(Success(Coll(false, true, false, true, false, false, true, true)), None)), + CBigInt(BigInteger.valueOf(-55)) -> new Expected(ExpectedResult(Success(Coll(true, true, false, false, true, false, false, true)), None)), + CBigInt(BigInteger.valueOf(-1L)) -> new Expected(ExpectedResult(Success(Colls.fromArray(Array.fill(8)(true))), None)), + CBigInt(BigInteger.valueOf(-10001L)) -> new Expected(ExpectedResult(Success(Coll(true,true,false,true,true,false,false,false,true,true,true,false,true,true,true,true)), None)) + ), + toBits + ) + + lazy val shiftLeft = newFeature( + { (x: (BigInt, Int)) => if(x._2 < 0 || x._2 >= 256) throw new IllegalArgumentException() else (x._1.asInstanceOf[BigInt].shiftLeft(x._2)) }, + "{ (x: (BigInt, Int)) => x._1.shiftLeft(x._2) }", + FuncValue( + Array((1, SPair(SBigInt, SInt))), + MethodCall.typed[Value[SBigInt.type]]( + SelectField.typed[Value[SBigInt.type]](ValUse(1, SPair(SBigInt, SInt)), 1.toByte), + SBigIntMethods.v6Methods.find(_.name == "shiftLeft").get, + Vector(SelectField.typed[Value[SInt.type]](ValUse(1, SPair(SBigInt, SInt)), 2.toByte)), + Map() + ) + ), + sinceVersion = V6SoftForkVersion) + + verifyCases( + Seq( + (CBigInt(BigInteger.valueOf(3)), 3) -> new Expected(ExpectedResult(Success(CBigInt(BigInteger.valueOf(24))), None)), + (CBigInt(BigInteger.valueOf(3)), 8) -> new Expected(ExpectedResult(Success(CBigInt(BigInteger.valueOf(768))), None)), + (CBigInt(BigInteger.valueOf(-2)), 10) -> new Expected(ExpectedResult(Success(CBigInt(BigInteger.valueOf(-2048))), None)), + (CBigInt(BigInteger.valueOf(-222)), 10) -> new Expected(ExpectedResult(Success(CBigInt(BigInteger.valueOf(-227328L))), None)), + (CBigInt(BigInteger.valueOf(-222)), -1) -> new Expected(ExpectedResult(Failure(new IllegalArgumentException()), None)), + (CBigInt(BigInteger.valueOf(-222)), 256) -> new Expected(ExpectedResult(Failure(new IllegalArgumentException()), None)) + ), + shiftLeft, + preGeneratedSamples = Some(Seq()) + ) + + lazy val shiftRight = newFeature( + { (x: (BigInt, Int)) => if(x._2 < 0 || x._2 >= 256) throw new IllegalArgumentException() else (x._1.asInstanceOf[BigInt].shiftRight(x._2)) }, + "{ (x: (BigInt, Int)) => x._1.shiftRight(x._2) }", + FuncValue( + Array((1, SPair(SBigInt, SInt))), + MethodCall.typed[Value[SBigInt.type]]( + SelectField.typed[Value[SBigInt.type]](ValUse(1, SPair(SBigInt, SInt)), 1.toByte), + SBigIntMethods.v6Methods.find(_.name == "shiftRight").get, + Vector(SelectField.typed[Value[SInt.type]](ValUse(1, SPair(SBigInt, SInt)), 2.toByte)), + Map() + ) + ), + sinceVersion = V6SoftForkVersion) + + verifyCases( + Seq( + (CBigInt(BigInteger.valueOf(24)), 3) -> new Expected(ExpectedResult(Success(CBigInt(BigInteger.valueOf(3))), None)), + (CBigInt(BigInteger.valueOf(1600)), 8) -> new Expected(ExpectedResult(Success(CBigInt(BigInteger.valueOf(6))), None)), + (CBigInt(BigInteger.valueOf(-3200)), 8) -> new Expected(ExpectedResult(Success(CBigInt(BigInteger.valueOf(-13))), None)), + (CBigInt(BigInteger.valueOf(-320019)), 18) -> new Expected(ExpectedResult(Success(CBigInt(BigInteger.valueOf(-2))), None)), + (CBigInt(BigInteger.valueOf(-320019)), 63) -> new Expected(ExpectedResult(Success(CBigInt(BigInteger.valueOf(-1))), None)), + (CBigInt(BigInteger.valueOf(24)), -1) -> new Expected(ExpectedResult(Failure(new IllegalArgumentException()), None)), + (CBigInt(BigInteger.valueOf(24)), 256) -> new Expected(ExpectedResult(Failure(new IllegalArgumentException()), None)) + ), + shiftRight, + preGeneratedSamples = Some(Seq()) + ) } property("Box properties equivalence (new features)") { - // TODO v6.0: 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 }", - sinceVersion = VersionContext.V6SoftForkVersion) + // related to https://github.com/ScorexFoundation/sigmastate-interpreter/issues/416 + def getReg = newFeature((x: Box) => x.getReg[Long](0).get, + "{ (x: Box) => x.getReg[Long](0).get }", + FuncValue( + Array((1, SBox)), + OptionGet(ExtractRegisterAs(ValUse(1, SBox), ErgoBox.R0, SOption(SLong))) + ), + sinceVersion = V6SoftForkVersion) - if (activatedVersionInTests < VersionContext.V6SoftForkVersion) { + if (activatedVersionInTests < V6SoftForkVersion) { // NOTE, for such versions getReg is not supported // which is checked below forAll { box: Box => Seq(getReg).foreach(_.checkEquality(box)) } + } else { + val value = 10L + val box = CBox(new ErgoBox(value, TrueTree, Colls.emptyColl[Token], Map.empty, + ModifierId @@ Base16.encode(Array.fill(32)(0)), 0, 0)) + verifyCases( + Seq( + box -> new Expected(ExpectedResult(Success(value), None)) + ), + getReg + ) } } @@ -284,7 +1221,7 @@ class LanguageSpecificationV6 extends LanguageSpecificationBase { suite => 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} ) }", - sinceVersion = VersionContext.V6SoftForkVersion) + sinceVersion = V6SoftForkVersion) if (activatedVersionInTests < VersionContext.V6SoftForkVersion) { // NOTE, for such versions getReg is not supported @@ -303,7 +1240,7 @@ class LanguageSpecificationV6 extends LanguageSpecificationBase { suite => if (x.size > 2) x.slice(0, x.size - 2) else Colls.emptyColl[Boolean] }, "{ (x: Coll[Boolean]) => x >> 2 }", - sinceVersion = VersionContext.V6SoftForkVersion) + sinceVersion = V6SoftForkVersion) if (activatedVersionInTests < VersionContext.V6SoftForkVersion) { // NOTE, for such versions getReg is not supported @@ -319,7 +1256,7 @@ class LanguageSpecificationV6 extends LanguageSpecificationBase { suite => 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) }", - sinceVersion = VersionContext.V6SoftForkVersion) + sinceVersion = V6SoftForkVersion) if (activatedVersionInTests < VersionContext.V6SoftForkVersion) { // NOTE, for such versions getReg is not supported @@ -336,7 +1273,7 @@ class LanguageSpecificationV6 extends LanguageSpecificationBase { suite => val n = ExactNumeric.LongIsExactNumeric val fold = newFeature({ (x: Option[Long]) => x.fold(5.toLong)( (v: Long) => n.plus(v, 1) ) }, "{ (x: Option[Long]) => x.fold(5, { (v: Long) => v + 1 }) }", - sinceVersion = VersionContext.V6SoftForkVersion) + sinceVersion = V6SoftForkVersion) if (activatedVersionInTests < VersionContext.V6SoftForkVersion) { // NOTE, for such versions getReg is not supported @@ -352,7 +1289,7 @@ class LanguageSpecificationV6 extends LanguageSpecificationBase { suite => property("allZK equivalence") { lazy val allZK = newFeature((x: Coll[SigmaProp]) => SigmaDsl.allZK(x), "{ (x: Coll[SigmaProp]) => allZK(x) }", - sinceVersion = VersionContext.V6SoftForkVersion) + sinceVersion = V6SoftForkVersion) if (activatedVersionInTests < VersionContext.V6SoftForkVersion) { // NOTE, for such versions getReg is not supported @@ -368,7 +1305,7 @@ class LanguageSpecificationV6 extends LanguageSpecificationBase { suite => property("anyZK equivalence") { lazy val anyZK = newFeature((x: Coll[SigmaProp]) => SigmaDsl.anyZK(x), "{ (x: Coll[SigmaProp]) => anyZK(x) }", - sinceVersion = VersionContext.V6SoftForkVersion) + sinceVersion = V6SoftForkVersion) if (activatedVersionInTests < VersionContext.V6SoftForkVersion) { // NOTE, for such versions getReg is not supported @@ -380,6 +1317,28 @@ class LanguageSpecificationV6 extends LanguageSpecificationBase { suite => } } + property("Numeric.toBytes methods equivalence") { + lazy val toBytes = newFeature( + { (x: Byte) => x.toBigEndianBytes }, + "{ (x: Byte) => x.toBytes }", + FuncValue( + Array((1, SByte)), + MethodCall.typed[Value[SCollection[SByte.type]]]( + ValUse(1, SByte), + SByteMethods.getMethodByName("toBytes"), + Vector(), + Map() + ) + ), + sinceVersion = V6SoftForkVersion) + val cases = Seq( + (0.toByte, Success(Coll(0.toByte))), + (1.toByte, Success(Coll(1.toByte))) + ) + + testCases(cases, toBytes) + } + property("Fix substConstants in v6.0 for ErgoTree version > 0") { // tree with one segregated constant and v0 val t1 = ErgoTree( @@ -407,7 +1366,6 @@ class LanguageSpecificationV6 extends LanguageSpecificationBase { suite => ast.SeqCostItem(CompanionDesc(SubstConstants), PerItemCost(JitCost(100), JitCost(100), 1), nItems) ) ) - val expectedTreeBytes_beforeV6 = Helpers.decodeBytes("1b0108d27300") val expectedTreeBytes_V6 = Helpers.decodeBytes("1b050108d27300") @@ -463,4 +1421,106 @@ class LanguageSpecificationV6 extends LanguageSpecificationBase { suite => tree.root shouldBe t2.root } + property("Header new methods") { + + def checkPoW = { + newFeature( + { (x: Header) => x.checkPow}, + "{ (x: Header) => x.checkPow }", + FuncValue( + Array((1, SHeader)), + MethodCall.typed[Value[SBoolean.type]]( + ValUse(1, SHeader), + SHeaderMethods.checkPowMethod, + IndexedSeq(), + Map() + ) + ), + sinceVersion = VersionContext.V6SoftForkVersion + ) + } + + // bytes of real mainnet block header at height 614,440 + val headerBytes = "02ac2101807f0000ca01ff0119db227f202201007f62000177a080005d440896d05d3f80dcff7f5e7f59007294c180808d0158d1ff6ba10000f901c7f0ef87dcfff17fffacb6ff7f7f1180d2ff7f1e24ffffe1ff937f807f0797b9ff6ebdae007e5c8c00b8403d3701557181c8df800001b6d5009e2201c6ff807d71808c00019780f087adb3fcdbc0b3441480887f80007f4b01cf7f013ff1ffff564a0000b9a54f00770e807f41ff88c00240000080c0250000000003bedaee069ff4829500b3c07c4d5fe6b3ea3d3bf76c5c28c1d4dcdb1bed0ade0c0000000000003105" + val header1 = new CHeader(ErgoHeader.sigmaSerializer.fromBytes(Base16.decode(headerBytes).get)) + + verifyCases( + Seq( + header1 -> new Expected(ExpectedResult(Success(true), None)) + ), + checkPoW + ) + } + + property("higher order lambdas") { + val f = newFeature[Coll[Int], Coll[Int]]( + { (xs: Coll[Int]) => + val inc = { (x: Int) => x + 1 } + + def apply(in: (Int => Int, Int)) = in._1(in._2) + + xs.map { (x: Int) => apply((inc, x)) } + }, + """{(xs: Coll[Int]) => + | val inc = { (x: Int) => x + 1 } + | def apply(in: (Int => Int, Int)) = in._1(in._2) + | xs.map { (x: Int) => apply((inc, x)) } + | } + |""".stripMargin, + FuncValue( + Array((1, SCollectionType(SInt))), + MapCollection( + ValUse(1, SCollectionType(SInt)), + FuncValue( + Array((3, SInt)), + Apply( + FuncValue( + Array((5, SPair(SFunc(Array(SInt), SInt, List()), SInt))), + Apply( + SelectField.typed[Value[SFunc]]( + ValUse(5, SPair(SFunc(Array(SInt), SInt, List()), SInt)), + 1.toByte + ), + Array( + SelectField.typed[Value[SInt.type]]( + ValUse(5, SPair(SFunc(Array(SInt), SInt, List()), SInt)), + 2.toByte + ) + ) + ) + ), + Array( + Tuple( + Vector( + FuncValue( + Array((5, SInt)), + ArithOp(ValUse(5, SInt), IntConstant(1), OpCode @@ (-102.toByte)) + ), + ValUse(3, SInt) + ) + ) + ) + ) + ) + ) + ), + sinceVersion = VersionContext.V6SoftForkVersion + ) + + verifyCases( + Seq( + Coll(1, 2) -> Expected( + Success(Coll(2, 3)), + cost = 1793, + expectedDetails = CostDetails.ZeroCost + ) + ), + f, + preGeneratedSamples = Some(Seq( + Coll(Int.MinValue, Int.MaxValue - 1), + Coll(0, 1, 2, 3, 100, 1000) + )) + ) + } + } diff --git a/sc/shared/src/test/scala/sigma/SigmaDslTesting.scala b/sc/shared/src/test/scala/sigma/SigmaDslTesting.scala index 58873449b4..2eb73428a2 100644 --- a/sc/shared/src/test/scala/sigma/SigmaDslTesting.scala +++ b/sc/shared/src/test/scala/sigma/SigmaDslTesting.scala @@ -261,6 +261,7 @@ class SigmaDslTesting extends AnyPropSpec s"""Should succeed with the same value or fail with the same exception, but was: |First result: $b1 |Second result: $b2 + |Input: $x |Root cause: $cause |""".stripMargin) } @@ -715,11 +716,17 @@ class SigmaDslTesting extends AnyPropSpec override def checkEquality(input: A, logInputOutput: Boolean = false): Try[(B, CostDetails)] = { // check the old implementation against Scala semantic function var oldRes: Try[(B, CostDetails)] = null - if (ergoTreeVersionInTests < VersionContext.JitActivationVersion) oldRes = VersionContext.withVersions(activatedVersionInTests, ergoTreeVersionInTests) { try checkEq(scalaFunc)(oldF)(input) catch { - case e: TestFailedException => throw e + case e: TestFailedException => + if(activatedVersionInTests < changedInVersion) { + throw e + } else { + // old ergoscript may succeed in new version while old scalafunc may fail, + // see e.g. "Option.getOrElse with lazy default" test + Failure(e) + } case t: Throwable => Failure(t) } @@ -872,10 +879,14 @@ class SigmaDslTesting extends AnyPropSpec extends Feature[A, B] { override def isSupportedIn(vc: VersionContext): Boolean = - vc.activatedVersion >= sinceVersion && vc.ergoTreeVersion >= sinceVersion + vc.activatedVersion >= sinceVersion override def scalaFunc: A => B = { x => - sys.error(s"Semantic Scala function is not defined for old implementation: $this") + if (isSupportedIn(VersionContext.current)) { + scalaFuncNew(x) + } else { + sys.error(s"Semantic Scala function is not defined for old implementation: $this") + } } implicit val cs = compilerSettingsInTests @@ -925,8 +936,19 @@ class SigmaDslTesting extends AnyPropSpec printTestCases: Boolean, failOnTestVectors: Boolean): Unit = { val funcRes = checkEquality(input, printTestCases) - funcRes.isFailure shouldBe true - Try(scalaFunc(input)) shouldBe expected.value + if(!isSupportedIn(VersionContext.current)) { + funcRes.isFailure shouldBe true + } + if(isSupportedIn(VersionContext.current)) { + val res = Try(scalaFunc(input)) + if(expected.value.isSuccess) { + res shouldBe expected.value + } else { + res.isFailure shouldBe true + } + } else { + Try(scalaFunc(input)).isFailure shouldBe true + } } } @@ -994,6 +1016,20 @@ class SigmaDslTesting extends AnyPropSpec } } + /** Used when the old and new value are the same for all versions + * and the expected costs are not specified. + * + * @param value expected result of tested function + * @param expectedDetails expected cost details for all versions + */ + def apply[A](value: Try[A], expectedDetails: CostDetails): Expected[A] = + new Expected(ExpectedResult(value, None)) { + override val newResults = defaultNewResults.map { + case (ExpectedResult(v, _), _) => + (ExpectedResult(v, None), Some(expectedDetails)) + } + } + /** Used when the old and new value and costs are the same for all versions. * * @param value expected result of tested function diff --git a/sc/shared/src/test/scala/sigmastate/ErgoTreeSpecification.scala b/sc/shared/src/test/scala/sigmastate/ErgoTreeSpecification.scala index 7539bd5e48..4403716e5a 100644 --- a/sc/shared/src/test/scala/sigmastate/ErgoTreeSpecification.scala +++ b/sc/shared/src/test/scala/sigmastate/ErgoTreeSpecification.scala @@ -21,14 +21,14 @@ import sigma.compiler.CompilerSettings import sigma.eval.EvalSettings import sigma.exceptions.{CostLimitException, InterpreterException} import sigma.serialization.ErgoTreeSerializer.DefaultSerializer -import sigmastate.Plus +import sigmastate.{CrossVersionProps, Plus} import sigmastate.utils.Helpers.TryOps /** Regression tests with ErgoTree related test vectors. * This test vectors verify various constants which are consensus critical and should not change. */ -class ErgoTreeSpecification extends SigmaDslTesting with ContractsTestkit { +class ErgoTreeSpecification extends SigmaDslTesting with ContractsTestkit with CrossVersionProps { property("Value.sourceContext") { val srcCtx = SourceContext.fromParserIndex(0, "") @@ -313,37 +313,126 @@ class ErgoTreeSpecification extends SigmaDslTesting with ContractsTestkit { */ case class MInfo(methodId: Byte, method: SMethod, isResolvableFromIds: Boolean = true) + def isV6Activated = VersionContext.current.isV6SoftForkActivated + // NOTE, the type code constants are checked above // The methodId codes as checked here, they MUST be PRESERVED. - // The following table should be made dependent on HF activation - val methods = Table( + // Note, the following table is dependent on SF activation. + def methods = { + import SNumericTypeMethods._ + Table( ("typeId", "methods", "CanHaveMethods"), (SBoolean.typeId, Seq.empty[MInfo], true), - (SByte.typeId, Seq.empty[MInfo], false), - (SShort.typeId, Seq.empty[MInfo], false), - (SInt.typeId, Seq.empty[MInfo], false), - (SLong.typeId, Seq.empty[MInfo], false), - - { // SNumericType.typeId is erroneously shadowed by SGlobal.typeId - // this should be preserved in v3.x and fixed in v4.0 - (SNumericType.typeId, Seq( - MInfo(methodId = 1, SGlobalMethods.groupGeneratorMethod), - MInfo(2, SGlobalMethods.xorMethod) - ), true) + { + if (isV6Activated) + (SByte.typeId, Seq( + MInfo(methodId = 1, ToByteMethod), + MInfo(2, ToShortMethod), + MInfo(3, ToIntMethod), + MInfo(4, ToLongMethod), + MInfo(5, ToBigIntMethod), + MInfo(6, ToBytesMethod), + MInfo(7, ToBitsMethod), + MInfo(8, BitwiseInverseMethod, isResolvableFromIds = true), + MInfo(9, BitwiseOrMethod, isResolvableFromIds = true), + MInfo(10, BitwiseAndMethod, isResolvableFromIds = true), + MInfo(11, BitwiseXorMethod, isResolvableFromIds = true), + MInfo(12, ShiftLeftMethod, isResolvableFromIds = true), + MInfo(13, ShiftRightMethod, isResolvableFromIds = true) + ), true) + else + (SByte.typeId, Seq.empty[MInfo], false) + }, + { + if (isV6Activated) + (SShort.typeId, Seq( + MInfo(methodId = 1, ToByteMethod), + MInfo(2, ToShortMethod), + MInfo(3, ToIntMethod), + MInfo(4, ToLongMethod), + MInfo(5, ToBigIntMethod), + MInfo(6, ToBytesMethod), + MInfo(7, ToBitsMethod), + MInfo(8, BitwiseInverseMethod, isResolvableFromIds = true), + MInfo(9, BitwiseOrMethod, isResolvableFromIds = true), + MInfo(10, BitwiseAndMethod, isResolvableFromIds = true), + MInfo(11, BitwiseXorMethod, isResolvableFromIds = true), + MInfo(12, ShiftLeftMethod, isResolvableFromIds = true), + MInfo(13, ShiftRightMethod, isResolvableFromIds = true) + ), true) + else + (SShort.typeId, Seq.empty[MInfo], false) + }, + { + if (isV6Activated) + (SInt.typeId, Seq( + MInfo(methodId = 1, ToByteMethod), + MInfo(2, ToShortMethod), + MInfo(3, ToIntMethod), + MInfo(4, ToLongMethod), + MInfo(5, ToBigIntMethod), + MInfo(6, ToBytesMethod), + MInfo(7, ToBitsMethod), + MInfo(8, BitwiseInverseMethod, isResolvableFromIds = true), + MInfo(9, BitwiseOrMethod, isResolvableFromIds = true), + MInfo(10, BitwiseAndMethod, isResolvableFromIds = true), + MInfo(11, BitwiseXorMethod, isResolvableFromIds = true), + MInfo(12, ShiftLeftMethod, isResolvableFromIds = true), + MInfo(13, ShiftRightMethod, isResolvableFromIds = true) + ), true) + else + (SInt.typeId, Seq.empty[MInfo], false) + }, + { + if (isV6Activated) + (SLong.typeId, Seq( + MInfo(methodId = 1, ToByteMethod), + MInfo(2, ToShortMethod), + MInfo(3, ToIntMethod), + MInfo(4, ToLongMethod), + MInfo(5, ToBigIntMethod), + MInfo(6, ToBytesMethod), + MInfo(7, ToBitsMethod), + MInfo(8, BitwiseInverseMethod, isResolvableFromIds = true), + MInfo(9, BitwiseOrMethod, isResolvableFromIds = true), + MInfo(10, BitwiseAndMethod, isResolvableFromIds = true), + MInfo(11, BitwiseXorMethod, isResolvableFromIds = true), + MInfo(12, ShiftLeftMethod, isResolvableFromIds = true), + MInfo(13, ShiftRightMethod, isResolvableFromIds = true) + ), true) + else + (SLong.typeId, Seq.empty[MInfo], false) }, +// { // SNumericType.typeId is erroneously shadowed by SGlobal.typeId +// // this should be preserved in v3.x and fixed in v4.0 +// (SNumericType.typeId, Seq( +// MInfo(methodId = 1, SGlobalMethods.groupGeneratorMethod), +// MInfo(2, SGlobalMethods.xorMethod) +// ), true) +// }, + { // SBigInt inherit methods from SNumericType.methods - // however they are not resolvable via SBigInt.typeId + // however they are not resolvable via SBigInt.typeId before v6.0 import SNumericTypeMethods._ (SBigInt.typeId, Seq( - MInfo(methodId = 1, ToByteMethod, isResolvableFromIds = false), - MInfo(2, ToShortMethod, isResolvableFromIds = false), - MInfo(3, ToIntMethod, isResolvableFromIds = false), - MInfo(4, ToLongMethod, isResolvableFromIds = false), - MInfo(5, ToBigIntMethod, isResolvableFromIds = false), - MInfo(6, ToBytesMethod, isResolvableFromIds = false), - MInfo(7, ToBitsMethod, isResolvableFromIds = false) - ), true) + MInfo(methodId = 1, ToByteMethod, isResolvableFromIds = if (isV6Activated) true else false), + MInfo(2, ToShortMethod, isResolvableFromIds = if (isV6Activated) true else false), + MInfo(3, ToIntMethod, isResolvableFromIds = if (isV6Activated) true else false), + MInfo(4, ToLongMethod, isResolvableFromIds = if (isV6Activated) true else false), + MInfo(5, ToBigIntMethod, isResolvableFromIds = if (isV6Activated) true else false), + MInfo(6, ToBytesMethod, isResolvableFromIds = if (isV6Activated) true else false), + MInfo(7, ToBitsMethod, isResolvableFromIds = if (isV6Activated) true else false)) ++ + (if (isV6Activated) Seq( + // methods added in v6.0 + MInfo(8, BitwiseInverseMethod, isResolvableFromIds = true), + MInfo(9, BitwiseOrMethod, isResolvableFromIds = true), + MInfo(10, BitwiseAndMethod, isResolvableFromIds = true), + MInfo(11, BitwiseXorMethod, isResolvableFromIds = true), + MInfo(12, ShiftLeftMethod, isResolvableFromIds = true), + MInfo(13, ShiftRightMethod, isResolvableFromIds = true) + ) else Seq.empty) + , true) }, { import SGroupElementMethods._ (SGroupElement.typeId, Seq( @@ -367,9 +456,12 @@ class ErgoTreeSpecification extends SigmaDslTesting with ContractsTestkit { MInfo(4, BytesWithoutRefMethod), MInfo(5, IdMethod), MInfo(6, creationInfoMethod), - MInfo(7, getRegMethod), MInfo(8, tokensMethod) - ) ++ registers(idOfs = 8) + ) ++ (if (isV6Activated) { + Seq(MInfo(7, getRegMethodV6)) + } else { + Seq(MInfo(7, getRegMethodV5)) + }) ++ registers(idOfs = 8) .zipWithIndex .map { case (m,i) => MInfo((8 + i + 1).toByte, m) }, true) }, @@ -399,7 +491,11 @@ class ErgoTreeSpecification extends SigmaDslTesting with ContractsTestkit { MInfo(7, timestampMethod), MInfo(8, nBitsMethod), MInfo(9, heightMethod), MInfo(10, extensionRootMethod), MInfo(11, minerPkMethod), MInfo(12, powOnetimePkMethod), MInfo(13, powNonceMethod), MInfo(14, powDistanceMethod), MInfo(15, votesMethod) - ), true) + ) ++ (if (isV6Activated) { + Seq(MInfo(16, checkPowMethod)) // methods added in v6.0 + } else { + Seq.empty[MInfo] + }), true) }, { import SPreHeaderMethods._ (SPreHeader.typeId, Seq( @@ -419,7 +515,11 @@ class ErgoTreeSpecification extends SigmaDslTesting with ContractsTestkit { { import SGlobalMethods._ (SGlobal.typeId, Seq( MInfo(1, groupGeneratorMethod), MInfo(2, xorMethod) - ), true) + ) ++ (if (isV6Activated) { + Seq(MInfo(3, serializeMethod)) // methods added in v6.0 + } else { + Seq.empty[MInfo] + }), true) }, { import SCollectionMethods._ (SCollection.typeId, Seq( @@ -475,7 +575,13 @@ class ErgoTreeSpecification extends SigmaDslTesting with ContractsTestkit { MInfo(8, FilterMethod) ), true) } - ) + ) + } + + property("SNumericType.typeId resolves to SGlobal") { + SNumericType.typeId shouldBe SGlobal.typeId + SMethod.fromIds(SNumericType.typeId, 1) shouldBe SGlobalMethods.groupGeneratorMethod + } property("MethodCall Codes") { forAll(methods) { (typeId, methods, canHaveMethods) => @@ -484,7 +590,9 @@ class ErgoTreeSpecification extends SigmaDslTesting with ContractsTestkit { assert(canHaveMethods, s"Type $tyDesc should NOT have methods") val mc = MethodsContainer(tyDesc.typeId) + mc.methods.length shouldBe methods.length + for (expectedMethod <- methods) { if (expectedMethod.isResolvableFromIds) { @@ -508,21 +616,28 @@ class ErgoTreeSpecification extends SigmaDslTesting with ContractsTestkit { assert(!canHaveMethods, s"Type with code $typeId can have methods") } } - } property("MethodCall on numerics") { forAll(Table[STypeCompanion]("type", SByte, SShort, SInt, SLong, SBigInt)) { t => - // this methods are expected to fail resolution in v3.x (but may change in future) - (1 to 7).foreach { methodId => - assertExceptionThrown( - SMethod.fromIds(t.typeId, methodId.toByte), - { - case _: ValidationException => true - case _ => false - }, - s"SMethod mustn't resolve for typeId = ${t.typeId} and methodId = $methodId" - ) + // this methods are expected to fail resolution in before v6.0 + if (!isV6Activated) { + (1 to 7).foreach { methodId => + assertExceptionThrown( + SMethod.fromIds(t.typeId, methodId.toByte), + { + case _: ValidationException => true + case _ => false + }, + s"SMethod mustn't resolve for typeId = ${t.typeId} and methodId = $methodId" + ) + } + } else { + // in v6.0 these codes should resolve to the methods of the concrete numeric type + (1 to 7).foreach { methodId => + val m = SMethod.fromIds(t.typeId, methodId.toByte) + m.objType.ownerType shouldBe t + } } } } diff --git a/sc/shared/src/test/scala/sigmastate/TestingInterpreterSpecification.scala b/sc/shared/src/test/scala/sigmastate/TestingInterpreterSpecification.scala index 1012e9a189..4ba6b1a9f7 100644 --- a/sc/shared/src/test/scala/sigmastate/TestingInterpreterSpecification.scala +++ b/sc/shared/src/test/scala/sigmastate/TestingInterpreterSpecification.scala @@ -6,18 +6,23 @@ import sigma.ast._ import sigma.ast.syntax._ import sigmastate.interpreter._ import Interpreter._ -import sigma.ast.syntax._ import org.ergoplatform._ import org.scalatest.BeforeAndAfterAll -import scorex.util.encode.Base58 +import scorex.util.encode.{Base16, Base58} +import sigma.Colls +import sigma.VersionContext.V6SoftForkVersion import sigma.VersionContext -import sigma.crypto.CryptoConstants -import sigma.data.{AvlTreeData, CAND, ProveDlog, SigmaBoolean, TrivialProp} +import sigma.data.{CAND, CAvlTree, CHeader, ProveDlog, SigmaBoolean, TrivialProp} +import sigma.interpreter.ContextExtension import sigma.util.Extensions.IntOps import sigmastate.helpers.{CompilerTestingCommons, ErgoLikeContextTesting, ErgoLikeTestInterpreter, ErgoLikeTestProvingInterpreter} import sigmastate.helpers.TestingHelpers._ -import sigma.serialization.ValueSerializer +import sigma.serialization.{GroupElementSerializer, SigmaSerializer, ValueSerializer} +import sigmastate.eval.CPreHeader +import sigmastate.helpers.ErgoLikeContextTesting.noBoxes +import sigmastate.interpreter.CErgoTreeEvaluator.DefaultEvalSettings import sigmastate.utils.Helpers._ +import sigma.util.Extensions._ import scala.util.Random @@ -30,12 +35,29 @@ class TestingInterpreterSpecification extends CompilerTestingCommons lazy val verifier = new ErgoLikeTestInterpreter - def testingContext(h: Int) = - ErgoLikeContextTesting(h, - AvlTreeData.dummy, ErgoLikeContextTesting.dummyPubkey, IndexedSeq(fakeSelf), - ErgoLikeTransaction(IndexedSeq.empty, IndexedSeq.empty), - fakeSelf, activatedVersionInTests) - .withErgoTreeVersion(ergoTreeVersionInTests) + def testingContext(h: Int = 614401) = { + // bytes of real mainnet block header at height 614,440 + val headerBytes = "02ac2101807f0000ca01ff0119db227f202201007f62000177a080005d440896d05d3f80dcff7f5e7f59007294c180808d0158d1ff6ba10000f901c7f0ef87dcfff17fffacb6ff7f7f1180d2ff7f1e24ffffe1ff937f807f0797b9ff6ebdae007e5c8c00b8403d3701557181c8df800001b6d5009e2201c6ff807d71808c00019780f087adb3fcdbc0b3441480887f80007f4b01cf7f013ff1ffff564a0000b9a54f00770e807f41ff88c00240000080c0250000000003bedaee069ff4829500b3c07c4d5fe6b3ea3d3bf76c5c28c1d4dcdb1bed0ade0c0000000000003105" + val header1 = new CHeader(ErgoHeader.sigmaSerializer.fromBytes(Base16.decode(headerBytes).get)) + + val boxesToSpend = IndexedSeq(fakeSelf) + + val preHeader = CPreHeader(activatedVersionInTests, + parentId = header1.id, + timestamp = 3, + nBits = 0, + height = h, + minerPk = GroupElementSerializer.parse(SigmaSerializer.startReader(ErgoLikeContextTesting.dummyPubkey)).toGroupElement, + votes = Colls.emptyColl[Byte] + ) + + new ErgoLikeContext( + header1.stateRoot.asInstanceOf[CAvlTree].treeData, Colls.fromArray(Array(header1)), + preHeader, noBoxes, + boxesToSpend, ErgoLikeTransaction(IndexedSeq.empty, IndexedSeq.empty), + boxesToSpend.indexOf(fakeSelf), ContextExtension.empty, vs, DefaultEvalSettings.scriptCostLimitInEvaluator, + initCost = 0L, activatedVersionInTests).withErgoTreeVersion(ergoTreeVersionInTests) + } property("Reduction to crypto #1") { forAll() { i: Int => @@ -117,7 +139,7 @@ class TestingInterpreterSpecification extends CompilerTestingCommons val dk1 = prover.dlogSecrets(0).publicImage val dk2 = prover.dlogSecrets(1).publicImage - val ctx = testingContext(99) + val ctx = testingContext() val env = Map( "dk1" -> dk1, "dk2" -> dk2, @@ -125,7 +147,9 @@ class TestingInterpreterSpecification extends CompilerTestingCommons "bytes2" -> Array[Byte](4, 5, 6), "box1" -> testBox(10, TrueTree, 0, Seq(), Map( reg1 -> IntArrayConstant(Array[Int](1, 2, 3)), - reg2 -> BoolArrayConstant(Array[Boolean](true, false, true))))) + reg2 -> BoolArrayConstant(Array[Boolean](true, false, true)) + )) + ) val prop = mkTestErgoTree(compile(env, code)(IR).asBoolValue.toSigmaProp) val challenge = Array.fill(32)(Random.nextInt(100).toByte) val proof1 = prover.prove(prop, ctx, challenge).get.proof @@ -378,6 +402,24 @@ class TestingInterpreterSpecification extends CompilerTestingCommons verifier.verify(prop3, env, proof, challenge).map(_._1).getOrElse(false) shouldBe false } + property("blake2b - test vector") { + testEval( + """ { + | val input = fromBase16("68656c6c6f20776f726c64") + | val output = fromBase16("256c83b297114d201b30179f3f0ef0cace9783622da5974326b436178aeef610") + | blake2b256(input) == output + | }""".stripMargin) + } + + property("blake2b - test vector #2") { + testEval( + """ { + | val input = fromBase16("02ac2101807f0000ca01ff0119db227f202201007f62000177a080005d440896d05d3f80dcff7f5e7f59007294c180808d0158d1ff6ba10000f901c7f0ef87dcfff17fffacb6ff7f7f1180d2ff7f1e24ffffe1ff937f807f0797b9ff6ebdae007e5c8c00b8403d3701557181c8df800001b6d5009e2201c6ff807d71808c00019780d085adb3fcdbc0b3441480887f80007f4b01cf7f013ff1ffff564a0000b9a54f00770e807f41ff88c00240000080c02500000000") + | val output = fromBase16("bdb84cda5b105c3eb522857b50a0882f88ed5bb3cc8cf3325a1edf7eeb6a0954") + | blake2b256(input) == output + | }""".stripMargin) + } + property("passing a lambda argument") { // single expression testEval( @@ -412,6 +454,29 @@ class TestingInterpreterSpecification extends CompilerTestingCommons testEval(s"""deserialize[Coll[Byte]]("$str")(0) == 2""") } + property("header.id") { + testEval( + """ { + | val h = CONTEXT.headers(0) + | val id = h.id + | id.size == 32 + | }""".stripMargin) + } + + property("checkPow") { + val source = """ { + | val h = CONTEXT.headers(0) + | h.checkPow + | } + | """.stripMargin + + if (activatedVersionInTests < V6SoftForkVersion) { + an [Exception] should be thrownBy testEval(source) + } else { + testEval(source) + } + } + override protected def afterAll(): Unit = { } diff --git a/sc/shared/src/test/scala/sigmastate/TypesSpecification.scala b/sc/shared/src/test/scala/sigmastate/TypesSpecification.scala index 9a4c5ce1b8..6d13863f26 100644 --- a/sc/shared/src/test/scala/sigmastate/TypesSpecification.scala +++ b/sc/shared/src/test/scala/sigmastate/TypesSpecification.scala @@ -13,8 +13,6 @@ class TypesSpecification extends SigmaTestingData { implicit val tWrapped = wrappedTypeGen(t) forAll { x: SPredefType#WrappedType => isValueOfType(x, t) shouldBe true - // since forall t. SHeader != t - isValueOfType(x, SHeader) shouldBe false } } } diff --git a/sc/shared/src/test/scala/sigmastate/lang/SigmaBinderTest.scala b/sc/shared/src/test/scala/sigmastate/lang/SigmaBinderTest.scala index aa552e9b69..3c3687cb37 100644 --- a/sc/shared/src/test/scala/sigmastate/lang/SigmaBinderTest.scala +++ b/sc/shared/src/test/scala/sigmastate/lang/SigmaBinderTest.scala @@ -216,4 +216,15 @@ class SigmaBinderTest extends AnyPropSpec with ScalaCheckPropertyChecks with Mat e.source shouldBe Some(SourceContext(2, 5, "val x = 10")) } + property("predefined `serialize` should be transformed to MethodCall") { + runWithVersion(VersionContext.V6SoftForkVersion) { + checkBound(env, "serialize(1)", + MethodCall.typed[Value[SCollection[SByte.type]]]( + Global, + SGlobalMethods.getMethodByName("serialize").withConcreteTypes(Map(STypeVar("T") -> SInt)), + Array(IntConstant(1)), + Map() + )) + } + } } diff --git a/sc/shared/src/test/scala/sigmastate/lang/SigmaTyperTest.scala b/sc/shared/src/test/scala/sigmastate/lang/SigmaTyperTest.scala index c68c37a4dc..5992259628 100644 --- a/sc/shared/src/test/scala/sigmastate/lang/SigmaTyperTest.scala +++ b/sc/shared/src/test/scala/sigmastate/lang/SigmaTyperTest.scala @@ -21,6 +21,7 @@ import sigma.serialization.generators.ObjectGenerators import sigma.ast.Select import sigma.compiler.phases.{SigmaBinder, SigmaTyper} import sigma.exceptions.TyperException +import sigmastate.exceptions.MethodNotFound import sigmastate.helpers.SigmaPPrint class SigmaTyperTest extends AnyPropSpec @@ -518,6 +519,48 @@ class SigmaTyperTest extends AnyPropSpec typefail(env, "1.toSuperBigInteger", 1, 1) } + property("toBytes method for numeric types") { + typecheck(env, "1.toByte.toBytes", + expected = MethodCall.typed[Value[SCollection[SByte.type]]]( + Select(IntConstant(1), "toByte", Some(SByte)), + SNumericTypeMethods.ToBytesMethod.withConcreteTypes(Map(STypeVar("TNum") -> SByte)), + Vector(), + Map() + )) shouldBe SByteArray + + typecheck(env, "1.toShort.toBytes", + expected = MethodCall.typed[Value[SCollection[SByte.type]]]( + Select(IntConstant(1), "toShort", Some(SShort)), + SNumericTypeMethods.ToBytesMethod.withConcreteTypes(Map(STypeVar("TNum") -> SShort)), + Vector(), + Map() + )) shouldBe SByteArray + + typecheck(env, "1.toBytes", + expected = MethodCall.typed[Value[SCollection[SByte.type]]]( + IntConstant(1), + SNumericTypeMethods.ToBytesMethod.withConcreteTypes(Map(STypeVar("TNum") -> SInt)), + Vector(), + Map() + )) shouldBe SByteArray + + typecheck(env, "1.toLong.toBytes", + expected = MethodCall.typed[Value[SCollection[SByte.type]]]( + Select(IntConstant(1), "toLong", Some(SLong)), + SNumericTypeMethods.ToBytesMethod.withConcreteTypes(Map(STypeVar("TNum") -> SLong)), + Vector(), + Map() + )) shouldBe SByteArray + + typecheck(env, "1.toBigInt.toBytes", + expected = MethodCall.typed[Value[SCollection[SByte.type]]]( + Select(IntConstant(1), "toBigInt", Some(SBigInt)), + SNumericTypeMethods.ToBytesMethod.withConcreteTypes(Map(STypeVar("TNum") -> SBigInt)), + Vector(), + Map() + )) shouldBe SByteArray + } + property("string concat") { typecheck(env, """ "a" + "b" """) shouldBe SString } @@ -670,4 +713,36 @@ class SigmaTyperTest extends AnyPropSpec ) typecheck(customEnv, "substConstants(scriptBytes, positions, newVals)") shouldBe SByteArray } + + property("Global.serialize") { + runWithVersion(VersionContext.V6SoftForkVersion) { + typecheck(env, "Global.serialize(1)", + MethodCall.typed[Value[SCollection[SByte.type]]]( + Global, + SGlobalMethods.getMethodByName("serialize").withConcreteTypes(Map(STypeVar("T") -> SInt)), + Array(IntConstant(1)), + Map() + )) shouldBe SByteArray + } + + runWithVersion((VersionContext.V6SoftForkVersion - 1).toByte) { + assertExceptionThrown( + typecheck(env, "Global.serialize(1)"), + exceptionLike[MethodNotFound]("Cannot find method 'serialize' in in the object Global") + ) + } + } + + property("predefined serialize") { + runWithVersion(VersionContext.V6SoftForkVersion) { + typecheck(env, "serialize((1, 2L))", + expected = MethodCall.typed[Value[SCollection[SByte.type]]]( + Global, + SGlobalMethods.getMethodByName("serialize").withConcreteTypes(Map(STypeVar("T") -> SPair(SInt, SLong))), + Array(Tuple(Vector(IntConstant(1), LongConstant(2L)))), + Map() + )) shouldBe SByteArray + } + } + } diff --git a/sc/shared/src/test/scala/sigmastate/utxo/BasicOpsSpecification.scala b/sc/shared/src/test/scala/sigmastate/utxo/BasicOpsSpecification.scala index 1dbbd580ce..431c5166e1 100644 --- a/sc/shared/src/test/scala/sigmastate/utxo/BasicOpsSpecification.scala +++ b/sc/shared/src/test/scala/sigmastate/utxo/BasicOpsSpecification.scala @@ -2,8 +2,12 @@ package sigmastate.utxo import org.ergoplatform.ErgoBox.{AdditionalRegisters, R6, R8} import org.ergoplatform._ +import org.scalatest.Assertion import scorex.util.encode.Base16 +import org.scalatest.Assertion import sigma.Extensions.ArrayOps +import sigma.{SigmaTestingData, VersionContext} +import sigma.VersionContext.V6SoftForkVersion import sigma.ast.SCollection.SByteArray import sigma.ast.SType.AnyOps import sigma.data.{AvlTreeData, CAnyValue, CSigmaDslBuilder} @@ -21,6 +25,7 @@ import sigma.ast.Apply import sigma.eval.EvalSettings import sigma.exceptions.InvalidType import sigma.serialization.ErgoTreeSerializer +import sigmastate.utils.Helpers import sigmastate.utils.Helpers._ import java.math.BigInteger @@ -137,6 +142,397 @@ class BasicOpsSpecification extends CompilerTestingCommons flexVerifier.verify(verifyEnv, tree, ctxExt, pr.proof, fakeMessage).get._1 shouldBe true } + property("Byte.toBits") { + def toBitsTest() = test("Byte.toBits", env, ext, + """{ + | val b = 1.toByte + | b.toBits == Coll(false, false, false, false, false, false, false, true) + |}""".stripMargin, + null + ) + + if (VersionContext.current.isV6SoftForkActivated) { + toBitsTest() + } else { + an[Exception] shouldBe thrownBy(toBitsTest()) + } + } + + property("Long.toBits") { + def toBitsTest() = test("Long.toBits", env, ext, + """{ + | val b = 1L + | val ba = b.toBits + | + | // only rightmost bit is set + | ba.size == 64 && ba(63) == true && ba.slice(0, 63).forall({ (b: Boolean ) => b == false }) + |}""".stripMargin, + null + ) + + if (VersionContext.current.isV6SoftForkActivated) { + toBitsTest() + } else { + an[Exception] shouldBe thrownBy(toBitsTest()) + } + } + + property("BigInt.toBits") { + def toBitsTest() = test("BigInt.toBits", env, ext, + s"""{ + | val b = bigInt("${CryptoConstants.groupOrder.divide(new BigInteger("2"))}") + | val ba = b.toBits + | ba.size == 256 + |}""".stripMargin, + null + ) + + if (VersionContext.current.isV6SoftForkActivated) { + toBitsTest() + } else { + an[Exception] shouldBe thrownBy(toBitsTest()) + } + } + + property("BigInt.bitwiseInverse") { + def bitwiseInverseTest(): Assertion = test("BigInt.bitwiseInverse", env, ext, + s"""{ + | val b = bigInt("${CryptoConstants.groupOrder.divide(new BigInteger("2"))}") + | val bi = b.bitwiseInverse + | bi.bitwiseInverse == b + |}""".stripMargin, + null + ) + + if (VersionContext.current.isV6SoftForkActivated) { + bitwiseInverseTest() + } else { + an[Exception] shouldBe thrownBy(bitwiseInverseTest()) + } + } + + property("Byte.bitwiseInverse") { + def bitwiseInverseTest(): Assertion = test("Byte.bitwiseInverse", env, ext, + s"""{ + | val b = (126 + 1).toByte // max byte value + | b.bitwiseInverse == (-128).toByte + |}""".stripMargin, + null + ) + + if (VersionContext.current.isV6SoftForkActivated) { + bitwiseInverseTest() + } else { + an[Exception] shouldBe thrownBy(bitwiseInverseTest()) + } + } + + property("Long.bitwiseInverse") { + def bitwiseInverseTest(): Assertion = test("Long.bitwiseInverse", env, ext, + s"""{ + | val l = 9223372036854775807L + | val lb = l.bitwiseInverse + | lb.bitwiseInverse == l + |}""".stripMargin, + null + ) + + if (VersionContext.current.isV6SoftForkActivated) { + bitwiseInverseTest() + } else { + an[Exception] shouldBe thrownBy(bitwiseInverseTest()) + } + } + + property("Byte.bitwiseOr") { + def bitwiseOrTest(): Assertion = test("Byte.bitwiseOrTest", env, ext, + s"""{ + | val x = 127.toByte + | val y = (-128).toByte + | x.bitwiseOr(y) == -1 + |}""".stripMargin, + null + ) + + if (VersionContext.current.isV6SoftForkActivated) { + bitwiseOrTest() + } else { + an[Exception] shouldBe thrownBy(bitwiseOrTest()) + } + } + + property("BigInt.bitwiseOr") { + def bitwiseOrTest(): Assertion = test("BigInt.bitwiseOr", env, ext, + s"""{ + | val x = bigInt("${CryptoConstants.groupOrder.divide(new BigInteger("2"))}") + | x.bitwiseOr(x) == x + |}""".stripMargin, + null + ) + + if (VersionContext.current.isV6SoftForkActivated) { + bitwiseOrTest() + } else { + an[Exception] shouldBe thrownBy(bitwiseOrTest()) + } + } + + property("BigInt.bitwiseAnd") { + def bitwiseAndTest(): Assertion = test("BigInt.bitwiseAnd", env, ext, + s"""{ + | val x = bigInt("${CryptoConstants.groupOrder.divide(new BigInteger("2"))}") + | val y = 0.toBigInt + | x.bitwiseAnd(y) == y + |}""".stripMargin, + null + ) + + if (VersionContext.current.isV6SoftForkActivated) { + bitwiseAndTest() + } else { + an[Exception] shouldBe thrownBy(bitwiseAndTest()) + } + } + + property("Short.bitwiseAnd") { + def bitwiseAndTest(): Assertion = test("Short.bitwiseAnd", env, ext, + s"""{ + | val x = (32767).toShort + | val y = (-32768).toShort + | x.bitwiseAnd(y) == 0 + |}""".stripMargin, + null + ) + + if (VersionContext.current.isV6SoftForkActivated) { + bitwiseAndTest() + } else { + an[Exception] shouldBe thrownBy(bitwiseAndTest()) + } + } + + property("Short.bitwiseXor") { + def bitwiseXorTest(): Assertion = test("Short.bitwiseXor", env, ext, + s"""{ + | val x = (32767).toShort + | val y = (-32768).toShort + | x.bitwiseXor(y) == -1 + |}""".stripMargin, + null + ) + + if (VersionContext.current.isV6SoftForkActivated) { + bitwiseXorTest() + } else { + an[Exception] shouldBe thrownBy(bitwiseXorTest()) + } + } + + property("Byte.shiftLeft") { + def shiftLeftTest(): Assertion = test("Byte.shiftLeft", env, ext, + s"""{ + | val x = 4.toByte + | val y = 2 + | x.shiftLeft(y) == 16.toByte + |}""".stripMargin, + null + ) + + if (VersionContext.current.isV6SoftForkActivated) { + shiftLeftTest() + } else { + an[Exception] shouldBe thrownBy(shiftLeftTest()) + } + } + + property("Byte.shiftLeft - over limit") { + def shiftLeftTest(): Assertion = test("Byte.shiftLeft2", env, ext, + s"""{ + | val x = 4.toByte + | val y = 2222 + | x.shiftLeft(y) == 0 + |}""".stripMargin, + null + ) + + if (VersionContext.current.isV6SoftForkActivated) { + an[IllegalArgumentException] shouldBe thrownBy(shiftLeftTest()) + } else { + an[Exception] shouldBe thrownBy(shiftLeftTest()) + } + } + + property("Byte.shiftLeft - over limit 2") { + def shiftLeftTest(): Assertion = test("Byte.shiftLeft2", env, ext, + s"""{ + | val x = (-128).toByte + | val y = 1 + | x.shiftLeft(y) == 0 + |}""".stripMargin, + null + ) + + if (VersionContext.current.isV6SoftForkActivated) { + shiftLeftTest() + } else { + an[Exception] shouldBe thrownBy(shiftLeftTest()) + } + } + + property("BigInt.shiftLeft") { + def shiftLeftTest(): Assertion = test("BigInt.shiftLeft", env, ext, + s"""{ + | val x = bigInt("${CryptoConstants.groupOrder.divide(new BigInteger("8"))}") + | val y = bigInt("${CryptoConstants.groupOrder.divide(new BigInteger("2"))}") + | x.shiftLeft(2) == y + |}""".stripMargin, + null + ) + + if (VersionContext.current.isV6SoftForkActivated) { + shiftLeftTest() + } else { + an[Exception] shouldBe thrownBy(shiftLeftTest()) + } + } + + property("BigInt.shiftLeft over limits") { + def shiftLeftTest(): Assertion = test("BigInt.shiftLeft", env, ext, + s"""{ + | val x = bigInt("${CryptoConstants.groupOrder.divide(new BigInteger("2"))}") + | x.shiftLeft(1) > x + |}""".stripMargin, + null + ) + + if (VersionContext.current.isV6SoftForkActivated) { + an[ArithmeticException] shouldBe thrownBy(shiftLeftTest()) + } else { + an[Exception] shouldBe thrownBy(shiftLeftTest()) + } + } + + property("Byte.shiftRight") { + def shiftRightTest(): Assertion = test("Byte.shiftRight", env, ext, + s"""{ + | val x = 8.toByte + | val y = 2 + | x.shiftRight(y) == 2.toByte + |}""".stripMargin, + null + ) + + if (VersionContext.current.isV6SoftForkActivated) { + shiftRightTest() + } else { + an[Exception] shouldBe thrownBy(shiftRightTest()) + } + } + + property("Byte.shiftRight - neg") { + def shiftRightTest(): Assertion = test("Byte.shiftRight", env, ext, + s"""{ + | val x = (-8).toByte + | val y = 2 + | x.shiftRight(y) == (-2).toByte + |}""".stripMargin, + null + ) + + if (VersionContext.current.isV6SoftForkActivated) { + shiftRightTest() + } else { + an[Exception] shouldBe thrownBy(shiftRightTest()) + } + } + + property("Byte.shiftRight - neg - neg shift") { + def shiftRightTest(): Assertion = test("Byte.shiftRight", env, ext, + s"""{ + | val x = (-8).toByte + | val y = -2 + | x.shiftRight(y) == (-1).toByte + |}""".stripMargin, + null + ) + + if (VersionContext.current.isV6SoftForkActivated) { + an[IllegalArgumentException] shouldBe thrownBy(shiftRightTest()) + } else { + an[Exception] shouldBe thrownBy(shiftRightTest()) + } + } + + property("Long.shiftRight - neg") { + def shiftRightTest(): Assertion = test("Long.shiftRight", env, ext, + s"""{ + | val x = -32L + | val y = 2 + | x.shiftRight(y) == -8L + |}""".stripMargin, + null + ) + + if (VersionContext.current.isV6SoftForkActivated) { + shiftRightTest() + } else { + an[Exception] shouldBe thrownBy(shiftRightTest()) + } + } + + property("Long.shiftRight - neg - neg shift") { + def shiftRightTest(): Assertion = test("Long.shiftRight", env, ext, + s"""{ + | val x = -32L + | val y = -2 + | x.shiftRight(y) == -1L + |}""".stripMargin, + null + ) + + if (VersionContext.current.isV6SoftForkActivated) { + an[IllegalArgumentException] shouldBe thrownBy(shiftRightTest()) + } else { + an[Exception] shouldBe thrownBy(shiftRightTest()) + } + } + + property("BigInt.shiftRight") { + def shiftRightTest(): Assertion = test("BigInt.shiftRight", env, ext, + s"""{ + | val x = bigInt("${CryptoConstants.groupOrder.divide(new BigInteger("2"))}") + | val y = 2 + | val z = bigInt("${CryptoConstants.groupOrder.divide(new BigInteger("8"))}") + | x.shiftRight(y) == z + |}""".stripMargin, + null + ) + + if (VersionContext.current.isV6SoftForkActivated) { + shiftRightTest() + } else { + an[Exception] shouldBe thrownBy(shiftRightTest()) + } + } + + property("BigInt.shiftRight - neg shift") { + def shiftRightTest(): Assertion = test("BigInt.shiftRight", env, ext, + s"""{ + | val x = bigInt("${CryptoConstants.groupOrder.divide(new BigInteger("2"))}") + | val y = -2 + | val z = bigInt("${CryptoConstants.groupOrder.divide(new BigInteger("8"))}") + | z.shiftRight(y) == x + |}""".stripMargin, + null + ) + + if (VersionContext.current.isV6SoftForkActivated) { + an[IllegalArgumentException] shouldBe thrownBy(shiftRightTest()) + } else { + an[Exception] shouldBe thrownBy(shiftRightTest()) + } + } + property("Unit register") { // TODO frontend: implement missing Unit support in compiler // https://github.com/ScorexFoundation/sigmastate-interpreter/issues/820 @@ -159,8 +555,257 @@ class BasicOpsSpecification extends CompilerTestingCommons ) } + property("Int.toBytes") { + def toBytesTest() = test("Int.toBytes", env, ext, + """{ + | val l = 1 + | l.toBytes == Coll(0.toByte, 0.toByte, 0.toByte, 1.toByte) + | }""".stripMargin, + null + ) + + if (VersionContext.current.isV6SoftForkActivated) { + toBytesTest() + } else { + an[Exception] shouldBe thrownBy(toBytesTest()) + } + } + + property("Int.toBits") { + def toBytesTest() = test("Int.toBytes", env, ext, + """{ + | val l = 1477959696 + | l.toBits == Coll(false, true, false, true, true, false, false, false, false, false, false, true, false, true, true ,true, true, true, true, false, false, false, false, false, false, false, false, true, false, false, false, false) + | }""".stripMargin, + null + ) + + if (VersionContext.current.isV6SoftForkActivated) { + toBytesTest() + } else { + an[Exception] shouldBe thrownBy(toBytesTest()) + } + } + + property("Byte.toBytes") { + def toBytesTest() = test("Byte.toBytes", env, ext, + """{ + | val l = 10.toByte + | l.toBytes == Coll(10.toByte) + | }""".stripMargin, + null + ) + + if (VersionContext.current.isV6SoftForkActivated) { + toBytesTest() + } else { + an[Exception] shouldBe thrownBy(toBytesTest()) + } + } + + + property("BigInt.toBytes") { + def toBytesTest() = test("BigInt.toBytes", env, ext, + s"""{ + | val l = bigInt("${CryptoConstants.groupOrder.divide(new BigInteger("2"))}") + | l.toBytes.size == 32 + | }""".stripMargin, + null + ) + + if (VersionContext.current.isV6SoftForkActivated) { + toBytesTest() + } else { + an[Exception] shouldBe thrownBy(toBytesTest()) + } + } + + property("serialize - byte array") { + def deserTest() = test("serialize", env, ext, + s"""{ + val ba = fromBase16("c0ffee"); + Global.serialize(ba).size > ba.size + }""", + null, + true + ) + + if (activatedVersionInTests < V6SoftForkVersion) { + an [sigma.exceptions.TyperException] should be thrownBy deserTest() + } else { + deserTest() + } + } + + property("serialize - collection of boxes") { + def deserTest() = test("serialize", env, ext, + s"""{ + val boxes = INPUTS; + Global.serialize(boxes).size > 0 + }""", + null, + true + ) + + if (activatedVersionInTests < V6SoftForkVersion) { + an [sigma.exceptions.TyperException] should be thrownBy deserTest() + } else { + deserTest() + } + } + + property("serialize - optional collection") { + def deserTest() = test("serialize", env, ext, + s"""{ + val opt = SELF.R1[Coll[Byte]]; + Global.serialize(opt).size > SELF.R1[Coll[Byte]].get.size + }""", + null, + true + ) + + if (activatedVersionInTests < V6SoftForkVersion) { + an [sigma.exceptions.TyperException] should be thrownBy deserTest() + } else { + deserTest() + } + } + + property("serialize(long) is producing different result from longToByteArray()") { + def deserTest() = test("serialize", env, ext, + s"""{ + val l = -1000L + val ba1 = Global.serialize(l); + val ba2 = longToByteArray(l) + ba1 != ba2 + }""", + null, + true + ) + + if (activatedVersionInTests < V6SoftForkVersion) { + an [sigma.exceptions.TyperException] should be thrownBy deserTest() + } else { + deserTest() + } + } + + // the test shows that serialize(groupElement) is the same as groupElement.getEncoded + property("serialize - group element - equivalence with .getEncoded") { + val ge = Helpers.decodeGroupElement("026930cb9972e01534918a6f6d6b8e35bc398f57140d13eb3623ea31fbd069939b") + // val ba = Base16.encode(ge.getEncoded.toArray) + def deserTest() = test("serialize", env, Seq(21.toByte -> GroupElementConstant(ge)), + s"""{ + val ge = getVar[GroupElement](21).get + val ba = serialize(ge); + ba == ge.getEncoded + }""", + null, + true + ) + + if (activatedVersionInTests < V6SoftForkVersion) { + an [Exception] should be thrownBy deserTest() + } else { + deserTest() + } + } + + // the test shows that serialize(sigmaProp) is the same as sigmaProp.propBytes without first 2 bytes + property("serialize and .propBytes correspondence") { + def deserTest() = test("deserializeTo", env, ext, + s"""{ + val p1 = getVar[SigmaProp]($propVar1).get + val bytes = p1.propBytes + val ba = bytes.slice(2, bytes.size) + val ba2 = serialize(p1) + ba == ba2 + }""", + null, + true + ) + + if (activatedVersionInTests < V6SoftForkVersion) { + an [Exception] should be thrownBy deserTest() + } else { + deserTest() + } + } + + // todo: failing, needs for Header (de)serialization support from https://github.com/ScorexFoundation/sigmastate-interpreter/pull/972 + property("serialize - collection of collection of headers") { + val td = new SigmaTestingData {} + val h1 = td.TestData.h1 + + val customExt = Seq(21.toByte -> HeaderConstant(h1)) + + def deserTest() = test("serialize", env, customExt, + s"""{ + val h1 = getVar[Header](21).get; + val c = Coll(Coll(h1)) + Global.serialize(c).size > 0 + }""", + null, + true + ) + + if (activatedVersionInTests < V6SoftForkVersion) { + an [sigma.exceptions.TyperException] should be thrownBy deserTest() + } else { + deserTest() + } + } + + // todo: roundtrip tests with deserializeTo from https://github.com/ScorexFoundation/sigmastate-interpreter/pull/979 + + // todo: move spam tests to dedicated test suite? + property("serialize - not spam") { + val customExt = Seq(21.toByte -> ShortArrayConstant((1 to Short.MaxValue).map(_.toShort).toArray), + 22.toByte -> ByteArrayConstant(Array.fill(1)(1.toByte))) + def deserTest() = test("serialize", env, customExt, + s"""{ + val indices = getVar[Coll[Short]](21).get + val base = getVar[Coll[Byte]](22).get + + def check(index:Short): Boolean = { serialize(base) != base } + indices.forall(check) + }""", + null, + true + ) + + if (activatedVersionInTests < V6SoftForkVersion) { + an[Exception] should be thrownBy deserTest() + } else { + deserTest() + } + } + + property("serialize - spam attempt") { + val customExt = Seq(21.toByte -> ShortArrayConstant((1 to Short.MaxValue).map(_.toShort).toArray), + 22.toByte -> ByteArrayConstant(Array.fill(16000)(1.toByte))) + def deserTest() = test("serialize", env, customExt, + s"""{ + val indices = getVar[Coll[Short]](21).get + val base = getVar[Coll[Byte]](22).get + + def check(index:Short): Boolean = { serialize(base) != base } + indices.forall(check) + }""", + null, + true + ) + + if (activatedVersionInTests < V6SoftForkVersion) { + an[Exception] should be thrownBy deserTest() + } else { + // we have wrapped CostLimitException here + an[Exception] should be thrownBy deserTest() + } + } + property("coll equality") { - test("R1", env, ext, + def collTest() = test("R1", env, ext, "{ SELF.tokens == Coll[(Coll[Byte], Long)]() }", null ) @@ -463,6 +1108,32 @@ class BasicOpsSpecification extends CompilerTestingCommons rootCause(_).isInstanceOf[NoSuchElementException]) } + property("higher order lambdas") { + def holTest() = test("HOL", env, ext, + """ + | { + | val c = Coll(Coll(1)) + | def fn(xs: Coll[Int]) = { + | val inc = { (x: Int) => x + 1 } + | def apply(in: (Int => Int, Int)) = in._1(in._2) + | val ys = xs.map { (x: Int) => apply((inc, x)) } + | ys.size == xs.size && ys != xs + | } + | + | c.exists(fn) + | } + |""".stripMargin, + null, + true + ) + + if(VersionContext.current.isV6SoftForkActivated) { + holTest() + } else { + an[Exception] shouldBe thrownBy(holTest()) + } + } + property("OptionGetOrElse") { test("OptGet1", env, ext, "{ SELF.R5[Int].getOrElse(3) == 1 }", @@ -772,4 +1443,45 @@ class BasicOpsSpecification extends CompilerTestingCommons test("subst", env, ext, hostScript, null) } + property("Box.getReg") { + def getRegTest(): Assertion = { + test("Box.getReg", env, ext, + """{ + | val x = SELF + | x.getReg[Long](0).get == SELF.value && + | x.getReg[Coll[(Coll[Byte], Long)]](2).get == SELF.tokens && + | x.getReg[Int](9).isEmpty + |}""".stripMargin, + null + ) + } + + if (VersionContext.current.isV6SoftForkActivated) { + getRegTest() + } else { + an[Exception] should be thrownBy getRegTest() + } + } + + property("Box.getReg - computable index") { + val ext: Seq[VarBinding] = Seq( + (intVar1, IntConstant(0)) + ) + def getRegTest(): Assertion = { + test("Box.getReg", env, ext, + """{ + | val x = SELF.getReg[Long](getVar[Int](1).get).get + | x == SELF.value + |}""".stripMargin, + null + ) + } + + if (VersionContext.current.isV6SoftForkActivated) { + getRegTest() + } else { + an[Exception] should be thrownBy getRegTest() + } + } + } diff --git a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Header.scala b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Header.scala index ef53e13dbd..2220c9827a 100644 --- a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Header.scala +++ b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Header.scala @@ -12,7 +12,7 @@ import scala.scalajs.js.annotation.JSExportTopLevel class Header( /** Hex representation of ModifierId of this Header */ val id: String, - /** Block version, to be increased on every soft and hardfork. */ + /** Block version, to be increased on every soft- or hard-fork. */ val version: Byte, /** Hex representation of ModifierId of the parent block */ val parentId: String, @@ -48,5 +48,7 @@ class Header( val powDistance: js.BigInt, /** Miner votes for changing system parameters. */ - val votes: String + val votes: String, + + val unparsedBytes: String ) extends js.Object diff --git a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Isos.scala b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Isos.scala index 0f50a52686..84f2b21da8 100644 --- a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Isos.scala +++ b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Isos.scala @@ -7,9 +7,10 @@ import sigma.ast.{Constant, SType} import sigma.data.Iso import sigma.data.Iso.{isoStringToArray, isoStringToColl} import sigma.data.js.{Isos => DataIsos} +import sigma.data.CHeader import sigma.interpreter.{ContextExtension, ProverResult} import sigma.js.AvlTree -import sigmastate.eval.{CHeader, CPreHeader} +import sigmastate.eval.CPreHeader import sigmastate.fleetSdkCommon.distEsmTypesBoxesMod.Box import sigmastate.fleetSdkCommon.distEsmTypesRegistersMod.NonMandatoryRegisters import sigmastate.fleetSdkCommon.distEsmTypesTokenMod.TokenAmount @@ -27,11 +28,10 @@ object Isos { implicit val isoHeader: Iso[Header, sigma.Header] = new Iso[Header, sigma.Header] { override def to(a: Header): sigma.Header = { CHeader( - id = isoStringToColl.to(a.id), version = a.version, parentId = isoStringToColl.to(a.parentId), ADProofsRoot = isoStringToColl.to(a.ADProofsRoot), - stateRoot = AvlTree.isoAvlTree.to(a.stateRoot), + stateRootDigest = AvlTree.isoAvlTree.to(a.stateRoot).digest, transactionsRoot = isoStringToColl.to(a.transactionsRoot), timestamp = sigma.js.Isos.isoBigIntToLong.to(a.timestamp), nBits = sigma.js.Isos.isoBigIntToLong.to(a.nBits), @@ -41,7 +41,8 @@ object Isos { powOnetimePk = DataIsos.isoGroupElement.to(a.powOnetimePk), powNonce = isoStringToColl.to(a.powNonce), powDistance = sigma.js.Isos.isoBigInt.to(a.powDistance), - votes = isoStringToColl.to(a.votes) + votes = isoStringToColl.to(a.votes), + unparsedBytes = isoStringToColl.to(a.unparsedBytes) ) } override def from(b: sigma.Header): Header = { @@ -61,7 +62,8 @@ object Isos { powOnetimePk = DataIsos.isoGroupElement.from(header.powOnetimePk), powNonce = isoStringToColl.from(header.powNonce), powDistance = sigma.js.Isos.isoBigInt.from(header.powDistance), - votes = isoStringToColl.from(header.votes) + votes = isoStringToColl.from(header.votes), + unparsedBytes = isoStringToColl.from(header.unparsedBytes) ) } } diff --git a/sdk/js/src/test/scala/org/ergoplatform/sdk/js/IsosSpec.scala b/sdk/js/src/test/scala/org/ergoplatform/sdk/js/IsosSpec.scala index a040731b20..31b8a84c2b 100644 --- a/sdk/js/src/test/scala/org/ergoplatform/sdk/js/IsosSpec.scala +++ b/sdk/js/src/test/scala/org/ergoplatform/sdk/js/IsosSpec.scala @@ -1,8 +1,9 @@ package org.ergoplatform.sdk.js +import io.circe.parser.parse import org.ergoplatform.ErgoBox.{AdditionalRegisters, BoxId, TokenId} import org.ergoplatform._ -import org.ergoplatform.sdk.ExtendedInputBox +import org.ergoplatform.sdk.{ExtendedInputBox, JsonCodecs} import org.ergoplatform.sdk.wallet.protocol.context.BlockchainStateContext import org.scalacheck.Arbitrary import sigma.ast.{Constant, SType} @@ -11,6 +12,7 @@ import sigma.interpreter.{ContextExtension, ProverResult} import sigma.js.AvlTree import sigma.{Coll, GroupElement} import sigma.data.js.{Isos => DataIsos} + import scala.scalajs.js class IsosSpec extends IsosSpecBase with sdk.generators.ObjectGenerators { diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/DataJsonEncoder.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/DataJsonEncoder.scala index fc95b77e61..6c0c866baf 100644 --- a/sdk/shared/src/main/scala/org/ergoplatform/sdk/DataJsonEncoder.scala +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/DataJsonEncoder.scala @@ -122,6 +122,10 @@ object DataJsonEncoder { val w = SigmaSerializer.startWriter() DataSerializer.serialize(v, tpe, w) encodeBytes(w.toBytes) + case SHeader => + val w = SigmaSerializer.startWriter() + DataSerializer.serialize(v, tpe, w) + encodeBytes(w.toBytes) case SAvlTree => val w = SigmaSerializer.startWriter() DataSerializer.serialize(v, tpe, w) @@ -203,6 +207,10 @@ object DataJsonEncoder { val str = decodeBytes(json) val r = SigmaSerializer.startReader(str) DataSerializer.deserialize(SSigmaProp, r) + case SHeader => // for Sigma < 6.0 , exception will be thrown by DataSerializer + val str = decodeBytes(json) + val r = SigmaSerializer.startReader(str) + DataSerializer.deserialize(SHeader, r) case SBox => val value = decodeData(json.hcursor.downField(s"value").focus.get, SLong) val tree = ErgoTreeSerializer.DefaultSerializer.deserializeErgoTree(decodeBytes(json.hcursor.downField(s"ergoTree").focus.get)) diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/JsonCodecs.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/JsonCodecs.scala index ae14fd831a..98e8011bcc 100644 --- a/sdk/shared/src/main/scala/org/ergoplatform/sdk/JsonCodecs.scala +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/JsonCodecs.scala @@ -12,7 +12,7 @@ import scorex.crypto.hash.Digest32 import scorex.util.ModifierId import sigma.Extensions.ArrayOps import sigma.ast.{ErgoTree, EvaluatedValue, SType} -import sigma.data.{AvlTreeData, AvlTreeFlags, CBigInt, Digest32Coll, WrapperOf} +import sigma.data.{AvlTreeData, AvlTreeFlags, CBigInt, CHeader, Digest32Coll, WrapperOf} import sigma.eval.Extensions.EvalIterableOps import sigma.eval.SigmaDsl import sigma.interpreter.{ContextExtension, ProverResult} @@ -125,13 +125,18 @@ trait JsonCodecs { "powOnetimePk" -> h.powOnetimePk.getEncoded.asJson, "powNonce" -> h.powNonce.asJson, "powDistance" -> h.powDistance.asJson, - "votes" -> h.votes.asJson + "votes" -> h.votes.asJson, + "unparsedBytes" -> h.unparsedBytes.asJson ).asJson }) + /** + * JSON decoder for Header instance. Field "unparsedBytes" is optional for now, to preserve compatibility + * with clients using older JSON decoders (before node 5.0.23). Better to add an (empty) field anyway if possible. + * This field could become mandatory in future. + */ implicit val headerDecoder: Decoder[Header] = Decoder.instance({ cursor => for { - id <- cursor.downField("id").as[Coll[Byte]] version <- cursor.downField("version").as[Byte] parentId <- cursor.downField("parentId").as[Coll[Byte]] adProofsRoot <- cursor.downField("adProofsRoot").as[Coll[Byte]] @@ -146,8 +151,10 @@ trait JsonCodecs { powNonce <- cursor.downField("powNonce").as[Coll[Byte]] powDistance <- cursor.downField("powDistance").as[sigma.BigInt] votes <- cursor.downField("votes").as[Coll[Byte]] - } yield new CHeader(id, version, parentId, adProofsRoot, stateRoot, transactionsRoot, timestamp, nBits, - height, extensionRoot, SigmaDsl.decodePoint(minerPk), SigmaDsl.decodePoint(powOnetimePk), powNonce, powDistance, votes) + unparsedBytes <- cursor.downField("unparsedBytes").as[Option[Coll[Byte]]] + } yield CHeader(version, parentId, adProofsRoot, stateRoot.digest, transactionsRoot, timestamp, nBits, + height, extensionRoot, SigmaDsl.decodePoint(minerPk), SigmaDsl.decodePoint(powOnetimePk), powNonce, powDistance, + votes, unparsedBytes.getOrElse(Colls.emptyColl)) }) implicit val preHeaderEncoder: Encoder[PreHeader] = Encoder.instance({ v: PreHeader => diff --git a/sdk/shared/src/test/scala/org/ergoplatform/sdk/DataJsonEncoderSpecification.scala b/sdk/shared/src/test/scala/org/ergoplatform/sdk/DataJsonEncoderSpecification.scala index c3f7b43af4..5835f399cb 100644 --- a/sdk/shared/src/test/scala/org/ergoplatform/sdk/DataJsonEncoderSpecification.scala +++ b/sdk/shared/src/test/scala/org/ergoplatform/sdk/DataJsonEncoderSpecification.scala @@ -13,7 +13,7 @@ import sigma.serialization.SerializerException import sigma.util.Extensions.{BigIntegerOps, EcpOps, SigmaBooleanOps} import sigma.Extensions.ArrayOps import sigma.eval.SigmaDsl -import sigma.{AvlTree, Box, Colls, Evaluation} +import sigma.{AvlTree, Box, Colls, Evaluation, Header, VersionContext} import sigma.serialization.SerializationSpecification import scala.annotation.nowarn @@ -22,29 +22,48 @@ import scala.reflect.ClassTag 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) - res shouldBe obj + def roundtrip[T <: SType](obj: T#WrappedType, tpe: T, withVersion: Option[Byte] = None) = { + def test() = { + val json = DataJsonEncoder.encode(obj, tpe) + val res = DataJsonEncoder.decode(json) + res shouldBe obj + } + + withVersion match { + case Some(ver) => + VersionContext.withVersions(ver, 0) { + test() + } + case None => + test() + } } def testCollection[T <: SType](tpe: T) = { implicit val wWrapped = wrappedTypeGen(tpe) implicit val tT = Evaluation.stypeToRType(tpe) implicit val tagT = tT.classTag + + val withVersion = if (tpe == SHeader) { + Some(VersionContext.V6SoftForkVersion) + } else { + None + } + forAll { xs: Array[T#WrappedType] => - roundtrip[SCollection[T]](xs.toColl, SCollection(tpe)) - roundtrip[SType](xs.toColl.map(x => (x, x)).asWrappedType, SCollection(STuple(tpe, tpe))) + roundtrip[SCollection[T]](xs.toColl, SCollection(tpe), withVersion) + roundtrip[SType](xs.toColl.map(x => (x, x)).asWrappedType, SCollection(STuple(tpe, tpe)), withVersion) val nested = xs.toColl.map(x => Colls.fromItems[T#WrappedType](x, x)) - roundtrip[SCollection[SCollection[T]]](nested, SCollection(SCollection(tpe))) + roundtrip[SCollection[SCollection[T]]](nested, SCollection(SCollection(tpe)), withVersion) roundtrip[SType]( xs.toColl.map { x => val arr = Colls.fromItems[T#WrappedType](x, x) (arr, arr) }.asWrappedType, - SCollection(STuple(SCollection(tpe), SCollection(tpe))) + SCollection(STuple(SCollection(tpe), SCollection(tpe))), + withVersion ) } } @@ -54,11 +73,18 @@ class DataJsonEncoderSpecification extends SerializationSpecification { val tT = Evaluation.stypeToRType(tpe) @nowarn implicit val tag : ClassTag[T#WrappedType] = tT.classTag @nowarn implicit val tAny : RType[Any] = sigma.AnyType + + val withVersion = if (tpe == SHeader) { + Some(VersionContext.V6SoftForkVersion) + } else { + None + } + forAll { in: (T#WrappedType, T#WrappedType) => val (x,y) = (in._1, in._2) - roundtrip[SType]((x, y).asWrappedType, STuple(tpe, tpe)) - roundtrip[SType](((x, y), (x, y)).asWrappedType, STuple(STuple(tpe, tpe), STuple(tpe, tpe))) - roundtrip[SType](((x, y), ((x, y), (x, y))).asWrappedType, STuple(STuple(tpe, tpe), STuple(STuple(tpe, tpe), STuple(tpe, tpe)))) + roundtrip[SType]((x, y).asWrappedType, STuple(tpe, tpe), withVersion) + roundtrip[SType](((x, y), (x, y)).asWrappedType, STuple(STuple(tpe, tpe), STuple(tpe, tpe)), withVersion) + roundtrip[SType](((x, y), ((x, y), (x, y))).asWrappedType, STuple(STuple(tpe, tpe), STuple(STuple(tpe, tpe), STuple(tpe, tpe))), withVersion) } } @@ -98,6 +124,7 @@ class DataJsonEncoderSpecification extends SerializationSpecification { forAll { x: AvlTree => roundtrip[SAvlTree.type](x, SAvlTree) } forAll { x: Array[Byte] => roundtrip[SByteArray](x.toColl, SByteArray) } forAll { x: Box => roundtrip[SBox.type](x, SBox) } + forAll { x: Header => roundtrip[SHeader.type](x, SHeader, Some(VersionContext.V6SoftForkVersion)) } forAll { x: Option[Byte] => roundtrip[SOption[SByte.type]](x, SOption[SByte.type]) } testCollection(SOption[SLong.type]) testTuples(SOption[SLong.type]) @@ -187,25 +214,44 @@ class DataJsonEncoderSpecification extends SerializationSpecification { val tT = Evaluation.stypeToRType(tpe) @nowarn implicit val tag = tT.classTag @nowarn implicit val tAny = sigma.AnyType - forAll { x: T#WrappedType => - 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))) + def test() = { + forAll { x: T#WrappedType => + 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))) + } } } + + if (tpe == SHeader) { + VersionContext.withVersions(VersionContext.V6SoftForkVersion, 0) { + test() + } + } else { + test() + } } property("AnyValue") { forAll { t: SPredefType => - testAnyValue(t) - testAnyValue(SOption(t)) + if (t == SHeader) { + VersionContext.withVersions(VersionContext.V6SoftForkVersion, 1) { + testAnyValue(t) + testAnyValue(SOption(t)) + } + } else { + testAnyValue(t) + testAnyValue(SOption(t)) + } } }