跳到主要内容

3 篇博文 含有标签「solidity」

查看所有标签

NFT(非同质化代币)作为区块链技术的重要应用,已经在艺术、游戏、收藏品等领域展现出巨大潜力。本文将带你从零开始构建一个功能完整的NFT市场。

NFT基础概念

什么是NFT?

NFT(Non-Fungible Token)是非同质化代币,每个代币都是独一无二的,不可互换。这与比特币等同质化代币形成对比。

ERC721标准

ERC721是以太坊上NFT的标准协议,定义了NFT的基本接口:

interface IERC721 {
function balanceOf(address owner) external view returns (uint256 balance);
function ownerOf(uint256 tokenId) external view returns (address owner);
function safeTransferFrom(address from, address to, uint256 tokenId) external;
function transferFrom(address from, address to, uint256 tokenId) external;
function approve(address to, uint256 tokenId) external;
function getApproved(uint256 tokenId) external view returns (address operator);
function setApprovalForAll(address operator, bool approved) external;
function isApprovedForAll(address owner, address operator) external view returns (bool);
}

构建NFT合约

1. 基础NFT合约

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

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Counters.sol";

contract MyNFT is ERC721, ERC721Enumerable, ERC721URIStorage, Ownable {
using Counters for Counters.Counter;
Counters.Counter private _tokenIds;

uint256 public maxSupply = 10000;
uint256 public mintPrice = 0.01 ether;

event Minted(address indexed to, uint256 indexed tokenId, string tokenURI);

constructor(string memory name, string memory symbol)
ERC721(name, symbol)
Ownable(msg.sender)
{}

function mintNFT(string memory tokenURI) public payable returns (uint256) {
require(_tokenIds.current() < maxSupply, "Max supply reached");
require(msg.value >= mintPrice, "Insufficient payment");

_tokenIds.increment();
uint256 newTokenId = _tokenIds.current();

_safeMint(msg.sender, newTokenId);
_setTokenURI(newTokenId, tokenURI);

emit Minted(msg.sender, newTokenId, tokenURI);

return newTokenId;
}

function totalMinted() public view returns (uint256) {
return _tokenIds.current();
}

function withdraw() public onlyOwner {
uint256 balance = address(this).balance;
require(balance > 0, "No funds to withdraw");
payable(owner()).transfer(balance);
}

// Override required functions
function _beforeTokenTransfer(address from, address to, uint256 tokenId, uint256 batchSize)
internal
override(ERC721, ERC721Enumerable)
{
super._beforeTokenTransfer(from, to, tokenId, batchSize);
}

function _burn(uint256 tokenId) internal override(ERC721, ERC721URIStorage) {
super._burn(tokenId);
}

function tokenURI(uint256 tokenId)
public
view
override(ERC721, ERC721URIStorage)
returns (string memory)
{
return super.tokenURI(tokenId);
}

function supportsInterface(bytes4 interfaceId)
public
view
override(ERC721, ERC721Enumerable, ERC721URIStorage)
returns (bool)
{
return super.supportsInterface(interfaceId);
}
}

2. 带有版税的NFT合约

import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Royalty.sol";

contract RoyaltyNFT is MyNFT, ERC721Royalty {
uint96 public royaltyFee = 250; // 2.5% royalty (250 / 10000)

constructor(string memory name, string memory symbol)
MyNFT(name, symbol)
ERC721Royalty()
{
_setDefaultRoyalty(msg.sender, royaltyFee);
}

function mintNFT(string memory tokenURI) public payable returns (uint256) {
uint256 tokenId = super.mintNFT(tokenURI);
return tokenId;
}

function setRoyalty(address receiver, uint96 feeNumerator) external onlyOwner {
_setDefaultRoyalty(receiver, feeNumerator);
}

// Override required functions
function _beforeTokenTransfer(address from, address to, uint256 tokenId, uint256 batchSize)
internal
override(MyNFT, ERC721Enumerable)
{
super._beforeTokenTransfer(from, to, tokenId, batchSize);
}

function _burn(uint256 tokenId)
internal
override(MyNFT, ERC721URIStorage, ERC721Royalty)
{
super._burn(tokenId);
}

function supportsInterface(bytes4 interfaceId)
public
view
override(MyNFT, ERC721Royalty)
returns (bool)
{
return super.supportsInterface(interfaceId);
}
}

构建NFT市场合约

1. 基础市场合约

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

import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/security/Pausable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract NFTMarketplace is ReentrancyGuard, Pausable, Ownable, IERC721Receiver {
struct Listing {
address seller;
address nftContract;
uint256 tokenId;
uint256 price;
bool isActive;
}

struct Auction {
address seller;
address nftContract;
uint256 tokenId;
uint256 startingPrice;
uint256 highestBid;
address highestBidder;
uint256 endTime;
bool isActive;
}

mapping(uint256 => Listing) public listings;
mapping(uint256 => Auction) public auctions;
mapping(address => uint256) public pendingWithdrawals;

uint256 public listingFee = 0.001 ether;
uint256 public auctionFee = 0.002 ether;
uint256 public marketplaceFee = 250; // 2.5% (250/10000)
uint256 public nextListingId;
uint256 public nextAuctionId;

event NFTListed(uint256 indexed listingId, address indexed seller, address indexed nftContract, uint256 tokenId, uint256 price);
event NFTPurchased(uint256 indexed listingId, address indexed buyer, uint256 price);
event NFTDelisted(uint256 indexed listingId);
event AuctionCreated(uint256 indexed auctionId, address indexed seller, address indexed nftContract, uint256 tokenId, uint256 startingPrice, uint256 endTime);
event BidPlaced(uint256 indexed auctionId, address indexed bidder, uint256 amount);
event AuctionEnded(uint256 indexed auctionId, address indexed winner, uint256 amount);

constructor() Ownable(msg.sender) {}

// List NFT for sale
function listNFT(address _nftContract, uint256 _tokenId, uint256 _price) external payable nonReentrant whenNotPaused {
require(msg.value >= listingFee, "Insufficient listing fee");
require(_price > 0, "Price must be greater than 0");

IERC721 nft = IERC721(_nftContract);
require(nft.ownerOf(_tokenId) == msg.sender, "Not the owner");
require(nft.getApproved(_tokenId) == address(this), "Marketplace not approved");

uint256 listingId = nextListingId++;
listings[listingId] = Listing({
seller: msg.sender,
nftContract: _nftContract,
tokenId: _tokenId,
price: _price,
isActive: true
});

nft.safeTransferFrom(msg.sender, address(this), _tokenId);

emit NFTListed(listingId, msg.sender, _nftContract, _tokenId, _price);
}

// Purchase listed NFT
function purchaseNFT(uint256 _listingId) external payable nonReentrant {
Listing storage listing = listings[_listingId];
require(listing.isActive, "Listing not active");
require(msg.value >= listing.price, "Insufficient payment");

listing.isActive = false;

uint256 fee = (listing.price * marketplaceFee) / 10000;
uint256 sellerProceeds = listing.price - fee;

pendingWithdrawals[listing.seller] += sellerProceeds;
pendingWithdrawals[owner()] += fee;

if (msg.value > listing.price) {
pendingWithdrawals[msg.sender] += msg.value - listing.price;
}

IERC721(listing.nftContract).safeTransferFrom(address(this), msg.sender, listing.tokenId);

emit NFTPurchased(_listingId, msg.sender, listing.price);
}

// Create auction
function createAuction(address _nftContract, uint256 _tokenId, uint256 _startingPrice, uint256 _duration) external payable nonReentrant whenNotPaused {
require(msg.value >= auctionFee, "Insufficient auction fee");
require(_startingPrice > 0, "Starting price must be greater than 0");
require(_duration >= 3600 && _duration <= 604800, "Duration must be between 1 hour and 7 days");

IERC721 nft = IERC721(_nftContract);
require(nft.ownerOf(_tokenId) == msg.sender, "Not the owner");
require(nft.getApproved(_tokenId) == address(this), "Marketplace not approved");

uint256 auctionId = nextAuctionId++;
auctions[auctionId] = Auction({
seller: msg.sender,
nftContract: _nftContract,
tokenId: _tokenId,
startingPrice: _startingPrice,
highestBid: _startingPrice,
highestBidder: address(0),
endTime: block.timestamp + _duration,
isActive: true
});

nft.safeTransferFrom(msg.sender, address(this), _tokenId);

emit AuctionCreated(auctionId, msg.sender, _nftContract, _tokenId, _startingPrice, block.timestamp + _duration);
}

// Place bid
function placeBid(uint256 _auctionId) external payable nonReentrant {
Auction storage auction = auctions[_auctionId];
require(auction.isActive, "Auction not active");
require(block.timestamp < auction.endTime, "Auction ended");
require(msg.value > auction.highestBid, "Bid too low");

if (auction.highestBidder != address(0)) {
pendingWithdrawals[auction.highestBidder] += auction.highestBid;
}

auction.highestBid = msg.value;
auction.highestBidder = msg.sender;

emit BidPlaced(_auctionId, msg.sender, msg.value);
}

// End auction
function endAuction(uint256 _auctionId) external nonReentrant {
Auction storage auction = auctions[_auctionId];
require(auction.isActive, "Auction not active");
require(block.timestamp >= auction.endTime, "Auction not ended");

auction.isActive = false;

if (auction.highestBidder != address(0)) {
uint256 fee = (auction.highestBid * marketplaceFee) / 10000;
uint256 sellerProceeds = auction.highestBid - fee;

pendingWithdrawals[auction.seller] += sellerProceeds;
pendingWithdrawals[owner()] += fee;

IERC721(auction.nftContract).safeTransferFrom(address(this), auction.highestBidder, auction.tokenId);

emit AuctionEnded(_auctionId, auction.highestBidder, auction.highestBid);
} else {
IERC721(auction.nftContract).safeTransferFrom(address(this), auction.seller, auction.tokenId);
}
}

// Withdraw funds
function withdraw() external nonReentrant {
uint256 amount = pendingWithdrawals[msg.sender];
require(amount > 0, "No funds to withdraw");

pendingWithdrawals[msg.sender] = 0;
payable(msg.sender).transfer(amount);
}

// Admin functions
function setListingFee(uint256 _fee) external onlyOwner {
listingFee = _fee;
}

function setAuctionFee(uint256 _fee) external onlyOwner {
auctionFee = _fee;
}

function setMarketplaceFee(uint256 _fee) external onlyOwner {
require(_fee <= 1000, "Fee too high"); // Max 10%
marketplaceFee = _fee;
}

function pause() external onlyOwner {
_pause();
}

function unpause() external onlyOwner {
_unpause();
}

// Emergency functions
function emergencyWithdraw() external onlyOwner {
payable(owner()).transfer(address(this).balance);
}

// IERC721Receiver implementation
function onERC721Received(address, address, uint256, bytes calldata) external pure override returns (bytes4) {
return this.onERC721Received.selector;
}

receive() external payable {}
}

2. 批量交易功能

contract NFTMarketplaceBatch is NFTMarketplace {
struct BatchListing {
address nftContract;
uint256[] tokenIds;
uint256[] prices;
}

event BatchNFTListed(uint256 indexed batchId, address indexed seller, uint256 totalItems);
event BatchNFTPurchased(uint256 indexed batchId, address indexed buyer, uint256 totalPrice);

function listBatch(BatchListing calldata batchListing) external payable nonReentrant whenNotPaused {
require(batchListing.tokenIds.length == batchListing.prices.length, "Arrays length mismatch");
require(batchListing.tokenIds.length > 0, "Empty batch");
require(msg.value >= listingFee * batchListing.tokenIds.length, "Insufficient listing fee");

IERC721 nft = IERC721(batchListing.nftContract);

for (uint i = 0; i < batchListing.tokenIds.length; i++) {
require(batchListing.prices[i] > 0, "Price must be greater than 0");
require(nft.ownerOf(batchListing.tokenIds[i]) == msg.sender, "Not the owner");

uint256 listingId = nextListingId++;
listings[listingId] = Listing({
seller: msg.sender,
nftContract: batchListing.nftContract,
tokenId: batchListing.tokenIds[i],
price: batchListing.prices[i],
isActive: true
});

nft.safeTransferFrom(msg.sender, address(this), batchListing.tokenIds[i]);
}

emit BatchNFTListed(nextListingId - 1, msg.sender, batchListing.tokenIds.length);
}

function purchaseBatch(uint256[] calldata listingIds) external payable nonReentrant {
uint256 totalPrice = 0;

for (uint i = 0; i < listingIds.length; i++) {
Listing storage listing = listings[listingIds[i]];
require(listing.isActive, "Listing not active");
totalPrice += listing.price;
}

require(msg.value >= totalPrice, "Insufficient payment");

for (uint i = 0; i < listingIds.length; i++) {
Listing storage listing = listings[listingIds[i]];
listing.isActive = false;

uint256 fee = (listing.price * marketplaceFee) / 10000;
uint256 sellerProceeds = listing.price - fee;

pendingWithdrawals[listing.seller] += sellerProceeds;
pendingWithdrawals[owner()] += fee;

IERC721(listing.nftContract).safeTransferFrom(address(this), msg.sender, listing.tokenId);
}

if (msg.value > totalPrice) {
pendingWithdrawals[msg.sender] += msg.value - totalPrice;
}

emit BatchNFTPurchased(listingIds[0], msg.sender, totalPrice);
}
}

前端集成

1. 连接钱包

import { ethers } from 'ethers';
import NFTMarketplace from './artifacts/contracts/NFTMarketplace.sol/NFTMarketplace.json';

const MARKETPLACE_ADDRESS = "0x...";

export async function connectWallet() {
if (typeof window.ethereum !== 'undefined') {
try {
await window.ethereum.request({ method: 'eth_requestAccounts' });
const provider = new ethers.providers.Web3Provider(window.ethereum);
const signer = provider.getSigner();
const address = await signer.getAddress();

const marketplace = new ethers.Contract(
MARKETPLACE_ADDRESS,
NFTMarketplace.abi,
signer
);

return { provider, signer, address, marketplace };
} catch (error) {
console.error('Failed to connect wallet:', error);
throw error;
}
} else {
throw new Error('MetaMask not installed');
}
}

2. 列出NFT

export async function listNFT(marketplace, nftContract, tokenId, price) {
try {
const tx = await marketplace.listNFT(
nftContract,
tokenId,
ethers.utils.parseEther(price.toString()),
{ value: ethers.utils.parseEther("0.001") } // listing fee
);

await tx.wait();
console.log('NFT listed successfully');
return tx.hash;
} catch (error) {
console.error('Failed to list NFT:', error);
throw error;
}
}

3. 购买NFT

export async function purchaseNFT(marketplace, listingId, price) {
try {
const tx = await marketplace.purchaseNFT(listingId, {
value: ethers.utils.parseEther(price.toString())
});

await tx.wait();
console.log('NFT purchased successfully');
return tx.hash;
} catch (error) {
console.error('Failed to purchase NFT:', error);
throw error;
}
}

部署和测试

1. Hardhat配置

// hardhat.config.js
require("@nomiclabs/hardhat-waffle");
require("@nomiclabs/hardhat-etherscan");
require("dotenv").config();

module.exports = {
solidity: "0.8.19",
networks: {
goerli: {
url: `https://goerli.infura.io/v3/${process.env.INFURA_PROJECT_ID}`,
accounts: [process.env.PRIVATE_KEY]
},
mainnet: {
url: `https://mainnet.infura.io/v3/${process.env.INFURA_PROJECT_ID}`,
accounts: [process.env.PRIVATE_KEY]
}
},
etherscan: {
apiKey: process.env.ETHERSCAN_API_KEY
}
};

2. 部署脚本

// scripts/deploy.js
const hre = require("hardhat");

async function main() {
const NFTMarketplace = await hre.ethers.getContractFactory("NFTMarketplace");
const marketplace = await NFTMarketplace.deploy();

await marketplace.deployed();

console.log("NFTMarketplace deployed to:", marketplace.address);

// 验证合约
if (network.name !== "localhost" && network.name !== "hardhat") {
await hre.run("verify:verify", {
address: marketplace.address,
constructorArguments: [],
});
}
}

main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});

总结

通过构建这个NFT市场,我们学习了:

  1. ERC721标准: NFT的基本实现
  2. 市场合约: 买卖NFT的核心逻辑
  3. 拍卖功能: 竞价机制的实现
  4. 版税机制: 创作者收益的保障
  5. 批量交易: 提高用户体验
  6. 前端集成: Web3应用的完整流程

下一步

  1. 优化gas费用: 使用代理模式降低部署成本
  2. 添加更多功能: 报价、分期付款等
  3. 多链支持: 支持多个区块链网络
  4. IPFS集成: 去中心化存储NFT元数据
  5. 移动应用: 开发移动端DApp

继续探索NFT的无限可能!

参考资料

nftsoliditysmart-contractsweb3ethereum阅读需 6 分钟

去中心化金融(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. 进行安全审计

参考资料

defisolidityuniswapdexsmart-contracts阅读需 5 分钟

在区块链世界中,智能合约的安全性至关重要。一个微小的漏洞可能导致数百万美元的损失。作为Web3开发者,我们必须将安全性放在首位。

常见的智能合约漏洞

1. 重入攻击 (Reentrancy Attack)

重入攻击是最著名的智能合约漏洞之一,The DAO事件就是典型案例。

// 不安全的代码
contract VulnerableBank {
mapping(address => uint) public balances;

function withdraw(uint amount) public {
require(balances[msg.sender] >= amount);
// 先转账,后更新状态 - 危险!
(bool success, ) = msg.sender.call{value: amount}("");
require(success);
balances[msg.sender] -= amount;
}
}

解决方案:使用检查-生效-交互 (Checks-Effects-Interactions) 模式

// 安全的代码
contract SecureBank {
mapping(address => uint) public balances;

function withdraw(uint amount) public {
// 检查
require(balances[msg.sender] >= amount, "Insufficient balance");

// 生效
balances[msg.sender] -= amount;

// 交互
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "Transfer failed");
}
}

2. 整数溢出和下溢

在Solidity 0.8之前,整数溢出是常见的安全问题。

// 不安全的代码 (Solidity < 0.8)
contract VulnerableToken {
mapping(address => uint) public balances;

function transfer(address to, uint amount) public {
// 可能导致下溢
balances[msg.sender] -= amount;
balances[to] += amount;
}
}

解决方案:

  • 使用Solidity 0.8+(内置溢出检查)
  • 或使用OpenZeppelin的SafeMath库

3. 访问控制问题

确保只有授权用户能调用敏感函数。

contract SecureContract {
address public owner;

modifier onlyOwner() {
require(msg.sender == owner, "Not authorized");
_;
}

function criticalFunction() public onlyOwner {
// 只有owner能调用
}
}

安全开发最佳实践

1. 使用成熟的开发框架

  • OpenZeppelin Contracts: 提供经过审计的安全合约
  • Hardhat: 专业的开发环境
  • Foundry: 快速测试框架

2. 全面的测试策略

// 使用Foundry进行模糊测试
contract CounterTest is Test {
Counter public counter;

function setUp() public {
counter = new Counter();
}

function testIncrement() public {
counter.increment();
assertEq(counter.number(), 1);
}

// 模糊测试
function testFuzzIncrement(uint256 x) public {
counter.setNumber(x);
counter.increment();
assertEq(counter.number(), x + 1);
}
}

3. 代码审计和静态分析

使用工具进行自动化安全检查:

  • Slither: 静态分析工具
  • MythX: 安全分析平台
  • Echidna: 属性测试工具

4. 遵循已验证的设计模式

代理模式 (Proxy Pattern)

contract Proxy {
address public implementation;

function upgradeTo(address newImplementation) public {
implementation = newImplementation;
}

fallback() external payable {
address impl = implementation;
assembly {
let ptr := mload(0x40)
calldatacopy(ptr, 0, calldatasize())
let result := delegatecall(gas(), impl, ptr, calldatasize(), 0, 0)
let size := returndatasize()
returndatacopy(ptr, 0, size)
switch result
case 0 { revert(ptr, size) }
default { return(ptr, size) }
}
}
}

拉取模式 (Pull Pattern)

contract PullPayment {
mapping(address => uint) public payments;

function withdrawPayment() public {
uint payment = payments[msg.sender];
require(payment > 0, "No payment");
payments[msg.sender] = 0;
(bool success, ) = msg.sender.call{value: payment}("");
require(success);
}
}

总结

智能合约安全是一个持续学习的过程。记住这些核心原则:

  1. 简单性: 保持代码简单明了
  2. 可升级性: 考虑未来的升级需求
  3. 测试: 编写全面的测试用例
  4. 审计: 定期进行安全审计
  5. 监控: 部署后持续监控

安全不是一次性的事情,而是整个开发生命周期的持续过程。保持学习,保持警惕!

参考资料

soliditysecurityblockchainsmart-contracts阅读需 2 分钟