はじめに

初めまして。 CryptoGamesというブロックチェーンゲーム企業でエンジニアをしている cardene(かるでね) です! スマートコントラクトを書いたり、フロントエンド・バックエンド・インフラと幅広く触れています。

代表的なゲームはクリプトスペルズというブロックチェーンゲームです。

以下でも情報発信しているので、興味ある記事があればぜひ読んでみてください!

今回はコントラクトのストレージに格納されているデータに直接アクセスして、コントラクトのどこにどのようにデータが保存されているかを見ていきます。 実際に手を動かしながら確認できるので、実行しながら理解を深めていきましょう!

今回の記事ではRemixというブラウザ上で動くエディタを使用していきます。 https://remix.ethereum.org/

ストレージ

概要

まずはコントラクトのストレージについての理解を深めていきましょう。 コントラクトにはデータの保管場所が3つあります。

保管場所 特徴 ガス代消費
storage コントラクト(ブロックチェーン)にデータを永続的に書き込みます。どこからでもデータの取得が可能になります。 多い
memory コントラクト内の関数実行開始から完了までの間アクセスできる、一時的なデータ保管場所です。関数内からのみアクセス可能です。 普通
calldata memoryのように振る舞い、データの更新ができません。 少ない

今回は上記のstorageに焦点を当て、どのようにデータが保存されているのかを見ていきます。

スロット

ストレージ内のデータは、slotと呼ばれる単位で保管されています。

スロットの図

1slotは32byteで構成され、最大22562^{256} slotまでデータを保管可能です。

これはあくまで最大保存可能量であり、実際はそんなに大量のデータを保管していません。

stringやuint256などのデータは32byteであるため、1slotずつデータが保存されていきます。

スロットの図

それ以下のサイズのデータは上記のように同じslot内に格納される場合があります。 この時注意しないといけないのが、変数などの定義順によってはデータの保管効率が悪くなります。

スロットの図

上記の画像だと、コントラクト内で以下のような定義になっています。

solidity
string name;
uint8 income;
uint8 age;
string description;
address walletAddress;
bool enginner;
bool creator;

ちなみにslotには定義した値が右詰めで格納されています。 では、以下のような定義だとどうなるでしょうか?

solidity
string name;
string description;
address walletAddress;
uint8 income;
uint8 age;
bool enginner;
bool creator;

スロットの図

上記のように4つのslotを使用しています。 これでは余分にstorageを使用しているため、ガス代が余計にかかってしまいます。 このように、storageにどのようにしてデータを格納するかは、ガス代の削減につながるので意識しておくと良いです。

スロットの図

実際にstorageに格納されているデータは、上記のように16進数になっています。

ストレージのまとめ

この章ではストレージについての理解を深めてきました。 コントラクトでのデータ保管方法を意識してコントラクトの作成までできると、1段エンジニアとしてステップアップできるはずです。 では、次の章から実際にストレージの中身を見ていきましょう。

コントラクトの作成&デプロイ

では、早速今回使用するコントラクトを紹介していきます。 Remixを使い慣れていない方は以下の記事などを参考にしてください。

コントラクトの作成

コードは以下にも格納しています。

Remixに以下のコードを貼り付けてください。

solidity
// 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);
    }
}

コントラクトの説明

先ほど作成したコントラクトについて簡単に説明します。

solidity
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 〇〇」というのは、各データがどのストレージスロットに格納されているかを示しています。

solidity
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でいろいろ値を格納しています。

solidity
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」の部分にコピペしてください。

js
web3.eth.getStorageAt("0xB57ee0797C3fc0205714a577c02F7205bB89dF30", 0)

これは「コントラクトアドレス0xB57ee0797C3fc0205714a577c02F7205bB89dF30のstorage領域内のslot0のデータを取得する」という処理を実行します。 では早速実行していきましょう!

slot 0の図

上記のように、Remix内のTerminalと呼ばれる部分に先ほどのコマンドをコピペして実行してください。

そうすると以下のような16進数データが出力されます。

bash
0x53746f7261676520436f6e747261637400000000000000000000000000000020

うまくいかない場合は何度か試してください。 また、コントラクトアドレスが間違っていないかも確認してください。

では、次に以下のコマンドを実行します。

js
web3.utils.hexToUtf8("0x53746f7261676520436f6e747261637400000000000000000000000000000020")

上記は16進数文字列をUTF-8形式の文字列に変換する処理を実行します。

bash
Storage Contract

上記のように出力されたはずです。 これでコントラクトのstorageに直接アクセスしてデータを取得できました。 この調子で他のデータにもアクセスしていきましょう。

slot 1~slot 6

slot1~slot6まではほとんど同じであるため一気にデータを取得してみましょう。

slot1

bash
web3.eth.getStorageAt("0x838F9b8228a5C95a7c431bcDAb58E289f5D2A4DC", 1)
# 0x436865636c20636f6e747261637420736f74726167650000000000000000002c
web3.utils.hexToUtf8("0x436865636c20636f6e747261637420736f74726167650000000000000000002c")
# Checl contract sotrage

slot2

bash
web3.eth.getStorageAt("0x838F9b8228a5C95a7c431bcDAb58E289f5D2A4DC", 2)
# 0x01
web3.utils.toBN(0x01).toString();
# 1

今回は数値データであったため、16進数から数値データに変換しています。

slot3・slot4

bash
# 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は以下のコードの部分で、固定長の配列の要素です。

solidity
uint256[2] public timestampArry; // slot 3 ~ 4

固定長の配列は事前に確保する領域がわかるため、このように連続したslotに格納されています。 では固定長ではない配列の場合はどうなるのでしょうか? これについては後ほど解説していきます。

slot5

bash
web3.eth.getStorageAt("0x838F9b8228a5C95a7c431bcDAb58E289f5D2A4DC", 5)
# 0x5b38da6a701c568545dcfcb03fcb875f56beddc4

これはウォレットアドレスなので変換が不要です。

slot6

bash
web3.eth.getStorageAt("0x838F9b8228a5C95a7c431bcDAb58E289f5D2A4DC", 6)
# 0x5b38da6a701c568545dcfcb03fcb875f56beddc4

privateデータにアクセス

slot7には、コントラクト内の以下のデータが格納されています。

solidity
uint128 private cardNumber; // slot 7
uint128 private cardPassword; // slot 7

まずはこの変数にデータを格納する必要があります。

privateデータにアクセスの図

上記のように、以下のデータを左にコピペして、「registerCard」というボタンを押してください。

bash
123456789, 987654321

これでデータを登録できました。

uint128サイズのデータであるため、同じslot内に保存されています。 ここでのポイントはprivateというアクセス制限がついています。 これは、「コントラクト内からのみアクセス可能」という意味です。 では実際データを取得した場合どうなるのかみていきましょう。

bash
web3.eth.getStorageAt("0x838F9b8228a5C95a7c431bcDAb58E289f5D2A4DC", 7)
# 0x3ade68b1000000000000000000000000075bcd15
web3.utils.toBN(0x75bcd15).toString();
# 123456789
web3.utils.toBN(0x3ade68b1).toString();
# 987654321

しっかりデータが取得できましたね。 このように、コントラクトでprivateと定義していても、外部から見えてしまいます。

privateデータが外部から見えることについてのより詳しい解説は以下の記事でしています。

構造体データにアクセス

slot 8~slot 10には以下の構造体データが格納されています。

solidity
struct Profile {
    string name;
    uint256 age;
    uint256 createdAt;
}
Profile public profile; // slot 8~10

ではデータを取得してみましょう。

bash
# 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には以下の構造体の配列に関するデータが保管されています。

solidity
Profile[] profiles; // slot 11

constructor() {
    ...
    profiles.push(Profile("Alice", 30, block.timestamp));
    profiles.push(Profile("Bob", 25, block.timestamp));
}

ではデータを取得してみましょう。

bash
web3.eth.getStorageAt("0x838F9b8228a5C95a7c431bcDAb58E289f5D2A4DC", 11)
# 0x02
web3.utils.toBN(0x02).toString();
# 2

ん?「2」? 想定外のデータが取得されました...。 実はこれは配列の長さデータです。 profilesは可変長の配列であるため、あらかじめデータ領域を確保しておくことが難しいです。 そのため、ストレージ内の別のslotにデータを保存しています。 そのため、slot 11には先ほどのように配列の長さデータが格納されています。

可変長配列データにアクセスの図

配列内の値は上記のようにかけ離れたslotにデータが保管されています。 また、配列内のそれぞれのデータはかけ離れた連続したslot内に保存されています。

ではそのデータを見ていきましょう。

bash
web3.utils.soliditySha3({ type: "uint", value: 11 }) # slot
# 0x0175b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01db9

先ほどデータの保管場所について、「かけ離れた」と説明しました。 では、具体的にはどこに保存するように指定しているのでしょうか? 正解は配列の長さデータをハッシュ化した値です。 ハッシュ化とは、特定のデータを別の値に変換することで、基本的に一位の値を出力します(一部重複が見つかっていますが)。

slotの値は一位であるため、コントラクト内の他のデータがアクセスすることはありません。

「ストレージが他のコントラクトとぶつかることはないのか!」と疑問に思う方がいるかもしれません。 これは全く問題ないです。 理由としてはコントラクトごとにこのストレージ領域が存在するためです。 先ほどから実行しているgetStorageAtの第1引数で、コントラクトアドレスを指定しているのがまさに良い例です。

では、まず先ほど取得した値のslotにアクセスしてデータを取得してみましょう。

bash
# index: 0
web3.eth.getStorageAt("0x838F9b8228a5C95a7c431bcDAb58E289f5D2A4DC", "0x0175b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01db9")
# 0x416c69636500000000000000000000000000000000000000000000000000000a
web3.utils.hexToUtf8("0x416c69636500000000000000000000000000000000000000000000000000000a")
# Alice

完璧です! 値が取得できました。 ただ、配列内のデータはこれだけではありません。 他のデータにアクセスするにはどうしたら良いでしょうか? 先ほど「かけ離れた連続したslot内に保存」と説明したように、連続してデータが保存されています。 この連続したslotにアクセスするのにはslotの番号に「+1」すれば良いです。

可変長配列データにアクセスの図

ただ、少し複雑なのが、今回取得したデータは16進数であるため、16進数の「+1」をする必要があります。 0x0175b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01db9に「+1」をすると、0x0175b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01dbaとなります。 ではこのデータをもとにアクセスしてみましょう。

bash
# 0x0175b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01dba
web3.eth.getStorageAt("0x838F9b8228a5C95a7c431bcDAb58E289f5D2A4DC", "0x0175b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01dba")
# 0x1e
web3.utils.toBN(0x1e).toString();
# 30

ちゃんと取得できました。 この調子で最後のデータも取得しましょう。

bash
# 0x0175b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01dbb
web3.eth.getStorageAt("0x838F9b8228a5C95a7c431bcDAb58E289f5D2A4DC", "0x0175b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01dbb")
# 0x665186b6
web3.utils.toBN(0x665186b6).toString();
# 1716618934

完璧です! が、まだBobのデータが残っています。

bash
profiles.push(Profile("Bob", 25, block.timestamp));

ただ、これも簡単に取得できます。 先ほどまでで取得したデータに、また「+1」すれば取得できます。

bash
## 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に保存されたデータへのアクセス方法を見ていきます。 コントラクトでは以下のように定義していました。

solidity
mapping(uint256 => string) public addressName; // slot 12

constructor() {
    ...
    addressName[7] = "cardene";
    addressName[10] = "test";
}

先ほどの配列とは異なり、今回は連続したslotにデータが保存されていません。 早速実行しながらどこに保存されているのかを見ていきましょう。 まずは先ほどの可変長アドレス同様、slot 12内のデータを取得してみます。

bash
web3.eth.getStorageAt("0xB57ee0797C3fc0205714a577c02F7205bB89dF30", 12)
# 0x0

空っぽです...。 もはや配列の長さなどではなく、何もデータを保存していません。

mappingデータにアクセスの図

mapping配列内のデータにアクセスするには、key(mappingのキー)とslotindex(0からの連番)をハッシュ化する必要があります。 今回でいえば、keyは7でslotindexは12になります。

bash
web3.utils.soliditySha3({type: "uint", value: 7}, { type: "uint", value: 12 }) # key, slot
# 0x63617264656e650000000000000000000000000000000000000000000000000e

上記のように16進数の値が取得できました。 では取得した値にアクセスしてみましょう。

bash
web3.utils.hexToUtf8("0x63617264656e650000000000000000000000000000000000000000000000000e")
# cardene

ちゃんと値が取得できました。 では、もう片方のデータも取得してみます。

bash
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

取得できました! これで一通りのデータを取得することができました! お疲れ様です!

記事で使用したコマンドなどを以下にまとめておきます。

コマンド
bash
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

最後に

今回は、コントラクトのストレージに格納されているデータに直接アクセスして、コントラクトのどこにどのようにデータが保存されているかを見てきました。 いかがだったでしょうか? 普段あまり意識しない部分だと思いますが、仕組みを知るとより興味を持てると思います。 このような技術記事を今後も発信していきます。

以下でも情報発信しているので、興味ある記事があればぜひ読んでみてください!

参考