Skip to content

Commit 664c4d2

Browse files
committed
init two scalus validators
add helper function to make json representation of transactions fix utilities plumbing add scalus test case add utf-8 flag to docker exec hacking attempt to fix unlock clean up fee calculation
1 parent e9fbf52 commit 664c4d2

24 files changed

Lines changed: 670 additions & 15 deletions

.gitignore

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@ utilities/
88
node-*/
99
/scripts/poison
1010
/scripts/poison/
11-
/plutus
11+
plutus/
1212

1313
# Python virtual environment
1414
venv/
1515
__pycache__/
1616
*.pyc
1717

1818
# Config file (user-specific, example is tracked)
19-
config/cardano-node-config.json
19+
config/cardano-node-config.json
20+
21+
.scala-build/

docker-compose.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ services:
2020
- ./keys:/keys
2121
- ./txs:/txs
2222
- ./dumps:/dumps
23+
- ./plutus:/plutus
2324
- ./utilities:/utilities
2425
logging:
2526
driver: "json-file"

scalus/.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
target/
2+
project/target/
3+
project/project/
4+
.bsp/
5+
.metals/
6+
.idea/

scalus/build.sbt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
val scalusVersion = "0.15.0"
2+
3+
ThisBuild / scalaVersion := "3.3.7"
4+
ThisBuild / scalacOptions ++= Seq("-feature", "-deprecation", "-unchecked")
5+
6+
addCompilerPlugin("org.scalus" %% "scalus-plugin" % scalusVersion)
7+
8+
lazy val root = (project in file("."))
9+
.settings(
10+
name := "pv11-validators",
11+
libraryDependencies ++= Seq(
12+
"org.scalus" %% "scalus" % scalusVersion,
13+
"org.scalus" %% "scalus-cardano-ledger" % scalusVersion,
14+
"org.scalus" %% "scalus-testkit" % scalusVersion % Test,
15+
"org.scalatest" %% "scalatest" % "3.2.19" % Test,
16+
"org.scalatestplus" %% "scalacheck-1-18" % "3.2.19.0" % Test
17+
)
18+
)

scalus/compile.sh

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#!/bin/bash
2+
set -euo pipefail
3+
4+
# Compile PV11 Plutus validators using Scalus 0.15.0
5+
# Outputs .plutus TextEnvelope JSON files to ../plutus/pv11/
6+
7+
script_dir="$(cd "$(dirname "$0")" && pwd)"
8+
cd "$script_dir"
9+
10+
if ! command -v sbt &> /dev/null; then
11+
echo "Error: sbt not found. Install sbt to compile Scalus validators."
12+
echo "See https://www.scala-sbt.org/download/"
13+
exit 1
14+
fi
15+
16+
output_dir="../plutus/pv11"
17+
mkdir -p "$output_dir"
18+
19+
echo "Compiling PV11 validators with Scalus..."
20+
sbt "run $output_dir"
21+
22+
echo ""
23+
echo "Compiled validators:"
24+
ls -la "$output_dir/"*.plutus

scalus/project/build.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
sbt.version=1.10.11

scalus/project/plugins.sbt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
// No sbt plugins required - Scalus compiler plugin is added via addCompilerPlugin in build.sbt
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package pv11
2+
3+
import scalus.*
4+
import scalus.uplc.builtin.{Data, BuiltinArray, BuiltinList}
5+
import scalus.uplc.builtin.Data.{FromData, ToData}
6+
import scalus.uplc.builtin.Builtins.*
7+
import scalus.cardano.onchain.plutus.prelude.*
8+
import scalus.cardano.onchain.plutus.v3.*
9+
10+
/** CIP-138: Array Type validator Example
11+
*
12+
* Datum is a Plutus Data list of integers stored on-chain.
13+
* Redeemer specifies an index and expected value.
14+
* The validator converts the list to an array (O(1) access),
15+
* looks up the element at the given index, and asserts it
16+
* matches the expected value.
17+
*
18+
* Test case: Array [10, 20, 30, 40, 50], index 2, expect 30
19+
*
20+
* Note: Array builtins (listToArray, indexArray) are PV11 features.
21+
*/
22+
23+
case class ArrayRedeemer(index: BigInt, expectedValue: BigInt) derives FromData, ToData
24+
25+
@Compile object ArrayRedeemer
26+
27+
@Compile
28+
object ArrayValidator {
29+
inline def validate(scData: Data): Unit = {
30+
val ctx = scData.to[ScriptContext]
31+
ctx.scriptInfo match
32+
case ScriptInfo.SpendingScript(_, datum) =>
33+
val d = datum.getOrFail("Missing datum")
34+
// Decode datum as a Plutus Data list of integers
35+
val dataList: BuiltinList[Data] = unListData(d)
36+
// Convert to array for O(1) indexed access (CIP-138)
37+
val arr: BuiltinArray[Data] = listToArray(dataList)
38+
// Decode redeemer
39+
val r = ctx.redeemer.to[ArrayRedeemer]
40+
// Look up element and verify
41+
val element: Data = indexArray(arr, r.index)
42+
val value: BigInt = unIData(element)
43+
require(value == r.expectedValue, "Array element does not match expected value")
44+
case _ => fail("Not a spending script")
45+
}
46+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package pv11
2+
3+
import scalus.compiler.Options
4+
import scalus.uplc.PlutusV3
5+
import scalus.uplc.PlutusV1
6+
import java.nio.file.{Files, Path, Paths}
7+
8+
/** Compiles all PV11 validators and writes .plutus TextEnvelope JSON files.
9+
*
10+
* Usage: sbt run [output-dir]
11+
* Default output: ../plutus/pv11/
12+
*/
13+
object CompileAll {
14+
private given Options = Options.release
15+
16+
lazy val expModCompiled = PlutusV3.compile(ExpModValidator.validate)
17+
lazy val arrayCompiled = PlutusV3.compile(ArrayValidator.validate)
18+
19+
private def writeTextEnvelope(path: Path, plutusVersion: String, cborHex: String): Unit = {
20+
val json = s"""|{
21+
| "type": "$plutusVersion",
22+
| "description": "",
23+
| "cborHex": "$cborHex"
24+
|}""".stripMargin
25+
Files.writeString(path, json + "\n")
26+
}
27+
28+
def main(args: Array[String]): Unit = {
29+
val outputDir = if (args.nonEmpty) args(0) else "../plutus/pv11"
30+
Files.createDirectories(Paths.get(outputDir))
31+
32+
println("Compiling ExpMod validator (CIP-109: Modular Exponentiation)...")
33+
val expModHex = expModCompiled.program.doubleCborHex
34+
writeTextEnvelope(
35+
Paths.get(s"$outputDir/expmod-validator.plutus"),
36+
"PlutusScriptV3",
37+
expModHex
38+
)
39+
println(s" Written to $outputDir/expmod-validator.plutus")
40+
println(s" Script size: ${expModCompiled.program.flatEncoded.length} bytes")
41+
42+
println("Compiling Array validator (CIP-138: Array Type)...")
43+
val arrayHex = arrayCompiled.program.doubleCborHex
44+
writeTextEnvelope(
45+
Paths.get(s"$outputDir/array-validator.plutus"),
46+
"PlutusScriptV3",
47+
arrayHex
48+
)
49+
println(s" Written to $outputDir/array-validator.plutus")
50+
println(s" Script size: ${arrayCompiled.program.flatEncoded.length} bytes")
51+
52+
println("Done! All validators compiled successfully.")
53+
}
54+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package pv11
2+
3+
import scalus.*
4+
import scalus.uplc.builtin.Data
5+
import scalus.uplc.builtin.Data.{FromData, ToData}
6+
import scalus.uplc.builtin.Builtins.*
7+
import scalus.cardano.onchain.plutus.prelude.*
8+
import scalus.cardano.onchain.plutus.v3.*
9+
10+
/** CIP-109: Modular Exponentiation validator Example
11+
*
12+
* Datum contains base, exponent, modulus, and expected result.
13+
* The validator computes expModInteger(base, exponent, modulus)
14+
* and asserts the result equals the expected value.
15+
*
16+
* Test case: 3^5 mod 7 = 243 mod 7 = 5
17+
*/
18+
19+
case class ExpModDatum(
20+
base: BigInt,
21+
exponent: BigInt,
22+
modulus: BigInt,
23+
expected: BigInt
24+
) derives FromData, ToData
25+
26+
@Compile object ExpModDatum
27+
28+
@Compile
29+
object ExpModValidator {
30+
inline def validate(scData: Data): Unit = {
31+
val ctx = scData.to[ScriptContext]
32+
ctx.scriptInfo match
33+
case ScriptInfo.SpendingScript(_, datum) =>
34+
val d = datum.getOrFail("Missing datum").to[ExpModDatum]
35+
val result = expModInteger(d.base, d.exponent, d.modulus)
36+
require(result == d.expected, "expModInteger result does not match expected")
37+
case _ => fail("Not a spending script")
38+
}
39+
}

0 commit comments

Comments
 (0)