Skip to content

Add claim contract for weight-based fund distribution#405

Merged
p-offtermatt merged 5 commits intomainfrom
claim-contract
Mar 25, 2026
Merged

Add claim contract for weight-based fund distribution#405
p-offtermatt merged 5 commits intomainfrom
claim-contract

Conversation

@p-offtermatt
Copy link
Copy Markdown
Member

Summary

  • New claim contract: admin creates distributions by sending funds with (address, weight) pairs
  • Users call Claim to receive their proportional share across all active distributions (multi-denom)
  • Expired distributions can be swept by anyone, returning unclaimed funds to a configured treasury
  • Duplicate addresses in claims are accumulated (not overwritten), expired entries are lazily cleaned up, and storage errors are properly propagated

Test plan

  • 14 unit tests covering all execute/query paths
  • Run make compile to generate WASM artifact
  • Run make schema to generate JSON schemas and TS types

A CosmWasm contract that allows an admin to create distributions by
sending funds along with a list of (address, weight) pairs. Users
claim their proportional share across all active distributions in a
single transaction. Expired distributions can be swept by anyone,
returning unclaimed funds to a configured treasury address.

Supports multi-denom distributions, duplicate address accumulation,
lazy cleanup of expired claim entries, and proper storage error
propagation.
Copilot AI review requested due to automatic review settings March 24, 2026 16:43
@p-offtermatt p-offtermatt requested a review from a team as a code owner March 24, 2026 16:43
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Introduces a new CosmWasm claim contract that lets an admin create weight-based, multi-denom distributions with expiry, enabling users to claim their proportional share and allowing expired distributions to be swept to a treasury.

Changes:

  • Adds the claim contract (instantiate/execute/query), including state, messages, errors, and schema generation.
  • Implements create-distribution, multi-distribution claiming, expiry checks, and sweep-to-treasury behavior.
  • Adds unit tests and wires the contract into the workspace + make schema flow.

Reviewed changes

Copilot reviewed 11 out of 13 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
contracts/claim/src/contract.rs Core contract logic for creating distributions, claiming across active distributions, sweeping expired funds, and queries.
contracts/claim/src/state.rs Defines on-chain storage layout for config, distributions, and per-user claims.
contracts/claim/src/msg.rs Instantiate/execute message types (CreateDistribution, Claim, SweepExpired, UpdateConfig).
contracts/claim/src/query.rs Query message types and response structs (config, distribution, pending claims).
contracts/claim/src/error.rs ContractError definitions for execution failures.
contracts/claim/src/lib.rs Crate module wiring and test module inclusion.
contracts/claim/src/testing.rs Unit tests covering main execute/query paths and edge behaviors.
contracts/claim/src/bin/claim_schema.rs Schema generation entrypoint for make schema.
contracts/claim/Cargo.toml New contract crate manifest and dependencies.
Cargo.toml Adds contracts/claim to the workspace members.
Makefile Includes claim schema generation + TS codegen target for the new contract.
.changelog/unreleased/features/claim-contract.md Changelog entry for the new claim contract feature.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@claude
Copy link
Copy Markdown

claude bot commented Mar 24, 2026

PR Review: Add claim contract for weight-based fund distribution

Overall this is a well-structured new contract with good test coverage and clear separation of concerns. A few issues worth addressing before merge:


BUGS / CORRECTNESS

1. Redundant addr_validate in execute_create_distribution (contract.rs:86-101)

Addresses are validated once when building weight_by_addr, then validated again needlessly when writing to CLAIMS. Since the keys of weight_by_addr are already canonical Addr::to_string() values, the second call to addr_validate is wasted work. Use Addr::unchecked(addr_str) or restructure weight_by_addr to store Addr directly.


2. DistributionAlreadySwept fires even when all funds were legitimately claimed (contract.rs:202-206)

If all recipients claimed before expiry, remaining_funds will be empty and this error fires — even though no sweep ever occurred. The error message is misleading. Consider a dedicated already_swept: bool flag on Distribution, or rename the error to NoRemainingFunds.


3. Missing migrate entry point

CLAUDE.md states that the migration entrypoint must be implemented in migrate.rs. This contract has no migrate function. Even a passthrough migration should be present for upgradeability.


GAS / SCALABILITY

4. Unbounded iteration in execute_claim and query_pending_claims

Both functions iterate over all distributions for a user in a single tx/query. If a user accumulates claims across many distributions, this will eventually exceed gas limits. Adding an optional limit / start_after pagination parameter to both Claim {} and PendingClaims {} would make the contract production-safe.


STATE MANAGEMENT

5. SweepExpired does not clean up stale CLAIMS entries

After SweepExpired is called, CLAIMS entries for that distribution remain in storage indefinitely. They are lazily removed only if the user later calls Claim, but users who never call Claim leave orphaned storage entries. Consider removing claims as part of the sweep (requires iterating the prefix), or at minimum document this trade-off.


MISSING TEST COVERAGE

The following error paths have no tests:

Error Trigger
ContractError::EmptyClaims CreateDistribution { claims: vec![] }
ContractError::ZeroTotalWeight All ClaimEntry weights are zero
ContractError::ExpiryInPast expiry <= env.block.time
ContractError::DistributionNotFound SweepExpired on non-existent id
ContractError::Unauthorized UpdateConfig called by non-admin

Also worth adding: a test confirming the 'all claimed before expiry' path, which currently triggers the misleading DistributionAlreadySwept error (see point 2 above).


MINOR

6. No way to enumerate distributions. There is no AllDistributions or paginated list query. Offchain tooling and sweep-callers have no way to discover which distribution IDs exist without tracking events. A ListDistributions { start_after, limit } query would be useful.

7. execute_sweep_expired attributes do not include amounts swept. Adding .add_attribute("amount", ...) to the sweep response would make it easier to audit sweep activity from event logs.

8. Distribution IDs start at 0. Starting at 1 is a common convention and avoids 0 being misread as a sentinel/default value in error messages and tooling.

Records what each user claimed (amounts, timestamp, distribution ID)
and exposes it via a ClaimHistory query with start_after/limit pagination.
Copy link
Copy Markdown
Collaborator

@dusan-maksimovic dusan-maksimovic left a comment

Choose a reason for hiding this comment

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

Except from minor things, LGTM.

- Fix claim cleanup rollback: return Ok (no BankMsg) when only expired
  entries are cleaned up, instead of Err which rolls back state changes
- Rename DistributionAlreadySwept to NoFundsToSweep (covers both swept
  and fully-claimed cases)
- Use Addr as HashMap key to avoid double addr_validate
- Sum weights during iteration instead of separate pass
- Filter out zero-weight claim entries
- Add funds attribute to create_distribution response
- Add swept_funds attribute to sweep_expired response
- Record claim history even for 0 funds claimed (leaves a trace)
@p-offtermatt p-offtermatt merged commit 329b32b into main Mar 25, 2026
4 checks passed
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.

3 participants