跳到主要内容

从零构建DeFi协议:去中心化交易所(DEX)实战

阅读需 5 分钟

去中心化金融(DeFi)是区块链技术最具革命性的应用之一。本文将带你从零开始构建一个简化版的去中心化交易所,理解AMM(自动做市商)的核心机制。

什么是AMM?

AMM(Automated Market Maker)自动做市商,通过算法自动为交易对提供流动性。不同于传统订单簿模式,AMM使用流动性池和定价公式来实现代币交换。

核心概念

  1. 流动性池(Liquidity Pool): 存放两种代币的智能合约
  2. 恒定乘积公式: x * y = k
  3. 流动性提供者(LP): 向池子提供代币的用户
  4. 滑点(Slippage): 交易对价格的影响程度

构建基础DEX

1. 核心合约结构

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IERC20 {
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
function transfer(address recipient, uint256 amount) external returns (bool);
function balanceOf(address account) external view returns (uint256);
}

contract SimpleDEX {
IERC20 public token0;
IERC20 public token1;

uint256 public reserve0; // 代币0的储备量
uint256 public reserve1; // 代币1的储备量
uint256 public totalSupply; // LP代币总供应量
mapping(address => uint256) public balanceOf; // 用户LP代币余额

event Swap(address indexed sender, uint amount0In, uint amount1In, uint amount0Out, uint amount1Out, address indexed to);
event LiquidityAdded(address indexed provider, uint amount0, uint amount1, uint liquidity);
event LiquidityRemoved(address indexed provider, uint amount0, uint amount1, uint liquidity);

constructor(address _token0, address _token1) {
token0 = IERC20(_token0);
token1 = IERC20(_token1);
}

// 获取流动性份额
function _mint(address _to, uint _amount) private {
balanceOf[_to] += _amount;
totalSupply += _amount;
}

// 销毁流动性份额
function _burn(address _from, uint _amount) private {
balanceOf[_from] -= _amount;
totalSupply -= _amount;
}

// 更新储备量
function _update(uint _reserve0, uint _reserve1) private {
reserve0 = _reserve0;
reserve1 = _reserve1;
}
}

2. 添加流动性

function addLiquidity(uint _amount0, uint _amount1) external returns (uint liquidity) {
// 转账代币到合约
token0.transferFrom(msg.sender, address(this), _amount0);
token1.transferFrom(msg.sender, address(this), _amount1);

uint _reserve0 = reserve0;
uint _reserve1 = reserve1;

if (_reserve0 == 0 && _reserve1 == 0) {
// 首次添加流动性
liquidity = sqrt(_amount0 * _amount1);
} else {
// 按比例添加
require(_amount0 * _reserve1 == _amount1 * _reserve0, "Invalid ratio");
liquidity = min((_amount0 * totalSupply) / _reserve0, (_amount1 * totalSupply) / _reserve1);
}

require(liquidity > 0, "Insufficient liquidity");

_mint(msg.sender, liquidity);
_update(_reserve0 + _amount0, _reserve1 + _amount1);

emit LiquidityAdded(msg.sender, _amount0, _amount1, liquidity);
}

3. 移除流动性

function removeLiquidity(uint _liquidity) external returns (uint amount0, uint amount1) {
require(balanceOf[msg.sender] >= _liquidity, "Insufficient balance");

uint _reserve0 = reserve0;
uint _reserve1 = reserve1;

// 计算可提取的代币数量
amount0 = (_liquidity * _reserve0) / totalSupply;
amount1 = (_liquidity * _reserve1) / totalSupply;

require(amount0 > 0 && amount1 > 0, "Invalid amounts");

_burn(msg.sender, _liquidity);

// 转账代币给用户
token0.transfer(msg.sender, amount0);
token1.transfer(msg.sender, amount1);

_update(_reserve0 - amount0, _reserve1 - amount1);

emit LiquidityRemoved(msg.sender, amount0, amount1, _liquidity);
}

4. 代币交换

function swap(uint _amount0Out, uint _amount1Out, address _to) external {
require(_amount0Out > 0 || _amount1Out > 0, "Invalid output");
require(_amount0Out == 0 || _amount1Out == 0, "Only one output allowed");

uint _reserve0 = reserve0;
uint _reserve1 = reserve1;

require(_amount0Out < _reserve0 && _amount1Out < _reserve1, "Insufficient liquidity");

uint amount0In = 0;
uint amount1In = 0;

if (_amount0Out > 0) {
token0.transfer(_to, _amount0Out);
amount1In = token1.balanceOf(address(this)) - _reserve1;
} else {
token1.transfer(_to, _amount1Out);
amount0In = token0.balanceOf(address(this)) - _reserve0;
}

require(amount0In > 0 || amount1In > 0, "Invalid input");

// 恒定乘积公式验证
uint balance0 = token0.balanceOf(address(this));
uint balance1 = token1.balanceOf(address(this));

require(balance0 * balance1 >= _reserve0 * _reserve1, "K invariant violated");

_update(balance0, balance1);

emit Swap(msg.sender, amount0In, amount1In, _amount0Out, _amount1Out, _to);
}

5. 价格计算

function getAmountOut(uint _amountIn, uint _reserveIn, uint _reserveOut) public pure returns (uint amountOut) {
require(_amountIn > 0, "Invalid amount");
require(_reserveIn > 0 && _reserveOut > 0, "Invalid reserves");

// 简化版,实际应该考虑手续费
uint amountInWithFee = _amountIn * 997; // 0.3% 手续费
uint numerator = amountInWithFee * _reserveOut;
uint denominator = (_reserveIn * 1000) + amountInWithFee;
amountOut = numerator / denominator;
}

function getAmountsOut(uint _amountIn, address[] calldata _path) public view returns (uint[] memory amounts) {
require(_path.length >= 2, "Invalid path");
amounts = new uint[](_path.length);
amounts[0] = _amountIn;

for (uint i; i < _path.length - 1; i++) {
(uint reserveIn, uint reserveOut) = getReserves(_path[i], _path[i + 1]);
amounts[i + 1] = getAmountOut(amounts[i], reserveIn, reserveOut);
}
}

高级功能

1. 价格预言机

contract PriceOracle {
struct Observation {
uint timestamp;
uint price0Cumulative;
uint price1Cumulative;
}

Observation[] public observations;
uint public constant PERIOD = 3600; // 1小时

function update() external {
uint timeElapsed = block.timestamp - observations[observations.length - 1].timestamp;
require(timeElapsed >= PERIOD, "Period not elapsed");

uint price0Cumulative = reserve1 * 2**112 / reserve0;
uint price1Cumulative = reserve0 * 2**112 / reserve1;

observations.push(Observation(block.timestamp, price0Cumulative, price1Cumulative));
}

function getPrice() external view returns (uint price) {
require(observations.length >= 2, "Insufficient data");

Observation memory first = observations[observations.length - 2];
Observation memory last = observations[observations.length - 1];

uint timeElapsed = last.timestamp - first.timestamp;
require(timeElapsed >= PERIOD, "Period too short");

price = (last.price0Cumulative - first.price0Cumulative) / timeElapsed;
}
}

2. 闪电贷

interface IFlashLoanReceiver {
function executeOperation(address token, uint amount, uint fee, bytes calldata data) external;
}

contract FlashLoan {
function flashLoan(address _token, uint _amount, bytes calldata _data) external {
uint balanceBefore = IERC20(_token).balanceOf(address(this));
require(balanceBefore >= _amount, "Insufficient liquidity");

// 转账给借款人
IERC20(_token).transfer(msg.sender, _amount);

// 执行借款人的操作
IFlashLoanReceiver(msg.sender).executeOperation(_token, _amount, _fee, _data);

// 检查还款
uint balanceAfter = IERC20(_token).balanceOf(address(this));
require(balanceAfter >= balanceBefore + _fee, "Flash loan not repaid");
}
}

部署和测试

使用Hardhat进行测试

const { expect } = require("chai");
const { ethers } = require("hardhat");

describe("SimpleDEX", function () {
let dex, token0, token1, owner, user1, user2;

beforeEach(async () => {
[owner, user1, user2] = await ethers.getSigners();

// 部署测试代币
const Token = await ethers.getContractFactory("Token");
token0 = await Token.deploy("Token0", "TK0", ethers.utils.parseEther("1000"));
token1 = await Token.deploy("Token1", "TK1", ethers.utils.parseEther("1000"));

// 部署DEX
const SimpleDEX = await ethers.getContractFactory("SimpleDEX");
dex = await SimpleDEX.deploy(token0.address, token1.address);

// 授权DEX使用代币
await token0.approve(dex.address, ethers.constants.MaxUint256);
await token1.approve(dex.address, ethers.constants.MaxUint256);
});

it("Should add liquidity", async () => {
const amount0 = ethers.utils.parseEther("100");
const amount1 = ethers.utils.parseEther("100");

await dex.addLiquidity(amount0, amount1);

expect(await dex.reserve0()).to.equal(amount0);
expect(await dex.reserve1()).to.equal(amount1);
expect(await dex.balanceOf(owner.address)).to.equal(ethers.utils.parseEther("100"));
});

it("Should perform swap", async () => {
// 添加流动性
await dex.addLiquidity(
ethers.utils.parseEther("100"),
ethers.utils.parseEther("100")
);

// 执行交换
const amountIn = ethers.utils.parseEther("10");
await token0.connect(user1).approve(dex.address, amountIn);
await token0.connect(user1).transfer(dex.address, amountIn);

const amountOut = await dex.getAmountOut(
amountIn,
await dex.reserve0(),
await dex.reserve1()
);

await dex.swap(0, amountOut, user1.address);

expect(await token1.balanceOf(user1.address)).to.equal(amountOut);
});
});

总结

通过构建这个DEX,我们学习了:

  1. 恒定乘积公式: x * y = k 是AMM的核心
  2. 流动性提供: 如何添加和移除流动性
  3. 代币交换: 基于定价算法的交换机制
  4. 价格计算: 滑点和价格影响的计算
  5. 高级功能: 价格预言机和闪电贷

这只是一个基础实现,实际的DEX如Uniswap还包含更多复杂功能:

  • 多跳路由
  • 价格影响保护
  • 闪电交换
  • 协议费用
  • 治理代币

继续探索,构建更强大的DeFi协议!

下一步

  1. 实现更复杂的AMM算法(如Curve的稳定币交换)
  2. 添加治理机制
  3. 集成跨链功能
  4. 构建用户界面
  5. 进行安全审计

参考资料

Loading Comments...