Solidity 基础语法
Solidity 是以太坊上最流行的智能合约编程语言。它是一门静态类型的、面向对象的高级语言,语法受到 JavaScript、C++ 和 Python 的影响。本章将系统介绍 Solidity 的基础语法,帮助你快速入门智能合约开发。
第一个智能合约
让我们从一个简单的合约开始:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract HelloWorld {
string public greet = "Hello, World!";
function getGreet() public view returns (string memory) {
return greet;
}
function setGreet(string memory _greet) public {
greet = _greet;
}
}
代码解析:
SPDX-License-Identifier: MIT:声明代码的开源许可证,编译器会将其加入字节码pragma solidity ^0.8.0:指定编译器版本,^表示兼容 0.8.x 版本contract HelloWorld:定义一个名为 HelloWorld 的合约string public greet:声明一个公开的状态变量,自动生成 getter 函数function:定义函数,public表示可被外部调用view:表示函数不修改状态memory:表示数据存储在内存中,而非存储
数据类型
Solidity 提供了丰富的数据类型,分为值类型和引用类型。
值类型
值类型变量在赋值时会被复制,修改新变量不影响原变量。
布尔类型
bool public isActive = true;
bool public isComplete = false;
// 布尔运算
bool public result = !isActive; // 非
bool public both = isActive && isComplete; // 与
bool public either = isActive || isComplete; // 或
bool public equal = isActive == isComplete; // 相等
bool public notEqual = isActive != isComplete; // 不等
整数类型
Solidity 提供有符号整数 int 和无符号整数 uint,支持 8 到 256 位(步长 8)。
int8 public smallInt = -128; // -128 到 127
int256 public bigInt = -2**255; // 大整数
uint8 public smallUint = 255; // 0 到 255
uint256 public bigUint = 2**256 - 1;
uint public defaultUint; // uint256 的简写
// 算术运算
uint256 public a = 10;
uint256 public b = 3;
uint256 public sum = a + b; // 加法: 13
uint256 public diff = a - b; // 减法: 7
uint256 public product = a * b; // 乘法: 30
uint256 public quotient = a / b; // 除法: 3(整数除法)
uint256 public remainder = a % b; // 取模: 1
uint256 public power = a ** 2; // 幂运算: 100
// 自增自减
uint256 public counter = 0;
counter++; // counter = 1
counter--; // counter = 0
注意:Solidity 0.8.0 之前,整数溢出不会报错,需要使用 SafeMath 库。0.8.0 之后,溢出会自动回滚交易。
地址类型
地址类型用于存储以太坊地址,有两种变体:
address public owner = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4;
// address payable 可以接收 ETH
address payable public recipient = payable(0x5B38Da6a701c568545dCfcB03FcB875f56beddC4);
// 地址属性
uint256 public balance = owner.balance; // 地址余额(Wei)
// 地址方法
function sendEther() public {
// transfer:发送 ETH,失败抛出异常(2300 Gas 限制)
recipient.transfer(1 ether);
// send:发送 ETH,返回成功与否(2300 Gas 限制)
bool success = recipient.send(1 ether);
require(success, "Send failed");
// call:推荐方式,可转发所有 Gas
(bool sent, ) = recipient.call{value: 1 ether}("");
require(sent, "Call failed");
}
定长字节数组
bytes1 public b1 = 0x12; // 1 字节
bytes2 public b2 = 0x1234; // 2 字节
bytes32 public b32 = 0x1234; // 32 字节,常用于存储哈希
// 属性
uint256 public length = b1.length; // 长度
// 比较
bool public equal = b1 == b2;
枚举类型
枚举用于定义一组命名常量:
enum Status { Pending, Active, Completed, Cancelled }
Status public currentStatus = Status.Pending;
function updateStatus(Status _status) public {
currentStatus = _status;
}
function complete() public {
currentStatus = Status.Completed;
}
// 枚举可以与整数转换
function getStatusIndex() public view returns (uint) {
return uint(currentStatus);
}
引用类型
引用类型变量存储的是数据的引用,包括数组、结构体和映射。
数组
Solidity 支持固定长度数组和动态数组:
// 固定长度数组
uint256[5] public fixedArray = [1, 2, 3, 4, 5];
// 动态数组
uint256[] public dynamicArray;
// 初始化动态数组
function initArray() public {
dynamicArray = [1, 2, 3];
}
// 添加元素
function pushElement(uint256 _value) public {
dynamicArray.push(_value);
}
// 删除最后一个元素
function popElement() public {
dynamicArray.pop();
}
// 获取长度
function getLength() public view returns (uint256) {
return dynamicArray.length;
}
// 删除元素(不改变长度,重置为默认值)
function removeElement(uint256 _index) public {
delete dynamicArray[_index];
}
// 内存数组
function createMemoryArray() public pure returns (uint256[] memory) {
uint256[] memory memArray = new uint256[](3);
memArray[0] = 1;
memArray[1] = 2;
memArray[2] = 3;
return memArray;
}
字符串和字节
string public name = "Hello";
bytes public data = "Hello";
// 字符串操作
function concatenate(string memory _a, string memory _b) public pure returns (string memory) {
return string(abi.encodePacked(_a, _b));
}
// 字符串比较
function compareStrings(string memory _a, string memory _b) public pure returns (bool) {
return keccak256(abi.encodePacked(_a)) == keccak256(abi.encodePacked(_b));
}
// 获取字节长度
function getByteLength(string memory _str) public pure returns (uint256) {
return bytes(_str).length;
}
结构体
结构体用于组合多个数据字段:
struct User {
string name;
uint256 age;
address wallet;
bool isActive;
}
User public admin;
// 初始化结构体
function createUser(string memory _name, uint256 _age) public {
admin = User({
name: _name,
age: _age,
wallet: msg.sender,
isActive: true
});
}
// 另一种初始化方式
function createUserSimple(string memory _name, uint256 _age) public {
admin = User(_name, _age, msg.sender, true);
}
// 修改字段
function updateAge(uint256 _age) public {
admin.age = _age;
}
映射
映射是键值对存储结构,类似于哈希表:
mapping(address => uint256) public balances;
mapping(address => mapping(address => uint256)) public allowances;
// 设置值
function setBalance(uint256 _amount) public {
balances[msg.sender] = _amount;
}
// 获取值(不存在返回默认值)
function getBalance(address _user) public view returns (uint256) {
return balances[_user];
}
// 删除值
function removeBalance(address _user) public {
delete balances[_user];
}
映射的特点:
- 所有键都存在,不存在的键返回默认值
- 无法遍历所有键值对
- 无法获取映射的长度
- 只能用于状态变量或存储引用
变量作用域
状态变量
状态变量永久存储在区块链上:
contract StateVariables {
uint256 public count; // 存储
string internal name; // 内部可见
address private owner; // 私有
bool external constant ACTIVE = true; // 常量
uint256 immutable createdAt; // 不可变
constructor() {
owner = msg.sender;
createdAt = block.timestamp;
}
}
constant 和 immutable 的区别:
constant:编译时确定值,不占用存储槽immutable:部署时确定值,存储在字节码中
局部变量
局部变量在函数执行期间存在:
function localVariables() public pure returns (uint256) {
uint256 a = 1; // 存储在栈上
uint256 b = 2;
uint256 sum = a + b;
return sum;
}
全局变量
Solidity 提供了多个全局变量:
function globalVariables() public view returns (
address, // msg.sender
uint256, // msg.value
uint256, // block.timestamp
uint256, // block.number
address // block.coinbase
) {
return (
msg.sender, // 调用者地址
msg.value, // 发送的 ETH(Wei)
block.timestamp, // 区块时间戳
block.number, // 区块高度
block.coinbase // 矿工地址
);
}
常用全局变量:
| 变量 | 类型 | 说明 |
|---|---|---|
| msg.sender | address | 调用者地址 |
| msg.value | uint256 | 发送的 ETH 数量 |
| msg.data | bytes | 完整的调用数据 |
| block.timestamp | uint256 | 区块时间戳(秒) |
| block.number | uint256 | 区块高度 |
| block.coinbase | address | 出块者地址 |
| block.difficulty | uint256 | 区块难度 |
| block.gaslimit | uint256 | 区块 Gas 上限 |
| tx.origin | address | 交易发起者地址 |
| tx.gasprice | uint256 | 交易的 Gas 价格 |
| gasleft() | uint256 | 剩余 Gas |
函数
函数是合约的核心组件,定义了合约的行为。
函数定义
function functionName(
parameterType parameterName,
parameterType parameterName
)
visibility
[modifier]
[stateMutability]
returns (returnType returnName)
{
// 函数体
}
可见性
Solidity 提供四种可见性修饰符:
contract Visibility {
uint256 private privateVar = 1; // 仅本合约
uint256 internal internalVar = 2; // 本合约和子合约
uint256 public publicVar = 3; // 所有合约和外部
uint256 internalVar2 = 4; // 默认 internal
function privateFunc() private pure returns (string memory) {
return "private";
}
function internalFunc() internal pure returns (string memory) {
return "internal";
}
function publicFunc() public pure returns (string memory) {
return "public";
}
function externalFunc() external pure returns (string memory) {
return "external";
}
}
| 可见性 | 本合约 | 子合约 | 外部合约 | 外部调用 |
|---|---|---|---|---|
| private | ✓ | ✗ | ✗ | ✗ |
| internal | ✓ | ✓ | ✗ | ✗ |
| public | ✓ | ✓ | ✓ | ✓ |
| external | ✗ | ✗ | ✓ | ✓ |
状态可变性
contract StateMutability {
uint256 public count = 0;
// pure:不读取也不修改状态
function add(uint256 a, uint256 b) public pure returns (uint256) {
return a + b;
}
// view:读取但不修改状态
function getCount() public view returns (uint256) {
return count;
}
// 无修饰符:可以修改状态
function increment() public returns (uint256) {
count += 1;
return count;
}
// payable:可以接收 ETH
function deposit() public payable {
// msg.value 是发送的 ETH 数量
}
}
函数修饰器
修饰器用于在函数执行前后添加逻辑:
contract Modifier {
address public owner;
uint256 public count;
bool public paused = false;
constructor() {
owner = msg.sender;
}
// 仅所有者
modifier onlyOwner() {
require(msg.sender == owner, "Not owner");
_; // 继续执行函数体
}
// 非暂停状态
modifier whenNotPaused() {
require(!paused, "Contract is paused");
_;
}
// 组合使用修饰器
function increment() public onlyOwner whenNotPaused {
count += 1;
}
function pause() public onlyOwner {
paused = true;
}
}
构造函数
构造函数在合约部署时执行一次:
contract Constructor {
address public owner;
string public name;
constructor(string memory _name) {
owner = msg.sender;
name = _name;
}
}
接收 ETH
contract ReceiveEther {
event Received(address sender, uint256 amount);
// 接收 ETH(无数据)
receive() external payable {
emit Received(msg.sender, msg.value);
}
// 接收 ETH(有数据)
fallback() external payable {
emit Received(msg.sender, msg.value);
}
// 查询合约余额
function getBalance() public view returns (uint256) {
return address(this).balance;
}
}
控制流
Solidity 支持常见的控制流语句。
条件语句
function conditional(uint256 _value) public pure returns (string memory) {
if (_value > 100) {
return "Large";
} else if (_value > 50) {
return "Medium";
} else {
return "Small";
}
}
// 三元运算符
function ternary(uint256 _value) public pure returns (string memory) {
return _value > 50 ? "Large" : "Small";
}
循环语句
function loop() public pure returns (uint256) {
uint256 sum = 0;
// for 循环
for (uint256 i = 1; i <= 10; i++) {
sum += i;
}
// while 循环
uint256 j = 0;
while (j < 5) {
sum += j;
j++;
}
// do-while 循环
uint256 k = 0;
do {
sum += k;
k++;
} while (k < 3);
return sum;
}
// 循环中的控制
function loopControl() public pure returns (uint256) {
uint256 sum = 0;
for (uint256 i = 0; i < 10; i++) {
if (i == 3) continue; // 跳过本次迭代
if (i == 7) break; // 跳出循环
sum += i;
}
return sum; // 0 + 1 + 2 + 4 + 5 + 6 = 18
}
注意:循环次数过多可能导致 Gas 耗尽,需要谨慎设计。
错误处理
Solidity 提供三种错误处理方式。
require
用于验证外部输入和条件:
function requireExample(uint256 _value) public pure returns (string memory) {
require(_value > 0, "Value must be greater than 0");
require(_value < 100, "Value must be less than 100");
return "Valid";
}
revert
用于复杂条件判断后回滚:
function revertExample(uint256 _value) public pure returns (string memory) {
if (_value <= 0) {
revert("Value must be greater than 0");
}
if (_value >= 100) {
revert("Value must be less than 100");
}
return "Valid";
}
assert
用于检查内部不变量,失败表示代码有 bug:
function assertExample(uint256 _value) public pure returns (uint256) {
uint256 result = _value * 2;
assert(result >= _value); // 检查溢出(0.8.0 后不需要)
return result;
}
自定义错误
Solidity 0.8.4 引入自定义错误,节省 Gas:
error InsufficientBalance(uint256 available, uint256 required);
function customError(uint256 _amount) public view returns (bool) {
uint256 balance = address(this).balance;
if (balance < _amount) {
revert InsufficientBalance(balance, _amount);
}
return true;
}
try-catch
用于外部调用失败时继续执行:
interface IExternal {
function someFunction() external returns (bool);
}
function tryCatch(address _contract) public returns (bool) {
IExternal externalContract = IExternal(_contract);
try externalContract.someFunction() returns (bool result) {
return result;
} catch Error(string memory reason) {
// require/revert 错误
return false;
} catch (bytes memory) {
// 其他错误
return false;
}
}
事件
事件用于记录日志,前端可以监听:
contract Events {
event Transfer(address indexed from, address indexed to, uint256 amount);
event Deposit(address indexed user, uint256 amount, uint256 timestamp);
function transfer(address _to, uint256 _amount) public {
emit Transfer(msg.sender, _to, _amount);
}
function deposit() public payable {
emit Deposit(msg.sender, msg.value, block.timestamp);
}
}
indexed 参数:最多三个 indexed 参数,可以被前端过滤查询。
小结
本章介绍了 Solidity 的基础语法,包括数据类型、函数、控制流和错误处理。掌握这些基础知识是编写智能合约的前提。下一章我们将学习 Solidity 的高级特性,包括继承、接口和库。