Skip to content

Commit

Permalink
Merge pull request #6807 from Agoric/dc-wallet-balances-dry
Browse files Browse the repository at this point in the history
fix!(smart-wallet): omit redundant chainStorage balances updates
  • Loading branch information
mergify[bot] authored Feb 10, 2023
2 parents e7c9a78 + 20fd7a2 commit 6ca3660
Show file tree
Hide file tree
Showing 10 changed files with 176 additions and 258 deletions.
6 changes: 3 additions & 3 deletions packages/agoric-cli/src/lib/format.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,11 +100,11 @@ export const fmtRecordOfLines = record => {
* @param {Awaited<ReturnType<typeof makeAgoricNames>>} agoricNames
*/
export const offerStatusTuples = (state, agoricNames) => {
const { brands, offerStatuses } = state;
const { offerStatuses } = state;
const fmt = makeAmountFormatter(
// eslint-disable-next-line @typescript-eslint/prefer-ts-expect-error -- https://github.com/Agoric/agoric-sdk/issues/4620 */
// @ts-ignore xxx RpcRemote
[...brands.values()],
Object.values(agoricNames.vbankAsset),
);
const fmtRecord = r =>
r
Expand Down Expand Up @@ -170,7 +170,7 @@ export const summarize = (current, coalesced, agoricNames) => {
[...current.purses.values()],
// eslint-disable-next-line @typescript-eslint/prefer-ts-expect-error -- https://github.com/Agoric/agoric-sdk/issues/4620 */
// @ts-ignore xxx RpcRemote
[...current.brands.values()],
Object.values(agoricNames.vbankAsset),
),
usedInvitations: Object.entries(current.offerToUsedInvitation).map(
([offerId, invitationAmt]) => [
Expand Down
17 changes: 15 additions & 2 deletions packages/agoric-cli/src/lib/rpc.js
Original file line number Diff line number Diff line change
Expand Up @@ -239,12 +239,25 @@ harden(storageHelper);
/**
* @param {IdMap} ctx
* @param {VStorage} vstorage
* @returns {Promise<{ brand: Record<string, RpcRemote>, instance: Record<string, RpcRemote>, reverse: Record<string, string> }>}
* @returns {Promise<{
* brand: Record<string, RpcRemote>,
* instance: Record<string, RpcRemote>,
* vbankAsset: Record<string, VBankAssetDetail>,
* reverse: Record<string, string>,
* }>}
* @typedef {{
* brand: RpcRemote,
* denom: string,
* displayInfo: DisplayInfo,
* issuer: RpcRemote,
* issuerName: string,
* proposedName: string,
* }} VBankAssetDetail
*/
export const makeAgoricNames = async (ctx, vstorage) => {
const reverse = {};
const entries = await Promise.all(
['brand', 'instance'].map(async kind => {
['brand', 'instance', 'vbankAsset'].map(async kind => {
const content = await vstorage.readLatest(
`published.agoricNames.${kind}`,
);
Expand Down
18 changes: 15 additions & 3 deletions packages/inter-protocol/test/smartWallet/contexts.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,16 +53,27 @@ export const makeDefaultTestContext = async (t, makeSpace) => {
);

/** @param {string} address */
const simpleProvideWallet = async address => {
const provideWalletAndBalances = async address => {
// copied from makeClientBanks()
const bank = E(consume.bankManager).getBankForAddress(address);
const bank = await E(consume.bankManager).getBankForAddress(address);

const [wallet, _isNew] = await E(
walletFactory.creatorFacet,
).provideSmartWallet(address, bank, consume.namesByAddressAdmin);
return wallet;

/**
* Read-only facet of bank
*
* @param {Brand<'nat'>} brand
*/
const getBalanceFor = brand =>
E(E(bank).getPurse(brand)).getCurrentAmount();
return { getBalanceFor, wallet };
};

const simpleProvideWallet = address =>
provideWalletAndBalances(address).then(({ wallet }) => wallet);

/**
*
* @param {string[]} oracleAddresses
Expand Down Expand Up @@ -121,6 +132,7 @@ export const makeDefaultTestContext = async (t, makeSpace) => {
sendToBridge:
walletBridgeManager && (obj => E(walletBridgeManager).toBridge(obj)),
consume,
provideWalletAndBalances,
simpleProvideWallet,
simpleCreatePriceFeed,
};
Expand Down
116 changes: 86 additions & 30 deletions packages/inter-protocol/test/smartWallet/test-psm-integration.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,19 +51,6 @@ test.before(async t => {
t.context = await makeDefaultTestContext(t, makePsmTestSpace);
});

/**
* @param {Awaited<ReturnType<typeof coalesceUpdates>>} state
* @param {Brand<'nat'>} brand
*/
const purseBalance = (state, brand) => {
const balances = Array.from(state.balances.values());
const match = balances.find(b => b.brand === brand);
if (!match) {
console.debug('balances', ...balances);
assert.fail(`${brand} not found in record`);
}
return match.value;
};
/**
* @param {import('@agoric/smart-wallet/src/smartWallet.js').CurrentWalletRecord} record
* @param {Brand<'nat'>} brand
Expand All @@ -83,8 +70,9 @@ test('null swap', async t => {
const { agoricNames } = await E.get(t.context.consume);
const mintedBrand = await E(agoricNames).lookup('brand', 'IST');

const wallet = await t.context.simpleProvideWallet('agoric1nullswap');
const computedState = coalesceUpdates(E(wallet).getUpdatesSubscriber());
const { getBalanceFor, wallet } = await t.context.provideWalletAndBalances(
'agoric1nullswap',
);
const offersFacet = wallet.getOffersFacet();

const psmInstance = await E(agoricNames).lookup('instance', 'psm-IST-AUSD');
Expand Down Expand Up @@ -112,8 +100,8 @@ test('null swap', async t => {
await offersFacet.executeOffer(offerSpec);
await eventLoopIteration();

t.is(purseBalance(computedState, anchor.brand), 0n);
t.is(purseBalance(computedState, mintedBrand), 0n);
t.is(await E.get(getBalanceFor(anchor.brand)).value, 0n);
t.is(await E.get(getBalanceFor(mintedBrand)).value, 0n);

// success if nothing threw
t.pass();
Expand All @@ -130,21 +118,16 @@ test('want stable', async t => {
const psmInstance = await E(agoricNames).lookup('instance', 'psm-IST-AUSD');
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 { getBalanceFor, wallet } = await t.context.provideWalletAndBalances(
'agoric1wantstable',
);

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

t.deepEqual(current.purses.find(b => b.brand === anchor.brand).balance, {
brand: anchor.brand,
value: 0n,
});
t.is(await E.get(getBalanceFor(anchor.brand)).value, 0n);

t.log('Fund the wallet');
assert(anchor.mint);
Expand Down Expand Up @@ -173,8 +156,8 @@ test('want stable', async t => {
t.log('Execute the swap');
await offersFacet.executeOffer(offerSpec);
await eventLoopIteration();
t.is(purseBalance(computedState, anchor.brand), 0n);
t.is(purseBalance(computedState, stableBrand), swapSize); // assume 0% fee
t.is(await E.get(getBalanceFor(anchor.brand)).value, 0n);
t.is(await E.get(getBalanceFor(stableBrand)).value, swapSize); // assume 0% fee
});

test('govern offerFilter', async t => {
Expand All @@ -184,7 +167,10 @@ test('govern offerFilter', async t => {
const { psm: psmInstance } = await E(psmKit).get(anchor.brand);

const wallet = await t.context.simpleProvideWallet(committeeAddress);
const computedState = coalesceUpdates(E(wallet).getUpdatesSubscriber());
const computedState = coalesceUpdates(
E(wallet).getUpdatesSubscriber(),
invitationBrand,
);
const currentSub = E(wallet).getCurrentSubscriber();

const offersFacet = wallet.getOffersFacet();
Expand Down Expand Up @@ -212,9 +198,14 @@ test('govern offerFilter', async t => {
E(E(zoe).getInvitationIssuer())
.getBrand()
.then(brand => {
t.is(
brand,
invitationBrand,
'invitation brand from context matches zoe',
);
/** @type {Amount<'set'>} */
const invitationsAmount = NonNullish(balances.get(brand));
t.is(invitationsAmount?.value.length, len);
t.is(invitationsAmount?.value.length, len, 'invitation count');
return invitationsAmount.value.filter(i => i.description === desc);
});

Expand Down Expand Up @@ -369,6 +360,71 @@ test('deposit unknown brand', async t => {
t.deepEqual(result, { brand: rial.brand, value: 0n });
});

test.failing('deposit > 1 payment to unknown brand #6961', async t => {
const rial = withAmountUtils(makeIssuerKit('rial'));

const wallet = await t.context.simpleProvideWallet('agoric1queue');

for await (const _ of [1, 2]) {
const payment = rial.mint.mintPayment(rial.make(1_000n));
// @ts-expect-error deposit does take a FarRef<Payment>
const result = await wallet.getDepositFacet().receive(harden(payment));
// successful request but not deposited
t.deepEqual(result, { brand: rial.brand, value: 0n });
}
});

// XXX belongs in smart-wallet package, but needs lots of set-up that's handy here.
test('recover when some withdrawals succeed and others fail', async t => {
const { fromEntries } = Object;
const { make } = AmountMath;
const { anchor } = t.context;
const { agoricNames, bankManager } = t.context.consume;
const getBalance = (addr, brand) => {
const bank = E(bankManager).getBankForAddress(addr);
const purse = E(bank).getPurse(brand);
return E(purse).getCurrentAmount();
};
const namedBrands = kws =>
Promise.all(
kws.map(kw =>
E(agoricNames)
.lookup('brand', kw)
.then(b => [kw, b]),
),
).then(fromEntries);

t.log('Johnny has 10 AUSD');
const jAddr = 'addrForJohnny';
const smartWallet = await t.context.simpleProvideWallet(jAddr);
await E(E(smartWallet).getDepositFacet()).receive(
// @ts-expect-error FarRef grumble
E(anchor.mint).mintPayment(make(anchor.brand, 10n)),
);
t.deepEqual(await getBalance(jAddr, anchor.brand), make(anchor.brand, 10n));

t.log('He accidentally offers 10 BLD as well in a trade for IST');
const instance = await E(agoricNames).lookup('instance', 'psm-IST-AUSD');
const brand = await namedBrands(['BLD', 'IST']);
const proposal = harden({
give: { Anchor: make(anchor.brand, 10n), Oops: make(brand.BLD, 10n) },
want: { Proceeds: make(brand.IST, 1n) },
});
await E(smartWallet.getOffersFacet()).executeOffer({
id: '1',
invitationSpec: {
source: 'contract',
instance,
publicInvitationMaker: 'makeWantMintedInvitation',
invitationArgs: [],
},
proposal,
});

t.log('He still has 10 AUSD');
t.deepEqual(await getBalance(jAddr, anchor.brand), make(anchor.brand, 10n));
});

test.todo('bad offer schema');
test.todo('not enough funds');
test.todo(
Expand Down
Loading

0 comments on commit 6ca3660

Please sign in to comment.