๐Ÿงช

Module M002

Testing with Mock Transactions

Duration: 2 hours

Format: 30 min lecture + 1.5 hours hands-on

DirectEd x CATS Hackathon Aiken Development Workshop

SLIDE 1

Your Instructor

John Ndigirigi

John Ndigirigi

Cardano blockchain developer and educator specializing in Aiken smart contract development.

๐•

@ndigirigi__john

Follow on X
in

linkedin.com/in/ndigirigijohn

Connect on LinkedIn
SLIDE 2

Module Overview

Build realistic transactions to test your validators!

What You'll Learn Today

  • Transaction structure
  • Mock spending transactions
  • Mock minting transactions
  • Mock locking transactions
  • Mock unlocking transactions

Why Mock Transactions?

โœ“ Test validators thoroughly
โœ“ Understand transaction context
โœ“ Simulate real scenarios
โœ“ Catch bugs before deployment
โœ“ Build confidence in code

Goal: Master building realistic test transactions for comprehensive validator testing!
SLIDE 3

Quick Recap: Module M001

Spending Validator

validator my_spend { spend(datum, redeemer, input, tx) { True } }

4 parameters โ†’ Script Address

Minting Validator

validator my_mint { mint(redeemer, policy_id, tx) { True } }

3 parameters โ†’ Policy ID

The Missing Piece

We tested with placeholder transactions.
Now we'll build realistic transactions!

SLIDE 4

Cardano Transaction Anatomy

Complete Transaction Structure

Transaction { inputs: List<Input>, // UTxOs being spent outputs: List<Output>, // New UTxOs created fee: Int, // Transaction fee mint: Value, // Tokens minted/burned certificates: List<Certificate>, // Staking certs withdrawals: Pairs<...>, // Rewards withdrawn validity_range: ValidityRange, // Time window extra_signatories: List<Hash>, // Required signatures redeemers: Pairs<...>, // Unlock data datums: Dict<...>, // Datum dictionary id: TransactionId, // Unique ID // ... and more fields }
The Balance Rule: Sum(Inputs) = Sum(Outputs) + Fee
SLIDE 5

Understanding Inputs & Outputs

Input

Input { output_reference: OutputReference, output: Output }

Points to a UTxO from a previous transaction

Output

Output { address: Address, value: Value, datum: Datum, reference_script: Option<Script> }

Creates a new UTxO on the blockchain

Payment Credentials

VerificationKey

โ†’ Wallet Address
โ†’ Controlled by private key
โ†’ Regular user addresses

Script

โ†’ Validator Address
โ†’ Controlled by smart contract
โ†’ Script addresses

SLIDE 6

Live Code: Mock Spending Transaction

Building a Spending Transaction

use cardano/assets.{from_lovelace} use mocktail.{ mocktail_tx, tx_in, tx_out, complete, set_fee, mock_pub_key_address, mock_tx_hash } fn mock_spending_tx() -> Transaction { mocktail_tx() |> tx_in(True, mock_tx_hash(0), 0, from_lovelace(10_000_000), // 10 ADA input mock_pub_key_address(0, None)) |> tx_out(True, mock_pub_key_address(1, None), from_lovelace(9_000_000)) // 9 ADA output |> complete() |> set_fee(True, 1_000_000) // 1 ADA fee } test test_balance() { let tx = mock_spending_tx() // 10 ADA in = 9 ADA out + 1 ADA fee โœ“ }

The Pipe Operator |> chains functions together

SLIDE 7

Live Code: Mock Minting Transaction

Minting Tokens

use cardano/assets.{from_asset} use mocktail.{mocktail_tx, mint, complete, mock_policy_id} // Single NFT fn mock_nft_mint() -> Transaction { mocktail_tx() |> mint(True, 1, mock_policy_id(0), "MyNFT_001") |> complete() } // Multiple tokens fn mock_multi_mint() -> Transaction { mocktail_tx() |> mint(True, 1, mock_policy_id(0), "Token_A") |> mint(True, 1, mock_policy_id(0), "Token_B") |> mint(True, 5, mock_policy_id(1), "OtherToken") |> complete() }

Minting: Positive quantity
Burning: Negative quantity

SLIDE 8

Live Code: Mock Locking Transaction

Locking Funds at Script Address

use mocktail.{ mocktail_tx, tx_out, tx_out_inline_datum, complete, mock_script_address } pub type MyDatum { owner: ByteArray, deadline: Int, } fn mock_locking_tx() -> Transaction { mocktail_tx() |> tx_out( True, mock_script_address(0, None), // Script address! from_lovelace(10_000_000) ) |> tx_out_inline_datum( True, MyDatum { owner: #"aabbcc", deadline: 1000 } ) |> complete() }
Key: Locking = sending to script address with datum. No redeemer needed!
SLIDE 9

Live Code: Mock Unlocking Transaction

Unlocking Funds from Script Address

use mocktail.{ mocktail_tx, tx_in, tx_in_inline_datum, tx_out, complete, set_fee, mock_script_address, mock_pub_key_address, mock_tx_hash } fn mock_unlocking_tx() -> Transaction { mocktail_tx() // Input from script address |> tx_in(True, mock_tx_hash(0), 0, from_lovelace(10_000_000), mock_script_address(0, None)) |> tx_in_inline_datum(True, MyDatum { owner: #"aabbcc", deadline: 1000 }) // Output to wallet |> tx_out(True, mock_pub_key_address(0, None), from_lovelace(9_000_000)) |> complete() |> set_fee(True, 1_000_000) }

Note: Validator receives redeemer separately when testing

SLIDE 10

Locking vs Unlocking Transactions

Aspect Locking Unlocking
Direction Wallet โ†’ Script Script โ†’ Wallet
Output Address Script address Wallet address (usually)
Datum Must attach datum Datum must be available
Redeemer NOT needed Must provide redeemer
Validation No validator runs Validator MUST return True
Use Case Deposit funds Withdraw funds
Remember: Anyone can lock funds. Only valid redeemers can unlock!
SLIDE 11

Mocktail (Vodka) Utilities

Essential Mock Functions

Addresses

  • mock_pub_key_address
  • mock_script_address
  • mock_pub_key_hash
  • mock_script_hash

Transactions

  • mocktail_tx()
  • tx_in / tx_out
  • mint
  • complete()
  • set_fee

References

  • mock_utxo_ref
  • mock_tx_hash
  • mock_policy_id

Transaction Builder Pattern

mocktail_tx() |> tx_in(...) // Add input |> tx_out(...) // Add output |> mint(...) // Add minting |> complete() // Finalize |> set_fee(...) // Set fee
SLIDE 12

Testing Validators with Mock Transactions

Complete Test Example

validator time_lock { spend(datum_opt: Option<MyDatum>, _redeemer, _input, tx: Transaction) { expect Some(MyDatum { deadline, .. }) = datum_opt let Transaction { validity_range, .. } = tx interval.is_entirely_after(validity_range, deadline) } else(_) { fail } } test test_before_deadline() { let tx = mocktail_tx() |> tx_in(True, mock_tx_hash(0), 0, from_lovelace(10_000_000), mock_script_address(0, None)) |> tx_in_inline_datum(True, MyDatum { owner: #"abc", deadline: 1000 }) |> complete() |> set_validity_range(True, 500, 999) // Before deadline !time_lock.spend( Some(MyDatum { owner: #"abc", deadline: 1000 }), Void, mock_utxo_ref(0, 0), tx ) // Should fail โœ— } test test_after_deadline() { let tx = mocktail_tx() |> /* ... same setup ... */ |> set_validity_range(True, 1001, 2000) // After deadline time_lock.spend(/* ... */) // Should succeed โœ“ }
SLIDE 13

Common Transaction Patterns

Multiple Inputs

mocktail_tx() |> tx_in(True, ..., 0, from_lovelace(5_000_000), ...) |> tx_in(True, ..., 1, from_lovelace(5_000_000), ...) |> tx_out(True, ..., from_lovelace(9_000_000)) |> complete() |> set_fee(True, 1_000_000)

With Token Values

use cardano/assets.{add, zero} let value = zero |> add(policy, "Token_A", 10) |> add(policy, "Token_B", 20)

Script to Script

mocktail_tx() |> tx_in(True, ..., mock_script_address(0, None)) |> tx_in_inline_datum(True, datum1) |> tx_out(True, mock_script_address(1, None), ...) |> tx_out_inline_datum(True, datum2) |> complete()

Mint + Lock

mocktail_tx() |> mint(True, 1, policy, "NFT") |> tx_out(True, mock_script_address(0, None), nft_value) |> tx_out_inline_datum(True, datum) |> complete()
SLIDE 14

Hands-On Exercises (90 Minutes)

๐Ÿ”ฌ

Time to build mock transactions!

Exercise 1: Mock Spending Transaction (15 min)

Build transaction with 2 inputs, 2 outputs

Exercise 2: Mock Minting Transaction (15 min)

Mint multiple NFTs in one transaction

Exercise 3: Mock Locking Transaction (15 min)

Send funds to script with inline datum

Exercise 4: Mock Unlocking Transaction (20 min)

Spend from script with redeemer

Exercise 5: Test a Validator (25 min)

Use mock transactions to test real validator

SLIDE 15

Assignment M002

Extend M001 with Mock Transaction Tests

Requirements

  1. Complete M001 validator (if not done)
  2. Add mock locking transaction test
  3. Add mock unlocking transaction test
  4. Test validator with both transactions
  5. Show passing & failing tests

Deliverables

  • GitHub repo URL (extended M001)
  • Custom datum with 2+ fields
  • Locking & unlocking transactions
  • Test results (pass & fail)
  • Brief learning reflection
Deadline: Before Module M003
SLIDE 16

Common Issues & Solutions

โŒ Transaction doesn't balance

Check: Sum(inputs) = Sum(outputs) + fee
Remember: 1 ADA = 1,000,000 lovelace

โŒ Type mismatch in datum

Ensure datum type matches between locking and unlocking

โŒ Can't find script address

Use mock_script_address, not mock_pub_key_address

โœ“ Use trace for debugging

trace @"Transaction inputs" trace tx.inputs
SLIDE 17

Key Takeaways

You can now:

โœ… Understand complete Cardano transaction structure

โœ… Build mock spending transactions with inputs/outputs

โœ… Create mock minting transactions with tokens

โœ… Write mock locking transactions to script addresses

โœ… Create mock unlocking transactions with redeemers

โœ… Test validators with realistic transaction contexts

Next: Module M003 - Validators with Real Logic ๐Ÿ”ฅ
๐ŸŽฏ

Excellent Work!

Module M002 Complete

You can now build realistic test transactions!

Practice building complex scenarios ๐Ÿงช

See you in M003! ๐Ÿš€

/ 18