Conversation
Closes #21. list_vc_ids gains offset/limit parameters and returns the slice [offset, min(offset + limit, count)). Empty result when offset >= count or limit == 0. Panics with the new LimitTooLarge = 16 error when limit exceeds MAX_LIST_LIMIT (200). The cap keeps the worst- case enumeration well under Soroban's 1.4M instruction budget while still allowing the full MAX_VCS_PER_VAULT (1000) to be retrieved in five paginated calls. vc_count is a new O(1) entrypoint that reads VaultVCCount directly, so SDKs can size their iteration without paying for any slot read. Returns 0 for unknown vaults — no error path needed for callers inspecting addresses they do not own. Breaking change: callers of list_vc_ids must pass offset and limit. All existing test callsites were updated. Adds eight regression tests: pagination consistency across windows, zero limit, offset beyond count, limit clamped to count, limit above MAX_LIST_LIMIT, vc_count tracking through issue/revoke/push, empty vault, and unknown vault. Tests: 77 passed (69 prior + 8 new), 0 failed.
Closes #22. Vaults that existed before #20 still hold their vc_ids in the legacy `VaultVCIds(owner)` Vec. After upgrade, the new O(1) helpers ignore that Vec, so list_vc_ids returns empty and reissuance starts the position counter from zero. migrate_vc_index moves each legacy vc_id into the new VaultVCCount/VaultVCIndex/VaultVCPosition layout, preserving stored order. The function takes no auth: the migration is fully deterministic from on-chain state and only relocates vc_ids, so any caller can drive it. Backend services can migrate vaults proactively, avoiding a "first-write-after-upgrade is expensive" cliff for users who would otherwise pay the legacy-Vec cost on their next issue() call. Semantics: - Panics with VCSAlreadyMigrated when vc_count > 0. Catches both double-calls and post-v0.2 vaults that never had legacy data. - Empty vault (no legacy entry, vc_count == 0) is a clean no-op. - Iterates the legacy Vec in stored order so list_vc_ids returns the same sequence post-migration. - Removes VaultVCIds(owner) afterward. - Extends vault TTL after the writes. Six new tests: - successful migration of three vc_ids into the new index - double-call panics - no-legacy vault is a no-op - post-upgrade vault with new entries panics (catches misuse) - no-auth: migration runs after env.set_auths(&[]) - legacy order is preserved in the new positions Tests: 83 passed (77 prior + 6 new), 0 failed.
Closes #24. Issuers like BAF (130+ credentials per program) need a batch path so they don't pay 130 separate transaction fees. batch_issue accepts up to MAX_BATCH_SIZE = 5 (vc_id, vc_data) tuples and reuses one auth signature, one fee transfer, and one vault TTL extension across the batch. Cap rationale: each VC writes 4 ledger entries (VaultVC, VaultVCIndex, VaultVCPosition, VCStatus). At 5 VCs that's 20 + 1 shared VaultVCCount = 21, leaving margin under Soroban's ~25 entry write limit for the optional fee transfer (~3 entries: token, source balance, destination balance). Semantics: - Single issuer.require_auth() for the whole batch. - Single fee transfer of fee_override × n when fees enabled and fee_override > 0. Uses saturating_mul; absurd inputs cap at i128::MAX and the token contract rejects on insufficient balance. - Per VC: existence check (catches both intra-batch and pre-existing duplicates), VaultVC + VaultVCIndex + VaultVCPosition writes via vault::store_vc, VCStatus = Valid, per-VC TTL extend, individual VCIssued event. - Single extend_vault_ttl after the loop. - Mirrors single issue() auto-authorization: an issuer not yet on the vault's authorized list and not denied is auto-added. - Returns the vc_ids in input order. New error codes: - BatchTooLarge = 17 (n > MAX_BATCH_SIZE) - BatchEmpty = 18 (n == 0) Ten new tests: - writes all VCs in input order - batch of 5 (at MAX_BATCH_SIZE) succeeds - batch of 6 panics with BatchTooLarge - empty batch panics with BatchEmpty - duplicate vc_id within the batch panics with VCAlreadyExists - duplicate against an existing VC panics with VCAlreadyExists - revoked vault panics with VaultRevoked - wrong vault contract panics with InvalidVaultContract - emits one VCIssued event per VC in the batch - auto-authorizes an unknown issuer (parity with single issue) Tests: 93 passed (83 prior + 10 new), 0 failed.
Closes #23. Bumps vc-vault-contract version from 0.1.0 to 0.2.0 and records the new testnet deployment. Contract ID: CBXC6LXBY5FGEG46VZ4AJ2AH2EJBINBA7BMILIEO4EJYI6ZTY7K7J5D5 WASM hash: c8da61dd3dd46b2810a743d50a388c09a00f0b7e8e2df7ceb5a71c8ce5dc4dd8 The v0.1.0 entry stays in docs/deployments/testnet.md as historical record. The new contract is a fresh deploy (not an upgrade) since v0.1.0 was never `initialize`d on chain and has no production state. Tests: 93 passed. WASM 44188 bytes (optimized).
Closes #25. Every public entrypoint that accepts a user-controlled string or unbounded list now validates length before doing any storage I/O. An attacker submitting megabyte-sized vc_data, did_uri, or a 10k-address issuer list previously could: - inflate per-vault storage rent indefinitely, - cost legitimate readers extra CPU on every list/lookup, and - amplify the cost of any future migration that re-reads the entry. The caps are conservative — 4-10x the largest realistic value — so they bite only on adversarial or buggy callers, never on real flows. Caps: vc_id 64 bytes (UUIDs are 36) vc_data 10,000 bytes (encrypted payloads typically 1-5KB) did_uri 256 bytes (longest realistic DIDs are ~60 chars) issuer_did 256 bytes (same shape as did_uri) date 64 bytes (ISO 8601 is 20-30 chars) issuers list 100 entries New error codes: InputTooLong = 19 (oversize string) IssuerListTooLong = 20 (oversize authorize_issuers list) Caps apply at every entrypoint that accepts the relevant input, including read paths (get_vc, verify_vc, get_vc_parent, push) so a caller can't force the contract to spend instructions hashing a 1MB key before the lookup misses. Eleven new tests: - create_vault accepts did_uri at MAX_DID_URI_LEN - create_vault rejects did_uri over the cap - issue accepts vc_id at MAX_VC_ID_LEN - issue rejects vc_id over the cap - issue rejects vc_data over MAX_VC_DATA_LEN - issue rejects issuer_did over MAX_ISSUER_DID_LEN - revoke rejects date over MAX_DATE_LEN - authorize_issuers accepts list at MAX_ISSUERS_LIST - authorize_issuers rejects list over the cap - batch_issue rejects an oversize vc_id within the batch - get_vc rejects oversize vc_id Tests: 104 passed (93 prior + 11 new), 0 failed.
CodeRabbit flagged that the cap added in the previous commit only covered the bulk replace path (authorize_issuers). Single-add and the auto-authorization triggered by issue / batch_issue / issue_linked through ensure_issuer_authorized still let the stored list grow past the cap, leaving the DoS surface partially open: an attacker could spam issue() from many fresh addresses and inflate VaultIssuers indefinitely. The fix moves the cap check into vault::authorize_issuer, the helper shared by both single-add and the auto-auth fallback. authorize_issuers (bulk replace) keeps its entrypoint-level check because it overwrites the stored list entirely — the new list's size is what matters there. Two regression tests: - direct authorize_issuer past 100 panics with IssuerListTooLong - issue() from a 101st auto-authorized address panics on the auto-auth step inside ensure_issuer_authorized Tests: 106 passed (104 prior + 2 new), 0 failed.
Closes #26. vc-vault previously emitted events for vault and VC lifecycle but every administrative mutation was silent: initialize, admin transfer (both nominate and accept), fee config, upgrade, sponsor management, and the two migration entrypoints all changed state without observable output. Indexers had to diff storage between blocks to track these, which is brittle and expensive. This change adds 15 #[contractevent] structs covering every previously silent path: Admin / governance ContractInitialized admin AdminNominated current_admin, nominee AdminTransferred old_admin, new_admin Upgrade ContractUpgraded new_wasm_hash Fees FeeEnabledChanged enabled FeeConfigSet token_contract, fee_dest, fee_amount FeeAdminSet amount FeeStandardSet amount FeeEarlySet amount FeeCustomSet issuer, amount Sponsors SponsorOpenToAllChanged open SponsorAdded sponsor SponsorRemoved sponsor Migrations VaultMigrated owner VaultIndexMigrated owner, migrated_count Design notes: - AdminNominated and AdminTransferred are two events for the two-step transfer so the indexer sees the full flow, not just the result. The accept path captures the outgoing admin before overwriting so the event carries both sides. - Fee presets emit separate events per kind (admin / standard / early / custom) instead of a combined FeePresetSet { kind, amount }. Indexers can filter by exact preset without parsing an enum. - ContractUpgraded carries only new_wasm_hash. Soroban v23 has no getter for the current WASM hash, so emitting old + new would require manually tracking the hash in instance storage — extra storage and initialize-flow complexity for data already in ledger history. - VaultIndexMigrated reports migrated_count so an indexer can size the move without diffing state. - Upgrade emits BEFORE update_current_contract_wasm so the event is registered against the WASM driving the upgrade, not the incoming one (which may have a different schema). 14 new tests cover every event emitter except upgrade. The upgrade entrypoint reverts when given an unresolved WASM hash, which discards the event from the same invocation; testing it requires uploading a second WASM and using its hash, out of scope here. The publisher follows the identical pattern as every other event in this file. Tests: 121 passed (106 prior + 15 new), 0 failed.
Replaces VaultIssuers and VaultDeniedIssuers monolithic Vec entries with a three-key index per side (count + slot→issuer + issuer→slot), making authorize, revoke, and existence checks O(1) regardless of list size. Adds migrate_issuer_index entrypoint to bridge legacy data forward, plus list_authorized_issuers, list_denied_issuers, authorized_issuer_count, and denied_issuer_count for paginated enumeration.
) On the auto-authorize path, is_authorized and denied_issuer_index_contains already confirm the issuer is absent from both indexes before we reach the append step. Calling vault::authorize_issuer from there triggered two extra persistent reads: a duplicate VaultIssuerPosition check and a no-op VaultDeniedIssuerPosition lookup inside remove_denied_issuer_from_index. Replace the vault::authorize_issuer call with storage::append_issuer_to_index directly, saving 2 persistent reads (~6-10k instructions) on every issue, batch_issue, and issue_linked invocation with a new issuer.
Adds InvalidFeeAmount (#22) and FeeOutOfBounds (#23) errors, a MAX_FEE_AMOUNT constant (10^18 stroops), and a require_fee_amount helper called at set_fee_config, set_fee_admin, set_fee_standard, set_fee_early, set_fee_custom, issue, and batch_issue. Negative amounts and values above the cap are rejected before any storage write or auth check.
…closes #35) Replace the arbitrary 1,000-VC cap with a u32 overflow guard in append_vc_to_index. Add migrate_vc_index_chunk for large legacy vaults that cannot be migrated in a single transaction due to Soroban's ledger-write limit.
Remove the four migration entrypoints (migrate, migrate_vc_index, migrate_vc_index_chunk, migrate_issuer_index), their storage keys, helper functions, events, error codes, tests, and seed helpers. All data has been migrated on testnet; the O(1) index is the sole active write path since v0.2.0.
Merge pull request #43 from ACTA-Team/chore/remove-legacy-migration-scaffolding
- Move all MAX_* and TTL constants to constants.rs; storage re-exports them via pub use so all existing callers are unchanged - Extract 13 private helpers (auth guards + input validators) to validator.rs; rename validate_* to require_* to match Soroban idiom - Rename api/mod.rs to interface.rs and update contract.rs import - Replace issuance::revoke_vc with vault::revoke_vc contract.rs shrinks from 841 to ~560 lines (entrypoints only)
… layer - api/mod.rs (single trait file in a solo folder) renamed to interface.rs - issuance/mod.rs (single-function module) absorbed into vault/credential.rs; revoke_vc and store_vc_with_fee now live alongside store_vc in the vault layer - Delete the api/ and issuance/ directories
storage/mod.rs (844 lines) split into focused files: - config.rs — admin and fee helpers (instance storage) - vault.rs — vault metadata helpers - issuer.rs — authorized and denied issuer index (O(1) swap-and-pop) - credential.rs — VC payloads, O(1) VC index, parent links, VC status - ttl.rs — TTL extension helpers - sponsor.rs — sponsored vault config mod.rs becomes a ~60-line hub: DataKey enum + pub use re-exports. All callers continue to use storage::* unchanged.
If the tail slot is absent during a swap-and-pop, the previous if-let-Some silently skipped the swap but still decremented count and removed the position mapping, leaving a stale forward index entry. Replace with .unwrap() so the operation panics instead of corrupting the index. Affects remove_vc_from_index, remove_issuer_from_index, and remove_denied_issuer_from_index.
Eliminates the initialize/frontrunning window: contract admin is now set atomically at deploy time via the Soroban constructor. Removes the AlreadyInitialized guard (constructors run exactly once) and the require_auth call (deployer controls constructor args). Tests updated: register() now passes (admin,) as constructor args; standalone client.initialize() calls removed from all tests; obsolete initialize-specific test cases replaced with constructor equivalents.
The AlreadyInitialized error variant no longer describes contract initialization — that path was removed when initialize() was replaced by the constructor. Both remaining usages guard against duplicate vault creation in create_vault and create_sponsored_vault. Discriminant stays 1 for on-chain compatibility.
Eliminates a single-file solo directory; consistent with the api/mod.rs → interface.rs rename already applied. No functional change.
Moves VC transfer logic (tombstone management, parent link migration, index updates, TTL extensions) to vault/credential.rs alongside store_vc, store_vc_with_fee, and revoke_vc. contract.rs push() is now a thin dispatcher: 5 guard calls + vault::push_vc().
model/ implies MVC/ORM semantics that don't apply here. types/ is the idiomatic name in the Soroban ecosystem for contracttype-annotated domain types (VCStatus, VerifiableCredential). Updates all import sites.
- Replace initialize() row with __constructor deployment note - Rename error #1 AlreadyInitialized → VaultAlreadyExists - Add missing batch_issue, vc_count, list_authorized_issuers, list_denied_issuers, authorized_issuer_count, denied_issuer_count - Fix list_vc_ids signature to include offset + limit params - Add ContractInitialized to events table - Fix deploy script example: vc-vault-contract → vc-vault - Remove Initialize from contract_admin capabilities in auth table
- Remove initialize from vc-vault admin functions (now __constructor) - Add batch_issue, vc_count, authorize_issuers, list_authorized_issuers, list_denied_issuers to vc-vault function summary - Update vc-vault test count: 63 → 127
VCStatus(from_owner, vc_id) is intentionally left as Valid after push to block re-issuance of the same vc_id in the source vault. Without refreshing its TTL at push time, the entry expires ~180 days after issue — allowing re-issuance once the tombstone is gone.
Bump version to 0.3.0. Deploy to testnet: Contract ID: CATL4IDH7XXPDC2UHSEX2GP45PPBVDFSKUDTKCSQICDOJVDLYNKISXFH WASM hash: 775a141520de56fb4b1ebeb55d63e49fadf03f467ea8444cddb2caed2756ca8c Also fix deploy.sh to pass --contract_admin constructor arg for vc-vault.
Merge pull request #49 from ACTA-Team/chore/release-vc-vault-v0.3.0
Merge pull request #50 from ACTA-Team/chore/release-vc-vault-v0.3.0
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.