Skip to content

Conversation

@hakymulla
Copy link
Contributor

@hakymulla hakymulla commented Jan 21, 2026

🧠 SkillSphere Pull Request 🌐

Mark with an x all the checkboxes that apply (like [x])

  • Closes Implement Batch Verification #4
  • Added tests (if necessary)
  • Ran cargo test (All tests passed)
  • Evidence attached (Screenshots, Logs, or Transaction Hashes)
  • Commented the code

📌 Type of Change

  • 📚 Documentation (updates to README, docs, or comments)
  • 🐛 Bug fix (non-breaking change which fixes an issue)
  • ✨ Enhancement (non-breaking change which adds functionality)
  • 💥 Breaking change (fix or feature that would cause existing functionality to change)
  • 🏗️ Refactor (code improvement/cleanup without logical changes)

📝 Changes Description

(Briefly describe what you changed and why. If this modifies a Smart Contract, explain the impact on storage or gas.)


📸 Evidence

(A photo, log output, or Soroban CLI output is required as evidence. If this is a smart contract change, please include the test results or invocation logs.)
Screenshot 2026-01-21 at 19 39 25


🌌 Comments

(Any extra notes for the reviewer? Are there any specific edge cases they should test?)


Thank you for contributing to SkillSphere! 🌍

We are glad you have chosen to help us democratize access to knowledge on the Stellar network. Your contribution brings us one step closer to a trustless, peer-to-peer consulting economy. Let's build the future together! 🚀

Summary by CodeRabbit

  • New Features

    • Batch operation to add and verify multiple experts at once (limit: 20), admin-only.
    • Emits status-change events when experts are verified.
  • Bug Fixes

    • Explicit error for batches that exceed the allowed size.
    • Enforce admin authorization for verification operations.
    • Prevent verifying an already-verified expert.
  • Tests

    • Expanded tests for batch additions, admin authorization, status checks, and batch-size limits.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 21, 2026

📝 Walkthrough

Walkthrough

Adds a new admin-gated API batch_add_experts(env, experts: Vec<Address>) (max 20 items, error ExpertVecMax) that verifies multiple experts, rejects already-verified entries, updates storage statuses, and emits status-change events; tests added for auth, success, and size-limit behavior.

Changes

Cohort / File(s) Summary
Error Definition
contracts/identity-registry-contract/src/error.rs
Added enum variant ExpertVecMax = 7 to pub enum RegistryError.
Public API (lib)
contracts/identity-registry-contract/src/lib.rs
Added pub fn batch_add_experts(env: Env, experts: Vec<Address>) -> Result<(), RegistryError> to expose batch verification.
Contract Logic
contracts/identity-registry-contract/src/contract.rs
New batch_add_experts: checks experts.len() <= 20 (returns ExpertVecMax otherwise), requires admin auth, iterates experts, rejects AlreadyVerified, sets status to Verified, emits status-change events; also added admin.require_auth() in verify_expert and uses admin in events.
Tests
contracts/identity-registry-contract/src/test.rs
Added tests: non-admin rejection, successful batch verification asserting statuses, and max-vector-size boundary test expecting contract error #7.

Sequence Diagram(s)

sequenceDiagram
    rect rgba(200,200,255,0.5)
    actor Admin
    end
    rect rgba(200,255,200,0.5)
    participant Contract as IdentityRegistryContract
    end
    rect rgba(255,200,200,0.5)
    participant Storage
    participant Events
    end

    Admin->>Contract: batch_add_experts(experts: Vec<Address>)
    Contract->>Contract: if experts.len() > 20 -> Err(ExpertVecMax)
    alt length OK
        Contract->>Storage: get_admin()
        Contract->>Contract: admin.require_auth()
        alt not admin
            Contract-->>Admin: Err(Unauthorized)
        else admin authorized
            loop for each expert
                Contract->>Storage: get_expert_status(addr)
                alt status == Verified
                    Contract-->>Admin: Err(AlreadyVerified)
                else
                    Contract->>Storage: set_expert_status(addr, Verified)
                    Contract->>Events: emit StatusChanged(addr, prev -> Verified, admin)
                end
            end
            Contract-->>Admin: Ok(())
        end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 I hop through lists, up to twenty at a time,
I stamp them Verified with a rhythmic rhyme.
Carrots of events pop up in a row,
Admin nods, I bounce — off we go! 🥕

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Implement Batch Verification' directly and clearly matches the main change in the changeset, which adds a batch_add_experts function for verifying multiple experts in a single transaction.
Description check ✅ Passed The PR description follows the template structure with all critical checkboxes marked complete, change type selected, and evidence (screenshot) provided; however, the 'Changes Description' section is empty and lacks explicit details about implementation.
Linked Issues check ✅ Passed All coding requirements from issue #4 are met: batch_add_experts interface added [#4], validation ensures vector size ≤ 20 [#4], admin authentication and event emission implemented [#4], and three comprehensive tests added covering admin auth, status verification, and vector size validation [#4].
Out of Scope Changes check ✅ Passed All changes are in scope: error variant and imports are necessary dependencies, batch_add_experts function directly fulfills the requirement, updated verify_expert with admin auth aligns with admin functionality, and all three tests address the batch verification feature.
Docstring Coverage ✅ Passed Docstring coverage is 90.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@contracts/identity-registry-contract/src/lib.rs`:
- Around line 32-34: Replace the panic caused by unwrap() when fetching the
admin in batch_add_experts with a proper contract error: call
storage::get_admin(&env) and if it returns None return
Err(ContractError::NotInitialized) (or the crate's NotInitialized variant)
instead of unwrapping; then call admin.require_auth() on the Some(admin) value.
Update any function signature/return types for batch_add_experts if needed to
propagate the ContractError/Result so callers receive NotInitialized rather than
panicking. Ensure references: storage::get_admin, admin.require_auth,
batch_add_experts, init, and NotInitialized.

In `@contracts/identity-registry-contract/src/test.rs`:
- Around line 24-38: Update the test_batch_verification test to meet the
acceptance criteria by creating five expert addresses instead of three, calling
client.batch_add_experts with that five-entry vector, and adding assertions
after the call that verify each address has the expected expert status (use the
contract read/query method that returns expert/verified status for an Address,
e.g., client.is_expert or the relevant getter) and any expected overall batch
result; keep the same setup (Env::default, env.mock_all_auths,
register_contract, IdentityRegistryContractClient::new, client.init(&admin)) but
replace the experts vec to contain five Address::generate(&env) entries and add
per-address assertions checking their verification/status via the contract
client methods.
- Around line 40-52: The test currently panics due to unwrap on get_admin() and
bypasses auth because env.mock_all_auths() is used; remove env.mock_all_auths(),
create an admin address and call client.init(&admin) (using the admin as the
signer) so the contract has an admin, then call client.batch_add_experts(...)
without authenticating as that admin so the panic comes from the require_auth()
check; reference the test name test_batch_verification_no_admin and the methods
client.init, client.batch_add_experts, env.mock_all_auths, get_admin, and
require_auth to locate and fix the code.
🧹 Nitpick comments (1)
contracts/identity-registry-contract/src/test.rs (1)

84-110: Consider asserting the error via try_* instead of should_panic.
The panic string can be brittle across soroban-sdk versions; explicit error checking is more stable.

♻️ Possible adjustment (verify client error behavior)
-#[test]
-#[should_panic(expected = "Error(Contract, `#7`)")]
+#[test]
 fn test_batch_verification_max_vec() {
     let env = Env::default();
     env.mock_all_auths();
@@
-    client.batch_add_experts(&experts);
+    let res = client.try_batch_add_experts(&experts);
+    assert_eq!(res, Err(RegistryError::ExpertVecMax));
 }

Comment on lines 24 to 38
#[test]
fn test_batch_verification() {
let env = Env::default();
env.mock_all_auths();

let contract_id = env.register_contract(None, IdentityRegistryContract);
let client = IdentityRegistryContractClient::new(&env, &contract_id);

let admin = soroban_sdk::Address::generate(&env);
client.init(&admin);

let experts = vec![&env, Address::generate(&env), Address::generate(&env), Address::generate(&env)];

client.batch_add_experts(&experts);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Align this test with the acceptance criteria (5 experts + status assertions).
Currently it uses 3 experts and doesn’t assert verification. Consider either updating this test or renaming/using test_batch_verification_check_status as the required “test_batch_add”.

🧩 Possible update (one option)
-#[test]
-fn test_batch_verification() {
+#[test]
+fn test_batch_add() {
     let env = Env::default();
     env.mock_all_auths();
@@
-    let experts = vec![&env, Address::generate(&env), Address::generate(&env), Address::generate(&env)];
+    let e1 = Address::generate(&env);
+    let e2 = Address::generate(&env);
+    let e3 = Address::generate(&env);
+    let e4 = Address::generate(&env);
+    let e5 = Address::generate(&env);
+    let experts = vec![&env, e1.clone(), e2.clone(), e3.clone(), e4.clone(), e5.clone()];
 
     client.batch_add_experts(&experts);
+
+    env.as_contract(&contract_id, ||{
+        assert_eq!(storage::get_expert_status(&env, &e1), ExpertStatus::Verified);
+        assert_eq!(storage::get_expert_status(&env, &e2), ExpertStatus::Verified);
+        assert_eq!(storage::get_expert_status(&env, &e3), ExpertStatus::Verified);
+        assert_eq!(storage::get_expert_status(&env, &e4), ExpertStatus::Verified);
+        assert_eq!(storage::get_expert_status(&env, &e5), ExpertStatus::Verified);
+    })
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
#[test]
fn test_batch_verification() {
let env = Env::default();
env.mock_all_auths();
let contract_id = env.register_contract(None, IdentityRegistryContract);
let client = IdentityRegistryContractClient::new(&env, &contract_id);
let admin = soroban_sdk::Address::generate(&env);
client.init(&admin);
let experts = vec![&env, Address::generate(&env), Address::generate(&env), Address::generate(&env)];
client.batch_add_experts(&experts);
}
#[test]
fn test_batch_add() {
let env = Env::default();
env.mock_all_auths();
let contract_id = env.register_contract(None, IdentityRegistryContract);
let client = IdentityRegistryContractClient::new(&env, &contract_id);
let admin = soroban_sdk::Address::generate(&env);
client.init(&admin);
let e1 = Address::generate(&env);
let e2 = Address::generate(&env);
let e3 = Address::generate(&env);
let e4 = Address::generate(&env);
let e5 = Address::generate(&env);
let experts = vec![&env, e1.clone(), e2.clone(), e3.clone(), e4.clone(), e5.clone()];
client.batch_add_experts(&experts);
env.as_contract(&contract_id, ||{
assert_eq!(storage::get_expert_status(&env, &e1), ExpertStatus::Verified);
assert_eq!(storage::get_expert_status(&env, &e2), ExpertStatus::Verified);
assert_eq!(storage::get_expert_status(&env, &e3), ExpertStatus::Verified);
assert_eq!(storage::get_expert_status(&env, &e4), ExpertStatus::Verified);
assert_eq!(storage::get_expert_status(&env, &e5), ExpertStatus::Verified);
})
}
🤖 Prompt for AI Agents
In `@contracts/identity-registry-contract/src/test.rs` around lines 24 - 38,
Update the test_batch_verification test to meet the acceptance criteria by
creating five expert addresses instead of three, calling
client.batch_add_experts with that five-entry vector, and adding assertions
after the call that verify each address has the expected expert status (use the
contract read/query method that returns expert/verified status for an Address,
e.g., client.is_expert or the relevant getter) and any expected overall batch
result; keep the same setup (Env::default, env.mock_all_auths,
register_contract, IdentityRegistryContractClient::new, client.init(&admin)) but
replace the experts vec to contain five Address::generate(&env) entries and add
per-address assertions checking their verification/status via the contract
client methods.

Comment on lines 40 to 52
#[test]
#[should_panic]
fn test_batch_verification_no_admin() {
let env = Env::default();
env.mock_all_auths();

let contract_id = env.register_contract(None, IdentityRegistryContract);
let client = IdentityRegistryContractClient::new(&env, &contract_id);

let experts = vec![&env, Address::generate(&env), Address::generate(&env), Address::generate(&env)];

client.batch_add_experts(&experts);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

git ls-files contracts/identity-registry-contract/src/ | head -20

Repository: LightForgeHub/SkillSphere-Contracts

Length of output: 431


🏁 Script executed:

find contracts -name "test.rs" -o -name "lib.rs" | head -10

Repository: LightForgeHub/SkillSphere-Contracts

Length of output: 264


🏁 Script executed:

find . -type f -name "*.rs" | grep -E "(test|contract)" | head -20

Repository: LightForgeHub/SkillSphere-Contracts

Length of output: 687


🏁 Script executed:

cat -n contracts/identity-registry-contract/src/test.rs | head -80

Repository: LightForgeHub/SkillSphere-Contracts

Length of output: 3395


🏁 Script executed:

cat -n contracts/identity-registry-contract/src/contract.rs | head -100

Repository: LightForgeHub/SkillSphere-Contracts

Length of output: 487


🏁 Script executed:

cat -n contracts/identity-registry-contract/src/contract.rs | grep -A 30 "batch_add_experts"

Repository: LightForgeHub/SkillSphere-Contracts

Length of output: 61


🏁 Script executed:

cat -n contracts/identity-registry-contract/src/contract.rs | grep -A 30 "pub fn batch"

Repository: LightForgeHub/SkillSphere-Contracts

Length of output: 61


🏁 Script executed:

wc -l contracts/identity-registry-contract/src/contract.rs

Repository: LightForgeHub/SkillSphere-Contracts

Length of output: 134


🏁 Script executed:

cat -n contracts/identity-registry-contract/src/contract.rs

Repository: LightForgeHub/SkillSphere-Contracts

Length of output: 487


🏁 Script executed:

cat -n contracts/identity-registry-contract/src/lib.rs

Repository: LightForgeHub/SkillSphere-Contracts

Length of output: 1679


This test doesn't actually verify admin auth.

The panic occurs from the .unwrap() on line 32 of batch_add_experts() (when get_admin() returns None due to missing initialization), not from auth enforcement. Additionally, env.mock_all_auths() bypasses the require_auth() check entirely, so the test would never catch an actual auth failure.

Remove env.mock_all_auths(), initialize the admin with client.init(&admin), and let the test fail on the proper auth check:

Suggested fix
 #[test]
 #[should_panic]
 fn test_batch_verification_no_admin() {
     let env = Env::default();
-    env.mock_all_auths();
 
     let contract_id = env.register_contract(None, IdentityRegistryContract);
     let client = IdentityRegistryContractClient::new(&env, &contract_id);
 
+    let admin = Address::generate(&env);
+    client.init(&admin);
+
     let experts = vec![&env, Address::generate(&env), Address::generate(&env), Address::generate(&env)];
 
     client.batch_add_experts(&experts);
 }
🤖 Prompt for AI Agents
In `@contracts/identity-registry-contract/src/test.rs` around lines 40 - 52, The
test currently panics due to unwrap on get_admin() and bypasses auth because
env.mock_all_auths() is used; remove env.mock_all_auths(), create an admin
address and call client.init(&admin) (using the admin as the signer) so the
contract has an admin, then call client.batch_add_experts(...) without
authenticating as that admin so the panic comes from the require_auth() check;
reference the test name test_batch_verification_no_admin and the methods
client.init, client.batch_add_experts, env.mock_all_auths, get_admin, and
require_auth to locate and fix the code.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@contracts/identity-registry-contract/src/lib.rs`:
- Around line 35-42: Loop fetching each expert's current status via
storage::get_expert_status stores it in status but the event emission hardcodes
ExpertStatus::Unverified; update the call to events::emit_status_change to pass
the previously read status variable (status) as the "from" state instead of
ExpertStatus::Unverified so the event reflects the actual previous state; keep
storage::set_expert_record(&env, &expert, ExpertStatus::Verified) and the rest
of the call parameters (expert, ExpertStatus::Verified, admin.clone())
unchanged.
♻️ Duplicate comments (1)
contracts/identity-registry-contract/src/test.rs (1)

40-51: Test name and purpose mismatch: tests initialization, not authorization.

This test verifies that calling batch_add_experts on an uninitialized contract fails (returning NotInitialized), not that an unauthorized caller is rejected. Consider either:

  1. Renaming to test_batch_verification_not_initialized, or
  2. Adding a separate test that initializes the contract, then calls batch_add_experts without mocking auth for the admin.
🧪 Add a test for actual admin authorization
#[test]
#[should_panic]
fn test_batch_verification_unauthorized() {
    let env = Env::default();
    // Do NOT mock auth - this is intentional
    
    let contract_id = env.register_contract(None, IdentityRegistryContract);
    let client = IdentityRegistryContractClient::new(&env, &contract_id);

    let admin = Address::generate(&env);
    // Mock auth only for init
    env.mock_all_auths();
    client.init(&admin);
    
    // Clear auth mocks so batch_add_experts fails on require_auth()
    // Note: In Soroban, you may need to create a new Env or use specific auth testing utilities
    let experts = vec![&env, Address::generate(&env)];
    
    // This should panic due to missing admin authorization
    client.batch_add_experts(&experts);
}
🧹 Nitpick comments (1)
contracts/identity-registry-contract/src/lib.rs (1)

11-11: Remove unused import.

The core::ops::Add import is not used anywhere in this file.

🧹 Suggested fix
-use core::ops::Add;
-

Comment on lines 35 to 42
for expert in experts {
let status = storage::get_expert_status(&env, &expert);
if status == ExpertStatus::Verified {
return Err(RegistryError::AlreadyVerified);
}
storage::set_expert_record(&env, &expert, ExpertStatus::Verified);
events::emit_status_change(&env, expert, ExpertStatus::Unverified, ExpertStatus::Verified, admin.clone());
}
Copy link

@coderabbitai coderabbitai bot Jan 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find ExpertStatus enum definition
ast-grep --pattern 'enum ExpertStatus {
  $$$
}'

Repository: LightForgeHub/SkillSphere-Contracts

Length of output: 61


🏁 Script executed:

#!/bin/bash
# Also search for ExpertStatus definition more broadly
rg -A 10 'enum ExpertStatus' contracts/identity-registry-contract/src/

Repository: LightForgeHub/SkillSphere-Contracts

Length of output: 847


🏁 Script executed:

#!/bin/bash
# Check the file structure and understand the broader context
head -100 contracts/identity-registry-contract/src/lib.rs

Repository: LightForgeHub/SkillSphere-Contracts

Length of output: 1386


Use the actual status variable for event emission, as ExpertStatus has three variants.

The code retrieves the expert's status on line 36 but hardcodes ExpertStatus::Unverified in the event on line 41. Since ExpertStatus has three variants—Unverified, Verified, and Banned—the event will incorrectly report Unverified → Verified when a Banned expert is re-verified. Replace the hardcoded value with the status variable to accurately reflect the previous state:

         for expert in experts {
             let status = storage::get_expert_status(&env, &expert);
             if status == ExpertStatus::Verified {
                 return Err(RegistryError::AlreadyVerified);
             }
             storage::set_expert_record(&env, &expert, ExpertStatus::Verified);
-            events::emit_status_change(&env, expert, ExpertStatus::Unverified, ExpertStatus::Verified, admin.clone());
+            events::emit_status_change(&env, expert, status, ExpertStatus::Verified, admin.clone());
🤖 Prompt for AI Agents
In `@contracts/identity-registry-contract/src/lib.rs` around lines 35 - 42, Loop
fetching each expert's current status via storage::get_expert_status stores it
in status but the event emission hardcodes ExpertStatus::Unverified; update the
call to events::emit_status_change to pass the previously read status variable
(status) as the "from" state instead of ExpertStatus::Unverified so the event
reflects the actual previous state; keep storage::set_expert_record(&env,
&expert, ExpertStatus::Verified) and the rest of the call parameters (expert,
ExpertStatus::Verified, admin.clone()) unchanged.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please look into this

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Bosun-Josh121, I've reviewed the code again and can confirm the issue. Here's the problem:

Current behavior: The event always reports Unverified → Verified, regardless of the expert's actual previous state.

Concrete example:

  • If an expert is currently Banned and an admin calls batch_add_experts to verify them:
    • Line 36 retrieves status = ExpertStatus::Banned
    • Line 37 check passes (since Banned != Verified)
    • Line 40 sets the expert to Verified
    • Line 41 emits event: Unverified → Verified ❌ (should be Banned → Verified)

Why this matters:
Events provide an audit trail for state changes. Inaccurate events can break off-chain indexers, analytics, and compliance tracking that rely on knowing the actual state transitions.

The fix:
Replace the hardcoded ExpertStatus::Unverified with the status variable that's already retrieved on line 36:

events::emit_status_change(&env, expert, status, ExpertStatus::Verified, admin.clone());

This ensures the event accurately reflects the actual state transition, whether it's Unverified → Verified or Banned → Verified.

Copy link
Collaborator

@Bosun-Josh121 Bosun-Josh121 Jan 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pls look into this @hakymulla

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find all calls to emit_status_change to check for similar issues
rg -n 'emit_status_change' contracts/identity-registry-contract/

Length of output: 250


🏁 Script executed:

#!/bin/bash
# Look for the verify_expert or similar single-expert verification logic to compare
rg -nP 'fn\s+(verify_expert|add_expert|ban_expert)' contracts/identity-registry-contract/

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Find the emit_status_change function definition to understand its signature
ast-grep --pattern 'fn emit_status_change($$$) {
  $$$
}'

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Check the test file to see what's being tested for batch_add_experts
cat contracts/identity-registry-contract/src/test.rs

Length of output: 3688


🏁 Script executed:

#!/bin/bash
# Search for any test that checks event emissions or status changes
rg -n 'status_change|emit|event' contracts/identity-registry-contract/src/test.rs

Length of output: 0


@Bosun-Josh121, I've conducted a thorough investigation of the codebase. Here's what I found:

Evidence from the Codebase

1. Event Function Signature (events.rs:15-21):

pub fn emit_status_change(
    env: &Env, 
    expert: Address, 
    old_status: ExpertStatus,  // ← Parameter expects the ACTUAL old status
    new_status: ExpertStatus, 
    admin: Address
)

2. Current Implementation Issue (lib.rs:35-41):

for expert in experts {
    let status = storage::get_expert_status(&env, &expert);  // ← Retrieved but not used
    if status == ExpertStatus::Verified {
        return Err(RegistryError::AlreadyVerified);
    }
    storage::set_expert_record(&env, &expert, ExpertStatus::Verified);
    events::emit_status_change(&env, expert, ExpertStatus::Unverified, /* ← HARDCODED */ ExpertStatus::Verified, admin.clone());
}

3. Why Tests Didn't Catch This:
The test file only verifies final status, not event accuracy:

assert_eq!(storage::get_expert_status(&env, &e1), ExpertStatus::Verified);

No tests check event emissions or handle the Banned → Verified scenario.

The Problem

When verifying a Banned expert:

  • ✅ Line 36: Retrieves status = ExpertStatus::Banned
  • ✅ Line 37: Check passes (Banned ≠ Verified)
  • ✅ Line 40: Sets expert to Verified
  • ❌ Line 41: Emits event as Unverified → Verified (should be Banned → Verified)

This breaks audit trails and off-chain indexers that rely on accurate state transitions.

The Fix

Replace line 41:

events::emit_status_change(&env, expert, status, ExpertStatus::Verified, admin.clone());

Would you like me to implement this fix for you? I can update the code and add a test case for the Banned → Verified scenario.


🧬 Code Graph Analysis Results

File: contracts/identity-registry-contract/src/contract.rs

  • Relevant snippet (lines 5-13):
pub fn initialize_registry(env: &Env, admin: &Address) -> Result<(), RegistryError> {
    if storage::has_admin(env) {
        return Err(RegistryError::AlreadyInitialized);
    }

    storage::set_admin(env, admin);

    Ok(())
}

File: contracts/identity-registry-contract/src/events.rs

  • Relevant snippet (lines 15-31):
pub fn emit_status_change(
    env: &Env, 
    expert: Address, 
    old_status: ExpertStatus, 
    new_status: ExpertStatus, 
    admin: Address
) {
    let event = ExpertStatusChangedEvent {
        expert,
        old_status,
        new_status,
        admin,
    };
    
    // We publish with the topic "status_change" so indexers can find it easily
    env.events().publish((Symbol::new(env, "status_change"),), event);
}

File: contracts/identity-registry-contract/src/storage.rs

  • Relevant snippet (lines 34-36):
pub fn get_admin(env: &Env) -> Option<Address> {
    env.storage().instance().get(&DataKey::Admin)
}
  • Relevant snippet (lines 40-59):
pub fn set_expert_record(env: &Env, expert: &Address, status: ExpertStatus) {
    let key = DataKey::Expert(expert.clone());
    
    let record = ExpertRecord {
        status,
        updated_at: env.ledger().timestamp(),
    };

    // 1. Save the data
    env.storage().persistent().set(&key, &record);

    // 2. Extend the TTL
    // This tells the network: "If this data is going to die in less than 2 months, 
    // extend its life to 1 full year from now."
    env.storage().persistent().extend_ttl(
        &key, 
        LEDGERS_THRESHOLD, 
        LEDGERS_EXTEND_TO
    );
}
  • Relevant snippet (lines 83-85):
pub fn get_expert_status(env: &Env, expert: &Address) -> ExpertStatus {
    get_expert_record(env, expert).status
}

File: contracts/identity-registry-contract/src/identity-registry-contract/src/lib.rs (already shown in file; no extra snippet)

@Bosun-Josh121
Copy link
Collaborator

Put function logic in contract.rs and use in lib.rs then resolve conflicts. Thanks @hakymulla

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@contracts/identity-registry-contract/src/contract.rs`:
- Around line 17-34: The function batch_add_experts currently lacks a final
Ok(()) return which prevents it from compiling; update the function
batch_add_experts to return Ok(()) at the end (after the for loop) so the
signature Result<(), RegistryError> is satisfied, leaving all existing early
Err(...) returns and side-effects (storage::set_expert_record,
events::emit_status_change, admin.require_auth, etc.) unchanged.
- Around line 1-5: Remove the duplicate import of ExpertStatus (the second "use
crate::types::ExpertStatus;") and drop the unused soroban_sdk items that belong
in lib.rs: remove "contract", "contractimpl", and "Vec" from the soroban_sdk
import list; keep only the actually used imports such as Address and Env and the
crate items like storage, events, error::RegistryError and types::ExpertStatus
so the file compiles cleanly.

In `@contracts/identity-registry-contract/src/test.rs`:
- Line 99: The function test_add_expert is missing the #[test] attribute so it
won't be executed by cargo test; add the #[test] attribute immediately above the
fn test_add_expert() definition (placing it on the line before the fn) to
register it as a unit test so the test runner picks it up.
♻️ Duplicate comments (1)
contracts/identity-registry-contract/src/test.rs (1)

28-39: Test panics from NotInitialized, not from auth failure.

This test will panic because client.init(&admin) is never called, so get_admin() returns None and the function returns NotInitialized error. It does not test the require_auth() check as the test name suggests.

To properly test admin authorization failure, initialize the contract but don't mock auth:

🛠️ Suggested fix to test actual auth failure
 #[test]
 #[should_panic]
 fn test_batch_verification_no_admin() {
     let env = Env::default();
-
+    // Don't call env.mock_all_auths() to test real auth failure
+    
     let contract_id = env.register_contract(None, IdentityRegistryContract);
     let client = IdentityRegistryContractClient::new(&env, &contract_id);
 
+    let admin = Address::generate(&env);
+    client.init(&admin);
+
     let experts = vec![&env, Address::generate(&env), Address::generate(&env), Address::generate(&env)];
 
+    // Should panic on require_auth() failure, not NotInitialized
     client.batch_add_experts(&experts);
 }
🧹 Nitpick comments (3)
contracts/identity-registry-contract/src/contract.rs (1)

26-33: AlreadyVerified error aborts the entire batch on any duplicate.

If one expert in the middle of the vector is already verified, the function returns an error and none of the subsequent experts are processed. This may be intentional, but it differs from typical batch semantics where partial success or skip-and-continue is expected.

Consider whether:

  1. Skipping already-verified experts silently (continue instead of error) would be more user-friendly
  2. The current strict behavior is desired (document this clearly)
♻️ Alternative: skip already-verified experts
     for expert in experts {
         let status = storage::get_expert_status(&env, &expert);
         if status == ExpertStatus::Verified {
-            return Err(RegistryError::AlreadyVerified);
+            continue; // Skip already verified experts
         }
         storage::set_expert_record(&env, &expert, ExpertStatus::Verified);
         events::emit_status_change(&env, expert, status, ExpertStatus::Verified, admin.clone());
     }
contracts/identity-registry-contract/src/lib.rs (1)

24-27: Inconsistent indentation in doc comment.

Line 24 uses 2-space indentation while line 25 uses 4-space indentation. This is a minor formatting inconsistency.

♻️ Suggested fix
-  /// Batch Add an expert to the whitelist (Admin only)
+    /// Batch Add experts to the whitelist (Admin only)
     pub fn batch_add_experts(env: Env, experts: Vec<Address>) -> Result<(), RegistryError> {
         contract::batch_add_experts(env, experts)
     }
contracts/identity-registry-contract/src/test.rs (1)

71-97: Test may fail with AlreadyVerified before reaching ExpertVecMax check.

The vector contains 24 elements but reuses only 4 unique addresses (e1-e4). When the batch is processed, after the first 4 experts are verified, the 5th element (e1.clone()) will trigger AlreadyVerified error before the vector length check matters.

Since the length check (>20) happens before iteration in batch_add_experts, this test should work. However, the test would be clearer and more robust if it used 21+ unique addresses.

♻️ Suggested fix using unique addresses
 #[test]
 #[should_panic(expected = "Error(Contract, `#7`)")]
 fn test_batch_verification_max_vec() {
     let env = Env::default();
     env.mock_all_auths();

     let contract_id = env.register_contract(None, IdentityRegistryContract);
     let client = IdentityRegistryContractClient::new(&env, &contract_id);

     let admin = soroban_sdk::Address::generate(&env);
     client.init(&admin);

-    let e1 = Address::generate(&env);
-    let e2 = Address::generate(&env);
-    let e3 = Address::generate(&env);
-    let e4 = Address::generate(&env);
-
-    let experts = vec![&env, e1.clone(), e2.clone(), e3.clone(), e4.clone(), 
-        e1.clone(), e2.clone(), e3.clone(), e4.clone(), 
-        e1.clone(), e2.clone(), e3.clone(), e4.clone(), 
-        e1.clone(), e2.clone(), e3.clone(), e4.clone(),
-        e1.clone(), e2.clone(), e3.clone(), e4.clone(),
-        e1.clone(), e2.clone(), e3.clone(), e4.clone()
-    ];
+    // Generate 21 unique addresses to exceed the limit of 20
+    let mut experts = Vec::new(&env);
+    for _ in 0..21 {
+        experts.push_back(Address::generate(&env));
+    }

     client.batch_add_experts(&experts);
 }

Comment on lines 1 to 5
use soroban_sdk::{contract, contractimpl, Address, Env, Vec};
use crate::storage;
use crate::error::RegistryError;
use crate::types::ExpertStatus;
use crate::events;
use crate::{error::RegistryError, types::ExpertStatus};
use crate::types::ExpertStatus;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Duplicate import and unused imports will cause compilation errors.

Line 4 imports ExpertStatus via crate::types::ExpertStatus, and line 5 imports it again from crate::types::ExpertStatus. This is a duplicate import that will fail to compile.

Additionally, contract, contractimpl, and Vec from soroban_sdk are imported but not used in this file (they belong in lib.rs).

🛠️ Proposed fix
-use soroban_sdk::{contract, contractimpl, Address, Env, Vec};
+use soroban_sdk::{Address, Env, Vec};
 use crate::storage;
 use crate::events;
-use crate::{error::RegistryError, types::ExpertStatus};
-use crate::types::ExpertStatus;
+use crate::error::RegistryError;
+use crate::types::ExpertStatus;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
use soroban_sdk::{contract, contractimpl, Address, Env, Vec};
use crate::storage;
use crate::error::RegistryError;
use crate::types::ExpertStatus;
use crate::events;
use crate::{error::RegistryError, types::ExpertStatus};
use crate::types::ExpertStatus;
use soroban_sdk::{Address, Env, Vec};
use crate::storage;
use crate::events;
use crate::error::RegistryError;
use crate::types::ExpertStatus;
🤖 Prompt for AI Agents
In `@contracts/identity-registry-contract/src/contract.rs` around lines 1 - 5,
Remove the duplicate import of ExpertStatus (the second "use
crate::types::ExpertStatus;") and drop the unused soroban_sdk items that belong
in lib.rs: remove "contract", "contractimpl", and "Vec" from the soroban_sdk
import list; keep only the actually used imports such as Address and Env and the
crate items like storage, events, error::RegistryError and types::ExpertStatus
so the file compiles cleanly.

@Bosun-Josh121 Bosun-Josh121 merged commit 935870a into LightForgeHub:main Jan 22, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement Batch Verification

2 participants