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] Box.getReg implementation #1015

Merged
merged 2 commits into from
Sep 3, 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
45 changes: 32 additions & 13 deletions data/shared/src/main/scala/sigma/ast/methods.scala
Original file line number Diff line number Diff line change
Expand Up @@ -309,13 +309,6 @@ case object SBigIntMethods extends SNumericTypeMethods {
/** Type for which this container defines methods. */
override def ownerType: SMonoType = SBigInt

final val ToNBitsCostInfo = OperationCostInfo(
FixedCost(JitCost(5)), NamedDesc("NBitsMethodCall"))

//id = 8 to make it after toBits
val ToNBits = SMethod(this, "nbits", SFunc(this.ownerType, SLong), 8, ToNBitsCostInfo.costKind)
.withInfo(ModQ, "Encode this big integer value as NBits")

/** The following `modQ` methods are not fully implemented in v4.x and this descriptors.
* This descritors are remain here in the code and are waiting for full implementation
* is upcoming soft-forks at which point the cost parameters should be calculated and
Expand All @@ -333,7 +326,7 @@ case object SBigIntMethods extends SNumericTypeMethods {

protected override def getMethods(): Seq[SMethod] = {
if (VersionContext.current.isV6SoftForkActivated) {
super.getMethods() ++ Seq(ToNBits)
super.getMethods()
// ModQMethod,
// PlusModQMethod,
// MinusModQMethod,
Expand Down Expand Up @@ -1063,7 +1056,7 @@ case object SBoxMethods extends MonoTypeMethods {
| identifier followed by box index in the transaction outputs.
""".stripMargin ) // see ExtractCreationInfo

lazy val getRegMethod = SMethod(this, "getReg",
lazy val getRegMethodV5 = SMethod(this, "getReg",
SFunc(Array(SBox, SInt), SOption(tT), Array(paramT)), 7, ExtractRegisterAs.costKind)
.withInfo(ExtractRegisterAs,
""" Extracts register by id and type.
Expand All @@ -1072,23 +1065,49 @@ case object SBoxMethods extends MonoTypeMethods {
""".stripMargin,
ArgInfo("regId", "zero-based identifier of the register."))

lazy val getRegMethodV6 = SMethod(this, "getReg",
SFunc(Array(SBox, SInt), SOption(tT), Array(paramT)), 7, ExtractRegisterAs.costKind, Seq(tT))
.withIRInfo(MethodCallIrBuilder,
javaMethodOf[Box, Int, RType[_]]("getReg"),
{ mtype => Array(mtype.tRange.asOption[SType].elemType) })
.withInfo(MethodCall, """ Extracts register by id and type.
| Type param \lst{T} expected type of the register.
| Returns \lst{Some(value)} if the register is defined and has given type and \lst{None} otherwise
""".stripMargin,
ArgInfo("regId", "zero-based identifier of the register."))

lazy val tokensMethod = SMethod(
this, "tokens", SFunc(SBox, ErgoBox.STokensRegType), 8, FixedCost(JitCost(15)))
.withIRInfo(MethodCallIrBuilder)
.withInfo(PropertyCall, "Secondary tokens")


// should be lazy to solve recursive initialization
protected override def getMethods() = super.getMethods() ++ Array(
lazy val commonBoxMethods = super.getMethods() ++ Array(
ValueMethod, // see ExtractAmount
PropositionBytesMethod, // see ExtractScriptBytes
BytesMethod, // see ExtractBytes
BytesWithoutRefMethod, // see ExtractBytesWithNoRef
IdMethod, // see ExtractId
creationInfoMethod,
getRegMethod,
tokensMethod
) ++ registers(8)

lazy val v5Methods = commonBoxMethods ++ Array(
getRegMethodV5
)

lazy val v6Methods = commonBoxMethods ++ Array(
getRegMethodV6
)

// should be lazy to solve recursive initialization
protected override def getMethods() = {
if (VersionContext.current.isV6SoftForkActivated) {
v6Methods
} else {
v5Methods
}
}

}

/** Type descriptor of `AvlTree` type of ErgoTree. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import org.scalacheck.Arbitrary._
import org.scalacheck.Gen.{choose, frequency}
import org.scalacheck.util.Buildable
import org.scalacheck.{Arbitrary, Gen}
import sigma.data._
import scorex.crypto.authds.{ADDigest, ADKey}
import scorex.util.encode.{Base58, Base64}
import scorex.util.{ModifierId, bytesToId}
Expand All @@ -27,6 +26,7 @@ import sigma.util.Extensions.EcpOps
import sigma.validation.{ChangedRule, DisabledRule, EnabledRule, ReplacedRule, RuleStatus}
import sigma.validation.ValidationRules.FirstRuleId
import ErgoTree.ZeroHeader
import sigma.data.{AvlTreeData, AvlTreeFlags, CAND, CBox, COR, CTHRESHOLD, Digest32Coll, ProveDHTuple, ProveDlog, RType, SigmaBoolean}
import sigma.eval.Extensions.{EvalIterableOps, SigmaBooleanOps}
import sigma.eval.SigmaDsl
import sigma.interpreter.{ContextExtension, ProverResult}
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.ast.SType.tT
import sigma.ast.TypeCodes.LastConstantCode
import sigma.ast.Value.Typed
import sigma.ast.syntax.{SValue, ValueOps}
Expand Down Expand Up @@ -928,7 +929,7 @@ trait GraphBuilding extends Base with DefRewriting { IR: IRContext =>
sigmaDslBuilder.decodePoint(bytes)

// fallback rule for MethodCall, should be the last case in the list
case sigma.ast.MethodCall(obj, method, args, _) =>
case sigma.ast.MethodCall(obj, method, args, typeSubst) =>
val objV = eval(obj)
val argsV = args.map(eval)
(objV, method.objType) match {
Expand Down Expand Up @@ -1017,6 +1018,10 @@ trait GraphBuilding extends Base with DefRewriting { IR: IRContext =>
case (box: Ref[Box]@unchecked, SBoxMethods) => method.name match {
case SBoxMethods.tokensMethod.name =>
box.tokens
case SBoxMethods.getRegMethodV6.name if VersionContext.current.isV6SoftForkActivated =>
val c1 = asRep[Int](argsV(0))
val c2 = stypeToElem(typeSubst.apply(tT))
box.getReg(c1)(c2)
case _ => throwError
}
case (ctx: Ref[Context]@unchecked, SContextMethods) => method.name match {
Expand Down
2 changes: 1 addition & 1 deletion sc/shared/src/main/scala/sigma/compiler/ir/IRContext.scala
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ trait IRContext
override def invokeUnlifted(e: Elem[_], mc: MethodCall, dataEnv: DataEnv): Any = e match {
case _: CollElem[_,_] => mc match {
case CollMethods.map(_, f) =>
val newMC = mc.copy(args = mc.args :+ f.elem.eRange)(mc.resultType, mc.isAdapterCall)
val newMC = mc.copy(args = mc.args :+ f.elem.eRange)(mc.resultType, mc.isAdapterCall, mc.typeSubst)
super.invokeUnlifted(e, newMC, dataEnv)
case _ =>
super.invokeUnlifted(e, mc, dataEnv)
Expand Down
16 changes: 12 additions & 4 deletions sc/shared/src/main/scala/sigma/compiler/ir/MethodCalls.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package sigma.compiler.ir

import debox.{cfor, Buffer => DBuffer}
import sigma.ast.{SType, STypeVar}
import sigma.compiler.DelayInvokeException
import sigma.reflection.RMethod
import sigma.util.CollectionUtil.TraversableOps
Expand All @@ -26,7 +27,9 @@ trait MethodCalls extends Base { self: IRContext =>
* given `method`.
*/
case class MethodCall private[MethodCalls](receiver: Sym, method: RMethod, args: Seq[AnyRef], neverInvoke: Boolean)
(val resultType: Elem[Any], val isAdapterCall: Boolean = false) extends Def[Any] {
(val resultType: Elem[Any],
val isAdapterCall: Boolean = false,
val typeSubst: Map[STypeVar, SType]) extends Def[Any] {

override def mirror(t: Transformer): Ref[Any] = {
val len = args.length
Expand Down Expand Up @@ -99,9 +102,14 @@ trait MethodCalls extends Base { self: IRContext =>
}

/** Creates new MethodCall node and returns its node ref. */
def mkMethodCall(receiver: Sym, method: RMethod, args: Seq[AnyRef],
neverInvoke: Boolean, isAdapterCall: Boolean, resultElem: Elem[_]): Sym = {
reifyObject(MethodCall(receiver, method, args, neverInvoke)(asElem[Any](resultElem), isAdapterCall))
def mkMethodCall(receiver: Sym,
method: RMethod,
args: Seq[AnyRef],
neverInvoke: Boolean,
isAdapterCall: Boolean,
resultElem: Elem[_],
typeSubst: Map[STypeVar, SType] = Map.empty): Sym = {
reifyObject(MethodCall(receiver, method, args, neverInvoke)(asElem[Any](resultElem), isAdapterCall, typeSubst))
}

@tailrec
Expand Down
16 changes: 7 additions & 9 deletions sc/shared/src/main/scala/sigma/compiler/ir/TreeBuilding.scala
Original file line number Diff line number Diff line change
Expand Up @@ -290,13 +290,10 @@ trait TreeBuilding extends Base { IR: IRContext =>
mkExtractAmount(box.asBox)
case BoxM.propositionBytes(In(box)) =>
mkExtractScriptBytes(box.asBox)
case BoxM.getReg(In(box), regId, _) =>
case BoxM.getReg(In(box), regId, _) if regId.isConst =>
val tpe = elemToSType(s.elem).asOption
if (regId.isConst)
mkExtractRegisterAs(box.asBox, ErgoBox.allRegisters(valueFromRep(regId)), tpe)
else
error(s"Non constant expressions (${regId.node}) are not supported in getReg")
case BoxM.creationInfo(In(box)) =>
mkExtractRegisterAs(box.asBox, ErgoBox.allRegisters(valueFromRep(regId)), tpe)
case BoxM.creationInfo(In(box)) =>
mkExtractCreationInfo(box.asBox)
case BoxM.id(In(box)) =>
mkExtractId(box.asBox)
Expand Down Expand Up @@ -399,13 +396,14 @@ trait TreeBuilding extends Base { IR: IRContext =>
mkMultiplyGroup(obj.asGroupElement, arg.asGroupElement)

// Fallback MethodCall rule: should be the last in this list of cases
case Def(MethodCall(objSym, m, argSyms, _)) =>
case Def(mc @ MethodCall(objSym, m, argSyms, _)) =>
val obj = recurse[SType](objSym)
val args = argSyms.collect { case argSym: Sym => recurse[SType](argSym) }
MethodsContainer.getMethod(obj.tpe, m.getName) match {
case Some(method) =>
val specMethod = method.specializeFor(obj.tpe, args.map(_.tpe))
builder.mkMethodCall(obj, specMethod, args.toIndexedSeq, Map())
val typeSubst = mc.typeSubst
val specMethod = method.specializeFor(obj.tpe, args.map(_.tpe)).withConcreteTypes(typeSubst)
builder.mkMethodCall(obj, specMethod, args.toIndexedSeq, typeSubst)
case None =>
error(s"Cannot find method ${m.getName} in object $obj")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import sigma.compiler.ir.wrappers.sigma.impl.SigmaDslDefs
import scala.collection.compat.immutable.ArraySeq

package impl {
import sigma.Evaluation
import sigma.ast.SType.tT
import sigma.compiler.ir.meta.ModuleInfo
import sigma.compiler.ir.wrappers.sigma.SigmaDsl
import sigma.compiler.ir.{Base, GraphIRReflection, IRContext}
Expand Down Expand Up @@ -620,10 +622,11 @@ object Box extends EntityObject("Box") {
}

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

override def tokens: Ref[Coll[(Coll[Byte], Long)]] = {
Expand Down Expand Up @@ -695,10 +698,11 @@ object Box extends EntityObject("Box") {
}

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

def tokens: Ref[Coll[(Coll[Byte], Long)]] = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ class SigmaTyper(val builder: SigmaBuilder,
case Apply(ApplyTypes(sel @ Select(obj, n, _), Seq(rangeTpe)), args) =>
val newObj = assignType(env, obj)
val newArgs = args.map(assignType(env, _))
obj.tpe match {
newObj.tpe match {
aslesarenko marked this conversation as resolved.
Show resolved Hide resolved
case p: SProduct =>
MethodsContainer.getMethod(p, n) match {
case Some(method: SMethod) =>
Expand All @@ -160,10 +160,10 @@ class SigmaTyper(val builder: SigmaBuilder,
case Some(method) =>
error(s"Don't know how to handle method $method in obj $p", sel.sourceContext)
case None =>
throw new MethodNotFound(s"Cannot find method '$n' in in the object $obj of Product type $p", obj.sourceContext.toOption)
throw new MethodNotFound(s"Cannot find method '$n' in in the object $newObj of Product type $p", newObj.sourceContext.toOption)
}
case _ =>
error(s"Cannot get field '$n' in in the object $obj of non-product type ${obj.tpe}", sel.sourceContext)
error(s"Cannot get field '$n' in in the object $newObj of non-product type ${newObj.tpe}", sel.sourceContext)
}

case app @ Apply(sel @ Select(obj, n, _), args) =>
Expand Down
25 changes: 21 additions & 4 deletions sc/shared/src/test/scala/sigma/LanguageSpecificationV6.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@ package sigma

import org.ergoplatform.ErgoHeader
import scorex.util.encode.Base16
import org.ergoplatform.ErgoBox
import org.ergoplatform.ErgoBox.Token
import scorex.util.ModifierId
import sigma.ast.ErgoTree.ZeroHeader
import sigma.ast.SCollection.SByteArray
import sigma.ast.syntax.TrueSigmaProp
import sigma.ast._
import sigma.data.{CBigInt, CHeader, ExactNumeric}
import sigma.data.{CBigInt, CHeader, CBox, ExactNumeric}
import sigma.eval.{CostDetails, SigmaDsl, TracedCost}
import sigma.util.Extensions.{BooleanOps, ByteOps, IntOps, LongOps}
import sigmastate.exceptions.MethodNotFound
Expand Down Expand Up @@ -267,9 +270,13 @@ class LanguageSpecificationV6 extends LanguageSpecificationBase { suite =>
}

property("Box properties equivalence (new features)") {
// TODO v6.0: related to https://github.com/ScorexFoundation/sigmastate-interpreter/issues/416
val getReg = newFeature((x: Box) => x.getReg[Int](1).get,
"{ (x: Box) => x.getReg[Int](1).get }",
// related to https://github.com/ScorexFoundation/sigmastate-interpreter/issues/416
def getReg = newFeature((x: Box) => x.getReg[Long](0).get,
"{ (x: Box) => x.getReg[Long](0).get }",
FuncValue(
Array((1, SBox)),
OptionGet(ExtractRegisterAs(ValUse(1, SBox), ErgoBox.R0, SOption(SLong)))
),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea behind getReg was to have "computable" index, not just constant.
For example the index can come from Context variable and thus the concrete register will not be fixed in the ErgoTree. This will allow code like:
val x = box.getReg[Int](getVar[Int](varId).get).get

So I suggest to add additional test with variable register passed as function argument and have a series of cases in verifyCases.
"{ (x: (Box, Int)) => x._1.getReg[Long](x._2).get }"

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

{ (x: (Box, Int)) => x._1.getReg[Long](x._2).get } is way different from box.getReg[Int](getVar[Int](varId).get).get ,
so I've made a test in BasicOpsSpecification which is checking exactly context variable usage:

{
   val x = SELF.getReg[Long](getVar[Int](1).get).get
   x == SELF.value
}

and so the new test can also be used for reference for people asked for computable index.

sinceVersion = VersionContext.V6SoftForkVersion)

if (activatedVersionInTests < VersionContext.V6SoftForkVersion) {
Expand All @@ -279,6 +286,16 @@ class LanguageSpecificationV6 extends LanguageSpecificationBase { suite =>
forAll { box: Box =>
Seq(getReg).foreach(_.checkEquality(box))
}
} else {
val value = 10L
val box = CBox(new ErgoBox(value, TrueTree, Colls.emptyColl[Token], Map.empty,
ModifierId @@ Base16.encode(Array.fill(32)(0)), 0, 0))
verifyCases(
Seq(
box -> new Expected(ExpectedResult(Success(value), None))
),
getReg
)
}
}

Expand Down
13 changes: 8 additions & 5 deletions sc/shared/src/test/scala/sigmastate/ErgoTreeSpecification.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,14 @@ import sigma.compiler.CompilerSettings
import sigma.eval.EvalSettings
import sigma.exceptions.{CostLimitException, InterpreterException}
import sigma.serialization.ErgoTreeSerializer.DefaultSerializer
import sigmastate.Plus
import sigmastate.{CrossVersionProps, Plus}
import sigmastate.utils.Helpers.TryOps


/** Regression tests with ErgoTree related test vectors.
* This test vectors verify various constants which are consensus critical and should not change.
*/
class ErgoTreeSpecification extends SigmaDslTesting with ContractsTestkit {
class ErgoTreeSpecification extends SigmaDslTesting with ContractsTestkit with CrossVersionProps {

property("Value.sourceContext") {
val srcCtx = SourceContext.fromParserIndex(0, "")
Expand Down Expand Up @@ -316,7 +316,7 @@ class ErgoTreeSpecification extends SigmaDslTesting with ContractsTestkit {
// NOTE, the type code constants are checked above
// The methodId codes as checked here, they MUST be PRESERVED.
// The following table should be made dependent on HF activation
val methods = Table(
def methods = Table(
("typeId", "methods", "CanHaveMethods"),
(SBoolean.typeId, Seq.empty[MInfo], true),
(SByte.typeId, Seq.empty[MInfo], false),
Expand Down Expand Up @@ -367,9 +367,12 @@ class ErgoTreeSpecification extends SigmaDslTesting with ContractsTestkit {
MInfo(4, BytesWithoutRefMethod),
MInfo(5, IdMethod),
MInfo(6, creationInfoMethod),
MInfo(7, getRegMethod),
MInfo(8, tokensMethod)
) ++ registers(idOfs = 8)
) ++ (if (VersionContext.current.isV6SoftForkActivated) {
Seq(MInfo(7, getRegMethodV6))
} else {
Seq(MInfo(7, getRegMethodV5))
}) ++ registers(idOfs = 8)
.zipWithIndex
.map { case (m,i) => MInfo((8 + i + 1).toByte, m) }, true)
},
Expand Down
Loading
Loading