Description
Make resign_guardian clean up guardian membership the same way remove_guardian does.
Problem Statement
resign_guardian clears only the membership flag (src/contracts/vault_ops.rs:134-135):
let g_key = DataKey::Guardian(guardian.clone());
env.storage().instance().remove(&g_key);
It never removes the address from DataKey::AllGuardians, nor from the GuardianIndexAt/GuardianIndexOf/GuardianIndexCount slot index — all of which guardian::remove_guardian (src/guardian.rs:62-133) cleans up properly.
Verified against current main: after a guardian resigns, is_guardian is false, but get_snapshot_meta().guardian_count is still 1, get_snapshot().guardians still holds 1 entry, and add_guardian(&admin, &g) returns Err(DuplicateGuardian) — because add_guardian checks all_guardians.contains(guardian) at src/guardian.rs:25, which still holds.
So a guardian who resigns is permanently locked out of the protocol, and the leaked slot inflates guardian_count and get_guardians_page indefinitely.
Proposed Changes
Technical Implementation Scaffolding
- Target Repository: vero-core-contracts
- Target Path: src/contracts/vault_ops.rs, src/guardian.rs
- Branch Naming: fix/issue--resign-guardian-deregister
- Authority Context: Correctness — guardian membership; locks users out permanently
Acceptance Criteria
Definition of Done
Description
Make
resign_guardianclean up guardian membership the same wayremove_guardiandoes.Problem Statement
resign_guardianclears only the membership flag (src/contracts/vault_ops.rs:134-135):It never removes the address from
DataKey::AllGuardians, nor from theGuardianIndexAt/GuardianIndexOf/GuardianIndexCountslot index — all of whichguardian::remove_guardian(src/guardian.rs:62-133) cleans up properly.Verified against current
main: after a guardian resigns,is_guardianisfalse, butget_snapshot_meta().guardian_countis still1,get_snapshot().guardiansstill holds 1 entry, andadd_guardian(&admin, &g)returnsErr(DuplicateGuardian)— becauseadd_guardianchecksall_guardians.contains(guardian)atsrc/guardian.rs:25, which still holds.So a guardian who resigns is permanently locked out of the protocol, and the leaked slot inflates
guardian_countandget_guardians_pageindefinitely.Proposed Changes
resign_guardiancallguardian::remove_guardian(or a sharedderegister_guardianhelper) instead of the bareremove(&g_key), soAllGuardiansand the slot index are compacted identicallyNotGuardianearly returnTechnical Implementation Scaffolding
Acceptance Criteria
resign_guardian,get_snapshot_meta().guardian_countdecreases by 1 and the address no longer appears inget_guardians_page(0, 50)add_guardian(admin, g)succeeds aftergresigned, andis_guardian(g)is thentrueresign_guardianandremove_guardianleave byte-identical guardian-index state for the same addressDefinition of Done