跳到主要内容

Solidity 高级特性

掌握了 Solidity 的基础语法后,本章将介绍更高级的特性,包括继承、接口、抽象合约、库以及常用的设计模式。这些特性帮助你编写更模块化、可维护的智能合约。

继承

Solidity 支持多重继承,允许合约继承其他合约的代码。

基本继承

contract Animal {
string public name;

constructor(string memory _name) {
name = _name;
}

function speak() public virtual returns (string memory) {
return "Some sound";
}
}

contract Dog is Animal {
constructor(string memory _name) Animal(_name) {}

function speak() public override returns (string memory) {
return "Woof!";
}
}

要点

  • 使用 is 关键字继承
  • 父合约构造函数需要在子合约构造函数中调用
  • 使用 virtualoverride 实现函数重写

多重继承

contract A {
function foo() public virtual returns (string memory) {
return "A";
}
}

contract B is A {
function foo() public virtual override returns (string memory) {
return "B";
}
}

contract C is A {
function foo() public virtual override returns (string memory) {
return "C";
}
}

contract D is B, C {
function foo() public override(B, C) returns (string memory) {
// 调用特定父合约的函数
return super.foo(); // 返回 "C"(线性化顺序)
}
}

继承顺序

Solidity 使用 C3 线性化算法确定继承顺序。从"最基类"到"最派生类"的顺序。在多重继承时,顺序很重要:

// 正确:从最基类到最派生类
contract Child is A, B, C {}

// 错误:C 依赖 B,B 依赖 A
contract Child is C, B, A {}

调用父合约函数

contract Parent {
function getValue() public virtual returns (uint256) {
return 100;
}
}

contract Child is Parent {
function getValue() public override returns (uint256) {
// 方式一:直接调用
uint256 parentValue = Parent.getValue();

// 方式二:使用 super
uint256 superValue = super.getValue();

return parentValue + 1;
}
}

抽象合约和接口

抽象合约

包含未实现函数的合约必须声明为 abstract:

abstract contract Animal {
string public name;

constructor(string memory _name) {
name = _name;
}

// 未实现的函数
function speak() public virtual returns (string memory);
}

contract Cat is Animal {
constructor(string memory _name) Animal(_name) {}

function speak() public override returns (string memory) {
return "Meow!";
}
}

接口

接口定义了合约的外部 API,所有函数必须是 external 且没有实现:

interface IERC20 {
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function transfer(address to, uint256 amount) external returns (bool);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
function transferFrom(address from, address to, uint256 amount) external returns (bool);

event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
}

contract MyToken is IERC20 {
mapping(address => uint256) private _balances;
mapping(address => mapping(address => uint256)) private _allowances;
uint256 private _totalSupply;

function totalSupply() external view override returns (uint256) {
return _totalSupply;
}

function balanceOf(address account) external view override returns (uint256) {
return _balances[account];
}

function transfer(address to, uint256 amount) external override returns (bool) {
_transfer(msg.sender, to, amount);
return true;
}

function allowance(address owner, address spender) external view override returns (uint256) {
return _allowances[owner][spender];
}

function approve(address spender, uint256 amount) external override returns (bool) {
_approve(msg.sender, spender, amount);
return true;
}

function transferFrom(address from, address to, uint256 amount) external override returns (bool) {
uint256 currentAllowance = _allowances[from][msg.sender];
require(currentAllowance >= amount, "ERC20: transfer amount exceeds allowance");
_approve(from, msg.sender, currentAllowance - amount);
_transfer(from, to, amount);
return true;
}

function _transfer(address from, address to, uint256 amount) internal {
require(from != address(0), "ERC20: transfer from zero address");
require(to != address(0), "ERC20: transfer to zero address");

uint256 fromBalance = _balances[from];
require(fromBalance >= amount, "ERC20: transfer amount exceeds balance");
_balances[from] = fromBalance - amount;
_balances[to] += amount;

emit Transfer(from, to, amount);
}

function _approve(address owner, address spender, uint256 amount) internal {
require(owner != address(0), "ERC20: approve from zero address");
require(spender != address(0), "ERC20: approve to zero address");

_allowances[owner][spender] = amount;
emit Approval(owner, spender, amount);
}

function _mint(address account, uint256 amount) internal {
require(account != address(0), "ERC20: mint to zero address");

_totalSupply += amount;
_balances[account] += amount;
emit Transfer(address(0), account, amount);
}
}

接口的限制

  • 不能有状态变量
  • 不能有构造函数
  • 不能实现任何函数
  • 所有函数必须是 external

库是可重用的代码块,不能有状态变量,也不能继承或被继承。

使用库

library Math {
function max(uint256 a, uint256 b) internal pure returns (uint256) {
return a >= b ? a : b;
}

function min(uint256 a, uint256 b) internal pure returns (uint256) {
return a <= b ? a : b;
}

function sqrt(uint256 x) internal pure returns (uint256) {
if (x == 0) return 0;
uint256 z = (x + 1) / 2;
uint256 y = x;
while (z < y) {
y = z;
z = (x / z + z) / 2;
}
return y;
}
}

contract UsingLibrary {
using Math for uint256;

function testMath(uint256 a, uint256 b) public pure returns (uint256, uint256, uint256) {
// 使用库函数
return (a.max(b), a.min(b), a.sqrt());
}

// 或者直接调用
function directCall(uint256 a, uint256 b) public pure returns (uint256) {
return Math.max(a, b);
}
}

库的调用方式

library ArrayUtils {
// 内联调用(节省部署成本)
function sum(uint256[] memory arr) internal pure returns (uint256) {
uint256 total = 0;
for (uint256 i = 0; i < arr.length; i++) {
total += arr[i];
}
return total;
}

// 外部调用(需要单独部署库)
function remove(uint256[] storage arr, uint256 index) public returns (uint256[] storage) {
require(index < arr.length, "Index out of bounds");
arr[index] = arr[arr.length - 1];
arr.pop();
return arr;
}
}

存储布局

理解存储布局对于优化 Gas 消耗至关重要。

存储槽

以太坊的存储是 32 字节(256 位)的槽数组。状态变量按声明顺序存储,从槽 0 开始。

contract StorageLayout {
// 槽 0
uint256 public a; // 32 字节

// 槽 1
uint128 public b; // 16 字节
uint128 public c; // 16 字节(与 b 打包在同一槽)

// 槽 2
uint256 public d; // 32 字节

// 槽 3
address public owner; // 20 字节
bool public isActive; // 1 字节(与 owner 打包)
uint96 public value; // 12 字节(与 owner 打包)
}

打包规则

  • 如果多个变量可以放入一个 32 字节的槽,它们会被打包
  • 打包顺序按声明顺序
  • 不同类型的变量可能需要填充

存储优化

contract StorageOptimized {
uint128 a;
uint128 b;
uint256 c;
}

// 上面的布局使用 2 个槽:
// 槽 0: a (16) + b (16) = 32
// 槽 1: c (32)

contract StorageNotOptimized {
uint128 a;
uint256 c;
uint128 b;
}

// 上面的布局使用 3 个槽:
// 槽 0: a (16) + 填充 (16)
// 槽 1: c (32)
// 槽 2: b (16) + 填充 (16)

映射和动态数组的存储

映射和动态数组使用不同的存储方式:

contract MappingStorage {
mapping(address => uint256) public balances;
uint256[] public items;

// balances[key] 的存储位置:
// keccak256(key . slot)

// items 数组:
// 槽 n: 数组长度
// items[i] 存储在 keccak256(n) + i
}

设计模式

访问控制模式

contract AccessControl {
address public owner;
mapping(bytes32 => mapping(address => bool)) public roles;

bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE");
bytes32 public constant USER_ROLE = keccak256("USER_ROLE");

modifier onlyOwner() {
require(msg.sender == owner, "Not owner");
_;
}

modifier onlyRole(bytes32 role) {
require(roles[role][msg.sender], "Not authorized");
_;
}

constructor() {
owner = msg.sender;
roles[ADMIN_ROLE][msg.sender] = true;
}

function grantRole(bytes32 role, address account) public onlyOwner {
roles[role][account] = true;
}

function revokeRole(bytes32 role, address account) public onlyOwner {
roles[role][account] = false;
}

function transferOwnership(address newOwner) public onlyOwner {
require(newOwner != address(0), "Invalid address");
owner = newOwner;
}
}

可升级代理模式

contract Proxy {
address public implementation;
address public admin;

constructor(address _implementation) {
implementation = _implementation;
admin = msg.sender;
}

function upgrade(address _implementation) public {
require(msg.sender == admin, "Not admin");
implementation = _implementation;
}

fallback() external payable {
address impl = implementation;
assembly {
calldatacopy(0, 0, calldatasize())
let result := delegatecall(gas(), impl, 0, calldatasize(), 0, 0)
returndatacopy(0, 0, returndatasize())
switch result
case 0 { revert(0, returndatasize()) }
default { return(0, returndatasize()) }
}
}

receive() external payable {
fallback();
}
}

工厂模式

contract Child {
address public parent;
string public name;

constructor(string memory _name) {
parent = msg.sender;
name = _name;
}
}

contract Factory {
Child[] public children;

event ChildCreated(address child, string name);

function createChild(string memory _name) public returns (Child) {
Child child = new Child(_name);
children.push(child);
emit ChildCreated(address(child), _name);
return child;
}

function getChildCount() public view returns (uint256) {
return children.length;
}
}

状态机模式

contract StateMachine {
enum State { Created, Active, Paused, Completed }

State public currentState;

event StateChanged(State from, State to);

modifier inState(State _state) {
require(currentState == _state, "Invalid state");
_;
}

constructor() {
currentState = State.Created;
}

function activate() public inState(State.Created) {
_transition(State.Active);
}

function pause() public inState(State.Active) {
_transition(State.Paused);
}

function resume() public inState(State.Paused) {
_transition(State.Active);
}

function complete() public inState(State.Active) {
_transition(State.Completed);
}

function _transition(State _newState) internal {
State oldState = currentState;
currentState = _newState;
emit StateChanged(oldState, _newState);
}
}

提款模式

contract Withdrawal {
mapping(address => uint256) public balances;

event Deposit(address indexed user, uint256 amount);
event Withdrawal(address indexed user, uint256 amount);

function deposit() public payable {
require(msg.value > 0, "No value sent");
balances[msg.sender] += msg.value;
emit Deposit(msg.sender, msg.value);
}

function withdraw(uint256 _amount) public {
require(balances[msg.sender] >= _amount, "Insufficient balance");

// 先更新状态,再发送 ETH(防止重入攻击)
balances[msg.sender] -= _amount;

(bool success, ) = msg.sender.call{value: _amount}("");
require(success, "Transfer failed");

emit Withdrawal(msg.sender, _amount);
}

function withdrawAll() public {
uint256 amount = balances[msg.sender];
require(amount > 0, "No balance");

balances[msg.sender] = 0;

(bool success, ) = msg.sender.call{value: amount}("");
require(success, "Transfer failed");

emit Withdrawal(msg.sender, amount);
}
}

安全最佳实践

重入攻击防护

contract ReentrancyGuard {
bool private locked;

modifier nonReentrant() {
require(!locked, "Reentrant call");
locked = true;
_;
locked = false;
}

mapping(address => uint256) public balances;

function withdraw(uint256 _amount) public nonReentrant {
require(balances[msg.sender] >= _amount, "Insufficient balance");

balances[msg.sender] -= _amount;

(bool success, ) = msg.sender.call{value: _amount}("");
require(success, "Transfer failed");
}
}

整数溢出检查

Solidity 0.8.0 之后自动检查溢出,但需要注意:

contract OverflowCheck {
uint256 public balance;

function uncheckedExample(uint256 a, uint256 b) public pure returns (uint256) {
// 在特定情况下跳过溢出检查(节省 Gas)
unchecked {
return a + b;
}
}

function safeAdd(uint256 a, uint256 b) public pure returns (uint256) {
// 0.8.0 之后自动检查溢出
return a + b;
}
}

访问控制

contract SecureContract {
address public owner;
bool public paused = false;

modifier onlyOwner() {
require(msg.sender == owner, "Not owner");
_;
}

modifier whenNotPaused() {
require(!paused, "Contract paused");
_;
}

modifier whenPaused() {
require(paused, "Contract not paused");
_;
}

constructor() {
owner = msg.sender;
}

function pause() public onlyOwner whenNotPaused {
paused = true;
}

function unpause() public onlyOwner whenPaused {
paused = false;
}
}

小结

本章介绍了 Solidity 的高级特性,包括继承、接口、库和常用的设计模式。掌握这些知识可以帮助你编写更安全、更模块化的智能合约。下一章我们将学习 Web3 开发工具和框架,开始构建完整的 DApp。

参考资料