Skip to content

Commit 8b18171

Browse files
committed
chore: tx statuses in vstorage
- ensure "minted before observed" `Observe` statuses are recorded in vstorage - ensure "minted while Advancing" `Advanced` and `AdvanceFailed` statuses are recorded in vstorage
1 parent 2d2dc7b commit 8b18171

File tree

6 files changed

+87
-76
lines changed

6 files changed

+87
-76
lines changed

packages/fast-usdc/src/exos/advancer.js

Lines changed: 9 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -164,25 +164,13 @@ export const prepareAdvancerKit = (
164164
const destination = chainHub.makeChainAddress(EUD);
165165

166166
const fullAmount = toAmount(evidence.tx.amount);
167-
const {
168-
tx: { forwardingAddress },
169-
txHash,
170-
} = evidence;
171-
172167
const { borrowerFacet, notifyFacet, poolAccount } = this.state;
173-
if (
174-
notifyFacet.forwardIfMinted(
175-
destination,
176-
forwardingAddress,
177-
fullAmount,
178-
txHash,
179-
)
180-
) {
181-
// settlement already received; tx will Forward.
182-
// do not add to `pendingSettleTxs` by calling `.observe()`
183-
log('⚠️ minted before Observed');
184-
return;
185-
}
168+
// do not advance if we've already received a mint/settlement
169+
const mintedEarly = notifyFacet.checkMintedEarly(
170+
evidence,
171+
destination,
172+
);
173+
if (mintedEarly) return;
186174

187175
// throws if requested does not exceed fees
188176
const advanceAmount = feeTools.calculateAdvance(fullAmount);
@@ -206,7 +194,7 @@ export const prepareAdvancerKit = (
206194
forwardingAddress: evidence.tx.forwardingAddress,
207195
fullAmount,
208196
tmpSeat,
209-
txHash,
197+
txHash: evidence.txHash,
210198
});
211199
} catch (error) {
212200
log('Advancer error:', error);
@@ -225,13 +213,7 @@ export const prepareAdvancerKit = (
225213
*/
226214
onFulfilled(result, ctx) {
227215
const { poolAccount, intermediateRecipient } = this.state;
228-
const {
229-
destination,
230-
advanceAmount,
231-
// eslint-disable-next-line no-unused-vars
232-
tmpSeat,
233-
...detail
234-
} = ctx;
216+
const { destination, advanceAmount, tmpSeat: _, ...detail } = ctx;
235217
const transferV = E(poolAccount).transfer(
236218
destination,
237219
{ denom: usdc.denom, value: advanceAmount.value },
@@ -296,11 +278,7 @@ export const prepareAdvancerKit = (
296278
onRejected(error, ctx) {
297279
const { notifyFacet } = this.state;
298280
log('Advance transfer rejected', error);
299-
const {
300-
// eslint-disable-next-line no-unused-vars
301-
advanceAmount,
302-
...restCtx
303-
} = ctx;
281+
const { advanceAmount: _, ...restCtx } = ctx;
304282
notifyFacet.notifyAdvancingResult(restCtx, false);
305283
},
306284
},

packages/fast-usdc/src/exos/settler.js

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@ import { M } from '@endo/patterns';
88
import { decodeAddressHook } from '@agoric/cosmic-proto/address-hooks.js';
99
import { PendingTxStatus } from '../constants.js';
1010
import { makeFeeTools } from '../utils/fees.js';
11-
import { EvmHashShape, makeNatAmountShape } from '../type-guards.js';
11+
import {
12+
CctpTxEvidenceShape,
13+
EvmHashShape,
14+
makeNatAmountShape,
15+
} from '../type-guards.js';
1216

1317
/**
1418
* @import {FungibleTokenPacketData} from '@agoric/cosmic-proto/ibc/applications/transfer/v2/packet.js';
@@ -18,7 +22,7 @@ import { EvmHashShape, makeNatAmountShape } from '../type-guards.js';
1822
* @import {Zone} from '@agoric/zone';
1923
* @import {HostOf, HostInterface} from '@agoric/async-flow';
2024
* @import {TargetRegistration} from '@agoric/vats/src/bridge-target.js';
21-
* @import {NobleAddress, LiquidityPoolKit, FeeConfig, EvmHash, LogFn} from '../types.js';
25+
* @import {NobleAddress, LiquidityPoolKit, FeeConfig, EvmHash, LogFn, CctpTxEvidence} from '../types.js';
2226
* @import {StatusManager} from './status-manager.js';
2327
*/
2428

@@ -81,8 +85,9 @@ export const prepareSettler = (
8185
makeAdvanceDetailsShape(USDC),
8286
M.boolean(),
8387
).returns(),
84-
forwardIfMinted: M.call(
85-
...Object.values(makeAdvanceDetailsShape(USDC)),
88+
checkMintedEarly: M.call(
89+
CctpTxEvidenceShape,
90+
ChainAddressShape,
8691
).returns(M.boolean()),
8792
}),
8893
self: M.interface('SettlerSelfI', {
@@ -216,17 +221,13 @@ export const prepareSettler = (
216221
) {
217222
const { mintedEarly } = this.state;
218223
const { value: fullValue } = fullAmount;
219-
// XXX i think this only contains Advancing txs - should this be a separate store?
220224
const key = makeMintedEarlyKey(forwardingAddress, fullValue);
221225
if (mintedEarly.has(key)) {
222226
mintedEarly.delete(key);
227+
statusManager.advanceOutcomeForMintedEarly(txHash, success);
223228
if (success) {
224-
// TODO: does not write `ADVANCED` to vstorage
225-
// this is the "slow" experience, but we've already earmarked fees so disburse
226229
void this.facets.self.disburse(txHash, fullValue);
227230
} else {
228-
// TODO: does not write `ADVANCE_FAILED` to vstorage
229-
// if advance fails, attempt to forward (no fees)
230231
void this.facets.self.forward(
231232
txHash,
232233
fullValue,
@@ -238,26 +239,27 @@ export const prepareSettler = (
238239
}
239240
},
240241
/**
242+
* @param {CctpTxEvidence} evidence
241243
* @param {ChainAddress} destination
242-
* @param {NobleAddress} forwardingAddress
243-
* @param {Amount<'nat'>} fullAmount
244-
* @param {EvmHash} txHash
245244
* @returns {boolean}
246245
* @throws {Error} if minted early, so advancer doesn't advance
247246
*/
248-
forwardIfMinted(destination, forwardingAddress, fullAmount, txHash) {
249-
const { value: fullValue } = fullAmount;
250-
const key = makeMintedEarlyKey(forwardingAddress, fullValue);
247+
checkMintedEarly(evidence, destination) {
248+
const {
249+
tx: { forwardingAddress, amount },
250+
txHash,
251+
} = evidence;
252+
const key = makeMintedEarlyKey(forwardingAddress, amount);
251253
const { mintedEarly } = this.state;
252254
if (mintedEarly.has(key)) {
253255
log(
254256
'matched minted early key, initiating forward',
255257
forwardingAddress,
256-
fullValue,
258+
amount,
257259
);
258260
mintedEarly.delete(key);
259-
// TODO: does not write `OBSERVED` to vstorage
260-
void this.facets.self.forward(txHash, fullValue, destination.value);
261+
statusManager.advanceOutcomeForUnknownMint(evidence);
262+
void this.facets.self.forward(txHash, amount, destination.value);
261263
return true;
262264
}
263265
return false;

packages/fast-usdc/src/exos/status-manager.js

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -196,12 +196,12 @@ export const prepareStatusManager = (
196196
'Fast USDC Status Manager',
197197
M.interface('StatusManagerI', {
198198
// TODO: naming scheme for transition events
199-
advance: M.call(CctpTxEvidenceShape).returns(M.undefined()),
199+
advance: M.call(CctpTxEvidenceShape).returns(),
200200
advanceOutcome: M.call(M.string(), M.nat(), M.boolean()).returns(),
201-
skipAdvance: M.call(CctpTxEvidenceShape, M.arrayOf(M.string())).returns(
202-
M.undefined(),
203-
),
204-
observe: M.call(CctpTxEvidenceShape).returns(M.undefined()),
201+
skipAdvance: M.call(CctpTxEvidenceShape, M.arrayOf(M.string())).returns(),
202+
advanceOutcomeForMintedEarly: M.call(EvmHashShape, M.boolean()).returns(),
203+
advanceOutcomeForUnknownMint: M.call(CctpTxEvidenceShape).returns(),
204+
observe: M.call(CctpTxEvidenceShape).returns(),
205205
hasBeenObserved: M.call(CctpTxEvidenceShape).returns(M.boolean()),
206206
deleteCompletedTxs: M.call().returns(M.undefined()),
207207
dequeueStatus: M.call(M.string(), M.bigint()).returns(
@@ -216,7 +216,7 @@ export const prepareStatusManager = (
216216
disbursed: M.call(EvmHashShape, AmountKeywordRecordShape).returns(
217217
M.undefined(),
218218
),
219-
forwarded: M.call(EvmHashShape, M.boolean()).returns(M.undefined()),
219+
forwarded: M.call(EvmHashShape, M.boolean()).returns(),
220220
lookupPending: M.call(M.string(), M.bigint()).returns(
221221
M.arrayOf(PendingTxShape),
222222
),
@@ -266,6 +266,43 @@ export const prepareStatusManager = (
266266
);
267267
},
268268

269+
/**
270+
* If minted while advancing, publish a status update for the advance
271+
* to vstorage.
272+
*
273+
* Does not add or amend `pendingSettleTxs` as this has
274+
* already settled.
275+
*
276+
* @param {EvmHash} txHash
277+
* @param {boolean} success whether the Transfer succeeded
278+
*/
279+
advanceOutcomeForMintedEarly(txHash, success) {
280+
void publishTxnRecord(
281+
txHash,
282+
harden({
283+
status: success
284+
? PendingTxStatus.Advanced
285+
: PendingTxStatus.AdvanceFailed,
286+
}),
287+
);
288+
},
289+
290+
/**
291+
* If minted before observed and the evidence is eventually
292+
* reported, publish the evidence without adding to `pendingSettleTxs`
293+
*
294+
* @param {CctpTxEvidence} evidence
295+
*/
296+
advanceOutcomeForUnknownMint(evidence) {
297+
const { txHash } = evidence;
298+
// unexpected path, since `hasBeenObserved` will be called before this
299+
if (seenTxs.has(txHash)) {
300+
throw makeError(`Transaction already seen: ${q(txHash)}`);
301+
}
302+
seenTxs.add(txHash);
303+
publishEvidence(txHash, evidence);
304+
},
305+
269306
/**
270307
* Add a new transaction with OBSERVED status
271308
* @param {CctpTxEvidence} evidence

packages/fast-usdc/test/exos/advancer.test.ts

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ import {
66
} from '@agoric/cosmic-proto/address-hooks.js';
77
import type { NatAmount } from '@agoric/ertp';
88
import { eventLoopIteration } from '@agoric/internal/src/testing-utils.js';
9-
import { denomHash } from '@agoric/orchestration';
9+
import { ChainAddressShape, denomHash } from '@agoric/orchestration';
1010
import fetchedChainInfo from '@agoric/orchestration/src/fetched-chain-info.js';
1111
import { type ZoeTools } from '@agoric/orchestration/src/utils/zoe-tools.js';
12-
import { Fail, q } from '@endo/errors';
12+
import { q } from '@endo/errors';
1313
import { Far } from '@endo/pass-style';
1414
import type { TestFn } from 'ava';
1515
import { makeTracer } from '@agoric/internal';
@@ -34,6 +34,7 @@ import {
3434
prepareMockOrchAccounts,
3535
} from '../mocks.js';
3636
import { commonSetup } from '../supports.js';
37+
import { CctpTxEvidenceShape } from '../../src/type-guards.js';
3738

3839
const trace = makeTracer('AdvancerTest', false);
3940

@@ -118,11 +119,9 @@ const createTestExtensions = (t, common: CommonSetup) => {
118119
notifyAdvancingResultCalls.push(args);
119120
},
120121
// assume this never returns true for most tests
121-
forwardIfMinted: (...args) => {
122-
mustMatch(
123-
harden(args),
124-
harden([...Object.values(makeAdvanceDetailsShape(usdc.brand))]),
125-
);
122+
checkMintedEarly: (evidence, destination) => {
123+
mustMatch(harden(evidence), CctpTxEvidenceShape);
124+
mustMatch(destination, ChainAddressShape);
126125
return false;
127126
},
128127
});
@@ -674,7 +673,7 @@ test('rejects advances to unknown settlementAccount', async t => {
674673
]);
675674
});
676675

677-
test('no status update if `forwardIfMinted` returns true', async t => {
676+
test('no status update if `checkMintedEarly` returns true', async t => {
678677
const {
679678
brands: { usdc },
680679
bootstrap: { storage },
@@ -687,7 +686,7 @@ test('no status update if `forwardIfMinted` returns true', async t => {
687686

688687
const mockNotifyF = Far('Settler Notify Facet', {
689688
notifyAdvancingResult: () => {},
690-
forwardIfMinted: (destination, forwardingAddress, fullAmount, txHash) => {
689+
checkMintedEarly: (evidence, destination) => {
691690
return true;
692691
},
693692
});
@@ -712,6 +711,6 @@ test('no status update if `forwardIfMinted` returns true', async t => {
712711

713712
t.deepEqual(inspectLogs(), [
714713
['decoded EUD: dydx183dejcnmkka5dzcu9xw6mywq0p2m5peks28men'],
715-
['⚠️ minted before Observed'],
714+
// no add'l logs as we return early
716715
]);
717716
});

packages/fast-usdc/test/exos/settler.test.ts

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -180,12 +180,7 @@ const makeTestContext = async t => {
180180
const cctpTxEvidence = makeEvidence(evidence);
181181
const { destination, forwardingAddress, fullAmount, txHash } =
182182
makeNotifyInfo(cctpTxEvidence);
183-
notifyFacet.forwardIfMinted(
184-
destination,
185-
forwardingAddress,
186-
fullAmount,
187-
txHash,
188-
);
183+
notifyFacet.checkMintedEarly(cctpTxEvidence, destination);
189184
return cctpTxEvidence;
190185
},
191186
});
@@ -542,7 +537,7 @@ test('Settlement for unknown transaction (minted early)', async t => {
542537
accounts.settlement.transferVResolver.resolve(undefined);
543538
await eventLoopIteration();
544539
t.deepEqual(storage.getDeserialized(`fun.txns.${evidence.txHash}`), [
545-
/// TODO with no observed / evidence, does this break reporting reqs?
540+
{ evidence, status: 'OBSERVED' },
546541
{ status: 'FORWARDED' },
547542
]);
548543
});
@@ -598,7 +593,7 @@ test('Settlement for Advancing transaction (advance succeeds)', async t => {
598593
t.deepEqual(storage.getDeserialized(`fun.txns.${cctpTxEvidence.txHash}`), [
599594
{ evidence: cctpTxEvidence, status: 'OBSERVED' },
600595
{ status: 'ADVANCING' },
601-
// { status: 'ADVANCED' }, TODO: not reported by notifyAdvancingResult
596+
{ status: 'ADVANCED' },
602597
{ split: expectedSplit, status: 'DISBURSED' },
603598
]);
604599
});
@@ -664,7 +659,7 @@ test('Settlement for Advancing transaction (advance fails)', async t => {
664659
t.deepEqual(storage.getDeserialized(`fun.txns.${cctpTxEvidence.txHash}`), [
665660
{ evidence: cctpTxEvidence, status: 'OBSERVED' },
666661
{ status: 'ADVANCING' },
667-
// { status: 'ADVANCE_FAILED' }, TODO: not reported by notifyAdvancingResult
662+
{ status: 'ADVANCE_FAILED' },
668663
{ status: 'FORWARDED' },
669664
]);
670665
});

packages/fast-usdc/test/fast-usdc.contract.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -918,7 +918,7 @@ test.serial('Settlement for unknown transaction (operator down)', async t => {
918918
await eventLoopIteration();
919919

920920
t.deepEqual(storage.getDeserialized(`fun.txns.${sent.txHash}`), [
921-
// { evidence: sent, status: 'OBSERVED' }, // no OBSERVED state recorded
921+
{ evidence: sent, status: 'OBSERVED' },
922922
{ status: 'FORWARDED' },
923923
]);
924924
});
@@ -959,7 +959,7 @@ test.serial('mint received while ADVANCING', async t => {
959959
t.deepEqual(storage.getDeserialized(`fun.txns.${sent.txHash}`), [
960960
{ evidence: sent, status: 'OBSERVED' },
961961
{ status: 'ADVANCING' },
962-
// { status: 'ADVANCED' }, TODO: not reported by notifyAdvancingResult
962+
{ status: 'ADVANCED' },
963963
{ split, status: 'DISBURSED' },
964964
]);
965965
});

0 commit comments

Comments
 (0)