harden: state an undetermined swap input token as undetermined, not as ETH (closes #357)
tokenInfo(null) yielded "ETH (native)", so a swap whose input token the calldata never named was asserted to the user as ETH. The determination established for the output side in #353 applies unchanged: Currency is a value type over address, so native ETH arrives as the truthy zero-address string and WRAP_ETH sets it explicitly -- null can only mean undetermined. The rule now lives in tokenInfo() itself rather than at each call site, so the redundant output-side guard added by #356 is removed; both sides read one UNNAMED_CURRENCY constant and cannot drift. Verified by execution that a genuine native-ETH input still renders as ETH, including via WRAP_ETH and a V4 zero-address PoolKey, and that the real mainnet output-side fixture is unchanged.
This commit was merged in pull request #365.
This commit is contained in:
@@ -48,13 +48,32 @@ function formatAmount(raw, decimals) {
|
||||
return truncateAmountNeverZero(formatUnits(raw, decimals));
|
||||
}
|
||||
|
||||
// One wording for either side of the screen: a currency the calldata never
|
||||
// named. It reads as a refusal, the same stance unknownDecimalsAmount() takes
|
||||
// on a scale — not as a token name, and not as a quantity.
|
||||
const UNNAMED_CURRENCY = "Unknown (not named in the calldata)";
|
||||
|
||||
// `decimals` is null when nothing knows this token's scale. It is not
|
||||
// defaulted to 18: the swap lines land on the same approval screen as the
|
||||
// ERC-20 line, and a scale guessed there is what showed a 1,000 USDT swap as
|
||||
// 0.000000000001. `sources` is { trackedTokens, wallets }, shaped as they are
|
||||
// on `state`; resolveTokenDecimals() reads the bundled list, then those.
|
||||
//
|
||||
// A null `address` means UNDETERMINED — the calldata named no currency for
|
||||
// that side — and is refused rather than named. It is not native ETH: Uniswap
|
||||
// V4 spells native ETH as `Currency.wrap(address(0))` (v4-core
|
||||
// `type Currency is address`), and a Currency is ABI-encoded as a plain
|
||||
// address word, so every decode site here gets back the truthy string
|
||||
// "0x0000000000000000000000000000000000000000" for it — never null. WRAP_ETH
|
||||
// sets that same explicit zero address, and an UNWRAP_WETH output is caught by
|
||||
// its caller before this is consulted, so nothing that genuinely is ETH
|
||||
// arrives null. Naming a null ETH states the wrong asset and formats its
|
||||
// amount at the wrong scale.
|
||||
function tokenInfo(address, sources) {
|
||||
if (!address || address === "0x0000000000000000000000000000000000000000") {
|
||||
if (!address) {
|
||||
return { symbol: null, decimals: null, address: null };
|
||||
}
|
||||
if (address === "0x0000000000000000000000000000000000000000") {
|
||||
return { symbol: "ETH", decimals: 18, address: null };
|
||||
}
|
||||
const t = TOKEN_BY_ADDRESS.get(address.toLowerCase());
|
||||
@@ -451,26 +470,14 @@ function decode(data, toAddress, sources) {
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve token info.
|
||||
//
|
||||
// A null `outputToken` means undetermined, not native ETH, so it is
|
||||
// not handed to tokenInfo() — which maps null to ETH at 18 decimals
|
||||
// for the input side's benefit. Uniswap V4 spells native ETH as
|
||||
// `Currency.wrap(address(0))` (v4-core `type Currency is address`),
|
||||
// and a Currency is ABI-encoded as a plain address word, so every
|
||||
// decode site here gets back the truthy string
|
||||
// "0x0000000000000000000000000000000000000000" for it — never null.
|
||||
// tokenInfo() already names that ETH, and an UNWRAP_WETH output is
|
||||
// caught above, so nothing that genuinely outputs ETH arrives null.
|
||||
// Only a step whose output currency did not decode does, and naming
|
||||
// that ETH states the wrong asset and formats Min. received at the
|
||||
// wrong scale.
|
||||
// Resolve token info. A null token on either side means the calldata
|
||||
// named no currency for it; tokenInfo() refuses rather than calling it
|
||||
// ETH. UNWRAP_WETH is the one output that is ETH without a currency to
|
||||
// decode, and it is answered here rather than left to that rule.
|
||||
const inInfo = tokenInfo(inputToken, sources);
|
||||
const outInfo = hasUnwrapWeth
|
||||
? { symbol: "ETH", decimals: 18, address: null }
|
||||
: outputToken
|
||||
? tokenInfo(outputToken, sources)
|
||||
: { symbol: null, decimals: null, address: null };
|
||||
: tokenInfo(outputToken, sources);
|
||||
|
||||
const inSymbol = inInfo.symbol;
|
||||
const outSymbol = outInfo.symbol;
|
||||
@@ -500,6 +507,12 @@ function decode(data, toAddress, sources) {
|
||||
});
|
||||
} else if (inSymbol === "ETH") {
|
||||
details.push({ label: "Token In", value: "ETH (native)" });
|
||||
} else {
|
||||
// Nothing established the input token, so the line says that
|
||||
// rather than going missing or naming a token by default. Same
|
||||
// wording as the Token Out refusal below: the two sides of this
|
||||
// screen must not describe the same condition in two ways.
|
||||
details.push({ label: "Token In", value: UNNAMED_CURRENCY });
|
||||
}
|
||||
|
||||
if (inputAmount !== null && inputAmount !== undefined) {
|
||||
@@ -538,14 +551,10 @@ function decode(data, toAddress, sources) {
|
||||
details.push({ label: "Token Out", value: outSymbol });
|
||||
} else {
|
||||
// Nothing established the output token, so the line says that
|
||||
// rather than going missing or naming a token by default. It reads
|
||||
// as a refusal, the same stance unknownDecimalsAmount() takes on a
|
||||
// scale, so a Min. received figure below it is never attached to a
|
||||
// token the calldata did not state.
|
||||
details.push({
|
||||
label: "Token Out",
|
||||
value: "Unknown (not named in the calldata)",
|
||||
});
|
||||
// rather than going missing or naming a token by default, and a
|
||||
// Min. received figure below it is never attached to a token the
|
||||
// calldata did not state.
|
||||
details.push({ label: "Token Out", value: UNNAMED_CURRENCY });
|
||||
}
|
||||
|
||||
if (minOutput !== null && minOutput !== undefined) {
|
||||
|
||||
Reference in New Issue
Block a user