From f24e9ad44614ed9c91949c7594eb68bb19f5569d Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Mon, 30 Sep 2024 16:04:25 +0300 Subject: [PATCH] predefined fn, LangSpec notes, polishing PR --- .../src/main/scala/sigma/ast/SMethod.scala | 7 +++++ .../main/scala/sigma/ast/SigmaPredef.scala | 15 ++++++++- .../src/main/scala/sigma/ast/methods.scala | 8 +++-- .../sigma/interpreter/ContextExtension.scala | 4 +++ docs/LangSpec.md | 14 ++++++++- .../sigma/compiler/phases/SigmaTyper.scala | 1 + .../utxo/BasicOpsSpecification.scala | 31 ++++++++++++++++--- 7 files changed, 70 insertions(+), 10 deletions(-) diff --git a/data/shared/src/main/scala/sigma/ast/SMethod.scala b/data/shared/src/main/scala/sigma/ast/SMethod.scala index 5c3e5faf40..e5481cee5b 100644 --- a/data/shared/src/main/scala/sigma/ast/SMethod.scala +++ b/data/shared/src/main/scala/sigma/ast/SMethod.scala @@ -302,6 +302,13 @@ 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 = diff --git a/data/shared/src/main/scala/sigma/ast/SigmaPredef.scala b/data/shared/src/main/scala/sigma/ast/SigmaPredef.scala index 8b89851938..7a28ee6dd9 100644 --- a/data/shared/src/main/scala/sigma/ast/SigmaPredef.scala +++ b/data/shared/src/main/scala/sigma/ast/SigmaPredef.scala @@ -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( @@ -448,7 +460,8 @@ object SigmaPredef { SubstConstantsFunc, ExecuteFromVarFunc, ExecuteFromSelfRegFunc, - SerializeFunc + SerializeFunc, + GetVarFromInputFunc ).map(f => f.name -> f).toMap def comparisonOp(symbolName: String, opDesc: ValueCompanion, desc: String, args: Seq[ArgInfo]) = { diff --git a/data/shared/src/main/scala/sigma/ast/methods.scala b/data/shared/src/main/scala/sigma/ast/methods.scala index ae05152630..33ffa9c9b2 100644 --- a/data/shared/src/main/scala/sigma/ast/methods.scala +++ b/data/shared/src/main/scala/sigma/ast/methods.scala @@ -1586,16 +1586,18 @@ case object SContextMethods extends MonoTypeMethods { MethodCallIrBuilder, javaMethodOf[Context, Byte, RType[_]]("getVar"), { mtype => Array(mtype.tRange.asOption[SType].elemType) }) - .withInfo(MethodCall, "") // todo: desc + .withInfo(MethodCall, "Get context variable with given \\lst{varId} and type.") - // todo: costing, desc 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, "Multiply this number with \\lst{other} by module Q.", ArgInfo("other", "Number to multiply with this.")) + .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, diff --git a/data/shared/src/main/scala/sigma/interpreter/ContextExtension.scala b/data/shared/src/main/scala/sigma/interpreter/ContextExtension.scala index fd269c177c..f03d076d43 100644 --- a/data/shared/src/main/scala/sigma/interpreter/ContextExtension.scala +++ b/data/shared/src/main/scala/sigma/interpreter/ContextExtension.scala @@ -20,6 +20,10 @@ case class ContextExtension(values: scala.collection.Map[Byte, EvaluatedValue[_ 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) } diff --git a/docs/LangSpec.md b/docs/LangSpec.md index 5b72a8659d..7ec7dd7729 100644 --- a/docs/LangSpec.md +++ b/docs/LangSpec.md @@ -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. @@ -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 id, 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. + *
+  *   {
+  *       val idx = CONTEXT.selfBoxIndex
+  *       sigmaProp(CONTEXT.getVarFromInput[Int](idx.toShort, 1.toByte).get == 5)
+  *   }
+  * 
+ */ +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. diff --git a/sc/shared/src/main/scala/sigma/compiler/phases/SigmaTyper.scala b/sc/shared/src/main/scala/sigma/compiler/phases/SigmaTyper.scala index cc771cbac9..833bd413b9 100644 --- a/sc/shared/src/main/scala/sigma/compiler/phases/SigmaTyper.scala +++ b/sc/shared/src/main/scala/sigma/compiler/phases/SigmaTyper.scala @@ -134,6 +134,7 @@ 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[_]] && diff --git a/sc/shared/src/test/scala/sigmastate/utxo/BasicOpsSpecification.scala b/sc/shared/src/test/scala/sigmastate/utxo/BasicOpsSpecification.scala index df4e85de3b..b43fcdd90e 100644 --- a/sc/shared/src/test/scala/sigmastate/utxo/BasicOpsSpecification.scala +++ b/sc/shared/src/test/scala/sigmastate/utxo/BasicOpsSpecification.scala @@ -167,7 +167,7 @@ class BasicOpsSpecification extends CompilerTestingCommons 1.toByte -> IntConstant(5) ).toSeq test("R1", env, customExt, - "{ sigmaProp(CONTEXT.getVarFromInput[Int](0, 1).get == 5) }", + "{ sigmaProp(getVarFromInput[Int](0, 1).get == 5) }", null ) } @@ -179,19 +179,40 @@ class BasicOpsSpecification extends CompilerTestingCommons } } - property("getVarFromInput - invalid input") { + property("getVarFromInput - self index") { def getVarTest(): Assertion = { val customExt = Map( 1.toByte -> IntConstant(5) ).toSeq test("R1", env, customExt, - "{ sigmaProp(CONTEXT.getVarFromInput[Int](1, 1).get == 5) }", + """{ + | val idx = CONTEXT.selfBoxIndex + | sigmaProp(CONTEXT.getVarFromInput[Int](idx.toShort, 1.toByte).get == 5) + | }""".stripMargin, null ) } if (VersionContext.current.isV6SoftForkActivated) { + getVarTest() + } else { an[Exception] should be thrownBy getVarTest() + } + } + + property("getVarFromInput - invalid input") { + def getVarTest(): Assertion = { + val customExt = Map( + 1.toByte -> IntConstant(5) + ).toSeq + test("R1", env, customExt, + "{ sigmaProp(CONTEXT.getVarFromInput[Int](1, 1).isDefined == false) }", + null + ) + } + + if (VersionContext.current.isV6SoftForkActivated) { + getVarTest() } else { an[Exception] should be thrownBy getVarTest() } @@ -593,13 +614,13 @@ class BasicOpsSpecification extends CompilerTestingCommons 1.toByte -> IntConstant(5) ).toSeq test("R1", env, customExt, - "{ sigmaProp(CONTEXT.getVarFromInput[Int](0, 2).get == 5) }", + "{ sigmaProp(CONTEXT.getVarFromInput[Int](0, 2).isDefined == false) }", null ) } if (VersionContext.current.isV6SoftForkActivated) { - an[Exception] should be thrownBy getVarTest() + getVarTest() } else { an[Exception] should be thrownBy getVarTest() }