@agoric/fast-usdc 0.1.1-other-dev-3eb1a1d.0 → 0.1.1-other-dev-d15096d.0.d15096d

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.
@@ -1,255 +0,0 @@
1
- import { AmountMath, AmountShape } from '@agoric/ertp';
2
- import { assertAllDefined, makeTracer } from '@agoric/internal';
3
- import { ChainAddressShape } from '@agoric/orchestration';
4
- import { pickFacet } from '@agoric/vat-data';
5
- import { VowShape } from '@agoric/vow';
6
- import { q } from '@endo/errors';
7
- import { E } from '@endo/far';
8
- import { M } from '@endo/patterns';
9
- import { CctpTxEvidenceShape, EudParamShape } from '../type-guards.js';
10
- import { addressTools } from '../utils/address.js';
11
- import { makeFeeTools } from '../utils/fees.js';
12
-
13
- const { isGTE } = AmountMath;
14
-
15
- /**
16
- * @import {HostInterface} from '@agoric/async-flow';
17
- * @import {NatAmount} from '@agoric/ertp';
18
- * @import {ChainAddress, ChainHub, Denom, OrchestrationAccount} from '@agoric/orchestration';
19
- * @import {ZoeTools} from '@agoric/orchestration/src/utils/zoe-tools.js';
20
- * @import {VowTools} from '@agoric/vow';
21
- * @import {Zone} from '@agoric/zone';
22
- * @import {CctpTxEvidence, FeeConfig, LogFn} from '../types.js';
23
- * @import {StatusManager} from './status-manager.js';
24
- * @import {LiquidityPoolKit} from './liquidity-pool.js';
25
- */
26
-
27
- /**
28
- * @typedef {{
29
- * chainHub: ChainHub;
30
- * feeConfig: FeeConfig;
31
- * localTransfer: ZoeTools['localTransfer'];
32
- * log?: LogFn;
33
- * statusManager: StatusManager;
34
- * usdc: { brand: Brand<'nat'>; denom: Denom; };
35
- * vowTools: VowTools;
36
- * zcf: ZCF;
37
- * }} AdvancerKitPowers
38
- */
39
-
40
- /** type guards internal to the AdvancerKit */
41
- const AdvancerKitI = harden({
42
- advancer: M.interface('AdvancerI', {
43
- handleTransactionEvent: M.callWhen(CctpTxEvidenceShape).returns(),
44
- }),
45
- depositHandler: M.interface('DepositHandlerI', {
46
- onFulfilled: M.call(M.undefined(), {
47
- amount: AmountShape,
48
- destination: ChainAddressShape,
49
- tmpSeat: M.remotable(),
50
- }).returns(VowShape),
51
- onRejected: M.call(M.error(), {
52
- amount: AmountShape,
53
- destination: ChainAddressShape,
54
- tmpSeat: M.remotable(),
55
- }).returns(),
56
- }),
57
- transferHandler: M.interface('TransferHandlerI', {
58
- // TODO confirm undefined, and not bigint (sequence)
59
- onFulfilled: M.call(M.undefined(), {
60
- amount: AmountShape,
61
- destination: ChainAddressShape,
62
- }).returns(M.undefined()),
63
- onRejected: M.call(M.error(), {
64
- amount: AmountShape,
65
- destination: ChainAddressShape,
66
- }).returns(M.undefined()),
67
- }),
68
- });
69
-
70
- /**
71
- * @param {Zone} zone
72
- * @param {AdvancerKitPowers} caps
73
- */
74
- export const prepareAdvancerKit = (
75
- zone,
76
- {
77
- chainHub,
78
- feeConfig,
79
- localTransfer,
80
- log = makeTracer('Advancer', true),
81
- statusManager,
82
- usdc,
83
- vowTools: { watch, when },
84
- zcf,
85
- } = /** @type {AdvancerKitPowers} */ ({}),
86
- ) => {
87
- assertAllDefined({
88
- chainHub,
89
- feeConfig,
90
- statusManager,
91
- watch,
92
- when,
93
- });
94
- const feeTools = makeFeeTools(feeConfig);
95
- /** @param {bigint} value */
96
- const toAmount = value => AmountMath.make(usdc.brand, value);
97
-
98
- return zone.exoClassKit(
99
- 'Fast USDC Advancer',
100
- AdvancerKitI,
101
- /**
102
- * @param {{
103
- * borrowerFacet: LiquidityPoolKit['borrower'];
104
- * poolAccount: HostInterface<OrchestrationAccount<{chainId: 'agoric'}>>;
105
- * }} config
106
- */
107
- config => harden(config),
108
- {
109
- advancer: {
110
- /**
111
- * Must perform a status update for every observed transaction.
112
- *
113
- * We do not expect any callers to depend on the settlement of
114
- * `handleTransactionEvent` - errors caught are communicated to the
115
- * `StatusManager` - so we don't need to concern ourselves with
116
- * preserving the vow chain for callers.
117
- *
118
- * @param {CctpTxEvidence} evidence
119
- */
120
- async handleTransactionEvent(evidence) {
121
- await null;
122
- try {
123
- const { borrowerFacet, poolAccount } = this.state;
124
- const { recipientAddress } = evidence.aux;
125
- const { EUD } = addressTools.getQueryParams(
126
- recipientAddress,
127
- EudParamShape,
128
- );
129
-
130
- // this will throw if the bech32 prefix is not found, but is handled by the catch
131
- const destination = chainHub.makeChainAddress(EUD);
132
- const requestedAmount = toAmount(evidence.tx.amount);
133
- const advanceAmount = feeTools.calculateAdvance(requestedAmount);
134
-
135
- // TODO: consider skipping and using `borrow()`s internal balance check
136
- const poolBalance = borrowerFacet.getBalance();
137
- if (!isGTE(poolBalance, requestedAmount)) {
138
- log(
139
- `Insufficient pool funds`,
140
- `Requested ${q(advanceAmount)} but only have ${q(poolBalance)}`,
141
- );
142
- statusManager.observe(evidence);
143
- return;
144
- }
145
-
146
- try {
147
- // Mark as Advanced since `transferV` initiates the advance.
148
- // Will throw if we've already .skipped or .advanced this evidence.
149
- statusManager.advance(evidence);
150
- } catch (e) {
151
- // Only anticipated error is `assertNotSeen`, so intercept the
152
- // catch so we don't call .skip which also performs this check
153
- log('Advancer error:', q(e).toString());
154
- return;
155
- }
156
-
157
- const { zcfSeat: tmpSeat } = zcf.makeEmptySeatKit();
158
- const amountKWR = harden({ USDC: advanceAmount });
159
- try {
160
- borrowerFacet.borrow(tmpSeat, amountKWR);
161
- } catch (e) {
162
- // We do not expect this to fail since there are no turn boundaries
163
- // between .getBalance() and .borrow().
164
- // We catch to report outside of the normal error flow since this is
165
- // not expected.
166
- log('🚨 advance borrow failed', q(e).toString());
167
- }
168
-
169
- const depositV = localTransfer(
170
- tmpSeat,
171
- // @ts-expect-error LocalAccountMethods vs OrchestrationAccount
172
- poolAccount,
173
- amountKWR,
174
- );
175
- void watch(depositV, this.facets.depositHandler, {
176
- amount: advanceAmount,
177
- destination,
178
- tmpSeat,
179
- });
180
- } catch (e) {
181
- log('Advancer error:', q(e).toString());
182
- statusManager.observe(evidence);
183
- }
184
- },
185
- },
186
- depositHandler: {
187
- /**
188
- * @param {undefined} result
189
- * @param {{ amount: Amount<'nat'>; destination: ChainAddress; tmpSeat: ZCFSeat }} ctx
190
- */
191
- onFulfilled(result, { amount, destination }) {
192
- const { poolAccount } = this.state;
193
- const transferV = E(poolAccount).transfer(destination, {
194
- denom: usdc.denom,
195
- value: amount.value,
196
- });
197
- return watch(transferV, this.facets.transferHandler, {
198
- destination,
199
- amount,
200
- });
201
- },
202
- /**
203
- * @param {Error} error
204
- * @param {{ amount: Amount<'nat'>; destination: ChainAddress; tmpSeat: ZCFSeat }} ctx
205
- */
206
- onRejected(error, { tmpSeat }) {
207
- // TODO return seat allocation from ctx to LP?
208
- log('🚨 advance deposit failed', q(error).toString());
209
- // TODO #10510 (comprehensive error testing) determine
210
- // course of action here
211
- log(
212
- 'TODO live payment on seat to return to LP',
213
- q(tmpSeat).toString(),
214
- );
215
- },
216
- },
217
- transferHandler: {
218
- /**
219
- * @param {undefined} result TODO confirm this is not a bigint (sequence)
220
- * @param {{ destination: ChainAddress; amount: NatAmount; }} ctx
221
- */
222
- onFulfilled(result, { destination, amount }) {
223
- // TODO vstorage update? We don't currently have a status for
224
- // Advanced + transferV settled
225
- log(
226
- 'Advance transfer fulfilled',
227
- q({ amount, destination, result }).toString(),
228
- );
229
- },
230
- onRejected(error) {
231
- // TODO #10510 (comprehensive error testing) determine
232
- // course of action here. This might fail due to timeout.
233
- log('Advance transfer rejected', q(error).toString());
234
- },
235
- },
236
- },
237
- {
238
- stateShape: harden({
239
- borrowerFacet: M.remotable(),
240
- poolAccount: M.remotable(),
241
- }),
242
- },
243
- );
244
- };
245
- harden(prepareAdvancerKit);
246
-
247
- /**
248
- * @param {Zone} zone
249
- * @param {AdvancerKitPowers} caps
250
- */
251
- export const prepareAdvancer = (zone, caps) => {
252
- const makeAdvancerKit = prepareAdvancerKit(zone, caps);
253
- return pickFacet(makeAdvancerKit, 'advancer');
254
- };
255
- harden(prepareAdvancer);
@@ -1,365 +0,0 @@
1
- import { AmountMath, AmountShape } from '@agoric/ertp';
2
- import {
3
- makeRecorderTopic,
4
- TopicsRecordShape,
5
- } from '@agoric/zoe/src/contractSupport/topics.js';
6
- import { SeatShape } from '@agoric/zoe/src/typeGuards.js';
7
- import { M } from '@endo/patterns';
8
- import { Fail, q } from '@endo/errors';
9
- import {
10
- borrowCalc,
11
- depositCalc,
12
- makeParity,
13
- repayCalc,
14
- withdrawCalc,
15
- } from '../pool-share-math.js';
16
- import {
17
- makeNatAmountShape,
18
- makeProposalShapes,
19
- PoolMetricsShape,
20
- } from '../type-guards.js';
21
-
22
- /**
23
- * @import {Zone} from '@agoric/zone';
24
- * @import {Remote} from '@agoric/internal'
25
- * @import {StorageNode} from '@agoric/internal/src/lib-chainStorage.js'
26
- * @import {MakeRecorderKit} from '@agoric/zoe/src/contractSupport/recorder.js'
27
- * @import {USDCProposalShapes, ShareWorth} from '../pool-share-math.js'
28
- * @import {PoolStats} from '../types.js';
29
- */
30
-
31
- const { add, isEqual, makeEmpty } = AmountMath;
32
-
33
- /** @param {Brand} brand */
34
- const makeDust = brand => AmountMath.make(brand, 1n);
35
-
36
- /**
37
- * Verifies that the total pool balance (unencumbered + encumbered) matches the
38
- * shareWorth numerator. The total pool balance consists of:
39
- * 1. unencumbered balance - USDC available in the pool for borrowing
40
- * 2. encumbered balance - USDC currently lent out
41
- *
42
- * A negligible `dust` amount is used to initialize shareWorth with a non-zero
43
- * denominator. It must remain in the pool at all times.
44
- *
45
- * @param {ZCFSeat} poolSeat
46
- * @param {ShareWorth} shareWorth
47
- * @param {Brand} USDC
48
- * @param {Amount<'nat'>} encumberedBalance
49
- */
50
- const checkPoolBalance = (poolSeat, shareWorth, USDC, encumberedBalance) => {
51
- const unencumberedBalance = poolSeat.getAmountAllocated('USDC', USDC);
52
- const dust = makeDust(USDC);
53
- const grossBalance = add(add(unencumberedBalance, dust), encumberedBalance);
54
- isEqual(grossBalance, shareWorth.numerator) ||
55
- Fail`🚨 pool balance ${q(unencumberedBalance)} and encumbered balance ${q(encumberedBalance)} inconsistent with shareWorth ${q(shareWorth)}`;
56
- };
57
-
58
- /**
59
- * @typedef {{
60
- * Principal: Amount<'nat'>;
61
- * PoolFee: Amount<'nat'>;
62
- * ContractFee: Amount<'nat'>;
63
- * }} RepayAmountKWR
64
- */
65
-
66
- /**
67
- * @typedef {{
68
- * Principal: Payment<'nat'>;
69
- * PoolFee: Payment<'nat'>;
70
- * ContractFee: Payment<'nat'>;
71
- * }} RepayPaymentKWR
72
- */
73
-
74
- /**
75
- * @param {Zone} zone
76
- * @param {ZCF} zcf
77
- * @param {Brand<'nat'>} USDC
78
- * @param {{
79
- * makeRecorderKit: MakeRecorderKit;
80
- * }} tools
81
- */
82
- export const prepareLiquidityPoolKit = (zone, zcf, USDC, tools) => {
83
- return zone.exoClassKit(
84
- 'Liquidity Pool',
85
- {
86
- borrower: M.interface('borrower', {
87
- getBalance: M.call().returns(AmountShape),
88
- borrow: M.call(
89
- SeatShape,
90
- harden({ USDC: makeNatAmountShape(USDC, 1n) }),
91
- ).returns(),
92
- }),
93
- repayer: M.interface('repayer', {
94
- repay: M.call(
95
- SeatShape,
96
- harden({
97
- Principal: makeNatAmountShape(USDC, 1n),
98
- PoolFee: makeNatAmountShape(USDC, 0n),
99
- ContractFee: makeNatAmountShape(USDC, 0n),
100
- }),
101
- ).returns(),
102
- }),
103
- external: M.interface('external', {
104
- publishPoolMetrics: M.call().returns(),
105
- }),
106
- depositHandler: M.interface('depositHandler', {
107
- handle: M.call(SeatShape, M.any()).returns(M.promise()),
108
- }),
109
- withdrawHandler: M.interface('withdrawHandler', {
110
- handle: M.call(SeatShape, M.any()).returns(M.promise()),
111
- }),
112
- public: M.interface('public', {
113
- makeDepositInvitation: M.call().returns(M.promise()),
114
- makeWithdrawInvitation: M.call().returns(M.promise()),
115
- getPublicTopics: M.call().returns(TopicsRecordShape),
116
- }),
117
- },
118
- /**
119
- * @param {ZCFMint<'nat'>} shareMint
120
- * @param {Remote<StorageNode>} node
121
- */
122
- (shareMint, node) => {
123
- const { brand: PoolShares } = shareMint.getIssuerRecord();
124
- const proposalShapes = makeProposalShapes({ USDC, PoolShares });
125
- const shareWorth = makeParity(makeDust(USDC), PoolShares);
126
- const { zcfSeat: poolSeat } = zcf.makeEmptySeatKit();
127
- const { zcfSeat: feeSeat } = zcf.makeEmptySeatKit();
128
- const poolMetricsRecorderKit = tools.makeRecorderKit(
129
- node,
130
- PoolMetricsShape,
131
- );
132
- const encumberedBalance = makeEmpty(USDC);
133
- /** @type {PoolStats} */
134
- const poolStats = harden({
135
- totalBorrows: makeEmpty(USDC),
136
- totalContractFees: makeEmpty(USDC),
137
- totalPoolFees: makeEmpty(USDC),
138
- totalRepays: makeEmpty(USDC),
139
- });
140
- return {
141
- /** used for `checkPoolBalance` invariant. aka 'outstanding borrows' */
142
- encumberedBalance,
143
- feeSeat,
144
- poolStats,
145
- poolMetricsRecorderKit,
146
- poolSeat,
147
- PoolShares,
148
- proposalShapes,
149
- shareMint,
150
- shareWorth,
151
- };
152
- },
153
- {
154
- borrower: {
155
- getBalance() {
156
- const { poolSeat } = this.state;
157
- return poolSeat.getAmountAllocated('USDC', USDC);
158
- },
159
- /**
160
- * @param {ZCFSeat} toSeat
161
- * @param {{ USDC: Amount<'nat'>}} amountKWR
162
- */
163
- borrow(toSeat, amountKWR) {
164
- const { encumberedBalance, poolSeat, poolStats } = this.state;
165
-
166
- // Validate amount is available in pool
167
- const post = borrowCalc(
168
- amountKWR.USDC,
169
- poolSeat.getAmountAllocated('USDC', USDC),
170
- encumberedBalance,
171
- poolStats,
172
- );
173
-
174
- // COMMIT POINT
175
- try {
176
- zcf.atomicRearrange(harden([[poolSeat, toSeat, amountKWR]]));
177
- } catch (cause) {
178
- const reason = Error('🚨 cannot commit borrow', { cause });
179
- console.error(reason.message, cause);
180
- zcf.shutdownWithFailure(reason);
181
- }
182
-
183
- Object.assign(this.state, post);
184
- this.facets.external.publishPoolMetrics();
185
- },
186
- // TODO method to repay failed `LOA.deposit()`
187
- },
188
- repayer: {
189
- /**
190
- * @param {ZCFSeat} fromSeat
191
- * @param {RepayAmountKWR} amounts
192
- */
193
- repay(fromSeat, amounts) {
194
- const {
195
- encumberedBalance,
196
- feeSeat,
197
- poolSeat,
198
- poolStats,
199
- shareWorth,
200
- } = this.state;
201
- checkPoolBalance(poolSeat, shareWorth, USDC, encumberedBalance);
202
-
203
- const fromSeatAllocation = fromSeat.getCurrentAllocation();
204
- // Validate allocation equals amounts and Principal <= encumberedBalance
205
- const post = repayCalc(
206
- shareWorth,
207
- fromSeatAllocation,
208
- amounts,
209
- encumberedBalance,
210
- poolStats,
211
- );
212
-
213
- const { ContractFee, ...rest } = amounts;
214
-
215
- // COMMIT POINT
216
- try {
217
- zcf.atomicRearrange(
218
- harden([
219
- [
220
- fromSeat,
221
- poolSeat,
222
- rest,
223
- { USDC: add(amounts.PoolFee, amounts.Principal) },
224
- ],
225
- [fromSeat, feeSeat, { ContractFee }, { USDC: ContractFee }],
226
- ]),
227
- );
228
- } catch (cause) {
229
- const reason = Error('🚨 cannot commit repay', { cause });
230
- console.error(reason.message, cause);
231
- zcf.shutdownWithFailure(reason);
232
- }
233
-
234
- Object.assign(this.state, post);
235
- this.facets.external.publishPoolMetrics();
236
- },
237
- },
238
- external: {
239
- publishPoolMetrics() {
240
- const { poolStats, shareWorth, encumberedBalance } = this.state;
241
- const { recorder } = this.state.poolMetricsRecorderKit;
242
- // Consumers of this .write() are off-chain / outside the VM.
243
- // And there's no way to recover from a failed write.
244
- // So don't await.
245
- void recorder.write({
246
- encumberedBalance,
247
- shareWorth,
248
- ...poolStats,
249
- });
250
- },
251
- },
252
-
253
- depositHandler: {
254
- /** @param {ZCFSeat} lp */
255
- async handle(lp) {
256
- const { shareWorth, shareMint, poolSeat, encumberedBalance } =
257
- this.state;
258
- const { external } = this.facets;
259
-
260
- /** @type {USDCProposalShapes['deposit']} */
261
- // @ts-expect-error ensured by proposalShape
262
- const proposal = lp.getProposal();
263
- checkPoolBalance(poolSeat, shareWorth, USDC, encumberedBalance);
264
- const post = depositCalc(shareWorth, proposal);
265
-
266
- // COMMIT POINT
267
-
268
- try {
269
- const mint = shareMint.mintGains(post.payouts);
270
- this.state.shareWorth = post.shareWorth;
271
- zcf.atomicRearrange(
272
- harden([
273
- // zoe guarantees lp has proposal.give allocated
274
- [lp, poolSeat, proposal.give],
275
- // mintGains() above establishes that mint has post.payouts
276
- [mint, lp, post.payouts],
277
- ]),
278
- );
279
- lp.exit();
280
- mint.exit();
281
- } catch (cause) {
282
- const reason = Error('🚨 cannot commit deposit', { cause });
283
- console.error(reason.message, cause);
284
- zcf.shutdownWithFailure(reason);
285
- }
286
- external.publishPoolMetrics();
287
- },
288
- },
289
- withdrawHandler: {
290
- /** @param {ZCFSeat} lp */
291
- async handle(lp) {
292
- const { shareWorth, shareMint, poolSeat, encumberedBalance } =
293
- this.state;
294
- const { external } = this.facets;
295
-
296
- /** @type {USDCProposalShapes['withdraw']} */
297
- // @ts-expect-error ensured by proposalShape
298
- const proposal = lp.getProposal();
299
- const { zcfSeat: burn } = zcf.makeEmptySeatKit();
300
- checkPoolBalance(poolSeat, shareWorth, USDC, encumberedBalance);
301
- const post = withdrawCalc(shareWorth, proposal);
302
-
303
- // COMMIT POINT
304
-
305
- try {
306
- this.state.shareWorth = post.shareWorth;
307
- zcf.atomicRearrange(
308
- harden([
309
- // zoe guarantees lp has proposal.give allocated
310
- [lp, burn, proposal.give],
311
- // checkPoolBalance() + withdrawCalc() guarantee poolSeat has enough
312
- [poolSeat, lp, post.payouts],
313
- ]),
314
- );
315
- shareMint.burnLosses(proposal.give, burn);
316
- lp.exit();
317
- burn.exit();
318
- } catch (cause) {
319
- const reason = Error('🚨 cannot commit withdraw', { cause });
320
- console.error(reason.message, cause);
321
- zcf.shutdownWithFailure(reason);
322
- }
323
- external.publishPoolMetrics();
324
- },
325
- },
326
- public: {
327
- makeDepositInvitation() {
328
- return zcf.makeInvitation(
329
- this.facets.depositHandler,
330
- 'Deposit',
331
- undefined,
332
- this.state.proposalShapes.deposit,
333
- );
334
- },
335
- makeWithdrawInvitation() {
336
- return zcf.makeInvitation(
337
- this.facets.withdrawHandler,
338
- 'Withdraw',
339
- undefined,
340
- this.state.proposalShapes.withdraw,
341
- );
342
- },
343
- getPublicTopics() {
344
- const { poolMetricsRecorderKit } = this.state;
345
- return {
346
- poolMetrics: makeRecorderTopic(
347
- 'poolMetrics',
348
- poolMetricsRecorderKit,
349
- ),
350
- };
351
- },
352
- },
353
- },
354
- {
355
- finish: ({ facets: { external } }) => {
356
- void external.publishPoolMetrics();
357
- },
358
- },
359
- );
360
- };
361
- harden(prepareLiquidityPoolKit);
362
-
363
- /**
364
- * @typedef {ReturnType<ReturnType<typeof prepareLiquidityPoolKit>>} LiquidityPoolKit
365
- */