diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..116519f --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,43 @@ +name: CI + +on: + push: + branches: [main, dev, "fix/**"] + pull_request: + branches: [main, dev] + +env: + CARGO_TERM_COLOR: always + +jobs: + check-and-test: + name: Build & Test + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt, clippy + + - name: Cache cargo registry & build + uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-cargo- + + - name: Check compilation + run: cargo check --workspace --exclude program_methods + + - name: Run tests + run: cargo test --workspace --exclude program_methods + + - name: Check formatting + run: cargo fmt --all -- --check \ No newline at end of file diff --git a/e_identity_sdk/src/ffi.rs b/e_identity_sdk/src/ffi.rs index 3592177..a5edd13 100644 --- a/e_identity_sdk/src/ffi.rs +++ b/e_identity_sdk/src/ffi.rs @@ -262,7 +262,10 @@ pub unsafe extern "C" fn ffi_username_registry_lookup_by_username( Err(_) => return error_json("invalid UTF-8"), }; match (*handle).inner.lookup_by_username(name) { - Some(commitment) => ok_json(&format!("{{\"commitment\":\"{}\"}}", hex::encode(commitment))), + Some(commitment) => ok_json(&format!( + "{{\"commitment\":\"{}\"}}", + hex::encode(commitment) + )), None => ok_json("{\"commitment\":null}"), } } diff --git a/program_methods/guest/src/bin/membership_registry.rs b/program_methods/guest/src/bin/membership_registry.rs index a2a08df..aaff3f3 100644 --- a/program_methods/guest/src/bin/membership_registry.rs +++ b/program_methods/guest/src/bin/membership_registry.rs @@ -1,9 +1,8 @@ #![no_main] -use membership_registry::{ - state::ForumInstance, - initialize, join_room, record_strike, register, - register_room, slash, verify_post, +use membership_registry::{ + initialize, join_room, record_strike, register, register_room, slash, state::ForumInstance, + verify_post, }; use nssa_core::account::AccountWithMetadata; use spel_framework::prelude::*; @@ -136,7 +135,10 @@ mod forum_registry { message: "Data too large".into(), })?; - Ok(SpelOutput::execute(vec![state.account, member.account], vec![])) + Ok(SpelOutput::execute( + vec![state.account, member.account], + vec![], + )) } #[instruction] diff --git a/programs/membership_registry/src/join_room.rs b/programs/membership_registry/src/join_room.rs index 4f4fcef..ce6cd2b 100644 --- a/programs/membership_registry/src/join_room.rs +++ b/programs/membership_registry/src/join_room.rs @@ -1,6 +1,6 @@ use crate::state::{ForumInstance, OnChainMembership}; -/// Process a room join instruction. +/// Process a room join instruction. /// Validates that: /// 1. The room exists on-chain /// 2. The member commitment is registered and not revoked diff --git a/programs/membership_registry/src/register.rs b/programs/membership_registry/src/register.rs index 839daa2..4572ee9 100644 --- a/programs/membership_registry/src/register.rs +++ b/programs/membership_registry/src/register.rs @@ -13,9 +13,58 @@ pub fn process_register( return Err("Registration failed: This commitment is already registered."); } + // Reject commitments that were previously revoked via slashing. + // This prevents re-use of a compromised identity whose NSK was exposed. + if forum.revoked_commitments.contains(&commitment_bytes) { + return Err("Registration failed: This commitment has been revoked."); + } + forum.registered_commitments.push(commitment_bytes); forum.member_stakes.push((commitment_bytes, stake_amount)); forum.total_staked += stake_amount; Ok(()) } + +#[cfg(test)] +mod tests { + use super::*; + use crate::initialize::process_initialize; + + #[test] + fn test_register_success() { + let mut forum = process_initialize(3, 2, 3).unwrap(); + let commitment = [1u8; 32]; + assert!(process_register(&mut forum, commitment, 1000).is_ok()); + assert_eq!(forum.registered_commitments.len(), 1); + assert_eq!(forum.total_staked, 1000); + } + + #[test] + fn test_register_insufficient_stake() { + let mut forum = process_initialize(3, 2, 3).unwrap(); + let commitment = [1u8; 32]; + assert!(process_register(&mut forum, commitment, 999).is_err()); + } + + #[test] + fn test_register_duplicate_commitment() { + let mut forum = process_initialize(3, 2, 3).unwrap(); + let commitment = [1u8; 32]; + assert!(process_register(&mut forum, commitment, 1000).is_ok()); + assert!(process_register(&mut forum, commitment, 1000).is_err()); + } + + #[test] + fn test_register_revoked_commitment_rejected() { + let mut forum = process_initialize(3, 2, 3).unwrap(); + let commitment = [2u8; 32]; + forum.revoked_commitments.push(commitment); + let res = process_register(&mut forum, commitment, 1000); + assert!(res.is_err()); + assert_eq!( + res.unwrap_err(), + "Registration failed: This commitment has been revoked." + ); + } +}