Skip to content

feat: M010 CosmWasm reputation-signal contract with unit tests#71

Open
brawlaphant wants to merge 1 commit intoregen-network:mainfrom
brawlaphant:pr/m010-cosmwasm-contract
Open

feat: M010 CosmWasm reputation-signal contract with unit tests#71
brawlaphant wants to merge 1 commit intoregen-network:mainfrom
brawlaphant:pr/m010-cosmwasm-contract

Conversation

@brawlaphant
Copy link
Copy Markdown
Contributor

Summary

  • Implements the M010 Reputation Signal mechanism as a CosmWasm smart contract (contracts/reputation-signal/)
  • Full signal lifecycle: submit (with 24h activation delay) -> activate -> challenge -> resolve/escalate -> withdraw/invalidate
  • v0 decay-weighted reputation score computation (no stake weighting per spec)
  • Challenge system with bond enforcement, arbiter management, and deadline-based escalation
  • Admin controls for config parameters, per-category minimum stake, and arbiter set
  • 38 unit tests covering all spec acceptance criteria from SPEC.md sections 6 and 11
  • Adds target/ and Cargo.lock to .gitignore for Rust build artifacts

Contract structure

File Purpose
src/state.rs Storage types: Signal, Challenge, Config, Evidence, SubjectType, SignalStatus enums
src/msg.rs InstantiateMsg, ExecuteMsg (10 variants), QueryMsg (7 variants) with response types
src/error.rs ContractError enum with 18 error variants
src/contract.rs Entry points and all execute/query handler logic
src/tests.rs 38 unit tests covering lifecycle, challenges, scoring, admin, bonds

Spec coverage

  • Signal lifecycle states: Submitted, Active, Challenged, Escalated, ResolvedValid, ResolvedInvalid, Withdrawn, Invalidated
  • Challenge guards: self-challenge prevention, evidence requirements, rationale minimum, window expiry, single active challenge
  • Score computation: decay-weighted average of endorsement_level/5 per spec section 5.2 (v0)
  • Admin: invalidation with required rationale, config updates, arbiter management

Test plan

  • cargo test passes all 38 tests
  • cargo build compiles cleanly with no warnings
  • Review against SPEC.md acceptance tests (section 11)
  • Verify compatibility with existing M012 dynamic-supply contract patterns

Generated with Claude Code

Implements the M010 Reputation Signal mechanism as a CosmWasm smart
contract for Regen Network. Includes signal submission with activation
delay, challenge/dispute lifecycle (submit, resolve, escalate),
admin invalidation, v0 decay-weighted score computation, bond
enforcement, arbiter management, and 38 passing unit tests covering
all spec acceptance criteria.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces the reputation-signal CosmWasm contract, which manages a lifecycle for stake-weighted endorsement signals, including submission, activation, and a challenge/dispute mechanism. Several critical issues were identified during the review: the use of floating-point arithmetic (f64) and transcendental functions in reputation score calculations poses a risk to blockchain consensus due to potential non-determinism; the storage of signal IDs in a Vec for subjects is not scalable and could lead to gas limit exhaustion; and the string-based composite keys for subjects are vulnerable to collisions. Additionally, the implementation lacks the required minimum stake verification for signal submission, and the active challenges query is inefficient as it filters the entire challenge set in memory.


/// Index: (subject_type_str, subject_id, category) -> Vec<signal_id>
/// We store a composite key as a string: "{subject_type}:{subject_id}:{category}"
pub const SUBJECT_SIGNALS: Map<&str, Vec<u64>> = Map::new("subject_signals");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

Storing a Vec<u64> as the value in SUBJECT_SIGNALS is not scalable. As the number of signals for a subject grows, loading and saving this entire list will eventually exceed gas limits. Consider changing this to a composite key Map, such as Map<(&str, u64), ()>, where the key is (subject_key, signal_id). This allows you to iterate over signals for a subject using .prefix() without loading the entire list into memory.

Comment on lines +694 to +715
let half_life = config.decay_half_life_seconds as f64;
let lambda = (2.0_f64).ln() / half_life;

let mut w_sum: f64 = 0.0;
let mut d_sum: f64 = 0.0;
let mut contributing: u32 = 0;

for id in &ids {
if let Ok(signal) = SIGNALS.load(deps.storage, *id) {
if !signal.status.contributes_to_score() {
continue;
}
contributing += 1;

let age_secs = now.seconds().saturating_sub(signal.submitted_at.seconds()) as f64;
let decay = (-lambda * age_secs).exp();
let w = signal.endorsement_level as f64 / 5.0;

w_sum += w * decay;
d_sum += decay;
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

The use of f64 and transcendental functions like ln() and exp() for reputation score computation is risky in a blockchain environment. Floating-point operations can lead to non-deterministic results across different hardware architectures or compiler versions, potentially causing consensus issues. It is highly recommended to use fixed-point arithmetic (e.g., cosmwasm_std::Decimal) for all scoring logic. For the exponential decay, you can implement a fixed-point approximation or use a lookup table.

Comment on lines +162 to +171
fn exec_submit_signal(
deps: DepsMut,
env: Env,
info: MessageInfo,
subject_type: SubjectType,
subject_id: String,
category: String,
endorsement_level: u8,
evidence: Evidence,
) -> Result<Response, ContractError> {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The exec_submit_signal function does not verify if the signaler meets the minimum stake requirement for the category, which is a requirement specified in the SPEC.md (Section 5.4 and Acceptance Test 2). While the PR description mentions that stake weighting is omitted in v0, the minimum stake gating is a separate security mechanism to prevent signal spam. You should query the staking module or bank balance via deps.querier to enforce this requirement.

Comment on lines +775 to +777
fn subject_key(subject_type: &SubjectType, subject_id: &str, category: &str) -> String {
format!("{}:{}:{}", subject_type, subject_id, category)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The subject_key helper generates a string key by concatenating subject_type, subject_id, and category with colons. This approach is vulnerable to key collisions if any of the input strings contain colons themselves. A safer approach is to use a composite key tuple (SubjectType, &str, &str) directly with cw-storage-plus Maps, which handles serialization and prefixing safely without risk of collision.

Comment on lines +743 to +753
for result in CHALLENGES
.range(deps.storage, Some(Bound::inclusive(start)), None, Order::Ascending)
{
let (_, challenge) = result?;
if matches!(challenge.outcome, ChallengeOutcome::Pending) {
challenges.push(challenge);
if challenges.len() >= limit {
break;
}
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

This query iterates through the CHALLENGES map and filters for Pending status in memory. If there are many resolved challenges, this will become inefficient and may exceed gas limits. Consider maintaining a separate index for active challenges or using a status-prefixed key in the storage map to allow direct iteration over pending challenges.

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.

1 participant