Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: V2 routes quote with fee-on-transfer fees off the quote amount #395

Merged
merged 36 commits into from
Sep 12, 2023
Merged
Show file tree
Hide file tree
Changes from 33 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
e282c91
sor quote with fot fees
jsy1218 Sep 7, 2023
1981c2f
prettier
jsy1218 Sep 7, 2023
d60a6b0
remove comments in quote provider and alpha router pass in enable FOT…
jsy1218 Sep 8, 2023
bd20685
remove comments in quote provider and alpha router pass in enable FOT…
jsy1218 Sep 8, 2023
6477e92
address feedback
jsy1218 Sep 8, 2023
bc5a376
flatten instead of flatMap
jsy1218 Sep 8, 2023
fb8436c
fix integ-test after making tokenPropertiesProvider a required proper…
jsy1218 Sep 8, 2023
0ef3fb8
fix token properties provider unit test
jsy1218 Sep 8, 2023
93b75d2
update sdk-core to use Token with fot tax fields
jsy1218 Sep 8, 2023
6b413c7
fix CLI command to include FOT fee fetching
jsy1218 Sep 8, 2023
e0beaa2
fix cli for passing in enableFOT flag
jsy1218 Sep 8, 2023
f3fd809
pass enable FOT flag to v2 subgraph candidate pools loading
jsy1218 Sep 8, 2023
a5ca952
make tokenValidatorProvider before tokenPropertiesProvider due to dep…
jsy1218 Sep 8, 2023
506bdb7
fix v2 quote provider to get the fot tax from the pool objects from s…
jsy1218 Sep 8, 2023
27a94a6
populate enable fot tax everywhere to v2 pool provider
jsy1218 Sep 9, 2023
5587a4e
fix quote provider copy paste error
jsy1218 Sep 9, 2023
48fa051
get rid of explicit enableFeeOnTransferFeeFetching passing
jsy1218 Sep 9, 2023
85c5051
Revert "fix quote provider copy paste error"
jsy1218 Sep 9, 2023
21a2b44
actual quote provider copy paste error fix
jsy1218 Sep 9, 2023
3373902
replace tokenIn and tokenOut with fox tax one after getting the candi…
jsy1218 Sep 11, 2023
cca826d
amount distribution also adding fot tax
jsy1218 Sep 11, 2023
5bfef60
remove quote provider changes
jsy1218 Sep 11, 2023
4d43634
v2 get routes from chain and cache use pools to re instantiate tokens…
jsy1218 Sep 11, 2023
d3f7d53
get amount distribution add back black line
jsy1218 Sep 11, 2023
884936c
cached routes get pools for matched tokenIn and tokenOut only once
jsy1218 Sep 11, 2023
b4ff5e7
compute all v2 routes no longer need to get token with fot tax again
jsy1218 Sep 11, 2023
c7f60b5
fix quote cli exact out to pass in debugRouting and enableFeeOnTransf…
jsy1218 Sep 11, 2023
a508f9e
fix the input currency amount to not mutate during the swap methods
jsy1218 Sep 11, 2023
96b391e
use currency for getSwapRouteFromCache v2 amountWithFotTax
jsy1218 Sep 11, 2023
a70caeb
amount to flash borror 10x for rebase tokens e.g. stETH
jsy1218 Sep 11, 2023
cf6a01d
bump v2-sdk version
jsy1218 Sep 11, 2023
552d87c
extract matched pools to for tokens to util functions
jsy1218 Sep 11, 2023
bdb99f3
@mikeki offline feedback on no need to filter pools on getSwapRouteFr…
jsy1218 Sep 11, 2023
70cf680
3.16.22
jsy1218 Sep 11, 2023
39994ab
fix cached routes side of bugs that caused fot quote to not work prop…
jsy1218 Sep 12, 2023
3049dc7
remove custom pool reserve matching token logics
jsy1218 Sep 12, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion cli/base-command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,17 @@ import {
setGlobalMetric,
SimulationStatus,
TenderlySimulator,
TokenPropertiesProvider,
TokenProvider,
TokenValidatorProvider,
UniswapMulticallProvider,
V2PoolProvider,
V3PoolProvider,
V3RouteWithValidQuote,
} from '../src';
import { LegacyGasPriceProvider } from '../src/providers/legacy-gas-price-provider';
import { OnChainGasPriceProvider } from '../src/providers/on-chain-gas-price-provider';
import { OnChainTokenFeeFetcher } from '../src/providers/token-fee-fetcher';

export abstract class BaseCommand extends Command {
static flags = {
Expand Down Expand Up @@ -284,7 +287,22 @@ export abstract class BaseCommand extends Command {
new V3PoolProvider(chainId, multicall2Provider),
new NodeJSCache(new NodeCache({ stdTTL: 360, useClones: false }))
);
const v2PoolProvider = new V2PoolProvider(chainId, multicall2Provider);
const tokenValidatorProvider = new TokenValidatorProvider(
chainId,
multicall2Provider,
new NodeJSCache(new NodeCache({ stdTTL: 360, useClones: false }))
)
const tokenFeeFetcher = new OnChainTokenFeeFetcher(
chainId,
provider
)
const tokenPropertiesProvider = new TokenPropertiesProvider(
chainId,
tokenValidatorProvider,
new NodeJSCache(new NodeCache({ stdTTL: 360, useClones: false })),
tokenFeeFetcher
)
const v2PoolProvider = new V2PoolProvider(chainId, multicall2Provider, tokenPropertiesProvider);

const tenderlySimulator = new TenderlySimulator(
chainId,
Expand Down
8 changes: 8 additions & 0 deletions cli/commands/quote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ export class Quote extends BaseCommand {
default: false,
}),
simulate: flags.boolean({ required: false, default: false }),
debugRouting: flags.boolean({ required: false, default: true }),
enableFeeOnTransferFeeFetching: flags.boolean({ required: false, default: true }),
};

async run() {
Expand Down Expand Up @@ -63,6 +65,8 @@ export class Quote extends BaseCommand {
forceCrossProtocol,
forceMixedRoutes,
simulate,
debugRouting,
enableFeeOnTransferFeeFetching
} = flags;

const topNSecondHopForTokenAddress = new MapWithLowerCaseKey();
Expand Down Expand Up @@ -151,6 +155,8 @@ export class Quote extends BaseCommand {
protocols,
forceCrossProtocol,
forceMixedRoutes,
debugRouting,
enableFeeOnTransferFeeFetching,
}
);
} else {
Expand Down Expand Up @@ -186,6 +192,8 @@ export class Quote extends BaseCommand {
protocols,
forceCrossProtocol,
forceMixedRoutes,
debugRouting,
enableFeeOnTransferFeeFetching,
}
);
}
Expand Down
32 changes: 16 additions & 16 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@
"@uniswap/token-lists": "^1.0.0-beta.31",
"@uniswap/universal-router": "^1.0.1",
"@uniswap/universal-router-sdk": "^1.5.7",
"@uniswap/v2-sdk": "^3.2.0",
"@uniswap/v2-sdk": "^3.2.1",
"@uniswap/v3-sdk": "^3.10.0",
"@uniswap/sdk-core": "^4.0.6",
"@uniswap/sdk-core": "^4.0.7",
"async-retry": "^1.3.1",
"await-timeout": "^1.1.1",
"axios": "^0.21.1",
Expand Down
4 changes: 4 additions & 0 deletions src/providers/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ export type ProviderConfig = {
* Debug flag to test some codepaths
*/
debugRouting?: boolean;
/**
* Flag for token properties provider to enable fetching fee-on-transfer tokens.
*/
enableFeeOnTransferFeeFetching?: boolean;
jsy1218 marked this conversation as resolved.
Show resolved Hide resolved
};

export type LocalCacheEntry<T> = {
Expand Down
5 changes: 3 additions & 2 deletions src/providers/token-fee-fetcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,9 @@ const FEE_DETECTOR_ADDRESS = (chainId: ChainId) => {

// Amount has to be big enough to avoid rounding errors, but small enough that
// most v2 pools will have at least this many token units
// 10000 is the smallest number that avoids rounding errors in bps terms
const AMOUNT_TO_FLASH_BORROW = '10000';
// 100000 is the smallest number that avoids rounding errors in bps terms
// 10000 was not sufficient due to rounding errors for rebase token (e.g. stETH)
const AMOUNT_TO_FLASH_BORROW = '100000';
// 1M gas limit per validate call, should cover most swap cases
const GAS_LIMIT_PER_VALIDATE = 1_000_000;

Expand Down
9 changes: 7 additions & 2 deletions src/providers/token-properties-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,19 @@ export class TokenPropertiesProvider implements ITokenPropertiesProvider {
private tokenValidatorProvider: ITokenValidatorProvider,
private tokenPropertiesCache: ICache<TokenPropertiesResult>,
private tokenFeeFetcher: ITokenFeeFetcher,
private allowList = DEFAULT_ALLOWLIST
private allowList = DEFAULT_ALLOWLIST,
) {}

public async getTokensProperties(
tokens: Token[],
providerConfig?: ProviderConfig
): Promise<TokenPropertiesMap> {
const tokenToResult: TokenPropertiesMap = {};

if (!providerConfig?.enableFeeOnTransferFeeFetching || this.chainId !== ChainId.MAINNET) {
return tokenToResult;
}

const nonAllowlistTokens = tokens.filter(
(token) => !this.allowList.has(token.address.toLowerCase())
);
Expand All @@ -57,7 +63,6 @@ export class TokenPropertiesProvider implements ITokenPropertiesProvider {
nonAllowlistTokens,
providerConfig
);
const tokenToResult: TokenPropertiesMap = {};

tokens.forEach((token) => {
if (this.allowList.has(token.address.toLowerCase())) {
Expand Down
84 changes: 74 additions & 10 deletions src/providers/v2/pool-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,20 @@ import retry, { Options as RetryOptions } from 'async-retry';
import _ from 'lodash';

import { IUniswapV2Pair__factory } from '../../types/v2/factories/IUniswapV2Pair__factory';
import { CurrencyAmount, ID_TO_NETWORK_NAME, metric, MetricLoggerUnit } from '../../util';
import {
CurrencyAmount,
ID_TO_NETWORK_NAME,
metric,
MetricLoggerUnit,
} from '../../util';
import { log } from '../../util/log';
import { poolToString } from '../../util/routes';
import { IMulticallProvider, Result } from '../multicall-provider';
import { ProviderConfig } from '../provider';
import {
ITokenPropertiesProvider,
} from '../token-properties-provider';
import { TokenValidationResult } from '../token-validator-provider';

type IReserves = {
reserve0: BigNumber;
Expand Down Expand Up @@ -66,18 +75,19 @@ export class V2PoolProvider implements IV2PoolProvider {
* Creates an instance of V2PoolProvider.
* @param chainId The chain id to use.
* @param multicall2Provider The multicall provider to use to get the pools.
* @param tokenPropertiesProvider The token properties provider to use to get token properties.
* @param retryOptions The retry options for each call to the multicall.
*/
constructor(
protected chainId: ChainId,
protected multicall2Provider: IMulticallProvider,
protected tokenPropertiesProvider: ITokenPropertiesProvider,
protected retryOptions: V2PoolRetryOptions = {
retries: 2,
minTimeout: 50,
maxTimeout: 500,
}
) {
}
) {}

public async getPools(
tokenPairs: [Token, Token][],
Expand Down Expand Up @@ -109,18 +119,28 @@ export class V2PoolProvider implements IV2PoolProvider {
);

metric.putMetric('V2_RPC_POOL_RPC_CALL', 1, MetricLoggerUnit.None);
metric.putMetric('V2GetReservesBatchSize', sortedPoolAddresses.length, MetricLoggerUnit.Count);
metric.putMetric(
'V2GetReservesBatchSize',
sortedPoolAddresses.length,
MetricLoggerUnit.Count
);
metric.putMetric(
`V2GetReservesBatchSize_${ID_TO_NETWORK_NAME(this.chainId)}`,
sortedPoolAddresses.length,
MetricLoggerUnit.Count
);

const reservesResults = await this.getPoolsData<IReserves>(
sortedPoolAddresses,
'getReserves',
providerConfig
);
const [reservesResults, tokenPropertiesMap] = await Promise.all([
this.getPoolsData<IReserves>(
sortedPoolAddresses,
'getReserves',
providerConfig
),
this.tokenPropertiesProvider.getTokensProperties(
this.flatten(tokenPairs),
providerConfig
),
]);

log.info(
`Got reserves for ${poolAddressSet.size} pools ${
Expand All @@ -144,7 +164,39 @@ export class V2PoolProvider implements IV2PoolProvider {
continue;
}

const [token0, token1] = sortedTokenPairs[i]!;
let [token0, token1] = sortedTokenPairs[i]!;
if (
jsy1218 marked this conversation as resolved.
Show resolved Hide resolved
tokenPropertiesMap[token0.address.toLowerCase()]
?.tokenValidationResult === TokenValidationResult.FOT
) {
token0 = new Token(
token0.chainId,
token0.address,
token0.decimals,
token0.symbol,
token0.name,
true, // at this point we know it's valid token address
tokenPropertiesMap[token0.address.toLowerCase()]?.tokenFeeResult?.buyFeeBps,
tokenPropertiesMap[token0.address.toLowerCase()]?.tokenFeeResult?.sellFeeBps
);
}

if (
tokenPropertiesMap[token1.address.toLowerCase()]
?.tokenValidationResult === TokenValidationResult.FOT
) {
token1 = new Token(
token1.chainId,
token1.address,
token1.decimals,
token1.symbol,
token1.name,
true, // at this point we know it's valid token address
tokenPropertiesMap[token1.address.toLowerCase()]?.tokenFeeResult?.buyFeeBps,
tokenPropertiesMap[token1.address.toLowerCase()]?.tokenFeeResult?.sellFeeBps
);
}

const { reserve0, reserve1 } = reservesResult.result;

const pool = new Pair(
Expand Down Expand Up @@ -228,4 +280,16 @@ export class V2PoolProvider implements IV2PoolProvider {

return results;
}

// We are using ES2017. ES2019 has native flatMap support
private flatten(tokenPairs: Array<[Token, Token]>): Token[] {
const tokens = new Array<Token>();

for (const [tokenA, tokenB] of tokenPairs) {
tokens.push(tokenA);
tokens.push(tokenB);
}

return tokens;
}
}
Loading
Loading