Skip to content

feat: M014 CosmWasm validator-governance contract with unit tests#73

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

feat: M014 CosmWasm validator-governance contract with unit tests#73
brawlaphant wants to merge 1 commit intoregen-network:mainfrom
brawlaphant:pr/m014-cosmwasm-contract

Conversation

@brawlaphant
Copy link
Copy Markdown
Contributor

Summary

  • Implements M014 Authority Validator Governance as a CosmWasm smart contract at contracts/validator-governance/
  • Full validator lifecycle state machine: Candidate -> Approved -> Active -> Probation -> Removed/TermExpired, with Reapply path
  • Composite performance scoring per SPEC section 5: uptime (weight 0.4), governance participation (0.3), ecosystem contribution (0.3) — all integer basis-point arithmetic, no floating point
  • Weighted governance proposals: active validators create/vote, score-weighted ballots, time-bounded voting periods, auto-execute on passage
  • Security invariants enforced on-chain: composition guarantees (min per category), minimum validator count, no double voting, admin-gated lifecycle transitions

Contract structure

File Purpose
Cargo.toml Package config, cosmwasm-std 2.2, cw-storage-plus 2.0
src/lib.rs Module exports
src/error.rs 15 error variants (unauthorized, composition violation, voting guards, etc.)
src/state.rs Config, Validator, Proposal, Vote structs + storage maps
src/msg.rs InstantiateMsg, 12 ExecuteMsg variants, 8 QueryMsg variants with response types
src/contract.rs All handlers + 25 unit tests

Spec coverage

SPEC Section Contract Coverage
5.1 Composite score compute_score_and_confidence() — weighted sum with re-normalization for missing factors
5.2 Confidence Data availability mapping: 3 factors=1.0, 2=0.67, 1=0.33, 0=0.0
5.3 Thresholds compute_flags() — below_performance_threshold, below_uptime_minimum, probation_recommended
6.1 State machine All 7 transitions implemented with guards
6.2 Composition Category count enforcement on removal
10 Parameters All 10 governance parameters configurable via InstantiateMsg and UpdateConfig
11 Invariants Composition guarantee, min validators, no self-approval, compensation cap (fee routing is M013)

Test plan

  • 25 unit tests passing (cargo test)
  • Instantiation with defaults and custom params
  • Full validator lifecycle (apply -> approve -> activate)
  • Probation and restore flow
  • Remove with composition and min-validator guards
  • Term expiry and reapply
  • Score update, query, and flag generation
  • Proposal creation, weighted voting, execution after period
  • Double-vote prevention
  • Unauthorized access rejection
  • Pure scoring function tests (all/some/no factors)

🤖 Generated with Claude Code

Implements the Authority Validator Governance mechanism (M014) as a
CosmWasm smart contract for Regen Network's PoA transition. Covers
PoA validator set management (add/remove/approve/activate), composite
performance scoring (uptime 0.4, governance 0.3, ecosystem 0.3),
validator lifecycle state machine (Candidate -> Active -> Probation ->
Removed/TermExpired), weighted governance proposals with voting, and
composition guarantees (min validators per category). All arithmetic
uses integer basis points (no floating point). 25 unit tests passing.

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 regen-validator-governance CosmWasm contract, which manages PoA validator sets, performance scoring, and governance for the Regen Network. The implementation includes validator lifecycle management (Candidate to Active/Removed), performance-weighted voting, and automated configuration updates via proposals. Feedback focuses on critical scalability and security issues: several functions perform unbounded iterations over state maps which will eventually lead to Out of Gas (OOG) errors, and pagination is missing from query endpoints. Additionally, the governance execution logic for removing validators fails to enforce the minimum validator security invariant, and there is a lack of input validation for configuration updates (e.g., ensuring min_validators <= max_validators) when triggered via proposals.

Comment on lines +1014 to +1021
fn count_validators_by_status(deps: Deps, status: Option<ValidatorStatus>) -> StdResult<u32> {
let count = VALIDATORS
.range(deps.storage, None, None, Order::Ascending)
.filter_map(|item| item.ok())
.filter(|(_, v)| status.as_ref().map_or(true, |s| v.status == *s))
.count() as u32;
Ok(count)
}
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 count_validators_by_status helper iterates over the entire VALIDATORS map. Since this map stores all historical validator records (including Removed and TermExpired), this operation becomes increasingly expensive over time. This can lead to Out of Gas (OOG) errors in critical execution paths like execute_apply_validator and execute_activate_validator. It is recommended to maintain status-based counters in Item storage for O(1) access.

Comment on lines +665 to +675
let cat_count =
count_active_validators_by_category(deps.as_ref(), &v.category)?;
if cat_count <= config.min_per_category {
proposal.status = ProposalStatus::Rejected;
PROPOSALS.save(deps.storage, proposal_id, &proposal)?;
return Ok(Response::new()
.add_attribute("action", "execute_proposal")
.add_attribute("proposal_id", proposal_id.to_string())
.add_attribute("result", "rejected_composition_violation"));
}
}
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 execution logic for RemoveValidator proposals enforces the category composition invariant but fails to check the min_validators security invariant (defined in SPEC section 11). If a proposal to remove a validator is executed when the active set is already at the minimum size, it will drop the network below the required security threshold.

References
  1. SPEC Section 11: Security Invariants - Graceful Degradation: If active set drops below min_validators, trigger emergency governance escalation. (link)
  2. Security invariants such as minimum validator count must be enforced on-chain during all state transitions, including those triggered by governance execution.

Comment on lines +818 to +826
.range(deps.storage, None, None, Order::Ascending)
.filter_map(|item| item.ok())
.map(|(_, v)| v)
.filter(|v| {
let status_match = status_filter.as_ref().map_or(true, |s| v.status == *s);
let category_match = category_filter.as_ref().map_or(true, |c| v.category == *c);
status_match && category_match
})
.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

Range queries on VALIDATORS, PROPOSALS, and VOTES (lines 818, 874, 894, 905) lack pagination. As the number of historical records grows, these queries will eventually exceed the block gas limit, leading to a denial of service for both external queries and internal logic. CosmWasm best practices require using limit and start_after parameters for all map iterations.

Comment on lines +151 to +157
let total = count_validators_by_status(deps.as_ref(), None)?;
if total >= config.max_validators {
return Err(ContractError::ValidatorSetFull {
current: total,
max: config.max_validators,
});
}
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

execute_apply_validator checks the total number of entries in the VALIDATORS map against max_validators. Because Removed and TermExpired validators are never deleted from the map, this logic will eventually prevent any new applications once the historical total reaches the limit, even if the active set is empty. The check should likely only consider Candidate, Approved, Active, and Probation statuses.

Comment on lines +717 to +720
if let Some(v) = voting_period_seconds {
c.voting_period_seconds = *v;
}
CONFIG.save(deps.storage, &c)?;
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 UpdateConfig proposal execution does not validate that voting_period_seconds is greater than zero, unlike the direct execute_update_config handler (line 776). Setting this to zero via governance would break the voting mechanism for all future proposals. Additionally, neither path validates that min_validators <= max_validators.

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