跳到主要内容

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.senderaddress调用者地址
msg.valueuint256发送的 ETH 数量
msg.databytes完整的调用数据
block.timestampuint256区块时间戳(秒)
block.numberuint256区块高度
block.coinbaseaddress出块者地址
block.difficultyuint256区块难度
block.gaslimituint256区块 Gas 上限
tx.originaddress交易发起者地址
tx.gaspriceuint256交易的 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 的高级特性,包括继承、接口和库。

参考资料