diff --git a/packages/ERTP/src/amountStore.js b/packages/ERTP/src/amountStore.js new file mode 100644 index 00000000000..899afa3e39f --- /dev/null +++ b/packages/ERTP/src/amountStore.js @@ -0,0 +1,32 @@ +import { AmountMath } from './amountMath.js'; + +/** + * @template {AssetKind} [K=AssetKind] + * @typedef {object} AmountStore + * @property {() => Amount} getAmount + * @property {(delta: Amount) => void} increment + * @property {(delta: Amount) => boolean} decrement + */ + +/** + * @template {AssetKind} [K=AssetKind] + * @param {object} state + * @param {string} key + * @returns {AmountStore} + */ +export const makeAmountStore = (state, key) => { + return harden({ + getAmount: () => state[key], + increment: delta => { + state[key] = AmountMath.add(state[key], delta); + }, + decrement: delta => { + if (AmountMath.isGTE(state[key], delta)) { + state[key] = AmountMath.subtract(state[key], delta); + return true; + } + return false; + }, + }); +}; +harden(makeAmountStore); diff --git a/packages/ERTP/src/paymentLedger.js b/packages/ERTP/src/paymentLedger.js index b11ecc925a7..a20f6e4be5e 100644 --- a/packages/ERTP/src/paymentLedger.js +++ b/packages/ERTP/src/paymentLedger.js @@ -200,10 +200,6 @@ export const preparePaymentLedger = ( } }; - /** @type {(left: Amount, right: Amount) => Amount} */ - const add = (left, right) => AmountMath.add(left, right, brand); - /** @type {(left: Amount, right: Amount) => Amount} */ - const subtract = (left, right) => AmountMath.subtract(left, right, brand); /** @type {(allegedAmount: Amount) => Amount} */ const coerce = allegedAmount => AmountMath.coerce(brand, allegedAmount); /** @type {(left: Amount, right: Amount) => boolean} */ @@ -239,17 +235,13 @@ export const preparePaymentLedger = ( /** * Used by the purse code to implement purse.deposit * - * @param {Amount} currentBalance - the current balance of the purse before a - * deposit - * @param {(newPurseBalance: Amount) => void} updatePurseBalance - commit the - * purse balance + * @param {import('./amountStore.js').AmountStore} balanceStore * @param {Payment} srcPayment * @param {Pattern} [optAmountShape] * @returns {Amount} */ const depositInternal = ( - currentBalance, - updatePurseBalance, + balanceStore, srcPayment, optAmountShape = undefined, ) => { @@ -261,13 +253,12 @@ export const preparePaymentLedger = ( assertLivePayment(srcPayment); const srcPaymentBalance = paymentLedger.get(srcPayment); assertAmountConsistent(srcPaymentBalance, optAmountShape); - const newPurseBalance = add(srcPaymentBalance, currentBalance); try { // COMMIT POINT // Move the assets in `srcPayment` into this purse, using up the // source payment, such that total assets are conserved. deletePayment(srcPayment); - updatePurseBalance(newPurseBalance); + balanceStore.increment(srcPaymentBalance); } catch (err) { shutdownLedgerWithFailure(err); throw err; @@ -278,30 +269,19 @@ export const preparePaymentLedger = ( /** * Used by the purse code to implement purse.withdraw * - * @param {Amount} currentBalance - the current balance of the purse before a - * withdrawal - * @param {(newPurseBalance: Amount) => void} updatePurseBalance - commit the - * purse balance + * @param {import('./amountStore.js').AmountStore} balanceStore * @param {Amount} amount - the amount to be withdrawn * @param {SetStore} recoverySet * @returns {Payment} */ - const withdrawInternal = ( - currentBalance, - updatePurseBalance, - amount, - recoverySet, - ) => { + const withdrawInternal = (balanceStore, amount, recoverySet) => { amount = coerce(amount); - AmountMath.isGTE(currentBalance, amount) || - Fail`Withdrawal of ${amount} failed because the purse only contained ${currentBalance}`; - const newPurseBalance = subtract(currentBalance, amount); - const payment = makePayment(); + // COMMIT POINT Move the withdrawn assets from this purse into + // payment. Total assets must remain conserved. + balanceStore.decrement(amount) || + Fail`Withdrawal of ${amount} failed because the purse only contained ${balanceStore.getAmount()}`; try { - // COMMIT POINT Move the withdrawn assets from this purse into - // payment. Total assets must remain conserved. - updatePurseBalance(newPurseBalance); initPayment(payment, amount, recoverySet); } catch (err) { shutdownLedgerWithFailure(err); diff --git a/packages/ERTP/src/purse.js b/packages/ERTP/src/purse.js index 74451170fe0..52fc1ca11e3 100644 --- a/packages/ERTP/src/purse.js +++ b/packages/ERTP/src/purse.js @@ -2,6 +2,7 @@ import { M } from '@agoric/store'; import { prepareExoClassKit, makeScalarBigSetStore } from '@agoric/vat-data'; import { AmountMath } from './amountMath.js'; import { makeTransientNotifierKit } from './transientNotifier.js'; +import { makeAmountStore } from './amountStore.js'; // TODO `InterfaceGuard` type parameter /** @typedef {import('@endo/patterns').InterfaceGuard} InterfaceGuard */ @@ -37,11 +38,6 @@ export const preparePurseKind = ( // broken across an upgrade. const { provideNotifier, update: updateBalance } = makeTransientNotifierKit(); - const updatePurseBalance = (state, newPurseBalance, purse) => { - state.currentBalance = newPurseBalance; - updateBalance(purse, purse.getCurrentAmount()); - }; - // - This kind is a pair of purse and depositFacet that have a 1:1 // correspondence. // - They are virtualized together to share a single state record. @@ -72,28 +68,34 @@ export const preparePurseKind = ( // PurseI does *not* delay `deposit` until `srcPayment` is fulfulled. // See the comments on PurseI.deposit in typeGuards.js const { state } = this; + const { purse } = this.facets; + const balanceStore = makeAmountStore(state, 'currentBalance'); // Note COMMIT POINT within deposit. - return depositInternal( - state.currentBalance, - newPurseBalance => - updatePurseBalance(state, newPurseBalance, this.facets.purse), + const srcPaymentBalance = depositInternal( + balanceStore, srcPayment, optAmountShape, ); + updateBalance(purse, balanceStore.getAmount()); + return srcPaymentBalance; }, withdraw(amount) { const { state } = this; + const { purse } = this.facets; + const balanceStore = makeAmountStore(state, 'currentBalance'); // Note COMMIT POINT within withdraw. - return withdrawInternal( - state.currentBalance, - newPurseBalance => - updatePurseBalance(state, newPurseBalance, this.facets.purse), + const payment = withdrawInternal( + balanceStore, amount, state.recoverySet, ); + updateBalance(purse, balanceStore.getAmount()); + return payment; }, getCurrentAmount() { - return this.state.currentBalance; + const { state } = this; + const balanceStore = makeAmountStore(state, 'currentBalance'); + return balanceStore.getAmount(); }, getCurrentAmountNotifier() { return provideNotifier(this.facets.purse);