Skip to content

Commit

Permalink
Update SDK and Balancer Maths dependencies (#1243)
Browse files Browse the repository at this point in the history
* Update SDK and Balancer Maths dependencies

* Add changeset

* Fix build issues

* Fix filename case

* Revert filename case change
  • Loading branch information
brunoguerios authored Dec 7, 2024
1 parent 2bef1fc commit 6d4c98e
Show file tree
Hide file tree
Showing 8 changed files with 161 additions and 135 deletions.
5 changes: 5 additions & 0 deletions .changeset/light-sloths-draw.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'backend': patch
---

Update SDK and Balancer Maths dependencies
20 changes: 7 additions & 13 deletions modules/sor/sor-debug.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,29 +36,23 @@ describe('sor debugging', () => {
expect(parseFloat(swaps.returnAmount)).toBeGreaterThan(0);
}, 5000000);

it('sor v3 sepolia eth->usdc', async () => {
const chain = Chain.MAINNET;
it('sor v3 sepolia usdc->usdt', async () => {
const chain = Chain.SEPOLIA;

const chainId = Object.keys(chainIdToChain).find((key) => chainIdToChain[key] === chain) as string;
initRequestScopedContext();
setRequestScopedContextValue('chainId', chainId);
//only do once before starting to debug
// await PoolController().reloadPoolsV3(chain);
await PoolController().reloadPoolsV3(chain);

const swaps = await sorService.getSorSwapPaths({
chain,
tokenIn: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
tokenOut: '0x220e4201aa472262df2c24dd8069243cf4b76c12',
tokenIn: '0x94a9d9ac8a22534e3faca9f4e7f2e2cf85d5e4c8', // usdc-aave
tokenOut: '0xaa8e23fb1079ea71e0a56f48a2aa51851d8433d0', // usdt-aave
swapType: 'EXACT_IN',
swapAmount: '100',
queryBatchSwap: false,
swapAmount: '1',
useProtocolVersion: 3,
considerPoolsWithHooks: true,
// callDataInput: {
// receiver: '0xb5e6b895734409Df411a052195eb4EE7e40d8696',
// sender: '0xb5e6b895734409Df411a052195eb4EE7e40d8696',
// slippagePercentage: '0.1',
// },
poolIds: ['0x1017d7b181ab63ceeb36d96c52a8056e02b7edd0'], // boosted pool stataUSDC/stataUSDT
});

console.log(swaps.returnAmount);
Expand Down
5 changes: 3 additions & 2 deletions modules/sor/sorV2/lib/poolsV3/stable/stablePool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -255,14 +255,15 @@ export class StablePool implements BasePoolV3 {

public getPoolState(): StableState {
return {
poolType: 'Stable',
poolType: 'STABLE',
poolAddress: this.address,
swapFee: this.swapFee,
balancesLiveScaled18: this.tokens.map((t) => t.scale18),
tokenRates: this.tokens.map((t) => t.rate),
totalSupply: this.totalShares,
amp: this.amp,
tokens: this.tokens.map((t) => t.token.address),
scalingFactors: this.tokens.map((t) => t.scalar * WAD),
scalingFactors: this.tokens.map((t) => t.scalar),
aggregateSwapFee: 0n,
};
}
Expand Down
5 changes: 3 additions & 2 deletions modules/sor/sorV2/lib/poolsV3/weighted/weightedPool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -251,14 +251,15 @@ export class WeightedPoolV3 implements BasePoolV3 {

public getPoolState(): WeightedState {
return {
poolType: 'Weighted',
poolType: 'WEIGHTED',
poolAddress: this.address,
swapFee: this.swapFee,
balancesLiveScaled18: this.tokens.map((t) => t.scale18),
tokenRates: this.tokens.map((_) => WAD),
totalSupply: this.totalShares,
weights: this.tokens.map((t) => t.weight),
tokens: this.tokens.map((t) => t.token.address),
scalingFactors: this.tokens.map((t) => t.scalar * WAD),
scalingFactors: this.tokens.map((t) => t.scalar),
aggregateSwapFee: 0n,
};
}
Expand Down
3 changes: 3 additions & 0 deletions modules/sor/sorV2/sorPathService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import {
SwapKind,
ExactInQueryOutput,
ExactOutQueryOutput,
VAULT,
} from '@balancer/sdk';
import { PathWithAmount } from './lib/path';
import { calculatePriceImpact, getInputAmount, getOutputAmount } from './lib/utils/helpers';
Expand Down Expand Up @@ -271,6 +272,7 @@ class SorPathService implements SwapService {
swapKind,
expectedAmountOut: outputAmount,
amountIn: inputAmount,
to: VAULT[parseInt(chainToIdMap[chain])],
},
slippage: Slippage.fromPercentage(`${parseFloat(callDataInput.slippagePercentage)}`),
deadline: callDataInput.deadline ? BigInt(callDataInput.deadline) : 999999999999999999n,
Expand All @@ -290,6 +292,7 @@ class SorPathService implements SwapService {
swapKind,
expectedAmountIn: inputAmount,
amountOut: outputAmount,
to: VAULT[parseInt(chainToIdMap[chain])],
},
slippage: Slippage.fromPercentage(callDataInput.slippagePercentage as `${number}`),
deadline: callDataInput.deadline ? BigInt(callDataInput.deadline) : 999999999999999999n,
Expand Down
238 changes: 130 additions & 108 deletions modules/subgraphs/blocks-subgraph/generated/blocks-subgraph-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,38 +27,20 @@ export enum Aggregation_Interval {

export type Block = {
__typename?: 'Block';
/** address of the beneficiary to whom the mining rewards were given */
author: Scalars['Bytes'];
/** the minimum gas fee a user must pay to include a transaction in the block */
baseFeePerGas?: Maybe<Scalars['BigInt']>;
/** number of leading zeroes that are required in the resulting block hash for it to be considered valid - PoW only */
difficulty: Scalars['BigInt'];
/** maximum gas allowed in this block */
gasLimit: Scalars['BigInt'];
/** the actual amount of gas used in this block */
gasUsed: Scalars['BigInt'];
/** the block hash */
hash: Scalars['Bytes'];
/** the block hash */
author?: Maybe<Scalars['String']>;
difficulty?: Maybe<Scalars['BigInt']>;
gasLimit?: Maybe<Scalars['BigInt']>;
gasUsed?: Maybe<Scalars['BigInt']>;
id: Scalars['ID'];
/** the block number */
number: Scalars['BigInt'];
/** hash of the parent block */
parentHash: Scalars['Bytes'];
/** hash of the transaction receipts trie */
receiptsRoot: Scalars['Bytes'];
/** the size of the block in bytes */
parentHash?: Maybe<Scalars['String']>;
receiptsRoot?: Maybe<Scalars['String']>;
size?: Maybe<Scalars['BigInt']>;
/** root hash for the global state after applying changes in this block */
stateRoot: Scalars['Bytes'];
/** the block time */
stateRoot?: Maybe<Scalars['String']>;
timestamp: Scalars['BigInt'];
/** the sum of the Ethash mining difficulty for all blocks up to some specific point in the blockchain */
totalDifficulty: Scalars['BigInt'];
/** root hash of the transactions in the payload */
transactionsRoot: Scalars['Bytes'];
/** hash of the uncle block */
unclesHash: Scalars['Bytes'];
totalDifficulty?: Maybe<Scalars['BigInt']>;
transactionsRoot?: Maybe<Scalars['String']>;
unclesHash?: Maybe<Scalars['String']>;
};

export type BlockChangedFilter = {
Expand All @@ -69,24 +51,26 @@ export type Block_Filter = {
/** Filter for the block changed event. */
_change_block?: InputMaybe<BlockChangedFilter>;
and?: InputMaybe<Array<InputMaybe<Block_Filter>>>;
author?: InputMaybe<Scalars['Bytes']>;
author_contains?: InputMaybe<Scalars['Bytes']>;
author_gt?: InputMaybe<Scalars['Bytes']>;
author_gte?: InputMaybe<Scalars['Bytes']>;
author_in?: InputMaybe<Array<Scalars['Bytes']>>;
author_lt?: InputMaybe<Scalars['Bytes']>;
author_lte?: InputMaybe<Scalars['Bytes']>;
author_not?: InputMaybe<Scalars['Bytes']>;
author_not_contains?: InputMaybe<Scalars['Bytes']>;
author_not_in?: InputMaybe<Array<Scalars['Bytes']>>;
baseFeePerGas?: InputMaybe<Scalars['BigInt']>;
baseFeePerGas_gt?: InputMaybe<Scalars['BigInt']>;
baseFeePerGas_gte?: InputMaybe<Scalars['BigInt']>;
baseFeePerGas_in?: InputMaybe<Array<Scalars['BigInt']>>;
baseFeePerGas_lt?: InputMaybe<Scalars['BigInt']>;
baseFeePerGas_lte?: InputMaybe<Scalars['BigInt']>;
baseFeePerGas_not?: InputMaybe<Scalars['BigInt']>;
baseFeePerGas_not_in?: InputMaybe<Array<Scalars['BigInt']>>;
author?: InputMaybe<Scalars['String']>;
author_contains?: InputMaybe<Scalars['String']>;
author_contains_nocase?: InputMaybe<Scalars['String']>;
author_ends_with?: InputMaybe<Scalars['String']>;
author_ends_with_nocase?: InputMaybe<Scalars['String']>;
author_gt?: InputMaybe<Scalars['String']>;
author_gte?: InputMaybe<Scalars['String']>;
author_in?: InputMaybe<Array<Scalars['String']>>;
author_lt?: InputMaybe<Scalars['String']>;
author_lte?: InputMaybe<Scalars['String']>;
author_not?: InputMaybe<Scalars['String']>;
author_not_contains?: InputMaybe<Scalars['String']>;
author_not_contains_nocase?: InputMaybe<Scalars['String']>;
author_not_ends_with?: InputMaybe<Scalars['String']>;
author_not_ends_with_nocase?: InputMaybe<Scalars['String']>;
author_not_in?: InputMaybe<Array<Scalars['String']>>;
author_not_starts_with?: InputMaybe<Scalars['String']>;
author_not_starts_with_nocase?: InputMaybe<Scalars['String']>;
author_starts_with?: InputMaybe<Scalars['String']>;
author_starts_with_nocase?: InputMaybe<Scalars['String']>;
difficulty?: InputMaybe<Scalars['BigInt']>;
difficulty_gt?: InputMaybe<Scalars['BigInt']>;
difficulty_gte?: InputMaybe<Scalars['BigInt']>;
Expand All @@ -111,16 +95,6 @@ export type Block_Filter = {
gasUsed_lte?: InputMaybe<Scalars['BigInt']>;
gasUsed_not?: InputMaybe<Scalars['BigInt']>;
gasUsed_not_in?: InputMaybe<Array<Scalars['BigInt']>>;
hash?: InputMaybe<Scalars['Bytes']>;
hash_contains?: InputMaybe<Scalars['Bytes']>;
hash_gt?: InputMaybe<Scalars['Bytes']>;
hash_gte?: InputMaybe<Scalars['Bytes']>;
hash_in?: InputMaybe<Array<Scalars['Bytes']>>;
hash_lt?: InputMaybe<Scalars['Bytes']>;
hash_lte?: InputMaybe<Scalars['Bytes']>;
hash_not?: InputMaybe<Scalars['Bytes']>;
hash_not_contains?: InputMaybe<Scalars['Bytes']>;
hash_not_in?: InputMaybe<Array<Scalars['Bytes']>>;
id?: InputMaybe<Scalars['ID']>;
id_gt?: InputMaybe<Scalars['ID']>;
id_gte?: InputMaybe<Scalars['ID']>;
Expand All @@ -138,26 +112,46 @@ export type Block_Filter = {
number_not?: InputMaybe<Scalars['BigInt']>;
number_not_in?: InputMaybe<Array<Scalars['BigInt']>>;
or?: InputMaybe<Array<InputMaybe<Block_Filter>>>;
parentHash?: InputMaybe<Scalars['Bytes']>;
parentHash_contains?: InputMaybe<Scalars['Bytes']>;
parentHash_gt?: InputMaybe<Scalars['Bytes']>;
parentHash_gte?: InputMaybe<Scalars['Bytes']>;
parentHash_in?: InputMaybe<Array<Scalars['Bytes']>>;
parentHash_lt?: InputMaybe<Scalars['Bytes']>;
parentHash_lte?: InputMaybe<Scalars['Bytes']>;
parentHash_not?: InputMaybe<Scalars['Bytes']>;
parentHash_not_contains?: InputMaybe<Scalars['Bytes']>;
parentHash_not_in?: InputMaybe<Array<Scalars['Bytes']>>;
receiptsRoot?: InputMaybe<Scalars['Bytes']>;
receiptsRoot_contains?: InputMaybe<Scalars['Bytes']>;
receiptsRoot_gt?: InputMaybe<Scalars['Bytes']>;
receiptsRoot_gte?: InputMaybe<Scalars['Bytes']>;
receiptsRoot_in?: InputMaybe<Array<Scalars['Bytes']>>;
receiptsRoot_lt?: InputMaybe<Scalars['Bytes']>;
receiptsRoot_lte?: InputMaybe<Scalars['Bytes']>;
receiptsRoot_not?: InputMaybe<Scalars['Bytes']>;
receiptsRoot_not_contains?: InputMaybe<Scalars['Bytes']>;
receiptsRoot_not_in?: InputMaybe<Array<Scalars['Bytes']>>;
parentHash?: InputMaybe<Scalars['String']>;
parentHash_contains?: InputMaybe<Scalars['String']>;
parentHash_contains_nocase?: InputMaybe<Scalars['String']>;
parentHash_ends_with?: InputMaybe<Scalars['String']>;
parentHash_ends_with_nocase?: InputMaybe<Scalars['String']>;
parentHash_gt?: InputMaybe<Scalars['String']>;
parentHash_gte?: InputMaybe<Scalars['String']>;
parentHash_in?: InputMaybe<Array<Scalars['String']>>;
parentHash_lt?: InputMaybe<Scalars['String']>;
parentHash_lte?: InputMaybe<Scalars['String']>;
parentHash_not?: InputMaybe<Scalars['String']>;
parentHash_not_contains?: InputMaybe<Scalars['String']>;
parentHash_not_contains_nocase?: InputMaybe<Scalars['String']>;
parentHash_not_ends_with?: InputMaybe<Scalars['String']>;
parentHash_not_ends_with_nocase?: InputMaybe<Scalars['String']>;
parentHash_not_in?: InputMaybe<Array<Scalars['String']>>;
parentHash_not_starts_with?: InputMaybe<Scalars['String']>;
parentHash_not_starts_with_nocase?: InputMaybe<Scalars['String']>;
parentHash_starts_with?: InputMaybe<Scalars['String']>;
parentHash_starts_with_nocase?: InputMaybe<Scalars['String']>;
receiptsRoot?: InputMaybe<Scalars['String']>;
receiptsRoot_contains?: InputMaybe<Scalars['String']>;
receiptsRoot_contains_nocase?: InputMaybe<Scalars['String']>;
receiptsRoot_ends_with?: InputMaybe<Scalars['String']>;
receiptsRoot_ends_with_nocase?: InputMaybe<Scalars['String']>;
receiptsRoot_gt?: InputMaybe<Scalars['String']>;
receiptsRoot_gte?: InputMaybe<Scalars['String']>;
receiptsRoot_in?: InputMaybe<Array<Scalars['String']>>;
receiptsRoot_lt?: InputMaybe<Scalars['String']>;
receiptsRoot_lte?: InputMaybe<Scalars['String']>;
receiptsRoot_not?: InputMaybe<Scalars['String']>;
receiptsRoot_not_contains?: InputMaybe<Scalars['String']>;
receiptsRoot_not_contains_nocase?: InputMaybe<Scalars['String']>;
receiptsRoot_not_ends_with?: InputMaybe<Scalars['String']>;
receiptsRoot_not_ends_with_nocase?: InputMaybe<Scalars['String']>;
receiptsRoot_not_in?: InputMaybe<Array<Scalars['String']>>;
receiptsRoot_not_starts_with?: InputMaybe<Scalars['String']>;
receiptsRoot_not_starts_with_nocase?: InputMaybe<Scalars['String']>;
receiptsRoot_starts_with?: InputMaybe<Scalars['String']>;
receiptsRoot_starts_with_nocase?: InputMaybe<Scalars['String']>;
size?: InputMaybe<Scalars['BigInt']>;
size_gt?: InputMaybe<Scalars['BigInt']>;
size_gte?: InputMaybe<Scalars['BigInt']>;
Expand All @@ -166,16 +160,26 @@ export type Block_Filter = {
size_lte?: InputMaybe<Scalars['BigInt']>;
size_not?: InputMaybe<Scalars['BigInt']>;
size_not_in?: InputMaybe<Array<Scalars['BigInt']>>;
stateRoot?: InputMaybe<Scalars['Bytes']>;
stateRoot_contains?: InputMaybe<Scalars['Bytes']>;
stateRoot_gt?: InputMaybe<Scalars['Bytes']>;
stateRoot_gte?: InputMaybe<Scalars['Bytes']>;
stateRoot_in?: InputMaybe<Array<Scalars['Bytes']>>;
stateRoot_lt?: InputMaybe<Scalars['Bytes']>;
stateRoot_lte?: InputMaybe<Scalars['Bytes']>;
stateRoot_not?: InputMaybe<Scalars['Bytes']>;
stateRoot_not_contains?: InputMaybe<Scalars['Bytes']>;
stateRoot_not_in?: InputMaybe<Array<Scalars['Bytes']>>;
stateRoot?: InputMaybe<Scalars['String']>;
stateRoot_contains?: InputMaybe<Scalars['String']>;
stateRoot_contains_nocase?: InputMaybe<Scalars['String']>;
stateRoot_ends_with?: InputMaybe<Scalars['String']>;
stateRoot_ends_with_nocase?: InputMaybe<Scalars['String']>;
stateRoot_gt?: InputMaybe<Scalars['String']>;
stateRoot_gte?: InputMaybe<Scalars['String']>;
stateRoot_in?: InputMaybe<Array<Scalars['String']>>;
stateRoot_lt?: InputMaybe<Scalars['String']>;
stateRoot_lte?: InputMaybe<Scalars['String']>;
stateRoot_not?: InputMaybe<Scalars['String']>;
stateRoot_not_contains?: InputMaybe<Scalars['String']>;
stateRoot_not_contains_nocase?: InputMaybe<Scalars['String']>;
stateRoot_not_ends_with?: InputMaybe<Scalars['String']>;
stateRoot_not_ends_with_nocase?: InputMaybe<Scalars['String']>;
stateRoot_not_in?: InputMaybe<Array<Scalars['String']>>;
stateRoot_not_starts_with?: InputMaybe<Scalars['String']>;
stateRoot_not_starts_with_nocase?: InputMaybe<Scalars['String']>;
stateRoot_starts_with?: InputMaybe<Scalars['String']>;
stateRoot_starts_with_nocase?: InputMaybe<Scalars['String']>;
timestamp?: InputMaybe<Scalars['BigInt']>;
timestamp_gt?: InputMaybe<Scalars['BigInt']>;
timestamp_gte?: InputMaybe<Scalars['BigInt']>;
Expand All @@ -192,26 +196,46 @@ export type Block_Filter = {
totalDifficulty_lte?: InputMaybe<Scalars['BigInt']>;
totalDifficulty_not?: InputMaybe<Scalars['BigInt']>;
totalDifficulty_not_in?: InputMaybe<Array<Scalars['BigInt']>>;
transactionsRoot?: InputMaybe<Scalars['Bytes']>;
transactionsRoot_contains?: InputMaybe<Scalars['Bytes']>;
transactionsRoot_gt?: InputMaybe<Scalars['Bytes']>;
transactionsRoot_gte?: InputMaybe<Scalars['Bytes']>;
transactionsRoot_in?: InputMaybe<Array<Scalars['Bytes']>>;
transactionsRoot_lt?: InputMaybe<Scalars['Bytes']>;
transactionsRoot_lte?: InputMaybe<Scalars['Bytes']>;
transactionsRoot_not?: InputMaybe<Scalars['Bytes']>;
transactionsRoot_not_contains?: InputMaybe<Scalars['Bytes']>;
transactionsRoot_not_in?: InputMaybe<Array<Scalars['Bytes']>>;
unclesHash?: InputMaybe<Scalars['Bytes']>;
unclesHash_contains?: InputMaybe<Scalars['Bytes']>;
unclesHash_gt?: InputMaybe<Scalars['Bytes']>;
unclesHash_gte?: InputMaybe<Scalars['Bytes']>;
unclesHash_in?: InputMaybe<Array<Scalars['Bytes']>>;
unclesHash_lt?: InputMaybe<Scalars['Bytes']>;
unclesHash_lte?: InputMaybe<Scalars['Bytes']>;
unclesHash_not?: InputMaybe<Scalars['Bytes']>;
unclesHash_not_contains?: InputMaybe<Scalars['Bytes']>;
unclesHash_not_in?: InputMaybe<Array<Scalars['Bytes']>>;
transactionsRoot?: InputMaybe<Scalars['String']>;
transactionsRoot_contains?: InputMaybe<Scalars['String']>;
transactionsRoot_contains_nocase?: InputMaybe<Scalars['String']>;
transactionsRoot_ends_with?: InputMaybe<Scalars['String']>;
transactionsRoot_ends_with_nocase?: InputMaybe<Scalars['String']>;
transactionsRoot_gt?: InputMaybe<Scalars['String']>;
transactionsRoot_gte?: InputMaybe<Scalars['String']>;
transactionsRoot_in?: InputMaybe<Array<Scalars['String']>>;
transactionsRoot_lt?: InputMaybe<Scalars['String']>;
transactionsRoot_lte?: InputMaybe<Scalars['String']>;
transactionsRoot_not?: InputMaybe<Scalars['String']>;
transactionsRoot_not_contains?: InputMaybe<Scalars['String']>;
transactionsRoot_not_contains_nocase?: InputMaybe<Scalars['String']>;
transactionsRoot_not_ends_with?: InputMaybe<Scalars['String']>;
transactionsRoot_not_ends_with_nocase?: InputMaybe<Scalars['String']>;
transactionsRoot_not_in?: InputMaybe<Array<Scalars['String']>>;
transactionsRoot_not_starts_with?: InputMaybe<Scalars['String']>;
transactionsRoot_not_starts_with_nocase?: InputMaybe<Scalars['String']>;
transactionsRoot_starts_with?: InputMaybe<Scalars['String']>;
transactionsRoot_starts_with_nocase?: InputMaybe<Scalars['String']>;
unclesHash?: InputMaybe<Scalars['String']>;
unclesHash_contains?: InputMaybe<Scalars['String']>;
unclesHash_contains_nocase?: InputMaybe<Scalars['String']>;
unclesHash_ends_with?: InputMaybe<Scalars['String']>;
unclesHash_ends_with_nocase?: InputMaybe<Scalars['String']>;
unclesHash_gt?: InputMaybe<Scalars['String']>;
unclesHash_gte?: InputMaybe<Scalars['String']>;
unclesHash_in?: InputMaybe<Array<Scalars['String']>>;
unclesHash_lt?: InputMaybe<Scalars['String']>;
unclesHash_lte?: InputMaybe<Scalars['String']>;
unclesHash_not?: InputMaybe<Scalars['String']>;
unclesHash_not_contains?: InputMaybe<Scalars['String']>;
unclesHash_not_contains_nocase?: InputMaybe<Scalars['String']>;
unclesHash_not_ends_with?: InputMaybe<Scalars['String']>;
unclesHash_not_ends_with_nocase?: InputMaybe<Scalars['String']>;
unclesHash_not_in?: InputMaybe<Array<Scalars['String']>>;
unclesHash_not_starts_with?: InputMaybe<Scalars['String']>;
unclesHash_not_starts_with_nocase?: InputMaybe<Scalars['String']>;
unclesHash_starts_with?: InputMaybe<Scalars['String']>;
unclesHash_starts_with_nocase?: InputMaybe<Scalars['String']>;
};

export type Block_Height = {
Expand All @@ -222,11 +246,9 @@ export type Block_Height = {

export enum Block_OrderBy {
Author = 'author',
BaseFeePerGas = 'baseFeePerGas',
Difficulty = 'difficulty',
GasLimit = 'gasLimit',
GasUsed = 'gasUsed',
Hash = 'hash',
Id = 'id',
Number = 'number',
ParentHash = 'parentHash',
Expand Down
Loading

0 comments on commit 6d4c98e

Please sign in to comment.