# 实战案例四:DeFi 去中心化交易所 **Published by:** [icepy](https://paragraph.com/@icepy/) **Published on:** 2022-03-20 **URL:** https://paragraph.com/@icepy/defi ## Content 现实情况是期望代币可以在去中心化的交易场所中交换,这篇文章就是从一个简单案例来说明交换,流动性该如何实现。 我们需要先梳理一下,期望这个应用具备哪些功能:只用一个代币对建立交易场所交易收取 1% 的费用用户可以为 UseWeb3Token 添加或删除流动性为用户提供 LP 代币说明:实现会比这个例子复杂的多// SPDX-License-Identifier: SEE LICENSE IN LICENSE pragma solidity ^0.8.4; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; contract UseWeb3Exchange is ERC20 { address public useweb3TokenAddress; constructor(address useweb3TokenContract) ERC20("LP Token", "LP") { useweb3TokenAddress = useweb3TokenContract; } function getReserve() public view returns(uint256){ return ERC20(useweb3TokenAddress).balanceOf(address(this)); } } 因为要提供 LP 所以直接继承 ERC20 同时定义 getReserve 获取当前合约持有的 UseWeb3Token 数量function getReserve() public view returns(uint256){ return ERC20(useweb3TokenAddress).balanceOf(address(this)); } 接下来,我们开始编写添加流动性方法function addLiquidity(uint256 _amount) public payable returns(uint256){ uint256 liquidity; // 用当前合约的 eth 做初始值 uint256 ethBalance = address(this).balance; // 查询当前合约持有 useweb3Token 的数量 uint256 currentContractHoldUseweb3TokenReserve = getReserve(); ERC20 useweb3Token = ERC20(useweb3TokenAddress); // currentContractHoldUseweb3TokenReserve = 0 表示第一次添加流动性 if (currentContractHoldUseweb3TokenReserve == 0){ // 将 _amount 数量 从发起者地址转移到当前合约地址 useweb3Token.transferFrom(msg.sender, address(this), _amount); liquidity = ethBalance; // 给发起者地址 LP 代币 _mint(msg.sender, liquidity); } else { // 已经存在流动性时的处理逻辑 // 当前合约的 eth 数量 - 用户发送来的 eth 数量 uint256 ethReserve = ethBalance - msg.value; // (用用户发送来的 eth 数量乘以当前合约持有的 useweb3Token的数量)除以(已经减过的eth储备) ,获取一定比率的 useweb3Token uint256 useweb3TokenAmount = (msg.value * currentContractHoldUseweb3TokenReserve)/(ethReserve); // 检查 _amount 参数必须大于 计算出来的 useweb3Token 的数量 require(_amount > useweb3TokenAmount, "Amount of tokens sent is less than the minimum tokens required"); useweb3Token.transferFrom(msg.sender, address(this), useweb3TokenAmount); // (LP 代币的总量 * 用户发送来的 eth 数量)除以(已经减过的eth储备) liquidity = (totalSupply() * msg.value)/ethReserve; // 给发送者地址 LP 代币 _mint(msg.sender, liquidity); } return liquidity; } 删除流动性方法function removeLiquidity(uint256 _amount) public returns(uint256, uint256){ require(_amount > 0, "amount > 0"); // 查询当前合约持有的 eth 数量 uint256 ethReserve = address(this).balance; uint256 lpTotal = totalSupply(); uint256 ethAmount = (ethReserve * _amount)/lpTotal; uint256 useweb3TokenAmount = (getReserve() * _amount)/lpTotal; // 烧毁用户的 lp 代币 _burn(msg.sender, _amount); // 将 eth 和 useweb3Token 代币还给用户 payable(msg.sender).transfer(ethAmount); ERC20(useweb3TokenAddress).transfer(msg.sender, useweb3TokenAmount); return (ethAmount, useweb3TokenAmount); } 交易收取费用的方法function getAmountOfTokens(uint256 inputAmount, uint256 inputReserve, uint256 outputReserve) public pure returns(uint256){ require(inputReserve > 0 && outputReserve > 0, "invalid reserves"); uint256 inputAmountWithFee = inputAmount * 99; uint256 numerator = inputAmountWithFee * outputReserve; uint256 denominator = (inputReserve * 100) + inputAmountWithFee; return numerator / denominator; } 交换的方法function ethToUseWeb3Token(uint256 minTokens) public payable{ uint256 currentContractHoldUseweb3TokenReserve = getReserve(); uint256 tokensBought = getAmountOfTokens(msg.value, address(this).balance - msg.value, currentContractHoldUseweb3TokenReserve); require(tokensBought >= minTokens, "insufficient output amount"); ERC20(useweb3TokenAddress).transfer(msg.sender, tokensBought); } function useWeb3TokenToEth(uint256 tokenSold, uint256 minEth) public{ uint256 currentContractHoldUseweb3TokenReserve = getReserve(); uint256 ethBought = getAmountOfTokens(tokenSold, currentContractHoldUseweb3TokenReserve, address(this).balance); require(ethBought >= minEth, "insufficient output amount"); ERC20(useweb3TokenAddress).transferFrom(msg.sender, address(this), tokenSold); payable(msg.sender).transfer(ethBought); } 观察阶段我们编写一个简单的前端代码来看一看合约运行的结果,需要注意的是,我们需要为交易合约授权。const web3Provider = getWeb3Provider(); const signer = web3Provider.getSigner(); const exchangeContract = new ethers.Contract(USEWEB3_EXCHANGE_ADDRESS, USEWEB3_EXCHANGE_ABI, signer); const tokenContract = new ethers.Contract(USEWEB3_TOKEN_ADDRESS, USEWEB3_TOKEN_ABI, signer); const tokenTx = await tokenContract.approve(USEWEB3_EXCHANGE_ADDRESS, ethers.utils.parseEther("10")); await tokenTx.wait(); 然后初始添加流动性const exchangeTx = await exchangeContract.addLiquidity(ethers.utils.parseEther("10"), {value: ethers.utils.parseEther("0.01")}); await exchangeTx.wait(); 通过 https://rinkeby.etherscan.io/ 来观察最后的结果 ## Publication Information - [icepy](https://paragraph.com/@icepy/): Publication homepage - [All Posts](https://paragraph.com/@icepy/): More posts from this publication - [RSS Feed](https://api.paragraph.com/blogs/rss/@icepy): Subscribe to updates - [Twitter](https://twitter.com/i_icepy): Follow on Twitter