Skip to content

feat: M008 CosmWasm attestation-bonding contract with unit tests#76

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

feat: M008 CosmWasm attestation-bonding contract with unit tests#76
brawlaphant wants to merge 1 commit intoregen-network:mainfrom
brawlaphant:pr/m008-cosmwasm-contract

Conversation

@brawlaphant
Copy link
Copy Markdown
Contributor

Summary

  • Implements the M008 Data Attestation Bonding mechanism as a CosmWasm smart contract under contracts/attestation-bonding/
  • Full attestation lifecycle: Bonded (with 48h activation delay) -> Active -> Released, with challenge/dispute/slash flows
  • 42 unit tests covering all 20 SPEC acceptance test scenarios plus edge cases (bond conservation, arbiter neutrality, resolution finality, etc.)

Contract structure

File Purpose
Cargo.toml cosmwasm-std 2.2, cw-storage-plus 2.0, cw2, thiserror 2, schemars, serde
src/lib.rs Module exports
src/error.rs ContractError enum (13 error variants)
src/state.rs Storage items, Config, Attestation, Challenge structs, status enums
src/msg.rs InstantiateMsg, ExecuteMsg (7 variants), QueryMsg (8 variants), response types
src/contract.rs Full implementation with instantiate/execute/query entry points + 42 unit tests

Key features per SPEC

  • Attestation types: ProjectBoundary (500 REGEN min), BaselineMeasurement (1000), CreditIssuanceClaim (2000), MethodologyValidation (5000) with configurable lock/challenge periods
  • 48h activation delay: Bonded -> Active transition requires delay, challengeable during delay
  • Challenge deposit: 10% of bond amount (configurable via basis points)
  • Resolution flows: AttesterWins (bond + deposit - fee returned) or ChallengerWins (50/50 slash to challenger + community pool)
  • Arbiter fee: 5% of bond (configurable)
  • Security invariants: Bond conservation, single active challenge, arbiter neutrality, resolution finality, lock period enforcement

Dependencies

  • cosmwasm-std 2.2 (resolved to 2.3.2)
  • cw-storage-plus 2.0
  • cw2 2.0
  • thiserror 2
  • schemars 0.8, serde 1

Test plan

  • cargo test — 42 tests pass (0 failed)
  • Review alignment with SPEC.md acceptance tests 1-20
  • Review integration points with M010 (reputation) and M009 (service escrow)
  • Verify Cargo.toml dependency versions match repo conventions

🤖 Generated with Claude Code

Implements the Data Attestation Bonding mechanism (M008) as a CosmWasm
smart contract per the SPEC and phase-3 architecture. Attesters bond
REGEN tokens to back ecological claims; challengers can dispute with a
deposit; arbiter DAO or admin resolves disputes with slash/reward flows.

Contract features:
- Attestation lifecycle: Bonded -> Active -> Released (happy path)
- 48h activation delay with permissionless crank
- Challenge/dispute with 10% deposit requirement
- Resolution: attester wins (bond+deposit-fee) or challenger wins (50/50 slash)
- Bond conservation invariant enforced in all flows
- Arbiter neutrality check (resolver cannot be attester or challenger)
- Configurable attestation types with per-type min bond, lock period, challenge window
- Admin functions: UpdateConfig, AddAttestationType
- Query endpoints: attestation, by-attester, challenge, bond info, types

42 unit tests covering all 20 SPEC acceptance test scenarios plus edge cases.

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 attestation-bonding CosmWasm contract, which implements a system for bonded ecological attestations and challenge-based dispute resolution. The contract manages the lifecycle of attestations, including bonding, activation delays, and arbiter-mediated slashing. Feedback identifies several critical security and scalability issues: the arbiter neutrality check needs to include beneficiaries to prevent conflicts of interest; queries for attestations and challenges require secondary indices to avoid gas-limit failures from full table scans; and additional input validation is necessary to prevent trapping extraneous funds, cap fee ratios, and ensure the challenge window does not exceed the bond lock period.

Comment on lines +323 to +325
if info.sender == attestation.attester || info.sender == challenge.challenger {
return Err(ContractError::ArbiterConflict {});
}
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 arbiter neutrality check is incomplete. According to the SPEC (Section 9, Security Invariants), arbiters cannot be the attester, challenger, or beneficiary. The current implementation only checks against the attester and challenger. This could allow a beneficiary to resolve a challenge in their own favor.

Suggested change
if info.sender == attestation.attester || info.sender == challenge.challenger {
return Err(ContractError::ArbiterConflict {});
}
if info.sender == attestation.attester || info.sender == challenge.challenger || attestation.beneficiary.as_ref() == Some(&info.sender) {
return Err(ContractError::ArbiterConflict {});
}

Comment on lines +604 to +610
.range(deps.storage, None, None, Order::Ascending)
.filter_map(|item| item.ok())
.filter(|(id, a)| a.attester == attester_addr && *id > start)
.take(limit)
.map(|(_, a)| a)
.collect();

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

This query performs a full table scan of all attestations to filter by attester. As the number of attestations grows, this query will become increasingly expensive and will eventually exceed the gas limit, causing it to fail. To support efficient pagination and filtering, a secondary index (e.g., a Map with (Addr, u64) as key) should be implemented in state.rs.

Comment on lines +623 to +629
let challenges: Vec<Challenge> = CHALLENGES
.range(deps.storage, None, None, Order::Ascending)
.filter_map(|item| item.ok())
.filter(|(_, c)| c.attestation_id == attestation_id)
.map(|(_, c)| c)
.collect();

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

Similar to the attester query, this performs a full scan of all challenges in the contract. This is inefficient and not scalable. An index should be used to map attestation IDs to their associated challenges.

Comment on lines +129 to +133
let bond = info
.funds
.iter()
.find(|c| c.denom == config.bond_denom)
.ok_or(ContractError::NoBondProvided {})?;
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 contract does not validate that only the required bond denom is sent. If a user sends multiple coins in info.funds, any coins not matching config.bond_denom will be trapped in the contract with no mechanism for recovery. It is recommended to enforce that info.funds.len() == 1 and that the denom matches.

Comment on lines +250 to +254
let deposit = info
.funds
.iter()
.find(|c| c.denom == config.bond_denom)
.ok_or(ContractError::NoDepositProvided {})?;
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

Similar to attestation creation, the challenge mechanism does not validate that only the required deposit denom is sent. Any additional coins sent in info.funds will be trapped in the contract. Consider enforcing that only the expected denom and amount are provided.

Comment on lines +508 to +512
config.min_challenge_deposit_ratio = ratio;
}
if let Some(ratio) = arbiter_fee_ratio {
config.arbiter_fee_ratio = ratio;
}
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

There is no validation on the updated ratios. If arbiter_fee_ratio is set to a value exceeding 10,000 (100%) or even 5,000 (50%), it could cause overflows or logic errors during challenge resolution (e.g., negative payouts for the challenger). Ratios should be capped at BASIS_POINTS_DIVISOR.

Comment on lines +545 to +546
challenge_window_days: attestation_type.challenge_window_days,
};
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 contract should validate that the challenge_window_days is less than or equal to the lock_period_days. If the challenge window exceeds the lock period, an attester could release their bond before the window for challenging has closed, effectively bypassing the economic security of the bond.

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