Solidity 入门与开发环境
学习目标
- 理解智能合约的本质与核心特性
- 掌握合约在以太坊上的运行原理(Transaction + EVM)
- 认识 Solidity 语言特点与开发工具链
- 编写并部署第一个智能合约
前置知识
- 了解区块链基本概念(区块、交易、共识)
- 了解以太坊账户模型(EOA 与合约账户)
- 基本编程经验(任意语言均可)
一、智能合约的根本性质
1.1 本质是契约(合同)
智能合约的英文是 Smart Contract,重点在 Contract(合同/契约)。它不仅仅是一段程序,更是一种跨组织、跨个体的信任关系的数字化表达。
传统合同依赖法律体系和中介机构来保障执行,而智能合约依赖区块链网络和代码逻辑来保障执行——Code is Law。
1.2 开发流程:定约、修约、废约
智能合约的生命周期可以类比传统合同:
| 阶段 | 传统合同 | 智能合约 |
|---|---|---|
| 定约 | 起草、签署合同 | 编写、部署合约(deploy) |
| 修约 | 签订补充协议 | 部署新版本合约(代理模式) |
| 废约 | 终止合同 | 调用 selfdestruct 或停用逻辑 |
1.3 不可变性(Immutable)与开源
智能合约一旦部署到区块链上,其代码不可被修改——这就是不可变性。这意味着:
- 部署前必须经过充分的测试和审计
- 修改逻辑只能通过部署新合约或使用代理模式(Proxy Pattern)
- 合约代码通常会在 Etherscan 等区块浏览器上开源验证,任何人都可以阅读和审计
1.4 DeFi 说明商业契约,DAO 说明社会契约
智能合约的应用场景完美印证了它的「契约」本质:
- DeFi(去中心化金融):借贷、交易、保险等金融合约的数字化,体现的是商业契约关系。例如 Uniswap 的交易合约、Aave 的借贷合约。
- DAO(去中心化自治组织):投票、治理、提案等组织规则的数字化,体现的是社会契约关系。例如 MakerDAO 的治理合约。
二、合约运行原理
2.1 节点的双重角色
以太坊网络中的每个节点承担两个核心职责:
- 一侧处理交易(Transaction):接收、验证、广播交易
- 一侧运行 EVM(以太坊虚拟机):执行合约代码,更新状态
2.2 顺序执行,无并发
这是理解智能合约的关键:
- Block 顺序生成:区块按照固定的时间间隔依次产生
- Transaction 顺序执行:一个区块内的交易按照固定顺序逐个执行
- 没有并发访问:不存在多线程、不存在竞态条件(Race Condition)
这与传统后端开发截然不同——你不需要考虑锁、信号量等并发控制问题,但也意味着性能受限于链的吞吐量。
2.3 Transaction 触发 EVM 执行
合约的执行永远是由一笔 Transaction 触发的,理解 Transaction 的结构至关重要:
| 字段 | 说明 |
|---|---|
| from | 发起者地址(EOA 账户) |
| to | 目标地址(合约地址,或为空表示部署新合约) |
| value | 转账的 ETH 数量(单位 wei) |
| calldata | 调用数据(函数选择器 + 参数编码) |
关键理解:合约执行的背后是 Transaction 和 EVM。每次调用合约函数,本质上都是发送一笔交易,由 EVM 解析 calldata 并执行对应的函数逻辑。读取操作(view/pure 函数)不需要发送交易,可以在本地节点直接执行。
三、Solidity 语言介绍
Solidity 是以太坊智能合约的主流开发语言,具有以下特点:
- 强类型:所有变量必须声明类型,编译时检查类型安全
- 面向对象:支持合约继承、接口、抽象合约
- 多重继承:一个合约可以同时继承多个父合约(采用 C3 线性化解决菱形继承问题)
- 语法参照 C++:大括号语法、分号结尾,对有 C/C++/Java/JavaScript 经验的开发者友好
- 专为 EVM 设计:编译为 EVM 字节码,运行在以太坊虚拟机上
四、开发环境与工具链
4.1 solc 编译器
solc 是 Solidity 的官方编译器,将 .sol 源文件编译为 EVM 字节码和 ABI(Application Binary Interface)。
# 安装(macOS)
brew install solidity
# 编译合约
solc --bin --abi MyContract.sol
4.2 Remix IDE
Remix 是最适合入门的智能合约开发工具:
- 在线版:直接在浏览器中使用,无需安装
- 桌面版:Remix Desktop,提供离线开发能力
- 内置编译器、部署工具、调试器
- 支持连接 MetaMask 部署到真实网络
推荐入门阶段使用 Remix,后续项目开发再迁移到框架。
4.3 开发框架
| 框架 | 语言 | 特点 |
|---|---|---|
| Hardhat | JavaScript/TypeScript | 当前主流,插件生态丰富,内置 Hardhat Network |
| Truffle | JavaScript | 老牌框架,社区成熟,逐渐被 Hardhat 取代 |
| Foundry | Solidity | 用 Solidity 写测试,编译速度极快 |
4.4 SDK(前端交互库)
| 库 | 特点 |
|---|---|
| ethers.js | 轻量、模块化,当前推荐首选 |
| web3.js | 老牌库,API 丰富,社区庞大 |
| viem | TypeScript 优先,性能优异,新兴选择 |
五、第一个合约:NumberStorage
下面编写一个最简单的智能合约,实现数字的存储和读取:
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.2 <0.9.0;
contract NumberStorage {
uint public x;
function setX(uint px) public {
x = px;
}
function getX() public view returns(uint) {
return x;
}
function add(uint a, uint b) private pure returns(uint) {
return a + b;
}
}
代码逐行解析
第 1 行:许可证声明
// SPDX-License-Identifier: MIT
SPDX(Software Package Data Exchange)许可证标识符,声明代码的开源协议。从 Solidity 0.6.8 起,编译器会对缺少此声明的文件发出警告。常用值包括 MIT、GPL-3.0、UNLICENSED(不开源)。
第 2 行:版本声明
pragma solidity >=0.8.2 <0.9.0;
指定编译器版本范围。pragma 是编译器指令,告诉编译器只有在版本满足条件时才编译此文件。
第 4 行:合约定义
contract NumberStorage {
contract 关键字定义一个合约,类似面向对象语言中的 class。
第 5 行:状态变量
uint public x;
uint 是无符号整型(等同于 uint256),public 自动生成一个同名的 getter 函数。状态变量存储在区块链上,是合约的持久化数据。
第 7-9 行:写入函数
function setX(uint px) public {
x = px;
}
public 表示外部和内部都可以调用。修改状态变量需要发送交易(消耗 Gas)。
第 11-13 行:读取函数
function getX() public view returns(uint) {
return x;
}
view 表示只读取状态,不修改。调用 view 函数不消耗 Gas(本地执行)。
第 15-17 行:纯计算函数
function add(uint a, uint b) private pure returns(uint) {
return a + b;
}
private:仅合约内部可调用pure:既不读取也不修改状态,纯粹的计算函数
六、源文件结构
一个 Solidity 源文件的标准结构:
// 1. 许可证声明
// SPDX-License-Identifier: MIT
// 2. 版本声明
pragma solidity ^0.8.0;
// 3. import 语句(如有)
import "./OtherContract.sol";
// 4. 合约定义
contract MyContract {
// ...
}
七、版本问题
7.1 语义化版本号(Semantic Versioning)
Solidity 遵循语义化版本号:MAJOR.MINOR.PATCH
- MAJOR:不兼容的重大变更
- MINOR:向后兼容的功能新增
- PATCH:向后兼容的问题修复
7.2 版本声明的写法
pragma solidity ^0.8.0; // >=0.8.0 且 <0.9.0
pragma solidity >=0.8.2 <0.9.0; // 精确范围
pragma solidity 0.8.20; // 仅限指定版本
7.3 重要版本变化
| 版本 | 重要变化 |
|---|---|
| 0.8.0 | 整型溢出默认抛出异常(不再需要 SafeMath) |
| 0.8.0 | ABIEncoderV2 成为默认编码器 |
| 0.5.0 | 函数可见性必须显式声明(不再有默认的 public) |
| 0.6.0 | fallback 和 receive 替代匿名回退函数 |
特别注意 0.8.0 的整型溢出变化:低于 0.8.0 的版本中,
uint8(255) + 1的结果是0(取模运算),这是大量安全漏洞的根源。0.8.0 起默认会抛出异常,极大提升了安全性。
八、合约基本结构
一个完整的合约可能包含以下元素:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract MyContract {
// 1. 状态变量 - 永久存储在区块链上
uint public count;
address public owner;
// 2. 事件 - 用于记录日志,前端可以监听
event CountChanged(uint newCount);
// 3. 修饰符 - 函数执行的前置条件检查
modifier onlyOwner() {
require(msg.sender == owner, "Not owner");
_;
}
// 4. 构造函数 - 合约部署时执行一次
constructor() {
owner = msg.sender;
}
// 5. 函数 - 合约的行为逻辑
function increment() public onlyOwner {
count += 1;
emit CountChanged(count);
}
}
| 元素 | 说明 |
|---|---|
| 状态变量 | 合约的持久化数据,存储在链上 |
| 函数 | 合约的行为,可读取或修改状态 |
| 修饰符(modifier) | 可复用的函数前置/后置检查逻辑 |
| 事件(event) | 链上日志,便于前端监听和索引 |
| 构造函数(constructor) | 部署时执行一次,用于初始化 |
小结
本节我们了解了智能合约的本质是数字化的契约关系,理解了 Transaction + EVM 的执行模型,认识了 Solidity 语言和开发工具链,并编写了第一个智能合约。下一节我们将深入学习 Solidity 的值类型系统。
本文为 Web3 区块链系列第 3 篇,共 12 篇。
主题测试文章,只做测试使用。发布者:Walker,转转请注明出处:https://walker-learn.xyz/archives/7486
