feat(escrow): harden upgradeability, version visibility, and onboarding interop#246
Merged
Agbeleshe merged 1 commit intoApr 29, 2026
Conversation
…ng interop Addresses four hardening issues filed against the escrow contract. Hub-of-Evolution#230 — WasmUpgradeProposal validation - Reject zero-hash proposals up front with InvalidUpgradeHash so admins cannot accidentally propose the default BytesN<32> sentinel. - Refuse to overwrite a pending proposal silently; admin must cancel first, preventing a compromised admin from resetting the cooldown clock without a visible audit trail. - execute_upgrade now takes an expected_wasm_hash and re-checks it against the stored proposal as defense-in-depth against signing-tool spoofing. - Capture proposed_by / proposed_at on every proposal and emit UpgradeProposalEvent on propose / cancel / execute. Hub-of-Evolution#241 — ContractVersion migration visibility - New UpgradeRecord + bounded UpgradeHistory log (FIFO cap of 32) so every successful upgrade is queryable on-chain alongside its wasm_hash, executing admin, and timestamp. - Add get_upgrade_history and get_version_info query methods. - Document ContractVersion semantics on get_version: starts at 1, increments by exactly 1 per execute_upgrade, decoupled from the wasm_hash which is captured per-record in history. Hub-of-Evolution#243 — Onboarding cross-contract call hardening - Cross-contract calls now route through safe_update_reputation / safe_update_user_metrics, which use try_invoke_contract so a malicious or version-skewed onboarding contract cannot trap the escrow flow. - Reject set_onboarding_contract pointing at the escrow itself to remove a re-entrancy hazard if the OnboardingInterface ever expands. - Emit config_updated events on set / clear; expose get_onboarding_contract / has_onboarding_contract / clear_onboarding_contract. - Emit OB_FAIL warning event with the failing method name when a safe cross-contract call falls through; reputation events (from Hub-of-Evolution#211) keep off-chain reconstructions intact. Hub-of-Evolution#239 — FeeTokenIndex flexibility - Introduce FeeTokenInfo (active / custom_fee_bps / accumulated) keyed per token via DataKey::FeeTokenConfig(Address). Mirrors the running fee total alongside the legacy FeeTokenIndex Vec, giving future multi-token fee logic a per-token slot without a contract upgrade. - get_fee_token_config / get_fee_tokens / set_fee_token_config / migrate_fee_token_configs admin functions added for tuning and one-shot backfill of pre-existing tokens. accumulated remains contract-owned (admin can flip active or override bps but cannot rewrite historical fee accounting). Pre-existing lib build errors (Escrow batch_id/funded missing fields, stake_data scope) are out of scope for these issues and unchanged. closes Hub-of-Evolution#230 closes Hub-of-Evolution#241 closes Hub-of-Evolution#243 closes Hub-of-Evolution#239
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
Hardens four cross-cutting upgradeability and interoperability concerns in
craft-nexus-contract/src/lib.rs:WasmUpgradeProposalis now validated end-to-end: zero-hash rejection, explicit cancel-before-replace,execute_upgrade(expected_wasm_hash)re-check, proposer/timestamp metadata, and lifecycle events (UPG_PROP/UPG_CANC/UPG_EXEC).UpgradeHistorylog (FIFO-capped at 32) plusget_upgrade_history/get_version_infoqueries, and documents theContractVersionsemantics onget_version.safe_update_reputation/safe_update_user_metrics, which wraptry_invoke_contractso a malicious or version-skewed onboarding contract cannot trap escrow flows.set_onboarding_contractrejects self-pointers;get_onboarding_contract/has_onboarding_contract/clear_onboarding_contractare public; failed cross-contract calls emit anOB_FAILwarning event.FeeTokenInfo(active / custom_fee_bps / accumulated) atDataKey::FeeTokenConfig(Address), paired withget_fee_token_config/get_fee_tokens/set_fee_token_config/migrate_fee_token_configs.accumulatedis contract-owned — admin can flipactiveor override bps but cannot rewrite historical fee accounting. The legacyFeeTokenIndexVec remains the canonical enumeration source for backward compatibility.Behavior changes worth flagging on review
execute_upgrade()signature gained anexpected_wasm_hash: BytesN<32>argument. SDK callers and any deploy scripts must be updated. This is intentional defense-in-depth against signing-tool spoofing where the operator sees a different payload than what is stored on-chain.propose_upgrade_wasmno longer silently overwrites a pending proposal — callers receiveUpgradeProposalExistsand must explicitlycancel_upgrade_wasmfirst. This prevents a compromised admin from resetting the cooldown clock without an audit trail.cancel_upgrade_wasmnow returnsNoUpgradeProposedwhen there is nothing to cancel, instead of silently succeeding.InvalidUpgradeHash,UpgradeProposalExists,OnboardingContractNotSet.custom_fee_bpsonFeeTokenInfois storage-only in this PR —calculate_feedoes not yet consult it. A follow-up issue can wire it into the fee calculation once the storage shape stabilizes in production.Out of scope
The pre-existing lib build errors on
main(Escrowinitializers missingbatch_id/fundedfields,stake_datascope error) are unrelated to these four issues and intentionally untouched here.Test plan
cargo check --libshows the same 3 pre-existing errors and no new ones (verified locally).setup_testfor the newexecute_upgradesignature and to define the missingonboarding_contracttest binding.closes #230
closes #241
closes #243
closes #239