@paraswap/dex-lib 2.28.10 → 2.28.11-ramses-v2
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/build/dex/uniswap-v3/config.js +22 -0
- package/build/dex/uniswap-v3/config.js.map +1 -1
- package/build/dex/uniswap-v3/forks/ramses-v2/utils.d.ts +1 -1
- package/build/dex/uniswap-v3/types.d.ts +6 -1
- package/build/dex/uniswap-v3/types.js.map +1 -1
- package/build/dex/uniswap-v3/uniswap-v3-pool.d.ts +3 -2
- package/build/dex/uniswap-v3/uniswap-v3-pool.js +5 -2
- package/build/dex/uniswap-v3/uniswap-v3-pool.js.map +1 -1
- package/build/dex/uniswap-v3/uniswap-v3.js +7 -3
- package/build/dex/uniswap-v3/uniswap-v3.js.map +1 -1
- package/package.json +1 -1
- package/src/abi/RamsesV2Pool.abi.json +1554 -0
- package/src/abi/RamsesV2Quoter.abi.json +274 -0
- package/src/abi/RamsesV2StateMulticall.abi.json +891 -0
- package/src/dex/uniswap-v3/config.ts +22 -0
- package/src/dex/uniswap-v3/forks/ramses-v2/utils.ts +72 -0
- package/src/dex/uniswap-v3/types.ts +7 -1
- package/src/dex/uniswap-v3/uniswap-v3-e2e.test.ts +445 -354
- package/src/dex/uniswap-v3/uniswap-v3-integration.test.ts +171 -13
- package/src/dex/uniswap-v3/uniswap-v3-pool.ts +9 -4
- package/src/dex/uniswap-v3/uniswap-v3.ts +7 -2
- package/tests/constants-e2e.ts +1 -5
|
@@ -3,13 +3,13 @@ import dotenv from 'dotenv';
|
|
|
3
3
|
dotenv.config();
|
|
4
4
|
|
|
5
5
|
import { Interface, Result } from '@ethersproject/abi';
|
|
6
|
-
import { DummyDexHelper } from '../../dex-helper/index';
|
|
6
|
+
import { DummyDexHelper, IDexHelper } from '../../dex-helper/index';
|
|
7
7
|
import { Network, SwapSide } from '../../constants';
|
|
8
8
|
import { BI_POWS } from '../../bigint-constants';
|
|
9
9
|
import { UniswapV3 } from './uniswap-v3';
|
|
10
10
|
import { checkPoolPrices, checkPoolsLiquidity } from '../../../tests/utils';
|
|
11
11
|
import { Tokens } from '../../../tests/constants-e2e';
|
|
12
|
-
import
|
|
12
|
+
import UniswapV3QuoterV2ABI from '../../abi/uniswap-v3/UniswapV3QuoterV2.abi.json';
|
|
13
13
|
import { Address } from '@paraswap/core';
|
|
14
14
|
|
|
15
15
|
const network = Network.POLYGON;
|
|
@@ -28,10 +28,8 @@ const amounts = [
|
|
|
28
28
|
|
|
29
29
|
const amountsBuy = [0n, 1n * BI_POWS[18], 2n * BI_POWS[18], 3n * BI_POWS[18]];
|
|
30
30
|
|
|
31
|
-
const dexHelper = new DummyDexHelper(network);
|
|
32
|
-
const dexKey = 'UniswapV3';
|
|
33
31
|
|
|
34
|
-
const quoterIface = new Interface(
|
|
32
|
+
const quoterIface = new Interface(UniswapV3QuoterV2ABI);
|
|
35
33
|
|
|
36
34
|
function getReaderCalldata(
|
|
37
35
|
exchangeAddress: string,
|
|
@@ -45,11 +43,13 @@ function getReaderCalldata(
|
|
|
45
43
|
return amounts.map(amount => ({
|
|
46
44
|
target: exchangeAddress,
|
|
47
45
|
callData: readerIface.encodeFunctionData(funcName, [
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
46
|
+
[
|
|
47
|
+
tokenIn,
|
|
48
|
+
tokenOut,
|
|
49
|
+
amount.toString(),
|
|
50
|
+
fee.toString(),
|
|
51
|
+
0,
|
|
52
|
+
]
|
|
53
53
|
]),
|
|
54
54
|
}));
|
|
55
55
|
}
|
|
@@ -66,9 +66,11 @@ function decodeReaderResult(
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
async function checkOnChainPricing(
|
|
69
|
+
dexHelper: IDexHelper,
|
|
69
70
|
uniswapV3: UniswapV3,
|
|
70
71
|
funcName: string,
|
|
71
72
|
blockNumber: number,
|
|
73
|
+
exchangeAddress: string,
|
|
72
74
|
prices: bigint[],
|
|
73
75
|
tokenIn: Address,
|
|
74
76
|
tokenOut: Address,
|
|
@@ -76,7 +78,7 @@ async function checkOnChainPricing(
|
|
|
76
78
|
_amounts: bigint[],
|
|
77
79
|
) {
|
|
78
80
|
// Quoter address
|
|
79
|
-
const exchangeAddress = '0xb27308f9F90D607463bb33eA1BeBb41C27CE5AB6';
|
|
81
|
+
// const exchangeAddress = '0xb27308f9F90D607463bb33eA1BeBb41C27CE5AB6';
|
|
80
82
|
const readerIface = quoterIface;
|
|
81
83
|
|
|
82
84
|
const sum = prices.reduce((acc, curr) => (acc += curr), 0n);
|
|
@@ -117,6 +119,8 @@ async function checkOnChainPricing(
|
|
|
117
119
|
decodeReaderResult(readerResult, readerIface, funcName),
|
|
118
120
|
);
|
|
119
121
|
|
|
122
|
+
console.log('EXPECTED PRICES: ', expectedPrices);
|
|
123
|
+
|
|
120
124
|
let firstZeroIndex = prices.slice(1).indexOf(0n);
|
|
121
125
|
|
|
122
126
|
// we skipped first, so add +1 on result
|
|
@@ -130,6 +134,9 @@ async function checkOnChainPricing(
|
|
|
130
134
|
}
|
|
131
135
|
|
|
132
136
|
describe('UniswapV3', function () {
|
|
137
|
+
const dexHelper = new DummyDexHelper(network);
|
|
138
|
+
const dexKey = 'UniswapV3';
|
|
139
|
+
|
|
133
140
|
let blockNumber: number;
|
|
134
141
|
let uniswapV3: UniswapV3;
|
|
135
142
|
let uniswapV3Mainnet: UniswapV3;
|
|
@@ -138,9 +145,9 @@ describe('UniswapV3', function () {
|
|
|
138
145
|
blockNumber = await dexHelper.web3Provider.eth.getBlockNumber();
|
|
139
146
|
uniswapV3 = new UniswapV3(network, dexKey, dexHelper);
|
|
140
147
|
uniswapV3Mainnet = new UniswapV3(
|
|
141
|
-
Network.
|
|
148
|
+
Network.ARBITRUM,
|
|
142
149
|
dexKey,
|
|
143
|
-
new DummyDexHelper(Network.
|
|
150
|
+
new DummyDexHelper(Network.ARBITRUM),
|
|
144
151
|
);
|
|
145
152
|
});
|
|
146
153
|
|
|
@@ -173,9 +180,11 @@ describe('UniswapV3', function () {
|
|
|
173
180
|
poolPrices!.map(async price => {
|
|
174
181
|
const fee = uniswapV3.eventPools[price.poolIdentifier!]!.feeCode;
|
|
175
182
|
const res = await checkOnChainPricing(
|
|
183
|
+
dexHelper,
|
|
176
184
|
uniswapV3,
|
|
177
185
|
'quoteExactInputSingle',
|
|
178
186
|
blockNumber,
|
|
187
|
+
'0x61fFE014bA17989E743c5F6cB21bF9697530B21e',
|
|
179
188
|
price.prices,
|
|
180
189
|
TokenA.address,
|
|
181
190
|
TokenB.address,
|
|
@@ -219,9 +228,11 @@ describe('UniswapV3', function () {
|
|
|
219
228
|
poolPrices!.map(async price => {
|
|
220
229
|
const fee = uniswapV3.eventPools[price.poolIdentifier!]!.feeCode;
|
|
221
230
|
const res = await checkOnChainPricing(
|
|
231
|
+
dexHelper,
|
|
222
232
|
uniswapV3,
|
|
223
233
|
'quoteExactOutputSingle',
|
|
224
234
|
blockNumber,
|
|
235
|
+
'0x61fFE014bA17989E743c5F6cB21bF9697530B21e',
|
|
225
236
|
price.prices,
|
|
226
237
|
TokenA.address,
|
|
227
238
|
TokenB.address,
|
|
@@ -329,9 +340,11 @@ describe('UniswapV3', function () {
|
|
|
329
340
|
poolPrices!.map(async price => {
|
|
330
341
|
const fee = uniswapV3.eventPools[price.poolIdentifier!]!.feeCode;
|
|
331
342
|
const res = await checkOnChainPricing(
|
|
343
|
+
dexHelper,
|
|
332
344
|
uniswapV3,
|
|
333
345
|
'quoteExactInputSingle',
|
|
334
346
|
blockNumber,
|
|
347
|
+
'0x61fFE014bA17989E743c5F6cB21bF9697530B21e',
|
|
335
348
|
price.prices,
|
|
336
349
|
TokenA.address,
|
|
337
350
|
TokenB.address,
|
|
@@ -439,9 +452,11 @@ describe('UniswapV3', function () {
|
|
|
439
452
|
poolPrices!.map(async price => {
|
|
440
453
|
const fee = uniswapV3.eventPools[price.poolIdentifier!]!.feeCode;
|
|
441
454
|
const res = await checkOnChainPricing(
|
|
455
|
+
dexHelper,
|
|
442
456
|
uniswapV3,
|
|
443
457
|
'quoteExactOutputSingle',
|
|
444
458
|
blockNumber,
|
|
459
|
+
'0x61fFE014bA17989E743c5F6cB21bF9697530B21e',
|
|
445
460
|
price.prices,
|
|
446
461
|
TokenA.address,
|
|
447
462
|
TokenB.address,
|
|
@@ -466,3 +481,146 @@ describe('UniswapV3', function () {
|
|
|
466
481
|
}
|
|
467
482
|
});
|
|
468
483
|
});
|
|
484
|
+
|
|
485
|
+
describe('RamsesV2', () => {
|
|
486
|
+
const dexKey = 'RamsesV2';
|
|
487
|
+
let blockNumber: number;
|
|
488
|
+
let uniswapV3: UniswapV3;
|
|
489
|
+
let uniswapV3Mainnet: UniswapV3;
|
|
490
|
+
|
|
491
|
+
const network = Network.ARBITRUM;
|
|
492
|
+
const dexHelper = new DummyDexHelper(network);
|
|
493
|
+
const TokenASymbol = 'USDCe';
|
|
494
|
+
const TokenA = Tokens[network][TokenASymbol];
|
|
495
|
+
|
|
496
|
+
const TokenBSymbol = 'USDC';
|
|
497
|
+
const TokenB = Tokens[network][TokenBSymbol];
|
|
498
|
+
|
|
499
|
+
beforeEach(async () => {
|
|
500
|
+
blockNumber = await dexHelper.web3Provider.eth.getBlockNumber();
|
|
501
|
+
uniswapV3 = new UniswapV3(network, dexKey, dexHelper);
|
|
502
|
+
uniswapV3Mainnet = new UniswapV3(
|
|
503
|
+
Network.ARBITRUM,
|
|
504
|
+
dexKey,
|
|
505
|
+
dexHelper,
|
|
506
|
+
);
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
it('getPoolIdentifiers and getPricesVolume SELL', async function () {
|
|
510
|
+
|
|
511
|
+
const amounts = [
|
|
512
|
+
0n,
|
|
513
|
+
6000000n,
|
|
514
|
+
12000000n,
|
|
515
|
+
18000000n,
|
|
516
|
+
24000000n,
|
|
517
|
+
30000000n,
|
|
518
|
+
36000000n,
|
|
519
|
+
42000000n,
|
|
520
|
+
];
|
|
521
|
+
|
|
522
|
+
const pools = await uniswapV3.getPoolIdentifiers(
|
|
523
|
+
TokenA,
|
|
524
|
+
TokenB,
|
|
525
|
+
SwapSide.SELL,
|
|
526
|
+
blockNumber,
|
|
527
|
+
);
|
|
528
|
+
console.log(`${TokenASymbol} <> ${TokenBSymbol} Pool Identifiers: `, pools);
|
|
529
|
+
|
|
530
|
+
expect(pools.length).toBeGreaterThan(0);
|
|
531
|
+
|
|
532
|
+
const poolPrices = await uniswapV3.getPricesVolume(
|
|
533
|
+
TokenA,
|
|
534
|
+
TokenB,
|
|
535
|
+
amounts,
|
|
536
|
+
SwapSide.SELL,
|
|
537
|
+
blockNumber,
|
|
538
|
+
pools,
|
|
539
|
+
);
|
|
540
|
+
console.log(`${TokenASymbol} <> ${TokenBSymbol} Pool Prices: `, poolPrices);
|
|
541
|
+
|
|
542
|
+
expect(poolPrices).not.toBeNull();
|
|
543
|
+
checkPoolPrices(poolPrices!, amounts, SwapSide.SELL, dexKey);
|
|
544
|
+
|
|
545
|
+
let falseChecksCounter = 0;
|
|
546
|
+
await Promise.all(
|
|
547
|
+
poolPrices!.map(async price => {
|
|
548
|
+
const fee = uniswapV3.eventPools[price.poolIdentifier!]!.feeCode;
|
|
549
|
+
const res = await checkOnChainPricing(
|
|
550
|
+
dexHelper,
|
|
551
|
+
uniswapV3,
|
|
552
|
+
'quoteExactInputSingle',
|
|
553
|
+
blockNumber,
|
|
554
|
+
'0xAA20EFF7ad2F523590dE6c04918DaAE0904E3b20',
|
|
555
|
+
price.prices,
|
|
556
|
+
TokenA.address,
|
|
557
|
+
TokenB.address,
|
|
558
|
+
fee,
|
|
559
|
+
amounts,
|
|
560
|
+
);
|
|
561
|
+
if (res === false) falseChecksCounter++;
|
|
562
|
+
}),
|
|
563
|
+
);
|
|
564
|
+
|
|
565
|
+
expect(falseChecksCounter).toBeLessThan(poolPrices!.length);
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
it('getPoolIdentifiers and getPricesVolume BUY', async function () {
|
|
569
|
+
|
|
570
|
+
const amounts = [
|
|
571
|
+
0n,
|
|
572
|
+
6000000n,
|
|
573
|
+
12000000n,
|
|
574
|
+
18000000n,
|
|
575
|
+
24000000n,
|
|
576
|
+
30000000n,
|
|
577
|
+
36000000n,
|
|
578
|
+
42000000n,
|
|
579
|
+
];
|
|
580
|
+
|
|
581
|
+
const pools = await uniswapV3.getPoolIdentifiers(
|
|
582
|
+
TokenA,
|
|
583
|
+
TokenB,
|
|
584
|
+
SwapSide.BUY,
|
|
585
|
+
blockNumber,
|
|
586
|
+
);
|
|
587
|
+
console.log(`${TokenASymbol} <> ${TokenBSymbol} Pool Identifiers: `, pools);
|
|
588
|
+
|
|
589
|
+
expect(pools.length).toBeGreaterThan(0);
|
|
590
|
+
|
|
591
|
+
const poolPrices = await uniswapV3.getPricesVolume(
|
|
592
|
+
TokenA,
|
|
593
|
+
TokenB,
|
|
594
|
+
amounts,
|
|
595
|
+
SwapSide.BUY,
|
|
596
|
+
blockNumber,
|
|
597
|
+
pools,
|
|
598
|
+
);
|
|
599
|
+
console.log(`${TokenASymbol} <> ${TokenBSymbol} Pool Prices: `, poolPrices);
|
|
600
|
+
|
|
601
|
+
expect(poolPrices).not.toBeNull();
|
|
602
|
+
checkPoolPrices(poolPrices!, amounts, SwapSide.SELL, dexKey);
|
|
603
|
+
|
|
604
|
+
let falseChecksCounter = 0;
|
|
605
|
+
await Promise.all(
|
|
606
|
+
poolPrices!.map(async price => {
|
|
607
|
+
const fee = uniswapV3.eventPools[price.poolIdentifier!]!.feeCode;
|
|
608
|
+
const res = await checkOnChainPricing(
|
|
609
|
+
dexHelper,
|
|
610
|
+
uniswapV3,
|
|
611
|
+
'quoteExactOutputSingle',
|
|
612
|
+
blockNumber,
|
|
613
|
+
'0xAA20EFF7ad2F523590dE6c04918DaAE0904E3b20',
|
|
614
|
+
price.prices,
|
|
615
|
+
TokenA.address,
|
|
616
|
+
TokenB.address,
|
|
617
|
+
fee,
|
|
618
|
+
amounts,
|
|
619
|
+
);
|
|
620
|
+
if (res === false) falseChecksCounter++;
|
|
621
|
+
}),
|
|
622
|
+
);
|
|
623
|
+
|
|
624
|
+
expect(falseChecksCounter).toBeLessThan(poolPrices!.length);
|
|
625
|
+
});
|
|
626
|
+
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import _ from 'lodash';
|
|
2
2
|
import { Contract } from 'web3-eth-contract';
|
|
3
3
|
import { Interface } from '@ethersproject/abi';
|
|
4
|
-
import { ethers } from 'ethers';
|
|
4
|
+
import { BytesLike, ethers } from 'ethers';
|
|
5
5
|
import { assert, DeepReadonly } from 'ts-essentials';
|
|
6
6
|
import { Log, Logger, BlockHeader, Address } from '../../types';
|
|
7
7
|
import {
|
|
@@ -11,12 +11,12 @@ import {
|
|
|
11
11
|
import { IDexHelper } from '../../dex-helper/idex-helper';
|
|
12
12
|
import {
|
|
13
13
|
PoolState,
|
|
14
|
-
DecodedStateMultiCallResultWithRelativeBitmaps,
|
|
14
|
+
DecodedStateMultiCallResultWithRelativeBitmaps, DecodeStateMultiCallFunc,
|
|
15
15
|
} from './types';
|
|
16
16
|
import UniswapV3PoolABI from '../../abi/uniswap-v3/UniswapV3Pool.abi.json';
|
|
17
17
|
import { bigIntify, catchParseLogError, isSampled } from '../../utils';
|
|
18
18
|
import { uniswapV3Math } from './contract-math/uniswap-v3-math';
|
|
19
|
-
import { MultiCallParams } from '../../lib/multi-wrapper';
|
|
19
|
+
import { MultiCallParams, MultiResult } from '../../lib/multi-wrapper';
|
|
20
20
|
import {
|
|
21
21
|
OUT_OF_RANGE_ERROR_POSTFIX,
|
|
22
22
|
TICK_BITMAP_BUFFER,
|
|
@@ -60,6 +60,7 @@ export class UniswapV3EventPool extends StatefulEventSubscriber<PoolState> {
|
|
|
60
60
|
readonly dexHelper: IDexHelper,
|
|
61
61
|
parentName: string,
|
|
62
62
|
readonly stateMultiContract: Contract,
|
|
63
|
+
readonly decodeStateMultiCallResultWithRelativeBitmaps: DecodeStateMultiCallFunc | undefined,
|
|
63
64
|
readonly erc20Interface: Interface,
|
|
64
65
|
protected readonly factoryAddress: Address,
|
|
65
66
|
public readonly feeCode: bigint,
|
|
@@ -226,9 +227,13 @@ export class UniswapV3EventPool extends StatefulEventSubscriber<PoolState> {
|
|
|
226
227
|
this.getBitmapRangeToRequest(),
|
|
227
228
|
)
|
|
228
229
|
.encodeABI(),
|
|
229
|
-
decodeFunction:
|
|
230
|
+
decodeFunction:
|
|
231
|
+
this.decodeStateMultiCallResultWithRelativeBitmaps !== undefined
|
|
232
|
+
? this.decodeStateMultiCallResultWithRelativeBitmaps
|
|
233
|
+
: decodeStateMultiCallResultWithRelativeBitmaps,
|
|
230
234
|
},
|
|
231
235
|
];
|
|
236
|
+
|
|
232
237
|
this._stateRequestCallData = callData;
|
|
233
238
|
}
|
|
234
239
|
return this._stateRequestCallData;
|
|
@@ -91,7 +91,7 @@ export class UniswapV3
|
|
|
91
91
|
|
|
92
92
|
public static dexKeysWithNetwork: { key: string; networks: Network[] }[] =
|
|
93
93
|
getDexKeysWithNetwork(
|
|
94
|
-
_.pick(UniswapV3Config, ['UniswapV3', 'QuickSwapV3.1']),
|
|
94
|
+
_.pick(UniswapV3Config, ['UniswapV3', 'QuickSwapV3.1', 'RamsesV2']),
|
|
95
95
|
);
|
|
96
96
|
|
|
97
97
|
logger: Logger;
|
|
@@ -118,7 +118,9 @@ export class UniswapV3
|
|
|
118
118
|
this.config.uniswapMulticall,
|
|
119
119
|
);
|
|
120
120
|
this.stateMultiContract = new this.dexHelper.web3Provider.eth.Contract(
|
|
121
|
-
|
|
121
|
+
this.config.stateMultiCallAbi !== undefined
|
|
122
|
+
? this.config.stateMultiCallAbi
|
|
123
|
+
: UniswapV3StateMulticallABI as AbiItem[],
|
|
122
124
|
this.config.stateMulticall,
|
|
123
125
|
);
|
|
124
126
|
|
|
@@ -239,6 +241,7 @@ export class UniswapV3
|
|
|
239
241
|
this.dexHelper,
|
|
240
242
|
this.dexKey,
|
|
241
243
|
this.stateMultiContract,
|
|
244
|
+
this.config.decodeStateMultiCallResultWithRelativeBitmaps,
|
|
242
245
|
this.erc20Interface,
|
|
243
246
|
this.config.factory,
|
|
244
247
|
fee,
|
|
@@ -1055,6 +1058,8 @@ export class UniswapV3
|
|
|
1055
1058
|
deployer: this.config.deployer?.toLowerCase(),
|
|
1056
1059
|
initHash: this.config.initHash,
|
|
1057
1060
|
subgraphURL: this.config.subgraphURL,
|
|
1061
|
+
stateMultiCallAbi: this.config.stateMultiCallAbi,
|
|
1062
|
+
decodeStateMultiCallResultWithRelativeBitmaps: this.config.decodeStateMultiCallResultWithRelativeBitmaps,
|
|
1058
1063
|
};
|
|
1059
1064
|
return newConfig;
|
|
1060
1065
|
}
|
package/tests/constants-e2e.ts
CHANGED
|
@@ -785,10 +785,6 @@ export const Tokens: {
|
|
|
785
785
|
address: '0x2f2a2543b76a4166549f7aab2e75bef0aefc5b0f',
|
|
786
786
|
decimals: 8,
|
|
787
787
|
},
|
|
788
|
-
USDCe: {
|
|
789
|
-
address: '0xff970a61a04b1ca14834a43f5de4533ebddb5cc8',
|
|
790
|
-
decimals: 6,
|
|
791
|
-
},
|
|
792
788
|
LEX: {
|
|
793
789
|
address: '0x6bB7A17AcC227fd1F6781D1EEDEAE01B42047eE0',
|
|
794
790
|
decimals: 18,
|
|
@@ -1020,7 +1016,7 @@ export const Holders: {
|
|
|
1020
1016
|
USDCe: '0x62383739d68dd0f844103db8dfb05a7eded5bbe6',
|
|
1021
1017
|
USDC: '0xa843392198862f98d17e3aa1421b08f2c2020cff',
|
|
1022
1018
|
OHM: '0xebce5f29ff5ca9aa330ebdf7ec6b5f474bff271e',
|
|
1023
|
-
USDT: '
|
|
1019
|
+
USDT: '0x5ff47d4ab75bcaff6807c81f1367abb53439883c',
|
|
1024
1020
|
POPS: '0x4b78b52e7de4d8b7d367297cb8a87c1875a9d591',
|
|
1025
1021
|
FRAX: '0x59bf0545fca0e5ad48e13da269facd2e8c886ba4',
|
|
1026
1022
|
nUSD: '0x9dd329f5411466d9e0c488ff72519ca9fef0cb40',
|