Skip to content

Commit 926c8e7

Browse files
authored
Merge pull request #254 from AztecProtocol/josh/include-code-docs-testnet
Replace hand-written doc snippets with #include_code directives
2 parents e48f91a + 9d231c6 commit 926c8e7

File tree

9 files changed

+127
-300
lines changed

9 files changed

+127
-300
lines changed

.remarkrc.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { remarkIncludeCode } from 'include_code';
33
export default {
44
plugins: [
55
[remarkIncludeCode, {
6-
codeDir: './src',
6+
codeDir: '.',
77
repository: { owner: 'AztecProtocol', name: 'aztec-starter' },
88
commitTag: 'main',
99
validation: 'error',

ONBOARDING.md

Lines changed: 81 additions & 140 deletions
Large diffs are not rendered by default.

config/config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,14 @@ export class ConfigManager {
3333
private config!: EnvironmentConfig;
3434
private configPath: string;
3535

36+
// docs:start:config-loading
3637
private constructor() {
3738
const env = process.env.AZTEC_ENV || 'local-network';
3839
this.configPath = path.resolve(process.cwd(), `config/${env}.json`);
3940
this.loadConfig();
4041
console.log(`Loaded configuration: ${this.config.name} environment`);
4142
}
43+
// docs:end:config-loading
4244

4345
public static getInstance(): ConfigManager {
4446
if (!ConfigManager.instance) {

docs/ONBOARDING.src.md

Lines changed: 31 additions & 151 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ The Pod Racing contract ([`src/main.nr`](./src/main.nr)) is a two-player competi
8080

8181
Open `src/main.nr` and look at the `Storage` struct:
8282

83-
#include_code storage /main.nr rust
83+
#include_code storage /src/main.nr rust
8484

8585
**What is `Context`?** You'll notice `Context` appears as a generic parameter throughout the storage definition. In Aztec, the context is the execution environment passed to every function — it's how your contract accesses blockchain state like `context.msg_sender()` (the caller's address) and `context.block_number()`. Think of it as an expanded version of Solidity's global variables (`msg.sender`, `block.number`, etc.), but packaged as an object. The `<Context>` generic on storage types lets the same storage struct work in both public and private execution contexts. You don't need to construct it yourself — the framework provides `self.context` automatically in every contract function.
8686

@@ -119,33 +119,33 @@ These functions should feel familiar if you've written Solidity.
119119

120120
#### `constructor()`
121121

122-
#include_code constructor /main.nr rust
122+
#include_code constructor /src/main.nr rust
123123

124124
Sets the admin address. The `#[initializer]` macro means this runs once at deployment, like a Solidity constructor.
125125

126126
#### `create_game()`
127127

128-
#include_code create-game /main.nr rust
128+
#include_code create-game /src/main.nr rust
129129

130130
Creates a new game. Checks the game ID isn't taken (player1 must be zero address), then writes a new `Race` struct with the caller as player1 and an expiration time.
131131

132132
#### `join_game()`
133133

134-
#include_code join-game /main.nr rust
134+
#include_code join-game /src/main.nr rust
135135

136136
A second player joins. The `Race::join()` method validates that player1 exists, the player2 slot is empty, and the joiner isn't player1.
137137

138138
#### `finalize_game()`
139139

140-
#include_code finalize-game /main.nr rust
140+
#include_code finalize-game /src/main.nr rust
141141

142142
After both players have revealed, this compares track scores, determines the winner, and updates the leaderboard.
143143

144144
#### The `Race` struct ([`src/race.nr`](./src/race.nr))
145145

146146
The `Race` struct stores all public game state. It has 17 fields:
147147

148-
#include_code race-struct /race.nr rust
148+
#include_code race-struct /src/race.nr rust
149149

150150
Key methods:
151151

@@ -159,7 +159,7 @@ This is the "aha moment" — the part with no Ethereum equivalent.
159159

160160
#### `play_round()`
161161

162-
#include_code play-round /main.nr rust
162+
#include_code play-round /src/main.nr rust
163163

164164
Three things happen here that have no direct Ethereum equivalent:
165165

@@ -169,7 +169,7 @@ Three things happen here that have no direct Ethereum equivalent:
169169

170170
#### `finish_game()`
171171

172-
#include_code finish-game /main.nr rust
172+
#include_code finish-game /src/main.nr rust
173173

174174
This is the "reveal" phase:
175175

@@ -178,7 +178,7 @@ This is the "reveal" phase:
178178

179179
#### `GameRoundNote` (`src/game_round_note.nr`)
180180

181-
#include_code game-round-note /game_round_note.nr rust
181+
#include_code game-round-note /src/game_round_note.nr rust
182182

183183
The `#[note]` macro makes this a private state primitive. Each note stores one round's point allocation and the owner's address. Only the owner can read it.
184184

@@ -234,13 +234,13 @@ Here's exactly what an outside observer can and cannot see at each step.
234234

235235
Some functions are labeled **"private, then public"** in the Type column. On Aztec, there are only two function types: `#[private]` and `#[public]`. But a private function can _enqueue_ a public function to run after it — within the same transaction. The private part runs first (on the user's machine, hidden from everyone), then the public part runs on-chain (visible to all). This is how the contract hides sensitive data while still updating shared public state.
236236

237-
| Step | Function | Type | Observer **CAN** see | Observer **CANNOT** see |
238-
| ---- | --------------- | ------------------- | --------------------------------------------------------- | ---------------------------------------------- |
239-
| 1 | `create_game` | public | Game created, player1 address, expiration block | Nothing hidden |
240-
| 2 | `join_game` | public | Player2 joined, both addresses | Nothing hidden |
237+
| Step | Function | Type | Observer **CAN** see | Observer **CANNOT** see |
238+
| ---- | --------------- | -------------------- | --------------------------------------------------------- | ---------------------------------------------- |
239+
| 1 | `create_game` | public | Game created, player1 address, expiration block | Nothing hidden |
240+
| 2 | `join_game` | public | Player2 joined, both addresses | Nothing hidden |
241241
| 3 | `play_round` | private, then public | Round counter incremented (e.g. "player1 played round 1") | Point allocation across tracks |
242-
| 4 | `finish_game` | private, then public | Final track totals revealed (e.g. "player1: 7,7,7,3,3") | Individual round allocations |
243-
| 5 | `finalize_game` | public | Winner declared, leaderboard updated | Nothing hidden (all data public at this point) |
242+
| 4 | `finish_game` | private, then public | Final track totals revealed (e.g. "player1: 7,7,7,3,3") | Individual round allocations |
243+
| 5 | `finalize_game` | public | Winner declared, leaderboard updated | Nothing hidden (all data public at this point) |
244244

245245
The critical privacy window is between steps 3 and 4: both players have committed their strategies (as private notes), but neither can see the other's choices. This prevents the second player from gaining an advantage by observing the first player's moves.
246246

@@ -309,13 +309,13 @@ Tests live in `src/test/`:
309309

310310
**Basic initialization test:**
311311

312-
#include_code test-initializer /test/pod_racing.nr rust
312+
#include_code test-initializer /src/test/pod_racing.nr rust
313313

314314
The `unconstrained` keyword means this test runs outside the ZK circuit (it's a test, not a provable function). `utils::setup()` deploys a fresh contract and returns the environment, contract address, and admin.
315315

316316
**Expected failure test:**
317317

318-
#include_code test-fail-too-many-points /test/pod_racing.nr rust
318+
#include_code test-fail-too-many-points /src/test/pod_racing.nr rust
319319

320320
The `#[test(should_fail)]` attribute is like Foundry's `vm.expectRevert()`.
321321

@@ -327,15 +327,15 @@ This test creates a game, has both players play all 3 rounds with specific strat
327327

328328
Reusable allocation strategies:
329329

330-
#include_code allocation-strategies /test/helpers.nr rust
330+
#include_code allocation-strategies /src/test/helpers.nr rust
331331

332332
And higher-level helpers:
333333

334-
#include_code setup-helpers /test/helpers.nr rust
334+
#include_code setup-helpers /src/test/helpers.nr rust
335335

336336
#### Test setup (`src/test/utils.nr`)
337337

338-
#include_code test-setup /test/utils.nr rust
338+
#include_code test-setup /src/test/utils.nr rust
339339

340340
**Ethereum analogies:**
341341

@@ -396,10 +396,7 @@ The project uses JSON config files selected by the `AZTEC_ENV` environment varia
396396
397397
**`config/config.ts`** — A `ConfigManager` singleton that loads the appropriate JSON file:
398398
399-
```typescript
400-
const env = process.env.AZTEC_ENV || "local-network";
401-
this.configPath = path.resolve(process.cwd(), `config/${env}.json`);
402-
```
399+
#include_code config-loading /config/config.ts typescript
403400
404401
**`config/local-network.json`:**
405402
@@ -455,7 +452,7 @@ Every Aztec account is a smart contract. There are no Externally Owned Accounts
455452

456453
The `SponsoredFPC` is a canonical Fee Payment Contract deployed at a deterministic address (salt = 0). It pays transaction fees on behalf of users, useful for onboarding when users don't have Fee Juice yet. On the local network it's pre-deployed.
457454

458-
#include_code get-sponsored-fpc /utils/sponsored_fpc.ts typescript
455+
#include_code get-sponsored-fpc /src/utils/sponsored_fpc.ts typescript
459456

460457
**Run it:**
461458

@@ -481,15 +478,7 @@ AZTEC_ENV=local-network
481478
3. Deploy a Schnorr account (or use one from env)
482479
4. Deploy the contract:
483480

484-
```typescript
485-
const deployRequest = PodRacingContract.deploy(wallet, address);
486-
await deployRequest.simulate({ from: address });
487-
const { contract: podRacingContract, instance } = await deployRequest.send({
488-
from: address,
489-
fee: { paymentMethod: sponsoredPaymentMethod },
490-
wait: { timeout: timeouts.deployTimeout, returnReceipt: true }
491-
});
492-
```
481+
#include_code deploy-contract /scripts/deploy_contract.ts typescript
493482

494483
> **Important:** Always call `.simulate()` before `.send()`. Simulation runs the transaction locally and surfaces revert reasons immediately. Without it, a failing transaction hangs until timeout with an opaque error.
495484
@@ -510,20 +499,7 @@ The output includes the contract address, admin address, and instantiation data
510499
3. Register the contract with the wallet: `wallet.registerContract(instance, PodRacingContract.artifact)`
511500
4. Call methods:
512501

513-
```typescript
514-
const podRacingContract = await PodRacingContract.at(contractAddress, wallet);
515-
516-
// Simulate first — surfaces revert reasons instantly
517-
await podRacingContract.methods.create_game(gameId).simulate({ from: address });
518-
519-
// Then send — only after simulation succeeds
520-
await podRacingContract.methods.create_game(gameId)
521-
.send({
522-
from: address,
523-
fee: { paymentMethod: sponsoredPaymentMethod },
524-
wait: { timeout: timeouts.txTimeout }
525-
});
526-
```
502+
#include_code interact-existing /scripts/interaction_existing_contract.ts typescript
527503

528504
Set the env vars from your deploy output, then run:
529505

@@ -572,29 +548,7 @@ yarn test:nr
572548

573549
### 5.1 — Guided Exercise: Add a Forfeit Function
574550

575-
Add a `forfeit_game` function to `src/main.nr` that lets a player concede:
576-
577-
```rust
578-
#[external("public")]
579-
fn forfeit_game(game_id: Field) {
580-
let game = self.storage.races.at(game_id).read();
581-
let caller = self.context.maybe_msg_sender().unwrap();
582-
583-
// Only a player in this game can forfeit
584-
assert(caller.eq(game.player1) | caller.eq(game.player2));
585-
586-
// The other player wins
587-
let winner = if caller.eq(game.player1) {
588-
game.player2
589-
} else {
590-
game.player1
591-
};
592-
593-
// Update win history
594-
let previous_wins = self.storage.win_history.at(winner).read();
595-
self.storage.win_history.at(winner).write(previous_wins + 1);
596-
}
597-
```
551+
Add a `forfeit_game` function to `src/main.nr` that lets a player concede.
598552

599553
Compile and regenerate TypeScript bindings:
600554

@@ -604,33 +558,7 @@ yarn compile && yarn codegen
604558

605559
### 5.2 — Write a Noir Test
606560

607-
Add a test to `src/test/pod_racing.nr`:
608-
609-
```rust
610-
#[test]
611-
unconstrained fn test_forfeit_game() {
612-
let (mut env, contract_address, _) = utils::setup();
613-
let player1 = env.create_light_account();
614-
let player2 = env.create_light_account();
615-
let game_id = helpers::TEST_GAME_ID_9;
616-
617-
// Setup game
618-
helpers::setup_two_player_game(&mut env, contract_address, player1, player2, game_id);
619-
620-
// Player 1 forfeits
621-
env.call_public(player1, PodRacing::at(contract_address).forfeit_game(game_id));
622-
623-
// Verify player2's win count increased
624-
env.public_context_at(contract_address, |context| {
625-
let win_slot = derive_storage_slot_in_map(
626-
PodRacing::storage_layout().win_history.slot,
627-
player2.to_field()
628-
);
629-
let wins = context.storage_read(win_slot);
630-
assert_eq(wins, 1);
631-
});
632-
}
633-
```
561+
Add a test to `src/test/pod_racing.nr`.
634562

635563
Run:
636564

@@ -640,40 +568,7 @@ yarn test:nr
640568

641569
### 5.3 — Write a TypeScript E2E Test
642570

643-
Add a test case to `src/test/e2e/index.test.ts`:
644-
645-
```typescript
646-
it("Allows a player to forfeit", async () => {
647-
const gameId = new Fr(300);
648-
649-
await setupGame(
650-
contract,
651-
gameId,
652-
player1Account.address,
653-
player2Account.address,
654-
sponsoredPaymentMethod,
655-
getTimeouts().txTimeout,
656-
);
657-
658-
// Simulate first to surface revert reasons before sending
659-
await contract.methods.forfeit_game(gameId).simulate({
660-
from: player1Account.address,
661-
});
662-
663-
const tx = await contract.methods.forfeit_game(gameId).send({
664-
from: player1Account.address,
665-
fee: { paymentMethod: sponsoredPaymentMethod },
666-
wait: { timeout: getTimeouts().txTimeout },
667-
});
668-
669-
expect([
670-
TxStatus.PROPOSED,
671-
TxStatus.CHECKPOINTED,
672-
TxStatus.PROVEN,
673-
TxStatus.FINALIZED,
674-
]).toContain(tx.status);
675-
}, 600000);
676-
```
571+
Add a test case to `src/test/e2e/index.test.ts`.
677572

678573
Run:
679574

@@ -699,14 +594,9 @@ yarn test:js
699594

700595
**How it works (`scripts/multiple_wallet.ts`):**
701596

702-
The script creates two independent PXE instances (wallet1, wallet2), each with their own key store:
597+
The script creates two independent `EmbeddedWallet` instances, each with their own PXE:
703598

704-
```typescript
705-
const store1 = await createStore('pxe1', { dataDirectory: 'store', ... });
706-
const store2 = await createStore('pxe2', { dataDirectory: 'store', ... });
707-
const wallet1 = await TestWallet.create(node, fullConfig, { store: store1 });
708-
const wallet2 = await TestWallet.create(node, fullConfig, { store: store2 });
709-
```
599+
#include_code multiple-wallets /scripts/multiple_wallet.ts typescript
710600

711601
It then deploys a Token contract from wallet1, creates an account on wallet2, mints tokens to wallet2's account, registers the token contract on wallet2, and reads balances.
712602

@@ -749,13 +639,7 @@ Devnet uses real provers and connects to the Aztec devnet at `https://next.devne
749639

750640
**`scripts/profile_deploy.ts`** shows how to profile a transaction:
751641

752-
```typescript
753-
const profileTx = await PodRacingContract.deploy(wallet, address).profile({
754-
profileMode: "full",
755-
from: address,
756-
});
757-
console.dir(profileTx, { depth: 2 });
758-
```
642+
#include_code profile-tx /scripts/profile_deploy.ts typescript
759643

760644
The `.profile()` method runs the transaction through the prover and returns detailed metrics about gate counts and proving time.
761645

@@ -767,11 +651,7 @@ yarn profile
767651

768652
**`scripts/get_block.ts`** shows how to query the Aztec node directly:
769653

770-
```typescript
771-
const node = createAztecNodeClient(nodeUrl);
772-
let block = await node.getBlock(BlockNumber(1));
773-
console.log(block?.header);
774-
```
654+
#include_code get-block /scripts/get_block.ts typescript
775655

776656
```bash
777657
yarn get-block

scripts/deploy_contract.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,17 +41,18 @@ async function main() {
4141
logger.info(`📋 Admin address for pod racing contract: ${address}`);
4242

4343
logger.info('⏳ Simulating deployment transaction...');
44+
// docs:start:deploy-contract
4445
const deployRequest = PodRacingContract.deploy(wallet, address);
4546
await deployRequest.simulate({
4647
from: address,
4748
});
48-
logger.info('✅ Simulation successful, sending transaction...');
4949
const { receipt } = await deployRequest.send({
5050
from: address,
5151
fee: { paymentMethod: sponsoredPaymentMethod },
5252
wait: { timeout: timeouts.deployTimeout, returnReceipt: true }
5353
});
5454
const podRacingContract = receipt.contract;
55+
// docs:end:deploy-contract
5556
const instance = receipt.instance;
5657

5758
logger.info(`🎉 Pod Racing Contract deployed successfully!`);

0 commit comments

Comments
 (0)