😺

Solidity基礎学習14日目(Web3.jsとスマートコントラクト)

に公開

Solidity基礎学習 - Web3.jsとスマートコントラクトの相互作用

日付: 2025年9月7日
学習内容: Web3.jsによるブロックチェーンとの通信、アカウント管理、スマートコントラクトとの相互作用、デバッグ機能について

1. Web3.jsとブロックチェーン通信の基本概念

非同期処理の重要性

ブロックチェーン開発では、ノードとの通信やブロック生成の待機が必要なため、非同期処理が不可欠です。JavaScriptのasync/awaitパターンを使用して、効率的なブロックチェーン通信を実装します。

重要なポイント

非同期処理の基本構造

// 非同期関数の定義
(async() => {
    // ブロックチェーンとの通信処理
    let accounts = await web3.eth.getAccounts();
    console.log(accounts, accounts.length);
})()

非同期処理の特徴:

  • ノンブロッキング: 他の処理を待たずに実行継続
  • 効率的な通信: ブロックチェーンノードとの並行処理
  • エラーハンドリング: 適切な例外処理の実装
  • リソース管理: メモリとガスの効率的な使用

2. アカウント管理と残高確認

2.1 アカウント取得機能

実装コード

(async() => {
    // 利用可能なアカウントを取得
    let accounts = await web3.eth.getAccounts();
    console.log(accounts, accounts.length);
    
    // 最初のアカウントの残高を取得
    let balance = await web3.eth.getBalance(accounts[0]);
    console.log(balance);
})()

機能の説明:

  • アカウント一覧: ローカルノードの全アカウントを取得
  • 残高確認: 指定アカウントの現在の残高を取得
  • Wei単位: ブロックチェーン上の残高はWei単位で表示

2.2 単位変換の実装

実装コード

// WeiをEtherに変換
let balanceInEth = web3.utils.fromWei(balance.toString(), "ether");
console.log(balanceInEth);

単位変換の重要性:

// 各種単位への変換
let balanceInWei = web3.utils.toWei("1", "ether");        // 1 ETH → Wei
let balanceInGwei = web3.utils.fromWei(balance, "gwei");  // Wei → Gwei
let balanceInEth = web3.utils.fromWei(balance, "ether");  // Wei → ETH

変換可能な単位:

  • Wei: 最小単位(1 ETH = 10^18 Wei)
  • Gwei: ガス価格の単位(1 ETH = 10^9 Gwei)
  • Ether: 一般的な単位(1 ETH = 1 Ether)

3. スマートコントラクトとの相互作用

3.1 コントラクトインスタンスの作成

実装コード

// コントラクトのアドレスとABIを定義
const contractAddress = "0x1234567890123456789012345678901234567890"; 
const contractABI = [
    {
        "inputs": [
            {
                "internalType": "uint256",
                "name": "_newValue",
                "type": "uint256"
            }
        ],
        "name": "setValue",
        "outputs": [],
        "stateMutability": "nonpayable",
        "type": "function"
    },
    {
        "inputs": [],
        "name": "getValue",
        "outputs": [
            {
                "internalType": "uint256",
                "name": "",
                "type": "uint256"
            }
        ],
        "stateMutability": "view",
        "type": "function"
    }
];

// コントラクトインスタンスの作成
const contractInstance = new web3.eth.Contract(contractABI, contractAddress);

コントラクト通信の要素:

  • アドレス: ブロックチェーン上のコントラクト位置
  • ABI: 関数の定義とインターフェース
  • インスタンス: Web3.jsでのコントラクト操作オブジェクト

3.2 状態変数の読み取り

実装コード

// コントラクトの状態変数を読み取り
console.log(await contractInstance.methods.getValue().call());

読み取り操作の特徴:

  • view関数: 状態を変更しない読み取り専用関数
  • call()メソッド: ガスを消費しない読み取り操作
  • 非同期処理: awaitキーワードで結果を待機

3.3 状態変数の更新

実装コード

// アカウントを取得
let accounts = await web3.eth.getAccounts();

// コントラクトの状態を更新
let txResult = await contractInstance.methods.setValue(456).send({
    from: accounts[0]
});

// 更新結果の確認
console.log(await contractInstance.methods.getValue().call());
console.log(txResult);

更新操作の特徴:

  • send()メソッド: ガスを消費する状態変更操作
  • トランザクション: ブロックチェーンに記録される変更
  • fromフィールド: 送信者のアカウント指定

4. デバッグ機能の実装

4.1 基本的なデバッグコントラクト

実装コード

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.30;

contract DebugContract{
    uint public debugValue = 999;

    function updateDebugValue(uint _newValue) public {
        debugValue = _newValue;
    }
}

デバッグ機能の特徴:

  • 状態変数: デバッグ用の値を格納
  • 更新関数: 値を変更してテスト
  • 公開変数: 外部から値を確認可能

4.2 デバッグのベストプラクティス

実装コード

contract AdvancedDebugContract {
    uint public debugValue = 999;
    uint public lastUpdateTime;
    address public lastUpdater;
    
    event ValueUpdated(uint oldValue, uint newValue, address updater);
    
    function updateDebugValue(uint _newValue) public {
        uint oldValue = debugValue;
        debugValue = _newValue;
        lastUpdateTime = block.timestamp;
        lastUpdater = msg.sender;
        
        emit ValueUpdated(oldValue, _newValue, msg.sender);
    }
    
    function getDebugInfo() public view returns (
        uint currentValue,
        uint updateTime,
        address updater
    ) {
        return (debugValue, lastUpdateTime, lastUpdater);
    }
}

デバッグ機能の拡張:

  • イベントログ: 変更履歴の追跡
  • タイムスタンプ: 更新時刻の記録
  • 送信者記録: 誰が変更したかの追跡
  • 複合情報: 複数の値を一度に取得

5. Web3.jsの高度な機能

5.1 トランザクションの詳細管理

実装コード

// トランザクションの詳細設定
let txOptions = {
    from: accounts[0],
    gas: 200000,
    gasPrice: web3.utils.toWei('20', 'gwei'),
    value: web3.utils.toWei('0.1', 'ether')
};

// 詳細設定でのトランザクション送信
let txResult = await contractInstance.methods.setValue(789).send(txOptions);

// トランザクション結果の詳細確認
console.log('Transaction Hash:', txResult.transactionHash);
console.log('Gas Used:', txResult.gasUsed);
console.log('Block Number:', txResult.blockNumber);

トランザクション設定の要素:

  • gas: 使用するガス量の上限
  • gasPrice: ガス価格の設定
  • value: 送信するETH量
  • from: 送信者のアカウント

5.2 イベントの監視

実装コード

// イベントの監視設定
contractInstance.events.ValueUpdated({
    fromBlock: 'latest'
}, function(error, event) {
    if (error) {
        console.error('Event error:', error);
    } else {
        console.log('Value updated:', event.returnValues);
    }
});

// 過去のイベントを取得
let pastEvents = await contractInstance.getPastEvents('ValueUpdated', {
    fromBlock: 0,
    toBlock: 'latest'
});
console.log('Past events:', pastEvents);

イベント監視の機能:

  • リアルタイム監視: 新しいイベントの即座な検出
  • 過去イベント: 履歴の取得と分析
  • フィルタリング: 特定条件でのイベント抽出

6. エラーハンドリングと最適化

6.1 包括的なエラーハンドリング

実装コード

async function safeContractCall() {
    try {
        // コントラクトの状態確認
        let currentValue = await contractInstance.methods.getValue().call();
        console.log('Current value:', currentValue);
        
        // 安全な値の更新
        let txResult = await contractInstance.methods.setValue(currentValue + 1).send({
            from: accounts[0],
            gas: 100000
        });
        
        console.log('Update successful:', txResult.transactionHash);
        
    } catch (error) {
        console.error('Contract call failed:', error.message);
        
        // エラーの種類に応じた処理
        if (error.message.includes('revert')) {
            console.log('Transaction was reverted');
        } else if (error.message.includes('insufficient funds')) {
            console.log('Insufficient funds for gas');
        } else {
            console.log('Unknown error occurred');
        }
    }
}

エラーハンドリングの重要性:

  • トランザクション失敗: 適切なエラー処理
  • ガス不足: 十分なガスの確保
  • 権限不足: 適切な権限の確認
  • ネットワーク問題: 接続エラーの処理

6.2 パフォーマンス最適化

実装コード

// バッチ処理による効率化
async function batchContractCalls() {
    let accounts = await web3.eth.getAccounts();
    let promises = [];
    
    // 複数のコントラクト呼び出しを並行実行
    for (let i = 0; i < 5; i++) {
        promises.push(
            contractInstance.methods.setValue(i * 100).send({
                from: accounts[0],
                gas: 50000
            })
        );
    }
    
    // 全ての処理の完了を待機
    let results = await Promise.all(promises);
    console.log('Batch processing completed:', results);
}

// ガス価格の最適化
async function optimizeGasPrice() {
    // 現在のガス価格を取得
    let gasPrice = await web3.eth.getGasPrice();
    console.log('Current gas price:', web3.utils.fromWei(gasPrice, 'gwei'), 'Gwei');
    
    // 最適なガス価格を計算
    let optimizedGasPrice = web3.utils.toBN(gasPrice).mul(web3.utils.toBN(110)).div(web3.utils.toBN(100));
    
    return {
        gas: 100000,
        gasPrice: optimizedGasPrice.toString()
    };
}

最適化のポイント:

  • 並行処理: 複数の操作の同時実行
  • ガス最適化: 適切なガス価格の設定
  • バッチ処理: 複数操作の効率的な実行
  • リソース管理: メモリとガスの効率的な使用

7. 実用的な応用

7.1 ウォレット管理システム

実装コード

class WalletManager {
    constructor(web3Instance) {
        this.web3 = web3Instance;
        this.accounts = [];
    }
    
    async initialize() {
        this.accounts = await this.web3.eth.getAccounts();
        console.log('Wallet initialized with', this.accounts.length, 'accounts');
    }
    
    async getBalance(address) {
        let balance = await this.web3.eth.getBalance(address);
        return this.web3.utils.fromWei(balance, 'ether');
    }
    
    async getAllBalances() {
        let balances = {};
        for (let account of this.accounts) {
            balances[account] = await this.getBalance(account);
        }
        return balances;
    }
    
    async sendTransaction(to, value, data = '') {
        let txOptions = {
            from: this.accounts[0],
            to: to,
            value: this.web3.utils.toWei(value, 'ether'),
            data: data,
            gas: 21000
        };
        
        return await this.web3.eth.sendTransaction(txOptions);
    }
}

// 使用例
(async() => {
    let wallet = new WalletManager(web3);
    await wallet.initialize();
    
    let balances = await wallet.getAllBalances();
    console.log('All balances:', balances);
})();

7.2 コントラクトファクトリー

実装コード

class ContractFactory {
    constructor(web3Instance) {
        this.web3 = web3Instance;
        this.contracts = new Map();
    }
    
    async deployContract(abi, bytecode, constructorArgs = []) {
        let accounts = await this.web3.eth.getAccounts();
        
        let contract = new this.web3.eth.Contract(abi);
        
        let deployedContract = await contract.deploy({
            data: bytecode,
            arguments: constructorArgs
        }).send({
            from: accounts[0],
            gas: 1500000
        });
        
        this.contracts.set(deployedContract.options.address, deployedContract);
        return deployedContract;
    }
    
    getContract(address) {
        return this.contracts.get(address);
    }
    
    async callContractMethod(address, methodName, args = []) {
        let contract = this.getContract(address);
        if (!contract) {
            throw new Error('Contract not found');
        }
        
        return await contract.methods[methodName](...args).call();
    }
    
    async sendContractMethod(address, methodName, args = [], txOptions = {}) {
        let contract = this.getContract(address);
        if (!contract) {
            throw new Error('Contract not found');
        }
        
        let accounts = await this.web3.eth.getAccounts();
        let defaultOptions = {
            from: accounts[0],
            gas: 100000
        };
        
        let finalOptions = { ...defaultOptions, ...txOptions };
        
        return await contract.methods[methodName](...args).send(finalOptions);
    }
}

8. セキュリティとベストプラクティス

8.1 セキュアなコントラクト通信

実装コード

class SecureContractManager {
    constructor(web3Instance) {
        this.web3 = web3Instance;
        this.contracts = new Map();
        this.maxGasPrice = web3.utils.toWei('50', 'gwei');
    }
    
    async validateTransaction(txOptions) {
        // ガス価格の上限チェック
        if (txOptions.gasPrice && web3.utils.toBN(txOptions.gasPrice).gt(web3.utils.toBN(this.maxGasPrice))) {
            throw new Error('Gas price too high');
        }
        
        // 残高の確認
        let balance = await this.web3.eth.getBalance(txOptions.from);
        let requiredBalance = web3.utils.toBN(txOptions.gas || 21000).mul(web3.utils.toBN(txOptions.gasPrice || await this.web3.eth.getGasPrice()));
        
        if (web3.utils.toBN(balance).lt(requiredBalance)) {
            throw new Error('Insufficient balance for gas');
        }
        
        return true;
    }
    
    async safeContractCall(address, methodName, args = [], txOptions = {}) {
        try {
            await this.validateTransaction(txOptions);
            
            let contract = this.contracts.get(address);
            if (!contract) {
                throw new Error('Contract not found');
            }
            
            return await contract.methods[methodName](...args).send(txOptions);
            
        } catch (error) {
            console.error('Secure contract call failed:', error.message);
            throw error;
        }
    }
}

8.2 監査とログ機能

実装コード

class ContractAuditor {
    constructor(web3Instance) {
        this.web3 = web3Instance;
        this.transactionLog = [];
    }
    
    logTransaction(txHash, methodName, args, result) {
        let logEntry = {
            timestamp: new Date().toISOString(),
            txHash: txHash,
            method: methodName,
            arguments: args,
            result: result,
            blockNumber: result.blockNumber
        };
        
        this.transactionLog.push(logEntry);
        console.log('Transaction logged:', logEntry);
    }
    
    getTransactionHistory() {
        return this.transactionLog;
    }
    
    async analyzeContract(address, abi) {
        let contract = new this.web3.eth.Contract(abi, address);
        let events = await contract.getPastEvents('allEvents', {
            fromBlock: 0,
            toBlock: 'latest'
        });
        
        console.log('Contract analysis for', address);
        console.log('Total events:', events.length);
        
        return {
            address: address,
            totalEvents: events.length,
            events: events
        };
    }
}

9. 実装のポイント

9.1 非同期処理のベストプラクティス

適切な非同期処理の実装:

// 良い例: 適切なエラーハンドリング
async function properAsyncHandling() {
    try {
        let result = await web3.eth.getAccounts();
        return result;
    } catch (error) {
        console.error('Error getting accounts:', error);
        throw error;
    }
}

// 悪い例: エラーハンドリングなし
async function improperAsyncHandling() {
    let result = await web3.eth.getAccounts(); // エラーが発生する可能性
    return result;
}

9.2 ガス最適化

効率的なガス使用:

// ガス見積もりの実装
async function estimateGas(contract, methodName, args, txOptions) {
    try {
        let gasEstimate = await contract.methods[methodName](...args).estimateGas(txOptions);
        return Math.floor(gasEstimate * 1.1); // 10%の余裕を持たせる
    } catch (error) {
        console.error('Gas estimation failed:', error);
        return 200000; // デフォルト値
    }
}

10. 学習の成果

10.1 習得した概念

  1. 非同期処理: JavaScriptでのブロックチェーン通信
  2. Web3.js: ブロックチェーンとの相互作用ライブラリ
  3. コントラクト通信: スマートコントラクトとのデータ交換
  4. アカウント管理: ウォレットとアカウントの操作
  5. トランザクション: ブロックチェーンへの状態変更
  6. デバッグ: 開発時の問題解決手法

10.2 実装スキル

  • 非同期プログラミングの理解と実装
  • Web3.jsライブラリの活用
  • スマートコントラクトとの通信
  • エラーハンドリングの実装
  • ガス最適化の技術
  • セキュリティの考慮

10.3 技術的な理解

  • 非同期処理: ブロックチェーン通信の効率化
  • ABI: コントラクトインターフェースの定義
  • トランザクション: ブロックチェーンへの状態変更
  • ガス: ブロックチェーン操作のコスト
  • イベント: コントラクトからの通知システム
  • セキュリティ: 安全なブロックチェーン開発

11. 今後の学習への応用

11.1 発展的な機能

  • マルチチェーン対応: 複数ブロックチェーンでの動作
  • メタトランザクション: ガス代不要の操作
  • オラクル連携: 外部データの取得
  • 高度なデバッグ: より詳細な問題分析

11.2 パフォーマンスの向上

  • バッチ処理: 複数操作の効率的な実行
  • キャッシュ機能: 頻繁な読み取りの最適化
  • 接続プール: ネットワーク接続の管理
  • 並行処理: 複数操作の同時実行

11.3 セキュリティの強化

  • 入力検証: 不正なデータの防止
  • 権限管理: 適切なアクセス制御
  • 監査機能: 操作履歴の追跡
  • 異常検知: 疑わしい活動の検出

参考:

Discussion