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
26 changes: 24 additions & 2 deletions .github/workflows/ci-cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,29 @@ jobs:
fi

build-and-deploy:
needs: validate-pr # enforce validation before build
needs: validate-pr
runs-on: ubuntu-latest
steps:
# existing build steps here
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20.x
cache: npm
- name: Install dependencies
run: npm ci
- name: Build frontend
run: npm --workspace frontend run build
- name: Build backend
run: npm --workspace backend run build
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
targets: wasm32-unknown-unknown
- name: Cache Rust dependencies
uses: Swatinem/rust-cache@v2
with:
workspaces: "contract"
- name: Build contract
run: cargo build --target wasm32-unknown-unknown --release
working-directory: contract
27 changes: 27 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,30 @@ jobs:
run: npm --workspace frontend exec -- tsc --noEmit -p tsconfig.json
- name: Type-check backend
run: npm --workspace backend exec -- tsc --noEmit -p tsconfig.json

contracts:
name: contracts
runs-on: ubuntu-latest
defaults:
run:
working-directory: contract
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
targets: wasm32-unknown-unknown
- name: Cache dependencies
uses: Swatinem/rust-cache@v2
with:
workspaces: "contract"
- name: Check formatting
run: cargo fmt --all -- --check
- name: Run clippy
run: cargo clippy --all-targets --all-features -- -D warnings
- name: Build Contracts
run: cargo build --target wasm32-unknown-unknown --release
- name: Run tests
run: cargo test
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,6 @@ contributoNote.md

# Rust build artifacts
contract/target/
contract/Cargo.lock
contract/Cargo.lock

.DS_Store
33 changes: 32 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,39 @@ npm --workspace frontend exec -- tsc --noEmit -p tsconfig.json
npm --workspace backend exec -- tsc --noEmit -p tsconfig.json
```

## Contract Development

### Prerequisites
Install the following tools before working on contracts:

```bash
# Install Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

# Add wasm32-unknown-unknown target
rustup target add wasm32-unknown-unknown

# Install Stellar CLI
cargo install --locked stellar-cli
```

**MUST RUN** Local checks from inside `contracts/` before submitting a PR:

```bash
cd contracts/

# Check formatting
cargo fmt --check

# Build the contract
stellar contract build

# Run tests
cargo test
```

## Branch Protection
main and develop require status checks: lint-imports, build, type-check.
main and develop require status checks: lint-imports, build, type-check, contracts.
Require branches to be up-to-date before merging.

## Pull Request Standards
Expand Down
20 changes: 19 additions & 1 deletion contract/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,24 @@
[package]
name = "contract"
version = "0.1.0"
edition = "2024"
edition = "2021"

[dependencies]
soroban-sdk = "23"

[dev-dependencies]
soroban-sdk = { version = "23", features = ["testutils"] }

[profile.release]
opt-level = "z"
overflow-checks = true
debug = 0
strip = "symbols"
debug-assertions = false
panic = "abort"
codegen-units = 1
lto = true

[profile.release-with-logs]
inherits = "release"
debug-assertions = true
99 changes: 46 additions & 53 deletions contract/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#![no_std]
use soroban_sdk::{contract, contractimpl, contracttype, Address, Env, String, Vec, Map};
use soroban_sdk::{contract, contractimpl, contracttype, Address, Env, String, Vec};

#[derive(Clone)]
#[contracttype]
Expand Down Expand Up @@ -28,14 +28,9 @@ pub struct MindBlockContract;
#[contractimpl]
impl MindBlockContract {
/// Initialize a new player profile
pub fn register_player(
env: Env,
player: Address,
username: String,
iq_level: u32,
) -> Player {
pub fn register_player(env: Env, player: Address, username: String, iq_level: u32) -> Player {
player.require_auth();

let new_player = Player {
address: player.clone(),
username: username.clone(),
Expand All @@ -44,16 +39,16 @@ impl MindBlockContract {
puzzles_solved: 0,
current_streak: 0,
};

env.storage().instance().set(&player, &new_player);
new_player
}

/// Get player profile
pub fn get_player(env: Env, player: Address) -> Option<Player> {
env.storage().instance().get(&player)
}

/// Submit puzzle solution and award XP
pub fn submit_puzzle(
env: Env,
Expand All @@ -63,23 +58,24 @@ impl MindBlockContract {
score: u32,
) -> u64 {
player.require_auth();

let mut player_data: Player = env.storage()

let mut player_data: Player = env
.storage()
.instance()
.get(&player)
.unwrap_or_else(|| panic!("Player not registered"));

// Calculate XP based on score and IQ level
let xp_reward = (score as u64) * (player_data.iq_level as u64) / 10;

// Update player stats
player_data.xp += xp_reward;
player_data.puzzles_solved += 1;
player_data.current_streak += 1;

// Save updated player data
env.storage().instance().set(&player, &player_data);

// Record submission
let submission = PuzzleSubmission {
player: player.clone(),
Expand All @@ -88,66 +84,63 @@ impl MindBlockContract {
score,
timestamp: env.ledger().timestamp(),
};

let submission_key = (player.clone(), puzzle_id);
env.storage().instance().set(&submission_key, &submission);

player_data.xp
}

/// Get top players by XP (leaderboard)
pub fn get_leaderboard(env: Env, limit: u32) -> Vec<Player> {
pub fn get_leaderboard(env: Env, _limit: u32) -> Vec<Player> {
// Note: In production, implement proper pagination and sorting
// This is a simplified version
let mut leaderboard = Vec::new(&env);

// This would need to be implemented with proper indexing
// For now, returns empty vector as placeholder
leaderboard
Vec::new(&env)
}

/// Update player IQ level
pub fn update_iq_level(env: Env, player: Address, new_iq_level: u32) {
player.require_auth();

let mut player_data: Player = env.storage()

let mut player_data: Player = env
.storage()
.instance()
.get(&player)
.unwrap_or_else(|| panic!("Player not registered"));

player_data.iq_level = new_iq_level;
env.storage().instance().set(&player, &player_data);
}

/// Reset player streak (called when streak is broken)
pub fn reset_streak(env: Env, player: Address) {
player.require_auth();

let mut player_data: Player = env.storage()

let mut player_data: Player = env
.storage()
.instance()
.get(&player)
.unwrap_or_else(|| panic!("Player not registered"));

player_data.current_streak = 0;
env.storage().instance().set(&player, &player_data);
}

/// Get player's total XP
pub fn get_xp(env: Env, player: Address) -> u64 {
let player_data: Player = env.storage()
let player_data: Player = env
.storage()
.instance()
.get(&player)
.unwrap_or_else(|| panic!("Player not registered"));

player_data.xp
}

/// Get puzzle submission details
pub fn get_submission(
env: Env,
player: Address,
puzzle_id: u64,
) -> Option<PuzzleSubmission> {
pub fn get_submission(env: Env, player: Address, puzzle_id: u64) -> Option<PuzzleSubmission> {
let submission_key = (player, puzzle_id);
env.storage().instance().get(&submission_key)
}
Expand All @@ -161,35 +154,35 @@ mod test {
#[test]
fn test_register_player() {
let env = Env::default();
let contract_id = env.register_contract(None, MindBlockContract);
let contract_id = env.register(MindBlockContract, ());
let client = MindBlockContractClient::new(&env, &contract_id);

let player = Address::generate(&env);
let username = String::from_str(&env, "TestPlayer");

env.mock_all_auths();

let result = client.register_player(&player, &username, &100);

assert_eq!(result.xp, 0);
assert_eq!(result.iq_level, 100);
}

#[test]
fn test_submit_puzzle() {
let env = Env::default();
let contract_id = env.register_contract(None, MindBlockContract);
let contract_id = env.register(MindBlockContract, ());
let client = MindBlockContractClient::new(&env, &contract_id);

let player = Address::generate(&env);
let username = String::from_str(&env, "TestPlayer");
let category = String::from_str(&env, "coding");

env.mock_all_auths();

client.register_player(&player, &username, &100);
let xp = client.submit_puzzle(&player, &1, &category, &95);

assert!(xp > 0);
}
}
}
Loading