fix(wpi-token): authenticate admin from stored state, not caller-supplied argument - #55
Conversation
…lied argument mint, mint_from_deposit, burn, set_admin, set_paused, set_volume_limit_admin, configure_volume_limits, override_volume_limit, and upgrade took an admin address as a parameter, compared it to storage, and only then called require_auth() on it. This couples correctness to argument order and invites a bug where the wrong address is silently authenticated. These functions now drop the admin parameter entirely and authenticate the address read from storage directly. Adds tests confirming a non-admin signer is rejected regardless of which address it attempts to authenticate as, since there is no longer an argument to spoof. Fixes Pi-Defi-world#17
📝 WalkthroughWalkthroughWpiToken privileged entrypoints now authenticate stored administrators instead of caller-supplied admin arguments. Tests and README, quickstart, and testnet invocation examples were updated to match the revised contract interface. ChangesStored-admin authorization
Estimated code review effort: 3 (Moderate) | ~25 minutes Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
Stellar-contracts-v1/wpi-token/src/test.rs (1)
166-230: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winExtend non-admin-spoof coverage to the remaining admin-gated entrypoints.
Only
mint,configure_volume_limits, andoverride_volume_limitget a dedicated "attacker mocks their own auth" test.burn,mint_from_deposit,set_admin,set_paused,set_volume_limit_admin, andupgradewere changed identically but have no equivalent regression test proving a non-admin signer can't authenticate them.♻️ Suggested pattern to replicate for the remaining entrypoints
#[test] #[should_panic] fn non_admin_signer_cannot_authenticate_burn() { let env = Env::default(); let (_admin, client, user) = setup(&env, 10, 10, 10); let destination = BytesN::from_array(&env, &[9; 32]); let attacker = Address::generate(&env); client .mock_auths(&[MockAuth { address: &attacker, invoke: &MockAuthInvoke { contract: &client.address, fn_name: "burn", args: (&user, &1i128, &destination).into_val(&env), sub_invokes: &[], }, }]) .burn(&user, &1, &destination); }🤖 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 166 - 230, Add #[test] and #[should_panic] regression tests matching the existing non-admin signer patterns for burn, mint_from_deposit, set_admin, set_paused, set_volume_limit_admin, and upgrade. In each test, use setup, generate an attacker address, configure mock_auths for that entrypoint with matching arguments, and invoke it through the client so a non-admin signer is rejected.
🤖 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.
Inline comments:
In `@Stellar-contracts-v1/testnet_e2e_simulation.sh`:
- Around line 91-98: Update the wPi `burn` invocation in the Bridge Withdrawal
step to provide the required `pi_destination` argument in addition to `--from
$ALICE_PUB` and `--amount $SWAP_AMOUNT`, using Alice’s intended Pi destination
so the simulation completes successfully.
---
Nitpick comments:
In `@Stellar-contracts-v1/wpi-token/src/test.rs`:
- Around line 166-230: Add #[test] and #[should_panic] regression tests matching
the existing non-admin signer patterns for burn, mint_from_deposit, set_admin,
set_paused, set_volume_limit_admin, and upgrade. In each test, use setup,
generate an attacker address, configure mock_auths for that entrypoint with
matching arguments, and invoke it through the client so a non-admin signer is
rejected.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: f6deb509-3132-4734-a787-1fbb23f621d4
📒 Files selected for processing (5)
Stellar-contracts-v1/README.mdStellar-contracts-v1/scripts/quickstart.shStellar-contracts-v1/testnet_e2e_simulation.shStellar-contracts-v1/wpi-token/src/lib.rsStellar-contracts-v1/wpi-token/src/test.rs
| echo "[6/6] Bridge Withdrawal (Alice burns remaining wPi)..." | ||
| # Alice approves relayer to burn? Or relayer burns? | ||
| # In wpi-token, `burn` takes `admin` as auth, and `from` address. | ||
| # Alice approves relayer to burn? Or relayer burns? | ||
| # In wpi-token, `burn` authenticates the stored admin and takes a `from` address. | ||
| # So the user must transfer/approve or the admin just burns it. | ||
| # Usually, to burn, the admin must be the one calling `burn` acting on behalf of the user's bridge request. | ||
| # Let's say Alice requests withdrawal, Relayer sees it and burns her remaining 500 wPi. | ||
| soroban contract invoke --id $WPI_ID --source relayer --network $NETWORK -- \ | ||
| burn --admin $RELAYER_PUB --from $ALICE_PUB --amount $SWAP_AMOUNT | ||
| burn --from $ALICE_PUB --amount $SWAP_AMOUNT |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
burn invocation is missing the required pi_destination argument.
burn requires (from, amount, pi_destination); this call only supplies --from and --amount, so it will fail at invocation time and the "Bridge Withdrawal" step of the simulation won't complete.
🐛 Proposed fix
soroban contract invoke --id $WPI_ID --source relayer --network $NETWORK -- \
- burn --from $ALICE_PUB --amount $SWAP_AMOUNT
+ burn --from $ALICE_PUB --amount $SWAP_AMOUNT --pi_destination <32-byte-hex-destination>🧰 Tools
🪛 Shellcheck (0.11.0)
[info] 97-97: Double quote to prevent globbing and word splitting.
(SC2086)
[info] 98-98: Double quote to prevent globbing and word splitting.
(SC2086)
🤖 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/testnet_e2e_simulation.sh` around lines 91 - 98, Update
the wPi `burn` invocation in the Bridge Withdrawal step to provide the required
`pi_destination` argument in addition to `--from $ALICE_PUB` and `--amount
$SWAP_AMOUNT`, using Alice’s intended Pi destination so the simulation completes
successfully.
The contracts job has been failing on main since Pi-Defi-world#57, on three counts: - cargo fmt --check reported diffs in lib.rs (remove_proposed_admin) and test.rs (trailing whitespace, an unwrapped mock_auths chain). - test.rs called client.volume_limit_config().unwrap(), but the generated client already unwraps, so VolumeLimitConfig has no unwrap and the test target did not compile (E0599). - 14 `admin` bindings left unused after Pi-Defi-world#55 removed the admin argument, each fatal under clippy's -D warnings. Apply rustfmt, drop the stray unwrap, and prefix the unused bindings with an underscore. No contract behaviour or test assertion changes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Summary
mint,mint_from_deposit,burn,set_admin,set_paused,set_volume_limit_admin,configure_volume_limits,override_volume_limit, andupgradeall took anadmin: Addressparameter, compared it againstread_admin(&env)(orread_volume_limit_admin), and only then called.require_auth()on it. That couples correctness to argument order and leaves room for a caller-supplied address to be silently authenticated in place of the real admin.require_admin(&env)/require_volume_limit_admin(&env), which read the address from storage and call.require_auth()on that — there is nothing left for a caller to pass in that could stand in for the real admin.Error::NotAdminis kept (unused) so the existing contract error-code numbering doesn't shift for anyone decoding error 1.Test plan
cargo build(whole workspace) — compiles cleanly.non_admin_signer_cannot_authenticate_mint,..._configure_volume_limits,..._override_volume_limit,bridge_admin_cannot_authenticate_as_volume_limit_admin_after_rotation), using explicitmock_authsfor a specific non-admin address rather thanmock_all_auths.only_override_can_lift_a_tripped_circuit_breaker,volume_limit_admin_is_independent_from_bridge_admin, proptest property tests) to the new call signatures.cargo test -p wpi-token— could not run in my sandbox:soroban-sdk/soroban-env-host23.0.1 pull bothed25519-dalek2.2.0 and 3.0.0 into the dependency graph with incompatiblerand_corerequirements, which fails to compile under thetestutilsfeature. This reproduces identically onmainbefore this change (verified viagit stash), so it's a pre-existing lockfile/dependency issue unrelated to this PR, and I had no network access to bump the lock. Please run the test suite in CI/locally to confirm.scripts/quickstart.sh,testnet_e2e_simulation.sh, andREADME.mdinvocations to drop the now-removed--adminflags (kept it whereinitializestill legitimately takes it).Summary by CodeRabbit
New Features
Bug Fixes
Documentation