@agoric/fast-usdc 0.2.0-u19.1 → 0.2.0-u20.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.
- package/README.md +17 -91
- package/package.json +22 -29
- package/src/cli/config.js +1 -1
- package/src/cli/lp-commands.js +2 -2
- package/src/cli/operator-commands.js +1 -4
- package/src/cli/transfer.js +2 -2
- package/src/constants.js +2 -2
- package/src/operator-kit-interface.js +29 -0
- package/src/pool-share-math.js +18 -26
- package/src/type-guards.js +49 -13
- package/src/types.ts +58 -12
- package/src/utils/fees.js +104 -18
- package/tools/mock-evidence.ts +208 -0
- package/src/add-operators.core.js +0 -63
- package/src/distribute-fees.core.js +0 -93
- package/src/exos/advancer.js +0 -369
- package/src/exos/liquidity-pool.js +0 -414
- package/src/exos/operator-kit.js +0 -124
- package/src/exos/settler.js +0 -393
- package/src/exos/status-manager.js +0 -427
- package/src/exos/transaction-feed.js +0 -259
- package/src/fast-usdc-policy.core.js +0 -65
- package/src/fast-usdc.contract.js +0 -316
- package/src/fast-usdc.flows.js +0 -23
- package/src/start-fast-usdc.core.js +0 -246
- package/src/utils/chain-policies.js +0 -140
- package/src/utils/config-marshal.js +0 -130
- package/src/utils/core-eval.js +0 -73
- package/src/utils/deploy-config.js +0 -127
- package/src/utils/zoe.js +0 -28
package/src/exos/settler.js
DELETED
|
@@ -1,393 +0,0 @@
|
|
|
1
|
-
import { AmountMath } from '@agoric/ertp';
|
|
2
|
-
import { assertAllDefined, makeTracer } from '@agoric/internal';
|
|
3
|
-
import { ChainAddressShape } from '@agoric/orchestration';
|
|
4
|
-
import { atob } from '@endo/base64';
|
|
5
|
-
import { E } from '@endo/far';
|
|
6
|
-
import { M } from '@endo/patterns';
|
|
7
|
-
|
|
8
|
-
import { decodeAddressHook } from '@agoric/cosmic-proto/address-hooks.js';
|
|
9
|
-
import { PendingTxStatus } from '../constants.js';
|
|
10
|
-
import { makeFeeTools } from '../utils/fees.js';
|
|
11
|
-
import {
|
|
12
|
-
CctpTxEvidenceShape,
|
|
13
|
-
EvmHashShape,
|
|
14
|
-
makeNatAmountShape,
|
|
15
|
-
} from '../type-guards.js';
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* @import {FungibleTokenPacketData} from '@agoric/cosmic-proto/ibc/applications/transfer/v2/packet.js';
|
|
19
|
-
* @import {Amount, Brand, NatValue, Payment} from '@agoric/ertp';
|
|
20
|
-
* @import {Denom, OrchestrationAccount, ChainHub, ChainAddress} from '@agoric/orchestration';
|
|
21
|
-
* @import {WithdrawToSeat} from '@agoric/orchestration/src/utils/zoe-tools'
|
|
22
|
-
* @import {IBCChannelID, IBCPacket, VTransferIBCEvent} from '@agoric/vats';
|
|
23
|
-
* @import {Zone} from '@agoric/zone';
|
|
24
|
-
* @import {HostOf, HostInterface} from '@agoric/async-flow';
|
|
25
|
-
* @import {TargetRegistration} from '@agoric/vats/src/bridge-target.js';
|
|
26
|
-
* @import {NobleAddress, LiquidityPoolKit, FeeConfig, EvmHash, LogFn, CctpTxEvidence} from '../types.js';
|
|
27
|
-
* @import {StatusManager} from './status-manager.js';
|
|
28
|
-
*/
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* @param {IBCPacket} data
|
|
32
|
-
* @param {string} remoteDenom
|
|
33
|
-
* @returns {{ nfa: NobleAddress, amount: bigint, EUD: string } | {error: object[]}}
|
|
34
|
-
*/
|
|
35
|
-
const decodeEventPacket = ({ data }, remoteDenom) => {
|
|
36
|
-
// NB: may not be a FungibleTokenPacketData or even JSON
|
|
37
|
-
/** @type {FungibleTokenPacketData} */
|
|
38
|
-
let tx;
|
|
39
|
-
try {
|
|
40
|
-
tx = JSON.parse(atob(data));
|
|
41
|
-
} catch (e) {
|
|
42
|
-
return { error: ['could not parse packet data', data] };
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// given the sourceChannel check, we can be certain of this cast
|
|
46
|
-
const nfa = /** @type {NobleAddress} */ (tx.sender);
|
|
47
|
-
|
|
48
|
-
if (tx.denom !== remoteDenom) {
|
|
49
|
-
const { denom: actual } = tx;
|
|
50
|
-
return { error: ['unexpected denom', { actual, expected: remoteDenom }] };
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
let EUD;
|
|
54
|
-
try {
|
|
55
|
-
({ EUD } = decodeAddressHook(tx.receiver).query);
|
|
56
|
-
if (!EUD) {
|
|
57
|
-
return { error: ['no EUD parameter', tx.receiver] };
|
|
58
|
-
}
|
|
59
|
-
if (typeof EUD !== 'string') {
|
|
60
|
-
return { error: ['EUD is not a string', EUD] };
|
|
61
|
-
}
|
|
62
|
-
} catch (e) {
|
|
63
|
-
return { error: ['no query params', tx.receiver] };
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
let amount;
|
|
67
|
-
try {
|
|
68
|
-
amount = BigInt(tx.amount);
|
|
69
|
-
} catch (e) {
|
|
70
|
-
return { error: ['invalid amount', tx.amount] };
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
return { nfa, amount, EUD };
|
|
74
|
-
};
|
|
75
|
-
harden(decodeEventPacket);
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* NOTE: not meant to be parsable.
|
|
79
|
-
*
|
|
80
|
-
* @param {NobleAddress} addr
|
|
81
|
-
* @param {bigint} amount
|
|
82
|
-
*/
|
|
83
|
-
const makeMintedEarlyKey = (addr, amount) =>
|
|
84
|
-
`pendingTx:${JSON.stringify([addr, String(amount)])}`;
|
|
85
|
-
|
|
86
|
-
/** @param {Brand<'nat'>} USDC */
|
|
87
|
-
export const makeAdvanceDetailsShape = USDC =>
|
|
88
|
-
harden({
|
|
89
|
-
destination: ChainAddressShape,
|
|
90
|
-
forwardingAddress: M.string(),
|
|
91
|
-
fullAmount: makeNatAmountShape(USDC),
|
|
92
|
-
txHash: EvmHashShape,
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
export const stateShape = harden({
|
|
96
|
-
repayer: M.remotable('Repayer'),
|
|
97
|
-
settlementAccount: M.remotable('Account'),
|
|
98
|
-
registration: M.or(M.undefined(), M.remotable('Registration')),
|
|
99
|
-
sourceChannel: M.string(),
|
|
100
|
-
remoteDenom: M.string(),
|
|
101
|
-
mintedEarly: M.remotable('mintedEarly'),
|
|
102
|
-
intermediateRecipient: M.opt(ChainAddressShape),
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* @param {Zone} zone
|
|
107
|
-
* @param {object} caps
|
|
108
|
-
* @param {StatusManager} caps.statusManager
|
|
109
|
-
* @param {Brand<'nat'>} caps.USDC
|
|
110
|
-
* @param {Pick<ZCF, 'makeEmptySeatKit' | 'atomicRearrange'>} caps.zcf
|
|
111
|
-
* @param {FeeConfig} caps.feeConfig
|
|
112
|
-
* @param {HostOf<WithdrawToSeat>} caps.withdrawToSeat
|
|
113
|
-
* @param {import('@agoric/vow').VowTools} caps.vowTools
|
|
114
|
-
* @param {ChainHub} caps.chainHub
|
|
115
|
-
* @param {LogFn} [caps.log]
|
|
116
|
-
*/
|
|
117
|
-
export const prepareSettler = (
|
|
118
|
-
zone,
|
|
119
|
-
{
|
|
120
|
-
chainHub,
|
|
121
|
-
feeConfig,
|
|
122
|
-
log = makeTracer('Settler', true),
|
|
123
|
-
statusManager,
|
|
124
|
-
USDC,
|
|
125
|
-
vowTools,
|
|
126
|
-
withdrawToSeat,
|
|
127
|
-
zcf,
|
|
128
|
-
},
|
|
129
|
-
) => {
|
|
130
|
-
assertAllDefined({ statusManager });
|
|
131
|
-
return zone.exoClassKit(
|
|
132
|
-
'Fast USDC Settler',
|
|
133
|
-
{
|
|
134
|
-
creator: M.interface('SettlerCreatorI', {
|
|
135
|
-
monitorMintingDeposits: M.callWhen().returns(M.any()),
|
|
136
|
-
setIntermediateRecipient: M.call(ChainAddressShape).returns(),
|
|
137
|
-
}),
|
|
138
|
-
tap: M.interface('SettlerTapI', {
|
|
139
|
-
receiveUpcall: M.call(M.record()).returns(M.promise()),
|
|
140
|
-
}),
|
|
141
|
-
notifier: M.interface('SettlerNotifyI', {
|
|
142
|
-
notifyAdvancingResult: M.call(
|
|
143
|
-
makeAdvanceDetailsShape(USDC),
|
|
144
|
-
M.boolean(),
|
|
145
|
-
).returns(),
|
|
146
|
-
checkMintedEarly: M.call(
|
|
147
|
-
CctpTxEvidenceShape,
|
|
148
|
-
ChainAddressShape,
|
|
149
|
-
).returns(M.boolean()),
|
|
150
|
-
}),
|
|
151
|
-
self: M.interface('SettlerSelfI', {
|
|
152
|
-
disburse: M.call(EvmHashShape, M.nat()).returns(M.promise()),
|
|
153
|
-
forward: M.call(EvmHashShape, M.nat(), M.string()).returns(),
|
|
154
|
-
}),
|
|
155
|
-
transferHandler: M.interface('SettlerTransferI', {
|
|
156
|
-
onFulfilled: M.call(M.undefined(), M.string()).returns(),
|
|
157
|
-
onRejected: M.call(M.error(), M.string()).returns(),
|
|
158
|
-
}),
|
|
159
|
-
},
|
|
160
|
-
/**
|
|
161
|
-
* @param {{
|
|
162
|
-
* sourceChannel: IBCChannelID;
|
|
163
|
-
* remoteDenom: Denom;
|
|
164
|
-
* repayer: LiquidityPoolKit['repayer'];
|
|
165
|
-
* settlementAccount: HostInterface<OrchestrationAccount<{ chainId: 'agoric' }>>
|
|
166
|
-
* intermediateRecipient?: ChainAddress;
|
|
167
|
-
* }} config
|
|
168
|
-
*/
|
|
169
|
-
config => {
|
|
170
|
-
log('config', config);
|
|
171
|
-
return {
|
|
172
|
-
...config,
|
|
173
|
-
// make sure the state record has this property, perhaps with an undefined value
|
|
174
|
-
intermediateRecipient: config.intermediateRecipient,
|
|
175
|
-
/** @type {HostInterface<TargetRegistration>|undefined} */
|
|
176
|
-
registration: undefined,
|
|
177
|
-
/** @type {SetStore<ReturnType<typeof makeMintedEarlyKey>>} */
|
|
178
|
-
mintedEarly: zone.detached().setStore('mintedEarly'),
|
|
179
|
-
};
|
|
180
|
-
},
|
|
181
|
-
{
|
|
182
|
-
creator: {
|
|
183
|
-
async monitorMintingDeposits() {
|
|
184
|
-
const { settlementAccount } = this.state;
|
|
185
|
-
const registration = await vowTools.when(
|
|
186
|
-
settlementAccount.monitorTransfers(this.facets.tap),
|
|
187
|
-
);
|
|
188
|
-
assert.typeof(registration, 'object');
|
|
189
|
-
this.state.registration = registration;
|
|
190
|
-
},
|
|
191
|
-
/** @param {ChainAddress} intermediateRecipient */
|
|
192
|
-
setIntermediateRecipient(intermediateRecipient) {
|
|
193
|
-
this.state.intermediateRecipient = intermediateRecipient;
|
|
194
|
-
},
|
|
195
|
-
},
|
|
196
|
-
tap: {
|
|
197
|
-
/** @param {VTransferIBCEvent} event */
|
|
198
|
-
async receiveUpcall(event) {
|
|
199
|
-
log('upcall event', event.packet.sequence, event.blockTime);
|
|
200
|
-
const { sourceChannel, remoteDenom } = this.state;
|
|
201
|
-
const { packet } = event;
|
|
202
|
-
if (packet.source_channel !== sourceChannel) {
|
|
203
|
-
const { source_channel: actual } = packet;
|
|
204
|
-
log('unexpected channel', { actual, expected: sourceChannel });
|
|
205
|
-
return;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
const decoded = decodeEventPacket(event.packet, remoteDenom);
|
|
209
|
-
if ('error' in decoded) {
|
|
210
|
-
log('invalid event packet', decoded.error);
|
|
211
|
-
return;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
const { nfa, amount, EUD } = decoded;
|
|
215
|
-
const { self } = this.facets;
|
|
216
|
-
const found = statusManager.dequeueStatus(nfa, amount);
|
|
217
|
-
log('dequeued', found, 'for', nfa, amount);
|
|
218
|
-
switch (found?.status) {
|
|
219
|
-
case PendingTxStatus.Advanced:
|
|
220
|
-
return self.disburse(found.txHash, amount);
|
|
221
|
-
|
|
222
|
-
case PendingTxStatus.Advancing:
|
|
223
|
-
log('⚠️ tap: minted while advancing', nfa, amount);
|
|
224
|
-
this.state.mintedEarly.add(makeMintedEarlyKey(nfa, amount));
|
|
225
|
-
return;
|
|
226
|
-
|
|
227
|
-
case PendingTxStatus.Observed:
|
|
228
|
-
case PendingTxStatus.AdvanceSkipped:
|
|
229
|
-
case PendingTxStatus.AdvanceFailed:
|
|
230
|
-
return self.forward(found.txHash, amount, EUD);
|
|
231
|
-
|
|
232
|
-
case undefined:
|
|
233
|
-
default:
|
|
234
|
-
log('⚠️ tap: minted before observed', nfa, amount);
|
|
235
|
-
// XXX consider capturing in vstorage
|
|
236
|
-
// we would need a new key, as this does not have a txHash
|
|
237
|
-
this.state.mintedEarly.add(makeMintedEarlyKey(nfa, amount));
|
|
238
|
-
}
|
|
239
|
-
},
|
|
240
|
-
},
|
|
241
|
-
notifier: {
|
|
242
|
-
/**
|
|
243
|
-
* @param {object} ctx
|
|
244
|
-
* @param {EvmHash} ctx.txHash
|
|
245
|
-
* @param {NobleAddress} ctx.forwardingAddress
|
|
246
|
-
* @param {Amount<'nat'>} ctx.fullAmount
|
|
247
|
-
* @param {ChainAddress} ctx.destination
|
|
248
|
-
* @param {boolean} success
|
|
249
|
-
* @returns {void}
|
|
250
|
-
*/
|
|
251
|
-
notifyAdvancingResult(
|
|
252
|
-
{ txHash, forwardingAddress, fullAmount, destination },
|
|
253
|
-
success,
|
|
254
|
-
) {
|
|
255
|
-
const { mintedEarly } = this.state;
|
|
256
|
-
const { value: fullValue } = fullAmount;
|
|
257
|
-
const key = makeMintedEarlyKey(forwardingAddress, fullValue);
|
|
258
|
-
if (mintedEarly.has(key)) {
|
|
259
|
-
mintedEarly.delete(key);
|
|
260
|
-
statusManager.advanceOutcomeForMintedEarly(txHash, success);
|
|
261
|
-
if (success) {
|
|
262
|
-
void this.facets.self.disburse(txHash, fullValue);
|
|
263
|
-
} else {
|
|
264
|
-
void this.facets.self.forward(
|
|
265
|
-
txHash,
|
|
266
|
-
fullValue,
|
|
267
|
-
destination.value,
|
|
268
|
-
);
|
|
269
|
-
}
|
|
270
|
-
} else {
|
|
271
|
-
statusManager.advanceOutcome(forwardingAddress, fullValue, success);
|
|
272
|
-
}
|
|
273
|
-
},
|
|
274
|
-
/**
|
|
275
|
-
* @param {CctpTxEvidence} evidence
|
|
276
|
-
* @param {ChainAddress} destination
|
|
277
|
-
* @returns {boolean}
|
|
278
|
-
* @throws {Error} if minted early, so advancer doesn't advance
|
|
279
|
-
*/
|
|
280
|
-
checkMintedEarly(evidence, destination) {
|
|
281
|
-
const {
|
|
282
|
-
tx: { forwardingAddress, amount },
|
|
283
|
-
txHash,
|
|
284
|
-
} = evidence;
|
|
285
|
-
const key = makeMintedEarlyKey(forwardingAddress, amount);
|
|
286
|
-
const { mintedEarly } = this.state;
|
|
287
|
-
if (mintedEarly.has(key)) {
|
|
288
|
-
log(
|
|
289
|
-
'matched minted early key, initiating forward',
|
|
290
|
-
forwardingAddress,
|
|
291
|
-
amount,
|
|
292
|
-
);
|
|
293
|
-
mintedEarly.delete(key);
|
|
294
|
-
statusManager.advanceOutcomeForUnknownMint(evidence);
|
|
295
|
-
void this.facets.self.forward(txHash, amount, destination.value);
|
|
296
|
-
return true;
|
|
297
|
-
}
|
|
298
|
-
return false;
|
|
299
|
-
},
|
|
300
|
-
},
|
|
301
|
-
self: {
|
|
302
|
-
/**
|
|
303
|
-
* @param {EvmHash} txHash
|
|
304
|
-
* @param {NatValue} fullValue
|
|
305
|
-
*/
|
|
306
|
-
async disburse(txHash, fullValue) {
|
|
307
|
-
const { repayer, settlementAccount } = this.state;
|
|
308
|
-
const received = AmountMath.make(USDC, fullValue);
|
|
309
|
-
const { zcfSeat: settlingSeat } = zcf.makeEmptySeatKit();
|
|
310
|
-
const { calculateSplit } = makeFeeTools(feeConfig);
|
|
311
|
-
const split = calculateSplit(received);
|
|
312
|
-
log('disbursing', split);
|
|
313
|
-
|
|
314
|
-
// If this throws, which arguably can't occur since we don't ever
|
|
315
|
-
// withdraw more than has been deposited (as denoted by
|
|
316
|
-
// `FungibleTokenPacketData`), funds will remain in the
|
|
317
|
-
// `settlementAccount`. A remediation can occur in a future upgrade.
|
|
318
|
-
await vowTools.when(
|
|
319
|
-
withdrawToSeat(
|
|
320
|
-
// @ts-expect-error LocalAccountMethods vs OrchestrationAccount
|
|
321
|
-
settlementAccount,
|
|
322
|
-
settlingSeat,
|
|
323
|
-
harden({ In: received }),
|
|
324
|
-
),
|
|
325
|
-
);
|
|
326
|
-
zcf.atomicRearrange(
|
|
327
|
-
harden([[settlingSeat, settlingSeat, { In: received }, split]]),
|
|
328
|
-
);
|
|
329
|
-
repayer.repay(settlingSeat, split);
|
|
330
|
-
settlingSeat.exit();
|
|
331
|
-
|
|
332
|
-
// update status manager, marking tx `DISBURSED`
|
|
333
|
-
statusManager.disbursed(txHash, split);
|
|
334
|
-
},
|
|
335
|
-
/**
|
|
336
|
-
* @param {EvmHash} txHash
|
|
337
|
-
* @param {NatValue} fullValue
|
|
338
|
-
* @param {string} EUD
|
|
339
|
-
*/
|
|
340
|
-
forward(txHash, fullValue, EUD) {
|
|
341
|
-
const { settlementAccount, intermediateRecipient } = this.state;
|
|
342
|
-
|
|
343
|
-
const dest = (() => {
|
|
344
|
-
try {
|
|
345
|
-
return chainHub.makeChainAddress(EUD);
|
|
346
|
-
} catch (e) {
|
|
347
|
-
log('⚠️ forward transfer failed!', e, txHash);
|
|
348
|
-
statusManager.forwarded(txHash, false);
|
|
349
|
-
return null;
|
|
350
|
-
}
|
|
351
|
-
})();
|
|
352
|
-
if (!dest) return;
|
|
353
|
-
|
|
354
|
-
const txfrV = E(settlementAccount).transfer(
|
|
355
|
-
dest,
|
|
356
|
-
AmountMath.make(USDC, fullValue),
|
|
357
|
-
{ forwardOpts: { intermediateRecipient } },
|
|
358
|
-
);
|
|
359
|
-
void vowTools.watch(txfrV, this.facets.transferHandler, txHash);
|
|
360
|
-
},
|
|
361
|
-
},
|
|
362
|
-
transferHandler: {
|
|
363
|
-
/**
|
|
364
|
-
* @param {unknown} _result
|
|
365
|
-
* @param {EvmHash} txHash
|
|
366
|
-
*/
|
|
367
|
-
onFulfilled(_result, txHash) {
|
|
368
|
-
// update status manager, marking tx `FORWARDED` without fee split
|
|
369
|
-
statusManager.forwarded(txHash, true);
|
|
370
|
-
},
|
|
371
|
-
/**
|
|
372
|
-
* @param {unknown} reason
|
|
373
|
-
* @param {EvmHash} txHash
|
|
374
|
-
*/
|
|
375
|
-
onRejected(reason, txHash) {
|
|
376
|
-
// funds remain in `settlementAccount` and must be recovered via a
|
|
377
|
-
// contract upgrade
|
|
378
|
-
log('🚨 forward transfer rejected!', reason, txHash);
|
|
379
|
-
// update status manager, flagging a terminal state that needs to be
|
|
380
|
-
// manual intervention or a code update to remediate
|
|
381
|
-
statusManager.forwarded(txHash, false);
|
|
382
|
-
},
|
|
383
|
-
},
|
|
384
|
-
},
|
|
385
|
-
{
|
|
386
|
-
stateShape,
|
|
387
|
-
},
|
|
388
|
-
);
|
|
389
|
-
};
|
|
390
|
-
harden(prepareSettler);
|
|
391
|
-
|
|
392
|
-
// Expose the whole kit because the contract needs `creatorFacet` and the Advancer needs `notifier`
|
|
393
|
-
/** @typedef {ReturnType<ReturnType<typeof prepareSettler>>} SettlerKit */
|