Skip to content

Commit

Permalink
feat(zoe): revoke used-up payments
Browse files Browse the repository at this point in the history
  • Loading branch information
erights committed Feb 24, 2024
1 parent ade6ec9 commit 782cac8
Show file tree
Hide file tree
Showing 9 changed files with 69 additions and 34 deletions.
1 change: 1 addition & 0 deletions packages/ERTP/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"homepage": "https://github.com/Agoric/agoric-sdk#readme",
"dependencies": {
"@agoric/assert": "^0.6.0",
"@agoric/base-zone": "^0.1.0",
"@agoric/notifier": "^0.6.2",
"@agoric/store": "^0.9.2",
"@agoric/vat-data": "^0.5.2",
Expand Down
38 changes: 32 additions & 6 deletions packages/ERTP/src/payment.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,40 @@
// @jessie-check

import { prepareRevocableMakerKit } from '@agoric/base-zone/zone-helpers.js';
import { initEmpty } from '@agoric/store';

/**
* @template {AssetKind} K
* @typedef {object} PaymentMakerKit
* @property {(payment: Payment<K>) => boolean} revokePayment
* @property {() => Payment<K>} makePayment
*/

// TODO Type InterfaceGuard better than InterfaceGuard<any>
/**
* @template {AssetKind} K
* @param {import('@agoric/zone').Zone} issuerZone
* @param {string} name
* @param {Brand<K>} brand
* @param {import('@endo/patterns').InterfaceGuard<any>} PaymentI
* @returns {() => Payment<K>}
* @returns {PaymentMakerKit<K>}
*/
export const preparePaymentKind = (issuerZone, name, brand, PaymentI) => {
const makePayment = issuerZone.exoClass(
`${name} payment`,
export const preparePaymentMakerKit = (issuerZone, name, brand, PaymentI) => {
const paymentKindName = `${name} payment`;

/**
* @type {import('@agoric/base-zone/zone-helpers.js').RevocableMakerKit<
* Payment<K>
* >}
*/
const { revoke, makeRevocable } = prepareRevocableMakerKit(
issuerZone,
paymentKindName,
['getAllegedBrand'],
);

const makeUnderlyingPayment = issuerZone.exoClass(
paymentKindName,
PaymentI,
initEmpty,
{
Expand All @@ -22,6 +43,11 @@ export const preparePaymentKind = (issuerZone, name, brand, PaymentI) => {
},
},
);
return makePayment;

const makePayment = () => makeRevocable(makeUnderlyingPayment());
return harden({
revokePayment: revoke,
makePayment,
});
};
harden(preparePaymentKind);
harden(preparePaymentMakerKit);
46 changes: 26 additions & 20 deletions packages/ERTP/src/paymentLedger.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import { isPromise } from '@endo/promise-kit';
import { mustMatch, M, keyEQ } from '@agoric/store';
import { AmountMath } from './amountMath.js';
import { preparePaymentKind } from './payment.js';
import { preparePaymentMakerKit } from './payment.js';
import { preparePurseKind } from './purse.js';

import '@agoric/store/exported.js';
Expand Down Expand Up @@ -118,7 +118,12 @@ export const preparePaymentLedger = (
amountShape,
);

const makePayment = preparePaymentKind(issuerZone, name, brand, PaymentI);
const { revokePayment, makePayment } = preparePaymentMakerKit(
issuerZone,
name,
brand,
PaymentI,
);

/** @type {ShutdownWithFailure} */
const shutdownLedgerWithFailure = reason => {
Expand All @@ -135,7 +140,7 @@ export const preparePaymentLedger = (
throw reason;
};

/** @type {WeakMapStore<Payment, Amount>} */
/** @type {WeakMapStore<Payment<K>, Amount<K>>} */
const paymentLedger = issuerZone.weakMapStore('paymentLedger', {
valueShape: amountShape,
});
Expand Down Expand Up @@ -165,9 +170,9 @@ export const preparePaymentLedger = (
* To maintain the invariants listed in the `paymentRecoverySets` comment,
* `initPayment` should contain the only call to `paymentLedger.init`.
*
* @param {Payment} payment
* @param {Amount} amount
* @param {SetStore<Payment>} [optRecoverySet]
* @param {Payment<K>} payment
* @param {Amount<K>} amount
* @param {SetStore<Payment<K>>} [optRecoverySet]
*/
const initPayment = (payment, amount, optRecoverySet = undefined) => {
if (optRecoverySet !== undefined) {
Expand All @@ -181,7 +186,7 @@ export const preparePaymentLedger = (
* To maintain the invariants listed in the `paymentRecoverySets` comment,
* `deletePayment` should contain the only call to `paymentLedger.delete`.
*
* @param {Payment} payment
* @param {Payment<K>} payment
*/
const deletePayment = payment => {
paymentLedger.delete(payment);
Expand All @@ -190,9 +195,10 @@ export const preparePaymentLedger = (
paymentRecoverySets.delete(payment);
recoverySet.delete(payment);
}
revokePayment(payment);
};

/** @type {(allegedAmount: Amount) => Amount} */
/** @type {(allegedAmount: Amount<K>) => Amount<K>} */
const coerce = allegedAmount => AmountMath.coerce(brand, allegedAmount);
/** @type {(left: Amount, right: Amount) => boolean} */

Expand All @@ -203,7 +209,7 @@ export const preparePaymentLedger = (
*
* Note: `optAmountShape` is user-supplied with no previous validation.
*
* @param {Amount} paymentBalance
* @param {Amount<K>} paymentBalance
* @param {Pattern} [optAmountShape]
* @returns {void}
*/
Expand All @@ -214,7 +220,7 @@ export const preparePaymentLedger = (
};

/**
* @param {Payment} payment
* @param {Payment<K>} payment
* @returns {void}
*/
const assertLivePayment = payment => {
Expand All @@ -227,10 +233,10 @@ export const preparePaymentLedger = (
/**
* Used by the purse code to implement purse.deposit
*
* @param {import('./amountStore.js').AmountStore} balanceStore
* @param {Payment} srcPayment
* @param {import('./amountStore.js').AmountStore<K>} balanceStore
* @param {Payment<K>} srcPayment
* @param {Pattern} [optAmountShape]
* @returns {Amount}
* @returns {Amount<K>}
*/
const depositInternal = (
balanceStore,
Expand Down Expand Up @@ -261,10 +267,10 @@ export const preparePaymentLedger = (
/**
* Used by the purse code to implement purse.withdraw
*
* @param {import('./amountStore.js').AmountStore} balanceStore
* @param {Amount} amount - the amount to be withdrawn
* @param {SetStore<Payment>} recoverySet
* @returns {Payment}
* @param {import('./amountStore.js').AmountStore<K>} balanceStore
* @param {Amount<K>} amount - the amount to be withdrawn
* @param {SetStore<Payment<K>>} recoverySet
* @returns {Payment<K>}
*/
const withdrawInternal = (balanceStore, amount, recoverySet) => {
amount = coerce(amount);
Expand Down Expand Up @@ -318,19 +324,19 @@ export const preparePaymentLedger = (
makeEmptyPurse() {
return makeEmptyPurse();
},
/** @param {Payment} payment awaited by callWhen */
/** @param {Payment<K>} payment awaited by callWhen */
isLive(payment) {
// IssuerI delays calling this method until `payment` is a Remotable
return paymentLedger.has(payment);
},
/** @param {Payment} payment awaited by callWhen */
/** @param {Payment<K>} payment awaited by callWhen */
getAmountOf(payment) {
// IssuerI delays calling this method until `payment` is a Remotable
assertLivePayment(payment);
return paymentLedger.get(payment);
},
/**
* @param {Payment} payment awaited by callWhen
* @param {Payment<K>} payment awaited by callWhen
* @param {Pattern} optAmountShape
*/
burn(payment, optAmountShape = undefined) {
Expand Down
4 changes: 2 additions & 2 deletions packages/ERTP/test/unitTests/test-inputValidation.js
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ test('assertLivePayment', async t => {
// payment is of the wrong brand
await t.throwsAsync(() => claim(E(issuer).makeEmptyPurse(), paymentB), {
message:
'"[Alleged: fungibleB payment]" was not a live payment for brand "[Alleged: fungible brand]". It could be a used-up payment, a payment for another brand, or it might not be a payment at all.',
'"[Alleged: fungibleB payment_caretaker revocable]" was not a live payment for brand "[Alleged: fungible brand]". It could be a used-up payment, a payment for another brand, or it might not be a payment at all.',
});

// payment is used up
Expand All @@ -170,7 +170,7 @@ test('assertLivePayment', async t => {

await t.throwsAsync(() => claim(E(issuer).makeEmptyPurse(), payment), {
message:
'"[Alleged: fungible payment]" was not a live payment for brand "[Alleged: fungible brand]". It could be a used-up payment, a payment for another brand, or it might not be a payment at all.',
'"[Alleged: fungible payment_caretaker revocable]" was not a live payment for brand "[Alleged: fungible brand]". It could be a used-up payment, a payment for another brand, or it might not be a payment at all.',
});
});

Expand Down
2 changes: 1 addition & 1 deletion packages/ERTP/test/unitTests/test-interfaces.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,5 @@ test('interfaces - particular implementation', t => {
const depositFacet = purse.getDepositFacet();
t.is(getInterfaceOf(depositFacet), 'Alleged: bucks Purse depositFacet');
const payment = mint.mintPayment(AmountMath.make(brand, 2n));
t.is(getInterfaceOf(payment), 'Alleged: bucks payment');
t.is(getInterfaceOf(payment), 'Alleged: bucks payment_caretaker revocable');
});
5 changes: 3 additions & 2 deletions packages/ERTP/test/unitTests/test-issuerObj.js
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,8 @@ test('issuer.split good amount', async t => {
await t.throwsAsync(
() => issuer.getAmountOf(oldPayment),
{
message: `"[Alleged: fungible payment]" was not a live payment for brand "[Alleged: fungible brand]". It could be a used-up payment, a payment for another brand, or it might not be a payment at all.`,
message:
'"[Alleged: fungible payment_caretaker revocable]" was not a live payment for brand "[Alleged: fungible brand]". It could be a used-up payment, a payment for another brand, or it might not be a payment at all.',
},
`oldPayment no longer exists`,
);
Expand Down Expand Up @@ -448,6 +449,6 @@ test('issuer.combine bad payments', async t => {

await t.throwsAsync(() => combine(E(issuer).makeEmptyPurse(), payments), {
message:
/^"\[Alleged: other fungible payment\]" was not a live payment for brand "\[Alleged: fungible brand\]"./,
'"[Alleged: other fungible payment_caretaker revocable]" was not a live payment for brand "[Alleged: fungible brand]". It could be a used-up payment, a payment for another brand, or it might not be a payment at all.',
});
});
3 changes: 2 additions & 1 deletion packages/base-zone/src/prepare-revocable.js
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,8 @@ export const prepareRevocableMakerKit = (
* @returns {U}
*/
const makeRevocable = underlying =>
// @ts-expect-error some confusion about UU vs Guarded<U> I think
// Cannot use at-ts-expect-error because it errors here but not in ERTP
// @ts-ignore some confusion about UU vs Guarded<U> I think

Check warning on line 153 in packages/base-zone/src/prepare-revocable.js

View workflow job for this annotation

GitHub Actions / lint-rest

Use "@ts-expect-error" to ensure an error is actually being suppressed
makeRevocableKit(underlying).revocable;

/**
Expand Down
2 changes: 1 addition & 1 deletion packages/zoe/test/unitTests/test-zoe.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ test(`E(zoe).offer - payment instead of paymentKeywordRecord`, async t => {
// @ts-expect-error deliberate invalid arguments for testing
await t.throwsAsync(() => E(zoe).offer(invitation, proposal, payment), {
message:
'In "offer" method of (ZoeService): arg 2?: remotable "[Alleged: Token payment]" - Must be a copyRecord',
'In "offer" method of (ZoeService): arg 2?: remotable "[Alleged: Token payment_caretaker revocable]" - Must be a copyRecord',
});
});

Expand Down
2 changes: 1 addition & 1 deletion packages/zoe/test/unitTests/zoe/test-burnInvitation.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ test('burnInvitation - invitation already used', async t => {
() => burnInvitation(mockInvitationKit.issuer, invitation),
{
message:
'A Zoe invitation is required, not "[Alleged: mockInvitation payment]"',
'A Zoe invitation is required, not "[Alleged: mockInvitation payment_caretaker revocable]"',
},
);
});
Expand Down

0 comments on commit 782cac8

Please sign in to comment.