Skip to content

Commit

Permalink
Merge pull request #1015 from ScorexFoundation/i416
Browse files Browse the repository at this point in the history
[6.0] Box.getReg implementation
  • Loading branch information
kushti authored Sep 3, 2024
2 parents 2cd57e1 + 05c9e67 commit 8e46b69
Show file tree
Hide file tree
Showing 11 changed files with 140 additions and 43 deletions.
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 {
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)))
),
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

0 comments on commit 8e46b69

Please sign in to comment.