Install
$ agentstack add skill-cardano-foundation-cardano-dev-skills-optimize-validator ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Optimize Cardano Validator
Guide optimization of Aiken validators for lower execution costs (CPU/memory) and smaller compiled script size. Optimization should only be applied to validators that are already correct and tested.
When to use
- User has a working validator and wants to reduce transaction fees
- Script size exceeds limits or is unnecessarily large
- Execution units (CPU/memory) are higher than expected
- User wants to compare optimization strategies
- Before deploying a reference script (size directly affects storage deposit)
- Transaction is failing due to exceeding execution unit limits
When NOT to use
- User needs to write a new validator (use write-validator)
- User needs a security review (use review-contract)
- The validator has not been tested yet (correctness comes before performance)
- The optimization would remove a security check
Key principles
- Measure before optimizing: Use
aiken buildto check script size andaiken benchto measure execution units. Without baseline numbers, you cannot know if changes helped. - Script size and execution units are independent costs: A larger script is not necessarily more expensive to execute. Inlining functions increases size but can reduce CPU by avoiding closure allocation.
- CPU and memory budgets are separate: A transaction can fail by exceeding either limit independently. Identify which resource is the bottleneck before optimizing.
- The stdlib is well-optimized: Do not rewrite stdlib functions unless profiling shows they are a bottleneck. Custom implementations often end up larger and slower.
- Correctness over performance: Never sacrifice security checks for performance. Optimize how a check is performed, not whether it runs.
Workflow
Step 1: Establish baseline
Read the validator code and gather current metrics.
Suggest the user run:
# Build and check script sizes
aiken build
# Run benchmarks if available
aiken bench
# Check compiled script sizes
cat plutus.json | jq '.validators[] | {title, size: (.compiledCode | length / 2)}'
Note the following:
- Current script size in bytes (from
plutus.jsonor build output) - Execution unit estimates per action (from
aiken bench) - Which operations the validator performs (list traversals, value comparisons, datum deserialization)
Step 2: Search Bundled Documentation
Search the bundled documentation for relevant content:
${CLAUDE_SKILL_DIR}/../../docs/sources/aiken/- Aiken language docs${CLAUDE_SKILL_DIR}/../../docs/sources/aiken-stdlib/- Aiken standard library docs${CLAUDE_SKILL_DIR}/../../docs/sources/plutus/- Plutus docs
Step 3: Identify expensive operations
Search the validator code for known cost centers:
High CPU cost:
- Nested list traversals (O(n*m) comparisons)
- Value comparisons (comparing two
Values traverses both nested maps) -- expensive for multi-asset values - Large datum deserialization
- Cryptographic hashing (blake2b, sha256)
- On-chain signature verification (extremely expensive -- use
extra_signatoriesinstead)
High memory cost:
- Building large intermediate data structures
- Repeated datum deserialization
- String concatenation for trace messages
- Map/filter chains that create intermediate lists
Script size bloat:
- Trace messages (string literals embedded in compiled output)
- Dead code (unreachable branches, unused functions)
- Redundant type conversions
- Inlined functions that could be shared
Step 4: Apply optimizations
Execution unit optimizations
Order pattern matching by frequency:
// Most common action first -- UPLC checks branches sequentially
when redeemer is {
Swap -> check_swap(...) // 90% of transactions
AddLiquidity -> check_add(...) // 8%
RemoveLiquidity -> check_remove(...) // 1.5%
UpdateParams -> check_update(...) // 0.5%
}
Fail fast -- check cheap conditions before expensive ones:
// GOOD: Cheap signer check (O(small n)) before expensive output scan (O(large n))
list.has(tx.extra_signatories, datum.owner) &&
check_outputs(tx.outputs, expected_value)
Extract common sub-expressions:
// BAD: Resolves own input twice
list.any(tx.outputs, fn(o) { o.address == resolve_own_address(tx, own_ref) }) &&
list.all(tx.outputs, fn(o) {
o.address != resolve_own_address(tx, own_ref) || check_value(o)
})
// GOOD: Compute once, reuse
expect Some(own_input) = transaction.find_input(tx.inputs, own_ref)
let own_addr = own_input.output.address
list.any(tx.outputs, fn(o) { o.address == own_addr }) &&
list.all(tx.outputs, fn(o) { o.address != own_addr || check_value(o) })
Reduce list traversals:
// BAD: Two separate passes over outputs
let has_script_output = list.any(tx.outputs, fn(o) { o.address == script_addr })
let has_payment = list.any(tx.outputs, fn(o) { o.address == seller })
// GOOD: Single pass with combined check
let (has_script, has_pay) =
list.foldl(tx.outputs, (False, False), fn(o, acc) {
let (s, p) = acc
(s || o.address == script_addr, p || o.address == seller)
})
Use expect instead of when for single-variant destructuring:
// LARGER (compiled): when with explicit fail
when datum is {
MyDatum { owner, amount } -> use(owner, amount)
_ -> fail
}
// SMALLER: expect compiles to direct destructure
expect MyDatum { owner, amount } = datum
use(owner, amount)
Script size optimizations
Remove traces for production builds:
# Development (with traces for debugging)
aiken build
# Production (traces removed, 10-30% smaller)
aiken build --trace-level silent
Extract shared helper functions:
// BAD: Duplicated logic in each branch
when redeemer is {
Claim -> {
expect Some(out) = list.find(tx.outputs, fn(o) { o.address == addr })
assets.lovelace_of(out.value) >= amount && list.has(tx.extra_signatories, owner)
}
Update -> {
expect Some(out) = list.find(tx.outputs, fn(o) { o.address == addr })
assets.lovelace_of(out.value) >= amount && list.has(tx.extra_signatories, owner)
}
}
// GOOD: Shared function
fn check_output_and_signer(tx, addr, amount, signer) {
expect Some(out) = list.find(tx.outputs, fn(o) { o.address == addr })
assets.lovelace_of(out.value) >= amount && list.has(tx.extra_signatories, signer)
}
Remove dead code and unused imports: Search for functions, types, and imports that are not referenced. Unused code still contributes to script size.
Data structure optimizations
- Use
Pairinstead of 2-element tuples when possible (smaller UPLC representation) - For small fixed collections, explicit fields are cheaper than lists
- For lookups, sorted lists with early-exit beat unsorted lists
- Smaller datums mean less deserialization cost -- remove fields that can be computed from other fields
Step 5: Consider reference scripts
For scripts over approximately 4 KB that will be used in multiple transactions:
- Store the script as a reference script in a UTxO
- Reference it by hash in subsequent transactions
- The script is paid for once at creation and amortized over many uses
- Reduces per-transaction size significantly
Step 6: Verify and benchmark
After applying optimizations, the user should verify:
- All existing tests still pass:
aiken check - Script size delta: Compare compiled sizes before and after
- CPU units delta: Run
aiken benchand compare - Memory units delta: Run
aiken benchand compare - No security checks were removed: Review changes for correctness
Provide before/after comparison format:
Before After Delta
cpu: 2,345,678 1,890,123 -19.4%
mem: 234,567 198,432 -15.4%
size: 4,567 3,890 -14.8%
Step 7: Document trade-offs
If any optimization involves a trade-off (e.g., increased size for lower CPU), document:
- What was changed and why
- The measured impact on each metric
- Any correctness considerations
References
references/uplc-cost-model.md-- UPLC cost model basics, operation costs, and budget limits- Search
${CLAUDE_SKILL_DIR}/../../docs/sources/for benchmark results and performance requirements - Aiken documentation on optimization: https://aiken-lang.org
- Use
aiken build --trace-level silentfor production builds - Use
aiken benchfor execution unit measurements
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: cardano-foundation
- Source: cardano-foundation/cardano-dev-skills
- License: Apache-2.0
- Homepage: https://cardano-foundation.github.io/cardano-dev-skills/
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.