
在学习和使用 Solidity 时,很多人第一次接触 library 的时候,都会遇到这样的报错信息:
TypeError: Name has to refer to a user-defined type为什么会报这个错?为什么库函数经常被设计为使用 storage 引用?现在我们就通过一个实验来展示 storage 与 memory 的实际区别。
Name has to refer to a user-defined type在 Solidity 中,library 有两种典型的使用方式:
using ... for 给类型扩展方法第二种方式是大家常用的,例如:
struct Data { uint value; }
library DataLib {
function increment(Data storage self) public {
self.value += 1;
}
}
contract C {
using DataLib for Data;
Data public d;
function test() public {
d.increment(); // 自动传入 d
}
}但是,如果你写成:
library DataLib {
function increment(uint storage self) public {
self += 1;
}
}编译时就会报错:
TypeError: Name has to refer to a user-defined type原因在于:通过 using ... for 的库函数,第一个参数必须是用户自定义类型(struct 或 enum),而不能是 uint、address 等内置类型。
storage 引用?在 Solidity 中,参数有三种数据位置:
storage —— 持久化存储在区块链上的数据memory —— 函数调用过程中的临时内存calldata —— 外部调用时的只读数据区如果库函数参数写成 memory:
function increment(Data memory self) public {
self.value += 1;
}调用时会复制一份数据,函数里对副本的修改不会影响合约状态。
而如果使用 storage:
function increment(Data storage self) public {
self.value += 1; // 直接修改合约里的状态
}那么修改会直接作用在合约的存储上,符合大多数“扩展方法”的预期。
好处:
storage vs memory下面我们通过一个小实验来直观感受差异:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
struct Counter {
uint count;
}
library CounterLib {
// 使用 storage,能修改合约状态
function incStorage(Counter storage self) public {
self.count += 1;
}
// 使用 memory,只会修改副本,不影响状态
function incMemory(
Counter memory self
) public pure returns (Counter memory) {
self.count += 1;
return self;
}
}
contract C {
using CounterLib for Counter;
Counter public counter;
// 调用 storage 版本
function callStorage() public {
counter.incStorage();
}
// 调用 memory 版本
function callMemory() public view {
Counter memory temp = counter;
temp = temp.incMemory(); // 只改了副本
// counter 本身并没有被改变
}
function getCount() public view returns (uint) {
return counter.count;
}
}使用Foundry编写测试脚本 Counter.t.sol:
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import {Test} from "forge-std/Test.sol";
import "../src/Counter.sol";
contract CTest is Test {
C public counter;
function setUp() public {
counter = new C();
}
function testIncStorage() public {
counter.callStorage();
assertEq(counter.getCount(), 1);
}
function testIncMemory() public view {
counter.callMemory();
assertEq(counter.getCount(), 0);
}
}执行测试脚本:
➜ tutorial git:(main) ✗ forge test --match-path test/Counter.t.sol -vvv
[⠊] Compiling...
[⠔] Compiling 1 files with Solc 0.8.30
[⠒] Solc 0.8.30 finished in 430.02ms
Compiler run successful!
Ran 2 tests for test/Counter.t.sol:CTest
[PASS] testIncMemory() (gas: 13501)
[PASS] testIncStorage() (gas: 32255)
Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 3.19ms (1.03ms CPU time)
Ran 1 test suite in 155.29ms (3.19ms CPU time): 2 tests passed, 0 failed, 0 skipped (2 total tests)library 扩展函数的第一个参数必须是用户自定义类型(struct 或 enum),否则会报错 “Name has to refer to a user-defined type”。storage:storage 允许函数直接修改合约状态storage 参数:修改持久化状态memory 参数:只修改副本,不会影响状态所以在设计库函数时,如果你希望修改合约的状态变量,必须使用 storage 引用;如果只是做临时计算,可以用 memory 或 calldata。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。