Summary
The Permit2 visualizer (src/chain_parsers/visualsign-ethereum/src/protocols/uniswap/contracts/permit2.rs) carries the same silent-truncation-to-zero pattern that #333 fixed for the Universal Router. A uint256 amount above u128::MAX is collapsed to 0 before formatting, so a wallet would display a near-zero / zero amount for an adversarially-large value.
This was flagged during review of #333 and deliberately left out of scope there (different contract). Tracking it here.
Affected sites
permit2.rs:156 — let amount_u128: u128 = call.amount.to_string().parse().unwrap_or(0);
permit2.rs:339 — same pattern in the second permit/transfer path
permit2.rs:162 — let expiration_u64: u64 = call.expiration.to_string().parse().unwrap_or(0); (oversized expiration renders as the Unix epoch instead of surfacing the real value)
Note: the amount paths have an unregistered-token fallback (.unwrap_or_else(|| (call.amount.to_string(), ...))) that preserves the real figure, but the registry-known-token path formats amount_u128, so a known token (e.g. USDC/WETH) with an amount above u128::MAX still renders as 0.
Suggested fix
Mirror the #333 approach:
- Use the U256-aware
ContractRegistry::format_token_amount_u256 for amounts instead of the to_string().parse::<u128>().unwrap_or(0) round-trip.
- For
expiration, surface oversized values rather than truncating, consistent with the existing sigDeadline handling (match u64::try_from(...)).
Acceptance
- Regression tests covering
amount > u128::MAX (registry-known token) and expiration > u64::MAX for both permit paths, asserting the rendered output is not zero / not epoch-0.
Summary
The Permit2 visualizer (
src/chain_parsers/visualsign-ethereum/src/protocols/uniswap/contracts/permit2.rs) carries the same silent-truncation-to-zero pattern that #333 fixed for the Universal Router. Auint256amount aboveu128::MAXis collapsed to0before formatting, so a wallet would display a near-zero / zero amount for an adversarially-large value.This was flagged during review of #333 and deliberately left out of scope there (different contract). Tracking it here.
Affected sites
permit2.rs:156—let amount_u128: u128 = call.amount.to_string().parse().unwrap_or(0);permit2.rs:339— same pattern in the second permit/transfer pathpermit2.rs:162—let expiration_u64: u64 = call.expiration.to_string().parse().unwrap_or(0);(oversized expiration renders as the Unix epoch instead of surfacing the real value)Note: the amount paths have an unregistered-token fallback (
.unwrap_or_else(|| (call.amount.to_string(), ...))) that preserves the real figure, but the registry-known-token path formatsamount_u128, so a known token (e.g. USDC/WETH) with an amount aboveu128::MAXstill renders as0.Suggested fix
Mirror the #333 approach:
ContractRegistry::format_token_amount_u256for amounts instead of theto_string().parse::<u128>().unwrap_or(0)round-trip.expiration, surface oversized values rather than truncating, consistent with the existingsigDeadlinehandling (match u64::try_from(...)).Acceptance
amount > u128::MAX(registry-known token) andexpiration > u64::MAXfor both permit paths, asserting the rendered output is not zero / not epoch-0.