"""
This module contains utilities for sense checking prices returned from the library.
A user will not need to use anything here.
Developers can skip the sense check for additional tokens by manually adding them to the
:obj:`ACCEPTABLE_HIGH_PRICES` mapping in the source code. This involves directly editing
the mapping to include the new token addresses under the appropriate network. Note that
this requires modifying the source code, which may not be ideal for all users.
"""
import logging
from typing import Optional
import a_sync
from y.classes.common import ERC20
from y.constants import CHAINID, wbtc, weth
from y.contracts import Contract
from y.exceptions import NonStandardERC20
from y.networks import Network
from y.prices.lending.aave import aave
from y.prices.lending.compound import CToken
from y.prices.stable_swap.curve import CurvePool
from y.prices.utils import check_bucket
from y.prices.yearn import YearnInspiredVault
logger = logging.getLogger(__name__)
# This module is far from perfect, but provides an acceptable way to validate some of the prices returned by `get_price`
acceptable_all_chains = [
weth.address,
wbtc.address,
]
ACCEPTABLE_HIGH_PRICES = {
Network.Mainnet: [
# eth and eth-like
"0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", # eth
"0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0", # wsteth
"0xA3D87FffcE63B53E0d54fAa1cc983B7eB0b74A9c", # ecrv
"0xaA17A236F2bAdc98DDc0Cf999AbB47D47Fc0A6Cf", # ankrcrv
"0x53a901d48795C58f485cBB38df08FA96a24669D5", # rcrv
"0xC4C319E2D4d66CcA4464C0c2B32c9Bd23ebe784e", # alETH+ETH-f
"0x06325440D014e39736583c165C2963BA99fAf14E", # stecrv
"0x5e74C9036fb86BD7eCdcb084a0673EFc32eA31cb", # sETH
"0xae78736Cd615f374D3085123A210448E74Fc6393", # rETH
"0xE95A203B1a91a908F9B9CE46459d101078c2c3cb", # aETHc
"0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84", # stETH
"0x9559Aaa82d9649C7A7b220E7c461d2E74c9a3593", # rETH
"0x836A808d4828586A69364065A1e064609F5078c7", # pETH
"0xc3D088842DcF02C13699F936BB83DFBBc6f721Ab", # vETH
"0xC1330aCBbcE97cb9695B7ee161c0F95B875a8b0F", # onETH
"0x5E8422345238F34275888049021821E8E08CAa1f", # fxsETH
"0x856c4Efb76C1D1AE02e20CEB03A2A6a08b0b8dC3", # OETH
"0xE72B141DF173b999AE7c1aDcbF60Cc9833Ce56a8", # ETH+
"0x3d1E5Cf16077F349e999d6b21A4f646e83Cd90c5", # dETH
"0x0100546F2cD4C9D97f798fFC9755E47865FF7Ee6", # alETH
"0xBe9895146f7AF43049ca1c1AE358B0541Ea49704", # cbETH
"0x7C07F7aBe10CE8e33DC6C5aD68FE033085256A84", # icETH
"0xA35b1B31Ce002FBF2058D22F30f95D405200A15b", # ETHx
"0x64351fC9810aDAd17A690E4e1717Df5e7e085160", # msETH
"0x821A278dFff762c76410264303F25bF42e195C0C", # pETH
"0xa2E3356610840701BDf5611a53974510Ae27E2e1", # wbETH
"0x1BED97CBC3c24A4fb5C069C6E311a967386131f7", # yETH
"0x3A65cbaebBFecbeA5D0CB523ab56fDbda7fF9aAA", # ZUNFRXETH
"0x6951bDC4734b9f7F3E1B74afeBC670c736A0EDB6", # pxsteth
"0xf1C9acDc66974dFB6dEcB12aA385b9cD01190E38", # osETH
"0xCd5fE23C85820F7B72D0926FC9b05b43E359b7ee", # weETH
"0x04C154b66CB340F3Ae24111CC767e0184Ed00Cc6", # pxETH
"0x005F893EcD7bF9667195642f7649DA8163e23658", # dgnETH
"0xc2e660C62F72c2ad35AcE6DB78a616215E2F2222", # zunETH
"0xA1290d69c65A6Fe4DF752f95823fae25cB99e5A7", # rsETH
"0x09db87A538BD693E9d08544577d5cCfAA6373A48", # ynETH
# btc and btc-like
"0xEB4C2781e4ebA804CE9a9803C67d0893436bB27D", # renbtc
"0xfE18be6b3Bd88A2D2A7f928d00292E7a9963CfC6", # sbtc
"0x49849C98ae39Fff122806C06791Fa73784FB3675", # crvrenwbtc
"0x075b1bb99792c9E1041bA13afEf80C91a1e70fB3", # crvrenwsbtc
"0xb19059ebb43466C323583928285a49f558E572Fd", # hcrv
"0x64eda51d3Ad40D56b9dFc5554E06F94e1Dd786Fd", # tbtc/sbtcCrv
"0xDE5331AC4B3630f94853Ff322B66407e0D6331E8", # pBTC/sbtcCRV
"0x410e3E86ef427e30B9235497143881f717d93c2A", # bBTC/sbtcCRV
"0x2fE94ea3d5d4a175184081439753DE15AeF9d614", # oBTC/sbtcCRV
"0x0327112423F3A68efdF1fcF402F6c5CB9f7C33fd", # btc++
"0x3212b29E33587A00FB1C83346f5dBFA69A458923", # imbtc
"0x5228a22e72ccC52d415EcFd199F99D0665E7733b", # pbtc
"0xFbdCA68601f835b27790D98bbb8eC7f05FDEaA9B", # ibbtc/sbtcCRV-f
"0x0316EB71485b0Ab14103307bf65a021042c6d380", # hbtc
"0x9BE89D2a4cd102D8Fecc6BF9dA793be995C22541", # bbtc
"0x8064d9Ae6cDf087b1bcd5BDf3531bD5d8C537a68", # obtc
"0x8dAEBADE922dF735c38C80C7eBD708Af50815fAa", # tbtc
"0x8751D4196027d4e6DA63716fA7786B5174F04C15", # wibbtc
"0x66eFF5221ca926636224650Fd3B9c497FF828F7D", # multiBTC
"0x18084fbA666a33d37592fA2633fD49a74DD93a88", # tbtc
"0x661c70333AA1850CcDBAe82776Bb436A0fCfeEfB", # EBTC
"0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf", # cbBTC
# gold tokens
"0x45804880De22913dAFE09f4980848ECE6EcbAf78", # paxg
"0x4922a015c4407F87432B179bb209e125432E4a2A", # xaut
# other
"0x0bc529c00C6401aEF6D220BE8C6Ea1667F6Ad93e", # yfi
"0xFf71841EeFca78a64421db28060855036765c248", # coveYFI
"0xa3f152837492340dAAf201F4dFeC6cD73A8a9760", # COVEYFI
"0x41252E8691e964f7DE35156B68493bAb6797a275", # dYFI
"0x1cEB5cB57C4D4E2b2433641b95Dd330A33185A44", # kp3r
"0xD5525D397898e5502075Ea5E830d8914f6F0affe", # meme
"0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2", # mkr
"0x3A283D9c08E8b55966afb64C515f5143cf907611", # crvCVXETH
"0x23B608675a2B2fB1890d3ABBd85c5775c51691d5", # socks
"0xcA3d75aC011BF5aD07a98d02f18225F9bD9A6BDF", # crvtricrypto
"0xc4AD29ba4B3c580e6D59105FFf484999997675Ff", # crv3crypto
"0xd075e95423C5c4BA1E122CaE0f4CdFA19b82881b", # wPE
"0xe9F84dE264E91529aF07Fa2C746e934397810334", # sak3
"0xa1d0E215a23d7030842FC67cE582a6aFa3CCaB83", # yfii
"0x0fe629d1E84E171f8fF0C1Ded2Cc2221Caa48a3f", # mask
"0x5F0E628B693018f639D10e4A4F59BD4d8B2B6B44", # white
"0xdBdb4d16EdA451D0503b854CF79D55697F90c8DF", # alcx
"0x3aaDA3e213aBf8529606924d8D1c55CbDc70Bf74", # xMON
"0x892A6f9dF0147e5f079b0993F486F9acA3c87881", # xFUND
"0x0ab87046fBb341D058F17CBC4c1133F25a20a52f", # gOHM
"0x97983236bE88107Cc8998733Ef73D8d969c52E37", # sdYFI
"0x68749665FF8D2d112Fa859AA293F07A622782F38", # XAUt
"0xf5f5B97624542D72A9E06f04804Bf81baA15e2B4", # crvUSDTWBTCWETH
"0x7F86Bf177Dd4F3494b841a37e810A34dD56c829B", # crvUSDCWBTCWETH
"0x2889302a794dA87fBF1D6Db415C1492194663D13", # crvCRVUSDTBTCWSTETH
"0xBfAb6FA95E0091ed66058ad493189D2cB29385E6", # ETHwBETHCRV
# nfts
"0x641927E970222B10b2E8CDBC96b1B4F427316f16", # meeb
"0x9cea2eD9e47059260C97d697f82b8A14EfA61EA5", # punk
"0x269616D549D7e8Eaa82DFb17028d0B212D11232A", # punk (different)
"0x69BbE2FA02b4D90A944fF328663667DC32786385", # punk-basic
"0xD70240Dd62F4ea9a6A2416e0073D72139489d2AA", # glyph
"0x114f1388fAB456c4bA31B1850b244Eedcd024136", # coolcats
"0xEA47B64e1BFCCb773A0420247C0aa0a3C1D2E5C5", # bayc
],
Network.BinanceSmartChain: [
# eth and eth-like
"0x8d0e18c97e5dd8ee2b539ae8cd3a3654df5d79e5", # bweth
"0xbfF4a34A4644a113E8200D7F1D79b3555f723AfE", # ibeth
# btc and btc-like
"0x7130d2A12B9BCbFAe4f2634d864A1Ee1Ce3Ead9c", # btcb
"0x08FC9Ba2cAc74742177e0afC3dC8Aed6961c24e7", # ibbtcb
],
Network.Fantom: [
# eth and eth-like
"0xBDC8fd437C489Ca3c6DA3B5a336D11532a532303", # anyeth
# btc and btc-like
"0xDBf31dF14B66535aF65AaC99C32e9eA844e14501", # renbtc
"0x2406dCe4dA5aB125A18295f4fB9FD36a0f7879A2", # anybtc
# other
"0x29b0Da86e484E1C0029B56e817912d778aC0EC69", # yfi
"0xf43Cc235E686d7BC513F53Fbffb61F760c3a1882", # elite
"0x58e57cA18B7A47112b877E31929798Cd3D703b0f", # crv3crypto
],
Network.Avalanche: [
# btc and btc-like
"0xDBf31dF14B66535aF65AaC99C32e9eA844e14501", # renbtc
# other
"0xd6070ae98b8069de6B494332d1A1a81B6179D960", # bifi
],
Network.Optimism: [
# eth and eth-like
"0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", # eth
"0x9Bcef72be871e61ED4fBbc7630889beE758eb81D", # reth
"0x1F32b1c2345538c0c6f582fCB022739c4A194Ebb", # wsteth
"0x6806411765Af15Bddd26f8f544A34cC40cb9838B", # frxeth
"0x484c2D6e3cDd945a8B2DF735e079178C1036578c", # sfrxeth
"0x1610e3c85dd44Af31eD7f33a63642012Dca0C5A5", # mseth
"0x3E29D3A9316dAB217754d13b28646B76607c5f04", # aleth
"0xE405de8F52ba7559f9df3C368500B6E6ae6Cee49", # seth
# btc and btc-like
"0x298B9B95708152ff6968aafd889c6586e9169f1D", # sbtc
"0x6c84a8f1c29108F47a79964b5Fe888D4f4D0dE40", # tbtc
],
Network.Arbitrum: [
"0x8e0B8c8BB9db49a46697F3a5Bb8A308e744821D2", # crv3crypto
],
Network.Base: [
# eth and eth-like
"0x2Ae3F1Ec7F1F5012CFEab0185bfc7aa3cf0DEc22", # cbeth
"0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452", # wsteth
"0x1f55a02A049033E3419a8E2975cF3F572F4e6E9A", # sfrxETH
"0xCb327b99fF831bF8223cCEd12B1338FF3aA322Ff", # bsdETH
# btc and btc-like
"0x236aa50979D5f3De3Bd1Eeb40E81137F22ab794b", # tbtc
"0xcb327b99ff831bf8223cced12b1338ff3aa322ff", # bsdETH
],
}.get(CHAINID, []) + acceptable_all_chains
"""
List of tokens addresses for which high prices are acceptable.
Nothing will be logged for tokens in this list.
"""
[docs]
async def sense_check(token_address: str, block: Optional[int], price: float):
"""
Performs a sense check on the given token price and logs a warning if it is unexpectedly high.
This is just to help catch potential bugs before they cause issues.
Args:
token_address: The address of the token.
block: The optional block number to use for the error message.
price: The price to check.
Example:
>>> await sense_check("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", 14_000_000, 123450000.00)
unusually high price ($123450000.00) returned for USDC "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" on Mainnet block 14000000. This does not necessarily mean that the price is wrong, but you may want to validate the price for yourself before proceeding.
Note:
This function checks if the price is within acceptable ranges based on the token type and network.
It will not log for various special cases such as ETH-like tokens, BTC-like tokens, LP tokens,
and vault tokens where the underlying is exempt from the sense check.
See Also:
:func:`_exit_sense_check` for details on tokens that are exempt from the sense check.
"""
# if price is in "normal" range, exit sense check
if price < 1000:
return None
# if we've already validated that the token should have a high price, exit sense check
if token_address in ACCEPTABLE_HIGH_PRICES:
return None
# for some token types, its normal to have a crazy high nominal price
# we can skip the sense check for those
if await _exit_sense_check(token_address):
return None
# proceed with sense check
price_readable = round(price, 4)
network = Network.name(CHAINID)
try:
symbol = await ERC20(token_address, asynchronous=True).symbol
logger.warning(
f"unusually high price (${price_readable}) returned for {symbol} {token_address} on {network} block {block}. This does not necessarily mean that the price is wrong, but you may want to validate the price for yourself before proceeding."
)
except NonStandardERC20:
logger.warning(
f"unusually high price (${price_readable}) returned for {token_address} on {network} block {block}. This does not necessarily mean that the price is wrong, but you may want to validate the price for yourself before proceeding."
)
# yLazyLogger(logger)
async def _exit_sense_check(token_address: str) -> bool:
"""
For some token types, its normal to have a crazy high nominal price.
We can skip the sense check for those.
We can also skip wrapped versions of tokens in `ACCEPTABLE_HIGH_PRICES`.
Args:
token_address: The address of the token to check.
Returns:
True if the token is exempt from the sense check, False otherwise.
Example:
>>> await _exit_sense_check("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48")
False
See Also:
:data:`ACCEPTABLE_HIGH_PRICES` for the list of tokens that are exempt from the sense check.
"""
bucket = await check_bucket(token_address, sync=False)
if bucket in ("uni or uni-like lp", "balancer pool"):
return True
elif bucket == "curve lp":
underlyings = await CurvePool(token_address, asynchronous=True).coins
if questionable_underlyings := [
und for und in underlyings if und not in ACCEPTABLE_HIGH_PRICES
]:
return await a_sync.map(_exit_sense_check, questionable_underlyings).all(
sync=False
)
return True
elif bucket == "atoken":
underlying = await aave.underlying(token_address, sync=False)
elif bucket == "compound":
underlying = await CToken(token_address, asynchronous=True).underlying
elif bucket == "solidex":
contract = await Contract.coroutine(token_address)
underlying = await contract.pool
elif bucket == "yearn or yearn-like":
underlying = await YearnInspiredVault(
token_address, asynchronous=True
).underlying
else:
return False
return underlying in ACCEPTABLE_HIGH_PRICES or await _exit_sense_check(underlying)