核心关键词:智能合约、以太坊、存储变量、合约函数、Solidity、Vyper、构造函数、事件日志、合约示例
写在前面
阅读本文前,请确认你已经对「智能合约」这一概念有基本了解,并至少熟悉 JavaScript 或 Python 其中一种开发语言。这将帮助你更顺畅地理解后续代码示例与技术细节。
1. 合约灵魂:按作用把数据“安顿”好
以太坊上的智能合约永久存在于一个固定地址,它的任何文字、状态或算法都由数据与函数两部分组成。首要问题是:数据放哪才既经济又高效?
1.1 Storage(链上存储)
- 特点:随区块链永久存在,Gas 成本高。
- 场景:用户信息、代币余额、关键配置等长期占用数据。
数据类型(Solidity/Vyper 共有)
- 基础:uint、bool、address(20 字节以太坊地址)
- 高级:定长字节数组、不定长 bytes、枚举、结构体、string
pragma solidity ^0.8.0;
contract SimpleStorage {
uint storedData; // 存储于 storage
}1.2 Memory(函数内暂存)
- 特点:函数调用结束后自动销毁,Gas 花费低。
- 场景:函数运算过程中的中间值、缓存或返回值。
1.3 环境变量(无需声明即可使用)
链在执行智能合约时,会自动注入这些只读变量:
block.timestamp当前区块时间戳msg.sender当前调用者地址tx.origin原始交易发送者- 其他合约开发者关心的全局属性,如
block.number
2. 合约行为:函数如何与外界交互
函数是所有智能合约的业务入口,牢记以下四大关键字即可迅速构建可用的接口:
| 关键字 | 意义 | Gas 构成 |
|---|---|---|
| external | 仅允许外部调用(包括其他合约) | 非内部 |
| internal | 仅限当前合约或派生合约调用 | 内部 |
| public | 内、外部都可调用,自动生成 getter | 混合 |
| private | 仅当前合约内部可见 | 内部 |
2.1 纯查询 vs. 状态修改
- view:不修改任何状态,仅返回数据。常用于余额查询。
- pure:不读也不写合约状态。
- 未标记 view 或 pure 的函数默认具备“状态修改”能力,即需要消耗更多 Gas。
function balanceOf(address _owner) public view returns (uint _balance) {
return ownerPizzaCount[_owner];
}2.2 构造函数 Constructor
部署瞬间仅执行一次,负责设定初始值:
constructor() {
owner = msg.sender; // 谁部署,谁就拥有
}3. 事件与日志:链上与前端沟通的暗号
智能合约不会主动推送消息,但通过 事件 (event) 日志,可以通知前端成功或失败的结果。
event Transfer(address from, address to, uint amount);前端监听 Transfer 事件,即可实时更新用户余额、交易列表。
4. 完整合约范例拆解
4.1 Hello World
pragma solidity ^0.6.0;
contract HelloWorld {
string public message;
constructor(string memory initMessage) public {
message = initMessage;
}
function update(string memory newMessage) public {
message = newMessage;
}
}- constructor 初始化
message - 任何人都能通过
update修改
4.2 通用同质化代币(ERC-20 雏形)
pragma solidity ^0.5.10;
contract Token {
address public owner;
mapping(address => uint) public balances;
event Transfer(address from, address to, uint amount);
constructor() public {
owner = msg.sender;
}
function mint(address receiver, uint amount) public {
require(msg.sender == owner, "Not owner");
balances[receiver] += amount;
}
function transfer(address receiver, uint amount) public {
require(amount <= balances[msg.sender], "Insufficient balance");
balances[msg.sender] -= amount;
balances[receiver] += amount;
emit Transfer(msg.sender, receiver, amount);
}
}4.3 唯一数字资产(简化版 ERC-721)
CryptoPizza 合约展示了如何发行独一无二的披萨 NFT:披萨名称 + DNA 组成唯一索引;采用 mapping 与 struct 高效记录所有者与元数据。
5. 快速上手 FAQ
Q1:为什么把变量设为 public 时 Gas 会变高?
A:public 会自动生成外部可调的 getter 函数,涉及额外字节码,但总体增加极少,可视需求权衡。
Q2:Memory 存储数组类型有限制吗?
A:Memory 不能存储 mapping 或大体积复杂结构体。遇到大数据量场景,应拆解入参或改用 calldata。
Q3:view 函数除读状态外,可以访问外部合约吗?
A:可以,但应确保被调函数也标记为 view;否则触发链上调用将被拒。
Q4:事件日志真的可以永久删除吗?
A:不能。链上写入后的日志只可追加、无法修改或抹除。
Q5:构造函数支持重载吗?
A:Solidity 支持根据参数列表重载;Vyper 目前仅允许单一构造函数。
Q6:如何知道合约是否超大小限制?
A:编译器会给出 “Contract code size exceeds 24576 bytes” 警告,可借助库分离与代理模式缩减。
6. 最佳实践 & 下一步
- 利用 OpenZeppelin 库 的参考实现,减少重复造轮子。
- 先用 Remix 在线 IDE 完成编译和测试,再迁移到 Hardhat/Truffle。
- 关注 Gas Report,将只读逻辑抽离为 view 层,大量节约用户交易费。
👉 立即体验以太坊测试网部署,30 分钟完成首个可运行智能合约
延伸阅读
若你想深挖 Solidity 与 Vyper 高级模式,建议按主题阅读官方文档:
- Solidity 中文文档
- Vyper 官方文档
- 合约交互教程:与现有合约跨合约调用、升级代理模式
通过以上阅读与实践,你将获得 智能合约、以太坊开发、区块链数据存储 等关键能力的系统认知。