Files
mini-swap/contracts/MiniSwapAMM.sol
NoBey 7cddf1d987 feat: 添加 MiniSwap CLI 工具及相关文档
- 新增 CLI 工具,支持通过命令行与 MiniSwapAMM 合约交互
- 创建 CLI 使用指南文档,详细说明命令和操作流程
- 更新合约地址,确保与新部署的合约匹配
- 添加快速网络连接测试脚本,验证合约连接和网络状态
- 实现完整测试场景脚本,模拟用户操作和流动性管理
2025-07-12 02:28:36 +08:00

238 lines
8.1 KiB
Solidity

// 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));
}
}