v0.3.0 — audit remediation, real ECDSA/BLS, Mirror ownership handoff#77
v0.3.0 — audit remediation, real ECDSA/BLS, Mirror ownership handoff#77JakeHartnell wants to merge 25 commits into
Conversation
Internal heartbeat-driven security audit (5C/6H/5M/5L/4I). Headlines: ECDSA/BLS service-managers are signature-verification stubs (C-1, C-2), unauthenticated config setters (C-3, C-4), mirror sync handlers can't authorize downstream calls under CLI deploy (C-5).
Companion to the internal audit. Commits the per-finding remediation plan and the architectural decisions (real ECDSA + BLS, mirror kept, mock deleted, both deploy-flow ownership + TransferOwnership). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ECDSA/BLS digest + DST pinned from POA reference. Owner-only registration, owner-pause, separate OWNER/ADMIN roles, no migrate entrypoint, service-handler matches mirror's base. Open questions trimmed to three tactical items. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per the 2026-05-09 fix plan, the mock family was a footgun: unauthenticated
SetSigningKey, unauthenticated WavsSetServiceUri/WavsSetQuorumThreshold, and a
publishable api crate that exposed those types under a name a third party
could mistake for production. With ECDSA and BLS standalone families being
implemented in Phases 2 and 3, mock has no remaining purpose.
Changes:
- Delete packages/contracts/mock/{api,service-handler,service-manager}.
- Drop mock from workspace members and dependency aliases in Cargo.toml.
- Remove cw-wavs-mock-api deps from cli, sdk, utils, off-chain-tests,
on-chain-tests, simple-aggregator, trigger/simple (most were stale and
unused).
- Inline MessageWithId (HexBinary) into echo-with-id as a private module so
echo-with-id no longer pulls from a deleted crate.
- Strip InstantiateMock variants and Mock contract-kind enum members from the
CLI command surface; matching match arms removed from main.rs.
- Delete sdk/contract_kinds/mock.rs and remove from mod.rs.
- Delete on-chain/off-chain mock-specific test scaffolding (contract clients,
test files). Rename mock_sanity test fns to bls_sanity / ecdsa_sanity.
- Port concurrency tests from MockTestClient to EcdsaTestClient.
- Remove Mock variant from TestService enum + new_mock constructor.
- e2e: stub out the per-flavor response polling until Phase 2/3 wires up
real persistence on ECDSA/BLS handlers.
- README: drop the legacy "not for production" disclaimer in favor of an
honest 2026-05-09 fix-plan-anchored status note. Update contract list.
- taskfile/config.yml: drop "mock" from ALL_CONTRACT_KINDS.
cargo check clean for every contract crate, sdk, utils, cli, on-chain-tests,
off-chain-tests. shared-tests has a pre-existing recursion-limit error
unrelated to this change (see verification stash on the branch); to be
addressed separately.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…-6, H-1) Replaces the stub Empty/untagged-only api with a full surface that mirrors POA's StakeRegistry semantics, plus operational role split matching the 2026-05-09 fix plan. Service-manager api: - InstantiateMsg now requires owner + admin (separate roles) and takes optional initial quorum threshold (defaults 2/3). - ExecuteMsg adds owner-only operator-set messages (RegisterOperator, DeregisterOperator, UpdateOperatorWeight, UpdateOperatorSigningKey), owner-only Pause/Unpause, two-step TransferOwnership/AcceptOwnership, and admin-only SetAdmin/AcceptAdmin. WavsSetServiceUri and WavsSetQuorumThreshold remain part of the wavs-types untagged set per upstream protocol convention; auth gating is enforced in the service-manager impl (next commit). - QueryMsg adds Owner/PendingOwner/Admin/PendingAdmin/Paused, plus TotalWeight/OperatorWeight/OperatorSigningKey with optional reference_block (snapshot reads). OperatorRegistered for current membership. Service-handler api: - Adds EcdsaServiceHandlerQueryMessages with TriggerValidated / TriggerMessage / SignatureData parallel to mirror's surface so the handler can persist envelopes and let downstream callers retrieve them by trigger_id. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… C-3, H-2, H-3, H-6, M-4) Replaces the stub with a full operator-set + signature-validation implementation, mirroring poa-middleware/contracts/src/ecdsa/POAStakeRegistry.sol in semantics and porting mirror::stake_registry::is_valid_signature for the secp256k1 verification logic. State (cw-storage-plus snapshots throughout): - OWNER + PENDING_OWNER (operator-set role). - ADMIN + PENDING_ADMIN (quorum + URI role; separated from OWNER). - PAUSED flag (owner-controlled freeze). - QUORUM_NUMERATOR / QUORUM_DENOMINATOR as SnapshotItem so historical envelopes can be evaluated against the threshold in force at their reference_block (audit M-4 preventatively applied to the new family). - TOTAL_WEIGHT as SnapshotItem. - OPERATOR_WEIGHTS / OPERATOR_TO_SIGNING_KEY / SIGNING_KEY_TO_OPERATOR as SnapshotMap keyed on String. - OPERATOR_REGISTERED current-membership flag. Owner-only writes (audit C-3): - RegisterOperator: rejects duplicate operator, zero signing key, signing-key collisions; bumps TOTAL_WEIGHT via checked_add. - DeregisterOperator: removes maps + decrements TOTAL_WEIGHT via checked_sub. - UpdateOperatorWeight: recomputes TOTAL_WEIGHT via checked_sub + checked_add (audit H-3 preventative). - UpdateOperatorSigningKey: enforces uniqueness, snapshots both old removal and new add at current height. - Pause / Unpause toggle the freeze. Two-step role transfer (audit H-6): - TransferOwnership / AcceptOwnership. - SetAdmin / AcceptAdmin. Admin-only WAVS-standard writes (audit C-3): - WavsSetServiceUri. - WavsSetQuorumThreshold (validates n > 0, d > 0, n <= d). WavsValidate query: - Reads total_weight + quorum threshold via may_load_at_height at signature_data.reference_block. NO fall-back to latest (audit H-2 preventatively applied to new code). - Resolves signing_key -> operator via SIGNING_KEY_TO_OPERATOR may_load_at_height only; missing => signer rejected. - secp256k1 recovery against EIP-191 personal-sign of keccak256(envelope) -- ported byte-for-byte from mirror. - Enforces sorted+unique signers, non-zero signers, non-zero operator weight at reference_block. - Quorum check via Uint512 (signed * denominator >= total * numerator). - Returns paused error when PAUSED is set. Cargo deps add alloy-primitives (eip191_hash_message, keccak256) and sha3 (Keccak256 for pubkey -> address derivation). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…id (audit H-1, H-4) Replaces the no-op handler with envelope persistence parallel to mirror's base service-handler, plus the duplicate-trigger_id guard (audit H-4 preventatively applied to the new ECDSA family). Flow on WavsHandleSignedEnvelope: 1. Delegate validation to the ECDSA service-manager via WavsValidate query. Any failure short-circuits before persistence. 2. Decode the envelope, extract MessageWithId from the payload. 3. Reject if TRIGGER_MESSAGE already has an entry for the trigger_id. 4. Persist (trigger_id -> message bytes) and (trigger_id -> signature data). api/message_with_id.rs introduces an ECDSA-family MessageWithId using HexBinary for the message field (binary-safe; mirrors echo-with-id's local definition rather than mirror's String version). api/service_handler.rs (committed earlier) defines the matching EcdsaServiceHandlerQueryMessages so downstream callers can query TriggerValidated / TriggerMessage / SignatureData. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- CLI ServiceManagerCommand::InstantiateEcdsa now takes --owner, --admin, and optional --quorum-numerator / --quorum-denominator. - on-chain + off-chain test clients pass owner = admin = signer (same semantic shape they had before, just satisfying the new struct). - shared-tests: bump recursion_limit to 512 to clear the pre-existing query-depth overflow in run_sanity_tests (rustc compatibility, not introduced by this change). Workspace cargo check now clean. BLS CLI surface left as Empty for now; Phase 3 will mirror this CLI change once BLS gets its real InstantiateMsg. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ty (audit C-1, C-2, C-3, H-1, H-2, H-3, H-4, H-6, M-4) Replaces the BLS stub with a full operator-set + aggregate-signature verification implementation. Same shape as the ECDSA family from Phase 2; the BLS-specific differences are documented inline. Cryptography: - 48-byte compressed G1 pubkeys (cosmwasm-crypto convention). - 96-byte compressed G2 aggregate signatures. - DST locked to b"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_" -- byte-for-byte match with poa-middleware's HashToCurve.sol so off-chain signers can be re-used across both stacks. - Verification via cosmwasm-std's BLS12-381 host calls: bls12_381_aggregate_g1, bls12_381_hash_to_g2, and bls12_381_pairing_equality with G1::generator. - Signers in WavsSignatureData carry 20-byte BLS-key ids (= keccak256(g1_pubkey)[..20]), since the upstream wavs-types schema constrains signers to Vec<EvmAddr>. The full G1 pubkey is stored on-chain at registration time and looked up by id at validation. signature_data.signatures must have len == 1 carrying the aggregate G2 signature. State + execute messages parallel to ECDSA: - OWNER + PENDING_OWNER (operator-set role). - ADMIN + PENDING_ADMIN (quorum + URI role; separated). - PAUSED. - QUORUM_NUMERATOR / QUORUM_DENOMINATOR / TOTAL_WEIGHT as SnapshotItem. - OPERATOR_WEIGHTS + OPERATOR_TO_BLS_PUBKEY + OPERATOR_TO_BLS_KEY_ID + BLS_KEY_ID_TO_OPERATOR as SnapshotMap. - Owner-only RegisterOperator/Deregister/UpdateWeight/ UpdateOperatorBlsPubkey, all under PAUSED guard. - Admin-only WavsSetServiceUri / WavsSetQuorumThreshold. - Two-step TransferOwnership/AcceptOwnership and SetAdmin/ AcceptAdmin. WavsValidate enforces: - non-empty signers, len(sigs) == 1, sig length = 96. - sorted-ascending unique key ids (matches POA's pattern). - snapshot reads at reference_block (no fall-back to latest -- audit H-2 preventatively for the new family). - non-zero operator weights at reference_block. - pairing equality. - Uint512 quorum check. - paused short-circuits. Service-handler persists envelope keyed by trigger_id (parallel to ECDSA + mirror); rejects duplicate trigger_id (audit H-4 preventatively for the new family). Adds a BLS-family MessageWithId in the api crate using HexBinary for binary-safe payloads. CLI ServiceManagerCommand::InstantiateBls now takes --owner, --admin, and optional --quorum-numerator / --quorum-denominator matching the ECDSA shape. off-chain test client/trigger.rs cleanup: was using cw_wavs_bls_api::service_manager::InstantiateMsg as a stand-in for the trigger's empty InstantiateMsg (silent copy-paste); replaced with the correct cw_wavs_trigger_api::simple::InstantiateMsg. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ht arithmetic (H-3) H-2: WavsValidate previously fell back to the latest mapping when SIGNING_KEY_TO_OPERATOR.may_load_at_height(reference_block) returned None, and applied the same fallback to OPERATOR_WEIGHTS. That broke reference_block semantics — an operator registered (or with weight updated) AFTER reference_block could contribute weight to a historical signature. The fix removes both fallbacks: missing snapshot at reference_block now rejects the signer with an explicit error. H-3: set_operator_details_at computed new_total_weight = total_weight - current_weight + weight on Uint256, which panics on underflow (e.g. storage corruption, double-decrement, reorder bug). Replaced with checked_sub + checked_add returning ContractError::Std on failure, so the contract no longer becomes wedged on bad arithmetic. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…(audit H-4) The base service-handler used Map::save which silently overwrites. Two different signed envelopes sharing the same trigger_id (operator quorum signs both -- accidentally or maliciously) would silently clobber each other; downstream consumers reading TRIGGER_MESSAGE would see only the second. The mirror operator-sync and quorum-sync handlers each enforce monotonic LAST_TRIGGER_ID separately. The bare service-handler did not, so any deployment using the base handler directly was exposed. Now reject duplicates with an explicit error. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…_GAP (audit H-5) The strict-greater check `last_trigger_id < triggerId` allowed a malicious or buggy operator quorum to submit triggerId == u64::MAX, permanently bricking all future updates (every legitimate triggerId < MAX gets rejected forever). Cap the gap at 2^48 (~280 trillion -- ample headroom for real workloads while rejecting sentinel values like u64::MAX). Applied identically to mirror-operator-sync-handler and mirror-quorum-sync-handler. Also bound the initial value when no LAST_TRIGGER_ID is set yet, so a fresh contract can't be primed with a sentinel either. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…dit M-4, L-3) M-4: QUORUM_NUMERATOR / QUORUM_DENOMINATOR were Item<Uint256>, read at validation time and not snapshotted. In-flight envelopes signed under threshold A would be re-validated against threshold B if the admin flipped it before validation. Convert both to SnapshotItem with EveryBlock strategy; instantiate writes the default 2/3 at env.block.height; WavsSetQuorumThreshold writes at the current height; validate_quorum reads via may_load_at_height(reference_block) matching how operator weights are pulled. WavsQuorumThreshold query keeps the current-value semantics via load(). L-3: ethereum_address_raw used .try_into().unwrap() to extract the last 20 bytes of the keccak digest. The unwrap is currently unreachable but a defensive ? guards future refactors. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… handoff (audit C-5, H-6)
Adds the messages the post-deploy script needs to hand control of
the mirror stake-registry and service-manager to the corresponding
sync-handler contracts, resolving audit C-5 -- the previous flow
left the CLI signer as both OWNER and ADMIN, so mirror-operator-
sync-handler::BatchSetOperatorDetails and mirror-quorum-sync-
handler::WavsSetQuorumThreshold both reverted with Unauthorized
under the documented deploy.
stake_registry api/entry:
- ExecuteMsg::TransferOwnership { new_owner } -- single-step
transfer because the typical target is a contract address that
can't sign an AcceptOwnership message on its own. Owner-gated.
service_manager api/entry:
- ExecuteMsg::SetAdmin { new_admin } -- same shape, admin-gated.
CLI gains:
- ServiceManagerCommand::SetMirrorAdmin --address --new-admin.
- RegistryCommand::TransferOwnership --address --new-owner.
Recommended deploy flow now:
1. Deploy stake-registry (CLI = OWNER) and chained service-manager
(CLI = ADMIN).
2. Deploy mirror-operator-sync-handler and mirror-quorum-sync-
handler with the existing service-manager address.
3. CLI: registry transfer-ownership --address <stake_registry>
--new-owner <operator-sync-handler>.
4. CLI: service-manager set-mirror-admin --address <service_manager>
--new-admin <quorum-sync-handler>.
After step 4, the documented sync-handler call paths succeed.
TransferOwnership/SetAdmin remain available for emergency rotation.
The "canonical" handler-deploys-downstream path the fix plan
sketched would resolve a circular dependency between the two
sync-handlers and the service-manager via Instantiate2 address
prediction; left as a future architectural cycle. Post-deploy
transfer is functionally equivalent and far simpler.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The simple trigger contract was a public message bus -- anyone could
Push and downstream relayers/indexers were silently inheriting that
trust. The legacy default behavior is preserved (allowed_pushers =
None means public bus), but instantiating with
allowed_pushers: Some([..]) now restricts Push to known senders.
api/simple.rs: replace `pub type InstantiateMsg = Empty` with a
struct carrying `allowed_pushers: Option<Vec<String>>` (default
None). Existing call sites that used `Empty {}` migrate to
`InstantiateMsg::default()`.
simple/state.rs: ALLOWED_PUSHERS Item<Option<Vec<Addr>>>.
simple/entry.rs: instantiate validates each address in the optional
list and stores it. execute(Push) loads the allowlist and rejects
senders not on it; legacy behavior when None preserved.
The two test scaffolding sites now use InstantiateMsg::default().
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Companion to the audit and fix plan. Maps every Critical, High, Medium, and Low finding to the commit SHA that addresses it; calls out the deferred test work and the explicit acknowledged-and- deferred items (M-5 untagged enum kept per upstream protocol convention; L-4 migrate entrypoint deferred per Decision #7). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mechanical reformatting across files touched by the audit-fix work. Resolves the cargo fmt --check failure on PR #75 lint job. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…s for H-2 Two unrelated issues blocking the test job on PR #75: 1. Each `tests/*.rs` file is its own crate root for cargo, so the recursion_limit attribute on shared-tests/lib.rs doesn't apply. Default rustc recursion limit (128) is exceeded by the async test bodies' future depth (130). Add `#![recursion_limit = "512"]` to contracts_ecdsa.rs / contracts_bls.rs / contracts_mirror.rs. 2. The mirror stake-registry tests in shared-tests use `reference_block: 12345u32` (cw-multi-test's default starting block). With the H-2 fix removing the snapshot fall-back to latest, validation now correctly rejects signers whose registration snapshot doesn't exist at reference_block -- registration writes to block 12346+, not 12345. Bump the constant to 999_999u32 (well past any cw-multi-test block height) so SnapshotMap::may_load_at_height returns the actual registered values. After this all 11 off-chain tests pass: bls_sanity, ecdsa_sanity, mirror_sanity, mirror_stake_registry_sanity, mirror_multi_signer_validation, mirror_abi_binary_compatibility, mirror_service_manager_admin_only, mirror_negative_scenarios, mirror_ethereum_recovery_id_test, mirror_service_manager_validate_query_is_result. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
fix: validate mirror quorum against snapshot weights
layertau
left a comment
There was a problem hiding this comment.
Thanks for the very thorough audit-remediation PR and the finding-by-finding documentation. I reviewed this as a spec/audit-claim compliance pass first.
Spec compliance did not pass yet, so I have not proceeded to a broader code-quality approval pass.
Critical — H-5 is still not remediated in the simple trigger contract:
- packages/contracts/trigger/simple/src/entry.rs:56-59 computes the next
trigger_idwith plain+ 1on au64loaded from state. - This means the claimed H-5 fix is not implemented here. At
u64::MAX, this will either panic/abort in checked builds or wrap in optimized Wasm, creatingtrigger_id0 and allowing the counter sequence to become invalid/reused. - Please change this to checked arithmetic and return a contract error when the counter is exhausted, and add/adjust a regression test that seeds
TRIGGER_MESSAGE_COUNTtou64::MAXand verifiesPushfails without changing state or emitting a reused id.
Because the PR body/fixes doc mark H-5 as closed, this is a release-blocking spec compliance issue.
|
@layertau fix the issue you just described |
|
Pushed the H-5 fix to
Verification run:
Continuing the review pass now. |
layertau
left a comment
There was a problem hiding this comment.
Approved after re-review.
Pass 1 — spec compliance:
- The previously blocking H-5 issue is now fixed: trigger ID increment uses checked arithmetic and the regression test covers
u64::MAXexhaustion without state mutation/reuse. - The PR's audit-remediation scope is consistent with the documented closed/deferred findings.
Pass 2 — code quality:
- The fix is minimal, localized, and follows existing contract error style.
- Snapshot/quorum and ownership/admin patterns are documented and consistently applied across the reviewed surfaces.
- Tests/docs are adequate for this release scope, with broader BLS/ECDSA test buildout explicitly deferred in the PR docs.
Verification run locally:
cargo fmt --all -- --checkcargo test -p cw-wavs-trigger-simple push_fails_without_state_change_when_trigger_id_counter_exhaustedtask test:all-off-chaincargo clippy --workspace --exclude echo-with-id --exclude simple-aggregator --all-targets --all-features -- -D warningscargo check --workspace --exclude echo-with-id --exclude simple-aggregator
Note: unexcluded cargo clippy --all-targets --all-features -- -D warnings still requires the documented WIT setup for component crates; the excluded workspace check/clippy path passes as described.
Ships v0.3.0 — the first release with real cryptography across all three flavors and a clean
remediation pass against the 2026-05-09 internal audit.
Workspace version
0.2.0-alpha.15→0.3.0. Full readiness summary inaudits/2026-05-09-handoff.md; finding-by-finding map inaudits/2026-05-09-fixes.md.Architectural changes
purpose.
poa-middleware'sPOAStakeRegistry.solsemantics — owner-managed operator registration, weight tracking,signing-key snapshots, secp256k1 / BLS12-381 verification, Uint512 quorum check.
pause; ADMIN controls service URI + quorum threshold. Smaller blast radius if a key is compromised.
Mirror keeps its existing two-contract OWNER/ADMIN split.
mappings, and quorum threshold are all
SnapshotItem/SnapshotMap-backed and read atsignature_data.reference_blockduring validation. No fallback to "latest" anywhere; mirror'sexisting fallback (audit H-2) was removed.
stake-registry::TransferOwnershipandservice-manager::SetAdmin, plus CLI commandsregistry transfer-ownershipandservice-manager set-mirror-admin, so the deploy script hands control of the mirror contracts to theoperator-sync-handler / quorum-sync-handler post-deploy. Without this, every
BatchSetOperatorDetails/WavsSetQuorumThresholdfrom the sync-handlers reverted withUnauthorized.writes and validation while paused).
TransferOwnership→AcceptOwnership,SetAdmin→AcceptAdmin). Mirror uses single-step because the target is a contract address thatInstantiateMsg.allowed_pushers: Option<Vec<String>>.None= legacy public-bus;Some([...])restricts.b"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_"(RFC 9380, byte-for-byte matchwith
poa-middleware'sHashToCurve.sol). EVM-side digest contract for ECDSA pinned tokeccak256(envelope) → eip191_hash_message, also matching POA byte-for-byte.Finding-by-finding
wavs-typesprotocol convention — seeDocs + deploy story
README.md— new## Deployingsection covering ECDSA/BLS--owner/--admininstantiate,two-step transfer, pause, and the Mirror post-deploy
transfer-ownership+set-mirror-adminhandoff.
docs/USAGE.md— addsservice-manager instantiate-ecdsa,instantiate-bls, the triggerallowed_pushersfield, and a## Mirror ownership handoffsection.audits/2026-05-09-handoff.md(new) — one-page v0.3.0 audit-readiness package: severity table,architectural changes, code surface, CI status, deferred test buildout, schema/
cosmwasm-checkgap, known limitations, recommended audit sequencing.
audits/2026-05-09-fixes.md— split into "Closed in v0.3.0" and "Deferred to v0.3.x follow-up";I-2 (memory-note refresh) marked done.
Commit layout
One logical commit per finding (or tightly-grouped fixes). Each commit subject references the
finding ID it addresses, so revert-per-finding is clean.
What's NOT in this PR (deferred to later work)
Called out explicitly in
audits/2026-05-09-handoff.mdandaudits/2026-05-09-fixes.md:lifecycle, snapshot regression (H-2 reproducer), aggregate-signature validation, pause/transfer
coverage, boundary cases (zero signers, duplicates, exact-threshold, off-by-one). Requires a BLS
test-signing helper (
bls12_381 = "0.8"dev-dep) we haven't landed yet.cosmwasm-schema::write_api!examples per contract crate + generated schemas underpackages/contracts/*/schema/.cosmwasm-checkCI gate (.github/workflows/audit.yml) and a schema-diff guard.post-deploy transfer model already implemented; would resolve the circular dependency via
Instantiate2address prediction. Deferred as a future architectural cycle.migrateentrypoints (audit L-4) — explicit choice; will be designed at the firststate-shape change.
Verification
cargo fmt --all -- --checkclean.cargo clippy --all-targets --all-features -- -D warningsclean for every contract crate, sdk,utils, cli, on-chain-tests, off-chain-tests, shared-tests.
task test:all-off-chain(11 multitest cases) passing.cargo check --workspace --exclude echo-with-id --exclude simple-aggregatorclean after theversion bump.
echo-with-idandsimple-aggregatorhave pre-existing wit-bindgen requirements unrelated to thiswork; CI populates them via
task components:fetch-wit SKIP_CLONE=true.