【入門編】 AMM流動性プールにおけるスリッページとフロントランニング攻撃 – IoT・OT(制御システム) & ブロックチェーンセキュリティ防御ガイド

こんにちは!ブロックチェーンの世界へようこそ。
スマートコントラクトの開発やWeb3アプリ(dApp)の触り始めって、魔法のように動く仕組みにワクワクしますよね。「自分の手でトークンを交換(スワップ)するアプリを作ったぞ!」と感動した経験がある方も多いのではないでしょうか。

でも、この世界は「ダークフォレスト(暗黒森林)」とも呼ばれています。あなたがブロックチェーンのネットワークに投げ入れたトランザクション(取引データ)は、みんなから丸見えの状態。そこに目を光らせている「泥棒」たちが常にうろついているんです。

今回は、DeFi(分散型金融)の代表格であるAMM(自動マーケットメーカー)の流動性プールを狙った「フロントランニング攻撃」と「サンドイッチ攻撃」について、身近な防犯の例えを交えながら、一歩ずつ優しく紐解いていきましょう!

—

1. 家の鍵と一緒? ブロックチェーンの「メモリプール」と泥棒の話

まずは、私たちが普段使っているブロックチェーンの仕組みを、街中の「郵便ポスト」に例えてみましょう。

あなたが誰かに手紙(トランザクション)を出したいとき、街角のポストに投函しますよね。このポストの中身は、回収されて郵便局で処理されるまでの間、ちょっとした透明ケースに入っていると想像してください。中の手紙がどんな内容か、郵便局員だけでなく、近くをうろついている怪しい泥棒にも丸見えです。

ブロックチェーンの世界では、この「郵便局の回収待ちボックス」のことをメモリプール(mempool / Memory Pool)と呼びます。

ユーザーが「このトークンをこの価格で買いたい!」と注文を出すと、その注文はすぐにブロックチェーンに記録されるわけではなく、一度このメモリプールに溜まります。そして、マイナー(バリデーター)と呼ばれる人たちが、そこから注文をごっそり拾い上げて正しい順番で台帳(ブロック)に書き込んでいくんです。

ここで問題が発生します。
メモリプールの中身は誰でも覗き見ることができるため、悪意あるボット(プログラム)がこう考えてしまうのです。

> 「おっ、あそこの人が『大人気のトークンを大量に買う』っていう注文を出してるぞ! この注文が処理されたら、トークンの値段が跳ね上がるはずだ。……よし、その直前に同じトークンを安い値段で買い占めて、その直後にその人に高く売りつけて大儲けしてやろう!」

これが、まさにフロントランニング(先回り取引)であり、その合わせ技であるサンドイッチ攻撃の正体です。

—

2. パンにはさまれた具材の気持ち:「サンドイッチ攻撃」のメカニズム

サンドイッチ攻撃を、身近な食べ物に例えてみましょう。
あなたは今、人気のパン屋さんで「最後の一個のメロンパン」を買おうとレジに並び、お金を払おうとしています(これがあなたの通常の注文です)。

そこに、ずる賢い常連客の泥棒が割り込んできました。

1. パンの上の層(フロントランニング):
泥棒は、あなたがレジでお金を出す直前に、お店の棚にあるメロンパンを全部買い占めてしまいました。当然、メロンパンは品薄になり、市場価格(価値)が跳ね上がります。
2. 具材(あなたの悲しい取引):
その直後、あなたがレジで注文を処理します。あなたは「さっきより高くなった値段」で、さっきまで安かったはずのメロンパンを買わされてしまいます。
3. パンの下の層(バックランニング):
あなたが買った直後、泥棒は手元にあったメロンパンを、値上がりした「高い価格」で別の客に売りつけて差額をポケットに入れます。

結果として、あなたは大損して、泥棒だけが労せず大儲けすることになります。これが、あなたの注文が「悪意ある2つの注文(前と後ろ)」でサンドイッチされてしまう恐ろしい攻撃の仕組みです。

—

3. なぜ被害に遭うの?「スリッページ」の罠を知ろう

「でも、そんな勝手に値段を釣り上げられたら、買うのをやめればいいのに!」と思いますよね。ここで重要になってくるのが、スリッページ(許容スリッページ)という設定です。

スリッページとは、「注文を出してから実際に取引が完了するまでの間に発生する、価格のズレ(許容範囲)」のことです。

例えば、あなたが「100ドル」でトークンを買おうとしたとき、ネットワークの混雑や他の人の取引によって、実際に処理される瞬間に「101ドル」になってしまうことがあります。この「許容できるズレの幅」を、初期設定やアプリの仕様で甘く設定していると……大変なことになります。

  • スリッページを「5%」などと非常に高く(甘く)設定している場合:

ボットに価格を吊り上げられて、本来の価値よりもはるかに高い値段(例:110ドル)になっても、「あ、許容範囲内だから買っちゃえ!」とスマートコントラクトが自動で実行してしまいます。これが泥棒たちのカモになる瞬間です。

—

4. 開発者・IT担当者が今すぐ実装すべき防衛策

「じゃあ、私たちはどうやって身を守ればいいの?」
安心してください。スマートコントラクトを開発するエンジニアや、dAppを構築するIT担当者が実装できる強力な防衛策がいくつかあります。

ここでは、代表的な防御策である「スリッページの厳格な制限(minAmountOut の指定)」の実装例を、分かりやすいコメント付きのコードで見ていきましょう。

実装例:Solidityにおけるスリッページ保護のコード

DEX(分散型取引所)のルーターコントラクトを呼び出す際、単に「これだけのお金を払うから、トークンをちょうだい」とするのではなく、「最低でもこれだけの枚数をもらえないなら、この取引を全部キャンセル(リバート)してね!」という下限値を必ず指定します。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

interface IUniswapV2Router02 {
    function swapExactETHForTokens(
        uint amountOutMin, // ★ここが命綱!「最低限受け取るべきトークンの下限値」
        address[] calldata path,
        address to,
        uint deadline
    ) external payable returns (uint[] memory amounts);
}

contract SecureSwapExample {
    IUniswapV2Router02 public immutable uniswapV2Router;

    constructor(address _router) {
        uniswapV2Router = IUniswapV2Router02(_router);
    }

    // 安全なスワップ実行関数
    function safeSwapTokens(
        uint minTokensExpected, // フロントエンド側で計算したスリッページ考慮後の下限値
        address tokenAddress
    ) external payable {
        address[] memory path = new address[](2);
        path[0] = uniswapV2Router.WETH(); // 支払いに使うETH(WETH)
        path[1] = tokenAddress;         // 欲しいトークン

        // 有効期限(deadline)を設定し、古いトランザクションが放置されて狙われるのを防ぐ
        uint deadline = block.timestamp + 300; // 5分以内に処理されなければキャンセル

        // プレスのサンドイッチ攻撃を防ぐため、minTokensExpectedを下回ったら即座にロールバックされる
        uniswapV2Router.swapExactETHForTokens{value: msg.value}(
            minTokensExpected, 
            path, 
            msg.sender, 
            deadline
        );
    }
}

フロントエンド(TypeScript / JavaScript)でのパラメータ計算

ユーザーがUIを操作する際も、アプリ側で適切なスリッページ率(例: 0.5%など)を計算して、上記の minTokensExpected を算出してあげる必要があります。

import { ethers } from "ethers";

// ユーザーが買おうとしている想定のトークン量(例: 100枚)
const expectedAmountOut = ethers.parseUnits("100", 18);

// 許容するスリッページを「0.5%」(5000分の50)に設定する
// 泥棒が入り込む隙を与えないため、甘い設定(例: 5%など)は絶対に避ける!
const slippageToleranceBps = 50; // Basis Points (0.5%)

// 最低受け取り枚数(minAmountOut)を計算
// 計算式: 想定枚数 * (10,000 - スリッページ許容値) / 10,000
const minAmountOut = (expectedAmountOut * BigInt(10000 - slippageToleranceBps)) / BigInt(10000);

console.log("安全な最低受け取り枚数:", ethers.formatUnits(minAmountOut, 18));
// この `minAmountOut` をスマートコントラクトの引数に渡します!

—

5. まとめ:一歩ずつ、安全なWeb3開発の習慣をつけよう

今回は、AMMの流動性プールにおけるスリッページとフロントランニング攻撃について、メモリプールの仕組みやサンドイッチ攻撃の例えを交えて解説しました。

  • メモリプールは街の郵便ポスト。中身は全員から丸見え!
  • スリッページを甘く設定していると、ボットにサンドイッチされて大損してしまう!
  • コードを書くときは、必ず minAmountOut(最低受け取り量)や deadline(有効期限)を厳しく設定して身を守ろう!

セキュリティの世界は一見すると難しく感じるかもしれませんが、「泥棒がどこから狙っているか」の視点(攻撃者の目線)を持つだけで、書くコードの質が劇的に変わります。

最初は覚えることが多いかもしれませんが、一歩ずつ安全な実装の習慣を身につけて、信頼されるWeb3開発者を目指していきましょう!

コメント

タイトルとURLをコピーしました