目录
  1. 1. 一、合约基础结构
    1. 1.1. 1.1 第一个合约
    2. 1.2. 1.2 合约组件
  2. 2. 二、数据类型
    1. 2.1. 2.1 值类型(Value Types)
    2. 2.2. 2.2 引用类型(Reference Types)
    3. 2.3. 2.3 数据位置(Storage / Memory / Calldata)
  3. 3. 三、函数与修饰符
    1. 3.1. 3.1 函数可见性
    2. 3.2. 3.2 状态可变性修饰符
    3. 3.3. 3.3 修饰符(Modifier)
  4. 4. 四、ERC 代币标准
    1. 4.1. 4.1 ERC-20(同质化代币标准)
    2. 4.2. 4.2 ERC-721(非同质化代币 NFT 标准)
  5. 5. 五、安全攻防
    1. 5.1. 5.1 重入攻击(Reentrancy)
    2. 5.2. 5.2 整数溢出(Solidity 0.8+ 已内置检查)
    3. 5.3. 5.3 tx.origin vs msg.sender
    4. 5.4. 5.4 前置交易(Front-running)
    5. 5.5. 5.5 访问控制 — Ownable 模式
  6. 6. 六、DeFi 质押合约实战
  7. 7. 七、开发工具链
    1. 7.1. 7.1 Hardhat(推荐)
    2. 7.2. 7.2 前端集成(ethers.js)
    3. 7.3. 7.3 OpenZeppelin 合约库
区块链进阶系列-Solidity篇

Solidity 是以太坊智能合约的主要编程语言,是一种面向合约的高级语言,语法上受 C++、Python 和 JavaScript 的影响。本文从合约结构、数据类型、函数修饰符、存储位置、ERC 代币标准,到安全攻防和开发工具链,系统掌握 Solidity 开发全链路。

学习网站推荐:https://cryptozombies.io/ — 一个通过编写游戏学习 Solidity 的互动教程,非常适合入门。

一、合约基础结构

1.1 第一个合约

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

contract SimpleStorage {
// 状态变量 — 永久存储在区块链上
uint256 private storedData;

// 事件 — 记录到区块链日志(可被前端监听)
event DataStored(uint256 newValue, address indexed storedBy);
event DataRead(uint256 value, address indexed readBy);

// 写入函数 — 修改状态变量(消耗 gas)
function set(uint256 x) public {
storedData = x;
emit DataStored(x, msg.sender);
}

// 读取函数 — 不修改状态(view 函数,外部调用不消耗 gas)
function get() public view returns (uint256) {
emit DataRead(storedData, msg.sender);
return storedData;
}
}

SPDX 许可证标识符: Solidity 0.6.8+ 要求每个源文件以 SPDX 许可证标识符开头。// SPDX-License-Identifier: MIT 声明代码使用 MIT 许可证。

Pragma 版本指令:

pragma solidity ^0.8.19;    // 0.8.x (>=0.8.19, <0.9.0)
pragma solidity >=0.8.0 <0.9.0; // 范围指定
pragma solidity 0.8.19; // 精确锁定版本

1.2 合约组件

contract ContractComponents {
// === 1. 状态变量(State Variables)===
// 永久存储在区块链上,每次函数调用都可能读写
uint256 public count; // public 自动生成 getter
address private owner;
mapping(address => uint256) private balances;
bool internal paused;

// === 2. 事件(Events)===
// 索引参数可被日志过滤器搜索(最多 3 个 indexed)
event Transfer(address indexed from, address indexed to, uint256 value);

// === 3. 修饰符(Modifiers)===
modifier onlyOwner() {
require(msg.sender == owner, "Not the contract owner");
_; // 下划线代表被修饰函数的原始代码插入位置
}

modifier whenNotPaused() {
require(!paused, "Contract is paused");
_;
}

// === 4. 构造函数(Constructor)===
// 合约部署时执行一次
constructor() {
owner = msg.sender;
count = 0;
}

// === 5. 函数 ===
function increment() public onlyOwner whenNotPaused {
count += 1;
}

// === 6. fallback / receive ===
// 当调用数据不匹配任何函数或直接发送 ETH 时触发
receive() external payable {
// 接收纯 ETH 转账(无 calldata)
}

fallback() external payable {
// 当 calldata 不匹配任何函数时触发
// 也可以用于接收 ETH
}
}

二、数据类型

2.1 值类型(Value Types)

// === 布尔类型 ===
bool public isActive = true;
// 运算符: !, &&, ||, ==, !=

// === 整数类型 ===
// uint: uint256, uint8, uint16, uint32, uint64, uint128
// int: int256, int8, int16, int32, int64, int128
uint256 public maxUint = type(uint256).max; // 2^256 - 1
uint8 public small = 255; // 0-255
int256 public negative = -42;

// Solidity 0.8.x 默认开启溢出检查(SafeMath 不再需要)
// 如需允许溢出,使用 unchecked 块:
function safeOverflowExample() public pure returns (uint8) {
uint8 x = 255;
unchecked {
return x + 1; // 返回 0(溢出但不 revert)
}
}

// === 地址类型 ===
address public wallet = 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2;
address payable public receiver; // 可接收 ETH 的地址

// 地址成员:
// wallet.balance — 该地址的 ETH 余额 (uint256)
// wallet.code — 该地址的合约代码 (bytes memory)
// wallet.codehash — 合约代码的 keccak256 哈希
// payable(wallet).transfer(1 ether) — 转账(2300 gas,失败时 revert)
// payable(wallet).send(1 ether) — 转账(2300 gas,返回 bool)

// === 定长字节数组 ===
bytes1 public b1 = 0x01; // 1 字节
bytes2 public b2 = 0x1234;
bytes32 public b32 = keccak256(abi.encode("hello"));

// === 枚举 ===
enum Status { Pending, Approved, Rejected, Canceled }
Status public currentStatus = Status.Pending;

function updateStatus(Status newStatus) public {
currentStatus = newStatus;
}

2.2 引用类型(Reference Types)

// === 动态数组 ===
uint256[] public numbers; // 动态长度
uint256[5] public fixedNumbers; // 固定长度 5

function arrayExamples() public {
// 操作
numbers.push(1); // 末尾追加
numbers.push(2);
numbers.pop(); // 移除末尾
uint256 len = numbers.length; // 获取长度
delete numbers[0]; // 重置为默认值(不缩减长度)

// 内存数组(须指定大小)
uint256[] memory memArray = new uint256[](3);
memArray[0] = 10;
memArray[1] = 20;
memArray[2] = 30;

// 切片(仅对 calldata 数组可用)
// bytes calldata slice = msg.data[4:];
}

// === bytes 和 string ===
bytes public rawBytes; // 动态字节数组
string public greeting; // UTF-8 字符串

// 区别:
// bytes — 可修改,gas 成本更低(底层操作)
// string — 不可索引修改,适合文本

// === 映射 Mapping ===
// mapping(KeyType => ValueType) — KeyType 不支持 mapping、变长数组、struct
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => bool)) public isApproved;
// 注意:mapping 不可遍历,不可获取长度,不可作为函数返回(除非在 struct 中)

function setBalance(address user, uint256 amount) public {
balanceOf[user] = amount;
// 不存在的 key 返回默认值(uint256 默认为 0)
}

function getBalance(address user) public view returns (uint256) {
return balanceOf[user]; // 未设置的 key 返回 0
}

// === Struct 结构体 ===
struct Proposal {
uint256 id;
string title;
address proposer;
uint256 voteCount;
mapping(address => bool) hasVoted; // 嵌套 mapping
}

Proposal[] public proposals; // struct 数组

function createProposal(string memory title) public {
// 注意:含有 mapping 的 struct 无法整体 push
// 需要逐字段赋值
uint256 newId = proposals.length;
Proposal storage newProp = proposals.push();
newProp.id = newId;
newProp.title = title;
newProp.proposer = msg.sender;
}

2.3 数据位置(Storage / Memory / Calldata)

这是 Solidity 中最关键的概念之一,直接影响 gas 消耗和合约安全性。

位置 持久性 修改性 Gas 成本 用途
storage 永久存储 可修改 最高 状态变量
memory 函数执行期间 可修改 临时计算
calldata 函数执行期间 只读 最低 外部函数参数
contract DataLocationExample {
uint256[] private storageData; // storage(状态变量)

function locationDemo(
uint256[] calldata inputData // calldata — 外部调用者传入的数据(只读)
) public returns (uint256[] memory) {
// memory 数组 — 临时副本
uint256[] memory tempArray = new uint256[](inputData.length);

for (uint256 i = 0; i < inputData.length; i++) {
tempArray[i] = inputData[i] * 2;
}

// 将 memory 数据写入 storage
for (uint256 i = 0; i < tempArray.length; i++) {
storageData.push(tempArray[i]); // push 只能对 storage 数组
}

return tempArray; // 返回 memory 数组
}

// 赋值行为:
function assignmentBehavior() public view {
// storage → storage: 引用(指向同一位置)
// memory → memory: 引用(指向同一位置)
// storage → memory: 拷贝
// memory → storage: 拷贝
// calldata → memory/storage: 拷贝
}
}

// 最佳实践:
// - 外部函数参数:优先使用 calldata(省 gas)
// - 内部函数参数:使用 memory
// - 函数返回值:只能返回 memory

三、函数与修饰符

3.1 函数可见性

contract Visibility {
// public — 内部外部均可调用;自动生成 getter(状态变量)
uint256 public publicVar;

// private — 仅当前合约可调用
uint256 private privateVar;

// internal — 当前合约和子合约可调用
uint256 internal internalVar;

// external — 仅外部可调用(this.func() 也行但 gas 更高)
// external 函数参数读取时用 calldata 更省 gas

function publicFunc() public pure returns (string memory) {
return "Called public";
}

function externalFunc() external pure returns (string memory) {
return "Called external";
}

// 在合约内部调用 external 函数须用 this.
function callExternal() public view returns (string memory) {
return this.externalFunc(); // this 调用,作为外部调用
}
}

// 可见性 gas 消耗对比(从低到高):
// 1. external calldata — 最低(参数直接读 calldata)
// 2. external memory — 中等
// 3. public — 中等(需复制参数到 memory)

3.2 状态可变性修饰符

contract StateMutability {
uint256 private value;

// view: 只读状态,不修改(但不能修改 any 状态)
function getValue() public view returns (uint256) {
return value;
// value = 5; // ❌ 编译错误:不能在 view 函数中修改状态
}

// pure: 不读也不写状态
function add(uint256 a, uint256 b) public pure returns (uint256) {
return a + b;
// uint256 c = value; // ❌ 编译错误:不能在 pure 函数中读取状态
}

// payable: 可接收 ETH
function deposit() public payable {
// msg.value 是发送的 ETH 数量(wei)
require(msg.value > 0, "Must send ETH");
}

// 无修饰符:可读可写
function setValue(uint256 newValue) public {
value = newValue;
}

// view vs pure gas 消耗:
// - view 函数在外部调用(不发送交易)→ 免费(由节点本地执行)
// - pure 函数在外部调用 → 免费
// - 两者在交易内被调用时 → 消耗 gas
}

3.3 修饰符(Modifier)

contract ModifierExamples {
address public owner;
bool public paused;

constructor() {
owner = msg.sender;
}

// 基础修饰符
modifier onlyOwner() {
require(msg.sender == owner, "Not owner");
_; // 被修饰函数的主体代码在这里执行
}

modifier whenNotPaused() {
require(!paused, "Paused");
_;
}

// 带参数的修饰符
modifier checkAge(uint256 minAge) {
// 假设 msg.sender 的年龄可从某处获取
_;
}

// 修饰符可组合(按声明顺序执行)
function criticalOperation() public onlyOwner whenNotPaused {
// 先检查 onlyOwner,再检查 whenNotPaused
// 都通过后执行此代码
paused = true;
}

// 修饰符中 _ 前后都可写代码
modifier logBeforeAfter() {
emit LogBefore(msg.sender);
_; // 主体代码在此执行
emit LogAfter(msg.sender);
}

event LogBefore(address indexed caller);
event LogAfter(address indexed caller);
}

四、ERC 代币标准

4.1 ERC-20(同质化代币标准)

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

interface IERC20 {
// 可选函数
function name() external view returns (string memory);
function symbol() external view returns (string memory);
function decimals() external view returns (uint8);

// 必需函数
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);

// 事件
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
}

contract MyToken is IERC20 {
string public constant name = "My Token";
string public constant symbol = "MTK";
uint8 public constant decimals = 18; // 与 ETH 一致

uint256 private _totalSupply;
mapping(address => uint256) private _balances;
mapping(address => mapping(address => uint256)) private _allowances;

constructor(uint256 initialSupply) {
_mint(msg.sender, initialSupply);
}

function totalSupply() public view override returns (uint256) {
return _totalSupply;
}

function balanceOf(address account) public view override returns (uint256) {
return _balances[account];
}

function transfer(address to, uint256 amount) public override returns (bool) {
_transfer(msg.sender, to, 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) {
_approve(msg.sender, spender, amount);
return true;
}

function transferFrom(address from, address to, uint256 amount)
public override returns (bool)
{
uint256 currentAllowance = _allowances[from][msg.sender];
require(currentAllowance >= amount, "ERC20: insufficient allowance");

// 更新 allowance(注意:如果 amount == type(uint256).max,不减少)
unchecked {
_approve(from, msg.sender, currentAllowance - amount);
}

_transfer(from, to, amount);
return true;
}

// === 内部函数 ===
function _transfer(address from, address to, uint256 amount) internal {
require(from != address(0), "ERC20: transfer from zero address");
require(to != address(0), "ERC20: transfer to zero address");
require(_balances[from] >= amount, "ERC20: insufficient balance");

unchecked {
_balances[from] -= amount;
_balances[to] += amount;
}

emit Transfer(from, to, amount);
}

function _approve(address owner, address spender, uint256 amount) internal {
require(owner != address(0), "ERC20: approve from zero address");
require(spender != address(0), "ERC20: approve to zero address");

_allowances[owner][spender] = amount;
emit Approval(owner, spender, amount);
}

function _mint(address account, uint256 amount) internal {
require(account != address(0), "ERC20: mint to zero address");

_totalSupply += amount;
unchecked {
_balances[account] += amount;
}
emit Transfer(address(0), account, amount);
}

function _burn(address account, uint256 amount) internal {
require(account != address(0), "ERC20: burn from zero address");
require(_balances[account] >= amount, "ERC20: burn amount exceeds balance");

unchecked {
_balances[account] -= amount;
_totalSupply -= amount;
}
emit Transfer(account, address(0), amount);
}
}

4.2 ERC-721(非同质化代币 NFT 标准)

interface IERC721 {
// 查询
function balanceOf(address owner) external view returns (uint256);
function ownerOf(uint256 tokenId) external view returns (address);

// 转移
function transferFrom(address from, address to, uint256 tokenId) external;
function safeTransferFrom(address from, address to, uint256 tokenId) external;
function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;

// 授权
function approve(address to, uint256 tokenId) external;
function setApprovalForAll(address operator, bool approved) external;
function getApproved(uint256 tokenId) external view returns (address);
function isApprovedForAll(address owner, address operator) external view returns (bool);

// 事件
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
}

// ERC-721 的核心区别:
// 1. 每个 tokenId 是唯一的(ERC-20 所有代币等价)
// 2. ownerOf(tokenId) 查询单个 NFT 的所有者
// 3. transferFrom 需要指定 tokenId
// 4. balanceOf 返回拥有的 NFT 数量(而非代币数量)

五、安全攻防

5.1 重入攻击(Reentrancy)

重入攻击是 Solidity 最著名的漏洞,The DAO 攻击导致以太坊硬分叉(ETH vs ETC)。

漏洞合约:

// ❌ 不安全的取款合约
contract VulnerableBank {
mapping(address => uint256) public balances;

function deposit() external payable {
balances[msg.sender] += msg.value;
}

function withdraw() external {
uint256 amount = balances[msg.sender];
require(amount > 0, "No balance");

// 危险!先转账,后更新状态
(bool success, ) = msg.sender.call{value: amount}("");
require(success);

balances[msg.sender] = 0; // 状态更新在转账之后!
}
}

// 攻击者合约
contract ReentrancyAttack {
VulnerableBank public bank;

constructor(address bankAddress) {
bank = VulnerableBank(bankAddress);
}

function attack() external payable {
bank.deposit{value: 1 ether}();
bank.withdraw(); // 触发重入
}

// fallback 被 bank 的 call 触发 → 重入 withdraw
receive() external payable {
if (address(bank).balance >= 1 ether) {
bank.withdraw(); // 再次取款,此时 balances 还未归零!
}
}
}

防御方案:

// ✅ 方案一:检查-生效-交互模式(Checks-Effects-Interactions)
contract SecureBank1 {
mapping(address => uint256) public balances;

function withdraw() external {
uint256 amount = balances[msg.sender];
require(amount > 0, "No balance");

// 1. Checks: 上面已做
// 2. Effects: 先更新状态
balances[msg.sender] = 0;
// 3. Interactions: 最后转账
(bool success, ) = msg.sender.call{value: amount}("");
require(success);
}
}

// ✅ 方案二:使用 ReentrancyGuard(OpenZeppelin)
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

contract SecureBank2 is ReentrancyGuard {
mapping(address => uint256) public balances;

function withdraw() external nonReentrant {
uint256 amount = balances[msg.sender];
require(amount > 0, "No balance");
balances[msg.sender] = 0;
(bool success, ) = msg.sender.call{value: amount}("");
require(success);
}
}
// nonReentrant 修饰符:
// 1. 进入函数时设置 _status = ENTERED
// 2. 退出时恢复 _status = NOT_ENTERED
// 3. 如果 _status 已是 ENTERED,直接 revert

5.2 整数溢出(Solidity 0.8+ 已内置检查)

// Solidity 0.8+ 默认开启溢出检查
// 但 unchecked 块内的运算不检查

contract OverflowExample {
function safeWithUnchecked() public pure {
uint256 max = type(uint256).max;

// max + 1 → ❌ 0.8+ 自动 revert

unchecked {
uint256 overflowed = max + 1; // ✅ 允许,返回 0
}
}
}

5.3 tx.origin vs msg.sender

contract PhishingAttack {
// ❌ 使用 tx.origin 做权限检查
function unsafeTransfer(address to, uint256 amount) external {
require(tx.origin == owner, "Not owner"); // 危险!
payable(to).transfer(amount);
}
// 攻击:诱导 owner 调用攻击者合约 → 攻击者合约调用 unsafeTransfer
// → tx.origin 仍然是 owner → 权限检查通过!

// ✅ 使用 msg.sender 做权限检查
function safeTransfer(address to, uint256 amount) external {
require(msg.sender == owner, "Not owner");
payable(to).transfer(amount);
}
}

// tx.origin: 发起整个交易的 EOA 地址(始终是用户钱包)
// msg.sender: 当前调用的直接调用者(可能是用户,也可能是合约)
// 规则:永远不要用 tx.origin 做权限验证

5.4 前置交易(Front-running)

// 漏洞场景:用户提交了一个有利可图的交易
// MEV 搜索者(MEV Searcher)看到后,在它之前插入自己的交易

// 防范:使用 commit-reveal 方案
contract CommitReveal {
mapping(address => bytes32) public commits;

// 第一阶段:提交哈希(隐藏真实答案)
function commit(bytes32 hash) external {
commits[msg.sender] = hash;
}

// 第二阶段:揭示答案(验证与哈希匹配)
function reveal(string memory answer, bytes32 salt) external {
require(
commits[msg.sender] == keccak256(abi.encodePacked(answer, salt)),
"Invalid reveal"
);
// 处理答案...
delete commits[msg.sender];
}
}

5.5 访问控制 — Ownable 模式

// OpenZeppelin Ownable 的简化实现
contract Ownable {
address public owner;

event OwnershipTransferred(address indexed previous, address indexed newOwner);

constructor() {
owner = msg.sender;
emit OwnershipTransferred(address(0), msg.sender);
}

modifier onlyOwner() {
require(msg.sender == owner, "Ownable: caller is not the owner");
_;
}

function transferOwnership(address newOwner) public onlyOwner {
require(newOwner != address(0), "Ownable: new owner is zero address");
emit OwnershipTransferred(owner, newOwner);
owner = newOwner;
}
}

// 两步转移(更安全,防止误转所有权到不可达地址)
contract Ownable2Step is Ownable {
address private _pendingOwner;

function transferOwnership(address newOwner) public override onlyOwner {
_pendingOwner = newOwner;
}

function acceptOwnership() public {
require(msg.sender == _pendingOwner, "Not pending owner");
emit OwnershipTransferred(owner, _pendingOwner);
owner = _pendingOwner;
_pendingOwner = address(0);
}
}

六、DeFi 质押合约实战

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

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract StakingContract is ReentrancyGuard, Ownable {
IERC20 public stakingToken; // 质押代币
IERC20 public rewardToken; // 奖励代币

uint256 public rewardRate; // 每秒每代币的奖励率
uint256 public totalStaked; // 总质押量

struct Stake {
uint256 amount;
uint256 rewardDebt; // 已领取奖励的债务
uint256 lastUpdateTime;
}

mapping(address => Stake) public stakes;

event Staked(address indexed user, uint256 amount);
event Withdrawn(address indexed user, uint256 amount);
event RewardClaimed(address indexed user, uint256 amount);

constructor(address stakingToken_, address rewardToken_, uint256 rewardRate_) {
stakingToken = IERC20(stakingToken_);
rewardToken = IERC20(rewardToken_);
rewardRate = rewardRate_;
}

function stake(uint256 amount) external nonReentrant {
require(amount > 0, "Cannot stake 0");

// 领取待领取的奖励
_updateReward(msg.sender);

// 转入质押代币
stakingToken.transferFrom(msg.sender, address(this), amount);

stakes[msg.sender].amount += amount;
totalStaked += amount;

emit Staked(msg.sender, amount);
}

function withdraw(uint256 amount) external nonReentrant {
Stake storage s = stakes[msg.sender];
require(s.amount >= amount, "Insufficient stake");

_updateReward(msg.sender);

s.amount -= amount;
totalStaked -= amount;

stakingToken.transfer(msg.sender, amount);

emit Withdrawn(msg.sender, amount);
}

function claimReward() external nonReentrant returns (uint256) {
_updateReward(msg.sender);
uint256 reward = stakes[msg.sender].rewardDebt;
require(reward > 0, "No reward to claim");

stakes[msg.sender].rewardDebt = 0;
rewardToken.transfer(msg.sender, reward);

emit RewardClaimed(msg.sender, reward);
return reward;
}

function earned(address user) public view returns (uint256) {
Stake storage s = stakes[user];
if (s.amount == 0) return s.rewardDebt;

uint256 timeElapsed = block.timestamp - s.lastUpdateTime;
uint256 newReward = s.amount * rewardRate * timeElapsed;

return s.rewardDebt + newReward;
}

function _updateReward(address user) internal {
Stake storage s = stakes[user];
if (s.amount > 0) {
s.rewardDebt = earned(user);
}
s.lastUpdateTime = block.timestamp;
}

// 管理员功能
function setRewardRate(uint256 newRate) external onlyOwner {
rewardRate = newRate;
}
}

合约机制说明:

  1. 质押(stake):用户转入 stakingToken 到合约,记录质押金额
  2. 奖励计算earned = amount * rewardRate * timeElapsed — 时间线性累积
  3. 提取奖励(claimReward):将累积奖励转为 rewardToken 转给用户
  4. 提取本金(withdraw):归还质押代币
  5. rewardDebt 机制:避免每次操作都发放奖励,用债务值追踪已累积的奖励

七、开发工具链

7.1 Hardhat(推荐)

# 项目初始化
mkdir my-defi-project && cd my-defi-project
npm init -y
npm install --save-dev hardhat @nomicfoundation/hardhat-toolbox
npx hardhat init

# 编译
npx hardhat compile

# 测试
npx hardhat test

# 部署(配置 hardhat.config.js 中的网络)
npx hardhat run scripts/deploy.js --network sepolia

# 验证合约
npx hardhat verify --network sepolia DEPLOYED_ADDRESS "constructor_arg1"

hardhat.config.js 示例:

require("@nomicfoundation/hardhat-toolbox");

module.exports = {
solidity: {
version: "0.8.19",
settings: {
optimizer: { enabled: true, runs: 200 },
},
},
networks: {
sepolia: {
url: `https://sepolia.infura.io/v3/${process.env.INFURA_KEY}`,
accounts: [process.env.PRIVATE_KEY],
},
},
etherscan: {
apiKey: process.env.ETHERSCAN_API_KEY,
},
};

7.2 前端集成(ethers.js)

// ethers.js v6 前端与合约交互
import { ethers } from "ethers";

// 连接钱包
const provider = new ethers.BrowserProvider(window.ethereum);
const signer = await provider.getSigner();

// 合约实例
const abi = [...]; // 合约 ABI
const contractAddress = "0x...";
const contract = new ethers.Contract(contractAddress, abi, signer);

// 调用只读函数
const totalStaked = await contract.totalStaked();
console.log(ethers.formatEther(totalStaked));

// 调用写入函数(触发交易)
const tx = await contract.stake(ethers.parseEther("10"));
const receipt = await tx.wait();
console.log(`Transaction hash: ${receipt.transactionHash}`);

// 监听事件
contract.on("Staked", (user, amount, event) => {
console.log(`User ${user} staked ${ethers.formatEther(amount)} ETH`);
});

7.3 OpenZeppelin 合约库

// 常用 OpenZeppelin 合约
import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; // ERC-20 实现
import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; // ERC-721 实现
import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; // ERC-1155 实现
import "@openzeppelin/contracts/access/Ownable.sol"; // 所有权管理
import "@openzeppelin/contracts/access/AccessControl.sol"; // 基于角色的访问控制
import "@openzeppelin/contracts/security/Pausable.sol"; // 可暂停功能
import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; // 重入保护
import "@openzeppelin/contracts/utils/Counters.sol"; // 计数器
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; // 可升级代理

// 简单可暂停的 ERC-20 代币
contract PausableToken is ERC20, Ownable, Pausable {
constructor() ERC20("Pausable Token", "PST") {
_mint(msg.sender, 1000000 * 10 ** decimals());
}

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

function _beforeTokenTransfer(address from, address to, uint256 amount)
internal override whenNotPaused
{
super._beforeTokenTransfer(from, to, amount);
}
}

Solidity 作为以太坊生态的核心编程语言,其设计精妙地将区块链的不可变性和智能合约的灵活性结合在一起。掌握 Solidity 不仅仅是学习一门新语言的语法,更重要的是理解 EVM 的执行模型、Gas 经济学、以及区块链环境中特有的安全威胁模型。建议从 OpenZeppelin 的合约库入手学习最佳实践,配合 Hardhat 测试框架建立”先测试、后部署、再验证”的工程习惯。在 DeFi、NFT 和 DAO 等应用场景中,Solidity 将继续扮演智能合约开发的核心角色。

文章作者: Leo·Cheung
文章链接: http://tufusi.com/2020/12/15/%E5%8C%BA%E5%9D%97%E9%93%BE%E8%BF%9B%E9%98%B6%E7%B3%BB%E5%88%97-Solidity%E7%AF%87/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 ONE·PIECE
打赏
  • 微信
  • 支付宝

评论