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/parsers/shared/src/main/scala/sigmastate/lang/ContractParser.scala b/parsers/shared/src/main/scala/sigmastate/lang/ContractParser.scala index 87dd20ab0c..a192a789b9 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 @@ -187,9 +187,9 @@ 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)) + 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 9a412b7100..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).asWrappedType)), - ContractParam("p2", SString, Some(StringConstant("default string").asWrappedType)), + ContractParam("p1", SInt, Some(5.asWrappedType)), + ContractParam("p2", SString, Some("default string".asWrappedType)), ContractParam("param3", SLong, None) ) } 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/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 ec94bdfd3a..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 -import org.ergoplatform.sdk.Parameter +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,13 +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 result = sigmaCompiler.compileParsed(env, 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..416f2d7fbb 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 @@ -14,11 +13,15 @@ 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, + typeEnv: Map[String, SType], lowerMethodCalls: Boolean) { import SigmaTyper._ import builder._ @@ -28,8 +31,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/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 df8f644172..966a7c4c74 100644 --- a/sc/shared/src/test/scala/sigmastate/lang/SigmaTemplateCompilerTest.scala +++ b/sc/shared/src/test/scala/sigmastate/lang/SigmaTemplateCompilerTest.scala @@ -2,13 +2,18 @@ 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 sigmastate.eval.CompiletimeIRContext +import sigma.VersionContext +import sigma.ast.ErgoTree.HeaderType +import sigma.ast.{BinAnd, BoolToSigmaProp, ConstantPlaceholder, ErgoTree, FalseLeaf, GT, Height, IntConstant, LT, SBoolean, SInt, SLong, SString, TrueLeaf} +import sigma.exceptions.TyperException +import sigmastate.CompilerTestsBase +import sigmastate.interpreter.Interpreter.ScriptEnv + +class SigmaTemplateCompilerTest extends AnyPropSpec with ScalaCheckPropertyChecks with CompilerTestsBase { + val templateCompiler = SigmaTemplateCompiler(ErgoAddressEncoder.MainnetNetworkPrefix) -class SigmaTemplateCompilerTest extends AnyPropSpec with ScalaCheckPropertyChecks with Matchers { property("compiles full contract template") { val source = """/** This is my contracts description. @@ -24,8 +29,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." @@ -36,16 +40,12 @@ 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 ) - val sigmaCompiler = new SigmaCompiler(ErgoAddressEncoder.MainnetNetworkPrefix) - implicit val ir = new CompiletimeIRContext - val result = sigmaCompiler.compile(Map.empty, "{ sigmaProp(true) }") - - template.expressionTree shouldBe result.buildTree + checkEquals(template.expressionTree, BoolToSigmaProp(TrueLeaf)) } property("compiles contract template without braces") { @@ -62,8 +62,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." @@ -73,16 +72,137 @@ 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") + ) + + checkEquals(template.expressionTree, BoolToSigmaProp(TrueLeaf)) + } + + property("uses default value from parameter definition") { + val source = + """/**/ + |@contract def contractName(p: Boolean = true) = sigmaProp(p && HEIGHT > 1000) + |""".stripMargin + val template = templateCompiler.compile(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 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(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 + checkEquals( + template.applyTemplate( + version = Some(VersionContext.JitActivationVersion), + paramValues = Map.empty + ), + expectedTree + ) + + // apply overriding the default values + 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)") { + val source = + """/**/ + |@contract def contractName(low: Int = 0, high: Int) = sigmaProp(low < HEIGHT && HEIGHT < high) + |""".stripMargin + val template = templateCompiler.compile(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 + ) + checkEquals( + template.expressionTree, + BoolToSigmaProp( + BinAnd( + LT(ConstantPlaceholder(0, SInt), Height), + LT(Height, ConstantPlaceholder(1, SInt)) + ) + ) ) - val sigmaCompiler = new SigmaCompiler(ErgoAddressEncoder.MainnetNetworkPrefix) - implicit val ir = new CompiletimeIRContext - val result = sigmaCompiler.compile(Map.empty, "sigmaProp(true)") + // 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 expectedTree = new ErgoTree( + HeaderType @@ 26.toByte, + Vector(IntConstant(0), IntConstant(100)), + Right( + BoolToSigmaProp( + BinAnd(LT(ConstantPlaceholder(0, SInt), Height), LT(Height, ConstantPlaceholder(1, SInt))) + ) + ) + ) + + // apply providing the parameter without default value + checkEquals( + template.applyTemplate( + version = Some(VersionContext.JitActivationVersion), + paramValues = Map("high" -> IntConstant(100)) + ), + expectedTree + ) - template.expressionTree shouldBe result.buildTree + // 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") { + // NOTE: parameter `condition` is not provided */ + val source = + """/**/ + |@contract def contractName(low: Int = 0, high: Int) = sigmaProp(low < HEIGHT && HEIGHT < high) && condition + |""".stripMargin + assertExceptionThrown( + 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 => 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 new file mode 100644 index 0000000000..ffa6505377 --- /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 + 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 (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..8dd20ecf81 --- /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 (should be 0) + |Bit 7: 0 -- header contains more than 1 byte (default == 0) + |""".stripMargin + } +}