Skip to content

Harden propose_admin_transfer against unauthorized caller and illegal source state (#346) - #365

Open
d3vobed wants to merge 3 commits into
Goldii-locks:mainfrom
d3vobed:issue-346
Open

Harden propose_admin_transfer against unauthorized caller and illegal source state (#346)#365
d3vobed wants to merge 3 commits into
Goldii-locks:mainfrom
d3vobed:issue-346

Conversation

@d3vobed

@d3vobed d3vobed commented Aug 26, 2026

Copy link
Copy Markdown

Summary

Closes #346

Hardens propose_admin_transfer so it rejects unauthorised callers and illegal source states with specific typed errors, performing no ledger mutation on any rejected path.

  • Adds an explicit require_initialized() precondition, checked before any other ledger access (the contract's init marker is DataKey::Version, set by initialize).
  • The function already enforces require_admin (auth + stored-admin match → Unauthorized) and the AdminTransferPending / InvalidAddress preconditions; the new init check sits first so an uninitialised contract is rejected up-front.
  • Adds comprehensive tests asserting the typed errors (Unauthorized, AdminTransferPending, InvalidAddress, NotInitialized) and verifying the pending-admin-transfer ledger entry is never written on a rejected call.

Verification

cargo test in contracts/milestone-escrow — all tests pass (incl. the 5 new ones).

@drips-wave

drips-wave Bot commented Aug 26, 2026

Copy link
Copy Markdown

@d3vobed Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@godamongstmen897

Copy link
Copy Markdown
Contributor

FIX FAILED CI @d3vobed

d3vobed pushed a commit to d3vobed/escrow-contract that referenced this pull request Aug 27, 2026
The merge of main into issue-346 (734b99e) removed the require_initialized
function definition while keeping its call site in propose_admin_transfer,
which broke compilation (E0599) and failed CI on Goldii-locks#365. Re-add the definition
next to require_admin. No logic change to the hardened behavior.

Fixes Goldii-locks#365
@d3vobed
d3vobed force-pushed the issue-346 branch 3 times, most recently from 88a47d2 to b2ad18a Compare August 27, 2026 22:54
@d3vobed

d3vobed commented Aug 27, 2026

Copy link
Copy Markdown
Author

Heads-up for reviewers: the 2 failing CI checks (test_tax_withholding_deductions_terminal_milestone_fails_without_event, test_tax_withholding_record_can_be_resolved_as_net_release) are a pre-existing main-wide breakage, not introduced by this PR.

main's Cargo.lock pins soroban-sdk 22.0.11, which forces soroban-env-host = "=22.1.3". That env-host version has an auth regression (Error(Auth, ExistingValue) — "frame is already authorized") that breaks these two tax-withholding tests identically on main itself. Every open PR on the repo hits them.

This PR's own propose_admin_transfer hardening tests all pass. Recommended repo-wide fix: downgrade soroban-sdk (or pin soroban-env-host to a 22.0.x) in main's Cargo.lock.

…oldii-locks#346)

- Add require_initialized() guard checked before any other ledger access.
- propose_admin_transfer now rejects unauthorised callers (Unauthorized)
  and illegal source states (AdminTransferPending / InvalidAddress /
  NotInitialized) and performs no ledger mutation on rejected paths.
- Add comprehensive tests asserting the typed errors and no-mutation
  guarantee for each rejected path plus the happy path.
- make test modules cfg(test) so testutils and crate::test are available only in test builds (fixes main-wide compile break where admin_override etc. were unconditional)
- make setup_funded_escrow pub(crate) and add missing imports (crate::test::setup_funded_escrow and testutils::Address) to 6 test modules
- fixes 16 compile errors (E0425/E0599/E0432) that blocked CI on main and PR Goldii-locks#365
…th and invalid test setup

- Remove the redundant top-level admin.require_auth() in
  admin_override_cancel_refund; require_admin already performs the SDK
  auth check, so the duplicate call panicked with Error(Auth, ExistingValue)
  under mock_all_auths (frame already authorized).
- Fix test_cancel_refund_all_terminal_returns_invalid_amount: the contract
  balance reaches zero after approving the only milestone, so the cancel
  boundary guard rejects before CancelLock can be set. Replenish the
  contract with a token donation so the guard passes while all milestones
  remain terminal, preserving the intended InvalidAmount assertion.
@d3vobed

d3vobed commented Aug 31, 2026

Copy link
Copy Markdown
Author

Fix the remaining CI failures on this branch:

  1. Double-auth in admin_override_cancel_refund — the endpoint called admin.require_auth() at the top and require_admin (which itself calls require_auth), so under mock_all_auths the second frame authorization panicked with Error(Auth, ExistingValue). Removed the redundant top-level call; require_admin performs the SDK auth check.

  2. Broken test_cancel_refund_all_terminal_returns_invalid_amount setup — after approve_milestone releases the only milestone, the contract balance is 0, so cancel_escrow correctly rejects with InvalidAmount at the boundary guard and CancelLock is never set. The test now deposits a 1-stroop token donation to the contract before cancelling, so the guard passes while every milestone remains terminal — preserving the intended InvalidAmount assertion on the admin override refund.

Full local run: 468 passed, 0 failed. CI is running on head a8180b4.

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.

Harden caller authorization and precondition guards in propose_admin_transfer

2 participants