feat: add fallback oracle chain for FX (#547) - #1
Open
thelmaoffiong wants to merge 17 commits into
Open
Conversation
Add a shared storage-layout registry that emits docs/STORAGE_LAYOUT.json at build time and fails CI when enum keys drift from the checked-in schema. Co-authored-by: Cursor <cursoragent@cursor.com>
Restore the tracked .vscode/settings.json file so the storage-layout branch only carries the intended RevoraOrg#617 changes. Co-authored-by: Cursor <cursoragent@cursor.com>
Implements issue RevoraOrg#547: when the primary FX oracle is stale, the contract now falls through an ordered chain of oracles before failing the transaction. Changes: - Add OracleEntry and OracleChain contracttype structs - Add DataKey2::OracleChain(OfferingId) persistent storage key - Add MAX_ORACLE_CHAIN_LEN = 10 safety cap - Add RevoraError::OracleQuoteStale (62) and AllOraclesStale (63) - Add EVENT_ORACLE_SOURCE_USED (orc_used) event constant - Add set_oracle_chain() entrypoint (issuer-auth guarded) - Add get_oracle_chain() read-only entrypoint - Add try_oracle_entry() private helper for single-entry staleness check - Wire oracle chain into convert_report_amount_if_needed(): chain takes priority over legacy FxOracleConfig; chain iterates entries in order, short-circuits on first non-stale quote, emits oracle_source_used event with (oracle, from, to, chain_index) - 11 new tests in oracle_chain_tests module covering: round-trip storage, no-chain fallback, first-fresh happy path, stale-fallback, all-stale error, only-last-fresh, empty chain, priority over legacy oracle, chain length limit, auth guard, zero max_age disables staleness check Security: only the current offering issuer may set the oracle chain; chain evaluation is deterministic and short-circuits at first fresh quote; all oracles stale returns AllOraclesStale without any state mutation. Closes RevoraOrg#547
* feat: paginate get_holder_statement Add get_holder_statement_page with a stable PeriodEntry cursor so long holder ledgers can be read under gas limits without changing claim semantics. Co-authored-by: Cursor <cursoragent@cursor.com> * chore: restore tracked vscode settings Restore the tracked .vscode/settings.json file so the holder-statement pagination branch only carries the intended RevoraOrg#599 changes. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Realbaji16 <bajiemeks16@gmail.com> Co-authored-by: Cursor <cursoragent@cursor.com>
Add a registration API for per-key migration hooks that allow custom transform functions (Identity, Rename, Custom) to be attached to specific legacy storage keys. When the storage walker runs during a layout upgrade, it applies each registered hook deterministically and emits audit events. === Changes === src/lib.rs: - Add MigrationTransform enum (Identity, Rename, Custom) - Add MigrationHook struct binding legacy key to transform - Extend MigrationDataKey with hook count, index, and per-key storage - Add EVENT_MIG_HOOK_APPLIED event symbol - Implement register_migration_hook() (admin-gated) - Implement clear_migration_hook() (admin-gated, idempotent) - Implement get_registered_hooks() (read-only enumeration) - Add apply_migration_hook() internal helper (dry-run aware) - Update migrate_storage_walker() to iterate and apply hooks src/test_storage_layout_version.rs: - 16 new tests covering registration, clear, auth, dry-run, walker integration, replay protection, identity no-op, hook enumeration Security: - All hooks are deterministic and pure (enum variants, not closures) - Admin auth required for register/clear (gates on stored DataKey::Admin) - Dry-run mode emits plan events without mutating storage - Replay protection preserved (cannot re-apply same migration) Co-authored-by: thlpkee20-wq <thlpkee20@gmail.com>
…ml (RevoraOrg#640) Co-authored-by: thlpkee20-wq <thlpkee20@gmail.com>
Co-authored-by: Developer <developer@teachlink.com> Co-authored-by: thlpkee20-wq <thlpkee20@gmail.com>
* feat: add tax-bucket rollover past cost basis * feat: gate register_offering on multi-issuer quorum --------- Co-authored-by: Developer <developer@teachlink.com> Co-authored-by: thlpkee20-wq <thlpkee20@gmail.com>
Co-authored-by: thlpkee20-wq <thlpkee20@gmail.com>
…rg#644) * feat: block cross-class transfers by default (RevoraOrg#522) Add class-restricted transfer rules preventing Class A to Class B cross-flow to satisfy legal covenants requiring strict separation between share classes. Changes: - Add ClassTransferBlocked error variant (wire value 58) - Add EVENT_CLASS_XFER_BLOCK event (cls_block) on rejection - Add get_primary_class helper to determine holder's share class - Add missing DataKey2 variants (OfferingClasses, HolderShareClass, TransferRestrictions, HolderCategory, CategoryHolderCount) - Add class transfer guard in both transfer_with_attestation paths - Add zero-value guard in first transfer_with_attestation - Add 20 comprehensive tests covering same-class, cross-class, backward compat, event emission, bypasses, custom classes, and multi-class holder edge cases Closes RevoraOrg#522 * docs: add comprehensive PR description for class-restricted transfer rules (RevoraOrg#522) --------- Co-authored-by: thlpkee20-wq <thlpkee20@gmail.com>
…flag (RevoraOrg#647) Add emit_v2_compat admin flag that controls whether V2 indexed events are emitted alongside V3. Defaults to true for backward compatibility. Includes DataKey2::EmitV2Compat storage, is_emit_v2_compat() helper, emit_v2_and_v3() function, admin setter, tests, and deprecation docs. Closes RevoraOrg#566 Co-authored-by: OpadijoIdris <opadijoidris@branda.com.ng> Co-authored-by: thlpkee20-wq <thlpkee20@gmail.com>
* feat: add estimate_transfer eligibility query * feat: enforce per-class supply cap independent of aggregate --------- Co-authored-by: thlpkee20-wq <thlpkee20@gmail.com>
…aOrg#649) Persist class_priority_index under (offering_id, class_id) and emit a canonical class_pay_order event from close_period, ascending by (priority_index, share_class.to_xdr() bytes) so ties tie-break deterministically across reruns and across single-sig / dual-sig close. * New DataKey2 variants `ClassPriority` and `ClassPayOrder`. * New event symbols `EVENT_CLASS_PAY_ORDER` ("clspayo") and `EVENT_CLASS_PRIORITY_SET` ("clprio"); constant `DEFAULT_CLASS_PRIORITY = 0`. * Public API: `set_class_priority`, `get_class_priority`, `get_class_pay_order`. `set_class_priority` requires issuer-quorum authorization matching `set_holder_share`'s governance model. * `close_period` and `close_period_dual_sig` now record and emit the resolved `Vec<ShareClass>` after sealing, idempotent because the existing period-already-closed guard rejects repeats. * `resolve_class_pay_order` performs an in-place bubble sort on `soroban_sdk::Vec` keyed by `(priority, xdr_bytes)`; n is bounded by the per-offering class count. Tests: 11 new tests in `src/test_close_period.rs` cover happy path, default-zero fallback, wrong-issuer, unknown-offering, unregistered class, ascending-priority ordering, XDR tie-break correctness across reruns, empty order for classless offerings, dual-sig consistency, and the legacy-period empty-order migration fallback. Docs: `docs/class-priority.md` documents storage, public API, resolution semantics (with a worked example), security notes, event payload reference, and migration / backwards-compatibility story. Co-authored-by: thlpkee20-wq <thlpkee20@gmail.com>
* feat: log admin rotation history * quick fix [ci skip] * quick fix [ci skip] * feat: two-phase admin rotation with delay * quick fix [ci skip] --------- Co-authored-by: thlpkee20-wq <thlpkee20@gmail.com>
Adds resolve_dispute(dispute_id, outcome, evidence_hash) entrypoint that allows the global admin to resolve open IssuerDispute freezes. - Add DisputeOutcome enum (Upheld, Rejected, PartiallyUpheld) - Add DisputeEntry struct for on-chain dispute tracking - Add resolve_dispute() with admin-only auth, atomic status update, freeze removal on Rejected outcome, and disp_res event emission - Add get_dispute() read-only query - Add set_dispute_window / get_dispute_window for window configuration - Add dispute window enforcement in emergency_freeze_holder - Add auto-creation of DisputeEntry on IssuerDispute freeze - Add 17 comprehensive tests covering auth, outcomes, events, edge cases Closes RevoraOrg#593 Co-authored-by: thlpkee20-wq <thlpkee20@gmail.com>
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.
Summary
Implements RevoraOrg#547: when the primary FX oracle returns a stale quote, the contract now walks an ordered oracle fallback chain and uses the first non-stale entry. If every oracle in the chain is stale the call returns
AllOraclesStalewithout any state mutation.Changes
New types
OracleEntry— a single chain entry: oracle address + currency pair symbols +max_age_secsOracleChain— persisted ordered list ofOracleEntryvalues (DataKey2::OracleChain(OfferingId))New error codes (wire-stable)
OracleQuoteStale = 62— single entry's quote is too oldAllOraclesStale = 63— every entry in the chain is staleNew event
orc_used(EVENT_ORACLE_SOURCE_USED) — emitted when a chain entry wins, carrying(oracle, from, to, chain_index)New entrypoints
set_oracle_chain(issuer, namespace, token, entries)— issuer-auth guarded; rejects ifentries.len() > 10get_oracle_chain(issuer, namespace, token)— read-onlyImplementation
try_oracle_entry()private helper checks staleness; returnsOracleQuoteStaleif age exceedsmax_age_secs(0 = disabled)convert_report_amount_if_needed()wired: chain takes priority over legacyFxOracleConfig; short-circuits on first fresh quote; emitsorc_usedevent; falls through to legacy path when no chain storedTests (11 in
oracle_chain_testsmodule)set_and_get_oracle_chain_round_tripsno_chain_no_single_oracle_returns_payout_asset_mismatchchain_first_oracle_fresh_uses_first_entrychain_first_stale_second_fresh_falls_throughall_oracles_stale_returns_all_oracles_stale_errorchain_only_last_entry_freshempty_chain_falls_back_to_single_oraclechain_takes_priority_over_legacy_single_oracleset_oracle_chain_too_many_entries_returns_limit_reachedset_oracle_chain_requires_issuer_authchain_entry_zero_max_age_never_staleSecurity notes
AllOraclesStaleis returned without writing any stateBuild notes
cargo fmt --all -- --checkpassesmaster; our changes introduce zero new root-cause errorsCloses RevoraOrg#547