feat: M008 CosmWasm attestation-bonding contract with unit tests#76
feat: M008 CosmWasm attestation-bonding contract with unit tests#76brawlaphant wants to merge 1 commit intoregen-network:mainfrom
Conversation
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>
There was a problem hiding this comment.
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.
| if info.sender == attestation.attester || info.sender == challenge.challenger { | ||
| return Err(ContractError::ArbiterConflict {}); | ||
| } |
There was a problem hiding this comment.
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.
| 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 {}); | |
| } |
| .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(); | ||
|
|
There was a problem hiding this comment.
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.
| 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(); | ||
|
|
| let bond = info | ||
| .funds | ||
| .iter() | ||
| .find(|c| c.denom == config.bond_denom) | ||
| .ok_or(ContractError::NoBondProvided {})?; |
There was a problem hiding this comment.
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.
| let deposit = info | ||
| .funds | ||
| .iter() | ||
| .find(|c| c.denom == config.bond_denom) | ||
| .ok_or(ContractError::NoDepositProvided {})?; |
There was a problem hiding this comment.
| config.min_challenge_deposit_ratio = ratio; | ||
| } | ||
| if let Some(ratio) = arbiter_fee_ratio { | ||
| config.arbiter_fee_ratio = ratio; | ||
| } |
There was a problem hiding this comment.
| challenge_window_days: attestation_type.challenge_window_days, | ||
| }; |
There was a problem hiding this comment.
Summary
contracts/attestation-bonding/Contract structure
Cargo.tomlsrc/lib.rssrc/error.rssrc/state.rssrc/msg.rssrc/contract.rsKey features per SPEC
Dependencies
cosmwasm-std2.2 (resolved to 2.3.2)cw-storage-plus2.0cw22.0thiserror2schemars0.8,serde1Test plan
cargo test— 42 tests pass (0 failed)🤖 Generated with Claude Code