Skip to content
Open
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
11 changes: 11 additions & 0 deletions .github/workflows/circuit_examples.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,25 @@ jobs:

- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: wasm32v1-none

- name: Cache dependencies
uses: Swatinem/rust-cache@v2
with:
workspaces: examples/${{ matrix.example }}

- name: Test ${{ matrix.example }}
working-directory: examples/${{ matrix.example }}
run: cargo test

- name: Install Stellar CLI
uses: stellar/stellar-cli@v25.1.0

- name: Stellar contract build
working-directory: examples/${{ matrix.example }}
run: stellar contract build

circuits-lib:
name: Circuits Module
runs-on: ubuntu-latest
Expand Down
28 changes: 28 additions & 0 deletions .github/workflows/session_arena.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: Session Arena Example CI

on:
push:
branches: [main, develop]
paths:
- 'examples/session_arena/**'
- '!examples/session_arena/**/*.md'
- '.github/workflows/example_reusable.yml'
- '.github/workflows/session_arena.yml'
pull_request:
branches: [main, develop]
paths:
- 'examples/session_arena/**'
- '!examples/session_arena/**/*.md'
- '.github/workflows/example_reusable.yml'
- '.github/workflows/session_arena.yml'

concurrency:
group: session-arena-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
ci:
uses: ./.github/workflows/example_reusable.yml
with:
job_name: Session Arena Example CI
working_directory: examples/session_arena
28 changes: 28 additions & 0 deletions .github/workflows/spawn_and_move.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: Spawn And Move Example CI

on:
push:
branches: [main, develop]
paths:
- 'examples/spawn_and_move/**'
- '!examples/spawn_and_move/**/*.md'
- '.github/workflows/example_reusable.yml'
- '.github/workflows/spawn_and_move.yml'
pull_request:
branches: [main, develop]
paths:
- 'examples/spawn_and_move/**'
- '!examples/spawn_and_move/**/*.md'
- '.github/workflows/example_reusable.yml'
- '.github/workflows/spawn_and_move.yml'

concurrency:
group: spawn-and-move-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
ci:
uses: ./.github/workflows/example_reusable.yml
with:
job_name: Spawn And Move Example CI
working_directory: examples/spawn_and_move
10 changes: 8 additions & 2 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,17 @@ them as the default onboarding path.

The maintained reference architectures for Cougr should be read in this order:

- `spawn_and_move`: **start here** — canonical onboarding example showing the full modern Cougr pattern
- `tic_tac_toe`: turn-based game with rich components (`impl_rich_component!`) for `Address` and `Vec` fields
- `spawn_and_move`: **start here** — canonical onboarding example showing the full modern Cougr pattern (`SorobanGame`, `impl_component_observed!`, typed ECS)
- `tic_tac_toe`: turn-based game showing rich components (`impl_rich_component!`, `impl_soroban_game!`) for `Address` and `Vec` fields
- `session_arena`: session lifecycle, authorization scopes, and fallback direct-auth flows (`SessionManager`)
- `snake`: canonical arcade loop and `GameApp` tick model
- `battleship`: canonical hidden-information / commit-reveal flow using `privacy::stable` Merkle primitives
- `guild_arena`: canonical account recovery and multi-device authorization flow
- **ZK Circuit Reference Examples**:
- `hidden_hand`: private card deals via `circuits::hidden_cards`
- `fog_explorer`: private line-of-sight map verification via `circuits::fog_of_war`
- `dice_duel`: verifiable on-chain dice rolling via `circuits::fair_dice`
- `blind_auction`: sealed-bid auction reveals via `circuits::sealed_bid`

## Conventions

Expand Down
3 changes: 2 additions & 1 deletion examples/battleship/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ crate-type = ["cdylib", "rlib"]

[dependencies]
soroban-sdk = "25.1.0"
cougr-core = "1.0.0"
cougr-core = "1.1.0"

[dev-dependencies]
soroban-sdk = { version = "25.1.0", features = ["testutils"] }
cougr-core = { version = "1.1.0", features = ["testutils"] }

[profile.release]
opt-level = "z"
Expand Down
3 changes: 3 additions & 0 deletions examples/battleship/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,9 @@ cargo build --release --target wasm32v1-none
cargo test
```

**Recommended Testing Approach:**
For verification of complex setup commitment sequences and attack/reveal turn-based interactions, utilize `GameHarness` and `Scenario` (see [sandbox_tests.rs](file:///Users/kevinbrenes/Cougr/examples/battleship/src/sandbox_tests.rs)). This ensures cryptographic commitments and Merkle proofs verify correctly across turns.

**Test Coverage (10 tests):**
- ✅ Game initialization
- ✅ Board commitment
Expand Down
3 changes: 3 additions & 0 deletions examples/battleship/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ const GRID_SIZE: u32 = 10;
const TOTAL_SHIP_CELLS: u32 = 17; // 5+4+3+3+2

#[contract]
#[derive(Clone)]
pub struct BattleshipContract;

#[contractimpl]
Expand Down Expand Up @@ -344,5 +345,7 @@ impl BattleshipContract {
}
}

#[cfg(test)]
mod sandbox_tests;
#[cfg(test)]
mod test;
97 changes: 97 additions & 0 deletions examples/battleship/src/sandbox_tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
#![cfg(test)]

use crate::{BattleshipContract, BattleshipContractClient, Phase};
use cougr_core::privacy::stable::{to_on_chain_proof, MerkleTree};
use cougr_core::test::{GameHarness, PlayerSlot, Scenario};
use soroban_sdk::{Bytes, BytesN, Env};

fn build_merkle_tree(env: &Env, board: &[u32; 100]) -> (BytesN<32>, MerkleTree) {
let mut leaves = [[0u8; 32]; 100];
for (idx, &value) in board.iter().enumerate() {
let mut data = Bytes::new(env);
data.append(&Bytes::from_array(env, &(idx as u32).to_be_bytes()));
data.append(&Bytes::from_array(env, &value.to_be_bytes()));
let leaf: BytesN<32> = env.crypto().sha256(&data).into();
leaves[idx] = leaf.to_array();
}
let tree = MerkleTree::from_leaves(env, &leaves).unwrap();
(tree.root_bytes(env), tree)
}

fn make_commitment(env: &Env, board: &[u32; 100], salt: &BytesN<32>) -> BytesN<32> {
let mut data = Bytes::new(env);
for &cell in board.iter() {
data.append(&Bytes::from_array(env, &cell.to_be_bytes()));
}
for i in 0..32 {
data.push_back(salt.get(i).unwrap());
}
env.crypto().sha256(&data).into()
}

#[test]
fn sandbox_battleship_gameplay_scenario() {
let env = Env::default();
let mut harness = GameHarness::new(env, BattleshipContract);
harness.mock_players(2);
harness.mock_all_auths();

let client = BattleshipContractClient::new(harness.env(), harness.contract_id());
let p_a = harness.player(PlayerSlot(0)).clone();
let p_b = harness.player(PlayerSlot(1)).clone();

client.new_game(&p_a, &p_b);

// Setup boards and commitments
let board_a = [0u32; 100];
let mut board_b = [0u32; 100];
board_b[10] = 1; // Ship at (1, 0) (index 10)

let salt_a = BytesN::from_array(harness.env(), &[1u8; 32]);
let salt_b = BytesN::from_array(harness.env(), &[2u8; 32]);

let commitment_a = make_commitment(harness.env(), &board_a, &salt_a);
let (root_a, _) = build_merkle_tree(harness.env(), &board_a);

let commitment_b = make_commitment(harness.env(), &board_b, &salt_b);
let (root_b, tree_b) = build_merkle_tree(harness.env(), &board_b);

Scenario::new("battleship deployment and attack")
.players(2)
.turns(4)
.run(&harness, |player_slot, turn, h| {
let c = BattleshipContractClient::new(h.env(), h.contract_id());
let p = h.player(player_slot).clone();
match turn.0 {
0 => {
// Player A commits
c.commit_board(&p, &commitment_a, &root_a);
assert_eq!(c.get_state().turn_state.phase, Phase::Setup);
}
1 => {
// Player B commits -> transitions to Attack phase
c.commit_board(&p, &commitment_b, &root_b);
assert_eq!(c.get_state().turn_state.phase, Phase::Attack);
}
2 => {
// Player A attacks cell (0, 1) (index 10)
c.attack(&p, &0, &1);
let state = c.get_state();
assert!(state.turn_state.has_pending);
assert_eq!(state.turn_state.pending_reveal_x, 0);
assert_eq!(state.turn_state.pending_reveal_y, 1);
}
3 => {
// Player B reveals cell (0, 1)
let proof = to_on_chain_proof(&tree_b.proof(10).unwrap(), h.env());
c.reveal_cell(&p, &0, &1, &1, &proof);
let state = c.get_state();
assert!(!state.turn_state.has_pending);
// Check hit recorded
let cell = state.attack_grid_b.cells.get(10).unwrap();
assert_eq!(cell, crate::CellResult::Hit);
}
_ => {}
}
});
}
4 changes: 2 additions & 2 deletions examples/blind_auction/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ crate-type = ["cdylib", "rlib"]

[dependencies]
soroban-sdk = "25.1.0"
cougr-core = { path = "../../" }
cougr-core = "1.1.0"

[dev-dependencies]
soroban-sdk = { version = "25.1.0", features = ["testutils"] }
cougr-core = { features = ["testutils"], path = "../../" }
cougr-core = { version = "1.1.0", features = ["testutils"] }

[profile.release]
opt-level = "z"
Expand Down
66 changes: 66 additions & 0 deletions examples/blind_auction/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# blind_auction

**Canonical** ZK circuit example demonstrating `circuits::sealed_bid` for sealed-bid reveals.

## Purpose and pattern

This example demonstrates how to run a blind auction on-chain. Bidders commit their encrypted bids, and during the reveal phase, submit a ZK proof to verify their bid value matches the original commitment and is below the maximum allowed bid limits, without revealing bids early or leaking bid range details.

## Public contract API

| Function | Parameters | Returns | Description |
|---|---|---|---|
| `init_auction` | `max_bid: u32`, `auction_id: BytesN<32>` | `AuctionConfig` | Starts a new auction config with maximum bid limits and an auction identifier. |
| `reveal_bid` | `bidder: Address`, `bid_commitment: BytesN<32>`, `revealed_bid: u32`, `proof: Groth16Proof` | `bool` | Verifies a bidder's reveal proof, storing the bid record if valid. |
| `bid_reveal` | `bidder: Address` | `BidReveal` | Retrieves the revealed bid details for a bidder. |

## Architecture overview

```
┌───────────────┐
│ Bidder │
└──────┬────────┘
│ Submits Bid & ZK Proof
┌──────────▼──────────┐
│ BlindAuction │
│ (Soroban Contract) │
└──────────┬──────────┘
│ Loads Spec
┌──────────▼──────────┐
│ circuits:: │
│ sealed_bid │
└─────────────────────┘
```

The bidder submits their bid value and proof that verifies their bid corresponds to the committed hash. The contract runs the `sealed_bid` verifier logic to register the bid.

## Storage model

Auction configuration records and active bid listings are stored in **Instance Storage** on-chain.

## Main gameplay flow

1. **Setup**: Call `init_auction` to setup maximum bid constraints and the auction ID.
2. **Commit Phase**: Bidders record hash commitments of their bids (handled off-chain or via standard storage).
3. **Reveal Phase**: Bidders call `reveal_bid` with their bids and ZK proofs to unlock and register their bid weights.

## Cougr APIs used

- `circuits::sealed_bid`: Handles verify calculations for sealed on-chain bidding logic.
- `zk::Groth16Proof`: Contains the proof struct representation.

## Recommended testing approach

Use `GameHarness` and standard `test_fixtures` to execute happy-path reveals and invalid proof rejection flows.

## Build and test commands

```bash
cargo test
stellar contract build
```

## Known limitations

- Simple single-item auction model.
- Winner computation logic is not included (focuses on verification of bid validity).
6 changes: 4 additions & 2 deletions examples/blind_auction/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@

use cougr_core::circuits::sealed_bid;
use cougr_core::zk::Groth16Proof;
use soroban_sdk::{contract, contractimpl, contracttype, symbol_short, Address, BytesN, Env, Symbol};
use soroban_sdk::{
contract, contractimpl, contracttype, symbol_short, Address, BytesN, Env, Symbol,
};

#[contracttype]
#[derive(Clone, Debug)]
Expand Down Expand Up @@ -87,4 +89,4 @@ fn bid_key(_env: &Env, bidder: &Address) -> (Symbol, Address) {
}

#[cfg(test)]
mod tests;
mod tests;
2 changes: 1 addition & 1 deletion examples/blind_auction/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,4 @@ fn reveal_bid_accepts_pipeline_proof() {
assert!(client.reveal_bid(&bidder, &commit, &50, &proof));
let reveal = client.bid_reveal(&bidder);
assert_eq!(reveal.revealed_bid, 50);
}
}
4 changes: 2 additions & 2 deletions examples/dice_duel/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ crate-type = ["cdylib", "rlib"]

[dependencies]
soroban-sdk = "25.1.0"
cougr-core = { path = "../../" }
cougr-core = "1.1.0"

[dev-dependencies]
soroban-sdk = { version = "25.1.0", features = ["testutils"] }
cougr-core = { path = "../../", features = ["testutils"] }
cougr-core = { version = "1.1.0", features = ["testutils"] }

[profile.release]
opt-level = "z"
Expand Down
Loading
Loading