DeFi 去中心化金融
DeFi(Decentralized Finance,去中心化金融)是 Web3 最成功的应用领域之一。它利用智能合约重建传统金融服务,如借贷、交易、衍生品等,无需银行等中介机构。本章将深入介绍 DeFi 的核心概念和开发实践。
DeFi 概述
什么是 DeFi
DeFi 是基于区块链的金融服务体系,通过智能合约自动执行金融协议。与传统金融相比,DeFi 具有以下特点:
去中心化:没有中心化的金融机构控制,协议由智能合约自动执行。
无需许可:任何人都可以使用 DeFi 服务,无需通过 KYC 或信用审核。
透明公开:所有交易和协议代码都是公开的,可以被审计。
可组合性:不同 DeFi 协议可以相互组合,构建复杂的金融产品。
DeFi 的主要领域
| 领域 | 说明 | 代表项目 |
|---|---|---|
| 去中心化交易所(DEX) | 代币交易 | Uniswap, SushiSwap |
| 借贷协议 | 借款和存款 | Aave, Compound |
| 稳定币 | 价值稳定的代币 | DAI, USDC |
| 收益聚合器 | 自动优化收益 | Yearn Finance |
| 衍生品 | 期货、期权等 | dYdX, Synthetix |
去中心化交易所(DEX)
自动做市商(AMM)
传统交易所使用订单簿模式,买卖双方挂单匹配。DEX 则采用自动做市商(AMM)模式,通过流动性池实现交易。
流动性池:用户存入代币对(如 ETH/USDC)形成池子,其他用户可以与池子交易。
价格机制:AMM 使用数学公式确定价格。最常用的是恒定乘积公式:
其中 和 是两种代币的数量, 是常数。交易时保持 不变,价格由代币比例决定。
Uniswap V2 原理
Uniswap 是最流行的 AMM DEX。以下是简化版的实现:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract SimpleAMM is ERC20 {
IERC20 public token0;
IERC20 public token1;
uint256 public reserve0;
uint256 public reserve1;
uint256 public constant FEE_NUMERATOR = 997;
uint256 public constant FEE_DENOMINATOR = 1000;
constructor(address _token0, address _token1) ERC20("LP Token", "LP") {
token0 = IERC20(_token0);
token1 = IERC20(_token1);
}
// 添加流动性
function addLiquidity(
uint256 amount0,
uint256 amount1
) external returns (uint256 liquidity) {
token0.transferFrom(msg.sender, address(this), amount0);
token1.transferFrom(msg.sender, address(this), amount1);
uint256 totalSupply_ = totalSupply();
if (totalSupply_ == 0) {
liquidity = sqrt(amount0 * amount1);
} else {
liquidity = min(
(amount0 * totalSupply_) / reserve0,
(amount1 * totalSupply_) / reserve1
);
}
require(liquidity > 0, "Insufficient liquidity minted");
_mint(msg.sender, liquidity);
reserve0 += amount0;
reserve1 += amount1;
}
// 移除流动性
function removeLiquidity(
uint256 liquidity
) external returns (uint256 amount0, uint256 amount1) {
uint256 totalSupply_ = totalSupply();
amount0 = (liquidity * reserve0) / totalSupply_;
amount1 = (liquidity * reserve1) / totalSupply_;
_burn(msg.sender, liquidity);
reserve0 -= amount0;
reserve1 -= amount1;
token0.transfer(msg.sender, amount0);
token1.transfer(msg.sender, amount1);
}
// 交换代币
function swap(
uint256 amountIn,
uint256 amountOutMin,
address tokenIn,
address tokenOut
) external returns (uint256 amountOut) {
require(tokenIn == address(token0) || tokenIn == address(token1), "Invalid token");
require(tokenOut == address(token0) || tokenOut == address(token1), "Invalid token");
bool isToken0 = tokenIn == address(token0);
(IERC20 tokenIn_, IERC20 tokenOut_, uint256 reserveIn, uint256 reserveOut) =
isToken0
? (token0, token1, reserve0, reserve1)
: (token1, token0, reserve1, reserve0);
tokenIn_.transferFrom(msg.sender, address(this), amountIn);
// 计算输出金额(扣除 0.3% 手续费)
uint256 amountInWithFee = (amountIn * FEE_NUMERATOR) / FEE_DENOMINATOR;
amountOut = (amountInWithFee * reserveOut) / (reserveIn + amountInWithFee);
require(amountOut >= amountOutMin, "Slippage too high");
tokenOut_.transfer(msg.sender, amountOut);
// 更新储备
if (isToken0) {
reserve0 += amountIn;
reserve1 -= amountOut;
} else {
reserve1 += amountIn;
reserve0 -= amountOut;
}
}
// 获取预期输出
function getAmountOut(
uint256 amountIn,
address tokenIn
) external view returns (uint256) {
bool isToken0 = tokenIn == address(token0);
(uint256 reserveIn, uint256 reserveOut) =
isToken0 ? (reserve0, reserve1) : (reserve1, reserve0);
uint256 amountInWithFee = (amountIn * FEE_NUMERATOR) / FEE_DENOMINATOR;
return (amountInWithFee * reserveOut) / (reserveIn + amountInWithFee);
}
function sqrt(uint256 y) internal pure returns (uint256 z) {
if (y > 3) {
z = y;
uint256 x = y / 2 + 1;
while (x < z) {
z = x;
x = (y / x + x) / 2;
}
} else if (y != 0) {
z = 1;
}
}
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}
}
流动性挖矿
流动性提供者(LP)通过提供流动性获得交易手续费收益。流动性挖矿则是额外给予 LP 代币奖励:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract LiquidityMining is ERC20 {
IERC20 public lpToken;
IERC20 public rewardToken;
uint256 public rewardPerBlock;
uint256 public lastUpdateBlock;
uint256 public accRewardPerShare;
mapping(address => uint256) public userRewardDebt;
constructor(
address _lpToken,
address _rewardToken,
uint256 _rewardPerBlock
) ERC20("Mining Pool", "MP") {
lpToken = IERC20(_lpToken);
rewardToken = IERC20(_rewardToken);
rewardPerBlock = _rewardPerBlock;
}
// 存入 LP 代币
function deposit(uint256 amount) external {
updatePool();
uint256 balance = balanceOf(msg.sender);
if (balance > 0) {
uint256 pending = (balance * accRewardPerShare / 1e12) - userRewardDebt[msg.sender];
rewardToken.transfer(msg.sender, pending);
}
lpToken.transferFrom(msg.sender, address(this), amount);
_mint(msg.sender, amount);
userRewardDebt[msg.sender] = balanceOf(msg.sender) * accRewardPerShare / 1e12;
}
// 取出 LP 代币
function withdraw(uint256 amount) external {
require(balanceOf(msg.sender) >= amount, "Insufficient balance");
updatePool();
uint256 pending = (balanceOf(msg.sender) * accRewardPerShare / 1e12) - userRewardDebt[msg.sender];
rewardToken.transfer(msg.sender, pending);
_burn(msg.sender, amount);
lpToken.transfer(msg.sender, amount);
userRewardDebt[msg.sender] = balanceOf(msg.sender) * accRewardPerShare / 1e12;
}
// 领取奖励
function claim() external {
updatePool();
uint256 pending = (balanceOf(msg.sender) * accRewardPerShare / 1e12) - userRewardDebt[msg.sender];
if (pending > 0) {
rewardToken.transfer(msg.sender, pending);
userRewardDebt[msg.sender] = balanceOf(msg.sender) * accRewardPerShare / 1e12;
}
}
// 更新池子
function updatePool() public {
if (block.number <= lastUpdateBlock) return;
uint256 totalStaked = totalSupply();
if (totalStaked == 0) {
lastUpdateBlock = block.number;
return;
}
uint256 blocksPassed = block.number - lastUpdateBlock;
uint256 reward = blocksPassed * rewardPerBlock;
accRewardPerShare += (reward * 1e12) / totalStaked;
lastUpdateBlock = block.number;
}
// 查询待领取奖励
function pendingReward(address user) external view returns (uint256) {
uint256 totalStaked = totalSupply();
uint256 acc = accRewardPerShare;
if (block.number > lastUpdateBlock && totalStaked > 0) {
uint256 blocksPassed = block.number - lastUpdateBlock;
uint256 reward = blocksPassed * rewardPerBlock;
acc += (reward * 1e12) / totalStaked;
}
return (balanceOf(user) * acc / 1e12) - userRewardDebt[user];
}
}
借贷协议
借贷原理
借贷协议允许用户存入资产赚取利息,或抵押资产借出其他资产。核心概念:
存款:用户存入资产成为储户,获得利息收益。
借款:用户抵押资产后可以借出其他资产,需要支付利息。
抵押率:借款金额与抵押物价值的比例。例如,ETH 抵押率 75%,意味着价值 100 美元的 ETH 最多借 75 美元。
清算:当借款人抵押物价值下降,抵押率超过阈值时,任何人都可以清算部分抵押物。
简化借贷合约
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract SimpleLending is Ownable {
struct DepositInfo {
uint256 amount;
uint256 depositTime;
}
struct BorrowInfo {
uint256 amount;
uint256 collateralAmount;
address collateralToken;
}
IERC20 public lendingToken;
mapping(address => uint256) public collateralRatio; // 抵押率(百分比)
mapping(address => uint256) public tokenPrice; // 代币价格(USD)
mapping(address => DepositInfo) public deposits;
mapping(address => BorrowInfo) public borrows;
uint256 public interestRate = 5; // 年利率 5%
uint256 public constant SECONDS_PER_YEAR = 31536000;
constructor(address _lendingToken) Ownable(msg.sender) {
lendingToken = IERC20(_lendingToken);
}
// 设置抵押率
function setCollateralRatio(address token, uint256 ratio) external onlyOwner {
require(ratio > 0 && ratio <= 100, "Invalid ratio");
collateralRatio[token] = ratio;
}
// 设置代币价格
function setTokenPrice(address token, uint256 price) external onlyOwner {
tokenPrice[token] = price;
}
// 存款
function deposit(uint256 amount) external {
lendingToken.transferFrom(msg.sender, address(this), amount);
if (deposits[msg.sender].amount > 0) {
uint256 interest = calculateInterest(msg.sender);
deposits[msg.sender].amount += interest;
}
deposits[msg.sender].amount += amount;
deposits[msg.sender].depositTime = block.timestamp;
}
// 取款
function withdraw(uint256 amount) external {
require(deposits[msg.sender].amount >= amount, "Insufficient balance");
uint256 interest = calculateInterest(msg.sender);
deposits[msg.sender].amount = deposits[msg.sender].amount - amount + interest;
deposits[msg.sender].depositTime = block.timestamp;
lendingToken.transfer(msg.sender, amount);
}
// 抵押借款
function borrow(
uint256 borrowAmount,
uint256 collateralAmount,
address collateralToken
) external {
require(collateralRatio[collateralToken] > 0, "Token not supported");
require(tokenPrice[collateralToken] > 0, "Price not set");
uint256 collateralValue = collateralAmount * tokenPrice[collateralToken];
uint256 maxBorrow = (collateralValue * collateralRatio[collateralToken]) / 100;
require(borrowAmount <= maxBorrow, "Over collateral limit");
require(lendingToken.balanceOf(address(this)) >= borrowAmount, "Insufficient liquidity");
IERC20(collateralToken).transferFrom(msg.sender, address(this), collateralAmount);
lendingToken.transfer(msg.sender, borrowAmount);
borrows[msg.sender] = BorrowInfo({
amount: borrowAmount,
collateralAmount: collateralAmount,
collateralToken: collateralToken
});
}
// 还款
function repay(uint256 amount) external {
BorrowInfo storage borrowInfo = borrows[msg.sender];
require(borrowInfo.amount >= amount, "Over repayment");
lendingToken.transferFrom(msg.sender, address(this), amount);
borrowInfo.amount -= amount;
if (borrowInfo.amount == 0) {
IERC20(borrowInfo.collateralToken).transfer(
msg.sender,
borrowInfo.collateralAmount
);
delete borrows[msg.sender];
}
}
// 清算
function liquidate(address borrower) external {
BorrowInfo storage borrowInfo = borrows[borrower];
require(borrowInfo.amount > 0, "No borrow");
uint256 collateralValue = borrowInfo.collateralAmount * tokenPrice[borrowInfo.collateralToken];
uint256 maxBorrow = (collateralValue * collateralRatio[borrowInfo.collateralToken]) / 100;
require(borrowInfo.amount > maxBorrow, "Not liquidatable");
lendingToken.transferFrom(msg.sender, address(this), borrowInfo.amount);
uint256 liquidationReward = (borrowInfo.collateralAmount * 105) / 100; // 5% 奖励
IERC20(borrowInfo.collateralToken).transfer(msg.sender, liquidationReward);
delete borrows[borrower];
}
// 计算利息
function calculateInterest(address user) public view returns (uint256) {
if (deposits[user].amount == 0) return 0;
uint256 timePassed = block.timestamp - deposits[user].depositTime;
return (deposits[user].amount * interestRate * timePassed) / (100 * SECONDS_PER_YEAR);
}
// 查询存款余额(含利息)
function getDepositBalance(address user) external view returns (uint256) {
return deposits[user].amount + calculateInterest(user);
}
}
稳定币
稳定币类型
法币抵押型:由法币储备支持,如 USDC、USDT
加密资产抵押型:由加密资产超额抵押,如 DAI
算法稳定币:通过算法调节供应量,如 UST(已失败)
抵押型稳定币示例
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract CollateralizedStablecoin is ERC20, Ownable {
uint256 public constant COLLATERAL_RATIO = 150; // 150% 抵押率
uint256 public constant LIQUIDATION_RATIO = 120; // 120% 清算线
mapping(address => uint256) public collateral; // 抵押物数量
mapping(address => uint256) public debt; // 债务数量
uint256 public collateralPrice = 2000e18; // 抵押物价格(如 ETH)
uint256 public totalCollateral;
uint256 public totalDebt;
constructor() ERC20("My Stablecoin", "MST") Ownable(msg.sender) {}
// 存入抵押物并铸造稳定币
function depositAndMint(uint256 collateralAmount, uint256 mintAmount) external payable {
require(msg.value == collateralAmount, "Invalid collateral amount");
uint256 collateralValue = (collateralAmount * collateralPrice) / 1e18;
uint256 newDebt = debt[msg.sender] + mintAmount;
require(
(collateralValue * 100) / newDebt >= COLLATERAL_RATIO,
"Insufficient collateral ratio"
);
collateral[msg.sender] += collateralAmount;
debt[msg.sender] = newDebt;
totalCollateral += collateralAmount;
totalDebt += mintAmount;
_mint(msg.sender, mintAmount);
}
// 偿还债务并取回抵押物
function repayAndWithdraw(uint256 repayAmount, uint256 withdrawAmount) external {
require(debt[msg.sender] >= repayAmount, "Insufficient debt");
require(collateral[msg.sender] >= withdrawAmount, "Insufficient collateral");
_burn(msg.sender, repayAmount);
debt[msg.sender] -= repayAmount;
collateral[msg.sender] -= withdrawAmount;
totalDebt -= repayAmount;
totalCollateral -= withdrawAmount;
if (debt[msg.sender] > 0) {
uint256 remainingCollateral = collateral[msg.sender];
uint256 collateralValue = (remainingCollateral * collateralPrice) / 1e18;
require(
(collateralValue * 100) / debt[msg.sender] >= COLLATERAL_RATIO,
"Would breach collateral ratio"
);
}
payable(msg.sender).transfer(withdrawAmount);
}
// 清算
function liquidate(address user) external {
require(debt[user] > 0, "No debt");
uint256 collateralValue = (collateral[user] * collateralPrice) / 1e18;
uint256 ratio = (collateralValue * 100) / debt[user];
require(ratio < LIQUIDATION_RATIO, "Not liquidatable");
uint256 debtAmount = debt[user];
uint256 collateralAmount = collateral[user];
_burn(msg.sender, debtAmount);
debt[user] = 0;
collateral[user] = 0;
totalDebt -= debtAmount;
totalCollateral -= collateralAmount;
// 清算奖励 10%
uint256 reward = (collateralAmount * 110) / 100;
payable(msg.sender).transfer(reward);
}
// 更新抵押物价格
function updateCollateralPrice(uint256 newPrice) external onlyOwner {
collateralPrice = newPrice;
}
// 查询抵押率
function getCollateralRatio(address user) external view returns (uint256) {
if (debt[user] == 0) return type(uint256).max;
uint256 collateralValue = (collateral[user] * collateralPrice) / 1e18;
return (collateralValue * 100) / debt[user];
}
receive() external payable {}
}
收益聚合器
收益聚合器自动将资金分配到收益最高的协议,优化用户收益。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
interface IYieldSource {
function deposit(uint256 amount) external;
function withdraw(uint256 amount) external returns (uint256);
function balanceOf(address user) external view returns (uint256);
}
contract YieldAggregator is ERC20, Ownable {
IERC20 public want;
IYieldSource[] public sources;
constructor(address _want) ERC20("Yield Token", "YT") Ownable(msg.sender) {
want = IERC20(_want);
}
// 添加收益源
function addSource(address source) external onlyOwner {
sources.push(IYieldSource(source));
}
// 存入资金
function deposit(uint256 amount) external {
want.transferFrom(msg.sender, address(this), amount);
uint256 totalShares = totalSupply();
uint256 totalValue = getTotalValue();
uint256 shares;
if (totalShares == 0) {
shares = amount;
} else {
shares = (amount * totalShares) / totalValue;
}
_mint(msg.sender, shares);
// 分配到收益源
_rebalance();
}
// 取出资金
function withdraw(uint256 shares) external {
require(balanceOf(msg.sender) >= shares, "Insufficient shares");
uint256 totalShares = totalSupply();
uint256 totalValue = getTotalValue();
uint256 amount = (shares * totalValue) / totalShares;
_burn(msg.sender, shares);
// 从收益源取回资金
_withdrawFromSources(amount);
want.transfer(msg.sender, amount);
}
// 获取总价值
function getTotalValue() public view returns (uint256) {
uint256 total = want.balanceOf(address(this));
for (uint256 i = 0; i < sources.length; i++) {
total += sources[i].balanceOf(address(this));
}
return total;
}
// 重新平衡资金分配
function _rebalance() internal {
uint256 balance = want.balanceOf(address(this));
if (balance > 0 && sources.length > 0) {
// 简单平均分配
uint256 amountPerSource = balance / sources.length;
for (uint256 i = 0; i < sources.length; i++) {
want.approve(address(sources[i]), amountPerSource);
sources[i].deposit(amountPerSource);
}
}
}
// 从收益源取回资金
function _withdrawFromSources(uint256 amount) internal {
uint256 remaining = amount;
for (uint256 i = 0; i < sources.length && remaining > 0; i++) {
uint256 sourceBalance = sources[i].balanceOf(address(this));
uint256 withdrawAmount = remaining > sourceBalance ? sourceBalance : remaining;
if (withdrawAmount > 0) {
sources[i].withdraw(withdrawAmount);
remaining -= withdrawAmount;
}
}
}
// 手动重新平衡
function rebalance() external onlyOwner {
_rebalance();
}
}
DeFi 安全注意事项
常见攻击向量
闪电贷攻击:攻击者利用闪电贷借出大量资金,操纵价格后获利。
预言机操纵:攻击者操纵价格预言机,影响协议的定价逻辑。
重入攻击:攻击者在合约更新状态前再次调用函数。
三明治攻击:攻击者在大额交易前后插入交易获利。
安全最佳实践
// 使用 TWAP 预言机防止价格操纵
contract TWAPOracle {
uint256 public constant PERIOD = 30 minutes;
struct Observation {
uint256 timestamp;
uint256 price0Cumulative;
uint256 price1Cumulative;
}
mapping(address => Observation) public observations;
function update(address pair) external {
Observation storage obs = observations[pair];
uint256 timeElapsed = block.timestamp - obs.timestamp;
if (timeElapsed >= PERIOD) {
(uint256 price0Cumulative, uint256 price1Cumulative, ) =
IUniswapV2Pair(pair).getReserves();
obs.timestamp = block.timestamp;
obs.price0Cumulative = price0Cumulative;
obs.price1Cumulative = price1Cumulative;
}
}
function consult(
address pair,
uint256 amountIn,
uint256 priceCumulativeStart,
uint256 priceCumulativeEnd,
uint256 timeElapsed
) external pure returns (uint256 amountOut) {
require(timeElapsed >= PERIOD, "Period not elapsed");
amountOut = (amountIn * (priceCumulativeEnd - priceCumulativeStart)) /
(2 ** 112) / timeElapsed;
}
}
// 使用重入保护
contract SecureVault {
bool private locked;
modifier nonReentrant() {
require(!locked, "Reentrant call");
locked = true;
_;
locked = false;
}
function withdraw() external nonReentrant {
// 先更新状态
uint256 amount = balances[msg.sender];
balances[msg.sender] = 0;
// 再发送资金
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "Transfer failed");
}
mapping(address => uint256) public balances;
}
小结
本章介绍了 DeFi 的核心概念和实现原理,包括 DEX、借贷协议、稳定币和收益聚合器。DeFi 是一个快速发展的领域,新的协议和模式不断涌现。开发 DeFi 应用时,安全性是首要考虑因素,建议使用经过审计的库如 OpenZeppelin,并进行充分测试。下一章我们将学习 NFT 的开发实践。