Skip to content

智能合约开发入门(以太坊篇)

本文档介绍以太坊智能合约开发基础,涵盖 Solidity 语言、EVM 操作码、ERC-20 标准、Hardhat 开发框架、类型系统、Gas 机制、签名验证等核心知识。


一、认识 Solidity

1.1 EVM 合约开发语言

语言特点
Solidity最流行,面向对象
Vyper类 Python 语法,优化安全性和可读性
Fe类 Rust 语法,模块化代码共享
Huff / Etk底层汇编级别

常用资源

1.2 合约工作流程

graph LR
    A["Solidity 源码"] --> B["编译器"]
    B --> C["字节码 Bytecode"]
    B --> D["ABI"]
    C --> E["部署到 EVM"]
    E --> F["链上执行"]

1.3 EVM 基础

操作码(Opcodes):EVM 的低级指令集,参考 evm.codes

预编译合约:与固定地址的 EVM 捆绑,提供高级功能(如椭圆曲线运算、哈希等),以确定的 Gas 成本调用。

EVM 是基于栈的

// 返回 42 的字节码
604260005260206000F3

60 42  // PUSH 42
60 00  // PUSH 00
52     // MSTORE
60 20  // PUSH 32 (length)
60 00  // PUSH 00 (offset)
F3     // RETURN

二、Hello World - Hardhat 框架

2.1 安装

bash
npm install --save-dev hardhat
npx hardhat init

2.2 基本合约结构

solidity
pragma solidity ^0.8.0;

contract Greeter {
    string private greeting;

    constructor(string memory _greeting) {
        greeting = _greeting;
    }

    function greet() public view returns (string memory) {
        return greeting;
    }

    function setGreeting(string memory _greeting) public {
        greeting = _greeting;
    }
}
  • pragma:指定编译器版本
  • constructor:构造函数,部署时执行一次
  • view 函数:只读,不修改状态

2.3 常用命令

bash
npx hardhat compile          # 编译
npx hardhat test             # 执行测试
npx hardhat run scripts/deploy.js --network sepolia  # 部署
npx hardhat verify --network sepolia <> "参数"   # 验证

三、从 ERC-20 看 Solidity

3.1 ERC/EIP 概念

EIP(以太坊改进提案)描述了以太坊平台标准。其中 ERC-20 制定了代币标准,ERC-721 制定了 NFT 标准。

3.2 ERC-20 核心接口

solidity
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);
}

3.3 核心数据结构

solidity
mapping(address => uint256) private _balances;                          // 余额
mapping(address => mapping(address => uint256)) private _allowed;       // 授权额度

四、Solidity 核心概念

4.1 合约、库与接口

类型特点
Contract类似类,包含状态变量、函数、事件等
Interface不能实现函数、无状态变量、所有函数为 external
Library代码复用,通过 DELEGATECALL,在调用合约上下文执行

4.2 类型系统

值类型 vs 引用类型

值类型引用类型
bool, int/uintstruct
addressarray
bytes1-bytes32mapping
枚举, 函数string

数据位置

位置说明
memory函数调用期间有效
storage合约存在期间永久存储
calldata只读,保存函数参数

**映射(Mapping)**特性:

  • 只能作为 storage,不能用于函数参数或返回值
  • 不存储 key,存储 keccak256(key) 的哈希值
  • 不存在的 key 返回类型默认值

4.3 函数

可见性

修饰符说明
external仅外部调用(this.f() 可以)
public内部和外部都可调用
internal当前合约和派生合约
private仅当前合约

状态修饰

修饰符说明
view不修改状态
pure不读取也不修改状态
(无)可读写状态

4.4 Modifier

用于在函数执行前自动检查条件:

solidity
modifier nonReentrant() {
    require(locked == 1, "REENTRANCY");
    locked = 2;
    _;           // 执行函数体
    locked = 1;
}

常见使用场景

  • 访问控制(onlyOwner)
  • 重入防护(nonReentrant)
  • 状态检查(whenNotPaused)

4.5 事件与错误处理

事件(Events):用于记录日志,通过 emit 触发,indexed 参数可被过滤。

错误处理

  • require(condition, "message"):条件检查,失败时回退
  • revert("message")revert CustomError():主动回退
  • assert(condition):仅用于测试内部错误

五、与传统编程不同的事

5.1 Calldata 数据布局

  • 前 4 字节为函数选择器(MethodID)
  • 每个参数占 32 字节(无论 uint8 还是 uint256)
  • 引用类型使用 offset → length → data 结构

5.2 Address 类型

成员说明
balance查询余额
transfer发送 ETH(失败回退)
send发送 ETH(返回 bool)
call低级调用
delegatecall委托调用(使用对方代码,本方状态)
staticcall静态调用(不允许修改状态)

5.3 Gas 费用

EIP-1559 后的 Gas 模型

Gas Price = Base Fee + Max Priority Fee
Transaction Fee = Gas Price × Gas Used
组成说明
Base Fee网络自动调节,基于区块使用率,被销毁
Max Priority Fee矿工小费,用于优先打包
Max Fee用户愿意付出的最高单位费用

Base Fee 调节规则:

  • 区块使用率 > 50%:Base Fee 上升(最多 12.5%)
  • 区块使用率 < 50%:Base Fee 下降(最多 12.5%)

5.4 Fallback 函数

合约最多有一个 fallback 函数,处理没有匹配函数选择器的调用。可用于代理模式(Proxy Pattern)。

5.5 创建合约

create:地址 = keccak256(rlp([sender, nonce]))

create2(加盐):地址 = keccak256(0xff + sender + salt + keccak256(bytecode))

create2 可在合约创建前推导出地址,提供更大的灵活性。

5.6 EIP-712 签名验证

EIP-712 提供结构化数据签名标准,包含三部分:

部分说明
Domain合约相关信息(名称、版本、chainId、合约地址)
TypeHash数据结构的哈希
Message实际签名数据

签名验证流程:

solidity
bytes32 digest = keccak256(abi.encodePacked(
    "\x19\x01",
    DOMAIN_SEPARATOR,
    hash(message)
));
address signer = ecrecover(digest, v, r, s);

相比 eth_signpersonal_sign,EIP-712 提供更好的安全性(防重放)和用户体验(格式化展示签名内容)。


总结

graph TB
    subgraph "开发流程"
        A["Solidity 编写"] --> B["Hardhat 编译/测试"]
        B --> C["部署到测试网"]
        C --> D["验证合约"]
    end

    subgraph "核心知识"
        E["ERC-20 标准"]
        F["Gas 机制"]
        G["签名验证"]
        H["代理模式"]
    end

智能合约开发需要理解 EVM 的执行模型和 Solidity 的特殊性。关键差异在于:所有操作都是交易、状态存储在链上、Gas 机制限制计算资源、address 类型是独有概念。