From 2ad11dfcbbf1dd25df1705ba565289516e222466 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Thu, 9 May 2024 20:13:58 +0100 Subject: [PATCH 1/9] i966-template-compiler: use default values of parameters in compiler env --- .../sigmastate/lang/ContractParser.scala | 6 +- .../sigmastate/lang/ContractParserSpec.scala | 4 +- .../lang/SigmaTemplateCompiler.scala | 12 ++- .../lang/SigmaTemplateCompilerTest.scala | 83 +++++++++++++++++-- 4 files changed, 89 insertions(+), 16 deletions(-) diff --git a/parsers/shared/src/main/scala/sigmastate/lang/ContractParser.scala b/parsers/shared/src/main/scala/sigmastate/lang/ContractParser.scala index 87dd20ab0c..69136c5a91 100644 --- a/parsers/shared/src/main/scala/sigmastate/lang/ContractParser.scala +++ b/parsers/shared/src/main/scala/sigmastate/lang/ContractParser.scala @@ -3,7 +3,7 @@ package sigmastate.lang import fastparse._ import fastparse.NoWhitespace._ import SigmaParser._ -import sigma.ast.SType +import sigma.ast.{Constant, SType} import sigma.ast.syntax.SValue import sigmastate.lang.parsers.Basic @@ -105,7 +105,7 @@ object ContractDoc { * @param tpe The type of the parameter. * @param defaultValue The default value assigned to the parameter, if it exists. */ -case class ContractParam(name: String, tpe: SType, defaultValue: Option[SType#WrappedType]) +case class ContractParam(name: String, tpe: SType, defaultValue: Option[Constant[SType]]) /** * Represents the signature of a contract. @@ -187,7 +187,7 @@ object ContractParser { def annotation[_: P] = P("@contract") - def paramDefault[_: P] = P(WL.? ~ `=` ~ WL.? ~ ExprLiteral).map(s => s.asWrappedType) + def paramDefault[_: P] = P(WL.? ~ `=` ~ WL.? ~ ExprLiteral) def param[_: P] = P(WL.? ~ Id.! ~ ":" ~ Type ~ paramDefault.?).map(s => ContractParam(s._1, s._2, s._3)) diff --git a/parsers/shared/src/test/scala/sigmastate/lang/ContractParserSpec.scala b/parsers/shared/src/test/scala/sigmastate/lang/ContractParserSpec.scala index 9a412b7100..a4c535ac8f 100644 --- a/parsers/shared/src/test/scala/sigmastate/lang/ContractParserSpec.scala +++ b/parsers/shared/src/test/scala/sigmastate/lang/ContractParserSpec.scala @@ -34,8 +34,8 @@ class ContractParserSpec extends AnyPropSpec with ScalaCheckPropertyChecks with parsed.name shouldBe "contractName" parsed.params should contain theSameElementsInOrderAs Seq( - ContractParam("p1", SInt, Some(IntConstant(5).asWrappedType)), - ContractParam("p2", SString, Some(StringConstant("default string").asWrappedType)), + ContractParam("p1", SInt, Some(IntConstant(5))), + ContractParam("p2", SString, Some(StringConstant("default string"))), ContractParam("param3", SLong, None) ) } diff --git a/sc/shared/src/main/scala/sigmastate/lang/SigmaTemplateCompiler.scala b/sc/shared/src/main/scala/sigmastate/lang/SigmaTemplateCompiler.scala index ec94bdfd3a..c751962826 100644 --- a/sc/shared/src/main/scala/sigmastate/lang/SigmaTemplateCompiler.scala +++ b/sc/shared/src/main/scala/sigmastate/lang/SigmaTemplateCompiler.scala @@ -2,9 +2,9 @@ package sigmastate.lang import fastparse.Parsed import org.ergoplatform.sdk.ContractTemplate -import sigmastate.eval.CompiletimeIRContext +import sigmastate.eval.{CompiletimeIRContext, msgCostLimitError} import org.ergoplatform.sdk.Parameter -import sigma.ast.SourceContext +import sigma.ast.{Constant, SType, SourceContext} import sigma.ast.syntax.SValue import sigmastate.interpreter.Interpreter.ScriptEnv import sigmastate.lang.parsers.ParserException @@ -23,7 +23,11 @@ class SigmaTemplateCompiler(networkPrefix: Byte) { ContractParser.parse(source) match { case Parsed.Success(template, _) => { implicit val ir = new CompiletimeIRContext - val result = sigmaCompiler.compileParsed(env, template.body) + val mergedEnv = template.signature.params + .collect { case ContractParam(name, tpe, Some(defaultValue)) => + name -> defaultValue + }.toMap ++ env + val result = sigmaCompiler.compileParsed(mergedEnv, template.body) assemble(template, result.buildTree) } case f: Parsed.Failure => @@ -46,7 +50,7 @@ class SigmaTemplateCompiler(networkPrefix: Byte) { name = parsed.signature.name, description = parsed.docs.description, constTypes = constTypes.toIndexedSeq, - constValues = constValues, + constValues = constValues.map(_.map(_.map(_.value))), parameters = contractParams.toIndexedSeq, expressionTree = expr.toSigmaProp ) diff --git a/sc/shared/src/test/scala/sigmastate/lang/SigmaTemplateCompilerTest.scala b/sc/shared/src/test/scala/sigmastate/lang/SigmaTemplateCompilerTest.scala index df8f644172..168815452d 100644 --- a/sc/shared/src/test/scala/sigmastate/lang/SigmaTemplateCompilerTest.scala +++ b/sc/shared/src/test/scala/sigmastate/lang/SigmaTemplateCompilerTest.scala @@ -2,13 +2,14 @@ package sigmastate.lang import org.ergoplatform.ErgoAddressEncoder import org.ergoplatform.sdk.Parameter -import org.scalatest.matchers.should.Matchers import org.scalatest.propspec.AnyPropSpec import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks -import sigma.ast.{IntConstant, SInt, SLong, SString, StringConstant} +import sigma.ast.{SBoolean, SInt, SLong, SString} +import sigma.exceptions.TyperException import sigmastate.eval.CompiletimeIRContext +import sigmastate.interpreter.Interpreter.ScriptEnv -class SigmaTemplateCompilerTest extends AnyPropSpec with ScalaCheckPropertyChecks with Matchers { +class SigmaTemplateCompilerTest extends AnyPropSpec with ScalaCheckPropertyChecks with LangTests { property("compiles full contract template") { val source = """/** This is my contracts description. @@ -36,8 +37,8 @@ class SigmaTemplateCompilerTest extends AnyPropSpec with ScalaCheckPropertyCheck ) template.constTypes should contain theSameElementsInOrderAs Seq(SInt, SString, SLong) template.constValues.get should contain theSameElementsInOrderAs IndexedSeq( - Some(IntConstant(5).asWrappedType), - Some(StringConstant("default string").asWrappedType), + Some(5), + Some("default string"), None ) @@ -73,8 +74,8 @@ class SigmaTemplateCompilerTest extends AnyPropSpec with ScalaCheckPropertyCheck ) template.constTypes should contain theSameElementsInOrderAs Seq(SInt, SString) template.constValues.get should contain theSameElementsInOrderAs IndexedSeq( - Some(IntConstant(5).asWrappedType), - Some(StringConstant("default string").asWrappedType) + Some(5), + Some("default string") ) val sigmaCompiler = new SigmaCompiler(ErgoAddressEncoder.MainnetNetworkPrefix) @@ -83,6 +84,74 @@ class SigmaTemplateCompilerTest extends AnyPropSpec with ScalaCheckPropertyCheck template.expressionTree shouldBe result.buildTree } + + property("uses default value from parameter definition") { + val source = + """/**/ + |@contract def contractName(p: Boolean = true) = sigmaProp(p) + |""".stripMargin + val compiler = SigmaTemplateCompiler(ErgoAddressEncoder.MainnetNetworkPrefix) + val template = compiler.compile(Map.empty, source) + + template.parameters should contain theSameElementsInOrderAs IndexedSeq( + Parameter("p", "", 0), + ) + template.constTypes should contain theSameElementsInOrderAs Seq(SBoolean) + template.constValues.get should contain theSameElementsInOrderAs IndexedSeq( + Some(true), + ) + + val sigmaCompiler = new SigmaCompiler(ErgoAddressEncoder.MainnetNetworkPrefix) + implicit val ir = new CompiletimeIRContext + val result = sigmaCompiler.compile(Map("p" -> true), "sigmaProp(p)") + + template.expressionTree shouldBe result.buildTree + } + + property("uses given environment when provided (overriding default value)") { + val explicitEnv = Map("low" -> 10, "high" -> 100) + val source = + """/**/ + |@contract def contractName(low: Int = 0, high: Int) = sigmaProp(low < HEIGHT && HEIGHT < high) + |""".stripMargin + val compiler = SigmaTemplateCompiler(ErgoAddressEncoder.MainnetNetworkPrefix) + val template = compiler.compile(explicitEnv, source) + + template.parameters should contain theSameElementsInOrderAs IndexedSeq( + Parameter("low", "", 0), + Parameter("high", "", 1), + ) + template.constTypes should contain theSameElementsInOrderAs Seq(SInt, SInt) + // check parsed default values + template.constValues.get should contain theSameElementsInOrderAs IndexedSeq( + Some(0), + None, + ) + + val sigmaCompiler = new SigmaCompiler(ErgoAddressEncoder.MainnetNetworkPrefix) + implicit val ir = new CompiletimeIRContext + val result = sigmaCompiler.compile( + env = explicitEnv, + code = "sigmaProp(low < HEIGHT && HEIGHT < high)" + ) + + template.expressionTree shouldBe result.buildTree + } + + property("fails when constant value in not provided") { + // NOTE: parameter `high` without default value */ + val source = + """/**/ + |@contract def contractName(low: Int = 0, high: Int) = sigmaProp(low < HEIGHT && HEIGHT < high) + |""".stripMargin + val compiler = SigmaTemplateCompiler(ErgoAddressEncoder.MainnetNetworkPrefix) + val env: ScriptEnv = Map.empty // no value for "high" + assertExceptionThrown( + compiler.compile(env, source), + exceptionLike[TyperException]("Cannot assign type for variable 'high' because it is not found in env") + ) + } + } From 98fd5b7d0bf93fc87ebc4a8e98d7e31eefa8c678 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Thu, 9 May 2024 20:20:30 +0100 Subject: [PATCH 2/9] i966-template-compiler: fix type of ContractParam field --- .../src/main/scala/sigmastate/lang/ContractParser.scala | 4 ++-- .../src/test/scala/sigmastate/lang/ContractParserSpec.scala | 5 +++-- .../main/scala/sigmastate/lang/SigmaTemplateCompiler.scala | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/parsers/shared/src/main/scala/sigmastate/lang/ContractParser.scala b/parsers/shared/src/main/scala/sigmastate/lang/ContractParser.scala index 69136c5a91..a192a789b9 100644 --- a/parsers/shared/src/main/scala/sigmastate/lang/ContractParser.scala +++ b/parsers/shared/src/main/scala/sigmastate/lang/ContractParser.scala @@ -105,7 +105,7 @@ object ContractDoc { * @param tpe The type of the parameter. * @param defaultValue The default value assigned to the parameter, if it exists. */ -case class ContractParam(name: String, tpe: SType, defaultValue: Option[Constant[SType]]) +case class ContractParam(name: String, tpe: SType, defaultValue: Option[SType#WrappedType]) /** * Represents the signature of a contract. @@ -189,7 +189,7 @@ object ContractParser { def paramDefault[_: P] = P(WL.? ~ `=` ~ WL.? ~ ExprLiteral) - def param[_: P] = P(WL.? ~ Id.! ~ ":" ~ Type ~ paramDefault.?).map(s => ContractParam(s._1, s._2, s._3)) + def param[_: P] = P(WL.? ~ Id.! ~ ":" ~ Type ~ paramDefault.?).map(s => ContractParam(s._1, s._2, s._3.map(_.value))) def params[_: P] = P("(" ~ param.rep(1, ",").? ~ ")") } diff --git a/parsers/shared/src/test/scala/sigmastate/lang/ContractParserSpec.scala b/parsers/shared/src/test/scala/sigmastate/lang/ContractParserSpec.scala index a4c535ac8f..bce252d866 100644 --- a/parsers/shared/src/test/scala/sigmastate/lang/ContractParserSpec.scala +++ b/parsers/shared/src/test/scala/sigmastate/lang/ContractParserSpec.scala @@ -3,6 +3,7 @@ package sigmastate.lang import org.scalatest.matchers.should.Matchers import org.scalatest.propspec.AnyPropSpec import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks +import sigma.ast.SType.AnyOps import sigma.ast._ class ContractParserSpec extends AnyPropSpec with ScalaCheckPropertyChecks with Matchers { @@ -34,8 +35,8 @@ class ContractParserSpec extends AnyPropSpec with ScalaCheckPropertyChecks with parsed.name shouldBe "contractName" parsed.params should contain theSameElementsInOrderAs Seq( - ContractParam("p1", SInt, Some(IntConstant(5))), - ContractParam("p2", SString, Some(StringConstant("default string"))), + ContractParam("p1", SInt, Some(5.asWrappedType)), + ContractParam("p2", SString, Some("default string".asWrappedType)), ContractParam("param3", SLong, None) ) } diff --git a/sc/shared/src/main/scala/sigmastate/lang/SigmaTemplateCompiler.scala b/sc/shared/src/main/scala/sigmastate/lang/SigmaTemplateCompiler.scala index c751962826..b124684d50 100644 --- a/sc/shared/src/main/scala/sigmastate/lang/SigmaTemplateCompiler.scala +++ b/sc/shared/src/main/scala/sigmastate/lang/SigmaTemplateCompiler.scala @@ -50,7 +50,7 @@ class SigmaTemplateCompiler(networkPrefix: Byte) { name = parsed.signature.name, description = parsed.docs.description, constTypes = constTypes.toIndexedSeq, - constValues = constValues.map(_.map(_.map(_.value))), + constValues = constValues, parameters = contractParams.toIndexedSeq, expressionTree = expr.toSigmaProp ) From 232c25d56ccd2ed035bbe55a426a7a59d96445b7 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Thu, 9 May 2024 22:16:52 +0100 Subject: [PATCH 3/9] i966-template-compiler: fix for Scala 2.11 --- .../scala/sigmastate/lang/SigmaTemplateCompilerTest.scala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sc/shared/src/test/scala/sigmastate/lang/SigmaTemplateCompilerTest.scala b/sc/shared/src/test/scala/sigmastate/lang/SigmaTemplateCompilerTest.scala index 168815452d..82a080cf1a 100644 --- a/sc/shared/src/test/scala/sigmastate/lang/SigmaTemplateCompilerTest.scala +++ b/sc/shared/src/test/scala/sigmastate/lang/SigmaTemplateCompilerTest.scala @@ -94,11 +94,11 @@ class SigmaTemplateCompilerTest extends AnyPropSpec with ScalaCheckPropertyCheck val template = compiler.compile(Map.empty, source) template.parameters should contain theSameElementsInOrderAs IndexedSeq( - Parameter("p", "", 0), + Parameter("p", "", 0) ) template.constTypes should contain theSameElementsInOrderAs Seq(SBoolean) template.constValues.get should contain theSameElementsInOrderAs IndexedSeq( - Some(true), + Some(true) ) val sigmaCompiler = new SigmaCompiler(ErgoAddressEncoder.MainnetNetworkPrefix) @@ -119,13 +119,13 @@ class SigmaTemplateCompilerTest extends AnyPropSpec with ScalaCheckPropertyCheck template.parameters should contain theSameElementsInOrderAs IndexedSeq( Parameter("low", "", 0), - Parameter("high", "", 1), + Parameter("high", "", 1) ) template.constTypes should contain theSameElementsInOrderAs Seq(SInt, SInt) // check parsed default values template.constValues.get should contain theSameElementsInOrderAs IndexedSeq( Some(0), - None, + None ) val sigmaCompiler = new SigmaCompiler(ErgoAddressEncoder.MainnetNetworkPrefix) From eb6270c88e5687a6d0b88000b05bee311b7d3fa5 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Sun, 12 May 2024 12:44:41 +0100 Subject: [PATCH 4/9] i966-template-compiler: create ConstantPlaceholder for each parameter in the compiled expression --- .../scala/sigmastate/eval/GraphBuilding.scala | 14 ++++ .../scala/sigmastate/eval/TreeBuilding.scala | 4 ++ .../scala/sigmastate/lang/SigmaCompiler.scala | 10 ++- .../lang/SigmaTemplateCompiler.scala | 23 +++---- .../scala/sigmastate/lang/SigmaTyper.scala | 8 ++- .../lang/SigmaTemplateCompilerTest.scala | 67 ++++++++----------- .../sigmastate/lang/SigmaTyperTest.scala | 6 +- 7 files changed, 73 insertions(+), 59 deletions(-) diff --git a/sc/shared/src/main/scala/sigmastate/eval/GraphBuilding.scala b/sc/shared/src/main/scala/sigmastate/eval/GraphBuilding.scala index 5ddcdfa946..4a126754a5 100644 --- a/sc/shared/src/main/scala/sigmastate/eval/GraphBuilding.scala +++ b/sc/shared/src/main/scala/sigmastate/eval/GraphBuilding.scala @@ -381,6 +381,18 @@ trait GraphBuilding extends SigmaLibrary { IR: IRContext => def error(msg: String) = throw new GraphBuildingException(msg, None) def error(msg: String, srcCtx: Option[SourceContext]) = throw new GraphBuildingException(msg, srcCtx) + /** Graph node to represent a placeholder of a constant in ErgoTree. + * @param id Zero based index in ErgoTree.constants array. + * @param resultType type descriptor of the constant value. + */ + case class ConstantPlaceholder[T](id: Int, resultType: Elem[T]) extends Def[T] + + /** Smart constructor method for [[ConstantPlaceholder]], should be used instead of the + * class constructor. + */ + @inline def constantPlaceholder[T](id: Int, eT: Elem[T]): Ref[T] = ConstantPlaceholder(id, eT) + + /** Translates the given typed expression to IR graph representing a function from * Context to some type T. * @param env contains values for each named constant used @@ -465,6 +477,8 @@ trait GraphBuilding extends SigmaLibrary { IR: IRContext => val resV = toRep(v)(e) resV } + case sigma.ast.ConstantPlaceholder(id, tpe) => + constantPlaceholder(id, stypeToElem(tpe)) case sigma.ast.Context => ctx case Global => sigmaDslBuilder case Height => ctx.HEIGHT diff --git a/sc/shared/src/main/scala/sigmastate/eval/TreeBuilding.scala b/sc/shared/src/main/scala/sigmastate/eval/TreeBuilding.scala index 27f659c1ed..11ae0b85f4 100644 --- a/sc/shared/src/main/scala/sigmastate/eval/TreeBuilding.scala +++ b/sc/shared/src/main/scala/sigmastate/eval/TreeBuilding.scala @@ -183,6 +183,10 @@ trait TreeBuilding extends SigmaLibrary { IR: IRContext => case None => mkConstant[tpe.type](x.asInstanceOf[tpe.WrappedType], tpe) } + case Def(IR.ConstantPlaceholder(id, elem)) => + val tpe = elemToSType(elem) + mkConstantPlaceholder[tpe.type](id, tpe) + case Def(wc: LiftedConst[a,_]) => val tpe = elemToSType(s.elem) mkConstant[tpe.type](wc.constValue.asInstanceOf[tpe.WrappedType], tpe) diff --git a/sc/shared/src/main/scala/sigmastate/lang/SigmaCompiler.scala b/sc/shared/src/main/scala/sigmastate/lang/SigmaCompiler.scala index 849167e159..3e83aa866c 100644 --- a/sc/shared/src/main/scala/sigmastate/lang/SigmaCompiler.scala +++ b/sc/shared/src/main/scala/sigmastate/lang/SigmaCompiler.scala @@ -72,7 +72,8 @@ class SigmaCompiler private(settings: CompilerSettings) { val predefinedFuncRegistry = new PredefinedFuncRegistry(builder) val binder = new SigmaBinder(env, builder, networkPrefix, predefinedFuncRegistry) val bound = binder.bind(parsed) - val typer = new SigmaTyper(builder, predefinedFuncRegistry, settings.lowerMethodCalls) + val typeEnv = env.collect { case (k, v: SType) => k -> v } + val typer = new SigmaTyper(builder, predefinedFuncRegistry, typeEnv, settings.lowerMethodCalls) val typed = typer.typecheck(bound) typed } @@ -91,7 +92,12 @@ class SigmaCompiler private(settings: CompilerSettings) { /** Compiles the given typed expression. */ def compileTyped(env: ScriptEnv, typedExpr: SValue)(implicit IR: IRContext): CompilerResult[IR.type] = { - val compiledGraph = IR.buildGraph(env, typedExpr) + val placeholdersEnv = env + .collect { case (name, t: SType) => name -> t } + .zipWithIndex + .map { case ((name, t), index) => name -> ConstantPlaceholder(index, t) } + .toMap + val compiledGraph = IR.buildGraph(env ++ placeholdersEnv, typedExpr) val compiledTree = IR.buildTree(compiledGraph) CompilerResult(env, "", compiledGraph, compiledTree) } diff --git a/sc/shared/src/main/scala/sigmastate/lang/SigmaTemplateCompiler.scala b/sc/shared/src/main/scala/sigmastate/lang/SigmaTemplateCompiler.scala index b124684d50..f77735118c 100644 --- a/sc/shared/src/main/scala/sigmastate/lang/SigmaTemplateCompiler.scala +++ b/sc/shared/src/main/scala/sigmastate/lang/SigmaTemplateCompiler.scala @@ -1,12 +1,10 @@ package sigmastate.lang import fastparse.Parsed -import org.ergoplatform.sdk.ContractTemplate -import sigmastate.eval.{CompiletimeIRContext, msgCostLimitError} -import org.ergoplatform.sdk.Parameter -import sigma.ast.{Constant, SType, SourceContext} +import org.ergoplatform.sdk.{ContractTemplate, Parameter} +import sigma.ast.SourceContext import sigma.ast.syntax.SValue -import sigmastate.interpreter.Interpreter.ScriptEnv +import sigmastate.eval.CompiletimeIRContext import sigmastate.lang.parsers.ParserException /** Compiler which compiles Ergo contract templates into a [[ContractTemplate]]. */ @@ -19,17 +17,14 @@ class SigmaTemplateCompiler(networkPrefix: Byte) { * @param source The ErgoScript contract source code. * @return The contract template. */ - def compile(env: ScriptEnv, source: String): ContractTemplate = { + def compile(source: String): ContractTemplate = { ContractParser.parse(source) match { - case Parsed.Success(template, _) => { + case Parsed.Success(parsedTemplate, _) => implicit val ir = new CompiletimeIRContext - val mergedEnv = template.signature.params - .collect { case ContractParam(name, tpe, Some(defaultValue)) => - name -> defaultValue - }.toMap ++ env - val result = sigmaCompiler.compileParsed(mergedEnv, template.body) - assemble(template, result.buildTree) - } + val parEnv = parsedTemplate.signature.params.map { p => p.name -> p.tpe }.toMap + val result = sigmaCompiler.compileParsed(parEnv, parsedTemplate.body) + assemble(parsedTemplate, result.buildTree) + case f: Parsed.Failure => throw new ParserException(s"Contract template syntax error: $f", Some(SourceContext.fromParserFailure(f))) } diff --git a/sc/shared/src/main/scala/sigmastate/lang/SigmaTyper.scala b/sc/shared/src/main/scala/sigmastate/lang/SigmaTyper.scala index 7fd6dadd45..857ed66338 100644 --- a/sc/shared/src/main/scala/sigmastate/lang/SigmaTyper.scala +++ b/sc/shared/src/main/scala/sigmastate/lang/SigmaTyper.scala @@ -1,6 +1,5 @@ package sigmastate.lang -import org.ergoplatform._ import sigma.ast.SCollection.{SBooleanArray, SByteArray} import sigma.ast._ import sigma.ast.syntax.SValue @@ -19,6 +18,7 @@ import scala.collection.mutable.ArrayBuffer */ class SigmaTyper(val builder: SigmaBuilder, predefFuncRegistry: PredefinedFuncRegistry, + typeEnv: Map[String, SType], lowerMethodCalls: Boolean) { import SigmaTyper._ import builder._ @@ -28,8 +28,10 @@ class SigmaTyper(val builder: SigmaBuilder, import SType.tT - private val predefinedEnv: Map[String, SType] = - predefFuncRegistry.funcs.map { case (k, f) => k -> f.declaration.tpe }.toMap + private val predefinedEnv: Map[String, SType] = { + val predefFuncs = predefFuncRegistry.funcs.map { case (k, f) => k -> f.declaration.tpe }.toMap + predefFuncs ++ typeEnv + } private def processGlobalMethod(srcCtx: Nullable[SourceContext], method: SMethod, diff --git a/sc/shared/src/test/scala/sigmastate/lang/SigmaTemplateCompilerTest.scala b/sc/shared/src/test/scala/sigmastate/lang/SigmaTemplateCompilerTest.scala index 82a080cf1a..a0f9c7d8aa 100644 --- a/sc/shared/src/test/scala/sigmastate/lang/SigmaTemplateCompilerTest.scala +++ b/sc/shared/src/test/scala/sigmastate/lang/SigmaTemplateCompilerTest.scala @@ -4,12 +4,14 @@ import org.ergoplatform.ErgoAddressEncoder import org.ergoplatform.sdk.Parameter import org.scalatest.propspec.AnyPropSpec import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks -import sigma.ast.{SBoolean, SInt, SLong, SString} +import sigma.ast.{BinAnd, BoolToSigmaProp, ConstantPlaceholder, Height, LT, SBoolean, SInt, SLong, SString, TrueLeaf} import sigma.exceptions.TyperException -import sigmastate.eval.CompiletimeIRContext +import sigmastate.helpers.SigmaPPrint import sigmastate.interpreter.Interpreter.ScriptEnv class SigmaTemplateCompilerTest extends AnyPropSpec with ScalaCheckPropertyChecks with LangTests { + val templateCompiler = SigmaTemplateCompiler(ErgoAddressEncoder.MainnetNetworkPrefix) + property("compiles full contract template") { val source = """/** This is my contracts description. @@ -25,8 +27,7 @@ class SigmaTemplateCompilerTest extends AnyPropSpec with ScalaCheckPropertyCheck |@contract def contractName(p1: Int = 5, p2: String = "default string", param3: Long) = { | sigmaProp(true) |}""".stripMargin - val compiler = SigmaTemplateCompiler(ErgoAddressEncoder.MainnetNetworkPrefix) - val template = compiler.compile(Map.empty, source) + val template = templateCompiler.compile(source) template.name shouldBe "contractName" template.description shouldBe "This is my contracts description. Here is another line describing what it does in more detail." @@ -42,11 +43,8 @@ class SigmaTemplateCompilerTest extends AnyPropSpec with ScalaCheckPropertyCheck None ) - val sigmaCompiler = new SigmaCompiler(ErgoAddressEncoder.MainnetNetworkPrefix) - implicit val ir = new CompiletimeIRContext - val result = sigmaCompiler.compile(Map.empty, "{ sigmaProp(true) }") - - template.expressionTree shouldBe result.buildTree + val expectedExpr = BoolToSigmaProp(TrueLeaf) + template.expressionTree shouldBe expectedExpr } property("compiles contract template without braces") { @@ -63,8 +61,7 @@ class SigmaTemplateCompilerTest extends AnyPropSpec with ScalaCheckPropertyCheck |*/ |@contract def contractName(p1: Int = 5, p2: String = "default string") = sigmaProp(true) |""".stripMargin - val compiler = SigmaTemplateCompiler(ErgoAddressEncoder.MainnetNetworkPrefix) - val template = compiler.compile(Map.empty, source) + val template = templateCompiler.compile(source) template.name shouldBe "contractName" template.description shouldBe "This is my contracts description. Here is another line describing what it does in more detail." @@ -78,11 +75,8 @@ class SigmaTemplateCompilerTest extends AnyPropSpec with ScalaCheckPropertyCheck Some("default string") ) - val sigmaCompiler = new SigmaCompiler(ErgoAddressEncoder.MainnetNetworkPrefix) - implicit val ir = new CompiletimeIRContext - val result = sigmaCompiler.compile(Map.empty, "sigmaProp(true)") - - template.expressionTree shouldBe result.buildTree + val expectedExpr = BoolToSigmaProp(TrueLeaf) + template.expressionTree shouldBe expectedExpr } property("uses default value from parameter definition") { @@ -90,8 +84,7 @@ class SigmaTemplateCompilerTest extends AnyPropSpec with ScalaCheckPropertyCheck """/**/ |@contract def contractName(p: Boolean = true) = sigmaProp(p) |""".stripMargin - val compiler = SigmaTemplateCompiler(ErgoAddressEncoder.MainnetNetworkPrefix) - val template = compiler.compile(Map.empty, source) + val template = templateCompiler.compile(source) template.parameters should contain theSameElementsInOrderAs IndexedSeq( Parameter("p", "", 0) @@ -101,21 +94,16 @@ class SigmaTemplateCompilerTest extends AnyPropSpec with ScalaCheckPropertyCheck Some(true) ) - val sigmaCompiler = new SigmaCompiler(ErgoAddressEncoder.MainnetNetworkPrefix) - implicit val ir = new CompiletimeIRContext - val result = sigmaCompiler.compile(Map("p" -> true), "sigmaProp(p)") - - template.expressionTree shouldBe result.buildTree + val expectedExpr = BoolToSigmaProp(ConstantPlaceholder(0, SBoolean)) + template.expressionTree shouldBe expectedExpr } property("uses given environment when provided (overriding default value)") { - val explicitEnv = Map("low" -> 10, "high" -> 100) val source = """/**/ |@contract def contractName(low: Int = 0, high: Int) = sigmaProp(low < HEIGHT && HEIGHT < high) |""".stripMargin - val compiler = SigmaTemplateCompiler(ErgoAddressEncoder.MainnetNetworkPrefix) - val template = compiler.compile(explicitEnv, source) + val template = templateCompiler.compile(source) template.parameters should contain theSameElementsInOrderAs IndexedSeq( Parameter("low", "", 0), @@ -128,27 +116,30 @@ class SigmaTemplateCompilerTest extends AnyPropSpec with ScalaCheckPropertyCheck None ) - val sigmaCompiler = new SigmaCompiler(ErgoAddressEncoder.MainnetNetworkPrefix) - implicit val ir = new CompiletimeIRContext - val result = sigmaCompiler.compile( - env = explicitEnv, - code = "sigmaProp(low < HEIGHT && HEIGHT < high)" + SigmaPPrint.pprintln(template.expressionTree, 100) + + val expectedExpr = BoolToSigmaProp( + BinAnd( + LT(ConstantPlaceholder(0, SInt), Height), + LT(Height, ConstantPlaceholder(1, SInt)) + ) ) + template.expressionTree shouldBe expectedExpr + + val explicitEnv = Map("low" -> 10, "high" -> 100) - template.expressionTree shouldBe result.buildTree } - property("fails when constant value in not provided") { - // NOTE: parameter `high` without default value */ + property("fails when the parameter is not provided") { + // NOTE: parameter `condition` is not provided */ val source = """/**/ - |@contract def contractName(low: Int = 0, high: Int) = sigmaProp(low < HEIGHT && HEIGHT < high) + |@contract def contractName(low: Int = 0, high: Int) = sigmaProp(low < HEIGHT && HEIGHT < high) && condition |""".stripMargin - val compiler = SigmaTemplateCompiler(ErgoAddressEncoder.MainnetNetworkPrefix) val env: ScriptEnv = Map.empty // no value for "high" assertExceptionThrown( - compiler.compile(env, source), - exceptionLike[TyperException]("Cannot assign type for variable 'high' because it is not found in env") + templateCompiler.compile(source), + exceptionLike[TyperException]("Cannot assign type for variable 'condition' because it is not found in env") ) } diff --git a/sc/shared/src/test/scala/sigmastate/lang/SigmaTyperTest.scala b/sc/shared/src/test/scala/sigmastate/lang/SigmaTyperTest.scala index a474727943..5bb38c7998 100644 --- a/sc/shared/src/test/scala/sigmastate/lang/SigmaTyperTest.scala +++ b/sc/shared/src/test/scala/sigmastate/lang/SigmaTyperTest.scala @@ -34,7 +34,8 @@ class SigmaTyperTest extends AnyPropSpec val predefinedFuncRegistry = new PredefinedFuncRegistry(builder) val binder = new SigmaBinder(env, builder, TestnetNetworkPrefix, predefinedFuncRegistry) val bound = binder.bind(parsed) - val typer = new SigmaTyper(builder, predefinedFuncRegistry, lowerMethodCalls = true) + val typeEnv = env.collect { case (k, v: SType) => k -> v } + val typer = new SigmaTyper(builder, predefinedFuncRegistry, typeEnv, lowerMethodCalls = true) val typed = typer.typecheck(bound) assertSrcCtxForAllNodes(typed) if (expected != null) typed shouldBe expected @@ -51,7 +52,8 @@ class SigmaTyperTest extends AnyPropSpec val predefinedFuncRegistry = new PredefinedFuncRegistry(builder) val binder = new SigmaBinder(env, builder, TestnetNetworkPrefix, predefinedFuncRegistry) val bound = binder.bind(parsed) - val typer = new SigmaTyper(builder, predefinedFuncRegistry, lowerMethodCalls = true) + val typeEnv = env.collect { case (k, v: SType) => k -> v } + val typer = new SigmaTyper(builder, predefinedFuncRegistry, typeEnv, lowerMethodCalls = true) typer.typecheck(bound) }, { case te: TyperException => From 6abf2925bf441d39bbb6807d0491864f2fc15f55 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Sun, 12 May 2024 13:53:31 +0100 Subject: [PATCH 5/9] i966-template-compiler: added ErgoTreeUtils.explainTreeHeader --- .../sdk/utils/ErgoTreeUtils.scala | 26 +++++++++++++++++++ .../sdk/utils/ErgoTreeUtilsSpec.scala | 23 ++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 sdk/shared/src/main/scala/org/ergoplatform/sdk/utils/ErgoTreeUtils.scala create mode 100644 sdk/shared/src/test/scala/org/ergoplatform/sdk/utils/ErgoTreeUtilsSpec.scala diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/utils/ErgoTreeUtils.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/utils/ErgoTreeUtils.scala new file mode 100644 index 0000000000..8feccb2dfc --- /dev/null +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/utils/ErgoTreeUtils.scala @@ -0,0 +1,26 @@ +package org.ergoplatform.sdk.utils + +import sigma.ast.ErgoTree +import sigma.ast.ErgoTree.HeaderType +import sigma.util.Extensions.BooleanOps + +/** SDK level utilities and helper methods to work with ErgoTrees. */ +object ErgoTreeUtils { + /** Prints description of the bits in the given ErgoTree header. */ + def explainTreeHeader(header: HeaderType): String = { + // convert byte to hex and decimal string + val byteToHex = (b: Byte) => f"Ox${b & 0xff}%02x" + val hasSize = ErgoTree.hasSize(header) + s""" + |Header: ${byteToHex(header)} (${header.toString}) + |Bit 0: ${header.toByte & 0x01} \\ + |Bit 1: ${(header.toByte & 0x02) >> 1}\t-- ErgoTree version ${ErgoTree.getVersion(header)} + |Bit 2: ${(header.toByte & 0x04) >> 2} / + |Bit 3: ${hasSize.toByte} \t-- size of the whole tree is serialized after the header byte + |Bit 4: ${ErgoTree.isConstantSegregation(header).toByte} \t-- constant segregation is used for this ErgoTree + |Bit 5: ${header.toByte & 0x20} \t-- reserved (should be 0) + |Bit 6: ${header.toByte & 0x40} \t-- reserved for GZIP compression (should be 0) + |Bit 7: ${header.toByte & 0x80} \t-- header contains more than 1 byte (default == 0) + |""".stripMargin + } +} diff --git a/sdk/shared/src/test/scala/org/ergoplatform/sdk/utils/ErgoTreeUtilsSpec.scala b/sdk/shared/src/test/scala/org/ergoplatform/sdk/utils/ErgoTreeUtilsSpec.scala new file mode 100644 index 0000000000..cb3321d28e --- /dev/null +++ b/sdk/shared/src/test/scala/org/ergoplatform/sdk/utils/ErgoTreeUtilsSpec.scala @@ -0,0 +1,23 @@ +package org.ergoplatform.sdk.utils + +import org.scalatest.matchers.should.Matchers +import org.scalatest.propspec.AnyPropSpec +import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks +import sigma.ast.ErgoTree.HeaderType + +class ErgoTreeUtilsSpec extends AnyPropSpec with ScalaCheckPropertyChecks with Matchers { + property("explainTreeHeader") { + ErgoTreeUtils.explainTreeHeader(HeaderType @@ 26.toByte) shouldBe + """| + |Header: Ox1a (26) + |Bit 0: 0 \ + |Bit 1: 1 -- ErgoTree version 2 + |Bit 2: 0 / + |Bit 3: 1 -- size of the whole tree is serialized after the header byte + |Bit 4: 1 -- constant segregation is used for this ErgoTree + |Bit 5: 0 -- reserved (should be 0) + |Bit 6: 0 -- reserved for GZIP compression (should be 0) + |Bit 7: 0 -- header contains more than 1 byte (default == 0) + |""".stripMargin + } +} From efabf245052c77cd4c386bdbc5ce0ba147e79199 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Sun, 12 May 2024 14:33:30 +0100 Subject: [PATCH 6/9] i966-template-compiler: test ContractTemplate.applyTemplate method --- .../src/main/scala/sigma/ast/ErgoTree.scala | 4 +-- .../lang/SigmaTemplateCompilerTest.scala | 27 ++++++++++++++++--- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/data/shared/src/main/scala/sigma/ast/ErgoTree.scala b/data/shared/src/main/scala/sigma/ast/ErgoTree.scala index d6ed6118dc..68d69abd91 100644 --- a/data/shared/src/main/scala/sigma/ast/ErgoTree.scala +++ b/data/shared/src/main/scala/sigma/ast/ErgoTree.scala @@ -25,13 +25,13 @@ case class UnparsedErgoTree(bytes: mutable.WrappedArray[Byte], error: Validation * ErgoTreeSerializer defines top-level serialization format of the scripts. * The interpretation of the byte array depend on the first `header` byte, which uses VLQ encoding up to 30 bits. * Currently we define meaning for only first byte, which may be extended in future versions. - * 7 6 5 4 3 2 1 0 + * 7 6 5 4 3 2 1 0 * ------------------------- * | | | | | | | | | * ------------------------- * Bit 7 == 1 if the header contains more than 1 byte (default == 0) * Bit 6 - reserved for GZIP compression (should be 0) - * Bit 5 == 1 - reserved for context dependent costing (should be = 0) + * Bit 5 == 1 - reserved (should be = 0) * Bit 4 == 1 if constant segregation is used for this ErgoTree (default = 0) * (see https://github.com/ScorexFoundation/sigmastate-interpreter/issues/264) * Bit 3 == 1 if size of the whole tree is serialized after the header byte (default = 0) diff --git a/sc/shared/src/test/scala/sigmastate/lang/SigmaTemplateCompilerTest.scala b/sc/shared/src/test/scala/sigmastate/lang/SigmaTemplateCompilerTest.scala index a0f9c7d8aa..d3e08a700f 100644 --- a/sc/shared/src/test/scala/sigmastate/lang/SigmaTemplateCompilerTest.scala +++ b/sc/shared/src/test/scala/sigmastate/lang/SigmaTemplateCompilerTest.scala @@ -4,7 +4,9 @@ import org.ergoplatform.ErgoAddressEncoder import org.ergoplatform.sdk.Parameter import org.scalatest.propspec.AnyPropSpec import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks -import sigma.ast.{BinAnd, BoolToSigmaProp, ConstantPlaceholder, Height, LT, SBoolean, SInt, SLong, SString, TrueLeaf} +import sigma.VersionContext +import sigma.ast.ErgoTree.HeaderType +import sigma.ast.{BinAnd, BoolToSigmaProp, ConstantPlaceholder, ErgoTree, FalseLeaf, Height, LT, SBoolean, SInt, SLong, SString, TrueLeaf} import sigma.exceptions.TyperException import sigmastate.helpers.SigmaPPrint import sigmastate.interpreter.Interpreter.ScriptEnv @@ -96,6 +98,27 @@ class SigmaTemplateCompilerTest extends AnyPropSpec with ScalaCheckPropertyCheck val expectedExpr = BoolToSigmaProp(ConstantPlaceholder(0, SBoolean)) template.expressionTree shouldBe expectedExpr + + val expectedTree = new ErgoTree( + HeaderType @@ 26.toByte, // use ErgoTreeUtils to get explanation + Vector(TrueLeaf), + Right(BoolToSigmaProp(ConstantPlaceholder(0, SBoolean)))) + + expectedTree.version shouldBe VersionContext.JitActivationVersion + expectedTree.hasSize shouldBe true + expectedTree.isConstantSegregation shouldBe true + + // apply using default values declared in the parameters + template.applyTemplate( + version = Some(VersionContext.JitActivationVersion), + paramValues = Map.empty + ) shouldBe expectedTree + + // apply overriding the default values + template.applyTemplate( + version = Some(VersionContext.JitActivationVersion), + paramValues = Map("p" -> FalseLeaf) + ) shouldBe expectedTree.copy(constants = Vector(FalseLeaf)) } property("uses given environment when provided (overriding default value)") { @@ -116,8 +139,6 @@ class SigmaTemplateCompilerTest extends AnyPropSpec with ScalaCheckPropertyCheck None ) - SigmaPPrint.pprintln(template.expressionTree, 100) - val expectedExpr = BoolToSigmaProp( BinAnd( LT(ConstantPlaceholder(0, SInt), Height), From dcee25851110f0241445f83da95f3baceff40806 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Sun, 12 May 2024 17:47:43 +0100 Subject: [PATCH 7/9] i966-template-compiler: more tests for CompilerTemplate.applyTemplate --- .../scala/sigmastate/lang/LangTests.scala | 3 +- .../scala/sigmastate/CompilerTestsBase.scala | 11 ++- .../lang/SigmaTemplateCompilerTest.scala | 90 +++++++++++++------ .../ergoplatform/sdk/ContractTemplate.scala | 2 +- .../sdk/utils/ErgoTreeUtils.scala | 4 +- 5 files changed, 79 insertions(+), 31 deletions(-) diff --git a/parsers/shared/src/test/scala/sigmastate/lang/LangTests.scala b/parsers/shared/src/test/scala/sigmastate/lang/LangTests.scala index 498c3934bf..32943bca44 100644 --- a/parsers/shared/src/test/scala/sigmastate/lang/LangTests.scala +++ b/parsers/shared/src/test/scala/sigmastate/lang/LangTests.scala @@ -1,16 +1,15 @@ package sigmastate.lang import org.scalatest.matchers.should.Matchers -import sigma.{Coll, _} import sigma.ast.SCollection.SByteArray import sigma.ast.syntax.{SValue, ValueOps} import sigma.ast._ import sigma.crypto.CryptoConstants import sigma.data.{CAnyValue, CSigmaDslBuilder, ProveDHTuple, ProveDlog, SigmaBoolean} import sigma.util.Extensions.BigIntegerOps +import sigma._ import sigmastate.helpers.NegativeTesting import sigmastate.interpreter.Interpreter.ScriptEnv -import sigma.ast.{Ident, MethodCallLike} import java.math.BigInteger diff --git a/sc/shared/src/test/scala/sigmastate/CompilerTestsBase.scala b/sc/shared/src/test/scala/sigmastate/CompilerTestsBase.scala index 28f907c199..0b2951b92f 100644 --- a/sc/shared/src/test/scala/sigmastate/CompilerTestsBase.scala +++ b/sc/shared/src/test/scala/sigmastate/CompilerTestsBase.scala @@ -9,8 +9,9 @@ import sigma.ast.syntax.{SValue, SigmaPropValue} import sigma.serialization.ValueSerializer import sigmastate.eval.IRContext import sigma.ast.syntax.ValueOps +import sigmastate.helpers.{NegativeTesting, SigmaPPrint} -trait CompilerTestsBase extends TestsBase { +trait CompilerTestsBase extends TestsBase with NegativeTesting { protected val _lowerMethodCalls = new DynamicVariable[Boolean](true) /** Returns true if MethodCall nodes should be lowered by TypeChecker to the @@ -63,4 +64,12 @@ trait CompilerTestsBase extends TestsBase { val tree = mkTestErgoTree(prop) (tree, prop) } + + /** Checks expectation pretty printing the actual value if there is a difference. */ + def checkEquals[T](actual: T, expected: T): Unit = { + if (expected != actual) { + SigmaPPrint.pprintln(actual, width = 100) + } + actual shouldBe expected + } } diff --git a/sc/shared/src/test/scala/sigmastate/lang/SigmaTemplateCompilerTest.scala b/sc/shared/src/test/scala/sigmastate/lang/SigmaTemplateCompilerTest.scala index d3e08a700f..35f16618ec 100644 --- a/sc/shared/src/test/scala/sigmastate/lang/SigmaTemplateCompilerTest.scala +++ b/sc/shared/src/test/scala/sigmastate/lang/SigmaTemplateCompilerTest.scala @@ -6,12 +6,12 @@ import org.scalatest.propspec.AnyPropSpec import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks import sigma.VersionContext import sigma.ast.ErgoTree.HeaderType -import sigma.ast.{BinAnd, BoolToSigmaProp, ConstantPlaceholder, ErgoTree, FalseLeaf, Height, LT, SBoolean, SInt, SLong, SString, TrueLeaf} +import sigma.ast.{BinAnd, BoolToSigmaProp, ConstantPlaceholder, ErgoTree, FalseLeaf, GT, Height, IntConstant, LT, SBoolean, SInt, SLong, SString, TrueLeaf} import sigma.exceptions.TyperException -import sigmastate.helpers.SigmaPPrint +import sigmastate.CompilerTestsBase import sigmastate.interpreter.Interpreter.ScriptEnv -class SigmaTemplateCompilerTest extends AnyPropSpec with ScalaCheckPropertyChecks with LangTests { +class SigmaTemplateCompilerTest extends AnyPropSpec with ScalaCheckPropertyChecks with CompilerTestsBase { val templateCompiler = SigmaTemplateCompiler(ErgoAddressEncoder.MainnetNetworkPrefix) property("compiles full contract template") { @@ -45,8 +45,7 @@ class SigmaTemplateCompilerTest extends AnyPropSpec with ScalaCheckPropertyCheck None ) - val expectedExpr = BoolToSigmaProp(TrueLeaf) - template.expressionTree shouldBe expectedExpr + checkEquals(template.expressionTree, BoolToSigmaProp(TrueLeaf)) } property("compiles contract template without braces") { @@ -77,14 +76,13 @@ class SigmaTemplateCompilerTest extends AnyPropSpec with ScalaCheckPropertyCheck Some("default string") ) - val expectedExpr = BoolToSigmaProp(TrueLeaf) - template.expressionTree shouldBe expectedExpr + checkEquals(template.expressionTree, BoolToSigmaProp(TrueLeaf)) } property("uses default value from parameter definition") { val source = """/**/ - |@contract def contractName(p: Boolean = true) = sigmaProp(p) + |@contract def contractName(p: Boolean = true) = sigmaProp(p && HEIGHT > 1000) |""".stripMargin val template = templateCompiler.compile(source) @@ -96,29 +94,35 @@ class SigmaTemplateCompilerTest extends AnyPropSpec with ScalaCheckPropertyCheck Some(true) ) - val expectedExpr = BoolToSigmaProp(ConstantPlaceholder(0, SBoolean)) - template.expressionTree shouldBe expectedExpr + val expectedExpr = BoolToSigmaProp(BinAnd(ConstantPlaceholder(0, SBoolean), GT(Height, IntConstant(1000)))) + checkEquals(template.expressionTree, expectedExpr) val expectedTree = new ErgoTree( HeaderType @@ 26.toByte, // use ErgoTreeUtils to get explanation Vector(TrueLeaf), - Right(BoolToSigmaProp(ConstantPlaceholder(0, SBoolean)))) + Right(BoolToSigmaProp(BinAnd(ConstantPlaceholder(0, SBoolean), GT(Height, IntConstant(1000)))))) expectedTree.version shouldBe VersionContext.JitActivationVersion expectedTree.hasSize shouldBe true expectedTree.isConstantSegregation shouldBe true // apply using default values declared in the parameters - template.applyTemplate( - version = Some(VersionContext.JitActivationVersion), - paramValues = Map.empty - ) shouldBe expectedTree + checkEquals( + template.applyTemplate( + version = Some(VersionContext.JitActivationVersion), + paramValues = Map.empty + ), + expectedTree + ) // apply overriding the default values - template.applyTemplate( - version = Some(VersionContext.JitActivationVersion), - paramValues = Map("p" -> FalseLeaf) - ) shouldBe expectedTree.copy(constants = Vector(FalseLeaf)) + checkEquals( + template.applyTemplate( + version = Some(VersionContext.JitActivationVersion), + paramValues = Map("p" -> FalseLeaf) + ), + expectedTree.copy(constants = Vector(FalseLeaf)) + ) } property("uses given environment when provided (overriding default value)") { @@ -138,17 +142,53 @@ class SigmaTemplateCompilerTest extends AnyPropSpec with ScalaCheckPropertyCheck Some(0), None ) + checkEquals( + template.expressionTree, + BoolToSigmaProp( + BinAnd( + LT(ConstantPlaceholder(0, SInt), Height), + LT(Height, ConstantPlaceholder(1, SInt)) + ) + ) + ) + + // incomplete application (missing `high` parameter) + assertExceptionThrown( + template.applyTemplate( + version = Some(VersionContext.JitActivationVersion), + paramValues = Map.empty + ), + exceptionLike[IllegalArgumentException]( + "requirement failed: value for parameter `high` was not provided while it does not have a default value.") + ) - val expectedExpr = BoolToSigmaProp( - BinAnd( - LT(ConstantPlaceholder(0, SInt), Height), - LT(Height, ConstantPlaceholder(1, SInt)) + val expectedTree = new ErgoTree( + HeaderType @@ 26.toByte, + Vector(IntConstant(0), IntConstant(100)), + Right( + BoolToSigmaProp( + BinAnd(LT(ConstantPlaceholder(0, SInt), Height), LT(Height, ConstantPlaceholder(1, SInt))) + ) ) ) - template.expressionTree shouldBe expectedExpr - val explicitEnv = Map("low" -> 10, "high" -> 100) + // apply providing the parameter without default value + checkEquals( + template.applyTemplate( + version = Some(VersionContext.JitActivationVersion), + paramValues = Map("high" -> IntConstant(100)) + ), + expectedTree + ) + // apply providing all parameters overriding the default values + checkEquals( + template.applyTemplate( + version = Some(VersionContext.JitActivationVersion), + paramValues = Map("low" -> IntConstant(10), "high" -> IntConstant(100)) + ), + expectedTree.copy(constants = Vector(IntConstant(10), IntConstant(100))) + ) } property("fails when the parameter is not provided") { diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/ContractTemplate.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/ContractTemplate.scala index 5e491630bd..8da420bee6 100644 --- a/sdk/shared/src/main/scala/org/ergoplatform/sdk/ContractTemplate.scala +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/ContractTemplate.scala @@ -153,7 +153,7 @@ case class ContractTemplate( .map(p => p.name) requiredParameterNames.foreach(name => require( paramValues.contains(name), - s"value for parameter $name was not provided while it does not have a default value.")) + s"value for parameter `$name` was not provided while it does not have a default value.")) val parameterizedConstantIndices = this.parameters.map(p => p.constantIndex).toSet val constIndexToParamIndex = this.parameters.zipWithIndex.map(pi => pi._1.constantIndex -> pi._2).toMap diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/utils/ErgoTreeUtils.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/utils/ErgoTreeUtils.scala index 8feccb2dfc..ffa6505377 100644 --- a/sdk/shared/src/main/scala/org/ergoplatform/sdk/utils/ErgoTreeUtils.scala +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/utils/ErgoTreeUtils.scala @@ -8,7 +8,7 @@ import sigma.util.Extensions.BooleanOps object ErgoTreeUtils { /** Prints description of the bits in the given ErgoTree header. */ def explainTreeHeader(header: HeaderType): String = { - // convert byte to hex and decimal string + // convert byte to hex val byteToHex = (b: Byte) => f"Ox${b & 0xff}%02x" val hasSize = ErgoTree.hasSize(header) s""" @@ -19,7 +19,7 @@ object ErgoTreeUtils { |Bit 3: ${hasSize.toByte} \t-- size of the whole tree is serialized after the header byte |Bit 4: ${ErgoTree.isConstantSegregation(header).toByte} \t-- constant segregation is used for this ErgoTree |Bit 5: ${header.toByte & 0x20} \t-- reserved (should be 0) - |Bit 6: ${header.toByte & 0x40} \t-- reserved for GZIP compression (should be 0) + |Bit 6: ${header.toByte & 0x40} \t-- reserved (should be 0) |Bit 7: ${header.toByte & 0x80} \t-- header contains more than 1 byte (default == 0) |""".stripMargin } From ba6abadfc39ea7f2f6c77d71adaa6a0a93101b52 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Sun, 12 May 2024 17:56:15 +0100 Subject: [PATCH 8/9] i966-template-compiler: fix ErgoTreeUtilsSpec --- .../scala/org/ergoplatform/sdk/utils/ErgoTreeUtilsSpec.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/shared/src/test/scala/org/ergoplatform/sdk/utils/ErgoTreeUtilsSpec.scala b/sdk/shared/src/test/scala/org/ergoplatform/sdk/utils/ErgoTreeUtilsSpec.scala index cb3321d28e..8dd20ecf81 100644 --- a/sdk/shared/src/test/scala/org/ergoplatform/sdk/utils/ErgoTreeUtilsSpec.scala +++ b/sdk/shared/src/test/scala/org/ergoplatform/sdk/utils/ErgoTreeUtilsSpec.scala @@ -16,7 +16,7 @@ class ErgoTreeUtilsSpec extends AnyPropSpec with ScalaCheckPropertyChecks with M |Bit 3: 1 -- size of the whole tree is serialized after the header byte |Bit 4: 1 -- constant segregation is used for this ErgoTree |Bit 5: 0 -- reserved (should be 0) - |Bit 6: 0 -- reserved for GZIP compression (should be 0) + |Bit 6: 0 -- reserved (should be 0) |Bit 7: 0 -- header contains more than 1 byte (default == 0) |""".stripMargin } From 28f7e1534960e6e3e56cde16a78e900366170543 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Sun, 12 May 2024 21:06:31 +0100 Subject: [PATCH 9/9] i966-template-compiler: addressed review comments --- sc/shared/src/main/scala/sigmastate/lang/SigmaTyper.scala | 7 +++++-- .../scala/sigmastate/lang/SigmaTemplateCompilerTest.scala | 1 - 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/sc/shared/src/main/scala/sigmastate/lang/SigmaTyper.scala b/sc/shared/src/main/scala/sigmastate/lang/SigmaTyper.scala index 857ed66338..416f2d7fbb 100644 --- a/sc/shared/src/main/scala/sigmastate/lang/SigmaTyper.scala +++ b/sc/shared/src/main/scala/sigmastate/lang/SigmaTyper.scala @@ -13,8 +13,11 @@ import sigma.serialization.OpCodes import scala.collection.mutable.ArrayBuffer -/** - * Type inference and analysis for Sigma expressions. +/** Type inference and analysis for Sigma expressions. + * @param builder SigmaBuilder instance to create new nodes + * @param predefFuncRegistry predefined functions registry used to resolve names + * @param typeEnv environment with types of variables/names + * @param lowerMethodCalls if true, then MethodCall nodes are lowered to the corresponding ErgoTree nodes */ class SigmaTyper(val builder: SigmaBuilder, predefFuncRegistry: PredefinedFuncRegistry, diff --git a/sc/shared/src/test/scala/sigmastate/lang/SigmaTemplateCompilerTest.scala b/sc/shared/src/test/scala/sigmastate/lang/SigmaTemplateCompilerTest.scala index 35f16618ec..966a7c4c74 100644 --- a/sc/shared/src/test/scala/sigmastate/lang/SigmaTemplateCompilerTest.scala +++ b/sc/shared/src/test/scala/sigmastate/lang/SigmaTemplateCompilerTest.scala @@ -197,7 +197,6 @@ class SigmaTemplateCompilerTest extends AnyPropSpec with ScalaCheckPropertyCheck """/**/ |@contract def contractName(low: Int = 0, high: Int) = sigmaProp(low < HEIGHT && HEIGHT < high) && condition |""".stripMargin - val env: ScriptEnv = Map.empty // no value for "high" assertExceptionThrown( templateCompiler.compile(source), exceptionLike[TyperException]("Cannot assign type for variable 'condition' because it is not found in env")