test: end-to-end deposit-from-CEX EIP-7702 sweep demo against a local Ethereum node#10670
Open
gregorydemay wants to merge 24 commits into
Open
test: end-to-end deposit-from-CEX EIP-7702 sweep demo against a local Ethereum node#10670gregorydemay wants to merge 24 commits into
gregorydemay wants to merge 24 commits into
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
Adds a standalone, runnable Foundry/Alloy proof-of-concept under rs/ethereum/cketh/docs/ that demonstrates the “deposit from CEX” EIP-7702 sweeping flow referenced by the design doc, including single and batched sweeps, helper-based sweeps that emit canonical events, and a rejection case for non-minter sweeping.
Changes:
- Introduces a Rust CLI demo that deploys mock contracts, simulates CEX deposits, and performs EIP-7702 single/batched sweeps (direct and via the real helper), asserting gas usage.
- Adds Solidity demo contracts (sweeper delegates + mock USDT) and embeds their compiled bytecode artifacts for reproducible runs.
- Documents how to run and rebuild the artifacts, plus Foundry configuration and local ignore rules.
Reviewed changes
Copilot reviewed 12 out of 13 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| rs/ethereum/cketh/docs/deposit_from_cex_demo/src/main.rs | Runnable Alloy-based demo driving the full sweep flow (single/batch, direct/via helper, attacker rejection) and asserting gas usage. |
| rs/ethereum/cketh/docs/deposit_from_cex_demo/README.md | Runbook and explanation of the demo, variants, and artifact regeneration steps. |
| rs/ethereum/cketh/docs/deposit_from_cex_demo/foundry.toml | Configures Foundry for Prague/Pectra-compatible EVM settings. |
| rs/ethereum/cketh/docs/deposit_from_cex_demo/contracts/CkSweeper.sol | Variant A sweeper delegate + batch entry point for direct-to-minter token/ETH sweeps. |
| rs/ethereum/cketh/docs/deposit_from_cex_demo/contracts/CkSweeperViaHelper.sol | Variant B sweeper delegate + minter-gated batch entry point that routes deposits through the helper contract. |
| rs/ethereum/cketh/docs/deposit_from_cex_demo/contracts/MockUSDT.sol | Mock USDT-like ERC-20 with non-standard return behavior to exercise tolerant transfer/approve handling. |
| rs/ethereum/cketh/docs/deposit_from_cex_demo/Cargo.toml | Declares a standalone (non-workspace) Rust crate for the demo. |
| rs/ethereum/cketh/docs/deposit_from_cex_demo/Cargo.lock | Locks dependencies for reproducible demo builds. |
| rs/ethereum/cketh/docs/deposit_from_cex_demo/artifacts/CkSweeper.bin.hex | Embedded compiled bytecode for the Variant A delegate/batcher. |
| rs/ethereum/cketh/docs/deposit_from_cex_demo/artifacts/CkSweeperViaHelper.bin.hex | Embedded compiled bytecode for the Variant B delegate/batcher. |
| rs/ethereum/cketh/docs/deposit_from_cex_demo/artifacts/MockUSDT.bin.hex | Embedded compiled bytecode for the mock USDT token. |
| rs/ethereum/cketh/docs/deposit_from_cex_demo/artifacts/CkDeposit.bin.hex | Embedded compiled bytecode for the real helper contract used in Variant B. |
| rs/ethereum/cketh/docs/deposit_from_cex_demo/.gitignore | Ignores Foundry/Cargo build outputs for the demo directory. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+133
to
+140
| fn encode_principal(principal_text: &str) -> FixedBytes<32> { | ||
| let principal = Principal::from_text(principal_text).expect("valid principal"); | ||
| let bytes = principal.as_slice(); | ||
| let mut encoded = [0u8; 32]; | ||
| encoded[0] = bytes.len() as u8; | ||
| encoded[1..=bytes.len()].copy_from_slice(bytes); | ||
| FixedBytes::from(encoded) | ||
| } |
eb1ca42 to
27806c5
Compare
60e0c3c to
8f0238a
Compare
8f0238a to
64abe70
Compare
ce3d105 to
382bfca
Compare
ef92401 to
d7f815a
Compare
Stacked on top of the design-doc PR (#10652). Adds the runnable alloy/foundry demo of the EIP-7702 sweep mechanism (both variants, gas assertions, attacker rejection) that the design document references. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Vendors the foundry v1.7.1 release binaries (anvil/cast/forge/chisel) via http_archive and adds a monorepo-native rust_test that spawns anvil and probes it over JSON-RPC. No docker, no InternetComputer, no alloy: the skeleton foundation for hand-rolling the deposit-from-CEX PoC's Ethereum layer on the already-vendored k256/sha3/rlp/ethnum crates. The pinned foundry release is >= v1.0 so anvil supports EIP-7702, unlike the v0.3.0 @foundry oci image used by //rs/tests/cross_chain:ic_xc_cketh_test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Vendor foundry binaries for all four host platforms (linux/darwin x amd64/arm64) and select the matching one, so the test runs on macOS as well as linux CI. Previously only linux_amd64 was vendored: on an arm64 Mac the Linux ELF spawned (via the execvp /bin/sh fallback) and died immediately, but the readiness loop kept polling a dead process until the 30s timeout. The loop now detects an early child exit and fails fast with the exit status. Inline the archive's build_file content instead of a separate third_party/BUILD.foundry.bazel. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Relocate the hermetic anvil smoke test to rs/ethereum/cketh/minter/tests and excludes it from the integration_tests suite, which runs against the IC test machinery rather than a local foundry node. The platform-selecting anvil alias and its dedicated rust_test target move alongside it into the minter BUILD. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-7702 tx layer Fills in the migrated test with the decided variant B flow, driving a local anvil node through the minter's own transaction layer (Eip7702TransactionRequest, Authorization/SignedAuthorization, Signed) rather than a hand-rolled encoder or alloy. Signing uses ic_secp256k1 locally as a stand-in for threshold ECDSA; ABI calldata, JSON-RPC transport and event decoding are minimal helpers. The test deploys a USDT-style ERC-20, the real ckETH helper and the sweeper delegate, then sweeps unfunded deposit EOAs in single and batched EIP-7702 transactions (gas paid by the minter), asserts the canonical ReceivedEthOrErc20 events carry the right IC principal, and shows a non-minter sweep is rejected before the minter finalizes it with a plain EIP-1559 transaction. Gas used by each sweep is asserted against deterministic constants. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Drops the checked-in .bin.hex artifacts and compiles the demo contracts from their Solidity sources at test time with the vendored solc, mirroring how ic_xc_cketh_test provisions its contracts. The real helper (CkDeposit) is compiled from its canonical DepositHelperWithSubaccount.sol rather than a duplicated bytecode blob, and the two demo-only sources are consumed directly from the PoC's contracts directory. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Vendors the latest stable solc (0.8.35) pinned exactly by sha256 and compiles the demo contracts with an explicit --evm-version prague, the fork that defines EIP-7702 and that anvil runs. Pinning both the compiler and the EVM version keeps the bytecode (and the asserted gas) reproducible instead of drifting with solc's default target (now osaka/fusaka). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Makes the demo test's solc host-platform aware like the anvil binary: vendors solc 0.8.35 for linux-amd64/linux-arm64/macos and selects it through a `solc` alias reusing the existing config_settings. The macOS build is a universal (x86_64 + arm64) binary, so the test runs natively on Linux and on both Intel and Apple Silicon Macs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Follows the repository convention for host-platform-selected tool binaries (bitcoind, dfx, ruff, ...): defines //:anvil and //:solc as aliases selecting on @bazel_tools//src/conditions in the top-level BUILD, and drops the bespoke config_settings from the minter BUILD. The demo test consumes //:anvil and //:solc. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…y PoC Moves the two contracts the test compiles (MockUSDT.sol, CkSweeperViaHelper.sol) into minter/tests/deposit_from_cex_demo/, next to the test that consumes them, and removes the now-redundant standalone alloy PoC (docs/deposit_from_cex_demo: main.rs, Cargo.*, README, foundry.toml, the pre-compiled artifacts/ bytecode and the unused variant-A CkSweeper.sol). The productive minter test is now the single deposit-from-CEX demo, with no duplicated contracts or bytecode. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Refactors the deposit-from-CEX demo into a table-driven test over the two batch sizes from the design doc's cost-estimation section: a single deposit swept in one batch (B=1) versus a batch of twenty (B=20). Each scenario funds fresh unfunded deposit EOAs from the CEX and sweeps them all in one EIP-7702 transaction, asserting the canonical events and that per-deposit gas amortizes as the batch grows (94_932 for B=1 vs 55_668/deposit for B=20). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Re-adds the attacker-rejection as a focused second test: the minter installs a deposit EOA's delegation without sweeping (its authorization rides in an empty batch), then a non-minter's sweepErc20 attempt reverts and the funds stay put, while the minter's own sweep still succeeds via a plain EIP-1559 transaction (the delegation persists). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Review renamed Eip1559Signature to TransactionSignature and Signed::raw_transaction_hex to raw_transaction_bytes before #10671 landed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ee71e0e to
05a19ab
Compare
Adds an average_gas_used field to BatchScenario (total / deposits) and asserts it alongside the total, making the amortization explicit: 94_932 per deposit at B=1 versus 55_668 at B=20. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Third point between B=1 and B=20 (570_151 gas, 57_015 per deposit), keeping the per-deposit amortization curve monotonic: 94_932 -> 57_015 -> 55_668. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
e13e8bf to
3521d47
Compare
…tion The first sweep installs the delegations (EIP-7702); a second sweep of the same addresses, after re-funding, reuses the persisted delegations and so needs only a regular EIP-1559 transaction. This isolates the EIP-7702 delegation-install overhead from the steady-state sweep cost. Per batch (total / per-deposit): B=1: EIP-7702 94_932/94_932 EIP-1559 63_252/63_252 B=10: EIP-7702 570_151/57_015 EIP-1559 390_151/39_015 B=20: EIP-7702 1_113_373/55_668 EIP-1559 753_373/37_668 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
pull Bot
pushed a commit
to bit-cook/ic
that referenced
this pull request
Jul 8, 2026
) Design doc for [DEFI-2096](https://dfinity.atlassian.net/browse/DEFI-2096): allow onramping ckUSDC/ckUSDT (and generally any ckERC20, later ckETH) directly from a centralized exchange withdrawal. Deposits currently require calling the helper smart contract, which a CEX cannot do: a CEX withdrawal is a plain transfer from a shared hot wallet, carrying no IC principal. The proposed design gives each IC account a unique tECDSA-derived deposit address and sweeps funds to the minter using EIP-7702 (Pectra), so deposit addresses never need ETH for gas and remain key-recoverable independently of any contract code. The doc covers the requirements, claim-based deposit detection, the sweeper delegate, fee model, a phased delivery (ckERC20 first, then ckETH with its balance-based detection and fixed-21k-gas constraints), and the discussed alternatives (CREATE2 forwarders, ERC-4337, permit-based sponsoring). The runnable proof-of-concept the doc references lives in a stacked follow-up, dfinity#10670. 🤖 Generated with [Claude Code](https://claude.com/claude-code) [DEFI-2096]: https://dfinity.atlassian.net/browse/DEFI-2096?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: IDX GitHub Automation <infra+github-automation@dfinity.org>
Adds CkSweeperAttested, an EIP-7702 delegate matching the proposed one-time
self-attestation design: sweeping is permissionless but each sweep carries an
attestation — a secp256k1 signature by the deposit address' own key (the
minter's role via threshold ECDSA) over keccak256("ck-deposit-owner" || chainid
|| helper || principal || subaccount), verified with ecrecover == address(this).
Runs the same table-driven batch gas test through the attested sweeper with a
non-minter relayer submitting the transactions, plus a test that a forged
attestation is rejected while the genuine one credits the attested account. The
attestation adds ~4k gas/deposit over the caller-gated variant (per batch of 20:
59_645 vs 55_668 for the delegating sweep, 41_645 vs 37_668 for the steady-state
EIP-1559 sweep).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
35d0086 to
fa6fac1
Compare
…led codec Replaces the custom Token enum + head/tail ABI encoder (including the TupleArray special case) and the manual return/event decoding with ethers-core's ethabi (encode/decode/Token/ParamType), already a dependency of the sibling integration_tests. The attestation preimage stays an explicit byte concatenation (it is a hash preimage, not ABI, and abi.encodePacked semantics differ). Gas figures are unchanged, confirming the encoding is byte-for-byte equivalent. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
33f2df6 to
f55bd97
Compare
The style_edition=2024 cargo-fmt check orders imports case-sensitively while the
autofix bot re-sorts them case-insensitively, so a mixed-case group (e.g.
{ParamType, Token, decode, encode}) ping-pongs between them and fails Cargo Lint.
Import only the uppercase items and fully-qualify the lowercase
ethers_core::abi::{encode,decode} and serde_json::json!, leaving no group whose
ordering depends on case.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Cargo Lint CI job runs clippy with -D clippy::unseparated_literal_suffix, so underscore-separate the u8 literals (0u8 -> 0_u8). Also declare reqwest as a dev-dependency of the crate (it was only in the Bazel target), which cargo needs to resolve the test, and record the edge in Cargo.lock. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
✅ No security or compliance issues detected. Reviewed everything up to 8993359. Security Overview
Detected Code Changes
|
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.
A hermetic, monorepo-native integration test that validates the "deposit from CEX" sweep (design in
rs/ethereum/cketh/docs/deposit_from_cex.md) end-to-end against a localanvilnode, through the minter's productive EIP-7702 transaction layer (#10671).It sweeps unfunded deposit addresses to the minter in minter-paid EIP-7702 transactions, checks each emits the canonical deposit event with the right IC principal, measures gas across batch sizes (1/10/20), and covers both sweeper designs — caller-gated and permissionless-with-minter-attestation — including rejection of unauthorized sweeps.
foundry and solc are vendored via Bazel (no docker, IC replica, or alloy).