构建你自己的去中心化交易所
现在是时候为您的 Crypto Dev 代币启动 DeFi 交易所了
要求
- 仅使用一个资产对(Eth / Crypto Dev)建立交易所
- 您的去中心化交易所应收取 1% 的掉期费用
- 当用户增加流动性时,应给予他们 Crypto Dev LP 代币(流动性提供者代币)
- CD LP 代币应与 Ether 用户愿意增加流动性的比例成比例
让我们开始。
预备
- 您已经完成了之前的 ICO 教程
- 您已经完成了之前的介绍和深入了解去中心化交易所教程
- 您已完成之前的提供者、签名者、ABI 和批准流程教程
智能合约
创建项目
mkdir DeFi-Exchange
cd DeFi-Exchange
mkdir hardhat-tutorial
cd hardhat-tutorial
npm init --yes
npm install --save-dev hardhat
npx hardhat
npm install @openzeppelin/contracts
创建 Exchange.sol
导入继承 ERC20.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract Exchange is ERC20 {
}
创建构造函数
- 它将您在 ICO 教程中部署的 _CryptoDevToken 的地址作为输入参数
- 然后检查地址是否为空地址
- 在所有检查之后,它将值分配给 cryptoDevTokenAddress 变量的输入参数
构造函数还为我们的 Crypto Dev LP 代币设置名称和符号
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract Exchange is ERC20 {
address public cryptoDevTokenAddress;
// Exchange is inheriting ERC20, because our exchange would keep track of Crypto Dev LP tokens
constructor(address _CryptoDevtoken) ERC20("CryptoDev LP Token", "CDLP") {
require(_CryptoDevtoken != address(0), "Token address passed is a null address");
cryptoDevTokenAddress = _CryptoDevtoken;
}
}
创建代币储备方法
是时候创建一个函数来获取合约持有的 Eth 和 Crypto Dev 代币的储备了。
ETH 储备金将等于合约的余额,可以使用 address(this).balance 找到,因此我们只需创建一个函数,仅用于获取 Crypto Dev 代币的储备金
我们知道我们部署的 Crypto Dev Token 合约是 ERC20。 所以我们可以调用 balanceOf 来查看合约持有的 CryptoDev Token 的余额
/**
* @dev Returns the amount of `Crypto Dev Tokens` held by the contract
*/
function getReserve() public view returns (uint) {
return ERC20(cryptoDevTokenAddress).balanceOf(address(this));
}
创建添加流动性函数
它将以 ETH 和 Crypto Dev 代币的形式将流动性添加到我们的合约中
如果 cryptoDevTokenReserve
为零,意味着这是第一次有人将 Crypto Dev 代币和 ETH 添加到合约中。在这种情况,我们不必保持代币之间的比率,因为我们没有任何流动性。因此,我们接受用户在初始调用中发送的任何数量的令牌
如果 cryptoDevTokenReserve
不为零,那么我们必须确保当有人增加流动性时,它不会影响市场当前的价格。为了确保这一点,我们维持一个必须保持不变的比率。比例为(cryptoDevTokenAmount user can add/cryptoDevTokenReserve in the contract) = (Eth Sent by the user/Eth Reserve in the contract)
。
这个比率决定了在给定一定数量的 ETH 的情况下,用户可以提供多少 Crypto Dev 代币。当用户增加流动性时,我们需要为他提供一些 LP 代币,因为我们需要跟踪他提供给合约的流动性数量。铸造给用户的 LP 代币数量与用户提供的 ETH 成正比。
在初始流动性情况下,当没有流动性时:将铸造给用户的 LP 代币数量等于合约的 ETH 余额(因为余额等于用户在 addLiquidity 调用中发送的 ETH)
当合约中已经存在流动性时,铸造的 LP 代币数量基于一个比率。比例为(LP tokens to be sent to the user (liquidity) / totalSupply of LP tokens in contract) = (Eth sent by the user) / (Eth reserve in the contract)
如果这让您感到有些困惑,请再次打开之前的理论课并复习 xy = k 价格函数和 LP 代币背后的数学。
/**
* @dev Adds liquidity to the exchange.
*/
function addLiquidity(uint _amount) public payable returns (uint) {
uint liquidity;
uint ethBalance = address(this).balance;
uint cryptoDevTokenReserve = getReserve();
ERC20 cryptoDevToken = ERC20(cryptoDevTokenAddress);
/*
If the reserve is empty, intake any user supplied value for
`Ether` and `Crypto Dev` tokens because there is no ratio currently
*/
if(cryptoDevTokenReserve == 0) {
// Transfer the `cryptoDevToken` from the user's account to the contract
cryptoDevToken.transferFrom(msg.sender, address(this), _amount);
// Take the current ethBalance and mint `ethBalance` amount of LP tokens to the user.
// `liquidity` provided is equal to `ethBalance` because this is the first time user
// is adding `Eth` to the contract, so whatever `Eth` contract has is equal to the one supplied
// by the user in the current `addLiquidity` call
// `liquidity` tokens that need to be minted to the user on `addLiquidity` call should always be proportional
// to the Eth specified by the user
liquidity = ethBalance;
_mint(msg.sender, liquidity);
// _mint is ERC20.sol smart contract function to mint ERC20 tokens
} else {
/*
If the reserve is not empty, intake any user supplied value for
`Ether` and determine according to the ratio how many `Crypto Dev` tokens
need to be supplied to prevent any large price impacts because of the additional
liquidity
*/
// EthReserve should be the current ethBalance subtracted by the value of ether sent by the user
// in the current `addLiquidity` call
uint ethReserve = ethBalance - msg.value;
// Ratio should always be maintained so that there are no major price impacts when adding liquidity
// Ratio here is -> (cryptoDevTokenAmount user can add/cryptoDevTokenReserve in the contract) = (Eth Sent by the user/Eth Reserve in the contract);
// So doing some maths, (cryptoDevTokenAmount user can add) = (Eth Sent by the user * cryptoDevTokenReserve /Eth Reserve);
uint cryptoDevTokenAmount = (msg.value * cryptoDevTokenReserve)/(ethReserve);
require(_amount >= cryptoDevTokenAmount, "Amount of tokens sent is less than the minimum tokens required");
// transfer only (cryptoDevTokenAmount user can add) amount of `Crypto Dev tokens` from users account
// to the contract
cryptoDevToken.transferFrom(msg.sender, address(this), cryptoDevTokenAmount);
// The amount of LP tokens that would be sent to the user should be proportional to the liquidity of
// ether added by the user
// Ratio here to be maintained is ->
// (LP tokens to be sent to the user (liquidity)/ totalSupply of LP tokens in contract) = (Eth sent by the user)/(Eth reserve in the contract)
// by some maths -> liquidity = (totalSupply of LP tokens in contract * (Eth sent by the user))/(Eth reserve in the contract)
liquidity = (totalSupply() * msg.value)/ ethReserve;
_mint(msg.sender, liquidity);
}
return liquidity;
}
创建移除流动性方法
将发送回用户的以太币数量将基于一个比率。 该比率是(Eth sent back to the user) / (current Eth reserve) = (amount of LP tokens that user wants to withdraw) / (total supply of LP tokens)
。
将被发送回用户的 Crypto Dev 代币的数量也将基于一个比率。 该比率是(Crypto Dev sent back to the user) / (current Crypto Dev token reserve) = (amount of LP tokens that user wants to withdraw) / (total supply of LP tokens)
。
用户用于消除流动性的 LP 代币数量将被烧毁
/**
* @dev Returns the amount Eth/Crypto Dev tokens that would be returned to the user
* in the swap
*/
function removeLiquidity(uint _amount) public returns (uint , uint) {
require(_amount > 0, "_amount should be greater than zero");
uint ethReserve = address(this).balance;
uint _totalSupply = totalSupply();
// The amount of Eth that would be sent back to the user is based
// on a ratio
// Ratio is -> (Eth sent back to the user) / (current Eth reserve)
// = (amount of LP tokens that user wants to withdraw) / (total supply of LP tokens)
// Then by some maths -> (Eth sent back to the user)
// = (current Eth reserve * amount of LP tokens that user wants to withdraw) / (total supply of LP tokens)
uint ethAmount = (ethReserve * _amount)/ _totalSupply;
// The amount of Crypto Dev token that would be sent back to the user is based
// on a ratio
// Ratio is -> (Crypto Dev sent back to the user) / (current Crypto Dev token reserve)
// = (amount of LP tokens that user wants to withdraw) / (total supply of LP tokens)
// Then by some maths -> (Crypto Dev sent back to the user)
// = (current Crypto Dev token reserve * amount of LP tokens that user wants to withdraw) / (total supply of LP tokens)
uint cryptoDevTokenAmount = (getReserve() * _amount)/ _totalSupply;
// Burn the sent LP tokens from the user's wallet because they are already sent to
// remove liquidity
_burn(msg.sender, _amount);
// Transfer `ethAmount` of Eth from the contract to the user's wallet
payable(msg.sender).transfer(ethAmount);
// Transfer `cryptoDevTokenAmount` of Crypto Dev tokens from the contract to the user's wallet
ERC20(cryptoDevTokenAddress).transfer(msg.sender, cryptoDevTokenAmount);
return (ethAmount, cryptoDevTokenAmount);
}
实现交换方法
交换有两种方式。
- 一种方法是
Eth 到
Crypto Dev` 代币。 - 一种方法是
Crypto Dev
到Eth
重要的是要确定:给定用户发送的 x
数量的 Eth
/Crypto Dev
代币,他将从交换中收到多少 Eth
/Crypto Dev
代币?
所以让我们创建一个计算这个的函数: