Skip to content

Commit

Permalink
refactor(ERTP,vats): AmountPatternShape
Browse files Browse the repository at this point in the history
  • Loading branch information
erights committed Aug 8, 2024
1 parent e8c4189 commit 7f538c6
Show file tree
Hide file tree
Showing 4 changed files with 131 additions and 9 deletions.
19 changes: 17 additions & 2 deletions packages/ERTP/src/typeGuards.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,19 @@ export const AmountShape = harden({
value: AmountValueShape,
});

/**
* To be used to guard an amount pattern argument, i.e., an argument which is a
* pattern that can be used to test amounts. Since amounts are keys, anywhere an
* amount pattern is expected, an amount can be provided and will match only
* that concrete amount, i.e., amounts that are `keyEQ` to that amount.
*
* The `AmountShape` guard above is an amount pattern. But not all amount
* patterns are like `AmountShape`. For example, `M.any()` is a valid amount
* pattern that will admit any amount, but is does not resemble the
* `AmountShape` pattern above.
*/
export const AmountPatternShape = M.pattern();

export const RatioShape = harden({
numerator: AmountShape,
denominator: AmountShape,
Expand Down Expand Up @@ -178,7 +191,7 @@ export const makeIssuerInterfaces = (
isLive: M.callWhen(M.await(PaymentShape)).returns(M.boolean()),
getAmountOf: M.callWhen(M.await(PaymentShape)).returns(amountShape),
burn: M.callWhen(M.await(PaymentShape))
.optional(M.pattern())
.optional(AmountPatternShape)
.returns(amountShape),
});

Expand All @@ -205,7 +218,9 @@ export const makeIssuerInterfaces = (
// `srcPayment` is a remotable, leaving it
// to this raw method to validate that this remotable is actually
// a live payment of the correct brand with sufficient funds.
deposit: M.call(PaymentShape).optional(M.pattern()).returns(amountShape),
deposit: M.call(PaymentShape)
.optional(AmountPatternShape)
.returns(amountShape),
getDepositFacet: M.call().returns(DepositFacetShape),
withdraw: M.call(amountShape).returns(PaymentShape),
getRecoverySet: M.call().returns(M.setOf(PaymentShape)),
Expand Down
9 changes: 7 additions & 2 deletions packages/vats/src/localchain.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@
import { Fail } from '@endo/errors';
import { E } from '@endo/far';
import { M } from '@endo/patterns';
import { AmountShape, BrandShape, PaymentShape } from '@agoric/ertp';
import {
AmountPatternShape,
AmountShape,
BrandShape,
PaymentShape,
} from '@agoric/ertp';
import { Shape as NetworkShape } from '@agoric/network';

const { Vow$ } = NetworkShape;
Expand Down Expand Up @@ -49,7 +54,7 @@ export const LocalChainAccountI = M.interface('LocalChainAccount', {
getAddress: M.callWhen().returns(Vow$(M.string())),
getBalance: M.callWhen(BrandShape).returns(Vow$(AmountShape)),
deposit: M.callWhen(PaymentShape)
.optional(M.pattern())
.optional(AmountPatternShape)
.returns(Vow$(AmountShape)),
withdraw: M.callWhen(AmountShape).returns(Vow$(PaymentShape)),
executeTx: M.callWhen(M.arrayOf(M.record())).returns(
Expand Down
31 changes: 26 additions & 5 deletions packages/vats/src/virtual-purse.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import { Fail } from '@endo/errors';
import { E } from '@endo/far';
import { isPromise } from '@endo/promise-kit';
import { getInterfaceGuardPayload } from '@endo/patterns';
import { getInterfaceGuardPayload, matches } from '@endo/patterns';

import { M } from '@agoric/store';
import {
AmountPatternShape,
AmountShape,
BrandShape,
DepositFacetShape,
NotifierShape,
PaymentShape,
} from '@agoric/ertp/src/typeGuards.js';
} from '@agoric/ertp';

/**
* @param {Pattern} [brandShape]
Expand All @@ -35,7 +36,7 @@ export const makeVirtualPurseKitIKit = (
// to this raw method to validate that this remotable is actually
// a live payment of the correct brand with sufficient funds.
deposit: M.callWhen(PaymentShape)
.optional(M.pattern())
.optional(AmountPatternShape)
.returns(amountShape),
getDepositFacet: M.callWhen().returns(DepositFacetShape),
withdraw: M.callWhen(amountShape).returns(PaymentShape),
Expand All @@ -48,15 +49,17 @@ export const makeVirtualPurseKitIKit = (
});

const RetainRedeemI = M.interface('RetainRedeem', {
retain: M.callWhen(PaymentShape).optional(amountShape).returns(amountShape),
retain: M.callWhen(PaymentShape)
.optional(AmountPatternShape)
.returns(amountShape),
redeem: M.callWhen(amountShape).returns(PaymentShape),
});

const UtilsI = M.interface('Utils', {
retain: getInterfaceGuardPayload(RetainRedeemI).methodGuards.retain,
redeem: getInterfaceGuardPayload(RetainRedeemI).methodGuards.redeem,
recoverableClaim: M.callWhen(M.await(PaymentShape))
.optional(amountShape)
.optional(AmountPatternShape)
.returns(PaymentShape),
});

Expand Down Expand Up @@ -102,6 +105,22 @@ export const makeVirtualPurseKitIKit = (
* current balance iterable for a given brand.
*/

/**
* Until https://github.com/Agoric/agoric-sdk/issues/9407 is fixed, this
* function restricts the `optAmountShape`, if provided, to be a concrete
* `Amount` rather than a `Pattern` as it is supposed to be.
*
* TODO: Once https://github.com/Agoric/agoric-sdk/issues/9407 is fixed, remove
* this function and all calls to it.
*
* @param {Pattern} [optAmountShape]
*/
const legacyRestrictAmountShapeArg = optAmountShape => {
if (optAmountShape && !matches(optAmountShape, AmountShape)) {
throw Fail`optAmountShape if provided, must still be a concrete Amount due to https://github.com/Agoric/agoric-sdk/issues/9407`;
}
};

/** @param {import('@agoric/zone').Zone} zone */
const prepareVirtualPurseKit = zone =>
zone.exoClassKit(
Expand Down Expand Up @@ -139,6 +158,7 @@ const prepareVirtualPurseKit = zone =>
* @returns {Promise<Payment<'nat'>>}
*/
async recoverableClaim(payment, optAmountShape) {
legacyRestrictAmountShapeArg(optAmountShape);
const {
state: { recoveryPurse },
} = this;
Expand Down Expand Up @@ -166,6 +186,7 @@ const prepareVirtualPurseKit = zone =>
minter: {
/** @type {Retain} */
async retain(payment, optAmountShape) {
legacyRestrictAmountShapeArg(optAmountShape);
!!this.state.mint || Fail`minter cannot retain without a mint.`;
return E(this.state.issuer).burn(payment, optAmountShape);
},
Expand Down
81 changes: 81 additions & 0 deletions packages/vats/test/vpurse.test.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { M } from '@endo/patterns';
import { test as rawTest } from '@agoric/swingset-vat/tools/prepare-test-env-ava.js';
import { reincarnate } from '@agoric/swingset-liveslots/tools/setup-vat-data.js';

Expand Down Expand Up @@ -176,6 +177,86 @@ test('makeVirtualPurse', async t => {
.then(checkWithdrawal);
});

// TODO Once https://github.com/Agoric/agoric-sdk/issues/9407 is fixed,
// This test should replace the similar one above.
test.failing('makeVirtualPurse with optAmountShape pattern', async t => {
t.plan(22);
const { baggage } = t.context;
const zone = makeDurableZone(baggage).subZone('makeVirtualPurse');

const { expected, balanceUpdater, issuer, mint, brand, vpurse } = setup(
t,
zone,
);

const payment = mint.mintPayment(AmountMath.make(brand, 837n));

const notifier = E(vpurse).getCurrentAmountNotifier();
let nextUpdateP = E(notifier).getUpdateSince();

const checkNotifier = async () => {
const { value: balance, updateCount } = await nextUpdateP;
t.assert(
AmountMath.isEqual(await E(vpurse).getCurrentAmount(), balance),
`the notifier balance is the same as the purse`,
);
nextUpdateP = E(notifier).getUpdateSince(updateCount);
};

balanceUpdater.updateState(AmountMath.makeEmpty(brand));
await checkNotifier();
t.assert(
AmountMath.isEqual(
await E(vpurse).getCurrentAmount(),
AmountMath.makeEmpty(brand),
),
`empty purse is empty`,
);
t.is(await E(vpurse).getAllegedBrand(), brand, `purse's brand is correct`);
const fungible837 = AmountMath.make(brand, 837n);

const checkDeposit = async newPurseBalance => {
t.assert(
AmountMath.isEqual(newPurseBalance, fungible837),
`the amount returned is the payment amount`,
);
await checkNotifier();
t.assert(
AmountMath.isEqual(await E(vpurse).getCurrentAmount(), fungible837),
`the new purse balance is the payment's old balance`,
);
};

const performWithdrawal = () => {
expected.pullAmount(fungible837);
return E(vpurse).withdraw(fungible837);
};

const checkWithdrawal = async newPayment => {
await issuer.getAmountOf(newPayment).then(amount => {
t.assert(
AmountMath.isEqual(amount, fungible837),
`the withdrawn payment has the right balance`,
);
});
await checkNotifier();
t.assert(
AmountMath.isEqual(
await E(vpurse).getCurrentAmount(),
AmountMath.makeEmpty(brand),
),
`the purse is empty again`,
);
};

expected.pushAmount(fungible837);
await E(vpurse)
.deposit(payment, M.and(fungible837))
.then(checkDeposit)
.then(performWithdrawal)
.then(checkWithdrawal);
});

test('makeVirtualPurse withdraw from escrowPurse', async t => {
const { baggage } = t.context;
const zone = makeDurableZone(baggage).subZone('withdraw from escrowPurse');
Expand Down

0 comments on commit 7f538c6

Please sign in to comment.