Skip to content
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ utilities/
node-*/
/scripts/poison
/scripts/poison/
plutus/

# Python virtual environment
venv/
Expand Down
1 change: 1 addition & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ services:
- ./keys:/keys
- ./txs:/txs
- ./dumps:/dumps
- ./plutus:/plutus
- ./utilities:/utilities
logging:
driver: "json-file"
Expand Down
6 changes: 6 additions & 0 deletions scalus/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
target/
project/target/
project/project/
.bsp/
.metals/
.idea/
15 changes: 15 additions & 0 deletions scalus/build.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
val scalusVersion = "0.15.0"

ThisBuild / scalaVersion := "3.3.7"
ThisBuild / scalacOptions ++= Seq("-feature", "-deprecation", "-unchecked")

addCompilerPlugin("org.scalus" %% "scalus-plugin" % scalusVersion)

lazy val root = (project in file("."))
.settings(
name := "pv11-validators",
libraryDependencies ++= Seq(
"org.scalus" %% "scalus" % scalusVersion,
"org.scalus" %% "scalus-cardano-ledger" % scalusVersion
)
)
24 changes: 24 additions & 0 deletions scalus/compile.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/bin/bash
set -euo pipefail

# Compile PV11 Plutus validators using Scalus 0.15.0
# Outputs .plutus TextEnvelope JSON files to ../plutus/pv11/

script_dir="$(cd "$(dirname "$0")" && pwd)"
cd "$script_dir"

if ! command -v sbt &> /dev/null; then
echo "Error: sbt not found. Install sbt to compile Scalus validators."
echo "See https://www.scala-sbt.org/download/"
exit 1
fi

output_dir="../plutus/pv11"
mkdir -p "$output_dir"

echo "Compiling PV11 validators with Scalus..."
sbt "run $output_dir"

echo ""
echo "Compiled validators:"
ls -la "$output_dir/"*.plutus
1 change: 1 addition & 0 deletions scalus/project/build.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
sbt.version=1.10.11
1 change: 1 addition & 0 deletions scalus/project/plugins.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// No sbt plugins required - Scalus compiler plugin is added via addCompilerPlugin in build.sbt
48 changes: 48 additions & 0 deletions scalus/src/main/scala/pv11/ArrayValidator.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package pv11

import scalus.*
import scalus.uplc.builtin.{Data, BuiltinArray, BuiltinList}
import scalus.uplc.builtin.Data.{FromData, ToData}
import scalus.uplc.builtin.Builtins.*
import scalus.cardano.onchain.plutus.prelude.*
import scalus.cardano.onchain.plutus.v3.*

/** CIP-138: Array Type validator
*
* Datum is a Plutus Data list of integers stored on-chain.
* Redeemer specifies an index and expected value.
* The validator converts the list to an array (O(1) access),
* looks up the element at the given index, and asserts it
* matches the expected value.
*
* Test case: Array [10, 20, 30, 40, 50], index 2, expect 30
*
* Note: Array builtins (listToArray, indexArray) are PV11 features.
* This script may need PlutusV4 compilation depending on how the
* Cardano node exposes these builtins.
*/

case class ArrayRedeemer(index: BigInt, expectedValue: BigInt) derives FromData, ToData

@Compile object ArrayRedeemer

@Compile
object ArrayValidator {
inline def validate(scData: Data): Unit = {
val ctx = scData.to[ScriptContext]
ctx.scriptInfo match
case ScriptInfo.SpendingScript(_, datum) =>
val d = datum.getOrFail("Missing datum")
// Decode datum as a Plutus Data list of integers
val dataList: BuiltinList[Data] = unListData(d)
// Convert to array for O(1) indexed access (CIP-138)
val arr: BuiltinArray[Data] = listToArray(dataList)
// Decode redeemer
val r = ctx.redeemer.to[ArrayRedeemer]
// Look up element and verify
val element: Data = indexArray(arr, r.index)
val value: BigInt = unIData(element)
require(value == r.expectedValue, "Array element does not match expected value")
case _ => fail("Not a spending script")
}
}
53 changes: 53 additions & 0 deletions scalus/src/main/scala/pv11/CompileAll.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package pv11

import scalus.compiler.Options
import scalus.uplc.PlutusV3
import java.nio.file.{Files, Path, Paths}

/** Compiles all PV11 validators and writes .plutus TextEnvelope JSON files.
*
* Usage: sbt run [output-dir]
* Default output: ../plutus/pv11/
*/
object CompileAll {
private given Options = Options.release

lazy val expModCompiled = PlutusV3.compile(ExpModValidator.validate)
lazy val arrayCompiled = PlutusV3.compile(ArrayValidator.validate)

private def writeTextEnvelope(path: Path, plutusVersion: String, cborHex: String): Unit = {
val json = s"""|{
| "type": "$plutusVersion",
| "description": "",
| "cborHex": "$cborHex"
|}""".stripMargin
Files.writeString(path, json + "\n")
}

def main(args: Array[String]): Unit = {
val outputDir = if (args.nonEmpty) args(0) else "../plutus/pv11"
Files.createDirectories(Paths.get(outputDir))

println("Compiling ExpMod validator (CIP-109: Modular Exponentiation)...")
val expModHex = expModCompiled.program.doubleCborHex
writeTextEnvelope(
Paths.get(s"$outputDir/expmod-validator.plutus"),
"PlutusScriptV3",
expModHex
)
println(s" Written to $outputDir/expmod-validator.plutus")
println(s" Script size: ${expModCompiled.program.flatEncoded.length} bytes")

println("Compiling Array validator (CIP-138: Array Type)...")
val arrayHex = arrayCompiled.program.doubleCborHex
writeTextEnvelope(
Paths.get(s"$outputDir/array-validator.plutus"),
"PlutusScriptV3",
arrayHex
)
println(s" Written to $outputDir/array-validator.plutus")
println(s" Script size: ${arrayCompiled.program.flatEncoded.length} bytes")

println("Done! All validators compiled successfully.")
}
}
39 changes: 39 additions & 0 deletions scalus/src/main/scala/pv11/ExpModValidator.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package pv11

import scalus.*
import scalus.uplc.builtin.Data
import scalus.uplc.builtin.Data.{FromData, ToData}
import scalus.uplc.builtin.Builtins.*
import scalus.cardano.onchain.plutus.prelude.*
import scalus.cardano.onchain.plutus.v3.*

/** CIP-109: Modular Exponentiation validator
*
* Datum contains base, exponent, modulus, and expected result.
* The validator computes expModInteger(base, exponent, modulus)
* and asserts the result equals the expected value.
*
* Test case: 3^5 mod 7 = 243 mod 7 = 5
*/

case class ExpModDatum(
base: BigInt,
exponent: BigInt,
modulus: BigInt,
expected: BigInt
) derives FromData, ToData

@Compile object ExpModDatum

@Compile
object ExpModValidator {
inline def validate(scData: Data): Unit = {
val ctx = scData.to[ScriptContext]
ctx.scriptInfo match
case ScriptInfo.SpendingScript(_, datum) =>
val d = datum.getOrFail("Missing datum").to[ExpModDatum]
val result = expModInteger(d.base, d.exponent, d.modulus)
require(result == d.expected, "expModInteger result does not match expected")
case _ => fail("Not a spending script")
}
}
1 change: 1 addition & 0 deletions scripts/ga/hardfork.sh
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ echo "Previous Hardfork GA: $PREV_GA_TX_HASH#$PREV_GA_INDEX"
echo "Creating and submitting hardfork governance action."

cardano_cli conway governance action create-hardfork \
--testnet \
--governance-action-deposit $(cardano_cli conway query gov-state | jq -r '.currentPParams.govActionDeposit') \
--deposit-return-stake-verification-key-file $keys_dir/stake.vkey \
--anchor-url "$METADATA_URL" \
Expand Down
1 change: 1 addition & 0 deletions scripts/ga/parameter.sh
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ PREV_GA_INDEX=$(echo "$GOV_STATE" | jq -r '.PParamUpdate.govActionIx')
echo "Previous Protocol Param Change GA: $PREV_GA_TX_HASH#$PREV_GA_INDEX"

cardano_cli conway governance action create-protocol-parameters-update \
--testnet \
--governance-action-deposit $(cardano_cli conway query gov-state | jq -r '.currentPParams.govActionDeposit') \
--deposit-return-stake-verification-key-file $keys_dir/stake.vkey \
--anchor-url "$METADATA_URL" \
Expand Down
1 change: 1 addition & 0 deletions scripts/helper/cardano-cli-wrapper.sh
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ convert_to_container_path() {
path=$(echo "$path" | sed "s|^$base_dir/txs|/txs|")
path=$(echo "$path" | sed "s|^$base_dir/keys|/keys|")
path=$(echo "$path" | sed "s|^$base_dir/dumps|/dumps|")
path=$(echo "$path" | sed "s|^$base_dir/plutus|/plutus|")
fi
echo "$path"
}
Expand Down
85 changes: 85 additions & 0 deletions scripts/pv11/demo-all.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
#!/bin/bash
set -euo pipefail

# End-to-end demo of PV11 (van Rossem) hard fork features
# Runs lock/unlock for both validators sequentially

# Get the script's directory (save before wrapper overrides script_dir)
pv11_dir="$(cd "$(dirname "$0")" && pwd)"
project_root=$(cd "$pv11_dir/../.." && pwd)

# Source wrapper for colors
source "$pv11_dir/../helper/cardano-cli-wrapper.sh"

echo -e "${CYAN}============================================${NC}"
echo -e "${CYAN} PV11 (van Rossem) Hard Fork Feature Demo ${NC}"
echo -e "${CYAN}============================================${NC}"
echo ""
echo "This demo will test two PV11 Plutus features:"
echo " 1. CIP-109: Modular Exponentiation (expModInteger)"
echo " 2. CIP-138: Array Type (listToArray, indexArray)"
echo ""

# Show current UTxOs at script addresses
plutus_dir="$project_root/plutus/pv11"

if [ -f "$plutus_dir/expmod-validator.plutus" ]; then
EXPMOD_ADDR=$(cardano_cli conway address build --payment-script-file "$plutus_dir/expmod-validator.plutus")
echo -e "${CYAN}ExpMod script address:${NC} $EXPMOD_ADDR"
echo -e "${CYAN}UTxOs at ExpMod validator:${NC}"
cardano_cli conway query utxo --address "$EXPMOD_ADDR" --out-file /dev/stdout
echo ""
fi

if [ -f "$plutus_dir/array-validator.plutus" ]; then
ARRAY_ADDR=$(cardano_cli conway address build --payment-script-file "$plutus_dir/array-validator.plutus")
echo -e "${CYAN}Array script address:${NC} $ARRAY_ADDR"
echo -e "${CYAN}UTxOs at Array validator:${NC}"
cardano_cli conway query utxo --address "$ARRAY_ADDR" --out-file /dev/stdout
echo ""
fi

# --- ExpMod Validator ---
echo -e "${YELLOW}--- Step 1/4: Lock ada at ExpMod Validator ---${NC}"
read -p "Press Enter to continue (or Ctrl+C to abort)..."
"$pv11_dir/lock-expmod.sh"
echo ""

echo -e "${YELLOW}Waiting for transaction to be confirmed...${NC}"
echo "Sleeping 30 seconds for block confirmation..."
sleep 30
echo ""

echo -e "${YELLOW}--- Step 2/4: Unlock ADA from ExpMod Validator ---${NC}"
read -p "Press Enter to continue..."
"$pv11_dir/unlock-expmod.sh"
echo ""

echo -e "${YELLOW}Waiting for transaction to be confirmed...${NC}"
echo "Sleeping 30 seconds for block confirmation..."
sleep 30
echo ""

# --- Array Validator ---
echo -e "${YELLOW}--- Step 3/4: Lock ADA at Array Validator ---${NC}"
read -p "Press Enter to continue..."
"$pv11_dir/lock-array.sh"
echo ""

echo -e "${YELLOW}Waiting for transaction to be confirmed...${NC}"
echo "Sleeping 30 seconds for block confirmation..."
sleep 30
echo ""

echo -e "${YELLOW}--- Step 4/4: Unlock ADA from Array Validator ---${NC}"
read -p "Press Enter to continue..."
"$pv11_dir/unlock-array.sh"
echo ""

echo -e "${GREEN}============================================${NC}"
echo -e "${GREEN} All PV11 feature tests completed! ${NC}"
echo -e "${GREEN}============================================${NC}"
echo ""
echo "Summary:"
echo " CIP-109 (expModInteger): 3^5 mod 7 = 5 ✓"
echo " CIP-138 (Array Type): arr[2] = 30 ✓"
Loading
Loading