Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Boolean.toByte operation #932

Open
wants to merge 9 commits into
base: v6.0.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ This library is
on Maven repository and can be added to the SBT configuration of Scala project.

```scala
libraryDependencies += "org.scorexfoundation" %% "sigma-state" % "5.0.6"
libraryDependencies += "org.scorexfoundation" %% "sigma-state" % "6.0.0"
```

## Repository Organization
Expand Down
14 changes: 12 additions & 2 deletions core/shared/src/main/scala/sigma/VersionContext.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package sigma

import VersionContext.JitActivationVersion
import VersionContext.{V6SoftForkVersion, JitActivationVersion}

import scala.util.DynamicVariable

Expand All @@ -21,6 +21,10 @@ case class VersionContext(activatedVersion: Byte, ergoTreeVersion: Byte) {
/** @return true, if the activated script version of Ergo protocol on the network is
* greater than v1. */
def isJitActivated: Boolean = activatedVersion >= JitActivationVersion

/** @return true, if the activated script version of Ergo protocol on the network is
* including Evolution update. */
def isV6SoftForkActivated: Boolean = activatedVersion >= V6SoftForkVersion
}

object VersionContext {
Expand All @@ -31,13 +35,19 @@ object VersionContext {
* - version 3.x this value must be 0
* - in v4.0 must be 1
* - in v5.x must be 2
* - in 6.x must be 3
* etc.
*/
val MaxSupportedScriptVersion: Byte = 2 // supported versions 0, 1, 2
val MaxSupportedScriptVersion: Byte = 3 // supported versions 0, 1, 2, 3

/** The first version of ErgoTree starting from which the JIT costing interpreter is used. */
val JitActivationVersion: Byte = 2

/**
* The version of ErgoTree corresponding to "evolution" (6.0) soft-fork
*/
val V6SoftForkVersion: Byte = 3

private val _defaultContext = VersionContext(
activatedVersion = 1/* v4.x */,
ergoTreeVersion = 1
Expand Down
9 changes: 4 additions & 5 deletions core/shared/src/test/scala/sigma/VersionTesting.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@ import scala.util.DynamicVariable

trait VersionTesting {

/** In v5.x we run test for only one activated version on the network (== 2).
* In the branch for v6.0 the new version 3 should be added so that the tests run for both.
*/
/** Tests run for all supported versions starting from 0. */
protected val activatedVersions: Seq[Byte] =
(0 to VersionContext.MaxSupportedScriptVersion).map(_.toByte).toArray[Byte]
ross-weir marked this conversation as resolved.
Show resolved Hide resolved

Expand Down Expand Up @@ -41,9 +39,10 @@ trait VersionTesting {
_ + 1) { j =>
val treeVersion = ergoTreeVers(j)
// for each tree version up to currently activated, set it up and execute block
_currErgoTreeVersion.withValue(treeVersion)(block)
_currErgoTreeVersion.withValue(treeVersion) {
VersionContext.withVersions(activatedVersion, treeVersion)(block)
}
Copy link
Member

@aslesarenko aslesarenko Dec 2, 2023

Choose a reason for hiding this comment

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

What is the reason for this change?

}

}
}
}
Expand Down
20 changes: 13 additions & 7 deletions interpreter/shared/src/main/scala/sigmastate/types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ object SType {
}

implicit class STypeOps(val tpe: SType) extends AnyVal {
def isBoolean: Boolean = tpe.isInstanceOf[SBoolean.type]
def isCollectionLike: Boolean = tpe.isInstanceOf[SCollection[_]]
def isCollection: Boolean = tpe.isInstanceOf[SCollectionType[_]]
def isOption: Boolean = tpe.isInstanceOf[SOption[_]]
Expand Down Expand Up @@ -907,13 +908,18 @@ case object SBoolean extends SPrimType with SEmbeddable with SLogical with SProd
override val reprClass: RClass[_] = RClass(classOf[Boolean])

val ToByte = "toByte"
protected override def getMethods() = super.getMethods()
/* TODO soft-fork: https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479
++ Seq(
SMethod(this, ToByte, SFunc(this, SByte), 1)
.withInfo(PropertyCall, "Convert true to 1 and false to 0"),
)
*/

lazy val ToByteMethod: SMethod = SMethod(
this, ToByte, SFunc(this, SByte), 1, FixedCost(JitCost(1)))
.withInfo(PropertyCall, "Convert true to 1 and false to 0")

protected override def getMethods(): Seq[SMethod] = {
if (VersionContext.current.isV6SoftForkActivated) {
super.getMethods() ++ Seq(ToByteMethod)
} else {
super.getMethods()
}
}
}

/** Descriptor of ErgoTree type `Byte` - 8-bit signed integer. */
Expand Down
6 changes: 6 additions & 0 deletions sc/shared/src/main/scala/scalan/primitives/LogicalOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ trait LogicalOps extends Base { self: Scalan =>
override def applySeq(x: Boolean): Int = if (x) 1 else 0
}

/** Boolean to Byte conversion unary operation. */
val BooleanToByte = new UnOp[Boolean, Byte]("ToByte") {
override def applySeq(x: Boolean): Byte = if (x) 1.toByte else 0.toByte
}

/** Extension methods over `Ref[Boolean]`. */
implicit class RepBooleanOps(value: Ref[Boolean]) {
def &&(y: Ref[Boolean]): Ref[Boolean] = And(value, y)
Expand All @@ -40,6 +45,7 @@ trait LogicalOps extends Base { self: Scalan =>

def unary_!() : Ref[Boolean] = Not(value)
def toInt: Ref[Int] = BooleanToInt(value)
def toByte: Ref[Byte] = BooleanToByte(value)
}


Expand Down
5 changes: 5 additions & 0 deletions sc/shared/src/main/scala/sigmastate/eval/GraphBuilding.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package sigmastate.eval

import org.ergoplatform._
import scalan.MutableLazy
import sigma.VersionContext
import sigma.data.ExactIntegral.{ByteIsExactIntegral, IntIsExactIntegral, LongIsExactIntegral, ShortIsExactIntegral}
import sigma.data.ExactOrdering.{ByteIsExactOrdering, IntIsExactOrdering, LongIsExactOrdering, ShortIsExactOrdering}
import sigma.util.Extensions.ByteOps
Expand Down Expand Up @@ -509,6 +510,10 @@ trait GraphBuilding extends SigmaLibrary { IR: IRContext =>
else
error(s"The type of $obj is expected to be Collection to select 'size' property", obj.sourceContext.toOption)

case Select(obj, SBoolean.ToByte, _) if obj.tpe.isBoolean && VersionContext.current.isV6SoftForkActivated =>
val bool = eval(obj.asBoolValue)
Copy link
Contributor Author

@ross-weir ross-weir Nov 8, 2023

Choose a reason for hiding this comment

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

Not sure if this can be done inline like this or maybe we need to add a class in transformers and a new (or existing?) OpCode for boolean.toByte

Copy link
Member

Choose a reason for hiding this comment

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

I guess opcode is not needed, from investigating MethodCallSerializer

Copy link
Member

Choose a reason for hiding this comment

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

yes, opcode is not necessary. Another question here is how to make sure VersionContext.current has correct version in place when this code is executed as part of the application/tooling compiling ErgoTree?

bool.toByte

// Rule: proof.isProven --> IsValid(proof)
case Select(p, SSigmaProp.IsProven, _) if p.tpe == SSigmaProp =>
eval(SigmaPropIsProven(p.asSigmaProp))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,6 @@ class ErgoTreeSpecification extends SigmaDslTesting with ContractsTestkit {
// The following table should be made dependent on HF activation
val methods = Table(
("typeId", "methods", "CanHaveMethods"),
(SBoolean.typeId, Seq.empty[MInfo], true),
(SByte.typeId, Seq.empty[MInfo], false),
(SShort.typeId, Seq.empty[MInfo], false),
(SInt.typeId, Seq.empty[MInfo], false),
Expand All @@ -233,6 +232,14 @@ class ErgoTreeSpecification extends SigmaDslTesting with ContractsTestkit {
), true)
},

{
import SBoolean._
if (VersionContext.current.isV6SoftForkActivated) {
(SBoolean.typeId, Seq(MInfo(1, ToByteMethod)), true)
} else {
(SBoolean.typeId, Seq.empty[MInfo], true)
}
},
{ // SBigInt inherit methods from SNumericType.methods
// however they are not resolvable via SBigInt.typeId
import SNumericType._
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import sigmastate.lang.Terms._
import org.ergoplatform._
import org.scalatest.BeforeAndAfterAll
import scorex.util.encode.Base58
import sigma.VersionContext.V6SoftForkVersion
import sigma.util.Extensions.IntOps
import sigmastate.crypto.CryptoConstants
import sigmastate.helpers.{CompilerTestingCommons, ErgoLikeContextTesting, ErgoLikeTestInterpreter, ErgoLikeTestProvingInterpreter}
Expand Down Expand Up @@ -202,6 +203,21 @@ class TestingInterpreterSpecification extends CompilerTestingCommons
|}""".stripMargin)
}

property("Evaluate boolean casting ops") {
Copy link
Contributor Author

@ross-weir ross-weir Nov 23, 2023

Choose a reason for hiding this comment

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

This property runs the test block for each activated script version / ergotree version. Something is causing SigmaParser to execute a different code path between test cases, SBoolean.getMethods is only called at the start when version is v0 so it returns Seq() for methods, presumably even for v3 since getMethods isn't called when the version is set to v3 and the test fails with a "Missing method" error.

I'm not sure if this is intended and methods are cached somewhere or if there's a global var not getting reset between test cases, or something else. Need to debug deeper

Copy link
Member

Choose a reason for hiding this comment

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

There's caching indeed, in types.scala: 370

  lazy val methods: Seq[SMethod] = {

changing lazy val to def is fixing tests.

I think methods should be reworked in a way that for every version methods should be cached, and then def methods should return value from cache for VersionContext.current (or calculate value and cache it).

Copy link
Member

Choose a reason for hiding this comment

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

yes, the cache should be versioned, the suggested change sounds good.
Basically the cache needs to change from Seq[SMethod] to a function Version => Seq[SMethod], which can be implemented with the help of Map[Byte, Seq[SMethod]] under the hood.

val source =
"""
|{
| val bool: Boolean = true
| bool.toByte == 1.toByte && false.toByte == 0.toByte
|}
|""".stripMargin
if (activatedVersionInTests < V6SoftForkVersion) {
assertExceptionThrown(testEval(source), rootCauseLike[sigmastate.exceptions.MethodNotFound]("Cannot find method 'toByte'"))
} else {
testEval(source)
}
}

property("Evaluate numeric casting ops") {
def testWithCasting(castSuffix: String): Unit = {
testEval(s"OUTPUTS.size.toByte.$castSuffix == 0.$castSuffix")
Expand Down