From 515453004c5406c4a7b719268a444088cf75e9d2 Mon Sep 17 00:00:00 2001 From: Zarutian Date: Thu, 21 Jan 2021 23:12:56 +0000 Subject: [PATCH] chore: fix authority leak in wallet by adding attenuatedPursesNotifier (#2218) chore: fix authority leak in wallet by adding attenuatedPursesNotifier (#2218) --- .../dapp-svelte-wallet/api/src/lib-wallet.js | 58 ++++++++- packages/dapp-svelte-wallet/api/src/types.js | 2 +- packages/dapp-svelte-wallet/api/src/wallet.js | 4 +- .../api/test/test-getPursesNotifier.js | 117 ++++++++++++++++++ 4 files changed, 174 insertions(+), 7 deletions(-) create mode 100644 packages/dapp-svelte-wallet/api/test/test-getPursesNotifier.js diff --git a/packages/dapp-svelte-wallet/api/src/lib-wallet.js b/packages/dapp-svelte-wallet/api/src/lib-wallet.js index 42be8cd4c32..b157ab901fc 100644 --- a/packages/dapp-svelte-wallet/api/src/lib-wallet.js +++ b/packages/dapp-svelte-wallet/api/src/lib-wallet.js @@ -156,10 +156,57 @@ export function makeWallet({ updater: offersUpdater, } = makeNotifierKit(); - /** @type {NotifierRecord} */ - const { notifier: pursesNotifier, updater: pursesUpdater } = makeNotifierKit( - [], - ); + const { pursesNotifier, attenuatedPursesNotifier, pursesUpdater } = (() => { + /** @type {NotifierRecord} */ + const { notifier: ipn, updater: ipu } = makeNotifierKit([]); + /** @type {NotifierRecord} */ + const { notifier: apn, updater: apu } = makeNotifierKit([]); + // explicit whitelist + /** + * @param {PursesFullState} _ + * @returns {PursesJSONState} + */ + const innerFilter = ({ + brandBoardId, + depositBoardId, + brandPetname, + pursePetname, + displayInfo, + value, + currentAmountSlots, + currentAmount, + }) => + harden({ + brandBoardId, + ...(depositBoardId && { depositBoardId }), + brandPetname, + pursePetname, + ...(displayInfo && { displayInfo }), + value, + currentAmountSlots, + currentAmount, + }); + const filter = state => state.map(innerFilter); + const pu = harden({ + updateState: newState => { + ipu.updateState(newState); + apu.updateState(filter(newState)); + }, + finish: finalState => { + ipu.finish(finalState); + apu.finish(filter(finalState)); + }, + fail: reason => { + ipu.fail(reason); + apu.fail(reason); + }, + }); + return harden({ + pursesNotifier: ipn, + attenuatedPursesNotifier: apn, + pursesUpdater: pu, + }); + })(); /** * @param {Petname} pursePetname @@ -1333,6 +1380,9 @@ export function makeWallet({ getPursesNotifier() { return pursesNotifier; }, + getAttenuatedPursesNotifier() { + return attenuatedPursesNotifier; + }, getIssuersNotifier() { return issuersNotifier; }, diff --git a/packages/dapp-svelte-wallet/api/src/types.js b/packages/dapp-svelte-wallet/api/src/types.js index 4c4dcc4003d..d08ff34373e 100644 --- a/packages/dapp-svelte-wallet/api/src/types.js +++ b/packages/dapp-svelte-wallet/api/src/types.js @@ -62,7 +62,7 @@ * addOfferInvitation add an invitation to the specified offer * @property {(brandBoardId: string) => Promise} getDepositFacetId * return the board ID to use to receive payments of the specified brand. - * @property {() => Promise>>} getPursesNotifier + * @property {() => Promise>>} getPursesNotifier * Follow changes to the purses. * @property {() => Promise>>} getOffersNotifier * Follow changes to the offers. diff --git a/packages/dapp-svelte-wallet/api/src/wallet.js b/packages/dapp-svelte-wallet/api/src/wallet.js index 51fec7ea276..a4a53200e44 100644 --- a/packages/dapp-svelte-wallet/api/src/wallet.js +++ b/packages/dapp-svelte-wallet/api/src/wallet.js @@ -109,7 +109,7 @@ export function buildRootObject(_vatPowers) { const bridge = { async getPursesNotifier() { await approve(); - const pursesNotifier = walletAdmin.getPursesNotifier(); + const pursesNotifier = walletAdmin.getAttenuatedPursesNotifier(); const { notifier, updater } = makeNotifierKit(); observeIteration(makeApprovedNotifier(pursesNotifier), updater); return notifier; @@ -206,7 +206,7 @@ export function buildRootObject(_vatPowers) { return walletAdmin.getOffersNotifier(); }, async getPursesNotifier() { - return walletAdmin.getPursesNotifier(); + return walletAdmin.getAttenuatedPursesNotifier(); }, suggestInstallation(petname, installationBoardId) { return walletAdmin.suggestInstallation(petname, installationBoardId); diff --git a/packages/dapp-svelte-wallet/api/test/test-getPursesNotifier.js b/packages/dapp-svelte-wallet/api/test/test-getPursesNotifier.js new file mode 100644 index 00000000000..4b50d99fa4d --- /dev/null +++ b/packages/dapp-svelte-wallet/api/test/test-getPursesNotifier.js @@ -0,0 +1,117 @@ +// @ts-check +// eslint-disable-next-line import/no-extraneous-dependencies +import '@agoric/install-ses'; // calls lockdown() +// eslint-disable-next-line import/no-extraneous-dependencies +import test from 'ava'; + +import { makeIssuerKit } from '@agoric/ertp'; +import { makeZoe } from '@agoric/zoe'; +import fakeVatAdmin from '@agoric/zoe/src/contractFacet/fakeVatAdmin'; +// eslint-disable-next-line import/no-extraneous-dependencies +import { makeBoard } from '@agoric/cosmic-swingset/lib/ag-solo/vats/lib-board'; +import { makeWallet } from '../src/lib-wallet'; + +import '../src/types'; + +const setup = async () => { + const zoe = makeZoe(fakeVatAdmin); + const board = makeBoard(); + + const pursesStateChangeHandler = _data => {}; + const inboxStateChangeHandler = _data => {}; + + const { admin: wallet, initialized } = makeWallet({ + zoe, + board, + pursesStateChangeHandler, + inboxStateChangeHandler, + }); + await initialized; + const MOOLA_ISSUER_PETNAME = 'moola'; + const moolaKit = makeIssuerKit(MOOLA_ISSUER_PETNAME); + + const MOOLA_PURSE_PETNAME = 'fun money'; + + const issuerManager = wallet.getIssuerManager(); + await issuerManager.add(MOOLA_ISSUER_PETNAME, moolaKit.issuer); + await wallet.makeEmptyPurse(MOOLA_ISSUER_PETNAME, MOOLA_PURSE_PETNAME); + return { wallet, moolaKit, MOOLA_ISSUER_PETNAME, MOOLA_PURSE_PETNAME }; +}; + +test('getPursesNotifier', async t => { + const { + wallet, + moolaKit, + MOOLA_ISSUER_PETNAME, + MOOLA_PURSE_PETNAME, + } = await setup(); + const pursesNotifier = wallet.getPursesNotifier(); + const update = await pursesNotifier.getUpdateSince(); + t.is(update.updateCount, 7); + // Has the default Zoe invitation purse and a moola purse + t.is(update.value.length, 2); + const moolaPurseInfo = update.value[1]; + t.truthy(moolaPurseInfo.actions); + t.is(moolaPurseInfo.brand, moolaKit.brand); + t.is(moolaPurseInfo.brandBoardId, '1532665031'); + t.is(moolaPurseInfo.brandPetname, MOOLA_ISSUER_PETNAME); + t.deepEqual(moolaPurseInfo.currentAmount, { + brand: { kind: 'brand', petname: 'moola' }, // not a real amount + value: 0, + }); + t.deepEqual(moolaPurseInfo.currentAmountSlots, { + body: + '{"brand":{"@qclass":"slot","iface":"Alleged: moola brand","index":0},"value":0}', + slots: [ + { + kind: 'brand', + petname: 'moola', + }, + ], + }); + t.deepEqual(moolaPurseInfo.displayInfo, { + amountMathKind: 'nat', + }); + const moolaPurse = wallet.getPurse(MOOLA_PURSE_PETNAME); + t.is(moolaPurseInfo.purse, moolaPurse); + t.is(moolaPurseInfo.pursePetname, MOOLA_PURSE_PETNAME); + t.is(moolaPurseInfo.value, 0); +}); + +test('getAttenuatedPursesNotifier', async t => { + const { wallet, MOOLA_ISSUER_PETNAME, MOOLA_PURSE_PETNAME } = await setup(); + const pursesNotifier = wallet.getAttenuatedPursesNotifier(); + const update = await pursesNotifier.getUpdateSince(); + t.is(update.updateCount, 7); + // Has the default Zoe invitation purse and a moola purse + t.is(update.value.length, 2); + const moolaPurseInfo = update.value[1]; + // @ts-ignore + t.is(moolaPurseInfo.actions, undefined); + // @ts-ignore + t.is(moolaPurseInfo.brand, undefined); + t.is(moolaPurseInfo.brandBoardId, '1532665031'); + t.is(moolaPurseInfo.brandPetname, MOOLA_ISSUER_PETNAME); + t.deepEqual(moolaPurseInfo.currentAmount, { + brand: { kind: 'brand', petname: 'moola' }, // not a real amount + value: 0, + }); + t.deepEqual(moolaPurseInfo.currentAmountSlots, { + body: + '{"brand":{"@qclass":"slot","iface":"Alleged: moola brand","index":0},"value":0}', + slots: [ + { + kind: 'brand', + petname: 'moola', + }, + ], + }); + t.deepEqual(moolaPurseInfo.displayInfo, { + amountMathKind: 'nat', + }); + + // @ts-ignore + t.is(moolaPurseInfo.purse, undefined); + t.is(moolaPurseInfo.pursePetname, MOOLA_PURSE_PETNAME); + t.is(moolaPurseInfo.value, 0); +});