😎
Solidity基礎学習15日目(Event)
Solidity基礎学習 - イベント(Event)システムとログ管理
日付: 2025年9月8日
学習内容: Solidityのイベント(Event)システム、ログ記録、フロントエンドとの通信、indexedパラメータによる検索・フィルタリングについて
1. イベント(Event)の基本概念
イベントの定義と役割
Solidityのイベントは、スマートコントラクトから外部アプリケーションへの通知システムです。ブロックチェーン上に永続的に記録されるログデータを作成し、トランザクションの実行履歴として残します。
重要なポイント
イベントの基本構造
// イベントの定義
event TokenTransfer(address indexed _sender, address indexed _receiver, uint _value);
// イベントの発火
emit TokenTransfer(msg.sender, _receiver, _value);
イベントの特徴:
- 永続的記録: ブロックチェーンに永続的に保存される
- ガス効率: ストレージ保存よりコストが安い
- 検索可能: indexedパラメータで効率的な検索が可能
- 外部通信: フロントエンドアプリケーションとの通信手段
2. イベントの主要な役割
2.1 ログの記録機能
実装コード
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
contract TokenManager {
mapping(address => uint256) public userBalance;
// トークン転送イベントの定義
event TokenTransfer(
address indexed _sender,
address indexed _receiver,
uint256 _amount
);
// 残高更新イベントの定義
event BalanceUpdated(
address indexed _user,
uint256 _oldBalance,
uint256 _newBalance
);
constructor() {
userBalance[msg.sender] = 1000;
}
function transferTokens(address _receiver, uint256 _amount) public returns(bool) {
require(userBalance[msg.sender] >= _amount, "Insufficient balance");
uint256 oldSenderBalance = userBalance[msg.sender];
uint256 oldReceiverBalance = userBalance[_receiver];
userBalance[msg.sender] -= _amount;
userBalance[_receiver] += _amount;
// イベントの発火
emit TokenTransfer(msg.sender, _receiver, _amount);
emit BalanceUpdated(msg.sender, oldSenderBalance, userBalance[msg.sender]);
emit BalanceUpdated(_receiver, oldReceiverBalance, userBalance[_receiver]);
return true;
}
}
ログ記録の重要性:
- 取引履歴: 全てのトークン転送の記録
- 監査証跡: 変更履歴の追跡可能性
- 透明性: ブロックチェーンの透明性の実現
- デバッグ: 開発時の問題特定
2.2 フロントエンドとの通信
実装コード
contract UserActivityTracker {
struct UserActivity {
uint256 lastActivity;
uint256 activityCount;
string lastAction;
}
mapping(address => UserActivity) public userActivities;
// ユーザーアクティビティイベント
event UserAction(
address indexed _user,
string _action,
uint256 _timestamp,
uint256 _activityCount
);
// システム通知イベント
event SystemNotification(
string _message,
uint256 _timestamp,
address _affectedUser
);
function performAction(string memory _action) public {
UserActivity storage activity = userActivities[msg.sender];
activity.lastActivity = block.timestamp;
activity.activityCount++;
activity.lastAction = _action;
// アクティビティイベントの発火
emit UserAction(msg.sender, _action, block.timestamp, activity.activityCount);
// 特定条件でのシステム通知
if (activity.activityCount % 10 == 0) {
emit SystemNotification(
"Congratulations! You've completed 10 actions!",
block.timestamp,
msg.sender
);
}
}
}
フロントエンド通信の特徴:
- リアルタイム通知: 即座な状態変化の通知
- イベント監視: 特定イベントの監視と処理
- 状態同期: フロントエンドとコントラクトの状態同期
- ユーザー体験: インタラクティブなユーザー体験の実現
3. indexedパラメータによる検索・フィルタリング
3.1 indexedパラメータの基本
実装コード
contract AdvancedEventSystem {
// indexedパラメータを使用したイベント定義
event UserRegistration(
address indexed _user, // indexed: 検索可能
string _username, // 非indexed: データのみ
uint256 indexed _registrationTime, // indexed: 検索可能
uint256 _userId // 非indexed: データのみ
);
event TokenPurchase(
address indexed _buyer, // indexed: 検索可能
address indexed _seller, // indexed: 検索可能
uint256 indexed _tokenId, // indexed: 検索可能
uint256 _price, // 非indexed: データのみ
string _tokenName // 非indexed: データのみ
);
uint256 public nextUserId = 1;
mapping(address => uint256) public userIds;
function registerUser(string memory _username) public {
require(userIds[msg.sender] == 0, "User already registered");
uint256 userId = nextUserId++;
userIds[msg.sender] = userId;
// indexedパラメータを含むイベントの発火
emit UserRegistration(
msg.sender,
_username,
block.timestamp,
userId
);
}
function purchaseToken(
address _seller,
uint256 _tokenId,
uint256 _price,
string memory _tokenName
) public {
// indexedパラメータを含むイベントの発火
emit TokenPurchase(
msg.sender,
_seller,
_tokenId,
_price,
_tokenName
);
}
}
indexedパラメータの特徴:
- 検索可能: 特定の値でイベントを検索可能
- フィルタリング: 条件に合致するイベントのみを取得
- 効率的: 高速な検索とフィルタリング
- 制限: 最大3つのindexedパラメータまで
3.2 indexedパラメータの制限とベストプラクティス
実装コード
contract OptimizedEventSystem {
// 適切なindexedパラメータの使用例
event OrderCreated(
address indexed _customer, // 顧客で検索
address indexed _merchant, // 店舗で検索
uint256 indexed _orderId, // 注文IDで検索
uint256 _totalAmount, // 非indexed: 金額データ
string _orderDetails, // 非indexed: 詳細情報
uint256 _timestamp // 非indexed: タイムスタンプ
);
event OrderStatusChanged(
uint256 indexed _orderId, // 注文IDで検索
string _oldStatus, // 非indexed: 旧ステータス
string _newStatus, // 非indexed: 新ステータス
uint256 _timestamp // 非indexed: 変更時刻
);
uint256 public nextOrderId = 1;
mapping(uint256 => address) public orderCustomers;
mapping(uint256 => address) public orderMerchants;
function createOrder(
address _merchant,
uint256 _totalAmount,
string memory _orderDetails
) public returns(uint256) {
uint256 orderId = nextOrderId++;
orderCustomers[orderId] = msg.sender;
orderMerchants[orderId] = _merchant;
emit OrderCreated(
msg.sender,
_merchant,
orderId,
_totalAmount,
_orderDetails,
block.timestamp
);
return orderId;
}
function updateOrderStatus(
uint256 _orderId,
string memory _newStatus
) public {
require(
orderMerchants[_orderId] == msg.sender,
"Only merchant can update status"
);
emit OrderStatusChanged(
_orderId,
"Pending", // 簡略化のため固定値
_newStatus,
block.timestamp
);
}
}
indexedパラメータの制限:
- 最大3つ: 1つのイベントで最大3つのindexedパラメータ
- 32バイト制限: indexedパラメータは32バイトに制限
- 完全一致: フィルタリングは完全一致のみ(部分一致不可)
- 検索優先: 最も検索頻度の高いパラメータをindexedに設定
4. イベントの実用的な応用
4.1 複雑なイベントシステム
実装コード
contract ComprehensiveEventSystem {
enum OrderStatus { Created, Confirmed, Shipped, Delivered, Cancelled }
struct Order {
uint256 orderId;
address customer;
address merchant;
uint256 totalAmount;
OrderStatus status;
uint256 createdAt;
}
mapping(uint256 => Order) public orders;
uint256 public nextOrderId = 1;
// 注文関連イベント
event OrderCreated(
uint256 indexed _orderId,
address indexed _customer,
address indexed _merchant,
uint256 _totalAmount,
uint256 _timestamp
);
event OrderStatusUpdated(
uint256 indexed _orderId,
OrderStatus _oldStatus,
OrderStatus _newStatus,
uint256 _timestamp
);
event PaymentProcessed(
uint256 indexed _orderId,
address indexed _payer,
uint256 _amount,
string _paymentMethod,
uint256 _timestamp
);
event RefundIssued(
uint256 indexed _orderId,
address indexed _recipient,
uint256 _amount,
string _reason,
uint256 _timestamp
);
function createOrder(
address _merchant,
uint256 _totalAmount
) public returns(uint256) {
uint256 orderId = nextOrderId++;
orders[orderId] = Order({
orderId: orderId,
customer: msg.sender,
merchant: _merchant,
totalAmount: _totalAmount,
status: OrderStatus.Created,
createdAt: block.timestamp
});
emit OrderCreated(
orderId,
msg.sender,
_merchant,
_totalAmount,
block.timestamp
);
return orderId;
}
function updateOrderStatus(
uint256 _orderId,
OrderStatus _newStatus
) public {
require(
orders[_orderId].merchant == msg.sender,
"Only merchant can update status"
);
OrderStatus oldStatus = orders[_orderId].status;
orders[_orderId].status = _newStatus;
emit OrderStatusUpdated(
_orderId,
oldStatus,
_newStatus,
block.timestamp
);
}
function processPayment(
uint256 _orderId,
string memory _paymentMethod
) public {
require(
orders[_orderId].customer == msg.sender,
"Only customer can process payment"
);
emit PaymentProcessed(
_orderId,
msg.sender,
orders[_orderId].totalAmount,
_paymentMethod,
block.timestamp
);
}
function issueRefund(
uint256 _orderId,
address _recipient,
uint256 _amount,
string memory _reason
) public {
require(
orders[_orderId].merchant == msg.sender,
"Only merchant can issue refund"
);
emit RefundIssued(
_orderId,
_recipient,
_amount,
_reason,
block.timestamp
);
}
}
4.2 イベントベースの通知システム
実装コード
contract NotificationSystem {
struct UserPreferences {
bool emailNotifications;
bool smsNotifications;
bool pushNotifications;
uint256 notificationFrequency;
}
mapping(address => UserPreferences) public userPreferences;
// 通知関連イベント
event NotificationSent(
address indexed _recipient,
string _type,
string _message,
uint256 _timestamp
);
event UserPreferencesUpdated(
address indexed _user,
bool _emailEnabled,
bool _smsEnabled,
bool _pushEnabled,
uint256 _frequency
);
event SystemAlert(
string _alertType,
string _message,
uint256 _severity,
uint256 _timestamp
);
function updatePreferences(
bool _emailNotifications,
bool _smsNotifications,
bool _pushNotifications,
uint256 _notificationFrequency
) public {
userPreferences[msg.sender] = UserPreferences({
emailNotifications: _emailNotifications,
smsNotifications: _smsNotifications,
pushNotifications: _pushNotifications,
notificationFrequency: _notificationFrequency
});
emit UserPreferencesUpdated(
msg.sender,
_emailNotifications,
_smsNotifications,
_pushNotifications,
_notificationFrequency
);
}
function sendNotification(
address _recipient,
string memory _type,
string memory _message
) public {
emit NotificationSent(
_recipient,
_type,
_message,
block.timestamp
);
}
function broadcastSystemAlert(
string memory _alertType,
string memory _message,
uint256 _severity
) public {
emit SystemAlert(
_alertType,
_message,
_severity,
block.timestamp
);
}
}
5. フロントエンドでのイベント処理
5.1 Web3.jsでのイベント監視
実装コード
// イベントの監視と処理
class EventMonitor {
constructor(web3, contractAddress, contractABI) {
this.web3 = web3;
this.contract = new web3.eth.Contract(contractABI, contractAddress);
this.eventHandlers = new Map();
}
// リアルタイムイベント監視
startEventMonitoring() {
// トークン転送イベントの監視
this.contract.events.TokenTransfer({
fromBlock: 'latest'
}, (error, event) => {
if (error) {
console.error('Event monitoring error:', error);
} else {
this.handleTokenTransfer(event);
}
});
// 注文作成イベントの監視
this.contract.events.OrderCreated({
fromBlock: 'latest'
}, (error, event) => {
if (error) {
console.error('Order event error:', error);
} else {
this.handleOrderCreated(event);
}
});
}
// イベントハンドラーの登録
registerEventHandler(eventName, handler) {
this.eventHandlers.set(eventName, handler);
}
// トークン転送イベントの処理
handleTokenTransfer(event) {
const { _sender, _receiver, _value } = event.returnValues;
console.log(`Token transfer: ${_sender} → ${_receiver} (${_value})`);
// カスタムハンドラーの実行
const handler = this.eventHandlers.get('TokenTransfer');
if (handler) {
handler(event);
}
}
// 注文作成イベントの処理
handleOrderCreated(event) {
const { _orderId, _customer, _merchant, _totalAmount } = event.returnValues;
console.log(`New order created: #${_orderId} by ${_customer}`);
// カスタムハンドラーの実行
const handler = this.eventHandlers.get('OrderCreated');
if (handler) {
handler(event);
}
}
}
5.2 イベントの検索とフィルタリング
実装コード
// イベントの検索とフィルタリング
class EventSearcher {
constructor(web3, contractAddress, contractABI) {
this.web3 = web3;
this.contract = new web3.eth.Contract(contractABI, contractAddress);
}
// 特定の送信者からのトークン転送を検索
async getTransfersFrom(senderAddress, fromBlock = 0, toBlock = 'latest') {
const filter = this.contract.filters.TokenTransfer(senderAddress, null, null);
return await this.contract.getPastEvents('TokenTransfer', {
filter: filter,
fromBlock: fromBlock,
toBlock: toBlock
});
}
// 特定の受信者へのトークン転送を検索
async getTransfersTo(receiverAddress, fromBlock = 0, toBlock = 'latest') {
const filter = this.contract.filters.TokenTransfer(null, receiverAddress, null);
return await this.contract.getPastEvents('TokenTransfer', {
filter: filter,
fromBlock: fromBlock,
toBlock: toBlock
});
}
// 特定の注文IDのイベントを検索
async getOrderEvents(orderId, fromBlock = 0, toBlock = 'latest') {
const filter = this.contract.filters.OrderStatusUpdated(orderId, null, null);
return await this.contract.getPastEvents('OrderStatusUpdated', {
filter: filter,
fromBlock: fromBlock,
toBlock: toBlock
});
}
// 複数条件での検索
async getComplexSearch(senderAddress, receiverAddress, fromBlock = 0, toBlock = 'latest') {
const filter = this.contract.filters.TokenTransfer(senderAddress, receiverAddress, null);
return await this.contract.getPastEvents('TokenTransfer', {
filter: filter,
fromBlock: fromBlock,
toBlock: toBlock
});
}
// イベントの統計情報を取得
async getEventStatistics(fromBlock = 0, toBlock = 'latest') {
const allTransfers = await this.contract.getPastEvents('TokenTransfer', {
fromBlock: fromBlock,
toBlock: toBlock
});
const statistics = {
totalTransfers: allTransfers.length,
uniqueSenders: new Set(),
uniqueReceivers: new Set(),
totalVolume: 0
};
allTransfers.forEach(event => {
statistics.uniqueSenders.add(event.returnValues._sender);
statistics.uniqueReceivers.add(event.returnValues._receiver);
statistics.totalVolume += parseInt(event.returnValues._value);
});
statistics.uniqueSenders = statistics.uniqueSenders.size;
statistics.uniqueReceivers = statistics.uniqueReceivers.size;
return statistics;
}
}
6. イベントの最適化とベストプラクティス
6.1 ガス効率の最適化
実装コード
contract OptimizedEventContract {
// 効率的なイベント設計
event UserAction(
address indexed _user, // 検索頻度の高いパラメータ
uint256 indexed _actionType, // アクションタイプでフィルタリング
uint256 _timestamp, // 非indexed: タイムスタンプ
bytes32 _dataHash // 非indexed: データハッシュ
);
// バッチイベント(複数の操作を1つのイベントで記録)
event BatchOperation(
address indexed _operator,
uint256 indexed _operationCount,
bytes32 _operationsHash, // 操作のハッシュ
uint256 _timestamp
);
uint256 public operationCount = 0;
function performAction(uint256 _actionType, bytes32 _dataHash) public {
operationCount++;
// 個別のイベント
emit UserAction(
msg.sender,
_actionType,
block.timestamp,
_dataHash
);
}
function performBatchOperation(
bytes32[] memory _operations
) public {
uint256 batchSize = _operations.length;
operationCount += batchSize;
// バッチイベント(ガス効率が良い)
bytes32 operationsHash = keccak256(abi.encodePacked(_operations));
emit BatchOperation(
msg.sender,
batchSize,
operationsHash,
block.timestamp
);
}
}
6.2 イベントの構造化設計
実装コード
contract StructuredEventSystem {
// 構造化されたイベント設計
event SystemEvent(
uint256 indexed _eventId,
string _eventType,
address indexed _actor,
uint256 _timestamp,
bytes _eventData
);
event UserEvent(
address indexed _user,
string _action,
uint256 indexed _category,
uint256 _timestamp,
string _metadata
);
event SystemAlert(
string _alertLevel,
string _message,
uint256 indexed _affectedSystem,
uint256 _timestamp
);
uint256 public nextEventId = 1;
// イベントの構造化された発火
function emitSystemEvent(
string memory _eventType,
address _actor,
bytes memory _eventData
) internal {
emit SystemEvent(
nextEventId++,
_eventType,
_actor,
block.timestamp,
_eventData
);
}
function emitUserEvent(
address _user,
string memory _action,
uint256 _category,
string memory _metadata
) internal {
emit UserEvent(
_user,
_action,
_category,
block.timestamp,
_metadata
);
}
function emitSystemAlert(
string memory _alertLevel,
string memory _message,
uint256 _affectedSystem
) internal {
emit SystemAlert(
_alertLevel,
_message,
_affectedSystem,
block.timestamp
);
}
}
7. 実装のポイント
7.1 イベント設計のベストプラクティス
適切なイベント設計の実装:
// 良い例: 適切なindexedパラメータの使用
event UserRegistration(
address indexed _user, // 検索頻度が高い
uint256 indexed _timestamp, // 時間範囲での検索
string _username, // 非indexed: データのみ
uint256 _userId // 非indexed: データのみ
);
// 悪い例: 過度なindexedパラメータの使用
event BadEvent(
address indexed _user, // OK
string indexed _username, // 悪い: 文字列はindexedに不適切
uint256 indexed _amount, // OK
uint256 indexed _timestamp, // 悪い: 4つ目のindexed
string _description // OK
);
7.2 イベントの効率的な使用
効率的なイベント発火の実装:
// 効率的なイベント発火
function efficientEventEmission() public {
// 必要な情報のみを含むイベント
emit UserAction(
msg.sender,
block.timestamp,
"action_performed"
);
}
// 非効率なイベント発火
function inefficientEventEmission() public {
// 不要な情報を含むイベント
emit UserAction(
msg.sender,
block.timestamp,
"action_performed",
"unnecessary_data",
"more_unnecessary_data"
);
}
8. 学習の成果
8.1 習得した概念
- イベントシステム: Solidityのイベント機能の理解
- ログ記録: ブロックチェーン上での永続的記録
- フロントエンド通信: 外部アプリケーションとの連携
- indexedパラメータ: 効率的な検索・フィルタリング
- ガス最適化: コスト効率の良いイベント設計
- 実用的な応用: 実際のプロジェクトでの活用方法
8.2 実装スキル
- イベント定義の適切な設計
- indexedパラメータの効果的な使用
- フロントエンド連携の実装
- イベント監視システムの構築
- 検索・フィルタリング機能の実装
- ガス最適化の技術
8.3 技術的な理解
- イベント: スマートコントラクトの通知システム
- ログ: ブロックチェーン上の永続的記録
- indexed: 効率的な検索のためのパラメータ
- emit: イベントの発火メカニズム
- フィルタリング: 条件に基づくイベント検索
- フロントエンド連携: リアルタイムな状態同期
9. 今後の学習への応用
9.1 発展的な機能
- 複雑なイベントシステム: 大規模アプリケーションでのイベント管理
- イベント集約: 複数イベントの統合処理
- リアルタイム監視: 高度なイベント監視システム
- イベント分析: データ分析とレポート機能
9.2 パフォーマンスの向上
- イベント最適化: ガス効率の向上
- バッチ処理: 複数イベントの効率的な処理
- キャッシュ機能: 頻繁なイベント検索の最適化
- 並行処理: 複数イベントの同時処理
9.3 セキュリティの強化
- イベント検証: 不正なイベントの検出
- アクセス制御: イベント発火の権限管理
- 監査機能: イベント履歴の追跡
- 異常検知: 疑わしいイベントパターンの検出
参考:
Discussion