Solidity
Solidity 是一种静态类型的编程语言,专门用于编写智能合约,这些智能合约运行在以太坊区块链平台的以太坊虚拟机(EVM)之上。Solidity 受到了 JavaScript、C++、Python 和其他语言的影响,它设计用来创建和实现复杂的业务逻辑,定义所有者、状态变量、错误处理、复杂的成员属性和函数修饰符等。

查看更多相关内容
Solidity 中 storage、memory 和 calldata 三种数据位置的区别是什么?在 Solidity 中,`storage`、`memory` 和 `calldata` 是三种不同的数据存储位置,理解它们的区别对于编写高效的智能合约至关重要。
### 1. Storage(存储)
* **特点**:永久存储在区块链上,是合约状态变量的默认存储位置
* **成本**:最昂贵,需要消耗 Gas 来写入
* **生命周期**:永久存在,直到合约被销毁
* **适用场景**:需要持久化保存的数据,如用户余额、合约配置等
```solidity
contract Example {
uint256 public storedData; // 默认 storage
function updateData(uint256 _data) public {
storedData = _data; // 写入 storage
}
}
```
### 2. Memory(内存)
* **特点**:临时存储,函数执行完毕后自动释放
* **成本**:中等,比 storage 便宜
* **生命周期**:仅在函数执行期间存在
* **适用场景**:函数参数、局部变量、临时计算结果
```solidity
function processArray(uint256[] memory _arr) public pure returns (uint256) {
uint256 sum = 0;
for (uint i = 0; i < _arr.length; i++) {
sum += _arr[i];
}
return sum;
}
```
### 3. Calldata(调用数据)
* **特点**:只读区域,存储函数调用的输入数据
* **成本**:最便宜,不消耗额外 Gas
* **生命周期**:仅在函数执行期间存在
* **适用场景**:外部函数的引用类型参数(数组、结构体等)
```solidity
function externalFunction(uint256[] calldata _data) external pure returns (uint256) {
// _data 是只读的,不能修改
return _data.length;
}
```
### 对比总结
| 特性 | Storage | Memory | Calldata |
| ------ | ------- | ------ | -------- |
| 持久性 | 永久 | 临时 | 临时 |
| 可修改性 | 可读写 | 可读写 | 只读 |
| Gas 成本 | 高 | 中 | 低 |
| 适用场景 | 状态变量 | 局部变量 | 外部函数参数 |
### 最佳实践
1. **优先使用 calldata**:对于外部函数的引用类型参数,使用 calldata 可以节省 Gas
2. **避免不必要的 storage 操作**:storage 操作昂贵,尽量减少写入次数
3. **内存管理**:复杂计算时,合理使用 memory 避免频繁的 storage 访问
4. **数据拷贝注意**:从 storage 复制到 memory 或 calldata 会消耗 Gas,注意优化
服务端 · 3月7日 19:35
Solidity 中的 view、pure 和 payable 函数修饰符有什么区别?在 Solidity 中,`view`、`pure` 和 `payable` 是三种重要的函数修饰符,它们定义了函数的行为特性和限制条件。
### 1. View 修饰符
**定义**:声明函数不会修改合约的状态变量,但可以读取状态。
**特点**:
* 可以读取状态变量(storage)
* 不能修改状态变量
* 不能发送 ETH
* 不消耗 Gas(当被外部调用时)
```solidity
contract ViewExample {
uint256 public storedData = 100;
// view 函数可以读取状态
function getData() public view returns (uint256) {
return storedData; // 读取状态变量
}
// 错误:view 函数不能修改状态
function setData(uint256 _data) public view {
// storedData = _data; // 编译错误!
}
}
```
### 2. Pure 修饰符
**定义**:声明函数既不读取也不修改合约状态,仅依赖于输入参数。
**特点**:
* 不能读取状态变量
* 不能修改状态变量
* 不能访问 `msg.sender`、`msg.value` 等全局变量
* 不消耗 Gas(当被外部调用时)
```solidity
contract PureExample {
uint256 public constant VALUE = 100;
// pure 函数只依赖输入参数
function add(uint256 a, uint256 b) public pure returns (uint256) {
return a + b;
}
// pure 函数可以读取常量
function getConstant() public pure returns (uint256) {
return VALUE; // 常量不算状态读取
}
// 错误:pure 函数不能读取状态变量
function getData() public pure returns (uint256) {
// return storedData; // 编译错误!
}
}
```
### 3. Payable 修饰符
**定义**:允许函数接收 ETH(以太币)。
**特点**:
* 可以接收 ETH 转账
* 可以读取和修改状态(默认行为)
* 可以访问 `msg.value` 获取转账金额
* 消耗 Gas
```solidity
contract PayableExample {
mapping(address => uint256) public balances;
// payable 函数可以接收 ETH
function deposit() public payable {
require(msg.value > 0, "Must send ETH");
balances[msg.sender] += msg.value;
}
// 查询合约余额
function getBalance() public view returns (uint256) {
return address(this).balance;
}
// 提取 ETH
function withdraw(uint256 amount) public {
require(balances[msg.sender] >= amount, "Insufficient balance");
balances[msg.sender] -= amount;
payable(msg.sender).transfer(amount);
}
}
```
### 修饰符对比表
| 特性 | view | pure | payable |
| ------------ | ---- | ---- | ------- |
| 读取状态 | ✅ | ❌ | ✅ |
| 修改状态 | ❌ | ❌ | ✅ |
| 接收 ETH | ❌ | ❌ | ✅ |
| 访问 msg.value | ❌ | ❌ | ✅ |
| Gas 消耗(外部调用) | 无 | 无 | 有 |
| 适用场景 | 查询数据 | 纯计算 | 资金操作 |
### 组合使用
某些修饰符可以组合使用:
```solidity
contract CombinedExample {
// payable + view 不能组合,因为 view 不消耗 Gas,而 payable 需要处理转账
// 可以定义接收 ETH 的函数
receive() external payable {}
fallback() external payable {}
// 计算函数使用 pure
function calculateFee(uint256 amount) public pure returns (uint256) {
return amount * 5 / 100; // 5% 手续费
}
// 查询函数使用 view
function getContractBalance() public view returns (uint256) {
return address(this).balance;
}
}
```
### 实际应用示例
```solidity
contract Bank {
mapping(address => uint256) private balances;
uint256 public totalDeposits;
// payable:接收存款
function deposit() public payable {
balances[msg.sender] += msg.value;
totalDeposits += msg.value;
}
// view:查询余额
function getBalance(address user) public view returns (uint256) {
return balances[user];
}
// pure:计算利息
function calculateInterest(uint256 principal, uint256 rate)
public
pure
returns (uint256)
{
return principal * rate / 100;
}
// payable + 其他操作
function withdraw() public payable {
uint256 amount = balances[msg.sender];
require(amount > 0, "No balance");
balances[msg.sender] = 0;
totalDeposits -= amount;
payable(msg.sender).transfer(amount);
}
}
```
### 最佳实践
1. **优先使用 pure**:如果函数不需要读取状态,使用 pure 可以节省 Gas
2. **明确使用 view**:对于只读操作,明确标记 view 提高代码可读性
3. **谨慎使用 payable**:确保 payable 函数有适当的访问控制和金额验证
4. **避免滥用**:不要为了 Gas 优化而错误地使用这些修饰符,可能导致安全问题
服务端 · 3月7日 12:09
Solidity 中如何使用 Assembly 进行底层优化?有哪些注意事项?Assembly(汇编)是 Solidity 中的底层编程方式,允许开发者直接操作 EVM 字节码。虽然它提供了极高的灵活性和 Gas 优化空间,但也增加了代码复杂性和安全风险。
### 1. 为什么使用 Assembly
```solidity
contract AssemblyBenefits {
// 1. Gas 优化:跳过 Solidity 的高级抽象
// 2. 访问底层 EVM 特性
// 3. 实现 Solidity 不支持的操作
// 4. 精细控制内存和存储
}
```
### 2. Assembly 基础语法
#### 内联汇编(Inline Assembly)
```solidity
contract BasicAssembly {
// 使用 assembly 关键字
function add(uint256 a, uint256 b) external pure returns (uint256 result) {
assembly {
// 直接使用 EVM 操作码
result := add(a, b) // 加法操作
}
}
// 多个操作
function calculate(uint256 x) external pure returns (uint256) {
assembly {
let y := add(x, 10) // 局部变量
let z := mul(y, 2) // 乘法
mstore(0x00, z) // 存储到内存
return(0x00, 32) // 返回内存中的值
}
}
}
```
#### 数据类型和操作
```solidity
contract AssemblyDataTypes {
// 基本类型操作
function dataTypes() external pure {
assembly {
// 整数
let a := 100
let b := 0xFF // 十六进制
// 布尔(0 或 1)
let flag := 1
// 地址
let addr := 0x1234567890123456789012345678901234567890
// 算术运算
let sum := add(a, b)
let diff := sub(a, b)
let prod := mul(a, b)
let quot := div(a, b)
let rem := mod(a, b)
// 位运算
let andResult := and(a, b)
let orResult := or(a, b)
let xorResult := xor(a, b)
let notResult := not(a)
let shifted := shl(2, a) // 左移
let shiftedR := shr(2, a) // 右移
}
}
}
```
### 3. 内存操作
#### 内存布局
```solidity
contract MemoryOperations {
/*
内存布局:
0x00 - 0x3f (64 bytes): 哈希函数的临时空间
0x40 - 0x5f (32 bytes): 当前分配的内存大小(空闲内存指针)
0x60 - ...: 实际数据存储
*/
function memoryBasics() external pure returns (uint256) {
assembly {
// 读取空闲内存指针
let freePtr := mload(0x40)
// 在空闲内存位置存储数据
mstore(freePtr, 12345)
// 更新空闲内存指针(32 字节 = 0x20)
mstore(0x40, add(freePtr, 0x20))
// 读取内存
let value := mload(freePtr)
mstore(0x00, value)
return(0x00, 32)
}
}
// 存储多个值
function storeMultiple() external pure returns (uint256, uint256) {
assembly {
let freePtr := mload(0x40)
// 存储多个 32 字节值
mstore(freePtr, 100)
mstore(add(freePtr, 0x20), 200)
mstore(add(freePtr, 0x40), 300)
// 更新空闲内存指针
mstore(0x40, add(freePtr, 0x60))
// 返回前两个值
mstore(0x00, mload(freePtr))
mstore(0x20, mload(add(freePtr, 0x20)))
return(0x00, 64)
}
}
// 字节操作
function byteOperations() external pure returns (bytes32) {
assembly {
let value := 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef
// 提取单个字节(从右往左,0-31)
let byte0 := byte(31, value) // 最后一个字节
let byte1 := byte(30, value) // 倒数第二个字节
mstore(0x00, byte0)
return(0x00, 32)
}
}
}
```
### 4. 存储操作
#### 存储布局
```solidity
contract StorageOperations {
uint256 public value1; // slot 0
uint256 public value2; // slot 1
mapping(address => uint256) public balances; // slot 2
address public owner; // slot 3
function storageRead() external view returns (uint256) {
assembly {
// 读取 slot 0 的值
let v := sload(0)
mstore(0x00, v)
return(0x00, 32)
}
}
function storageWrite(uint256 _value) external {
assembly {
// 写入 slot 0
sstore(0, _value)
}
}
// 读取 mapping
function readMapping(address _key) external view returns (uint256) {
assembly {
// mapping 的存储位置计算:keccak256(key . slot)
mstore(0x00, _key)
mstore(0x20, 2) // balances 在 slot 2
let slot := keccak256(0x00, 0x40)
let value := sload(slot)
mstore(0x00, value)
return(0x00, 32)
}
}
// 写入 mapping
function writeMapping(address _key, uint256 _value) external {
assembly {
mstore(0x00, _key)
mstore(0x20, 2)
let slot := keccak256(0x00, 0x40)
sstore(slot, _value)
}
}
// 读取动态数组
uint256[] public dynamicArray; // slot 4
function readArrayLength() external view returns (uint256) {
assembly {
// 动态数组的长度存储在声明的 slot
let length := sload(4)
mstore(0x00, length)
return(0x00, 32)
}
}
function readArrayElement(uint256 _index) external view returns (uint256) {
assembly {
// 数组元素的存储位置:keccak256(slot) + index
mstore(0x00, 4)
let baseSlot := keccak256(0x00, 0x20)
let elementSlot := add(baseSlot, _index)
let value := sload(elementSlot)
mstore(0x00, value)
return(0x00, 32)
}
}
}
```
### 5. 函数调用和返回值
#### 外部调用
```solidity
contract ExternalCalls {
function callExternal(address _target, bytes memory _data)
external
returns (bool success, bytes memory result)
{
assembly {
// 获取数据位置和大小
let dataPtr := add(_data, 0x20) // 跳过长度字段
let dataSize := mload(_data)
// 分配内存用于返回数据
let resultPtr := mload(0x40)
// 执行调用
success := call(
gas(), // 剩余 Gas
_target, // 目标地址
0, // 发送的 ETH
dataPtr, // 输入数据位置
dataSize, // 输入数据大小
resultPtr, // 返回数据位置
0x40 // 返回数据大小上限
)
// 获取返回数据大小
let resultSize := returndatasize()
// 复制返回数据
returndatacopy(resultPtr, 0, resultSize)
// 更新空闲内存指针
mstore(0x40, add(resultPtr, resultSize))
// 设置 result 指针和长度
mstore(result, resultSize)
mstore(add(result, 0x20), resultPtr)
}
}
// 发送 ETH
function sendETH(address _to, uint256 _amount) external returns (bool) {
assembly {
// 使用 call 发送 ETH
let success := call(
gas(),
_to,
_amount, // 发送的 ETH 数量
0, // 无输入数据
0,
0, // 不关心返回数据
0
)
mstore(0x00, success)
return(0x00, 32)
}
}
// 静态调用(不修改状态)
function staticCall(address _target, bytes memory _data)
external
view
returns (bool success, bytes memory result)
{
assembly {
let dataPtr := add(_data, 0x20)
let dataSize := mload(_data)
let resultPtr := mload(0x40)
success := staticcall(
gas(),
_target,
dataPtr,
dataSize,
resultPtr,
0x40
)
let resultSize := returndatasize()
returndatacopy(resultPtr, 0, resultSize)
mstore(0x40, add(resultPtr, resultSize))
mstore(result, resultSize)
mstore(add(result, 0x20), resultPtr)
}
}
}
```
#### 委托调用
```solidity
contract DelegateCallExample {
address public implementation;
uint256 public value;
function delegateToImplementation(bytes memory _data) external returns (bytes memory) {
assembly {
let dataPtr := add(_data, 0x20)
let dataSize := mload(_data)
let resultPtr := mload(0x40)
// 使用 delegatecall
let success := delegatecall(
gas(),
sload(0), // implementation 地址在 slot 0
dataPtr,
dataSize,
resultPtr,
0x40
)
let resultSize := returndatasize()
returndatacopy(resultPtr, 0, resultSize)
// 检查调用是否成功
if iszero(success) {
revert(resultPtr, resultSize)
}
return(resultPtr, resultSize)
}
}
}
```
### 6. Gas 优化技巧
#### 常见优化模式
```solidity
contract GasOptimizations {
uint256[] public items;
// 优化前:使用 Solidity 高级语法
function sumSolidity() external view returns (uint256 total) {
for (uint i = 0; i < items.length; i++) {
total += items[i];
}
}
// 优化后:使用 Assembly
function sumAssembly() external view returns (uint256 total) {
assembly {
// 获取数组存储位置
mstore(0x00, items.slot)
let baseSlot := keccak256(0x00, 0x20)
// 获取数组长度
let length := sload(items.slot)
// 遍历数组
for { let i := 0 } lt(i, length) { i := add(i, 1) } {
let slot := add(baseSlot, i)
let value := sload(slot)
total := add(total, value)
}
mstore(0x00, total)
return(0x00, 32)
}
}
// 批量操作优化
function batchTransfer(address[] memory _recipients, uint256[] memory _amounts)
external
payable
{
require(_recipients.length == _amounts.length, "Length mismatch");
assembly {
let recipientsPtr := add(_recipients, 0x20)
let amountsPtr := add(_amounts, 0x20)
let length := mload(_recipients)
for { let i := 0 } lt(i, length) { i := add(i, 1) } {
let recipient := mload(add(recipientsPtr, mul(i, 0x20)))
let amount := mload(add(amountsPtr, mul(i, 0x20)))
// 使用 call 发送 ETH
let success := call(
gas(),
recipient,
amount,
0,
0,
0,
0
)
if iszero(success) {
revert(0, 0)
}
}
}
}
// 短路求和优化
function optimizedSum(uint256[] memory _values) external pure returns (uint256) {
assembly {
let ptr := add(_values, 0x20)
let length := mload(_values)
let total := 0
// 使用 unchecked 的加法(Solidity 0.8.0+)
for { let i := 0 } lt(i, length) { i := add(i, 1) } {
total := add(total, mload(add(ptr, mul(i, 0x20))))
}
mstore(0x00, total)
return(0x00, 32)
}
}
}
```
### 7. 高级技巧
#### 创建合约
```solidity
contract ContractCreation {
function deploy(bytes memory _bytecode) external returns (address addr) {
assembly {
let size := mload(_bytecode)
let ptr := add(_bytecode, 0x20)
// 使用 create 部署合约
addr := create(0, ptr, size)
// 检查部署是否成功
if iszero(extcodesize(addr)) {
revert(0, 0)
}
}
}
// 使用 create2 部署(确定性地址)
function deployWithSalt(bytes memory _bytecode, bytes32 _salt)
external
returns (address addr)
{
assembly {
let size := mload(_bytecode)
let ptr := add(_bytecode, 0x20)
addr := create2(0, ptr, size, _salt)
if iszero(extcodesize(addr)) {
revert(0, 0)
}
}
}
// 计算 create2 地址
function computeAddress(
bytes32 _salt,
bytes32 _bytecodeHash,
address _deployer
) external pure returns (address) {
assembly {
// 地址 = 最后 20 字节 of keccak256(0xff + deployer + salt + bytecodeHash)
mstore(0x00, _deployer)
mstore(0x20, _salt)
mstore(0x40, _bytecodeHash)
let hash := keccak256(0x00, 0x60)
mstore(0x00, hash)
return(12, 20) // 返回最后 20 字节
}
}
}
```
#### 内联汇编与 Solidity 交互
```solidity
contract AssemblyInteraction {
struct User {
uint256 id;
address wallet;
uint256 balance;
}
mapping(uint256 => User) public users;
// 使用 Assembly 读取结构体
function getUserAssembly(uint256 _id) external view returns (User memory user) {
assembly {
// 计算 mapping 的存储位置
mstore(0x00, _id)
mstore(0x20, users.slot)
let baseSlot := keccak256(0x00, 0x40)
// 读取结构体的各个字段
// User 结构体占用 3 个 slot
let id := sload(baseSlot)
let wallet := sload(add(baseSlot, 1))
let balance := sload(add(baseSlot, 2))
// 存储到内存返回
let ptr := mload(0x40)
mstore(ptr, id)
mstore(add(ptr, 0x20), wallet)
mstore(add(ptr, 0x40), balance)
// 更新空闲内存指针
mstore(0x40, add(ptr, 0x60))
// 设置返回值
mstore(user, 0x60) // 指向内存位置
mstore(add(user, 0x20), ptr)
}
}
// 条件判断优化
function optimizedCondition(uint256 x) external pure returns (uint256) {
assembly {
// 使用 switch 进行多条件判断
switch x
case 0 {
mstore(0x00, 100)
}
case 1 {
mstore(0x00, 200)
}
default {
mstore(0x00, 300)
}
return(0x00, 32)
}
}
}
```
### 8. 安全注意事项
```solidity
contract AssemblySafety {
/*
安全注意事项:
1. 内存管理:确保正确更新空闲内存指针
2. 存储冲突:避免覆盖重要存储槽位
3. 溢出检查:Assembly 不自动检查溢出
4. 调用验证:检查外部调用返回值
5. 重入防护:手动实现重入锁
*/
bool private locked;
modifier noReentrant() {
require(!locked, "Reentrant call");
locked = true;
_;
locked = false;
}
// 安全的 ETH 转账
function safeTransferETH(address _to, uint256 _amount) external noReentrant {
assembly {
// 检查地址是否有效
if iszero(_to) {
revert(0, 0)
}
// 执行转账
let success := call(
gas(),
_to,
_amount,
0,
0,
0,
0
)
// 验证结果
if iszero(success) {
revert(0, 0)
}
}
}
// 防止整数溢出
function safeAdd(uint256 a, uint256 b) external pure returns (uint256) {
assembly {
let result := add(a, b)
// 检查溢出:如果 result < a,说明溢出
if lt(result, a) {
revert(0, 0)
}
mstore(0x00, result)
return(0x00, 32)
}
}
// 安全的存储写入
function safeStorageWrite(uint256 slot, uint256 value) external {
assembly {
// 检查 slot 是否在安全范围内
if lt(slot, 100) {
revert(0, 0)
}
sstore(slot, value)
}
}
}
```
### 9. 常见用例
```solidity
contract AssemblyUseCases {
// 1. 高效计算哈希
function efficientHash(bytes memory _data) external pure returns (bytes32) {
assembly {
let ptr := add(_data, 0x20)
let size := mload(_data)
let hash := keccak256(ptr, size)
mstore(0x00, hash)
return(0x00, 32)
}
}
// 2. 检查合约代码
function hasCode(address _addr) external view returns (bool) {
assembly {
let size := extcodesize(_addr)
mstore(0x00, gt(size, 0))
return(0x00, 32)
}
}
// 3. 获取当前合约地址
function currentAddress() external view returns (address) {
assembly {
mstore(0x00, address())
return(0x00, 32)
}
}
// 4. 获取调用者
function getCaller() external view returns (address) {
assembly {
mstore(0x00, caller())
return(0x00, 32)
}
}
// 5. 获取调用数据
function getCallData() external pure returns (bytes32) {
assembly {
// 获取函数选择器(前 4 字节)
let selector := calldataload(0)
mstore(0x00, selector)
return(0x00, 32)
}
}
// 6. 高效数组操作
function findAndRemove(uint256[] storage _arr, uint256 _value) external {
assembly {
// 获取数组长度
let length := sload(_arr.slot)
// 查找元素
mstore(0x00, _arr.slot)
let baseSlot := keccak256(0x00, 0x20)
for { let i := 0 } lt(i, length) { i := add(i, 1) } {
let slot := add(baseSlot, i)
if eq(sload(slot), _value) {
// 找到元素,用最后一个元素替换
let lastSlot := add(baseSlot, sub(length, 1))
sstore(slot, sload(lastSlot))
// 减少长度
sstore(_arr.slot, sub(length, 1))
// 停止循环
stop()
}
}
}
}
}
```
### 10. 总结
Assembly 是强大的底层工具,但需要谨慎使用:
1. **使用场景**:
* Gas 敏感的关键路径
* 实现 Solidity 不支持的功能
* 精细控制内存和存储
2. **最佳实践**:
* 仅在必要时使用 Assembly
* 充分测试和审计
* 添加详细注释
* 考虑使用 Yul(更安全的汇编方言)
3. **安全要点**:
* 正确管理内存指针
* 验证所有外部调用
* 手动检查溢出
* 避免覆盖关键存储
4. **性能权衡**:
* Assembly 可以显著节省 Gas
* 但会增加代码复杂性和维护成本
* 需要权衡可读性和性能
服务端 · 3月6日 23:07
Solidity 中如何处理随机数生成?有哪些安全方案?在 Solidity 中生成随机数是一个复杂的问题,因为区块链是确定性的环境,无法直接生成真正随机的数。以下是各种随机数生成方案及其安全性分析。
### 1. 不安全的随机数生成方式
#### 使用区块哈希(不安全)
```solidity
contract InsecureRandom {
// 危险:矿工可以操纵区块哈希
function getRandomNumber() public view returns (uint256) {
return uint256(keccak256(abi.encodePacked(blockhash(block.number - 1))));
}
// 危险:矿工可以操纵时间戳
function getRandomFromTimestamp() public view returns (uint256) {
return uint256(keccak256(abi.encodePacked(block.timestamp)));
}
// 危险:所有参数都可被矿工操纵
function getRandomInsecure() public view returns (uint256) {
return uint256(keccak256(abi.encodePacked(
block.timestamp,
block.difficulty,
msg.sender
)));
}
}
```
**为什么这些方式不安全?**
* 矿工可以操纵 `blockhash`、`block.timestamp`、`block.difficulty`
* 攻击者可以预测结果并选择性提交交易
* 所有链上数据都是公开可见的
### 2. Commit-Reveal 方案
这是一种两阶段提交方案,可以防止前置交易攻击。
```solidity
contract CommitReveal {
struct Commit {
bytes32 commitHash;
uint256 revealDeadline;
bool revealed;
}
mapping(address => Commit) public commits;
// 第一阶段:提交哈希
function commit(bytes32 _commitHash) external {
require(commits[msg.sender].commitHash == bytes32(0), "Already committed");
commits[msg.sender] = Commit({
commitHash: _commitHash,
revealDeadline: block.number + 10,
revealed: false
});
}
// 第二阶段:揭示原始值
function reveal(uint256 _secret, uint256 _guess) external {
Commit storage userCommit = commits[msg.sender];
require(userCommit.commitHash != bytes32(0), "No commit found");
require(!userCommit.revealed, "Already revealed");
require(block.number <= userCommit.revealDeadline, "Reveal period ended");
// 验证哈希
bytes32 revealHash = keccak256(abi.encodePacked(_secret, _guess));
require(revealHash == userCommit.commitHash, "Invalid reveal");
userCommit.revealed = true;
// 生成随机数
uint256 randomNumber = uint256(keccak256(abi.encodePacked(
_secret,
blockhash(userCommit.revealDeadline)
)));
// 使用随机数...
}
// 计算提交哈希的辅助函数
function getCommitHash(uint256 _secret, uint256 _guess) external pure returns (bytes32) {
return keccak256(abi.encodePacked(_secret, _guess));
}
}
```
### 3. Chainlink VRF(可验证随机函数)
Chainlink VRF 是目前最安全的链上随机数解决方案。
```solidity
import "@chainlink/contracts/src/v0.8/VRFConsumerBaseV2.sol";
import "@chainlink/contracts/src/v0.8/interfaces/VRFCoordinatorV2Interface.sol";
contract RandomNumberConsumer is VRFConsumerBaseV2 {
VRFCoordinatorV2Interface COORDINATOR;
// 订阅 ID
uint64 s_subscriptionId;
// 使用的 Gas 通道
bytes32 keyHash;
// 回调 Gas 限制
uint32 callbackGasLimit = 100000;
// 请求的确认数
uint16 requestConfirmations = 3;
// 请求的随机数数量
uint32 numWords = 1;
// 存储请求 ID 和对应的游戏数据
mapping(uint256 => address) public requestToGame;
mapping(uint256 => uint256[]) public requestIdToRandomWords;
event RandomWordsRequested(uint256 requestId, address game);
event RandomWordsFulfilled(uint256 requestId, uint256[] randomWords);
constructor(
uint64 subscriptionId,
address vrfCoordinator,
bytes32 _keyHash
) VRFConsumerBaseV2(vrfCoordinator) {
COORDINATOR = VRFCoordinatorV2Interface(vrfCoordinator);
s_subscriptionId = subscriptionId;
keyHash = _keyHash;
}
// 请求随机数
function requestRandomWords() external returns (uint256 requestId) {
requestId = COORDINATOR.requestRandomWords(
keyHash,
s_subscriptionId,
requestConfirmations,
callbackGasLimit,
numWords
);
requestToGame[requestId] = msg.sender;
emit RandomWordsRequested(requestId, msg.sender);
}
// Chainlink 回调函数
function fulfillRandomWords(uint256 requestId, uint256[] memory randomWords)
internal
override
{
requestIdToRandomWords[requestId] = randomWords;
emit RandomWordsFulfilled(requestId, randomWords);
// 使用随机数处理游戏逻辑
address game = requestToGame[requestId];
// ...
}
}
```
### 4. 使用预言机(Oracle)
通过预言机获取链外随机数。
```solidity
interface IOracle {
function requestRandomNumber() external returns (uint256 requestId);
function getRandomNumber(uint256 requestId) external view returns (uint256);
}
contract OracleRandom {
IOracle public oracle;
mapping(uint256 => bool) public usedRandomNumbers;
event RandomNumberRequested(uint256 requestId);
event RandomNumberReceived(uint256 randomNumber);
constructor(address _oracle) {
oracle = IOracle(_oracle);
}
function requestRandom() external returns (uint256 requestId) {
requestId = oracle.requestRandomNumber();
emit RandomNumberRequested(requestId);
return requestId;
}
function useRandom(uint256 requestId) external returns (uint256) {
uint256 randomNumber = oracle.getRandomNumber(requestId);
require(!usedRandomNumbers[randomNumber], "Random number already used");
usedRandomNumbers[randomNumber] = true;
emit RandomNumberReceived(randomNumber);
return randomNumber;
}
}
```
### 5. 多方计算(MPC)方案
通过多个参与方共同生成随机数。
```solidity
contract MPCRandom {
struct Contribution {
bytes32 hash;
uint256 value;
bool revealed;
}
mapping(address => Contribution) public contributions;
address[] public participants;
uint256 public revealDeadline;
bool public randomGenerated;
uint256 public randomNumber;
event ContributionSubmitted(address participant);
event RandomNumberGenerated(uint256 randomNumber);
modifier onlyParticipant() {
require(isParticipant(msg.sender), "Not a participant");
_;
}
function isParticipant(address _addr) public view returns (bool) {
for (uint i = 0; i < participants.length; i++) {
if (participants[i] == _addr) return true;
}
return false;
}
// 第一阶段:提交贡献的哈希
function submitHash(bytes32 _hash) external onlyParticipant {
require(block.number < revealDeadline, "Submission period ended");
require(contributions[msg.sender].hash == bytes32(0), "Already submitted");
contributions[msg.sender].hash = _hash;
emit ContributionSubmitted(msg.sender);
}
// 第二阶段:揭示贡献值
function reveal(uint256 _value, uint256 _nonce) external onlyParticipant {
require(block.number >= revealDeadline, "Reveal not started");
require(!contributions[msg.sender].revealed, "Already revealed");
bytes32 expectedHash = keccak256(abi.encodePacked(_value, _nonce));
require(expectedHash == contributions[msg.sender].hash, "Invalid reveal");
contributions[msg.sender].value = _value;
contributions[msg.sender].revealed = true;
}
// 生成随机数
function generateRandom() external {
require(block.number > revealDeadline + 10, "Wait for all reveals");
require(!randomGenerated, "Random already generated");
uint256 combined = 0;
for (uint i = 0; i < participants.length; i++) {
if (contributions[participants[i]].revealed) {
combined ^= contributions[participants[i]].value;
}
}
randomNumber = uint256(keccak256(abi.encodePacked(combined, blockhash(block.number - 1))));
randomGenerated = true;
emit RandomNumberGenerated(randomNumber);
}
}
```
### 6. 随机数使用场景
#### NFT 随机铸造
```solidity
contract NFTRandomMint is VRFConsumerBaseV2 {
// ... VRF 设置 ...
struct MintRequest {
address minter;
uint256 tokenId;
bool fulfilled;
}
mapping(uint256 => MintRequest) public mintRequests;
uint256 public nextTokenId;
function requestMint() external returns (uint256 requestId) {
requestId = requestRandomWords();
mintRequests[requestId] = MintRequest({
minter: msg.sender,
tokenId: nextTokenId++,
fulfilled: false
});
}
function fulfillRandomWords(uint256 requestId, uint256[] memory randomWords)
internal
override
{
MintRequest storage request = mintRequests[requestId];
request.fulfilled = true;
// 使用随机数确定 NFT 属性
uint256 randomness = randomWords[0];
uint256 rarity = randomness % 100; // 0-99
// 铸造 NFT
_safeMint(request.minter, request.tokenId);
// 设置属性
if (rarity < 5) {
_setTokenRarity(request.tokenId, "Legendary");
} else if (rarity < 20) {
_setTokenRarity(request.tokenId, "Epic");
} else if (rarity < 50) {
_setTokenRarity(request.tokenId, "Rare");
} else {
_setTokenRarity(request.tokenId, "Common");
}
}
}
```
#### 游戏随机结果
```solidity
contract DiceGame is VRFConsumerBaseV2 {
struct Game {
address player;
uint256 bet;
uint256 predictedNumber;
bool fulfilled;
uint256 result;
}
mapping(uint256 => Game) public games;
event GameStarted(uint256 requestId, address player, uint256 bet);
event GameFinished(uint256 requestId, uint256 result, bool won);
function play(uint256 _predictedNumber) external payable returns (uint256 requestId) {
require(msg.value >= 0.01 ether, "Minimum bet is 0.01 ETH");
require(_predictedNumber >= 1 && _predictedNumber <= 6, "Predict 1-6");
requestId = requestRandomWords();
games[requestId] = Game({
player: msg.sender,
bet: msg.value,
predictedNumber: _predictedNumber,
fulfilled: false,
result: 0
});
emit GameStarted(requestId, msg.sender, msg.value);
}
function fulfillRandomWords(uint256 requestId, uint256[] memory randomWords)
internal
override
{
Game storage game = games[requestId];
game.fulfilled = true;
// 生成 1-6 的骰子结果
game.result = (randomWords[0] % 6) + 1;
bool won = (game.result == game.predictedNumber);
if (won) {
// 支付奖金(6倍赔率)
payable(game.player).transfer(game.bet * 6);
}
emit GameFinished(requestId, game.result, won);
}
}
```
### 7. 方案对比
| 方案 | 安全性 | 成本 | 延迟 | 适用场景 |
| ------------- | --- | -- | -- | ----- |
| 区块哈希 | 低 | 免费 | 即时 | 测试环境 |
| Commit-Reveal | 中 | 低 | 高 | 简单游戏 |
| Chainlink VRF | 高 | 中等 | 中等 | 生产环境 |
| 预言机 | 中 | 中等 | 中等 | 特定需求 |
| MPC | 高 | 高 | 高 | 高价值场景 |
### 8. 最佳实践
1. **生产环境使用 Chainlink VRF**:最安全、最可靠的解决方案
2. **避免使用链上数据生成随机数**:所有链上数据都可被操纵
3. **实现重试机制**:处理 VRF 回调失败的情况
4. **限制随机数使用范围**:避免重复使用同一随机数
5. **添加时间锁**:给用户提供退出时间
```solidity
// 安全的随机数使用模式
contract SafeRandomUsage {
uint256 public constant MAX_RANDOM = 10000;
mapping(uint256 => bool) public usedRandoms;
function useRandom(uint256 _random) internal returns (uint256) {
uint256 normalized = _random % MAX_RANDOM;
require(!usedRandoms[normalized], "Random already used");
usedRandoms[normalized] = true;
return normalized;
}
}
```
服务端 · 3月6日 23:05
Solidity 中 delegatecall 和 call 的区别是什么?代理合约如何实现?`delegatecall` 和 `call` 是 Solidity 中用于合约间调用的两个重要底层函数,理解它们的区别对于实现代理合约和可升级合约至关重要。
### 1. call 的基本用法
`call` 是最常用的低级调用函数,在目标合约的上下文中执行代码。
```solidity
contract CallExample {
// 使用 call 发送 ETH
function sendEther(address payable recipient) public payable {
(bool success, ) = recipient.call{value: msg.value}("");
require(success, "Transfer failed");
}
// 使用 call 调用函数
function callFunction(address target, uint256 value) public returns (bool) {
// 编码函数调用:function setValue(uint256)
bytes memory data = abi.encodeWithSignature("setValue(uint256)", value);
(bool success, bytes memory returnData) = target.call(data);
require(success, "Call failed");
return success;
}
// 使用 call 调用并获取返回值
function callAndGetResult(address target) public view returns (uint256) {
bytes memory data = abi.encodeWithSignature("getValue()");
(bool success, bytes memory returnData) = target.staticcall(data);
require(success, "Call failed");
return abi.decode(returnData, (uint256));
}
}
```
### 2. delegatecall 的基本用法
`delegatecall` 在调用者的上下文中执行目标合约的代码,保持 `msg.sender` 和 `msg.value` 不变。
```solidity
contract DelegatecallExample {
uint256 public value; // 这个变量会被目标合约代码修改
// 使用 delegatecall 执行逻辑合约代码
function executeLogic(address logicContract, uint256 newValue) public {
bytes memory data = abi.encodeWithSignature("setValue(uint256)", newValue);
(bool success, ) = logicContract.delegatecall(data);
require(success, "Delegatecall failed");
}
}
// 逻辑合约
contract LogicContract {
uint256 public value; // 注意:这个变量布局必须与代理合约一致
function setValue(uint256 newValue) public {
value = newValue; // 修改的是调用者(代理合约)的 storage
}
function getValue() public view returns (uint256) {
return value;
}
}
```
### 3. call 与 delegatecall 对比
| 特性 | call | delegatecall |
| ---------- | ------------- | -------------- |
| 执行上下文 | 目标合约 | 调用者合约 |
| msg.sender | 当前合约地址 | 原始调用者地址 |
| msg.value | 传递的值 | 原始调用值 |
| storage 访问 | 目标合约的 storage | 调用者合约的 storage |
| 代码执行位置 | 目标合约地址 | 目标合约地址 |
| 使用场景 | 普通调用、转账 | 代理合约、库合约 |
### 4. 代理合约实现
#### 基础代理合约( EIP-1967 )
```solidity
contract Proxy {
// EIP-1967 标准存储槽
// bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1)
bytes32 private constant IMPLEMENTATION_SLOT =
0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
// bytes32(uint256(keccak256('eip1967.proxy.admin')) - 1)
bytes32 private constant ADMIN_SLOT =
0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
constructor(address _implementation) {
_setImplementation(_implementation);
_setAdmin(msg.sender);
}
modifier onlyAdmin() {
require(msg.sender == _getAdmin(), "Not admin");
_;
}
// 获取实现合约地址
function _getImplementation() internal view returns (address impl) {
assembly {
impl := sload(IMPLEMENTATION_SLOT)
}
}
// 设置实现合约地址
function _setImplementation(address _implementation) internal {
assembly {
sstore(IMPLEMENTATION_SLOT, _implementation)
}
}
// 获取管理员地址
function _getAdmin() internal view returns (address adm) {
assembly {
adm := sload(ADMIN_SLOT)
}
}
// 设置管理员地址
function _setAdmin(address _admin) internal {
assembly {
sstore(ADMIN_SLOT, _admin)
}
}
// 升级实现合约
function upgradeTo(address _newImplementation) public onlyAdmin {
_setImplementation(_newImplementation);
}
// 获取当前实现地址(便于查询)
function implementation() public view returns (address) {
return _getImplementation();
}
// 回退函数:将所有调用委托给实现合约
fallback() external payable {
_delegate(_getImplementation());
}
receive() external payable {
_delegate(_getImplementation());
}
// 核心委托逻辑
function _delegate(address _implementation) internal {
assembly {
// 复制 msg.data
calldatacopy(0, 0, calldatasize())
// 执行 delegatecall
let result := delegatecall(
gas(),
_implementation,
0,
calldatasize(),
0,
0
)
// 复制返回数据
returndatacopy(0, 0, returndatasize())
// 根据结果返回或回滚
switch result
case 0 {
revert(0, returndatasize())
}
default {
return(0, returndatasize())
}
}
}
}
```
#### 逻辑合约示例
```solidity
// 逻辑合约 V1
contract LogicV1 {
uint256 public value;
address public implementation; // 与代理合约存储布局一致
address public admin;
function setValue(uint256 _value) public {
value = _value;
}
function getValue() public view returns (uint256) {
return value;
}
}
// 逻辑合约 V2(升级版本)
contract LogicV2 {
uint256 public value;
address public implementation;
address public admin;
// 新增功能
function setValue(uint256 _value) public {
value = _value * 2; // 新逻辑:值翻倍
}
function getValue() public view returns (uint256) {
return value;
}
// 新增函数
function increment() public {
value++;
}
}
```
### 5. 透明代理模式
透明代理解决了代理合约和实现合约函数选择器冲突的问题。
```solidity
contract TransparentProxy {
bytes32 private constant IMPLEMENTATION_SLOT =
0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
bytes32 private constant ADMIN_SLOT =
0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
constructor(address _implementation, address _admin) {
_setImplementation(_implementation);
_setAdmin(_admin);
}
modifier ifAdmin() {
if (msg.sender == _getAdmin()) {
_;
} else {
_fallback();
}
}
// 管理员函数(只有管理员可以调用)
function upgradeTo(address _newImplementation) external ifAdmin {
_setImplementation(_newImplementation);
}
function admin() external ifAdmin returns (address) {
return _getAdmin();
}
function implementation() external ifAdmin returns (address) {
return _getImplementation();
}
// 修改管理员
function changeAdmin(address _newAdmin) external ifAdmin {
_setAdmin(_newAdmin);
}
// 非管理员调用会执行到这里
function _fallback() internal {
_delegate(_getImplementation());
}
fallback() external payable {
_fallback();
}
receive() external payable {
_fallback();
}
// 存储操作函数
function _getImplementation() internal view returns (address impl) {
assembly { impl := sload(IMPLEMENTATION_SLOT) }
}
function _setImplementation(address _implementation) internal {
assembly { sstore(IMPLEMENTATION_SLOT, _implementation) }
}
function _getAdmin() internal view returns (address adm) {
assembly { adm := sload(ADMIN_SLOT) }
}
function _setAdmin(address _admin) internal {
assembly { sstore(ADMIN_SLOT, _admin) }
}
function _delegate(address _implementation) internal {
assembly {
calldatacopy(0, 0, calldatasize())
let result := delegatecall(gas(), _implementation, 0, calldatasize(), 0, 0)
returndatacopy(0, 0, returndatasize())
switch result
case 0 { revert(0, returndatasize()) }
default { return(0, returndatasize()) }
}
}
}
```
### 6. UUPS 代理模式
UUPS(Universal Upgradeable Proxy Standard)将升级逻辑放在实现合约中。
```solidity
// UUPS 代理
contract UUPSProxy {
bytes32 private constant IMPLEMENTATION_SLOT =
0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
constructor(address _implementation) {
_setImplementation(_implementation);
}
function _getImplementation() internal view returns (address impl) {
assembly { impl := sload(IMPLEMENTATION_SLOT) }
}
function _setImplementation(address _implementation) internal {
assembly { sstore(IMPLEMENTATION_SLOT, _implementation) }
}
fallback() external payable {
_delegate(_getImplementation());
}
receive() external payable {
_delegate(_getImplementation());
}
function _delegate(address _implementation) internal {
assembly {
calldatacopy(0, 0, calldatasize())
let result := delegatecall(gas(), _implementation, 0, calldatasize(), 0, 0)
returndatacopy(0, 0, returndatasize())
switch result
case 0 { revert(0, returndatasize()) }
default { return(0, returndatasize()) }
}
}
}
// UUPS 实现合约接口
interface IUUPS {
function upgradeTo(address newImplementation) external;
function proxiableUUID() external view returns (bytes32);
}
// UUPS 实现合约
contract UUPSImplementation is IUUPS {
bytes32 private constant IMPLEMENTATION_SLOT =
0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
uint256 public value;
address public implementation;
address public owner;
modifier onlyOwner() {
require(msg.sender == owner, "Not owner");
_;
}
constructor() {
owner = msg.sender;
}
function upgradeTo(address newImplementation) external onlyOwner {
_authorizeUpgrade(newImplementation);
_upgradeTo(newImplementation);
}
function proxiableUUID() external pure returns (bytes32) {
return IMPLEMENTATION_SLOT;
}
function _authorizeUpgrade(address newImplementation) internal virtual {
// 可以在这里添加额外的升级验证逻辑
}
function _upgradeTo(address newImplementation) internal {
assembly {
sstore(IMPLEMENTATION_SLOT, newImplementation)
}
}
function setValue(uint256 _value) public {
value = _value;
}
}
```
### 7. 存储布局注意事项
```solidity
// 错误的存储布局示例
contract WrongLayoutV1 {
uint256 public value; // slot 0
address public owner; // slot 1
}
contract WrongLayoutV2 {
address public owner; // slot 0 - 错误!应该是 slot 1
uint256 public value; // slot 1 - 错误!应该是 slot 0
uint256 public newValue; // slot 2
}
// 正确的存储布局
contract CorrectLayoutV1 {
uint256 public value; // slot 0
address public owner; // slot 1
}
contract CorrectLayoutV2 {
uint256 public value; // slot 0 - 保持相同
address public owner; // slot 1 - 保持相同
uint256 public newValue; // slot 2 - 新增变量放在最后
}
```
### 8. 最佳实践
1. **使用标准存储槽**:遵循 EIP-1967 标准避免存储冲突
2. **保持存储布局一致**:升级时不能改变已有变量的顺序
3. **使用 OpenZeppelin**:使用经过审计的标准实现
4. **测试升级流程**:在测试网上充分测试升级过程
5. **考虑钻石模式**:对于超大型合约,考虑使用 EIP-2535 钻石标准
```solidity
// 使用 OpenZeppelin 的代理
import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
// 推荐:使用 UUPS 模式
import "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract MyContract is UUPSUpgradeable, Ownable {
function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}
}
```
服务端 · 3月6日 21:55
Solidity 中签名验证(ECDSA)的原理和实现方式是什么?ECDSA(椭圆曲线数字签名算法)是以太坊中用于验证交易和消息签名的核心密码学算法。在 Solidity 中实现签名验证对于实现元交易、免 Gas 交易、权限验证等场景非常重要。
### 1. ECDSA 基本原理
以太坊使用 secp256k1 椭圆曲线进行签名,签名包含三个部分:
* **r**:签名的 x 坐标
* **s**:签名的证明
* **v**:恢复标识符(27 或 28,或 0/1)
```solidity
// 签名结构
struct Signature {
bytes32 r;
bytes32 s;
uint8 v;
}
```
### 2. 基础签名验证
#### 使用 OpenZeppelin 的 ECDSA 库
```solidity
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
contract SignatureVerification {
using ECDSA for bytes32;
// 验证签名者地址
function verifySignature(
bytes32 messageHash,
bytes memory signature
) public pure returns (address signer) {
// 恢复签名者地址
signer = messageHash.recover(signature);
return signer;
}
// 验证签名是否有效
function isValidSignature(
bytes32 messageHash,
bytes memory signature,
address expectedSigner
) public pure returns (bool) {
address recoveredSigner = messageHash.recover(signature);
return recoveredSigner == expectedSigner;
}
}
```
### 3. 消息哈希处理
#### 以太坊标准消息格式
```solidity
contract MessageHashing {
// 方式 1:使用 keccak256 直接哈希
function getMessageHash(
address _to,
uint256 _amount,
uint256 _nonce
) public pure returns (bytes32) {
return keccak256(abi.encodePacked(_to, _amount, _nonce));
}
// 方式 2:使用标准以太坊消息格式(推荐)
function getEthSignedMessageHash(bytes32 _messageHash)
public
pure
returns (bytes32)
{
// 按照以太坊标准添加前缀
// "\x19Ethereum Signed Message:\n32" + messageHash
return keccak256(abi.encodePacked(
"\x19Ethereum Signed Message:\n32",
_messageHash
));
}
// 完整的签名验证流程
function verify(
address _signer,
address _to,
uint256 _amount,
uint256 _nonce,
bytes memory signature
) public pure returns (bool) {
// 1. 构建消息哈希
bytes32 messageHash = getMessageHash(_to, _amount, _nonce);
// 2. 添加以太坊消息前缀
bytes32 ethSignedMessageHash = getEthSignedMessageHash(messageHash);
// 3. 恢复签名者地址
address recoveredSigner = recoverSigner(ethSignedMessageHash, signature);
// 4. 验证签名者
return recoveredSigner == _signer;
}
function recoverSigner(
bytes32 _ethSignedMessageHash,
bytes memory _signature
) public pure returns (address) {
(bytes32 r, bytes32 s, uint8 v) = splitSignature(_signature);
return ecrecover(_ethSignedMessageHash, v, r, s);
}
function splitSignature(bytes memory sig)
public
pure
returns (bytes32 r, bytes32 s, uint8 v)
{
require(sig.length == 65, "Invalid signature length");
assembly {
r := mload(add(sig, 32))
s := mload(add(sig, 64))
v := byte(0, mload(add(sig, 96)))
}
// 调整 v 值(MetaMask 等钱包通常返回 27/28)
if (v < 27) {
v += 27;
}
}
}
```
### 4. 元交易(Meta-Transaction)实现
元交易允许用户在不支付 Gas 的情况下执行交易。
```solidity
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract MetaTransaction is ReentrancyGuard {
using ECDSA for bytes32;
// 防止重放攻击的 nonce
mapping(address => uint256) public nonces;
// 执行元交易的地址(可以支付 Gas)
address public relayer;
// 域分隔符(EIP-712)
bytes32 public DOMAIN_SEPARATOR;
// EIP-712 类型哈希
bytes32 public constant META_TRANSACTION_TYPEHASH =
keccak256("MetaTransaction(address from,address to,uint256 value,uint256 nonce,uint256 data)");
event MetaTransactionExecuted(
address indexed from,
address indexed to,
bytes functionSignature,
uint256 nonce
);
constructor() {
relayer = msg.sender;
// 构建域分隔符
DOMAIN_SEPARATOR = keccak256(abi.encode(
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
keccak256(bytes("MetaTransaction")),
keccak256(bytes("1")),
block.chainid,
address(this)
));
}
// 执行元交易
function executeMetaTransaction(
address from,
address to,
bytes memory functionSignature,
bytes32 sigR,
bytes32 sigS,
uint8 sigV
) external payable nonReentrant returns (bytes memory) {
require(msg.sender == relayer, "Only relayer can execute");
// 构建 EIP-712 结构化数据哈希
bytes32 digest = keccak256(abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR,
keccak256(abi.encode(
META_TRANSACTION_TYPEHASH,
from,
to,
msg.value,
nonces[from],
keccak256(functionSignature)
))
));
// 恢复签名者
address signer = ecrecover(digest, sigV, sigR, sigS);
require(signer == from, "Invalid signature");
require(signer != address(0), "Zero address signer");
// 增加 nonce 防止重放
nonces[from]++;
// 执行目标调用
(bool success, bytes memory returnData) = to.call{value: msg.value}(functionSignature);
require(success, "Meta transaction failed");
emit MetaTransactionExecuted(from, to, functionSignature, nonces[from] - 1);
return returnData;
}
function getNonce(address from) external view returns (uint256) {
return nonces[from];
}
}
```
### 5. EIP-712 结构化数据签名
EIP-712 提供了更友好的签名体验,用户在签名时可以看到结构化数据。
```solidity
import "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
contract EIP712Example is EIP712 {
using ECDSA for bytes32;
// 定义 Permit 类型哈希
bytes32 public constant PERMIT_TYPEHASH =
keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
mapping(address => uint256) public nonces;
constructor() EIP712("MyToken", "1") {}
// 构建域分隔符
function DOMAIN_SEPARATOR() external view returns (bytes32) {
return _domainSeparatorV4();
}
// 验证 Permit 签名
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external {
require(block.timestamp <= deadline, "Permit expired");
bytes32 structHash = keccak256(abi.encode(
PERMIT_TYPEHASH,
owner,
spender,
value,
nonces[owner]++,
deadline
));
bytes32 hash = _hashTypedDataV4(structHash);
address signer = hash.recover(v, r, s);
require(signer == owner, "Invalid signature");
// 执行授权逻辑
_approve(owner, spender, value);
}
function _approve(address owner, address spender, uint256 value) internal {
// 实现授权逻辑
}
}
```
### 6. 多签钱包实现
```solidity
contract MultiSigWallet {
using ECDSA for bytes32;
address[] public owners;
mapping(address => bool) public isOwner;
uint256 public requiredSignatures;
struct Transaction {
address to;
uint256 value;
bytes data;
bool executed;
uint256 signatureCount;
}
Transaction[] public transactions;
mapping(uint256 => mapping(address => bool)) public signatures;
event TransactionSubmitted(uint256 indexed txId, address indexed to, uint256 value);
event TransactionSigned(uint256 indexed txId, address indexed signer);
event TransactionExecuted(uint256 indexed txId);
modifier onlyOwner() {
require(isOwner[msg.sender], "Not an owner");
_;
}
constructor(address[] memory _owners, uint256 _required) {
require(_owners.length > 0, "Owners required");
require(_required > 0 && _required <= _owners.length, "Invalid required");
for (uint i = 0; i < _owners.length; i++) {
address owner = _owners[i];
require(owner != address(0), "Invalid owner");
require(!isOwner[owner], "Owner not unique");
isOwner[owner] = true;
owners.push(owner);
}
requiredSignatures = _required;
}
// 提交交易并验证签名
function submitTransaction(
address _to,
uint256 _value,
bytes memory _data,
bytes[] memory _signatures
) external onlyOwner returns (uint256 txId) {
require(_signatures.length >= requiredSignatures, "Insufficient signatures");
// 构建交易哈希
bytes32 txHash = keccak256(abi.encodePacked(
address(this),
_to,
_value,
keccak256(_data),
transactions.length
));
bytes32 ethSignedHash = keccak256(abi.encodePacked(
"\x19Ethereum Signed Message:\n32",
txHash
));
// 验证每个签名
address[] memory signers = new address[](_signatures.length);
for (uint i = 0; i < _signatures.length; i++) {
address signer = recoverSigner(ethSignedHash, _signatures[i]);
require(isOwner[signer], "Signer not an owner");
// 检查重复签名
for (uint j = 0; j < i; j++) {
require(signers[j] != signer, "Duplicate signature");
}
signers[i] = signer;
}
txId = transactions.length;
transactions.push(Transaction({
to: _to,
value: _value,
data: _data,
executed: false,
signatureCount: _signatures.length
}));
emit TransactionSubmitted(txId, _to, _value);
// 自动执行
executeTransaction(txId);
}
function executeTransaction(uint256 _txId) internal {
Transaction storage transaction = transactions[_txId];
require(!transaction.executed, "Already executed");
require(transaction.signatureCount >= requiredSignatures, "Insufficient signatures");
transaction.executed = true;
(bool success, ) = transaction.to.call{value: transaction.value}(transaction.data);
require(success, "Transaction failed");
emit TransactionExecuted(_txId);
}
function recoverSigner(bytes32 _ethSignedMessageHash, bytes memory _signature)
internal
pure
returns (address)
{
return _ethSignedMessageHash.recover(_signature);
}
}
```
### 7. 签名重放攻击防护
```solidity
contract ReplayProtection {
using ECDSA for bytes32;
// 已使用的签名记录
mapping(bytes32 => bool) public usedSignatures;
// 用户的 nonce
mapping(address => uint256) public nonces;
// 链 ID
uint256 public chainId;
constructor() {
chainId = block.chainid;
}
// 安全的签名验证(包含链 ID 和合约地址)
function verifyWithReplayProtection(
address _signer,
bytes memory _signature,
bytes memory _data
) external returns (bool) {
uint256 nonce = nonces[_signer];
// 构建包含防重放信息的消息
bytes32 messageHash = keccak256(abi.encodePacked(
chainId,
address(this),
_signer,
nonce,
_data
));
bytes32 ethSignedMessageHash = keccak256(abi.encodePacked(
"\x19Ethereum Signed Message:\n32",
messageHash
));
// 检查签名是否已使用
require(!usedSignatures[ethSignedMessageHash], "Signature already used");
// 恢复签名者
address recoveredSigner = ethSignedMessageHash.recover(_signature);
require(recoveredSigner == _signer, "Invalid signature");
// 标记签名已使用
usedSignatures[ethSignedMessageHash] = true;
// 增加 nonce
nonces[_signer]++;
return true;
}
}
```
### 8. 签名验证最佳实践
1. **始终使用标准以太坊消息格式**:添加 `\x19Ethereum Signed Message:\n32` 前缀
2. **防止重放攻击**:使用 nonce 和链 ID
3. **验证签名长度**:确保签名长度为 65 字节
4. **检查签名者地址**:确保恢复的地址不是零地址
5. **使用 EIP-712**:提供更好的用户体验和安全性
6. **考虑使用 OpenZeppelin**:经过审计的标准实现
```solidity
// 完整的签名验证最佳实践
contract BestPracticeSignature {
using ECDSA for bytes32;
bytes32 public constant EIP712_DOMAIN_TYPEHASH =
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");
bytes32 public immutable DOMAIN_SEPARATOR;
constructor() {
DOMAIN_SEPARATOR = keccak256(abi.encode(
EIP712_DOMAIN_TYPEHASH,
keccak256(bytes("BestPractice")),
keccak256(bytes("1")),
block.chainid,
address(this)
));
}
function verifyEIP712Signature(
bytes32 structHash,
bytes memory signature,
address expectedSigner
) external view returns (bool) {
bytes32 digest = keccak256(abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR,
structHash
));
address signer = digest.recover(signature);
return signer == expectedSigner && signer != address(0);
}
}
```
服务端 · 3月6日 21:52
Solidity 中 ERC20 和 ERC721 代币标准的核心实现原理是什么?ERC20 和 ERC721 是以太坊上最广泛使用的代币标准,分别代表同质化代币和非同质化代币(NFT)。理解它们的核心实现原理对于开发 DeFi 和 NFT 项目至关重要。
### 1. ERC20 标准详解
ERC20 定义了同质化代币的标准接口,每个代币都是可互换的。
#### ERC20 接口定义
```solidity
interface IERC20 {
// 查询总供应量
function totalSupply() external view returns (uint256);
// 查询账户余额
function balanceOf(address account) external view returns (uint256);
// 查询授权额度
function allowance(address owner, address spender) external view returns (uint256);
// 转账
function transfer(address to, uint256 amount) external returns (bool);
// 授权
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);
}
```
#### ERC20 完整实现
```solidity
contract ERC20 is IERC20 {
// 状态变量
mapping(address => uint256) private _balances;
mapping(address => mapping(address => uint256)) private _allowances;
uint256 private _totalSupply;
string private _name;
string private _symbol;
uint8 private _decimals;
// 构造函数
constructor(string memory name_, string memory symbol_, uint8 decimals_) {
_name = name_;
_symbol = symbol_;
_decimals = decimals_;
}
// 查询函数
function name() public view returns (string memory) {
return _name;
}
function symbol() public view returns (string memory) {
return _symbol;
}
function decimals() public view returns (uint8) {
return _decimals;
}
function totalSupply() public view override returns (uint256) {
return _totalSupply;
}
function balanceOf(address account) public view override returns (uint256) {
return _balances[account];
}
function allowance(address owner, address spender)
public
view
override
returns (uint256)
{
return _allowances[owner][spender];
}
// 转账函数
function transfer(address to, uint256 amount)
public
override
returns (bool)
{
address owner = msg.sender;
_transfer(owner, to, amount);
return true;
}
// 授权函数
function approve(address spender, uint256 amount)
public
override
returns (bool)
{
address owner = msg.sender;
_approve(owner, spender, amount);
return true;
}
// 授权转账
function transferFrom(address from, address to, uint256 amount)
public
override
returns (bool)
{
address spender = msg.sender;
_spendAllowance(from, spender, 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: insufficient balance");
unchecked {
_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 _spendAllowance(address owner, address spender, uint256 amount) internal {
uint256 currentAllowance = allowance(owner, spender);
if (currentAllowance != type(uint256).max) {
require(currentAllowance >= amount, "ERC20: insufficient allowance");
unchecked {
_approve(owner, spender, currentAllowance - amount);
}
}
}
// 铸造代币(内部函数)
function _mint(address account, uint256 amount) internal {
require(account != address(0), "ERC20: mint to zero address");
_totalSupply += amount;
unchecked {
_balances[account] += amount;
}
emit Transfer(address(0), account, amount);
}
// 销毁代币(内部函数)
function _burn(address account, uint256 amount) internal {
require(account != address(0), "ERC20: burn from zero address");
uint256 accountBalance = _balances[account];
require(accountBalance >= amount, "ERC20: burn amount exceeds balance");
unchecked {
_balances[account] = accountBalance - amount;
_totalSupply -= amount;
}
emit Transfer(account, address(0), amount);
}
}
```
#### ERC20 扩展功能
```solidity
// 可暂停的 ERC20
import "@openzeppelin/contracts/security/Pausable.sol";
contract PausableERC20 is ERC20, Pausable {
constructor() ERC20("PausableToken", "PTK", 18) {}
function _beforeTokenTransfer(address from, address to, uint256 amount)
internal
override
whenNotPaused
{
super._beforeTokenTransfer(from, to, amount);
}
function pause() public onlyOwner {
_pause();
}
function unpause() public onlyOwner {
_unpause();
}
}
// 可销毁的 ERC20
contract BurnableERC20 is ERC20 {
constructor() ERC20("BurnableToken", "BTK", 18) {}
function burn(uint256 amount) public {
_burn(msg.sender, amount);
}
function burnFrom(address account, uint256 amount) public {
_spendAllowance(account, msg.sender, amount);
_burn(account, amount);
}
}
```
### 2. ERC721 标准详解
ERC721 定义了非同质化代币(NFT)的标准,每个代币都是唯一的。
#### ERC721 接口定义
```solidity
interface IERC721 {
// 查询余额
function balanceOf(address owner) external view returns (uint256 balance);
// 查询代币所有者
function ownerOf(uint256 tokenId) external view returns (address owner);
// 安全转账
function safeTransferFrom(address from, address to, uint256 tokenId) external;
function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;
// 转账
function transferFrom(address from, address to, uint256 tokenId) external;
// 授权
function approve(address to, uint256 tokenId) external;
function setApprovalForAll(address operator, bool approved) external;
// 查询授权
function getApproved(uint256 tokenId) external view returns (address operator);
function isApprovedForAll(address owner, address operator) external view returns (bool);
// 事件
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
}
// 元数据接口
interface IERC721Metadata {
function name() external view returns (string memory);
function symbol() external view returns (string memory);
function tokenURI(uint256 tokenId) external view returns (string memory);
}
```
#### ERC721 完整实现
```solidity
contract ERC721 is IERC721, IERC721Metadata {
// Token name
string private _name;
// Token symbol
string private _symbol;
// Token ID 到所有者地址的映射
mapping(uint256 => address) private _owners;
// 所有者地址到代币数量的映射
mapping(address => uint256) private _balances;
// Token ID 到授权地址的映射
mapping(uint256 => address) private _tokenApprovals;
// 所有者到操作员授权的映射
mapping(address => mapping(address => bool)) private _operatorApprovals;
// Token ID 到 token URI 的映射
mapping(uint256 => string) private _tokenURIs;
constructor(string memory name_, string memory symbol_) {
_name = name_;
_symbol = symbol_;
}
function name() public view override returns (string memory) {
return _name;
}
function symbol() public view override returns (string memory) {
return _symbol;
}
function tokenURI(uint256 tokenId) public view override returns (string memory) {
require(_exists(tokenId), "ERC721: URI query for nonexistent token");
return _tokenURIs[tokenId];
}
function balanceOf(address owner) public view override returns (uint256) {
require(owner != address(0), "ERC721: balance query for zero address");
return _balances[owner];
}
function ownerOf(uint256 tokenId) public view override returns (address) {
address owner = _owners[tokenId];
require(owner != address(0), "ERC721: owner query for nonexistent token");
return owner;
}
function approve(address to, uint256 tokenId) public override {
address owner = ownerOf(tokenId);
require(to != owner, "ERC721: approval to current owner");
require(
msg.sender == owner || isApprovedForAll(owner, msg.sender),
"ERC721: approve caller is not owner nor approved for all"
);
_approve(to, tokenId);
}
function getApproved(uint256 tokenId) public view override returns (address) {
require(_exists(tokenId), "ERC721: approved query for nonexistent token");
return _tokenApprovals[tokenId];
}
function setApprovalForAll(address operator, bool approved) public override {
require(operator != msg.sender, "ERC721: approve to caller");
_operatorApprovals[msg.sender][operator] = approved;
emit ApprovalForAll(msg.sender, operator, approved);
}
function isApprovedForAll(address owner, address operator)
public
view
override
returns (bool)
{
return _operatorApprovals[owner][operator];
}
function transferFrom(address from, address to, uint256 tokenId) public override {
require(_isApprovedOrOwner(msg.sender, tokenId), "ERC721: transfer caller is not owner nor approved");
_transfer(from, to, tokenId);
}
function safeTransferFrom(address from, address to, uint256 tokenId) public override {
safeTransferFrom(from, to, tokenId, "");
}
function safeTransferFrom(
address from,
address to,
uint256 tokenId,
bytes memory _data
) public override {
require(_isApprovedOrOwner(msg.sender, tokenId), "ERC721: transfer caller is not owner nor approved");
_safeTransfer(from, to, tokenId, _data);
}
// 内部函数
function _exists(uint256 tokenId) internal view returns (bool) {
return _owners[tokenId] != address(0);
}
function _isApprovedOrOwner(address spender, uint256 tokenId) internal view returns (bool) {
require(_exists(tokenId), "ERC721: operator query for nonexistent token");
address owner = ownerOf(tokenId);
return (spender == owner || getApproved(tokenId) == spender || isApprovedForAll(owner, spender));
}
function _safeTransfer(address from, address to, uint256 tokenId, bytes memory _data) internal {
_transfer(from, to, tokenId);
require(_checkOnERC721Received(from, to, tokenId, _data), "ERC721: transfer to non ERC721Receiver implementer");
}
function _transfer(address from, address to, uint256 tokenId) internal {
require(ownerOf(tokenId) == from, "ERC721: transfer from incorrect owner");
require(to != address(0), "ERC721: transfer to the zero address");
// 清除授权
_approve(address(0), tokenId);
_balances[from] -= 1;
_balances[to] += 1;
_owners[tokenId] = to;
emit Transfer(from, to, tokenId);
}
function _approve(address to, uint256 tokenId) internal {
_tokenApprovals[tokenId] = to;
emit Approval(ownerOf(tokenId), to, tokenId);
}
// 铸造函数
function _mint(address to, uint256 tokenId) internal {
require(to != address(0), "ERC721: mint to the zero address");
require(!_exists(tokenId), "ERC721: token already minted");
_balances[to] += 1;
_owners[tokenId] = to;
emit Transfer(address(0), to, tokenId);
}
// 安全铸造
function _safeMint(address to, uint256 tokenId) internal {
_safeMint(to, tokenId, "");
}
function _safeMint(address to, uint256 tokenId, bytes memory _data) internal {
_mint(to, tokenId);
require(
_checkOnERC721Received(address(0), to, tokenId, _data),
"ERC721: transfer to non ERC721Receiver implementer"
);
}
// 销毁函数
function _burn(uint256 tokenId) internal {
address owner = ownerOf(tokenId);
_approve(address(0), tokenId);
_balances[owner] -= 1;
delete _owners[tokenId];
emit Transfer(owner, address(0), tokenId);
}
// 检查接收者是否实现了 ERC721Receiver 接口
function _checkOnERC721Received(
address from,
address to,
uint256 tokenId,
bytes memory _data
) private returns (bool) {
if (to.code.length > 0) {
try IERC721Receiver(to).onERC721Received(msg.sender, from, tokenId, _data) returns (bytes4 retval) {
return retval == IERC721Receiver.onERC721Received.selector;
} catch (bytes memory reason) {
if (reason.length == 0) {
revert("ERC721: transfer to non ERC721Receiver implementer");
} else {
assembly {
revert(add(32, reason), mload(reason))
}
}
}
}
return true;
}
}
// ERC721Receiver 接口
interface IERC721Receiver {
function onERC721Received(
address operator,
address from,
uint256 tokenId,
bytes calldata data
) external returns (bytes4);
}
```
### 3. ERC20 vs ERC721 对比
| 特性 | ERC20 | ERC721 |
| ---- | ------------------------ | ------------------------------------------------------------ |
| 代币类型 | 同质化 | 非同质化 |
| 互换性 | 可互换 | 唯一不可互换 |
| 余额查询 | balanceOf(address) | balanceOf(address) + ownerOf(tokenId) |
| 转账 | transfer(to, amount) | transferFrom(from, to, tokenId) |
| 授权 | approve(spender, amount) | approve(to, tokenId) + setApprovalForAll(operator, approved) |
| 使用场景 | 货币、积分、治理代币 | 数字艺术品、游戏道具、身份凭证 |
### 4. 实际应用示例
#### 完整的 ERC20 Token
```solidity
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract MyToken is ERC20, Ownable {
constructor(uint256 initialSupply) ERC20("MyToken", "MTK") {
_mint(msg.sender, initialSupply * 10 ** decimals());
}
function mint(address to, uint256 amount) public onlyOwner {
_mint(to, amount);
}
}
```
#### 完整的 NFT 合约
```solidity
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract MyNFT is ERC721URIStorage, Ownable {
uint256 private _tokenIds;
uint256 public mintPrice = 0.01 ether;
uint256 public maxSupply = 10000;
string public baseURI;
constructor(string memory _baseURI) ERC721("MyNFT", "MNFT") {
baseURI = _baseURI;
}
function mint(string memory tokenURI) public payable returns (uint256) {
require(msg.value >= mintPrice, "Insufficient payment");
require(_tokenIds < maxSupply, "Max supply reached");
_tokenIds++;
uint256 newTokenId = _tokenIds;
_mint(msg.sender, newTokenId);
_setTokenURI(newTokenId, tokenURI);
return newTokenId;
}
function withdraw() public onlyOwner {
payable(owner()).transfer(address(this).balance);
}
function _baseURI() internal view override returns (string memory) {
return baseURI;
}
}
```
### 5. 安全注意事项
1. **整数溢出**:使用 Solidity 0.8+ 或 SafeMath 库
2. **重入攻击**:遵循 Checks-Effects-Interactions 模式
3. **授权管理**:正确处理 approve 和 allowance
4. **零地址检查**:防止代币发送到零地址
5. **事件发射**:所有状态变更都应触发事件
### 6. 扩展标准
* **ERC777**:更高级的代币标准,支持钩子函数
* **ERC1155**:多代币标准,支持同质化和非同质化代币
* **ERC2981**:NFT 版税标准
* **ERC4907**:NFT 租赁标准
服务端 · 3月6日 21:51
Solidity 智能合约如何进行 Gas 优化?有哪些常见的优化技巧?Gas 优化是 Solidity 开发中的关键技能,直接影响合约的执行成本和用户体验。以下是系统性的 Gas 优化策略和技巧。
### 1. 存储优化
Storage 是最昂贵的资源,优化存储使用是 Gas 优化的首要任务。
#### 使用合适的数据类型
```solidity
contract StorageOptimization {
// 不推荐:使用 uint256 存储小数值
uint256 public smallValue; // 占用 32 字节
// 推荐:使用更小的数据类型
uint128 public value128; // 占用 16 字节
uint64 public value64; // 占用 8 字节
uint32 public value32; // 占用 4 字节
uint8 public value8; // 占用 1 字节
// 最佳:打包存储变量
uint128 public balance; // slot 0: 16 字节
uint32 public timestamp; // slot 0: 4 字节
uint16 public status; // slot 0: 2 字节
address public owner; // slot 0: 20 字节 - 需要 slot 1
// 注意:uint128 + uint32 + uint16 = 22 字节,address 需要新的 slot
}
```
#### 存储变量打包
```solidity
contract PackingExample {
// 未优化:占用 3 个 storage slot
struct Unoptimized {
uint256 a; // slot 0
uint128 b; // slot 1
uint128 c; // slot 2
}
// 优化后:只占用 2 个 storage slot
struct Optimized {
uint128 b; // slot 0 (16 bytes)
uint128 c; // slot 0 (16 bytes) - 打包在一起
uint256 a; // slot 1
}
// 最佳实践:按大小排序
struct BestPractice {
uint128 a; // 16 bytes
uint128 b; // 16 bytes - 与 a 共用一个 slot
uint64 c; // 8 bytes
uint64 d; // 8 bytes - 与 c 共用
uint32 e; // 4 bytes
uint32 f; // 4 bytes - 与 e 共用
}
}
```
#### 使用 memory 代替 storage
```solidity
contract MemoryVsStorage {
uint256[] public data;
// 昂贵:多次 storage 访问
function expensiveSum() public view returns (uint256) {
uint256 sum = 0;
for (uint i = 0; i < data.length; i++) {
sum += data[i]; // 每次循环都访问 storage
}
return sum;
}
// 优化:先加载到 memory
function optimizedSum() public view returns (uint256) {
uint256[] memory localData = data; // 一次性加载
uint256 sum = 0;
for (uint i = 0; i < localData.length; i++) {
sum += localData[i]; // 访问 memory,便宜很多
}
return sum;
}
}
```
### 2. 变量和计算优化
#### 使用常量和不变量
```solidity
contract ConstantOptimization {
// 昂贵:storage 变量
uint256 public constantValue = 1000;
// 优化:常量(不占用 storage)
uint256 public constant CONSTANT_VALUE = 1000;
// 更优:不可变量(编译时确定,不占用 storage)
uint256 public immutable immutableValue;
constructor(uint256 _value) {
immutableValue = _value;
}
// 使用常量计算
function calculate(uint256 amount) public pure returns (uint256) {
return amount * CONSTANT_VALUE / 10000;
}
}
```
#### 短路求优
```solidity
contract ShortCircuit {
// 优化:利用短路求值
function checkConditions(bool a, bool b, bool c) public pure returns (bool) {
// 将最可能为 false 的条件放在前面
return a && b && c;
}
// 优化:|| 运算符
function checkOr(bool a, bool b, bool c) public pure returns (bool) {
// 将最可能为 true 的条件放在前面
return a || b || c;
}
}
```
### 3. 循环优化
#### 循环变量优化
```solidity
contract LoopOptimization {
uint256[] public data;
// 未优化:每次循环都访问 storage
function unoptimizedLoop() public view returns (uint256) {
uint256 sum = 0;
for (uint256 i = 0; i < data.length; i++) {
sum += data[i];
}
return sum;
}
// 优化 1:缓存数组长度
function optimizedLoop1() public view returns (uint256) {
uint256 sum = 0;
uint256 len = data.length; // 缓存长度
for (uint256 i = 0; i < len; i++) {
sum += data[i];
}
return sum;
}
// 优化 2:使用 unchecked 和 ++i
function optimizedLoop2() public view returns (uint256) {
uint256 sum = 0;
uint256 len = data.length;
for (uint256 i = 0; i < len; ) {
sum += data[i];
unchecked { ++i; } // 使用 unchecked 和前置递增
}
return sum;
}
// 优化 3:使用 memory 数组
function optimizedLoop3() public view returns (uint256) {
uint256[] memory localData = data;
uint256 sum = 0;
uint256 len = localData.length;
for (uint256 i = 0; i < len; ) {
sum += localData[i];
unchecked { ++i; }
}
return sum;
}
}
```
### 4. 函数优化
#### 使用 calldata
```solidity
contract CalldataOptimization {
// 昂贵:使用 memory
function processMemory(uint256[] memory data) external pure returns (uint256) {
uint256 sum = 0;
for (uint i = 0; i < data.length; i++) {
sum += data[i];
}
return sum;
}
// 优化:使用 calldata(只读,不复制)
function processCalldata(uint256[] calldata data) external pure returns (uint256) {
uint256 sum = 0;
for (uint i = 0; i < data.length; i++) {
sum += data[i];
}
return sum;
}
}
```
#### 函数修饰符优化
```solidity
contract ModifierOptimization {
// 昂贵:修饰符代码内联到每个函数
modifier expensiveCheck() {
require(msg.sender == owner, "Not owner");
require(!paused, "Paused");
require(balance > 0, "No balance");
_;
}
// 优化:将检查提取为内部函数
modifier optimizedCheck() {
_checkAccess();
_;
}
function _checkAccess() internal view {
require(msg.sender == owner, "Not owner");
require(!paused, "Paused");
require(balance > 0, "No balance");
}
// 这样编译器可以更有效地优化
function action1() external optimizedCheck { }
function action2() external optimizedCheck { }
function action3() external optimizedCheck { }
}
```
### 5. 错误处理优化
#### 自定义错误(Solidity 0.8.4+)
```solidity
// 文件: ErrorOptimization.sol
// 开源许可: MIT
pragma solidity ^0.8.4;
contract ErrorOptimization {
address public owner;
uint256 public balance;
// 定义自定义错误(比 require 字符串更省 Gas)
error NotOwner(address caller);
error InsufficientBalance(uint256 requested, uint256 available);
error InvalidAmount(uint256 amount);
modifier onlyOwner() {
if (msg.sender != owner) {
revert NotOwner(msg.sender);
}
_;
}
function withdraw(uint256 amount) external onlyOwner {
if (amount == 0) {
revert InvalidAmount(amount);
}
if (amount > balance) {
revert InsufficientBalance(amount, balance);
}
balance -= amount;
payable(msg.sender).transfer(amount);
}
}
```
### 6. 事件和日志优化
```solidity
contract EventOptimization {
// 昂贵:存储大量数据
mapping(uint256 => Transaction) public transactions;
struct Transaction {
address from;
address to;
uint256 amount;
uint256 timestamp;
string metadata;
}
// 优化:使用事件代替 storage
event TransactionExecuted(
address indexed from,
address indexed to,
uint256 amount,
uint256 timestamp
);
function executeTransaction(address to, uint256 amount) external {
// 执行业务逻辑...
// 使用事件记录(比 storage 便宜得多)
emit TransactionExecuted(msg.sender, to, amount, block.timestamp);
}
}
```
### 7. 库的使用
```solidity
// 使用库减少代码重复
library SafeMath {
function add(uint256 a, uint256 b) internal pure returns (uint256) {
unchecked {
uint256 c = a + b;
require(c >= a, "Addition overflow");
return c;
}
}
}
contract UsingLibrary {
using SafeMath for uint256;
function calculate(uint256 a, uint256 b) public pure returns (uint256) {
return a.add(b); // 使用库函数
}
}
```
### 8. 位运算优化
```solidity
contract BitwiseOptimization {
// 使用位运算代替数学运算
// 乘以/除以 2 的幂
function multiplyBy2(uint256 x) public pure returns (uint256) {
return x << 1; // 等同于 x * 2
}
function divideBy2(uint256 x) public pure returns (uint256) {
return x >> 1; // 等同于 x / 2
}
// 检查是否为 2 的幂
function isPowerOf2(uint256 x) public pure returns (bool) {
return x > 0 && (x & (x - 1)) == 0;
}
// 使用位掩码存储多个布尔值
uint256 private flags;
uint256 constant FLAG_PAUSED = 1 << 0; // 1
uint256 constant FLAG_FINALIZED = 1 << 1; // 2
uint256 constant FLAG_APPROVED = 1 << 2; // 4
function setFlag(uint256 flag) internal {
flags |= flag;
}
function clearFlag(uint256 flag) internal {
flags &= ~flag;
}
function hasFlag(uint256 flag) internal view returns (bool) {
return (flags & flag) != 0;
}
}
```
### 9. 编译器优化
#### 使用优化器
```json
// hardhat.config.js
module.exports = {
solidity: {
version: "0.8.19",
settings: {
optimizer: {
enabled: true,
runs: 200 // 根据合约调用频率调整
}
}
}
};
```
### 10. Gas 优化对比表
| 优化技术 | Gas 节省 | 适用场景 |
| ------------ | --------------- | ------ |
| 存储打包 | 20,000 Gas/slot | 多个小变量 |
| 使用常量 | 20,000 Gas | 固定值 |
| 使用 calldata | 3,800 Gas/32字节 | 外部函数参数 |
| 自定义错误 | \~50 Gas | 错误处理 |
| unchecked | \~80 Gas/操作 | 数学运算 |
| 事件替代 storage | \~19,000 Gas | 日志记录 |
| 短路求值 | 变量 | 条件判断 |
### 11. 优化工具
1. **hardhat-gas-reporter**: 报告每个测试的 Gas 消耗
2. **eth-gas-reporter**: 详细的 Gas 分析报告
3. **Remix IDE**: 内置 Gas 估算
4. **Tenderly**: Gas 分析和优化建议
```javascript
// hardhat.config.js
require("hardhat-gas-reporter");
module.exports = {
gasReporter: {
enabled: true,
currency: "USD",
gasPrice: 21
}
};
```
### 12. 最佳实践总结
1. **优先优化存储**:Storage 操作是最昂贵的
2. **使用合适的数据类型**:避免过度分配
3. **利用编译器优化**:启用优化器并调整 runs 参数
4. **批量操作**:减少函数调用次数
5. **使用事件**:代替不必要的 storage 写入
6. **延迟计算**:尽可能在需要时计算而非存储
7. **定期审计**:使用工具检查 Gas 消耗
记住:过度优化可能降低代码可读性,需要在性能和可维护性之间找到平衡。
服务端 · 3月6日 21:51
Solidity 中的 Library 和 Contract 有什么区别?如何正确使用 Library?Library(库)是 Solidity 中一种特殊的代码复用机制,与 Contract(合约)有本质区别。正确使用 Library 可以节省 Gas、提高代码复用性。
### 1. Library 和 Contract 的核心区别
| 特性 | Library | Contract |
| ------ | ---------------- | --------- |
| 部署 | 可以独立部署或内联 | 必须独立部署 |
| 状态变量 | 不能有状态变量 | 可以有状态变量 |
| 继承 | 不能被继承 | 可以被继承 |
| ETH 接收 | 不能接收 ETH | 可以接收 ETH |
| 销毁 | 不能自销毁 | 可以自销毁 |
| 调用方式 | DELEGATECALL 或内联 | CALL |
| 使用场景 | 工具函数、复用代码 | 业务逻辑、状态管理 |
### 2. Library 的基本用法
#### 独立函数库
```solidity
// 文件: MathLibrary.sol
// 纯函数库,所有函数都是 pure
library MathLibrary {
// 计算最大公约数
function gcd(uint256 a, uint256 b) internal pure returns (uint256) {
while (b != 0) {
uint256 temp = b;
b = a % b;
a = temp;
}
return a;
}
// 计算平方根
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;
}
// 计算幂
function pow(uint256 base, uint256 exponent) internal pure returns (uint256) {
if (exponent == 0) return 1;
if (exponent == 1) return base;
uint256 result = 1;
uint256 b = base;
uint256 e = exponent;
while (e > 0) {
if (e & 1 == 1) {
result *= b;
}
b *= b;
e >>= 1;
}
return result;
}
}
// 使用库
contract Calculator {
// 使用 using for 语法
using MathLibrary for uint256;
function calculate(uint256 a, uint256 b) external pure returns (uint256, uint256, uint256) {
uint256 gcdResult = MathLibrary.gcd(a, b);
uint256 sqrtResult = a.sqrt(); // 使用 using for 语法
uint256 powResult = a.pow(b); // 使用 using for 语法
return (gcdResult, sqrtResult, powResult);
}
}
```
#### 结构体库
```solidity
// 文件: ArrayLibrary.sol
library ArrayLibrary {
// 查找元素索引
function find(uint256[] storage arr, uint256 value)
internal
view
returns (int256)
{
for (uint i = 0; i < arr.length; i++) {
if (arr[i] == value) {
return int256(i);
}
}
return -1;
}
// 删除元素(保持顺序)
function removeAt(uint256[] storage arr, uint256 index) internal {
require(index < arr.length, "Index out of bounds");
for (uint i = index; i < arr.length - 1; i++) {
arr[i] = arr[i + 1];
}
arr.pop();
}
// 删除元素(不保持顺序,更高效)
function quickRemove(uint256[] storage arr, uint256 index) internal {
require(index < arr.length, "Index out of bounds");
if (index != arr.length - 1) {
arr[index] = arr[arr.length - 1];
}
arr.pop();
}
// 插入排序
function sort(uint256[] storage arr) internal {
for (uint i = 1; i < arr.length; i++) {
uint256 key = arr[i];
int256 j = int256(i) - 1;
while (j >= 0 && arr[uint256(j)] > key) {
arr[uint256(j) + 1] = arr[uint256(j)];
j--;
}
arr[uint256(j) + 1] = key;
}
}
// 检查是否包含
function contains(uint256[] storage arr, uint256 value)
internal
view
returns (bool)
{
for (uint i = 0; i < arr.length; i++) {
if (arr[i] == value) {
return true;
}
}
return false;
}
}
// 使用结构体库
contract DataManager {
using ArrayLibrary for uint256[];
uint256[] public data;
function addData(uint256 value) external {
data.push(value);
}
function removeData(uint256 index) external {
data.quickRemove(index); // 调用库函数
}
function findData(uint256 value) external view returns (int256) {
return data.find(value); // 调用库函数
}
function sortData() external {
data.sort(); // 调用库函数
}
}
```
### 3. Library 的高级用法
#### 使用 storage 的库
```solidity
// 文件: MappingLibrary.sol
library MappingLibrary {
// 为 mapping 添加迭代功能
struct IterableMapping {
mapping(address => uint256) data;
address[] keys;
mapping(address => uint256) keyIndex;
}
function set(IterableMapping storage self, address key, uint256 value) internal {
if (self.keyIndex[key] == 0) {
// 新键
self.keys.push(key);
self.keyIndex[key] = self.keys.length;
}
self.data[key] = value;
}
function get(IterableMapping storage self, address key)
internal
view
returns (uint256)
{
return self.data[key];
}
function remove(IterableMapping storage self, address key) internal {
require(self.keyIndex[key] != 0, "Key not found");
uint256 index = self.keyIndex[key] - 1;
address lastKey = self.keys[self.keys.length - 1];
// 用最后一个元素替换要删除的元素
self.keys[index] = lastKey;
self.keyIndex[lastKey] = index + 1;
self.keys.pop();
delete self.keyIndex[key];
delete self.data[key];
}
function contains(IterableMapping storage self, address key)
internal
view
returns (bool)
{
return self.keyIndex[key] != 0;
}
function size(IterableMapping storage self) internal view returns (uint256) {
return self.keys.length;
}
function getKeyAt(IterableMapping storage self, uint256 index)
internal
view
returns (address)
{
require(index < self.keys.length, "Index out of bounds");
return self.keys[index];
}
}
// 使用可迭代映射
contract TokenHolder {
using MappingLibrary for MappingLibrary.IterableMapping;
MappingLibrary.IterableMapping private balances;
function setBalance(address holder, uint256 amount) external {
balances.set(holder, amount);
}
function getBalance(address holder) external view returns (uint256) {
return balances.get(holder);
}
function removeHolder(address holder) external {
balances.remove(holder);
}
function getAllHolders() external view returns (address[] memory) {
address[] memory holders = new address[](balances.size());
for (uint i = 0; i < balances.size(); i++) {
holders[i] = balances.getKeyAt(i);
}
return holders;
}
}
```
### 4. 常用的标准库
#### SafeMath(Solidity \< 0.8.0)
```solidity
// 文件: SafeMath.sol
library SafeMath {
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a, "SafeMath: addition overflow");
return c;
}
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
require(b <= a, "SafeMath: subtraction underflow");
return a - b;
}
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
if (a == 0) return 0;
uint256 c = a * b;
require(c / a == b, "SafeMath: multiplication overflow");
return c;
}
function div(uint256 a, uint256 b) internal pure returns (uint256) {
require(b > 0, "SafeMath: division by zero");
return a / b;
}
function mod(uint256 a, uint256 b) internal pure returns (uint256) {
require(b != 0, "SafeMath: modulo by zero");
return a % b;
}
}
// 使用 SafeMath
contract SafeCalculator {
using SafeMath for uint256;
function safeAdd(uint256 a, uint256 b) external pure returns (uint256) {
return a.add(b); // 使用库函数
}
}
```
#### Address 库
```solidity
// OpenZeppelin 的 Address 库
library Address {
function isContract(address account) internal view returns (bool) {
return account.code.length > 0;
}
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Insufficient balance");
(bool success, ) = recipient.call{value: amount}("");
require(success, "Transfer failed");
}
function functionCall(address target, bytes memory data)
internal
returns (bytes memory)
{
return functionCall(target, data, "Address: low-level call failed");
}
function functionCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
(bool success, bytes memory returndata) = target.call(data);
if (!success) {
if (returndata.length > 0) {
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
return returndata;
}
}
// 使用 Address 库
contract ContractInteractor {
using Address for address;
function checkIsContract(address account) external view returns (bool) {
return account.isContract();
}
function safeTransfer(address payable recipient, uint256 amount) external {
recipient.sendValue(amount);
}
}
```
### 5. Library 的部署方式
#### 内联库(Internal 函数)
```solidity
// 所有函数都是 internal,代码会被内联到调用合约中
library InlineLibrary {
function internalFunction(uint256 x) internal pure returns (uint256) {
return x * 2;
}
}
// 使用时不需要部署库,代码直接内联
contract UsingInlineLibrary {
using InlineLibrary for uint256;
function double(uint256 x) external pure returns (uint256) {
return x.internalFunction();
}
}
```
#### 外部库(External 函数)
```solidity
// 包含 external 函数,需要单独部署
library ExternalLibrary {
function externalFunction(uint256 x) external pure returns (uint256) {
return x * 2;
}
}
// 使用时需要链接已部署的库地址
contract UsingExternalLibrary {
function double(uint256 x) external pure returns (uint256) {
return ExternalLibrary.externalFunction(x);
}
}
```
### 6. Library 的最佳实践
#### 命名规范
```solidity
// 库名使用 PascalCase,以 Library 结尾(可选但推荐)
library StringLibrary {
// 函数名使用 camelCase
function toUpperCase(string memory str) internal pure returns (string memory) {
// 实现...
}
}
```
#### 函数可见性
```solidity
library VisibilityExample {
// internal:代码内联,不需要部署库(推荐)
function internalFunc() internal pure returns (uint256) {
return 1;
}
// external:需要部署库,通过 DELEGATECALL 调用
function externalFunc() external pure returns (uint256) {
return 2;
}
// public:可以内部调用也可以外部调用
function publicFunc() public pure returns (uint256) {
return 3;
}
// private:只能在库内部使用
function privateFunc() private pure returns (uint256) {
return 4;
}
}
```
#### 使用 using for
```solidity
library EnhancedMath {
function square(uint256 x) internal pure returns (uint256) {
return x * x;
}
function cube(uint256 x) internal pure returns (uint256) {
return x * x * x;
}
}
contract MathUser {
// 方式 1:对特定类型使用 using for
using EnhancedMath for uint256;
function calculate(uint256 x) external pure returns (uint256, uint256) {
return (x.square(), x.cube());
}
// 方式 2:全局使用(所有 uint256)
using EnhancedMath for *;
}
```
### 7. Library 的实际应用案例
#### 字符串处理库
```solidity
library StringUtils {
function length(string memory str) internal pure returns (uint256) {
return bytes(str).length;
}
function concat(string memory a, string memory b)
internal
pure
returns (string memory)
{
bytes memory ba = bytes(a);
bytes memory bb = bytes(b);
bytes memory result = new bytes(ba.length + bb.length);
for (uint i = 0; i < ba.length; i++) {
result[i] = ba[i];
}
for (uint i = 0; i < bb.length; i++) {
result[ba.length + i] = bb[i];
}
return string(result);
}
function substring(
string memory str,
uint256 start,
uint256 length
) internal pure returns (string memory) {
bytes memory strBytes = bytes(str);
require(start + length <= strBytes.length, "Out of bounds");
bytes memory result = new bytes(length);
for (uint i = 0; i < length; i++) {
result[i] = strBytes[start + i];
}
return string(result);
}
function compare(string memory a, string memory b)
internal
pure
returns (int256)
{
bytes memory ba = bytes(a);
bytes memory bb = bytes(b);
uint256 minLength = ba.length < bb.length ? ba.length : bb.length;
for (uint i = 0; i < minLength; i++) {
if (ba[i] < bb[i]) return -1;
if (ba[i] > bb[i]) return 1;
}
if (ba.length < bb.length) return -1;
if (ba.length > bb.length) return 1;
return 0;
}
}
// 使用字符串库
contract StringProcessor {
using StringUtils for string;
function processString(string memory input) external pure returns (
uint256 len,
string memory sub,
int256 cmp
) {
len = input.length();
sub = input.substring(0, 5);
cmp = input.compare("Hello");
}
}
```
### 8. Library 的 Gas 优化
```solidity
// 优化前:多次重复代码
contract BeforeOptimization {
function operation1(uint256 x) external pure returns (uint256) {
// 复杂计算...
return x * 2 + 1;
}
function operation2(uint256 x) external pure returns (uint256) {
// 相同复杂计算...
return x * 2 + 1;
}
}
// 优化后:使用库复用代码
library OptimizedMath {
function complexCalculation(uint256 x) internal pure returns (uint256) {
return x * 2 + 1;
}
}
contract AfterOptimization {
using OptimizedMath for uint256;
function operation1(uint256 x) external pure returns (uint256) {
return x.complexCalculation();
}
function operation2(uint256 x) external pure returns (uint256) {
return x.complexCalculation();
}
}
```
### 9. 总结
Library 是 Solidity 中强大的代码复用工具:
1. **使用场景**:工具函数、数据结构扩展、代码复用
2. **部署方式**:internal 函数内联,external 函数需要部署
3. **最佳实践**:
* 优先使用 internal 函数
* 使用 using for 语法提高可读性
* 遵循命名规范
* 利用 OpenZeppelin 等标准库
4. **Gas 优化**:减少代码重复,提高部署效率
服务端 · 3月6日 21:50
Solidity 中的内联汇编(Inline Assembly)如何使用?有哪些注意事项?Solidity 内联汇编允许开发者直接编写 EVM 汇编代码,用于优化 Gas 成本、执行底层操作或访问 Solidity 无法直接提供的功能。
### 1. 基础语法
Solidity 支持两种汇编方言:`assembly { ... }`(推荐)和 `assembly { ... }`。
```solidity
contract AssemblyBasic {
function add(uint256 a, uint256 b) public pure returns (uint256) {
uint256 result;
assembly {
// 使用 mload 和 mstore 操作内存
result := add(a, b) // := 是赋值操作符
}
return result;
}
function getCaller() public view returns (address) {
address caller;
assembly {
caller := caller() // 获取调用者地址
}
return caller;
}
}
```
### 2. 访问变量
汇编代码可以读写 Solidity 变量,但需要注意变量类型和存储位置。
```solidity
contract VariableAccess {
uint256 public storedValue; // storage 变量
function manipulateVariables(uint256 input) public returns (uint256) {
uint256 localVar = 10; // memory 变量
assembly {
// 读取 storage 变量
let stored := sload(storedValue.slot)
// 写入 storage 变量
sstore(storedValue.slot, add(stored, input))
// 修改 memory 变量(注意:需要知道内存位置)
// localVar 在内存中的位置需要计算
}
return storedValue;
}
// 更安全的变量访问方式
function safeAccess(uint256 newValue) public {
assembly {
// 使用 .slot 访问 storage 变量位置
sstore(storedValue.slot, newValue)
// 使用 .offset 访问结构体成员偏移
}
}
}
```
### 3. 内存操作
EVM 内存是一个线性字节数组,需要手动管理。
```solidity
contract MemoryOperations {
function memoryDemo() public pure returns (bytes32) {
bytes32 result;
assembly {
// 在内存位置 0x00 存储 32 字节数据
mstore(0x00, 0x1234567890abcdef)
// 从内存位置 0x00 读取 32 字节
result := mload(0x00)
// 只存储 1 字节(使用 mstore8)
mstore8(0x20, 0xff)
}
return result;
}
function copyMemory(bytes memory data) public pure returns (bytes memory) {
bytes memory result = new bytes(data.length);
assembly {
// 获取 data 的内存位置(跳过 32 字节长度前缀)
let dataPtr := add(data, 0x20)
// 获取 result 的内存位置
let resultPtr := add(result, 0x20)
// 数据长度
let len := mload(data)
// 复制内存
for { let i := 0 } lt(i, len) { i := add(i, 0x20) } {
mstore(add(resultPtr, i), mload(add(dataPtr, i)))
}
}
return result;
}
}
```
### 4. 存储操作
Storage 是永久存储,操作成本很高。
```solidity
contract StorageOperations {
uint256 public value;
mapping(address => uint256) public balances;
function storageDemo(address user) public {
assembly {
// 简单 storage 变量
let slot := value.slot
let current := sload(slot)
sstore(slot, add(current, 1))
// mapping 的 storage 位置计算
// mapping[key] 的位置 = keccak256(key . slot)
mstore(0x00, user)
mstore(0x20, balances.slot)
let mappingSlot := keccak256(0x00, 0x40)
// 读取 mapping 值
let balance := sload(mappingSlot)
// 写入 mapping 值
sstore(mappingSlot, add(balance, 100))
}
}
}
```
### 5. 函数调用和返回
```solidity
contract FunctionCalls {
function externalCall(address target, bytes memory data) public returns (bytes memory) {
bytes memory result;
assembly {
// 获取 data 的长度和指针
let dataLen := mload(data)
let dataPtr := add(data, 0x20)
// 分配返回数据内存
result := mload(0x40) // 获取空闲内存指针
// 调用外部合约
let success := call(
gas(), // 传递所有可用 gas
target, // 目标地址
0, // 发送的 ETH 数量
dataPtr, // 输入数据指针
dataLen, // 输入数据长度
result, // 返回数据指针
0x40 // 返回数据最大长度
)
// 检查调用是否成功
if iszero(success) {
revert(0, 0)
}
// 更新空闲内存指针
mstore(0x40, add(result, 0x60))
}
return result;
}
// 使用 delegatecall
function delegateCall(address target, bytes memory data) public returns (bytes memory) {
bytes memory result;
assembly {
let dataLen := mload(data)
let dataPtr := add(data, 0x20)
result := mload(0x40)
let success := delegatecall(
gas(),
target,
dataPtr,
dataLen,
result,
0x40
)
if iszero(success) {
revert(0, 0)
}
mstore(0x40, add(result, 0x60))
}
return result;
}
}
```
### 6. 条件判断和循环
```solidity
contract ControlFlow {
function findMax(uint256[] memory arr) public pure returns (uint256) {
require(arr.length > 0, "Empty array");
uint256 max;
assembly {
// 数组长度
let len := mload(arr)
// 数组数据开始位置
let dataPtr := add(arr, 0x20)
// 初始最大值设为第一个元素
max := mload(dataPtr)
// 循环遍历
for { let i := 1 } lt(i, len) { i := add(i, 1) } {
// 当前元素位置
let elemPtr := add(dataPtr, mul(i, 0x20))
let elem := mload(elemPtr)
// 如果当前元素更大,更新最大值
if gt(elem, max) {
max := elem
}
}
}
return max;
}
function conditional(uint256 x) public pure returns (uint256) {
uint256 result;
assembly {
// if-else 逻辑
switch gt(x, 10)
case 1 {
result := mul(x, 2)
}
default {
result := add(x, 5)
}
}
return result;
}
}
```
### 7. 实用优化示例
#### Gas 优化的字符串拼接
```solidity
contract StringOptimization {
// Solidity 方式(Gas 较高)
function concatSolidity(string memory a, string memory b)
public
pure
returns (string memory)
{
return string(abi.encodePacked(a, b));
}
// 汇编方式(Gas 更低)
function concatAssembly(string memory a, string memory b)
public
pure
returns (string memory result)
{
assembly {
let aLen := mload(a)
let bLen := mload(b)
let totalLen := add(aLen, bLen)
// 分配内存
result := mload(0x40)
mstore(result, totalLen)
// 复制 a
let aPtr := add(a, 0x20)
let resultPtr := add(result, 0x20)
// 使用 identity 预编译合约进行内存复制(更高效)
// 或者手动复制
for { let i := 0 } lt(i, aLen) { i := add(i, 0x20) } {
mstore(add(resultPtr, i), mload(add(aPtr, i)))
}
// 复制 b
let bPtr := add(b, 0x20)
let bResultPtr := add(resultPtr, aLen)
for { let i := 0 } lt(i, bLen) { i := add(i, 0x20) } {
mstore(add(bResultPtr, i), mload(add(bPtr, i)))
}
// 更新空闲内存指针
mstore(0x40, add(add(resultPtr, totalLen), 0x20))
}
}
}
```
#### 高效的数组操作
```solidity
contract ArrayOptimization {
// 高效的数组元素删除(不保持顺序)
function removeElement(uint256[] storage arr, uint256 index) internal {
require(index < arr.length, "Invalid index");
assembly {
// 获取数组长度位置
let lenSlot := arr.slot
let len := sload(lenSlot)
// 如果不是最后一个元素,用最后一个元素替换
if lt(add(index, 1), len) {
// 计算存储位置
let lastIndex := sub(len, 1)
let indexSlot := add(keccak256(lenSlot, 0x20), index)
let lastSlot := add(keccak256(lenSlot, 0x20), lastIndex)
// 替换
sstore(indexSlot, sload(lastSlot))
}
// 减少长度
sstore(lenSlot, sub(len, 1))
}
}
}
```
### 8. 安全注意事项
#### 危险操作示例
```solidity
contract AssemblyDangers {
// 危险:直接操作 storage 可能导致数据损坏
function dangerousStorageWrite(uint256 slot, uint256 value) public {
assembly {
sstore(slot, value) // 可以写入任意 slot!
}
}
// 危险:内存溢出
function dangerousMemory() public pure {
assembly {
// 写入超出分配范围的内存
mstore(0x1000000, 1) // 可能覆盖其他数据
}
}
// 危险:未检查的外部调用
function dangerousCall(address target) public {
assembly {
let success := call(gas(), target, 0, 0, 0, 0, 0)
// 没有检查 success!
}
}
}
```
#### 安全使用指南
```solidity
contract SafeAssembly {
// 安全:验证输入
function safeStorageWrite(bytes32 slot, uint256 value) public {
// 只允许写入特定 slot
require(slot == keccak256("allowed_slot"), "Invalid slot");
assembly {
sstore(slot, value)
}
}
// 安全:检查内存边界
function safeMemoryOperation(bytes memory data) public pure returns (bytes32) {
require(data.length >= 32, "Data too short");
bytes32 result;
assembly {
result := mload(add(data, 0x20))
}
return result;
}
// 安全:检查外部调用结果
function safeExternalCall(address target, bytes memory data) public returns (bool) {
bool success;
assembly {
let dataLen := mload(data)
let dataPtr := add(data, 0x20)
success := call(
gas(),
target,
0,
dataPtr,
dataLen,
0,
0
)
}
return success;
}
}
```
### 9. 常见使用场景
| 场景 | 汇编优势 | 示例 |
| ------ | ------------ | ------- |
| Gas 优化 | 减少操作码 | 自定义存储布局 |
| 底层操作 | 访问 EVM 特性 | 调用预编译合约 |
| 复杂计算 | 更高效的算法 | 加密操作 |
| 代理合约 | delegatecall | 可升级合约 |
| 数据编码 | 自定义序列化 | 紧凑存储 |
### 10. 最佳实践
1. **仅在必要时使用汇编**:Solidity 编译器已经很优化
2. **充分测试**:汇编代码难以调试,需要更多测试
3. **添加详细注释**:汇编代码可读性差,需要解释
4. **使用常量**:避免魔法数字
5. **验证所有输入**:防止意外行为
6. **考虑使用库**:将汇编封装在库中复用
7. **审计必不可少**:汇编代码必须经过专业审计
```solidity
library SafeAssembly {
// 将汇编操作封装在库中
function safeDelegateCall(address target, bytes memory data)
internal
returns (bool success, bytes memory result)
{
// 实现...
}
}
```
服务端 · 3月6日 21:50