@agoric/inter-protocol 0.13.2-dev-02e99df.0 → 0.13.2-dev-ab7a341.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,303 @@
1
+ import { Fail } from '@agoric/assert';
2
+ import { AmountMath, AmountShape, IssuerShape } from '@agoric/ertp';
3
+ import { makeTracer } from '@agoric/internal';
4
+ import { M, makeScalarBigMapStore, prepareExoClassKit } from '@agoric/vat-data';
5
+ import { atomicTransfer } from '@agoric/zoe/src/contractSupport/atomicTransfer.js';
6
+ import {
7
+ makeRecorderTopic,
8
+ SubscriberShape,
9
+ TopicsRecordShape,
10
+ } from '@agoric/zoe/src/contractSupport/topics.js';
11
+ import { AmountKeywordRecordShape } from '@agoric/zoe/src/typeGuards.js';
12
+ import { E } from '@endo/eventual-send';
13
+ import { UnguardedHelperI } from '../typeGuards.js';
14
+
15
+ const { quote: q } = assert;
16
+
17
+ const trace = makeTracer('ReserveKit', false);
18
+
19
+ /**
20
+ * @typedef {object} MetricsNotification
21
+ *
22
+ * @property {AmountKeywordRecord} allocations
23
+ * @property {Amount<'nat'>} shortfallBalance shortfall from liquidation that
24
+ * has not yet been compensated.
25
+ * @property {Amount<'nat'>} totalFeeMinted total Fee tokens minted to date
26
+ * @property {Amount<'nat'>} totalFeeBurned total Fee tokens burned to date
27
+ */
28
+
29
+ /**
30
+ * @param {import('@agoric/vat-data').Baggage} baggage
31
+ * @param {{
32
+ * feeMint: ZCFMint<'nat'>,
33
+ * makeRecorderKit: import('@agoric/zoe/src/contractSupport/recorder.js').MakeRecorderKit,
34
+ * storageNode: StorageNode,
35
+ * zcf: ZCF}} powers
36
+ */
37
+ export const prepareAssetReserveKit = async (
38
+ baggage,
39
+ { feeMint, makeRecorderKit, storageNode, zcf },
40
+ ) => {
41
+ trace('prepareAssetReserveKit', [...baggage.keys()]);
42
+ const feeKit = feeMint.getIssuerRecord();
43
+ const emptyAmount = AmountMath.makeEmpty(feeKit.brand);
44
+
45
+ const makeAssetReserveKitInternal = prepareExoClassKit(
46
+ baggage,
47
+ 'AssetReserveKit',
48
+ {
49
+ helper: UnguardedHelperI,
50
+ governedApis: M.interface('AssetReserve governedApis', {
51
+ burnFeesToReduceShortfall: M.call(AmountShape).returns(),
52
+ }),
53
+ machine: M.interface('AssetReserve machine', {
54
+ addIssuer: M.call(IssuerShape, M.string()).returns(M.promise()),
55
+ getAllocations: M.call().returns(AmountKeywordRecordShape),
56
+ makeShortfallReportingInvitation: M.call().returns(M.promise()),
57
+ }),
58
+ public: M.interface('AssetReserve public', {
59
+ makeAddCollateralInvitation: M.call().returns(M.promise()),
60
+ getMetrics: M.call().returns(SubscriberShape),
61
+ getPublicTopics: M.call().returns(TopicsRecordShape),
62
+ }),
63
+ shortfallReportingFacet: M.interface('AssetReserve shortfall reporter', {
64
+ increaseLiquidationShortfall: M.call(AmountShape).returns(),
65
+ reduceLiquidationShortfall: M.call(AmountShape).returns(),
66
+ }),
67
+ },
68
+ /**
69
+ *
70
+ * @param {StorageNode} metricsNode
71
+ */
72
+ metricsNode => {
73
+ /**
74
+ * Used to look up the unique keyword for each brand, including Fee brand.
75
+ *
76
+ * @type {MapStore<Brand, Keyword>}
77
+ */
78
+ const keywordForBrand = makeScalarBigMapStore('keywordForBrand', {
79
+ durable: true,
80
+ });
81
+ /**
82
+ * Used to look up the brands for keywords, excluding Fee because it's a special case.
83
+ *
84
+ * @type {MapStore<Keyword, Brand>}
85
+ */
86
+ const brandForKeyword = makeScalarBigMapStore('brandForKeyword', {
87
+ durable: true,
88
+ });
89
+
90
+ return {
91
+ brandForKeyword,
92
+ // We keep the associated liquidity tokens in the same seat
93
+ collateralSeat: zcf.makeEmptySeatKit().zcfSeat,
94
+ keywordForBrand,
95
+ metricsKit: makeRecorderKit(
96
+ metricsNode,
97
+ /** @type {import('@agoric/zoe/src/contractSupport/recorder.js').TypedMatcher<MetricsNotification>} */ (
98
+ M.any()
99
+ ),
100
+ ),
101
+ totalFeeMinted: emptyAmount,
102
+ totalFeeBurned: emptyAmount,
103
+ shortfallBalance: emptyAmount,
104
+ };
105
+ },
106
+ {
107
+ helper: {
108
+ /** @param {Brand} brand */
109
+ getKeywordForBrand(brand) {
110
+ const { keywordForBrand } = this.state;
111
+ keywordForBrand.has(brand) ||
112
+ Fail`Issuer not defined for brand ${q(
113
+ brand,
114
+ )}; first call addIssuer()`;
115
+ return keywordForBrand.get(brand);
116
+ },
117
+
118
+ /**
119
+ * @param {Brand} brand
120
+ * @param {Keyword} keyword
121
+ */
122
+ saveBrandKeyword(brand, keyword) {
123
+ trace('saveBrandKeyword', brand, keyword);
124
+ const { keywordForBrand, brandForKeyword } = this.state;
125
+ keywordForBrand.init(brand, keyword);
126
+ brandForKeyword.init(keyword, brand);
127
+ },
128
+
129
+ writeMetrics() {
130
+ const { state } = this;
131
+ const metrics = harden({
132
+ allocations: state.collateralSeat.getCurrentAllocation(),
133
+ shortfallBalance: state.shortfallBalance,
134
+ totalFeeMinted: state.totalFeeMinted,
135
+ totalFeeBurned: state.totalFeeBurned,
136
+ });
137
+ state.metricsKit.recorder.write(metrics);
138
+ },
139
+ },
140
+ governedApis: {
141
+ /**
142
+ *
143
+ * @param {Amount<'nat'>} reduction
144
+ * @returns {void}
145
+ */
146
+ burnFeesToReduceShortfall(reduction) {
147
+ const { facets, state } = this;
148
+ trace('burnFeesToReduceShortfall', reduction);
149
+ reduction = AmountMath.coerce(feeKit.brand, reduction);
150
+ const feeKeyword = state.keywordForBrand.get(feeKit.brand);
151
+ const feeBalance =
152
+ state.collateralSeat.getAmountAllocated(feeKeyword);
153
+ const amountToBurn = AmountMath.min(reduction, feeBalance);
154
+ if (AmountMath.isEmpty(amountToBurn)) {
155
+ return;
156
+ }
157
+
158
+ feeMint.burnLosses(
159
+ harden({ [feeKeyword]: amountToBurn }),
160
+ state.collateralSeat,
161
+ );
162
+ state.totalFeeBurned = AmountMath.add(
163
+ state.totalFeeBurned,
164
+ amountToBurn,
165
+ );
166
+ facets.helper.writeMetrics();
167
+ },
168
+ },
169
+ machine: {
170
+ // add makeRedeemLiquidityTokensInvitation later. For now just store them
171
+ /**
172
+ * @param {Issuer} issuer
173
+ * @param {string} keyword
174
+ */
175
+ async addIssuer(issuer, keyword) {
176
+ const brand = await E(issuer).getBrand();
177
+ trace('addIssuer', { brand, keyword });
178
+ assert(
179
+ keyword !== 'Fee' && brand !== feeKit.brand,
180
+ `'Fee' brand is a special case handled by the reserve contract`,
181
+ );
182
+
183
+ trace('addIssuer storing', {
184
+ keyword,
185
+ brand,
186
+ });
187
+
188
+ this.facets.helper.saveBrandKeyword(brand, keyword);
189
+ await zcf.saveIssuer(issuer, keyword);
190
+ },
191
+
192
+ /** @deprecated use getPublicTopics metrics allocation */
193
+ getAllocations() {
194
+ return this.state.collateralSeat.getCurrentAllocation();
195
+ },
196
+
197
+ makeShortfallReportingInvitation() {
198
+ const { facets } = this;
199
+ const handleShortfallReportingOffer = () => {
200
+ return facets.shortfallReportingFacet;
201
+ };
202
+
203
+ return zcf.makeInvitation(
204
+ handleShortfallReportingOffer,
205
+ 'getFacetForReportingShortfalls',
206
+ );
207
+ },
208
+ },
209
+ /**
210
+ * XXX missing governance public methods https://github.com/Agoric/agoric-sdk/issues/5200
211
+ */
212
+ public: {
213
+ /**
214
+ * Anyone can deposit any assets to the reserve
215
+ */
216
+ makeAddCollateralInvitation() {
217
+ /** @type {OfferHandler<Promise<string>>} */
218
+ const handler = async seat => {
219
+ const {
220
+ give: { Collateral: amountIn },
221
+ } = seat.getProposal();
222
+ const { facets, state } = this;
223
+ const { helper } = facets;
224
+
225
+ const collateralKeyword = helper.getKeywordForBrand(amountIn.brand);
226
+
227
+ atomicTransfer(
228
+ zcf,
229
+ seat,
230
+ state.collateralSeat,
231
+ { Collateral: amountIn },
232
+ { [collateralKeyword]: amountIn },
233
+ );
234
+ seat.exit();
235
+ helper.writeMetrics();
236
+
237
+ trace('received collateral', amountIn);
238
+ return 'added Collateral to the Reserve';
239
+ };
240
+ return zcf.makeInvitation(handler, 'Add Collateral');
241
+ },
242
+ /** @deprecated */
243
+ getMetrics() {
244
+ return this.state.metricsKit.subscriber;
245
+ },
246
+ getPublicTopics() {
247
+ return {
248
+ metrics: makeRecorderTopic(
249
+ 'Asset Reserve metrics',
250
+ this.state.metricsKit,
251
+ ),
252
+ };
253
+ },
254
+ },
255
+ shortfallReportingFacet: {
256
+ /**
257
+ * @param {Amount<"nat">} shortfall
258
+ */
259
+ increaseLiquidationShortfall(shortfall) {
260
+ const { facets, state } = this;
261
+ state.shortfallBalance = AmountMath.add(
262
+ state.shortfallBalance,
263
+ shortfall,
264
+ );
265
+ facets.helper.writeMetrics();
266
+ },
267
+
268
+ // currently exposed for testing. Maybe it only gets called internally?
269
+ /**
270
+ * @param {Amount<"nat">} reduction
271
+ */
272
+ reduceLiquidationShortfall(reduction) {
273
+ const { state } = this;
274
+ if (AmountMath.isGTE(reduction, state.shortfallBalance)) {
275
+ state.shortfallBalance = emptyAmount;
276
+ } else {
277
+ state.shortfallBalance = AmountMath.subtract(
278
+ state.shortfallBalance,
279
+ reduction,
280
+ );
281
+ }
282
+ this.facets.helper.writeMetrics();
283
+ },
284
+ },
285
+ },
286
+ {
287
+ finish: ({ facets: { helper } }) => {
288
+ // no need to saveIssuer() b/c registerFeeMint did it
289
+ helper.saveBrandKeyword(feeKit.brand, 'Fee');
290
+
291
+ helper.writeMetrics();
292
+ },
293
+ },
294
+ );
295
+
296
+ const makeAssetReserveKit = async () => {
297
+ const metricsNode = await E(storageNode).makeChildNode('metrics');
298
+ return makeAssetReserveKitInternal(metricsNode);
299
+ };
300
+ return makeAssetReserveKit;
301
+ };
302
+ harden(prepareAssetReserveKit);
303
+ /** @typedef {ReturnType<Awaited<ReturnType<typeof prepareAssetReserveKit>>>} AssetReserveKit */
@@ -489,6 +489,7 @@ export const prepareVaultManagerKit = (
489
489
 
490
490
  /** @type {(accounting: { overage: Amount<'nat'>, shortfall: Amount<'nat'> }) => void} */
491
491
  recordShortfallAndProceeds(accounting) {
492
+ trace('recordShortfallAndProceeds', accounting);
492
493
  const { state } = this;
493
494
 
494
495
  const { overage, shortfall } = accounting;