sigma.ast.methods.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of sigma-state_2.12 Show documentation
Show all versions of sigma-state_2.12 Show documentation
Interpreter of a Sigma-State language
The newest version!
package sigma.ast
import org.ergoplatform._
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.syntax.{SValue, ValueOps}
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.utils.SparseArrayContainer
import scala.annotation.unused
import scala.language.implicitConversions
/** Base type for all companions of AST nodes of sigma lang. */
trait SigmaNodeCompanion
/** Defines recognizer method which allows the derived object to be used in patterns
* to recognize method descriptors by method name.
* @see SCollecton
*/
trait MethodByNameUnapply extends MethodsContainer {
def unapply(methodName: String): Option[SMethod] = methods.find(_.name == methodName)
}
/** Base trait for all method containers (which store methods and properties) */
sealed trait MethodsContainer {
/** Type for which this container defines methods. */
def ownerType: STypeCompanion
override def toString: String =
getClass.getSimpleName.stripSuffix("$") // e.g. SInt, SCollection, etc
/** Represents class of `this`. */
lazy val thisRClass: RClass[_] = RClass(this.getClass)
def typeId: Byte = ownerType.typeId
def typeName: String = ownerType.typeName
/** Returns -1 if `method` is not found. */
def methodIndex(name: String): Int = methods.indexWhere(_.name == name)
/** Returns true if this type has a method with the given name. */
def hasMethod(name: String): Boolean = methodIndex(name) != -1
/** This method should be overriden in derived classes to add new methods in addition to inherited.
* Typical override: `super.getMethods() ++ Seq(m1, m2, m3)`
*/
protected def getMethods(): Seq[SMethod] = Nil
/** Returns all the methods of this type. */
lazy val methods: Seq[SMethod] = {
val ms = getMethods().toArray
assert(ms.map(_.name).distinct.length == ms.length, s"Duplicate method names in $this")
ms.groupBy(_.objType).foreach { case (comp, ms) =>
assert(ms.map(_.methodId).distinct.length == ms.length, s"Duplicate method ids in $comp: $ms")
}
ms
}
private lazy val _methodsMap: Map[Byte, Map[Byte, SMethod]] = methods
.groupBy(_.objType.typeId)
.map { case (typeId, ms) => (typeId -> ms.map(m => m.methodId -> m).toMap) }
/** Lookup method by its id in this type. */
@inline def getMethodById(methodId: Byte): Option[SMethod] =
_methodsMap.get(typeId) match {
case Some(ms) => ms.get(methodId)
case None => None
}
/** Lookup method in this type by method's id or throw ValidationException.
* This method can be used in trySoftForkable section to either obtain valid method
* or catch ValidatioinException which can be checked for soft-fork condition.
* It delegate to getMethodById to lookup method.
*
* @see getMethodById
*/
def methodById(methodId: Byte): SMethod = {
ValidationRules.CheckAndGetMethod(this, methodId)
}
/** Finds a method descriptor [[SMethod]] for the given name. */
def method(methodName: String): Option[SMethod] = methods.find(_.name == methodName)
/** Looks up the method descriptor by the method name. */
def getMethodByName(name: String): SMethod = methods.find(_.name == name).get
}
object MethodsContainer {
private val containers = new SparseArrayContainer[MethodsContainer](Array(
SByteMethods,
SShortMethods,
SIntMethods,
SLongMethods,
SBigIntMethods,
SBooleanMethods,
SStringMethods,
SGroupElementMethods,
SSigmaPropMethods,
SBoxMethods,
SAvlTreeMethods,
SHeaderMethods,
SPreHeaderMethods,
SGlobalMethods,
SContextMethods,
SCollectionMethods,
SOptionMethods,
STupleMethods,
SUnitMethods,
SAnyMethods
).map(m => (m.typeId, m)))
def contains(typeId: TypeCode): Boolean = containers.contains(typeId)
def apply(typeId: TypeCode): MethodsContainer = containers(typeId)
/** Finds the method of the give type.
*
* @param tpe type of the object for which the method is looked up
* @param methodName name of the method
* @return method descriptor or None if not found
*/
def getMethod(tpe: SType, methodName: String): Option[SMethod] = tpe match {
case tup: STuple =>
STupleMethods.getTupleMethod(tup, methodName)
case _ =>
containers.get(tpe.typeCode).flatMap(_.method(methodName))
}
}
trait MonoTypeMethods extends MethodsContainer {
def ownerType: SMonoType
/** Helper method to create method descriptors for properties (i.e. methods without args). */
protected def propertyCall(
name: String,
tpeRes: SType,
id: Byte,
costKind: CostKind): SMethod =
SMethod(this, name, SFunc(this.ownerType, tpeRes), id, costKind)
.withIRInfo(MethodCallIrBuilder)
.withInfo(PropertyCall, "")
/** Helper method to create method descriptors for properties (i.e. methods without args). */
protected def property(
name: String,
tpeRes: SType,
id: Byte,
valueCompanion: ValueCompanion): SMethod =
SMethod(this, name, SFunc(this.ownerType, tpeRes), id, valueCompanion.costKind)
.withIRInfo(MethodCallIrBuilder)
.withInfo(valueCompanion, "")
}
trait SNumericTypeMethods extends MonoTypeMethods {
import SNumericTypeMethods.tNum
protected override def getMethods(): Seq[SMethod] = {
super.getMethods() ++ SNumericTypeMethods.methods.map {
m => m.copy(stype = applySubst(m.stype, Map(tNum -> this.ownerType)).asFunc)
}
}
}
object SNumericTypeMethods extends MethodsContainer {
/** Type for which this container defines methods. */
override def ownerType: STypeCompanion = SNumericType
/** Type variable used in generic signatures of method descriptors. */
val tNum = STypeVar("TNum")
/** Cost function which is assigned for numeric cast MethodCall nodes in ErgoTree.
* It is called as part of MethodCall.eval method. */
val costOfNumericCast: MethodCostFunc = new MethodCostFunc {
override def apply(
E: ErgoTreeEvaluator,
mc: MethodCall,
obj: Any,
args: Array[Any]): CostDetails = {
val targetTpe = mc.method.stype.tRange
val cast = getNumericCast(mc.obj.tpe, mc.method.name, targetTpe).get
val costKind = if (cast == Downcast) Downcast.costKind else Upcast.costKind
TracedCost(Array(TypeBasedCostItem(MethodDesc(mc.method), costKind, targetTpe)))
}
}
/** The following SMethod instances are descriptors of methods available on all numeric
* types.
*
* @see `val methods` below
* */
val ToByteMethod : SMethod = SMethod(this, "toByte", SFunc(tNum, SByte), 1, null)
.withCost(costOfNumericCast)
.withInfo(PropertyCall, "Converts this numeric value to \\lst{Byte}, throwing exception if overflow.")
val ToShortMethod : SMethod = SMethod(this, "toShort", SFunc(tNum, SShort), 2, null)
.withCost(costOfNumericCast)
.withInfo(PropertyCall, "Converts this numeric value to \\lst{Short}, throwing exception if overflow.")
val ToIntMethod : SMethod = SMethod(this, "toInt", SFunc(tNum, SInt), 3, null)
.withCost(costOfNumericCast)
.withInfo(PropertyCall, "Converts this numeric value to \\lst{Int}, throwing exception if overflow.")
val ToLongMethod : SMethod = SMethod(this, "toLong", SFunc(tNum, SLong), 4, null)
.withCost(costOfNumericCast)
.withInfo(PropertyCall, "Converts this numeric value to \\lst{Long}, throwing exception if overflow.")
val ToBigIntMethod: SMethod = SMethod(this, "toBigInt", SFunc(tNum, SBigInt), 5, null)
.withCost(costOfNumericCast)
.withInfo(PropertyCall, "Converts this numeric value to \\lst{BigInt}")
/** Cost of: 1) creating Byte collection from a numeric value */
val ToBytes_CostKind = FixedCost(JitCost(5))
val ToBytesMethod: SMethod = SMethod(
this, "toBytes", SFunc(tNum, SByteArray), 6, ToBytes_CostKind)
.withIRInfo(MethodCallIrBuilder)
.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
| collection of bytes \lst{[0x12, 0x13, 0x14, 0x15]}.
""".stripMargin)
/** Cost of: 1) creating Boolean collection (one bool for each bit) from a numeric
* value. */
val ToBits_CostKind = FixedCost(JitCost(5))
val ToBitsMethod: SMethod = SMethod(
this, "toBits", SFunc(tNum, SBooleanArray), 7, ToBits_CostKind)
.withIRInfo(MethodCallIrBuilder)
.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(
ToByteMethod, // see Downcast
ToShortMethod, // see Downcast
ToIntMethod, // see Downcast
ToLongMethod, // see Downcast
ToBigIntMethod, // see Downcast
ToBytesMethod,
ToBitsMethod
)
/** Collection of names of numeric casting methods (like `toByte`, `toInt`, etc). */
val castMethods: Array[String] =
Array(ToByteMethod, ToShortMethod, ToIntMethod, ToLongMethod, ToBigIntMethod)
.map(_.name)
/** Checks the given name is numeric type cast method (like toByte, toInt, etc.). */
def isCastMethod(name: String): Boolean = castMethods.contains(name)
/** Convert the given method to a cast operation from fromTpe to resTpe. */
def getNumericCast(
fromTpe: SType,
methodName: String,
resTpe: SType): Option[NumericCastCompanion] = (fromTpe, resTpe) match {
case (from: SNumericType, to: SNumericType) if isCastMethod(methodName) =>
val op = if (to > from) Upcast else Downcast
Some(op)
case _ => None // the method in not numeric type cast
}
}
/** Methods of ErgoTree type `Boolean`. */
case object SBooleanMethods extends MonoTypeMethods {
/** Type for which this container defines methods. */
override def ownerType: SMonoType = SBoolean
val ToByte = "toByte"
protected override def getMethods() = super.getMethods()
/* TODO soft-fork: https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479
++ Seq(
SMethod(this, ToByte, SFunc(this, SByte), 1)
.withInfo(PropertyCall, "Convert true to 1 and false to 0"),
)
*/
}
/** Methods of ErgoTree type `Byte`. */
case object SByteMethods extends SNumericTypeMethods {
/** Type for which this container defines methods. */
override def ownerType: SMonoType = SByte
}
/** Methods of ErgoTree type `Short`. */
case object SShortMethods extends SNumericTypeMethods {
/** Type for which this container defines methods. */
override def ownerType: SMonoType = ast.SShort
}
/** Descriptor of ErgoTree type `Int`. */
case object SIntMethods extends SNumericTypeMethods {
/** Type for which this container defines methods. */
override def ownerType: SMonoType = SInt
}
/** Descriptor of ErgoTree type `Long`. */
case object SLongMethods extends SNumericTypeMethods {
/** Type for which this container defines methods. */
override def ownerType: SMonoType = SLong
}
/** Methods of BigInt type. Implemented using [[java.math.BigInteger]]. */
case object SBigIntMethods extends SNumericTypeMethods {
/** Type for which this container defines methods. */
override def ownerType: SMonoType = SBigInt
/** 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() = super.getMethods() ++ Seq(
// ModQMethod,
// PlusModQMethod,
// MinusModQMethod,
// TODO soft-fork: https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479
// MultModQMethod,
)
}
/** Methods of type `String`. */
case object SStringMethods extends MonoTypeMethods {
/** Type for which this container defines methods. */
override def ownerType: SMonoType = SString
}
/** Methods of type `GroupElement`. */
case object SGroupElementMethods extends MonoTypeMethods {
/** Type for which this container defines methods. */
override def ownerType: SMonoType = SGroupElement
/** Cost of: 1) serializing EcPointType to bytes 2) packing them in Coll. */
val GetEncodedCostKind = FixedCost(JitCost(250))
/** The following SMethod instances are descriptors of methods defined in `GroupElement` type. */
lazy val GetEncodedMethod: SMethod = SMethod(
this, "getEncoded", SFunc(Array(this.ownerType), SByteArray), 2, GetEncodedCostKind)
.withIRInfo(MethodCallIrBuilder)
.withInfo(PropertyCall, "Get an encoding of the point value.")
lazy val ExponentiateMethod: SMethod = SMethod(
this, "exp", SFunc(Array(this.ownerType, SBigInt), this.ownerType), 3, Exponentiate.costKind)
.withIRInfo({ case (builder, obj, _, Seq(arg), _) =>
builder.mkExponentiate(obj.asGroupElement, arg.asBigInt)
})
.withInfo(Exponentiate,
"Exponentiate this \\lst{GroupElement} to the given number. Returns this to the power of k",
ArgInfo("k", "The power"))
lazy val MultiplyMethod: SMethod = SMethod(
this, "multiply", SFunc(Array(this.ownerType, SGroupElement), this.ownerType), 4, MultiplyGroup.costKind)
.withIRInfo({ case (builder, obj, _, Seq(arg), _) =>
builder.mkMultiplyGroup(obj.asGroupElement, arg.asGroupElement)
})
.withInfo(MultiplyGroup, "Group operation.", ArgInfo("other", "other element of the group"))
/** Cost of: 1) calling EcPoint.negate 2) wrapping in GroupElement. */
val Negate_CostKind = FixedCost(JitCost(45))
lazy val NegateMethod: SMethod = SMethod(
this, "negate", SFunc(this.ownerType, this.ownerType), 5, Negate_CostKind)
.withIRInfo(MethodCallIrBuilder)
.withInfo(PropertyCall, "Inverse element of the group.")
protected override def getMethods(): Seq[SMethod] = super.getMethods() ++ Seq(
/* TODO soft-fork: https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479
SMethod(this, "isIdentity", SFunc(this, SBoolean), 1)
.withInfo(PropertyCall, "Checks if this value is identity element of the eliptic curve group."),
*/
GetEncodedMethod,
ExponentiateMethod,
MultiplyMethod,
NegateMethod
)
}
/** Methods of type `SigmaProp` which represent sigma-protocol propositions. */
case object SSigmaPropMethods extends MonoTypeMethods {
/** Type for which this container defines methods. */
override def ownerType: SMonoType = SSigmaProp
/** The maximum size of SigmaProp value in serialized byte array representation. */
val MaxSizeInBytes: Long = SigmaConstants.MaxSigmaPropSizeInBytes.value
val PropBytes = "propBytes"
val IsProven = "isProven"
lazy val PropBytesMethod = SMethod(
this, PropBytes, SFunc(this.ownerType, SByteArray), 1, SigmaPropBytes.costKind)
.withInfo(SigmaPropBytes, "Serialized bytes of this sigma proposition taken as ErgoTree.")
lazy val IsProvenMethod = SMethod(this, IsProven, SFunc(this.ownerType, SBoolean), 2, null)
.withInfo(// available only at frontend of ErgoScript
"Verify that sigma proposition is proven.")
protected override def getMethods() = super.getMethods() ++ Seq(
PropBytesMethod, IsProvenMethod
)
}
/** Any other type is implicitly subtype of this type. */
case object SAnyMethods extends MonoTypeMethods {
override def ownerType: SMonoType = SAny
}
/** The type with single inhabitant value `()` */
case object SUnitMethods extends MonoTypeMethods {
/** Type for which this container defines methods. */
override def ownerType = SUnit
}
object SOptionMethods extends MethodsContainer {
/** Type for which this container defines methods. */
override def ownerType: STypeCompanion = SOption
/** Code of `Option[_]` type constructor. */
val OptionTypeConstrId = 3
/** Type code for `Option[T] for some T` type used in TypeSerializer. */
val OptionTypeCode: TypeCode = ((SPrimType.MaxPrimTypeCode + 1) * OptionTypeConstrId).toByte
/** Code of `Option[Coll[_]]` type constructor. */
val OptionCollectionTypeConstrId = 4
/** Type code for `Option[Coll[T]] for some T` type used in TypeSerializer. */
val OptionCollectionTypeCode: TypeCode = ((SPrimType.MaxPrimTypeCode + 1) * OptionCollectionTypeConstrId).toByte
val IsDefined = "isDefined"
val Get = "get"
val GetOrElse = "getOrElse"
import SType.{paramR, paramT, tR, tT}
/** Type descriptor of `this` argument used in the methods below. */
val ThisType = SOption(tT)
/** The following SMethod instances are descriptors of methods defined in `Option` type. */
val IsDefinedMethod = SMethod(
this, IsDefined, SFunc(ThisType, SBoolean), 2, OptionIsDefined.costKind)
.withIRInfo({
case (builder, obj, _, args, _) if args.isEmpty => builder.mkOptionIsDefined(obj.asValue[SOption[SType]])
})
.withInfo(OptionIsDefined,
"Returns \\lst{true} if the option is an instance of \\lst{Some}, \\lst{false} otherwise.")
val GetMethod = SMethod(this, Get, SFunc(ThisType, tT), 3, OptionGet.costKind)
.withIRInfo({
case (builder, obj, _, args, _) if args.isEmpty => builder.mkOptionGet(obj.asValue[SOption[SType]])
})
.withInfo(OptionGet,
"""Returns the option's value. The option must be nonempty. Throws exception if the option is empty.""")
lazy val GetOrElseMethod = SMethod(
this, GetOrElse, SFunc(Array(ThisType, tT), tT, Array[STypeParam](tT)), 4, OptionGetOrElse.costKind)
.withIRInfo(irBuilder = {
case (builder, obj, _, Seq(d), _) => builder.mkOptionGetOrElse(obj.asValue[SOption[SType]], d)
})
.withInfo(OptionGetOrElse,
"""Returns the option's value if the option is nonempty, otherwise
|return the result of evaluating \lst{default}.
""".stripMargin, ArgInfo("default", "the default value"))
// TODO soft-fork: https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479
// val FoldMethod = SMethod(
// this, Fold, SFunc(Array(ThisType, tR, SFunc(tT, tR)), tR, Array[STypeParam](tT, tR)), 5, FixedCost(JitCost(1)))
// .withInfo(MethodCall,
// """Returns the result of applying \lst{f} to this option's
// | value if the option is nonempty. Otherwise, evaluates
// | expression \lst{ifEmpty}.
// | This is equivalent to \lst{option map f getOrElse ifEmpty}.
// """.stripMargin,
// ArgInfo("ifEmpty", "the expression to evaluate if empty"),
// ArgInfo("f", "the function to apply if nonempty"))
val MapMethod = SMethod(this, "map",
SFunc(Array(ThisType, SFunc(tT, tR)), SOption(tR), Array(paramT, paramR)), 7, FixedCost(JitCost(20)))
.withIRInfo(MethodCallIrBuilder)
.withInfo(MethodCall,
"""Returns a \lst{Some} containing the result of applying \lst{f} to this option's
| value if this option is nonempty.
| Otherwise return \lst{None}.
""".stripMargin, ArgInfo("f", "the function to apply"))
val FilterMethod = SMethod(this, "filter",
SFunc(Array(ThisType, SFunc(tT, SBoolean)), ThisType, Array(paramT)), 8, FixedCost(JitCost(20)))
.withIRInfo(MethodCallIrBuilder)
.withInfo(MethodCall,
"""Returns this option if it is nonempty and applying the predicate \lst{p} to
| this option's value returns true. Otherwise, return \lst{None}.
""".stripMargin, ArgInfo("p", "the predicate used for testing"))
override protected def getMethods(): Seq[SMethod] = super.getMethods() ++
Seq(
IsDefinedMethod,
GetMethod,
GetOrElseMethod,
/* TODO soft-fork: https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479
FoldMethod,
*/
MapMethod,
FilterMethod
)
/** Creates a descriptor of `Option[T]` type for the given element type `T`. */
def apply[T <: SType](implicit elemType: T, @unused ov: Overloaded1): SOption[T] = SOption(elemType)
}
object SCollectionMethods extends MethodsContainer with MethodByNameUnapply {
import SType.{paramIV, paramIVSeq, paramOV}
/** Type for which this container defines methods. */
override def ownerType: STypeCompanion = SCollection
/** Helper descriptors reused across different method descriptors. */
def tIV = SType.tIV
def tOV = SType.tOV
/** This descriptors are instantiated once here and then reused. */
val ThisType = SCollection(tIV)
val tOVColl = SCollection(tOV)
val tPredicate = SFunc(tIV, SBoolean)
/** The following SMethod instances are descriptors of methods defined in `Coll` type. */
val SizeMethod = SMethod(this, "size", SFunc(ThisType, SInt), 1, SizeOf.costKind)
.withInfo(SizeOf, "The size of the collection in elements.")
val GetOrElseMethod = SMethod(
this, "getOrElse", SFunc(Array(ThisType, SInt, tIV), tIV, paramIVSeq), 2, DynamicCost)
.withIRInfo({ case (builder, obj, _, Seq(index, defaultValue), _) =>
val index1 = index.asValue[SInt.type]
val defaultValue1 = defaultValue.asValue[SType]
builder.mkByIndex(obj.asValue[SCollection[SType]], index1, Some(defaultValue1))
})
.withInfo(ByIndex, "Return the element of collection if \\lst{index} is in range \\lst{0 .. size-1}",
ArgInfo("index", "index of the element of this collection"),
ArgInfo("default", "value to return when \\lst{index} is out of range"))
/** Implements evaluation of Coll.getOrElse method call ErgoTree node.
* Called via reflection based on naming convention.
* @see SMethod.evalMethod
*/
def getOrElse_eval[A](mc: MethodCall, xs: Coll[A], i: Int, default: A)(implicit E: ErgoTreeEvaluator): A = {
E.addCost(ByIndex.costKind, mc.method.opDesc)
// the following lines should be semantically the same as in ByIndex.eval
Value.checkType(mc.args.last.tpe, default)
xs.getOrElse(i, default)
}
val MapMethod = SMethod(this, "map",
SFunc(Array(ThisType, SFunc(tIV, tOV)), tOVColl, Array(paramIV, paramOV)), 3, MapCollection.costKind)
.withIRInfo({
case (builder, obj, _, Seq(mapper), _) => builder.mkMapCollection(obj.asValue[SCollection[SType]], mapper.asFunc)
})
.withInfo(MapCollection,
""" Builds a new collection by applying a function to all elements of this collection.
| Returns a new collection of type \lst{Coll[B]} resulting from applying the given function
| \lst{f} to each element of this collection and collecting the results.
""".stripMargin,
ArgInfo("f", "the function to apply to each element"))
/** Implements evaluation of Coll.map method call ErgoTree node.
* Called via reflection based on naming convention.
* @see SMethod.evalMethod
*/
def map_eval[A,B](mc: MethodCall, xs: Coll[A], f: A => B)(implicit E: ErgoTreeEvaluator): Coll[B] = {
val tpeB = mc.tpe.asInstanceOf[SCollection[SType]].elemType
val tB = Evaluation.stypeToRType(tpeB).asInstanceOf[RType[B]]
E.addSeqCostNoOp(MapCollection.costKind, xs.length, mc.method.opDesc)
xs.map(f)(tB)
}
val ExistsMethod = SMethod(this, "exists",
SFunc(Array(ThisType, tPredicate), SBoolean, paramIVSeq), 4, Exists.costKind)
.withIRInfo({
case (builder, obj, _, Seq(c), _) => builder.mkExists(obj.asValue[SCollection[SType]], c.asFunc)
})
.withInfo(Exists,
"""Tests whether a predicate holds for at least one element of this collection.
|Returns \lst{true} if the given predicate \lst{p} is satisfied by at least one element of this collection, otherwise \lst{false}
""".stripMargin,
ArgInfo("p", "the predicate used to test elements"))
val FoldMethod = SMethod(
this, "fold",
SFunc(Array(ThisType, tOV, SFunc(Array(tOV, tIV), tOV)), tOV, Array(paramIV, paramOV)),
5, Fold.costKind)
.withIRInfo({
case (builder, obj, _, Seq(z, op), _) => builder.mkFold(obj.asValue[SCollection[SType]], z, op.asFunc)
})
.withInfo(Fold, "Applies a binary operator to a start value and all elements of this collection, going left to right.",
ArgInfo("zero", "a starting value"),
ArgInfo("op", "the binary operator"))
val ForallMethod = SMethod(this, "forall",
SFunc(Array(ThisType, tPredicate), SBoolean, paramIVSeq), 6, ForAll.costKind)
.withIRInfo({
case (builder, obj, _, Seq(c), _) => builder.mkForAll(obj.asValue[SCollection[SType]], c.asFunc)
})
.withInfo(ForAll,
"""Tests whether a predicate holds for all elements of this collection.
|Returns \lst{true} if this collection is empty or the given predicate \lst{p}
|holds for all elements of this collection, otherwise \lst{false}.
""".stripMargin,
ArgInfo("p", "the predicate used to test elements"))
val SliceMethod = SMethod(this, "slice",
SFunc(Array(ThisType, SInt, SInt), ThisType, paramIVSeq), 7, Slice.costKind)
.withIRInfo({
case (builder, obj, _, Seq(from, until), _) =>
builder.mkSlice(obj.asCollection[SType], from.asIntValue, until.asIntValue)
})
.withInfo(Slice,
"""Selects an interval of elements. The returned collection is made up
| of all elements \lst{x} which satisfy the invariant:
| \lst{
| from <= indexOf(x) < until
| }
""".stripMargin,
ArgInfo("from", "the lowest index to include from this collection"),
ArgInfo("until", "the lowest index to EXCLUDE from this collection"))
val FilterMethod = SMethod(this, "filter",
SFunc(Array(ThisType, tPredicate), ThisType, paramIVSeq), 8, Filter.costKind)
.withIRInfo({
case (builder, obj, _, Seq(l), _) => builder.mkFilter(obj.asValue[SCollection[SType]], l.asFunc)
})
.withInfo(Filter,
"""Selects all elements of this collection which satisfy a predicate.
| Returns a new collection consisting of all elements of this collection that satisfy the given
| predicate \lst{p}. The order of the elements is preserved.
""".stripMargin,
ArgInfo("p", "the predicate used to test elements."))
val AppendMethod = SMethod(this, "append",
SFunc(Array(ThisType, ThisType), ThisType, paramIVSeq), 9, Append.costKind)
.withIRInfo({
case (builder, obj, _, Seq(xs), _) =>
builder.mkAppend(obj.asCollection[SType], xs.asCollection[SType])
})
.withInfo(Append, "Puts the elements of other collection after the elements of this collection (concatenation of 2 collections)",
ArgInfo("other", "the collection to append at the end of this"))
val ApplyMethod = SMethod(this, "apply",
SFunc(Array(ThisType, SInt), tIV, Array[STypeParam](tIV)), 10, ByIndex.costKind)
.withInfo(ByIndex,
"""The element at given index.
| Indices start at \lst{0}; \lst{xs.apply(0)} is the first element of collection \lst{xs}.
| Note the indexing syntax \lst{xs(i)} is a shorthand for \lst{xs.apply(i)}.
| Returns the element at the given index.
| Throws an exception if \lst{i < 0} or \lst{length <= i}
""".stripMargin, ArgInfo("i", "the index"))
/** Cost of creating a collection of indices */
val IndicesMethod_CostKind = PerItemCost(
baseCost = JitCost(20), perChunkCost = JitCost(2), chunkSize = 16)
val IndicesMethod = SMethod(
this, "indices", SFunc(ThisType, SCollection(SInt)), 14, IndicesMethod_CostKind)
.withIRInfo(MethodCallIrBuilder)
.withInfo(PropertyCall,
"""Produces the range of all indices of this collection as a new collection
| containing [0 .. length-1] values.
""".stripMargin)
/** Implements evaluation of Coll.indices method call ErgoTree node.
* Called via reflection based on naming convention.
* @see SMethod.evalMethod
*/
def indices_eval[A, B](mc: MethodCall, xs: Coll[A])
(implicit E: ErgoTreeEvaluator): Coll[Int] = {
val m = mc.method
E.addSeqCost(m.costKind.asInstanceOf[PerItemCost], xs.length, m.opDesc) { () =>
xs.indices
}
}
/** BaseCost:
* 1) base cost of Coll.flatMap
* PerChunkCost:
* 1) cost of Coll.flatMap (per item)
* 2) new collection is allocated for each item
* 3) each collection is then appended to the resulting collection */
val FlatMapMethod_CostKind = PerItemCost(
baseCost = JitCost(60), perChunkCost = JitCost(10), chunkSize = 8)
val FlatMapMethod = SMethod(this, "flatMap",
SFunc(Array(ThisType, SFunc(tIV, tOVColl)), tOVColl, Array(paramIV, paramOV)),
15, FlatMapMethod_CostKind)
.withIRInfo(
MethodCallIrBuilder,
javaMethodOf[Coll[_], Function1[_,_], RType[_]]("flatMap"),
{ mtype => Array(mtype.tRange.asCollection[SType].elemType) })
.withInfo(MethodCall,
""" Builds a new collection by applying a function to all elements of this collection
| and using the elements of the resulting collections.
| Function \lst{f} is constrained to be of the form \lst{x => x.someProperty}, otherwise
| it is illegal.
| Returns a new collection of type \lst{Coll[B]} resulting from applying the given collection-valued function
| \lst{f} to each element of this collection and concatenating the results.
""".stripMargin, ArgInfo("f", "the function to apply to each element."))
/** We assume all flatMap body patterns have similar executon cost. */
final val CheckFlatmapBody_Info = OperationCostInfo(
PerItemCost(baseCost = JitCost(20), perChunkCost = JitCost(20), chunkSize = 1),
NamedDesc("CheckFlatmapBody"))
/** This patterns recognize all expressions, which are allowed as lambda body
* of flatMap. Other bodies are rejected with throwing exception.
*/
val flatMap_BodyPatterns = Array[PartialFunction[SValue, Int]](
{ case MethodCall(ValUse(id, tpe), m, args, _) if args.isEmpty => id },
{ case ExtractScriptBytes(ValUse(id, _)) => id },
{ case ExtractId(ValUse(id, _)) => id },
{ case SigmaPropBytes(ValUse(id, _)) => id },
{ case ExtractBytes(ValUse(id, _)) => id },
{ case ExtractBytesWithNoRef(ValUse(id, _)) => id }
)
/** Check the given expression is valid body of flatMap argument lambda.
* @param varId id of lambda variable (see [[FuncValue]].args)
* @param expr expression with is expected to use varId in ValUse node.
* @return true if the body is allowed
*/
def isValidPropertyAccess(varId: Int, expr: SValue)
(implicit E: ErgoTreeEvaluator): Boolean = {
var found = false
// NOTE: the cost depends on the position of the pattern since
// we are checking until the first matching pattern found.
E.addSeqCost(CheckFlatmapBody_Info) { () =>
// the loop is bounded because flatMap_BodyPatterns is fixed
var i = 0
val nPatterns = flatMap_BodyPatterns.length
while (i < nPatterns && !found) {
val p = flatMap_BodyPatterns(i)
found = p.lift(expr) match {
case Some(id) => id == varId // `id` in the pattern is equal to lambda `varId`
case None => false
}
i += 1
}
i // how many patterns checked
}
found
}
/** Operation descriptor for matching `flatMap` method calls with valid lambdas. */
final val MatchSingleArgMethodCall_Info = OperationCostInfo(
FixedCost(JitCost(30)), NamedDesc("MatchSingleArgMethodCall"))
/** Recognizer of `flatMap` method calls with valid lambdas. */
object IsSingleArgMethodCall {
def unapply(mc:MethodCall)
(implicit E: ErgoTreeEvaluator): Nullable[(Int, SValue)] = {
var res: Nullable[(Int, SValue)] = Nullable.None
E.addFixedCost(MatchSingleArgMethodCall_Info) {
res = mc match {
case MethodCall(_, m, Seq(FuncValue(args, body)), _) if args.length == 1 =>
val id = args(0)._1
Nullable((id, body))
case _ =>
Nullable.None
}
}
res
}
}
/** Checks that the given [[MethodCall]] operation is valid flatMap. */
def checkValidFlatmap(mc: MethodCall)(implicit E: ErgoTreeEvaluator) = {
mc match {
case IsSingleArgMethodCall(varId, lambdaBody)
if isValidPropertyAccess(varId, lambdaBody) =>
// ok, do nothing
case _ =>
throwInvalidFlatmap(mc)
}
}
def throwInvalidFlatmap(mc: MethodCall) = {
sys.error(
s"Unsupported lambda in flatMap: allowed usage `xs.flatMap(x => x.property)`: $mc")
}
/** Implements evaluation of Coll.flatMap method call ErgoTree node.
* Called via reflection based on naming convention.
* @see SMethod.evalMethod
*/
def flatMap_eval[A, B](mc: MethodCall, xs: Coll[A], f: A => Coll[B])
(implicit E: ErgoTreeEvaluator): Coll[B] = {
val m = mc.method
var res: Coll[B] = null
E.addSeqCost(m.costKind.asInstanceOf[PerItemCost], m.opDesc) { () =>
val tpeB = mc.tpe.asInstanceOf[SCollection[SType]].elemType
val tB = Evaluation.stypeToRType(tpeB).asInstanceOf[RType[B]]
res = xs.flatMap(f)(tB)
res.length
}
res
}
val PatchMethod = SMethod(this, "patch",
SFunc(Array(ThisType, SInt, ThisType, SInt), ThisType, paramIVSeq),
19, PerItemCost(baseCost = JitCost(30), perChunkCost = JitCost(2), chunkSize = 10))
.withIRInfo(MethodCallIrBuilder)
.withInfo(MethodCall,
"Produces a new Coll where a slice of elements in this Coll is replaced by another Coll.")
/** Implements evaluation of Coll.patch method call ErgoTree node.
* Called via reflection based on naming convention.
* @see SMethod.evalMethod
*/
def patch_eval[A](mc: MethodCall, xs: Coll[A], from: Int, patch: Coll[A], replaced: Int)
(implicit E: ErgoTreeEvaluator): Coll[A] = {
val m = mc.method
val nItems = xs.length + patch.length
E.addSeqCost(m.costKind.asInstanceOf[PerItemCost], nItems, m.opDesc) { () =>
xs.patch(from, patch, replaced)
}
}
val UpdatedMethod = SMethod(this, "updated",
SFunc(Array(ThisType, SInt, tIV), ThisType, paramIVSeq),
20, PerItemCost(baseCost = JitCost(20), perChunkCost = JitCost(1), chunkSize = 10))
.withIRInfo(MethodCallIrBuilder, javaMethodOf[Coll[_], Int, Any]("updated"))
.withInfo(MethodCall,
"A copy of this Coll with one single replaced element.")
/** Implements evaluation of Coll.updated method call ErgoTree node.
* Called via reflection based on naming convention.
* @see SMethod.evalMethod
*/
def updated_eval[A](mc: MethodCall, coll: Coll[A], index: Int, elem: A)
(implicit E: ErgoTreeEvaluator): Coll[A] = {
val m = mc.method
val costKind = m.costKind.asInstanceOf[PerItemCost]
E.addSeqCost(costKind, coll.length, m.opDesc) { () =>
coll.updated(index, elem)
}
}
val UpdateManyMethod = SMethod(this, "updateMany",
SFunc(Array(ThisType, SCollection(SInt), ThisType), ThisType, paramIVSeq),
21, PerItemCost(baseCost = JitCost(20), perChunkCost = JitCost(2), chunkSize = 10))
.withIRInfo(MethodCallIrBuilder).withInfo(MethodCall, "")
/** Implements evaluation of Coll.updateMany method call ErgoTree node.
* Called via reflection based on naming convention.
* @see SMethod.evalMethod
*/
def updateMany_eval[A](mc: MethodCall, coll: Coll[A], indexes: Coll[Int], values: Coll[A])
(implicit E: ErgoTreeEvaluator): Coll[A] = {
val costKind = mc.method.costKind.asInstanceOf[PerItemCost]
E.addSeqCost(costKind, coll.length, mc.method.opDesc) { () =>
coll.updateMany(indexes, values)
}
}
val IndexOfMethod = SMethod(this, "indexOf",
SFunc(Array(ThisType, tIV, SInt), SInt, paramIVSeq),
26, PerItemCost(baseCost = JitCost(20), perChunkCost = JitCost(10), chunkSize = 2))
.withIRInfo(MethodCallIrBuilder, javaMethodOf[Coll[_], Any, Int]("indexOf"))
.withInfo(MethodCall, "")
/** Implements evaluation of Coll.indexOf method call ErgoTree node.
* Called via reflection based on naming convention.
* @see SMethod.evalMethod
*/
def indexOf_eval[A](mc: MethodCall, xs: Coll[A], elem: A, from: Int)
(implicit E: ErgoTreeEvaluator): Int = {
val costKind = mc.method.costKind.asInstanceOf[PerItemCost]
var res: Int = -1
E.addSeqCost(costKind, mc.method.opDesc) { () =>
// this loop is bounded because MaxArrayLength limit is enforced
val len = xs.length
val start = math.max(from, 0)
var i = start
var different = true
while (i < len && different) {
different = !DataValueComparer.equalDataValues(xs(i), elem)
i += 1
}
if (!different)
res = i - 1
i - start // return number of performed iterations
}
res
}
/** Cost descriptor of Coll.zip operation. */
val Zip_CostKind = PerItemCost(
baseCost = JitCost(10), perChunkCost = JitCost(1), chunkSize = 10)
val ZipMethod = SMethod(this, "zip",
SFunc(Array(ThisType, tOVColl), SCollection(STuple(tIV, tOV)), Array[STypeParam](tIV, tOV)),
29, Zip_CostKind)
.withIRInfo(MethodCallIrBuilder)
.withInfo(MethodCall, "")
/** Implements evaluation of Coll.zip method call ErgoTree node.
* Called via reflection based on naming convention.
* @see SMethod.evalMethod
*/
def zip_eval[A, B](mc: MethodCall, xs: Coll[A], ys: Coll[B])
(implicit E: ErgoTreeEvaluator): Coll[(A,B)] = {
val m = mc.method
E.addSeqCost(m.costKind.asInstanceOf[PerItemCost], xs.length, m.opDesc) { () =>
xs.zip(ys)
}
}
/** This method should be overriden in derived classes to add new methods in addition to inherited.
* Typical override: `super.getMethods() ++ Seq(m1, m2, m3)`
*/
override protected def getMethods(): Seq[SMethod] = super.getMethods() ++
Seq(
SizeMethod,
GetOrElseMethod,
MapMethod,
ExistsMethod,
FoldMethod,
ForallMethod,
SliceMethod,
FilterMethod,
AppendMethod,
ApplyMethod,
IndicesMethod,
FlatMapMethod,
PatchMethod,
UpdatedMethod,
UpdateManyMethod,
IndexOfMethod,
ZipMethod
)
}
object STupleMethods extends MethodsContainer {
/** Type for which this container defines methods. */
override def ownerType: STypeCompanion = STuple
private val MaxTupleLength: Int = SigmaConstants.MaxTupleLength.value
private val componentNames = Array.tabulate(MaxTupleLength) { i => s"_${i + 1}" }
/** A list of Coll methods inherited from Coll type and available as method of tuple. */
lazy val colMethods: Seq[SMethod] = {
val subst = Map(SType.tIV -> SAny)
// TODO: implement other methods
val activeMethods = Set(1.toByte /*Coll.size*/, 10.toByte /*Coll.apply*/)
SCollectionMethods.methods.filter(m => activeMethods.contains(m.methodId)).map { m =>
m.copy(stype = applySubst(m.stype, subst).asFunc)
}
}
def getTupleMethod(tup: STuple, name: String): Option[SMethod] = {
colMethods.find(_.name == name).orElse {
val iComponent = componentNames.lastIndexOf(name, end = tup.items.length - 1)
if (iComponent == -1) None
else
Some(SMethod(
STupleMethods, name, SFunc(tup, tup.items(iComponent)),
(iComponent + 1).toByte, SelectField.costKind))
}
}
override protected def getMethods(): Seq[SMethod] = super.getMethods()
}
/** Type descriptor of `Box` type of ErgoTree. */
case object SBoxMethods extends MonoTypeMethods {
import ErgoBox._
import SType.{paramT, tT}
override def ownerType: SMonoType = SBox
/** Defined once here and then reused in SMethod descriptors. */
lazy val GetRegFuncType = SFunc(Array(SBox), SOption(tT), Array(paramT))
/** Creates a descriptor for the given register method. (i.e. R1, R2, etc) */
def registers(idOfs: Int): Seq[SMethod] = {
allRegisters.map { i =>
i match {
case r: MandatoryRegisterId =>
SMethod(this, s"R${i.asIndex}",
GetRegFuncType, (idOfs + i.asIndex + 1).toByte, ExtractRegisterAs.costKind)
.withInfo(ExtractRegisterAs, r.purpose)
case _ =>
SMethod(this, s"R${i.asIndex}",
GetRegFuncType, (idOfs + i.asIndex + 1).toByte, ExtractRegisterAs.costKind)
.withInfo(ExtractRegisterAs, "Non-mandatory register")
}
}
}
val PropositionBytes = "propositionBytes"
val Value = "value"
val Id = "id"
val Bytes = "bytes"
val BytesWithoutRef = "bytesWithoutRef"
val CreationInfo = "creationInfo"
val GetReg = "getReg"
// should be lazy, otherwise lead to initialization error
lazy val ValueMethod = SMethod(
this, Value, SFunc(SBox, SLong), 1, ExtractAmount.costKind)
.withInfo(ExtractAmount,
"Mandatory: Monetary value, in Ergo tokens (NanoErg unit of measure)")
lazy val PropositionBytesMethod = SMethod(
this, PropositionBytes, SFunc(SBox, SByteArray), 2, ExtractScriptBytes.costKind)
.withInfo(ExtractScriptBytes,
"Serialized bytes of guarding script, which should be evaluated to true in order to\n" +
" open this box. (aka spend it in a transaction)")
lazy val BytesMethod = SMethod(
this, Bytes, SFunc(SBox, SByteArray), 3, ExtractBytes.costKind)
.withInfo(ExtractBytes, "Serialized bytes of this box's content, including proposition bytes.")
lazy val BytesWithoutRefMethod = SMethod(
this, BytesWithoutRef, SFunc(SBox, SByteArray), 4, ExtractBytesWithNoRef.costKind)
.withInfo(ExtractBytesWithNoRef,
"Serialized bytes of this box's content, excluding transactionId and index of output.")
lazy val IdMethod = SMethod(this, Id, SFunc(SBox, SByteArray), 5, ExtractId.costKind)
.withInfo(ExtractId,
"Blake2b256 hash of this box's content, basically equals to \\lst{blake2b256(bytes)}")
lazy val creationInfoMethod = SMethod(
this, CreationInfo, ExtractCreationInfo.OpType, 6, ExtractCreationInfo.costKind)
.withInfo(ExtractCreationInfo,
""" If \lst{tx} is a transaction which generated this box, then \lst{creationInfo._1}
| is a height of the tx's block. The \lst{creationInfo._2} is a serialized transaction
| identifier followed by box index in the transaction outputs.
""".stripMargin ) // see ExtractCreationInfo
lazy val getRegMethod = SMethod(this, "getReg",
SFunc(Array(SBox, SInt), SOption(tT), Array(paramT)), 7, ExtractRegisterAs.costKind)
.withInfo(ExtractRegisterAs,
""" 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(
ValueMethod, // see ExtractAmount
PropositionBytesMethod, // see ExtractScriptBytes
BytesMethod, // see ExtractBytes
BytesWithoutRefMethod, // see ExtractBytesWithNoRef
IdMethod, // see ExtractId
creationInfoMethod,
getRegMethod,
tokensMethod
) ++ registers(8)
}
/** Type descriptor of `AvlTree` type of ErgoTree. */
case object SAvlTreeMethods extends MonoTypeMethods {
import SOption._
override def ownerType: SMonoType = SAvlTree
lazy val TCollOptionCollByte = SCollection(SByteArrayOption)
lazy val CollKeyValue = SCollection(STuple(SByteArray, SByteArray))
lazy val digestMethod = SMethod(this, "digest", SFunc(SAvlTree, SByteArray), 1, FixedCost(JitCost(15)))
.withIRInfo(MethodCallIrBuilder)
.withInfo(PropertyCall,
"""Returns digest of the state represented by this tree.
| Authenticated tree \lst{digest} = \lst{root hash bytes} ++ \lst{tree height}
""".stripMargin)
/** Cost descriptor of `digest` method. */
lazy val digest_Info = {
val m = digestMethod
OperationCostInfo(m.costKind.asInstanceOf[FixedCost], m.opDesc)
}
lazy val enabledOperationsMethod = SMethod(
this, "enabledOperations", SFunc(SAvlTree, SByte), 2, FixedCost(JitCost(15)))
.withIRInfo(MethodCallIrBuilder)
.withInfo(PropertyCall,
""" Flags of enabled operations packed in single byte.
| \lst{isInsertAllowed == (enabledOperations & 0x01) != 0}\newline
| \lst{isUpdateAllowed == (enabledOperations & 0x02) != 0}\newline
| \lst{isRemoveAllowed == (enabledOperations & 0x04) != 0}
""".stripMargin)
lazy val keyLengthMethod = SMethod(
this, "keyLength", SFunc(SAvlTree, SInt), 3, FixedCost(JitCost(15)))
.withIRInfo(MethodCallIrBuilder)
.withInfo(PropertyCall,
"""
|
""".stripMargin)
lazy val valueLengthOptMethod = SMethod(
this, "valueLengthOpt", SFunc(SAvlTree, SIntOption), 4, FixedCost(JitCost(15)))
.withIRInfo(MethodCallIrBuilder)
.withInfo(PropertyCall,
"""
|
""".stripMargin)
lazy val isInsertAllowedMethod = SMethod(
this, "isInsertAllowed", SFunc(SAvlTree, SBoolean), 5, FixedCost(JitCost(15)))
.withIRInfo(MethodCallIrBuilder)
.withInfo(PropertyCall,
"""
|
""".stripMargin)
lazy val isInsertAllowed_Info = {
val m = isInsertAllowedMethod
OperationCostInfo(m.costKind.asInstanceOf[FixedCost], m.opDesc)
}
lazy val isUpdateAllowedMethod = SMethod(
this, "isUpdateAllowed", SFunc(SAvlTree, SBoolean), 6, FixedCost(JitCost(15)))
.withIRInfo(MethodCallIrBuilder)
.withInfo(PropertyCall,
"""
|
""".stripMargin)
lazy val isUpdateAllowed_Info = {
val m = isUpdateAllowedMethod
OperationCostInfo(m.costKind.asInstanceOf[FixedCost], m.opDesc)
}
lazy val isRemoveAllowedMethod = SMethod(
this, "isRemoveAllowed", SFunc(SAvlTree, SBoolean), 7, FixedCost(JitCost(15)))
.withIRInfo(MethodCallIrBuilder)
.withInfo(PropertyCall,
"""
|
""".stripMargin)
lazy val isRemoveAllowed_Info = {
val m = isRemoveAllowedMethod
OperationCostInfo(m.costKind.asInstanceOf[FixedCost], m.opDesc)
}
lazy val updateOperationsMethod = SMethod(this, "updateOperations",
SFunc(Array(SAvlTree, SByte), SAvlTree), 8, FixedCost(JitCost(45)))
.withIRInfo(MethodCallIrBuilder)
.withInfo(MethodCall,
"""
|
""".stripMargin)
lazy val containsMethod = SMethod(this, "contains",
SFunc(Array(SAvlTree, SByteArray, SByteArray), SBoolean), 9, DynamicCost)
.withIRInfo(MethodCallIrBuilder)
.withInfo(MethodCall,
"""
| /** Checks if an entry with key `key` exists in this tree using proof `proof`.
| * Throws exception if proof is incorrect
|
| * @note CAUTION! Does not support multiple keys check, use [[getMany]] instead.
| * Return `true` if a leaf with the key `key` exists
| * Return `false` if leaf with provided key does not exist.
| * @param key a key of an element of this authenticated dictionary.
| * @param proof
| */
|
""".stripMargin)
/** The proof may contain keys, labels and values, we don't know for sure how many,
* but we assume the cost is O(proof.length).
* So the following is an approximation of the proof parsing cost.
*/
final val CreateAvlVerifier_Info = OperationCostInfo(
PerItemCost(baseCost = JitCost(110), perChunkCost = JitCost(20), chunkSize = 64),
NamedDesc("CreateAvlVerifier"))
final val LookupAvlTree_Info = OperationCostInfo(
PerItemCost(baseCost = JitCost(40), perChunkCost = JitCost(10), chunkSize = 1),
NamedDesc("LookupAvlTree"))
final val InsertIntoAvlTree_Info = OperationCostInfo(
PerItemCost(baseCost = JitCost(40), perChunkCost = JitCost(10), chunkSize = 1),
NamedDesc("InsertIntoAvlTree"))
final val UpdateAvlTree_Info = OperationCostInfo(
PerItemCost(baseCost = JitCost(120), perChunkCost = JitCost(20), chunkSize = 1),
NamedDesc("UpdateAvlTree"))
final val RemoveAvlTree_Info = OperationCostInfo(
PerItemCost(baseCost = JitCost(100), perChunkCost = JitCost(15), chunkSize = 1),
NamedDesc("RemoveAvlTree"))
/** Implements evaluation of AvlTree.contains method call ErgoTree node.
* Called via reflection based on naming convention.
* @see SMethod.evalMethod
*/
def contains_eval(mc: MethodCall, tree: AvlTree, key: Coll[Byte], proof: Coll[Byte])
(implicit E: ErgoTreeEvaluator): Boolean = {
E.contains_eval(mc, tree, key, proof)
}
lazy val getMethod = SMethod(this, "get",
SFunc(Array(SAvlTree, SByteArray, SByteArray), SByteArrayOption), 10, DynamicCost)
.withIRInfo(MethodCallIrBuilder)
.withInfo(MethodCall,
"""
| /** Perform a lookup of key `key` in this tree using proof `proof`.
| * Throws exception if proof is incorrect
| *
| * @note CAUTION! Does not support multiple keys check, use [[getMany]] instead.
| * Return Some(bytes) of leaf with key `key` if it exists
| * Return None if leaf with provided key does not exist.
| * @param key a key of an element of this authenticated dictionary.
| * @param proof
| */
|
""".stripMargin)
/** Implements evaluation of AvlTree.get method call ErgoTree node.
* Called via reflection based on naming convention.
* @see SMethod.evalMethod
*/
def get_eval(mc: MethodCall, tree: AvlTree, key: Coll[Byte], proof: Coll[Byte])
(implicit E: ErgoTreeEvaluator): Option[Coll[Byte]] = {
E.get_eval(mc, tree, key, proof)
}
lazy val getManyMethod = SMethod(this, "getMany",
SFunc(Array(SAvlTree, SByteArray2, SByteArray), TCollOptionCollByte), 11, DynamicCost)
.withIRInfo(MethodCallIrBuilder)
.withInfo(MethodCall,
"""
| /** Perform a lookup of many keys `keys` in this tree using proof `proof`.
| *
| * @note CAUTION! Keys must be ordered the same way they were in lookup before proof was generated.
| * For each key return Some(bytes) of leaf if it exists and None if is doesn't.
| * @param keys keys of elements of this authenticated dictionary.
| * @param proof
| */
|
""".stripMargin)
/** Implements evaluation of AvlTree.getMany method call ErgoTree node.
* Called via reflection based on naming convention.
* @see SMethod.evalMethod
*/
def getMany_eval(mc: MethodCall, tree: AvlTree, keys: Coll[Coll[Byte]], proof: Coll[Byte])
(implicit E: ErgoTreeEvaluator): Coll[Option[Coll[Byte]]] = {
E.getMany_eval(mc, tree, keys, proof)
}
lazy val insertMethod = SMethod(this, "insert",
SFunc(Array(SAvlTree, CollKeyValue, SByteArray), SAvlTreeOption), 12, DynamicCost)
.withIRInfo(MethodCallIrBuilder)
.withInfo(MethodCall,
"""
| /** Perform insertions of key-value entries into this tree using proof `proof`.
| * Throws exception if proof is incorrect
| *
| * @note CAUTION! Pairs must be ordered the same way they were in insert ops before proof was generated.
| * Return Some(newTree) if successful
| * Return None if operations were not performed.
| * @param operations collection of key-value pairs to insert in this authenticated dictionary.
| * @param proof
| */
|
""".stripMargin)
/** Implements evaluation of AvlTree.insert method call ErgoTree node.
* Called via reflection based on naming convention.
* @see SMethod.evalMethod
*/
def insert_eval(mc: MethodCall, tree: AvlTree, entries: KeyValueColl, proof: Coll[Byte])
(implicit E: ErgoTreeEvaluator): Option[AvlTree] = {
E.insert_eval(mc, tree, entries, proof)
}
lazy val updateMethod = SMethod(this, "update",
SFunc(Array(SAvlTree, CollKeyValue, SByteArray), SAvlTreeOption), 13, DynamicCost)
.withIRInfo(MethodCallIrBuilder)
.withInfo(MethodCall,
"""
| /** Perform updates of key-value entries into this tree using proof `proof`.
| * Throws exception if proof is incorrect
| *
| * @note CAUTION! Pairs must be ordered the same way they were in update ops before proof was generated.
| * Return Some(newTree) if successful
| * Return None if operations were not performed.
| * @param operations collection of key-value pairs to update in this authenticated dictionary.
| * @param proof
| */
|
""".stripMargin)
/** Implements evaluation of AvlTree.update method call ErgoTree node.
* Called via reflection based on naming convention.
* @see SMethod.evalMethod
*/
def update_eval(mc: MethodCall, tree: AvlTree,
operations: KeyValueColl, proof: Coll[Byte])
(implicit E: ErgoTreeEvaluator): Option[AvlTree] = {
E.update_eval(mc, tree, operations, proof)
}
lazy val removeMethod = SMethod(this, "remove",
SFunc(Array(SAvlTree, SByteArray2, SByteArray), SAvlTreeOption), 14, DynamicCost)
.withIRInfo(MethodCallIrBuilder)
.withInfo(MethodCall,
"""
| /** Perform removal of entries into this tree using proof `proof`.
| * Throws exception if proof is incorrect
| * Return Some(newTree) if successful
| * Return None if operations were not performed.
| *
| * @note CAUTION! Keys must be ordered the same way they were in remove ops before proof was generated.
| * @param operations collection of keys to remove from this authenticated dictionary.
| * @param proof
| */
|
""".stripMargin)
/** Implements evaluation of AvlTree.remove method call ErgoTree node.
* Called via reflection based on naming convention.
* @see SMethod.evalMethod
*/
def remove_eval(mc: MethodCall, tree: AvlTree,
operations: Coll[Coll[Byte]], proof: Coll[Byte])
(implicit E: ErgoTreeEvaluator): Option[AvlTree] = {
E.remove_eval(mc, tree, operations, proof)
}
lazy val updateDigestMethod = SMethod(this, "updateDigest",
SFunc(Array(SAvlTree, SByteArray), SAvlTree), 15, FixedCost(JitCost(40)))
.withIRInfo(MethodCallIrBuilder)
.withInfo(MethodCall,
"""
|
""".stripMargin)
lazy val updateDigest_Info = {
val m = updateDigestMethod
OperationCostInfo(m.costKind.asInstanceOf[FixedCost], m.opDesc)
}
protected override def getMethods(): Seq[SMethod] = super.getMethods() ++ Seq(
digestMethod,
enabledOperationsMethod,
keyLengthMethod,
valueLengthOptMethod,
isInsertAllowedMethod,
isUpdateAllowedMethod,
isRemoveAllowedMethod,
updateOperationsMethod,
containsMethod,
getMethod,
getManyMethod,
insertMethod,
updateMethod,
removeMethod,
updateDigestMethod
)
}
/** Type descriptor of `Context` type of ErgoTree. */
case object SContextMethods extends MonoTypeMethods {
override def ownerType: SMonoType = SContext
/** Arguments on context operation such as getVar, DeserializeContext etc.
* This value can be reused where necessary to avoid allocations. */
val ContextFuncDom: IndexedSeq[SType] = Array(SContext, SByte)
import SType.{paramT, tT}
lazy val dataInputsMethod = propertyCall("dataInputs", SBoxArray, 1, FixedCost(JitCost(15)))
lazy val headersMethod = propertyCall("headers", SHeaderArray, 2, FixedCost(JitCost(15)))
lazy val preHeaderMethod = propertyCall("preHeader", SPreHeader, 3, FixedCost(JitCost(15)))
lazy val inputsMethod = property("INPUTS", SBoxArray, 4, Inputs)
lazy val outputsMethod = property("OUTPUTS", SBoxArray, 5, Outputs)
lazy val heightMethod = property("HEIGHT", SInt, 6, Height)
lazy val selfMethod = property("SELF", SBox, 7, Self)
lazy val selfBoxIndexMethod = propertyCall("selfBoxIndex", SInt, 8, FixedCost(JitCost(20)))
lazy val lastBlockUtxoRootHashMethod = property("LastBlockUtxoRootHash", SAvlTree, 9, LastBlockUtxoRootHash)
lazy val minerPubKeyMethod = property("minerPubKey", SByteArray, 10, MinerPubkey)
lazy val getVarMethod = SMethod(
this, "getVar", SFunc(ContextFuncDom, SOption(tT), Array(paramT)), 11, GetVar.costKind)
.withInfo(GetVar, "Get context variable with given \\lst{varId} and type.",
ArgInfo("varId", "\\lst{Byte} identifier of context variable"))
protected override def getMethods() = super.getMethods() ++ Seq(
dataInputsMethod, headersMethod, preHeaderMethod, inputsMethod, outputsMethod, heightMethod, selfMethod,
selfBoxIndexMethod, lastBlockUtxoRootHashMethod, minerPubKeyMethod, getVarMethod
)
/** Names of methods which provide blockchain context.
* This value can be reused where necessary to avoid allocations. */
val BlockchainContextMethodNames: IndexedSeq[String] = Array(
headersMethod.name, preHeaderMethod.name, heightMethod.name,
lastBlockUtxoRootHashMethod.name, minerPubKeyMethod.name
)
}
/** Type descriptor of `Header` type of ErgoTree. */
case object SHeaderMethods extends MonoTypeMethods {
override def ownerType: SMonoType = SHeader
lazy val idMethod = propertyCall("id", SByteArray, 1, FixedCost(JitCost(10)))
lazy val versionMethod = propertyCall("version", SByte, 2, FixedCost(JitCost(10)))
lazy val parentIdMethod = propertyCall("parentId", SByteArray, 3, FixedCost(JitCost(10)))
lazy val ADProofsRootMethod = propertyCall("ADProofsRoot", SByteArray, 4, FixedCost(JitCost(10)))
lazy val stateRootMethod = propertyCall("stateRoot", SAvlTree, 5, FixedCost(JitCost(10)))
lazy val transactionsRootMethod = propertyCall("transactionsRoot", SByteArray, 6, FixedCost(JitCost(10)))
lazy val timestampMethod = propertyCall("timestamp", SLong, 7, FixedCost(JitCost(10)))
lazy val nBitsMethod = propertyCall("nBits", SLong, 8, FixedCost(JitCost(10)))
lazy val heightMethod = propertyCall("height", SInt, 9, FixedCost(JitCost(10)))
lazy val extensionRootMethod = propertyCall("extensionRoot", SByteArray, 10, FixedCost(JitCost(10)))
lazy val minerPkMethod = propertyCall("minerPk", SGroupElement, 11, FixedCost(JitCost(10)))
lazy val powOnetimePkMethod = propertyCall("powOnetimePk", SGroupElement, 12, FixedCost(JitCost(10)))
lazy val powNonceMethod = propertyCall("powNonce", SByteArray, 13, FixedCost(JitCost(10)))
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(
idMethod, versionMethod, parentIdMethod, ADProofsRootMethod, stateRootMethod, transactionsRootMethod,
timestampMethod, nBitsMethod, heightMethod, extensionRootMethod, minerPkMethod, powOnetimePkMethod,
powNonceMethod, powDistanceMethod, votesMethod
)
}
/** Type descriptor of `PreHeader` type of ErgoTree. */
case object SPreHeaderMethods extends MonoTypeMethods {
override def ownerType: SMonoType = SPreHeader
lazy val versionMethod = propertyCall("version", SByte, 1, FixedCost(JitCost(10)))
lazy val parentIdMethod = propertyCall("parentId", SByteArray, 2, FixedCost(JitCost(10)))
lazy val timestampMethod = propertyCall("timestamp", SLong, 3, FixedCost(JitCost(10)))
lazy val nBitsMethod = propertyCall("nBits", SLong, 4, FixedCost(JitCost(10)))
lazy val heightMethod = propertyCall("height", SInt, 5, FixedCost(JitCost(10)))
lazy val minerPkMethod = propertyCall("minerPk", SGroupElement, 6, FixedCost(JitCost(10)))
lazy val votesMethod = propertyCall("votes", SByteArray, 7, FixedCost(JitCost(10)))
protected override def getMethods() = super.getMethods() ++ Seq(
versionMethod, parentIdMethod, timestampMethod, nBitsMethod, heightMethod, minerPkMethod, votesMethod
)
}
/** This type is introduced to unify handling of global and non-global (i.e. methods) operations.
* It unifies implementation of global operation with implementation of methods and avoids code
* duplication (following DRY principle https://en.wikipedia.org/wiki/Don%27t_repeat_yourself).
* The WrappedType is `sigma.SigmaDslBuilder`, which is an interface implemented by
* the singleton sigmastate.eval.CostingSigmaDslBuilder
*
* The Constant(...) tree node of this type are not allowed, as well as using it in register and
* context variables (aka ContextExtension)
*
* When new methods are added to this type via a soft-fork, they will be serialized as part
* of ErgoTree using MethodCallSerializer, where SGlobal.typeCode will be used.
*
* @see sigmastate.lang.SigmaPredef
* */
case object SGlobalMethods extends MonoTypeMethods {
override def ownerType: SMonoType = SGlobal
lazy val groupGeneratorMethod = SMethod(
this, "groupGenerator", SFunc(SGlobal, SGroupElement), 1, GroupGenerator.costKind)
.withIRInfo({ case (builder, obj, method, args, tparamSubst) => GroupGenerator })
.withInfo(GroupGenerator, "")
lazy val xorMethod = SMethod(
this, "xor", SFunc(Array(SGlobal, SByteArray, SByteArray), SByteArray), 2, Xor.costKind)
.withIRInfo({
case (_, _, _, Seq(l, r), _) => Xor(l.asByteArray, r.asByteArray)
})
.withInfo(Xor, "Byte-wise XOR of two collections of bytes",
ArgInfo("left", "left operand"), ArgInfo("right", "right operand"))
/** Implements evaluation of Global.xor method call ErgoTree node.
* Called via reflection based on naming convention.
* @see SMethod.evalMethod, Xor.eval, Xor.xorWithCosting
*/
def xor_eval(mc: MethodCall, G: SigmaDslBuilder, ls: Coll[Byte], rs: Coll[Byte])
(implicit E: ErgoTreeEvaluator): Coll[Byte] = {
Xor.xorWithCosting(ls, rs)
}
protected override def getMethods() = super.getMethods() ++ Seq(
groupGeneratorMethod,
xorMethod
)
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy