Skip to content

feat(wpi-token): cap mint size per transaction - #59

Merged
Junman140 merged 4 commits into
Pi-Defi-world:mainfrom
System625:feat/issue-19-mint-tx-cap
Jul 30, 2026
Merged

feat(wpi-token): cap mint size per transaction#59
Junman140 merged 4 commits into
Pi-Defi-world:mainfrom
System625:feat/issue-19-mint-tx-cap

Conversation

@System625

@System625 System625 commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Closes #19

Problem

The rolling per-window cap already existed, but nothing bounded a single call. mint and mint_from_deposit could mint an unbounded amount in one transaction as long as the window had capacity.

Change

Adds max_mint_per_tx, checked on both mint paths, so no privileged path bypasses it.
configure_max_mint_per_tx / max_mint_per_tx are gated on the volume-limit admin (the delegated multisig role from #5), not the bridge admin — a compromised minting key cannot raise its own ceiling. Mints fail closed with MintTxCapNotConfigured until it is set.
An over-cap mint returns false and publishes MintTxCapExceeded { to, amount, max_mint_per_tx }. It returns successfully at the transaction layer on purpose: an Err would roll the event back, and the alert must be visible off-chain. No balances change, no window capacity is consumed, and the deposit id stays unprocessed, so it is re-submittable if governance raises the cap.

Design notes for review

An over-cap mint does not pause the bridge. The per-tx cap is an input ceiling; the window limit stays the circuit breaker. Pausing on one oversized deposit would give any depositor a bridge-wide DoS. Splitting a large mint into cap-sized mints still trips the window breaker (tested).
Stored under its own instance key rather than added to VolumeLimitConfig as adding a field would make read_volume_config panic with ConversionError on any instance upgraded via upgrade().

Testing

31 contract tests (10 new) and 55 relayer tests pass. Full CI-equivalent run is green: fmt --check, clippy --locked --all-targets -D warnings, test --locked --all, wasm release build, dependency provenance.

New coverage: over-cap mint rejected with the exact event asserted and all state untouched; mint(i128::MAX) rejected; exactly-at-cap accepted; under-cap mints still trip the window breaker; cap enforced behind the pause and replay gates; invalid caps rejected; non-admin and post-rotation bridge admin cannot configure; delegated guardian raises the cap and the rejected deposit then mints.

Note for maintainers

main is currently red: test_existing_admin_retains_privileges_until_acceptance (from #57) calls .unwrap() on a non-Result, so cargo test does not compile, plus 14 unused-binding warnings and rustfmt drift that fail CI. Fixed here so the suite runs; happy to split into a separate PR.

Summary by CodeRabbit

  • New Features
    • Added a configurable per-transaction mint ceiling for minting via both direct mints and deposit-based mints, fail-closed until configured.
    • Added admin controls to set and view the current per-transaction cap; over-cap attempts are rejected with an alert/event and no state changes.
    • Added redemption replay protection for burns using a redemption_id, emitting the redemption_id in the burn event.
  • Bug Fixes
    • Improved relayer messaging to clearly distinguish per-transaction cap rejections from rolling-window circuit breaker rejections.
  • Documentation
    • Updated admin-role separation docs and the changelog to reflect the new cap behavior and permissions.

`mint` and `mint_from_deposit` were bounded only by the rolling-window
volume limit, so a single call from a compromised admin key or a buggy
relayer could mint an unbounded amount in one transaction as long as the
window had capacity.

Add a `max_mint_per_tx` ceiling checked on every mint path, owned by the
same independent volume-limit admin as the window limits so the bridge
admin cannot raise its own ceiling. Mints fail closed with
`MintTxCapNotConfigured` until it is set.

An over-cap mint returns `false` without changing balances, consuming
window capacity, or marking its deposit processed, and publishes
`MintTxCapExceeded` — the call succeeds at the transaction layer so the
alert is committed rather than rolled back with the invocation. It does
not pause the bridge: the per-transaction ceiling is an input check,
while sustained over-minting still trips the rolling-window breaker.

Stored under its own instance key so an upgraded deployment's existing
`VolumeLimitConfig` keeps decoding.

Also fixes a test that did not compile on main
(`volume_limit_config().unwrap()`), unused bindings that fail
`clippy -D warnings`, and rustfmt drift.

Closes Pi-Defi-world#19
@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 443e584d-c1cb-418a-991c-b461a3dba414

📥 Commits

Reviewing files that changed from the base of the PR and between e09883e and 175a4f3.

📒 Files selected for processing (4)
  • Stellar-contracts-v1/README.md
  • Stellar-contracts-v1/wpi-token/src/lib.rs
  • Stellar-contracts-v1/wpi-token/src/test.rs
  • relayer/src/stellar/wpiContractClient.ts

📝 Walkthrough

Walkthrough

The WPI token contract adds configurable per-transaction mint-cap enforcement, redemption replay protection, storage TTL handling, and related tests. README, architecture documentation, changelog, and relayer messaging describe the new cap behavior and committed rejection outcomes.

Changes

Token controls and operational integration

Layer / File(s) Summary
Mint-cap contract and configuration
Stellar-contracts-v1/wpi-token/src/lib.rs, Stellar-contracts-v1/README.md
Adds stored cap configuration, admin and view methods, cap events, fail-closed behavior, and enforcement before mint state changes.
Mint and redemption path validation
Stellar-contracts-v1/wpi-token/src/test.rs
Tests cap rejection, boundaries, events, authorization, role changes, mint-path updates, and redemption replay protection.
Storage TTL behavior
Stellar-contracts-v1/wpi-token/src/test.rs
Validates balance and allowance TTL refresh, instance TTL bumping, and expiration ordering across users.
Relayer and contract documentation
relayer/src/stellar/*, docs/design/admin-role-separation.md, CHANGELOG.md
Updates cap-related role documentation, changelog entries, relayer warnings, persisted errors, and interface documentation.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Relayer
  participant WpiToken
  participant MintTxCap
  participant DepositState
  Relayer->>WpiToken: mint_from_deposit
  WpiToken->>MintTxCap: enforce_mint_tx_cap
  MintTxCap-->>WpiToken: reject oversized mint and publish MintTxCapExceeded
  WpiToken-->>Relayer: committed cap rejection
  WpiToken->>DepositState: process deposit only after accepted mint
Loading

Possibly related PRs

Suggested reviewers: princess-peekay, charlesedeh021-cell

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title concisely and accurately summarizes the main change: adding a per-transaction mint cap.
Linked Issues check ✅ Passed The PR adds the per-transaction cap, delegated admin configuration, and rejection event while preserving the existing rolling-window circuit breaker.
Out of Scope Changes check ✅ Passed The changes stay focused on the mint-cap feature, related tests, docs, changelog, and relayer messaging, with no unrelated scope evident.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
relayer/src/stellar/mintSubmitter.ts (1)

47-47: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Make the warning cover both rejection causes.

This branch handles both the per-transaction ceiling and the rolling-window circuit breaker, but the log says only “a bridge mint cap.” Use wording such as “per-transaction or rolling-window mint cap” so operators are not given an inaccurate diagnosis.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@relayer/src/stellar/mintSubmitter.ts` at line 47, Update the warning message
in the mint-blocking branch of the mint submitter to identify both possible
rejection causes: the per-transaction mint cap and the rolling-window mint
cap/circuit breaker. Keep the existing warning metadata unchanged.
Stellar-contracts-v1/wpi-token/src/test.rs (1)

186-227: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider narrowing the bare #[should_panic] attributes.

Both authorization tests pass on any panic, including one from a mistyped fn_name or an unrelated future failure — which would silently void the "only the volume-limit admin owns the cap" guarantee these tests exist to protect. Adding expected = "..." with a substring of the auth-failure panic ties them to the intended cause.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Stellar-contracts-v1/wpi-token/src/test.rs` around lines 186 - 227, Narrow
the #[should_panic] attributes on
non_admin_signer_cannot_authenticate_configure_max_mint_per_tx and
bridge_admin_cannot_raise_the_tx_cap_after_rotation by adding expected
substrings matching the authorization-failure panic. Use the actual panic text
emitted by configure_max_mint_per_tx so both tests fail for mistyped calls or
unrelated panics.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@relayer/src/stellar/mintSubmitter.ts`:
- Line 47: Update the warning message in the mint-blocking branch of the mint
submitter to identify both possible rejection causes: the per-transaction mint
cap and the rolling-window mint cap/circuit breaker. Keep the existing warning
metadata unchanged.

In `@Stellar-contracts-v1/wpi-token/src/test.rs`:
- Around line 186-227: Narrow the #[should_panic] attributes on
non_admin_signer_cannot_authenticate_configure_max_mint_per_tx and
bridge_admin_cannot_raise_the_tx_cap_after_rotation by adding expected
substrings matching the authorization-failure panic. Use the actual panic text
emitted by configure_max_mint_per_tx so both tests fail for mistyped calls or
unrelated panics.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 3eb290de-5dea-43b5-a99e-bf54ae9a84c8

📥 Commits

Reviewing files that changed from the base of the PR and between cf7526c and 0ebc3a3.

📒 Files selected for processing (5)
  • Stellar-contracts-v1/README.md
  • Stellar-contracts-v1/wpi-token/src/lib.rs
  • Stellar-contracts-v1/wpi-token/src/test.rs
  • relayer/src/stellar/mintSubmitter.ts
  • relayer/src/stellar/wpiContractClient.ts

Name both rejection causes in the relayer warn log, and pin the two new
auth tests to the auth panic so an unrelated panic cannot satisfy them.
Upstream split the single admin key into admin/minter/pauser (Pi-Defi-world#5), so
`mint` and `mint_from_deposit` are now minter-gated. The per-transaction
cap sits above that gate unchanged and is still owned by the volume-limit
admin, which is now a stronger separation: the hot minter key cannot raise
its own ceiling.

- Kept upstream's `require_minter` gates and merged the `mint` doc comment.
- Dropped this branch's drive-by CI fixes in favour of upstream's eef6676,
  which fixed the same fmt/clippy/compile breakage on main.
- Took upstream's rewrite of
  `test_existing_admin_retains_privileges_until_acceptance`, which now
  asserts on `propose_admin` since pausing moved to the pauser role.
- `every_role_can_be_a_contract_address_not_only_an_eoa` configures a cap:
  it mints, and mints now fail closed until the ceiling is set.
- Renamed the cap authorization test to rotate the minter as well, so it
  covers the post-split threat model, and renamed the bypass test to match
  the minter role.
- Recorded `configure_max_mint_per_tx` in the role tables in the contract
  README and the role-separation design doc, and added a CHANGELOG entry.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
docs/design/admin-role-separation.md (1)

74-77: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Document the required cap configuration before resuming minting.

The upgraded contract fails closed with MintTxCapNotConfigured when the new storage key is absent, but this plan says no migration step is required and that the relayer continues working. Add a mandatory configure_max_mint_per_tx step, performed by the VolumeLimitAdmin, before routing bridge traffic; otherwise existing deployments will stop all mints after upgrade.

Also applies to: 100-102

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/design/admin-role-separation.md` around lines 74 - 77, Update the
upgrade plan in the admin role separation document to include a mandatory
configure_max_mint_per_tx call by VolumeLimitAdmin after the WASM upgrade and
before resuming bridge traffic or minting. Remove the claim that no migration
step is required, and state that relaying must remain paused until the cap is
configured.
🧹 Nitpick comments (1)
Stellar-contracts-v1/wpi-token/src/test.rs (1)

90-93: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Retry the same rejected deposit in the state-preservation test.

The test confirms deposit_id(1) remains unprocessed, but the successful call uses deposit_id(2). After raising the cap, retry deposit_id(1) and assert it succeeds to cover the documented replay/state-preservation guarantee directly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Stellar-contracts-v1/wpi-token/src/test.rs` around lines 90 - 93, Update the
state-preservation test to retry deposit_id(1) after governance raises the
minting ceiling, rather than using deposit_id(2). Assert that this retry
succeeds while preserving the existing checks that the rejected attempt consumed
no window capacity and did not mark deposit_id(1) as processed.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@docs/design/admin-role-separation.md`:
- Around line 74-77: Update the upgrade plan in the admin role separation
document to include a mandatory configure_max_mint_per_tx call by
VolumeLimitAdmin after the WASM upgrade and before resuming bridge traffic or
minting. Remove the claim that no migration step is required, and state that
relaying must remain paused until the cap is configured.

---

Nitpick comments:
In `@Stellar-contracts-v1/wpi-token/src/test.rs`:
- Around line 90-93: Update the state-preservation test to retry deposit_id(1)
after governance raises the minting ceiling, rather than using deposit_id(2).
Assert that this retry succeeds while preserving the existing checks that the
rejected attempt consumed no window capacity and did not mark deposit_id(1) as
processed.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 39ec529e-836a-486c-abab-9652ebe3e68c

📥 Commits

Reviewing files that changed from the base of the PR and between 0ebc3a3 and e09883e.

📒 Files selected for processing (6)
  • CHANGELOG.md
  • Stellar-contracts-v1/README.md
  • Stellar-contracts-v1/wpi-token/src/lib.rs
  • Stellar-contracts-v1/wpi-token/src/test.rs
  • docs/design/admin-role-separation.md
  • relayer/src/stellar/mintSubmitter.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • Stellar-contracts-v1/README.md
  • relayer/src/stellar/mintSubmitter.ts
  • Stellar-contracts-v1/wpi-token/src/lib.rs

@Junman140
Junman140 merged commit f1ab79e into Pi-Defi-world:main Jul 30, 2026
1 check was pending
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.

Issue 19 — No mint cap / circuit breaker

2 participants