Skip to content

feat(SpokePoolPeriphery): support extended ERC-3009 bytes-signature#1420

Open
mrice32 wants to merge 5 commits into
masterfrom
feat/erc3009-bytes-signature
Open

feat(SpokePoolPeriphery): support extended ERC-3009 bytes-signature#1420
mrice32 wants to merge 5 commits into
masterfrom
feat/erc3009-bytes-signature

Conversation

@mrice32

@mrice32 mrice32 commented May 1, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds two new entry points on SpokePoolPeriphery that pull tokens via the extended EIP-3009 receiveWithAuthorization(...,bytes signature) overload (e.g. USDC v2.2):
    • depositWithAuthorizationBytes
    • swapAndBridgeWithAuthorizationBytes
  • Adds the matching IERC20AuthBytes interface alongside IERC20Auth. Selectors differ from the canonical v/r/s receiveWithAuthorization, so a token can implement both — and our periphery now supports either.
  • This unlocks contract-wallet (EIP-1271) signers for ERC-3009 deposit/swap-and-bridge, which the existing v/r/s flows cannot support.
  • Tail logic (witness binding, submission fee payment, dispatch) is shared with the v/r/s variants via small private helpers; only the token-pull call differs.
  • MockERC20 gains the bytes overload (sharing verification logic with the v/r/s overload) so we can exercise both code paths.

Test plan

  • yarn build-evm-foundry
  • yarn test-evm-foundry -- --match-contract SpokePoolPeripheryTest — 33 passed (existing 29 + 4 new)
  • New tests cover:
    • testTransferWithAuthBytesDepositValidWitness — happy path, EOA signer
    • testTransferWithAuthBytesSwapAndBridgeValidWitness — happy path, swap + bridge
    • testTransferWithAuthBytesSwapAndBridgeInvalidWitness — wrong witness reverts (fuzz)
    • testTransferWithAuthBytesDepositEIP1271ContractSigner — proves a contract wallet can deposit, which is impossible via the v/r/s entry points
  • yarn lint-fix

🤖 Generated with Claude Code

Adds depositWithAuthorizationBytes and swapAndBridgeWithAuthorizationBytes
to pull tokens via the extended EIP-3009 receiveWithAuthorization overload
that takes an unstructured bytes signature (e.g. USDC v2.2). This unlocks
contract-wallet (EIP-1271) signers, in addition to the existing v/r/s form
which is restricted to EOA/ECDSA signatures.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@mrice32 mrice32 requested review from bmzig, nicholaspai and pxrl as code owners May 1, 2026 17:41

@fusmanii fusmanii left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just one Q

* @param validBefore The unix time before which the `receiveWithAuthorization` signature is valid.
* @param receiveWithAuthSignature EIP3009 signature, unstructured bytes (EOA or EIP-1271).
*/
function depositWithAuthorizationBytes(

@fusmanii fusmanii Jun 30, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should depositWithAuthorization be changed to accept r, s, and v and this can also be called depositWithAuthorization? similar to how token contracts overload receiveWithAuthorization

grasphoper
grasphoper previously approved these changes Jul 5, 2026
Comment thread contracts/periphery/SpokePoolPeriphery.sol
…nBytes

Addresses review nit: the cached local was used only once after the tail
logic moved into _finishSwapAndBridgeWithAuthorization, and reading the
calldata struct field directly matches the deposit-side bytes variant.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…n too

Same leftover as the bytes variant: the cached local is used only once
now that the tail logic lives in _finishSwapAndBridgeWithAuthorization.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
grasphoper
grasphoper previously approved these changes Jul 5, 2026
…llet signers (#1467)

* feat(SpokePoolPeriphery): support ERC-6492 counterfactual signers

The bytes-signature ERC-3009 entry points (depositWithAuthorizationBytes,
swapAndBridgeWithAuthorizationBytes) delegate signature verification to the
token (e.g. USDC v2.2), which supports EIP-1271 contract wallets but has no
concept of ERC-6492. A *counterfactual* (not-yet-deployed) contract wallet
therefore cannot authorize a transfer, since there is no code at the signer's
address to run isValidSignature on.

We cannot change the token's verification, so we replicate only the deploy half
of the ERC-6492 flow: a new ERC6492SignatureHandler base contract detects the
ERC-6492 magic suffix and, if present, runs the wrapped factory call to
materialize the wallet before handing the unwrapped inner signature to the
token. The token's own EIP-1271 check then succeeds against the deployed signer.

The factory target/calldata are attacker-controlled, so the prepare call is
routed through the canonical Multicall3 singleton (supplied as an immutable
constructor arg) — this contract is never the msg.sender of that call, so it
cannot be tricked into abusing its own allowances. The call uses
requireSuccess=false so an already-deployed wallet is tolerated; the token
remains the source of truth on validity. Mirrors Coinbase's
ERC6492SignatureHandler (base/commerce-payments).

- New IMulticall3 interface and ERC6492SignatureHandler base contract.
- SpokePoolPeriphery inherits it; constructor now takes the Multicall3 address.
- Deploy script + Constants.getMulticall3() pass the canonical 0xcA11...CA11.
- Non-ERC-6492 signatures pass through unchanged (existing bytes/EIP-1271/EOA
  tests continue to exercise the passthrough path).

Test plan:
- yarn build-evm-foundry
- yarn test-evm-foundry -- --match-contract "SpokePoolPeripheryTest|TransferProxy"
  53 passed (49 existing + 4 new)
- New tests: counterfactual-wallet deposit & swap-and-bridge, already-deployed
  wallet with wrapped sig (idempotent prepare), and failing-prepare tolerance.
- yarn lint-fix

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(tron): forward multicall3 arg to SpokePoolPeriphery deploy

The new SpokePoolPeriphery constructor requires a Multicall3 address; the
Tron deployment script was still encoding only `<permit2>`, so the
documented `yarn tron-deploy-spoke-pool-periphery` flow would have
reverted in the constructor's zero-address check. Take a second Tron
Base58Check address, validate it, and ABI-encode it as the second arg.

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

* perf(ERC6492SignatureHandler): decode wrapped signature from calldata

Take the signature as calldata and decode the (factory, factoryCalldata,
innerSignature) tuple directly from a calldata slice. Drops the entry
calldata-to-memory copy and the byte-by-byte loop into a memory scratch
buffer that used to precede abi.decode.

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

* refactor(ERC6492SignatureHandler): use magic-value length over literal 32

Replace the hard-coded 32 magic-suffix length with _ERC6492_MAGIC_VALUE.length
(a compile-time constant for the bytes32) so the length check and the calldata
slices are self-documenting and stay in sync with the constant.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* move isContract check && add Permit2 handling of erc6492 (#1488)

Signed-off-by: Ihor Farion <ihor@umaproject.org>

* named fn arguments

Signed-off-by: Ihor Farion <ihor@umaproject.org>

---------

Signed-off-by: Ihor Farion <ihor@umaproject.org>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: droplet-rl <284132418+droplet-rl@users.noreply.github.com>
Co-authored-by: Ihor Farion <ihor@umaproject.org>
Co-authored-by: grasphoper <65650773+grasphoper@users.noreply.github.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.

3 participants