Skip to content

Commit

Permalink
feat(smart-wallet): initialize with existing vbank assets
Browse files Browse the repository at this point in the history
 - add anyBank to walletFactory terms so we can subscribe to
   vbank assets without waiting for creation of the 1st wallet
   - refine customTermsShape from M.not(M.undefined()) to M.eref(M.remotable())
 - 'want stable' test: don't assume incremental updates include all
   assets; get the full current state to start with
 - constrain BrandDescriptor to take settled issuers, since
   publishing records that include promises doesn't let off-chain
   callers compare identities
 - losen addBrand() typeguard to allow M.eref(PurseShape) since
   we await the purse inside the function
  • Loading branch information
dckc committed Jan 12, 2023
1 parent 181f0b6 commit 4c5b1bc
Show file tree
Hide file tree
Showing 7 changed files with 59 additions and 38 deletions.
2 changes: 2 additions & 0 deletions packages/inter-protocol/test/smartWallet/contexts.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export const makeDefaultTestContext = async (t, makeSpace) => {
'wallet',
);

const anyBank = await E(consume.bankManager).getBankForAddress('anyAddress');
const bridgeManager = await consume.bridgeManager;
const walletBridgeManager = await (bridgeManager &&
E(bridgeManager).register(BridgeId.WALLET));
Expand All @@ -44,6 +45,7 @@ export const makeDefaultTestContext = async (t, makeSpace) => {
{
agoricNames,
board: consume.board,
anyBank,
},
{ storageNode, walletBridgeManager },
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,14 +131,20 @@ test('want stable', async t => {
const stableBrand = await E(agoricNames).lookup('brand', Stable.symbol);

const wallet = await t.context.simpleProvideWallet('agoric1wantstable');
const current = await E(E(wallet).getCurrentSubscriber())
.subscribeAfter()
.then(pub => pub.head.value);
const computedState = coalesceUpdates(E(wallet).getUpdatesSubscriber());

const offersFacet = wallet.getOffersFacet();
t.assert(offersFacet, 'undefined offersFacet');
// let promises settle to notify brands and create purses
await eventLoopIteration();

t.is(purseBalance(computedState, anchor.brand), 0n);
t.deepEqual(current.purses.find(b => b.brand === anchor.brand).balance, {
brand: anchor.brand,
value: 0n,
});

t.log('Fund the wallet');
assert(anchor.mint);
Expand Down
21 changes: 16 additions & 5 deletions packages/smart-wallet/src/smartWallet.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ const mapToRecord = map => Object.fromEntries(map.entries());
* @typedef {{
* brand: Brand,
* displayInfo: DisplayInfo,
* issuer: ERef<Issuer>,
* issuer: Issuer,
* petname: import('./types').Petname
* }} BrandDescriptor
* For use by clients to describe brands to users. Includes `displayInfo` to save a remote call.
Expand All @@ -99,7 +99,7 @@ const mapToRecord = map => Object.fromEntries(map.entries());
* getRegisteredAsset: (b: Brand) => BrandDescriptor,
* getRegisteredBrands: () => BrandDescriptor[],
* },
* invitationIssuer: ERef<Issuer<'set'>>,
* invitationIssuer: Issuer<'set'>,
* invitationBrand: Brand<'set'>,
* publicMarshaller: Marshaller,
* storageNode: ERef<StorageNode>,
Expand Down Expand Up @@ -216,7 +216,7 @@ const behaviorGuards = {
petname: M.string(),
displayInfo: M.any(),
},
PurseShape,
M.eref(PurseShape),
).returns(M.promise()),
}),
deposit: M.interface('depositFacetI', {
Expand Down Expand Up @@ -281,14 +281,14 @@ const SmartWalletKit = defineVirtualFarClassKit(
});
},

/** @type {(desc: BrandDescriptor, purse: RemotePurse) => Promise<void>} */
/** @type {(desc: BrandDescriptor, purse: ERef<RemotePurse>) => Promise<void>} */
async addBrand(desc, purseRef) {
const { address, brandPurses, paymentQueues, updatePublishKit } =
this.state;
const pursesHas = brandPurses.has(desc.brand);
assert(!pursesHas, 'repeated brand from bank asset subscription');

const purse = await purseRef; // @@Why?
const purse = await purseRef; // promises don't fit in durable storage

brandPurses.init(desc.brand, purse);

Expand Down Expand Up @@ -495,6 +495,17 @@ const SmartWalletKit = defineVirtualFarClassKit(
// @ts-expect-error cast to RemotePurse
/** @type {RemotePurse} */ (invitationPurse),
);

// Schedule creation of a purse for each registered brand.
state.registry.getRegisteredBrands().forEach(desc => {
// In this sync method, we can't await the outcome.
void E(desc.issuer)
.makeEmptyPurse()
// @ts-expect-error cast
.then((/** @type {RemotePurse} */ purse) =>
helper.addBrand(desc, purse),
);
});
},
},
);
Expand Down
55 changes: 26 additions & 29 deletions packages/smart-wallet/src/walletFactory.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@ export const privateArgsShape = harden(
);

export const customTermsShape = harden({
agoricNames: M.not(M.undefined()),
board: M.not(M.undefined()),
agoricNames: M.eref(M.remotable()),
board: M.eref(M.remotable()),
anyBank: M.eref(M.remotable()),
});

/**
Expand Down Expand Up @@ -61,6 +62,7 @@ export const publishDepositFacet = async (
* @typedef {{
* agoricNames: ERef<NameHub>,
* board: ERef<import('@agoric/vats').Board>,
* anyBank: ERef<import('@agoric/vats/src/vat-bank').Bank>,
* }} SmartWalletContractTerms
*
* @typedef {import('@agoric/vats').NameHub} NameHub
Expand All @@ -78,7 +80,7 @@ export const publishDepositFacet = async (
* }} privateArgs
*/
export const start = async (zcf, privateArgs) => {
const { agoricNames, board } = zcf.getTerms();
const { agoricNames, board, anyBank } = zcf.getTerms();

const zoe = zcf.getZoeService();
const { storageNode, walletBridgeManager } = privateArgs;
Expand Down Expand Up @@ -136,36 +138,32 @@ export const start = async (zcf, privateArgs) => {
* @typedef {{
* brand: Brand,
* displayInfo: DisplayInfo,
* issuer: ERef<Issuer>,
* issuer: Issuer,
* petname: import('./types').Petname
* }} BrandDescriptor
* For use by clients to describe brands to users. Includes `displayInfo` to save a remote call.
*/
/** @type {MapStore<Brand, BrandDescriptor>} */
const brandDescriptors = makeScalarMapStore();

let subscribed = false;
/** @param {ERef<import('@agoric/vats/src/vat-bank').Bank>} bank */
const ensureSubscribed = bank => {
if (subscribed) {
return;
}
// watch the bank for new issuers to make purses out of
void observeIteration(E(bank).getAssetSubscription(), {
async updateState(desc) {
const { brand, issuer, issuerName: petname } = desc;
const displayInfo = await E(brand).getDisplayInfo();

brandDescriptors.init(desc.brand, {
brand,
issuer,
petname,
displayInfo,
});
},
});
subscribed = true;
};
// watch the bank for new issuers to make purses out of
void observeIteration(E(anyBank).getAssetSubscription(), {
async updateState(desc) {
const { brand, issuer: issuerP, issuerName: petname } = desc;
// await issuer identity for use in chainStorage
const [issuer, displayInfo] = await Promise.all([
issuerP,
E(brand).getDisplayInfo(),
]);

brandDescriptors.init(desc.brand, {
brand,
issuer,
petname,
displayInfo,
});
},
});

// XXX this isn't far, is it?
const registry = Far('AssetRegistry', {
Expand All @@ -175,9 +173,9 @@ export const start = async (zcf, privateArgs) => {
},
getRegisteredBrands: () => [...brandDescriptors.values()],
});
return { registry, ensureSubscribed };
return registry;
};
const { registry, ensureSubscribed } = makeAssetRegistry();
const registry = makeAssetRegistry();

const shared = harden({
agoricNames,
Expand Down Expand Up @@ -224,7 +222,6 @@ export const start = async (zcf, privateArgs) => {
return wallet;
};

ensureSubscribed(bank);
return provider
.provideAsync(address, maker)
.then(w => [w, makerCalled]);
Expand Down
2 changes: 2 additions & 0 deletions packages/smart-wallet/test/contexts.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export const makeDefaultTestContext = async (t, makeSpace) => {
'wallet',
);

const anyBank = await E(consume.bankManager).getBankForAddress('anyAddress');
const bridgeManager = await consume.bridgeManager;
const walletBridgeManager = await (bridgeManager &&
E(bridgeManager).register(BridgeId.WALLET));
Expand All @@ -43,6 +44,7 @@ export const makeDefaultTestContext = async (t, makeSpace) => {
{
agoricNames,
board: consume.board,
anyBank,
},
{ storageNode, walletBridgeManager },
);
Expand Down
6 changes: 4 additions & 2 deletions packages/smart-wallet/test/test-startWalletFactory.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,15 @@ test('customTermsShape', async t => {
{
agoricNames,
board,
anyBank: /** @type {any} */ ({}),
// @ts-expect-error extra term
extra: board,
},
privateArgs,
),
{
message:
'{"agoricNames":"[Promise]","board":"[Promise]","extra":"[Seen]"} - Must not have unexpected properties: ["extra"]',
'{"agoricNames":"[Promise]","anyBank":{},"board":"[Promise]","extra":"[Seen]"} - Must not have unexpected properties: ["extra"]',
},
);

Expand All @@ -88,7 +89,7 @@ test('customTermsShape', async t => {
),
{
message:
'{"agoricNames":"[Promise]"} - Must have missing properties ["board"]',
'{"agoricNames":"[Promise]"} - Must have missing properties ["board","anyBank"]',
},
);
});
Expand All @@ -99,6 +100,7 @@ test('privateArgsShape', async t => {
const terms = {
agoricNames,
board,
anyBank: /** @type {any} */ ({}),
};

// missing an arg
Expand Down
3 changes: 2 additions & 1 deletion packages/vats/src/core/startWalletFactory.js
Original file line number Diff line number Diff line change
Expand Up @@ -184,10 +184,12 @@ export const startWalletFactory = async (
feeIssuerP,
]);

const poolBank = E(bankManager).getBankForAddress(poolAddr);
const terms = await deeplyFulfilled(
harden({
agoricNames,
board,
anyBank: poolBank,
}),
);
/** @type {WalletFactoryStartResult} */
Expand All @@ -202,7 +204,6 @@ export const startWalletFactory = async (
);
walletFactoryStartResult.resolve(wfFacets);
instanceProduce.walletFactory.resolve(wfFacets.instance);
const poolBank = E(bankManager).getBankForAddress(poolAddr);

const ppFacets = await startGovernedInstance(
{
Expand Down

0 comments on commit 4c5b1bc

Please sign in to comment.