🐥

Solidity基礎学習7日目(Mapping)

に公開

Solidity基礎学習 - マッピング(Mapping)データ構造の理解

日付: 2025年8月27日
学習内容: Solidityのマッピング(Mapping)データ構造とその使用方法について

1. マッピングの基本概念

013_Mapping.solの概要

Solidityのマッピングデータ構造について学習します。マッピングはハッシュテーブルのような構造を持ち、効率的なデータアクセスを提供します。

重要なポイント

マッピングの基本構造

contract ExampleMapping{
    // 基本的なマッピング
    mapping(uint => bool) public myMapping;
    
    // アドレスをキーとするマッピング
    mapping(address => bool) public myAddressMapping;
    
    // ネストしたマッピング
    mapping(uint => mapping(uint => bool)) public uintUintBoolMapping;
}

マッピングの特徴:

  • ハッシュテーブル構造: 配列やリストと異なり、インデックスではなく任意のキーを使用
  • 高速アクセス: O(1)の時間複雑度でデータにアクセス可能
  • 動的サイズ: 事前にサイズを定義する必要がない
  • キー・値ペア: 任意の型のキーと値の組み合わせ

2. ハッシュテーブルの仕組み

ハッシュ関数の動作

キー → ハッシュ関数 → ハッシュ値(配列のインデックス)

具体例:

  • キー: "apple" → ハッシュ関数 → インデックス3
  • キー: "banana" → ハッシュ関数 → インデックス7
  • キー: "orange" → ハッシュ関数 → インデックス2

データ格納:

hashtable[3] = "リンゴ"
hashtable[7] = "バナナ"
hashtable[2] = "オレンジ"

アクセス方法:

hashtable["apple"] = "リンゴ"    // キーから直接アクセス
hashtable["banana"] = "バナナ"   // キーから直接アクセス

3. 各マッピングの詳細分析

3.1 基本的なマッピング(myMapping)

実装コード

mapping(uint => bool) public myMapping;

function setValue(uint _index) public{
    myMapping[_index] = true;
}

機能の説明:

  • キー: uint型(符号なし整数)
  • : bool型(真偽値)
  • 動作: 指定されたインデックスに対応する値をtrueに設定

使用例:

// インデックス5に値を設定
setValue(5);  // myMapping[5] = true

// インデックス10に値を設定
setValue(10); // myMapping[10] = true

// 値の確認
myMapping(5);   // true が返される
myMapping(10);  // true が返される
myMapping(3);   // false が返される(デフォルト値)

3.2 アドレスマッピング(myAddressMapping)

実装コード

mapping(address => bool) public myAddressMapping;

function setMyAddressToTrue() public{
    myAddressMapping[msg.sender]=true;
}

機能の説明:

  • キー: address型(イーサリアムアドレス)
  • : bool型(真偽値)
  • 動作: 関数を呼び出したユーザーのアドレスをtrueに設定

使用例:

// ユーザーAが関数を呼び出し
// msg.sender = 0x1234...(ユーザーAのアドレス)
setMyAddressToTrue();  // myAddressMapping[0x1234...] = true

// ユーザーBが関数を呼び出し
// msg.sender = 0x5678...(ユーザーBのアドレス)
setMyAddressToTrue();  // myAddressMapping[0x5678...] = true

3.3 ネストしたマッピング(uintUintBoolMapping)

実装コード

mapping(uint => mapping(uint => bool)) public uintUintBoolMapping;

function setUintUintBoolMapping(uint _key1, uint _key2, bool _value) public{
    uintUintBoolMapping[_key1][_key2]= _value;
}

機能の説明:

  • 第1層: uintUintBoolMapping[_key1]mapping(uint => bool)型を返す
  • 第2層: [uintUintBoolMapping[_key1]][_key2]bool型の値を返す
  • キーの対応: _key1は1つめのuint_key2は2つめのuint

使用例:

// キー1=5、キー2=10にtrueを設定
setUintUintBoolMapping(5, 10, true);

// キー1=3、キー2=7にfalseを設定
setUintUintBoolMapping(3, 7, false);

// 値の確認
uintUintBoolMapping(5, 10);  // true
uintUintBoolMapping(3, 7);   // false
uintUintBoolMapping(5, 7);   // false(デフォルト値)

4. 自動生成されるgetter関数

4.1 getter関数の自動生成条件

自動生成される要素

// 以下の要素にpublic修飾子が付いている場合、getter関数が自動生成される
uint public balance;                                    // 基本型
uint[] public numbers;                                  // 配列
mapping(uint => bool) public myMapping;                 // マッピング
mapping(uint => mapping(uint => bool)) public nested;   // ネストしたマッピング

自動生成されない場合:

  • private修飾子
  • internal修飾子
  • 関数内のローカル変数

4.2 生成されるgetter関数の例

基本型の場合

uint public balance;

// 自動生成される関数
function balance() public view returns(uint) {
    return balance;
}

マッピングの場合

mapping(uint => bool) public myMapping;

// 自動生成される関数
function myMapping(uint _key) public view returns(bool) {
    return myMapping[_key];
}

ネストしたマッピングの場合

mapping(uint => mapping(uint => bool)) public uintUintBoolMapping;

// 自動生成される関数
function uintUintBoolMapping(uint _key1, uint _key2) public view returns(bool) {
    return uintUintBoolMapping[_key1][_key2];
}

5. アクセス方法の違い

5.1 外部からのアクセス

正しい使用方法

// コントラクトの外部から
uint currentBalance = contract.balance();           // 基本型
uint number = contract.numbers(5);                  // 配列
bool value = contract.myMapping(5);                 // マッピング
bool nestedValue = contract.uintUintBoolMapping(5, 10);  // ネストしたマッピング

間違った使用方法

// エラー!変数に直接アクセスできない
uint currentBalance = contract.balance;             // エラー
uint number = contract.numbers;                     // エラー
bool value = contract.myMapping;                    // エラー

5.2 内部からのアクセス

コントラクト内部での使用方法

contract Example {
    uint public balance;
    
    function internalAccess() public view returns(uint) {
        // 内部では変数名で直接アクセス可能
        return balance;  // 正しい
    }
    
    function externalAccess() public view returns(uint) {
        // 外部からは関数呼び出しが必要
        return this.balance();  // 正しい(thisを使用)
    }
}

6. 実装のポイント

6.1 セキュリティ設計

基本的なセキュリティ考慮事項

function setValue(uint _index) public {
    // 適切な検証を追加
    require(_index > 0, "Index must be greater than 0");
    
    // 値の設定
    myMapping[_index] = true;
}

セキュリティの考慮点:

  • 入力値の検証: 適切な範囲チェック
  • アクセス制御: 必要に応じて権限管理
  • エラーハンドリング: 適切なエラーメッセージ

6.2 ガスコストの最適化

効率的な実装

// 読み取り専用関数はview修飾子を使用
function getValue(uint _index) public view returns(bool) {
    return myMapping[_index];
}

// 状態変更は必要最小限に
function setValue(uint _index) public {
    myMapping[_index] = true;
}

最適化のポイント:

  • view修飾子: 状態変更しない関数でのガス節約
  • 効率的な状態更新: 必要最小限の処理のみ実行
  • 適切な関数設計: 各機能を適切に分離

7. 実用的な応用

7.1 拡張可能な機能

権限管理の追加

contract SecureMapping {
    mapping(uint => bool) private myMapping;
    mapping(address => bool) public authorizedUsers;
    address public owner;
    
    constructor() {
        owner = msg.sender;
        authorizedUsers[msg.sender] = true;
    }
    
    modifier onlyAuthorized() {
        require(authorizedUsers[msg.sender], "Not authorized");
        _;
    }
    
    function setValue(uint _index) public onlyAuthorized {
        myMapping[_index] = true;
    }
    
    function getValue(uint _index) public view onlyAuthorized returns(bool) {
        return myMapping[_index];
    }
}

拡張機能:

  • 権限管理: 承認されたユーザーのみが利用可能
  • 所有者制御: 管理者によるユーザー管理
  • セキュリティ強化: 未承認ユーザーからの操作を拒否

7.2 より柔軟なマッピング機能

複数の値を持つマッピング

struct UserInfo {
    string name;
    uint age;
    bool isActive;
}

mapping(uint => UserInfo) public users;

function setUser(uint _id, string memory _name, uint _age) public {
    users[_id] = UserInfo(_name, _age, true);
}

function getUser(uint _id) public view returns(string memory name, uint age, bool isActive) {
    UserInfo memory user = users[_id];
    return (user.name, user.age, user.isActive);
}

複合データの利点:

  • 柔軟性: 複数の情報を1つのキーで管理
  • 効率性: 関連データの一元管理
  • 拡張性: 新しいフィールドの追加が容易

8. 学習の成果

8.1 習得した概念

  1. マッピング: ハッシュテーブル構造のデータ型
  2. ネストしたマッピング: 複数層のマッピング構造
  3. getter関数: 自動生成されるアクセス関数
  4. アクセス制御: 内部・外部からのアクセス方法の違い
  5. ハッシュ関数: キーからインデックスへの変換

8.2 実装スキル

  • マッピング機能の実装能力
  • ネストしたマッピングの適切な設計
  • getter関数の理解と活用
  • セキュリティを考慮したデータ管理

8.3 技術的な理解

  • ハッシュテーブル: 効率的なデータアクセスの仕組み
  • 自動生成: コンパイラによるgetter関数の生成
  • アクセス制御: Solidityのカプセル化の実現方法
  • ガス最適化: 効率的なマッピング操作

9. 今後の学習への応用

9.1 発展的な機能

  • 複雑なネスト: 3層以上のマッピング構造
  • 動的マッピング: 実行時のマッピング操作
  • マッピングの削除: キー・値ペアの管理

9.2 セキュリティの向上

  • リエントランシー攻撃: 再入攻撃への対策
  • オーバーフロー: 数値計算の安全性確保
  • アクセス制御: 適切な権限管理の実装

9.3 パフォーマンスの最適化

  • ガスコスト: 効率的なマッピング操作
  • メモリ管理: 適切なデータ構造の選択
  • スケーラビリティ: 大量データの効率的な処理

参考:

Discussion