コントラクトのストレージを解剖してみよう!
はじめに
初めまして。
CryptoGamesというブロックチェーンゲーム企業でエンジニアをしている cardene(かるでね) です!
スマートコントラクトを書いたり、フロントエンド・バックエンド・インフラと幅広く触れています。
代表的なゲームはクリプトスペルズというブロックチェーンゲームです。
以下でも情報発信しているので、興味ある記事があればぜひ読んでみてください!
今回はコントラクトのストレージに格納されているデータに直接アクセスして、コントラクトのどこにどのようにデータが保存されているかを見ていきます。
実際に手を動かしながら確認できるので、実行しながら理解を深めていきましょう!
今回の記事ではRemixというブラウザ上で動くエディタを使用していきます。
ストレージ
概要
まずはコントラクトのストレージについての理解を深めていきましょう。
コントラクトにはデータの保管場所が3つあります。
保管場所 | 特徴 | ガス代消費 |
---|---|---|
storage |
コントラクト(ブロックチェーン)にデータを永続的に書き込みます。どこからでもデータの取得が可能になります。 | 多い |
memory |
コントラクト内の関数実行開始から完了までの間アクセスできる、一時的なデータ保管場所です。関数内からのみアクセス可能です。 | 普通 |
calldata |
memory のように振る舞い、データの更新ができません。 |
少ない |
今回は上記のstorage
に焦点を当て、どのようにデータが保存されているのかを見ていきます。
スロット
ストレージ内のデータは、slot
と呼ばれる単位で保管されています。
1slot
は32byteで構成され、最大
string
やuint256
などのデータは32byteであるため、1slot
ずつデータが保存されていきます。
それ以下のサイズのデータは上記のように同じslot
内に格納される場合があります。
この時注意しないといけないのが、変数などの定義順によってはデータの保管効率が悪くなります。
上記の画像だと、コントラクト内で以下のような定義になっています。
string name;
uint8 income;
uint8 age;
string description;
address walletAddress;
bool enginner;
bool creator;
ちなみにslot
には定義した値が右詰めで格納されています。
では、以下のような定義だとどうなるでしょうか?
string name;
string description;
address walletAddress;
uint8 income;
uint8 age;
bool enginner;
bool creator;
上記のように4つのslot
を使用しています。
これでは余分にstorageを使用しているため、ガス代が余計にかかってしまいます。
このように、storageにどのようにしてデータを格納するかは、ガス代の削減につながるので意識しておくと良いです。
実際にstorageに格納されているデータは、上記のように16進数になっています。
ストレージのまとめ
この章ではストレージについての理解を深めてきました。
コントラクトでのデータ保管方法を意識してコントラクトの作成までできると、1段エンジニアとしてステップアップできるはずです。
では、次の章から実際にストレージの中身を見ていきましょう。
コントラクトの作成&デプロイ
では、早速今回使用するコントラクトを紹介していきます。
Remixを使い慣れていない方は以下の記事などを参考にしてください。
コントラクトの作成
Remixに以下のコードを貼り付けてください。
// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;
contract Storage {
string public name; // slot 0
string public description; // slot 1
uint256 public version; // slot 2
uint256[2] public timestampArry; // slot 3 ~ 4
address public admin; // slot 5
address public owner; // slot 6
uint128 private cardNumber; // slot 7
uint128 private cardPassword; // slot 7
struct Profile {
string name;
uint256 age;
uint256 createdAt;
}
Profile public profile; // slot 8~10
Profile[] profiles; // slot 11
mapping(uint256 => string) public addressName; // slot 12
constructor() {
name = "Storage Contract";
description = "Checl contract sotrage";
version = 1;
timestampArry[0] = block.timestamp;
timestampArry[1] = block.timestamp;
profile = Profile("cardene", 100, block.timestamp);
admin = msg.sender;
owner = msg.sender;
profiles.push(Profile("Alice", 30, block.timestamp));
profiles.push(Profile("Bob", 25, block.timestamp));
addressName[7] = "cardene";
addressName[10] = "test";
}
function registerCard(uint128 cardNumber_, uint128 cardPassword_) public {
cardNumber = cardNumber_;
cardPassword = cardPassword_;
}
function getRegisterCard() public view returns (uint128, uint128) {
return (cardNumber, cardPassword);
}
}
コントラクトの説明
先ほど作成したコントラクトについて簡単に説明します。
string public name; // slot 0
string public description; // slot 1
uint256 public version; // slot 2
uint256[2] public timestampArry; // slot 3 ~ 4
address public admin; // slot 5
address public owner; // slot 6
uint128 private cardNumber; // slot 7
uint128 private cardPassword; // slot 7
struct Profile {
string name;
uint256 age;
uint256 createdAt;
}
Profile public profile; // slot 8~10
Profile[] profiles; // slot 11
mapping(address => string) public addressName; // slot 12
上記部分では変数などをいろいろ定義して、ストレージにデータを格納しています。
「slot 〇〇」というのは、各データがどのストレージスロットに格納されているかを示しています。
constructor() {
name = "Storage Contract";
description = "Checl contract sotrage";
version = 1;
timestampArry[0] = block.timestamp;
timestampArry[1] = block.timestamp;
profile = Profile("cardene", 100, block.timestamp);
admin = msg.sender;
owner = msg.sender;
profiles.push(Profile("Alice", 30, block.timestamp));
profiles.push(Profile("Bob", 25, block.timestamp));
addressName[7] = "cardene";
addressName[10] = "test";
}
constructor
はコントラクトをデプロイするときに一度だけ実行される関数です。
このconstructor
でいろいろ値を格納しています。
function registerCard(uint128 cardNumber_, uint128 cardPassword_) public {
cardNumber = cardNumber_;
cardPassword = cardPassword_;
}
function getRegisterCard() public view returns (uint128, uint128) {
return (cardNumber, cardPassword);
}
mappingや配列にデータを格納・取得するための関数です。
コントラクトのデプロイ
ここまでできたら、コントラクトのコンパイルとデプロイをしていきましょう。
デプロイまでできたらOKです!
slot 0
まずはslot0
に保存されているデータから見ていきましょう。
デプロイしたコントラクトのアドレスを以下の「0xB57ee0797C3fc0205714a577c02F7205bB89dF30
」の部分にコピペしてください。
web3.eth.getStorageAt("0xB57ee0797C3fc0205714a577c02F7205bB89dF30", 0)
これは「コントラクトアドレス0xB57ee0797C3fc0205714a577c02F7205bB89dF30
のstorage領域内のslot0
のデータを取得する」という処理を実行します。
では早速実行していきましょう!
上記のように、Remix内のTerminalと呼ばれる部分に先ほどのコマンドをコピペして実行してください。
そうすると以下のような16進数データが出力されます。
0x53746f7261676520436f6e747261637400000000000000000000000000000020
では、次に以下のコマンドを実行します。
web3.utils.hexToUtf8("0x53746f7261676520436f6e747261637400000000000000000000000000000020")
上記は16進数文字列をUTF-8形式の文字列に変換する処理を実行します。
Storage Contract
上記のように出力されたはずです。
これでコントラクトのstorageに直接アクセスしてデータを取得できました。
この調子で他のデータにもアクセスしていきましょう。
slot 1
~slot 6
slot1
~slot6
まではほとんど同じであるため一気にデータを取得してみましょう。
slot1
web3.eth.getStorageAt("0x838F9b8228a5C95a7c431bcDAb58E289f5D2A4DC", 1)
# 0x436865636c20636f6e747261637420736f74726167650000000000000000002c
web3.utils.hexToUtf8("0x436865636c20636f6e747261637420736f74726167650000000000000000002c")
# Checl contract sotrage
slot2
web3.eth.getStorageAt("0x838F9b8228a5C95a7c431bcDAb58E289f5D2A4DC", 2)
# 0x01
web3.utils.toBN(0x01).toString();
# 1
今回は数値データであったため、16進数から数値データに変換しています。
slot3
・slot4
# slot3
web3.eth.getStorageAt("0x838F9b8228a5C95a7c431bcDAb58E289f5D2A4DC", 3)
# 0x66517164
web3.utils.toBN(0x66517164).toString();
# 1716613476
# slot4
web3.eth.getStorageAt("0x838F9b8228a5C95a7c431bcDAb58E289f5D2A4DC", 4)
# 0x66517164
web3.utils.toBN(0x66517164).toString();
# 1716613476
slot3
とslot4
は以下のコードの部分で、固定長の配列の要素です。
uint256[2] public timestampArry; // slot 3 ~ 4
固定長の配列は事前に確保する領域がわかるため、このように連続したslot
に格納されています。
では固定長ではない配列の場合はどうなるのでしょうか?
これについては後ほど解説していきます。
slot5
web3.eth.getStorageAt("0x838F9b8228a5C95a7c431bcDAb58E289f5D2A4DC", 5)
# 0x5b38da6a701c568545dcfcb03fcb875f56beddc4
これはウォレットアドレスなので変換が不要です。
slot6
web3.eth.getStorageAt("0x838F9b8228a5C95a7c431bcDAb58E289f5D2A4DC", 6)
# 0x5b38da6a701c568545dcfcb03fcb875f56beddc4
privateデータにアクセス
slot7
には、コントラクト内の以下のデータが格納されています。
uint128 private cardNumber; // slot 7
uint128 private cardPassword; // slot 7
まずはこの変数にデータを格納する必要があります。
上記のように、以下のデータを左にコピペして、「registerCard」というボタンを押してください。
123456789, 987654321
これでデータを登録できました。
uint128
サイズのデータであるため、同じslot内に保存されています。
ここでのポイントはprivate
というアクセス制限がついています。
これは、「コントラクト内からのみアクセス可能」という意味です。
では実際データを取得した場合どうなるのかみていきましょう。
web3.eth.getStorageAt("0x838F9b8228a5C95a7c431bcDAb58E289f5D2A4DC", 7)
# 0x3ade68b1000000000000000000000000075bcd15
web3.utils.toBN(0x75bcd15).toString();
# 123456789
web3.utils.toBN(0x3ade68b1).toString();
# 987654321
しっかりデータが取得できましたね。
このように、コントラクトでprivate
と定義していても、外部から見えてしまいます。
構造体データにアクセス
slot 8
~slot 10
には以下の構造体データが格納されています。
struct Profile {
string name;
uint256 age;
uint256 createdAt;
}
Profile public profile; // slot 8~10
ではデータを取得してみましょう。
# slot 8
web3.eth.getStorageAt("0x838F9b8228a5C95a7c431bcDAb58E289f5D2A4DC", 8)
# 0x63617264656e650000000000000000000000000000000000000000000000000e
web3.utils.hexToUtf8("0x63617264656e650000000000000000000000000000000000000000000000000e")
# cardene
# slot 9
web3.eth.getStorageAt("0x838F9b8228a5C95a7c431bcDAb58E289f5D2A4DC", 9)
# 0x64
web3.utils.toBN(0x64).toString();
# 100
# slot 10
web3.eth.getStorageAt("0x838F9b8228a5C95a7c431bcDAb58E289f5D2A4DC", 10)
# 0x665173de
web3.utils.toBN(0x665173de).toString();
# 1716614110
構造体も連続したデータ領域に保存されているのが確認できました。
可変長配列データにアクセス
slot 11
には以下の構造体の配列に関するデータが保管されています。
Profile[] profiles; // slot 11
constructor() {
...
profiles.push(Profile("Alice", 30, block.timestamp));
profiles.push(Profile("Bob", 25, block.timestamp));
}
ではデータを取得してみましょう。
web3.eth.getStorageAt("0x838F9b8228a5C95a7c431bcDAb58E289f5D2A4DC", 11)
# 0x02
web3.utils.toBN(0x02).toString();
# 2
ん?「2
」?
想定外のデータが取得されました...。
実はこれは配列の長さデータです。
profiles
は可変長の配列であるため、あらかじめデータ領域を確保しておくことが難しいです。
そのため、ストレージ内の別のslot
にデータを保存しています。
そのため、slot 11
には先ほどのように配列の長さデータが格納されています。
配列内の値は上記のようにかけ離れたslot
にデータが保管されています。
また、配列内のそれぞれのデータはかけ離れた連続したslot
内に保存されています。
ではそのデータを見ていきましょう。
web3.utils.soliditySha3({ type: "uint", value: 11 }) # slot
# 0x0175b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01db9
先ほどデータの保管場所について、「かけ離れた」と説明しました。
では、具体的にはどこに保存するように指定しているのでしょうか?
正解は配列の長さデータをハッシュ化した値です。
ハッシュ化とは、特定のデータを別の値に変換することで、基本的に一位の値を出力します(一部重複が見つかっていますが)。
slot
の値は一位であるため、コントラクト内の他のデータがアクセスすることはありません。
では、まず先ほど取得した値のslot
にアクセスしてデータを取得してみましょう。
# index: 0
web3.eth.getStorageAt("0x838F9b8228a5C95a7c431bcDAb58E289f5D2A4DC", "0x0175b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01db9")
# 0x416c69636500000000000000000000000000000000000000000000000000000a
web3.utils.hexToUtf8("0x416c69636500000000000000000000000000000000000000000000000000000a")
# Alice
完璧です!
値が取得できました。
ただ、配列内のデータはこれだけではありません。
他のデータにアクセスするにはどうしたら良いでしょうか?
先ほど「かけ離れた連続したslot
内に保存」と説明したように、連続してデータが保存されています。
この連続したslot
にアクセスするのにはslot
の番号に「+1
」すれば良いです。
ただ、少し複雑なのが、今回取得したデータは16進数であるため、16進数の「+1
」をする必要があります。
0x0175b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01db9
に「+1
」をすると、0x0175b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01dba
となります。
ではこのデータをもとにアクセスしてみましょう。
# 0x0175b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01dba
web3.eth.getStorageAt("0x838F9b8228a5C95a7c431bcDAb58E289f5D2A4DC", "0x0175b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01dba")
# 0x1e
web3.utils.toBN(0x1e).toString();
# 30
ちゃんと取得できました。
この調子で最後のデータも取得しましょう。
# 0x0175b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01dbb
web3.eth.getStorageAt("0x838F9b8228a5C95a7c431bcDAb58E289f5D2A4DC", "0x0175b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01dbb")
# 0x665186b6
web3.utils.toBN(0x665186b6).toString();
# 1716618934
完璧です!
が、まだBobのデータが残っています。
profiles.push(Profile("Bob", 25, block.timestamp));
ただ、これも簡単に取得できます。
先ほどまでで取得したデータに、また「+1
」すれば取得できます。
## index: 1
# 0x0175b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01dbc
web3.eth.getStorageAt("0x838F9b8228a5C95a7c431bcDAb58E289f5D2A4DC", "0x0175b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01dbc")
# 0x426f620000000000000000000000000000000000000000000000000000000006
web3.utils.hexToUtf8("0x426f620000000000000000000000000000000000000000000000000000000006")
# Bob
# 0x0175b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01dbd
web3.eth.getStorageAt("0x838F9b8228a5C95a7c431bcDAb58E289f5D2A4DC", "0x0175b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01dbd")
# 0x19
web3.utils.toBN(0x19).toString();
# 25
# 0x0175b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01dbe
web3.eth.getStorageAt("0x838F9b8228a5C95a7c431bcDAb58E289f5D2A4DC", "0x0175b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01dbe")
# 0x665186b6
web3.utils.toBN(0x665186b6).toString();
# 1716618934
ちゃんと取得できました。
これで可変長の配列へのアクセス方法が理解できました。
mappingデータにアクセス
これが最後の章になります。
最後はmappingに保存されたデータへのアクセス方法を見ていきます。
コントラクトでは以下のように定義していました。
mapping(uint256 => string) public addressName; // slot 12
constructor() {
...
addressName[7] = "cardene";
addressName[10] = "test";
}
先ほどの配列とは異なり、今回は連続したslot
にデータが保存されていません。
早速実行しながらどこに保存されているのかを見ていきましょう。
まずは先ほどの可変長アドレス同様、slot 12
内のデータを取得してみます。
web3.eth.getStorageAt("0xB57ee0797C3fc0205714a577c02F7205bB89dF30", 12)
# 0x0
空っぽです...。
もはや配列の長さなどではなく、何もデータを保存していません。
mapping配列内のデータにアクセスするには、key
(mappingのキー)とslot
index(0からの連番)をハッシュ化する必要があります。
今回でいえば、keyは7
でslot
indexは12
になります。
web3.utils.soliditySha3({type: "uint", value: 7}, { type: "uint", value: 12 }) # key, slot
# 0x63617264656e650000000000000000000000000000000000000000000000000e
上記のように16進数の値が取得できました。
では取得した値にアクセスしてみましょう。
web3.utils.hexToUtf8("0x63617264656e650000000000000000000000000000000000000000000000000e")
# cardene
ちゃんと値が取得できました。
では、もう片方のデータも取得してみます。
web3.utils.soliditySha3({type: "uint", value: 10}, { type: "uint", value: 12 }) # key, slot
# 0x9e6c92d7be355807bd948171438a5e65aaf9e4c36f1405c1b9ca25d27c4ea3a0
web3.eth.getStorageAt("0x838F9b8228a5C95a7c431bcDAb58E289f5D2A4DC", "0x9e6c92d7be355807bd948171438a5e65aaf9e4c36f1405c1b9ca25d27c4ea3a0")
# 0x7465737400000000000000000000000000000000000000000000000000000008
web3.utils.hexToUtf8("0x7465737400000000000000000000000000000000000000000000000000000008")
# test
取得できました!
これで一通りのデータを取得することができました!
お疲れ様です!
記事で使用したコマンドなどを以下にまとめておきます。
コマンド
web3.eth.getStorageAt("0x838F9b8228a5C95a7c431bcDAb58E289f5D2A4DC", 0)
# 0x53746f7261676520436f6e747261637400000000000000000000000000000020
web3.utils.hexToUtf8("0x53746f7261676520436f6e747261637400000000000000000000000000000020")
# Storage Contract
web3.eth.getStorageAt("0x838F9b8228a5C95a7c431bcDAb58E289f5D2A4DC", 1)
# 0x436865636c20636f6e747261637420736f74726167650000000000000000002c
web3.utils.hexToUtf8("0x436865636c20636f6e747261637420736f74726167650000000000000000002c")
# Checl contract sotrage
web3.eth.getStorageAt("0x838F9b8228a5C95a7c431bcDAb58E289f5D2A4DC", 2)
# 0x01
web3.utils.toBN(0x01).toString();
# 1
web3.eth.getStorageAt("0x838F9b8228a5C95a7c431bcDAb58E289f5D2A4DC", 3)
# 0x66517164
web3.utils.toBN(0x66517164).toString();
# 1716613476
web3.eth.getStorageAt("0x838F9b8228a5C95a7c431bcDAb58E289f5D2A4DC", 4)
# 0x66517164
web3.utils.toBN(0x66517164).toString();
# 1716613476
web3.eth.getStorageAt("0x838F9b8228a5C95a7c431bcDAb58E289f5D2A4DC", 5)
# 0x5b38da6a701c568545dcfcb03fcb875f56beddc4
web3.eth.getStorageAt("0x838F9b8228a5C95a7c431bcDAb58E289f5D2A4DC", 6)
# 0x5b38da6a701c568545dcfcb03fcb875f56beddc4
web3.eth.getStorageAt("0x838F9b8228a5C95a7c431bcDAb58E289f5D2A4DC", 7)
# 0x3ade68b1000000000000000000000000075bcd15
web3.utils.toBN(0x75bcd15).toString();
# 123456789
web3.utils.toBN(0x3ade68b1).toString();
# 987654321
web3.eth.getStorageAt("0x838F9b8228a5C95a7c431bcDAb58E289f5D2A4DC", 8)
# 0x63617264656e650000000000000000000000000000000000000000000000000e
web3.utils.hexToUtf8("0x63617264656e650000000000000000000000000000000000000000000000000e")
# cardene
web3.eth.getStorageAt("0x838F9b8228a5C95a7c431bcDAb58E289f5D2A4DC", 9)
# 0x64
web3.utils.toBN(0x64).toString();
# 100
web3.eth.getStorageAt("0x838F9b8228a5C95a7c431bcDAb58E289f5D2A4DC", 10)
# 0x665173de
web3.utils.toBN(0x665173de).toString();
# 1716614110
## Array
## index: 0
web3.eth.getStorageAt("0x838F9b8228a5C95a7c431bcDAb58E289f5D2A4DC", 11)
# 0x02
web3.utils.toBN(0x02).toString();
# 2
web3.utils.soliditySha3({ type: "uint", value: 11 }) # slot
# 0x0175b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01db9
web3.eth.getStorageAt("0x838F9b8228a5C95a7c431bcDAb58E289f5D2A4DC", "0x0175b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01db9")
# 0x416c69636500000000000000000000000000000000000000000000000000000a
web3.utils.hexToUtf8("0x416c69636500000000000000000000000000000000000000000000000000000a")
# Alice
# 0x0175b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01dba
web3.eth.getStorageAt("0x838F9b8228a5C95a7c431bcDAb58E289f5D2A4DC", "0x0175b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01dba")
# 0x1e
web3.utils.toBN(0x1e).toString();
# 30
# 0x0175b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01dbb
web3.eth.getStorageAt("0x838F9b8228a5C95a7c431bcDAb58E289f5D2A4DC", "0x0175b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01dbb")
# 0x665186b6
web3.utils.toBN(0x665186b6).toString();
# 1716618934
## index: 1
# 0x0175b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01dbc
web3.eth.getStorageAt("0x838F9b8228a5C95a7c431bcDAb58E289f5D2A4DC", "0x0175b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01dbc")
# 0x426f620000000000000000000000000000000000000000000000000000000006
web3.utils.hexToUtf8("0x426f620000000000000000000000000000000000000000000000000000000006")
# Bob
# 0x0175b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01dbd
web3.eth.getStorageAt("0x838F9b8228a5C95a7c431bcDAb58E289f5D2A4DC", "0x0175b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01dbd")
# 0x19
web3.utils.toBN(0x19).toString();
# 25
# 0x0175b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01dbe
web3.eth.getStorageAt("0x838F9b8228a5C95a7c431bcDAb58E289f5D2A4DC", "0x0175b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01dbe")
# 0x665186b6
web3.utils.toBN(0x665186b6).toString();
# 1716618934
## Mapping
web3.eth.getStorageAt("0x838F9b8228a5C95a7c431bcDAb58E289f5D2A4DC", 12)
# 0x0
web3.utils.soliditySha3({type: "uint", value: 7}, { type: "uint", value: 12 }) # key, slot
# 0x949c9a93ae746871004aabe5aa16f6723dba12da47a01a4a63a7f95d60037905
web3.eth.getStorageAt("0x838F9b8228a5C95a7c431bcDAb58E289f5D2A4DC", "0xdae089abd7155aa13ce498edb0d7a7156b783d015031f10c9a3d4f5fcb518971")
# 0x63617264656e650000000000000000000000000000000000000000000000000e
web3.utils.hexToUtf8("0x63617264656e650000000000000000000000000000000000000000000000000e")
# cardene
web3.utils.soliditySha3({type: "uint", value: 10}, { type: "uint", value: 12 }) # key, slot
# 0x9e6c92d7be355807bd948171438a5e65aaf9e4c36f1405c1b9ca25d27c4ea3a0
web3.eth.getStorageAt("0x838F9b8228a5C95a7c431bcDAb58E289f5D2A4DC", "0x9e6c92d7be355807bd948171438a5e65aaf9e4c36f1405c1b9ca25d27c4ea3a0")
# 0x7465737400000000000000000000000000000000000000000000000000000008
web3.utils.hexToUtf8("0x7465737400000000000000000000000000000000000000000000000000000008")
# test
最後に
今回は、コントラクトのストレージに格納されているデータに直接アクセスして、コントラクトのどこにどのようにデータが保存されているかを見てきました。
いかがだったでしょうか?
普段あまり意識しない部分だと思いますが、仕組みを知るとより興味を持てると思います。
このような技術記事を今後も発信していきます。
以下でも情報発信しているので、興味ある記事があればぜひ読んでみてください!
参考
Discussion