@agoric/fast-usdc 0.2.0-u18.0 → 0.2.0-u19.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/package.json +26 -25
- package/src/add-operators.core.js +63 -0
- package/src/cli/cli.js +1 -1
- package/src/cli/config-commands.js +1 -1
- package/src/cli/config.js +1 -1
- package/src/cli/lp-commands.js +21 -39
- package/src/cli/operator-commands.js +1 -0
- package/src/cli/transfer.js +5 -5
- package/src/clientSupport.js +98 -0
- package/src/distribute-fees.core.js +93 -0
- package/src/exos/advancer.js +109 -41
- package/src/exos/liquidity-pool.js +81 -34
- package/src/exos/operator-kit.js +0 -2
- package/src/exos/settler.js +154 -95
- package/src/exos/status-manager.js +97 -59
- package/src/exos/transaction-feed.js +30 -9
- package/src/fast-usdc-policy.core.js +2 -12
- package/src/fast-usdc.contract.js +23 -31
- package/src/main.js +1 -0
- package/src/pool-share-math.js +55 -9
- package/src/{fast-usdc.start.js → start-fast-usdc.core.js} +13 -81
- package/src/type-guards.js +37 -8
- package/src/types.ts +34 -5
- package/src/utils/chain-policies.js +140 -0
- package/src/utils/core-eval.js +73 -0
- package/src/utils/deploy-config.js +36 -75
- package/src/utils/fees.js +3 -4
- /package/src/{util → cli/util}/agoric.js +0 -0
- /package/src/{util → cli/util}/bank.js +0 -0
- /package/src/{util → cli/util}/cctp.js +0 -0
- /package/src/{util → cli/util}/file.js +0 -0
- /package/src/{util → cli/util}/noble.js +0 -0
package/src/exos/settler.js
CHANGED
|
@@ -8,20 +8,72 @@ import { M } from '@endo/patterns';
|
|
|
8
8
|
import { decodeAddressHook } from '@agoric/cosmic-proto/address-hooks.js';
|
|
9
9
|
import { PendingTxStatus } from '../constants.js';
|
|
10
10
|
import { makeFeeTools } from '../utils/fees.js';
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
CctpTxEvidenceShape,
|
|
13
|
+
EvmHashShape,
|
|
14
|
+
makeNatAmountShape,
|
|
15
|
+
} from '../type-guards.js';
|
|
12
16
|
|
|
13
17
|
/**
|
|
14
18
|
* @import {FungibleTokenPacketData} from '@agoric/cosmic-proto/ibc/applications/transfer/v2/packet.js';
|
|
19
|
+
* @import {Amount, Brand, NatValue, Payment} from '@agoric/ertp';
|
|
15
20
|
* @import {Denom, OrchestrationAccount, ChainHub, ChainAddress} from '@agoric/orchestration';
|
|
16
21
|
* @import {WithdrawToSeat} from '@agoric/orchestration/src/utils/zoe-tools'
|
|
17
|
-
* @import {IBCChannelID, VTransferIBCEvent} from '@agoric/vats';
|
|
22
|
+
* @import {IBCChannelID, IBCPacket, VTransferIBCEvent} from '@agoric/vats';
|
|
18
23
|
* @import {Zone} from '@agoric/zone';
|
|
19
24
|
* @import {HostOf, HostInterface} from '@agoric/async-flow';
|
|
20
25
|
* @import {TargetRegistration} from '@agoric/vats/src/bridge-target.js';
|
|
21
|
-
* @import {NobleAddress, LiquidityPoolKit, FeeConfig, EvmHash, LogFn} from '../types.js';
|
|
26
|
+
* @import {NobleAddress, LiquidityPoolKit, FeeConfig, EvmHash, LogFn, CctpTxEvidence} from '../types.js';
|
|
22
27
|
* @import {StatusManager} from './status-manager.js';
|
|
23
28
|
*/
|
|
24
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
|
+
|
|
25
77
|
/**
|
|
26
78
|
* NOTE: not meant to be parsable.
|
|
27
79
|
*
|
|
@@ -31,6 +83,25 @@ import { EvmHashShape } from '../type-guards.js';
|
|
|
31
83
|
const makeMintedEarlyKey = (addr, amount) =>
|
|
32
84
|
`pendingTx:${JSON.stringify([addr, String(amount)])}`;
|
|
33
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
|
+
|
|
34
105
|
/**
|
|
35
106
|
* @param {Zone} zone
|
|
36
107
|
* @param {object} caps
|
|
@@ -67,26 +138,23 @@ export const prepareSettler = (
|
|
|
67
138
|
tap: M.interface('SettlerTapI', {
|
|
68
139
|
receiveUpcall: M.call(M.record()).returns(M.promise()),
|
|
69
140
|
}),
|
|
70
|
-
|
|
141
|
+
notifier: M.interface('SettlerNotifyI', {
|
|
71
142
|
notifyAdvancingResult: M.call(
|
|
72
|
-
|
|
143
|
+
makeAdvanceDetailsShape(USDC),
|
|
73
144
|
M.boolean(),
|
|
74
145
|
).returns(),
|
|
146
|
+
checkMintedEarly: M.call(
|
|
147
|
+
CctpTxEvidenceShape,
|
|
148
|
+
ChainAddressShape,
|
|
149
|
+
).returns(M.boolean()),
|
|
75
150
|
}),
|
|
76
151
|
self: M.interface('SettlerSelfI', {
|
|
77
|
-
disburse: M.call(EvmHashShape, M.
|
|
78
|
-
|
|
79
|
-
),
|
|
80
|
-
forward: M.call(
|
|
81
|
-
M.opt(EvmHashShape),
|
|
82
|
-
M.string(),
|
|
83
|
-
M.nat(),
|
|
84
|
-
M.string(),
|
|
85
|
-
).returns(),
|
|
152
|
+
disburse: M.call(EvmHashShape, M.nat()).returns(M.promise()),
|
|
153
|
+
forward: M.call(EvmHashShape, M.nat(), M.string()).returns(),
|
|
86
154
|
}),
|
|
87
155
|
transferHandler: M.interface('SettlerTransferI', {
|
|
88
|
-
onFulfilled: M.call(M.
|
|
89
|
-
onRejected: M.call(M.
|
|
156
|
+
onFulfilled: M.call(M.undefined(), M.string()).returns(),
|
|
157
|
+
onRejected: M.call(M.error(), M.string()).returns(),
|
|
90
158
|
}),
|
|
91
159
|
},
|
|
92
160
|
/**
|
|
@@ -137,61 +205,40 @@ export const prepareSettler = (
|
|
|
137
205
|
return;
|
|
138
206
|
}
|
|
139
207
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
);
|
|
144
|
-
|
|
145
|
-
// given the sourceChannel check, we can be certain of this cast
|
|
146
|
-
const nfa = /** @type {NobleAddress} */ (tx.sender);
|
|
147
|
-
|
|
148
|
-
if (tx.denom !== remoteDenom) {
|
|
149
|
-
const { denom: actual } = tx;
|
|
150
|
-
log('unexpected denom', { actual, expected: remoteDenom });
|
|
151
|
-
return;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
let EUD;
|
|
155
|
-
try {
|
|
156
|
-
({ EUD } = decodeAddressHook(tx.receiver).query);
|
|
157
|
-
if (!EUD) {
|
|
158
|
-
log('no EUD parameter', tx.receiver);
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
|
-
if (typeof EUD !== 'string') {
|
|
162
|
-
log('EUD is not a string', EUD);
|
|
163
|
-
return;
|
|
164
|
-
}
|
|
165
|
-
} catch (e) {
|
|
166
|
-
log('no query params', tx.receiver);
|
|
208
|
+
const decoded = decodeEventPacket(event.packet, remoteDenom);
|
|
209
|
+
if ('error' in decoded) {
|
|
210
|
+
log('invalid event packet', decoded.error);
|
|
167
211
|
return;
|
|
168
212
|
}
|
|
169
213
|
|
|
170
|
-
const
|
|
171
|
-
|
|
214
|
+
const { nfa, amount, EUD } = decoded;
|
|
172
215
|
const { self } = this.facets;
|
|
173
216
|
const found = statusManager.dequeueStatus(nfa, amount);
|
|
174
217
|
log('dequeued', found, 'for', nfa, amount);
|
|
175
218
|
switch (found?.status) {
|
|
176
219
|
case PendingTxStatus.Advanced:
|
|
177
|
-
return self.disburse(found.txHash,
|
|
220
|
+
return self.disburse(found.txHash, amount);
|
|
178
221
|
|
|
179
222
|
case PendingTxStatus.Advancing:
|
|
223
|
+
log('⚠️ tap: minted while advancing', nfa, amount);
|
|
180
224
|
this.state.mintedEarly.add(makeMintedEarlyKey(nfa, amount));
|
|
181
225
|
return;
|
|
182
226
|
|
|
183
227
|
case PendingTxStatus.Observed:
|
|
184
228
|
case PendingTxStatus.AdvanceSkipped:
|
|
185
229
|
case PendingTxStatus.AdvanceFailed:
|
|
186
|
-
return self.forward(found.txHash,
|
|
230
|
+
return self.forward(found.txHash, amount, EUD);
|
|
187
231
|
|
|
188
232
|
case undefined:
|
|
189
233
|
default:
|
|
190
|
-
log('⚠️ tap:
|
|
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));
|
|
191
238
|
}
|
|
192
239
|
},
|
|
193
240
|
},
|
|
194
|
-
|
|
241
|
+
notifier: {
|
|
195
242
|
/**
|
|
196
243
|
* @param {object} ctx
|
|
197
244
|
* @param {EvmHash} ctx.txHash
|
|
@@ -210,16 +257,12 @@ export const prepareSettler = (
|
|
|
210
257
|
const key = makeMintedEarlyKey(forwardingAddress, fullValue);
|
|
211
258
|
if (mintedEarly.has(key)) {
|
|
212
259
|
mintedEarly.delete(key);
|
|
260
|
+
statusManager.advanceOutcomeForMintedEarly(txHash, success);
|
|
213
261
|
if (success) {
|
|
214
|
-
void this.facets.self.disburse(
|
|
215
|
-
txHash,
|
|
216
|
-
forwardingAddress,
|
|
217
|
-
fullValue,
|
|
218
|
-
);
|
|
262
|
+
void this.facets.self.disburse(txHash, fullValue);
|
|
219
263
|
} else {
|
|
220
264
|
void this.facets.self.forward(
|
|
221
265
|
txHash,
|
|
222
|
-
forwardingAddress,
|
|
223
266
|
fullValue,
|
|
224
267
|
destination.value,
|
|
225
268
|
);
|
|
@@ -228,14 +271,39 @@ export const prepareSettler = (
|
|
|
228
271
|
statusManager.advanceOutcome(forwardingAddress, fullValue, success);
|
|
229
272
|
}
|
|
230
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
|
+
},
|
|
231
300
|
},
|
|
232
301
|
self: {
|
|
233
302
|
/**
|
|
234
303
|
* @param {EvmHash} txHash
|
|
235
|
-
* @param {NobleAddress} nfa
|
|
236
304
|
* @param {NatValue} fullValue
|
|
237
305
|
*/
|
|
238
|
-
async disburse(txHash,
|
|
306
|
+
async disburse(txHash, fullValue) {
|
|
239
307
|
const { repayer, settlementAccount } = this.state;
|
|
240
308
|
const received = AmountMath.make(USDC, fullValue);
|
|
241
309
|
const { zcfSeat: settlingSeat } = zcf.makeEmptySeatKit();
|
|
@@ -259,76 +327,67 @@ export const prepareSettler = (
|
|
|
259
327
|
harden([[settlingSeat, settlingSeat, { In: received }, split]]),
|
|
260
328
|
);
|
|
261
329
|
repayer.repay(settlingSeat, split);
|
|
330
|
+
settlingSeat.exit();
|
|
262
331
|
|
|
263
|
-
// update status manager, marking tx `
|
|
332
|
+
// update status manager, marking tx `DISBURSED`
|
|
264
333
|
statusManager.disbursed(txHash, split);
|
|
265
334
|
},
|
|
266
335
|
/**
|
|
267
336
|
* @param {EvmHash} txHash
|
|
268
|
-
* @param {NobleAddress} nfa
|
|
269
337
|
* @param {NatValue} fullValue
|
|
270
338
|
* @param {string} EUD
|
|
271
339
|
*/
|
|
272
|
-
forward(txHash,
|
|
340
|
+
forward(txHash, fullValue, EUD) {
|
|
273
341
|
const { settlementAccount, intermediateRecipient } = this.state;
|
|
274
342
|
|
|
275
|
-
const dest =
|
|
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;
|
|
276
353
|
|
|
277
|
-
// TODO? statusManager.forwarding(txHash, sender, amount);
|
|
278
354
|
const txfrV = E(settlementAccount).transfer(
|
|
279
355
|
dest,
|
|
280
356
|
AmountMath.make(USDC, fullValue),
|
|
281
357
|
{ forwardOpts: { intermediateRecipient } },
|
|
282
358
|
);
|
|
283
|
-
void vowTools.watch(txfrV, this.facets.transferHandler,
|
|
284
|
-
txHash,
|
|
285
|
-
nfa,
|
|
286
|
-
fullValue,
|
|
287
|
-
});
|
|
359
|
+
void vowTools.watch(txfrV, this.facets.transferHandler, txHash);
|
|
288
360
|
},
|
|
289
361
|
},
|
|
290
362
|
transferHandler: {
|
|
291
363
|
/**
|
|
292
364
|
* @param {unknown} _result
|
|
293
|
-
* @param {
|
|
294
|
-
*
|
|
295
|
-
* @typedef {{
|
|
296
|
-
* txHash: EvmHash;
|
|
297
|
-
* nfa: NobleAddress;
|
|
298
|
-
* fullValue: NatValue;
|
|
299
|
-
* }} SettlerTransferCtx
|
|
365
|
+
* @param {EvmHash} txHash
|
|
300
366
|
*/
|
|
301
|
-
onFulfilled(_result,
|
|
302
|
-
|
|
303
|
-
statusManager.forwarded(txHash,
|
|
367
|
+
onFulfilled(_result, txHash) {
|
|
368
|
+
// update status manager, marking tx `FORWARDED` without fee split
|
|
369
|
+
statusManager.forwarded(txHash, true);
|
|
304
370
|
},
|
|
305
371
|
/**
|
|
306
372
|
* @param {unknown} reason
|
|
307
|
-
* @param {
|
|
373
|
+
* @param {EvmHash} txHash
|
|
308
374
|
*/
|
|
309
|
-
onRejected(reason,
|
|
310
|
-
|
|
311
|
-
//
|
|
312
|
-
|
|
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);
|
|
313
382
|
},
|
|
314
383
|
},
|
|
315
384
|
},
|
|
316
385
|
{
|
|
317
|
-
stateShape
|
|
318
|
-
repayer: M.remotable('Repayer'),
|
|
319
|
-
settlementAccount: M.remotable('Account'),
|
|
320
|
-
registration: M.or(M.undefined(), M.remotable('Registration')),
|
|
321
|
-
sourceChannel: M.string(),
|
|
322
|
-
remoteDenom: M.string(),
|
|
323
|
-
mintedEarly: M.remotable('mintedEarly'),
|
|
324
|
-
intermediateRecipient: M.opt(ChainAddressShape),
|
|
325
|
-
}),
|
|
386
|
+
stateShape,
|
|
326
387
|
},
|
|
327
388
|
);
|
|
328
389
|
};
|
|
329
390
|
harden(prepareSettler);
|
|
330
391
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
* @typedef {ReturnType<ReturnType<typeof prepareSettler>>} SettlerKit
|
|
334
|
-
*/
|
|
392
|
+
// Expose the whole kit because the contract needs `creatorFacet` and the Advancer needs `notifier`
|
|
393
|
+
/** @typedef {ReturnType<ReturnType<typeof prepareSettler>>} SettlerKit */
|
|
@@ -53,6 +53,12 @@ const pendingTxKeyOf = evidence => {
|
|
|
53
53
|
* }} StatusManagerPowers
|
|
54
54
|
*/
|
|
55
55
|
|
|
56
|
+
export const stateShape = harden({
|
|
57
|
+
pendingSettleTxs: M.remotable(),
|
|
58
|
+
seenTxs: M.remotable(),
|
|
59
|
+
storedCompletedTxs: M.remotable(),
|
|
60
|
+
});
|
|
61
|
+
|
|
56
62
|
/**
|
|
57
63
|
* The `StatusManager` keeps track of Pending and Seen Transactions
|
|
58
64
|
* via {@link PendingTxStatus} states, aiding in coordination between the `Advancer`
|
|
@@ -69,14 +75,15 @@ export const prepareStatusManager = (
|
|
|
69
75
|
txnsNode,
|
|
70
76
|
{
|
|
71
77
|
marshaller,
|
|
72
|
-
|
|
78
|
+
// eslint-disable-next-line no-unused-vars
|
|
79
|
+
log = makeTracer('StatusManager', true),
|
|
73
80
|
} = /** @type {StatusManagerPowers} */ ({}),
|
|
74
81
|
) => {
|
|
75
82
|
/**
|
|
76
83
|
* Keyed by a tuple of the Noble Forwarding Account and amount.
|
|
77
84
|
* @type {MapStore<PendingTxKey, PendingTx[]>}
|
|
78
85
|
*/
|
|
79
|
-
const
|
|
86
|
+
const pendingSettleTxs = zone.mapStore('PendingSettleTxs', {
|
|
80
87
|
keyShape: M.string(),
|
|
81
88
|
valueShape: M.arrayOf(PendingTxShape),
|
|
82
89
|
});
|
|
@@ -84,13 +91,18 @@ export const prepareStatusManager = (
|
|
|
84
91
|
/**
|
|
85
92
|
* Transactions seen *ever* by the contract.
|
|
86
93
|
*
|
|
87
|
-
* Note that like all durable stores, this
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
94
|
+
* Note that like all durable stores, this MapStore is kept in IAVL. It stores
|
|
95
|
+
* the `blockTimestamp` so that later we can prune old transactions.
|
|
96
|
+
*
|
|
97
|
+
* Note that `blockTimestamp` can drift between chains. Fortunately all CCTP
|
|
98
|
+
* chains use the same Unix epoch and won't drift more than minutes apart,
|
|
99
|
+
* which is more than enough precision for pruning old transaction.
|
|
100
|
+
*
|
|
101
|
+
* @type {MapStore<EvmHash, NatValue>}
|
|
91
102
|
*/
|
|
92
|
-
const seenTxs = zone.
|
|
103
|
+
const seenTxs = zone.mapStore('SeenTxs', {
|
|
93
104
|
keyShape: M.string(),
|
|
105
|
+
valueShape: M.nat(),
|
|
94
106
|
});
|
|
95
107
|
|
|
96
108
|
/**
|
|
@@ -153,10 +165,10 @@ export const prepareStatusManager = (
|
|
|
153
165
|
if (seenTxs.has(txHash)) {
|
|
154
166
|
throw makeError(`Transaction already seen: ${q(txHash)}`);
|
|
155
167
|
}
|
|
156
|
-
seenTxs.
|
|
168
|
+
seenTxs.init(txHash, evidence.blockTimestamp);
|
|
157
169
|
|
|
158
170
|
appendToStoredArray(
|
|
159
|
-
|
|
171
|
+
pendingSettleTxs,
|
|
160
172
|
pendingTxKeyOf(evidence),
|
|
161
173
|
harden({ ...evidence, status }),
|
|
162
174
|
);
|
|
@@ -177,8 +189,8 @@ export const prepareStatusManager = (
|
|
|
177
189
|
*/
|
|
178
190
|
function setPendingTxStatus({ nfa, amount }, status) {
|
|
179
191
|
const key = makePendingTxKey(nfa, amount);
|
|
180
|
-
|
|
181
|
-
const pending =
|
|
192
|
+
pendingSettleTxs.has(key) || Fail`no advancing tx with ${{ nfa, amount }}`;
|
|
193
|
+
const pending = pendingSettleTxs.get(key);
|
|
182
194
|
const ix = pending.findIndex(tx => tx.status === PendingTxStatus.Advancing);
|
|
183
195
|
ix >= 0 || Fail`no advancing tx with ${{ nfa, amount }}`;
|
|
184
196
|
const [prefix, tx, suffix] = [
|
|
@@ -187,7 +199,7 @@ export const prepareStatusManager = (
|
|
|
187
199
|
pending.slice(ix + 1),
|
|
188
200
|
];
|
|
189
201
|
const txpost = { ...tx, status };
|
|
190
|
-
|
|
202
|
+
pendingSettleTxs.set(key, harden([...prefix, txpost, ...suffix]));
|
|
191
203
|
void publishTxnRecord(tx.txHash, harden({ status }));
|
|
192
204
|
}
|
|
193
205
|
|
|
@@ -195,24 +207,19 @@ export const prepareStatusManager = (
|
|
|
195
207
|
'Fast USDC Status Manager',
|
|
196
208
|
M.interface('StatusManagerI', {
|
|
197
209
|
// TODO: naming scheme for transition events
|
|
198
|
-
advance: M.call(CctpTxEvidenceShape).returns(
|
|
210
|
+
advance: M.call(CctpTxEvidenceShape).returns(),
|
|
199
211
|
advanceOutcome: M.call(M.string(), M.nat(), M.boolean()).returns(),
|
|
200
|
-
skipAdvance: M.call(CctpTxEvidenceShape, M.arrayOf(M.string())).returns(
|
|
201
|
-
|
|
202
|
-
),
|
|
203
|
-
observe: M.call(CctpTxEvidenceShape).returns(
|
|
212
|
+
skipAdvance: M.call(CctpTxEvidenceShape, M.arrayOf(M.string())).returns(),
|
|
213
|
+
advanceOutcomeForMintedEarly: M.call(EvmHashShape, M.boolean()).returns(),
|
|
214
|
+
advanceOutcomeForUnknownMint: M.call(CctpTxEvidenceShape).returns(),
|
|
215
|
+
observe: M.call(CctpTxEvidenceShape).returns(),
|
|
204
216
|
hasBeenObserved: M.call(CctpTxEvidenceShape).returns(M.boolean()),
|
|
205
217
|
deleteCompletedTxs: M.call().returns(M.undefined()),
|
|
206
218
|
dequeueStatus: M.call(M.string(), M.bigint()).returns(
|
|
207
219
|
M.or(
|
|
208
220
|
{
|
|
209
221
|
txHash: EvmHashShape,
|
|
210
|
-
status: M.or(
|
|
211
|
-
PendingTxStatus.Advanced,
|
|
212
|
-
PendingTxStatus.AdvanceSkipped,
|
|
213
|
-
PendingTxStatus.AdvanceFailed,
|
|
214
|
-
PendingTxStatus.Observed,
|
|
215
|
-
),
|
|
222
|
+
status: M.or(...Object.values(PendingTxStatus)),
|
|
216
223
|
},
|
|
217
224
|
M.undefined(),
|
|
218
225
|
),
|
|
@@ -220,9 +227,7 @@ export const prepareStatusManager = (
|
|
|
220
227
|
disbursed: M.call(EvmHashShape, AmountKeywordRecordShape).returns(
|
|
221
228
|
M.undefined(),
|
|
222
229
|
),
|
|
223
|
-
forwarded: M.call(
|
|
224
|
-
M.undefined(),
|
|
225
|
-
),
|
|
230
|
+
forwarded: M.call(EvmHashShape, M.boolean()).returns(),
|
|
226
231
|
lookupPending: M.call(M.string(), M.bigint()).returns(
|
|
227
232
|
M.arrayOf(PendingTxShape),
|
|
228
233
|
),
|
|
@@ -258,7 +263,7 @@ export const prepareStatusManager = (
|
|
|
258
263
|
},
|
|
259
264
|
|
|
260
265
|
/**
|
|
261
|
-
* Record result of ADVANCING
|
|
266
|
+
* Record result of an ADVANCING transaction
|
|
262
267
|
*
|
|
263
268
|
* @param {NobleAddress} nfa Noble Forwarding Account
|
|
264
269
|
* @param {import('@agoric/ertp').NatValue} amount
|
|
@@ -272,6 +277,43 @@ export const prepareStatusManager = (
|
|
|
272
277
|
);
|
|
273
278
|
},
|
|
274
279
|
|
|
280
|
+
/**
|
|
281
|
+
* If minted while advancing, publish a status update for the advance
|
|
282
|
+
* to vstorage.
|
|
283
|
+
*
|
|
284
|
+
* Does not add or amend `pendingSettleTxs` as this has
|
|
285
|
+
* already settled.
|
|
286
|
+
*
|
|
287
|
+
* @param {EvmHash} txHash
|
|
288
|
+
* @param {boolean} success whether the Transfer succeeded
|
|
289
|
+
*/
|
|
290
|
+
advanceOutcomeForMintedEarly(txHash, success) {
|
|
291
|
+
void publishTxnRecord(
|
|
292
|
+
txHash,
|
|
293
|
+
harden({
|
|
294
|
+
status: success
|
|
295
|
+
? PendingTxStatus.Advanced
|
|
296
|
+
: PendingTxStatus.AdvanceFailed,
|
|
297
|
+
}),
|
|
298
|
+
);
|
|
299
|
+
},
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* If minted before observed and the evidence is eventually
|
|
303
|
+
* reported, publish the evidence without adding to `pendingSettleTxs`
|
|
304
|
+
*
|
|
305
|
+
* @param {CctpTxEvidence} evidence
|
|
306
|
+
*/
|
|
307
|
+
advanceOutcomeForUnknownMint(evidence) {
|
|
308
|
+
const { txHash } = evidence;
|
|
309
|
+
// unexpected path, since `hasBeenObserved` will be called before this
|
|
310
|
+
if (seenTxs.has(txHash)) {
|
|
311
|
+
throw makeError(`Transaction already seen: ${q(txHash)}`);
|
|
312
|
+
}
|
|
313
|
+
seenTxs.init(txHash, evidence.blockTimestamp);
|
|
314
|
+
publishEvidence(txHash, evidence);
|
|
315
|
+
},
|
|
316
|
+
|
|
275
317
|
/**
|
|
276
318
|
* Add a new transaction with OBSERVED status
|
|
277
319
|
* @param {CctpTxEvidence} evidence
|
|
@@ -303,34 +345,32 @@ export const prepareStatusManager = (
|
|
|
303
345
|
},
|
|
304
346
|
|
|
305
347
|
/**
|
|
306
|
-
* Remove and return
|
|
348
|
+
* Remove and return the oldest pending settlement transaction that matches the given
|
|
349
|
+
* forwarding account and amount. Since multiple pending transactions may exist with
|
|
350
|
+
* identical (account, amount) pairs, we process them in FIFO order.
|
|
307
351
|
*
|
|
308
352
|
* @param {NobleAddress} nfa
|
|
309
353
|
* @param {bigint} amount
|
|
310
|
-
* @returns {Pick<PendingTx, 'status' | 'txHash'> | undefined} undefined if
|
|
311
|
-
*
|
|
354
|
+
* @returns {Pick<PendingTx, 'status' | 'txHash'> | undefined} undefined if no pending
|
|
355
|
+
* transactions exist for this address and amount combination.
|
|
312
356
|
*/
|
|
313
357
|
dequeueStatus(nfa, amount) {
|
|
314
358
|
const key = makePendingTxKey(nfa, amount);
|
|
315
|
-
if (!
|
|
316
|
-
const pending =
|
|
359
|
+
if (!pendingSettleTxs.has(key)) return undefined;
|
|
360
|
+
const pending = pendingSettleTxs.get(key);
|
|
317
361
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
362
|
+
if (pending.length === 0) {
|
|
363
|
+
return undefined;
|
|
364
|
+
}
|
|
365
|
+
// extract first item
|
|
366
|
+
const [{ status, txHash }, ...remaining] = pending;
|
|
322
367
|
|
|
323
|
-
if (
|
|
324
|
-
|
|
325
|
-
pendingCopy.splice(dequeueIdx, 1);
|
|
326
|
-
pendingTxs.set(key, harden(pendingCopy));
|
|
368
|
+
if (remaining.length) {
|
|
369
|
+
pendingSettleTxs.set(key, harden(remaining));
|
|
327
370
|
} else {
|
|
328
|
-
|
|
371
|
+
pendingSettleTxs.delete(key);
|
|
329
372
|
}
|
|
330
373
|
|
|
331
|
-
const { status, txHash } = pending[dequeueIdx];
|
|
332
|
-
// TODO: store txHash -> evidence for txs pending settlement?
|
|
333
|
-
// If necessary for vstorage writes in `forwarded` and `settled`
|
|
334
374
|
return harden({ status, txHash });
|
|
335
375
|
},
|
|
336
376
|
|
|
@@ -348,21 +388,18 @@ export const prepareStatusManager = (
|
|
|
348
388
|
},
|
|
349
389
|
|
|
350
390
|
/**
|
|
351
|
-
* Mark a transaction as `FORWARDED`
|
|
391
|
+
* Mark a transaction as `FORWARDED` or `FORWARD_FAILED`
|
|
352
392
|
*
|
|
353
|
-
* @param {EvmHash
|
|
354
|
-
* @param {
|
|
355
|
-
* @param {bigint} amount
|
|
393
|
+
* @param {EvmHash} txHash
|
|
394
|
+
* @param {boolean} success
|
|
356
395
|
*/
|
|
357
|
-
forwarded(txHash,
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
);
|
|
365
|
-
}
|
|
396
|
+
forwarded(txHash, success) {
|
|
397
|
+
void publishTxnRecord(
|
|
398
|
+
txHash,
|
|
399
|
+
harden({
|
|
400
|
+
status: success ? TxStatus.Forwarded : TxStatus.ForwardFailed,
|
|
401
|
+
}),
|
|
402
|
+
);
|
|
366
403
|
},
|
|
367
404
|
|
|
368
405
|
/**
|
|
@@ -376,12 +413,13 @@ export const prepareStatusManager = (
|
|
|
376
413
|
*/
|
|
377
414
|
lookupPending(nfa, amount) {
|
|
378
415
|
const key = makePendingTxKey(nfa, amount);
|
|
379
|
-
if (!
|
|
416
|
+
if (!pendingSettleTxs.has(key)) {
|
|
380
417
|
return harden([]);
|
|
381
418
|
}
|
|
382
|
-
return
|
|
419
|
+
return pendingSettleTxs.get(key);
|
|
383
420
|
},
|
|
384
421
|
},
|
|
422
|
+
{ stateShape },
|
|
385
423
|
);
|
|
386
424
|
};
|
|
387
425
|
harden(prepareStatusManager);
|