UniswapV2应用案例分析-beefyUniswapZap.sol

// SPDX-License-Identifier: GPLv2

// 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 Wivern for Beefy.Finance
// @notice This contract adds liquidity to Uniswap V2 compatible liquidity pair pools and stake.

pragma solidity >=0.6.2;
//导入Pair接口
import '@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol';
//导入一个平方根计算工具
import '@uniswap/lib/contracts/libraries/Babylonian.sol';

//导入Router02接口 
import 'https://github.com/Uniswap/uniswap-v2-periphery/blob/master/contracts/interfaces/IUniswapV2Router02.sol';

//导入SafeERC20
import 'https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.3.0/contracts/token/ERC20/SafeERC20.sol';
//导入LowGasSafeMath
import 'https://github.com/Uniswap/uniswap-v3-core/blob/main/contracts/libraries/LowGasSafeMath.sol';

//定义一个WETH池的接口
interface IWETH is IERC20 {
    function deposit() external payable;
    function withdraw(uint256 wad) external;
}

//定义一个Beefu池的接口
interface IBeefyVaultV6 is IERC20 {
    function deposit(uint256 amount) external;
    function withdraw(uint256 shares) external;
    function want() external pure returns (address);
}

contract BeefyUniV2Zap {
    using LowGasSafeMath for uint256;
    using SafeERC20 for IERC20;
    using SafeERC20 for IBeefyVaultV6;

    //提供一个不可变的router
    IUniswapV2Router02 public immutable router;
    //提供一个不可变的WETH池地址
    address public immutable WETH;
    //提供一个不可变的最小交易量
    uint256 public constant minimumAmount = 1000;

    //部署合约时需提供一个不可更改的router地址、一个不可更改的WETH池地址
    constructor(address _router, address _WETH) {
        router = IUniswapV2Router02(_router);
        WETH = _WETH;
    }

    //当外界与本合约进行交互,但并未调用函数或传递数据时,receive()被触发,
    //使得本合约可以接受外界转账,但必须保证转账人==WETH池,
    //即不接收非WETH池以外的对象的莫名其妙的转账
    receive() external payable {
        assert(msg.sender == WETH);
    }

    //用户调用该函数,注入ETH,提交其要访问的beefy池地址、最终兑换到的流动性代币的最小量(后台自动为用户提供)
    function beefInETH (address beefyVault, uint256 tokenAmountOutMin) external payable {
        //保证用户注入的ETH>=最小交易量
        require(msg.value >= minimumAmount, 'Beefy: Insignificant input amount');

        //将ETH注入WETH池,兑换WETH
        IWETH(WETH).deposit{value: msg.value}();

        //提交用户想要访问beefy池地址、最终兑换到的流动性代币的最小量、用户注入的币种(在这里自动兑换成了WETH)
        _swapAndStake(beefyVault, tokenAmountOutMin, WETH);
    }

    //功能同上,用户注入的代币为任何REC20代币
    function beefIn (address beefyVault, uint256 tokenAmountOutMin, address tokenIn, uint256 tokenInAmount) external {
        require(tokenInAmount >= minimumAmount, 'Beefy: Insignificant input amount');
        //allowance()得到A地址授权给B地址的token使用量
        //保证用户授权给本合约的tokenIn使用量>=用户想要注入的量
        require(IERC20(tokenIn).allowance(msg.sender, address(this)) >= tokenInAmount, 'Beefy: Input token is not approved');

        //把tokenIn从用户手里转移到本合约中
        IERC20(tokenIn).safeTransferFrom(msg.sender, address(this), tokenInAmount);

        //进行swap和注入
        _swapAndStake(beefyVault, tokenAmountOutMin, tokenIn);
    }

    //用户提取资产,提交想要互动的beefy池地址、想要撤出的资产量
    function beefOut (address beefyVault, uint256 withdrawAmount) external {
        //获取beefy池对象、pair
        (IBeefyVaultV6 vault, IUniswapV2Pair pair) = _getVaultPair(beefyVault);

        //把用户的beefy池代币转入本合约
        IERC20(beefyVault).safeTransferFrom(msg.sender, address(this), withdrawAmount);
        //向beefy池申请撤资(提交beefy池代币,换回原本资产,即流动性代币)
        vault.withdraw(withdrawAmount);

        //如果交易对中没有WETH,则直接提取流动性,接收地址直接设为用户地址
        if (pair.token0() != WETH && pair.token1() != WETH) {
            return _removeLiqudity(address(pair), msg.sender);
        }

        //若交易对中有ETH,则需要将资产传回本地后,通过_returnAssets把资产传回给用户,
        //因为其有处理WETH和ETH兑换的步骤
        _removeLiqudity(address(pair), address(this));

        address[] memory tokens = new address[](2);
        tokens[0] = pair.token0();
        tokens[1] = pair.token1();

        _returnAssets(tokens);
    }

    //在用户提取资产的基础上,增加了一个把两种代币换回成一种的操作
    //提及用户想要互动的beefy池、撤资额、想要撤资的token、想要撤资的token交易最小单位
    function beefOutAndSwap(address beefyVault, uint256 withdrawAmount, address desiredToken, uint256 desiredTokenOutMin) external {
        //获取beefy池对象,获取beefy池的pair交易对
        (IBeefyVaultV6 vault, IUniswapV2Pair pair) = _getVaultPair(beefyVault);
        //获取代币地址
        address token0 = pair.token0();
        address token1 = pair.token1();
        //保证交易对中的代币有用户想要兑换的币种
        require(token0 == desiredToken || token1 == desiredToken, 'Beefy: desired token not present in liqudity pair');

        //把用户的vault代币转进本合约
        vault.safeTransferFrom(msg.sender, address(this), withdrawAmount);
        //撤资,取回流动性代币
        vault.withdraw(withdrawAmount);
        //兑换流动性代币,接收到的流动性存在本合约
        _removeLiqudity(address(pair), address(this));

        //获取想要兑换掉的代币地址
        address swapToken = token1 == desiredToken ? token0 : token1;
        address[] memory path = new address[](2);
        //兑换链上第一个为想要兑换掉的代币,第二个为用户想要得到的
        path[0] = swapToken;
        path[1] = desiredToken;

        //授权router可以调用本合约的swapToken(将被换掉的token)
        _approveTokenIfNeeded(path[0], address(router));
        //在router提供的池子里兑换掉
        router.swapExactTokensForTokens(IERC20(swapToken).balanceOf(address(this)), desiredTokenOutMin, path, address(this), block.timestamp);

        //把资金传给用户
        _returnAssets(path);
    }

    //提交交易对、收币者地址,提取流动性
    function _removeLiqudity(address pair, address to) private {
        //把本合约持有的pair币(流动性代币)余额的全部量转给pair池子
        IERC20(pair).safeTransfer(pair, IERC20(pair).balanceOf(address(this)));
        //调用Pair.burn(to),把pair池子中收到的流动性代币烧掉,同时给to发放兑换出的流动性
        (uint256 amount0, uint256 amount1) = IUniswapV2Pair(pair).burn(to);

        //保证兑换出的流动性量>最小交易额,此时验证是否晚了?待考虑
        require(amount0 >= minimumAmount, 'UniswapV2Router: INSUFFICIENT_A_AMOUNT');
        require(amount1 >= minimumAmount, 'UniswapV2Router: INSUFFICIENT_B_AMOUNT');
    }

    //提交一个beefy交易池地址,获取beefy池对象,及一个交易对
    function _getVaultPair (address beefyVault) private view returns (IBeefyVaultV6 vault, IUniswapV2Pair pair) {
        //将beefy交易池地址转化为VaultV6类型,即为beefy池对象
        vault = IBeefyVaultV6(beefyVault);
        //通过want()获取到beefy交易池对象的交易对
        pair = IUniswapV2Pair(vault.want());
        //保证pair的工厂==router的工厂,
        //即确保beefy池子提供的交易对服务与其调用的router的交易对对应的上
        require(pair.factory() == router.factory(), 'Beefy: Incompatible liquidity pair factory');
    }

    //兑换代币并注入router提供的de-fi池
    function _swapAndStake(address beefyVault, uint256 tokenAmountOutMin, address tokenIn) private {
        //给_getVaultPair()提供一个beefy池地址,获取到一个beefy池对象、一个交易对
        (IBeefyVaultV6 vault, IUniswapV2Pair pair) = _getVaultPair(beefyVault);

        //调用UniswapV2Pair的getReserves(),获得交易对中两种代币的储备量
        (uint256 reserveA, uint256 reserveB,) = pair.getReserves();
        //保证两种代币的储备量>最小交易量
        require(reserveA > minimumAmount && reserveB > minimumAmount, 'Beefy: Liquidity pair reserves too low');

        //若交易对中的第一个代币类型==调用函数时用户提交的想要注入的代币类型,则将isInputA标记为true
        bool isInputA = pair.token0() == tokenIn;
        //保证isInputA为真,或者交易对中的第二个代币类型==调用函数时用户提交的想要注入的代币类型,
        //即保证了tokenIn要么为token0,要么为token1
        require(isInputA || pair.token1() == tokenIn, 'Beefy: Input token not present in liqudity pair');

        //创建一个用于存放交易链的数列
        address[] memory path = new address[](2);
        //交易链中的第一个坑放入用户注入的代币
        path[0] = tokenIn;
        //交易链中的第二个坑放入另外一种代币
        path[1] = isInputA ? pair.token1() : pair.token0();

        //该参数存放用户的总投资量,
        uint256 fullInvestment = IERC20(tokenIn).balanceOf(address(this));
        uint256 swapAmountIn;
        //若token0为用户注入的资产,则调用函数时提交参数的顺序为reserveA在前
        //通过_getSwapAmount()函数,提交注入单一币数量,两种代币储备量,得到应该用多少单一币先换取另一种币
        if (isInputA) {
            swapAmountIn = _getSwapAmount(fullInvestment, reserveA, reserveB);
        } else {
            swapAmountIn = _getSwapAmount(fullInvestment, reserveB, reserveA);
        }

        //授权router可以调用本合约的token0,此时本合约的池子中只有用户提交的token0
        _approveTokenIfNeeded(path[0], address(router));
        //定义一个交换量数列
        //swap函数的功能为,计算以一种明确的token能换取另一种token的量,并执行该交易,
        //可以链式交易,所以返回值为一个数列
        //提交的参数为注入token0的数量、取出token1的最小量、交易链、收币地址、交易最晚时间
        uint256[] memory swapedAmounts = router
            .swapExactTokensForTokens(swapAmountIn, tokenAmountOutMin, path, address(this), block.timestamp);

        //授权router可以调用本合约的token1,此时本合约的池子中已有兑换来的token1
        _approveTokenIfNeeded(path[1], address(router));
        //addLiquidity的功能为提交token0、1地址,提交A、B期望投入量,提交A、B最小交易量,提交收币地址、deadline
        //返回值为交易的A、B量,及兑换到的流动性代币数量
        //amountLiquidity记录的是成交后,用户得到的流动性代币数量,这里的用户为本合约(address(this))
        (,, uint256 amountLiquidity) = router
            .addLiquidity(path[0], path[1], fullInvestment.sub(swapedAmounts[0]), swapedAmounts[1], 1, 1, address(this), block.timestamp);

        //授权vault(beefy池对象)可以调用交易对
        _approveTokenIfNeeded(address(pair), address(vault));
        //beefy池中注入流动性代币数量的beefy池中定义的币
        vault.deposit(amountLiquidity);

        //把在vault池中的vault币转给用户,转账量为本合约在vault池中的全部余额
        vault.safeTransfer(msg.sender, vault.balanceOf(address(this)));
        //把余出来的token0、1退还用户
        _returnAssets(path);
    }

    //提交一个token数列,
    function _returnAssets(address[] memory tokens) private {
        uint256 balance;
        //正序依次读取tokens[]
        for (uint256 i; i < tokens.length; i++) {
            //读取本合约的tokens[i]币的余额
            balance = IERC20(tokens[i]).balanceOf(address(this));
            //如果有余额
            if (balance > 0) {
                //如果该币为WETH
                if (tokens[i] == WETH) {
                    //从WETH池中取出全部余额
                    IWETH(WETH).withdraw(balance);
                    //把全部ETH转给用户
                    (bool success,) = msg.sender.call{value: balance}(new bytes(0));
                    //保证转账成功
                    require(success, 'Beefy: ETH transfer failed');
                } else {
                    //如果该币不为WETH,直接转给用户
                    IERC20(tokens[i]).safeTransfer(msg.sender, balance);
                }
            }
        }
    }

    //提交投入A的量、AB在池子中的储备量,得到换得的量
    function _getSwapAmount(uint256 investmentA, uint256 reserveA, uint256 reserveB) private view returns (uint256 swapAmount) {
        uint256 halfInvestment = investmentA / 2;
        //getAmountOut()函数为V2Router借用的V2Library中的函数,
        //其功能为提交Atoken注入量、A储备量、B储备量,得到能兑换出的Btoken量
        //中间参数 = halfInvestment能兑换出的B的最大量
        uint256 nominator = router.getAmountOut(halfInvestment, reserveA, reserveB);
        //quote()函数同样为V2Router借用的V2Library中的函数
        //其功能为提交A期望的交易量、A储备量、B储备量,得到与A等价的B的数量
        //分母 = halfInvestment数量的tokenA,在提交到资产量为reserveA + halfInvestment,reserveB - nominator的
        //交易对池子之后,能够换回多少等量的tokenB
        uint256 denominator = router.quote(halfInvestment, reserveA.add(halfInvestment), reserveB.sub(nominator));
        //应该拿多少A换B = tokenA的投入量 - 开方(halfInvestment * halfInvestment * 一半A能兑换出的B最大量 / 在调整后的池子中一半A等于多少B)
        //计算细节待研究
        swapAmount = investmentA.sub(Babylonian.sqrt(halfInvestment * halfInvestment * nominator / denominator));
    }

    //对交换进行估计
    //提交想要互动的beefy池、注入的token地址、全部投资额,
    //返回注入的交换量、得到的交换量、得到的token地址
    function estimateSwap(address beefyVault, address tokenIn, uint256 fullInvestmentIn) public view returns(uint256 swapAmountIn, uint256 swapAmountOut, address swapTokenOut) {
        //检查WETH池
        checkWETH();
        //获取交易对
        (, IUniswapV2Pair pair) = _getVaultPair(beefyVault);

        bool isInputA = pair.token0() == tokenIn;
        require(isInputA || pair.token1() == tokenIn, 'Beefy: Input token not present in liqudity pair');

        (uint256 reserveA, uint256 reserveB,) = pair.getReserves();
        (reserveA, reserveB) = isInputA ? (reserveA, reserveB) : (reserveB, reserveA);

        swapAmountIn = _getSwapAmount(fullInvestmentIn, reserveA, reserveB);
        swapAmountOut = router.getAmountOut(swapAmountIn, reserveA, reserveB);
        swapTokenOut = isInputA ? pair.token1() : pair.token0();
    }

    //检查WETH兑换池是否有效
    function checkWETH() public view returns (bool isValid) {
        //若WETH池为router提供的WETH,则isValid为有效(true)
        isValid = WETH == router.WETH();
        //保证isValid为有效
        require(isValid, 'Beefy: WETH address not matching Router.WETH()');
    }

    //授权token,如果需要的话
    //提交token地址、调用token的用户地址
    function _approveTokenIfNeeded(address token, address spender) private {
        //如果本合约未授权spender调用token
        if (IERC20(token).allowance(address(this), spender) == 0) {
            //则授权spender使用本合约的token
            IERC20(token).safeApprove(spender, uint256(~0));
        }
    }

}
 

上一篇:智能合约实战 solidity 语法学习 10 [ 以太坊 ether ERC20标准API介绍及示例 ]name symbol decimals totalSupply balanceOf...


下一篇:发行自己的以太坊ERC20代币---超级详细