Conversation
Add two new event emitters to signal key rotation on-chain: - admin_transferred(old_admin, new_admin) - oracle_updated(old_oracle, new_oracle) Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Allow the admin to rotate privileged keys without redeploying: - transfer_admin(new_admin): requires current admin auth, overwrites DataKey::Admin so old key instantly loses all privileges - set_oracle(new_oracle): requires admin auth, overwrites DataKey::Oracle so old oracle can no longer finalize sessions Closes LightForgeHub#34 Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Cover the acceptance criteria from issue LightForgeHub#34: - transfer_admin succeeds and new admin gains pause/unpause privileges - old admin loses privileges after transfer (auth cleared) - set_oracle succeeds and new oracle can finalize sessions - non-admin cannot call transfer_admin or set_oracle Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
📝 WalkthroughWalkthroughThis PR adds admin and oracle key rotation functionality to the payment vault contract. It introduces two new externally callable functions— Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Possibly related issues
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ 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.
🧹 Nitpick comments (1)
contracts/payment-vault-contract/src/test.rs (1)
700-831: Good test coverage for key rotation functionality.The tests cover the essential scenarios: successful transfers, new principals gaining privileges, and authorization enforcement. A few observations:
test_new_admin_can_pause_after_transfer- Withmock_all_auths(), this confirms the storage was updated and pause succeeds. Combined withtest_old_admin_loses_privileges_after_transfer, you get confidence in the auth model.
test_old_admin_loses_privileges_after_transfer- Correctly verifies that auth is required, though the test name suggests testing the old admin specifically. What's actually verified is that no auth = failure. This is still valuable coverage.Consider adding tests for edge cases if desired:
📝 Suggested additional tests (optional)
#[test] fn test_transfer_admin_not_initialized() { let env = Env::default(); let client = create_client(&env); let new_admin = Address::generate(&env); // Should fail with NotInitialized let result = client.try_transfer_admin(&new_admin); assert!(result.is_err()); } #[test] fn test_old_oracle_cannot_finalize_after_rotation() { let env = Env::default(); env.mock_all_auths(); let admin = Address::generate(&env); let oracle_old = Address::generate(&env); let oracle_new = Address::generate(&env); let user = Address::generate(&env); let expert = Address::generate(&env); let token_admin = Address::generate(&env); let token = create_token_contract(&env, &token_admin); token.mint(&user, &10_000); let client = create_client(&env); client.init(&admin, &token.address, &oracle_old); client.set_my_rate(&expert, &10_i128); let booking_id = client.book_session(&user, &expert, &100); // Rotate oracle client.set_oracle(&oracle_new); // Clear auths - now only explicit auth will work env.set_auths(&[]); // Without auth, finalize should fail (proves oracle_new auth is required) let result = client.try_finalize_session(&booking_id, &50); assert!(result.is_err()); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@contracts/payment-vault-contract/src/test.rs` around lines 700 - 831, Add two edge-case tests: (1) test_transfer_admin_not_initialized — create an Env and client via create_client but do not call client.init, call client.try_transfer_admin with a generated Address and assert it returns Err (expecting NotInitialized behavior); (2) test_old_oracle_cannot_finalize_after_rotation — follow the pattern in test_set_oracle_success to init with oracle_old, book a session (use client.set_my_rate and client.book_session), call client.set_oracle(&oracle_new) to rotate, clear auths via env.set_auths(&[]), then call client.try_finalize_session(&booking_id, &50) and assert it returns Err to ensure the old oracle cannot finalize after rotation; use create_token_contract and token.mint as in other tests to fund the booking.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@contracts/payment-vault-contract/src/test.rs`:
- Around line 700-831: Add two edge-case tests: (1)
test_transfer_admin_not_initialized — create an Env and client via create_client
but do not call client.init, call client.try_transfer_admin with a generated
Address and assert it returns Err (expecting NotInitialized behavior); (2)
test_old_oracle_cannot_finalize_after_rotation — follow the pattern in
test_set_oracle_success to init with oracle_old, book a session (use
client.set_my_rate and client.book_session), call client.set_oracle(&oracle_new)
to rotate, clear auths via env.set_auths(&[]), then call
client.try_finalize_session(&booking_id, &50) and assert it returns Err to
ensure the old oracle cannot finalize after rotation; use create_token_contract
and token.mint as in other tests to fund the booking.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: bb9cafc7-13a6-4100-a43a-93b00d0431ac
📒 Files selected for processing (4)
contracts/payment-vault-contract/src/contract.rscontracts/payment-vault-contract/src/events.rscontracts/payment-vault-contract/src/lib.rscontracts/payment-vault-contract/src/test.rs
feat: payment vault key rotation (transfer_admin + set_oracle)
cargo test(All tests passed)📌 Type of Change
📝 Changes Description
Adds key rotation to the
payment-vault-contractso compromised keys can be replaced without redeploying. Without this, a leaked admin or oracle key permanently breaks the contract.transfer_admin(new_admin)— current admin signs,DataKey::Adminis overwritten; old key instantly loses all privileges (pause, set_oracle, transfer_admin)set_oracle(new_oracle)— admin signs,DataKey::Oracleis overwritten; old oracle can no longer callfinalize_sessionadmin_transferred,oracle_updated) so off-chain monitors can react to key changesNo breaking changes — existing storage layout and all prior entry points are unchanged.
📸 Evidence
🌌 Comments
Key points for reviewers:
transfer_adminreads the current stored admin before overwriting — there is no window where both keys are valid simultaneously.set_oraclefollows the same pattern astransfer_adminfor consistency.VaultError::NotInitializedinstead of panicking.Summary by CodeRabbit
New Features
Tests