Compare commits
1
Commits
next
...
a388a66e97
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a388a66e97 |
@@ -2387,7 +2387,9 @@ view would leave a wallet one click from deletion.
|
|||||||
- Contract: color dot + full address + etherscan link, token symbol label if
|
- Contract: color dot + full address + etherscan link, token symbol label if
|
||||||
known; for a contract creation, the same sentence as on WaitTx
|
known; for a contract creation, the same sentence as on WaitTx
|
||||||
- Value: amount in ETH (4 decimal places with the nonzero floor, USD in
|
- Value: amount in ETH (4 decimal places with the nonzero floor, USD in
|
||||||
parentheses)
|
parentheses), the USD value of the exact amount at the price on the
|
||||||
|
network the transaction was populated on, as on ConfirmTx: none for a
|
||||||
|
testnet transaction or one on a chain id that names no network
|
||||||
- Network fee (max): gas limit × fee per gas in ETH (4 decimal places with
|
- Network fee (max): gas limit × fee per gas in ETH (4 decimal places with
|
||||||
the nonzero floor, USD in parentheses), with the gas limit and the fee per
|
the nonzero floor, USD in parentheses), with the gas limit and the fee per
|
||||||
gas in gwei below it
|
gas in gwei below it
|
||||||
|
|||||||
@@ -367,11 +367,12 @@ function showTxApproval(details) {
|
|||||||
$("approve-tx-to").innerHTML = escapeHtml(CONTRACT_CREATION_TEXT);
|
$("approve-tx-to").innerHTML = escapeHtml(CONTRACT_CREATION_TEXT);
|
||||||
}
|
}
|
||||||
|
|
||||||
const ethValueFormatted = formatTxValue(
|
const ethValueFormatted = formatTxValue(ethValue);
|
||||||
formatEther(approvedTx.value || "0"),
|
// Priced from the exact value, not the four-decimal figure, at the price
|
||||||
);
|
// on the network the transaction is for, as the confirmation screen prices
|
||||||
const ethPrice = getPrice("ETH");
|
// its amount, so the same value reads the same on both.
|
||||||
const ethUsd = ethPrice ? parseFloat(ethValueFormatted) * ethPrice : null;
|
const ethPrice = getPrice("ETH", approvedTx.chainId);
|
||||||
|
const ethUsd = ethPrice ? parseFloat(ethValue) * ethPrice : null;
|
||||||
const usdStr = formatUsd(ethUsd);
|
const usdStr = formatUsd(ethUsd);
|
||||||
// In the native currency of the network the transaction is for, which the
|
// In the native currency of the network the transaction is for, which the
|
||||||
// Network line names, not the active network's: the active network can
|
// Network line names, not the active network's: the active network can
|
||||||
|
|||||||
@@ -38,11 +38,12 @@ function clearPrices() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Return the USD price for a symbol on the network with `chainId`, or on the
|
// Return the USD price for a symbol on the network with `chainId`, or on the
|
||||||
// active network when no chain id is given; null on testnet / unknown.
|
// active network when no chain id is given; null on testnet, on a chain id
|
||||||
|
// that names no network, or for a symbol with no price.
|
||||||
function getPrice(symbol, chainId) {
|
function getPrice(symbol, chainId) {
|
||||||
const { currentNetwork } = require("./state");
|
const { currentNetwork } = require("./state");
|
||||||
const network = chainId ? networkByChainId(chainId) : currentNetwork();
|
const network = chainId ? networkByChainId(chainId) : currentNetwork();
|
||||||
if (network.isTestnet) return null;
|
if (!network || network.isTestnet) return null;
|
||||||
return prices[symbol] || null;
|
return prices[symbol] || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,170 @@
|
|||||||
|
// The USD value on the approval screen's Value line, for a site's ETH send.
|
||||||
|
//
|
||||||
|
// It was worked out from the four-decimal figure the line shows, at the price
|
||||||
|
// on the active network, so the same value read a few cents different here than
|
||||||
|
// on the confirmation screen, which prices the exact amount at the price on the
|
||||||
|
// transaction's own network, and a transaction on another network than the
|
||||||
|
// active one was priced at the active one's rate
|
||||||
|
// (https://git.eeqj.de/sneak/AutistMask/issues/614).
|
||||||
|
//
|
||||||
|
// Driven through the real approval screen, against a minimal DOM stub in the
|
||||||
|
// shape tests/approvalTokenNetwork.test.js uses.
|
||||||
|
|
||||||
|
globalThis.chrome = {
|
||||||
|
storage: { local: { get: async () => ({}), set: async () => {} } },
|
||||||
|
};
|
||||||
|
|
||||||
|
const { state } = require("../src/shared/state");
|
||||||
|
const { NETWORKS } = require("../src/shared/networks");
|
||||||
|
const { prices, clearPrices } = require("../src/shared/prices");
|
||||||
|
const approval = require("../src/popup/views/approval");
|
||||||
|
|
||||||
|
const FROM = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266";
|
||||||
|
const RECIPIENT = "0x711e691962E5c054b78071C789A0C8CE657377aD";
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open the approval screen, with the network `networkId` active, for the
|
||||||
|
// site's transaction sending 1.23414 ETH to RECIPIENT on the chain `chainId`.
|
||||||
|
async function showEthSend(chainId, networkId) {
|
||||||
|
globalThis.document = makeDocument();
|
||||||
|
globalThis.window = { location: { search: "" }, close: () => {} };
|
||||||
|
globalThis.chrome.runtime = {
|
||||||
|
connect: () => ({ postMessage: () => {} }),
|
||||||
|
sendMessage: (msg, reply) => {
|
||||||
|
if (!reply) return;
|
||||||
|
reply({
|
||||||
|
type: "tx",
|
||||||
|
origin: "https://dapp.example",
|
||||||
|
isPhishingDomain: false,
|
||||||
|
approvedFrom: FROM,
|
||||||
|
approvedTx: {
|
||||||
|
type: 2,
|
||||||
|
from: FROM,
|
||||||
|
chainId,
|
||||||
|
nonce: "0x7",
|
||||||
|
gasLimit: "0x5208",
|
||||||
|
maxPriorityFeePerGas: "0x3b9aca00",
|
||||||
|
maxFeePerGas: "0x77359400",
|
||||||
|
to: RECIPIENT,
|
||||||
|
value: "1234140000000000000",
|
||||||
|
data: "0x",
|
||||||
|
accessList: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
state.networkId = networkId;
|
||||||
|
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" }],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
state.trackedTokens = [];
|
||||||
|
state.networkTrackedTokens = {};
|
||||||
|
state.currentView = null;
|
||||||
|
approval.init({});
|
||||||
|
await approval.show(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
prices.ETH = 2000;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
clearPrices();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 1.23414 ETH reads 1.2341 ETH. At $2,000 the exact value is $2,468.28, the
|
||||||
|
// figure the confirmation screen shows for it; the shown figure would be
|
||||||
|
// $2,468.20.
|
||||||
|
test.each([
|
||||||
|
[
|
||||||
|
"a mainnet transaction is priced from its exact value",
|
||||||
|
NETWORKS.mainnet.chainId,
|
||||||
|
"mainnet",
|
||||||
|
"1.2341 ETH ($2,468.28)",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"a mainnet transaction shown with Sepolia active is priced at the mainnet price",
|
||||||
|
NETWORKS.mainnet.chainId,
|
||||||
|
"sepolia",
|
||||||
|
"1.2341 ETH ($2,468.28)",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"a Sepolia transaction shown with mainnet active has no USD value",
|
||||||
|
NETWORKS.sepolia.chainId,
|
||||||
|
"mainnet",
|
||||||
|
"1.2341 SepoliaETH",
|
||||||
|
],
|
||||||
|
// Chain id 1337 names no network here, so nothing knows its price.
|
||||||
|
[
|
||||||
|
"a transaction on a chain id that names no network has no USD value",
|
||||||
|
"0x539",
|
||||||
|
"mainnet",
|
||||||
|
"1.2341 ETH",
|
||||||
|
],
|
||||||
|
])("%s", async (_label, chainId, networkId, expected) => {
|
||||||
|
await showEthSend(chainId, networkId);
|
||||||
|
expect(state.currentView).toBe("approve-tx");
|
||||||
|
expect(node("approve-tx-value").textContent).toBe(expected);
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user