Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit ffe20a3

Browse files
committedNov 7, 2024·
feat: Advancer exo behaviors
- refs: #10390
1 parent 757e0b2 commit ffe20a3

File tree

4 files changed

+371
-145
lines changed

4 files changed

+371
-145
lines changed
 

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

+92-37
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { AmountMath, BrandShape } from '@agoric/ertp';
12
import { assertAllDefined } from '@agoric/internal';
23
import { ChainAddressShape } from '@agoric/orchestration';
34
import { VowShape } from '@agoric/vow';
@@ -28,7 +29,7 @@ import { addressTools } from '../utils/address.js';
2829
*/
2930
export const prepareAdvancer = (
3031
zone,
31-
{ chainHub, feed, log, statusManager, vowTools: { watch } },
32+
{ chainHub, feed, log, statusManager, vowTools: { watch, when } },
3233
) => {
3334
assertAllDefined({ feed, statusManager, watch });
3435

@@ -51,6 +52,7 @@ export const prepareAdvancer = (
5152
* @param {{ destination: ChainAddress; amount: bigint; }} ctx
5253
*/
5354
onFulfilled(result, { destination, amount }) {
55+
// TODO vstorage update?
5456
log(
5557
'Advance transfer fulfilled',
5658
q({ amount, destination, result }).toString(),
@@ -67,12 +69,15 @@ export const prepareAdvancer = (
6769
return zone.exoClass(
6870
'Fast USDC Advancer',
6971
M.interface('AdvancerI', {
70-
handleTransactionEvent: M.call(CctpTxEvidenceShape).returns(VowShape),
72+
handleTransactionEvent: M.callWhen(CctpTxEvidenceShape).returns(
73+
M.or(M.undefined(), VowShape),
74+
),
7175
}),
7276
/**
7377
* @param {{
7478
* localDenom: Denom;
7579
* poolAccount: HostInterface<OrchestrationAccount<{ chainId: 'agoric' }>>;
80+
* usdcBrand: Brand<'nat'>;
7681
* }} config
7782
*/
7883
config => harden(config),
@@ -81,56 +86,106 @@ export const prepareAdvancer = (
8186
* XXX For now, assume this is invoked when a new entry is
8287
* observed via the EventFeed.
8388
*
89+
* Returns a Promise for a Vow , since we we `await vt.when()`the
90+
* `poolAccount.getBalance()` call. `getBalance()` interfaces with
91+
* `BankManager` and `ChainHub`, so we can expect the calls to resolve
92+
* promptly. We also don't care how many time `.getBalance` is called,
93+
* and watched vows are not dependent on its result.
94+
*
95+
* This also might return `undefined` (unwrapped) if precondition checks
96+
* fail.
97+
*
98+
* We don't expect any callers to depend on the settlement of
99+
* `handleTransactionEvent` - errors caught are communicated to the
100+
* `StatusManager` - so we don't need to concern ourselves with
101+
* preserving the vow chain for callers.
102+
*
84103
* @param {CctpTxEvidence} evidence
85104
*/
86-
handleTransactionEvent(evidence) {
87-
// XXX assume EventFeed performs validation checks. Advancer will assert uniqueness.
88-
89-
const { recipientAddress } = evidence.aux;
90-
const { EUD } = addressTools.getQueryParams(recipientAddress).params;
91-
if (!EUD) {
92-
statusManager.skip(evidence);
93-
throw makeError(
94-
`recipientAddress does not contain EUD param: ${q(recipientAddress)}`,
95-
);
96-
}
105+
async handleTransactionEvent(evidence) {
106+
await null;
107+
try {
108+
const { recipientAddress } = evidence.aux;
109+
const { EUD } = addressTools.getQueryParams(recipientAddress).params;
110+
// XXX assume EventFeed performs validation checks. Advancer will assert uniqueness.
111+
if (!EUD) {
112+
throw makeError(
113+
`recipientAddress does not contain EUD param: ${q(recipientAddress)}`,
114+
);
115+
}
97116

98-
// TODO #10391 this can throw, and should make a status update in the catch
99-
const { chainId } = chainHub.getChainInfoByAddress(EUD);
117+
// this will throw if the bech32 prefix is not found
118+
const { chainId } = chainHub.getChainInfoByAddress(EUD);
100119

101-
/** @type {DenomAmount} */
102-
const requestedAmount = harden({
103-
denom: this.state.localDenom,
104-
value: BigInt(evidence.tx.amount),
105-
});
120+
/** @type {DenomAmount} */
121+
const requestedAmount = harden({
122+
denom: this.state.localDenom,
123+
value: BigInt(evidence.tx.amount),
124+
});
125+
/**
126+
* Ensure there's enough funds in poolAccount.
127+
*
128+
* It's safe to await here since we don't care how many
129+
* times we call `getBalance`. Our later Vow call - `transferV` and
130+
* its ctx - are also not reliant on the consistency of this value.
131+
*/
132+
const poolBalance = await when(
133+
E(this.state.poolAccount).getBalance(this.state.localDenom),
134+
);
106135

107-
// TODO #10391 ensure there's enough funds in poolAccount
136+
if (
137+
!AmountMath.isGTE(
138+
AmountMath.make(this.state.usdcBrand, poolBalance.value),
139+
AmountMath.make(this.state.usdcBrand, requestedAmount.value),
140+
)
141+
) {
142+
log(
143+
`Insufficient pool funds`,
144+
`Requested ${q(requestedAmount)} but only have ${q(poolBalance)}`,
145+
);
146+
statusManager.skip(evidence);
147+
return;
148+
}
108149

109-
/** @type {ChainAddress} */
110-
const destination = harden({
111-
chainId,
112-
value: EUD,
113-
encoding: /** @type {const} */ ('bech32'),
114-
});
150+
/** @type {ChainAddress} */
151+
const destination = harden({
152+
chainId,
153+
value: EUD,
154+
encoding: /** @type {const} */ ('bech32'),
155+
});
115156

116-
const transferV = E(this.state.poolAccount).transfer(
117-
destination,
118-
requestedAmount,
119-
);
157+
// mark as Advanced since we're initiating the advance
158+
try {
159+
// will throw if we've already .skipped or .advanced this evidence
160+
statusManager.advance(evidence);
161+
} catch (e) {
162+
// only anticipated error is `assertNotSeen`, so
163+
// intercept the catch so we don't call .skip which
164+
// also performs this check
165+
log('Advancer error:', q(e).toString());
166+
return;
167+
}
120168

121-
// mark as Advanced since we're initiating the advance
122-
statusManager.advance(evidence);
169+
const transferV = E(this.state.poolAccount).transfer(
170+
destination,
171+
requestedAmount,
172+
);
123173

124-
return watch(transferV, transferHandler, {
125-
destination,
126-
amount: requestedAmount.value,
127-
});
174+
return watch(transferV, transferHandler, {
175+
destination,
176+
amount: requestedAmount.value,
177+
});
178+
} catch (e) {
179+
log(`Advancer error:`, q(e).toString());
180+
statusManager.skip(evidence);
181+
}
128182
},
129183
},
130184
{
131185
stateShape: harden({
132186
localDenom: M.string(),
133187
poolAccount: M.remotable(),
188+
usdcBrand: BrandShape,
134189
}),
135190
},
136191
);
+238-105
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,37 @@
11
import type { TestFn } from 'ava';
22
import { test as anyTest } from '@agoric/zoe/tools/prepare-test-env-ava.js';
3+
import { eventLoopIteration } from '@agoric/internal/src/testing-utils.js';
34

4-
import { denomHash, type Denom } from '@agoric/orchestration';
5+
import { denomHash, type DenomAmount } from '@agoric/orchestration';
56
import fetchedChainInfo from '@agoric/orchestration/src/fetched-chain-info.js';
6-
import { eventLoopIteration } from '@agoric/internal/src/testing-utils.js';
7-
import type { Zone } from '@agoric/zone';
8-
import type { VowTools } from '@agoric/vow';
7+
import { TxStatus } from '../../src/constants.js';
98
import { prepareAdvancer } from '../../src/exos/advancer.js';
109
import { prepareStatusManager } from '../../src/exos/status-manager.js';
1110
import { prepareTransactionFeed } from '../../src/exos/transaction-feed.js';
1211

1312
import { commonSetup } from '../supports.js';
1413
import { MockCctpTxEvidences } from '../fixtures.js';
15-
import {
16-
makeTestLogger,
17-
prepareMockOrchAccounts,
18-
type TestLogger,
19-
} from '../mocks.js';
20-
import { TxStatus } from '../../src/constants.js';
14+
import { makeTestLogger, prepareMockOrchAccounts } from '../mocks.js';
2115

22-
const test = anyTest as TestFn<{
23-
localDenom: Denom;
24-
makeAdvancer: ReturnType<typeof prepareAdvancer>;
25-
rootZone: Zone;
26-
statusManager: ReturnType<typeof prepareStatusManager>;
27-
vowTools: VowTools;
28-
inspectLogs: TestLogger['inspectLogs'];
29-
}>;
16+
const LOCAL_DENOM = `ibc/${denomHash({
17+
denom: 'uusdc',
18+
channelId:
19+
fetchedChainInfo.agoric.connections['noble-1'].transferChannel.channelId,
20+
})}`;
3021

31-
test.beforeEach(async t => {
32-
const common = await commonSetup(t);
22+
const MOCK_POOL_BALANCE: DenomAmount = harden({
23+
denom: LOCAL_DENOM,
24+
// XXX amountUtils at some point
25+
value: 1_000_000n * 10n ** 6n, // 1M USDC
26+
});
27+
28+
type CommonSetup = Awaited<ReturnType<typeof commonSetup>>;
29+
30+
const createTestExtensions = (t, common: CommonSetup) => {
3331
const {
3432
bootstrap: { rootZone, vowTools },
3533
facadeServices: { chainHub },
34+
brands: { usdc },
3635
} = common;
3736

3837
const { log, inspectLogs } = makeTestLogger(t.log);
@@ -44,141 +43,275 @@ test.beforeEach(async t => {
4443
rootZone.subZone('status-manager'),
4544
);
4645
const feed = prepareTransactionFeed(rootZone.subZone('feed'));
46+
const mockAccounts = prepareMockOrchAccounts(rootZone.subZone('accounts'), {
47+
vowTools,
48+
log: t.log,
49+
});
50+
4751
const makeAdvancer = prepareAdvancer(rootZone.subZone('advancer'), {
4852
chainHub,
4953
feed,
5054
statusManager,
5155
vowTools,
5256
log,
5357
});
54-
const localDenom = `ibc/${denomHash({
55-
denom: 'uusdc',
56-
channelId:
57-
fetchedChainInfo.agoric.connections['noble-1'].transferChannel.channelId,
58-
})}`;
5958

59+
const advancer = makeAdvancer({
60+
poolAccount: mockAccounts.pool.account,
61+
localDenom: LOCAL_DENOM,
62+
usdcBrand: usdc.brand,
63+
});
64+
65+
return {
66+
accounts: mockAccounts,
67+
constants: {
68+
localDenom: LOCAL_DENOM,
69+
},
70+
helpers: {
71+
inspectLogs,
72+
},
73+
services: {
74+
advancer,
75+
feed,
76+
statusManager,
77+
},
78+
} as const;
79+
};
80+
81+
type TestContext = CommonSetup & {
82+
extensions: ReturnType<typeof createTestExtensions>;
83+
};
84+
85+
const test = anyTest as TestFn<TestContext>;
86+
87+
test.beforeEach(async t => {
88+
const common = await commonSetup(t);
6089
t.context = {
61-
localDenom,
62-
makeAdvancer,
63-
rootZone,
64-
statusManager,
65-
vowTools,
66-
inspectLogs,
90+
...common,
91+
extensions: createTestExtensions(t, common),
6792
};
6893
});
6994

70-
test('advancer updated status to ADVANCED', async t => {
95+
test('updates status to ADVANCED in happy path', async t => {
7196
const {
72-
inspectLogs,
73-
localDenom,
74-
makeAdvancer,
75-
statusManager,
76-
rootZone,
77-
vowTools,
97+
extensions: {
98+
services: { advancer, statusManager },
99+
helpers: { inspectLogs },
100+
accounts: { pool },
101+
},
78102
} = t.context;
79103

80-
const { poolAccount, poolAccountTransferVResolver } = prepareMockOrchAccounts(
81-
rootZone.subZone('poolAcct'),
82-
{ vowTools, log: t.log },
83-
);
104+
const mockEvidence = MockCctpTxEvidences.AGORIC_PLUS_OSMO();
105+
const handleTxP = advancer.handleTransactionEvent(mockEvidence);
106+
107+
pool.getBalanceVResolver.resolve(MOCK_POOL_BALANCE);
108+
pool.transferVResolver.resolve();
109+
110+
await handleTxP;
111+
await eventLoopIteration();
84112

85-
const advancer = makeAdvancer({
86-
poolAccount,
87-
localDenom,
88-
});
89-
t.truthy(advancer, 'advancer instantiates');
90-
91-
// simulate input from EventFeed
92-
const mockCttpTxEvidence = MockCctpTxEvidences.AGORIC_PLUS_OSMO();
93-
advancer.handleTransactionEvent(mockCttpTxEvidence);
94-
t.log('Simulate advance `.transfer()` vow fulfillment');
95-
poolAccountTransferVResolver.resolve();
96-
await eventLoopIteration(); // wait for StatusManager to receive update
97113
const entries = statusManager.view(
98-
mockCttpTxEvidence.tx.forwardingAddress,
99-
mockCttpTxEvidence.tx.amount,
114+
mockEvidence.tx.forwardingAddress,
115+
mockEvidence.tx.amount,
100116
);
117+
101118
t.deepEqual(
102119
entries,
103-
[{ ...mockCttpTxEvidence, status: TxStatus.Advanced }],
120+
[{ ...mockEvidence, status: TxStatus.Advanced }],
104121
'tx status updated to ADVANCED',
105122
);
106123

124+
t.deepEqual(inspectLogs(0), [
125+
'Advance transfer fulfilled',
126+
'{"amount":"[150000000n]","destination":{"chainId":"osmosis-1","encoding":"bech32","value":"osmo183dejcnmkka5dzcu9xw6mywq0p2m5peks28men"},"result":"[undefined]"}',
127+
]);
128+
});
129+
130+
test('updates status to SKIPPED on insufficient pool funds', async t => {
131+
const {
132+
extensions: {
133+
services: { advancer, statusManager },
134+
helpers: { inspectLogs },
135+
accounts: { pool },
136+
},
137+
} = t.context;
138+
139+
const mockEvidence = MockCctpTxEvidences.AGORIC_PLUS_DYDX();
140+
const handleTxP = advancer.handleTransactionEvent(mockEvidence);
141+
142+
pool.getBalanceVResolver.resolve({ ...MOCK_POOL_BALANCE, value: 1n });
143+
await handleTxP;
144+
145+
const entries = statusManager.view(
146+
mockEvidence.tx.forwardingAddress,
147+
mockEvidence.tx.amount,
148+
);
149+
107150
t.deepEqual(
108-
inspectLogs(0),
109-
[
110-
'Advance transfer fulfilled',
111-
'{"amount":"[150000000n]","destination":{"chainId":"osmosis-1","encoding":"bech32","value":"osmo183dejcnmkka5dzcu9xw6mywq0p2m5peks28men"},"result":"[undefined]"}',
112-
],
113-
'contract logs advance',
151+
entries,
152+
[{ ...mockEvidence, status: TxStatus.Skipped }],
153+
'tx is recorded as SKIPPED',
114154
);
155+
156+
t.deepEqual(inspectLogs(0), [
157+
'Insufficient pool funds',
158+
'Requested {"denom":"ibc/FE98AAD68F02F03565E9FA39A5E627946699B2B07115889ED812D8BA639576A9","value":"[200000000n]"} but only have {"denom":"ibc/FE98AAD68F02F03565E9FA39A5E627946699B2B07115889ED812D8BA639576A9","value":"[1n]"}',
159+
]);
115160
});
116161

117-
test('advancer does not update status on failed transfer', async t => {
162+
test('updates status to SKIPPED if balance query fails', async t => {
118163
const {
119-
inspectLogs,
120-
localDenom,
121-
makeAdvancer,
122-
statusManager,
123-
rootZone,
124-
vowTools,
164+
extensions: {
165+
services: { advancer, statusManager },
166+
helpers: { inspectLogs },
167+
accounts: { pool },
168+
},
169+
} = t.context;
170+
171+
const mockEvidence = MockCctpTxEvidences.AGORIC_PLUS_DYDX();
172+
const handleTxP = advancer.handleTransactionEvent(mockEvidence);
173+
174+
pool.getBalanceVResolver.reject(new Error('Unexpected balanceQuery error'));
175+
await handleTxP;
176+
177+
const entries = statusManager.view(
178+
mockEvidence.tx.forwardingAddress,
179+
mockEvidence.tx.amount,
180+
);
181+
182+
t.deepEqual(
183+
entries,
184+
[{ ...mockEvidence, status: TxStatus.Skipped }],
185+
'tx is recorded as SKIPPED',
186+
);
187+
188+
t.deepEqual(inspectLogs(0), [
189+
'Advancer error:',
190+
'"[Error: Unexpected balanceQuery error]"',
191+
]);
192+
});
193+
194+
test('updates status to SKIPPED if getChainInfoByAddress fails', async t => {
195+
const {
196+
extensions: {
197+
services: { advancer, statusManager },
198+
helpers: { inspectLogs },
199+
accounts: { pool },
200+
},
125201
} = t.context;
126202

127-
const { poolAccount, poolAccountTransferVResolver } = prepareMockOrchAccounts(
128-
rootZone.subZone('poolAcct2'),
129-
{ vowTools, log: t.log },
203+
const mockEvidence = MockCctpTxEvidences.AGORIC_UNKNOWN_EUD();
204+
await advancer.handleTransactionEvent(mockEvidence);
205+
206+
const entries = statusManager.view(
207+
mockEvidence.tx.forwardingAddress,
208+
mockEvidence.tx.amount,
130209
);
131210

132-
const advancer = makeAdvancer({ poolAccount, localDenom });
133-
t.truthy(advancer, 'advancer instantiates');
211+
t.deepEqual(
212+
entries,
213+
[{ ...mockEvidence, status: TxStatus.Skipped }],
214+
'tx is recorded as SKIPPED',
215+
);
216+
217+
t.deepEqual(inspectLogs(0), [
218+
'Advancer error:',
219+
'"[Error: Chain info not found for bech32Prefix \\"random\\"]"',
220+
]);
221+
});
222+
223+
test('does not update status on failed transfer', async t => {
224+
const {
225+
extensions: {
226+
services: { advancer, statusManager },
227+
helpers: { inspectLogs },
228+
accounts: { pool },
229+
},
230+
} = t.context;
231+
232+
const mockEvidence = MockCctpTxEvidences.AGORIC_PLUS_DYDX();
233+
const handleTxP = advancer.handleTransactionEvent(mockEvidence);
234+
235+
pool.getBalanceVResolver.resolve(MOCK_POOL_BALANCE);
236+
pool.transferVResolver.reject(new Error('simulated error'));
237+
238+
await handleTxP;
239+
await eventLoopIteration();
134240

135-
// simulate input from EventFeed
136-
const mockCttpTxEvidence = MockCctpTxEvidences.AGORIC_PLUS_DYDX();
137-
advancer.handleTransactionEvent(mockCttpTxEvidence);
138-
t.log('Simulate advance `.transfer()` vow rejection');
139-
poolAccountTransferVResolver.reject(new Error('simulated error'));
140-
await eventLoopIteration(); // wait for StatusManager to receive update
141241
const entries = statusManager.view(
142-
mockCttpTxEvidence.tx.forwardingAddress,
143-
mockCttpTxEvidence.tx.amount,
242+
mockEvidence.tx.forwardingAddress,
243+
mockEvidence.tx.amount,
144244
);
245+
145246
t.deepEqual(
146247
entries,
147-
[{ ...mockCttpTxEvidence, status: TxStatus.Advanced }],
148-
'tx status is still Advanced even though advance failed',
248+
[{ ...mockEvidence, status: TxStatus.Advanced }],
249+
'tx status is still ADVANCED even though advance failed',
149250
);
251+
150252
t.deepEqual(inspectLogs(0), [
151253
'Advance transfer rejected',
152254
'"[Error: simulated error]"',
153255
]);
154256
});
155257

156-
test('advancer updated status to SKIPPED if pre-condition checks fail', async t => {
157-
const { localDenom, makeAdvancer, statusManager, rootZone, vowTools } =
158-
t.context;
159-
160-
const { poolAccount } = prepareMockOrchAccounts(
161-
rootZone.subZone('poolAcct2'),
162-
{ vowTools, log: t.log },
163-
);
258+
test('updates status to SKIPPED if pre-condition checks fail', async t => {
259+
const {
260+
extensions: {
261+
services: { advancer, statusManager },
262+
helpers: { inspectLogs },
263+
},
264+
} = t.context;
164265

165-
const advancer = makeAdvancer({ poolAccount, localDenom });
166-
t.truthy(advancer, 'advancer instantiates');
266+
const mockEvidence = MockCctpTxEvidences.AGORIC_NO_PARAMS();
167267

168-
// simulate input from EventFeed
169-
const mockCttpTxEvidence = MockCctpTxEvidences.AGORIC_NO_PARAMS();
170-
t.throws(() => advancer.handleTransactionEvent(mockCttpTxEvidence), {
171-
message:
172-
'recipientAddress does not contain EUD param: "agoric16kv2g7snfc4q24vg3pjdlnnqgngtjpwtetd2h689nz09lcklvh5s8u37ek"',
173-
});
268+
await advancer.handleTransactionEvent(mockEvidence);
174269

175270
const entries = statusManager.view(
176-
mockCttpTxEvidence.tx.forwardingAddress,
177-
mockCttpTxEvidence.tx.amount,
271+
mockEvidence.tx.forwardingAddress,
272+
mockEvidence.tx.amount,
178273
);
274+
179275
t.deepEqual(
180276
entries,
181-
[{ ...mockCttpTxEvidence, status: TxStatus.Skipped }],
182-
'tx status is still OBSERVED',
277+
[{ ...mockEvidence, status: TxStatus.Skipped }],
278+
'tx is recorded as SKIPPED',
183279
);
280+
281+
t.deepEqual(inspectLogs(0), [
282+
'Advancer error:',
283+
'"[Error: recipientAddress does not contain EUD param: \\"agoric16kv2g7snfc4q24vg3pjdlnnqgngtjpwtetd2h689nz09lcklvh5s8u37ek\\"]"',
284+
]);
285+
});
286+
287+
test('will not advance same txHash:chainId evidence twice', async t => {
288+
const {
289+
extensions: {
290+
services: { advancer },
291+
helpers: { inspectLogs },
292+
accounts: { pool },
293+
},
294+
} = t.context;
295+
296+
const mockEvidence = MockCctpTxEvidences.AGORIC_PLUS_OSMO();
297+
298+
// First attempt
299+
const handleTxP = advancer.handleTransactionEvent(mockEvidence);
300+
pool.getBalanceVResolver.resolve(MOCK_POOL_BALANCE);
301+
pool.transferVResolver.resolve();
302+
await handleTxP;
303+
await eventLoopIteration();
304+
305+
t.deepEqual(inspectLogs(0), [
306+
'Advance transfer fulfilled',
307+
'{"amount":"[150000000n]","destination":{"chainId":"osmosis-1","encoding":"bech32","value":"osmo183dejcnmkka5dzcu9xw6mywq0p2m5peks28men"},"result":"[undefined]"}',
308+
]);
309+
310+
// Second attempt
311+
await advancer.handleTransactionEvent(mockEvidence);
312+
313+
t.deepEqual(inspectLogs(1), [
314+
'Advancer error:',
315+
'"[Error: Transaction already seen: \\"0xc81bc6105b60a234c7c50ac17816ebcd5561d366df8bf3be59ff387552761702+1\\"]"',
316+
]);
184317
});

‎packages/fast-usdc/test/fixtures.ts

+29
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const mockScenarios = [
77
'AGORIC_PLUS_OSMO',
88
'AGORIC_PLUS_DYDX',
99
'AGORIC_NO_PARAMS',
10+
'AGORIC_UNKNOWN_EUD',
1011
] as const;
1112

1213
type MockScenario = (typeof mockScenarios)[number];
@@ -72,6 +73,25 @@ export const MockCctpTxEvidences: Record<
7273
},
7374
chainId: 1,
7475
}),
76+
AGORIC_UNKNOWN_EUD: (receiverAddress?: string) => ({
77+
blockHash:
78+
'0x70d7343e04f8160892e94f02d6a9b9f255663ed0ac34caca98544c8143fee699',
79+
blockNumber: 21037669n,
80+
blockTimestamp: 1730762099n,
81+
txHash:
82+
'0xa81bc6105b60a234c7c50ac17816ebcd5561d366df8bf3be59ff387552761799',
83+
tx: {
84+
amount: 200000000n,
85+
forwardingAddress: 'noble1x0ydg69dh6fqvr27xjvp6maqmrldam6yfelyyy',
86+
},
87+
aux: {
88+
forwardingChannel: 'channel-21',
89+
recipientAddress:
90+
receiverAddress ||
91+
'agoric16kv2g7snfc4q24vg3pjdlnnqgngtjpwtetd2h689nz09lcklvh5s8u37ek?EUD=random1addr',
92+
},
93+
chainId: 1,
94+
}),
7595
};
7696

7797
const nobleDefaultVTransferParams = {
@@ -115,4 +135,13 @@ export const MockVTransferEvents: Record<
115135
recieverAddress ||
116136
MockCctpTxEvidences.AGORIC_NO_PARAMS().aux.recipientAddress,
117137
}),
138+
AGORIC_UNKNOWN_EUD: (recieverAddress?: string) =>
139+
buildVTransferEvent({
140+
...nobleDefaultVTransferParams,
141+
amount: MockCctpTxEvidences.AGORIC_UNKNOWN_EUD().tx.amount,
142+
sender: MockCctpTxEvidences.AGORIC_UNKNOWN_EUD().tx.forwardingAddress,
143+
receiver:
144+
recieverAddress ||
145+
MockCctpTxEvidences.AGORIC_UNKNOWN_EUD().aux.recipientAddress,
146+
}),
118147
};

‎packages/fast-usdc/test/mocks.ts

+12-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type {
22
ChainAddress,
33
DenomAmount,
4+
DenomArg,
45
OrchestrationAccount,
56
} from '@agoric/orchestration';
67
import type { Zone } from '@agoric/zone';
@@ -15,14 +16,19 @@ export const prepareMockOrchAccounts = (
1516
log,
1617
}: { vowTools: VowTools; log: (...args: any[]) => void },
1718
) => {
18-
// can only be called once per test
19+
// XXX each can only be resolved/rejected once per test
1920
const poolAccountTransferVK = makeVowKit();
21+
const poolAccountGetBalanceVK = makeVowKit();
2022

2123
const mockedPoolAccount = zone.exo('Pool LocalOrchAccount', undefined, {
2224
transfer(destination: ChainAddress, amount: DenomAmount) {
2325
log('PoolAccount.transfer() called with', destination, amount);
2426
return poolAccountTransferVK.vow;
2527
},
28+
getBalance(denomArg: DenomArg) {
29+
log('PoolAccount.getBalance() called with', denomArg);
30+
return poolAccountGetBalanceVK.vow;
31+
},
2632
});
2733

2834
const poolAccount = mockedPoolAccount as unknown as HostInterface<
@@ -32,8 +38,11 @@ export const prepareMockOrchAccounts = (
3238
>;
3339

3440
return {
35-
poolAccount,
36-
poolAccountTransferVResolver: poolAccountTransferVK.resolver,
41+
pool: {
42+
account: poolAccount,
43+
transferVResolver: poolAccountTransferVK.resolver,
44+
getBalanceVResolver: poolAccountGetBalanceVK.resolver,
45+
},
3746
};
3847
};
3948

0 commit comments

Comments
 (0)
Please sign in to comment.