Skip to content

feat: add two-step admin transfer - #34

Merged
JamesVictor-O merged 2 commits into
Ads-Bazaar:mainfrom
josephchimebuka:feat/admin-transfer-handover
Jul 25, 2026
Merged

feat: add two-step admin transfer#34
JamesVictor-O merged 2 commits into
Ads-Bazaar:mainfrom
josephchimebuka:feat/admin-transfer-handover

Conversation

@josephchimebuka

Copy link
Copy Markdown
Contributor

Summary

  • Add propose_admin / accept_admin for safe two-step escrow admin rotation.
  • Store and clear PendingAdmin, emit typed admin handover events, and add an admin-only update_fee_bps setter to verify post-transfer authority.
  • Clean up stale scaffold code that prevented the escrow test suite from compiling cleanly.

Test plan

  • cargo test -p ads-bazaar-campaign-escrow

Closes #17

Allow the escrow admin to safely rotate keys through a propose-and-accept flow, preventing accidental lockout while preserving admin-only controls after handover.

Co-authored-by: Cursor <cursoragent@cursor.com>

@JamesVictor-O JamesVictor-O left a comment

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.

The actual two-step admin transfer logic here is well done — matches the issue spec closely and the auth pattern is correct in both directions:

  • propose_admin: gated by the existing require_admin helper (current admin only), stores the candidate, emits AdminProposed.
  • accept_admin: correctly calls new_admin.require_auth() (the candidate proves they hold the key, not the old admin), checks it matches PendingAdmin exactly, then finalizes and clears the pending slot. This is the right way around — a single-step transfer or one that only checks the old admin's auth would defeat the whole point of the pattern.
  • update_fee_bps as the "prove the new admin has real authority" vehicle is a reasonable call given no other admin-mutating setter existed yet to test against, and it's independently useful (fee_bps was otherwise fixed forever at initialize).

Test coverage is solid and maps directly onto the issue's acceptance criteria: non-admin propose rejected, pending-candidate storage verified directly, the full propose→accept→old-admin-loses-access→new-admin-gains-access round trip, wrong-address accept rejected, and accept-before-propose rejected. On this branch by itself, cargo fmt --check, cargo build, cargo test --workspace (35 + 7 passing), and cargo clippy -D warnings are all clean.

The blocker is purely a base-branch issue, not your code: this branch is rooted at 75ca6f6 (right after #22 merged), which was main's state before I had to repair it — #21 and #22 had been merged into main with unresolved conflicts (duplicate/colliding Error discriminants, dead code after an early return, a corrupted reclaim_surplus body) that I fixed directly on main on 2026-07-20, and #33 has since added InvalidDeadlineOrder = 20. GitHub is correctly flagging this PR as CONFLICTING in error.rs and test.rs as a result — not because your changes are wrong, but because both files have moved out from under this branch.

Good news: this PR doesn't add any new Error variants (it only reuses the existing Unauthorized and InvalidAmount), so once rebased onto current main, error.rs shouldn't need any changes from this PR at all — the conflict should resolve by just taking main's version of that file. test.rs will need your new test_admin_transfer module (and whichever of your test_admin_basics tests, if any, aren't already covered by main's test_pause/test_version_upgrade modules) reapplied on top of the current file rather than the version this branch currently has.

Could you rebase onto latest main and push? Everything else here looks ready.

Resolve campaign escrow conflicts by preserving upstream fee, treasury, metadata, and dispute updates while keeping the two-step admin transfer flow.

Co-authored-by: Cursor <cursoragent@cursor.com>
@JamesVictor-O

Copy link
Copy Markdown
Contributor

Merged, thanks for this — the two-step propose/accept pattern is exactly right for an admin handover (avoids the single-fat-fingered-address footgun of a direct update_admin).

Since this branch was based on a commit just before #31 merged, I double-checked the merge for silent regressions the way I have for a few other PRs this cycle (git merge-tree + git show on the resulting tree, then verified again post-merge on real main): confirmed FeeTooHigh and InvalidCreatorCount (added by #31) survived correctly alongside your new PendingAdmin storage, AdminProposed/AdminTransferred events, and propose_admin/accept_admin. No follow-up fix was needed — clean merge.

Verified before merging: fmt, full workspace build, all 68 tests (including your new test_admin_transfer module), clippy -D warnings, and the wasm32v1-none release build. CI is green on main post-merge: https://github.com/Ads-Bazaar/ads-bazaar-contract/actions/runs/30161062224

JamesVictor-O added a commit that referenced this pull request Jul 25, 2026
PR49's branch was cut before #31 and #34 merged, so its new PayoutFrozen
error variant independently claimed discriminant 24, the same slot #31
had already given to InvalidCreatorCount. Resolved the conflict by
bumping PayoutFrozen to 25, keeping the additive-only numbering
convention; no other change to the PR's logic.

Co-Authored-By: Anuoluwapo Ali <anuoluwapoali25@gmail.com>
JamesVictor-O pushed a commit that referenced this pull request Jul 25, 2026
…50)

storage::add_campaign_applicant rewrote an ever-growing Vec<Address> on every apply_to_campaign call just to answer a yes/no question in has_campaign_applicants. Replaces it with a u32 counter under DataKey::ApplicantCount, matching the existing approved_count pattern on Campaign, so applying costs O(1) storage-writes regardless of how many creators already applied.

Adds a regression test that applies 200+ creators and asserts (via env.cost_estimate()) that the storage write cost of a later apply matches an early one, plus confirms update_campaign_metadata's lock-after-first-application behavior from #38 is unaffected.

Closes #43

Verified locally in an isolated worktree before merge:
- cargo fmt --all -- --check
- cargo build --workspace
- cargo test --workspace (64 tests)
- cargo clippy --workspace --all-targets -- -D warnings
- cargo build --workspace --target wasm32v1-none --release
- Confirmed via git merge-tree that the merge combines cleanly with #34 and #49, which landed after this branch was cut.
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.

feat: add transfer_admin() two-step handover for admin key rotation

2 participants