智能合约开发基础
智能合约是存储在区块链上的自执行程序,它们按照预设的规则自动执行,无需第三方介入。以太坊是最主要的智能合约平台,使用Solidity作为主要的编程语言。
智能合约概述
基本概念
智能合约是部署在区块链上的程序,具有以下特征:
- 自动执行:满足条件时自动执行
- 不可篡改:一旦部署,代码无法修改
- 透明公开:所有代码公开可见
- 去中心化:无需中心化管理机构
工作原理
graph TD
A[触发条件] --> B[智能合约]
B --> C{条件验证}
C -->|满足| D[执行操作]
C -->|不满足| E[保持状态]
D --> F[更新区块链状态]
应用场景
- 代币发行:创建自定义代币
- 去中心化金融:借贷、交易、保险
- 供应链管理:追踪商品流转
- 数字身份:身份验证和管理
- 投票系统:透明公正的投票
Solidity基础
开发环境搭建
必要工具
# 安装Node.js(推荐v16+)
# 安装Hardhat开发框架
npm install --save-dev hardhat
# 安装OpenZeppelin合约库
npm install @openzeppelin/contracts
# 安装Web3.js库
npm install web3
项目初始化
# 创建新的Hardhat项目
npx hardhat
# 选择"Create a JavaScript project"
# 安装依赖包
npm install
基础语法
合约结构
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract SimpleStorage {
// 状态变量
uint256 private storedData;
// 事件
event DataChanged(uint256 oldData, uint256 newData);
// 构造函数
constructor(uint256 initialData) {
storedData = initialData;
}
// 修改函数
function set(uint256 x) public {
uint256 oldData = storedData;
storedData = x;
emit DataChanged(oldData, x);
}
// 查询函数
function get() public view returns (uint256) {
return storedData;
}
}
数据类型
基本类型
contract DataTypes {
// 布尔类型
bool public isActive = true;
// 整数类型
uint256 public positiveNumber = 100; // 无符号整数
int256 public signedNumber = -50; // 有符号整数
uint8 public smallNumber = 255; // 8位无符号整数
// 地址类型
address public owner; // 20字节地址
address payable public recipient; // 可接收以太币的地址
// 字节类型
bytes32 public hash; // 固定长度字节
bytes public dynamicData; // 动态长度字节
// 字符串
string public name = "Crypto Token";
}
引用类型
contract ReferenceTypes {
// 数组
uint256[] public numbers;
string[] public names;
// 映射
mapping(address => uint256) public balances;
mapping(string => address) public nameToAddress;
// 结构体
struct User {
string name;
uint256 age;
bool isActive;
}
mapping(address => User) public users;
// 枚举
enum Status { Pending, Active, Inactive }
Status public currentStatus;
}
函数修饰符
可见性修饰符
contract Visibility {
uint256 private privateData; // 仅合约内部
uint256 internal internalData; // 合约内部和子合约
uint256 public publicData; // 公开访问
function privateFunc() private pure returns (string memory) {
return "Private";
}
function internalFunc() internal pure returns (string memory) {
return "Internal";
}
function publicFunc() public pure returns (string memory) {
return "Public";
}
function externalFunc() external pure returns (string memory) {
return "External";
}
}
状态修饰符
contract StateModifiers {
uint256 public data;
// 修改状态
function modifyData(uint256 newData) public {
data = newData;
}
// 只读,不修改状态
function readData() public view returns (uint256) {
return data;
}
// 纯函数,不读取也不修改状态
function pureFunction(uint256 a, uint256 b) public pure returns (uint256) {
return a + b;
}
// 支付函数,可接收以太币
function payFunction() public payable {
// 处理支付的以太币
require(msg.value > 0, "Must send some ether");
}
}
错误处理
require语句
contract ErrorHandling {
function checkAge(uint256 age) public pure {
require(age >= 18, "Must be 18 or older");
// 继续执行
}
function checkBalance(uint256 amount) public view {
require(address(this).balance >= amount, "Insufficient balance");
// 继续执行
}
}
revert语句
contract RevertExample {
function complexCheck(uint256 value) public pure {
if (value < 10) {
revert("Value too small");
}
if (value > 100) {
revert("Value too large");
}
// 继续执行
}
}
assert语句
contract AssertExample {
uint256 public counter;
function increment() public {
uint256 oldCounter = counter;
counter++;
assert(counter == oldCounter + 1); // 内部错误检查
}
}
事件和日志
事件定义和使用
contract EventExample {
// 事件定义
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
mapping(address => uint256) public balances;
function transfer(address to, uint256 amount) public {
require(balances[msg.sender] >= amount, "Insufficient balance");
balances[msg.sender] -= amount;
balances[to] += amount;
// 触发事件
emit Transfer(msg.sender, to, amount);
}
}
代币合约开发
ERC-20标准实现
完整代币合约
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IERC20 {
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function transfer(address recipient, 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 sender, address recipient, uint256 amount) external returns (bool);
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
}
contract CryptoToken is IERC20 {
mapping(address => uint256) private _balances;
mapping(address => mapping(address => uint256)) private _allowances;
uint256 private _totalSupply;
string public name;
string public symbol;
uint8 public decimals;
constructor(string memory _name, string memory _symbol, uint256 _total) {
name = _name;
symbol = _symbol;
decimals = 18;
_totalSupply = _total * 10**decimals;
_balances[msg.sender] = _totalSupply;
emit Transfer(address(0), msg.sender, _totalSupply);
}
function totalSupply() public view override returns (uint256) {
return _totalSupply;
}
function balanceOf(address account) public view override returns (uint256) {
return _balances[account];
}
function transfer(address recipient, uint256 amount) public override returns (bool) {
require(_balances[msg.sender] >= amount, "Insufficient balance");
_balances[msg.sender] -= amount;
_balances[recipient] += amount;
emit Transfer(msg.sender, recipient, amount);
return true;
}
function allowance(address owner, address spender) public view override returns (uint256) {
return _allowances[owner][spender];
}
function approve(address spender, uint256 amount) public override returns (bool) {
_allowances[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
return true;
}
function transferFrom(address sender, address recipient, uint256 amount) public override returns (bool) {
require(_balances[sender] >= amount, "Insufficient balance");
require(_allowances[sender][msg.sender] >= amount, "Insufficient allowance");
_balances[sender] -= amount;
_balances[recipient] += amount;
_allowances[sender][msg.sender] -= amount;
emit Transfer(sender, recipient, amount);
return true;
}
}
使用OpenZeppelin库
简化开发
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract AdvancedToken is ERC20, Ownable {
uint256 public maxSupply;
mapping(address => bool) public minters;
event MinterAdded(address indexed account);
event MinterRemoved(address indexed account);
modifier onlyMinter() {
require(minters[msg.sender], "Only minter can mint");
_;
}
constructor(
string memory name,
string memory symbol,
uint256 _maxSupply
) ERC20(name, symbol) {
maxSupply = _maxSupply * 10**decimals();
_addMinter(msg.sender);
}
function mint(address to, uint256 amount) public onlyMinter {
require(totalSupply() + amount <= maxSupply, "Max supply exceeded");
_mint(to, amount);
}
function addMinter(address account) public onlyOwner {
_addMinter(account);
}
function removeMinter(address account) public onlyOwner {
_removeMinter(account);
}
function _addMinter(address account) internal {
minters[account] = true;
emit MinterAdded(account);
}
function _removeMinter(address account) internal {
minters[account] = false;
emit MinterRemoved(account);
}
}
高级特性
可升级合约
代理模式
// 代理合约
contract Proxy {
address public implementation;
address public admin;
constructor(address _implementation) {
implementation = _implementation;
admin = msg.sender;
}
function upgradeTo(address newImplementation) external {
require(msg.sender == admin, "Only admin");
implementation = newImplementation;
}
fallback() external payable {
address impl = implementation;
require(impl != address(0), "Implementation not set");
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) }
}
}
}
合约安全性
常见安全漏洞
重入攻击
// 不安全的提款函数
contract UnsafeWithdraw {
mapping(address => uint256) public balances;
function withdraw(uint256 amount) public {
require(balances[msg.sender] >= amount, "Insufficient balance");
// 危险:先转账后更新状态
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "Transfer failed");
balances[msg.sender] -= amount; // 状态更新太晚
}
}
// 安全的提款函数
contract SafeWithdraw {
mapping(address => uint256) public balances;
bool private locked;
modifier nonReentrant() {
require(!locked, "Reentrant call");
locked = true;
_;
locked = false;
}
function withdraw(uint256 amount) public nonReentrant {
require(balances[msg.sender] >= amount, "Insufficient balance");
// 先更新状态,再转账
balances[msg.sender] -= amount;
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "Transfer failed");
}
}
整数溢出
// Solidity 0.8+ 自动检查溢出
contract SafeMath {
function safeAdd(uint256 a, uint256 b) public pure returns (uint256) {
uint256 c = a + b;
require(c >= a, "Addition overflow");
return c;
}
function safeSub(uint256 a, uint256 b) public pure returns (uint256) {
require(b <= a, "Subtraction overflow");
return a - b;
}
}
Gas优化技巧
存储优化
contract GasOptimization {
// 使用更小的数据类型
struct CompactData {
uint128 amount; // 16字节
uint64 timestamp; // 8字节
uint32 counter; // 4字节
bool isActive; // 1字节
// 总共:29字节,适合一个存储槽(32字节)
}
// 打包存储变量
uint128 public packed1;
uint128 public packed2;
// 两个变量打包在一个存储槽中
// 使用事件而不是存储
event DataProcessed(uint256 indexed id, uint256 result);
function processData(uint256 data) public {
uint256 result = data * 2;
// 使用事件记录,而不是存储到状态变量
emit DataProcessed(block.timestamp, result);
}
}
部署和测试
使用Hardhat部署
部署脚本
// scripts/deploy.js
const { ethers } = require("hardhat");
async function main() {
const [deployer] = await ethers.getSigners();
console.log("Deploying contracts with the account:", deployer.address);
console.log("Account balance:", (await deployer.getBalance()).toString());
// 部署代币合约
const Token = await ethers.getContractFactory("CryptoToken");
const token = await Token.deploy("Crypto Token", "CTK", 1000000);
console.log("Token address:", token.address);
console.log("Transaction hash:", token.deployTransaction.hash);
// 等待部署确认
await token.deployed();
console.log("Contract deployed successfully!");
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
测试用例
// test/Token.test.js
const { expect } = require("chai");
const { ethers } = require("hardhat");
describe("CryptoToken", function () {
let Token;
let token;
let owner;
let addr1;
let addr2;
beforeEach(async function () {
Token = await ethers.getContractFactory("CryptoToken");
[owner, addr1, addr2] = await ethers.getSigners();
token = await Token.deploy("Crypto Token", "CTK", 1000000);
await token.deployed();
});
describe("Deployment", function () {
it("Should set the right name and symbol", async function () {
expect(await token.name()).to.equal("Crypto Token");
expect(await token.symbol()).to.equal("CTK");
});
it("Should assign the total supply 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);
});
});
});
最佳实践
1. 使用成熟的库
- OpenZeppelin合约库
- 经过审计的代码
- 社区验证的解决方案
2. 遵循安全原则
- 检查-生效-交互模式
- 使用重入保护
- 适当的访问控制
- 输入验证和边界检查
3. Gas优化
- 减少存储使用
- 使用事件记录
- 优化数据类型
- 批量操作
4. 代码质量
- 详细的注释
- 清晰的命名
- 模块化设计
- 充分的测试
5. 安全审计
- 专业审计服务
- 社区审查
- 形式化验证
- 持续监控
智能合约开发是区块链技术中最具挑战性和创造性的领域之一。掌握这些基础知识和最佳实践,将为您在去中心化应用开发领域打下坚实的基础。随着经验的积累,您将能够构建更加复杂和安全的智能合约系统。