Skip to content

Commit 19e4c73

Browse files
committed
Update ONBOARDING.md with line number corrections and add debug logging examples in contract functions
1 parent 06ee585 commit 19e4c73

1 file changed

Lines changed: 46 additions & 20 deletions

File tree

ONBOARDING.md

Lines changed: 46 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ The Pod Racing contract ([`src/main.nr`](./src/main.nr)) is a two-player competi
7878
7979
### 1.3 — Contract Walkthrough: Storage
8080

81-
Open `src/main.nr` and look at the `Storage` struct (lines 39-56):
81+
Open `src/main.nr` and look at the `Storage` struct (lines 40-57):
8282

8383
```rust
8484
#[storage]
@@ -136,19 +136,20 @@ contract PodRacing {
136136

137137
These functions should feel familiar if you've written Solidity.
138138

139-
#### `constructor()` (line 58-62)
139+
#### `constructor()` (line 59-64)
140140

141141
```rust
142142
#[external("public")]
143143
#[initializer]
144144
fn constructor(admin: AztecAddress) {
145+
debug_log_format("Initializing PodRacing contract with admin {0}", [admin.to_field()]);
145146
self.storage.admin.write(admin);
146147
}
147148
```
148149

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

151-
#### `create_game()` (line 67-79)
152+
#### `create_game()` (line 69-87)
152153

153154
```rust
154155
#[external("public")]
@@ -157,6 +158,10 @@ fn create_game(game_id: Field) {
157158
assert(self.storage.races.at(game_id).read().player1.eq(AztecAddress::zero()));
158159

159160
let player1 = self.context.maybe_msg_sender().unwrap();
161+
debug_log_format(
162+
"Creating game {0} by player {1}",
163+
[game_id, player1.to_field()],
164+
);
160165

161166
// Initialize a new Race with the caller as player1
162167
let game = Race::new(
@@ -170,14 +175,15 @@ fn create_game(game_id: Field) {
170175

171176
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.
172177

173-
#### `join_game()` (line 83-90)
178+
#### `join_game()` (line 91-101)
174179

175180
```rust
176181
#[external("public")]
177182
fn join_game(game_id: Field) {
178183
let maybe_existing_game = self.storage.races.at(game_id).read();
179184

180185
let player2 = self.context.maybe_msg_sender().unwrap();
186+
debug_log_format("Player {0} joining game {1}", [player2.to_field(), game_id]);
181187

182188
// Add the caller as player2 (validates that player1 exists and player2 is empty)
183189
let joined_game = maybe_existing_game.join(player2);
@@ -187,18 +193,24 @@ fn join_game(game_id: Field) {
187193

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

190-
#### `finalize_game()` (line 222-232)
196+
#### `finalize_game()` (line 262-278)
191197

192198
```rust
193199
#[external("public")]
194200
fn finalize_game(game_id: Field) {
201+
debug_log_format("Finalizing game {0}", [game_id]);
195202
let game_in_progress = self.storage.races.at(game_id).read();
196203

197204
// Calculate winner by comparing track scores (validates game has ended)
198205
let winner = game_in_progress.calculate_winner(self.context.block_number());
206+
debug_log_format("Winner determined: {0}", [winner.to_field()]);
199207

200208
// Update the winner's total win count in the public leaderboard
201209
let previous_wins = self.storage.win_history.at(winner).read();
210+
debug_log_format(
211+
"Updating win count from {0} to {1}",
212+
[previous_wins as Field, (previous_wins + 1) as Field],
213+
);
202214
self.storage.win_history.at(winner).write(previous_wins + 1);
203215
}
204216
```
@@ -207,7 +219,7 @@ After both players have revealed, this compares track scores, determines the win
207219

208220
#### The `Race` struct ([`src/race.nr`](./src/race.nr))
209221

210-
The `Race` struct (lines 8-38) stores all public game state. It has 17 fields:
222+
The `Race` struct (lines 10-39) stores all public game state. It has 17 fields:
211223

212224
```rust
213225
pub struct Race {
@@ -240,7 +252,7 @@ Key methods:
240252

241253
This is the "aha moment" — the part with no Ethereum equivalent.
242254

243-
#### `play_round()` (line 99-131)
255+
#### `play_round()` (line 110-150)
244256

245257
```rust
246258
#[external("private")]
@@ -276,7 +288,7 @@ Three things happen here that have no direct Ethereum equivalent:
276288
2. **Creating a private note**`.insert(GameRoundNote::new(...))` stores the round choices as an encrypted note. Only the player can read it later. The `.deliver(MessageDelivery.CONSTRAINED_ONCHAIN)` commits the note's hash on-chain without revealing the content.
277289
3. **Enqueuing a public call**`self.enqueue(...)` schedules a public function to run after the private proof is verified. This updates the round counter publicly (so both players can see progress) without revealing the point allocation.
278290

279-
#### `finish_game()` (line 149-184)
291+
#### `finish_game()` (line 172-216)
280292

281293
```rust
282294
#[external("private")]
@@ -332,8 +344,8 @@ The `#[note]` macro makes this a private state primitive. Each note stores one r
332344

333345
Two functions are marked `#[only_self]`, meaning they can only be called by the contract itself (via `self.enqueue(...)`):
334346

335-
- **`validate_and_play_round`** (line 136-142) — Validates the round is sequential and increments the player's round counter
336-
- **`validate_finish_game_and_reveal`** (line 189-211) — Stores the player's revealed track totals, checking they haven't already been revealed
347+
- **`validate_and_play_round`** (line 155-165) — Validates the round is sequential and increments the player's round counter
348+
- **`validate_finish_game_and_reveal`** (line 221-251) — Stores the player's revealed track totals, checking they haven't already been revealed
337349

338350
**Key insight:** On Ethereum, commit-reveal requires at least 2 transactions (one to commit, one to reveal after a delay). On Aztec, the "commit" happens automatically when a private function creates a note — the data is committed on-chain (as a hash) without ever being visible. The "reveal" is a separate transaction, but the privacy was enforced by the protocol the whole time.
339351

@@ -673,14 +685,17 @@ AZTEC_ENV=local-network
673685
4. Deploy the contract:
674686

675687
```typescript
676-
const podRacingContract = await PodRacingContract.deploy(wallet, address)
677-
.send({
688+
const deployRequest = PodRacingContract.deploy(wallet, address);
689+
await deployRequest.simulate({ from: address });
690+
const { contract: podRacingContract, instance } = await deployRequest.send({
678691
from: address,
679692
fee: { paymentMethod: sponsoredPaymentMethod },
680-
})
681-
.deployed({ timeout: timeouts.deployTimeout });
693+
wait: { timeout: timeouts.deployTimeout, returnReceipt: true }
694+
});
682695
```
683696

697+
> **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.
698+
684699
**Run it:**
685700

686701
```bash
@@ -700,13 +715,17 @@ The output includes the contract address, admin address, and instantiation data
700715

701716
```typescript
702717
const podRacingContract = await PodRacingContract.at(contractAddress, wallet);
703-
await podRacingContract.methods
704-
.create_game(gameId)
718+
719+
// Simulate first — surfaces revert reasons instantly
720+
await podRacingContract.methods.create_game(gameId).simulate({ from: address });
721+
722+
// Then send — only after simulation succeeds
723+
await podRacingContract.methods.create_game(gameId)
705724
.send({
706725
from: address,
707726
fee: { paymentMethod: sponsoredPaymentMethod },
708-
})
709-
.wait({ timeout: timeouts.txTimeout });
727+
wait: { timeout: timeouts.txTimeout }
728+
});
710729
```
711730

712731
Set the env vars from your deploy output, then run:
@@ -839,6 +858,11 @@ it("Allows a player to forfeit", async () => {
839858
getTimeouts().txTimeout,
840859
);
841860

861+
// Simulate first to surface revert reasons before sending
862+
await contract.methods.forfeit_game(gameId).simulate({
863+
from: player1Account.address,
864+
});
865+
842866
const tx = await contract.methods.forfeit_game(gameId).send({
843867
from: player1Account.address,
844868
fee: { paymentMethod: sponsoredPaymentMethod },
@@ -949,8 +973,7 @@ yarn profile
949973
```typescript
950974
const node = createAztecNodeClient(nodeUrl);
951975
let block = await node.getBlock(BlockNumber(1));
952-
console.log(block);
953-
console.log(await block?.hash());
976+
console.log(block?.header);
954977
```
955978

956979
```bash
@@ -973,6 +996,7 @@ yarn get-block
973996
| `src/test/helpers.nr` | Test helpers — strategies, game setup |
974997
| `src/test/utils.nr` | Test setup — deploy contract, create admin |
975998
| `src/test/e2e/index.test.ts` | TypeScript E2E tests (Jest) |
999+
| `src/test/e2e/public_logging.test.ts` | Logging E2E tests |
9761000
| `src/artifacts/PodRacing.ts` | Generated TypeScript contract bindings |
9771001
| `src/utils/deploy_account.ts` | Deploy a Schnorr account |
9781002
| `src/utils/sponsored_fpc.ts` | SponsoredFPC instance helper |
@@ -984,6 +1008,7 @@ yarn get-block
9841008
| `scripts/multiple_wallet.ts` | Multi-PXE / multi-wallet demo |
9851009
| `scripts/fees.ts` | Fee payment strategies demo |
9861010
| `scripts/profile_deploy.ts` | Transaction profiling |
1011+
| `scripts/read_debug_logs.ts` | Debug logging utility demo |
9871012
| `scripts/get_block.ts` | Block querying |
9881013
| `config/config.ts` | Config manager (loads JSON by env) |
9891014
| `config/local-network.json` | Local network configuration |
@@ -1008,6 +1033,7 @@ yarn get-block
10081033
| `yarn multiple-wallet` | Multi-PXE demo |
10091034
| `yarn fees` | Fee payment methods demo |
10101035
| `yarn profile` | Profile a transaction |
1036+
| `yarn read-logs` | Demo debug logging utility |
10111037
| `yarn get-block` | Query block data |
10121038
| `yarn clean` | Delete `./src/artifacts` and `./target` |
10131039
| `yarn clear-store` | Delete `./store` (PXE data) |

0 commit comments

Comments
 (0)