Appearance
L1-5: Token Wrapper(WETH)
1. 課題
ETHのERC20互換ラッパートークンを作成します。ユーザーはETHを預け入れてWETHを受け取り、WETHを焼却してETHを引き出します。為替レートは1:1です。
2. 背景
ネイティブETHはERC20標準に準拠しておらず、DeFiプロトコルと直接やり取りできません。WETHはETHのERC20等価物として機能します:
- すべてのDeFiプロトコルが同じインターフェースでトークンを処理可能
- ETH保有者がAMM、レンディングなどのプロトコルに参加可能
- プロトコル側でネイティブETHの特別な処理が不要
3. 解決策
OpenZeppelin ERC20の継承
solidity
contract WrappedETH is ERC20("WrappedEth", "WETH")
コア関数
deposit()payable: ETH → WETH(mint)withdraw(uint256): WETH → ETH(burn + transfer)receive(): 自動deposit
4. 遭遇した落とし穴
4.1 直接のETH送金が無視される
receive()を実装していない場合、ユーザーがコントラクトに直接ETHを送金すると拒否されるか、黙って失われます。
4.2 関数名とイベント名の競合
Solidityではdeposit関数名とDepositイベント名が曖昧さを引き起こす可能性があります。receive()内でdeposit()を呼び出すと、コンパイラがエラーを報告する場合があります。
4.3 残高不足の未チェック
withdrawで残高をチェックしないと、burnが失敗します。
5. 落とし穴の原因
5.1
receive()のないコントラクトはETHの受け取り時にリバートします。deposit()の呼び出しを忘れて直接ETHを送金したユーザーのトランザクションは失敗します。
5.2
receive関数内でdeposit()を呼び出すと、Solidityコンパイラはdepositを関数ではなくDepositイベントへの参照として解決する場合があります。this.deposit()を使用するか、ロジックをインライン化します。
5.3
OZ ERC20の_burnは残高をチェックしますが、withdrawの入口で先にチェックしないと、失敗するburnにgasを無駄にする可能性があります。
6. 落とし穴の解決方法
receive() external payableを実装し、depositロジックをインライン化withdrawで最初にrequire(balanceOf(msg.sender) >= amount)をチェック- 明示的な
this.deposit()を使用するか、receive内にロジックを直接記述
7. 技術的ポイント
| ポイント | 説明 |
|---|---|
| WETH標準 | 1 ETH = 1 WETH、完全に代替可能 |
| ERC20継承 | OZ ERC20が完全な実装を提供 |
| receive vs fallback | receive()は純粋なETHを処理、fallback()はデータ付きを処理 |
| ETHカストディのセキュリティ | コントラクトは総供給量と等しいETHを保持 |