Skip to content

chore(deps): begin ethers→alloy migration (phases 1-3)#368

Open
clawdbot-glitch003 wants to merge 4 commits into
nextfrom
glitch003/ethers-to-alloy-migration
Open

chore(deps): begin ethers→alloy migration (phases 1-3)#368
clawdbot-glitch003 wants to merge 4 commits into
nextfrom
glitch003/ethers-to-alloy-migration

Conversation

@clawdbot-glitch003
Copy link
Copy Markdown
Collaborator

Summary

First slice of the ethers → alloy migration. Adds alloy 1.0 (resolved to 1.8.3) alongside ethers and ports the low-risk EIP-712 verification path. See plans/ethers-to-alloy.md for the full 7-phase plan; this PR lands phases 1-3.

Why now: ethers is unmaintained and accounts for 5 of the 9 vulnerability-class advisories currently ignored in deny.toml (RUSTSEC-2023-0071, 2026-0049, 0098, 0099, 0104). Those entries can be removed once Phase 7 lands.

Phase 1 — Add alloy alongside ethers

  • Bump lit-core workspace dep alloy 0.12.51.0 (it was declared but unused — pre-staged for this work)
  • Add alloy = "1.0" to lit-api-server/Cargo.toml with features needed by later phases: contract, dyn-abi, eip712, network, providers, rpc-types, signer-local, sol-types
  • Both libraries coexist during the migration

Phase 2 — Leaf util migrations (keccak256-only)

Only the two truly-leaf keccak256 sites swap to alloy::primitives::keccak256:

  • src/dstack/v1/mod.rs
  • src/core/v1/guards/billing_auth.rs

Every other "leaf" candidate (utils/mod.rs, parse_with_hash.rs, curve_type.rs, api_status.rs, blockchain_cache.rs, decode_revert.rs) has ethers types in its public API and so migration must be batched with callers — they've been moved into Phase 4 alongside the signer/provider work where they bundle naturally.

Phase 3 — EIP-712

  • src/core/eip712.rs ported to alloy::dyn_abi::TypedData for digest computation and alloy::primitives::Signature::recover_address_from_prehash for recovery
  • Schema introspection done via a local TypedDataSchemaView deserialized from the same JSON (alloy's Resolver has private fields, so we can't inspect it directly)
  • cross_impl_parity_ethers_signed_verifies_under_alloy test signs canonical TypedData with ethers LocalWallet and verifies it under the alloy verifier — confirms byte-identical digest between the two libs. The test is removed in Phase 7 along with the ethers dep.
  • Caller bridges: wallet.as_bytes()wallet.as_slice() in billing_auth.rs; one H160::from_slice(signer.as_slice()) bridge in account_management.rs::convert_to_chain_secured_account (the other two verify_eip712_signature call sites only Debug-print the recovered signer — no bridge needed)
  • Plan correction (now in the doc): src/accounts/signable_contract.rs had been listed under Phase 3 but contains zero EIP-712 code — it's pure SignerMiddleware/NonceManagerMiddleware/ContractCall. Moved into Phase 4.

Test plan

  • cargo check clean in lit-api-server (post-merge with origin/next)
  • cargo test --lib — 227/227 pass (including new cross-impl parity test)
  • cargo clippy --lib --tests clean
  • All 19 core::eip712::tests::* pass

Risk

Low for this PR. The cross-impl parity test pins that an ethers-signed payload still verifies under alloy, so existing client signatures continue to work unchanged. The higher-risk signer/provider/middleware work is deferred to Phase 4.

🤖 Generated with Claude Code

glitch003 and others added 2 commits May 20, 2026 15:06
Phase 1: add alloy 1.0 (resolved to 1.8.3) alongside ethers in
lit-api-server and bump the unused alloy declaration in lit-core
workspace deps from 0.12.5 with the feature set needed by later
phases (contract, dyn-abi, eip712, network, providers, rpc-types,
signer-local, sol-types).

Phase 2: migrate the two truly-leaf keccak256 sites to
alloy::primitives::keccak256 — src/dstack/v1/mod.rs and
src/core/v1/guards/billing_auth.rs. The other "leaf" candidates
all have ethers types (H160/U256/ProviderError) in their public
API and have been bundled into phase 4 alongside the signer work
where they belong.

Phase 3: migrate src/core/eip712.rs to alloy::dyn_abi::TypedData
for digest computation and alloy::primitives::Signature::
recover_address_from_prehash for recovery. Schema introspection
uses a local TypedDataSchemaView deserialized from the same JSON
since alloy's Resolver has private fields. A cross-impl parity
test confirms an ethers-signed payload verifies byte-identically
under the alloy verifier — that test will be deleted in phase 7
when ethers is dropped. Callers bridged at two sites
(billing_auth.rs, account_management.rs::convert_to_chain_secured_account).

Motivation and full phase plan in plans/ethers-to-alloy.md.
Clears 5 of the 9 vulnerability-class advisories currently ignored
in deny.toml (RUSTSEC-2023-0071, 2026-0049/0098/0099/0104) once
phase 7 lands. 227/227 lib tests pass; clippy clean.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…lloy-migration

# Conflicts:
#	lit-api-server/Cargo.lock
@clawdbot-glitch003 clawdbot-glitch003 requested a review from a team May 20, 2026 22:08
glitch003 and others added 2 commits May 20, 2026 16:31
CI fmt check picked up two small formatting drifts in the Phase 3
file — the Signature parse chain indentation and an out-of-order
use statement in the ethers cross-impl parity test.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Codex challenge mode flagged 4 actionable findings on the Phase 3
EIP-712 verifier; this commit addresses all of them.

1. (High) `Eip712FieldDef` now uses `#[serde(deny_unknown_fields)]`.
   A phishing dApp could otherwise embed a decoy key like
   `{ "name": "address", "type": "address", "label": "Approve $500" }`.
   The EIP-712 type hash only commits to `(name, type)`, so the
   digest still recovers and the canonical schema-equality check
   would silently pass — meanwhile some wallet UIs surface the
   unknown metadata to the user. The old ethers `Eip712DomainType`
   had this guard; the new local struct was missing it.

2. (Medium) `DomainView` now uses `#[serde(deny_unknown_fields)]`.
   The pre-existing `verifying_contract`/`salt` checks only fired
   for those two specific keys; an arbitrary key like `displayName`
   would slip through. Same anti-phishing rationale as (1).

3. (Medium) `verify_eip712_signature` now accepts both wire shapes:
   the typed-data object inline AND a JSON string containing it
   (what `eth_signTypedData_v4` returns from MetaMask). Both ethers'
   and alloy's `TypedData::Deserialize` accept both shapes, so the
   prior verifier handled both; rejecting the stringified form here
   would have silently broken any client forwarding the wallet
   response verbatim.

4. (Medium) `extract_issued_at` now parses `issuedAt` as `uint256`
   (decimal string / hex string / JSON number) matching the field
   declaration, then bounds-checks against `i64::MAX` before
   downcasting. The previous decimal-i64 parser would have rejected
   hex strings pre-recovery even though alloy's digest computation
   accepts them, silently breaking otherwise-valid signatures.

Also adds `TypedDataSchemaView`-level `deny_unknown_fields` so
unknown top-level keys (e.g. `displayMessage` alongside `domain`/
`types`/`primaryType`/`message`) are rejected — small defense-in-
depth gain.

5 new tests cover each fix:
- rejects_extra_key_in_field_def
- rejects_extra_key_in_domain
- accepts_stringified_typed_data
- accepts_hex_issued_at
- rejects_issued_at_overflows_i64

The existing `rejects_i64_min_issued_at` test was retargeted: the
i64::MIN payload is now rejected one layer earlier (uint256 can't
hold negatives) before reaching `validate_timestamp`, so the
assertion checks for "issuedAt" in the error rather than
"timestamp". The i128 widening in `validate_timestamp` stays as
defense in depth.

Codex's 5th finding (Low/compat) — alloy rejects non-standard v
values 2..26 and 29..34 that ethers normalized — is acceptable
strictness, not addressed here.

Test result: 24/24 eip712 tests pass; 217/217 lib tests; clippy +
fmt clean.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants