Solidity

Solidityの安全なコントラクト設計

動くコードと、攻撃や誤操作に耐えるコードの違いを学びます。

Solidity入門の講座一覧

01

アクセス制御

重要な関数には「誰が呼べるか」を必ず書きます。msg.senderは引数で受け取ったアドレスではなく、実際の呼び出し元です。

管理者を固定するSOLIDITY
modifier onlyAdmin() {
    require(msg.sender == admin, "not admin");
    _;
}

function setFee(uint256 nextFee) external onlyAdmin {
    require(nextFee <= 100, "fee too high");
    fee = nextFee;
}
実行結果OUTPUT
adminでsetFee(5) → fee: 5
admin以外でsetFee(5) → revert "not admin"
setFee(101) → revert "fee too high"

アドレスを引数にして権限判定すると、他人のアドレスを指定してなりすます設計になりかねません。

02

Checks-Effects-Interactions

外部コントラクト呼び出しを含む処理は、①条件確認、②自分の状態更新、③外部呼び出しの順に分けます。外部呼び出し先が予想外のコードを実行する可能性を忘れないための基本パターンです。

引き出しの順序SOLIDITY
function withdraw() external {
    uint256 amount = credits[msg.sender];
    require(amount > 0, "no credit");
    credits[msg.sender] = 0;
    (bool success, ) = payable(msg.sender).call{value: amount}("");
    require(success, "send failed");
}
実行結果OUTPUT
credits[Account 0] = 1 ether
withdraw() → credits[Account 0] = 0、送金成功
03

再入可能性を知る

外部へ送金すると、相手のfallbackやreceiveが呼ばれ、相手から元の関数を再度呼ばれることがあります。状態更新を送金の前に行う、再入防止ロックを使う、pull paymentに分けるなどの対策を検討します。

オンライン例をそのまま資産管理へ使わない

安全性はコントラクト全体、依存ライブラリ、コンパイラー、運用方法で決まります。小さな例でもレビューとテストが必要です。

04

計算と入力を検証する

0.8系の通常の算術演算はオーバーフロー時にrevertしますが、入力値の上限やゼロ除算、配列の範囲外、ガスを使い切るループは別に検証します。ユーザーが入力する値は必ず境界値をテストします。

05

公開前の確認リスト

  • 権限のないアカウントで管理関数を呼んだか
  • 0、最大値、残高不足、二重実行を試したか
  • 外部呼び出しの前に状態を更新しているか
  • イベントとエラーで失敗を調査できるか
  • コンパイラーと依存ライブラリのバージョン指定・互換性を確認したか

この講座の例ではpragma solidity ^0.8.20;のように0.8系の互換範囲を指定しています。本番では使用するコンパイラー、最適化設定、依存ライブラリのバージョンを記録し、互換性を確認してから公開します。

次はテストとデプロイで、自動確認と公開前の手順を学びます。