こんにちは!Web3の世界へようこそ。
スマートコントラクトの開発、日々ワクワクしながら進めていますよね。「自分でルールを決められるデジタルな自動販売機」を作っているような感覚で、本当に面白い分野だと思います。
でも、この「自分でルールを決められる」という強力な特徴は、裏を返すと「ルールを悪用されたときに、誰も止めてくれない」という怖さも秘めています。
今回は、DeFi(分散型金融)の世界で恐れられている「ガバナンス攻撃(投票権の買い占めと悪意ある提案の実行)」について、身近な防犯の例えを交えながら、一歩ずつ優しく紐解いていきたいと思います。難しい用語が出てきても置いてけぼりにしませんので、リラックスして読んでくださいね!
—
1. 家の鍵と「ガバナンス」の仕組みを例えてみよう
皆さんが住んでいるマンションには、住民全員で話し合って決める「管理組合」のような仕組みがあるかもしれません。「来年はエントランスの修繕をしよう」「防犯カメラを増やそう」といったルールは、住民の投票(多数決)で決まりますよね。
ブロックチェーンの世界にあるプロトコル(金融システムなど)も同じです。誰か一人の社長さんが決めるのではなく、そのサービス専用の「ガバナンストークン(投票権)」を持っている人たちが集まって、今後の運営方針や資金の使い道を投票で決めています。
- 1トークン = 1票
これが基本ルールです。たくさんトークンを持っている人ほど、発言力が強くなる仕組みですね。平和なときはとても民主的で素晴らしい仕組みなのですが、ここに「フラッシュローン」という、現代のデジタル怪盗が使う「裏技」が絡むと、一気に悪夢に変わります。
—
2. デジタル怪盗の早業:フラッシュローンを使ったガバナンス攻撃の手口
フラッシュローンというのは、簡単に言うと「担保なしで、何億円ものお金を数秒の間だけ借りられる魔法の銀行」のようなものです。ただし、「借りたその瞬間のトランザクション(一連の処理)の中で、必ず全額を返済しなければならない」という絶対のルールがあります。
泥棒(攻撃者)はこのフラッシュローンを使って、次のような恐ろしい犯罪をたった1回のトランザクションの中で実行します。
1. 大金をごっそり借りる:フラッシュローンを使って、市場にあるほとんどのガナンストークンを一時的に買い占めます。
2. 投票権を我が物にする:手元に大量の「投票権(トークン)」が集まった状態で、悪意ある提案(例:「プロトコルの資金をすべて私の財布に送金せよ」という提案)を自分で出します。
3. 即座に可決させる:圧倒的な票数を持っているため、即座に投票が可決されます。
4. 資金を持ち逃げする:提案が実行され、プロトコルの金庫から資金が引き出されます。
5. 借金を返す:最後にフラッシュローンの元金を銀行に返し、残った巨額の利益を持ち逃げします!
映画のワンシーンのようですが、現実のブロックチェーン上ではこれがほんの数秒で行われてしまうのです。「そんなのあり?」と思いますよね。これがガバナンストークンの買い占め攻撃の正体です。
—
3. どうやって防ぐの?実務で使える対策コード
「じゃあ、どうやってこの泥棒を防げばいいの?」という話ですよね。
一番効果的な対策は、「今買ったばかりの投票権は、すぐには使わせない」というルール(時間差攻撃への備え)をスマートコントラクトに組み込むことです。
身近な例で言えば、合鍵を作ってすぐにマンションの総会に出て投票しようとしても、「あなた、先ほど引っ越してきたばかりでまだ住民登録の確認が取れていないので、今日の投票には参加できません」と門前払いにされるようなものです。
具体的に、OpenZeppelinなどのライブラリを使って、投票権に「スナップショット(過去の記録)」の仕組みを取り入れたコードを見てみましょう。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
// ERC20Votesを継承することで、過去のブロック時点での投票権を正確に計算できるようになります
contract SecureGovernanceToken is ERC20Votes {
constructor(string memory name, string memory symbol)
ERC20Permit(name)
ERC20(name, symbol)
{
// 初回発行分のトークンをデプロイ者に渡すなどの初期化処理
_mint(msg.sender, 1000000 * 10 ** decimals());
}
// オーバライドが必要な内部関数(ERC20Votesの仕様に合わせるため)
function _update(address from, address to, uint256 value)
internal
override(ERC20Votes)
{
super._update(from, to, value);
}
function nonces(address owner)
public
view
override(ERC20Permit, Nonces)
returns (uint256)
{
return super.nonces(owner);
}
}
このコードがすごい理由(解説)
上記のコードでは、通常のERC-20トークンにERC20Votesという拡張機能を組み込んでいます。
これにより、プロトコル側で投票を行う際、「現在のトークン残高」ではなく、「提案が作成された過去の特定のブロック時点でのトークン残高」をチェックできるようになります。
もし攻撃者がフラッシュローンを使って「今」大量のトークンを買い占めたとしても、提案が作られた「過去の時点」では一文無しだったため、投票権は「0票」として無視されます。これでフラッシュローンを使った一撃離脱の攻撃を完全に封じ込めることができるのです!
—
4. セキュリティリサーチャーからの現場アドバイス
私たちセキュリティの現場にいる人間が、新しいプロトコルのコードレビュー(監査)をするときは、必ず次のポイントを確認しています。
- ガバナンストークンに投票権の遅延(タイムロックやスナップショット)が実装されているか?
- 緊急時の提案(Emergency Proposal)において、手続きが短縮されすぎて攻撃者の踏み台になっていないか?
- マルチシグ(複数人の承認者)やタイムロックコントラクトを組み合わせて、万が一のときに管理者がストップをかけられる逃げ道(キルスイッチ)を用意しているか?
技術は日々進化し、攻撃者の手口も巧妙になっています。でも、基礎的な防犯の考え方(「すぐに信用しない」「過去の履歴を確認する」「逃げ道を用意する」)は、現実世界もWeb3の世界も全く同じです。
一歩ずつ、安全で信頼できるスマートコントラクトの書き方をマスターしていきましょう!あなたの開発するサービスが、安全で素敵な世界の一部になることを応援しています。
コメント