Skip to content

feat(reputation-contract): implement penalize_expert for dispute pena…#64

Merged
Bosun-Josh121 merged 1 commit intoLightForgeHub:mainfrom
arandomogg:automated-dispute
Mar 30, 2026
Merged

feat(reputation-contract): implement penalize_expert for dispute pena…#64
Bosun-Josh121 merged 1 commit intoLightForgeHub:mainfrom
arandomogg:automated-dispute

Conversation

@arandomogg
Copy link
Copy Markdown

@arandomogg arandomogg commented Mar 30, 2026

Adds penalize_expert() function to automatically dock reputation points when
vault admin resolves severe disputes. Protects against score underflow and
only allows authorized callers (admin). Includes event emission and tests.

Closes #55

Summary by CodeRabbit

  • New Features
    • Administrators can now penalize experts by deducting reputation points from their scores.
    • Penalties are automatically logged with event emissions for tracking and transparency.
    • Score protection ensures penalties cannot reduce a score below zero.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 30, 2026

📝 Walkthrough

Walkthrough

A new penalize_expert function was added to enable reputation score deductions for experts following dispute resolutions, with authorization checks, score floor protection at zero, and comprehensive test coverage.

Changes

Cohort / File(s) Summary
Penalization Logic
contract.rs, error.rs, events.rs
Implemented core penalization entrypoint with admin authorization enforcement, score calculation with underflow protection, and event emission for transparency.
Storage & Data Models
storage.rs
Extended DataKey enum with per-expert storage variants (ExpertScore, ExpertReviews) and added accessor/mutator functions with zero-default getters and a vault address helper.
Public API
lib.rs
Exposed penalize_expert as a contract method delegating to the core implementation.
Test Coverage
test.rs
Added five unit tests validating admin/vault authorization, unauthorized access rejection, underflow boundary protection, and successive score deductions.

Sequence Diagram

sequenceDiagram
    participant Caller
    participant Contract as Penalization Contract
    participant Auth as Authorization
    participant Storage as Storage Layer
    participant Events as Event Emitter

    Caller->>Contract: penalize_expert(expert, penalty_points)
    Contract->>Auth: require_auth() on admin
    Auth-->>Contract: ✓ Authorized
    Contract->>Storage: get_expert_score(expert)
    Storage-->>Contract: current_score
    Contract->>Contract: new_score = max(0, current_score - penalty_points)
    Contract->>Storage: set_expert_score(expert, new_score)
    Storage-->>Contract: ✓ Persisted
    Contract->>Events: expert_penalized(expert, penalty_points, new_score)
    Events-->>Contract: ✓ Emitted
    Contract-->>Caller: Result::Ok(())
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Poem

🐰 Hops of Honor
When disputes arise and mischief's found,
The admin's paw stamps on the ground—
Penalties dock with righteous grace,
Floored at zero—a safety space!
Reputation tested, trust restored divine. 🌟

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 36.84% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: implementing the penalize_expert function for dispute penalties in the reputation contract.
Description check ✅ Passed The description clearly outlines the key changes (penalize_expert function, underflow protection, authorization, event emission, and tests) and references the closed issue (#55).
Linked Issues check ✅ Passed All coding requirements from issue #55 have been met: NotAuthorized error added, penalize_expert implemented with authorization and underflow protection, expert_penalized event added, function exposed in lib.rs, and comprehensive tests included.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing the penalize_expert functionality specified in issue #55; no out-of-scope modifications detected.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

Review ran into problems

🔥 Problems

Timed out fetching pipeline failures after 30000ms


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
Copy Markdown

@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: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@contracts/reputation-scoring-contract/src/contract.rs`:
- Around line 58-63: The authorization currently only calls admin.require_auth()
after retrieving admin via storage::get_admin(env), missing the “admin or vault”
branch and never returning ReputationError::NotAuthorized; update the
authorization in the relevant function to: fetch both admin and vault (e.g.,
storage::get_admin(env) and storage::get_vault(env)), allow the call if either
admin.require_auth() succeeds or the vault authorization path succeeds (e.g.,
vault.require_auth() or an explicit check of env.message.sender == vault
address), and if neither authorizes the caller return
Err(ReputationError::NotAuthorized) instead of unconditionally calling
admin.require_auth(); ensure you reference and use the existing symbols admin,
vault, admin.require_auth(), and ReputationError::NotAuthorized when
implementing the branch.

In `@contracts/reputation-scoring-contract/src/test.rs`:
- Around line 79-99: The test test_penalize_expert_unauthorized currently
asserts is_err() too broadly; change it to capture the error from
client_strict.try_penalize_expert(&expert_strict, &50).unwrap_err() and assert
it specifically corresponds to ReputationError::NotAuthorized (error code 8).
Use the contract's error mapping (e.g., compare the returned
ContractError/Custom code or pattern-match to ReputationError::NotAuthorized) so
the test verifies the unauthorized path produces the exact
ReputationError::NotAuthorized rather than any failure.
- Around line 102-126: The two tests lack assertions and one assumes an initial
score of 100 without setting it; update
test_penalize_expert_score_underflow_protection to assert the expert’s score
stays at 0 after penalizing (use client.get_expert_score(&expert) and assert_eq!
to 0) and update test_penalize_expert_partial_deduction to explicitly initialize
the expert’s score to 100 (using client.set_expert_score(&expert, &100) or
client.reward_expert(&expert, &100) depending on available API), then assert
after the two penalize_expert calls that the final score equals 50 via
client.get_expert_score(&expert); keep references to setup(), client.init(),
penalize_expert(), get_expert_score(), and the initialization method you have in
the contract.
- Around line 56-76: The two tests (test_penalize_expert_by_admin and
test_penalize_expert_by_vault) currently call client.penalize_expert the same
way and only check for no panic, so they don't validate separate authorization
paths; update tests to explicitly simulate different callers and assert
authorization behavior: in test_penalize_expert_by_admin keep the default auth
context (no env.mock_all_auths) and call client.penalize_expert(&expert, &50)
and assert success, and in test_penalize_expert_by_vault use env.mock_all_auths
(or the appropriate mocking helper) to set the vault as the caller before
calling client.penalize_expert(&_expert, &50) and assert success; additionally
add a negative case for each test where the wrong caller is set and
client.penalize_expert returns or raises an unauthorized error (assert the
specific error/Result variant) so admin and vault authorization paths are
independently verified.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d92235cd-b057-454b-b345-b648d884ef66

📥 Commits

Reviewing files that changed from the base of the PR and between b30fd9a and f1bfbaf.

📒 Files selected for processing (6)
  • contracts/reputation-scoring-contract/src/contract.rs
  • contracts/reputation-scoring-contract/src/error.rs
  • contracts/reputation-scoring-contract/src/events.rs
  • contracts/reputation-scoring-contract/src/lib.rs
  • contracts/reputation-scoring-contract/src/storage.rs
  • contracts/reputation-scoring-contract/src/test.rs

Comment on lines +58 to +63
// Verify contract is initialized
let admin = storage::get_admin(env).ok_or(ReputationError::NotInitialized)?;

// Require auth from admin (vault authorization is handled through admin)
admin.require_auth();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Authorization logic misses the vault path and never returns NotAuthorized.

Line 62 enforces admin auth only. This does not satisfy the required “admin or vault” authorization behavior, and there is no branch returning ReputationError::NotAuthorized when caller is neither.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@contracts/reputation-scoring-contract/src/contract.rs` around lines 58 - 63,
The authorization currently only calls admin.require_auth() after retrieving
admin via storage::get_admin(env), missing the “admin or vault” branch and never
returning ReputationError::NotAuthorized; update the authorization in the
relevant function to: fetch both admin and vault (e.g., storage::get_admin(env)
and storage::get_vault(env)), allow the call if either admin.require_auth()
succeeds or the vault authorization path succeeds (e.g., vault.require_auth() or
an explicit check of env.message.sender == vault address), and if neither
authorizes the caller return Err(ReputationError::NotAuthorized) instead of
unconditionally calling admin.require_auth(); ensure you reference and use the
existing symbols admin, vault, admin.require_auth(), and
ReputationError::NotAuthorized when implementing the branch.

Comment on lines +56 to +76
#[test]
fn test_penalize_expert_by_admin() {
let (env, admin, vault, client) = setup();
client.init(&admin, &vault);
let expert = Address::generate(&env);

// Admin should be able to penalize
client.penalize_expert(&expert, &50);
}

#[test]
fn test_penalize_expert_by_vault() {
let (env, admin, vault, client) = setup();
client.init(&admin, &vault);
let _expert = Address::generate(&env);

// Vault should be able to penalize
env.mock_all_auths();
// Simulate vault calling by mocking auth context
client.penalize_expert(&_expert, &50);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Admin vs vault authorization is not actually distinguished in these tests.

Both tests execute the same call pattern and only verify “no error”, so they do not prove the vault-specific authorization path independently.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@contracts/reputation-scoring-contract/src/test.rs` around lines 56 - 76, The
two tests (test_penalize_expert_by_admin and test_penalize_expert_by_vault)
currently call client.penalize_expert the same way and only check for no panic,
so they don't validate separate authorization paths; update tests to explicitly
simulate different callers and assert authorization behavior: in
test_penalize_expert_by_admin keep the default auth context (no
env.mock_all_auths) and call client.penalize_expert(&expert, &50) and assert
success, and in test_penalize_expert_by_vault use env.mock_all_auths (or the
appropriate mocking helper) to set the vault as the caller before calling
client.penalize_expert(&_expert, &50) and assert success; additionally add a
negative case for each test where the wrong caller is set and
client.penalize_expert returns or raises an unauthorized error (assert the
specific error/Result variant) so admin and vault authorization paths are
independently verified.

Comment on lines +79 to +99
fn test_penalize_expert_unauthorized() {
let (env, admin, vault, client) = setup();
client.init(&admin, &vault);
let _expert = Address::generate(&env);

// Create a new environment without mocking all auths to test unauthorized access
let env_strict = Env::default();
let contract_id = env_strict.register(ReputationScoringContract, ());
let client_strict = ReputationScoringContractClient::new(&env_strict, &contract_id);
let admin_strict = Address::generate(&env_strict);
let vault_strict = Address::generate(&env_strict);

// Initialize with strict env
client_strict.init(&admin_strict, &vault_strict);

let expert_strict = Address::generate(&env_strict);
let _unauthorized = Address::generate(&env_strict);

// Unauthorized address should not be able to penalize (no auth mocking for this env)
assert!(client_strict.try_penalize_expert(&expert_strict, &50).is_err());
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Unauthorized test should assert the specific contract error.

is_err() is too broad here; this should validate the unauthorized path maps to ReputationError::NotAuthorized (error code 8), not just any failure.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@contracts/reputation-scoring-contract/src/test.rs` around lines 79 - 99, The
test test_penalize_expert_unauthorized currently asserts is_err() too broadly;
change it to capture the error from
client_strict.try_penalize_expert(&expert_strict, &50).unwrap_err() and assert
it specifically corresponds to ReputationError::NotAuthorized (error code 8).
Use the contract's error mapping (e.g., compare the returned
ContractError/Custom code or pattern-match to ReputationError::NotAuthorized) so
the test verifies the unauthorized path produces the exact
ReputationError::NotAuthorized rather than any failure.

Comment on lines +102 to +126
fn test_penalize_expert_score_underflow_protection() {
let (env, admin, vault, client) = setup();
client.init(&admin, &vault);
let expert = Address::generate(&env);

// Penalize with more points than current score (default score is 0)
// Should result in score of 0, not underflow
client.penalize_expert(&expert, &10);

// Penalize again with 5 points, score should stay at 0
client.penalize_expert(&expert, &5);
}

#[test]
fn test_penalize_expert_partial_deduction() {
let (env, admin, vault, client) = setup();
client.init(&admin, &vault);
let expert = Address::generate(&env);

// First penalize to set a score (100 - 30 = 70)
client.penalize_expert(&expert, &30);

// Second penalize (70 - 20 = 50)
client.penalize_expert(&expert, &20);
} No newline at end of file
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Score-behavior tests have no assertions and one invalid precondition.

These tests never assert resulting score values, and the “partial deduction” case assumes an initial score of 100 but never initializes it, so it does not verify the intended math.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@contracts/reputation-scoring-contract/src/test.rs` around lines 102 - 126,
The two tests lack assertions and one assumes an initial score of 100 without
setting it; update test_penalize_expert_score_underflow_protection to assert the
expert’s score stays at 0 after penalizing (use client.get_expert_score(&expert)
and assert_eq! to 0) and update test_penalize_expert_partial_deduction to
explicitly initialize the expert’s score to 100 (using
client.set_expert_score(&expert, &100) or client.reward_expert(&expert, &100)
depending on available API), then assert after the two penalize_expert calls
that the final score equals 50 via client.get_expert_score(&expert); keep
references to setup(), client.init(), penalize_expert(), get_expert_score(), and
the initialization method you have in the contract.

@Bosun-Josh121 Bosun-Josh121 merged commit f1bfbaf into LightForgeHub:main Mar 30, 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 Automated Dispute Penalties

3 participants