fix: version stored state, validate its shape, and give a corrupt blob a way out (closes #311)
Stored state had no version and no structural validation, so a corrupt blob produced a completely blank popup with no message and no recovery control, and made every dApp RPC call from every page answer a generic -32603. There was no reset or wipe control anywhere in the UI. saveState() now stamps a schema version and loadState() validates the shape. A version it does not understand, or a wallets array it cannot parse, lands on a recovery screen that names the problem, offers the stored record verbatim for export, and offers a destructive reset behind a typed confirmation. Unversioned but valid state -- which every existing install has -- migrates in place and keeps working; it is never shown a wipe prompt. A dApp call against unusable state answers -32007, which EIP-1474 leaves unassigned, rather than -32603. networkById() refuses an unknown id loudly instead of returning mainnet, and networkId is validated so a corrupt value cannot be used as an object key. Fields the gate does not refuse are floored by type, container and entries both: a malformed trackedTokens or tokenBalances entry is dropped rather than dereferenced. Verified by an independent sweep of 1152 corrupt blobs producing no blank popup, with the same harness showing 9 blanks against the previous revision.
This commit was merged in pull request #360.
This commit is contained in:
@@ -31,8 +31,42 @@ const SUPPORTED_CHAIN_IDS = new Set(
|
||||
Object.values(NETWORKS).map((n) => n.chainId),
|
||||
);
|
||||
|
||||
// Thrown rather than defaulted. An id this build does not know used to answer
|
||||
// with MAINNET, so a stored `{networkId:"base"}` rendered the selector as
|
||||
// Ethereum Mainnet with no banner and answered eth_chainId 0x1, while rpcUrl
|
||||
// still pointed at Base — the wallet telling the user and the page one chain
|
||||
// while transacting on another. Nothing in this codebase has an unknown id to
|
||||
// offer: stored state is validated against this table before it is loaded
|
||||
// (src/shared/stateSchema.js), and every other caller passes an id it took
|
||||
// from here. So an unknown id is a defect, and it says so, the same way
|
||||
// getProvider() (src/shared/balances.js) already refuses one.
|
||||
class UnknownNetworkError extends Error {
|
||||
constructor(id) {
|
||||
super(
|
||||
"AutistMask does not know the network " +
|
||||
JSON.stringify(id) +
|
||||
"; it supports " +
|
||||
Object.keys(NETWORKS).join(", "),
|
||||
);
|
||||
this.name = "UnknownNetworkError";
|
||||
this.networkId = id;
|
||||
}
|
||||
}
|
||||
|
||||
// Own properties only: NETWORKS inherits from Object.prototype, so
|
||||
// NETWORKS["constructor"] and NETWORKS["__proto__"] both answer with something
|
||||
// truthy that is not a network. A stored id is untrusted input, and this is
|
||||
// the test the validator uses to decide whether it may be adopted at all.
|
||||
function isKnownNetworkId(id) {
|
||||
return (
|
||||
typeof id === "string" &&
|
||||
Object.prototype.hasOwnProperty.call(NETWORKS, id)
|
||||
);
|
||||
}
|
||||
|
||||
function networkById(id) {
|
||||
return NETWORKS[id] || NETWORKS.mainnet;
|
||||
if (!isKnownNetworkId(id)) throw new UnknownNetworkError(id);
|
||||
return NETWORKS[id];
|
||||
}
|
||||
|
||||
function networkByChainId(chainId) {
|
||||
@@ -51,6 +85,8 @@ function explorerLink(network, type, value) {
|
||||
module.exports = {
|
||||
NETWORKS,
|
||||
SUPPORTED_CHAIN_IDS,
|
||||
UnknownNetworkError,
|
||||
isKnownNetworkId,
|
||||
networkById,
|
||||
networkByChainId,
|
||||
explorerLink,
|
||||
|
||||
Reference in New Issue
Block a user