Skip to content
Closed
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
18 changes: 16 additions & 2 deletions .github/workflows/ci-cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ jobs:
steps:
- name: Validate PR title
uses: amannn/action-semantic-pull-request@v5
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
types: |
feat
Expand All @@ -22,8 +24,10 @@ jobs:
requireScope: false

- name: Validate PR description
env:
PR_BODY: ${{ github.event.pull_request.body }}
run: |
if ! grep -qE ".{20,}" <<< "${{ github.event.pull_request.body }}"; then
if ! grep -qE ".{20,}" <<< "$PR_BODY"; then
echo "❌ PR description too short. Please provide context."
exit 1
fi
Expand All @@ -32,4 +36,14 @@ jobs:
needs: validate-pr # enforce validation before build
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
26 changes: 25 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,35 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18
node-version: 20.x
cache: npm
- name: Install dependencies
run: npm ci
- name: Type-check frontend
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
steps:
- uses: actions/checkout@v4
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
targets: wasm32-unknown-unknown
components: rustfmt
- name: Install Stellar CLI
run: |
curl -fsSL https://github.com/stellar/stellar-cli/raw/main/install.sh | sh -s -- --dir=/usr/local/bin --install-deps
- name: Check formatting
run: cargo fmt --check
working-directory: contracts
- name: Build contract
run: stellar contract build
working-directory: contracts
- name: Run tests
run: cargo test
working-directory: contracts
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ node_modules
contributoNote.md

# Rust build artifacts
contract/target/
contract/Cargo.lock
contracts/target/
contracts/Cargo.lock
10 changes: 9 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ CI will reject PRs containing src/* imports.
Issue/PR: https://github.com/MindBlockLabs/mindBlock_app/pull/0000 (placeholder)

**MUST RUN** Local check to before submitting a pr:

For frontend/backend contributors:
```bash
npm ci
npm --workspace frontend run build
Expand All @@ -30,8 +32,14 @@ npm --workspace frontend exec -- tsc --noEmit -p tsconfig.json
npm --workspace backend exec -- tsc --noEmit -p tsconfig.json
```

For contract contributors:
- Install prerequisites: Rust, wasm32-unknown-unknown target, and Stellar CLI
- Check formatting: `cargo fmt --check` run from inside `contracts/`
- Build the contract: `stellar contract build` run from inside `contracts/`
- Run tests: `cargo test` run from inside `contracts/`

## 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
6 changes: 0 additions & 6 deletions contract/Cargo.toml

This file was deleted.

File renamed without changes.
13 changes: 13 additions & 0 deletions contracts/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
name = "contract"
version = "0.1.0"
edition = "2024"

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

[lib]
crate-type = ["cdylib", "rlib"]

[profile.release]
overflow-checks = true
File renamed without changes.
101 changes: 48 additions & 53 deletions contract/src/lib.rs → contracts/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::{Address, Env, String, Vec, contract, contractimpl, contracttype};

#[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,65 @@ 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);
let leaderboard = Vec::new(&env);

// This would need to be implemented with proper indexing
// For now, returns empty vector as placeholder
leaderboard
}

/// 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 @@ -156,40 +151,40 @@ impl MindBlockContract {
#[cfg(test)]
mod test {
use super::*;
use soroban_sdk::{testutils::Address as _, Address, Env, String};
use soroban_sdk::{Address, Env, String, testutils::Address as _};

#[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