Skip to content

Commit

Permalink
Add PolicyBuilder (#56)
Browse files Browse the repository at this point in the history
* Add PolicyBuilder
Make slot a Long (not Int)
Add test for DISH policy

* Ensure only one Bound of each type exists.

* Correct the org name in sbt file
Remove redundant test
Make slots Long instead of Int
  • Loading branch information
mcsherrylabs authored Jul 15, 2022
1 parent 61d6ad9 commit adeb55b
Show file tree
Hide file tree
Showing 9 changed files with 183 additions and 31 deletions.
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ lazy val rootProject = (project in file("."))
IntegrationTest / dependencyClasspath := (IntegrationTest / dependencyClasspath).value ++ (Test / exportedProducts).value,
name:= "psg-cardano-wallet-api",
scalaVersion := "2.13.3",
organization := "solutions.iog",
organization := "iog.psg",
homepage := Some(url("https://github.com/input-output-hk/psg-cardano-wallet-api")),
scmInfo := Some(ScmInfo(url("https://github.com/input-output-hk/psg-cardano-wallet-api"), "scm:git@github.com:input-output-hk/psg-cardano-wallet-api.git")),
developers := List(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,8 +161,8 @@ case class CardanoCliApi(cardanoCli: CardanoCli)(implicit networkChooser: Networ
txOuts: NonEmptyList[TxOut],
maybeMetadata: Option[MetadataJson] = None,
maybeMinting: Option[(NonEmptyList[NativeAsset], Policy)] = None,
invalidBefore: Option[Int] = None,
invalidHereafter: Option[Int] = None,
invalidBefore: Option[Long] = None,
invalidHereafter: Option[Long] = None,
): CliApiRequest[Tx] = new CliApiRequest[Tx] {
override def execute: Future[Tx] = Future {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,13 @@ case class CardanoCliCmdTransactionBuildRaw(protected val builder: ProcessBuilde
/**
* Time that transaction is valid from (in slots)
*/
def invalidBefore(slot: Int): CardanoCliCmdTransactionBuildRaw =
def invalidBefore(slot: Long): CardanoCliCmdTransactionBuildRaw =
withParam("--invalid-before", slot)

/**
* Time that transaction is valid until (in slots)
*/
def invalidHereafter(slot: Int): CardanoCliCmdTransactionBuildRaw =
def invalidHereafter(slot: Long): CardanoCliCmdTransactionBuildRaw =
withParam("--invalid-hereafter", slot)

private def mintParam(assets: NonEmptyList[NativeAsset]): String = {
Expand Down
18 changes: 2 additions & 16 deletions src/main/scala/iog/psg/cardano/experimental/cli/model/Policy.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import iog.psg.cardano.experimental.cli.util.RandomTempFolder

case class PolicyId(value: String) extends AnyVal


case class Policy(
scripts: NonEmptyList[Policy.Script],
kind: Policy.Kind
Expand All @@ -21,21 +22,6 @@ case class Policy(

object Policy {

def all(scripts: NonEmptyList[Script])(implicit f: RandomTempFolder): Policy =
Policy(scripts, Policy.Kind.All)

def any(scripts: NonEmptyList[Script])(implicit f: RandomTempFolder): Policy =
Policy(scripts, Policy.Kind.Any)

def atLeast(value: Int, scripts: NonEmptyList[Script])(implicit f: RandomTempFolder): Policy =
Policy(scripts, Policy.Kind.AtLeast(value))

def after(slot: Int, signatures: NonEmptyList[Script.Signature])(implicit f: RandomTempFolder): Policy =
all(Script.Bound(slot, after = true) :: signatures)

def before(slot: Int, signatures: NonEmptyList[Script.Signature])(implicit f: RandomTempFolder): Policy =
all(Script.Bound(slot, after = false) :: signatures)

sealed trait Kind
object Kind {
case object All extends Kind
Expand All @@ -46,7 +32,7 @@ object Policy {
sealed trait Script
object Script {
case class Signature(keyHash: KeyHash[_ <: KeyType]) extends Script
case class Bound(slot: Int, after: Boolean) extends Script
case class Bound(slot: Long, after: Boolean) extends Script
}

def asString(policy: Policy): String = codec(policy.rootFolder)(policy).noSpaces
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package iog.psg.cardano.experimental.cli.model

import cats.data.NonEmptyList
import iog.psg.cardano.experimental.cli.api.KeyType
import iog.psg.cardano.experimental.cli.model.Policy.Script
import iog.psg.cardano.experimental.cli.util.RandomTempFolder

case class PolicyBuilder(
private val scripts: Seq[Script] = Seq.empty,
private val kind: Policy.Kind = Policy.Kind.All
) {

def withAllSigsRequired(): PolicyBuilder = this.copy(kind = Policy.Kind.All)
def withAnySigRequired(): PolicyBuilder = this.copy(kind = Policy.Kind.Any)

def withAtLeastSigsRequired(numberOfSigsRequired: Int): PolicyBuilder =
this.copy(kind = Policy.Kind.AtLeast(numberOfSigsRequired))

def withSignatureOf(keyHash: KeyHash[_ <: KeyType]): PolicyBuilder =
this.copy(scripts = scripts :+ Policy.Script.Signature(keyHash))


def withBeforeConstraint(slot: Long): PolicyBuilder =
this.copy(scripts = scripts.filter {
case Policy.Script.Bound(_, false) => false
case _ => true
} :+ Policy.Script.Bound(slot, after = false))

def withAfterConstraint(slot: Long): PolicyBuilder =
this.copy(scripts = scripts.filter {
case Policy.Script.Bound(_, true) => false
case _ => true
} :+ Policy.Script.Bound(slot, after = true))

def build(implicit rootFolder: RandomTempFolder): Policy = {
require(
scripts.exists {
case _: Script.Signature => true
case _ => false
},
"There must be a at least one script of type Signature!"
)

Policy(NonEmptyList.of[Policy.Script](scripts.head, scripts.tail: _*), kind)

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import cats.data.NonEmptyList
import iog.psg.cardano.experimental.cli.api.Ops.CliApiReqOps
import iog.psg.cardano.experimental.cli.api._
import iog.psg.cardano.experimental.cli.command.CardanoCli
import iog.psg.cardano.experimental.cli.model.{Key, Policy, TxIn, TxOut}
import iog.psg.cardano.experimental.cli.model.{Key, PolicyBuilder, TxIn, TxOut}
import iog.psg.cardano.experimental.cli.processrunner.{BlockingProcessResult, BlockingProcessRunner}
import iog.psg.cardano.experimental.cli.util.RandomFolderFactory
import org.scalatest.BeforeAndAfterAll
Expand Down Expand Up @@ -94,12 +94,12 @@ class CardanoCliApiSpec extends AnyFlatSpec with Matchers with ScalaFutures with
s"[./cardano-cli, address, key-hash, --payment-verification-key-file, ${policyVerKey.file.toString}]",
)

val policy = Policy.all(
NonEmptyList.of(
Policy.Script.Signature(paymentVerKeyHash),
Policy.Script.Signature(policyVerKeyHash)
)
)

val policy = PolicyBuilder()
.withSignatureOf(paymentVerKeyHash)
.withSignatureOf(policyVerKeyHash)
.withAllSigsRequired()
.build

val policyId = sut
.policyId(policy)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class MetadataJsonSpec extends AnyFlatSpec with Matchers with ScalaFutures {

implicit val rootFolder = RandomTempFolder(Files.createTempDirectory("testNFTMeta"))

val meta = NftMetadataJson(policyId, nfts)
val meta = NftMetadataJson(policyId, nfts.toIndexedSeq)

"the encoding" should "work as expected" in {
val str = asString(meta)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package iog.psg.cardano.experimental.cli.model

import iog.psg.cardano.experimental.cli.api.Verification
import iog.psg.cardano.experimental.cli.model.Policy.Script.{Bound, Signature}
import iog.psg.cardano.experimental.cli.util.RandomTempFolder
import org.scalatest.EitherValues
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers

import java.nio.file.Path

class PolicyBuilderSpec extends AnyFlatSpec with Matchers with EitherValues {

implicit val rootFolder: RandomTempFolder = RandomTempFolder(Path.of("."))

"PolicyBuilder" should "fail if no signature" in {

intercept[IllegalArgumentException] {
PolicyBuilder().build
}

intercept[IllegalArgumentException] {
PolicyBuilder().withAfterConstraint(3).build
}

}

it should "create a minimal policy with one key" in {
val kh = KeyHash[Verification]("somestring")
val p = PolicyBuilder().withSignatureOf(kh).build
p.scripts.toList shouldBe List(Signature(kh))
p.kind shouldBe Policy.Kind.All
}

it should "respect the `kind`" in {
val kh = KeyHash[Verification]("somestring")
var p = PolicyBuilder().withSignatureOf(kh).withAllSigsRequired().build

p.scripts.toList shouldBe List(Signature(kh))
p.kind shouldBe Policy.Kind.All

p = PolicyBuilder().withSignatureOf(kh).withAnySigRequired().build

p.scripts.toList shouldBe List(Signature(kh))
p.kind shouldBe Policy.Kind.Any

p = PolicyBuilder().withSignatureOf(kh).withAtLeastSigsRequired(2).build

p.scripts.toList shouldBe List(Signature(kh))
p.kind shouldBe Policy.Kind.AtLeast(2)
}

it should "aggregrate signatures" in {
val kh = KeyHash[Verification]("somestring")
val kh2 = KeyHash[Verification]("somestring2")
val p = PolicyBuilder()
.withSignatureOf(kh)
.withSignatureOf(kh2)
.build

p.scripts.toList should contain (Signature(kh))
p.scripts.toList should contain (Signature(kh2))

}

it should "respect slot bounds" in {
val kh = KeyHash[Verification]("somestring")

val p = PolicyBuilder()
.withSignatureOf(kh)
.withAfterConstraint(400)
.withBeforeConstraint(800)
.build

p.scripts.toList should contain (Signature(kh))
p.scripts.toList should contain (Bound(400, after = true))
p.scripts.toList should contain (Bound(800, after = false))
p.scripts.toList.size shouldBe(3)
}

it should "only allow one before and one after slot bound" in {
val kh = KeyHash[Verification]("somestring")

val p = PolicyBuilder()
.withSignatureOf(kh)
.withAfterConstraint(400)
.withAfterConstraint(200)
.withBeforeConstraint(800)
.withBeforeConstraint(900)
.build

p.scripts.toList should contain (Signature(kh))
p.scripts.toList should contain (Bound(200, after = true))
p.scripts.toList should contain (Bound(900, after = false))
p.scripts.toList.size shouldBe(3)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ package iog.psg.cardano.experimental.cli.model
import cats.data.NonEmptyList
import io.circe.Json
import io.circe.syntax._
import iog.psg.cardano.experimental.cli.api.Verification
import iog.psg.cardano.experimental.cli.model.Policy.Script.{Bound, Signature}
import iog.psg.cardano.experimental.cli.model.Policy.{Kind, Script, asString}
import iog.psg.cardano.experimental.cli.util.RandomTempFolder
import org.scalatest.Assertion
import org.scalatest.{Assertion, EitherValues}
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers

class PolicySpec extends AnyFlatSpec with Matchers {
class PolicySpec extends AnyFlatSpec with Matchers with EitherValues {

"Policy" should "be properly json encoded" in {

Expand All @@ -34,4 +36,23 @@ class PolicySpec extends AnyFlatSpec with Matchers {

List(Policy.Kind.All, Policy.Kind.AtLeast(10), Policy.Kind.Any).foreach(assert)
}

"A policy added by string" should "parse correctly" in {

val keyHash = "3e6ea29f537e0783f469077723b9ef6f740993e84f616db36ad355a9"
val slot = 56894689

val dish =
s"""{\n \"type\": \"all\",\n \"scripts\":\n [\n {\n \"type\": \"after\",\n \"slot\": ${slot}\n },\n {\n \"type\": \"sig\",\n \"keyHash\": \"${keyHash}\"\n }\n ]\n }"""
implicit val dummy: RandomTempFolder = RandomTempFolder(null)

val policyOrError = Policy.fromString(dish)

val policy = policyOrError.value

policy.kind shouldBe Policy.Kind.All
policy.scripts.toList should contain (Signature(KeyHash[Verification](keyHash)))
policy.scripts.toList should contain (Bound(slot, after = true))
policy.scripts.toList.size shouldBe 2
}
}

0 comments on commit adeb55b

Please sign in to comment.