Solidity 是以太坊智能合约开发语言,类 JavaScript 语法,支持合约、函数、变量、修饰符等。常用关键字包括 contract、function、public、view、require。实战中常结合 Remix 测试部署,涉及钱包地址、代币转账等功能。
作者
经验值
阅读时间
在 Solidity 中,数据类型和函数写法是智能合约开发的基础。下面我将分两部分介绍:
uint
:无符号整数(只能为正)int
:有符号整数(正负都可)uint256
和 int256
uint256 public age = 30;
int256 public temperature = -10;
布尔类型,取值为 true 或 false。
bool public isActive = true;
以太坊地址类型,存储用户或合约地址。
address public owner = msg.sender;
string
:字符串(UTF-8)bytes
:可变长度的字节数组(如 bytes32
是固定长度)
string public name = "Alice";
bytes32 public id = "0xabc123...";
定义一组状态常量。
enum Status { Pending, Shipped, Delivered }
Status public currentStatus = Status.Pending;
自定义数据结构。
struct User {
string name;
uint age;
bool isActive;
}
User public user = User("Bob", 25, true);
function 函数名(参数) 修饰符 返回值类型 {
// 函数体
}
// 状态变量
uint public count = 0;
// 增加计数器的函数
function increment() public {
count += 1;
}
// 读取函数,带返回值
function getCount() public view returns (uint) {
return count;
}
// 修改状态变量的函数
function setCount(uint _count) public {
count = _count;
}
public
:任何人都可以调用external
:仅外部调用internal
:只能本合约或继承合约调用private
:只能本合约调用view
:读取数据但不修改状态pure
:不读也不写区块链数据payable
:允许接收 ETH
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract BasicTypesDemo {
// === 基础类型 ===
uint256 public age = 30;
int256 public temperature = -5;
bool public isActive = true;
address public owner;
string public name = "Alice";
bytes32 public id = keccak256(abi.encodePacked("Alice"));
// === 枚举类型 ===
enum Status { Pending, Approved, Rejected }
Status public currentStatus = Status.Pending;
// === 结构体 ===
struct User {
string username;
uint256 balance;
bool registered;
}
User public user;
// === 构造函数 ===
constructor() {
owner = msg.sender;
}
// === 设置用户信息 ===
function registerUser(string memory _username, uint256 _balance) public {
user = User(_username, _balance, true);
}
// === 修改状态 ===
function setStatus(Status _status) public {
currentStatus = _status;
}
// === 简单逻辑函数 ===
function incrementAge() public {
age += 1;
}
function setTemperature(int256 _temp) public {
temperature = _temp;
}
function toggleActive() public {
isActive = !isActive;
}
// === 查看用户余额(纯函数)===
function doubleBalance(uint256 _balance) public pure returns (uint256) {
return _balance * 2;
}
// === 查询当前状态(只读)===
function getStatus() public view returns (Status) {
return currentStatus;
}
}
registerUser(...)
注册用户setStatus(...)
切换状态toggleActive()
改变布尔值user
的结构体内容在 Solidity 中,event(事件) 是一种在链上记录日志的机制,主要用于合约与前端应用或开发者工具之间的通信桥梁。它不会影响区块链的状态,但会在交易日志中留下记录,可被监听、查询、筛选。
用途总结:
用途 | 说明 |
1. 记录链上重要操作 | 如转账、注册、授权等 |
2. 前端监听合约行为 | Web3.js / Ethers.js 等可以实时监听 |
3. 降低链上开销 | 比 storage 更省 gas,只用于读写日志 |
4. 调试合约行为 | 像 console.log,但是链上级别 |
event Registered(address indexed user, string name, uint256 age);
indexed:最多 3 个字段可标记为 indexed,方便筛选查询。
emit Registered(msg.sender, _name, _age);
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract EventDemo {
event Registered(address indexed user, string name, uint age);
event MessagePosted(address indexed sender, string message, uint256 timestamp);
function register(string memory _name, uint _age) public {
emit Registered(msg.sender, _name, _age);
}
function postMessage(string memory _msg) public {
emit MessagePosted(msg.sender, _msg, block.timestamp);
}
}
register(...)
或 postMessage(...)
下方“交易详情”面板中有 Logs
,显示事件内容
contract.on("Registered", (user, name, age) => {
console.log(`New user: ${name}, age: ${age}, address: ${user}`);
});
访问合约地址
indexed
字段可用于日志搜索筛选event
记录重要链上行为是 DApp 开发标准实践在 Solidity 合约开发中,权限控制是非常关键的一环,用来防止非授权地址调用敏感函数。常见的权限控制方式包括使用 msg.sender 判断调用者,或者使用 OpenZeppelin 提供的 Ownable 合约来实现更优雅的权限管理。
address public owner;
constructor() {
owner = msg.sender; // 部署者为初始拥有者
}
modifier onlyOwner() {
require(msg.sender == owner, "Not the owner");
_;
}
function setSomething(uint _x) public onlyOwner {
// 只有 owner 才能调用
}
OpenZeppelin 是社区广泛使用的安全合约库。它的 Ownable 合约已经帮你封装好了权限控制逻辑,简单且安全。
npm install @openzeppelin/contracts
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/access/Ownable.sol";
contract MyContract is Ownable {
uint public value;
function setValue(uint _value) public onlyOwner {
value = _value;
}
}
说明:
owner = msg.sender
(部署者)onlyOwner
修饰器transferOwnership(address newOwner)
:转移权限renounceOwnership()
:放弃控制权(不推荐轻易使用)操作 | 函数 |
更换拥有者 | transferOwnership(newOwner) |
查询拥有者 | owner() |
放弃控制权(慎用) | renounceOwnership() |
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/access/Ownable.sol";
contract AccessControlDemo is Ownable {
event ValueChanged(uint newValue);
uint public value;
function setValue(uint _value) public onlyOwner {
value = _value;
emit ValueChanged(_value);
}
}
项目 | 手动 msg.sender 检查 | OpenZeppelin Ownable |
可读性 | 一般 | 高(语义清晰) |
安全性 | 容易出错 | 社区审核库,强烈推荐 |
功能性 | 需手动添加转移权限功能 | 自带 transferOwnership() |
这是 Solidity 安全开发中非常核心的一部分!我们分两个板块讲解:
Solidity 提供三种主要的错误处理方式:
用于校验输入参数、状态条件,失败会撤销交易并退还 gas 未使用部分。
require(msg.sender == owner, "Only owner can call");
require(amount > 0, "Amount must be positive");
用于检查程序内部不会出错的条件,失败会消耗所有 gas,适合检测 bug。
assert(balance[msg.sender] >= 0); // 这应该永远为真
不推荐频繁使用 assert,它通常用于验证严重逻辑错误或不变量。
等价于 require(false, "reason"),但可用于复杂嵌套逻辑中。
if (x < 10) {
revert("x must be >= 10");
}
重入攻击是指攻击者合约在接收到合约转账时(例如 call.value()),再次调用目标合约的函数,从而绕过原有逻辑反复盗取资产。
function withdraw() public {
require(balances[msg.sender] > 0);
payable(msg.sender).call{value: balances[msg.sender]}("");
balances[msg.sender] = 0;
}
如果攻击者合约在 call 后立即再次调用 withdraw(),那 balances[msg.sender] 还没归零,就能反复提钱。
3.1. 先更新状态,再转账!(Checks-Effects-Interactions 模式)
function withdraw() public {
uint amount = balances[msg.sender];
require(amount > 0);
balances[msg.sender] = 0; // ✅ 先归零
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "Transfer failed");
}
3.2. 使用 ReentrancyGuard(OpenZeppelin 提供)
npm install @openzeppelin/contracts
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract SafeWithdraw is ReentrancyGuard {
mapping(address => uint) public balances;
function deposit() public payable {
balances[msg.sender] += msg.value;
}
function withdraw() public nonReentrant {
uint amount = balances[msg.sender];
require(amount > 0, "No balance");
balances[msg.sender] = 0;
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "Transfer failed");
}
}
ReentrancyGuard 提供 nonReentrant 修饰器,阻止函数重入。
安全建议 | 描述 |
使用 call 时小心 | 它会转移控制权,容易触发攻击 |
永远先修改状态 | 然后再进行外部交互(转账等) |
使用 ReentrancyGuard | 简洁有效地阻止重入 |
审计代码逻辑 | 特别是涉及转账、循环调用的部分 |