feat: 完成 Mini Swap DEX AMM 项目开发
- 添加智能合约: TokenA, TokenB, MiniSwapAMM - 实现 AMM 流动性池功能 (x * y = k 公式) - 支持添加/移除流动性和代币交换 - 包含完整的测试套件 - 创建 React 前端界面,支持钱包连接 - 添加 Web3 集成和现代化 UI 设计 - 包含部署脚本和完整的项目配置
This commit is contained in:
239
contracts/MiniSwapAMM.sol
Normal file
239
contracts/MiniSwapAMM.sol
Normal file
@ -0,0 +1,239 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.19;
|
||||
|
||||
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||||
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
||||
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
|
||||
import "@openzeppelin/contracts/utils/math/Math.sol";
|
||||
|
||||
/**
|
||||
* @title MiniSwapAMM
|
||||
* @dev 简单的自动做市商 (AMM) 实现,基于 x * y = k 公式
|
||||
*/
|
||||
contract MiniSwapAMM is ERC20, ReentrancyGuard {
|
||||
IERC20 public immutable tokenA;
|
||||
IERC20 public immutable tokenB;
|
||||
|
||||
uint256 public reserveA;
|
||||
uint256 public reserveB;
|
||||
|
||||
uint256 private constant MINIMUM_LIQUIDITY = 10**3;
|
||||
|
||||
event AddLiquidity(
|
||||
address indexed provider,
|
||||
uint256 amountA,
|
||||
uint256 amountB,
|
||||
uint256 liquidity
|
||||
);
|
||||
|
||||
event RemoveLiquidity(
|
||||
address indexed provider,
|
||||
uint256 amountA,
|
||||
uint256 amountB,
|
||||
uint256 liquidity
|
||||
);
|
||||
|
||||
event Swap(
|
||||
address indexed trader,
|
||||
address tokenIn,
|
||||
address tokenOut,
|
||||
uint256 amountIn,
|
||||
uint256 amountOut
|
||||
);
|
||||
|
||||
constructor(address _tokenA, address _tokenB) ERC20("MiniSwap LP Token", "MSLP") {
|
||||
tokenA = IERC20(_tokenA);
|
||||
tokenB = IERC20(_tokenB);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev 添加流动性
|
||||
* @param amountADesired 期望添加的 tokenA 数量
|
||||
* @param amountBDesired 期望添加的 tokenB 数量
|
||||
* @param amountAMin 最小添加的 tokenA 数量
|
||||
* @param amountBMin 最小添加的 tokenB 数量
|
||||
* @return amountA 实际添加的 tokenA 数量
|
||||
* @return amountB 实际添加的 tokenB 数量
|
||||
* @return liquidity 获得的 LP 代币数量
|
||||
*/
|
||||
function addLiquidity(
|
||||
uint256 amountADesired,
|
||||
uint256 amountBDesired,
|
||||
uint256 amountAMin,
|
||||
uint256 amountBMin
|
||||
) external nonReentrant returns (uint256 amountA, uint256 amountB, uint256 liquidity) {
|
||||
(amountA, amountB) = _calculateOptimalAmounts(
|
||||
amountADesired,
|
||||
amountBDesired,
|
||||
amountAMin,
|
||||
amountBMin
|
||||
);
|
||||
|
||||
// 转移代币到合约
|
||||
tokenA.transferFrom(msg.sender, address(this), amountA);
|
||||
tokenB.transferFrom(msg.sender, address(this), amountB);
|
||||
|
||||
liquidity = _mintLiquidity(amountA, amountB);
|
||||
|
||||
emit AddLiquidity(msg.sender, amountA, amountB, liquidity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev 移除流动性
|
||||
* @param liquidity 要移除的 LP 代币数量
|
||||
* @param amountAMin 最小获得的 tokenA 数量
|
||||
* @param amountBMin 最小获得的 tokenB 数量
|
||||
* @return amountA 获得的 tokenA 数量
|
||||
* @return amountB 获得的 tokenB 数量
|
||||
*/
|
||||
function removeLiquidity(
|
||||
uint256 liquidity,
|
||||
uint256 amountAMin,
|
||||
uint256 amountBMin
|
||||
) external nonReentrant returns (uint256 amountA, uint256 amountB) {
|
||||
require(liquidity > 0, "MiniSwapAMM: INSUFFICIENT_LIQUIDITY");
|
||||
|
||||
uint256 totalSupply = totalSupply();
|
||||
amountA = (liquidity * reserveA) / totalSupply;
|
||||
amountB = (liquidity * reserveB) / totalSupply;
|
||||
|
||||
require(amountA >= amountAMin, "MiniSwapAMM: INSUFFICIENT_A_AMOUNT");
|
||||
require(amountB >= amountBMin, "MiniSwapAMM: INSUFFICIENT_B_AMOUNT");
|
||||
|
||||
_burn(msg.sender, liquidity);
|
||||
|
||||
tokenA.transfer(msg.sender, amountA);
|
||||
tokenB.transfer(msg.sender, amountB);
|
||||
|
||||
_updateReserves();
|
||||
|
||||
emit RemoveLiquidity(msg.sender, amountA, amountB, liquidity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev 交换代币 A 到代币 B
|
||||
* @param amountAIn 输入的 tokenA 数量
|
||||
* @param amountBOutMin 最小输出的 tokenB 数量
|
||||
* @return amountBOut 实际输出的 tokenB 数量
|
||||
*/
|
||||
function swapAForB(uint256 amountAIn, uint256 amountBOutMin)
|
||||
external nonReentrant returns (uint256 amountBOut) {
|
||||
require(amountAIn > 0, "MiniSwapAMM: INSUFFICIENT_INPUT_AMOUNT");
|
||||
|
||||
amountBOut = getAmountOut(amountAIn, reserveA, reserveB);
|
||||
require(amountBOut >= amountBOutMin, "MiniSwapAMM: INSUFFICIENT_OUTPUT_AMOUNT");
|
||||
|
||||
tokenA.transferFrom(msg.sender, address(this), amountAIn);
|
||||
tokenB.transfer(msg.sender, amountBOut);
|
||||
|
||||
_updateReserves();
|
||||
|
||||
emit Swap(msg.sender, address(tokenA), address(tokenB), amountAIn, amountBOut);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev 交换代币 B 到代币 A
|
||||
* @param amountBIn 输入的 tokenB 数量
|
||||
* @param amountAOutMin 最小输出的 tokenA 数量
|
||||
* @return amountAOut 实际输出的 tokenA 数量
|
||||
*/
|
||||
function swapBForA(uint256 amountBIn, uint256 amountAOutMin)
|
||||
external nonReentrant returns (uint256 amountAOut) {
|
||||
require(amountBIn > 0, "MiniSwapAMM: INSUFFICIENT_INPUT_AMOUNT");
|
||||
|
||||
amountAOut = getAmountOut(amountBIn, reserveB, reserveA);
|
||||
require(amountAOut >= amountAOutMin, "MiniSwapAMM: INSUFFICIENT_OUTPUT_AMOUNT");
|
||||
|
||||
tokenB.transferFrom(msg.sender, address(this), amountBIn);
|
||||
tokenA.transfer(msg.sender, amountAOut);
|
||||
|
||||
_updateReserves();
|
||||
|
||||
emit Swap(msg.sender, address(tokenB), address(tokenA), amountBIn, amountAOut);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev 根据输入数量计算输出数量 (基于 x * y = k 公式)
|
||||
* @param amountIn 输入数量
|
||||
* @param reserveIn 输入代币储备量
|
||||
* @param reserveOut 输出代币储备量
|
||||
* @return amountOut 输出数量
|
||||
*/
|
||||
function getAmountOut(uint256 amountIn, uint256 reserveIn, uint256 reserveOut)
|
||||
public pure returns (uint256 amountOut) {
|
||||
require(amountIn > 0, "MiniSwapAMM: INSUFFICIENT_INPUT_AMOUNT");
|
||||
require(reserveIn > 0 && reserveOut > 0, "MiniSwapAMM: INSUFFICIENT_LIQUIDITY");
|
||||
|
||||
// 考虑 0.3% 的交易费用
|
||||
uint256 amountInWithFee = amountIn * 997;
|
||||
uint256 numerator = amountInWithFee * reserveOut;
|
||||
uint256 denominator = (reserveIn * 1000) + amountInWithFee;
|
||||
amountOut = numerator / denominator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev 获取当前储备量
|
||||
*/
|
||||
function getReserves() external view returns (uint256 _reserveA, uint256 _reserveB) {
|
||||
return (reserveA, reserveB);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev 计算最优的代币数量比例
|
||||
*/
|
||||
function _calculateOptimalAmounts(
|
||||
uint256 amountADesired,
|
||||
uint256 amountBDesired,
|
||||
uint256 amountAMin,
|
||||
uint256 amountBMin
|
||||
) internal view returns (uint256 amountA, uint256 amountB) {
|
||||
if (reserveA == 0 && reserveB == 0) {
|
||||
// 首次添加流动性
|
||||
return (amountADesired, amountBDesired);
|
||||
} else {
|
||||
uint256 amountBOptimal = (amountADesired * reserveB) / reserveA;
|
||||
if (amountBOptimal <= amountBDesired) {
|
||||
require(amountBOptimal >= amountBMin, "MiniSwapAMM: INSUFFICIENT_B_AMOUNT");
|
||||
return (amountADesired, amountBOptimal);
|
||||
} else {
|
||||
uint256 amountAOptimal = (amountBDesired * reserveA) / reserveB;
|
||||
require(amountAOptimal <= amountADesired && amountAOptimal >= amountAMin,
|
||||
"MiniSwapAMM: INSUFFICIENT_A_AMOUNT");
|
||||
return (amountAOptimal, amountBDesired);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev 铸造流动性代币
|
||||
*/
|
||||
function _mintLiquidity(uint256 amountA, uint256 amountB) internal returns (uint256 liquidity) {
|
||||
uint256 totalSupply = totalSupply();
|
||||
|
||||
if (totalSupply == 0) {
|
||||
// 首次添加流动性
|
||||
liquidity = Math.sqrt(amountA * amountB);
|
||||
require(liquidity > MINIMUM_LIQUIDITY, "MiniSwapAMM: INSUFFICIENT_LIQUIDITY_MINTED");
|
||||
// 从流动性中减去最小流动性,永久锁定
|
||||
liquidity = liquidity - MINIMUM_LIQUIDITY;
|
||||
} else {
|
||||
liquidity = Math.min(
|
||||
(amountA * totalSupply) / reserveA,
|
||||
(amountB * totalSupply) / reserveB
|
||||
);
|
||||
}
|
||||
|
||||
require(liquidity > 0, "MiniSwapAMM: INSUFFICIENT_LIQUIDITY_MINTED");
|
||||
_mint(msg.sender, liquidity);
|
||||
|
||||
_updateReserves();
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev 更新储备量
|
||||
*/
|
||||
function _updateReserves() internal {
|
||||
reserveA = tokenA.balanceOf(address(this));
|
||||
reserveB = tokenB.balanceOf(address(this));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user