在看這篇文章之前必須要知道一件事,那就是透過智能合約存在區塊鏈上的資訊都是公開的。
不管你是否有在智能合約上面有用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'
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