WTF Solidity 超シンプル入門: Solidity の制御フローと挿入ソート(InsertionSort)実装の落とし穴
【免费下载链接】WTF-SolidityWTF Solidity 极简入门教程,供小白们使用。Now supports English! 官网: https://wtf.academy项目地址: https://gitcode.com/GitHub_Trending/wt/WTF-Solidity
Solidity の制御フロー(if-else・for・while・do-while・三項演算子)を体系的に学び、その知識を活かして「挿入ソート」を Solidity で実装します。本記事は WTF-Solidity の第 10 講(日本語版)をベースに、リポジトリ内の実源码(10_InsertionSort/InsertionSort.sol)と突き合わせながら、uintの underflow が引き起こすバグの原因と正しい回避策を解説します。読了後は、Solidity 特有の型制約(符号なし整数)を意識した安全なループ実装ができるようになります。
Solidity の制御フロー
Solidity の制御フローは他のプログラミング言語(JavaScript・Python など)とほぼ同様で、以下の構成要素を持ちます。
1.if-else
条件分岐の基本形です。_number == 0のときtrueを、それ以外のときfalseを返します。
function ifElseTest(uint256 _number) public pure returns(bool){ if(_number == 0){ return(true); }else{ return(false); } }2.forループ
初期化式・条件式・更新式を 1 行にまとめた繰り返し処理です。下記は0 + 1 + ... + 9を計算して45を返します。
function forLoopTest() public pure returns(uint256){ uint sum = 0; for(uint i = 0; i < 10; i++){ sum += i; } return(sum); }3.whileループ
条件がtrueの間、処理を繰り返します。forと同じ計算結果(45)を返します。
function whileTest() public pure returns(uint256){ uint sum = 0; uint i = 0; while(i < 10){ sum += i; i++; } return(sum); }4.do-whileループ
whileと異なり、条件を後置するため、条件の真偽にかかわらず少なくとも 1 回は処理を実行します。
function doWhileTest() public pure returns(uint256){ uint sum = 0; uint i = 0; do{ sum += i; i++; }while(i < 10); return(sum); }5. 三項演算子(条件演算子)
三項演算子は Solidity で唯一 3 つのオペランドを受け取る演算子です。構文は条件 ? 条件が true のときの式 : 条件が false のときの式で、if-else文のショートカットとして頻繁に使われます。
// 三項演算子 ternary/conditional operator function ternaryTest(uint256 x, uint256 y) public pure returns(uint256){ // x と y の最大値を返す return x >= y ? x : y; }この 5 つの関数はすべてリポジトリ内の 10_InsertionSort/InsertionSort.sol(および Languages/ja/10_InsertionSort_ja/InsertionSort.sol)にそのまま実装されています。pure指定のため、状態変数を読まず計算のみを行う関数である点も確認できます。
補足:continueとbreak
上記に加えて、continue(現在の反復をスキップして直ちに次のループへ進む)とbreak(現在のループから抜け出す)キーワードが利用可能です。
Solidity で挿入ソート(InsertionSort)を実装する
先に注意: 90% 以上の人が Solidity で挿入ソートを初回実装すると間違える、と言われています。本章ではその「間違い」と「正解」の両方を追体験します。
挿入ソートとは
ソートアルゴリズムは、順不同の数値列(例:[2, 5, 3, 1])を小さい順([1, 2, 3, 5])に並べ替える問題を解きます。挿入ソート(InsertionSort)は最もシンプルで、コンピュータサイエンスの授業で多くの開発者が最初に学ぶアルゴリズムです。
そのロジックは次の 1 点に集約されます。
- 配列
xの先頭から末尾に向かって、要素x[i]をその直前の要素x[i-1]と比較する。x[i]が小さければ位置を入れ替え、さらにx[i-2]と比較し、この過程を続ける。
つまり「カードを手札に挿入していく」感覚で、左側に常にソート済みの部分配列を構築していきます。
Python での実装(基準となるアルゴリズム)
まず教科書的な Python 実装を見てみましょう。keyを基準値として、前方の要素を順にずらしながら挿入位置を探します。
# Insertion Sort を実装する Python プログラム def insertionSort(arr): for i in range(1, len(arr)): key = arr[i] j = i-1 while j >=0 and key < arr[j] : arr[j+1] = arr[j] j -= 1 arr[j+1] = key return arrこの実装の要点は、jが-1になる可能性があることです(ループ内でj -= 1が実行されるため)。Python の整数は符号付きなので問題ありませんが、これが Solidity に移植した際に致命傷となります。
Solidity への移植(バグ入りバージョン)
Python 版(約 9 行)の関数・変数・ループを Solidity 構文に置き換えると、こちらも約 9 行で書けます。
// 挿入ソート(間違いバージョン) function insertionSortWrong(uint[] memory a) public pure returns(uint[] memory) { for (uint i = 1;i < a.length;i++){ uint temp = a[i]; uint j=i-1; while( (j >= 0) && (temp < a[j])){ a[j+1] = a[j]; j--; } a[j+1] = temp; } return(a); }一見正しそうに見えます。しかし、このコードをコンパイルして[2, 5, 3, 1]をソートしようとすると、実行時にエラー(revert)が発生します。Remix の decoded output にはエラー内容が表示されます。
バグの正体:uintの underflow
問題を特定するのに数時間を要し、最終的に判明した原因は次のとおりです。
- Solidity で最もよく使われる整数型
uint(正確にはuint256)は符号なし整数であり、負の値を表現できません。 - 上記コードでは変数
jがj--によって-1になろうとします。 - Solidity 0.8.x 以降はデフォルトで算術オーバーフロー/アンダーフロー検査が有効化されており、
uintが負方向に桁あふれ(underflow)するとトランザクション全体が revert します。
実際にリポジトリの源码 10_InsertionSort/InsertionSort.sol には誤バージョンがそのまま残されており、ソースコード中でもuint j=i-1;とj--の組み合わせが問題を生むことが確認できます。プロジェクト全体のコンパイラは foundry.toml のsolc = "0.8.34"で固定されており、このバージョンではデフォルトの算術検査により underflow が確実に revert として顕在化します。
正しい Solidity 実装(バグ修正版)
修正方針はシンプルです:jが決して負の値を取らないように、jに 1 を加えて基準をずらす。つまりjを「挿入位置の右隣のインデックス」として扱います。
// 挿入ソート(正確なバージョン) function insertionSort(uint[] memory a) public pure returns(uint[] memory) { // 注意: uint 型は負の値を取れない for (uint i = 1;i < a.length;i++){ uint temp = a[i]; uint j=i; while( (j >= 1) && (temp < a[j-1])){ a[j] = a[j-1]; j--; } a[j] = temp; } return(a); }修正点の比較:
| 項目 | 誤バージョン | 正バージョン |
|---|---|---|
| 初期値 | uint j = i - 1;(i=1のときj=0、その後-1に陥る) | uint j = i;(常に非負) |
| ループ条件 | j >= 0(常に真になり得る) | j >= 1(jが 0 になった時点で終了) |
| 比較対象 | temp < a[j] | temp < a[j-1] |
| ずらし処理 | a[j+1] = a[j] | a[j] = a[j-1] |
| 挿入位置 | a[j+1] = temp | a[j] = temp |
jの取りうる範囲が1..a.length-1に限定されるため、a[j-1]の添え字も常に0以上となり、underflow も範囲外アクセスも発生しません。[2, 5, 3, 1]を入力すると[1, 2, 3, 5]が正しく返ります。
リポジトリ内での活用例
この第 10 講のソート関数は、以降の講義でも再利用されています。例えばインターフェースの講義(14_Interface/Interface.sol)では、挿入ソートを外部関数として抽象化した宣言
function insertionSort(uint[] memory a) public pure virtual returns(uint[] memory);が定義されており、インターフェースを介してソートロジックを呼び出す実例として登場します。第 10 講の実装をマスターすることで、このような抽象化・再利用パターンも自然に理解できるようになります。
なお、10_InsertionSort/readme.md(中文版)や Languages/en/10_InsertionSort_en/readme.md(英語版)にも同じ内容のチュートリアルがあり、多言語で比較学習が可能です。源码は 10_InsertionSort/InsertionSort.sol にInsertionSortコントラクトとして一式(制御フロー 5 関数 + 誤・正 2 バージョンのソート)がまとまっているため、Remix に貼り付けるか Foundry 環境(forge build)でそのままコンパイル・実行して確認できます。
まとめ
本講では、Solidity の制御フロー(if-else/for/while/do-while/ 三項演算子、およびcontinue・break)を紹介し、それを用いて「一見簡単だが実はバグが発生しやすい」挿入ソートを実装しました。最大の学びは、Python など符号付き整数を扱う言語のアルゴリズムを Solidity に移植する際、uintの underflow という Solidity 特有の罠に注意しなければならないという点です。
Solidity はシンプルに見えますが、型システムや算術検査の仕様に起因する罠が多く存在します。毎月、スマートコントラクトの小さなバグが原因でプロジェクトがハッキングされ、数百万ドル規模の損失が発生しています。安全なコントラクトを書くためには、基礎を確実にマスターし、継続的に練習を重ねることが不可欠です。
【免费下载链接】WTF-SolidityWTF Solidity 极简入门教程,供小白们使用。Now supports English! 官网: https://wtf.academy项目地址: https://gitcode.com/GitHub_Trending/wt/WTF-Solidity
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考