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
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,14 @@ utilities/
node-*/
/scripts/poison
/scripts/poison/
plutus/

# Python virtual environment
venv/
__pycache__/
*.pyc

# Config file (user-specific, example is tracked)
config/cardano-node-config.json
config/cardano-node-config.json

.scala-build/
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/
18 changes: 18 additions & 0 deletions scalus/build.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
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,
"org.scalus" %% "scalus-testkit" % scalusVersion % Test,
"org.scalatest" %% "scalatest" % "3.2.19" % Test,
"org.scalatestplus" %% "scalacheck-1-18" % "3.2.19.0" % Test
)
)
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
46 changes: 46 additions & 0 deletions scalus/src/main/scala/pv11/ArrayValidator.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
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 Example
*
* 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.
*/

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")
}
}
54 changes: 54 additions & 0 deletions scalus/src/main/scala/pv11/CompileAll.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package pv11

import scalus.compiler.Options
import scalus.uplc.PlutusV3
import scalus.uplc.PlutusV1
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 Example
*
* 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")
}
}
51 changes: 51 additions & 0 deletions scalus/src/test/scala/pv11/ExpModValidatorTest.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package pv11

import org.scalatest.funsuite.AnyFunSuite
import scalus.compiler
import scalus.compiler.Options
import scalus.testing.kit.ScalusTest
import scalus.uplc.builtin.Data.toData
import scalus.cardano.onchain.plutus.prelude.List

class ExpModValidatorTest extends AnyFunSuite with ScalusTest:

given Options = compiler.defaultOptions
val sir = compiler.compile(ExpModValidator.validate)

test("ExpModValidator validates correct modular exponentiation via datum") {
// 3^5 mod 7 = 243 mod 7 = 5
val datum = ExpModDatum(
base = 3,
exponent = 5,
modulus = 7,
expected = 5
)

val context = makeSpendingScriptContext(
datum = datum.toData,
redeemer = ().toData,
signatories = List()
)

val result = runScript(sir)(context)
assert(result.isSuccess)
}

test("ExpModValidator fails when expected result is wrong") {
// 3^5 mod 7 = 5, but we claim 6
val datum = ExpModDatum(
base = 3,
exponent = 5,
modulus = 7,
expected = 6
)

val context = makeSpendingScriptContext(
datum = datum.toData,
redeemer = ().toData,
signatories = List()
)

val result = runScript(sir)(context)
assert(result.isFailure)
}
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
13 changes: 8 additions & 5 deletions scripts/ga/parameter.sh
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
#!/bin/bash

set -euo pipefail

# ~~~~~~~~~~~~ CHANGE THIS ~~~~~~~~~~~~
METADATA_URL="ipfs://bafkreia5vseqm3hqmds45gje4szvekwkzd4mebzeepbh2cdlr3krxcj2ou"
METADATA_HASH="dfa2df398319b48e80a2caf02f4165bf12b6689d0ed57eee5e13dfa94857ed71"
METADATA_URL="https://raw.githubusercontent.com/Ryun1/metadata/refs/heads/main/cip108/cost-model.jsonld"
METADATA_HASH="a0cb1774a243ee15a83516a2128062127b2d4032ecd290666e1439475dbf846e"
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

# Get the script's directory and project root
Expand All @@ -17,7 +19,8 @@ tx_cert_path="$tx_path_stub.action"
tx_unsigned_path="$tx_path_stub.unsigned"
tx_signed_path="$tx_path_stub.signed"

guardrails_script_path="./config/guardrails-script.plutus"
utils_dir="$project_root/utilities"
guardrails_script_path="$utils_dir/guardrails-script.plutus"

# Get the script's directory
# Source the cardano-cli wrapper
Expand Down Expand Up @@ -56,14 +59,14 @@ 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" \
--anchor-data-hash "$METADATA_HASH" \
--check-anchor-data \
--constitution-script-hash $SCRIPT_HASH \
--max-tx-execution-units "(10000000000, 16500000)" \
--max-block-execution-units "(20000000000, 72000000)" \
--cost-model-file "$utils_dir/new-cost-model-collapsed-values.json" \
--prev-governance-action-tx-id "$PREV_GA_TX_HASH" \
--prev-governance-action-index "$PREV_GA_INDEX" \
--out-file "$tx_cert_path"
Expand Down
6 changes: 4 additions & 2 deletions scripts/helper/cardano-cli-wrapper.sh
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ 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|")
path=$(echo "$path" | sed "s|^$base_dir/utilities|/utilities|")
fi
echo "$path"
}
Expand Down Expand Up @@ -217,9 +219,9 @@ cardano_cli() {
done

if [ ${#network_flag_args[@]} -gt 0 ]; then
docker exec -ti "$container_name" cardano-cli "${network_flag_args[@]}" "${converted_args[@]}"
docker exec -e LANG=C.UTF-8 -ti "$container_name" cardano-cli "${network_flag_args[@]}" "${converted_args[@]}"
else
docker exec -ti "$container_name" cardano-cli "${converted_args[@]}"
docker exec -e LANG=C.UTF-8 -ti "$container_name" cardano-cli "${converted_args[@]}"
fi
fi
}
Expand Down
33 changes: 33 additions & 0 deletions scripts/helper/create-json.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/bin/bash
set -euo pipefail

# ~~~~~~~~~~~~ CHANGE THIS ~~~~~~~~~~~~
TRANSACTION_FILE="out-transaction.unsigned"
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

# Get the script's directory and project root
script_dir=$(dirname "$0")
project_root=$(cd "$script_dir/../.." && pwd)

# Define directory paths relative to project root
keys_dir="$project_root/keys"
txs_dir="$project_root/txs"

TRANSACTION_PATH="$txs_dir/$TRANSACTION_FILE"

# Check required files exist
if [ ! -f "$TRANSACTION_PATH" ]; then
echo "Error: Transaction file not found: $TRANSACTION_PATH"
exit 1
fi

# Source the cardano-cli wrapper
source "$script_dir/cardano-cli-wrapper.sh"

# Send ada to the multisig payment script
echo "Creating $TRANSACTION_PATH JSON representation at $TRANSACTION_PATH.json."

cardano_cli debug transaction view \
--tx-file "$TRANSACTION_PATH" \
--output-json \
--out-file "$TRANSACTION_PATH.json"
2 changes: 1 addition & 1 deletion scripts/helper/sign-submit-tx.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
set -euo pipefail

# ~~~~~~~~~~~~ CHANGE THIS ~~~~~~~~~~~~
TRANSACTION_FILE="treasury-contract"
TRANSACTION_FILE="out-transaction"
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

# Get the script's directory and project root
Expand Down
Loading
Loading