如何查看透過智能合約存在以太坊上的資料?

Posted by Kubeguts on 2019-02-10

在看這篇文章之前必須要知道一件事,那就是透過智能合約存在區塊鏈上的資訊都是公開的

不管你是否有在智能合約上面有用requre(), revert(), assert()等等來限制某地址的人來存取智能合約上的變數,但這些變數都會存以太坊EVM的Storage上面,每個節點都可以透過以下方式來對EVM的Storage內的編碼進行解析,並還原當初透過合約所儲存的變數資料!!!

以下透過一個範例來瞭解如何透過web3.js library中的eth.getStorage()方法來解析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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
prama solidity ^0.4.0;

contract testStorage {

uint storeduint1 = 15;
uint constant constuint = 16;
uint128 investmentsLimit = 17055;
uint32 investmentsDeadlinedTimeStamp = 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[addresss1] = dev1;
structs1[addresss2] = dev2;

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

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

假設我們合約的地址是在:0xf1f5896ace3a78c347eb7eab503450bc93bd0c3b

所有儲存在storage內的參數都可以用index來索引,index的格式為256 bytes以及對應64碼。
在以上的範例中我們有 10個參數,所以可以透過index來一一遍歷他們:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let contractAddress = '0xf1f5896ace3a78c347eb7eab503450bc93bd0c3b'
for (index = 0; index < 10; index++){
console.log(`[${index}]` +
web3.eth.getStorageAt(contractAddress, index))
}
result:
[0] 0x000000000000000000000000000000000000000000000000000000000000000f
[1] 0x00000000000000000000000059b92d9a0000000000000000000000000000429f
[2] 0x0000000000000000000000000000000074657374310000000000000000000000
[3] 0x7465737431323336000000000000000000000000000000000000000000000000
[4] 0x6c65747320737472696e6720736f6d657468696e67000000000000000000002a
[5] 0x0000000000000000000000000000000000000000000000000000000000000000
[6] 0x0000000000000000000000000000000000000000000000000000000000000000
[7] 0x0000000000000000000000000000000000000000000000000000000000000002
[8] 0x0000000000000000000000000000000000000000000000000000000000000002
[9] 0x0000000000000000000000000000000000000000000000000000000000000000

開始針對合約內的10個變數一一做遍歷和解析

index 0 - storeduint1

1
2
3
4
5
6
7
let contractAddress = '0xf1f5896ace3a78c347eb7eab503450bc93bd0c3b'
let index = 0
console.log(web3.eth.getStorageAt(contractAddress, index))
console.log('DEC:' + web3.toDecimal(web3.eth.getStorageAt(contractAddress, index)))
result:
0x000000000000000000000000000000000000000000000000000000000000000f
DEC:15

constuint

Constants(常數) 不會被存在storage內,只能夠透過code來存取

index 1 - investmentsLimit, investmentsDeadlineTimeStamp

1
2
3
4
5
let index = 1
console.log(web3.eth.getStorageAt(contractAddress, index))
result:
0x00000000000000000000000059b92d9a0000000000000000000000000000429f
DEC: 1505308058 and 17055

在index 1中,會將2個變數(investmentsLimit, investmentsDeadlineTimeStamp)做合併來最佳化storage的存放

index 2 - string1

1
2
3
4
5
6
7
index = 2
console.log(web3.eth.getStorageAt(contractAddress, index))
console.log('ASCII: ' +
web3.toAscii(web3.eth.getStorageAt(contractAddress, index)))
result:
0x0000000000000000000000000000000074657374310000000000000000000000
ASCII: test1

index 3 - string2

1
2
3
4
5
6
7
index = 3
console.log(web3.eth.getStorageAt(contractAddress, index))
console.log('ASCII: ' +
web3.toAscii(web3.eth.getStorageAt(contractAddress, index)))
result:
0x7465737431323336000000000000000000000000000000000000000000000000
ASCII: test1236

index 4 - string3

1
2
3
4
5
6
7
index = 4
console.log(web3.eth.getStorageAt(contractAddress, index))
console.log('ASCII: ' +
web3.toAscii(web3.eth.getStorageAt(contractAddress, index)))
result:
0x6c65747320737472696e6720736f6d657468696e67000000000000000000002a
ASCII: lets string something * (42)

在result最後有 2a(dec42)為 string儲存的長度
可參閱solidity官方文件描述變數在storage中呈現的格式:https://solidity.readthedocs.io/en/latest/miscellaneous.html#layout-of-state-variables-in-storage

index 5 — uints1

1
2
3
4
5
index = 5
console.log(web3.eth.getStorageAt(contractAddress, index))
result:
0x0000000000000000000000000000000000000000000000000000000000000000
PROBLEM!!!!

若遇到 mapping struct宣告的變數,無法直接透過一般的index來取得struct內所儲存的值,必須得曉得對應value中的 key值,否則是不可能取得的。

1
2
3
4
/* 要取回在 uints1 struct所儲存在addresss1的值 :88
// uints1[address1] = 88;

address1 = 0xbccc714d56bc0da0fd33d96d2a87b680dd6d0df6
1
2
3
4
5
6
7
8
9
10
11
index = '0000000000000000000000000000000000000000000000000000000000000005'

// convert address1 to the 256 bytes...
key = '00000000000000000000000xbccc714d56bc0da0fd33d96d2a87b680dd6d0df6'

let newKey = web3.sha3(key + index, {"encoding":"hex"})
console.log(web3.eth.getStorageAt(contractAddress, newKey))
console.log('DEC: ' + web3.toDecimal(web3.eth.getStorageAt(contractAddress, newKey)))
result:
0x0000000000000000000000000000000000000000000000000000000000000058
DEC: 88

index6 - structs1

1
2
3
4
5
6
7
8
9
index = "0000000000000000000000000000000000000000000000000000000000000006"
key = "00000000000000000000000xbccc714d56bc0da0fd33d96d2a87b680dd6d0df6"
let newKey = web3.sha3(key + index, {"encoding":"hex"})
console.log(web3.eth.getStorageAt(contractAddress, newKey))
console.log('ASCII: ' +
web3.toAscii(web3.eth.getStorageAt(contractAddress, newKey)))
result:
0x6465766963654272616e64000000000000000000000000000000000000000016
ASCII: deviceBrand

若要讀取下一個struct的值,只需要將newKey的值+1即可取到。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function increaseHexByOne(hex) {
let x = new BigNumber(hex)
let sum = x.add(1)
let result = '0x' + sum.toString(16)
return result
}
index = "0000000000000000000000000000000000000000000000000000000000000006"
key = "00000000000000000000000xbccc714d56bc0da0fd33d96d2a87b680dd6d0df6"
let newKey = increaseHexByOne(
web3.sha3(key + index, {"encoding":"hex"}))
console.log(web3.eth.getStorageAt(contractAddress,newKey))
console.log('ASCII: ' +
web3.toAscii(web3.eth.getStorageAt(contractAddress, newKey)))
result:
0x6465766963655965617200000000000000000000000000000000000000000014
ASCII: deviceYear

若還需要第三個之後的值,繼續將newKey遞增,就能夠取到。

index 7 - uintarray

1
2
3
4
5
index = "7"
console.log(web3.eth.getStorageAt(contractAddress, index))

result:
0x0000000000000000000000000000000000000000000000000000000000000002

以上的result表示 array目前有2個值 (8000, 9000)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
index = "0000000000000000000000000000000000000000000000000000000000000007"
let newKey = web3.sha3(index, {"encoding":"hex"})
console.log(web3.eth.getStorageAt(contractAddress, newKey))
console.log('DEC: ' +
web3.toDecimal(web3.eth.getStorageAt(contractAddress, newKey)))
result:
0x0000000000000000000000000000000000000000000000000000000000001f40
DEC: 8000

---

newKey = increaseHexByOne(web3.sha3(index, {"encoding":"hex"}))
console.log(web3.eth.getStorageAt(contractAddress, newKey))
console.log('DEC: ' +
web3.toDecimal(web3.eth.getStorageAt(contractAddress, newKey)))
result:
0x0000000000000000000000000000000000000000000000000000000000002328
DEC: 9000

index 8 - deviceDataArray

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

index = "0000000000000000000000000000000000000000000000000000000000000008"
let newKey = web3.sha3(index, {"encoding":"hex"})
console.log(web3.eth.getStorageAt(contractAddress, newKey))
console.log('ASCII: ' +
web3.toAscii(web3.eth.getStorageAt(contractAddress, newKey)))
result:
0x6465766963654272616e64000000000000000000000000000000000000000016
ASCII: deviceBrand

---

index = "0000000000000000000000000000000000000000000000000000000000000008"
let newKey = increaseHexByOne(web3.sha3(index, {"encoding":"hex"}))
console.log(web3.eth.getStorageAt(contractAddress, newKey))
console.log('ASCII: ' +
web3.toAscii(web3.eth.getStorageAt(contractAddress, newKey)))
result:
0x6465766963655965617200000000000000000000000000000000000000000014
ASCII: deviceYear

繼續增加index的值(+1), result:

1
2
3
4
5
6
7
8
index = "0000000000000000000000000000000000000000000000000000000000000008"
let newKey = increaseHexByTwo(web3.sha3(index, {"encoding":"hex"}))
console.log(web3.eth.getStorageAt(contractAddress, newKey))
console.log('ASCII: ' +
web3.toAscii(web3.eth.getStorageAt(contractAddress, newKey)))
result:
0x776561724c6576656c0000000000000000000000000000000000000000000012
ASCII: wearLevel

若加3之後,result會顯示第二組(dev2) 存進array的值

1
2
3
4
result:
0x6465766963654272616e64320000000000000000000000000000000000000018

ASCII: deviceBrand2

Reference

How to read Ethereum contract storage - Darius

https://medium.com/aigang-network/how-to-read-ethereum-contract-storage-44252c8af925