区块链开发实战:从智能合约到DApp 引言 区块链技术正在重塑数字世界的信任机制。从加密货币到DeFi,从NFT到DAO,区块链应用场景不断扩展。本文将深入讲解区块链开发的核心技能、智能合约编程和DApp构建实践。 一、区块链基础 1.1 核心概念 区块链的本质: 分布式账本(Distributed Ledger) 不可篡改的数据结构 去中心化共识机制 智能合约(可编程的区块链) 关键技术: 哈希函数(SHA-256, Keccak-256) 非对称加密(ECC, RSA) 默克尔树(Merkle Tree) 共识算法(PoW, PoS, DPoS) 1.2 开发环境搭建 二、智能合约开发 2.1 Solidity基础 Hello World合约: 数据类型: 2.
区块链技术正在重塑数字世界的信任机制。从加密货币到DeFi,从NFT到DAO,区块链应用场景不断扩展。本文将深入讲解区块链开发的核心技能、智能合约编程和DApp构建实践。
区块链的本质:
关键技术:
# 安装Node.js和npm curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash - sudo apt-get install -y nodejs # 安装Truffle(以太坊开发框架) npm install -g truffle # 安装Hardhat(现代化开发环境) npm install --global hardhat-shorthand # 安装Ganache(本地区块链) npm install -g ganache # 安装Foundry(Solidity开发套件) curl -L https://foundry.paradigm.xyz | bash foundryup
Hello World合约:
// SPDX-License-Identifier: MIT pragma solidity ^0.8.19; contract HelloWorld { string public message = "Hello, World!"; // 设置消息 function setMessage(string memory _message) public { message = _message; } // 获取消息 function getMessage() public view returns (string memory) { return message; } }
数据类型:
contract DataTypes { // 值类型 bool public isActive = true; uint256 public count = 100; int256 public temperature = -5; address public owner = msg.sender; bytes32 public hash = keccak256("hello"); // 引用类型 string public name = "Alice"; uint256[] public numbers = [1, 2, 3]; mapping(address => uint256) public balances; // 结构体 struct User { string name; uint256 age; bool verified; } User public alice; // 枚举 enum Status { Pending, Active, Inactive } Status public currentStatus; constructor() { alice = User("Alice", 30, true); currentStatus = Status.Active; } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.19; interface IERC20 { function totalSupply() external view returns (uint256); function balanceOf(address account) external view returns (uint256); function transfer(address to, uint256 amount) external returns (bool); function allowance(address owner, address spender) external view returns (uint256); function approve(address spender, uint256 amount) external returns (bool); function transferFrom(address from, address to, uint256 amount) external returns (bool); } contract MyToken is IERC20 { string public name = "My Token"; string public symbol = "MTK"; uint8 public decimals = 18; uint256 public override totalSupply; mapping(address => uint256) public override balanceOf; mapping(address => mapping(address => uint256)) public override allowance; event Transfer(address indexed from, address indexed to, uint256 value); event Approval(address indexed owner, address indexed spender, uint256 value); constructor(uint256 initialSupply) { totalSupply = initialSupply * 10 ** decimals; balanceOf[msg.sender] = totalSupply; emit Transfer(address(0), msg.sender, totalSupply); } function transfer(address to, uint256 amount) public override returns (bool) { require(balanceOf[msg.sender] >= amount, "Insufficient balance"); balanceOf[msg.sender] -= amount; balanceOf[to] += amount; emit Transfer(msg.sender, to, amount); return true; } function approve(address spender, uint256 amount) public override returns (bool) { allowance[msg.sender][spender] = amount; emit Approval(msg.sender, spender, amount); return true; } function transferFrom(address from, address to, uint256 amount) public override returns (bool) { require(balanceOf[from] >= amount, "Insufficient balance"); require(allowance[from][msg.sender] >= amount, "Insufficient allowance"); balanceOf[from] -= amount; balanceOf[to] += amount; allowance[from][msg.sender] -= amount; emit Transfer(from, to, amount); return true; } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.19; import "@openzeppelin/contracts/token/ERC721/ERC721.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, ERC721URIStorage, Ownable { using Counters for Counters.Counter; Counters.Counter private _tokenIdCounter; constructor() ERC721("MyNFT", "MNFT") {} function safeMint(address to, string memory uri) public onlyOwner { uint256 tokenId = _tokenIdCounter.current(); _tokenIdCounter.increment(); _safeMint(to, tokenId); _setTokenURI(tokenId, uri); } // 必须重写以解决冲突 function tokenURI(uint256 tokenId) public view override(ERC721, ERC721URIStorage) returns (string memory) { return super.tokenURI(tokenId); } function supportsInterface(bytes4 interfaceId) public view override(ERC721, ERC721URIStorage) returns (bool) { return super.supportsInterface(interfaceId); } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.19; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; contract SimpleDEX is ReentrancyGuard { IERC20 public tokenA; IERC20 public tokenB; uint256 public reserveA; uint256 public reserveB; event LiquidityAdded(address provider, uint256 amountA, uint256 amountB); event LiquidityRemoved(address provider, uint256 amountA, uint256 amountB); event Swap(address trader, address tokenIn, uint256 amountIn, address tokenOut, uint256 amountOut); constructor(address _tokenA, address _tokenB) { tokenA = IERC20(_tokenA); tokenB = IERC20(_tokenB); } // 添加流动性 function addLiquidity(uint256 amountA, uint256 amountB) external nonReentrant { tokenA.transferFrom(msg.sender, address(this), amountA); tokenB.transferFrom(msg.sender, address(this), amountB); reserveA += amountA; reserveB += amountB; emit LiquidityAdded(msg.sender, amountA, amountB); } // 移除流动性 function removeLiquidity(uint256 amountA, uint256 amountB) external nonReentrant { require(reserveA >= amountA && reserveB >= amountB, "Insufficient liquidity"); reserveA -= amountA; reserveB -= amountB; tokenA.transfer(msg.sender, amountA); tokenB.transfer(msg.sender, amountB); emit LiquidityRemoved(msg.sender, amountA, amountB); } // 获取输出金额(恒定乘积公式: x * y = k) function getAmountOut(uint256 amountIn, uint256 reserveIn, uint256 reserveOut) public pure returns (uint256) { require(reserveIn > 0 && reserveOut > 0, "Invalid reserves"); uint256 amountInWithFee = amountIn * 997; // 0.3% fee uint256 numerator = amountInWithFee * reserveOut; uint256 denominator = reserveIn * 1000 + amountInWithFee; return numerator / denominator; } // 交换代币 function swap(address tokenIn, uint256 amountIn, uint256 minAmountOut) external nonReentrant { require(tokenIn == address(tokenA) || tokenIn == address(tokenB), "Invalid token"); uint256 amountOut; if (tokenIn == address(tokenA)) { amountOut = getAmountOut(amountIn, reserveA, reserveB); require(amountOut >= minAmountOut, "Insufficient output amount"); reserveA += amountIn; reserveB -= amountOut; tokenA.transferFrom(msg.sender, address(this), amountIn); tokenB.transfer(msg.sender, amountOut); emit Swap(msg.sender, tokenA, amountIn, address(tokenB), amountOut); } else { amountOut = getAmountOut(amountIn, reserveB, reserveA); require(amountOut >= minAmountOut, "Insufficient output amount"); reserveB += amountIn; reserveA -= amountOut; tokenB.transferFrom(msg.sender, address(this), amountIn); tokenA.transfer(msg.sender, amountOut); emit Swap(msg.sender, tokenB, amountIn, address(tokenA), amountOut); } } }
// 安装web3.js // npm install web3 import Web3 from 'web3'; // 初始化Web3 const web3 = new Web3(window.ethereum); // 连接钱包 async function connectWallet() { try { // 请求用户授权 const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' }); const account = accounts[0]; console.log('Connected account:', account); // 获取网络ID const networkId = await web3.eth.net.getId(); console.log('Network ID:', networkId); return { account, networkId }; } catch (error) { console.error('Failed to connect wallet:', error); } } // 获取余额 async function getBalance(tokenAddress, account) { const tokenContract = new web3.eth.Contract(ABI, tokenAddress); const balance = await tokenContract.methods.balanceOf(account).call(); return balance; } // 转账代币 async function transferToken(tokenAddress, to, amount) { const tokenContract = new web3.eth.Contract(ABI, tokenAddress); // 获取当前的gas价格 const gasPrice = await web3.eth.getGasPrice(); // 估算gas const gasEstimate = await tokenContract.methods.transfer(to, amount).estimateGas({ from: account }); // 发送交易 const receipt = await tokenContract.methods.transfer(to, amount).send({ from: account, gas: gasEstimate, gasPrice: gasPrice }); return receipt; } // 监听事件 function listenToEvents(contractAddress, eventName, callback) { const contract = new web3.eth.Contract(ABI, contractAddress); contract.events[eventName]() .on('data', (event) => { console.log('Event received:', event); callback(event); }) .on('error', console.error); } // 使用示例 listenToEvents(tokenAddress, 'Transfer', (event) => { const { from, to, value } = event.returnValues; console.log(`Transfer: ${from} -> ${to}, amount: ${value}`); });
// 安装ethers.js // npm install ethers import { ethers } from 'ethers'; // 连接到以太坊网络 async function connect() { // 使用MetaMask const provider = new ethers.providers.Web3Provider(window.ethereum); await provider.send("eth_requestAccounts", []); const signer = provider.getSigner(); // 或者使用RPC // const provider = new ethers.providers.JsonRpcProvider("https://eth-mainnet.alchemyapi.io/v2/YOUR-API-KEY"); return { provider, signer }; } // 发送交易 async function sendTransaction(to, value) { const { signer } = await connect(); // 创建交易 const tx = { to: to, value: ethers.utils.parseEther(value) }; // 发送交易 const receipt = await signer.sendTransaction(tx); console.log('Transaction hash:', receipt.hash); // 等待确认 await receipt.wait(); console.log('Transaction confirmed'); return receipt; } // 调用合约 async function callContract(contractAddress, ABI, method, params) { const { provider, signer } = await connect(); const contract = new ethers.Contract(contractAddress, ABI, signer); // 只读调用 if (method.includes('get') || method.includes('balance')) { const result = await contract[method](...params); return result; } // 写入交易 const tx = await contract[method](...params); const receipt = await tx.wait(); return receipt; } // 监听区块 function listenToBlocks(callback) { const { provider } = await connect(); provider.on('block', (blockNumber) => { console.log('New block:', blockNumber); callback(blockNumber); }); } // 单位转换 const wei = ethers.utils.parseEther("1.0"); // 1 ETH -> Wei const eth = ethers.utils.formatEther(wei); // Wei -> 1 ETH
import React, { useState, useEffect } from 'react'; import { ethers } from 'ethers'; import TokenABI from './TokenABI.json'; function TokenBalance() { const [balance, setBalance] = useState('0'); const [account, setAccount] = useState(''); const [loading, setLoading] = useState(false); const tokenAddress = '0x...'; // 代币合约地址 // 连接钱包 const connectWallet = async () => { try { const provider = new ethers.providers.Web3Provider(window.ethereum); await provider.send("eth_requestAccounts", []); const signer = provider.getSigner(); const address = await signer.getAddress(); setAccount(address); } catch (error) { console.error(error); } }; // 获取余额 const fetchBalance = async () => { if (!account) return; setLoading(true); try { const provider = new ethers.providers.Web3Provider(window.ethereum); const tokenContract = new ethers.Contract(tokenAddress, TokenABI, provider); const balance = await tokenContract.balanceOf(account); setBalance(ethers.utils.formatEther(balance)); } catch (error) { console.error(error); } finally { setLoading(false); } }; // 转账 const transfer = async (to, amount) => { setLoading(true); try { const provider = new ethers.providers.Web3Provider(window.ethereum); const signer = provider.getSigner(); const tokenContract = new ethers.Contract(tokenAddress, TokenABI, signer); const tx = await tokenContract.transfer( to, ethers.utils.parseEther(amount) ); await tx.wait(); alert('Transfer successful!'); fetchBalance(); // 刷新余额 } catch (error) { console.error(error); alert('Transfer failed!'); } finally { setLoading(false); } }; useEffect(() => { if (account) { fetchBalance(); } }, [account]); return ( <div> {!account ? ( <button onClick={connectWallet}>Connect Wallet</button> ) : ( <> <p>Account: {account}</p> <p>Balance: {loading ? 'Loading...' : balance} Tokens</p> <button onClick={fetchBalance}>Refresh Balance</button> </> )} </div> ); } export default TokenBalance;
// test/Token.test.js const { expect } = require("chai"); const { ethers } = require("hardhat"); describe("Token contract", function () { let Token; let token; let owner; let addr1; let addr2; beforeEach(async function () { Token = await ethers.getContractFactory("MyToken"); [owner, addr1, addr2] = await ethers.getSigners(); token = await Token.deploy(1000000); }); describe("Deployment", function () { it("Should set the right owner", async function () { expect(await token.owner()).to.equal(owner.address); }); it("Should assign the total supply of tokens to the owner", async function () { const ownerBalance = await token.balanceOf(owner.address); expect(await token.totalSupply()).to.equal(ownerBalance); }); }); describe("Transactions", function () { it("Should transfer tokens between accounts", async function () { await token.transfer(addr1.address, 50); const addr1Balance = await token.balanceOf(addr1.address); expect(addr1Balance).to.equal(50); await token.connect(addr1).transfer(addr2.address, 50); const addr2Balance = await token.balanceOf(addr2.address); expect(addr2Balance).to.equal(50); }); it("Should fail if sender doesn't have enough tokens", async function () { const initialOwnerBalance = await token.balanceOf(owner.address); await expect( token.connect(addr1).transfer(owner.address, 1) ).to.be.revertedWith("Insufficient balance"); expect(await token.balanceOf(owner.address)).to.equal(initialOwnerBalance); }); }); });
// scripts/deploy.js const hre = require("hardhat"); async function main() { const Token = await hre.ethers.getContractFactory("MyToken"); const token = await Token.deploy(1000000); await token.deployed(); console.log("Token deployed to:", token.address); // 验证合约(Etherscan) if (hre.network.name !== "hardhat" && hre.network.name !== "localhost") { await token.deployTransaction.wait(5); // 等待5个区块 await hre.run("verify:verify", { address: token.address, constructorArguments: [1000000], }); } } main() .then(() => process.exit(0)) .catch((error) => { console.error(error); process.exit(1); });
# 编译合约 npx hardhat compile # 运行测试 npx hardhat test # 部署到测试网 npx hardhat run scripts/deploy.js --network goerli # 部署到主网 npx hardhat run scripts/deploy.js --network mainnet # 验证合约 npx hardhat verify --network mainnet CONTRACT_ADDRESS CONSTRUCTOR_ARGS
// 重入攻击防护 contract ReentrancyGuard { bool private locked; modifier noReentrant() { require(!locked, "Reentrant call"); locked = true; _; locked = false; } function withdraw() external noReentrant { // 提取逻辑 } } // 整数溢出防护(Solidity 0.8.x内置) contract SafeMath { function safeAdd(uint256 a, uint256 b) internal pure returns (uint256) { require(a + b >= a, "Overflow"); return a + b; } } // 访问控制 contract AccessControl { address public owner; mapping(address => bool) public admins; constructor() { owner = msg.sender; admins[owner] = true; } modifier onlyOwner() { require(msg.sender == owner, "Not owner"); _; } modifier onlyAdmin() { require(admins[msg.sender], "Not admin"); _; } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.19; import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; import "@openzeppelin/contracts/security/Pausable.sol"; contract SecureContract is Ownable, ReentrancyGuard, Pausable { function pause() public onlyOwner { _pause(); } function unpause() public onlyOwner { _unpause(); } function sensitiveFunction() external whenNotPaused nonReentrant { // 敏感操作 } }
区块链开发核心要点: