Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
41dfdc8701 |
@@ -1116,23 +1116,22 @@ with the same floor (`.5` reads `.5000`).
|
|||||||
|
|
||||||
**Specific Exception — unknown token scale:** Calldata carries base units and no
|
**Specific Exception — unknown token scale:** Calldata carries base units and no
|
||||||
scale, so every amount on the dApp approval screen needs the token's `decimals`.
|
scale, so every amount on the dApp approval screen needs the token's `decimals`.
|
||||||
It is resolved from the bundled token list, then from the tokens the user tracks
|
It is resolved from the bundled token list, then from the tokens the user
|
||||||
on the network the transaction is for, then, when that is the active network,
|
tracks, then from what the block explorer reported for the contract
|
||||||
from what the block explorer reported for the contract in the wallet's balances,
|
(`resolveTokenDecimals()` in `src/shared/approvalAmount.js`). Where none of them
|
||||||
which are the active network's only (`resolveTokenDecimals()` in
|
answers, the amount is not formatted: the line reads
|
||||||
`src/shared/approvalAmount.js`). Where none of them answers, the amount is not
|
`5000000000 base units (decimals unknown)`. A guessed scale is not an
|
||||||
formatted: the line reads `5000000000 base units (decimals unknown)`. A guessed
|
approximation but a different number — 1,000 units of a 6-decimal token
|
||||||
scale is not an approximation but a different number — 1,000 units of a
|
formatted at 18 decimals reads `0.000000001` — on the screen whose only job is
|
||||||
6-decimal token formatted at 18 decimals reads `0.000000001` — on the screen
|
to state what is being authorized. Both amount paths of that screen take this
|
||||||
whose only job is to state what is being authorized. Both amount paths of that
|
rule: the ERC-20 `transfer`/`approve` line (`src/popup/views/approval.js`) and
|
||||||
screen take this rule: the ERC-20 `transfer`/`approve` line
|
the swap's `Amount` and `Min. received` lines (`src/shared/uniswap.js`). The
|
||||||
(`src/popup/views/approval.js`) and the swap's `Amount` and `Min. received`
|
token permission warning on the signature screen takes the same rule for its
|
||||||
lines (`src/shared/uniswap.js`). The token permission warning on the signature
|
amounts. An unbounded allowance or permit needs no scale to describe and is
|
||||||
screen takes the same rule for its amounts. An unbounded allowance or permit
|
still shown as `Unlimited`. A source's answer counts only if it is a whole
|
||||||
needs no scale to describe and is still shown as `Unlimited`. A source's answer
|
number from 0 to 80: `decimals()` returns a `uint8`, but `formatUnits()` cannot
|
||||||
counts only if it is a whole number from 0 to 80: `decimals()` returns a
|
format more than 80 decimal places, so a token that reports 81 to 255 is shown
|
||||||
`uint8`, but `formatUnits()` cannot format more than 80 decimal places, so a
|
as one whose scale nothing knows.
|
||||||
token that reports 81 to 255 is shown as one whose scale nothing knows.
|
|
||||||
|
|
||||||
The rule holds only if nothing invents a scale UPSTREAM of it. Those three
|
The rule holds only if nothing invents a scale UPSTREAM of it. Those three
|
||||||
sources are read as authoritative, so a value written into one of them cannot be
|
sources are read as authoritative, so a value written into one of them cannot be
|
||||||
|
|||||||
+14
-12
@@ -11,7 +11,6 @@ const {
|
|||||||
onViewLeave,
|
onViewLeave,
|
||||||
formatFee,
|
formatFee,
|
||||||
tokenLabel,
|
tokenLabel,
|
||||||
tokenSourcesByChainId,
|
|
||||||
} = require("./helpers");
|
} = require("./helpers");
|
||||||
const { state, saveState, currentNetwork } = require("../../shared/state");
|
const { state, saveState, currentNetwork } = require("../../shared/state");
|
||||||
const {
|
const {
|
||||||
@@ -86,12 +85,13 @@ function tokenAmountText(rawAmount, decimals, symbol) {
|
|||||||
function decodeCalldata(data, toAddress, chainId, fromAddress) {
|
function decodeCalldata(data, toAddress, chainId, fromAddress) {
|
||||||
if (!data || data === "0x" || data.length < 10) return null;
|
if (!data || data === "0x" || data.length < 10) return null;
|
||||||
|
|
||||||
// Where a token's name and scale are looked for, for every decoder below:
|
// Where a token's scale is looked for, for every decoder below: the ERC-20
|
||||||
// what the wallet knows of the tokens on the network with `chainId`, which
|
// amount line and the swap's Amount and Min. received lines resolve it the
|
||||||
// need not be the active one. The ERC-20 amount line and the swap's Amount
|
// same way, and refuse to format the same way when it is nowhere.
|
||||||
// and Min. received lines resolve the scale the same way, and refuse to
|
const decimalsSources = {
|
||||||
// format the same way when it is nowhere.
|
trackedTokens: state.trackedTokens,
|
||||||
const decimalsSources = tokenSourcesByChainId(chainId);
|
wallets: state.wallets,
|
||||||
|
};
|
||||||
|
|
||||||
// Try ERC-20 (approve / transfer)
|
// Try ERC-20 (approve / transfer)
|
||||||
try {
|
try {
|
||||||
@@ -261,7 +261,10 @@ function showTxApproval(details) {
|
|||||||
|
|
||||||
const toAddr = approvedTx.to;
|
const toAddr = approvedTx.to;
|
||||||
const ethValue = formatEther(approvedTx.value || "0");
|
const ethValue = formatEther(approvedTx.value || "0");
|
||||||
const sources = tokenSourcesByChainId(approvedTx.chainId);
|
const sources = {
|
||||||
|
trackedTokens: state.trackedTokens,
|
||||||
|
wallets: state.wallets,
|
||||||
|
};
|
||||||
|
|
||||||
// Build txInfo for status screens
|
// Build txInfo for status screens
|
||||||
pendingTxDetails = {
|
pendingTxDetails = {
|
||||||
@@ -341,9 +344,8 @@ function showTxApproval(details) {
|
|||||||
$("approve-tx-origin").textContent = details.origin;
|
$("approve-tx-origin").textContent = details.origin;
|
||||||
$("approve-tx-from").innerHTML = approvalAddressHtml(details.approvedFrom);
|
$("approve-tx-from").innerHTML = approvalAddressHtml(details.approvedFrom);
|
||||||
|
|
||||||
// Show token symbol next to contract address if known on the network the
|
// Show token symbol next to contract address if known
|
||||||
// transaction is for
|
const symbol = toAddr ? tokenLabel(toAddr) : null;
|
||||||
const symbol = toAddr ? tokenLabel(toAddr, approvedTx.chainId) : null;
|
|
||||||
if (toAddr) {
|
if (toAddr) {
|
||||||
let toHtml = "";
|
let toHtml = "";
|
||||||
if (symbol) {
|
if (symbol) {
|
||||||
@@ -386,7 +388,7 @@ function showTxApproval(details) {
|
|||||||
detailsHtml += `<div class="text-muted">${escapeHtml(d.label)}</div>`;
|
detailsHtml += `<div class="text-muted">${escapeHtml(d.label)}</div>`;
|
||||||
if (d.address) {
|
if (d.address) {
|
||||||
if (d.isToken) {
|
if (d.isToken) {
|
||||||
detailsHtml += `<div class="font-bold">${escapeHtml(tokenLabel(d.address, approvedTx.chainId) || "Unknown token")}</div>`;
|
detailsHtml += `<div class="font-bold">${escapeHtml(tokenLabel(d.address) || "Unknown token")}</div>`;
|
||||||
}
|
}
|
||||||
detailsHtml += approvalAddressHtml(d.address);
|
detailsHtml += approvalAddressHtml(d.address);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+11
-27
@@ -1,7 +1,7 @@
|
|||||||
// Helpers the views in this directory share: showing a view and going back,
|
// Helpers the views in this directory share: showing a view and going back,
|
||||||
// error and flash lines and the warning banners, formatting of amounts, fees,
|
// error and flash lines and the warning banners, formatting of amounts, fees,
|
||||||
// balances, addresses and dates, the RPC and Blockscout URLs, node and tokens
|
// balances, addresses and dates, the RPC and Blockscout URLs and node of the
|
||||||
// of the network a chain id names, explorer links, and click-to-copy.
|
// network a chain id names, explorer links, and click-to-copy.
|
||||||
//
|
//
|
||||||
// Escaping rule for every view in this directory, since they all build
|
// Escaping rule for every view in this directory, since they all build
|
||||||
// markup by concatenation: any VALUE interpolated into an innerHTML string
|
// markup by concatenation: any VALUE interpolated into an innerHTML string
|
||||||
@@ -300,30 +300,15 @@ function nativeCurrency() {
|
|||||||
return currentNetwork().nativeCurrency;
|
return currentNetwork().nativeCurrency;
|
||||||
}
|
}
|
||||||
|
|
||||||
// What the wallet knows of the tokens on the network with this chain id, or
|
// The symbol shown for a token line, resolved from the bundled list, the
|
||||||
// on the active network when no chain id is given, as the `sources` that
|
// tokens the user tracks, and the explorer's report — the same chain the
|
||||||
// resolveTokenSymbol() and resolveTokenDecimals() take. A contract address
|
// amount line's scale comes from. Null when no source names one, so the token
|
||||||
// names a token on one network only, and every token balance held is the
|
// lines keep saying `Unknown token` for a token nothing knows.
|
||||||
// active network's, so another network gets the tokens tracked on it
|
function tokenLabel(address) {
|
||||||
// (state.networkTrackedTokens, see applyChainSwitchFields()) and no balances.
|
return resolveTokenSymbol(address, {
|
||||||
function tokenSourcesByChainId(chainId) {
|
trackedTokens: state.trackedTokens,
|
||||||
const net = chainId ? networkByChainId(chainId) : currentNetwork();
|
wallets: state.wallets,
|
||||||
if (net && net.id === state.networkId) {
|
});
|
||||||
return { trackedTokens: state.trackedTokens, wallets: state.wallets };
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
trackedTokens: (net && state.networkTrackedTokens[net.id]) || [],
|
|
||||||
wallets: [],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// The symbol shown for a token line on the network with `chainId`, or on the
|
|
||||||
// active network when none is given: from the bundled list, the tokens the
|
|
||||||
// user tracks, and the explorer's report, the sources the amount line's scale
|
|
||||||
// comes from. Null when no source names one, so the token lines keep saying
|
|
||||||
// `Unknown token` for a token nothing knows.
|
|
||||||
function tokenLabel(address, chainId) {
|
|
||||||
return resolveTokenSymbol(address, tokenSourcesByChainId(chainId));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// A network fee in wei as the confirmation and approval screens both show it:
|
// A network fee in wei as the confirmation and approval screens both show it:
|
||||||
@@ -790,7 +775,6 @@ module.exports = {
|
|||||||
keyMaterialName,
|
keyMaterialName,
|
||||||
unknownableAmount,
|
unknownableAmount,
|
||||||
nativeCurrency,
|
nativeCurrency,
|
||||||
tokenSourcesByChainId,
|
|
||||||
tokenLabel,
|
tokenLabel,
|
||||||
formatFee,
|
formatFee,
|
||||||
addressDotHtml,
|
addressDotHtml,
|
||||||
|
|||||||
@@ -280,7 +280,7 @@ function decodedDetailsHtml(decoded, chainId) {
|
|||||||
html += `<div class="text-xs text-muted mb-1">${escapeHtml(d.label)}</div>`;
|
html += `<div class="text-xs text-muted mb-1">${escapeHtml(d.label)}</div>`;
|
||||||
if (d.address) {
|
if (d.address) {
|
||||||
if (d.isToken) {
|
if (d.isToken) {
|
||||||
const sym = tokenLabel(d.address, chainId) || "Unknown token";
|
const sym = tokenLabel(d.address) || "Unknown token";
|
||||||
html += `<div class="font-bold">${escapeHtml(sym)}</div>`;
|
html += `<div class="font-bold">${escapeHtml(sym)}</div>`;
|
||||||
html += toAddressHtml(d.address, chainId);
|
html += toAddressHtml(d.address, chainId);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,220 +0,0 @@
|
|||||||
// The approval screen for a site's Sepolia token transfer while Ethereum
|
|
||||||
// Mainnet is active (https://git.eeqj.de/sneak/AutistMask/issues/629).
|
|
||||||
//
|
|
||||||
// The screen named the token, and scaled its amount, from the active network's
|
|
||||||
// tracked tokens and balances, so a token the wallet tracks on Sepolia read as
|
|
||||||
// an unknown token with its amount in base units, and the screens after it
|
|
||||||
// labelled the amount `?`. It now uses what the wallet knows of the tokens on
|
|
||||||
// the network the transaction is for.
|
|
||||||
//
|
|
||||||
// Driven through the real approval screen, password and Confirm included,
|
|
||||||
// against a minimal DOM stub in the shape tests/swapStatusAmount.test.js uses.
|
|
||||||
// The vault is mocked so that no password has to be hashed.
|
|
||||||
|
|
||||||
jest.mock("../src/shared/vault", () => ({
|
|
||||||
decryptWithPassword: jest.fn(),
|
|
||||||
}));
|
|
||||||
|
|
||||||
globalThis.chrome = {
|
|
||||||
storage: { local: { get: async () => ({}), set: async () => {} } },
|
|
||||||
};
|
|
||||||
|
|
||||||
const { Interface } = require("ethers");
|
|
||||||
const { state } = require("../src/shared/state");
|
|
||||||
const { NETWORKS } = require("../src/shared/networks");
|
|
||||||
const { decryptWithPassword } = require("../src/shared/vault");
|
|
||||||
const approval = require("../src/popup/views/approval");
|
|
||||||
const txStatus = require("../src/popup/views/txStatus");
|
|
||||||
|
|
||||||
// A well-known test phrase, and its first address.
|
|
||||||
const PHRASE = "test test test test test test test test test test test junk";
|
|
||||||
const FROM = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266";
|
|
||||||
const RECIPIENT = "0x711e691962E5c054b78071C789A0C8CE657377aD";
|
|
||||||
// A token the bundled token list does not name.
|
|
||||||
const TOKEN = "0x74de5d4FCbf63E00296fd95d33236B9794016631";
|
|
||||||
|
|
||||||
// FROM sends 1234.5 of the 6-decimal token to RECIPIENT.
|
|
||||||
const TRANSFER_DATA = new Interface([
|
|
||||||
"function transfer(address to, uint256 amount)",
|
|
||||||
]).encodeFunctionData("transfer", [RECIPIENT, 1234500000n]);
|
|
||||||
|
|
||||||
function makeElement(id) {
|
|
||||||
const classes = new Set();
|
|
||||||
const el = {
|
|
||||||
id,
|
|
||||||
textContent: "",
|
|
||||||
value: "",
|
|
||||||
innerHTML: "",
|
|
||||||
disabled: false,
|
|
||||||
style: {},
|
|
||||||
dataset: {},
|
|
||||||
listeners: {},
|
|
||||||
classList: {
|
|
||||||
add: (...names) => names.forEach((n) => classes.add(n)),
|
|
||||||
remove: (...names) => names.forEach((n) => classes.delete(n)),
|
|
||||||
contains: (n) => classes.has(n),
|
|
||||||
toggle: (n, force) => {
|
|
||||||
const on = force === undefined ? !classes.has(n) : force;
|
|
||||||
if (on) classes.add(n);
|
|
||||||
else classes.delete(n);
|
|
||||||
return on;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
addEventListener: (name, fn) => {
|
|
||||||
el.listeners[name] = el.listeners[name] || [];
|
|
||||||
el.listeners[name].push(fn);
|
|
||||||
},
|
|
||||||
querySelectorAll: () => [],
|
|
||||||
appendChild: () => {},
|
|
||||||
};
|
|
||||||
// Views reach for .parentElement to hide whole sections.
|
|
||||||
Object.defineProperty(el, "parentElement", {
|
|
||||||
get: () => node(id + "-parent"),
|
|
||||||
});
|
|
||||||
return el;
|
|
||||||
}
|
|
||||||
|
|
||||||
function makeDocument() {
|
|
||||||
const els = new Map();
|
|
||||||
return {
|
|
||||||
getElementById(id) {
|
|
||||||
// The debug banner is created on demand by helpers.js; absent
|
|
||||||
// is the state a non-debug, non-testnet popup is in.
|
|
||||||
if (id === "debug-banner") return null;
|
|
||||||
if (!els.has(id)) els.set(id, makeElement(id));
|
|
||||||
return els.get(id);
|
|
||||||
},
|
|
||||||
createElement: () => makeElement("created"),
|
|
||||||
body: { prepend: () => {} },
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function node(id) {
|
|
||||||
return globalThis.document.getElementById(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
function click(id) {
|
|
||||||
return Promise.all((node(id).listeners.click || []).map((fn) => fn()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// One line of the decoded details: its label and what is drawn under it.
|
|
||||||
function decodedLine(label) {
|
|
||||||
return node("approve-tx-decoded-details")
|
|
||||||
.innerHTML.split('<div class="mb-2">')
|
|
||||||
.find((line) =>
|
|
||||||
line.startsWith(`<div class="text-muted">${label}</div>`),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Open the approval screen, with Ethereum Mainnet active and FROM holding
|
|
||||||
// `tokenBalances` there, for the site's Sepolia transaction sending
|
|
||||||
// TRANSFER_DATA to the token. Confirm answers the signed transaction with a
|
|
||||||
// failure, which leads to the error screen.
|
|
||||||
async function showSepoliaTransfer(tokenBalances = []) {
|
|
||||||
globalThis.document = makeDocument();
|
|
||||||
globalThis.window = { location: { search: "" }, close: () => {} };
|
|
||||||
globalThis.chrome.runtime = {
|
|
||||||
connect: () => ({ postMessage: () => {} }),
|
|
||||||
sendMessage: (msg, reply) => {
|
|
||||||
if (!reply) return;
|
|
||||||
if (msg.type !== "AUTISTMASK_GET_APPROVAL") {
|
|
||||||
return reply({
|
|
||||||
error: "The transaction could not be sent.",
|
|
||||||
retryable: false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
reply({
|
|
||||||
type: "tx",
|
|
||||||
origin: "https://dapp.example",
|
|
||||||
isPhishingDomain: false,
|
|
||||||
approvedFrom: FROM,
|
|
||||||
approvedTx: {
|
|
||||||
type: 2,
|
|
||||||
from: FROM,
|
|
||||||
chainId: NETWORKS.sepolia.chainId,
|
|
||||||
nonce: "0x7",
|
|
||||||
gasLimit: "0x30d40",
|
|
||||||
maxPriorityFeePerGas: "0x3b9aca00",
|
|
||||||
maxFeePerGas: "0x77359400",
|
|
||||||
to: TOKEN,
|
|
||||||
value: "0x0",
|
|
||||||
data: TRANSFER_DATA,
|
|
||||||
accessList: [],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
state.networkId = "mainnet";
|
|
||||||
state.activeAddress = FROM;
|
|
||||||
state.wallets = [
|
|
||||||
{
|
|
||||||
type: "hd",
|
|
||||||
name: "Wallet 1",
|
|
||||||
xpub: "xpub-wallet-1",
|
|
||||||
encryptedSecret: "encrypted-secret-1",
|
|
||||||
nextIndex: 1,
|
|
||||||
addresses: [
|
|
||||||
{ address: FROM, index: 0, balance: "0", tokenBalances },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
state.currentView = null;
|
|
||||||
txStatus.init({ doRefreshAndRender: () => {} });
|
|
||||||
approval.init({});
|
|
||||||
await approval.show(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function confirm() {
|
|
||||||
decryptWithPassword.mockResolvedValue(PHRASE);
|
|
||||||
node("approve-tx-password").value = "any password";
|
|
||||||
await click("btn-approve-tx");
|
|
||||||
}
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
state.trackedTokens = [];
|
|
||||||
state.networkTrackedTokens = {};
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("a token tracked on Sepolia only", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
state.networkTrackedTokens = {
|
|
||||||
sepolia: [{ address: TOKEN, symbol: "TDOL", decimals: 6 }],
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
test("is named on the Contract line and the decoded Token line, with its amount at its scale", async () => {
|
|
||||||
await showSepoliaTransfer();
|
|
||||||
expect(node("approve-tx-to").innerHTML).toContain(
|
|
||||||
'<div class="font-bold mb-1">TDOL</div>',
|
|
||||||
);
|
|
||||||
expect(decodedLine("Token")).toContain(
|
|
||||||
'<div class="font-bold">TDOL</div>',
|
|
||||||
);
|
|
||||||
expect(decodedLine("Amount")).toContain(
|
|
||||||
'<div class="font-bold">1234.5000 TDOL</div>',
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("labels the amount on the error screen that follows", async () => {
|
|
||||||
await showSepoliaTransfer();
|
|
||||||
await confirm();
|
|
||||||
expect(state.currentView).toBe("error-tx");
|
|
||||||
expect(node("error-tx-summary").textContent).toBe("1234.5000 TDOL");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// The same contract address on mainnet is another token, so what the wallet
|
|
||||||
// tracks and holds there names nothing on Sepolia.
|
|
||||||
test("a token tracked and held on mainnet only is not named, and its amount is in base units", async () => {
|
|
||||||
state.trackedTokens = [{ address: TOKEN, symbol: "MDOL", decimals: 18 }];
|
|
||||||
await showSepoliaTransfer([
|
|
||||||
{ address: TOKEN, symbol: "MDOL", decimals: 18, balance: "1.0" },
|
|
||||||
]);
|
|
||||||
expect(node("approve-tx-to").innerHTML).not.toContain("MDOL");
|
|
||||||
expect(decodedLine("Token")).toContain(
|
|
||||||
'<div class="font-bold">Unknown token</div>',
|
|
||||||
);
|
|
||||||
expect(decodedLine("Amount")).toContain(
|
|
||||||
'<div class="font-bold">1234500000 base units (decimals unknown)</div>',
|
|
||||||
);
|
|
||||||
});
|
|
||||||
@@ -12,9 +12,7 @@
|
|||||||
// whichever network is active
|
// whichever network is active
|
||||||
// (https://git.eeqj.de/sneak/AutistMask/issues/584), and the transaction is
|
// (https://git.eeqj.de/sneak/AutistMask/issues/584), and the transaction is
|
||||||
// read from that network's Blockscout, the one Settings has for it
|
// read from that network's Blockscout, the one Settings has for it
|
||||||
// (https://git.eeqj.de/sneak/AutistMask/issues/623). Its decoded details name
|
// (https://git.eeqj.de/sneak/AutistMask/issues/623).
|
||||||
// the token, and scale its amount, from what the wallet knows of that
|
|
||||||
// network's tokens (https://git.eeqj.de/sneak/AutistMask/issues/629).
|
|
||||||
//
|
//
|
||||||
// The row is the one the real history parser makes from the explorer's answer,
|
// The row is the one the real history parser makes from the explorer's answer,
|
||||||
// opened through transactionDetail.show(), which is what a click on the row
|
// opened through transactionDetail.show(), which is what a click on the row
|
||||||
@@ -167,7 +165,6 @@ beforeEach(() => {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
state.trackedTokens = [{ address: TOKEN, symbol: "TDOL", decimals: 6 }];
|
state.trackedTokens = [{ address: TOKEN, symbol: "TDOL", decimals: 6 }];
|
||||||
state.networkTrackedTokens = {};
|
|
||||||
state.viewData = {};
|
state.viewData = {};
|
||||||
state.viewStack = [];
|
state.viewStack = [];
|
||||||
state.currentView = null;
|
state.currentView = null;
|
||||||
@@ -248,46 +245,3 @@ test("with mainnet active and no Blockscout remembered for Sepolia, a Sepolia tr
|
|||||||
NETWORKS.sepolia.defaultBlockscoutUrl + "/transactions/" + TX_HASH,
|
NETWORKS.sepolia.defaultBlockscoutUrl + "/transactions/" + TX_HASH,
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Open the Sepolia transfer's detail screen with mainnet active, and wait for
|
|
||||||
// its decoded details.
|
|
||||||
async function showSepoliaTransferWithMainnetActive() {
|
|
||||||
state.networkId = "mainnet";
|
|
||||||
state.blockscoutUrl = OTHER_EXPLORER;
|
|
||||||
state.networkEndpoints = { sepolia: { blockscoutUrl: EXPLORER } };
|
|
||||||
const [row] = await fetchRecentTransactions(
|
|
||||||
HOLDER,
|
|
||||||
EXPLORER,
|
|
||||||
NETWORKS.sepolia.chainId,
|
|
||||||
);
|
|
||||||
transactionDetail.show(row);
|
|
||||||
for (let i = 0; i < 10; i++) await new Promise((r) => setTimeout(r, 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
test("with mainnet active, a Sepolia token transfer's decoded details name the token tracked on Sepolia and its amount at its scale", async () => {
|
|
||||||
state.trackedTokens = [];
|
|
||||||
state.networkTrackedTokens = {
|
|
||||||
sepolia: [{ address: TOKEN, symbol: "TDOL", decimals: 6 }],
|
|
||||||
};
|
|
||||||
await showSepoliaTransferWithMainnetActive();
|
|
||||||
|
|
||||||
expect(decodedLine("Token")).toContain('<div class="font-bold">TDOL</div>');
|
|
||||||
expect(decodedLine("Amount")).toContain(
|
|
||||||
'<div class="font-bold">1234.5000 TDOL</div>',
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
// The same contract address on mainnet is another token, so what the wallet
|
|
||||||
// tracks and holds there names nothing on Sepolia.
|
|
||||||
test("with mainnet active, a Sepolia token transfer's decoded details take nothing from mainnet's tracked tokens or balances", async () => {
|
|
||||||
state.trackedTokens = [{ address: TOKEN, symbol: "MDOL", decimals: 18 }];
|
|
||||||
state.wallets[0].addresses[0].tokenBalances = [
|
|
||||||
{ address: TOKEN, symbol: "MDOL", decimals: 18, balance: "1.0" },
|
|
||||||
];
|
|
||||||
await showSepoliaTransferWithMainnetActive();
|
|
||||||
|
|
||||||
expect(decodedLine("Token")).not.toContain("MDOL");
|
|
||||||
expect(decodedLine("Amount")).toContain(
|
|
||||||
'<div class="font-bold">1234500000 base units (decimals unknown)</div>',
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -150,8 +150,6 @@ beforeEach(() => {
|
|||||||
state.rpcUrl = NETWORKS.mainnet.defaultRpcUrl;
|
state.rpcUrl = NETWORKS.mainnet.defaultRpcUrl;
|
||||||
state.networkEndpoints = {};
|
state.networkEndpoints = {};
|
||||||
state.wallets = [];
|
state.wallets = [];
|
||||||
state.trackedTokens = [];
|
|
||||||
state.networkTrackedTokens = {};
|
|
||||||
state.viewData = {};
|
state.viewData = {};
|
||||||
state.viewStack = [];
|
state.viewStack = [];
|
||||||
state.currentView = null;
|
state.currentView = null;
|
||||||
@@ -629,31 +627,6 @@ describe("SuccessTx and ErrorTx for a Sepolia transaction with mainnet active",
|
|||||||
expectSepoliaLinks();
|
expectSepoliaLinks();
|
||||||
});
|
});
|
||||||
|
|
||||||
// https://git.eeqj.de/sneak/AutistMask/issues/629
|
|
||||||
test("SuccessTx's decoded action names the token from the tokens tracked on Sepolia", async () => {
|
|
||||||
function expectTokenNamed() {
|
|
||||||
expect(getElement("success-tx-decoded").innerHTML).toContain(
|
|
||||||
'<div class="font-bold">TDOL</div>',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
state.networkTrackedTokens = {
|
|
||||||
sepolia: [{ address: TOKEN, symbol: "TDOL", decimals: 6 }],
|
|
||||||
};
|
|
||||||
const decoded = {
|
|
||||||
name: "Transfer",
|
|
||||||
details: [{ label: "Token", address: TOKEN, isToken: true }],
|
|
||||||
};
|
|
||||||
mockReceiptLookup.mockResolvedValue({ blockNumber: 21000000 });
|
|
||||||
txStatus.showWait({ ...sepoliaTx, decoded }, TX_HASH);
|
|
||||||
await jest.advanceTimersByTimeAsync(10000);
|
|
||||||
expect(state.currentView).toBe("success-tx");
|
|
||||||
expectTokenNamed();
|
|
||||||
|
|
||||||
elements.clear();
|
|
||||||
txStatus.renderSuccess();
|
|
||||||
expectTokenNamed();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("the recipient and hash on ErrorTx link to the Sepolia explorer", () => {
|
test("the recipient and hash on ErrorTx link to the Sepolia explorer", () => {
|
||||||
function expectSepoliaLinks() {
|
function expectSepoliaLinks() {
|
||||||
expect(getElement("error-tx-to").innerHTML).toContain(
|
expect(getElement("error-tx-to").innerHTML).toContain(
|
||||||
|
|||||||
Reference in New Issue
Block a user