Step-by-step course guide
Keep the lab proof and deployment evidence separate. The local ECVRF, VDF and lottery exercises require no payment. On the lottery showcase, identify the environment and inspect the draw evidence before interpreting it. UNI uses a simulated chain; a Base transaction must be checked on Base. The documented live entry is an unpaid work seat through
enterFromWork; paid tickets are disabled.
Audience: developers learning lottery smart-contract patterns before touching Base mainnet.
Languages:COURSE_LANG=en|ru|es· UI strings ini18n/· this guide in English.
Русский: step-by-step.ru.md · Español: step-by-step.es.md
Why this course
On-chain lotteries fail when the operator can quietly re-roll a draw until they like the winner — a "grinding" attack. The lottery/ contracts on Base defeat that with verifiable randomness (ECVRF via the Sortes oracle) and a Wesolowski VDF beacon from the Chronos oracle, settling payouts through a receipt-gated AIMarketEscrow channel. This course teaches each of those primitives as a small, runnable, LLM-free Python port. You verify the same Foundry test vector the Solidity contracts use (ChronosVDF.t.sol), walk a relayer round end to end, and finish with a capstone that simulates a full fair round with verifiable artifacts — so what you learn maps directly onto the lottery/ codebase.
Crypto honesty: The oracle primitives here are research/prototype tier (see oracles crypto-maturity) — fine for testnet and learning, not a substitute for external audit before mainnet TVL.
Table of contents
- Why this course
- Choose your track
- Setup (10 minutes)
- Module M1 — Unbiasable lottery draw
- Module M2 — VDF verify on-chain
- Module M3 — Escrow & payment channels
- Module M4 — Relayer round lifecycle
- Module M5 — Capstone: fair round
- Exercises & certificate
- Observability in every lab
- Bridge: from course to lottery/
- Self-check checklist
- Troubleshooting
Choose your track
| Track | Modules | Time | Oracles |
|---|---|---|---|
| Basic | M1 → M2 → M3 → M4 | ~1.5 h | Sortes + Chronos (fallback if not installed) |
| Advanced | Basic + M5 capstone | +30 min | Sortes + Chronos VDF path |
| lottery/ bridge | After M4 or M5 | +30 min | Live forge test + Base contracts |
Read Observability after each lab — every script prints a Trace you can inspect.
Setup (10 minutes)
Step 1 — Clone and install
git clone https://github.com/alexar76/aimarket-courses.git
cd aimarket-courses/smart-contracts-course
pip install -e ".[oracles,dev]"
The [oracles] extra wires in the Sortes (ECVRF) and Chronos (VDF) oracle packages. If the extra is unavailable in your environment the labs still run — courselib falls back to a deterministic stdlib implementation and the trace marks oracle=fallback. Python 3.11+ is required.
Step 2 — Verify tests
pytest -q
Expected: all tests pass (i18n parity across en/ru/es, contract primitives, the bundled Foundry vector, certificate rendering).
Step 3 — Run the first lab
python labs/lab01_unbiasable_draw.py
python labs/run_exercises.py
Step 4 — Pick a language (optional)
export COURSE_LANG=ru # or es, default en
python labs/lab01_unbiasable_draw.py
COURSE_LANG only switches the lab's printed strings (module titles, labels, hints) — code, commands, ticket numbers, and the Foundry vector are identical in every language.
Step 5 — Colab alternative
- Open the course site → Open in Colab on any lab.
- Run the setup cell (clone +
pip install -e ".[oracles,dev]"). - Set
os.environ["COURSE_LANG"] = "ru"in the setup cell if needed. - Run the lab cell.
Module M1 — Unbiasable lottery draw
Concept: Commit–reveal and ECVRF fix operator grinding attacks.
Lab: labs/lab01_unbiasable_draw.py · Time: ~15 min · Prerequisites: setup done
Steps
-
Open
labs/lab01_unbiasable_draw.pyand read the module docstring. -
Run locally:
python labs/lab01_unbiasable_draw.py -
Compare the two draws in the output:
- Biased lottery (operator grind) —
grindable=True: the operator re-hashes with a secret until a favorable ticket appears (BiasedLottery.operator_can_grind). - Fair lottery (ECVRF) — one
alphamaps to exactly onebeta, withverified=True. The operator cannot re-roll.
- Biased lottery (operator grind) —
-
Open
courselib/contracts.py— readBiasedLottery,FairLottery, and_sortes_draw(the Sortes ECVRF path with a stdlib fallback). -
Exercise:
python labs/run_exercises.py --module m1— proves the fair draw verifies while the biased one grinds.
What it proves
A verifiable draw removes operator discretion: the winner is a deterministic function of a public seed plus a proof anyone can check. On-chain consumer reference: lottery/docs/ and AIAgentLottery.sol.
Self-check
- [ ] You can explain why
BiasedLotteryis broken on purpose - [ ] You see why ECVRF (
alpha → beta) closes the grinding attack
Module M2 — VDF verify on-chain
Concept: Wesolowski proofs from Chronos match Foundry test vectors.
Lab: labs/lab02_vdf_verify.py · Time: ~15 min · Prerequisites: M1
Steps
- Run
python labs/lab02_vdf_verify.py. - The lab loads the bundled Foundry vector (
courselib/fixtures/chronos_vector.json, a copy oflottery/contracts/test/vectors/chronos_vector.json) and verifiesπ^l·g^r ≡ y (mod N). Output showsverify: True (AB_equals_y=True). - If the Chronos oracle is installed,
verify_wesolowski_vectoralso re-derives the proof live (vdf.hash_to_group → evaluate → prove → verify); otherwise the bundled intermediates alone are checked. - Exercise:
python labs/run_exercises.py --module m2— verifies the same vector the Solidity test uses.
What it proves
The Python verifier and the Solidity verifier agree on the exact same bytes. Run forge test in lottery/contracts — ChronosVDF.t.sol consumes the identical vector, so a green Python check predicts a green Foundry check.
Self-check
- [ ] You can name the Wesolowski check (
π^l·g^r ≡ y mod N) - [ ] You understand why a VDF makes randomness slow to compute, fast to verify
Module M3 — Escrow & payment channels
Concept: Hold funds until delivery; receipt-gated debit like AIMarketEscrow.
Lab: labs/lab03_escrow_channel.py · Time: ~20 min · Prerequisites: M1
Steps
- Run
python labs/lab03_escrow_channel.py. - Follow the channel lifecycle in the output: open (deposit) → debit (receipt-gated) → settle (split between hub and refund).
- Watch the Receipt replay line: a second
debitwith the samereceipt_idis rejected (blocked=True). This is the on-chain replay guard —EscrowChannel.used_receipts. - Skim
courselib/contracts.py→EscrowChannel. It modelsAIMarketEscrowopen/debit/settle (Protocol v2 §6): signature check, nonce, hub-match, balance guard. - Exercise:
python labs/run_exercises.py --module m3— debit twice with one receipt; the second must fail.
Self-check
- [ ] You can name the four debit guards (signature, replay, balance, hub-match)
- [ ] You can explain how
settlesplitsto_hubvsrefund
Module M4 — Relayer round lifecycle
Concept: baseSeed, Sortes draw, optional on-chain VDF path.
Lab: labs/lab04_relayer_round.py · Time: ~20 min · Prerequisites: M1–M2
Steps
- Run
python labs/lab04_relayer_round.py. - Trace the three phases in the output and the
Traceblock:- Chronos seed —
base_seed = sha256(roundId ‖ blockhash ‖ platonRandom), the keccak-packed seed the relayer feeds Chronos. - Sortes ticket — the verifiable draw for this round (
verified=True). - VDF vector — when
onchain_vdf=True, the round attaches aVdfProofbeacon and re-verifies the Foundry vector (vector_valid=True).
- Chronos seed —
- Cross-read:
lottery/relayer/ailottery_relayer/economy_draw.py— the production draw flow this lab mirrors. - Exercise:
python labs/run_exercises.py --module m4— tracebaseSeed → Sortes → VDF beacon.
Self-check
- [ ] You can describe what binds a round to its seed (roundId, blockhash, Platon random)
- [ ] You know when the on-chain VDF path is taken vs the prevrandao path
Module M5 — Capstone: fair round
Concept: Simulate a full lottery round with verifiable artifacts.
Lab: labs/lab05_lottery_capstone.py · Time: ~30 min · Prerequisites: M1–M4 · Track: advanced
Steps
- Run
python labs/lab05_lottery_capstone.py. - The lab calls
simulate_fair_round(onchain_vdf=True, client_seed=...)and prints the round artifacts:ticket,vdf_path=onchain, andchronos_vector=True. - Compare these artifacts to a real
lottery/relayerlog — every field has a production counterpart. - Exercise:
python labs/run_exercises.py --module m5— runsimulate_fair_roundwithonchain_vdf=Trueand confirm the path and vector.
What it proves
You can assemble the M1–M4 primitives into one auditable round: a verifiable ticket, a VDF beacon, and a checkable vector — the artifacts a player (or auditor) needs to trust the result without trusting the operator.
Self-check
- [ ] You can list the artifacts a fair round produces
- [ ] You can explain
vdf_path=onchainvsprevrandao
Exercises & certificate
After the labs, run the DIY checks:
python labs/run_exercises.py # all modules
python labs/run_exercises.py --module m3 # one module
Each module prints ✓ or ✗; fix any failures and re-run. When all pass, generate a certificate:
python labs/run_exercises.py --certificate "Your Name" --lang en
This writes certificate.html (override with -o). Open it in a browser and Print → Save as PDF. The credential ID is derived from name + date (courselib/certificate.py). You can also generate it from the Certificate section of the course site.
Observability in every lab
Concept: Every lab logs decisions to Trace — treat it as your audit log.
Covered in: every lab via courselib/trace.py · Time: 5 min after each lab
Steps (repeat after each lab)
- Scroll to the trace section at the end of the lab output.
- Count the
eventsand map each to a step you saw in stdout —biased_draw,fair_draw,vdf_verify,open/debit/settle,base_seed/sortes_draw/vdf_beacon,capstone. - In M4, correlate the three trace events to the three printed phases.
Self-check
- [ ] Trace is the lab's "on-chain event log" for the round
- [ ] You can point to the single event that proves a draw was fair
Bridge: from course to lottery/
Goal: Connect course primitives to the production lottery/ repo (~30 min).
-
Foundry vector —
lottery/contracts/test/vectors/chronos_vector.json(bundled incourselib/fixtures/). The course verifier reads the same file the Solidity test does. -
On-chain verifier —
lottery/contracts/src/ChronosVDF.sol; its test isChronosVDF.t.sol. Run it:cd lottery/contracts && forge test -
Relayer draw flow —
lottery/relayer/ailottery_relayer/economy_draw.py(mirrored by M4). -
Map course → lottery/
Course lottery/ M1 fair draw Sortes ECVRF + AIAgentLottery.solM2 VDF verify ChronosVDF.sol+ChronosVDF.t.solM3 escrow channel AIMarketEscrowopen/debit/settleM4 relayer round economy_draw.pyround lifecycleM5 capstone Full relayer round + on-chain VDF beacon -
Docs: lottery/docs/.
Self-check checklist
After the basic track you should be able to:
- [ ] Run lab01–lab04 locally and in Colab
- [ ] Explain why a verifiable draw beats operator grinding
- [ ] Verify the Chronos Foundry vector and say why it matches
ChronosVDF.t.sol - [ ] Describe the escrow open → debit → settle flow and the replay guard
- [ ] Read a
Traceblock and find the event proving fairness
After the advanced track add:
- [ ] Run lab05 and list the artifacts of a fair round
- [ ] Run
forge testinlottery/contractsand connect it to M2
Troubleshooting
| Problem | Fix |
|---|---|
ModuleNotFoundError: courselib |
Run from the repo root; labs prepend the parent dir to sys.path |
Oracle import fails / pip install ".[oracles]" errors |
Labs still run with the stdlib fallback (trace shows oracle=fallback); for the live ECVRF/VDF path run from the aicom monorepo where oracles/ resolves |
verify: False in M2 |
Confirm courselib/fixtures/chronos_vector.json is intact (valid and AB_equals_y must be true) |
| Empty RU/ES strings | Set COURSE_LANG; check i18n/ru.json keys match en.json |
| pytest i18n parity failure | Add the missing key to all five i18n/{en,ru,es,fr,zh}.json |
| Colab running old code | Re-run the setup cell; pin the main branch |
lottery/ deployment & on-chain issues: lottery/docs/.
Guides in docs/ are hand-maintained — update EN first, then the RU/ES mirrors.