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
108 changes: 108 additions & 0 deletions .github/workflows/prediction-market-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
name: Prediction Market Tests

on:
push:
branches:
- main
pull_request:
branches:
- main
paths:
- "prediction_market_contract/**"
- ".github/workflows/prediction-market-tests.yml"
workflow_dispatch:

jobs:
prediction-market-tests:
name: Prediction Market Tests
runs-on: ubuntu-latest
env:
AZTEC_ENV: sandbox
AZTEC_VERSION: 3.0.0-devnet.4

steps:
- name: Checkout repository
uses: actions/checkout@v5

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: "22"

- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.1.36

- name: Set up Docker
uses: docker/setup-buildx-action@v3

- name: Install Aztec CLI
run: |
curl -s https://install.aztec.network > tmp.sh
NON_INTERACTIVE=1 bash tmp.sh
rm tmp.sh

- name: Update path
run: echo "$HOME/.aztec/bin" >> $GITHUB_PATH

- name: Set Aztec version and start sandbox
run: |
aztec-up ${{ env.AZTEC_VERSION }}
docker tag aztecprotocol/aztec:${{ env.AZTEC_VERSION }} aztecprotocol/aztec:latest
aztec start --sandbox &

- name: Wait for sandbox to be ready
run: |
echo "Waiting for sandbox to start..."
MAX_RETRIES=60
for i in $(seq 1 $MAX_RETRIES); do
if curl -s http://localhost:8080/status >/dev/null 2>&1; then
echo "Sandbox is ready!"
break
fi
if [ $i -eq $MAX_RETRIES ]; then
echo "Sandbox failed to start after $MAX_RETRIES attempts"
exit 1
fi
echo "Waiting... ($i/$MAX_RETRIES)"
sleep 2
done

- name: Install project dependencies
working-directory: prediction_market_contract
run: bun install

- name: Run Noir unit tests
working-directory: prediction_market_contract
run: aztec test
timeout-minutes: 10

- name: Compile contract and generate artifacts
working-directory: prediction_market_contract
run: |
aztec-nargo compile
aztec-postprocess-contract
aztec codegen target -o artifacts

- name: Run end-to-end tests
working-directory: prediction_market_contract
run: bun test
timeout-minutes: 15

- name: Upload test results if failed
if: failure()
uses: actions/upload-artifact@v4
with:
name: test-logs
path: |
prediction_market_contract/tests/**/*.log
retention-days: 7

- name: Cleanup
if: always()
run: |
echo "Stopping Aztec sandbox..."
pkill -f "aztec" || true
docker stop $(docker ps -q) || true
docker rm $(docker ps -a -q) || true
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
target
.DS_Store
ivc
ivc
node_modules
6 changes: 6 additions & 0 deletions prediction-market/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
target/
artifacts/
node_modules/
dist/
*.log
.DS_Store
9 changes: 9 additions & 0 deletions prediction-market/Nargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[package]
name = "prediction_market_contract"
authors = [""]
compiler_version = ">=0.25.0"
type = "contract"

[dependencies]
aztec = { git = "https://github.com/AztecProtocol/aztec-packages/", tag = "v3.0.0-devnet.5", directory = "noir-projects/aztec-nr/aztec" }
uint_note = { git = "https://github.com/AztecProtocol/aztec-packages/", tag = "v3.0.0-devnet.5", directory = "noir-projects/aztec-nr/uint-note" }
164 changes: 164 additions & 0 deletions prediction-market/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
# Prediction Market Contract

A prediction market implementation on Aztec using a **Constant Sum Market Maker (CSMM)** for binary outcomes with **full identity privacy**.

## Overview

This contract allows users to:
- **Bet on binary outcomes** (YES/NO) **anonymously** - no one knows WHO placed a bet
- **Hold fully private balances** - collateral and share holdings are all hidden
- **Experience dynamic pricing** that adjusts based on market sentiment
- **Get slippage protection** to prevent adverse price movements
- **Single-transaction betting** using partial notes pattern

## Privacy Model

### What's Private

| Data | Privacy | Notes |
|------|---------|-------|
| Bettor identity | **PRIVATE** | Public function doesn't receive sender address |
| Collateral balances | **PRIVATE** | Stored as private notes (UintNote) |
| Share balances | **PRIVATE** | Stored as private notes (UintNote) |
| Your bet (YES/NO) | **PRIVATE** | Hidden via partial notes |

### What's Public

| Data | Visibility | Why |
|------|------------|-----|
| Price changes | PUBLIC | Necessary for AMM pricing |
| Trade amounts | PUBLIC | Affects price movement |
| That someone bought YES/NO | PUBLIC | Observable from price changes |

### Privacy Architecture

The contract uses **partial notes** for private betting (like the [AMM contract](https://github.com/AztecProtocol/aztec-packages/tree/master/noir-projects/noir-contracts/contracts/amm_contract)):

```
1. buy_outcome() [PRIVATE]
|
+-- Consumes private collateral notes
+-- Creates change note if needed
+-- Creates partial note commitment (hides owner)
+-- Enqueues public call WITHOUT sender address
v
2. _process_buy() [PUBLIC]
|
+-- Calculates shares based on CSMM pricing
+-- Updates YES/NO supplies (price changes)
+-- Completes partial note with share amount
v
Shares appear in user's private balance (single tx!)
```

Key privacy feature: The public `_process_buy()` function **does not receive the sender address**. It only knows WHAT was traded, not WHO traded.

## Usage

### Betting Flow (Single Transaction)

```typescript
// Deposit collateral privately
await market.methods.deposit(1000n).send({ from: myAddress }).wait()

// Buy outcome privately - single transaction!
await market.methods.buy_outcome(
true, // is_yes
500n, // collateral_amount
900n, // min_shares_out (slippage protection)
).send({ from: myAddress }).wait()

// Your shares are immediately in your private balance!
const myBalance = await market.methods.get_yes_balance(myAddress).simulate({ from: myAddress })
```

### Collateral Management (All Private)

```typescript
// Deposit collateral (private)
await market.methods.deposit(1000n).send({ from: myAddress }).wait()

// Withdraw collateral (private)
await market.methods.withdraw(500n).send({ from: myAddress }).wait()

// Check balance (view private notes)
const balance = await market.methods.get_collateral_balance(myAddress).simulate({ from: myAddress })
```

## Development

### Prerequisites

```bash
# Install Aztec tools
bash -i <(curl -s https://install.aztec.network)
aztec-up 3.0.0-devnet.5

# Install dependencies
yarn install
```

### Building

```bash
# Compile the contract
aztec-nargo compile

# Post-process and generate TypeScript bindings
aztec-postprocess-contract
aztec codegen target -o artifacts
```

### Testing

#### Noir Unit Tests

```bash
aztec test
```

Tests the CSMM pricing functions:
- Share calculations at various price points
- Price calculations and invariants
- Edge cases

#### End-to-End Tests

```bash
# Start Aztec sandbox (in separate terminal)
aztec start --sandbox

# Run tests
yarn test
```

Tests the full private betting flow:
- Contract deployment
- Private deposit/withdraw
- Private buy_outcome (single tx with partial notes)
- Price movements
- Balance privacy

## Constant Sum Market Maker (CSMM)

### Core Invariant
```
price_YES + price_NO = 1
```

### Pricing Formula
```
price_YES = yes_supply / (yes_supply + no_supply)
shares_out = collateral_in / current_price
```

### Example

```
Initial: YES=5000, NO=5000, price_YES=50%

Alice buys 1000 collateral of YES:
- Shares received: 1000 / 0.50 = 2000 YES
- New YES supply: 7000
- New price_YES: 7000 / 12000 = 58.3%
```
17 changes: 17 additions & 0 deletions prediction-market/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export default {
testEnvironment: 'node',
transform: {
'^.+\\.(t|j)sx?$': ['@swc/jest'],
'^.+\\.mjs$': ['@swc/jest'],
},
extensionsToTreatAsEsm: ['.ts'],
moduleNameMapper: {
'^(\\.{1,2}/.*)\\.js$': '$1',
},
transformIgnorePatterns: [
'node_modules/(?!(@aztec|ohash|fake-indexeddb)/)',
],
testMatch: ['**/tests/**/*.test.ts'],
testTimeout: 120000,
verbose: true,
};
31 changes: 31 additions & 0 deletions prediction-market/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"name": "prediction_market_contract",
"type": "module",
"scripts": {
"clean": "rm -rf target artifacts",
"ccc": "aztec-nargo compile && aztec-postprocess-contract && aztec codegen target -o artifacts",
"test": "jest",
"test:watch": "jest --watch",
"test:noir": "aztec test"
},
"devDependencies": {
"@jest/globals": "^29.0.0",
"@swc/core": "^1.3.0",
"@swc/jest": "^0.2.0",
"@types/jest": "^29.0.0",
"@types/node": "^20.0.0",
"fake-indexeddb": "^6.2.5",
"jest": "^29.0.0",
"tsx": "^4.21.0",
"typescript": "^5.0.0"
},
"peerDependencies": {
"typescript": "^5.0.0"
},
"dependencies": {
"@aztec/accounts": "3.0.0-devnet.5",
"@aztec/aztec.js": "3.0.0-devnet.5",
"@aztec/pxe": "3.0.0-devnet.5",
"@aztec/test-wallet": "3.0.0-devnet.5"
}
}
42 changes: 42 additions & 0 deletions prediction-market/run-tests.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#!/bin/bash
set -e

echo "=== Prediction Market Contract Tests ==="
echo ""

# Check if sandbox is running
if ! curl -s http://localhost:8080/status >/dev/null 2>&1; then
echo "Error: Aztec sandbox is not running!"
echo "Please start it with: aztec start --sandbox"
exit 1
fi

echo "1. Cleaning previous builds..."
rm -rf target artifacts

echo ""
echo "2. Running Noir unit tests..."
aztec test

echo ""
echo "3. Compiling contract..."
aztec-nargo compile

echo ""
echo "4. Post-processing contract..."
aztec-postprocess-contract

echo ""
echo "5. Generating TypeScript bindings..."
aztec codegen target -o artifacts

echo ""
echo "6. Installing dependencies..."
bun install

echo ""
echo "7. Running end-to-end tests..."
bun test

echo ""
echo "=== All tests passed! ==="
Loading
Loading