From 87bef2931bbb1436bf2558ff796d0b0690f09d26 Mon Sep 17 00:00:00 2001 From: "Mark S. Miller" Date: Thu, 6 Jul 2023 19:16:15 -0700 Subject: [PATCH] feat(ERTP): revoke used up payments --- packages/ERTP/package.json | 1 + packages/ERTP/src/payment.js | 17 +++++++++++++++-- packages/ERTP/src/paymentLedger.js | 9 ++++++++- packages/ERTP/test/unitTests/test-recovery.js | 15 +++++++++++++++ 4 files changed, 39 insertions(+), 3 deletions(-) diff --git a/packages/ERTP/package.json b/packages/ERTP/package.json index bb62821c1822..9f6e99610af3 100644 --- a/packages/ERTP/package.json +++ b/packages/ERTP/package.json @@ -53,6 +53,7 @@ "devDependencies": { "@agoric/swingset-vat": "^0.32.2", "@endo/bundle-source": "^3.0.1", + "@endo/exo": "^1.0.1", "@fast-check/ava": "^1.1.5", "ava": "^5.3.0", "tsd": "^0.28.1" diff --git a/packages/ERTP/src/payment.js b/packages/ERTP/src/payment.js index 809be8cc965b..4380c4edb6be 100644 --- a/packages/ERTP/src/payment.js +++ b/packages/ERTP/src/payment.js @@ -16,9 +16,13 @@ import { prepareExoClass } from '@agoric/vat-data'; * @param {string} name * @param {Brand} brand * @param {InterfaceGuard} PaymentI - * @returns {() => Payment} + * @returns {{ + * makePayment: () => Payment; + * revokePayment: (payment: Payment) => boolean; + * }} */ export const preparePaymentKind = (issuerBaggage, name, brand, PaymentI) => { + let revokePayment; const makePayment = prepareExoClass( issuerBaggage, `${name} payment`, @@ -29,7 +33,16 @@ export const preparePaymentKind = (issuerBaggage, name, brand, PaymentI) => { return brand; }, }, + { + receiveRevoker(revoke) { + revokePayment = revoke; + }, + }, ); - return makePayment; + assert(revokePayment !== undefined); + return harden({ + makePayment, + revokePayment, + }); }; harden(preparePaymentKind); diff --git a/packages/ERTP/src/paymentLedger.js b/packages/ERTP/src/paymentLedger.js index b11ecc925a76..65418356ba0f 100644 --- a/packages/ERTP/src/paymentLedger.js +++ b/packages/ERTP/src/paymentLedger.js @@ -121,7 +121,12 @@ export const preparePaymentLedger = ( amountShape, ); - const makePayment = preparePaymentKind(issuerBaggage, name, brand, PaymentI); + const { makePayment, revokePayment } = preparePaymentKind( + issuerBaggage, + name, + brand, + PaymentI, + ); /** @type {ShutdownWithFailure} */ const shutdownLedgerWithFailure = reason => { @@ -198,6 +203,8 @@ export const preparePaymentLedger = ( paymentRecoverySets.delete(payment); recoverySet.delete(payment); } + // @ts-expect-error The usual type param confusion + revokePayment(payment); }; /** @type {(left: Amount, right: Amount) => Amount} */ diff --git a/packages/ERTP/test/unitTests/test-recovery.js b/packages/ERTP/test/unitTests/test-recovery.js index 3ed6ef326a76..d7fbd23383ed 100644 --- a/packages/ERTP/test/unitTests/test-recovery.js +++ b/packages/ERTP/test/unitTests/test-recovery.js @@ -1,7 +1,9 @@ import { test } from '@agoric/swingset-vat/tools/prepare-test-env-ava.js'; +import { GET_INTERFACE_GUARD } from '@endo/exo'; import { getCopySetKeys, keyEQ, makeCopySet } from '@agoric/store'; import { makeIssuerKit, AmountMath } from '../../src/index.js'; +import { GET_METHOD_NAMES } from '@endo/marshal'; const { isEmpty, isEqual } = AmountMath; const emptySet = makeCopySet([]); @@ -67,4 +69,17 @@ test('payment recovery from mint recovery set', async t => { t.throws(() => bobPurse.deposit(payment2), { message: /was not a live payment for brand/, }); + t.throws(() => payment2.getAllegedBrand(), { + message: + '"In \\"getAllegedBrand\\" method of (precious payment)" may only be applied to a valid instance: "[Alleged: precious payment]"', + }); + t.deepEqual(payment2[GET_METHOD_NAMES](), [ + '__getInterfaceGuard__', + '__getMethodNames__', + 'getAllegedBrand', + ]); + t.throws(() => payment2[GET_INTERFACE_GUARD](), { + message: + '"In \\"__getInterfaceGuard__\\" method of (precious payment)" may only be applied to a valid instance: "[Alleged: precious payment]"', + }); });