Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[6.0] GetVar(inputIndex, varId) for reading context variable from another input and fix for Context.getVar #1016

Merged
merged 18 commits into from
Oct 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions core/shared/src/main/scala/sigma/SigmaDsl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -589,6 +589,17 @@ trait Context {
*/
def getVar[T](id: Byte)(implicit cT: RType[T]): Option[T]

/**
* A variant of `getVar` to extract a context variable by id and type from any input
*
* @param inputIndex - input index
* @param id - context variable id
* @tparam T - expected type of the variable
* @return Some(value) if the variable is defined in the context AND has the given type.
* None otherwise
aslesarenko marked this conversation as resolved.
Show resolved Hide resolved
*/
def getVarFromInput[T](inputIndex: Short, id: Byte)(implicit cT: RType[T]): Option[T]

def vars: Coll[AnyValue]

/** Maximum version of ErgoTree currently activated on the network.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,9 @@ object ReflectionData {
mkMethod(clazz, "getVar", Array[Class[_]](classOf[Byte], classOf[RType[_]])) { (obj, args) =>
obj.asInstanceOf[Context].getVar(args(0).asInstanceOf[Byte])(args(1).asInstanceOf[RType[_]])
},
mkMethod(clazz, "getVarFromInput", Array[Class[_]](classOf[Short], classOf[Byte], classOf[RType[_]])) { (obj, args) =>
obj.asInstanceOf[Context].getVarFromInput(args(0).asInstanceOf[Short], args(1).asInstanceOf[Byte])(args(2).asInstanceOf[RType[_]])
},
mkMethod(clazz, "headers", Array[Class[_]]()) { (obj, _) =>
obj.asInstanceOf[Context].headers
}
Expand Down
12 changes: 12 additions & 0 deletions data/shared/src/main/scala/sigma/ast/SMethod.scala
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,18 @@ object SMethod {
(implicit cT: ClassTag[T], cA1: ClassTag[A1], cA2: ClassTag[A2]): RMethod =
RClass(cT.runtimeClass).getMethod(methodName, cA1.runtimeClass, cA2.runtimeClass)

/** Return [[Method]] descriptor for the given `methodName` on the given `cT` type.
* @param methodName the name of the method to lookup
* @param cT the class where to search the methodName
* @param cA1 the class of the method's first argument
* @param cA2 the class of the method's second argument
* @param cA3 the class of the method's third argument
*/
def javaMethodOf[T, A1, A2, A3]
(methodName: String)
(implicit cT: ClassTag[T], cA1: ClassTag[A1], cA2: ClassTag[A2], cA3: ClassTag[A3]): RMethod =
RClass(cT.runtimeClass).getMethod(methodName, cA1.runtimeClass, cA2.runtimeClass, cA3.runtimeClass)

/** Default fallback method call recognizer which builds MethodCall ErgoTree nodes. */
val MethodCallIrBuilder: PartialFunction[(SigmaBuilder, SValue, SMethod, Seq[SValue], STypeSubst), SValue] = {
case (builder, obj, method, args, tparamSubst) =>
Expand Down
13 changes: 13 additions & 0 deletions data/shared/src/main/scala/sigma/ast/SigmaPredef.scala
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,18 @@ object SigmaPredef {
Seq(ArgInfo("varId", "\\lst{Byte} identifier of context variable")))
)

val GetVarFromInputFunc = PredefinedFunc("getVarFromInput",
Lambda(Array(paramT), Array("inputId" -> SShort, "varId" -> SByte), SOption(tT), None),
PredefFuncInfo(
{ case (Ident(_, SFunc(_, SOption(rtpe), _)), Seq(inputId: Constant[SNumericType]@unchecked, varId: Constant[SNumericType]@unchecked)) =>
mkMethodCall(Context, SContextMethods.getVarFromInputMethod, IndexedSeq(SShort.downcast(inputId.value.asInstanceOf[AnyVal]), SByte.downcast(varId.value.asInstanceOf[AnyVal])), Map(tT -> rtpe))
}),
OperationInfo(MethodCall,
"Get context variable with given \\lst{varId} and type.",
Seq(ArgInfo("inputId", "\\lst{Byte} index of input to read context variable from"),
ArgInfo("varId", "\\lst{Byte} identifier of context variable")))
)

def PKFunc(networkPrefix: NetworkPrefix) = PredefinedFunc("PK",
Lambda(Array("input" -> SString), SSigmaProp, None),
PredefFuncInfo(
Expand Down Expand Up @@ -468,6 +480,7 @@ object SigmaPredef {
ExecuteFromVarFunc,
ExecuteFromSelfRegFunc,
SerializeFunc,
GetVarFromInputFunc,
FromBigEndianBytesFunc
).map(f => f.name -> f).toMap

Expand Down
42 changes: 39 additions & 3 deletions data/shared/src/main/scala/sigma/ast/methods.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1677,16 +1677,52 @@ case object SContextMethods extends MonoTypeMethods {
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(

lazy val getVarV5Method = 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(
lazy val getVarV6Method = SMethod(
this, "getVar", SFunc(ContextFuncDom, SOption(tT), Array(paramT)), 11, GetVar.costKind, Seq(tT))
.withIRInfo(
MethodCallIrBuilder,
javaMethodOf[Context, Byte, RType[_]]("getVar"),
{ mtype => Array(mtype.tRange.asOption[SType].elemType) })
.withInfo(MethodCall, "Get context variable with given \\lst{varId} and type.")

lazy val getVarFromInputMethod = SMethod(
this, "getVarFromInput", SFunc(Array(SContext, SShort, SByte), SOption(tT), Array(paramT)), 12, GetVar.costKind, Seq(tT))
.withIRInfo(
MethodCallIrBuilder,
javaMethodOf[Context, Short, Byte, RType[_]]("getVarFromInput"),
{ mtype => Array(mtype.tRange.asOption[SType].elemType) })
.withInfo(MethodCall, "Get context variable with given \\lst{varId} and type.",
ArgInfo("inputIdx", "Index of input to read variable from."),
ArgInfo("varId", "Index of variable.")
)

private lazy val commonMethods = super.getMethods() ++ Array(
dataInputsMethod, headersMethod, preHeaderMethod, inputsMethod, outputsMethod, heightMethod, selfMethod,
selfBoxIndexMethod, lastBlockUtxoRootHashMethod, minerPubKeyMethod, getVarMethod
selfBoxIndexMethod, lastBlockUtxoRootHashMethod, minerPubKeyMethod
)

private lazy val v5Methods = commonMethods ++ Seq(
getVarV5Method
)

private lazy val v6Methods = commonMethods ++ Seq(
getVarV6Method, getVarFromInputMethod
)

protected override def getMethods(): Seq[SMethod] = {
if (VersionContext.current.isV6SoftForkActivated) {
v6Methods
} else {
v5Methods
}
}

/** Names of methods which provide blockchain context.
* This value can be reused where necessary to avoid allocations. */
val BlockchainContextMethodNames: IndexedSeq[String] = Array(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,15 @@ 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 =
def add(bindings: VarBinding*): ContextExtension = {
ContextExtension(values ++ bindings)
}

/**
* @param varId - index of context variable
* @return context variable with provided index or None if it is not there
*/
def get(varId: Byte): Option[EvaluatedValue[_ <: SType]] = values.get(varId)
}

object ContextExtension {
Expand Down
14 changes: 13 additions & 1 deletion docs/LangSpec.md
Original file line number Diff line number Diff line change
Expand Up @@ -919,7 +919,7 @@ def longToByteArray(input: Long): Coll[Byte]
def decodePoint(bytes: Coll[Byte]): GroupElement


/** Extracts Context variable by id and type.
/** Extracts Context variable from SELF input by id and type.
* ErgoScript is typed, so accessing a the variables is an operation which involves
* some expected type given in brackets. Thus `getVar[Int](id)` expression should
* evaluate to a valid value of the `Option[Int]` type.
Expand Down Expand Up @@ -976,6 +976,18 @@ def decodePoint(bytes: Coll[Byte]): GroupElement
*/
def getVar[T](tag: Int): Option[T]

/** Extracts Context variable from any input by input index, variable id and variable type.
* Unlike getVar, it is not throwing exception when expected type does not match real type of the variable.
* Thus it can be used to get context variable from self without exception, using selfBoxIndex, e.g.
* <pre class="stHighlight">
* {
* val idx = CONTEXT.selfBoxIndex
* sigmaProp(CONTEXT.getVarFromInput[Int](idx.toShort, 1.toByte).get == 5)
* }
* </pre>
*/
def getVarFromInput[T](inputId: Short, varId: Byte): Option[T]

/** Construct a new SigmaProp value representing public key of Diffie Hellman
* signature protocol. When executed as part of Sigma protocol allow to provide
* for a verifier a zero-knowledge proof of secret knowledge.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,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, vars, spendingTransaction, activatedScriptVersion, ergoTreeVersion)
}


Expand Down
12 changes: 12 additions & 0 deletions interpreter/shared/src/main/scala/sigmastate/eval/CContext.scala
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package sigmastate.eval

import debox.cfor
import org.ergoplatform.{ErgoLikeTransactionTemplate, UnsignedInput}
import sigma.Evaluation.stypeToRType
import sigma.Extensions.ArrayOps
import sigma._
import sigma.ast.SType
import sigma.data._
import sigma.exceptions.InvalidType

Expand All @@ -24,6 +27,7 @@ case class CContext(
lastBlockUtxoRootHash: AvlTree,
_minerPubKey: Coll[Byte],
vars: Coll[AnyValue],
spendingTransaction: ErgoLikeTransactionTemplate[_ <: UnsignedInput],
override val activatedScriptVersion: Byte,
override val currentErgoTreeVersion: Byte
) extends Context {
Expand Down Expand Up @@ -69,6 +73,14 @@ case class CContext(
} else None
}

override def getVarFromInput[T](inputIndex: Short, id: Byte)(implicit tT: RType[T]): Option[T] = {
spendingTransaction.inputs.lift(inputIndex).flatMap(_.extension.get(id)) match {
case Some(v) if stypeToRType[SType](v.tpe) == tT => Some(v.value.asInstanceOf[T])
case _ =>
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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ trait ContractsTestkit {
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.toColl, null, activatedScriptVersion, currErgoTreeVersion)

def newContext(
height: Int,
Expand Down
10 changes: 10 additions & 0 deletions sc/shared/src/main/scala/sigma/compiler/ir/GraphBuilding.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package sigma.compiler.ir

import org.ergoplatform._
import sigma.ast.SType.tT
import sigma.Evaluation.stypeToRType
import sigma.ast.SType.tT
import sigma.ast.TypeCodes.LastConstantCode
Expand Down Expand Up @@ -1059,6 +1060,15 @@ trait GraphBuilding extends Base with DefRewriting { IR: IRContext =>
ctx.LastBlockUtxoRootHash
case SContextMethods.minerPubKeyMethod.name =>
ctx.minerPubKey
case SContextMethods.getVarV6Method.name =>
val c2 = asRep[Byte](argsV(0))
val c3 = stypeToElem(typeSubst.apply(tT))
ctx.getVar(c2)(c3)
case SContextMethods.getVarFromInputMethod.name =>
val c1 = asRep[Short](argsV(0))
val c2 = asRep[Byte](argsV(1))
val c3 = stypeToElem(typeSubst.apply(tT))
ctx.getVarFromInput(c1, c2)(c3)
case _ => throwError()
}
case (tree: Ref[AvlTree]@unchecked, SAvlTreeMethods) => method.name match {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,9 @@ object GraphIRReflection {
mkMethod(clazz, "getVar", Array[Class[_]](classOf[Base#Ref[_]], classOf[TypeDescs#Elem[_]])) { (obj, args) =>
obj.asInstanceOf[ctx.Context].getVar(args(0).asInstanceOf[ctx.Ref[Byte]])(args(1).asInstanceOf[ctx.Elem[_]])
},
mkMethod(clazz, "getVarFromInput", Array[Class[_]](classOf[Base#Ref[_]], classOf[Base#Ref[_]], classOf[TypeDescs#Elem[_]])) { (obj, args) =>
obj.asInstanceOf[ctx.Context].getVarFromInput(args(0).asInstanceOf[ctx.Ref[Short]], args(1).asInstanceOf[ctx.Ref[Byte]])(args(2).asInstanceOf[ctx.Elem[_]])
},
mkMethod(clazz, "headers", Array[Class[_]]()) { (obj, args) =>
obj.asInstanceOf[ctx.Context].headers
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package sigma.compiler.ir

import org.ergoplatform._
import sigma.VersionContext
import sigma.Evaluation.{rtypeToSType, stypeToRType}
import sigma.ast.SType.tT
import sigma.ast._
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ import scalan._
def preHeader: Ref[PreHeader];
def minerPubKey: Ref[Coll[Byte]];
def getVar[T](id: Ref[Byte])(implicit cT: Elem[T]): Ref[WOption[T]];
def getVarFromInput[T](inputId: Ref[Short], id: Ref[Byte])(implicit cT: Elem[T]): Ref[WOption[T]];
};
trait SigmaDslBuilder extends Def[SigmaDslBuilder] {
def Colls: Ref[CollBuilder];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1626,10 +1626,19 @@ object Context extends EntityObject("Context") {
}

override def getVar[T](id: Ref[Byte])(implicit cT: Elem[T]): Ref[WOption[T]] = {
val st = Evaluation.rtypeToSType(cT.sourceType)
asRep[WOption[T]](mkMethodCall(self,
ContextClass.getMethod("getVar", classOf[Sym], classOf[Elem[_]]),
Array[AnyRef](id, cT),
true, false, element[WOption[T]]))
true, false, element[WOption[T]], Map(tT -> st)))
}

override def getVarFromInput[T](inputId: Ref[Short], varId: Ref[Byte])(implicit cT: Elem[T]): Ref[WOption[T]] = {
val st = Evaluation.rtypeToSType(cT.sourceType)
asRep[WOption[T]](mkMethodCall(self,
ContextClass.getMethod("getVarFromInput", classOf[Sym], classOf[Sym], classOf[Elem[_]]),
Array[AnyRef](inputId, varId, cT),
true, false, element[WOption[T]], Map(tT -> st)))
}

}
Expand Down Expand Up @@ -1723,10 +1732,19 @@ object Context extends EntityObject("Context") {
}

def getVar[T](id: Ref[Byte])(implicit cT: Elem[T]): Ref[WOption[T]] = {
val st = Evaluation.rtypeToSType(cT.sourceType)
asRep[WOption[T]](mkMethodCall(source,
ContextClass.getMethod("getVar", classOf[Sym], classOf[Elem[_]]),
Array[AnyRef](id, cT),
true, true, element[WOption[T]]))
true, true, element[WOption[T]], Map(tT -> st)))
}

def getVarFromInput[T](inputId: Ref[Short], varId: Ref[Byte])(implicit cT: Elem[T]): Ref[WOption[T]] = {
val st = Evaluation.rtypeToSType(cT.sourceType)
asRep[WOption[T]](mkMethodCall(source,
ContextClass.getMethod("getVarFromInput", classOf[Sym], classOf[Sym], classOf[Elem[_]]),
Array[AnyRef](inputId, varId, cT),
true, true, element[WOption[T]], Map(tT -> st)))
}
}

Expand All @@ -1745,7 +1763,7 @@ object Context extends EntityObject("Context") {
override protected def collectMethods: Map[RMethod, MethodDesc] = {
super.collectMethods ++
Elem.declaredMethods(RClass(classOf[Context]), RClass(classOf[SContext]), Set(
"OUTPUTS", "INPUTS", "dataInputs", "HEIGHT", "SELF", "selfBoxIndex", "LastBlockUtxoRootHash", "headers", "preHeader", "minerPubKey", "getVar", "vars"
"OUTPUTS", "INPUTS", "dataInputs", "HEIGHT", "SELF", "selfBoxIndex", "LastBlockUtxoRootHash", "headers", "preHeader", "minerPubKey", "getVar", "getVarFromInput", "vars"
))
}
}
Expand Down
25 changes: 18 additions & 7 deletions sc/shared/src/main/scala/sigma/compiler/phases/SigmaTyper.scala
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,19 @@ class SigmaTyper(val builder: SigmaBuilder,
res

case Apply(ApplyTypes(sel @ Select(obj, n, _), Seq(rangeTpe)), args) =>
// downcast getVarFromInput arguments to short and byte
val nArgs = if (n == SContextMethods.getVarFromInputMethod.name &&
args.length == 2 &&
args(0).isInstanceOf[Constant[_]] &&
args(1).isInstanceOf[Constant[_]] &&
args(0).tpe.isNumType &&
args(1).tpe.isNumType) {
IndexedSeq(ShortConstant(SShort.downcast(args(0).asInstanceOf[Constant[SNumericType]].value.asInstanceOf[AnyVal])).withSrcCtx(args(0).sourceContext),
ByteConstant(SByte.downcast(args(1).asInstanceOf[Constant[SNumericType]].value.asInstanceOf[AnyVal])).withSrcCtx(args(1).sourceContext))
} else args

val newObj = assignType(env, obj)
val newArgs = args.map(assignType(env, _))
val newArgs = nArgs.map(assignType(env, _))
newObj.tpe match {
case p: SProduct =>
MethodsContainer.getMethod(p, n) match {
Expand All @@ -155,7 +166,7 @@ class SigmaTyper(val builder: SigmaBuilder,
.getOrElse(mkMethodCall(newObj, method, newArgs, subst))
} else {
val newSelect = mkSelect(newObj, n, Some(concrFunTpe)).withSrcCtx(sel.sourceContext)
mkApply(newSelect, newArgs.toArray[SValue])
mkApply(newSelect, newArgs)
}
case Some(method) =>
error(s"Don't know how to handle method $method in obj $p", sel.sourceContext)
Expand Down Expand Up @@ -221,6 +232,11 @@ class SigmaTyper(val builder: SigmaBuilder,
case (Ident(GetVarFunc.name | ExecuteFromVarFunc.name, _), Seq(id: Constant[SNumericType]@unchecked))
if id.tpe.isNumType =>
Seq(ByteConstant(SByte.downcast(id.value.asInstanceOf[AnyVal])).withSrcCtx(id.sourceContext))
case (Ident(SContextMethods.getVarFromInputMethod.name, _),
Seq(inputId: Constant[SNumericType]@unchecked, varId: Constant[SNumericType]@unchecked))
if inputId.tpe.isNumType && varId.tpe.isNumType =>
Seq(ShortConstant(SShort.downcast(inputId.value.asInstanceOf[AnyVal])).withSrcCtx(inputId.sourceContext),
ByteConstant(SByte.downcast(varId.value.asInstanceOf[AnyVal])).withSrcCtx(varId.sourceContext))
case _ => typedArgs
}
val actualTypes = adaptedTypedArgs.map(_.tpe)
Expand Down Expand Up @@ -409,11 +425,6 @@ class SigmaTyper(val builder: SigmaBuilder,
error(s"Invalid application of type arguments $app: function $input doesn't have type parameters", input.sourceContext)
}

// case app @ ApplyTypes(in, targs) =>
// val newIn = assignType(env, in)
// ApplyTypes(newIn, targs)
// error(s"Invalid application of type arguments $app: expression doesn't have type parameters")

case If(c, t, e) =>
val c1 = assignType(env, c).asValue[SBoolean.type]
val t1 = assignType(env, t)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4533,6 +4533,7 @@ class LanguageSpecificationV5 extends LanguageSpecificationBase { suite =>
.append(Coll[AnyValue](
CAnyValue(Helpers.decodeBytes("00")),
CAnyValue(true))),
spendingTransaction = null,
activatedScriptVersion = activatedVersionInTests,
currentErgoTreeVersion = ergoTreeVersionInTests
)
Expand Down
Loading
Loading