Skip to content
Merged
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
3 changes: 2 additions & 1 deletion .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ RUN apt update && apt install -y \
jq \
python3 \
build-essential \
ca-certificates
ca-certificates \
netcat-openbsd

# Install nvm, node and npm
RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.5/install.sh | bash \
Expand Down
17 changes: 11 additions & 6 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,19 @@
"context": ".",
"dockerfile": "Dockerfile"
},
"postCreateCommand": "yarn ",
"postCreateCommand": "yarn ",
"customizations": {
// Configure properties specific to VS Code.
"vscode": {
// Set *default* container specific settings.json values on container create.
"settings": {},
"extensions": ["noir-lang.vscode-noir"]
}
"vscode": {
// Set *default* container specific settings.json values on container create.
"settings": {
// Noir syntax highlighting may not work in Codespaces; use Rust as fallback
"files.associations": {
"*.nr": "rust"
}
},
"extensions": ["noir-lang.vscode-noir"]
}
},
"workspaceMount": "source=${localWorkspaceFolder},target=/root/workspace,type=bind",
"workspaceFolder": "/root/workspace"
Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ log/
codegenCache.json
store/
.tsbuildinfo
.env
.env
.include-code-cache/
12 changes: 12 additions & 0 deletions .remarkrc.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { remarkIncludeCode } from 'include_code';

export default {
plugins: [
[remarkIncludeCode, {
codeDir: './src',
repository: { owner: 'AztecProtocol', name: 'aztec-starter' },
commitTag: 'main',
validation: 'error',
}]
]
};
27 changes: 27 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,3 +145,30 @@ When updating the Aztec version, update all of these locations:
2. `package.json` — all `@aztec/*` dependency versions
3. `config/local-network.json` and `config/devnet.json` — `settings.version`
4. `README.md` — install command version

## ONBOARDING.md Maintenance

`ONBOARDING.md` is **generated** — do not edit it directly. Edit `docs/ONBOARDING.src.md` instead, then rebuild:

```bash
yarn docs:build # remark docs/ONBOARDING.src.md -o ONBOARDING.md
```

The source file uses `#include_code` directives (via the `include_code` remark plugin) to extract code snippets from source files at build time. Source files have `// docs:start:<name>` / `// docs:end:<name>` marker pairs that define snippet boundaries.

**When making code changes:**

1. Source code snippets update automatically on rebuild — no manual copy needed
2. If you add/remove/rename a marker, update the corresponding `#include_code` directive in `docs/ONBOARDING.src.md`
3. Run `yarn docs:build` to regenerate `ONBOARDING.md`
4. Phase 5 exercises and non-source prose still need manual updates in `docs/ONBOARDING.src.md`

**Files with `docs:start`/`docs:end` markers:**

- `src/main.nr` — `storage`, `constructor`, `create-game`, `join-game`, `play-round`, `validate-and-play-round`, `finish-game`, `validate-finish-game`, `finalize-game`
- `src/race.nr` — `race-struct`, `calculate-winner`
- `src/game_round_note.nr` — `game-round-note`, `game-round-note-new`
- `src/test/utils.nr` — `test-setup`
- `src/test/helpers.nr` — `allocation-strategies`, `setup-helpers`
- `src/test/pod_racing.nr` — `test-initializer`, `test-fail-too-many-points`
- `src/utils/sponsored_fpc.ts` — `get-sponsored-fpc`
1,245 changes: 1,245 additions & 0 deletions ONBOARDING.md

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

This repo is meant to be a starting point for learning to write Aztec contracts and tests on the Aztec local network (local development environment). It includes an example contract, useful commands in `package.json` and helpful scripts in `./scripts`.

**New to Aztec? Start with the [Onboarding Guide](./ONBOARDING.md)** — a progressive walkthrough that takes Ethereum developers from reading code to deploying on devnet.

You can find the **Pod Racing Game contract** in `./src/main.nr`. A simple integration test is in `./src/test/e2e/index.test.ts`.

The Pod Racing contract is a two-player competitive game where players allocate points across 5 tracks over multiple rounds. The game demonstrates Aztec's private state capabilities - round choices remain private until players reveal their final scores.
Expand Down Expand Up @@ -45,6 +47,8 @@ export VERSION=4.0.0-devnet.2-patch.1
curl -fsSL "https://install.aztec.network/${VERSION}" | VERSION="${VERSION}" bash -s
```

Then install project dependencies:

### Environment Configuration

This project uses JSON configuration files to manage environment-specific settings:
Expand Down
857 changes: 857 additions & 0 deletions docs/ONBOARDING.src.md

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"scripts": {
"fees": "NODE_NO_WARNINGS=1 node --loader ts-node/esm scripts/fees.ts",
"fees::devnet": "NODE_NO_WARNINGS=1 AZTEC_ENV=devnet node --loader ts-node/esm scripts/fees.ts",
"clean": "rm -rf ./src/artifacts ./target",
"clean": "rm -rf ./src/artifacts ./target ./codegenCache.json",
"clear-store": "rm -rf ./store",
"codegen": "aztec codegen target --outdir src/artifacts",
"compile": "aztec compile",
Expand All @@ -32,6 +32,7 @@
"test::devnet": "AZTEC_ENV=devnet yarn test:js",
"test:js": "rm -rf store/pxe && NODE_NO_WARNINGS=1 node --experimental-vm-modules $(yarn bin jest) --no-cache --runInBand --config jest.integration.config.json",
"test:nr": "aztec test",
"docs:build": "remark docs/ONBOARDING.src.md -o ONBOARDING.md",
"update-readme-version": "node ./.github/scripts/update-readme-version.js"
},
"dependencies": {
Expand All @@ -51,7 +52,10 @@
"@types/jest": "^29.5.11",
"@types/mocha": "^10.0.6",
"@types/node": "^22.15.1",
"include_code": "^0.1.0",
"jest": "^29.7.0",
"remark": "^15.0.1",
"remark-cli": "^12.0.1",
"ts-jest": "^29.1.1",
"ts-node": "^10.9.2",
"typescript": "^5.4.4"
Expand Down
9 changes: 4 additions & 5 deletions scripts/get_block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@ import { getAztecNodeUrl } from "../config/config.js";

async function main() {

const nodeUrl = getAztecNodeUrl();
const node = createAztecNodeClient(nodeUrl);
let block = await node.getBlock(BlockNumber(1));
console.log(block)
console.log(await block?.hash())
const nodeUrl = getAztecNodeUrl();
const node = createAztecNodeClient(nodeUrl);
let block = await node.getBlock(BlockNumber(1));
console.log(block?.header)
}

main()
Expand Down
4 changes: 4 additions & 0 deletions src/game_round_note.nr
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use aztec::{macros::notes::note, protocol::{traits::Packable, address::AztecAddress}};

// docs:start:game-round-note
// GameRoundNote is a private note that stores a player's point allocation for one round
// These notes remain private until the player calls finish_game to reveal their totals
//
Expand All @@ -25,8 +26,10 @@ pub struct GameRoundNote {
// The player who created this note (only they can read it)
pub owner: AztecAddress,
}
// docs:end:game-round-note

impl GameRoundNote {
// docs:start:game-round-note-new
// Creates a new note with the player's round choices
// This note gets stored privately and can only be read by the owner
pub fn new(track1: u8, track2: u8, track3: u8, track4: u8, track5: u8, round: u8, owner: AztecAddress) -> Self {
Expand All @@ -40,6 +43,7 @@ impl GameRoundNote {
owner,
}
}
// docs:end:game-round-note-new

// Helper method to access the note data
pub fn get(self) -> Self {
Expand Down
18 changes: 18 additions & 0 deletions src/main.nr
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ pub contract PodRacing {
global TOTAL_ROUNDS: u8 = 3; // Each game consists of 3 rounds
global GAME_LENGTH: u32 = 300; // Games expire after 300 blocks

// docs:start:storage
#[storage]
struct Storage<Context> {
// Contract administrator address
Expand All @@ -55,14 +56,18 @@ pub contract PodRacing {
// Public leaderboard tracking career victories
win_history: Map<AztecAddress, PublicMutable<u64, Context>, Context>,
}
// docs:end:storage

// docs:start:constructor
#[external("public")]
#[initializer]
fn constructor(admin: AztecAddress) {
debug_log_format("Initializing PodRacing contract with admin {0}", [admin.to_field()]);
self.storage.admin.write(admin);
}
// docs:end:constructor

// docs:start:create-game
// Creates a new game instance
// The caller becomes player1 and waits for an opponent to join
// Sets the game expiration to current block + GAME_LENGTH
Expand All @@ -85,7 +90,9 @@ pub contract PodRacing {
);
self.storage.races.at(game_id).write(game);
}
// docs:end:create-game

// docs:start:join-game
// Allows a second player to join an existing game
// After joining, both players can start playing rounds
#[external("public")]
Expand All @@ -99,7 +106,9 @@ pub contract PodRacing {
let joined_game = maybe_existing_game.join(player2);
self.storage.races.at(game_id).write(joined_game);
}
// docs:end:join-game

// docs:start:play-round
// Plays a single round by allocating points across 5 tracks
// This is a PRIVATE function - the point allocation remains hidden from the opponent
// Players must play rounds sequentially (round 1, then 2, then 3)
Expand Down Expand Up @@ -148,7 +157,9 @@ pub contract PodRacing {
round,
));
}
// docs:end:play-round

// docs:start:validate-and-play-round
// Internal public function to validate and record that a player completed a round
// Updates the public game state to track which round each player is on
// Does NOT reveal the point allocation (that remains private)
Expand All @@ -163,7 +174,9 @@ pub contract PodRacing {
// Increment the player's round counter (validates sequential play)
self.storage.races.at(game_id).write(game_in_progress.increment_player_round(player, round));
}
// docs:end:validate-and-play-round

// docs:start:finish-game
// Called after all rounds are complete to reveal a player's total scores
// This is PRIVATE - only the caller can read their own GameRoundNotes
// The function sums up all round allocations per track and publishes totals
Expand Down Expand Up @@ -214,7 +227,9 @@ pub contract PodRacing {
total_track5,
));
}
// docs:end:finish-game

// docs:start:validate-finish-game
// Internal public function to store a player's revealed track totals
// Validates that the player hasn't already revealed their scores (all must be 0)
// After both players call finish_game, all scores are public and can be compared
Expand Down Expand Up @@ -249,7 +264,9 @@ pub contract PodRacing {
total_track5,
));
}
// docs:end:validate-finish-game

// docs:start:finalize-game
// Determines the winner after both players have revealed their scores
// Can only be called after the game's end_block (time limit expired)
// Compares track totals and declares the player who won more tracks as winner
Expand All @@ -276,6 +293,7 @@ pub contract PodRacing {
);
self.storage.win_history.at(winner).write(previous_wins + 1);
}
// docs:end:finalize-game

// Utility function: runs client-side in the PXE, not on-chain.
// Reads the public game state and logs it via debug_log_format.
Expand Down
4 changes: 4 additions & 0 deletions src/race.nr
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use ::aztec::protocol::{
traits::{Deserialize, Serialize, Packable, ToField},
};

// docs:start:race-struct
// Race struct stores the public state of a game
// This data is visible to everyone and tracks game progress
#[derive(Deserialize, Serialize, Eq, Packable)]
Expand Down Expand Up @@ -37,6 +38,7 @@ pub struct Race {
// Block number when the game expires (for timeout enforcement)
pub end_block: u32,
}
// docs:end:race-struct

impl Race {
// Creates a new game with player1 set, waiting for player2 to join
Expand Down Expand Up @@ -250,6 +252,7 @@ impl Race {
debug_log_format("End block: {0}", [self.end_block as Field]);
}

// docs:start:calculate-winner
// Determines the game winner by comparing track scores
// Winner is whoever won more tracks (best of 5)
//
Expand Down Expand Up @@ -305,4 +308,5 @@ impl Race {
self.player2
}
}
// docs:end:calculate-winner
}
4 changes: 4 additions & 0 deletions src/test/helpers.nr
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ pub global TEST_GAME_ID_8: Field = 8;
pub global TEST_GAME_ID_9: Field = 9;
pub global TEST_GAME_ID_10: Field = 10;

// docs:start:allocation-strategies
// Common point allocations for testing
// Balanced strategy: distribute points evenly
pub unconstrained fn balanced_allocation() -> (u8, u8, u8, u8, u8) {
Expand All @@ -35,7 +36,9 @@ pub unconstrained fn defensive_allocation() -> (u8, u8, u8, u8, u8) {
pub unconstrained fn max_allocation() -> (u8, u8, u8, u8, u8) {
(5, 2, 1, 1, 0)
}
// docs:end:allocation-strategies

// docs:start:setup-helpers
// Helper to setup a game with two players
pub unconstrained fn setup_two_player_game(
env: &mut TestEnvironment,
Expand Down Expand Up @@ -77,3 +80,4 @@ pub unconstrained fn play_all_rounds_with_strategy(
play_round_with_allocation(env, contract_address, player, game_id, round, allocations[i]);
}
}
// docs:end:setup-helpers
4 changes: 4 additions & 0 deletions src/test/pod_racing.nr
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crate::PodRacing;
// INITIALIZATION TESTS
// ============================================================================

// docs:start:test-initializer
// Test: Contract initialization sets admin correctly
#[test]
unconstrained fn test_initializer() {
Expand All @@ -16,6 +17,7 @@ unconstrained fn test_initializer() {
assert_eq(current_admin, admin);
});
}
// docs:end:test-initializer

// ============================================================================
// GAME CREATION TESTS
Expand Down Expand Up @@ -135,6 +137,7 @@ unconstrained fn test_play_round() {
});
}

// docs:start:test-fail-too-many-points
// Test: Cannot allocate more than 9 points in a round
#[test(should_fail)]
unconstrained fn test_fail_play_round_too_many_points() {
Expand All @@ -151,6 +154,7 @@ unconstrained fn test_fail_play_round_too_many_points() {
PodRacing::at(contract_address).play_round(game_id, 1, 2, 2, 2, 2, 2)
);
}
// docs:end:test-fail-too-many-points

// Test: Allocating exactly 9 points is allowed
#[test]
Expand Down
2 changes: 2 additions & 0 deletions src/test/utils.nr
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use ::aztec::{

use crate::PodRacing;

// docs:start:test-setup
// Setup function for pod racing contract tests
// Returns: (TestEnvironment, contract_address, admin_address)
// Note: Create player accounts in individual tests to avoid oracle errors
Expand All @@ -18,3 +19,4 @@ pub unconstrained fn setup() -> (TestEnvironment, AztecAddress, AztecAddress) {

(env, contract_address, admin)
}
// docs:end:test-setup
2 changes: 2 additions & 0 deletions src/utils/sponsored_fpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ import type { LogFn } from '@aztec/foundation/log';
import { SponsoredFPCContract, SponsoredFPCContractArtifact } from '@aztec/noir-contracts.js/SponsoredFPC';
import { SPONSORED_FPC_SALT } from '@aztec/constants';

// docs:start:get-sponsored-fpc
export async function getSponsoredFPCInstance(): Promise<ContractInstanceWithAddress> {
return await getContractInstanceFromInstantiationParams(SponsoredFPCContractArtifact, {
salt: new Fr(SPONSORED_FPC_SALT),
});
}
// docs:end:get-sponsored-fpc

export async function getSponsoredFPCAddress() {
return (await getSponsoredFPCInstance()).address;
Expand Down
Loading