Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Discrepancy in web3.contract.functions.getAmount()

I'm using Web3.py and I'm experiencing something strange.

For the following code (with 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, [WBNB, KONGSHIBA]).call())

And I'm getting the following:

[1, 19]
[1, 160]

WBNB and CAKE have 18 decimals and KONGSHIBA has 17.
While CAKE's worth is currently about $27.7, WBNB is $545.41291093
and KONGSHIBA is $0.00000000000000000332.
So I should have got back:

[1, 19]
[1, 16000000000000000000]

Please advise.

like image 907
EvgenyKolyakov Avatar asked May 01 '26 04:05

EvgenyKolyakov


1 Answers

The proper way to calculate a token price is by asking the liquidity pool (the pair of this token against the local PEG or some USD token) for the ratio of how much PEG was inserted agains how may tokens (for more details about what a liquidity pool represents, see https://uniswap.org/docs/v2/core-concepts/pools/).

So for python use:

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","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[],"name":"factory","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"getPair","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getReserves","outputs":[{"internalType":"uint112","name":"_reserve0","type":"uint112"},{"internalType":"uint112","name":"_reserve1","type":"uint112"},{"internalType":"uint32","name":"_blockTimestampLast","type":"uint32"}],"payable":false,"stateMutability":"view","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, decimals, pair_contract, is_reversed, is_price_in_peg), 'BNB')


And for JS use:

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":[],"name":"decimals","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"token0","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[],"name":"factory","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"getPair","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getReserves","outputs":[{"internalType":"uint112","name":"_reserve0","type":"uint112"},{"internalType":"uint112","name":"_reserve1","type":"uint112"},{"internalType":"uint32","name":"_blockTimestampLast","type":"uint32"}],"payable":false,"stateMutability":"view","type":"function"}];


var get_price = async function(token, decimals, pair_contract, 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, decimals, pair_contract, is_reversed, is_price_in_peg), 'BNB')

NOTE 1: This applies only for tokens that have liquidity against WBNB. If the liquidity is against some other coin, you have to recursively understand all the prices in that chain and correlate them one to another until you reach WBNB (or any other PEG in other networks).

NOTE 2. According to https://arxiv.org/pdf/2009.14021.pdf :

Expected Execution Price (E[P]): When a liquidity taker issues a trade on X/Y , the taker wishes to execute the trade with the expected execution price E[P] (based on the AMM algorithm and X/Y state), given the expected slippage.

Execution Price (P): During the time difference between a liquidity taker issuing a transaction, and the transaction being executed (e.g. mined in a block), the state of the AMM market X/Y may change. This state change may induce unexpected slippage resulting in an execution price P != E[P].

Unexpected Price Slippage (P − E[P]): is the difference between P and E[P].

Unexpected Slippage Rate ((P − E[P]) / E[P]): is the unexpected slippage over the expected price.

So in our situation, E[P] is the result of our get_price() and P is the result from getAmounsOut() of an amount (1Kwei for example) divided by the amount we provided (1K in this example) and thus we can even calculate the slippage by eventually subtracting P − E[P]

like image 138
EvgenyKolyakov Avatar answered May 02 '26 17:05

EvgenyKolyakov