Solidity 合约分析:3. Iterable Mapping
介绍
你好,我是 Pluveto。我发现多数 Solidity 的教程都是从语法开始讲起,但是我觉得这样不够直观,而且很消耗耐心。这是一个系列文章,我会在这个系列里分析一些简单的 Solidity 合约。在例子中学习。
希望你能学到东西,让我们开始吧!
代码
来自:Iterable Mapping | Solidity by Example | 0.8.20
1// SPDX-License-Identifier: MIT
2pragma solidity ^0.8.20;
3
4library IterableMapping {
5 // Iterable mapping from address to uint;
6 struct Map {
7 address[] keys;
8 mapping(address => uint) values;
9 mapping(address => uint) indexOf;
10 mapping(address => bool) inserted;
11 }
12
13 function get(Map storage map, address key) public view returns (uint) {
14 return map.values[key];
15 }
16
17 function getKeyAtIndex(Map storage map, uint index) public view returns (address) {
18 return map.keys[index];
19 }
20
21 function size(Map storage map) public view returns (uint) {
22 return map.keys.length;
23 }
24
25 function set(Map storage map, address key, uint val) public {
26 if (map.inserted[key]) {
27 map.values[key] = val;
28 } else {
29 map.inserted[key] = true;
30 map.values[key] = val;
31 map.indexOf[key] = map.keys.length;
32 map.keys.push(key);
33 }
34 }
35
36 function remove(Map storage map, address key) public {
37 if (!map.inserted[key]) {
38 return;
39 }
40
41 delete map.inserted[key];
42 delete map.values[key];
43
44 uint index = map.indexOf[key];
45 address lastKey = map.keys[map.keys.length - 1];
46
47 map.indexOf[lastKey] = index;
48 delete map.indexOf[key];
49
50 map.keys[index] = lastKey;
51 map.keys.pop();
52 }
53}
54
55contract TestIterableMap {
56 using IterableMapping for IterableMapping.Map;
57
58 IterableMapping.Map private map;
59
60 function testIterableMap() public {
61 map.set(address(0), 0);
62 map.set(address(1), 100);
63 map.set(address(2), 200); // insert
64 map.set(address(2), 200); // update
65 map.set(address(3), 300);
66
67 for (uint i = 0; i < map.size(); i++) {
68 address key = map.getKeyAtIndex(i);
69
70 assert(map.get(key) == i * 100);
71 }
72
73 map.remove(address(1));
74
75 // keys = [address(0), address(3), address(2)]
76 assert(map.size() == 3);
77 assert(map.getKeyAtIndex(0) == address(0));
78 assert(map.getKeyAtIndex(1) == address(3));
79 assert(map.getKeyAtIndex(2) == address(2));
80 }
81}
解释
由于原生的 Solidity 并不支持 Mapping 迭代,所以我们需要自己实现。
library 关键字
library 关键字在 Solidity 中用于定义库合约 (library contract)。
库合约与普通合约有以下不同:
-
库合约使用 library 关键字定义,普通合约使用 contract。
-
库合约中的函数及变量默认为内部的 (internal), 不能直接在交易或消息中直接访问。
-
需要通过其他合约使用 library 把库合约链接进来,以使调用库中的函数。
-
链接库合约后,库中的函数就可以像普通合约内部函数一样调用。
例如:
1library Math {
2 function sqrt(uint x) internal pure returns (uint y) {
3 // ...
4 }
5}
6
7contract C {
8 using Math for uint;
9 function f(uint x) public pure returns (uint) {
10 return x.sqrt();
11 }
12}
库合约可以让公共功能共享,提高代码可重用性。
IterableMapping 库中:
-
keys 是一个动态数组,用于存储所有的 key。
-
values 是一个 mapping,用于存储 key 对应的 value。
-
indexOf 是一个 mapping,用于存储 key 在 keys 中的索引。
-
inserted 是一个 mapping,用于存储 key 是否已经插入。
从而可以实现如下功能:
-
get: 通过 key 获取 value。直接查询 values。
-
getKeyAtIndex: 通过索引获取 key。直接查询 keys。
-
size: 获取 keys 的长度。直接查询 keys 的长度。
-
set: 设置 key 对应的 value。如果 key 已经存在,则更新 value;如果 key 不存在,则插入 key。
-
remove: 删除 key。如果 key 不存在,则不做任何操作。
-
对于 map,删除语法是
delete map[key]
。 -
对于动态数组,删除分为下面的步骤:
-
先获得 lastKey,即 keys 的最后一个元素。
-
把 lastKey 移动到 key 的位置。
-
删除最后一个元素:
keys.pop()
。
-