Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(smart-wallet): create purses for new assets lazily #6774

Merged
merged 4 commits into from
Jan 20, 2023

Conversation

dckc
Copy link
Member

@dckc dckc commented Jan 10, 2023

refs: #6652

Description

  • only subscribe to new assets once, in walletFactory.js.
  • make purses for all vbank assets when creating a smart wallet
  • for assets added to the vbank after a smart wallet is created, create purses lazily
    • Change purseForBrand given to makeOfferExecutor to create purses lazily.

Security Considerations

When a user executes an offer, if we don't have a purse for one of the relevant brands, we take the brand they give and "upgrade" it to an issuer, by way of info we got from the vbank, in order to make a purse. Normally we would expect the user to explicitly indicate the issuers that they trust. This isn't so much a change in the security properties of the contract - it trusted the vbank to provide issuers to make purses before - but this now happens in response to the user placing an offer rather than in response to the vbank announcing a new asset.

Documentation Considerations

We should tell users that if a vbank asset is added after they provision their smart wallet, they won't see a purse until they execute an offer that uses it.

Testing Considerations

I'm somewhat surprised at how little code needed to change for this in order to get yarn test in packages/smart-wallet to pass. I wonder whether those tests have good coverage of executing offers.

p.s. tests in inter-protocol provide more coverage.

@samsiegart
Copy link
Contributor

Purses appearing only when executing an offer could be a little surprising. In particular, if a user has some BLD and/or IST, they won't see it in their wallet until they make an offer involving those tokens.
Perhaps when a smart-wallet is created, a purse for each asset currently in the vbank should be created?

I like the suggestion at the end. The first part means we're in a bit of a bind. If we can query balances for different denoms outside of vstorage, like on the cosmos account level, we can mesh assets with and without purses seamlessly in the UI maybe?

@dckc dckc marked this pull request as draft January 11, 2023 20:17
@@ -135,7 +134,7 @@ const mapToRecord = map => Object.fromEntries(map.entries());
* @returns {State}
*/
export const initState = (unique, shared) => {
// Some validation of inputs. "any" erefs because this synchronous call can't check more than that.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why remove this explanation for M.any?

btw, should some of these M.remotable?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why remove this explanation for M.any?

Because I got rid of all the M.any cases. Oops... but then I reverted that change without putting the comment back.

btw, should some of these M.remotable?

Yes.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refining the typeguards isn't necessary to fix #6652, so arguably it should go in a separate PR. You don't mind if I include it here, do you?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SGTM, makes sense with the anys gone :)

packages/smart-wallet/src/smartWallet.js Outdated Show resolved Hide resolved
Comment on lines 406 to 413
// async so we consistently return a promise
purseForBrand: async brand => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alternately, in a way that doesn't need explaining:

Suggested change
// async so we consistently return a promise
purseForBrand: async brand => {
/** @returns {Promise<Brand>} */
purseForBrand: brand => {

then the return brandPurses.get(brand); would have a squiggly and force,

return Promise.resolve(brandPurses.get(brand));

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That exposed a conflict with withdrawGive where the purse for a brand is expected to be available synchronously. Ugh.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah... it's ok to have promises in keywordPaymentPromises...

@@ -490,35 +481,20 @@ const SmartWalletKit = defineVirtualFarClassKit(
},
{
finish: ({ state, facets }) => {
const { invitationBrand, invitationIssuer, invitationPurse, bank } =
state;
const invitationDisplayInfo = { assetKind: AssetKind.SET }; // XXX
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what should this be instead?

maybe a Zoe const?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A Zoe const might work... but I went with the caller passing it in along with the brand and issuer.

},
// @ts-expect-error cast to RemotePurse
/** @type {RemotePurse} */ (invitationPurse),
);
// watch the bank for new issuers to make purses out of
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👌

/** @type {import('ava').TestFn<Awaited<ReturnType<makeDefaultTestContext>>>} */
const test = anyTest;

const TODO = undefined;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

turns out undefined works, so I inlined it as noBridge.

});
t.true(
exp.addedWrites <= (base.addedWrites * exp.qty) / base.qty / 2,
'actual writes should be less than half of linear growth',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice

subscribed = true;
};

// XXX this isn't far, is it?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it doesn't need to be, because all the wallets are in the same vat. We've talked about separating them. Any reason not to Far it?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its clients make synchronous calls on it. So they have to be in the same vat.

E(board).getReadonlyMarshaller(),
]);

const makeAssetRegistry = () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this has access to all of start scope, making it a little harder to read and audit.

please move this function def to module scope or another file. consider unit testing it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hosting it is a good idea... done.

As to unit testing... I'm inclined to let the existing tests suffice. Mocking an anyBank would be tedious, and using the existing vbank code would be more or less another integration test like the others.

@dckc dckc marked this pull request as ready for review January 12, 2023 17:51
@dckc
Copy link
Member Author

dckc commented Jan 12, 2023

@arirubinstein this does include a simple test (test-addAsset.js) for the performance issue. I wonder if you consider it adequate.

@dckc dckc requested a review from turadg January 12, 2023 20:28
@dckc
Copy link
Member Author

dckc commented Jan 12, 2023

... If we can query balances for different denoms outside of vstorage, like on the cosmos account level, we can mesh assets with and without purses seamlessly in the UI maybe?

Yes, that's straightforward. agd query bank balances ... (or the equivalent JS) gives a list of { denom, amount } balances. The mapping of denom to brand, displayInfo, etc. is now published in agoricNames.vbankAssets.

Copy link
Member

@turadg turadg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. I assume it won't merge until fixups are rebased and you have some of the other reviews you're seeking

@dckc dckc marked this pull request as draft January 12, 2023 20:48
const terms = await deeplyFulfilled(
harden({
agoricNames,
board,
anyBank: poolBank,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hm. This gives the walletFactory authority to spend from the poolBank. That's not great. Gotta think that over...

@dckc
Copy link
Member Author

dckc commented Jan 13, 2023

... I assume it won't merge until fixups are rebased and ...

I intend to squash it all to one commit.

@dckc dckc force-pushed the 6652-wallet-perf branch 2 times, most recently from e688aaf to 53e7d7b Compare January 13, 2023 17:09
@dckc dckc marked this pull request as ready for review January 13, 2023 17:10
@dckc dckc added automerge:squash Automatically squash merge and removed automerge:squash Automatically squash merge labels Jan 17, 2023
@dckc dckc marked this pull request as draft January 17, 2023 15:56
const desc = registry.getRegisteredAsset(brand);
/** @type {RemotePurse} */
// @ts-expect-error cast to RemotePurse
const purse = E(desc.issuer).makeEmptyPurse();
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this needs to make a vbank purse a la E(bank).getPurse(desc.brand)

@mhofman
Copy link
Member

mhofman commented Jan 19, 2023

@Mergifyio refresh

@mergify
Copy link
Contributor

mergify bot commented Jan 19, 2023

refresh

✅ Pull request refreshed

@mhofman
Copy link
Member

mhofman commented Jan 19, 2023

@Mergifyio unqueue

@mergify
Copy link
Contributor

mergify bot commented Jan 19, 2023

unqueue

✅ The pull request has been removed from the queue

@mhofman
Copy link
Member

mhofman commented Jan 19, 2023

@dckc the mergify bot snafu might make it hard to land this PR. Please ping me if re-adding a label doesn't work (You might also try the bot command)

 - subscribe for new vbank assets once, in walletFactory.js
   - provide a read-only registry of the assets
 - no change to RPC protocol: still publish all brand descriptors in
   all wallet states
 - refactor: clarify invitationIssuer promise
 - add invitationDisplayInfo to synchronously-provided
   SharedParams
 - refine typeguards, esp. M.any() => M.remotable('...')
 - hoist makeAssetRegistry() to module scope to reduce in-scope authority

feat(smart-wallet): initialize with existing vbank assets

 - 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())
   - attenuate poolBank to {getAssetSubscription}
 - '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

 - static type for purseForBrand motivates tweak to keywordPaymentPromises

 - fixup: Promise<NameHub> isn't durable

   a promise for agoricNames isn't durable.  so I need
   deeplyFulfilledObject after all.  but the types don't work out. so
   back to deeplyFulfilled I guess
@dckc dckc added the automerge:no-update (expert!) Automatically merge without updates label Jan 20, 2023
@turadg turadg marked this pull request as ready for review January 20, 2023 16:40
@mergify mergify bot merged commit ac1a0c1 into master Jan 20, 2023
@mergify mergify bot deleted the 6652-wallet-perf branch January 20, 2023 18:19
@turadg turadg mentioned this pull request Jan 20, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
automerge:no-update (expert!) Automatically merge without updates
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants