test: drive the EIP-1193 dApp approval round trips in the browser (closes #183)
All checks were successful
check / check (push) Successful in 29s
All checks were successful
check / check (push) Successful in 29s
The dApp signing path was the largest unverified surface in the milestone: the only place where the content script, the inpage provider, the background worker and the popup all have to work together, with unit tests covering each side in isolation and none covering the seam. A page served by the harness speaks EIP-1193 to the real provider -- asserted by EIP-6963 object identity, not by shape -- and eth_requestAccounts, personal_sign, eth_signTypedData_v4 and eth_sendTransaction are each driven through to approval and to rejection. Every signature is recovered and compared to the approved address; the broadcast transaction is parsed from the bytes captured at eth_sendRawTransaction and checked for signer, recipient, value, calldata and chain. A signature that merely came back would pass against a wrong key, a wrong message or a wrong chain, so each assertion was demonstrated failing against a variant that is wrong in exactly one of those ways. The password is asserted absent from every message crossing the extension boundary, which gives #157's fix a permanent floor rather than a one-time review. Two defects this surfaced are tracked separately: EIP-1193 error codes never reach the page (#274), and approving a site connection races the popup teardown (#275). Neither is asserted as correct here. A real dApp with real funds against mainnet remains an uncovered human pass and is documented as such.
This commit was merged in pull request #273.
This commit is contained in:
28
README.md
28
README.md
@@ -169,6 +169,34 @@ reserve while sitting on the same side of the estimate, so swapping the two in
|
||||
what [#154](https://git.eeqj.de/sneak/AutistMask/issues/154) was, and it was
|
||||
previously correct by reading only.
|
||||
|
||||
It also covers the **dApp approval round trips** — the one place where the
|
||||
content script, the inpage provider, the background worker and the approval
|
||||
popup all have to work together. A local test page is served by the route
|
||||
handler on a reserved-TLD origin, gets `window.ethereum` from the shipped
|
||||
`MAIN`-world content script like any other page, and drives
|
||||
`eth_requestAccounts`, `personal_sign`, `eth_signTypedData_v4` and
|
||||
`eth_sendTransaction` through the real prompts. Every signature is recovered in
|
||||
the runner and compared against the active address, the transaction assertions
|
||||
run against the raw signed transaction captured at `eth_sendRawTransaction`
|
||||
rather than against anything the extension reported, rejecting each prompt is
|
||||
required to return a rejection to the page rather than hang or resolve, and the
|
||||
password is required to be absent from every message the approval window sends
|
||||
to the background — with the message that would carry it required to be present,
|
||||
so that check cannot pass by observing nothing. That last one is the standing
|
||||
floor under [#157](https://git.eeqj.de/sneak/AutistMask/issues/157).
|
||||
|
||||
Three limits of that coverage, none of them papered over. The RPC is stubbed
|
||||
throughout, so this is **not** a real dApp against a real network with real
|
||||
funds; that remains a human pass before 1.0.0. The site-connection prompt is
|
||||
raised through `chrome.action.openPopup()`, and headless Chromium's
|
||||
browser-action popup is not a page Playwright can see or click, so that one
|
||||
prompt is driven at the URL the extension itself puts on the action — the same
|
||||
page and the same approval id, but whether a real toolbar click shows it is not
|
||||
observable here. And the EIP-1193 error code does not survive the last hop: the
|
||||
rejection that crosses the boundary carries code 4001 and is asserted to, but
|
||||
`src/content/inpage.js` rebuilds it as `new Error(message)`, so the calling page
|
||||
catches an error with no `code` property.
|
||||
|
||||
Any test that drives a failure path on purpose declares the `console.error` it
|
||||
is about to provoke, via `errors.expect()`. That is not a mute: the declaration
|
||||
consumes exactly one matching record, and a declaration nothing matched fails
|
||||
|
||||
Reference in New Issue
Block a user