映射 Mapping

映射类型提供了键到值的数据存取能力。一个映射中,通过提供一个特定的键,可以存取与这个键相对应的值。

Solidity 这中,映射以 mapping(键的类型 => 值的类型) 的形式声明。比如, mapping(address => uint) 声明了一个地址uint 的映射。映射中,键的类型不可以是动态数组合约枚举结构体映射本身,其他类型则没有问题;值的类型没有以上限制,可以是包括映射在内的任何类型。此外,与其他语言的类似容器不同,Solidity 的映射不支持遍历,如果需要,可以寻找第三方的库,或手动维护一个数组来实现遍历的可能。

映射只允许作为状态变量,不过它也可以作为 storage 上的引用类型出现在合约的内部函数里。

与其他类型一样,通过用 public 可见性修饰符修饰,Solidity 会自动生成该映射的 getter 函数。最简单的情况下,没有数组和映射的嵌套, getter 函数需要一个参数作为键,返回对应的值。否则,getter 所需的参数会递归地为每一个键和索引而增加。

映射在 storage 中的存储形式

动态数组类似,映射storage 中的存储方式也很特殊。映射 在初始化时也会在 storage 的某位置 $p$ 占有一个槽位。但与动态数组不同的是,此槽位仅仅用来区分不同的映射,操作映射类型并不会用到此处的值。对于映射 $m$ 中每一个键 $k_x$,都可以通过函数 $f(p, k_x) = keccak256(k . p)$ 得到一个 32 字节长的哈希值。此哈希值便是映射 $m$ 中,键 $k_x$ 所对应的值在 storage 中的地址。同数组一样,由于碰撞的可能性实在太小, 因此不会检测此地址是否发生碰撞。

由于这种设计,Solidity 的映射并不能自行实现遍历。 Solidity 的映射没有键是否被被使用的概念,也没有存储"设置"过的键,直接将键所对应的值零散地存放在整个 Storage 中,即使可以遍历其中存放的所有的值,也无法判断那些值属于哪里。不过这个问题技术上并不难解决,声明一个用于记录目标映射的数组,在向映射插入值的同时,把对应的键同时记入该数组中。需要遍历映射的时候,用该数组中所存的键即可。

如何在geth中找到mapping的存储位置

注意:

  • 在数组中,key就是key
  • mapping中,pos是合约存储时数据定义的位置(pos是存储槽位置)。

因此, sha3(key+pos) 是mapping真正的key。geth中查找还需要转换为bigNumber然后hex格式化

> key
"00000000000000000000000046fb9a22689c4a4bfb494baeafbb8b2993725305"

> pos
"0000000000000000000000000000000000000000000000000000000000000001"


> bn=web3.sha3(key + pos, {"encoding":"hex"})
"0x4a6915a70ddb253ab9075c26d94720491095a5a0a6d31c6720a4db10b12f661e"

> sn=web3.toBigNumber(bn)
3.3656819177407030101749625369691302081266253965792325840314023187955028289054e+76

> eth.getStorageAt(con.address,sn)
"0x0000000000000000000000000000000000000000000000000000000000001edc"

results matching ""

    No results matching ""