Skip to content

feat: M010 reputation-signal + M012 dynamic-supply CosmWasm contracts#79

Merged
glandua merged 5 commits intomainfrom
merge/m010-m012-contracts
Apr 11, 2026
Merged

feat: M010 reputation-signal + M012 dynamic-supply CosmWasm contracts#79
glandua merged 5 commits intomainfrom
merge/m010-m012-contracts

Conversation

@glandua
Copy link
Copy Markdown
Contributor

@glandua glandua commented Apr 11, 2026

Summary

Merges the M010 (reputation-signal) and M012 (dynamic-supply) CosmWasm contracts from PRs #71 and #70, with .gitignore conflict resolution.

  • M010 reputation-signal — Reputation scoring, signal lifecycle, challenge mechanism. Follows correct patterns (cw-storage-plus, cw2 versioning, cfg_attr entry points).
  • M012 dynamic-supply — Fixed cap (221M) dynamic supply with mint/burn lifecycle. Note: missing cw2::set_contract_version and uses bare #[entry_point] — flagged for follow-up.

Resolves #71, Resolves #70

Review notes

  • .gitignore conflicts resolved (kept main's comprehensive Python + Rust patterns)
  • Both contracts add unique directories (contracts/reputation-signal/, contracts/dynamic-supply/)
  • Need to be added as workspace members in contracts/Cargo.toml post-merge

🤖 Generated with Claude Code

brawlaphant and others added 5 commits March 31, 2026 15:49
Implements the Fixed Cap Dynamic Supply mechanism (M012 SPEC) as a
CosmWasm smart contract. Covers:

- Hard-capped supply with configurable cap (default 221M REGEN)
- Algorithmic mint (regrowth) from cap headroom: M[t] = r * (C - S[t])
- Phase-gated effective multiplier (staking vs stability, M014 integration)
- Ecological multiplier (v0 disabled, ready for v1 oracle)
- Supply state machine: Transition -> Dynamic -> Equilibrium (with shock reversion)
- Admin controls for regrowth rate, M014 phase, ecological toggle, equilibrium params
- Query endpoints: supply state, params, period history, simulate
- 29 unit tests covering all 20 SPEC acceptance tests and security invariants

Follows the same structure and patterns as the M013 fee-router contract.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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>
…into merge/m010-m012-contracts

# Conflicts:
#	.gitignore
…into merge/m010-m012-contracts

# Conflicts:
#	.gitignore
@glandua glandua merged commit 36ca7db into main Apr 11, 2026
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 two new CosmWasm contracts: dynamic-supply, which implements an algorithmic token supply mechanism based on staking and ecological metrics, and reputation-signal, which provides a decentralized reputation system with a challenge-dispute lifecycle. The review feedback identifies several critical issues, including incorrect dependency versions in Cargo.toml and the use of non-deterministic floating-point arithmetic in reputation scoring. Additionally, the feedback suggests improving state safety by using saturating arithmetic, ensuring proper contract versioning with cw2, optimizing query efficiency through better indexing of active challenges, and standardizing entry point attributes for library compatibility.

Comment on lines +16 to +21
cosmwasm-std = "2.2"
cosmwasm-schema = "2.2"
cw-storage-plus = "2.0"
schemars = "0.8"
serde = { version = "1.0", default-features = false, features = ["derive"] }
thiserror = "2"
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.

critical

The versions specified for several dependencies are incorrect and will likely cause the build to fail:

  • cosmwasm-std & cosmwasm-schema: Version 2.2 does not exist on crates.io. The latest stable version is 2.0.4.
  • cw-storage-plus: Version 2.0 does not exist. The latest is 1.2.0.
  • thiserror: Version 2 is invalid. It should be 1.0.

Additionally, the cw2 dependency is missing. It's required for setting the contract version with cw2::set_contract_version, which is crucial for managing upgrades.

Suggested change
cosmwasm-std = "2.2"
cosmwasm-schema = "2.2"
cw-storage-plus = "2.0"
schemars = "0.8"
serde = { version = "1.0", default-features = false, features = ["derive"] }
thiserror = "2"
cosmwasm-std = "2.0.4"
cosmwasm-schema = "2.0.4"
cw-storage-plus = "1.2.0"
cw2 = "1.1.1"
schemars = "0.8"
serde = { version = "1.0", default-features = false, features = ["derive"] }
thiserror = "1.0"

Comment on lines +17 to +23
cosmwasm-schema = "2.2"
cosmwasm-std = "2.2"
cw-storage-plus = "2.0"
cw2 = "2.0"
schemars = "0.8"
serde = { version = "1.0", default-features = false, features = ["derive"] }
thiserror = "2.0"
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.

critical

The versions specified for several dependencies are incorrect and will likely cause the build to fail:

  • cosmwasm-std & cosmwasm-schema: Version 2.2 does not exist on crates.io. The latest stable version is 2.0.4.
  • cw-storage-plus & cw2: Version 2.0 does not exist. The latest versions are 1.2.0 and 1.1.1 respectively.
  • thiserror: Version 2.0 is invalid. It should be 1.0.
Suggested change
cosmwasm-schema = "2.2"
cosmwasm-std = "2.2"
cw-storage-plus = "2.0"
cw2 = "2.0"
schemars = "0.8"
serde = { version = "1.0", default-features = false, features = ["derive"] }
thiserror = "2.0"
cosmwasm-schema = "2.0.4"
cosmwasm-std = "2.0.4"
cw-storage-plus = "1.2.0"
cw2 = "1.1.1"
schemars = "0.8"
serde = { version = "1.0", default-features = false, features = ["derive"] }
thiserror = "1.0"

Comment on lines +691 to +719
// v0 scoring: decay-weighted average of endorsement_level/5 (no stake weighting)
// score = sum(decay * endorsement_level / 5) / sum(decay)
// decay = exp(-lambda * age_seconds) where lambda = ln(2) / half_life_seconds
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;
}
}

let score_0_1 = if d_sum > 0.0 { w_sum / d_sum } else { 0.0 };
// Scale to 0-1000
let score = (score_0_1 * 1000.0).round().min(1000.0) as u64;
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.

critical

The query_reputation_score function uses f64 floating-point arithmetic for calculating the score. Floating-point math can be non-deterministic across different machine architectures, which is a critical issue for blockchain applications that require all nodes to reach the same state. All calculations should be performed using the deterministic cosmwasm_std::Decimal type.

Since Decimal does not support ln() or exp(), you may need to use pre-calculated constants for lambda or use a Taylor series approximation for the exponential decay function.

Comment on lines +31 to +36
pub fn instantiate(
deps: DepsMut,
_env: Env,
info: MessageInfo,
msg: InstantiateMsg,
) -> 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.

high

The instantiate function is missing a call to cw2::set_contract_version. Setting the contract name and version is a best practice that is essential for managing contract upgrades and migrations. You will also need to add use cw2::set_contract_version; to the imports and the cw2 crate to your Cargo.toml.

Suggested change
pub fn instantiate(
deps: DepsMut,
_env: Env,
info: MessageInfo,
msg: InstantiateMsg,
) -> Result<Response, ContractError> {
pub fn instantiate(
deps: DepsMut,
_env: Env,
info: MessageInfo,
msg: InstantiateMsg,
) -> Result<Response, ContractError> {
cw2::set_contract_version(deps.storage, "dynamic-supply", env!("CARGO_PKG_VERSION"))?;


// Apply supply adjustment: S[t+1] = S[t] + M[t] - B[t]
let supply_before = state.current_supply;
let supply_after_mint = state.current_supply + mint_amount;
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 + operator is used for Uint128 arithmetic. This can lead to an overflow if the sum exceeds the maximum value of Uint128, causing it to wrap around. This could result in incorrect supply calculations. It's safer to use saturating_add to prevent this.

Suggested change
let supply_after_mint = state.current_supply + mint_amount;
let supply_after_mint = state.current_supply.saturating_add(mint_amount);

Comment on lines +189 to +190
state.total_minted += effective_mint;
state.total_burned += effective_burn;
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 += operator is used for Uint128 arithmetic, which can overflow and wrap around. This could lead to incorrect tracking of total minted and burned tokens. It's safer to use saturating_add to prevent this.

Suggested change
state.total_minted += effective_mint;
state.total_burned += effective_burn;
state.total_minted = state.total_minted.saturating_add(effective_mint);
state.total_burned = state.total_burned.saturating_add(effective_burn);

Comment on lines +733 to +756
fn query_active_challenges(
deps: Deps,
start_after: Option<u64>,
limit: Option<u32>,
) -> StdResult<ActiveChallengesResponse> {
let limit = limit.unwrap_or(30).min(100) as usize;
let start = start_after.map(|s| s + 1).unwrap_or(0);

let mut challenges = Vec::new();
// Iterate challenges from start
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;
}
}
}

Ok(ActiveChallengesResponse { challenges })
}
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 query_active_challenges function iterates over the CHALLENGES map, which stores all challenges ever created, and filters for pending ones. As the number of challenges grows, this query will become very inefficient and may exceed gas limits.

A more scalable approach is to maintain a separate index of only the active (pending) challenges. For example:

  1. Create a new storage map for pending challenges: pub const PENDING_CHALLENGES: Map<u64, ()> = Map::new("pending_challenges");
  2. In exec_submit_challenge, add the new challenge_id to this map.
  3. In exec_resolve_challenge, remove the challenge_id from this map.
  4. query_active_challenges can then iterate over the much smaller PENDING_CHALLENGES map, which would be more gas-efficient.

// Instantiate
// ---------------------------------------------------------------------------

#[entry_point]
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 uses the bare #[entry_point] attribute. The recommended best practice is to use #[cfg_attr(not(feature = "library"), entry_point)]. This allows the contract's logic to be imported as a library into other contracts without including the entry points, which can cause linker errors. This pattern is used in the reputation-signal contract and should be used here for consistency. This applies to execute (line 92) and query (line 358) as well.

Suggested change
#[entry_point]
#[cfg_attr(not(feature = "library"), entry_point)]

Comment on lines +188 to +189
let signal_id = NEXT_SIGNAL_ID.load(deps.storage)?;
NEXT_SIGNAL_ID.save(deps.storage, &(signal_id + 1))?;
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 signal ID is incremented in a non-atomic way (load then save). If an error occurs after the ID is saved but before the function completes successfully, the ID will have been consumed, but no signal created for it, leading to a gap in IDs. A safer pattern is to use Item::update. This also applies to NEXT_CHALLENGE_ID in exec_submit_challenge (lines 357-358).

Suggested change
let signal_id = NEXT_SIGNAL_ID.load(deps.storage)?;
NEXT_SIGNAL_ID.save(deps.storage, &(signal_id + 1))?;
let signal_id = NEXT_SIGNAL_ID.update(|id| -> StdResult<_> { Ok(id + 1) })?;

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.

2 participants