Skip to content

v0.5.21

tagged this 08 Jun 15:35
The Uniswap Universal Router is the dominant swap/approval entrypoint in
the EVM agent niche, but its calldata is shaped unlike every wrapper we
already decode: execute(bytes commands, bytes[] inputs[, uint256 deadline])
carries one byte per command (low 7 bits = type, 0x80 = allow-revert) and
inputs[i] is a raw ABI tuple with no inner 4-byte selector. The existing
selector-based recursion could not reach a drain routed through it, so a
PERMIT2_TRANSFER_FROM to an attacker or an unlimited PERMIT2_PERMIT flowed
through as a single opaque LOW call (ok=True, assert_safe passed) —
empirically confirmed fail-open. Same evasion class as the ERC-4337
execute gap (v0.5.19) and Seaport BulkOrder gap (v0.5.20).

Add a dedicated command decoder that emits flags directly for the four
dangerous Permit2 commands (0x02 transferFrom, 0x03 permit-batch,
0x0a permit, 0x0d transferFrom-batch) and recurses into 0x21
EXECUTE_SUB_PLAN, depth-capped and integrated into _collect_flags so an
execute/multicall wrapping a UR call is caught for free. Command bytes
verified against Uniswap's live Commands.sol (mask 0x7f). Non-noisy: a
plain swap/wrap/unwrap carries no such command and stays clean.

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