체인의정석

ZapperIn 분석 본문

블록체인/디파이

ZapperIn 분석

체인의정석 2022. 10. 27. 18:28
728x90
반응형

실제 유동성 풀을 제공할때는 Zapper라는 서비스에서 하나의 자산으로 만들어주는 걸 많이 사용한다고 한다.

 

https://etherscan.io/address/0x5c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f#code

 

Uniswap V2: Factory Contract | Address 0x5c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f | Etherscan

The Contract Address 0x5c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f page allows users to view the source code, transactions, balances, and analytics for the contract address. Users can also interact and make transactions to the contract directly on Etherscan.

etherscan.io

zapper의 경우 실제 코드는 이더스캔에 들어가서 볼 수 있다고 한다.

 

https://dashboard.tenderly.co/tx/mainnet/0xb66212a942a8816154b95b6887338b4c07f8f541d48fde97d37418bf23f912a6

 

Tenderly Dashboard

 

dashboard.tenderly.co

Tenderly를 통해서 보면 그냥 비율을 지정해서 나눠주는 것이지만 최적의 경로를 찾아서 나누어 준다는 면에 있어서 차이점이 있는 것 같다.

 

이건 깃허브 소스코드가 없다고 하여서 이더스캔에서 라이브러리를 제외한 소스코드만 따로 빼보았다.

//souce from 
//https://etherscan.io/address/0x80c5e6908368cb9db503ba968d7ec5a565bfb389#code

/**
 *Submitted for verification at Etherscan.io on 2020-08-19
*/

// ███████╗░█████╗░██████╗░██████╗░███████╗██████╗░░░░███████╗██╗
// ╚════██║██╔══██╗██╔══██╗██╔══██╗██╔════╝██╔══██╗░░░██╔════╝██║
// ░░███╔═╝███████║██████╔╝██████╔╝█████╗░░██████╔╝░░░█████╗░░██║
// ██╔══╝░░██╔══██║██╔═══╝░██╔═══╝░██╔══╝░░██╔══██╗░░░██╔══╝░░██║
// ███████╗██║░░██║██║░░░░░██║░░░░░███████╗██║░░██║██╗██║░░░░░██║
// ╚══════╝╚═╝░░╚═╝╚═╝░░░░░╚═╝░░░░░╚══════╝╚═╝░░╚═╝╚═╝╚═╝░░░░░╚═╝
// Copyright (C) 2020 zapper, nodar, suhail, seb, sumit, apoorv

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//

///@author Zapper
///@notice This contract adds liquidity to Uniswap V2 pools using ETH or any ERC20 Token.
// SPDX-License-Identifier: GPLv2

contract UniswapV2_ZapIn_General_V2_3 is ReentrancyGuard, Ownable {
    using SafeMath for uint256;
    using Address for address;
    using SafeERC20 for IERC20;

    bool public stopped = false;
    uint16 public goodwill;
    address
        private constant zgoodwillAddress = 0xE737b6AfEC2320f616297e59445b60a11e3eF75F;

    IUniswapV2Router02 private constant uniswapV2Router = IUniswapV2Router02(
        0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D
    );

    IUniswapV1Factory
        private constant UniSwapV1FactoryAddress = IUniswapV1Factory(
        0xc0a47dFe034B400B47bDaD5FecDa2621de6c4d95
    );

    IUniswapV2Factory
        private constant UniSwapV2FactoryAddress = IUniswapV2Factory(
        0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f
    );

    address
        private constant wethTokenAddress = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;

    constructor(uint16 _goodwill) public {
        goodwill = _goodwill;
    }

    // circuit breaker modifiers
    modifier stopInEmergency {
        if (stopped) {
            revert("Temporarily Paused");
        } else {
            _;
        }
    }

    /**
    @notice This function is used to invest in given Uniswap V2 pair through ETH/ERC20 Tokens
    @param _FromTokenContractAddress The ERC20 token used for investment (address(0x00) if ether)
    @param _ToUnipoolToken0 The Uniswap V2 pair token0 address
    @param _ToUnipoolToken1 The Uniswap V2 pair token1 address
    @param _amount The amount of fromToken to invest
    @param _minPoolTokens Reverts if less tokens received than this
    @return Amount of LP bought
     */
    function ZapIn(
        address _toWhomToIssue,
        address _FromTokenContractAddress,
        address _ToUnipoolToken0,
        address _ToUnipoolToken1,
        uint256 _amount,
        uint256 _minPoolTokens
    ) public payable nonReentrant stopInEmergency returns (uint256) {
        uint256 toInvest;
        if (_FromTokenContractAddress == address(0)) {
            require(msg.value > 0, "Error: ETH not sent");
            toInvest = msg.value;
        } else {
            require(msg.value == 0, "Error: ETH sent");
            require(_amount > 0, "Error: Invalid ERC amount");
            IERC20(_FromTokenContractAddress).safeTransferFrom(
                msg.sender,
                address(this),
                _amount
            );
            toInvest = _amount;
        }

        uint256 LPBought = _performZapIn(
            _toWhomToIssue,
            _FromTokenContractAddress,
            _ToUnipoolToken0,
            _ToUnipoolToken1,
            toInvest
        );

        require(LPBought >= _minPoolTokens, "ERR: High Slippage");

        //get pair address
        address _ToUniPoolAddress = UniSwapV2FactoryAddress.getPair(
            _ToUnipoolToken0,
            _ToUnipoolToken1
        );

        //transfer goodwill
        uint256 goodwillPortion = _transferGoodwill(
            _ToUniPoolAddress,
            LPBought
        );

        IERC20(_ToUniPoolAddress).safeTransfer(
            _toWhomToIssue,
            SafeMath.sub(LPBought, goodwillPortion)
        );
        return SafeMath.sub(LPBought, goodwillPortion);
    }

    function _performZapIn(
        address _toWhomToIssue,
        address _FromTokenContractAddress,
        address _ToUnipoolToken0,
        address _ToUnipoolToken1,
        uint256 _amount
    ) internal returns (uint256) {
        uint256 token0Bought;
        uint256 token1Bought;

        if (canSwapFromV2(_ToUnipoolToken0, _ToUnipoolToken1)) {
            (token0Bought, token1Bought) = exchangeTokensV2(
                _FromTokenContractAddress,
                _ToUnipoolToken0,
                _ToUnipoolToken1,
                _amount
            );
        } else if (
            canSwapFromV1(_ToUnipoolToken0, _ToUnipoolToken1, _amount, _amount)
        ) {
            (token0Bought, token1Bought) = exchangeTokensV1(
                _FromTokenContractAddress,
                _ToUnipoolToken0,
                _ToUnipoolToken1,
                _amount
            );
        }

        require(token0Bought > 0 && token1Bought > 0, "Could not exchange");

        IERC20(_ToUnipoolToken0).safeApprove(
            address(uniswapV2Router),
            token0Bought
        );

        IERC20(_ToUnipoolToken1).safeApprove(
            address(uniswapV2Router),
            token1Bought
        );

        (uint256 amountA, uint256 amountB, uint256 LP) = uniswapV2Router
            .addLiquidity(
            _ToUnipoolToken0,
            _ToUnipoolToken1,
            token0Bought,
            token1Bought,
            1,
            1,
            address(this),
            now + 60
        );

        //Reset allowance to zero
        IERC20(_ToUnipoolToken0).safeApprove(address(uniswapV2Router), 0);
        IERC20(_ToUnipoolToken1).safeApprove(address(uniswapV2Router), 0);

        uint256 residue;
        if (SafeMath.sub(token0Bought, amountA) > 0) {
            if (canSwapFromV2(_ToUnipoolToken0, _FromTokenContractAddress)) {
                residue = swapFromV2(
                    _ToUnipoolToken0,
                    _FromTokenContractAddress,
                    SafeMath.sub(token0Bought, amountA)
                );
            } else {
                IERC20(_ToUnipoolToken0).safeTransfer(
                    _toWhomToIssue,
                    SafeMath.sub(token0Bought, amountA)
                );
            }
        }

        if (SafeMath.sub(token1Bought, amountB) > 0) {
            if (canSwapFromV2(_ToUnipoolToken1, _FromTokenContractAddress)) {
                residue += swapFromV2(
                    _ToUnipoolToken1,
                    _FromTokenContractAddress,
                    SafeMath.sub(token1Bought, amountB)
                );
            } else {
                IERC20(_ToUnipoolToken1).safeTransfer(
                    _toWhomToIssue,
                    SafeMath.sub(token1Bought, amountB)
                );
            }
        }

        if (residue > 0) {
            if (_FromTokenContractAddress != address(0)) {
                IERC20(_FromTokenContractAddress).safeTransfer(
                    _toWhomToIssue,
                    residue
                );
            } else {
                (bool success, ) = _toWhomToIssue.call.value(residue)("");
                require(success, "Residual ETH Transfer failed.");
            }
        }

        return LP;
    }

    function exchangeTokensV1(
        address _FromTokenContractAddress,
        address _ToUnipoolToken0,
        address _ToUnipoolToken1,
        uint256 _amount
    ) internal returns (uint256 token0Bought, uint256 token1Bought) {
        IUniswapV2Pair pair = IUniswapV2Pair(
            UniSwapV2FactoryAddress.getPair(_ToUnipoolToken0, _ToUnipoolToken1)
        );
        (uint256 res0, uint256 res1, ) = pair.getReserves();
        if (_FromTokenContractAddress == address(0)) {
            token0Bought = _eth2Token(_ToUnipoolToken0, _amount);
            uint256 amountToSwap = calculateSwapInAmount(res0, token0Bought);
            //if no reserve or a new pair is created
            if (amountToSwap <= 0) amountToSwap = SafeMath.div(token0Bought, 2);
            token1Bought = _eth2Token(_ToUnipoolToken1, amountToSwap);
            token0Bought = SafeMath.sub(token0Bought, amountToSwap);
        } else {
            if (_ToUnipoolToken0 == _FromTokenContractAddress) {
                uint256 amountToSwap = calculateSwapInAmount(res0, _amount);
                //if no reserve or a new pair is created
                if (amountToSwap <= 0) amountToSwap = SafeMath.div(_amount, 2);
                token1Bought = _token2Token(
                    _FromTokenContractAddress,
                    address(this),
                    _ToUnipoolToken1,
                    amountToSwap
                );

                token0Bought = SafeMath.sub(_amount, amountToSwap);
            } else if (_ToUnipoolToken1 == _FromTokenContractAddress) {
                uint256 amountToSwap = calculateSwapInAmount(res1, _amount);
                //if no reserve or a new pair is created
                if (amountToSwap <= 0) amountToSwap = SafeMath.div(_amount, 2);
                token0Bought = _token2Token(
                    _FromTokenContractAddress,
                    address(this),
                    _ToUnipoolToken0,
                    amountToSwap
                );

                token1Bought = SafeMath.sub(_amount, amountToSwap);
            } else {
                token0Bought = _token2Token(
                    _FromTokenContractAddress,
                    address(this),
                    _ToUnipoolToken0,
                    _amount
                );
                uint256 amountToSwap = calculateSwapInAmount(
                    res0,
                    token0Bought
                );
                //if no reserve or a new pair is created
                if (amountToSwap <= 0) amountToSwap = SafeMath.div(_amount, 2);

                token1Bought = _token2Token(
                    _FromTokenContractAddress,
                    address(this),
                    _ToUnipoolToken1,
                    amountToSwap
                );
                token0Bought = SafeMath.sub(token0Bought, amountToSwap);
            }
        }
    }

    function exchangeTokensV2(
        address _FromTokenContractAddress,
        address _ToUnipoolToken0,
        address _ToUnipoolToken1,
        uint256 _amount
    ) internal returns (uint256 token0Bought, uint256 token1Bought) {
        IUniswapV2Pair pair = IUniswapV2Pair(
            UniSwapV2FactoryAddress.getPair(_ToUnipoolToken0, _ToUnipoolToken1)
        );
        (uint256 res0, uint256 res1, ) = pair.getReserves();
        if (
            canSwapFromV2(_FromTokenContractAddress, _ToUnipoolToken0) &&
            canSwapFromV2(_ToUnipoolToken0, _ToUnipoolToken1)
        ) {
            token0Bought = swapFromV2(
                _FromTokenContractAddress,
                _ToUnipoolToken0,
                _amount
            );
            uint256 amountToSwap = calculateSwapInAmount(res0, token0Bought);
            //if no reserve or a new pair is created
            if (amountToSwap <= 0) amountToSwap = SafeMath.div(token0Bought, 2);
            token1Bought = swapFromV2(
                _ToUnipoolToken0,
                _ToUnipoolToken1,
                amountToSwap
            );
            token0Bought = SafeMath.sub(token0Bought, amountToSwap);
        } else if (
            canSwapFromV2(_FromTokenContractAddress, _ToUnipoolToken1) &&
            canSwapFromV2(_ToUnipoolToken0, _ToUnipoolToken1)
        ) {
            token1Bought = swapFromV2(
                _FromTokenContractAddress,
                _ToUnipoolToken1,
                _amount
            );
            uint256 amountToSwap = calculateSwapInAmount(res1, token1Bought);
            //if no reserve or a new pair is created
            if (amountToSwap <= 0) amountToSwap = SafeMath.div(token1Bought, 2);
            token0Bought = swapFromV2(
                _ToUnipoolToken1,
                _ToUnipoolToken0,
                amountToSwap
            );
            token1Bought = SafeMath.sub(token1Bought, amountToSwap);
        }
    }

    //checks if tokens can be exchanged with UniV1
    function canSwapFromV1(
        address _fromToken,
        address _toToken,
        uint256 fromAmount,
        uint256 toAmount
    ) public view returns (bool) {
        require(
            _fromToken != address(0) || _toToken != address(0),
            "Invalid Exchange values"
        );

        if (_fromToken == address(0)) {
            IUniswapExchange toExchange = IUniswapExchange(
                UniSwapV1FactoryAddress.getExchange(_toToken)
            );
            uint256 tokenBalance = IERC20(_toToken).balanceOf(
                address(toExchange)
            );
            uint256 ethBalance = address(toExchange).balance;
            if (tokenBalance > toAmount && ethBalance > fromAmount) return true;
        } else if (_toToken == address(0)) {
            IUniswapExchange fromExchange = IUniswapExchange(
                UniSwapV1FactoryAddress.getExchange(_fromToken)
            );
            uint256 tokenBalance = IERC20(_fromToken).balanceOf(
                address(fromExchange)
            );
            uint256 ethBalance = address(fromExchange).balance;
            if (tokenBalance > fromAmount && ethBalance > toAmount) return true;
        } else {
            IUniswapExchange toExchange = IUniswapExchange(
                UniSwapV1FactoryAddress.getExchange(_toToken)
            );
            IUniswapExchange fromExchange = IUniswapExchange(
                UniSwapV1FactoryAddress.getExchange(_fromToken)
            );
            uint256 balance1 = IERC20(_fromToken).balanceOf(
                address(fromExchange)
            );
            uint256 balance2 = IERC20(_toToken).balanceOf(address(toExchange));
            if (balance1 > fromAmount && balance2 > toAmount) return true;
        }
        return false;
    }

    //checks if tokens can be exchanged with UniV2
    function canSwapFromV2(address _fromToken, address _toToken)
        public
        view
        returns (bool)
    {
        require(
            _fromToken != address(0) || _toToken != address(0),
            "Invalid Exchange values"
        );

        if (_fromToken == _toToken) return true;

        if (_fromToken == address(0) || _fromToken == wethTokenAddress) {
            if (_toToken == wethTokenAddress || _toToken == address(0))
                return true;
            IUniswapV2Pair pair = IUniswapV2Pair(
                UniSwapV2FactoryAddress.getPair(_toToken, wethTokenAddress)
            );
            if (_haveReserve(pair)) return true;
        } else if (_toToken == address(0) || _toToken == wethTokenAddress) {
            if (_fromToken == wethTokenAddress || _fromToken == address(0))
                return true;
            IUniswapV2Pair pair = IUniswapV2Pair(
                UniSwapV2FactoryAddress.getPair(_fromToken, wethTokenAddress)
            );
            if (_haveReserve(pair)) return true;
        } else {
            IUniswapV2Pair pair1 = IUniswapV2Pair(
                UniSwapV2FactoryAddress.getPair(_fromToken, wethTokenAddress)
            );
            IUniswapV2Pair pair2 = IUniswapV2Pair(
                UniSwapV2FactoryAddress.getPair(_toToken, wethTokenAddress)
            );
            IUniswapV2Pair pair3 = IUniswapV2Pair(
                UniSwapV2FactoryAddress.getPair(_fromToken, _toToken)
            );
            if (_haveReserve(pair1) && _haveReserve(pair2)) return true;
            if (_haveReserve(pair3)) return true;
        }
        return false;
    }

    //checks if the UNI v2 contract have reserves to swap tokens
    function _haveReserve(IUniswapV2Pair pair) internal view returns (bool) {
        if (address(pair) != address(0)) {
            (uint256 res0, uint256 res1, ) = pair.getReserves();
            if (res0 > 0 && res1 > 0) {
                return true;
            }
        }
    }

    function calculateSwapInAmount(uint256 reserveIn, uint256 userIn)
        public
        pure
        returns (uint256)
    {
        return
            Babylonian
                .sqrt(
                reserveIn.mul(userIn.mul(3988000) + reserveIn.mul(3988009))
            )
                .sub(reserveIn.mul(1997)) / 1994;
    }

    //swaps _fromToken for _toToken
    //for eth, address(0) otherwise ERC token address
    function swapFromV2(
        address _fromToken,
        address _toToken,
        uint256 amount
    ) internal returns (uint256) {
        require(
            _fromToken != address(0) || _toToken != address(0),
            "Invalid Exchange values"
        );
        if (_fromToken == _toToken) return amount;

        require(canSwapFromV2(_fromToken, _toToken), "Cannot be exchanged");
        require(amount > 0, "Invalid amount");

        if (_fromToken == address(0)) {
            if (_toToken == wethTokenAddress) {
                IWETH(wethTokenAddress).deposit.value(amount)();
                return amount;
            }
            address[] memory path = new address[](2);
            path[0] = wethTokenAddress;
            path[1] = _toToken;

            uint256[] memory amounts = uniswapV2Router
                .swapExactETHForTokens
                .value(amount)(0, path, address(this), now + 180);
            return amounts[1];
        } else if (_toToken == address(0)) {
            if (_fromToken == wethTokenAddress) {
                IWETH(wethTokenAddress).withdraw(amount);
                return amount;
            }
            address[] memory path = new address[](2);
            IERC20(_fromToken).safeApprove(address(uniswapV2Router), amount);
            path[0] = _fromToken;
            path[1] = wethTokenAddress;

            uint256[] memory amounts = uniswapV2Router.swapExactTokensForETH(
                amount,
                0,
                path,
                address(this),
                now + 180
            );
            IERC20(_fromToken).safeApprove(address(uniswapV2Router), 0);
            return amounts[1];
        } else {
            IERC20(_fromToken).safeApprove(address(uniswapV2Router), amount);
            uint256 returnedAmount = _swapTokenToTokenV2(
                _fromToken,
                _toToken,
                amount
            );
            IERC20(_fromToken).safeApprove(address(uniswapV2Router), 0);
            require(returnedAmount > 0, "Error in swap");
            return returnedAmount;
        }
    }

    //swaps 2 ERC tokens (UniV2)
    function _swapTokenToTokenV2(
        address _fromToken,
        address _toToken,
        uint256 amount
    ) internal returns (uint256) {
        IUniswapV2Pair pair1 = IUniswapV2Pair(
            UniSwapV2FactoryAddress.getPair(_fromToken, wethTokenAddress)
        );
        IUniswapV2Pair pair2 = IUniswapV2Pair(
            UniSwapV2FactoryAddress.getPair(_toToken, wethTokenAddress)
        );
        IUniswapV2Pair pair3 = IUniswapV2Pair(
            UniSwapV2FactoryAddress.getPair(_fromToken, _toToken)
        );

        uint256[] memory amounts;

        if (_haveReserve(pair3)) {
            address[] memory path = new address[](2);
            path[0] = _fromToken;
            path[1] = _toToken;

            amounts = uniswapV2Router.swapExactTokensForTokens(
                amount,
                0,
                path,
                address(this),
                now + 180
            );
            return amounts[1];
        } else if (_haveReserve(pair1) && _haveReserve(pair2)) {
            address[] memory path = new address[](3);
            path[0] = _fromToken;
            path[1] = wethTokenAddress;
            path[2] = _toToken;

            amounts = uniswapV2Router.swapExactTokensForTokens(
                amount,
                0,
                path,
                address(this),
                now + 180
            );
            return amounts[2];
        }
        return 0;
    }

    /**
    @notice This function is used to buy tokens from eth
    @param _tokenContractAddress Token address which we want to buy
    @param _amount The amount of eth we want to exchange
    @return The quantity of token bought
     */
    function _eth2Token(address _tokenContractAddress, uint256 _amount)
        internal
        returns (uint256 tokenBought)
    {
        IUniswapExchange FromUniSwapExchangeContractAddress = IUniswapExchange(
            UniSwapV1FactoryAddress.getExchange(_tokenContractAddress)
        );

        tokenBought = FromUniSwapExchangeContractAddress
            .ethToTokenSwapInput
            .value(_amount)(0, SafeMath.add(now, 300));
    }

    /**
    @notice This function is used to swap token with ETH
    @param _FromTokenContractAddress The token address to swap from
    @param tokens2Trade The quantity of tokens to swap
    @return The amount of eth bought
     */
    function _token2Eth(
        address _FromTokenContractAddress,
        uint256 tokens2Trade,
        address _toWhomToIssue
    ) internal returns (uint256 ethBought) {
        IUniswapExchange FromUniSwapExchangeContractAddress = IUniswapExchange(
            UniSwapV1FactoryAddress.getExchange(_FromTokenContractAddress)
        );

        IERC20(_FromTokenContractAddress).safeApprove(
            address(FromUniSwapExchangeContractAddress),
            tokens2Trade
        );

        ethBought = FromUniSwapExchangeContractAddress.tokenToEthTransferInput(
            tokens2Trade,
            0,
            SafeMath.add(now, 300),
            _toWhomToIssue
        );
        require(ethBought > 0, "Error in swapping Eth: 1");

        IERC20(_FromTokenContractAddress).safeApprove(
            address(FromUniSwapExchangeContractAddress),
            0
        );
    }

    /**
    @notice This function is used to swap tokens
    @param _FromTokenContractAddress The token address to swap from
    @param _ToWhomToIssue The address to transfer after swap
    @param _ToTokenContractAddress The token address to swap to
    @param tokens2Trade The quantity of tokens to swap
    @return The amount of tokens returned after swap
     */
    function _token2Token(
        address _FromTokenContractAddress,
        address _ToWhomToIssue,
        address _ToTokenContractAddress,
        uint256 tokens2Trade
    ) internal returns (uint256 tokenBought) {
        IUniswapExchange FromUniSwapExchangeContractAddress = IUniswapExchange(
            UniSwapV1FactoryAddress.getExchange(_FromTokenContractAddress)
        );

        IERC20(_FromTokenContractAddress).safeApprove(
            address(FromUniSwapExchangeContractAddress),
            tokens2Trade
        );

        tokenBought = FromUniSwapExchangeContractAddress
            .tokenToTokenTransferInput(
            tokens2Trade,
            0,
            0,
            SafeMath.add(now, 300),
            _ToWhomToIssue,
            _ToTokenContractAddress
        );
        require(tokenBought > 0, "Error in swapping ERC: 1");

        IERC20(_FromTokenContractAddress).safeApprove(
            address(FromUniSwapExchangeContractAddress),
            0
        );
    }

    /**
    @notice This function is used to calculate and transfer goodwill
    @param _tokenContractAddress Token in which goodwill is deducted
    @param tokens2Trade The total amount of tokens to be zapped in
    @return The quantity of goodwill deducted
     */
    function _transferGoodwill(
        address _tokenContractAddress,
        uint256 tokens2Trade
    ) internal returns (uint256 goodwillPortion) {
        goodwillPortion = SafeMath.div(
            SafeMath.mul(tokens2Trade, goodwill),
            10000
        );

        if (goodwillPortion == 0) {
            return 0;
        }

        IERC20(_tokenContractAddress).safeTransfer(
            zgoodwillAddress,
            goodwillPortion
        );
    }

    function set_new_goodwill(uint16 _new_goodwill) public onlyOwner {
        require(
            _new_goodwill >= 0 && _new_goodwill < 10000,
            "GoodWill Value not allowed"
        );
        goodwill = _new_goodwill;
    }

    function inCaseTokengetsStuck(IERC20 _TokenAddress) public onlyOwner {
        uint256 qty = _TokenAddress.balanceOf(address(this));
        _TokenAddress.safeTransfer(owner(), qty);
    }

    // - to Pause the contract
    function toggleContractActive() public onlyOwner {
        stopped = !stopped;
    }

    // - to withdraw any ETH balance sitting in the contract
    function withdraw() public onlyOwner {
        uint256 contractBalance = address(this).balance;
        address payable _to = owner().toPayable();
        _to.transfer(contractBalance);
    }

    function() external payable {}
}

보니까 설명 부터가 해당 컨트렉트는 uniswap V2 풀에 유동성을 공금해주는 컨트렉트라고 나온다.

유니스왑 V2와 세트 느낌으로 보면 되겠다.

 

그럼 일단 변수 선언부분부터 한번 보자 Address 라이브러리를 찾다가 정말 다양한 call, delegateCall, address에 대한 부분이 있는 유용한 라이브러리를 발견했다. 이건 양이 좀 있어서 따로 포스팅을 해버렸다.

 

https://it-timehacker.tistory.com/343

 

Openzepplin - Address 라이브러리 분석

zapper 코드를 분석하다가 Address 부분을 발견하여 글로 한번 남겨본다. https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Address.sol GitHub - OpenZeppelin/openzeppelin..

it-timehacker.tistory.com

    using SafeMath for uint256;
    using Address for address;
    using SafeERC20 for IERC20;

    bool public stopped = false;
    uint16 public goodwill;
    address
        private constant zgoodwillAddress = 0xE737b6AfEC2320f616297e59445b60a11e3eF75F;

    IUniswapV2Router02 private constant uniswapV2Router = IUniswapV2Router02(
        0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D
    );

    IUniswapV1Factory
        private constant UniSwapV1FactoryAddress = IUniswapV1Factory(
        0xc0a47dFe034B400B47bDaD5FecDa2621de6c4d95
    );

    IUniswapV2Factory
        private constant UniSwapV2FactoryAddress = IUniswapV2Factory(
        0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f
    );

    address
        private constant wethTokenAddress = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;

    constructor(uint16 _goodwill) public {
        goodwill = _goodwill;
    }

그리고 그 다음 stop이 있었는데 이건 서킷 브레이커라고 표현을 했다.

실제 주식의 서킷 브레이커와는 다른 역할 같고 그냥 pause같은데 사용하는 사람 맘일것 같고

이걸 자기 조건대로 서킷 브레이커를 걸때 써도 되겠지만 zapper에서는? 그냥 교환만해주는건데 뭔가 유니스왑이런데서 장이 멈추면 zapper도 멈추니까 이렇게 이름을 붙인거 같다라는 생각이 든다.

    // circuit breaker modifiers
    modifier stopInEmergency {
        if (stopped) {
            revert("Temporarily Paused");
        } else {
            _;
        }
    }

goodwill이라는 변수는 생성자에서 사용된다.

    constructor(uint16 _goodwill) public {
        goodwill = _goodwill;
    }

보내는 측에서 항상 차감하고 보내는 것을 보니 수수료 같았는데 역시 수수료라고 한다.

    IUniswapV2Router02 private constant uniswapV2Router = IUniswapV2Router02(
        0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D
    );

    IUniswapV1Factory
        private constant UniSwapV1FactoryAddress = IUniswapV1Factory(
        0xc0a47dFe034B400B47bDaD5FecDa2621de6c4d95
    );

    IUniswapV2Factory
        private constant UniSwapV2FactoryAddress = IUniswapV2Factory(
        0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f
    );

    address
        private constant wethTokenAddress = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;

 

유니스왑과의 거래를 해줘야 하니 유니스왑관련된 주소 한번씩 쭉 정의를 해주고

결국 접근 하는건 Factory랑 Router 정도인거 같다.

그리고 wrapped eth 주소도 필요할 것이다.

 

1. Zap In

그럼 먼저 zap in 부터 보도록 하겠다.

    function ZapIn(
        address _toWhomToIssue,
        address _FromTokenContractAddress,
        address _ToUnipoolToken0,
        address _ToUnipoolToken1,
        uint256 _amount,
        uint256 _minPoolTokens
    ) public payable nonReentrant stopInEmergency returns (uint256) {
        uint256 toInvest;
        if (_FromTokenContractAddress == address(0)) {
            require(msg.value > 0, "Error: ETH not sent");
            toInvest = msg.value;
        } else {
            require(msg.value == 0, "Error: ETH sent");
            require(_amount > 0, "Error: Invalid ERC amount");
            IERC20(_FromTokenContractAddress).safeTransferFrom(
                msg.sender,
                address(this),
                _amount
            );
            toInvest = _amount;
        }

        uint256 LPBought = _performZapIn(
            _toWhomToIssue,
            _FromTokenContractAddress,
            _ToUnipoolToken0,
            _ToUnipoolToken1,
            toInvest
        );

        require(LPBought >= _minPoolTokens, "ERR: High Slippage");

        //get pair address
        address _ToUniPoolAddress = UniSwapV2FactoryAddress.getPair(
            _ToUnipoolToken0,
            _ToUnipoolToken1
        );

        //transfer goodwill
        uint256 goodwillPortion = _transferGoodwill(
            _ToUniPoolAddress,
            LPBought
        );

        IERC20(_ToUniPoolAddress).safeTransfer(
            _toWhomToIssue,
            SafeMath.sub(LPBought, goodwillPortion)
        );
        return SafeMath.sub(LPBought, goodwillPortion);
    }

 

 

일단 차근차근히 살펴보도록 하겠다.

 

/**
@notice This function is used to invest in given Uniswap V2 pair through ETH/ERC20 Tokens
@param _FromTokenContractAddress The ERC20 token used for investment (address(0x00) if ether)
@param _ToUnipoolToken0 The Uniswap V2 pair token0 address
@param _ToUnipoolToken1 The Uniswap V2 pair token1 address
@param _amount The amount of fromToken to invest
@param _minPoolTokens Reverts if less tokens received than this
@return Amount of LP bought
*/

요건 주석인데 LP토큰을 유니스왑에서 안사고 여기서 사도록 만들어져 있다.

모디파이어는 재진입 방지 & pauser 일단 걸어놨다.

 

그리고 uin256 toInvset라는 변수를 맨위에 정의 한 후 다음 분기문이 나온다.

        if (_FromTokenContractAddress == address(0)) {
            require(msg.value > 0, "Error: ETH not sent");
            toInvest = msg.value;
        } else {
            require(msg.value == 0, "Error: ETH sent");
            require(_amount > 0, "Error: Invalid ERC amount");
            IERC20(_FromTokenContractAddress).safeTransferFrom(
                msg.sender,
                address(this),
                _amount
            );
            toInvest = _amount;
        }

첫번째 분기문은 fromToken 주소가 없는 경우. 이 경우를 zapper에서는 이더리움이 msg.value로 들어오는 경우라고 생각하나보다. 에러 메세지도 Error: ETH not sent라고 나와있다. 그리고 toInvest를 맨처음에 정의해뒀는데 이걸 msg.value로 선언해 두었다.

 

만약 fromTokenAddress가 0이 아니라면 msg.value가 들어오면 안된다 왜냐면 이건 이제 토큰이 교환되는 케이스니까.

그럼 토큰이 교환되는 케이스에서 amount는 0보다 커야하고

 

amount가 0 보다 크면 이제 zapper 컨트렉트로 토큰을 보내주게 된다. approve를 역시 미리 받아두는게 가정인가 보다.

그리고 그 다음에 

        uint256 LPBought = _performZapIn(
            _toWhomToIssue,
            _FromTokenContractAddress,
            _ToUnipoolToken0,
            _ToUnipoolToken1,
            toInvest
        );
        
    	require(LPBought >= _minPoolTokens, "ERR: High Slippage");

인터널 함수로 들어가서 구매한 LP를 구해온다. 근데 만약 슬리피지가 커서 입력한 조건보다 LP토큰이 조금 들어오게 되면 예외처리를 해주야 한다. (원하는 돈만큼 못받으니까)

 

        //get pair address
        address _ToUniPoolAddress = UniSwapV2FactoryAddress.getPair(
            _ToUnipoolToken0,
            _ToUnipoolToken1
        );

        //transfer goodwill
        uint256 goodwillPortion = _transferGoodwill(
            _ToUniPoolAddress,
            LPBought
        );

        IERC20(_ToUniPoolAddress).safeTransfer(
            _toWhomToIssue,
            SafeMath.sub(LPBought, goodwillPortion)
        );
        return SafeMath.sub(LPBought, goodwillPortion);

유니스왑 컨트렉트에 인터널 트랜잭션을 보내서 각 풀의 주소값을 먼저 받아와 주면 _ToUniPoolAddress가 나오게 되는데 일단 여기서 인터널 함수로 수수료 계산을 한번 해준다.

    /**
    @notice This function is used to calculate and transfer goodwill
    @param _tokenContractAddress Token in which goodwill is deducted
    @param tokens2Trade The total amount of tokens to be zapped in
    @return The quantity of goodwill deducted
     */
    function _transferGoodwill(
        address _tokenContractAddress,
        uint256 tokens2Trade
    ) internal returns (uint256 goodwillPortion) {
        goodwillPortion = SafeMath.div(
            SafeMath.mul(tokens2Trade, goodwill),
            10000
        );

        if (goodwillPortion == 0) {
            return 0;
        }

        IERC20(_tokenContractAddress).safeTransfer(
            zgoodwillAddress,
            goodwillPortion
        );
    }

여기서 나온 인터널 트랜잭션의 경우에는 수수료를 계산하는 경우인데

트레이딩 된 토큰 양 * 수수료 비율 / 10000 이렇게 나온다. 보니까 소수점 2자리 수까지 지원되어서 10000을 사용한게 아닐까 싶다.

만약에 수수료가 없다면 0을 리턴하는 코드도 포함되어 있다.

 

마지막으로는 계산한 만큼 수수료를 차감하는 로직이 들어가 있다.

예전에 nft 거래소 컨트렉트를 만들때도 수수료를 차감하고 나서 코드를 실행했었는데 여기서도 수수료를 차감하고 나서 실행을 시킨다.

 

아무튼 수수료까지 인출하고 난 후에는 

        IERC20(_ToUniPoolAddress).safeTransfer(
            _toWhomToIssue,
            SafeMath.sub(LPBought, goodwillPortion)
        );
        return SafeMath.sub(LPBought, goodwillPortion);

그만큼을 차감하고 돌려주게 된다.

 

그럼 토큰을 전송하는 부분을 제외한 나머지 LPBought 내가 구매한 LP 토큰을 계산하는 로직이 또 중요할 것이다.

    function _performZapIn(
        address _toWhomToIssue,
        address _FromTokenContractAddress,
        address _ToUnipoolToken0,
        address _ToUnipoolToken1,
        uint256 _amount
    ) internal returns (uint256) {
        uint256 token0Bought;
        uint256 token1Bought;

        if (canSwapFromV2(_ToUnipoolToken0, _ToUnipoolToken1)) {
            (token0Bought, token1Bought) = exchangeTokensV2(
                _FromTokenContractAddress,
                _ToUnipoolToken0,
                _ToUnipoolToken1,
                _amount
            );
        } else if (
            canSwapFromV1(_ToUnipoolToken0, _ToUnipoolToken1, _amount, _amount)
        ) {
            (token0Bought, token1Bought) = exchangeTokensV1(
                _FromTokenContractAddress,
                _ToUnipoolToken0,
                _ToUnipoolToken1,
                _amount
            );
        }

        require(token0Bought > 0 && token1Bought > 0, "Could not exchange");

        IERC20(_ToUnipoolToken0).safeApprove(
            address(uniswapV2Router),
            token0Bought
        );

        IERC20(_ToUnipoolToken1).safeApprove(
            address(uniswapV2Router),
            token1Bought
        );

        (uint256 amountA, uint256 amountB, uint256 LP) = uniswapV2Router
            .addLiquidity(
            _ToUnipoolToken0,
            _ToUnipoolToken1,
            token0Bought,
            token1Bought,
            1,
            1,
            address(this),
            now + 60
        );

        //Reset allowance to zero
        IERC20(_ToUnipoolToken0).safeApprove(address(uniswapV2Router), 0);
        IERC20(_ToUnipoolToken1).safeApprove(address(uniswapV2Router), 0);

        uint256 residue;
        if (SafeMath.sub(token0Bought, amountA) > 0) {
            if (canSwapFromV2(_ToUnipoolToken0, _FromTokenContractAddress)) {
                residue = swapFromV2(
                    _ToUnipoolToken0,
                    _FromTokenContractAddress,
                    SafeMath.sub(token0Bought, amountA)
                );
            } else {
                IERC20(_ToUnipoolToken0).safeTransfer(
                    _toWhomToIssue,
                    SafeMath.sub(token0Bought, amountA)
                );
            }
        }

        if (SafeMath.sub(token1Bought, amountB) > 0) {
            if (canSwapFromV2(_ToUnipoolToken1, _FromTokenContractAddress)) {
                residue += swapFromV2(
                    _ToUnipoolToken1,
                    _FromTokenContractAddress,
                    SafeMath.sub(token1Bought, amountB)
                );
            } else {
                IERC20(_ToUnipoolToken1).safeTransfer(
                    _toWhomToIssue,
                    SafeMath.sub(token1Bought, amountB)
                );
            }
        }

        if (residue > 0) {
            if (_FromTokenContractAddress != address(0)) {
                IERC20(_FromTokenContractAddress).safeTransfer(
                    _toWhomToIssue,
                    residue
                );
            } else {
                (bool success, ) = _toWhomToIssue.call.value(residue)("");
                require(success, "Residual ETH Transfer failed.");
            }
        }

        return LP;
    }

이것도 엄청 길다.. 일단 쪼개서 한번 확인해 보도록 하겠다.

        if (canSwapFromV2(_ToUnipoolToken0, _ToUnipoolToken1)) {
            (token0Bought, token1Bought) = exchangeTokensV2(
                _FromTokenContractAddress,
                _ToUnipoolToken0,
                _ToUnipoolToken1,
                _amount
            );
        } else if (
            canSwapFromV1(_ToUnipoolToken0, _ToUnipoolToken1, _amount, _amount)
        ) {
            (token0Bought, token1Bought) = exchangeTokensV1(
                _FromTokenContractAddress,
                _ToUnipoolToken0,
                _ToUnipoolToken1,
                _amount
            );
        }

        require(token0Bought > 0 && token1Bought > 0, "Could not exchange");

첫 부분은 canSwapFromV2라는 함수를 가져와서 쓰는데

유니스왑 V2에서 정보를 가져올 수 있는지 여부를 체크하고 있다.

reserve가 둘다 0보다 큰지 체크하는 _haveReserve를 정의한 후 이를 계속해서 호출하는 식으로 동작한다.

    //checks if tokens can be exchanged with UniV2
    function canSwapFromV2(address _fromToken, address _toToken)
        public
        view
        returns (bool)
    {
        require(
            _fromToken != address(0) || _toToken != address(0),
            "Invalid Exchange values"
        );

        if (_fromToken == _toToken) return true;

        if (_fromToken == address(0) || _fromToken == wethTokenAddress) {
            if (_toToken == wethTokenAddress || _toToken == address(0))
                return true;
            IUniswapV2Pair pair = IUniswapV2Pair(
                UniSwapV2FactoryAddress.getPair(_toToken, wethTokenAddress)
            );
            if (_haveReserve(pair)) return true;
        } else if (_toToken == address(0) || _toToken == wethTokenAddress) {
            if (_fromToken == wethTokenAddress || _fromToken == address(0))
                return true;
            IUniswapV2Pair pair = IUniswapV2Pair(
                UniSwapV2FactoryAddress.getPair(_fromToken, wethTokenAddress)
            );
            if (_haveReserve(pair)) return true;
        } else {
            IUniswapV2Pair pair1 = IUniswapV2Pair(
                UniSwapV2FactoryAddress.getPair(_fromToken, wethTokenAddress)
            );
            IUniswapV2Pair pair2 = IUniswapV2Pair(
                UniSwapV2FactoryAddress.getPair(_toToken, wethTokenAddress)
            );
            IUniswapV2Pair pair3 = IUniswapV2Pair(
                UniSwapV2FactoryAddress.getPair(_fromToken, _toToken)
            );
            if (_haveReserve(pair1) && _haveReserve(pair2)) return true;
            if (_haveReserve(pair3)) return true;
        }
        return false;
    }

    //checks if the UNI v2 contract have reserves to swap tokens
    function _haveReserve(IUniswapV2Pair pair) internal view returns (bool) {
        if (address(pair) != address(0)) {
            (uint256 res0, uint256 res1, ) = pair.getReserves();
            if (res0 > 0 && res1 > 0) {
                return true;
            }
        }
    }

위의 코드에 if 문이 많긴 하지만 wethTokenAddress가 포함되는 경우면 조건문에서 처리가 되게 되고 

아닌 경우에는 

            if (_haveReserve(pair1) && _haveReserve(pair2)) return true;

여기서 처리된다고 보면 좀 간단할 것 같다.

그리고 내부적으로 호출되는 함수가 몇개 더 있다.

 

1-1. swapFromV2

두번째 호출되는 함수의 경우에는 swapFromV2가 있다.

    //swaps _fromToken for _toToken
    //for eth, address(0) otherwise ERC token address
    function swapFromV2(
        address _fromToken,
        address _toToken,
        uint256 amount
    ) internal returns (uint256) {
        require(
            _fromToken != address(0) || _toToken != address(0),
            "Invalid Exchange values"
        );
        if (_fromToken == _toToken) return amount;

        require(canSwapFromV2(_fromToken, _toToken), "Cannot be exchanged");
        require(amount > 0, "Invalid amount");

        if (_fromToken == address(0)) {
            if (_toToken == wethTokenAddress) {
                IWETH(wethTokenAddress).deposit.value(amount)();
                return amount;
            }
            address[] memory path = new address[](2);
            path[0] = wethTokenAddress;
            path[1] = _toToken;

            uint256[] memory amounts = uniswapV2Router
                .swapExactETHForTokens
                .value(amount)(0, path, address(this), now + 180);
            return amounts[1];
        } else if (_toToken == address(0)) {
            if (_fromToken == wethTokenAddress) {
                IWETH(wethTokenAddress).withdraw(amount);
                return amount;
            }
            address[] memory path = new address[](2);
            IERC20(_fromToken).safeApprove(address(uniswapV2Router), amount);
            path[0] = _fromToken;
            path[1] = wethTokenAddress;

            uint256[] memory amounts = uniswapV2Router.swapExactTokensForETH(
                amount,
                0,
                path,
                address(this),
                now + 180
            );
            IERC20(_fromToken).safeApprove(address(uniswapV2Router), 0);
            return amounts[1];
        } else {
            IERC20(_fromToken).safeApprove(address(uniswapV2Router), amount);
            uint256 returnedAmount = _swapTokenToTokenV2(
                _fromToken,
                _toToken,
                amount
            );
            IERC20(_fromToken).safeApprove(address(uniswapV2Router), 0);
            require(returnedAmount > 0, "Error in swap");
            return returnedAmount;
        }
    }

먼저 초기에는 예외 처리부터 쭉 해준다.

        require(
            _fromToken != address(0) || _toToken != address(0),
            "Invalid Exchange values"
        );
        if (_fromToken == _toToken) return amount;

        require(canSwapFromV2(_fromToken, _toToken), "Cannot be exchanged");
        require(amount > 0, "Invalid amount");

첫번째 예외처리의 경우 fromToken과 toToken이 제대로 들어왔는지 체크하는 부분이며 

만약 두 토큰이 같다면 같은 경로이므로 amount만 그대로 리턴해 줍니다.

 

만약 fromToken이 0인 경우

            if (_toToken == wethTokenAddress) {
                IWETH(wethTokenAddress).deposit.value(amount)();
                return amount;
            }
            address[] memory path = new address[](2);
            path[0] = wethTokenAddress;
            path[1] = _toToken;

            uint256[] memory amounts = uniswapV2Router
                .swapExactETHForTokens
                .value(amount)(0, path, address(this), now + 180);
            return amounts[1];

toToken이 wethTokenAddress 라고 하면 wethTokenAddress에서 deposit을 해준다

그리고 리턴을 해준다. from이 0번 주소이기 때문에  mint를 하는 것과 같기 때문이다.

 

path를 만든 후에 wethTokenAddress 와 toTokenAddress를 넣어준다.

그러고 난 후 path를 이용해서 Router함수를 실행시키고 

여기서 swap함수 자체가 swapExactEthForTokens 이므로 이더리움 그 자체가 와서 저 함수 안에서 토큰이 mint가 되게 된다.

 

else if (_toToken == address(0)) {
            if (_fromToken == wethTokenAddress) {
                IWETH(wethTokenAddress).withdraw(amount);
                return amount;
            }
            address[] memory path = new address[](2);
            IERC20(_fromToken).safeApprove(address(uniswapV2Router), amount);
            path[0] = _fromToken;
            path[1] = wethTokenAddress;

            uint256[] memory amounts = uniswapV2Router.swapExactTokensForETH(
                amount,
                0,
                path,
                address(this),
                now + 180
            );
            IERC20(_fromToken).safeApprove(address(uniswapV2Router), 0);
            return amounts[1];

 

마지막 to 가 0이면 그냥 소각시켜버리면 된다. uniswapV2Router에서는 이에 맞추어서 다른 함수가 호출된다. path에 맞춰서 weth는 소각되고 이더리움은 address(this)로 반환되게 된다.

 

그리고 교환을 끝냈으니 토큰에 대한 approve 권한은 초기화 시켜준다.

 

마지막 부분은 토큰과 토큰을 교환해주는 부분이다.

            IERC20(_fromToken).safeApprove(address(uniswapV2Router), amount);
            uint256 returnedAmount = _swapTokenToTokenV2(
                _fromToken,
                _toToken,
                amount
            );
            IERC20(_fromToken).safeApprove(address(uniswapV2Router), 0);
            require(returnedAmount > 0, "Error in swap");
            return returnedAmount;

토큰과의 교환이기 때문에 Router에게 approve를 해준 후에 amount를 받는다.

fromToken을 toToken과 swap 해준 후에는 approve를 다시 0으로 만들어주고 새로 받은 토큰이 0개일 경우에는 에러를 리턴한다.


1-2. _swapTokenToTokenV2

_swapTokenToTokenV2도 살펴보겠다.

    //swaps 2 ERC tokens (UniV2)
    function _swapTokenToTokenV2(
        address _fromToken,
        address _toToken,
        uint256 amount
    ) internal returns (uint256) {
        IUniswapV2Pair pair1 = IUniswapV2Pair(
            UniSwapV2FactoryAddress.getPair(_fromToken, wethTokenAddress)
        );
        IUniswapV2Pair pair2 = IUniswapV2Pair(
            UniSwapV2FactoryAddress.getPair(_toToken, wethTokenAddress)
        );
        IUniswapV2Pair pair3 = IUniswapV2Pair(
            UniSwapV2FactoryAddress.getPair(_fromToken, _toToken)
        );

        uint256[] memory amounts;

        if (_haveReserve(pair3)) {
            address[] memory path = new address[](2);
            path[0] = _fromToken;
            path[1] = _toToken;

            amounts = uniswapV2Router.swapExactTokensForTokens(
                amount,
                0,
                path,
                address(this),
                now + 180
            );
            return amounts[1];
        } else if (_haveReserve(pair1) && _haveReserve(pair2)) {
            address[] memory path = new address[](3);
            path[0] = _fromToken;
            path[1] = wethTokenAddress;
            path[2] = _toToken;

            amounts = uniswapV2Router.swapExactTokensForTokens(
                amount,
                0,
                path,
                address(this),
                now + 180
            );
            return amounts[2];
        }
        return 0;
    }

여기선 한번에 pair 1,2,3를 정의한다. 각각 weth와 상호작용을 from/to로 할때와 둘다 토큰일 때를 가정한다.

 

먼저 라우터에서 연결 가능한 pair3가 존재하는 경우 두 토큰을 바로 교환하면 된다.

이 경우는 바로 토큰끼리 교환하는 작업을 진행하고 끝내면 된다.

        if (_haveReserve(pair3)) {
            address[] memory path = new address[](2);
            path[0] = _fromToken;
            path[1] = _toToken;

            amounts = uniswapV2Router.swapExactTokensForTokens(
                amount,
                0,
                path,
                address(this),
                now + 180
            );
            return amounts[1];

다음 케이스는 Pair가 없는 대신 각각 wethAdderss로 연결되어 있는 경우 이다.

해당 경우에는 

        } else if (_haveReserve(pair1) && _haveReserve(pair2)) {
            address[] memory path = new address[](3);
            path[0] = _fromToken;
            path[1] = wethTokenAddress;
            path[2] = _toToken;

            amounts = uniswapV2Router.swapExactTokensForTokens(
                amount,
                0,
                path,
                address(this),
                now + 180
            );
            return amounts[2];
        }
        return 0;

wethTokenAddress를 경로로 만들어서 path로 한번에 넘겨주면 Router에서 알아서 처리해 줄 수 있다.

 

2. exchageTokensV2

그럼 이제 인터널 호출을 살펴봣으니 다시 Zap In에 돌아와서 코드를 살펴보도록 하겠다.

    function exchangeTokensV2(
        address _FromTokenContractAddress,
        address _ToUnipoolToken0,
        address _ToUnipoolToken1,
        uint256 _amount
    ) internal returns (uint256 token0Bought, uint256 token1Bought) {
        IUniswapV2Pair pair = IUniswapV2Pair(
            UniSwapV2FactoryAddress.getPair(_ToUnipoolToken0, _ToUnipoolToken1)
        );
        (uint256 res0, uint256 res1, ) = pair.getReserves();
        if (
            canSwapFromV2(_FromTokenContractAddress, _ToUnipoolToken0) &&
            canSwapFromV2(_ToUnipoolToken0, _ToUnipoolToken1)
        ) {
            token0Bought = swapFromV2(
                _FromTokenContractAddress,
                _ToUnipoolToken0,
                _amount
            );
            uint256 amountToSwap = calculateSwapInAmount(res0, token0Bought);
            //if no reserve or a new pair is created
            if (amountToSwap <= 0) amountToSwap = SafeMath.div(token0Bought, 2);
            token1Bought = swapFromV2(
                _ToUnipoolToken0,
                _ToUnipoolToken1,
                amountToSwap
            );
            token0Bought = SafeMath.sub(token0Bought, amountToSwap);

if에서 canSwapFromV2를 통해 경로가 있는지 체크한 후에 만약 존재한다면 swapFromV2를 해서 token0Bought 값을 가져온다. 실제로 스왑을 한 후에 해당 변수에 스왑받은 결과가 들어오게 된다.

 

그 후 amount ToSwap 변수를 지정해준다.

 

이때 calculateSwapInAmount 함수가 호출된다. 

해당 함수는 다음과 같다.

    function calculateSwapInAmount(uint256 reserveIn, uint256 userIn)
        public
        pure
        returns (uint256)
    {
        return
            Babylonian
                .sqrt(
                reserveIn.mul(userIn.mul(3988000) + reserveIn.mul(3988009))
            )
                .sub(reserveIn.mul(1997)) / 1994;
    }

근데 이 공식은... 봐도 모르겠다.

https://ethereum.stackexchange.com/questions/107353/calculation-of-swap-amount-needed-for-adding-liquidity-to-uniswap-v2

 

Calculation of swap amount needed for adding liquidity to Uniswap V2

I'm trying to create a helper in a contract that lets the user deposit a single token and have it internally swap to the other token and add it as liquidity. Lets say there is an LP with ETH and BT...

ethereum.stackexchange.com

구글을 찾아보니 1년전 질문에도 답변이 없다 ㅎㅎㅎ 이건 그냥 패스!

 

패스하고 1번 토큰을 받는다. 대충 절반이라는 생각이 들긴하는데 저 공식을 쓰면.. 더 정확한 예측이 되나보다.

            //if no reserve or a new pair is created
            if (amountToSwap <= 0) amountToSwap = SafeMath.div(token0Bought, 2);
            token1Bought = swapFromV2(
                _ToUnipoolToken0,
                _ToUnipoolToken1,
                amountToSwap
            );
            token0Bought = SafeMath.sub(token0Bought, amountToSwap);

나는 사실 if문 밖에 이해를 못해가ㅔㅆ다. 저건 아무래도 슬리피지를 계산하는 공식 같다. 다만 내가 이해를 하기 너무 난해하고 문서도 없고 힌트도 없을 뿐..

 

아무튼 이게 바로 1번 테이스라고 할 수 있다. 

 

그다음 elseif 구문을 다시보면 

        } else if (
            canSwapFromV2(_FromTokenContractAddress, _ToUnipoolToken1) &&
            canSwapFromV2(_ToUnipoolToken0, _ToUnipoolToken1)
        ) {
            token1Bought = swapFromV2(
                _FromTokenContractAddress,
                _ToUnipoolToken1,
                _amount
            );
            uint256 amountToSwap = calculateSwapInAmount(res1, token1Bought);
            //if no reserve or a new pair is created
            if (amountToSwap <= 0) amountToSwap = SafeMath.div(token1Bought, 2);
            token0Bought = swapFromV2(
                _ToUnipoolToken1,
                _ToUnipoolToken0,
                amountToSwap
            );
            token1Bought = SafeMath.sub(token1Bought, amountToSwap);
        }
    }

V2에서 스왑이 되는지 체크하되 반대 자산을 한꺼번에 넣어서 touni 1,2로 나누는 경우를 여기서 걸러낸다.

만약 걸리게 되면 다시 같은 로직을 사용하여 반대의 작업을 수행한다.

 

3. _performZapIn

zapin을 수행하는걸 보면 

        uint256 token0Bought;
        uint256 token1Bought;

        if (canSwapFromV2(_ToUnipoolToken0, _ToUnipoolToken1)) {
            (token0Bought, token1Bought) = exchangeTokensV2(
                _FromTokenContractAddress,
                _ToUnipoolToken0,
                _ToUnipoolToken1,
                _amount
            );
        } else if (
            canSwapFromV1(_ToUnipoolToken0, _ToUnipoolToken1, _amount, _amount)
        ) {
            (token0Bought, token1Bought) = exchangeTokensV1(
                _FromTokenContractAddress,
                _ToUnipoolToken0,
                _ToUnipoolToken1,
                _amount
            );
        }

        require(token0Bought > 0 && token1Bought > 0, "Could not exchange");

도입부분에서 예외처리를 통하여 V1,V2에서 스왑이 되는지 확인해 준다. (V1은 해당 글에서 다루지 않는다.)

 

그리고 둘다 0이상인 것을 확인해서 둘다 구매가 된 것을 확인 받는다.

구매를 하는 로직은 위에서 볼 수 있듯이 최초 풀이면 절반 아니라면 슬리피지를 고려한 뭔가 복잡한 식 중 하나를 선택하여 실행을 하도록 되어 있다.

 

        IERC20(_ToUnipoolToken0).safeApprove(
            address(uniswapV2Router),
            token0Bought
        );

        IERC20(_ToUnipoolToken1).safeApprove(
            address(uniswapV2Router),
            token1Bought
        );

        (uint256 amountA, uint256 amountB, uint256 LP) = uniswapV2Router
            .addLiquidity(
            _ToUnipoolToken0,
            _ToUnipoolToken1,
            token0Bought,
            token1Bought,
            1,
            1,
            address(this),
            now + 60
        );

        //Reset allowance to zero
        IERC20(_ToUnipoolToken0).safeApprove(address(uniswapV2Router), 0);
        IERC20(_ToUnipoolToken1).safeApprove(address(uniswapV2Router), 0);

그 후에는 safeApprove라는 것을 쓰는데 

구매한 토큰 만큼을 approve 받아서 가지고 있는다.

 

그리고 구매한 토큰에 대해서 예치를 하여 LP 풀을 받아낸다.

그리고 받아낸 approve를 0으로 리셋시킨다.

 

zap이라는 것은 반반 나눈후 예치까지 해줘야 서비스의 완성이라고 할 수 있다.

이에 따라서 나눈후에는 LP풀을 민팅해주는 부분이 아래의 코드이다.

        uint256 residue;
        if (SafeMath.sub(token0Bought, amountA) > 0) {
            if (canSwapFromV2(_ToUnipoolToken0, _FromTokenContractAddress)) {
                residue = swapFromV2(
                    _ToUnipoolToken0,
                    _FromTokenContractAddress,
                    SafeMath.sub(token0Bought, amountA)
                );
            } else {
                IERC20(_ToUnipoolToken0).safeTransfer(
                    _toWhomToIssue,
                    SafeMath.sub(token0Bought, amountA)
                );
            }
        }

        if (SafeMath.sub(token1Bought, amountB) > 0) {
            if (canSwapFromV2(_ToUnipoolToken1, _FromTokenContractAddress)) {
                residue += swapFromV2(
                    _ToUnipoolToken1,
                    _FromTokenContractAddress,
                    SafeMath.sub(token1Bought, amountB)
                );
            } else {
                IERC20(_ToUnipoolToken1).safeTransfer(
                    _toWhomToIssue,
                    SafeMath.sub(token1Bought, amountB)
                );
            }
        }

        if (residue > 0) {
            if (_FromTokenContractAddress != address(0)) {
                IERC20(_FromTokenContractAddress).safeTransfer(
                    _toWhomToIssue,
                    residue
                );
            } else {
                (bool success, ) = _toWhomToIssue.call.value(residue)("");
                require(success, "Residual ETH Transfer failed.");
            }
        }

        return LP;
    }

먼저 residue라는 숫자를 정의한 후에 여기다가 아무래도 잔고를 넣어주는 것같다. 역시 뜻 자체부터 잔여물 이라는 뜻이다.

구매한 토큰을 대상으로 하여 유동성을 공급했던 수치인 AmountA, B를 각각 둬서 실제 유동성 공급을 하려던 숫자와 유동성 공급결과 나온 숫자를 차감하여서 잔여물이 존재하면 원래 입력값으로 넣었던 토큰이 스왑이 되는지 확인한 후에 스왑이 되면 스왑을 해서 보내주고 스왑이 안되면 스왑전에 있는 토큰을 바로 보내준다.

 

그리고 LP를 리턴해주는 로직이다.

 

 

728x90
반응형
Comments