@centrifuge/sdk 0.13.2 → 0.15.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.
@@ -9,7 +9,7 @@ import { context } from '../tests/setup.js';
9
9
  import { randomAddress } from '../tests/utils.js';
10
10
  import { makeThenable } from '../utils/rx.js';
11
11
  import { AssetId, PoolId, ShareClassId } from '../utils/types.js';
12
- import { getMerkleTree, MerkleProofManager } from './MerkleProofManager.js';
12
+ import { generateCombinations, getMerkleTree, MerkleProofManager } from './MerkleProofManager.js';
13
13
  import { Pool } from './Pool.js';
14
14
  import { PoolNetwork } from './PoolNetwork.js';
15
15
  const chainId = 11155111;
@@ -40,118 +40,352 @@ describe('MerkleProofManager', () => {
40
40
  assetId: undefined,
41
41
  decoder,
42
42
  target: someErc20,
43
- abi: 'function approve(address,uint256)',
43
+ selector: 'function approve(address,uint256)',
44
44
  valueNonZero: false,
45
- args: [vaultRouter, null],
46
- argsEncoded: encodePacked(['address'], [vaultRouter]),
45
+ inputs: [
46
+ {
47
+ parameter: 'address',
48
+ input: [vaultRouter],
49
+ },
50
+ {
51
+ parameter: 'uint256',
52
+ input: [],
53
+ },
54
+ ],
55
+ inputCombinations: [
56
+ {
57
+ inputs: [vaultRouter, null],
58
+ inputsEncoded: encodePacked(['address'], [vaultRouter]),
59
+ },
60
+ ],
47
61
  },
48
62
  {
49
63
  assetId: assetId.toString(),
50
64
  decoder,
51
65
  target,
52
- abi: 'function cancelDepositRequest(uint256, address controller)',
66
+ selector: 'function cancelDepositRequest(uint256, address controller)',
53
67
  valueNonZero: false,
54
- args: [null, randomUser],
55
- argsEncoded: encodePacked(['address'], [randomUser]),
68
+ inputs: [
69
+ {
70
+ parameter: 'uint256',
71
+ input: [],
72
+ },
73
+ {
74
+ parameter: 'address',
75
+ input: [randomUser],
76
+ },
77
+ ],
78
+ inputCombinations: [
79
+ {
80
+ inputs: [null, randomUser],
81
+ inputsEncoded: encodePacked(['address'], [randomUser]),
82
+ },
83
+ ],
56
84
  },
57
85
  {
58
86
  assetId: assetId.toString(),
59
87
  decoder,
60
88
  target,
61
- abi: 'function cancelRedeemRequest(uint256, address controller)',
89
+ selector: 'function cancelRedeemRequest(uint256, address controller)',
62
90
  valueNonZero: false,
63
- args: [null, randomUser],
64
- argsEncoded: encodePacked(['address'], [randomUser]),
91
+ inputs: [
92
+ {
93
+ parameter: 'uint256',
94
+ input: [],
95
+ },
96
+ {
97
+ parameter: 'address',
98
+ input: [randomUser],
99
+ },
100
+ ],
101
+ inputCombinations: [
102
+ {
103
+ inputs: [null, randomUser],
104
+ inputsEncoded: encodePacked(['address'], [randomUser]),
105
+ },
106
+ ],
65
107
  },
66
108
  {
67
109
  assetId: assetId.toString(),
68
110
  decoder,
69
111
  target,
70
- abi: 'function claimCancelDepositRequest(uint256, address receiver, address controller)',
112
+ selector: 'function claimCancelDepositRequest(uint256, address receiver, address controller)',
71
113
  valueNonZero: false,
72
- args: [null, randomUser, randomUser],
73
- argsEncoded: encodePacked(['address', 'address'], [randomUser, randomUser]),
114
+ inputs: [
115
+ {
116
+ parameter: 'uint256',
117
+ input: [],
118
+ },
119
+ {
120
+ parameter: 'address receiver',
121
+ input: [randomUser],
122
+ },
123
+ {
124
+ parameter: 'address controller',
125
+ input: [randomUser],
126
+ },
127
+ ],
128
+ inputCombinations: [
129
+ {
130
+ inputs: [null, randomUser, randomUser],
131
+ inputsEncoded: encodePacked(['address', 'address'], [randomUser, randomUser]),
132
+ },
133
+ ],
74
134
  },
75
135
  {
76
136
  assetId: assetId.toString(),
77
137
  decoder,
78
138
  target,
79
- abi: 'function claimCancelRedeemRequest(uint256, address receiver, address controller)',
139
+ selector: 'function claimCancelRedeemRequest(uint256, address receiver, address controller)',
80
140
  valueNonZero: false,
81
- args: [null, randomUser, randomUser],
82
- argsEncoded: encodePacked(['address', 'address'], [randomUser, randomUser]),
141
+ inputs: [
142
+ {
143
+ parameter: 'uint256',
144
+ input: [],
145
+ },
146
+ {
147
+ parameter: 'address receiver',
148
+ input: [randomUser],
149
+ },
150
+ {
151
+ parameter: 'address controller',
152
+ input: [randomUser],
153
+ },
154
+ ],
155
+ inputCombinations: [
156
+ {
157
+ inputs: [null, randomUser, randomUser],
158
+ inputsEncoded: encodePacked(['address', 'address'], [randomUser, randomUser]),
159
+ },
160
+ ],
83
161
  },
84
162
  {
85
163
  assetId: assetId.toString(),
86
164
  decoder,
87
165
  target,
88
- abi: 'function deposit(uint64 poolId, bytes16 scId, address asset, uint256, uint128)',
166
+ selector: 'function deposit(uint64 poolId, bytes16 scId, address asset, uint256, uint128)',
89
167
  valueNonZero: false,
90
- args: [poolId.toString(), scId.raw, someErc20, null, null],
91
- argsEncoded: encodePacked(['uint64', 'bytes16', 'address'], [poolId.raw, scId.raw, someErc20]),
168
+ inputs: [
169
+ {
170
+ parameter: 'poolId',
171
+ input: [poolId.toString()],
172
+ },
173
+ {
174
+ parameter: 'shareClassId',
175
+ input: [scId.raw],
176
+ },
177
+ {
178
+ parameter: 'erc20',
179
+ input: [someErc20],
180
+ },
181
+ {
182
+ parameter: 'uint256',
183
+ input: [],
184
+ },
185
+ {
186
+ parameter: 'uint128',
187
+ input: [],
188
+ },
189
+ ],
190
+ inputCombinations: [
191
+ {
192
+ inputs: [poolId.toString(), scId.raw, someErc20, null, null],
193
+ inputsEncoded: encodePacked(['uint64', 'bytes16', 'address'], [poolId.raw, scId.raw, someErc20]),
194
+ },
195
+ ],
92
196
  },
93
197
  {
94
198
  assetId: assetId.toString(),
95
199
  decoder,
96
200
  target,
97
- abi: 'function deposit(uint256, address receiver)',
201
+ selector: 'function deposit(uint256, address receiver)',
98
202
  valueNonZero: false,
99
- args: [null, randomUser],
100
- argsEncoded: encodePacked(['address'], [randomUser]),
203
+ inputs: [
204
+ {
205
+ parameter: 'uint256',
206
+ input: [],
207
+ },
208
+ {
209
+ parameter: 'address',
210
+ input: [randomUser],
211
+ },
212
+ ],
213
+ inputCombinations: [
214
+ {
215
+ inputs: [null, randomUser],
216
+ inputsEncoded: encodePacked(['address'], [randomUser]),
217
+ },
218
+ ],
101
219
  },
102
220
  {
103
221
  assetId: assetId.toString(),
104
222
  decoder,
105
223
  target,
106
- abi: 'function mint(uint256, address receiver)',
224
+ selector: 'function mint(uint256, address receiver)',
107
225
  valueNonZero: false,
108
- args: [null, randomUser],
109
- argsEncoded: encodePacked(['address'], [randomUser]),
226
+ inputs: [
227
+ {
228
+ parameter: 'uint256',
229
+ input: [],
230
+ },
231
+ {
232
+ parameter: 'address',
233
+ input: [randomUser],
234
+ },
235
+ ],
236
+ inputCombinations: [
237
+ {
238
+ inputs: [null, randomUser],
239
+ inputsEncoded: encodePacked(['address'], [randomUser]),
240
+ },
241
+ ],
110
242
  },
111
243
  {
112
244
  assetId: assetId.toString(),
113
245
  decoder,
114
246
  target,
115
- abi: 'function redeem(uint256, address receiver, address owner)',
247
+ selector: 'function redeem(uint256, address receiver, address owner)',
116
248
  valueNonZero: false,
117
- args: [null, randomUser, randomUser],
118
- argsEncoded: encodePacked(['address', 'address'], [randomUser, randomUser]),
249
+ inputs: [
250
+ {
251
+ parameter: 'uint256',
252
+ input: [],
253
+ },
254
+ {
255
+ parameter: 'address receiver',
256
+ input: [randomUser],
257
+ },
258
+ {
259
+ parameter: 'address controller',
260
+ input: [randomUser],
261
+ },
262
+ ],
263
+ inputCombinations: [
264
+ {
265
+ inputs: [null, randomUser, randomUser],
266
+ inputsEncoded: encodePacked(['address', 'address'], [randomUser, randomUser]),
267
+ },
268
+ ],
119
269
  },
120
270
  {
121
271
  assetId: assetId.toString(),
122
272
  decoder,
123
273
  target,
124
- abi: 'function requestDeposit(uint256, address controller, address owner)',
274
+ selector: 'function requestDeposit(uint256, address controller, address owner)',
125
275
  valueNonZero: false,
126
- args: [null, randomUser, randomUser],
127
- argsEncoded: encodePacked(['address', 'address'], [randomUser, randomUser]),
276
+ inputs: [
277
+ {
278
+ parameter: 'uint256',
279
+ input: [],
280
+ },
281
+ {
282
+ parameter: 'address receiver',
283
+ input: [randomUser],
284
+ },
285
+ {
286
+ parameter: 'address controller',
287
+ input: [randomUser],
288
+ },
289
+ ],
290
+ inputCombinations: [
291
+ {
292
+ inputs: [null, randomUser, randomUser],
293
+ inputsEncoded: encodePacked(['address', 'address'], [randomUser, randomUser]),
294
+ },
295
+ ],
128
296
  },
129
297
  {
130
298
  assetId: assetId.toString(),
131
299
  decoder,
132
300
  target,
133
- abi: 'function requestRedeem(uint256, address controller, address owner)',
301
+ selector: 'function requestRedeem(uint256, address controller, address owner)',
134
302
  valueNonZero: false,
135
- args: [null, randomUser, randomUser],
136
- argsEncoded: encodePacked(['address', 'address'], [randomUser, randomUser]),
303
+ inputs: [
304
+ {
305
+ parameter: 'uint256',
306
+ input: [],
307
+ },
308
+ {
309
+ parameter: 'address receiver',
310
+ input: [randomUser],
311
+ },
312
+ {
313
+ parameter: 'address controller',
314
+ input: [randomUser],
315
+ },
316
+ ],
317
+ inputCombinations: [
318
+ {
319
+ inputs: [null, randomUser, randomUser],
320
+ inputsEncoded: encodePacked(['address', 'address'], [randomUser, randomUser]),
321
+ },
322
+ ],
137
323
  },
138
324
  {
139
325
  assetId: assetId.toString(),
140
326
  decoder,
141
327
  target,
142
- abi: 'function withdraw(uint256, address receiver, address owner)',
328
+ selector: 'function withdraw(uint256, address receiver, address owner)',
143
329
  valueNonZero: false,
144
- args: [null, randomUser, randomUser],
145
- argsEncoded: encodePacked(['address', 'address'], [randomUser, randomUser]),
330
+ inputs: [
331
+ {
332
+ parameter: 'uint256',
333
+ input: [],
334
+ },
335
+ {
336
+ parameter: 'address receiver',
337
+ input: [randomUser],
338
+ },
339
+ {
340
+ parameter: 'address controller',
341
+ input: [randomUser],
342
+ },
343
+ ],
344
+ inputCombinations: [
345
+ {
346
+ inputs: [null, randomUser, randomUser],
347
+ inputsEncoded: encodePacked(['address', 'address'], [randomUser, randomUser]),
348
+ },
349
+ ],
146
350
  },
147
351
  {
148
352
  assetId: assetId.toString(),
149
353
  decoder,
150
354
  target,
151
- abi: 'function withdraw(uint64,bytes16,address,uint256,address,uint128)',
355
+ selector: 'function withdraw(uint64,bytes16,address,uint256,address,uint128)',
152
356
  valueNonZero: false,
153
- args: [poolId.toString(), scId.toString(), someErc20, null, randomUser, null],
154
- argsEncoded: encodePacked(['uint64', 'bytes16', 'address', 'address'], [poolId.raw, scId.raw, someErc20, randomUser]),
357
+ inputs: [
358
+ {
359
+ parameter: 'poolId',
360
+ input: [poolId.toString()],
361
+ },
362
+ {
363
+ parameter: 'shareClassId',
364
+ input: [scId.raw],
365
+ },
366
+ {
367
+ parameter: 'erc20',
368
+ input: [someErc20],
369
+ },
370
+ {
371
+ parameter: 'uint256',
372
+ input: [],
373
+ },
374
+ {
375
+ parameter: 'address',
376
+ input: [randomUser],
377
+ },
378
+ {
379
+ parameter: 'uint128',
380
+ input: [],
381
+ },
382
+ ],
383
+ inputCombinations: [
384
+ {
385
+ inputs: [poolId.toString(), scId.raw, someErc20, null, randomUser, null],
386
+ inputsEncoded: encodePacked(['uint64', 'bytes16', 'address', 'address'], [poolId.raw, scId.raw, someErc20, randomUser]),
387
+ },
388
+ ],
155
389
  },
156
390
  ];
157
391
  context.tenderlyFork.impersonateAddress = fundManager;
@@ -171,19 +405,67 @@ describe('MerkleProofManager', () => {
171
405
  assetId: assetId.toString(),
172
406
  decoder,
173
407
  target,
174
- abi: 'function withdraw(uint64,bytes16,address,uint256,address,uint128)',
408
+ selector: 'function withdraw(uint64,bytes16,address,uint256,address,uint128)',
175
409
  valueNonZero: false,
176
- args: [poolId.toString(), scId, erc20, null, manager, null],
177
- argsEncoded: encodePacked(['uint64', 'bytes16', 'address', 'address'], [poolId, scId, erc20, manager]),
410
+ inputs: [
411
+ {
412
+ parameter: 'poolId',
413
+ input: [poolId.toString()],
414
+ },
415
+ {
416
+ parameter: 'shareClassId',
417
+ input: [scId],
418
+ },
419
+ {
420
+ parameter: 'erc20',
421
+ input: [someErc20],
422
+ },
423
+ {
424
+ parameter: 'uint256',
425
+ input: [],
426
+ },
427
+ {
428
+ parameter: 'address',
429
+ input: [manager],
430
+ },
431
+ {
432
+ parameter: 'uint128',
433
+ input: [],
434
+ },
435
+ ],
436
+ inputCombinations: [
437
+ {
438
+ inputs: [poolId.toString(), scId, erc20, null, manager, null],
439
+ inputsEncoded: encodePacked(['uint64', 'bytes16', 'address', 'address'], [poolId, scId, erc20, manager]),
440
+ },
441
+ ],
178
442
  },
179
443
  {
180
444
  assetId: assetId.toString(),
181
445
  decoder,
182
446
  target,
183
- abi: 'function deposit(uint64,bytes16,address,uint256,uint128)',
447
+ selector: 'function deposit(uint64,bytes16,address,uint256,uint128)',
184
448
  valueNonZero: false,
185
- args: [poolId.toString(), scId, erc20, null],
186
- argsEncoded: encodePacked(['uint64', 'bytes16', 'address'], [poolId, scId, erc20]),
449
+ inputs: [
450
+ {
451
+ parameter: 'shareClassId',
452
+ input: [scId],
453
+ },
454
+ {
455
+ parameter: 'erc20',
456
+ input: [someErc20],
457
+ },
458
+ {
459
+ parameter: 'uint256',
460
+ input: [],
461
+ },
462
+ ],
463
+ inputCombinations: [
464
+ {
465
+ inputs: [poolId.toString(), scId, erc20],
466
+ inputsEncoded: encodePacked(['uint64', 'bytes16', 'address'], [poolId, scId, erc20]),
467
+ },
468
+ ],
187
469
  },
188
470
  ];
189
471
  const tree = getMerkleTree(SimpleMerkleTree, policies);
@@ -204,7 +486,7 @@ describe('MerkleProofManager', () => {
204
486
  const mpm = new MerkleProofManager(centrifugeWithPin, merkleProofManager.network, mpmAddress);
205
487
  context.tenderlyFork.impersonateAddress = fundManager;
206
488
  centrifugeWithPin.setSigner(context.tenderlyFork.signer);
207
- await mpm.setPolicies(strategist, mockPolicies.map((p) => ({ ...p, argsEncoded: undefined })));
489
+ await mpm.setPolicies(strategist, mockPolicies);
208
490
  const tree = getMerkleTree(SimpleMerkleTree, mockPolicies);
209
491
  const rootHash = await centrifugeWithPin.getClient(chainId).readContract({
210
492
  address: mpmAddress,
@@ -216,9 +498,6 @@ describe('MerkleProofManager', () => {
216
498
  });
217
499
  it('can execute calls', async () => {
218
500
  const { vaultRouter } = await context.centrifuge._protocolAddresses(chainId);
219
- await context.tenderlyFork.fundAccountEth(strategist, 10n ** 18n);
220
- context.tenderlyFork.impersonateAddress = strategist;
221
- context.centrifuge.setSigner(context.tenderlyFork.signer);
222
501
  const mock = sinon.stub(merkleProofManager.pool, 'metadata');
223
502
  mock.returns(makeThenable(of({
224
503
  merkleProofManager: {
@@ -227,7 +506,23 @@ describe('MerkleProofManager', () => {
227
506
  },
228
507
  },
229
508
  })));
230
- await merkleProofManager.execute([{ policy: mockPolicies[0], inputs: [123000000n] }]);
509
+ const centrifugeWithPin = new Centrifuge({
510
+ environment: 'testnet',
511
+ pinJson: async () => {
512
+ return 'abc';
513
+ },
514
+ rpcUrls: {
515
+ 11155111: context.tenderlyFork.rpcUrl,
516
+ },
517
+ });
518
+ context.tenderlyFork.impersonateAddress = fundManager;
519
+ centrifugeWithPin.setSigner(context.tenderlyFork.signer);
520
+ const mpm = new MerkleProofManager(centrifugeWithPin, merkleProofManager.network, mpmAddress);
521
+ await mpm.setPolicies(strategist, mockPolicies);
522
+ await context.tenderlyFork.fundAccountEth(strategist, 10n ** 18n);
523
+ context.tenderlyFork.impersonateAddress = strategist;
524
+ context.centrifuge.setSigner(context.tenderlyFork.signer);
525
+ await merkleProofManager.execute([{ policy: mockPolicies[0], inputs: [vaultRouter, 123000000n] }]);
231
526
  const balance = await context.centrifuge.getClient(chainId).readContract({
232
527
  address: someErc20,
233
528
  abi: ABI.Currency,
@@ -237,5 +532,152 @@ describe('MerkleProofManager', () => {
237
532
  expect(balance).to.equal(123000000n);
238
533
  mock.restore();
239
534
  });
535
+ it('cannot execute calls with invalid proof', async () => {
536
+ const { vaultRouter } = await context.centrifuge._protocolAddresses(chainId);
537
+ const mock = sinon.stub(merkleProofManager.pool, 'metadata');
538
+ mock.returns(makeThenable(of({
539
+ merkleProofManager: {
540
+ [chainId]: {
541
+ [strategist.toLowerCase()]: { policies: mockPolicies },
542
+ },
543
+ },
544
+ })));
545
+ const centrifugeWithPin = new Centrifuge({
546
+ environment: 'testnet',
547
+ pinJson: async () => {
548
+ return 'abc';
549
+ },
550
+ rpcUrls: {
551
+ 11155111: context.tenderlyFork.rpcUrl,
552
+ },
553
+ });
554
+ context.tenderlyFork.impersonateAddress = fundManager;
555
+ centrifugeWithPin.setSigner(context.tenderlyFork.signer);
556
+ await context.tenderlyFork.fundAccountEth(strategist, 10n ** 18n);
557
+ context.tenderlyFork.impersonateAddress = strategist;
558
+ context.centrifuge.setSigner(context.tenderlyFork.signer);
559
+ try {
560
+ await merkleProofManager.execute([{ policy: mockPolicies[0], inputs: [vaultRouter, 123000000n] }]);
561
+ }
562
+ catch (e) {
563
+ expect(e.message).to.include('Transaction reverted');
564
+ }
565
+ mock.restore();
566
+ });
567
+ describe('generateCombinations', () => {
568
+ it('generates all combinations of inputs', async () => {
569
+ const policyInput = {
570
+ decoder: '0xDecoder',
571
+ target: '0xTarget',
572
+ action: 'someAction',
573
+ selector: 'function doSomething(uint256 a, address b, uint256 c)',
574
+ inputs: [
575
+ {
576
+ parameter: 'a',
577
+ input: ['0x1', '0x2'],
578
+ },
579
+ {
580
+ parameter: 'b',
581
+ input: ['0xAddress1', '0xAddress2'],
582
+ },
583
+ {
584
+ parameter: 'c',
585
+ input: ['0x3'],
586
+ },
587
+ ],
588
+ };
589
+ const expectedCombinations = [
590
+ ['0x1', '0xAddress1', '0x3'],
591
+ ['0x1', '0xAddress2', '0x3'],
592
+ ['0x2', '0xAddress1', '0x3'],
593
+ ['0x2', '0xAddress2', '0x3'],
594
+ ];
595
+ const combinations = generateCombinations(policyInput.inputs);
596
+ expect(combinations).to.deep.equal(expectedCombinations);
597
+ });
598
+ it('handles inputs that are null', () => {
599
+ const policyInput = {
600
+ decoder: '0xDecoder',
601
+ target: '0xTarget',
602
+ action: 'someAction',
603
+ selector: 'function doSomething()',
604
+ inputs: [
605
+ {
606
+ parameter: 'a',
607
+ input: [],
608
+ },
609
+ {
610
+ parameter: 'b',
611
+ input: ['0xAddress1', '0xAddress2'],
612
+ },
613
+ {
614
+ parameter: 'c',
615
+ input: ['0x3'],
616
+ },
617
+ ],
618
+ };
619
+ const expectedCombinations = [
620
+ [null, '0xAddress1', '0x3'],
621
+ [null, '0xAddress2', '0x3'],
622
+ ];
623
+ const combinations = generateCombinations(policyInput.inputs);
624
+ expect(combinations).to.deep.equal(expectedCombinations);
625
+ });
626
+ it('handles many inputs with mix of nulls and values', () => {
627
+ const policyInput = {
628
+ decoder: '0xDecoder',
629
+ target: '0xTarget',
630
+ action: 'someAction',
631
+ selector: 'function doSomething()',
632
+ inputs: [
633
+ {
634
+ parameter: 'a',
635
+ input: ['0x1', '0x2', '0x3'],
636
+ },
637
+ {
638
+ parameter: 'b',
639
+ input: [],
640
+ },
641
+ {
642
+ parameter: 'c',
643
+ input: ['0x4', '0x5'],
644
+ },
645
+ {
646
+ parameter: 'd',
647
+ input: ['0x6'],
648
+ },
649
+ {
650
+ parameter: 'e',
651
+ input: [],
652
+ },
653
+ ],
654
+ };
655
+ const expectedCombinations = [
656
+ ['0x1', null, '0x4', '0x6', null],
657
+ ['0x1', null, '0x5', '0x6', null],
658
+ ['0x2', null, '0x4', '0x6', null],
659
+ ['0x2', null, '0x5', '0x6', null],
660
+ ['0x3', null, '0x4', '0x6', null],
661
+ ['0x3', null, '0x5', '0x6', null],
662
+ ];
663
+ const combinations = generateCombinations(policyInput.inputs);
664
+ expect(combinations).to.deep.equal(expectedCombinations);
665
+ });
666
+ it('handles empty inputs', () => {
667
+ const policyInput = {
668
+ decoder: '0xDecoder',
669
+ target: '0xTarget',
670
+ action: 'someAction',
671
+ selector: 'function doSomething()',
672
+ inputs: [],
673
+ };
674
+ try {
675
+ generateCombinations(policyInput.inputs);
676
+ }
677
+ catch (error) {
678
+ expect(error.message).to.include('No inputs provided for generating combinations');
679
+ }
680
+ });
681
+ });
240
682
  });
241
683
  //# sourceMappingURL=MerkleProofManager.test.js.map