web3.contract.functions.getAmount() 中的差异

问题描述

我正在使用 Web3.py,但遇到了一些奇怪的事情。

对于以下代码(使用 Pancake Router V2):

from web3 import Web3
from web3.middleware import geth_poa_middleware

web3 = Web3(Web3.HTTPProvider('https://bsc-dataseed1.binance.org:443'))
web3.middleware_onion.inject(geth_poa_middleware,layer=0)

ABI = {"inputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"}],"name":"getAmountsOut","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"view","type":"function"}

CAKE_ROUTER_V2 = web3.tochecksumAddress('0x10ed43c718714eb63d5aa57b78b54704e256024e')
router_contract = web3.eth.contract(address=CAKE_ROUTER_V2,abi=ABI),WBNB = web3.tochecksumAddress('0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c')
CAKE = web3.tochecksumAddress('0x0e09fabb73bd3ade0a17ecc321fd13a19e81ce82')
KONGSHIBA = web3.tochecksumAddress('0x126f5f2a88451d24544f79d11f869116351d46e1')

print(router_contract.functions.getAmountsOut(1,[WBNB,CAKE]).call())
print(router_contract.functions.getAmountsOut(1,KONGSHIBA]).call())

我得到以下信息:

[1,19]
[1,160]

WBNB 和 CAKE 有 18 位小数,KONGSHIBA 有 17 位。
CAKE 目前的价值约为 27.7 美元,WBNB 为 545.41291093 美元
而 KONGSHIBA 为 0.00000000000000000332 美元。
所以我应该回来:

[1,16000000000000000000]

请指教。

解决方法

经过艰苦的努力,我找到了正确的方法:)

计算代币价格的正确方法是询问流动性池(此代币与本地 PEG 或某些美元代币的对)再次插入 PEG 的数量与代币的比例(有关更多详细信息流动资金池代表什么,请参阅https://uniswap.org/docs/v2/core-concepts/pools/)。

所以对于python使用:

from web3 import Web3
from web3.middleware import geth_poa_middleware # Needed for Binance

from json import loads
from decimal import Decimal


ETHER = 10 ** 18

WBNB = '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c'
CAKE_ROUTER_V2 = web3.toChecksumAddress('0x10ed43c718714eb63d5aa57b78b54704e256024e')

web3 = Web3(Web3.HTTPProvider('https://bsc-dataseed1.binance.org:443'))
web3.middleware_onion.inject(geth_poa_middleware,layer=0) # Again,this is needed for Binance,not Ethirium

ABI = loads('[{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"token0","outputs":[{"internalType":"address","type":"address"}],"payable":false,{"inputs":[],"name":"factory","inputs":[{"internalType":"address","type":"address"},{"internalType":"address","name":"getPair","name":"getReserves","outputs":[{"internalType":"uint112","name":"_reserve0","type":"uint112"},{"internalType":"uint112","name":"_reserve1",{"internalType":"uint32","name":"_blockTimestampLast","type":"uint32"}],"type":"function"}]')

def get_price(token,decimals,pair_contract,is_reversed,is_price_in_peg):
    peg_reserve = 0
    token_reserve = 0
    (reserve0,reserve1,blockTimestampLast) = pair_contract.functions.getReserves().call()
    
    if is_reversed:
        peg_reserve = reserve0
        token_reserve = reserve1
    else:
        peg_reserve = reserve1
        token_reserve = reserve0
    
    if token_reserve and peg_reserve:
        if is_price_in_peg:
            # CALCULATE PRICE BY TOKEN PER PEG
            price = (Decimal(token_reserve) / 10 ** decimals) / (Decimal(peg_reserve) / ETHER)
        else:
            # CALCULATE PRICE BY PEG PER TOKEN
            price = (Decimal(peg_reserve) / ETHER) / (Decimal(token_reserve) / 10 ** decimals)
        
        return price
        
    return Decimal('0')


if __name__ == '__main__':
    CAKE_FACTORY_V2 = web3.eth.contract(address=CAKE_ROUTER_V2,abi=ABI).functions.factory().call()

    token = web3.toChecksumAddress('0x126f5f2a88451d24544f79d11f869116351d46e1')
    pair = web3.eth.contract(address=CAKE_FACTORY_V2,abi=ABI).functions.getPair(token,WBNB).call()
    pair_contract = web3.eth.contract(address=pair,abi=ABI)
    is_reversed = pair_contract.functions.token0().call() == WBNB
    decimals = web3.eth.contract(address=token,abi=ABI).functions.decimals().call()
    is_price_in_peg = True

    print(get_price(token,is_price_in_peg),'BNB')


对于 JS 使用:

var ETHER = Math.pow(10,18);

var WBNB = '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c';
var CAKE_ROUTER_V2 = Web3.utils.toChecksumAddress('0x10ed43c718714eb63d5aa57b78b54704e256024e');

var web3 = new Web3('https://bsc-dataseed1.binance.org:443');

var ABI = [{"inputs":[],"type":"function"}];


var get_price = async function(token,is_reverse,is_price_in_peg) {
    var price,peg_reserve = 0,token_reserve = 0,res = await pair_contract.methods.getReserves().call(),reserve0 = res[0],reserve1 = res[1];
        
    if (is_reverse) {
        peg_reserve = reserve0;
        token_reserve = reserve1;
    } else {
        peg_reserve = reserve1;
        token_reserve = reserve0;
    }
    
    if (token_reserve && peg_reserve) {
        if (is_price_in_peg) {
            // CALCULATE PRICE BY TOKEN PER PEG
            price = (Number(token_reserve) / Number(Math.pow(10,decimals))) / (Number(peg_reserve) / Number(ETHER));
        } else {
            // CALCULATE PRICE BY PEG PER TOKEN
            price = (Number(peg_reserve) / Number(ETHER)) / (Number(token_reserve) / Number(Math.pow(10,decimals)));
        }
            
        return price;
    }
    
    return Number(0);
};


var token = Web3.utils.toChecksumAddress('0x126f5f2a88451d24544f79d11f869116351d46e1');
var pair = await (await (new web3.eth.Contract(ABI,CAKE_FACTORY_V2))).methods.getPair(token,WBNB).call();
var pair_contract = await new web3.eth.Contract(ABI,pair);
var is_reversed = (await pair_contract.methods.token0().call()) == WBNB;
var decimals = await (await new web3.eth.Contract(ABI,token)).methods.decimals().call();
var is_price_in_peg = true;

console.log(await get_price(token,'BNB')

注意 1:这仅适用于对 WBNB 具有流动性的代币。如果流动性与其他代币相反,您必须递归地了解该链中的所有价格并将它们相互关联,直到您到达 WBNB(或其他网络中的任何其他 PEG)。

注意 2. 根据 https://arxiv.org/pdf/2009.14021.pdf :

预期执行价格 (E[P]):当流动性 接受者在 X/Y 上进行交易,接受者希望 以预期执行价格执行交易 E[P](基于AMM算法和X/Y状态), 鉴于预期的滑点。

执行价格(P):在流动性接受者发出交易与 正在执行的交易(例如在区块中挖掘), AMM 市场 X/Y 的状态可能会发生变化。 这种状态变化可能会导致意外的滑点 导致执行价格 P != E[P]。

意外价格滑点 (P − E[P]):是 P 和 E[P] 之间的差值。

意外滑点率 ((P − E[P]) / E[P]):是 超出预期价格的意外滑点。

所以在我们的情况下,E[P]get_price() 的结果,PgetAmounsOut() 的结果(例如 1Kwei)除以数量我们提供了(在本例中为 1K),因此我们甚至可以通过最终减去 P − E[P]

来计算滑点 ,

尝试将 WBNB 中的用户 toChecksumAddress 改为:WBNB = w3.toChecksumAddress('0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c')