diff --git a/core/shared/src/main/scala/sigma/ContextVarsMap.scala b/core/shared/src/main/scala/sigma/ContextVarsMap.scala new file mode 100644 index 0000000000..5343133491 --- /dev/null +++ b/core/shared/src/main/scala/sigma/ContextVarsMap.scala @@ -0,0 +1,11 @@ +package sigma + +trait ContextVarsMap { + + def maxKey: Byte + + def getNullable(key: Byte): AnyValue + + def anyIterator: Iterator[(Byte, AnyValue)] + +} diff --git a/core/shared/src/main/scala/sigma/SigmaDsl.scala b/core/shared/src/main/scala/sigma/SigmaDsl.scala index df2b419273..18016983db 100644 --- a/core/shared/src/main/scala/sigma/SigmaDsl.scala +++ b/core/shared/src/main/scala/sigma/SigmaDsl.scala @@ -557,7 +557,7 @@ trait Context { */ def getVar[T](id: Byte)(implicit cT: RType[T]): Option[T] - def vars: Coll[AnyValue] + def vars: ContextVarsMap /** Maximum version of ErgoTree currently activated on the network. * See [[ErgoLikeContext]] class for details. */ diff --git a/data/shared/src/main/scala/sigma/interpreter/ContextExtension.scala b/data/shared/src/main/scala/sigma/interpreter/ContextExtension.scala index e8cdb7d709..b2439916e3 100644 --- a/data/shared/src/main/scala/sigma/interpreter/ContextExtension.scala +++ b/data/shared/src/main/scala/sigma/interpreter/ContextExtension.scala @@ -1,7 +1,7 @@ package sigma.interpreter +import debox.cfor import sigma.ast.{EvaluatedValue, SType} -import sigma.interpreter.ContextExtension.VarBinding import sigma.serialization.{SigmaByteReader, SigmaByteWriter, SigmaSerializer} /** @@ -15,15 +15,12 @@ import sigma.serialization.{SigmaByteReader, SigmaByteWriter, SigmaSerializer} * * @param values internal container of the key-value pairs */ -case class ContextExtension(values: scala.collection.Map[Byte, EvaluatedValue[_ <: SType]]) { - def add(bindings: VarBinding*): ContextExtension = - ContextExtension(values ++ bindings) -} +case class ContextExtension(values: SigmaMap) object ContextExtension { /** Immutable instance of empty ContextExtension, which can be shared to avoid * allocations. */ - val empty = ContextExtension(Map()) + val empty: ContextExtension = ContextExtension(SigmaMap.empty) /** Type of context variable binding. */ type VarBinding = (Byte, EvaluatedValue[_ <: SType]) @@ -34,16 +31,37 @@ object ContextExtension { if (size > Byte.MaxValue) error(s"Number of ContextExtension values $size exceeds ${Byte.MaxValue}.") w.putUByte(size) - obj.values.foreach { case (id, v) => w.put(id).putValue(v) } + obj.values.iterator.foreach { case (id, v) => w.put(id).putValue(v) } } override def parse(r: SigmaByteReader): ContextExtension = { val extSize = r.getByte() - if (extSize < 0) + if (extSize < 0) { error(s"Negative amount of context extension values: $extSize") - val values = (0 until extSize) - .map(_ => (r.getByte(), r.getValue().asInstanceOf[EvaluatedValue[_ <: SType]])) - ContextExtension(values.toMap) + } + if (extSize > Byte.MaxValue + 1) { + error(s"Too many context extension values: $extSize") + } + if (extSize == 0) { + ContextExtension.empty + } else { + val size = extSize + val keys = new Array[Byte](size) + val values = new Array[EvaluatedValue[_ <: SType]](size) + var maxKey: Byte = -1 + cfor(0)(_ < size, _ + 1) { i => + val key = r.getByte() + if (key < 0) { + error(s"Negative key in context extension: $key") + } + if (key > maxKey) { + maxKey = key + } + keys(i) = key + values(i) = r.getValue().asInstanceOf[EvaluatedValue[_ <: SType]] + } + ContextExtension(SigmaMap(keys, values, maxKey)) + } } } } \ No newline at end of file diff --git a/data/shared/src/main/scala/sigma/interpreter/SigmaMap.scala b/data/shared/src/main/scala/sigma/interpreter/SigmaMap.scala new file mode 100644 index 0000000000..c214eaecd5 --- /dev/null +++ b/data/shared/src/main/scala/sigma/interpreter/SigmaMap.scala @@ -0,0 +1,344 @@ +package sigma.interpreter + +import debox.cfor +import sigma.{AnyValue, Coll, Colls, ContextVarsMap} +import sigma.ast.SType.AnyOps +import sigma.ast.{EvaluatedValue, SType} +import sigma.eval.Extensions.toAnyValue +import sigma.interpreter.SigmaMap.evalToAny + +import scala.collection.Iterator + +/** + * Map data structure with traversal ordering corresponding to used in default scala.collection.Map implementation + * as of Scala 2.12.18. Made in order to make order of ContextExtension variables independent of possible Scala SDK + * changes and to provide a simple data structure for translating into other programming languages. + */ +abstract class SigmaMap extends ContextVarsMap { + + def maxKey: Byte + + def size: Int + + def isEmpty: Boolean = size == 0 + + def contains(key: Byte): Boolean + + def getNullable(key: Byte): AnyValue + + def get(key: Byte): Option[EvaluatedValue[_ <: SType]] + + def apply(key: Byte): Option[EvaluatedValue[_ <: SType]] = get(key) + + def iterator: Iterator[(Byte, EvaluatedValue[_ <: SType])] + + override def anyIterator: Iterator[(Byte, AnyValue)] = { + iterator.map { case (k,v) => + val tVal = sigma.Evaluation.stypeToRType[SType](v.tpe) + k -> toAnyValue(v.value.asWrappedType)(tVal).asInstanceOf[AnyValue] + } + + } + + override def equals(obj: Any): Boolean = { + obj match { + case that: SigmaMap => + that.size == this.size && + that.maxKey == this.maxKey && + that.iterator.toMap == this.iterator.toMap + case _ => false + } + } + + // todo: define hashCode() +} + +object EmptySigmaMap extends SigmaMap { + override val maxKey = -1 + override val size = 0 + + override def iterator: Iterator[(Byte, EvaluatedValue[_ <: SType])] = Iterator.empty + + override def contains(key: Byte): Boolean = false + + override def getNullable(key: Byte): AnyValue = null + + override def get(key: Byte): Option[EvaluatedValue[_ <: SType]] = None +} + +class SigmaMap1(key1: Byte, value1: EvaluatedValue[_ <: SType]) extends SigmaMap { + override val maxKey = key1 + override val size = 1 + + override def iterator: Iterator[(Byte, EvaluatedValue[_ <: SType])] = Iterator.single((key1, value1)) + + override def contains(key: Byte): Boolean = { + key == key1 + } + + override def getNullable(key: Byte): AnyValue = { + if (key == key1) { + evalToAny(value1) + } else { + null + } + } + + override def get(key: Byte): Option[EvaluatedValue[_ <: SType]] = { + if (key == key1) { + Some(value1) + } else { + None + } + } +} + +class SigmaMap2(key1: Byte, value1: EvaluatedValue[_ <: SType], + key2: Byte, value2: EvaluatedValue[_ <: SType]) extends SigmaMap { + + override val maxKey = { + if (key1 >= key2) key1 + else key2 + } + + override val size = 2 + + override def iterator: Iterator[(Byte, EvaluatedValue[_ <: SType])] = { + new Iterator[(Byte, EvaluatedValue[_ <: SType])] { + private[this] var i = 0 + + override def hasNext: Boolean = i < 2 + + override def next(): (Byte, EvaluatedValue[_ <: SType]) = { + val result = i match { + case 0 => (key1, value1) + case 1 => (key2, value2) + case _ => Iterator.empty.next() + } + i += 1 + result + } + } + } + + override def contains(key: Byte): Boolean = { + key == key1 || key == key2 + } + + override def getNullable(key: Byte): AnyValue = { + if (key == key1) evalToAny(value1) + else if (key == key2) evalToAny(value2) + else null + } + + override def get(key: Byte): Option[EvaluatedValue[_ <: SType]] = { + if (key == key1) Some(value1) + else if (key == key2) Some(value2) + else null + } +} + + + +class SigmaMap3(key1: Byte, value1: EvaluatedValue[_ <: SType], + key2: Byte, value2: EvaluatedValue[_ <: SType], + key3: Byte, value3: EvaluatedValue[_ <: SType]) extends SigmaMap { + override val maxKey = Math.max(Math.max(key1, key2), key3).toByte + override val size = 3 + + override def contains(key: Byte): Boolean = { + key == key1 || key == key2 || key == key3 + } + + override def getNullable(key: Byte): AnyValue = { + if (key == key1) evalToAny(value1) + else if (key == key2) evalToAny(value2) + else if (key == key3) evalToAny(value3) + else null + } + + override def get(key: Byte): Option[EvaluatedValue[_ <: SType]] = { + if (key == key1) Some(value1) + else if (key == key2) Some(value2) + else if (key == key3) Some(value3) + else null + } + + override def iterator: Iterator[(Byte, EvaluatedValue[_ <: SType])] = { + new Iterator[(Byte, EvaluatedValue[_ <: SType])] { + private[this] var i = 0 + + override def hasNext: Boolean = i < 3 + + override def next(): (Byte, EvaluatedValue[_ <: SType]) = { + val result = i match { + case 0 => (key1, value1) + case 1 => (key2, value2) + case 2 => (key3, value3) + case _ => Iterator.empty.next() + } + i += 1 + result + } + } + } +} + +class SigmaMap4(key1: Byte, value1: EvaluatedValue[_ <: SType], + key2: Byte, value2: EvaluatedValue[_ <: SType], + key3: Byte, value3: EvaluatedValue[_ <: SType], + key4: Byte, value4: EvaluatedValue[_ <: SType]) extends SigmaMap { + override val maxKey = Math.max(Math.max(key1, key2), Math.max(key3, key4)).toByte + override val size = 4 + + override def contains(key: Byte): Boolean = { + key == key1 || key == key2 || key == key3 || key == key4 + } + + override def getNullable(key: Byte): AnyValue = { + if (key == key1) evalToAny(value1) + else if (key == key2) evalToAny(value2) + else if (key == key3) evalToAny(value3) + else if (key == key4) evalToAny(value4) + else null + } + + override def get(key: Byte): Option[EvaluatedValue[_ <: SType]] = { + if (key == key1) Some(value1) + else if (key == key2) Some(value2) + else if (key == key3) Some(value3) + else if (key == key4) Some(value4) + else null + } + + override def iterator: Iterator[(Byte, EvaluatedValue[_ <: SType])] = { + new Iterator[(Byte, EvaluatedValue[_ <: SType])] { + private[this] var i = 0 + + override def hasNext: Boolean = i < 4 + + override def next(): (Byte, EvaluatedValue[_ <: SType]) = { + val result = i match { + case 0 => (key1, value1) + case 1 => (key2, value2) + case 2 => (key3, value3) + case 3 => (key4, value4) + case _ => Iterator.empty.next() + } + i += 1 + result + } + } + } +} + +class SigmaMapMulti(private val sparseValues: Array[EvaluatedValue[_ <: SType]], + val maxKey: Byte, + val size: Int) extends SigmaMap { + + override def contains(key: Byte): Boolean = sparseValues(key) != null + + override def getNullable(key: Byte): AnyValue = { + val v = sparseValues(key) + val tVal = sigma.Evaluation.stypeToRType[SType](v.tpe) + toAnyValue(v.value.asWrappedType)(tVal).asInstanceOf[AnyValue] + } + + override def get(key: Byte): Option[EvaluatedValue[_ <: SType]] = { + val res = sparseValues(key) + Option(res) + } + + override def iterator: Iterator[(Byte, EvaluatedValue[_ <: SType])] = { + + val s = size + + new Iterator[(Byte, EvaluatedValue[_ <: SType])] { + var iteratedOver = 0 + + var indexPos = 0 + + override val knownSize = s + + override val size = s + + override def hasNext: Boolean = { + iteratedOver < size + } + + override def next(): (Byte, EvaluatedValue[_ <: SType]) = { + if (iteratedOver > size) { + throw new NoSuchElementException("next on empty iterator") + } else { + var res: EvaluatedValue[_ <: SType] = null + var key: Byte = 0 + do { + key = SigmaMap.indices(indexPos) + if (key <= maxKey) { + res = sparseValues(key) + } + indexPos += 1 + } while (res == null) + iteratedOver += 1 + key -> res + } + } + } + } +} + +object SigmaMap { + + def evalToAny(value: EvaluatedValue[_ <: SType]): AnyValue = { + val tVal = sigma.Evaluation.stypeToRType[SType](value.tpe) + toAnyValue(value.value.asWrappedType)(tVal).asInstanceOf[AnyValue] + } + + def apply(values: scala.collection.Map[Byte, EvaluatedValue[_ <: SType]]): SigmaMap = { + if (values.isEmpty) { + EmptySigmaMap + } else { + var size = values.size + val maxKey = values.keys.max + val ks = new Array[Byte](size) + val vs = new Array[EvaluatedValue[_ <: SType]](size) + + var i = 0; + values.foreach { case (k, v) => + ks(i) = k + vs(i) = v + i = i + 1 + } + SigmaMap(ks, vs, maxKey) + } + } + + // todo: cover with test + def apply(keys: Array[Byte], values: Array[EvaluatedValue[_ <: SType]], maxKey: Byte): SigmaMap = { + if(keys.isEmpty) { + EmptySigmaMap + } else if (keys.length == 1) { + new SigmaMap1(keys(0), values(0)) + } else if (keys.length == 2) { + new SigmaMap2(keys(0), values(0), keys(1), values(1)) + } else if (keys.length == 3) { + new SigmaMap3(keys(0), values(0), keys(1), values(1), keys(2), values(2)) + } else if (keys.length == 4) { + new SigmaMap4(keys(0), values(0), keys(1), values(1), keys(2), values(2), keys(3), values(3)) + } else { + val res = new Array[EvaluatedValue[_ <: SType]](maxKey + 1) + cfor(0)(_ < keys.length, _ + 1) { i => + val k = keys(i) + val v = values(i) + res(k) = v + } + new SigmaMapMulti(res, maxKey, keys.length) + } + } + + val indices: Array[Byte] = Array[Byte](69, 101, 0, 88, 115, 5, 120, 10, 56, 42, 24, 37, 25, 52, 14, 110, 125, 20, 46, 93, 57, 78, 29, 106, 121, 84, 61, 89, 116, 1, 74, 6, 60, 117, 85, 102, 28, 38, 70, 21, 33, 92, 65, 97, 9, 53, 109, 124, 77, 96, 13, 41, 73, 105, 2, 32, 34, 45, 64, 17, 22, 44, 59, 118, 27, 71, 12, 54, 49, 86, 113, 81, 76, 7, 39, 98, 103, 91, 66, 108, 3, 80, 35, 112, 123, 48, 63, 18, 95, 50, 67, 16, 127, 31, 11, 72, 43, 99, 87, 104, 40, 26, 55, 114, 23, 8, 75, 119, 58, 82, 36, 30, 51, 19, 107, 4, 126, 79, 94, 47, 15, 68, 62, 90, 111, 122, 83, 100) + + def empty: SigmaMap = EmptySigmaMap +} + + diff --git a/interpreter/shared/src/main/scala/org/ergoplatform/ErgoLikeContext.scala b/interpreter/shared/src/main/scala/org/ergoplatform/ErgoLikeContext.scala index 8468175631..252d7825e8 100644 --- a/interpreter/shared/src/main/scala/org/ergoplatform/ErgoLikeContext.scala +++ b/interpreter/shared/src/main/scala/org/ergoplatform/ErgoLikeContext.scala @@ -2,14 +2,13 @@ package org.ergoplatform import debox.cfor import sigma.Extensions.ArrayOps -import sigma.ast.SType.{AnyOps, TypeCode} +import sigma.ast.SType.TypeCode import sigma.ast._ -import sigma.data.{AvlTreeData, CAvlTree, CSigmaDslBuilder, SigmaConstants} -import sigma.eval.Extensions.toAnyValue +import sigma.data.{AvlTreeData, CAvlTree, SigmaConstants} import sigma.exceptions.InterpreterException import sigma.interpreter.ContextExtension import sigma.validation.SigmaValidationSettings -import sigma.{AnyValue, Coll, Header, PreHeader} +import sigma.{Coll, Header, PreHeader} import sigmastate.eval.Extensions._ import sigmastate.eval._ import sigmastate.interpreter.InterpreterContext @@ -136,17 +135,6 @@ class ErgoLikeContext(val lastBlockUtxoRoot: AvlTreeData, ErgoLikeContext.copy(this)(spendingTransaction = newSpendingTransaction) override def toSigmaContext(): sigma.Context = { - import sigma.Evaluation._ - - def contextVars(m: Map[Byte, AnyValue]): Coll[AnyValue] = { - val maxKey = if (m.keys.isEmpty) 0 else m.keys.max // TODO optimize: max takes 90% of this method - val res = new Array[AnyValue](maxKey + 1) - for ((id, v) <- m) { - res(id) = v - } - CSigmaDslBuilder.Colls.fromArray(res) - } - val dataInputs = this.dataBoxes.toArray.map(_.toTestBox).toColl val inputs = boxesToSpend.toArray.map(_.toTestBox).toColl /* NOHF PROOF: @@ -156,11 +144,7 @@ class ErgoLikeContext(val lastBlockUtxoRoot: AvlTreeData, Examined ergo code: all that leads to ErgoLikeContext creation. */ val outputs = spendingTransaction.outputs.toArray.map(_.toTestBox).toColl - val varMap = extension.values.map { case (k, v: EvaluatedValue[_]) => - val tVal = stypeToRType[SType](v.tpe) - k -> toAnyValue(v.value.asWrappedType)(tVal) - }.toMap - val vars = contextVars(varMap) + val contextExtVars = extension.values val avlTree = CAvlTree(lastBlockUtxoRoot) // so selfBox is never one of the `inputs` instances // as result selfBoxIndex is always (erroneously) returns -1 in ErgoTree v0, v1 @@ -169,7 +153,7 @@ class ErgoLikeContext(val lastBlockUtxoRoot: AvlTreeData, syntax.error(s"Undefined context property: currentErgoTreeVersion")) CContext( dataInputs, headers, preHeader, inputs, outputs, preHeader.height, selfBox, selfIndex, avlTree, - preHeader.minerPk.getEncoded, vars, activatedScriptVersion, ergoTreeVersion) + preHeader.minerPk.getEncoded, contextExtVars, activatedScriptVersion, ergoTreeVersion) } diff --git a/interpreter/shared/src/main/scala/sigmastate/eval/CContext.scala b/interpreter/shared/src/main/scala/sigmastate/eval/CContext.scala index 418f9e8959..24a5fa67ec 100644 --- a/interpreter/shared/src/main/scala/sigmastate/eval/CContext.scala +++ b/interpreter/shared/src/main/scala/sigmastate/eval/CContext.scala @@ -69,19 +69,19 @@ object CFunc { * @see [[Context]] for detailed descriptions */ case class CContext( - _dataInputs: Coll[Box], - override val headers: Coll[Header], - override val preHeader: PreHeader, - inputs: Coll[Box], - outputs: Coll[Box], - height: Int, - selfBox: Box, - private val selfIndex: Int, - lastBlockUtxoRootHash: AvlTree, - _minerPubKey: Coll[Byte], - vars: Coll[AnyValue], - override val activatedScriptVersion: Byte, - override val currentErgoTreeVersion: Byte + _dataInputs: Coll[Box], + override val headers: Coll[Header], + override val preHeader: PreHeader, + inputs: Coll[Box], + outputs: Coll[Box], + height: Int, + selfBox: Box, + private val selfIndex: Int, + lastBlockUtxoRootHash: AvlTree, + _minerPubKey: Coll[Byte], + vars: ContextVarsMap, + override val activatedScriptVersion: Byte, + override val currentErgoTreeVersion: Byte ) extends Context { @inline override def builder: SigmaDslBuilder = CSigmaDslBuilder @@ -112,8 +112,8 @@ case class CContext( override def getVar[T](id: Byte)(implicit tT: RType[T]): Option[T] = { @unused // avoid warning about unused ctA implicit val tag: ClassTag[T] = tT.classTag - if (id < 0 || id >= vars.length) return None - val value = vars(id) + if (id < 0 || id > vars.maxKey) return None + val value = vars.getNullable(id) if (value != null) { // once the value is not null it should be of the right type value match { @@ -125,33 +125,5 @@ case class CContext( } else None } - /** Return a new context instance with variables collection updated. - * @param bindings a new binding of the context variables with new values. - * @return a new instance (if `bindings` non-empty) with the specified bindings. - * other existing bindings are copied to the new instance - */ - def withUpdatedVars(bindings: (Int, AnyValue)*): CContext = { - if (bindings.isEmpty) return this - - val ids = bindings.map(_._1).toArray - val values = bindings.map(_._2).toArray - val maxVarId = ids.max // INV: ids is not empty - val requiredNewLength = maxVarId + 1 - - val newVars = if (vars.length < requiredNewLength) { - // grow vars collection - val currVars = vars.toArray - val buf = new Array[AnyValue](requiredNewLength) - Array.copy(currVars, 0, buf, 0, currVars.length) - cfor(0)(_ < ids.length, _ + 1) { i => - buf(ids(i)) = values(i) - } - buf.toColl - } else { - vars.updateMany(ids.toColl, values.toColl) - } - - this.copy(vars = newVars) - } } diff --git a/interpreter/shared/src/main/scala/sigmastate/interpreter/InterpreterContext.scala b/interpreter/shared/src/main/scala/sigmastate/interpreter/InterpreterContext.scala index 38fc78fdb6..3e03d54170 100644 --- a/interpreter/shared/src/main/scala/sigmastate/interpreter/InterpreterContext.scala +++ b/interpreter/shared/src/main/scala/sigmastate/interpreter/InterpreterContext.scala @@ -1,6 +1,6 @@ package sigmastate.interpreter -import sigma.interpreter.ContextExtension +import sigma.interpreter.{ContextExtension, SigmaMap} import sigma.interpreter.ContextExtension.VarBinding import sigma.validation.SigmaValidationSettings @@ -45,10 +45,13 @@ trait InterpreterContext { /** Creates a new instance with extension updated with given value. */ def withExtension(newExtension: ContextExtension): InterpreterContext - /** Creates a new instance with given bindings added to extension. */ + /** + * Creates a new instance with given bindings added to extension. + * USED IN TESTS ONLY! thus not optimized for efficiency + */ def withBindings(bindings: VarBinding*): InterpreterContext = { - val ext = extension.add(bindings: _*) - withExtension(ext) + val ext = extension.values.iterator.toMap ++ bindings + withExtension(ContextExtension(SigmaMap(ext))) } /** Creates a new instance with given validation settings. */ 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..0a07eb0a49 100644 --- a/interpreter/shared/src/test/scala/sigma/serialization/generators/ObjectGenerators.scala +++ b/interpreter/shared/src/test/scala/sigma/serialization/generators/ObjectGenerators.scala @@ -29,11 +29,10 @@ import sigma.validation.ValidationRules.FirstRuleId import ErgoTree.ZeroHeader import sigma.eval.Extensions.{EvalIterableOps, SigmaBooleanOps} import sigma.eval.SigmaDsl -import sigma.interpreter.{ContextExtension, ProverResult} +import sigma.interpreter.{ContextExtension, ProverResult, SigmaMap} import java.math.BigInteger import scala.collection.compat.immutable.ArraySeq -import scala.collection.mutable import scala.collection.mutable.ListBuffer import scala.reflect.ClassTag @@ -243,7 +242,7 @@ trait ObjectGenerators extends TypeGenerators lazy val contextExtensionGen: Gen[ContextExtension] = for { values: scala.collection.Seq[(Byte, EvaluatedValue[SType])] <- Gen.sequence(contextExtensionValuesGen(0, 5))(Buildable.buildableSeq) - } yield ContextExtension(mutable.LinkedHashMap[Byte, EvaluatedValue[SType]](values.sortBy(_._1).toSeq:_*)) + } yield ContextExtension(SigmaMap(values.sortBy(_._1).toMap)) lazy val serializedProverResultGen: Gen[ProverResult] = for { bytes <- arrayOfRange(1, 100, arbByte.arbitrary) diff --git a/interpreter/shared/src/test/scala/sigmastate/helpers/ContextEnrichingProverInterpreter.scala b/interpreter/shared/src/test/scala/sigmastate/helpers/ContextEnrichingProverInterpreter.scala index 2eaac82790..48a4688f08 100644 --- a/interpreter/shared/src/test/scala/sigmastate/helpers/ContextEnrichingProverInterpreter.scala +++ b/interpreter/shared/src/test/scala/sigmastate/helpers/ContextEnrichingProverInterpreter.scala @@ -2,7 +2,7 @@ package sigmastate.helpers import sigma.ast.{ErgoTree, SType} import sigma.ast.EvaluatedValue -import sigma.interpreter.{ContextExtension, CostedProverResult} +import sigma.interpreter.{ContextExtension, CostedProverResult, SigmaMap} import sigmastate.interpreter.Interpreter.ScriptEnv import sigmastate.interpreter.{HintsBag, ProverInterpreter} @@ -15,12 +15,14 @@ import scala.util.Try * Note: context is included into message (under hash function), thus changed context * also changes message. This trait may be useful for tests, that sign fake messages, * or for transactions which inputs does not require signatures. + * + * USED IN TESTS ONLY */ trait ContextEnrichingProverInterpreter extends ProverInterpreter { def contextExtenders: Map[Byte, EvaluatedValue[_ <: SType]] = Map.empty - val knownExtensions = ContextExtension(contextExtenders) + def knownExtensions = ContextExtension(SigmaMap(contextExtenders)) /** * Replace context.extension to knownExtensions and prove script in different context. diff --git a/interpreter/shared/src/test/scala/sigmastate/helpers/ContextEnrichingTestProvingInterpreter.scala b/interpreter/shared/src/test/scala/sigmastate/helpers/ContextEnrichingTestProvingInterpreter.scala index 0d4c1904ec..e43caea95e 100644 --- a/interpreter/shared/src/test/scala/sigmastate/helpers/ContextEnrichingTestProvingInterpreter.scala +++ b/interpreter/shared/src/test/scala/sigmastate/helpers/ContextEnrichingTestProvingInterpreter.scala @@ -13,13 +13,13 @@ class ContextEnrichingTestProvingInterpreter i.toByte -> ByteArrayConstant(ba) }.toMap - def withContextExtender(tag: Byte, value: EvaluatedValue[_ <: SType]): ContextEnrichingTestProvingInterpreter = { + def withContextExtender(varId: Byte, value: EvaluatedValue[_ <: SType]): ContextEnrichingTestProvingInterpreter = { val s = secrets val ce = contextExtenders new ContextEnrichingTestProvingInterpreter { override lazy val secrets = s - override lazy val contextExtenders: Map[Byte, EvaluatedValue[_ <: SType]] = ce + (tag -> value) + override lazy val contextExtenders: Map[Byte, EvaluatedValue[_ <: SType]] = ce + (varId -> value) } } diff --git a/interpreter/shared/src/test/scala/special/sigma/ContractsTestkit.scala b/interpreter/shared/src/test/scala/special/sigma/ContractsTestkit.scala index 720223ee3a..8ae4e12b7c 100644 --- a/interpreter/shared/src/test/scala/special/sigma/ContractsTestkit.scala +++ b/interpreter/shared/src/test/scala/special/sigma/ContractsTestkit.scala @@ -46,18 +46,6 @@ trait ContractsTestkit { Colls.fromArray(res) } - /** Converts a map of context vars to collection of context vars. */ - def contextVars(m: Map[Byte, AnyValue]): Coll[AnyValue] = { - val maxKey = if (m.keys.isEmpty) 0 else m.keys.max // TODO optimize: max takes 90% of this method - val res = new Array[AnyValue](maxKey) - for ( (id, v) <- m ) { - val i = id - 1 - assert(res(i) == null, s"register $id is defined more then once") - res(i) = v - } - Colls.fromArray(res) - } - val AliceId = Array[Byte](1) // 0x0001 def newAliceBox(@nowarn id: Byte, value: Long): Box = { @@ -70,29 +58,26 @@ trait ContractsTestkit { def testContext( inputs: Array[Box], outputs: Array[Box], height: Int, self: Box, tree: AvlTree, minerPk: Array[Byte], activatedScriptVersion: Byte, - currErgoTreeVersion: Byte, vars: Array[AnyValue]) = + currErgoTreeVersion: Byte, vars: ContextVarsMap) = new CContext( noInputs.toColl, noHeaders, dummyPreHeader, inputs.toColl, outputs.toColl, height, self, inputs.indexOf(self), tree, - minerPk.toColl, vars.toColl, activatedScriptVersion, currErgoTreeVersion) + minerPk.toColl, vars, activatedScriptVersion, currErgoTreeVersion) def newContext( height: Int, self: Box, activatedScriptVersion: Byte, currErgoTreeVersion: Byte, - vars: AnyValue*): CContext = { + vars: ContextVarsMap): CContext = { testContext( noInputs, noOutputs, height, self, emptyAvlTree, dummyPubkey, - activatedScriptVersion, currErgoTreeVersion, vars.toArray) + activatedScriptVersion, currErgoTreeVersion, vars) } implicit class TestContextOps(ctx: CContext) { def withInputs(inputs: Box*) = ctx.copy(inputs = inputs.toArray.toColl) def withOutputs(outputs: Box*) = ctx.copy(outputs = outputs.toArray.toColl) - - def withVariables(vars: Map[Int, AnyValue]) = - ctx.copy(vars = contextVars(vars.map { case (k, v) => (k.toByte, v) })) } } diff --git a/sc/js/src/main/scala/sigmastate/lang/js/SigmaCompiler.scala b/sc/js/src/main/scala/sigmastate/lang/js/SigmaCompiler.scala index 8fe26a30b0..bf72bdc429 100644 --- a/sc/js/src/main/scala/sigmastate/lang/js/SigmaCompiler.scala +++ b/sc/js/src/main/scala/sigmastate/lang/js/SigmaCompiler.scala @@ -19,19 +19,19 @@ class SigmaCompiler(_compiler: sigmastate.lang.SigmaCompiler) extends js.Object /** Compiles ErgoScript code to ErgoTree. * - * @param namedConstants named constants to be used in the script - * @param segregateConstants if true, then constants will be segregated from the tree + * @param namedConstants named constants to be used in the script + * @param segregateConstants if true, then constants will be segregated from the tree * @param additionalHeaderFlags additional header flags to be set in the tree - * @param ergoScript ErgoScript code to be compiled + * @param ergoScript ErgoScript code to be compiled * @return ErgoTree instance */ def compile( - namedConstants: StringDictionary[Value], - segregateConstants: Boolean, - treeHeader: Byte, ergoScript: String): ErgoTree = { + namedConstants: StringDictionary[Value], + segregateConstants: Boolean, + treeHeader: Byte, ergoScript: String): ErgoTree = { val env = StringDictionary - .wrapStringDictionary(namedConstants) - .view.mapValues(v => isoValueToConstant.to(v)).toMap + .wrapStringDictionary(namedConstants) + .view.mapValues(v => isoValueToConstant.to(v)).toMap val IR = new CompiletimeIRContext val prop = _compiler.compile(env, ergoScript)(IR).buildTree require(prop.tpe.isSigmaProp, s"Expected SigmaProp expression type bue got ${prop.tpe}: $prop") diff --git a/sc/js/src/test/scala/scalan/Platform.scala b/sc/js/src/test/scala/scalan/Platform.scala index 5f938111d2..1fc2ae1271 100644 --- a/sc/js/src/test/scala/scalan/Platform.scala +++ b/sc/js/src/test/scala/scalan/Platform.scala @@ -5,10 +5,10 @@ import scala.annotation.unused object Platform { /** In JS tests do nothing. The corresponding JVM method outputs graphs into files. */ def stage[Ctx <: Scalan](scalan: Ctx)( - @unused prefix: String, - @unused testName: String, - @unused name: String, - @unused sfs: Seq[() => scalan.Sym]): Unit = { + @unused prefix: String, + @unused testName: String, + @unused name: String, + @unused sfs: Seq[() => scalan.Sym]): Unit = { } /** On JS it is no-operation. */ diff --git a/sc/jvm/src/test/scala/scalan/Platform.scala b/sc/jvm/src/test/scala/scalan/Platform.scala index 43041a92c6..d0a2f3b31a 100644 --- a/sc/jvm/src/test/scala/scalan/Platform.scala +++ b/sc/jvm/src/test/scala/scalan/Platform.scala @@ -6,14 +6,15 @@ import org.scalatest.Assertions object Platform { /** Output graph given by symbols in `sfs` to files. - * @param scalan The Scalan context - * @param prefix The prefix of the directory where the graphs will be stored. + * + * @param scalan The Scalan context + * @param prefix The prefix of the directory where the graphs will be stored. * @param testName The name of the test. - * @param name The name of the graph. - * @param sfs A sequence of functions that return symbols of the graph roots. + * @param name The name of the graph. + * @param sfs A sequence of functions that return symbols of the graph roots. */ def stage[Ctx <: Scalan](scalan: Ctx) - (prefix: String, testName: String, name: String, sfs: Seq[() => scalan.Sym]): Unit = { + (prefix: String, testName: String, name: String, sfs: Seq[() => scalan.Sym]): Unit = { val directory = FileUtil.file(prefix, testName) val gv = new GraphVizExport(scalan) implicit val graphVizConfig = gv.defaultGraphVizConfig diff --git a/sc/jvm/src/test/scala/scalan/compilation/GraphVizExport.scala b/sc/jvm/src/test/scala/scalan/compilation/GraphVizExport.scala index 8568e6c1db..50c225b4ae 100644 --- a/sc/jvm/src/test/scala/scalan/compilation/GraphVizExport.scala +++ b/sc/jvm/src/test/scala/scalan/compilation/GraphVizExport.scala @@ -13,7 +13,9 @@ import scala.collection.immutable.StringOps /** Implementation of Graphviz's dot file generator. */ class GraphVizExport[Ctx <: Scalan](val scalan: Ctx) { + import scalan._ + case class GraphFile(file: File, fileType: String) { def open() = { Desktop.getDesktop.open(file) @@ -159,14 +161,19 @@ class GraphVizExport[Ctx <: Scalan](val scalan: Ctx) { def emitDepGraph(d: Def[_], directory: File, fileName: String)(implicit config: GraphVizConfig): Option[GraphFile] = emitDepGraph(d.deps, directory, fileName)(config) + def emitDepGraph(start: Sym, directory: File, fileName: String)(implicit config: GraphVizConfig): Option[GraphFile] = emitDepGraph(List(start), directory, fileName)(config) + def emitDepGraph(ss: Seq[Sym], directory: File, fileName: String)(implicit config: GraphVizConfig): Option[GraphFile] = emitDepGraph(new PGraph(ss.toList), directory, fileName)(config) + def emitExceptionGraph(e: Throwable, directory: File, fileName: String)(implicit config: GraphVizConfig): Option[GraphFile] = emitDepGraph(Left(e), directory, fileName) + def emitDepGraph(graph: AstGraph, directory: File, fileName: String)(implicit config: GraphVizConfig): Option[GraphFile] = emitDepGraph(Right(graph), directory, fileName) + def emitDepGraph(exceptionOrGraph: Either[Throwable, AstGraph], directory: File, fileName: String)(implicit config: GraphVizConfig): Option[GraphFile] = emitGraphFile(directory, fileName)(emitDepGraph(exceptionOrGraph, fileName)(_, config)) @@ -202,6 +209,7 @@ class GraphVizExport[Ctx <: Scalan](val scalan: Ctx) { implicit class SeqExpExtensionsForEmitGraph(symbols: Seq[Sym]) { // Not default argument to allow use from the debugger def show(): Unit = show(defaultGraphVizConfig) + def show(config: GraphVizConfig): Unit = showGraphs(symbols: _*)(config) } @@ -319,7 +327,9 @@ class GraphVizExport[Ctx <: Scalan](val scalan: Ctx) { private sealed trait Label { def label: String } + private case class NoAlias(label: String) extends Label + private case class Alias(label: String, rhs: String, td: TypeDesc) extends Label private case class GraphData(nodes: Map[Sym, Option[Def[_]]], labels: Map[TypeDesc, Label], aliases: List[Alias], aliasCounter: Int)(implicit config: GraphVizConfig) { @@ -398,10 +408,10 @@ class GraphVizExport[Ctx <: Scalan](val scalan: Ctx) { orderedAliases.foreach { case Alias(label, rhs, td) => stream.println(s"""$label [label="type $label = $rhs", shape=box, style=rounded, color=${nodeColor(td)}, fillcolor=white]""") - // Below code doesn't show the dependencies correctly -// if (config.typeAliasEdges) { -// partsIterator(td).foreach(emitAliasEdge(label, _)) -// } + // Below code doesn't show the dependencies correctly + // if (config.typeAliasEdges) { + // partsIterator(td).foreach(emitAliasEdge(label, _)) + // } } if (aliases.length > 1) { stream.println(orderedAliases.map(_.label).mkString(" -> ") + " [style=invis]") @@ -415,6 +425,7 @@ class GraphVizExport[Ctx <: Scalan](val scalan: Ctx) { } } } + private object GraphData { def empty(implicit config: GraphVizConfig) = GraphData(Map.empty, Map.empty, Nil, 0) } @@ -445,14 +456,18 @@ class GraphVizExport[Ctx <: Scalan](val scalan: Ctx) { } sealed trait Orientation + case object Portrait extends Orientation + case object Landscape extends Orientation object Orientation { } sealed trait ControlFlowStyle + case object ControlFlowWithBoxes extends ControlFlowStyle + case object ControlFlowWithArrows extends ControlFlowStyle // outside the cake to be usable from ItTestsUtil @@ -470,6 +485,7 @@ case class GraphVizConfig(emitGraphs: Boolean, // ensures nice line wrapping def nodeLabel(parts: Seq[String]): String = { def escape(s: String) = s.replace("""\""", """\\""").replace("\"", """\"""") + val lineBreak = """\l""" var lineLength = 0 @@ -482,7 +498,7 @@ case class GraphVizConfig(emitGraphs: Boolean, } else { lines match { case Seq() => - // do nothing + // do nothing case Seq(line) => val lineLengthIfNoNewLine = lineLength + 1 + line.length lineLength = if (lineLengthIfNoNewLine <= maxLabelLineLength) { diff --git a/sc/shared/src/test/scala/org/ergoplatform/ErgoAddressSpecification.scala b/sc/shared/src/test/scala/org/ergoplatform/ErgoAddressSpecification.scala index ad0d316519..aa388fe90a 100644 --- a/sc/shared/src/test/scala/org/ergoplatform/ErgoAddressSpecification.scala +++ b/sc/shared/src/test/scala/org/ergoplatform/ErgoAddressSpecification.scala @@ -18,11 +18,10 @@ import sigmastate.utils.Helpers._ import sigmastate.CompilerCrossVersionProps import sigma.SigmaDslTesting import sigma.ast.ErgoTree.{ZeroHeader, setConstantSegregation} - import sigma.ast.SType import sigma.data.ProveDlog import sigma.exceptions.{CostLimitException, InvalidType} -import sigma.interpreter.{ContextExtension, CostedProverResult} +import sigma.interpreter.{ContextExtension, CostedProverResult, SigmaMap} import sigma.serialization.GroupElementSerializer import sigma.validation.ValidationException import sigma.validation.ValidationRules.CheckTypeCode @@ -240,9 +239,9 @@ class ErgoAddressSpecification extends SigmaDslTesting val scriptId = Pay2SHAddress.scriptId val boxToSpend = testBox(10, address.script, creationHeight = 5) val ctx = ErgoLikeContextTesting.dummy(boxToSpend, activatedVersionInTests) - .withExtension(ContextExtension(Seq( + .withExtension(ContextExtension(SigmaMap(Seq( scriptId -> ByteArrayConstant(scriptBytes) // provide script bytes in context variable - ).toMap)) + ).toMap))) val env: ScriptEnv = Map() val prover = new ErgoLikeTestProvingInterpreter() @@ -271,9 +270,9 @@ class ErgoAddressSpecification extends SigmaDslTesting def testPay2SHAddress(address: Pay2SHAddress, script: VarBinding, costLimit: Int = scriptCostLimitInTests): CostedProverResult = { val boxToSpend = testBox(10, address.script, creationHeight = 5) val ctx = copyContext(ErgoLikeContextTesting.dummy(boxToSpend, activatedVersionInTests) - .withExtension(ContextExtension(Seq( + .withExtension(ContextExtension(SigmaMap(Seq( script // provide script bytes in context variable - ).toMap)))(costLimit = costLimit) + ).toMap))))(costLimit = costLimit) val prover = new ErgoLikeTestProvingInterpreter() val res = prover.prove(address.script, ctx, fakeMessage).getOrThrow diff --git a/sc/shared/src/test/scala/org/ergoplatform/ErgoLikeTransactionSpec.scala b/sc/shared/src/test/scala/org/ergoplatform/ErgoLikeTransactionSpec.scala index 6a7ef5a512..fb1617880d 100644 --- a/sc/shared/src/test/scala/org/ergoplatform/ErgoLikeTransactionSpec.scala +++ b/sc/shared/src/test/scala/org/ergoplatform/ErgoLikeTransactionSpec.scala @@ -14,7 +14,7 @@ import sigma.ast.syntax.{ErgoBoxCandidateRType, TrueSigmaProp} import sigma.ast._ import sigma.data.{CSigmaProp, Digest32Coll, TrivialProp} import sigma.eval.Extensions.{EvalCollOps, EvalIterableOps} -import sigma.interpreter.{ContextExtension, ProverResult} +import sigma.interpreter.{ContextExtension, ProverResult, SigmaMap} import sigma.serialization.SigmaSerializer import sigmastate.helpers.TestingHelpers.copyTransaction import sigmastate.utils.Helpers @@ -203,7 +203,10 @@ class ErgoLikeTransactionSpec extends SigmaDslTesting with JsonCodecs { (itx6.messageToSign sameElements initialMessage) shouldBe false // transaction with modified input extension - val newExtension7 = ContextExtension(headInput.spendingProof.extension.values ++ Map(Byte.MinValue -> ByteArrayConstant(Random.randomBytes(32)))) + val newExtension7 = ContextExtension(SigmaMap( + headInput.spendingProof.extension.values.iterator.toMap ++ + Map(Byte.MinValue -> ByteArrayConstant(Random.randomBytes(32))) + )) val newProof7 = new ProverResult(headInput.spendingProof.proof, newExtension7) val headInput7 = headInput.copy(spendingProof = newProof7) val itx7 = new ErgoLikeTransaction(headInput7 +: tailInputs, di, txIn.outputCandidates) @@ -284,10 +287,10 @@ class ErgoLikeTransactionSpec extends SigmaDslTesting with JsonCodecs { property("context extension serialization") { forAll { (tx: ErgoLikeTransaction, startIndex: Byte, endIndex: Byte) => - whenever(endIndex >= startIndex) { + whenever(endIndex >= startIndex && startIndex >= 0) { val idRange = endIndex - startIndex - val ce = ContextExtension(startIndex.to(endIndex).map(id => id.toByte -> IntConstant(4)).toMap) + val ce = ContextExtension(SigmaMap(startIndex.to(endIndex).map(id => id.toByte -> IntConstant(4)).toMap)) val wrongInput = Input(tx.inputs.head.boxId, ProverResult(Array.emptyByteArray, ce)) val ins = IndexedSeq(wrongInput) ++ tx.inputs.tail val tx2 = copyTransaction(tx)(inputs = ins) diff --git a/sc/shared/src/test/scala/org/ergoplatform/dsl/TestContractSpec.scala b/sc/shared/src/test/scala/org/ergoplatform/dsl/TestContractSpec.scala index 26e8b08b14..4eb5870542 100644 --- a/sc/shared/src/test/scala/org/ergoplatform/dsl/TestContractSpec.scala +++ b/sc/shared/src/test/scala/org/ergoplatform/dsl/TestContractSpec.scala @@ -3,7 +3,7 @@ package org.ergoplatform.dsl import sigmastate.interpreter.Interpreter.ScriptNameProp import scala.collection.mutable -import sigma.interpreter.{CostedProverResult, ProverResult} +import sigma.interpreter.{CostedProverResult, ProverResult, SigmaMap} import scala.collection.mutable.ArrayBuffer import org.ergoplatform.ErgoBox.{NonMandatoryRegisterId, TokenId} import sigma.data.{AvlTreeData, CAnyValue, CSigmaProp, Nullable} @@ -91,7 +91,7 @@ case class TestContractSpec(testSuite: CompilerTestingCommons)(implicit val IR: ctx } def runDsl(extensions: Map[Byte, EvaluatedValue[_ <: SType]] = Map()): SigmaProp = { - val ctx = toErgoContext.withExtension(ContextExtension(extensions)).toSigmaContext() + val ctx = toErgoContext.withExtension(ContextExtension(SigmaMap(extensions))).toSigmaContext() val res = utxoBox.propSpec.dslSpec(ctx) res } diff --git a/sc/shared/src/test/scala/sigma/SigmaDslSpecification.scala b/sc/shared/src/test/scala/sigma/SigmaDslSpecification.scala index 4dd576f03a..1aa954b866 100644 --- a/sc/shared/src/test/scala/sigma/SigmaDslSpecification.scala +++ b/sc/shared/src/test/scala/sigma/SigmaDslSpecification.scala @@ -29,6 +29,7 @@ import sigmastate.helpers.TestingHelpers._ import sigmastate.interpreter._ import sigma.ast.{Apply, MethodCall, PropertyCall} import sigma.exceptions.InvalidType +import sigma.interpreter.SigmaMap import sigma.serialization.ValueCodes.OpCode import sigmastate.utils.Extensions._ import sigmastate.utils.Helpers @@ -4757,16 +4758,12 @@ class SigmaDslSpecification extends SigmaDslTesting ) ), _minerPubKey = Helpers.decodeBytes("0227a58e9b2537103338c237c52c1213bf44bdb344fa07d9df8ab826cca26ca08f"), - vars = Colls - .replicate[AnyValue](10, null) // reserve 10 vars - .append(Coll[AnyValue]( - CAnyValue(Helpers.decodeBytes("00")), - CAnyValue(true))), + vars = SigmaMap(Map(10.toByte -> ByteArrayConstant(Helpers.decodeBytes("00")), 11.toByte -> BooleanConstant(true))), activatedScriptVersion = activatedVersionInTests, currentErgoTreeVersion = ergoTreeVersionInTests ) - val ctx2 = ctx.copy(vars = Coll[AnyValue](null, null, null)) - val ctx3 = ctx.copy(vars = Coll[AnyValue]()) + val ctx2 = ctx.copy(vars = SigmaMap(Map.empty)) + val ctx3 = ctx.copy(vars = SigmaMap(Map.empty)) (input, dataBox, header, ctx, ctx2, ctx3) } diff --git a/sc/shared/src/test/scala/sigma/SigmaDslStaginTests.scala b/sc/shared/src/test/scala/sigma/SigmaDslStaginTests.scala index 5ac9b80889..71a860b9a4 100644 --- a/sc/shared/src/test/scala/sigma/SigmaDslStaginTests.scala +++ b/sc/shared/src/test/scala/sigma/SigmaDslStaginTests.scala @@ -2,8 +2,10 @@ package sigma import org.scalatest.BeforeAndAfterAll import scalan.{BaseCtxTests, BaseLiftableTests} +import sigma.ast.IntConstant import sigma.data.TrivialProp import sigma.eval.Extensions.toAnyValue +import sigma.interpreter.SigmaMap import sigmastate.eval._ import scala.language.reflectiveCalls @@ -30,9 +32,8 @@ class SigmaDslStaginTests extends BaseCtxTests with ErgoScriptTestkit with BaseL type RSigmaProp = cake.SigmaProp val boxA1 = newAliceBox(1, 100) val boxA2 = newAliceBox(2, 200) - val ctx: SContext = newContext(10, boxA1, VersionContext.MaxSupportedScriptVersion, VersionContext.MaxSupportedScriptVersion) + val ctx: SContext = newContext(10, boxA1, VersionContext.MaxSupportedScriptVersion, VersionContext.MaxSupportedScriptVersion, SigmaMap(Map(1.toByte -> IntConstant(30), 2.toByte -> IntConstant(40)))) .withInputs(boxA2) - .withVariables(Map(1 -> toAnyValue(30), 2 -> toAnyValue(40))) val p1: SSigmaProp = sigma.eval.SigmaDsl.SigmaProp(TrivialProp(true)) val p2: SSigmaProp = sigma.eval.SigmaDsl.SigmaProp(TrivialProp(false)) diff --git a/sc/shared/src/test/scala/sigma/SigmaDslTesting.scala b/sc/shared/src/test/scala/sigma/SigmaDslTesting.scala index 46222d9fb1..f8faba03a7 100644 --- a/sc/shared/src/test/scala/sigma/SigmaDslTesting.scala +++ b/sc/shared/src/test/scala/sigma/SigmaDslTesting.scala @@ -21,7 +21,7 @@ import sigma.util.StringUtil.StringUtilExtensions import sigma.ast.ErgoTree.ZeroHeader import sigma.ast.SType.AnyOps import sigma.ast.syntax.{SValue, ValueOps} -import sigma.ast._ +import sigma.ast.{Apply, SContext, _} import sigma.eval.{CostDetails, EvalSettings, SigmaDsl} import sigmastate.crypto.DLogProtocol.DLogProverInput import sigmastate.crypto.SigmaProtocolPrivateInput @@ -30,9 +30,8 @@ import sigmastate.helpers.TestingHelpers._ import sigmastate.helpers.{CompilerTestingCommons, ErgoLikeContextTesting, ErgoLikeTestInterpreter, SigmaPPrint} import sigmastate.interpreter.Interpreter.{ScriptEnv, VerificationResult} import sigmastate.interpreter._ -import sigma.ast.Apply import sigma.eval.Extensions.SigmaBooleanOps -import sigma.interpreter.{ContextExtension, ProverResult} +import sigma.interpreter.{ContextExtension, ProverResult, SigmaMap} import sigma.serialization.ValueSerializer import sigma.serialization.generators.ObjectGenerators import sigmastate.utils.Helpers._ @@ -278,11 +277,13 @@ class SigmaDslTesting extends AnyPropSpec val selfIndex = boxesToSpend.indexWhere(b => java.util.Arrays.equals(b.id, ctx.selfBox.id.toArray)) val extension = ContextExtension( - values = ctx.vars.toArray.zipWithIndex.collect { - case (v, i) if v != null => - val tpe = Evaluation.rtypeToSType(v.tVal) - i.toByte -> ConstantNode(v.value.asWrappedType, tpe) - }.toMap + SigmaMap( + ctx.vars.anyIterator.toArray.map { + case (k, v) => + val tpe = Evaluation.rtypeToSType(v.tVal) + k -> ConstantNode(v.value.asWrappedType, tpe) + }.toMap + ) ) new ErgoLikeContext( treeData, ctx.headers, ctx.preHeader, @@ -352,11 +353,12 @@ class SigmaDslTesting extends AnyPropSpec ) // We add ctx as it's own variable with id = 1 - val ctxVar = sigma.eval.Extensions.toAnyValue[sigma.Context](ctx)(sigma.ContextRType) - val carolVar = sigma.eval.Extensions.toAnyValue[Coll[Byte]](pkCarolBytes.toColl)(RType[Coll[Byte]]) + val ctxVar = Constant[SContext.type](ctx, SContext) // sigma.eval.Extensions.toAnyValue[sigma.Context](ctx)(sigma.ContextRType) + val carolVar = ByteArrayConstant(pkCarolBytes.toColl) val newCtx = ctx - .withUpdatedVars(1 -> ctxVar, 2 -> carolVar) + // .withUpdatedVars( 1 -> ctxVar, 2 -> carolVar) .copy( + vars = SigmaMap(ctx.vars.asInstanceOf[SigmaMap].iterator.toMap ++ Map(1.toByte -> ctxVar, 2.toByte -> carolVar)), selfBox = newSelf, inputs = { val selfIndex = ctx.inputs.indexWhere(b => b.id == ctx.selfBox.id, 0) diff --git a/sc/shared/src/test/scala/sigmastate/SigmaMapSpecification.scala b/sc/shared/src/test/scala/sigmastate/SigmaMapSpecification.scala new file mode 100644 index 0000000000..4f6bd6d44e --- /dev/null +++ b/sc/shared/src/test/scala/sigmastate/SigmaMapSpecification.scala @@ -0,0 +1,82 @@ +package sigmastate + +import org.scalacheck.Gen +import sigma.ast.{EvaluatedValue, IntConstant, SType} +import sigma.interpreter.SigmaMap +import sigmastate.helpers.TestingCommons + +import scala.util.Random + +class SigmaMapSpecification extends TestingCommons { + + property("SigmaMap.empty") { + val empty = SigmaMap.empty + empty.size shouldBe 0 + empty.iterator.toSeq.isEmpty shouldBe true + } + + property("SigmaMap.get") { + val id = 1.toByte + val value = IntConstant(1) + val map = Map(id -> value) + val empty = SigmaMap(map) + empty.size shouldBe map.size + empty.maxKey shouldBe 1 + empty.iterator.toSeq.toMap shouldBe map + } + + property("traversal - vector - 0 to 4") { + val sm = SigmaMap(Map( + 0.toByte -> IntConstant(0), + 1.toByte -> IntConstant(1), + 2.toByte -> IntConstant(2), + 3.toByte -> IntConstant(3), + 4.toByte -> IntConstant(4) + )) + + sm.maxKey shouldBe 4 + sm.size shouldBe 5 + sm.iterator.toList.map(_._1) shouldBe Array[Byte](0, 1, 2, 3, 4) + } + + property("traversal - vector - [73,35,31]") { + val m = Map( + 31.toByte -> IntConstant(2), + 73.toByte -> IntConstant(0), + 35.toByte -> IntConstant(1) + ) + + val sm = SigmaMap(m) + val ml = m.iterator.toList + val sml = sm.iterator.toList + + println("ml: " + ml) + println("sml: " + sml) + + ml shouldBe sml + } + + property("traversal - single") { + val id = 106.toByte + + val sm = SigmaMap(Map( + id -> IntConstant(0) + )) + + sm.maxKey shouldBe id + sm.size shouldBe 1 + sm.iterator.toList.map(_._1) shouldBe Array[Byte](id) + } + + property("traversal equivalence") { + val fullArr = (0 to Byte.MaxValue).map { b => b.toByte -> IntConstant(b) } + forAll(Gen.chooseNum(0, Byte.MaxValue)) { n => + val rnd = Random.shuffle(fullArr).take(n) + val oldRepr: scala.collection.Map[Byte, EvaluatedValue[_ <: SType]] = rnd.toMap + val sm = SigmaMap(oldRepr) + + oldRepr.iterator.toList shouldBe sm.iterator.toList + } + } + +} diff --git a/sc/shared/src/test/scala/sigmastate/SoftForkabilitySpecification.scala b/sc/shared/src/test/scala/sigmastate/SoftForkabilitySpecification.scala index bca2d0e638..632e589b92 100644 --- a/sc/shared/src/test/scala/sigmastate/SoftForkabilitySpecification.scala +++ b/sc/shared/src/test/scala/sigmastate/SoftForkabilitySpecification.scala @@ -15,7 +15,7 @@ import ErgoTree.{EmptyConstants, HeaderType, ZeroHeader, setSizeBit} import sigmastate.helpers.TestingHelpers._ import sigmastate.helpers.{CompilerTestingCommons, ErgoLikeContextTesting, ErgoLikeTestInterpreter, ErgoLikeTestProvingInterpreter} import sigmastate.interpreter.Interpreter.{ScriptNameProp, emptyEnv} -import sigma.interpreter.{ContextExtension, ProverResult} +import sigma.interpreter.{ContextExtension, ProverResult, SigmaMap} import sigma.ast.syntax._ import sigma.eval.ErgoTreeEvaluator import sigma.eval.ErgoTreeEvaluator.DataEnv @@ -277,7 +277,7 @@ class SoftForkabilitySpecification extends SigmaTestingData val tx = createTransaction(createBox(boxAmt, mainTree, 1)) val bindings = Map(1.toByte -> ByteArrayConstant(Colls.fromArray(propBytes))) - val proof = new ProverResult(Array.emptyByteArray, ContextExtension(bindings)) + val proof = new ProverResult(Array.emptyByteArray, ContextExtension(SigmaMap(bindings))) // verify transaction on v1 node using v2 validation settings verifyTx("deserialize", tx, proof, v2vs) diff --git a/sc/shared/src/test/scala/sigmastate/eval/ErgoScriptTestkit.scala b/sc/shared/src/test/scala/sigmastate/eval/ErgoScriptTestkit.scala index abbed09992..8c28c02d00 100644 --- a/sc/shared/src/test/scala/sigmastate/eval/ErgoScriptTestkit.scala +++ b/sc/shared/src/test/scala/sigmastate/eval/ErgoScriptTestkit.scala @@ -14,7 +14,7 @@ import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, ErgoLikeConte import sigmastate.interpreter.Interpreter.ScriptEnv import sigmastate.interpreter.CErgoTreeEvaluator import sigma.ast.syntax.ValueOps -import sigma.interpreter.ContextExtension +import sigma.interpreter.{ContextExtension, SigmaMap} import sigmastate.lang.{CompilerResult, CompilerSettings, LangTests, SigmaCompiler} import sigma.serialization.ErgoTreeSerializer.DefaultSerializer import sigmastate.CompilerTestsBase @@ -49,7 +49,7 @@ trait ErgoScriptTestkit extends ContractsTestkit with LangTests boxesToSpend = IndexedSeq(boxToSpend), spendingTransaction = tx1, self = boxToSpend, activatedVersionInTests, - extension = ContextExtension(extension)) + extension = ContextExtension(SigmaMap(extension))) .withErgoTreeVersion(ergoTreeVersionInTests) ergoCtx } @@ -82,11 +82,11 @@ trait ErgoScriptTestkit extends ContractsTestkit with LangTests spendingTransaction = tx1, self = boxToSpend, activatedVersionInTests, - extension = ContextExtension(Map( + extension = ContextExtension(SigmaMap(Map( backerPubKeyId -> SigmaPropConstant(backerPubKey), projectPubKeyId -> SigmaPropConstant(projectPubKey), 3.toByte -> BigIntArrayConstant(bigIntegerArr1) - ))) + )))) case class Result(calc: Option[Any], cost: Option[Int], size: Option[Long]) object Result { diff --git a/sc/shared/src/test/scala/sigmastate/helpers/CompilerTestingCommons.scala b/sc/shared/src/test/scala/sigmastate/helpers/CompilerTestingCommons.scala index 332ee902a2..13afd5ebff 100644 --- a/sc/shared/src/test/scala/sigmastate/helpers/CompilerTestingCommons.scala +++ b/sc/shared/src/test/scala/sigmastate/helpers/CompilerTestingCommons.scala @@ -6,8 +6,8 @@ import org.scalacheck.Arbitrary.arbByte import org.scalacheck.Gen import sigma.util.BenchmarkUtil import scalan.TestContexts -import sigma.ast.{Constant, CostItem, ErgoTree, JitCost, SOption, SType} -import sigma.{Colls, Evaluation, TestUtils} +import sigma.ast.{Constant, CostItem, ErgoTree, JitCost, SContext, SOption, SType} +import sigma.{Evaluation, TestUtils} import sigma.data.{RType, SigmaBoolean} import sigma.validation.ValidationException import sigma.validation.ValidationRules.CheckSerializableTypeCode @@ -15,6 +15,7 @@ import sigma.ast.syntax.{SValue, SigmaPropValue} import sigma.eval.{CostDetails, EvalSettings, Extensions, GivenCost, TracedCost} import sigmastate.helpers.TestingHelpers._ import sigma.interpreter.ContextExtension.VarBinding +import sigma.interpreter.SigmaMap import sigmastate.interpreter.CErgoTreeEvaluator.DefaultProfiler import sigmastate.interpreter.Interpreter.ScriptEnv import sigmastate.interpreter._ @@ -57,16 +58,8 @@ trait CompilerTestingCommons extends TestingCommons // (ctx.HEIGHT method call compiled to Height IR node) // ------- // We add ctx as it's own variable with id = 1 - val ctxVar = Extensions.toAnyValue[sigma.Context](ctx)(sigma.ContextRType) - val newVars = if (ctx.vars.length < 2) { - val vars = ctx.vars.toArray - val buf = new Array[sigma.AnyValue](2) - Array.copy(vars, 0, buf, 0, vars.length) - buf(1) = ctxVar - Colls.fromArray(buf) - } else { - ctx.vars.updated(1, ctxVar) - } + val ctxVar = Constant[SContext.type](ctx, SContext) + val newVars = SigmaMap(ctx.vars.asInstanceOf[SigmaMap].iterator.toMap.updated(1.toByte, ctxVar)) val calcCtx = ctx.copy(vars = newVars) calcCtx case _ => diff --git a/sc/shared/src/test/scala/sigmastate/serialization/DeserializationResilience.scala b/sc/shared/src/test/scala/sigmastate/serialization/DeserializationResilience.scala index f4344fbf8d..dd7e859a80 100644 --- a/sc/shared/src/test/scala/sigmastate/serialization/DeserializationResilience.scala +++ b/sc/shared/src/test/scala/sigmastate/serialization/DeserializationResilience.scala @@ -18,7 +18,7 @@ import sigmastate._ import sigma.Extensions.ArrayOps import sigma.eval.Extensions.SigmaBooleanOps import sigma.eval.SigmaDsl -import sigma.interpreter.{ContextExtension, CostedProverResult} +import sigma.interpreter.{ContextExtension, CostedProverResult, SigmaMap} import sigma.eval.Extensions.EvalIterableOps import sigmastate.eval._ import sigmastate.helpers.{CompilerTestingCommons, ErgoLikeContextTesting, ErgoLikeTestInterpreter} @@ -312,8 +312,10 @@ class DeserializationResilience extends DeserializationResilienceTesting { property("recursion caught during verify") { assertExceptionThrown({ val verifier = new ErgoLikeTestInterpreter - val pr = CostedProverResult(Array[Byte](), - ContextExtension(Map(4.toByte -> IntConstant(1), 5.toByte -> IntConstant(2))), 0L) + val pr = CostedProverResult( + Array[Byte](), + ContextExtension(SigmaMap(Map(4.toByte -> IntConstant(1), 5.toByte -> IntConstant(2)))), + 0L) val ctx = ErgoLikeContextTesting.dummy(fakeSelf, activatedVersionInTests) val (res, _) = BenchmarkUtil.measureTime { verifier.verify(mkTestErgoTree(recursiveScript), ctx, pr, fakeMessage) diff --git a/sc/shared/src/test/scala/sigmastate/utxo/AVLTreeScriptsSpecification.scala b/sc/shared/src/test/scala/sigmastate/utxo/AVLTreeScriptsSpecification.scala index 566bf5bd58..feda2ba2bc 100644 --- a/sc/shared/src/test/scala/sigmastate/utxo/AVLTreeScriptsSpecification.scala +++ b/sc/shared/src/test/scala/sigmastate/utxo/AVLTreeScriptsSpecification.scala @@ -21,7 +21,7 @@ import sigma.ast.SAvlTree import sigma.ast.syntax.{GetVarByteArray, OptionValueOps} import sigma.data.{AvlTreeData, AvlTreeFlags, CSigmaProp, TrivialProp} import sigma.eval.SigmaDsl -import sigma.interpreter.ProverResult +import sigma.interpreter.{ContextExtension, ProverResult, SigmaMap} import sigma.{AvlTree, Context} import sigmastate.eval.Extensions.AvlTreeOps @@ -279,7 +279,9 @@ class AVLTreeScriptsSpecification extends CompilerTestingCommons val invalidProof = SerializedAdProof @@ Array[Byte](1, 2, 3) val invalidProofResult = new ProverResult( proof = proof.proof, - extension = proof.extension.add(proofId -> ByteArrayConstant(invalidProof)) + extension = ContextExtension( + SigmaMap(proof.extension.values.iterator.toMap ++ Map(proofId -> ByteArrayConstant(invalidProof))) + ) ) verifier.verify(prop, ctx, invalidProofResult, fakeMessage).get._1 shouldBe false diff --git a/sc/shared/src/test/scala/sigmastate/utxo/ErgoLikeInterpreterSpecification.scala b/sc/shared/src/test/scala/sigmastate/utxo/ErgoLikeInterpreterSpecification.scala index 615a826649..fbfe812a37 100644 --- a/sc/shared/src/test/scala/sigmastate/utxo/ErgoLikeInterpreterSpecification.scala +++ b/sc/shared/src/test/scala/sigmastate/utxo/ErgoLikeInterpreterSpecification.scala @@ -18,7 +18,7 @@ import sigmastate.helpers._ import sigmastate.helpers.TestingHelpers._ import sigma.interpreter.ContextExtension.VarBinding import sigma.eval.Extensions.SigmaBooleanOps -import sigma.interpreter.{ContextExtension, CostedProverResult} +import sigma.interpreter.{ContextExtension, CostedProverResult, SigmaMap} import sigma.serialization.{SerializationSpecification, ValueSerializer} import sigmastate.utils.Helpers._ @@ -737,7 +737,7 @@ class ErgoLikeInterpreterSpecification extends CompilerTestingCommons val boxToSpend = testBox(10, ergoTree, creationHeight = 5) val ctx = ErgoLikeContextTesting.dummy(boxToSpend, activatedVersionInTests) .withExtension( - ContextExtension(Seq(script).toMap)) // provide script bytes in context variable + ContextExtension(SigmaMap(Seq(script).toMap))) // provide script bytes in context variable val prover = new ErgoLikeTestProvingInterpreter() prover.prove(ergoTree, ctx, fakeMessage).getOrThrow diff --git a/sc/shared/src/test/scala/sigmastate/utxo/blockchain/BlockchainSimulationSpecification.scala b/sc/shared/src/test/scala/sigmastate/utxo/blockchain/BlockchainSimulationSpecification.scala index f063cf5aa0..0828d7196e 100644 --- a/sc/shared/src/test/scala/sigmastate/utxo/blockchain/BlockchainSimulationSpecification.scala +++ b/sc/shared/src/test/scala/sigmastate/utxo/blockchain/BlockchainSimulationSpecification.scala @@ -4,7 +4,7 @@ import sigma.ast.TrueLeaf import sigma.ast.syntax.{GetVarBoolean, OptionValueOps} import sigmastate.CompilerCrossVersionProps import sigmastate.helpers.ErgoLikeTestProvingInterpreter -import sigma.interpreter.ContextExtension +import sigma.interpreter.{ContextExtension, SigmaMap} import sigmastate.utxo.blockchain.BlockchainSimulationTestingCommons._ @@ -48,7 +48,7 @@ class BlockchainSimulationSpecification extends BlockchainSimulationTestingCommo } // spend boxes with context extension - val contextExtension = ContextExtension(Map(varId -> TrueLeaf)) + val contextExtension = ContextExtension(SigmaMap(Map(varId -> TrueLeaf))) checkState(state, miner, 0, randomDeepness, Some(mkTestErgoTree(prop)), contextExtension) } diff --git a/sc/shared/src/test/scala/sigmastate/utxo/examples/DemurrageExampleSpecification.scala b/sc/shared/src/test/scala/sigmastate/utxo/examples/DemurrageExampleSpecification.scala index 4ec6922c38..797a05c1d2 100644 --- a/sc/shared/src/test/scala/sigmastate/utxo/examples/DemurrageExampleSpecification.scala +++ b/sc/shared/src/test/scala/sigmastate/utxo/examples/DemurrageExampleSpecification.scala @@ -7,7 +7,7 @@ import sigma.ast.ShortConstant import sigmastate._ import sigmastate.helpers.{CompilerTestingCommons, ContextEnrichingTestProvingInterpreter, ErgoLikeContextTesting, ErgoLikeTestInterpreter} import sigmastate.helpers.TestingHelpers._ -import sigma.interpreter.ContextExtension +import sigma.interpreter.{ContextExtension, SigmaMap} import sigma.ast.syntax._ class DemurrageExampleSpecification extends CompilerTestingCommons @@ -81,7 +81,7 @@ class DemurrageExampleSpecification extends CompilerTestingCommons val approxSize = createBox(outValue, propTree, inHeight).bytes.length + 2 val inValue: Int = (outValue + demurrageCoeff * demurragePeriod * approxSize).toInt - val ce = ContextExtension(Map(outIdxVarId -> ShortConstant(0))) + val ce = ContextExtension(SigmaMap(Map(outIdxVarId -> ShortConstant(0)))) //case 1: demurrage time hasn't come yet val currentHeight1 = inHeight + demurragePeriod - 1 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 f6393f62bb..dfcb78be3e 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 @@ -13,7 +13,7 @@ import sigma.ast.syntax.GroupElementConstant import sigma.ast.{Constant, GroupElementConstant, SType} import sigma.data.Iso.{isoStringToArray, isoStringToColl} import sigma.data.{CBigInt, CGroupElement, Digest32Coll, Digest32CollRType, Iso} -import sigma.interpreter.{ContextExtension, ProverResult} +import sigma.interpreter.{ContextExtension, ProverResult, SigmaMap} import sigma.js.{AvlTree, GroupElement} import sigma.serialization.{ErgoTreeSerializer, ValueSerializer} import sigma.{Coll, Colls} @@ -198,14 +198,14 @@ object Isos { val c = isoHexStringToConstant.to(x.apply(id).get.get) map = map + (id -> c) } - ContextExtension(map) + ContextExtension(SigmaMap(map)) } override def from(x: ContextExtension): contextExtensionMod.ContextExtension = { val res = new Object().asInstanceOf[contextExtensionMod.ContextExtension] - x.values.foreach { case (k, v: Constant[_]) => + x.values.iterator.foreach { case (k, v: Constant[_]) => val hex = isoHexStringToConstant.from(v) - res.update(k, hex) + res.update(k, hex) // todo: will be order respected after? } res } 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..d733b3468e 100644 --- a/sdk/shared/src/main/scala/org/ergoplatform/sdk/JsonCodecs.scala +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/JsonCodecs.scala @@ -15,12 +15,12 @@ import sigma.ast.{ErgoTree, EvaluatedValue, SType} import sigma.data.{AvlTreeData, AvlTreeFlags, CBigInt, Digest32Coll, WrapperOf} import sigma.eval.Extensions.EvalIterableOps import sigma.eval.SigmaDsl -import sigma.interpreter.{ContextExtension, ProverResult} +import sigma.interpreter.{ContextExtension, ProverResult, SigmaMap} import sigma.serialization.{ErgoTreeSerializer, ValueSerializer} import sigma.validation.SigmaValidationSettings import sigma.{AnyValue, Coll, Colls, Header, PreHeader, SigmaException} import sigmastate.eval.{CPreHeader, _} -import sigmastate.utils.Helpers._ // required for Scala 2.11 +import sigmastate.utils.Helpers._ import java.math.BigInteger import scala.collection.mutable @@ -230,7 +230,7 @@ trait JsonCodecs { }) implicit val contextExtensionEncoder: Encoder[ContextExtension] = Encoder.instance({ extension => - Json.obj(extension.values.toSeq.map { case (key, value) => + Json.obj(extension.values.iterator.toSeq.map { case (key, value) => key.toString -> evaluatedValueEncoder(value) }: _*) }) @@ -238,7 +238,7 @@ trait JsonCodecs { implicit val contextExtensionDecoder: Decoder[ContextExtension] = Decoder.instance({ cursor => for { values <- cursor.as[Map[Byte, EvaluatedValue[SType]]] - } yield ContextExtension(values) + } yield ContextExtension(SigmaMap(values)) }) implicit val proverResultEncoder: Encoder[ProverResult] = Encoder.instance({ v =>