🐕
Solidity基礎学習18日目(スマートコントラクトの検証とERC20)
Solidity基礎学習 - スマートコントラクト検証とERC20トークンの実装
日付: 2025年9月12日
学習内容: スマートコントラクトの検証プロセスとERC20トークンの実装、アクセス制御システムについて
1. スマートコントラクト検証の基本概念
030_Verification.solの概要
スマートコントラクトの検証プロセスについて学習します。EtherScanでの検証手順、バイトコードの確認、ソースコードの公開について理解し、透明性のあるスマートコントラクト開発ができるようになります。
重要なポイント
スマートコントラクト検証の基本構造
contract SimpleContract {
// 残高管理システム
mapping(address => uint) public userBalance;
constructor() {
userBalance[msg.sender] = 100;
}
function transfer(address to, uint amount) public {
userBalance[msg.sender] -= amount;
userBalance[to] += amount;
}
function getBalance(address _addr) public view returns(uint) {
return userBalance[_addr];
}
}
コントラクトの特徴:
- 残高管理: ユーザーの残高を追跡
- 送金機能: ユーザー間でのトークン送金
- 照会機能: 任意のアドレスの残高確認
- 検証対応: EtherScanでの検証が可能
2. EtherScan検証プロセスの詳細分析
2.1 検証手順の実装
検証の流れ
// 1. Sepoliaテストネットにデプロイ
// 2. トランザクションハッシュでデプロイを確認
// 3. コントラクトアドレスでバイトコードを確認
// 4. Verifyボタンから検証を開始
// 5. 必要な情報を入力し、ソースコードをペースト
// 6. 検証完了後、ソースコードが公開される
検証の重要性:
- 透明性: ソースコードの公開による信頼性向上
- セキュリティ: コードの検証による脆弱性の早期発見
- ユーザビリティ: ユーザーがコントラクトの動作を理解可能
2.2 検証に必要な情報
必須情報:
// コントラクトの基本情報
contract SimpleContract {
// コントラクト名
// コンパイラバージョン
// ライセンス情報
// ソースコード
}
検証プロセスの詳細:
- コントラクト名: デプロイ時に使用した名前
- コンパイラバージョン: 使用したSolidityのバージョン
- 最適化設定: コンパイル時の最適化オプション
- コンストラクタ引数: デプロイ時の引数
3. ERC20トークンの実装
3.1 BeerTokenの基本構造
実装コード
contract BeerToken is ERC20, AccessControl {
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
event BeerPurchased(address indexed receiver, address indexed buyer);
constructor(address defaultAdmin, address minter) ERC20("BeerToken", "BRK") {
_grantRole(DEFAULT_ADMIN_ROLE, defaultAdmin);
_grantRole(MINTER_ROLE, minter);
}
}
機能の説明:
- ERC20継承: 標準的なトークン機能の実装
- アクセス制御: ロールベースの権限管理
- イベントログ: ビール購入の記録
- 権限管理: 管理者とミント権限者の分離
3.2 継承システムの詳細
多重継承の仕組み:
contract BeerToken is ERC20, AccessControl {
// ERC20: トークンの基本機能
// AccessControl: アクセス制御機能
// 両方の機能を組み合わせて使用
}
継承の優先順位:
- ERC20: トークンの基本機能(送金、残高管理)
- AccessControl: 権限管理機能(ロール、修飾子)
4. アクセス制御システムの実装
4.1 ロールベースアクセス制御
実装コード
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
constructor(address defaultAdmin, address minter) ERC20("BeerToken", "BRK") {
_grantRole(DEFAULT_ADMIN_ROLE, defaultAdmin);
_grantRole(MINTER_ROLE, minter);
}
機能の説明:
- MINTER_ROLE: トークン発行権限の識別子
- DEFAULT_ADMIN_ROLE: システム管理者権限
- 権限の分離: 管理者とミント権限者の役割分担
4.2 ロールの管理機能
管理者権限の活用:
// 新しいミント権限者の追加
function addMinter(address newMinter) public onlyRole(DEFAULT_ADMIN_ROLE) {
_grantRole(MINTER_ROLE, newMinter);
}
// ミント権限者の削除
function removeMinter(address minter) public onlyRole(DEFAULT_ADMIN_ROLE) {
_revokeRole(MINTER_ROLE, minter);
}
セキュリティの特徴:
- 最小権限の原則: 必要最小限の権限のみ付与
- 権限の分離: 管理者とミント権限者の分離
- 柔軟な管理: 必要に応じて権限の追加・削除
5. トークン発行機能の実装
5.1 ミント機能の詳細
実装コード
function mint(address to, uint256 amount) public onlyRole(MINTER_ROLE) {
_mint(to, amount * 10 ** decimals());
}
機能の説明:
- 権限チェック: MINTER_ROLEを持つアドレスのみ実行可能
- トークン発行: 指定されたアドレスに新しいトークンを発行
-
小数点調整:
10 ** decimals()を掛けて小数点以下の問題を回避 - 総供給量更新: 発行されたトークンが総供給量に反映
小数点調整の重要性:
// ERC20の標準的な小数点設定
function decimals() public view virtual returns (uint8) {
return 18; // 通常は18桁の小数点
}
// 実際のトークン量の計算
uint256 actualAmount = amount * 10 ** decimals();
// 例: amount = 1の場合、actualAmount = 1 * 10^18 = 1000000000000000000
5.2 ミント機能のセキュリティ
権限管理の実装:
// 修飾子による権限チェック
modifier onlyRole(bytes32 role) {
_checkRole(role);
_;
}
// 権限の確認
function _checkRole(bytes32 role) internal view virtual {
_checkRole(role, _msgSender());
}
セキュリティの特徴:
- 自動権限チェック: 関数実行前の自動的な権限確認
- 権限の継承: AccessControlから継承された機能
- 柔軟な拡張: 新しいロールの追加が容易
6. ビール購入機能の実装
6.1 自己購入機能
実装コード
function buyOneBeer() public {
_burn(_msgSender(), 1 * 10 ** decimals());
emit BeerPurchased(_msgSender(), _msgSender());
}
機能の説明:
- 自己完結型購入: 自分のトークンでビールを購入
- トークン消費: 1トークンを消費してビールを購入
-
小数点調整:
10 ** decimals()を掛けて小数点以下の問題を回避 - イベント発火: 購入記録をブロックチェーンに記録
小数点調整の実装:
// 実際の消費量の計算
uint256 actualAmount = 1 * 10 ** decimals();
// 例: decimals() = 18の場合、actualAmount = 1 * 10^18 = 1000000000000000000
6.2 代理購入機能
実装コード
function buyOneBeerFrom(address account) public{
_spendAllowance(account, _msgSender(), 1 * 10 ** decimals());
_burn(account, 1 * 10 ** decimals());
emit BeerPurchased(_msgSender(), account);
}
機能の説明:
- 代理購入: 他人のトークンでビールを購入
- 許可確認: 事前の許可が必要
-
小数点調整:
10 ** decimals()を掛けて小数点以下の問題を回避 - 権限管理: 許可された範囲内でのみ実行可能
小数点調整の一貫性:
// 許可量の確認と消費量の調整
_spendAllowance(account, _msgSender(), 1 * 10 ** decimals()); // 許可量の確認
_burn(account, 1 * 10 ** decimals()); // 実際の消費
7. 許可システムの詳細
7.1 許可の確認機能
許可確認の実装:
function _spendAllowance(address owner, address spender, uint256 amount) internal virtual {
uint256 currentAllowance = allowance(owner, spender);
if (currentAllowance != type(uint256).max) {
require(currentAllowance >= amount, "ERC20: insufficient allowance");
unchecked {
_approve(owner, spender, currentAllowance - amount);
}
}
}
機能の説明:
- 許可量確認: 現在の許可量をチェック
- 不足チェック: 必要な量が許可されているか確認
- 許可量更新: 使用した分だけ許可量を減らす
- 小数点対応: 調整された量での許可確認
7.2 小数点調整の重要性
ERC20の小数点システム:
// 標準的な小数点設定
function decimals() public view virtual returns (uint8) {
return 18; // 18桁の小数点
}
// 実際のトークン量の計算例
uint256 userInput = 1; // ユーザーが入力した量
uint256 actualAmount = userInput * 10 ** decimals(); // 実際のトークン量
// 結果: 1000000000000000000 (1 * 10^18)
小数点調整の理由:
- 整数演算: Solidityは小数点を直接サポートしない
- 精度保持: 小数点以下の精度を保持
- 標準準拠: ERC20標準に準拠した実装
- 互換性: 他のERC20トークンとの互換性
7.3 許可の付与機能
許可付与の実装:
// ERC20から継承された関数
function approve(address spender, uint256 amount) public virtual returns (bool) {
_approve(_msgSender(), spender, amount);
return true;
}
使用例:
// ユーザーAがユーザーBに5トークンの使用許可を与える
approve(ユーザーBのアドレス, 5 * 10 ** decimals());
// ユーザーBがユーザーAのトークンを使用
buyOneBeerFrom(ユーザーAのアドレス);
小数点調整の一貫性:
// 許可の付与時も小数点調整が必要
uint256 approvalAmount = 5 * 10 ** decimals(); // 5トークンの許可
approve(spender, approvalAmount);
// 使用時も同じ調整が適用される
_spendAllowance(account, _msgSender(), 1 * 10 ** decimals());
8. イベントシステムの実装
8.1 イベントの定義
実装コード
event BeerPurchased(address indexed receiver, address indexed buyer);
機能の説明:
- 永続的記録: ブロックチェーン上に永続的に記録
- 検索可能: indexedパラメータによる効率的な検索
- 透明性: 誰でも購入履歴を確認可能
8.2 イベントの活用
フロントエンドでの監視:
// 特定のユーザーのビール購入を監視
contract.on("BeerPurchased", (receiver, buyer, event) => {
console.log(`ビール購入: 受け取り人=${receiver}, 支払い人=${buyer}`);
});
// 特定のアドレスのイベントをフィルタリング
const filter = contract.filters.BeerPurchased(null, userAddress);
統計データの収集:
- ビールの購入回数
- 特定ユーザーの購入履歴
- 売上データの分析
9. 継承システムの詳細分析
9.1 多重継承の仕組み
継承チェーン:
BeerToken
├── ERC20
│ ├── Context ← _msgSender()が定義されている
│ ├── IERC20
│ └── IERC20Metadata
└── AccessControl
├── Context ← _msgSender()が定義されている
├── IAccessControl
└── IERC165
継承の優先順位:
- ERC20: 最初に継承されるため、ERC20の機能が優先
- AccessControl: 2番目に継承される
- Context: 両方から継承されるが、実装は同じ
9.2 関数の継承とオーバーライド
継承された関数:
// ERC20から継承
function _mint(address to, uint256 amount) internal virtual;
function _burn(address account, uint256 amount) internal virtual;
function _spendAllowance(address owner, address spender, uint256 amount) internal virtual;
// AccessControlから継承
function _grantRole(bytes32 role, address account) internal virtual;
function _revokeRole(bytes32 role, address account) internal virtual;
modifier onlyRole(bytes32 role);
10. コンストラクタの詳細分析
10.1 親コンストラクタの呼び出し
実装コード
constructor(address defaultAdmin, address minter) ERC20("BeerToken", "BRK") {
_grantRole(DEFAULT_ADMIN_ROLE, defaultAdmin);
_grantRole(MINTER_ROLE, minter);
}
実行順序:
- ERC20("BeerToken", "BRK"): 親コンストラクタの実行
- BeerTokenのコンストラクタ: 子コンストラクタの実行
10.2 初期化プロセス
ERC20の初期化:
// ERC20コンストラクタ内で実行
_name = "BeerToken"; // トークン名
_symbol = "BRK"; // トークンシンボル
BeerTokenの初期化:
// BeerTokenコンストラクタ内で実行
_grantRole(DEFAULT_ADMIN_ROLE, defaultAdmin);
_grantRole(MINTER_ROLE, minter);
11. セキュリティ設計の詳細
11.1 権限管理の階層
権限レベルの設計:
// レベル1: 管理者(完全な権限)
DEFAULT_ADMIN_ROLE
// レベル2: ミント権限者(トークン発行権限)
MINTER_ROLE
// レベル3: 一般ユーザー(制限付き権限)
// トークンの送金、購入のみ可能
権限チェックの実装:
// 管理者権限のチェック
require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender), "Not admin");
// ミント権限のチェック
require(hasRole(MINTER_ROLE, msg.sender), "Not minter");
11.2 セキュリティのベストプラクティス
最小権限の原則:
// 悪い例:管理者が直接mintする
function adminMint(address to, uint256 amount) public onlyRole(DEFAULT_ADMIN_ROLE) {
_mint(to, amount);
}
// 良い例:管理者はミント権限者を管理するのみ
function addMinter(address minter) public onlyRole(DEFAULT_ADMIN_ROLE) {
_grantRole(MINTER_ROLE, minter);
}
12. 実用的な応用
12.1 DeFiプロトコルとの連携
外部プロトコルとの相互作用:
// Uniswapでの流動性提供
function provideLiquidity(uint amount) public {
approve(uniswapRouter, amount);
// Uniswapでの流動性提供
}
// Compoundでの預金
function depositToCompound(uint amount) public {
approve(compoundContract, amount);
// Compoundでの預金
}
12.2 マルチシグウォレットの拡張
高度な権限管理:
contract AdvancedBeerToken {
// 時間ベースの権限
mapping(address => uint) public permissionExpiry;
// 日次送金制限
mapping(address => uint) public dailyLimit;
mapping(address => uint) public dailySpent;
mapping(address => uint) public lastSpendDate;
function checkDailyLimit(address user, uint amount) internal {
if(block.timestamp > lastSpendDate[user] + 1 days) {
dailySpent[user] = 0;
lastSpendDate[user] = block.timestamp;
}
require(dailySpent[user] + amount <= dailyLimit[user], "Daily limit exceeded");
dailySpent[user] += amount;
}
}
13. エラーハンドリングと最適化
13.1 包括的なエラーハンドリング
送金失敗の処理:
function safeTransfer(address to, uint256 amount) public returns (bool) {
require(balanceOf(msg.sender) >= amount, "Insufficient balance");
require(to != address(0), "Transfer to zero address");
_transfer(msg.sender, to, amount);
return true;
}
13.2 ガス最適化
効率的な送金処理:
contract GasOptimizedBeerToken {
// バッチ送金機能
function batchTransfer(
address[] memory recipients,
uint256[] memory amounts
) public {
require(recipients.length == amounts.length, "Array length mismatch");
for(uint i = 0; i < recipients.length; i++) {
_transfer(msg.sender, recipients[i], amounts[i]);
}
}
}
14. 実装のポイント
14.1 セキュリティのベストプラクティス
リエントランシー攻撃への対策:
contract SecureBeerToken {
bool private locked;
modifier noReentrancy() {
require(!locked, "Reentrancy detected");
locked = true;
_;
locked = false;
}
function secureBuyBeer() public noReentrancy {
_burn(msg.sender, 1);
emit BeerPurchased(msg.sender, msg.sender);
}
}
14.2 イベントログの実装
透明性の確保:
contract TransparentBeerToken {
event AdminChanged(address indexed oldAdmin, address indexed newAdmin);
event MinterAdded(address indexed minter);
event MinterRemoved(address indexed minter);
event BeerPurchased(address indexed receiver, address indexed buyer);
function addMinter(address minter) public onlyRole(DEFAULT_ADMIN_ROLE) {
_grantRole(MINTER_ROLE, minter);
emit MinterAdded(minter);
}
}
15. 学習の成果
15.1 習得した概念
- スマートコントラクト検証: EtherScanでの検証プロセス
- ERC20トークン: 標準的なトークンの実装
- アクセス制御: ロールベースの権限管理
- 多重継承: 複数コントラクトの機能統合
- イベントシステム: ブロックチェーン上のログ記録
- 許可システム: 第三者による制限付きトークン使用
-
小数点調整:
10 ** decimals()による精度管理
15.2 実装スキル
- 継承システムの設計と実装
- ロールベースアクセス制御の実装
- ERC20標準の準拠
- イベントログの活用
- セキュリティ強化のベストプラクティス
15.3 技術的な理解
- 多重継承: 複数コントラクトの機能統合
- アクセス制御: ロールベースの権限管理
- ERC20標準: トークンの標準的な実装
- イベントシステム: ブロックチェーン上のログ記録
- 検証プロセス: EtherScanでのソースコード公開
- 小数点管理: ERC20の精度システムと整数演算
16. 今後の学習への応用
16.1 発展的な機能
- 時間ベースの権限: 期限付きのトークン使用権限
- マルチチェーン対応: 複数ブロックチェーンでの動作
- オラクル連携: 外部データに基づく自動実行
- 高度なセキュリティ: より複雑な攻撃への対策
16.2 セキュリティの向上
- 監査機能: トークン使用履歴の追跡と分析
- 異常検知: 疑わしい活動の自動検出
- 緊急停止: セキュリティ侵害時の即座な対応
- 復旧機能: 攻撃後の状態回復
16.3 パフォーマンスの最適化
- バッチ処理: 複数送金の効率的な実行
- ガス最適化: 送金処理のコスト削減
- スケーラビリティ: 大量送金の効率的な処理
- メタトランザクション: ガス代不要の送金
参考:
Discussion