0

I'm trying to understand the "getAmountsOut" function from Uniswap / Pancakeswap, line by line:

// performs chained getAmountOut calculations on any number of pairs
function getAmountsOut(address factory, uint amountIn, address[] memory path) internal view returns (uint[] memory amounts) {
    require(path.length >= 2, 'PancakeLibrary: INVALID_PATH');
    amounts = new uint[](path.length);
    amounts[0] = amountIn;
    for (uint i; i < path.length - 1; i++) {
        (uint reserveIn, uint reserveOut) = getReserves(factory, path[i], path[i + 1]);
        amounts[i + 1] = getAmountOut(amounts[i], reserveIn, reserveOut);
    }
}

source: https://bscscan.com/address/0x10ed43c718714eb63d5aa57b78b54704e256024e#code (line 336)

My superficial understanding is that, given an amount you are putting in, you want to find out how much you would get, given the current reserves and assuming no one else changes the reserves before you do a swap.

My Question:

But how does the code above accomplish that?

My Analysis of each line:

The require statement is just a check that makes sure we are looking at at least 2 tokens. Fair enough so far.

The amounts = new uint[](path.length) is new to me, what is this called and what is it doing?

the amounts[0] = amountsIn; is just replacing the value of amounts 0.

Also, I notice the function returns a uint[] memory amounts, however I don't see a return statement at the end of the function. Is a return statement not required? And is the variable "amounts" being declared in the signature of the function? I know that's how it works with parameters, but never assumed that the returns (...) portion also can be used in this way. Is my understanding here correct?

The rest of the lines are also a bit confusing for me. If anyone else can explain them, would mean a lot.

2 Answers 2

2

Your understanding of the return signature is correct. In Solidity you can define the return variable in the signature. You can read more here.

TL;DR

getAmountsOut calculates dynamically the swap for n tokens. The for-loop is used to calculate each swap from token i to i + 1 where the input amount starts with amountIn and the result of each swap is the next input amount for the next swap. amountsOut is an array with the values transformed in the process starting with amountIn

Explaination

The for-loop is a dynamic way of computing a swap with n Tokens, where n >= 2.
Let's inspect the getAmountOut method:

    function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) internalpure returns (uint amountOut) {
        require(amountIn > 0, 'PancakeLibrary: INSUFFICIENT_INPUT_AMOUNT');
        require(reserveIn > 0 && reserveOut > 0, 'PancakeLibrary: INSUFFICIENT_LIQUIDITY');
        uint amountInWithFee = amountIn.mul(9975);
        uint numerator = amountInWithFee.mul(reserveOut);
        uint denominator = reserveIn.mul(10000).add(amountInWithFee);
        amountOut = numerator / denominator;
    }

It does some fancy computation to return the output value. Note that it works with reserves which is a clever generic implementation.
Let us say, we've tokens A, B and C and we would like to swap from A to C over B. Let's assume that there exist pools for each of them and they have enough reserves.
When swapping from A to B, we use calculate the swap with amountIn. The resulting output is used for the swap from B to C. Each output is stored in amountsOut and returned to the caller.

Simple Example

amountIn = 1000
A -> B, 1000 => 991
B -> C, 991 => 1992
amountsOut = [1000, 991, 1992]

In a nutshell

When working with two tokens the for-loop will run once. I used an example with three tokens to illustrate the dynamic nature of the getAmountsOut function.

1
  • 1
    Thank you Willbrand for the explanation. I also looked a bit deeper into getAmountOut, and noticed the clever way the 0.25% fee pancakeswap (and presumably uniswap, not sure if they have the same fee) has is being implemented. Instead of multiplying amountIn by 0.9975 so as to subtract the 0.25% fee, they multiply by 9975 which won't cause any decimal issues our rounding. Though it does mean the numerator requires to be divided by 10k at some point. However since the denominator finds itself in the same situation, the last line, amountOut = numerator / denominator; does the trick Commented May 22, 2023 at 14:35
0

The way i used it in mine to get arb opportunity ```

struct ArbitrageResult {
    bool isProfitable;
    string direction;
    uint256 percentageProfit;
}

function getDecimals(address token) internal view returns (uint8) {
    return IERC20(token).decimals();
}

function checkProfitability(
    address _startSwapAddress,
    address _endSwapAddress,
    address _token0,
    address _token1,
    uint256 _flashAmount,
    uint256 _threshold
) public view returns (ArbitrageResult memory) {
    ArbitrageResult memory result;

    address[] memory path = new address[](2);
    path[0] = _token0;
    path[1] = _token1;

    uint8 token0Decimals = getDecimals(_token0);
    uint8 token1Decimals = getDecimals(_token1);


    uint256[] memory startSwapAmount = IUniswapV2Router02(_startSwapAddress).getAmountsOut(_flashAmount, path);
    uint256[] memory endSwapAmount = IUniswapV2Router02(_endSwapAddress).getAmountsOut(_flashAmount, path);


    uint256 startSwapPrice = (startSwapAmount[1] * 10 ** uint256(token0Decimals)) / startSwapAmount[0];
    uint256 endSwapPrice = (endSwapAmount[1] * 10 ** uint256(token0Decimals)) / endSwapAmount[0];

    
    uint256 TX_FEE = 3; // 0.3% fee, represented as 3 for easier calculations with integers

    if (startSwapPrice > endSwapPrice) {
        uint256 effStartSwapPrice = startSwapPrice * (1000 - TX_FEE) / 1000;
        uint256 effEndSwapPrice = endSwapPrice * (1000 - TX_FEE) / 1000;
        uint256 percentageDifference = ((effStartSwapPrice - effEndSwapPrice) * 100) / effEndSwapPrice;

  

        if (percentageDifference > 0 && percentageDifference >= _threshold) {
            return ArbitrageResult(
                true,
                "ATOB",
                percentageDifference
            );

        }
    } else if (endSwapPrice > startSwapPrice) {
        uint256 effEndSwapPrice = endSwapPrice * (1000 - TX_FEE) / 1000;
        uint256 effStartSwapPrice = startSwapPrice * (1000 - TX_FEE) / 1000;
        uint256 percentageDifference = ((effEndSwapPrice - effStartSwapPrice) * 100) / effStartSwapPrice;

        if (percentageDifference > 0 && percentageDifference >= _threshold) {
            return ArbitrageResult(
                true,
                "BTOA",
                percentageDifference
            );
        }
    }

    result.isProfitable = false;
    result.direction = "";
    result.percentageProfit = 0;
    return result;
}


function estimateGasCost() public view returns (uint256) {
    // Gas estimation logic (e.g., specific to the operations performed)
    uint256 gasUsed = 21000 + 100000; // Example estimation
    return gasUsed * tx.gasprice;
}
New contributor
Codertjay is a new contributor to this site. Take care in asking for clarification, commenting, and answering. Check out our Code of Conduct.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.