核心关键词:智能合约、以太坊、存储变量、合约函数、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 官方文档
- 合约交互教程:与现有合约跨合约调用、升级代理模式
通过以上阅读与实践,你将获得 智能合约、以太坊开发、区块链数据存储 等关键能力的系统认知。