1
1
import type { TestFn } from 'ava' ;
2
2
import { test as anyTest } from '@agoric/zoe/tools/prepare-test-env-ava.js' ;
3
+ import { eventLoopIteration } from '@agoric/internal/src/testing-utils.js' ;
3
4
4
- import { denomHash , type Denom } from '@agoric/orchestration' ;
5
+ import { denomHash , type DenomAmount } from '@agoric/orchestration' ;
5
6
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' ;
9
8
import { prepareAdvancer } from '../../src/exos/advancer.js' ;
10
9
import { prepareStatusManager } from '../../src/exos/status-manager.js' ;
11
10
import { prepareTransactionFeed } from '../../src/exos/transaction-feed.js' ;
12
11
13
12
import { commonSetup } from '../supports.js' ;
14
13
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' ;
21
15
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
+ } ) } `;
30
21
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 ) => {
33
31
const {
34
32
bootstrap : { rootZone, vowTools } ,
35
33
facadeServices : { chainHub } ,
34
+ brands : { usdc } ,
36
35
} = common ;
37
36
38
37
const { log, inspectLogs } = makeTestLogger ( t . log ) ;
@@ -44,141 +43,275 @@ test.beforeEach(async t => {
44
43
rootZone . subZone ( 'status-manager' ) ,
45
44
) ;
46
45
const feed = prepareTransactionFeed ( rootZone . subZone ( 'feed' ) ) ;
46
+ const mockAccounts = prepareMockOrchAccounts ( rootZone . subZone ( 'accounts' ) , {
47
+ vowTools,
48
+ log : t . log ,
49
+ } ) ;
50
+
47
51
const makeAdvancer = prepareAdvancer ( rootZone . subZone ( 'advancer' ) , {
48
52
chainHub,
49
53
feed,
50
54
statusManager,
51
55
vowTools,
52
56
log,
53
57
} ) ;
54
- const localDenom = `ibc/${ denomHash ( {
55
- denom : 'uusdc' ,
56
- channelId :
57
- fetchedChainInfo . agoric . connections [ 'noble-1' ] . transferChannel . channelId ,
58
- } ) } `;
59
58
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 ) ;
60
89
t . context = {
61
- localDenom,
62
- makeAdvancer,
63
- rootZone,
64
- statusManager,
65
- vowTools,
66
- inspectLogs,
90
+ ...common ,
91
+ extensions : createTestExtensions ( t , common ) ,
67
92
} ;
68
93
} ) ;
69
94
70
- test ( 'advancer updated status to ADVANCED' , async t => {
95
+ test ( 'updates status to ADVANCED in happy path ' , async t => {
71
96
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
+ } ,
78
102
} = t . context ;
79
103
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 ( ) ;
84
112
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
97
113
const entries = statusManager . view (
98
- mockCttpTxEvidence . tx . forwardingAddress ,
99
- mockCttpTxEvidence . tx . amount ,
114
+ mockEvidence . tx . forwardingAddress ,
115
+ mockEvidence . tx . amount ,
100
116
) ;
117
+
101
118
t . deepEqual (
102
119
entries ,
103
- [ { ...mockCttpTxEvidence , status : TxStatus . Advanced } ] ,
120
+ [ { ...mockEvidence , status : TxStatus . Advanced } ] ,
104
121
'tx status updated to ADVANCED' ,
105
122
) ;
106
123
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
+
107
150
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' ,
114
154
) ;
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
+ ] ) ;
115
160
} ) ;
116
161
117
- test ( 'advancer does not update status on failed transfer ' , async t => {
162
+ test ( 'updates status to SKIPPED if balance query fails ' , async t => {
118
163
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
+ } ,
125
201
} = t . context ;
126
202
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 ,
130
209
) ;
131
210
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 ( ) ;
134
240
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
141
241
const entries = statusManager . view (
142
- mockCttpTxEvidence . tx . forwardingAddress ,
143
- mockCttpTxEvidence . tx . amount ,
242
+ mockEvidence . tx . forwardingAddress ,
243
+ mockEvidence . tx . amount ,
144
244
) ;
245
+
145
246
t . deepEqual (
146
247
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' ,
149
250
) ;
251
+
150
252
t . deepEqual ( inspectLogs ( 0 ) , [
151
253
'Advance transfer rejected' ,
152
254
'"[Error: simulated error]"' ,
153
255
] ) ;
154
256
} ) ;
155
257
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 ;
164
265
165
- const advancer = makeAdvancer ( { poolAccount, localDenom } ) ;
166
- t . truthy ( advancer , 'advancer instantiates' ) ;
266
+ const mockEvidence = MockCctpTxEvidences . AGORIC_NO_PARAMS ( ) ;
167
267
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 ) ;
174
269
175
270
const entries = statusManager . view (
176
- mockCttpTxEvidence . tx . forwardingAddress ,
177
- mockCttpTxEvidence . tx . amount ,
271
+ mockEvidence . tx . forwardingAddress ,
272
+ mockEvidence . tx . amount ,
178
273
) ;
274
+
179
275
t . deepEqual (
180
276
entries ,
181
- [ { ...mockCttpTxEvidence , status : TxStatus . Skipped } ] ,
182
- 'tx status is still OBSERVED ' ,
277
+ [ { ...mockEvidence , status : TxStatus . Skipped } ] ,
278
+ 'tx is recorded as SKIPPED ' ,
183
279
) ;
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
+ ] ) ;
184
317
} ) ;
0 commit comments