diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ed104882fd..6c1c2fffa8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,6 +16,8 @@ env: jobs: build: name: Test and publish a snapshot + env: + HAS_SECRETS: ${{ secrets.SONATYPE_PASSWORD != '' }} strategy: matrix: os: [ubuntu-latest] diff --git a/docs/LangSpec.md b/docs/LangSpec.md index 30eb450afc..15ff4ea31c 100644 --- a/docs/LangSpec.md +++ b/docs/LangSpec.md @@ -426,7 +426,6 @@ The following syntax is supported to access registers on box objects: ``` box.R3[Int].get // access R3 register, check that its value of type Int and return it box.R3[Int].isDefined // check that value of R3 is defined and has type Int -box.R3[Int].isEmpty // check that value of R3 is undefined box.R3[Int].getOrElse(d) // access R3 value if defined, otherwise return `d` ``` @@ -933,13 +932,19 @@ def proveDHTuple(g: GroupElement, h: GroupElement, */ def proveDlog(value: GroupElement): SigmaProp -/** Transforms Base58 encoded string litereal into constant of type Coll[Byte]. +/** Transforms Base16 encoded string literal into constant of type Coll[Byte]. + * It is a compile-time operation and only string literal (constant) can be its + * argument. + */ +def fromBase16(input: String): Coll[Byte] + +/** Transforms Base58 encoded string literal into constant of type Coll[Byte]. * It is a compile-time operation and only string literal (constant) can be its * argument. */ def fromBase58(input: String): Coll[Byte] -/** Transforms Base64 encoded string litereal into constant of type Coll[Byte]. +/** Transforms Base64 encoded string literal into constant of type Coll[Byte]. * It is a compile-time operation and only string literal (constant) can be its * argument. */ diff --git a/sigmastate/src/main/scala/sigmastate/lang/SigmaPredef.scala b/sigmastate/src/main/scala/sigmastate/lang/SigmaPredef.scala index 942a110d52..fae13da932 100644 --- a/sigmastate/src/main/scala/sigmastate/lang/SigmaPredef.scala +++ b/sigmastate/src/main/scala/sigmastate/lang/SigmaPredef.scala @@ -3,15 +3,15 @@ package sigmastate.lang import org.ergoplatform.ErgoAddressEncoder.NetworkPrefix import org.ergoplatform.{ErgoAddressEncoder, P2PKAddress} import scalan.Nullable -import scorex.util.encode.{Base64, Base58} -import sigmastate.SCollection.{SIntArray, SByteArray} +import scorex.util.encode.{Base16, Base58, Base64} +import sigmastate.SCollection.{SByteArray, SIntArray} import sigmastate.SOption._ -import sigmastate.Values.{StringConstant, Constant, EvaluatedValue, SValue, IntValue, SigmaPropConstant, ConstantPlaceholder, BoolValue, Value, ByteArrayConstant, SigmaPropValue, ValueCompanion} +import sigmastate.Values.{BoolValue, ByteArrayConstant, Constant, ConstantPlaceholder, EvaluatedValue, IntValue, SValue, SigmaPropConstant, SigmaPropValue, StringConstant, Value, ValueCompanion} import sigmastate._ import sigmastate.lang.Terms._ import sigmastate.lang.exceptions.InvalidArguments -import sigmastate.serialization.ValueSerializer -import sigmastate.utxo.{GetVar, DeserializeContext, DeserializeRegister, SelectField} +import sigmastate.serialization.{ConstantSerializer, SigmaSerializer, ValueSerializer} +import sigmastate.utxo.{DeserializeContext, DeserializeRegister, GetVar, SelectField} object SigmaPredef { @@ -180,6 +180,21 @@ object SigmaPredef { Seq(ArgInfo("", ""))) ) + val FromBase16Func = PredefinedFunc("fromBase16", + Lambda(Array(paramT), Array("input" -> SString), tT, None), + PredefFuncInfo( + { case (Ident(_, SFunc(_, tpe, _)), Seq(arg: EvaluatedValue[SString.type]@unchecked)) => + val bytes = Base16.decode(arg.value).get + val r = SigmaSerializer.startReader(bytes) + val ser = ConstantSerializer(DeserializationSigmaBuilder) + val res = ser.parse(r) + if (res.tpe != tpe) throw new InvalidArguments(s"Types doesn't match: declared $tpe, actual: ${res.tpe}") + res.asInstanceOf[Values.ConstantNode[SType]] + }), + OperationInfo(Constant, "", + Seq(ArgInfo("", ""))) + ) + val FromBase58Func = PredefinedFunc("fromBase58", Lambda(Array("input" -> SString), SByteArray, None), PredefFuncInfo( @@ -381,6 +396,7 @@ object SigmaPredef { SigmaPropFunc, GetVarFunc, DeserializeFunc, + FromBase16Func, FromBase64Func, FromBase58Func, Blake2b256Func, diff --git a/sigmastate/src/main/scala/sigmastate/types.scala b/sigmastate/src/main/scala/sigmastate/types.scala index f7acb838d2..c4423b3153 100644 --- a/sigmastate/src/main/scala/sigmastate/types.scala +++ b/sigmastate/src/main/scala/sigmastate/types.scala @@ -248,6 +248,36 @@ object SType { case _ => sys.error(s"Unknown type $tpe") } + def stringRepr(tpe: SType): String = tpe match { + case SBoolean => "Boolean" + case SByte => "Byte" + case SShort => "Short" + case SInt => "Int" + case SLong => "Long" + case SBigInt => "BigInt" + case SBox => "Box" + case SSigmaProp => "SigmaProp" + case SCollectionType(elemType) => "Coll[" + stringRepr(elemType) + "]" + case STuple(items) => items.map(stringRepr).mkString("(", ", ", ")") + case SContext => "Context" + case SOption(elemType) => "Option[" + stringRepr(elemType) + "]" + case SGroupElement => "GroupElement" + case SPreHeader => "PreHeader" + case SString => "String" + case SAny => "Any" + case SAvlTree => "AvlTree" + case SUnit => "Unit" + case SHeader => "Header" + case SGlobal => "Global" + // Missing serializer + case SFunc(tDom, tRange, tpeParams) => tDom.map(stringRepr).mkString("(", ", ", ")") + " => " + stringRepr(tRange) + // Missing serializer, not part of final ErgoTree, must be assigned some type during typing phase. + case NoType => "Error: NoType" + // Not used in final ergo tree + case STypeApply(_, _) => "Error: STypeApply node should be eliminated during compilation." + case STypeVar(_) => "Error: STypeVar node should be eliminated during compilation." + } + implicit class STypeOps(val tpe: SType) extends AnyVal { def isCollectionLike: Boolean = tpe.isInstanceOf[SCollection[_]] def isCollection: Boolean = tpe.isInstanceOf[SCollectionType[_]] diff --git a/sigmastate/src/test/scala/sigmastate/lang/SigmaCompilerTest.scala b/sigmastate/src/test/scala/sigmastate/lang/SigmaCompilerTest.scala index e9f1557ab9..640e4289c3 100644 --- a/sigmastate/src/test/scala/sigmastate/lang/SigmaCompilerTest.scala +++ b/sigmastate/src/test/scala/sigmastate/lang/SigmaCompilerTest.scala @@ -2,16 +2,16 @@ package sigmastate.lang import org.ergoplatform.ErgoAddressEncoder.TestnetNetworkPrefix import org.ergoplatform._ -import scorex.util.encode.Base58 +import scorex.util.encode.{Base16, Base58} import sigmastate.Values._ import sigmastate._ import sigmastate.helpers.SigmaTestingCommons import sigmastate.interpreter.Interpreter.ScriptEnv -import sigmastate.lang.Terms.{Apply, Ident, Lambda, MethodCall, ZKProofBlock} +import sigmastate.lang.Terms.{Apply, MethodCall, ZKProofBlock} import sigmastate.lang.exceptions.{CosterException, InvalidArguments, TyperException} -import sigmastate.serialization.ValueSerializer +import sigmastate.serialization.{ConstantSerializer, SigmaSerializer, ValueSerializer} import sigmastate.serialization.generators.ObjectGenerators -import sigmastate.utxo.{ByIndex, ExtractAmount, GetVar, SelectField} +import sigmastate.utxo.{ByIndex, ExtractAmount, GetVar} class SigmaCompilerTest extends SigmaTestingCommons with LangTests with ObjectGenerators { import CheckingSigmaBuilder._ @@ -135,6 +135,33 @@ class SigmaCompilerTest extends SigmaTestingCommons with LangTests with ObjectGe } property("fromBaseX") { + val constants = listOfConstantNodeGen.sample.get + val table = Table("constant", constants: _*) + forAll(table) { const => + val humanSType = SType.stringRepr(const.tpe) + val w = SigmaSerializer.startWriter() + val ser = ConstantSerializer(DeserializationSigmaBuilder) + ser.serialize(const, w) + val encodedConstant = Base16.encode(w.toBytes) + + // declared type equals parsed type + comp(s""" fromBase16[$humanSType]("$encodedConstant") """) shouldBe const + + // no declared type specified as type parameter + assertExceptionThrown( + comp(s""" fromBase16("$encodedConstant") """), + e => e.isInstanceOf[InvalidArguments] && e.getMessage.contains(s"Types doesn't match: declared T, actual: ${const.tpe}") + ) + + // declared type differs from parsed type + val tpe = primTypeGen.retryUntil(_.tpe != const.tpe).sample.get + val declaredSType = SType.stringRepr(tpe) + assertExceptionThrown( + comp(s""" fromBase16[$declaredSType]("$encodedConstant") """), + e => e.isInstanceOf[InvalidArguments] && e.getMessage.contains(s"Types doesn't match: declared $tpe, actual: ${const.tpe}") + ) + } + comp(""" fromBase58("r") """) shouldBe ByteArrayConstant(Array[Byte](49)) comp(""" fromBase64("MQ") """) shouldBe ByteArrayConstant(Array[Byte](49)) comp(""" fromBase64("M" + "Q") """) shouldBe ByteArrayConstant(Array[Byte](49)) diff --git a/sigmastate/src/test/scala/sigmastate/lang/SigmaParserTest.scala b/sigmastate/src/test/scala/sigmastate/lang/SigmaParserTest.scala index df9f900633..791f91a6a7 100644 --- a/sigmastate/src/test/scala/sigmastate/lang/SigmaParserTest.scala +++ b/sigmastate/src/test/scala/sigmastate/lang/SigmaParserTest.scala @@ -3,7 +3,7 @@ package sigmastate.lang import fastparse.core.Parsed import org.ergoplatform.{ErgoAddressEncoder, ErgoBox} import org.scalatest.prop.PropertyChecks -import org.scalatest.{PropSpec, Matchers} +import org.scalatest.{Matchers, PropSpec} import sigmastate.SCollection._ import sigmastate.Values._ import sigmastate._ @@ -11,8 +11,9 @@ import sigmastate.lang.SigmaPredef.PredefinedFuncRegistry import sigmastate.lang.Terms._ import sigmastate.lang.syntax.ParserException import sigmastate.serialization.OpCodes +import sigmastate.serialization.generators.ObjectGenerators -class SigmaParserTest extends PropSpec with PropertyChecks with Matchers with LangTests { +class SigmaParserTest extends PropSpec with PropertyChecks with Matchers with LangTests with ObjectGenerators { import StdSigmaBuilder._ private val predefFuncRegistry = new PredefinedFuncRegistry(StdSigmaBuilder) @@ -600,6 +601,15 @@ class SigmaParserTest extends PropSpec with PropertyChecks with Matchers with La } property("fromBaseX string decoding") { + val constants = listOfConstantNodeGen.sample.get + val table = Table("Constants", constants: _*) + forAll(table) { const => + val stringConst = stringConstGen.sample.get + parse(s"""fromBase16("${stringConst.value}")""") shouldBe Apply(FromBase16Func.symNoType, IndexedSeq(stringConst)) + val declaredType = SType.stringRepr(const.tpe) + parse(s"""fromBase16[$declaredType]("${stringConst.value}")""") shouldBe + Apply(ApplyTypes(FromBase16Func.symNoType, Seq(const.tpe)), IndexedSeq(stringConst)) + } parse("""fromBase58("111")""") shouldBe Apply(FromBase58Func.symNoType, IndexedSeq(StringConstant("111"))) parse("""fromBase64("111")""") shouldBe Apply(FromBase64Func.symNoType, IndexedSeq(StringConstant("111"))) } diff --git a/sigmastate/src/test/scala/sigmastate/lang/SigmaTyperTest.scala b/sigmastate/src/test/scala/sigmastate/lang/SigmaTyperTest.scala index e29977cd09..1c27cb50e5 100644 --- a/sigmastate/src/test/scala/sigmastate/lang/SigmaTyperTest.scala +++ b/sigmastate/src/test/scala/sigmastate/lang/SigmaTyperTest.scala @@ -4,6 +4,7 @@ import org.ergoplatform.ErgoAddressEncoder.TestnetNetworkPrefix import org.ergoplatform._ import org.scalatest.prop.PropertyChecks import org.scalatest.{Matchers, PropSpec} +import scorex.util.encode.Base16 import sigmastate.SCollection._ import sigmastate.Values._ import sigmastate._ @@ -13,9 +14,9 @@ import sigmastate.interpreter.CryptoConstants import sigmastate.interpreter.Interpreter.ScriptEnv import sigmastate.lang.SigmaPredef._ import sigmastate.lang.Terms.Select -import sigmastate.lang.exceptions.TyperException +import sigmastate.lang.exceptions.{InvalidArguments, TyperException} import sigmastate.lang.syntax.ParserException -import sigmastate.serialization.ErgoTreeSerializer +import sigmastate.serialization.{ConstantSerializer, ErgoTreeSerializer, SigmaSerializer} import sigmastate.serialization.generators.ObjectGenerators import sigmastate.utxo.{Append, ExtractCreationInfo} @@ -125,6 +126,32 @@ class SigmaTyperTest extends PropSpec with PropertyChecks with Matchers with Lan typecheck(env, "ZKProof { sigmaProp(HEIGHT > 1000) }") shouldBe SBoolean } + property("fromBase16") { + val constants = listOfConstantNodeGen.sample.get + val table = Table("Constants", constants: _*) + forAll(table){ const => + val humanSType = SType.stringRepr(const.tpe) + val w = SigmaSerializer.startWriter() + val ser = ConstantSerializer(DeserializationSigmaBuilder) + ser.serialize(const, w) + val encodedConstant = Base16.encode(w.toBytes) + // successful case + typecheck(env, s"""fromBase16[$humanSType]("$encodedConstant")""") shouldBe const.tpe + // no declared type specified as type parameter + assertExceptionThrown( + typecheck(env, s"""fromBase16("$encodedConstant")"""), + e => e.isInstanceOf[InvalidArguments] && e.getMessage.contains(s"Types doesn't match: declared T, actual: ${const.tpe}") + ) + // declared type differs from parsed type + val tpe = primTypeGen.retryUntil(_.tpe != const.tpe).sample.get + val declaredSType = SType.stringRepr(tpe) + assertExceptionThrown( + typecheck(env, s""" fromBase16[$declaredSType]("$encodedConstant") """), + e => e.isInstanceOf[InvalidArguments] && e.getMessage.contains(s"Types doesn't match: declared $tpe, actual: ${const.tpe}") + ) + } + } + property("val constructs") { typecheck(env, "{val X = 10; X > 2}") shouldBe SBoolean typecheck(env, """{val X = 10; X >= X}""".stripMargin) shouldBe SBoolean diff --git a/sigmastate/src/test/scala/sigmastate/serialization/generators/ObjectGenerators.scala b/sigmastate/src/test/scala/sigmastate/serialization/generators/ObjectGenerators.scala index 04728c3ae1..e6ade5b94b 100644 --- a/sigmastate/src/test/scala/sigmastate/serialization/generators/ObjectGenerators.scala +++ b/sigmastate/src/test/scala/sigmastate/serialization/generators/ObjectGenerators.scala @@ -67,7 +67,7 @@ trait ObjectGenerators extends TypeGenerators implicit lazy val arbByteArrayConstant: Arbitrary[CollectionConstant[SByte.type]] = Arbitrary(byteArrayConstGen) implicit lazy val arbGroupElementConstant: Arbitrary[GroupElementConstant] = Arbitrary(groupElementConstGen) implicit lazy val arbBoxConstant: Arbitrary[BoxConstant] = Arbitrary(boxConstantGen) - implicit lazy val arbAvlTreeConstant: Arbitrary[AvlTreeConstant] = Arbitrary(avlTreeConstantGen) + implicit lazy val arbAvlTreeConstant: Arbitrary[AvlTreeConstant] = Arbitrary(avlTreeConstGen) implicit lazy val arbBigIntConstant: Arbitrary[BigIntConstant] = Arbitrary(bigIntConstGen) implicit lazy val arbTaggedInt: Arbitrary[TaggedInt] = Arbitrary(taggedVar[SInt.type]) implicit lazy val arbTaggedLong: Arbitrary[TaggedLong] = Arbitrary(taggedVar[SLong.type]) @@ -111,10 +111,26 @@ trait ObjectGenerators extends TypeGenerators length <- Gen.chooseNum(1, 100) bytes <- Gen.listOfN(length, arbByte.arbitrary) } yield mkCollectionConstant[SByte.type](bytes.toArray, SByte) + val shortArrayConstGen: Gen[CollectionConstant[SShort.type]] = for { + length <- Gen.chooseNum(1, 100) + shorts <- Gen.listOfN(length, arbShort.arbitrary) + } yield mkCollectionConstant[SShort.type](shorts.toArray, SShort) val intArrayConstGen: Gen[CollectionConstant[SInt.type]] = for { length <- Gen.chooseNum(1, 100) ints <- Gen.listOfN(length, arbInt.arbitrary) } yield mkCollectionConstant[SInt.type](ints.toArray, SInt) + val longArrayConstGen: Gen[CollectionConstant[SLong.type]] = for { + length <- Gen.chooseNum(1, 100) + longs <- Gen.listOfN(length, arbLong.arbitrary) + } yield mkCollectionConstant[SLong.type](longs.toArray, SLong) + val bigIntArrayConstGen: Gen[CollectionConstant[SBigInt.type]] = for { + length <- Gen.chooseNum(1, 100) + bigInts <- Gen.listOfN(length, arbBigInt.arbitrary) + } yield mkCollectionConstant[SBigInt.type](bigInts.toArray, SBigInt) + val boolArrayConstGen: Gen[CollectionConstant[SBoolean.type]] = for { + length <- Gen.chooseNum(1, 100) + bools <- Gen.listOfN(length, arbBool.arbitrary) + } yield mkCollectionConstant[SBoolean.type](bools.toArray, SBoolean) val heightGen: Gen[Int] = Gen.chooseNum(0, 1000000) @@ -166,6 +182,10 @@ trait ObjectGenerators extends TypeGenerators val sigmaPropValueGen: Gen[SigmaPropValue] = Gen.oneOf(proveDlogGen.map(SigmaPropConstant(_)), proveDHTGen.map(SigmaPropConstant(_))) + val sigmaPropConstGen: Gen[SigmaPropConstant] = for { + p <- sigmaPropGen + } yield mkConstant[SSigmaProp.type](p, SSigmaProp) + val registerIdentifierGen: Gen[RegisterId] = Gen.oneOf(R0, R1, R2, R3, R4, R5, R6, R7, R8, R9) val taggedAvlTreeGen: Gen[TaggedAvlTree] = @@ -256,7 +276,7 @@ trait ObjectGenerators extends TypeGenerators def avlTreeGen: Gen[AvlTree] = avlTreeDataGen.map(SigmaDsl.avlTree) - def avlTreeConstantGen: Gen[AvlTreeConstant] = avlTreeGen.map { v => AvlTreeConstant(v) } + def avlTreeConstGen: Gen[AvlTreeConstant] = avlTreeGen.map { v => AvlTreeConstant(v) } implicit def arrayGen[T: Arbitrary : ClassTag]: Gen[Array[T]] = for { length <- Gen.chooseNum(1, 100) @@ -698,6 +718,10 @@ trait ObjectGenerators extends TypeGenerators implicit val arbHeader = Arbitrary(headerGen) + val headerConstantGen: Gen[Constant[SHeader.type]] = for { + h <- headerGen + } yield HeaderConstant(h) + val MaxHeaders = 2 def headersGen(stateRoot: AvlTree): Gen[Seq[Header]] = for { size <- Gen.chooseNum(0, MaxHeaders) @@ -723,6 +747,10 @@ trait ObjectGenerators extends TypeGenerators implicit val arbPreHeader = Arbitrary(preHeaderGen) + val preHeaderConstGen: Gen[Constant[SPreHeader.type]] = for { + ph <- preHeaderGen + } yield PreHeaderConstant(ph) + val ergoLikeTransactionGen: Gen[ErgoLikeTransaction] = for { inputBoxesIds <- Gen.nonEmptyListOf(boxIdGen) dataInputBoxIds <- Gen.listOf(boxIdGen) @@ -793,4 +821,28 @@ trait ObjectGenerators extends TypeGenerators activatedScriptVersion = activatedVersionInTests ).withErgoTreeVersion(ergoTreeVersionInTests) + val constantGen: Gen[Constant[SType]] = + Gen.oneOf( + booleanConstGen, + shortConstGen, + intConstGen, + longConstGen, + bigIntConstGen, + boxConstantGen, + sigmaPropConstGen, + groupElementConstGen, + stringConstGen, + avlTreeConstGen, + byteArrayConstGen, + shortArrayConstGen, + intArrayConstGen, + bigIntArrayConstGen, + longArrayConstGen, + boolArrayConstGen + // missing serializer + // headerConstGen, + // preHeaderConstGen, + ).map(_.asInstanceOf[Constant[SType]]) + + val listOfConstantNodeGen: Gen[List[Constant[SType]]] = Gen.listOf(constantGen) }