# dex的Swap操作 **Published by:** [想住大房子的java程序员](https://paragraph.com/@java-3/) **Published on:** 2022-11-04 **URL:** https://paragraph.com/@java-3/dex-swap ## Content 想快速换钱实现兑换。只能够在去中心化交易所(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 0x80466c64868e1ab14a1ddf27a676c3fcbe638fe5etherscan.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,同样找到相关合约地址: 0x4e68ccd3e89f51c3074ca5072bbac773960dfa36info.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颗核桃啊~。脑子不够用了。 点击转账三百块! ## Publication Information - [想住大房子的java程序员](https://paragraph.com/@java-3/): Publication homepage - [All Posts](https://paragraph.com/@java-3/): More posts from this publication - [RSS Feed](https://api.paragraph.com/blogs/rss/@java-3): Subscribe to updates