# dex的Swap操作

By [想住大房子的java程序员](https://paragraph.com/@java-3) · 2022-11-04

---

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

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

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

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

[https://curve.readthedocs.io/exchange-pools.html#exchange-pools-plain](https://curve.readthedocs.io/exchange-pools.html#exchange-pools-plain)

还有uniswap。

[https://docs.uniswap.org/protocol/guides/flash-integrations/inheritance-constructors](https://docs.uniswap.org/protocol/guides/flash-integrations/inheritance-constructors)

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

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

curve的有两个：

[0xd51a44d3fae010294c616388b506acda1bfaae46](https://etherscan.io/address/0xd51a44d3fae010294c616388b506acda1bfaae46) [0x80466c64868e1ab14a1ddf27a676c3fcbe638fe5](https://etherscan.io/address/0x80466c64868e1ab14a1ddf27a676c3fcbe638fe5)

![etherscan.io](https://storage.googleapis.com/papyrus_images/62b3d8d7bbc2c757d773ee77cc87f56a12b19184460aca1e5059305c881bb6ed.png)

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](https://storage.googleapis.com/papyrus_images/a2c82109444cef810512e389445da9ba2da070a087091d0bbbac28a97dc34670.png)

info.uniswap.org

记住，我们找的是ETH/USDT的。
------------------

[https://docs.uniswap.org/protocol/reference/core/UniswapV3Pool](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");
            _;
        }
    }
    

![工程目录](https://storage.googleapis.com/papyrus_images/0edc57f95597bcd2aef43ab83f42ef81cfd45a6b3384bd91dc99b0828e8fa319.png)

工程目录

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

点击转账三百块！

---

*Originally published on [想住大房子的java程序员](https://paragraph.com/@java-3/dex-swap)*
