Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ertp): purse amountStore abstraction #8861

Merged
merged 3 commits into from
Feb 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions packages/ERTP/src/amountStore.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { AmountMath } from './amountMath.js';

/**
* @template {AssetKind} [K=AssetKind]
* @typedef {object} AmountStore
* @property {() => Amount<K>} getAmount
* @property {(delta: Amount<K>) => void} increment
* @property {(delta: Amount<K>) => boolean} decrement
*/

/**
* @template {AssetKind} [K=AssetKind]
* @param {object} state
* @param {string} key
* @returns {AmountStore<K>}
*/
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);
erights marked this conversation as resolved.
Show resolved Hide resolved
return true;
}
return false;
},
});
};
harden(makeAmountStore);
38 changes: 9 additions & 29 deletions packages/ERTP/src/paymentLedger.js
Original file line number Diff line number Diff line change
Expand Up @@ -200,10 +200,6 @@ export const preparePaymentLedger = (
}
};

/** @type {(left: Amount, right: Amount) => Amount} */
erights marked this conversation as resolved.
Show resolved Hide resolved
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} */
Expand Down Expand Up @@ -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,
) => {
Expand All @@ -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;
Expand All @@ -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<Payment>} 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);
Expand Down
30 changes: 16 additions & 14 deletions packages/ERTP/src/purse.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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);
Expand Down
Loading