-
Notifications
You must be signed in to change notification settings - Fork 212
/
purse.js
123 lines (115 loc) · 3.87 KB
/
purse.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
import { M } from '@agoric/store';
import { prepareExoClassKit, makeScalarBigSetStore } from '@agoric/vat-data';
import { AmountMath } from './amountMath.js';
import { makeTransientNotifierKit } from './transientNotifier.js';
const { Fail } = assert;
export const preparePurseKind = (
issuerBaggage,
name,
assetKind,
brand,
PurseIKit,
purseMethods,
) => {
const amountShape = brand.getAmountShape();
// Note: Virtual for high cardinality, but *not* durable, and so
// 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.
// - An alternative design considered was to have this return a Purse alone
// that created depositFacet as needed. But this approach ensures a constant
// identity for the facet and exercises the multi-faceted object style.
const { depositInternal, withdrawInternal } = purseMethods;
const makePurseKit = prepareExoClassKit(
issuerBaggage,
`${name} Purse`,
PurseIKit,
() => {
const currentBalance = AmountMath.makeEmpty(brand, assetKind);
/** @type {SetStore<Payment>} */
const recoverySet = makeScalarBigSetStore('recovery set', {
durable: true,
});
return {
currentBalance,
recoverySet,
};
},
{
purse: {
deposit(srcPayment, optAmountShape = undefined) {
// PurseI does *not* delay `deposit` until `srcPayment` is fulfulled.
// See the comments on PurseI.deposit in typeGuards.js
const { state } = this;
// Note COMMIT POINT within deposit.
return depositInternal(
state.currentBalance,
newPurseBalance =>
updatePurseBalance(state, newPurseBalance, this.facets.purse),
srcPayment,
optAmountShape,
);
},
withdraw(amount) {
const { state } = this;
// Note COMMIT POINT within withdraw.
return withdrawInternal(
state.currentBalance,
newPurseBalance =>
updatePurseBalance(state, newPurseBalance, this.facets.purse),
amount,
state.recoverySet,
);
},
getCurrentAmount() {
return this.state.currentBalance;
},
getCurrentAmountNotifier() {
return provideNotifier(this.facets.purse);
},
getAllegedBrand() {
return brand;
},
// eslint-disable-next-line no-use-before-define
getDepositFacet() {
return this.facets.depositFacet;
},
getRecoverySet() {
return this.state.recoverySet.snapshot();
},
recoverAll() {
const { state, facets } = this;
let amount = AmountMath.makeEmpty(brand, assetKind);
for (const payment of state.recoverySet.keys()) {
// This does cause deletions from the set while iterating,
// but this special case is allowed.
const delta = facets.purse.deposit(payment);
amount = AmountMath.add(amount, delta, brand);
}
state.recoverySet.getSize() === 0 ||
Fail`internal: Remaining unrecovered payments: ${facets.purse.getRecoverySet()}`;
return amount;
},
},
depositFacet: {
receive(...args) {
return this.facets.purse.deposit(...args);
},
},
},
{
stateShape: {
currentBalance: amountShape,
recoverySet: M.remotable('recoverySet'),
},
},
);
return () => makePurseKit().purse;
};
harden(preparePurseKind);