-
Notifications
You must be signed in to change notification settings - Fork 432
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Feature: add token properties provider (#394)
* token properties provider * separate token fetcher from token properties provider * prettier * revert alpha-router prettier * fix tokenfeedetector factory import * address feedbacks * feedback on cache key format * add cache node unit test * address feedbacks * fix import * allowlist tokens don't call out to token validator provider at all * update with the unit test coverage against token properties provider so far (missing failure modes) * complete the unit test coverages * fix prettier for the new files * token fee fetcher returns when there's either buy fee or sell fee * in token properties provider, double check that only buy fee or sell fee populated should get cached * change token fee detector address to the same one interface uses * prettier
- Loading branch information
Showing
11 changed files
with
720 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
[ | ||
{ | ||
"inputs": [ | ||
{ | ||
"internalType": "address", | ||
"name": "_factoryV2", | ||
"type": "address" | ||
} | ||
], | ||
"stateMutability": "nonpayable", | ||
"type": "constructor" | ||
}, | ||
{ | ||
"inputs": [], | ||
"name": "PairLookupFailed", | ||
"type": "error" | ||
}, | ||
{ | ||
"inputs": [], | ||
"name": "SameToken", | ||
"type": "error" | ||
}, | ||
{ | ||
"inputs": [ | ||
{ | ||
"internalType": "address[]", | ||
"name": "tokens", | ||
"type": "address[]" | ||
}, | ||
{ | ||
"internalType": "address", | ||
"name": "baseToken", | ||
"type": "address" | ||
}, | ||
{ | ||
"internalType": "uint256", | ||
"name": "amountToBorrow", | ||
"type": "uint256" | ||
} | ||
], | ||
"name": "batchValidate", | ||
"outputs": [ | ||
{ | ||
"components": [ | ||
{ | ||
"internalType": "uint256", | ||
"name": "buyFeeBps", | ||
"type": "uint256" | ||
}, | ||
{ | ||
"internalType": "uint256", | ||
"name": "sellFeeBps", | ||
"type": "uint256" | ||
} | ||
], | ||
"internalType": "struct TokenFees[]", | ||
"name": "fotResults", | ||
"type": "tuple[]" | ||
} | ||
], | ||
"stateMutability": "nonpayable", | ||
"type": "function" | ||
}, | ||
{ | ||
"inputs": [ | ||
{ | ||
"internalType": "address", | ||
"name": "", | ||
"type": "address" | ||
}, | ||
{ | ||
"internalType": "uint256", | ||
"name": "amount0", | ||
"type": "uint256" | ||
}, | ||
{ | ||
"internalType": "uint256", | ||
"name": "", | ||
"type": "uint256" | ||
}, | ||
{ | ||
"internalType": "bytes", | ||
"name": "data", | ||
"type": "bytes" | ||
} | ||
], | ||
"name": "uniswapV2Call", | ||
"outputs": [], | ||
"stateMutability": "nonpayable", | ||
"type": "function" | ||
}, | ||
{ | ||
"inputs": [ | ||
{ | ||
"internalType": "address", | ||
"name": "token", | ||
"type": "address" | ||
}, | ||
{ | ||
"internalType": "address", | ||
"name": "baseToken", | ||
"type": "address" | ||
}, | ||
{ | ||
"internalType": "uint256", | ||
"name": "amountToBorrow", | ||
"type": "uint256" | ||
} | ||
], | ||
"name": "validate", | ||
"outputs": [ | ||
{ | ||
"components": [ | ||
{ | ||
"internalType": "uint256", | ||
"name": "buyFeeBps", | ||
"type": "uint256" | ||
}, | ||
{ | ||
"internalType": "uint256", | ||
"name": "sellFeeBps", | ||
"type": "uint256" | ||
} | ||
], | ||
"internalType": "struct TokenFees", | ||
"name": "fotResult", | ||
"type": "tuple" | ||
} | ||
], | ||
"stateMutability": "nonpayable", | ||
"type": "function" | ||
} | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
import { BigNumber } from '@ethersproject/bignumber'; | ||
import { BaseProvider } from '@ethersproject/providers'; | ||
import { ChainId } from '@uniswap/sdk-core'; | ||
|
||
import { TokenFeeDetector__factory } from '../types/other/factories/TokenFeeDetector__factory'; | ||
import { TokenFeeDetector } from '../types/other/TokenFeeDetector'; | ||
import { log, WRAPPED_NATIVE_CURRENCY } from '../util'; | ||
|
||
import { ProviderConfig } from './provider'; | ||
|
||
const DEFAULT_TOKEN_BUY_FEE_BPS = BigNumber.from(0); | ||
const DEFAULT_TOKEN_SELL_FEE_BPS = BigNumber.from(0); | ||
|
||
// on detector failure, assume no fee | ||
export const DEFAULT_TOKEN_FEE_RESULT = { | ||
buyFeeBps: DEFAULT_TOKEN_BUY_FEE_BPS, | ||
sellFeeBps: DEFAULT_TOKEN_SELL_FEE_BPS, | ||
}; | ||
|
||
type Address = string; | ||
|
||
export type TokenFeeResult = { | ||
buyFeeBps?: BigNumber; | ||
sellFeeBps?: BigNumber; | ||
}; | ||
export type TokenFeeMap = Record<Address, TokenFeeResult>; | ||
|
||
// address at which the FeeDetector lens is deployed | ||
const FEE_DETECTOR_ADDRESS = (chainId: ChainId) => { | ||
switch (chainId) { | ||
case ChainId.MAINNET: | ||
default: | ||
return '0x19C97dc2a25845C7f9d1d519c8C2d4809c58b43f'; | ||
} | ||
}; | ||
|
||
// 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'; | ||
// 1M gas limit per validate call, should cover most swap cases | ||
const GAS_LIMIT_PER_VALIDATE = 1_000_000; | ||
|
||
export interface ITokenFeeFetcher { | ||
fetchFees( | ||
addresses: Address[], | ||
providerConfig?: ProviderConfig | ||
): Promise<TokenFeeMap>; | ||
} | ||
|
||
export class OnChainTokenFeeFetcher implements ITokenFeeFetcher { | ||
private BASE_TOKEN: string; | ||
private readonly contract: TokenFeeDetector; | ||
|
||
constructor( | ||
private chainId: ChainId, | ||
rpcProvider: BaseProvider, | ||
private tokenFeeAddress = FEE_DETECTOR_ADDRESS(chainId), | ||
private gasLimitPerCall = GAS_LIMIT_PER_VALIDATE, | ||
private amountToFlashBorrow = AMOUNT_TO_FLASH_BORROW | ||
) { | ||
this.BASE_TOKEN = WRAPPED_NATIVE_CURRENCY[this.chainId]?.address; | ||
this.contract = TokenFeeDetector__factory.connect( | ||
this.tokenFeeAddress, | ||
rpcProvider | ||
); | ||
} | ||
|
||
public async fetchFees( | ||
addresses: Address[], | ||
providerConfig?: ProviderConfig | ||
): Promise<TokenFeeMap> { | ||
const tokenToResult: TokenFeeMap = {}; | ||
|
||
const functionParams = addresses.map((address) => [ | ||
address, | ||
this.BASE_TOKEN, | ||
this.amountToFlashBorrow, | ||
]) as [string, string, string][]; | ||
|
||
const results = await Promise.all( | ||
functionParams.map(async ([address, baseToken, amountToBorrow]) => { | ||
try { | ||
// We use the validate function instead of batchValidate to avoid poison pill problem. | ||
// One token that consumes too much gas could cause the entire batch to fail. | ||
const feeResult = await this.contract.callStatic.validate( | ||
address, | ||
baseToken, | ||
amountToBorrow, | ||
{ | ||
gasLimit: this.gasLimitPerCall, | ||
blockTag: providerConfig?.blockNumber, | ||
} | ||
); | ||
return { address, ...feeResult }; | ||
} catch (err) { | ||
log.error( | ||
{ err }, | ||
`Error calling validate on-chain for token ${address}` | ||
); | ||
// in case of FOT token fee fetch failure, we return null | ||
// so that they won't get returned from the token-fee-fetcher | ||
// and thus no fee will be applied, and the cache won't cache on FOT tokens with failed fee fetching | ||
return { address, buyFeeBps: undefined, sellFeeBps: undefined }; | ||
} | ||
}) | ||
); | ||
|
||
results.forEach(({ address, buyFeeBps, sellFeeBps }) => { | ||
if (buyFeeBps || sellFeeBps) { | ||
tokenToResult[address] = { buyFeeBps, sellFeeBps }; | ||
} | ||
}); | ||
|
||
return tokenToResult; | ||
} | ||
} |
Oops, something went wrong.