8e55

约 2302 字大约 8 分钟

8e55

代码解析

// SPDX-License-Identifier: AGPL-3.0
pragma solidity ^0.8.0;

import "openzeppelin/token/ERC20/IERC20.sol";
import "openzeppelin/access/Ownable.sol";
import "openzeppelin/utils/math/SafeMath.sol";
import "aave/core-v3/contracts/interfaces/IPoolAddressesProvider.sol";
import "aave/core-v3/contracts/flashloan/base/FlashLoanSimpleReceiverBase.sol";


interface IWETH is IERC20 {
    // weth存款
    function deposit() external payable;
    // 取款
    function withdraw(uint) external;
}

interface IUniswapV2Pair {
    // 获取代币对
    function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast);
    // 进行swap
    function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external;
    function token0() external view returns (address);
    function token1() external view returns (address);
}

// 代币对数据
interface IPairReserves{
     struct PairReserves {
        uint256 reserve0;
        uint256 reserve1;
        uint256 price;
        bool isWETHZero;
    }
}

// 闪电贷池子
interface ILendingPool {
    function flashLoan (
        address _receiver,
        address[] memory _assets,
        uint256[] memory _amounts,
        uint256[] memory _modes,
        address _onBehalfOf,
        bytes memory _params,
        uint16 _referralCode) external;
}

contract BlindBackrun is Ownable, FlashLoanSimpleReceiverBase {
    using SafeMath for uint256;
    uint256 uniswappyFee = 997;
    ILendingPool public lendingPool;
    address public immutable WETH_ADDRESS;
    // 闪电贷池子
    constructor(address _wethAddress, address _addressProvider)
        Ownable()
        FlashLoanSimpleReceiverBase(IPoolAddressesProvider(_addressProvider))
    {
        WETH_ADDRESS = _wethAddress;
        lendingPool = ILendingPool(IPoolAddressesProvider(_addressProvider).getPool());
    }
    // 执行闪电贷
    // Function to execute a flash loan
    function executeFlashLoan(
        address _asset,
        uint256 _amount,
        address firstPairAddress,
        address secondPairAddress,
        uint percentageToPayToCoinbase
    ) external onlyOwner {
        // 接收贷款地址
        address receiverAddress = address(this);
        // 贷款的token
        address[] memory assets = new address[](1);
        assets[0] = _asset;
        // 贷款数量
        uint256[] memory amounts = new uint256[](1);
        amounts[0] = _amount;
        // 贷款模式
        uint256[] memory modes = new uint256[](1);
        modes[0] = 0; // 0 = no debt, 1 = stable, 2 = variable

        address onBehalfOf = address(this);
        // 对地址进行加密
        bytes memory params = abi.encode(firstPairAddress, secondPairAddress, percentageToPayToCoinbase);
        uint16 referralCode = 0;

        lendingPool.flashLoan(
            receiverAddress,
            assets,
            amounts,
            modes,
            onBehalfOf,
            params,
            referralCode
        );
    }

    // Override this function from FlashLoanSimpleReceiverBase to specify what happens when you receive the flash loan
    function executeOperation(
        address asset,
        uint256 amount,
        uint256 premium,
        address initiator,
        bytes calldata params
    ) external override returns (bool) {
        // Ensure the loan is for the correct asset and is sent from the LendingPool
        // 确保贷款从正确的token的池子
        require(msg.sender == address(lendingPool), "Invalid sender");
        // 保证池子数量大于贷款数量
        require(IERC20(asset).balanceOf(address(this)) >= amount, "Did not receive loan");

        // Decode the params and execute the arbitrage
        // 解密获取两个池子地址
        (address firstPairAddress, address secondPairAddress, uint percentageToPayToCoinbase) = abi.decode(params, (address, address, uint));
        executeArbitrage(firstPairAddress, secondPairAddress, percentageToPayToCoinbase);
         // 计算还款
        // Calculate amount to repay, which is the loan amount plus the premium
        uint256 totalDebt = amount.add(premium);
        // 还款给池子
        // Transfer funds back to repay the flash loan
        require(IERC20(asset).transferFrom(address(this), address(lendingPool), totalDebt), "Failed to repay loan");

        return true;
    }

    /// @notice Executes an arbitrage transaction between two Uniswap V2 pairs.
    /// @notice Pair addresses need to be computed off-chain.
    /// @dev Only the contract owner can call this function.
    /// @param firstPairAddress Address of the first Uniswap V2 pair.
    /// @param secondPairAddress Address of the second Uniswap V2 pair.
    function executeArbitrage(
        address firstPairAddress,
        address secondPairAddress,
        uint percentageToPayToCoinbase
    ) internal {
        // 获取到之前有多少weth
        uint256 balanceBefore = IERC20(WETH_ADDRESS).balanceOf(address(this));
        // 第一个池子
        IUniswapV2Pair firstPair = IUniswapV2Pair(firstPairAddress);
        // 第二个池子
        IUniswapV2Pair secondPair = IUniswapV2Pair(secondPairAddress);
        // 池子数据
        IPairReserves.PairReserves memory firstPairData = getPairData(firstPair);
        IPairReserves.PairReserves memory secondPairData = getPairData(secondPair);

        // 获取借贷数量
        uint256 amountIn = getAmountIn(firstPairData, secondPairData);
        // 转移给第一个池子特定数量
        IERC20(WETH_ADDRESS).transfer(firstPairAddress, amountIn);

        uint256 firstPairAmountOut;
        uint256 finalAmountOut;
        if (firstPairData.isWETHZero == true){
            // 获取可以换出来的数量
            firstPairAmountOut = getAmountOut(amountIn, firstPairData.reserve0, firstPairData.reserve1);
            finalAmountOut = getAmountOut(firstPairAmountOut, secondPairData.reserve1, secondPairData.reserve0);

            firstPair.swap(0, firstPairAmountOut, secondPairAddress, "");
            secondPair.swap(finalAmountOut, 0, address(this), "");
        } else {
            firstPairAmountOut = getAmountOut(amountIn, firstPairData.reserve1, firstPairData.reserve0);
            finalAmountOut = getAmountOut(firstPairAmountOut, secondPairData.reserve0, secondPairData.reserve1);

            firstPair.swap(firstPairAmountOut, 0, secondPairAddress, "");
            secondPair.swap(0, finalAmountOut, address(this), "");
        }
        // 获取套利后的金额
        uint256 balanceAfter = IERC20(WETH_ADDRESS).balanceOf(address(this));
        // 保证赚钱了
        require(balanceAfter > balanceBefore, "Arbitrage failed");
        // 赚钱比例
        uint profit = balanceAfter.sub(balanceBefore);
        // 赚钱比例给矿工
        uint profitToCoinbase = profit.mul(percentageToPayToCoinbase).div(100);
        // 提款给矿工
        IWETH(WETH_ADDRESS).withdraw(profitToCoinbase);
        // 转账给矿工
        block.coinbase.transfer(profitToCoinbase);
    }

    /// @notice Calculates the required input amount for the arbitrage transaction.
    /// @param firstPairData Struct containing data about the first Uniswap V2 pair.
    /// @param secondPairData Struct containing data about the second Uniswap V2 pair.
    /// @return amountIn, the optimal amount to trade to arbitrage two v2 pairs.
    function getAmountIn(
        IPairReserves.PairReserves memory firstPairData,
        IPairReserves.PairReserves memory secondPairData
    ) public returns (uint256) {
        uint256 numerator = getNumerator(firstPairData, secondPairData);
        uint256 denominator = getDenominator(firstPairData, secondPairData);

        uint256 amountIn =
            numerator
            .mul(1000)
            .div(denominator);

        return amountIn;
    }

    function getNumerator(
        IPairReserves.PairReserves memory firstPairData,
        IPairReserves.PairReserves memory secondPairData
    ) public view returns (uint256) {
        if (firstPairData.isWETHZero == true) {
            uint presqrt =
                uniswappyFee
                    .mul(uniswappyFee)
                    .mul(firstPairData.reserve1)
                    .mul(secondPairData.reserve0)
                    .div(secondPairData.reserve1)
                    .div(firstPairData.reserve0);

            uint256 numerator =
            (
                sqrt(presqrt)
                .sub(1e3)
            )
            .mul(secondPairData.reserve1)
            .mul(firstPairData.reserve0);

            return numerator;
        } else {
            uint presqrt =
                uniswappyFee
                    .mul(uniswappyFee)
                    .mul(firstPairData.reserve0)
                    .mul(secondPairData.reserve1)
                    .div(secondPairData.reserve0)
                    .div(firstPairData.reserve1);

            uint256 numerator =
            (
                sqrt(presqrt)
                .sub(1e3)
            )
            .mul(secondPairData.reserve0)
            .mul(firstPairData.reserve1);

            return numerator;
        }
    }

    // 这个算法是什么?
    function getDenominator(
            IPairReserves.PairReserves memory firstPairData,
            IPairReserves.PairReserves memory secondPairData
        ) public returns (uint256){
        if (firstPairData.isWETHZero == true) {
            uint256 denominator =
                (
                    uniswappyFee
                    .mul(secondPairData.reserve1)
                    .mul(1000)
                )
                .add(
                    uniswappyFee
                    .mul(uniswappyFee)
                    .mul(firstPairData.reserve1)
                );
            return denominator;
        } else {
            uint256 denominator =
                (
                    uniswappyFee
                    .mul(secondPairData.reserve0)
                    .mul(1000)
                )
                .add(
                    uniswappyFee
                    .mul(uniswappyFee)
                    .mul(firstPairData.reserve0)
                );
            return denominator;
        }
    }

    /// @notice Retrieves price and reserve data for a given Uniswap V2 pair. Also checks which token is WETH.
    /// @param pair The Uniswap V2 pair to retrieve data for.
    /// @return A PairReserves struct containing price and reserve data for the given pair.
    // 获取代币对数据
    function getPairData(IUniswapV2Pair pair) private view returns (IPairReserves.PairReserves memory) {
        (uint256 reserve0, uint256 reserve1, ) = pair.getReserves();
        uint256 price;

        bool isWETHZero = false;
        if (pair.token0() == WETH_ADDRESS) {
            price = reserve1.mul(1e18).div(reserve0);
            isWETHZero = true;
        } else {
            price = reserve0.mul(1e18).div(reserve1);
        }

        return IPairReserves.PairReserves(reserve0, reserve1, price, isWETHZero);
    }

    /// @notice Calculates the square root of a given number.
    /// @param x: The number to calculate the square root of.
    /// @return y: The square root of the given number.
    function sqrt(uint256 x) private pure returns (uint256) {
        if (x == 0) return 0;
        uint256 z = x.add(1).div(2);
        uint256 y = x;
        while (z < y) {
            y = z;
            z = ((x.div(z)).add(z)).div(2);
        }
        return y;
    }

     // 计算可以获得的代币,后续研究下算法
    /// @notice Calculates the output amount for a given input amount and reserves.
    /// @param amountIn The input amount.
    /// @param reserveIn The reserve of the input token.
    /// @param reserveOut The reserve of the output token.
    /// @return amountOut The output amount.
    function getAmountOut(uint amountIn,
        uint reserveIn,
        uint reserveOut
    ) internal pure returns (uint amountOut) {
        // todo后续研究下白皮书,这里有重要算法
        uint amountInWithFee = amountIn.mul(997);
        uint numerator = amountInWithFee.mul(reserveOut);
        uint denominator = reserveIn.mul(1000).add(amountInWithFee);
        amountOut = numerator / denominator;
        return amountOut;
    }

    /// @notice Transfers all WETH held by the contract to the contract owner.
    /// @dev Only the contract owner can call this function.
    // 提款
    function withdrawWETHToOwner() external onlyOwner {
        uint256 balance = IERC20(WETH_ADDRESS).balanceOf(address(this));
        IERC20(WETH_ADDRESS).transfer(msg.sender, balance);
    }

    /// @notice Transfers all ETH held by the contract to the contract owner.
    /// @dev Only the contract owner can call this function.
    // 提款
    function withdrawETHToOwner() external onlyOwner {
        uint256 balance = address(this).balance;
        payable(msg.sender).transfer(balance);
    }

    /// @notice Executes a call to another contract with the provided data and value.
    /// @dev Only the contract owner can call this function.
    /// @dev Reverted calls will result in a revert.
    /// @param _to The address of the contract to call.
    /// @param _value The amount of Ether to send with the call.
    /// @param _data The calldata to send with the call.
    // 转账辅助函数
    function call(address payable _to, uint256 _value, bytes memory _data) external onlyOwner {
        (bool success, ) = _to.call{value: _value}(_data);
        require(success, "External call failed");
    }

    // 可以接受eth
    /// @notice Fallback function that allows the contract to receive Ether.
    receive() external payable {}
}

移除Flashloan

代码与上面一致,移除闪电贷。

pragma solidity ^0.8.0;

import "openzeppelin/token/ERC20/IERC20.sol";
import "openzeppelin/access/Ownable.sol";
import "openzeppelin/utils/math/SafeMath.sol";

interface IWETH is IERC20 {
    function deposit() external payable;
    function withdraw(uint) external;
}

interface IUniswapV2Pair {
    function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast);
    function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external;
    function token0() external view returns (address);
    function token1() external view returns (address);
}

interface IPairReserves{
     struct PairReserves {
        uint256 reserve0;
        uint256 reserve1;
        uint256 price;
        bool isWETHZero;
    }
}

contract BlindBackrun is Ownable {
    using SafeMath for uint256;
    uint256 uniswappyFee = 997;

    address public immutable WETH_ADDRESS;

    constructor(address _wethAddress) {
        WETH_ADDRESS = _wethAddress;
    }

    /// @notice Executes an arbitrage transaction between two Uniswap V2 pairs.
    /// @notice Pair addresses need to be computed off-chain.
    /// @dev Only the contract owner can call this function.
    /// @param firstPairAddress Address of the first Uniswap V2 pair.
    /// @param secondPairAddress Address of the second Uniswap V2 pair.
    function executeArbitrage(
        address firstPairAddress,
        address secondPairAddress,
        uint percentageToPayToCoinbase
    ) external onlyOwner {
        uint256 balanceBefore = IERC20(WETH_ADDRESS).balanceOf(address(this));
        IUniswapV2Pair firstPair = IUniswapV2Pair(firstPairAddress);
        IUniswapV2Pair secondPair = IUniswapV2Pair(secondPairAddress);

        IPairReserves.PairReserves memory firstPairData = getPairData(firstPair);
        IPairReserves.PairReserves memory secondPairData = getPairData(secondPair);


        uint256 amountIn = getAmountIn(firstPairData, secondPairData);
        IERC20(WETH_ADDRESS).transfer(firstPairAddress, amountIn);

        uint256 firstPairAmountOut;
        uint256 finalAmountOut;
        if (firstPairData.isWETHZero == true){
            firstPairAmountOut = getAmountOut(amountIn, firstPairData.reserve0, firstPairData.reserve1);
            finalAmountOut = getAmountOut(firstPairAmountOut, secondPairData.reserve1, secondPairData.reserve0);

            firstPair.swap(0, firstPairAmountOut, secondPairAddress, "");
            secondPair.swap(finalAmountOut, 0, address(this), "");
        } else {
            firstPairAmountOut = getAmountOut(amountIn, firstPairData.reserve1, firstPairData.reserve0);
            finalAmountOut = getAmountOut(firstPairAmountOut, secondPairData.reserve0, secondPairData.reserve1);

            firstPair.swap(firstPairAmountOut, 0, secondPairAddress, "");
            secondPair.swap(0, finalAmountOut, address(this), "");
        }

        uint256 balanceAfter = IERC20(WETH_ADDRESS).balanceOf(address(this));
        require(balanceAfter > balanceBefore, "Arbitrage failed");
        // 赚的钱
        uint profit = balanceAfter.sub(balanceBefore);
        // 小费
        uint profitToCoinbase = profit.mul(percentageToPayToCoinbase).div(100);
        // 提取矿工的钱
        // 这里可以优化 预留一些金额
        IWETH(WETH_ADDRESS).withdraw(profitToCoinbase);
        // 给矿工转账
        block.coinbase.transfer(profitToCoinbase);
    }

    /// @notice Calculates the required input amount for the arbitrage transaction.
    /// @param firstPairData Struct containing data about the first Uniswap V2 pair.
    /// @param secondPairData Struct containing data about the second Uniswap V2 pair.
    /// @return amountIn, the optimal amount to trade to arbitrage two v2 pairs.
    function getAmountIn(
        IPairReserves.PairReserves memory firstPairData,
        IPairReserves.PairReserves memory secondPairData
    ) public returns (uint256) {
        uint256 numerator = getNumerator(firstPairData, secondPairData);
        uint256 denominator = getDenominator(firstPairData, secondPairData);

        uint256 amountIn =
            numerator
            .mul(1000)
            .div(denominator);

        return amountIn;
    }

    function getNumerator(
        IPairReserves.PairReserves memory firstPairData,
        IPairReserves.PairReserves memory secondPairData
    ) public view returns (uint256) {
        if (firstPairData.isWETHZero == true) {
            uint presqrt =
                uniswappyFee
                    .mul(uniswappyFee)
                    .mul(firstPairData.reserve1)
                    .mul(secondPairData.reserve0)
                    .div(secondPairData.reserve1)
                    .div(firstPairData.reserve0);

            uint256 numerator =
            (
                sqrt(presqrt)
                .sub(1e3)
            )
            .mul(secondPairData.reserve1)
            .mul(firstPairData.reserve0);

            return numerator;
        } else {
            uint presqrt =
                uniswappyFee
                    .mul(uniswappyFee)
                    .mul(firstPairData.reserve0)
                    .mul(secondPairData.reserve1)
                    .div(secondPairData.reserve0)
                    .div(firstPairData.reserve1);

            uint256 numerator =
            (
                sqrt(presqrt)
                .sub(1e3)
            )
            .mul(secondPairData.reserve0)
            .mul(firstPairData.reserve1);

            return numerator;
        }
    }

    function getDenominator(
            IPairReserves.PairReserves memory firstPairData,
            IPairReserves.PairReserves memory secondPairData
        ) public returns (uint256){
        if (firstPairData.isWETHZero == true) {
            uint256 denominator =
                (
                    uniswappyFee
                    .mul(secondPairData.reserve1)
                    .mul(1000)
                )
                .add(
                    uniswappyFee
                    .mul(uniswappyFee)
                    .mul(firstPairData.reserve1)
                );
            return denominator;
        } else {
            uint256 denominator =
                (
                    uniswappyFee
                    .mul(secondPairData.reserve0)
                    .mul(1000)
                )
                .add(
                    uniswappyFee
                    .mul(uniswappyFee)
                    .mul(firstPairData.reserve0)
                );
            return denominator;
        }
    }

    /// @notice Retrieves price and reserve data for a given Uniswap V2 pair. Also checks which token is WETH.
    /// @param pair The Uniswap V2 pair to retrieve data for.
    /// @return A PairReserves struct containing price and reserve data for the given pair.
    // 获取池子数据
    function getPairData(IUniswapV2Pair pair) private view returns (IPairReserves.PairReserves memory) {
        (uint256 reserve0, uint256 reserve1, ) = pair.getReserves();
        uint256 price;

        bool isWETHZero = false;
        if (pair.token0() == WETH_ADDRESS) {
            price = reserve1.mul(1e18).div(reserve0);
            isWETHZero = true;
        } else {
            price = reserve0.mul(1e18).div(reserve1);
        }

        return IPairReserves.PairReserves(reserve0, reserve1, price, isWETHZero);
    }

    /// @notice Calculates the square root of a given number.
    /// @param x: The number to calculate the square root of.
    /// @return y: The square root of the given number.
    function sqrt(uint256 x) private pure returns (uint256) {
        if (x == 0) return 0;
        uint256 z = x.add(1).div(2);
        uint256 y = x;
        while (z < y) {
            y = z;
            z = ((x.div(z)).add(z)).div(2);
        }
        return y;
    }

    /// @notice Calculates the output amount for a given input amount and reserves.
    /// @param amountIn The input amount.
    /// @param reserveIn The reserve of the input token.
    /// @param reserveOut The reserve of the output token.
    /// @return amountOut The output amount.
    // 计算出代币的出量
    function getAmountOut(uint amountIn,
        uint reserveIn,
        uint reserveOut
    ) internal pure returns (uint amountOut) {
        uint amountInWithFee = amountIn.mul(997);
        uint numerator = amountInWithFee.mul(reserveOut);
        uint denominator = reserveIn.mul(1000).add(amountInWithFee);
        amountOut = numerator / denominator;
        return amountOut;
    }

    /// @notice Transfers all WETH held by the contract to the contract owner.
    /// @dev Only the contract owner can call this function.
    // 提款给weth给owner
    function withdrawWETHToOwner() external onlyOwner {
        uint256 balance = IERC20(WETH_ADDRESS).balanceOf(address(this));
        IERC20(WETH_ADDRESS).transfer(msg.sender, balance);
    }

    /// @notice Transfers all ETH held by the contract to the contract owner.
    /// @dev Only the contract owner can call this function.
    // 提款给合同所有
    function withdrawETHToOwner() external onlyOwner {
        uint256 balance = address(this).balance;
        payable(msg.sender).transfer(balance);
    }

    /// @notice Executes a call to another contract with the provided data and value.
    /// @dev Only the contract owner can call this function.
    /// @dev Reverted calls will result in a revert.
    /// @param _to The address of the contract to call.
    /// @param _value The amount of Ether to send with the call.
    /// @param _data The calldata to send with the call.
    // 调用其他合同允许提供数据
    function call(address payable _to, uint256 _value, bytes memory _data) external onlyOwner {
        (bool success, ) = _to.call{value: _value}(_data);
        require(success, "External call failed");
    }

    // 允许接收以太坊
    /// @notice Fallback function that allows the contract to receive Ether.
    receive() external payable {}
}

总结

  • 闪电贷款
  • 两个v2版本进行套利
  • 如何计算利润最大,后续研究下算法