Ethereum Storage(1)

利用web3读取storage

参考文章
参考
wiki部分

合约

看完wiki在网上找了个简单例子。

合约如下,地址为0xB70D357314CA537E275E1F5891C35026Bf61ad25

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
pragma solidity ^0.4.0;

contract testStorage {

uint storeduint1 = 15;
uint constant constuint = 16;
uint128 investmentsLimit = 17055;
uint32 investmentsDeadlineTimeStamp = uint32(now);

bytes16 string1 = 'test1';
bytes32 string2 = 'test1236';
string string3 = 'lets string something';

mapping (address => uint) uints1;
mapping (address => DeviceData) structs1;

uint[] uintarray;
DeviceData[] deviceDataArray;

struct DeviceData {
string deviceBrand;
string deviceYear;
string batteryWearLevel;
}

function testStorage() {
address address1 = 0xbccc714d56bc0da0fd33d96d2a87b680dd6d0df6;
address address2 = 0xaee905fdd3ed851e48d22059575b9f4245a82b04;

uints1[address1] = 88;
uints1[address2] = 99;

var dev1 = DeviceData('deviceBrand', 'deviceYear', 'wearLevel');
var dev2 = DeviceData('deviceBrand2', 'deviceYear2', 'wearLevel2');

structs1[address1] = dev1;
structs1[address2] = dev2;

uintarray.push(8000);
uintarray.push(9000);

deviceDataArray.push(dev1);
deviceDataArray.push(dev2);
}
}

这份代码里一共有10个变量,首先要注意的是,被标记为constant的变量是不会存在于storage的,他在代码中出现的地方会在编译时被编译器替换上。

分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
第一个变量为uint 单独占用一个slot
slot 0 0x000000000000000000000000000000000000000000000000000000000000000f
第二变量为constant 不会放在storage,可参考上述文章。

第三个变量uint128和第四个变量uint32会放在一个slot里面,因为如果 bytes 和 string 的数据很短,那么它们的长度也会和数据一起存储到同一个插槽。(如果一个slot就能装下的话)
slot 1 0x00000000000000000000000062f1f5880000000000000000000000000000429f

后面两个变量是bytes16和bytes32,都是直接占用一个slot(高位对齐)
slot 2 0x0000000000000000000000000000000074657374310000000000000000000000
slot 3 0x7465737431323336000000000000000000000000000000000000000000000000

然后是string变量,数据长度小于等于 31 字节, 则它存储在高位字节(左对齐),最低位字节存储 length * 2
slot 4 0x6c65747320737472696e6720736f6d657468696e67000000000000000000002a

然后是两个映射类型变量,都会占据一个slot但是真正的起始位置为keccak256(k . p),这里k,p就是键和键值。
slot 5 0x0000000000000000000000000000000000000000000000000000000000000000
slot 6 0x0000000000000000000000000000000000000000000000000000000000000000

然后是一个uint256数组,对于形如 uint[] b; 的动态数组,其同样会占用对应位置 p 处的插槽,用以储存数组的长度,而数组真正的起始点会位于 keccak256(p) 处.
这里由于在构造函数中先push了两个数所以长度初始就是2.
slot 7 0x0000000000000000000000000000000000000000000000000000000000000002

最后是一个结构体数组,存储方式和动态数组是一样的,无非就是每个结构体含有多个变量,所以数组里的一个索引的值会占用多个slot。
在构造函数中也给结构体数组push了两个元素,所以初始长度为2。
slot 8 0x0000000000000000000000000000000000000000000000000000000000000002

计算

所以如何计算数组或者映射的起始位置呢。这里可以使用合约也可以使用web3.js或者python下的web3.

  • 使用如下合约
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    pragma solidity ^0.4.24;
    contract calc{
    function read_slot(uint k) public view returns (bytes32 res) {
    assembly { res := sload(k) }
    }

    function cal_addr(uint k, uint p) public pure returns(bytes32 res) {
    res = keccak256(keccak256(abi.encodePacked(k, p)));
    }

    function calc_1(uint k, uint p) public pure returns(bytes32 res) {
    res = keccak256(abi.encodePacked(k, p));
    }

    function cal_addr(uint p) public pure returns(bytes32 res) {
    res = keccak256(abi.encodePacked(p));
    }
    function cal_uint(uint p) public pure returns(bytes32 res) {
    res = keccak256(p);
    }
    }
    调用函数可以获取uints1[address1] = 0xafef6be2b419f4d69d56fe34788202bf06650015554457a2470181981bcce7ef

  • 使用web3.js
1
2
3
4
5
6
7
8
var Web3 = require('web3');
var web3 = new Web3(HTTPProvider(your rpc));

index = '0000000000000000000000000000000000000000000000000000000000000005'
key = '000000000000000000000000bccc714d56bc0da0fd33d96d2a87b680dd6d0df6'
newKey = web3.utils.sha3(key + index, {"encoding":"hex"})

console.log(newKey);

也能得到相同的值。

  • 使用python下web3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from Crypto.Util.number import bytes_to_long
from web3 import Web3,HTTPProvider
from eth_abi import encode_abi
from eth_utils import keccak

def bytesTohex(data):
return hex(bytes_to_long(data))

index = '0000000000000000000000000000000000000000000000000000000000000005'
key = '000000000000000000000000bccc714d56bc0da0fd33d96d2a87b680dd6d0df6'
contract_address = "0xB70D357314CA537E275E1F5891C35026Bf61ad25"

slot5 = Web3.keccak(encode_abi(['address','uint'], ['0xbccc714d56bc0da0fd33d96d2a87b680dd6d0df6',0x05]))
slot7 = keccak(encode_abi(['uint'], [0x07]))
slot8 = keccak(encode_abi(['uint'], [0x08]))
print(bytesTohex(slot5))
print(bytesTohex(slot7))
print(bytesTohex(slot8))

所以可以计算得到

1
2
3
4
5
6
对于uints1[address1]位置在0xafef6be2b419f4d69d56fe34788202bf06650015554457a2470181981bcce7ef
uints1[address2]位置在0xb01ced36af2255129ceb004046ab791f2c883dd74d458026d7b81c7aaff3566b
structs1[address1]位置在0x720c187f2880b2567f9fccc279625ea13024b8b82a6f73e26d9ca6d82ede1cc5
structs1[address2]位置在0x0dd425230ba48d6e0641f6db78455be91ddb278d66cd6b80d80fdf73dde88f45
uintarray的第一个元素的位置在0xa66cc928b5edb82af9bd49922954155ab7b0942694bea4ce44661d9a8736c688
deviceDataArray的第一个位置在0xf3f7a9fe364faab93b216da50a3214154f22a0a2b415b23a84c8169e8b636ee3

读取storage

利用代码,getStorageAt第二个参数就是上面计算得到的slot索引值

1
2
3
4
5
6
7
8
9
10
11
12
13
var Web3 = require('web3');
// 建立web3物件
var web3 = new Web3();
// 連線到 ropsten 測試節點

contract_address = "0xB70D357314CA537E275E1F5891C35026Bf61ad25"
const getStorage = (async()=>{
for(var i = 0 ; i<10 ;i++){
// console.log(i)
await web3.eth.getStorageAt(contract_address, i).then(console.log)
}
})
getStorage();

或者用python都行。

1
data1 = w3.eth.getStorageAt(contract_address,slot5)

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!