dex的Swap操作

想快速换钱实现兑换。只能够在去中心化交易所(DEX)进行。

在这里鄙视一下GATEIO,入账最少半个小时以上,黄花菜都凉了~

现在排名前几的DEX有uni,sushi,curve。我们先选uni和curve。

说真的,curve官网文档配色真的是反人类。

https://curve.readthedocs.io/exchange-pools.html#exchange-pools-plain

还有uniswap。

https://docs.uniswap.org/protocol/guides/flash-integrations/inheritance-constructors

开始看他们的调用示例来决定我们的合约代码应该需要怎么写,扩展参数应该有哪些!

找到他们的池子合约地址。

curve的有两个:

0xd51a44d3fae010294c616388b506acda1bfaae46 0x80466c64868e1ab14a1ddf27a676c3fcbe638fe5

etherscan.io
etherscan.io

点看查看源码,发现又臭又长。决定放弃阅读。结合文档,拿到exchange的相关信息,然后定义外部接口

//Curve合约接口
interface ICurveSwap {
    /**
    fromIndex,要发送代币的索引值
    toIndex,要接收代币的索引值
    fromAmount,需要兑换的代币数量
    dyAmount,获得的最小代币数量
    **/
    function exchange(uint256 fromIndex, uint256 toIndex, uint256 fromAmount, uint256 dyAmount) external payable;
    /**
    计算获得的最小代币数量
   fromIndex,要发送代币的索引值
   toIndex,要接收代币的索引值
   fromAmount,需要兑换的代币数量
   **/
    function get_dy(uint256 fromIndex, uint256 toIndex, uint256 fromAmount) external view returns (uint256);
}

接下来是uniswap,同样找到相关合约地址:

0x4e68ccd3e89f51c3074ca5072bbac773960dfa36

info.uniswap.org
info.uniswap.org

记住,我们找的是ETH/USDT的。

https://docs.uniswap.org/protocol/reference/core/UniswapV3Pool

关于文档,uniswap真的比curve好太多太多了。

interface IUniSwap {

    /** recipient 接收地址
        zeroForOne 交换的方向,token0->token1为 true,token1->token0为 false
        amountSpecified 交换的数量,数值可正可负
        sqrtPriceLimitX96 价格平方根的Q64.96,如果是token0/token1方向的兑换,价格不能低于该参数。 如果是token1/token0方向,则不能大于该参数。
        data 回调函数参数
    **/
    function swap(
        address recipient,
        bool zeroForOne,
        int256 amountSpecified,
        uint160 sqrtPriceLimitX96,
        bytes data
    ) external returns (int256 amount0, int256 amount1);
}

说实话,看官方解释,看得我一脸懵逼。sqrtPriceLimitX96这个参数太难理解了。其实就是xian价的意思。设置为0,则忽略。

返回值暂时不用管。

开始动手编写curve的兑换入口。

function curveSwap(uint256 tokenIn, address tokenInAddress, uint256 tokenOut, uint256 amount) public {
        approve(CURVE_POOL, tokenInAddress, amount);
        ICurveSwap(CURVE_POOL).exchange(tokenIn, tokenOut, amount, 0);
    }

    // 请求授权
    function approve(address requester, address token, uint256 amount) internal {
        uint256 alowance = IERC20(token).allowance(address(this), requester);
        if (alowance < amount) {
            IERC20(token).safeApprove(requester, MAX_INT);
        }
    }

因为curve要授权,所以在调用之前,确保已经进行授权。

比较疑惑的是tokenIn和tokenOut。这两个是池子定义的数组。在coins变量里面。内容如下

coins: constant(address[N_COINS]) = [
0xdAC17F958D2ee523a2206206994597C13D831ec7, //usdt index:0
0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599, //wbtc
0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2, //weth index:2
]

我们取的是索引值0和2的。WBTC不用管了。

接下来是uniswap的兑换接口

function uniSwap(address tokenIn, address tokenOut, uint256 amount) public {
        //地址address类型本质上是一个160位的数字,可以进行加减
        bool zeroForOne = tokenIn < tokenOut;
        //这行代码是抄的,回头再仔细想想是什么意思
        uint160 sqrtPriceLimitX96 = (zeroForOne ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1);
        IUniSwap(UNISWAP_POOL).swap(address(this), zeroForOne, int256(amount), sqrtPriceLimitX96, abi.encode(amount));
    }

    //uni主动回调的函数
    function uniswapV3SwapCallback(int256 amount0Delta, int256 amount1Delta, bytes calldata _data) public {
        uint256 amount = abi.decode(_data, (uint256));
        IERC20(WETH).safeTransfer(UNISWAP_POOL, amount);
    }

因为uniswap餐用的是闪兑功能。需要我们提供一个回调函数uniswapV3SwapCallback。

我们在回调函数里面把钱给他转过去。

好了。写完之后便可以开始测试。

接下来命令一条龙服务:


instance = await MoneyBot.deployed();
//发送10以太币
instance.send(web3.utils.toWei('10', 'ether'))
//把eth转为WETH
instance.ETHtoWETH(web3.utils.toWei('8', 'ether'));
//查看合约里面的WETH余额
contractWethBalance  = await instance.getThisWethBalance();
contractWethBalance.toString();
//查看合约里面的USDT余额
contractUSDTBalance  = await instance.getThisUSDTBalance();
contractUSDTBalance.toString();
//定义两个合约地址变量
WETHADDR="0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2";
USDTADDR="0xdAC17F958D2ee523a2206206994597C13D831ec7";
//测试uni闪兑功能。
instance.uniSwap(WETHADDR,USDTADDR,web3.utils.toWei('1'));
//再次查看合约里面的USDT余额,看看是否有改变。
contractUSDTBalance  = await instance.getThisUSDTBalance();
contractUSDTBalance.toString();


//测试curve的exchange,从WETH兑换到USDT
instance.curveSwap(2,WETHADDR,0,web3.utils.toWei('2'));
//再次查看合约里面的USDT余额,看看是否有改变。
contractUSDTBalance  = await instance.getThisUSDTBalance();
contractUSDTBalance.toString();

OK,如果余额发生改变,证明兑换已经成功。

测试通过后,便可以再次进行优化代码。比如说定义一个统一接口swap,根据flag进行识别具体是要使用哪一个模块来兑换。我知道java程序员喜欢用字符串或者字典。但是solidity用字符串实在是牺牲太大。所以用一个uint8就可以!

下一节,准备开始把闪电贷和swap功能结合起来。

附上主类的代码。

pragma solidity ^0.8.17;

import "./Libs.sol";
import "./AddressList.sol";

contract MoneyBot {
    using SafeMath for uint256;
    using SafeERC20 for IERC20;

    //合约的部署者
    address owner;
    uint256 MAX_INT = 2 ** 256 - 1;

    constructor(){
        owner = address(tx.origin);
    }

    // 返回合约的所有者
    function getOwner() public view returns (address) {
        return owner;
    }
    // 返回某个账户的某个token的余额
    function getTokenBalance(address token, address account) public view returns (uint256) {
        return IERC20(token).balanceOf(account);
    }
    // 返回合约部署者账户中的ETH余额
    function getMyWethBalance() public view returns (uint256) {
        return IERC20(WETH).balanceOf(owner);
    }
    // 返回合约部署者账户中的USDT余额
    function getMyUSDTBalance() public view returns (uint256) {
        return IERC20(USDT).balanceOf(owner);
    }
    // 返回合约账户中的WETH余额
    function getThisWethBalance() public view returns (uint256) {
        return IERC20(WETH).balanceOf(address(this));
    }
    // 返回合约账户中的USDT余额
    function getThisUSDTBalance() public view returns (uint256) {
        return IERC20(USDT).balanceOf(address(this));
    }

    //把合约里面的ETH转到钱包
    function turnOutETH(uint256 amount) public onlyOwner {
        payable(owner).transfer(amount);
    }
    //把合约里面的token转到钱包
    function turnOutToken(address token, uint256 amount) public onlyOwner {
        IERC20(token).transfer(owner, amount);
    }
    // WETH转换为ETH
    function WETHToETH(uint256 amount) public onlyOwner {
        IWETH(WETH).withdraw(amount);
    }
    // ETH转换为WETH
    function ETHtoWETH(uint256 amount) public onlyOwner {
        IWETH(WETH).deposit{value : amount}();
    }


    function curveSwap(uint256 tokenIn, address tokenInAddress, uint256 tokenOut, uint256 amount) public {
        approve(CURVE_POOL, tokenInAddress, amount);
        ICurveSwap(CURVE_POOL).exchange(tokenIn, tokenOut, amount, 0);
    }

    // 请求授权
    function approve(address requester, address token, uint256 amount) internal {
        uint256 alowance = IERC20(token).allowance(address(this), requester);
        if (alowance < amount) {
            IERC20(token).safeApprove(requester, MAX_INT);
        }
    }

    function uniSwap(address tokenIn, address tokenOut, uint256 amount) public {
        //地址address类型本质上是一个160位的数字,可以进行加减
        bool zeroForOne = tokenIn < tokenOut;
        //这行代码是抄的,回头再仔细想想是什么意思
        uint160 sqrtPriceLimitX96 = (zeroForOne ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1);
        IUniSwap(UNISWAP_POOL).swap(address(this), zeroForOne, int256(amount), sqrtPriceLimitX96, abi.encode(amount));
    }

    //uni主动回调的函数
    function uniswapV3SwapCallback(int256 amount0Delta, int256 amount1Delta, bytes calldata _data) public {
        uint256 amount = abi.decode(_data, (uint256));
        IERC20(WETH).safeTransfer(UNISWAP_POOL, amount);
    }



    //发起一个eth的闪电贷
    function doFlashEth(uint256 amount) public onlyOwner {
        ILiquidity(LIQUIDITY_POOL).borrow(WETH, amount,
            abi.encodeWithSelector(this.receiveBuyBigHouse.selector, abi.encode(amount)));
    }

    // callback ,收到钱后可以拿来买一个大房子。
    function receiveBuyBigHouse(bytes memory data) public {
        require(msg.sender == BORROWER_PROXY, "bu shi BorrorwerProxy,paochu yichang.");
        //购买大房子
        uint256 amount = abi.decode(data, (uint256));
        IERC20(WETH).transfer(LIQUIDITY_POOL, amount);
    }



    //fallback
    receive() external payable {}
    // modifier
    modifier onlyOwner(){
        require(address(msg.sender) == owner, "No authority");
        _;
    }
}
工程目录
工程目录

最后,大家记得帮忙众筹6颗核桃啊~。脑子不够用了。

点击转账三百块!