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关键字继承 - 父合约构造函数需要在子合约构造函数中调用
- 使用
virtual和override实现函数重写
多重继承
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。