0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Foundryのチートコード

Posted at

よく使われる Cheatcodes

Cheatcode 説明
deal(address, uint) アドレスの ETH 残高を変更
writeTokenBalance(...) 任意のERC20トークンの残高を変更
prank(address) 次の1つの呼び出しで msg.sender を偽装
startPrank(address) / stopPrank() 複数呼び出しの間、ずっと msg.sender を偽装
warp(uint) ブロックタイムスタンプを変更(block.timestamp
roll(uint) ブロックナンバーを変更(block.number
expectRevert() revert を期待(例外テスト)
expectEmit() イベントの発火を検証
store(address, bytes32 key, bytes32 value) ストレージを直接書き換え
load(address, bytes32 key) ストレージを直接読み出し
record() / accesses(address) ストレージの読み書きを追跡
ffi(string[]) 外部コマンドを実行(例:シェルコマンド)※安全性に注意
broadcast() スクリプトをブロードキャストモードに(デプロイ用)

function testFakeSenderAndTime() public {
// msg.sender を別アドレスに偽装
address hacker = address(0xBEEF);
vm.prank(hacker);

myContract.doSomething(); // msg.sender == hacker になる

// ブロックタイムを未来に進める
vm.warp(block.timestamp + 1 days);
assertEq(block.timestamp, original + 1 days);

}

// スロット1に任意の値を書き込む
vm.store(address(myContract), bytes32(uint256(1)), bytes32(uint256(123)));

// 値を読み取る
bytes32 val = vm.load(address(myContract), bytes32(uint256(1)));

参考ドキュメント
Foundry公式ドキュメント:
👉 https://book.getfoundry.sh/cheatcodes/

過去のバグの例

// contracts/VulnerableVault.sol
pragma solidity ^0.8.0;

contract VulnerableVault {
mapping(address => uint256) public balances;

function deposit() public payable {
    balances[msg.sender] += msg.value;
}

function withdraw() public {
    uint256 bal = balances[msg.sender];
    require(bal > 0, "Nothing to withdraw");

    // ✅ 問題:送金が先、残高更新が後
    (bool sent, ) = msg.sender.call{value: bal}("");
    require(sent, "Failed to send");

    balances[msg.sender] = 0;
}

// allow deposits to contract
receive() external payable {}

}

Foundry テスト 

// test/VulnerableVault.t.sol
import "forge-std/Test.sol";

contract Attacker {
VulnerableVault public target;
address owner;

constructor(address _target) {
    target = VulnerableVault(_target);
    owner = msg.sender;
}

// fallback to re-enter
receive() external payable {
    if (address(target).balance > 0) {
        target.withdraw();
    }
}

function attack() external payable {
    require(msg.value >= 1 ether);
    target.deposit{value: 1 ether}();
    target.withdraw();
}

function withdrawAll() external {
    payable(owner).transfer(address(this).balance);
}

}

contract VulnerableVaultTest is Test {
VulnerableVault vault;
Attacker attacker;

function setUp() public {
    vault = new VulnerableVault();
    attacker = new Attacker(address(vault));

    // Setup: vault に 10 ETH 入れておく
    vm.deal(address(this), 20 ether);
    vault.deposit{value: 10 ether}();
}

function testExploit() public {
    // AttackerにETHを持たせる
    vm.deal(address(attacker), 1 ether);
    vm.prank(address(attacker));
    attacker.attack{value: 1 ether}();

    // 🎯 攻撃成功:Vaultの残高が0になっている
    assertEq(address(vault).balance, 0);
}

}

修正済みコード(BugFix)

function withdraw() public {
uint256 bal = balances[msg.sender];
require(bal > 0, "Nothing to withdraw");

// ✅ 修正:状態を先に更新
balances[msg.sender] = 0;

(bool sent, ) = msg.sender.call{value: bal}("");
require(sent, "Failed to send");

}

✅ 解説:何が問題で、どう直したか?

外部コール(call)を先に行う 

内部状態(残高)を先に更新することで、再入可能な状態を防止

call はフォールバック関数を呼ぶため危険

ガス制限付き transfer/send にする方法もあるが、EIP-1884 以降では非推奨になったため状態更新が最善策

🎓 学習ポイントまとめ
Foundryは本物の攻撃を再現できる強力なテスト環境。

状態変更と外部呼び出しの順番はセキュリティに直結する。それほど順番が大事になる。

Bug Bountyは「攻撃シナリオをテストで再現+修正コードを書く」という点でFoundryと相性抜群。

🔐 よくある脆弱性とFoundryによる再現+修正

  1. ✅ Reentrancy(再入可能性)
    説明: 外部コール中に再び同じ関数が呼ばれ、状態が壊される。

再現: .callの直後に状態更新がある。

修正: 状態を先に更新し、後でコール。

// 修正: 状態更新を先にする
balances[msg.sender] = 0;
(bool sent, ) = msg.sender.call{value: amount}("");

  1. 🧮 Integer Overflow / Underflow
    説明: uint256 におけるオーバーフロー / アンダーフロー

再現: Solidity 0.8未満 or unchecked で a - b が underflow。

修正: SafeMath か、Solidity 0.8以降のデフォルトチェックに依存。

// 脆弱な例 (unchecked ブロック内)
unchecked {
uint256 x = a - b; // underflow の危険
}

  1. 🔗 Unchecked External Call
    説明: call, delegatecall, send, transfer の戻り値を確認しない。

再現: (addr.call{...}(...)); ← 戻り値を確認しない。

修正: require(success, "Call failed") を追加。

(bool success, ) = addr.call(data);
require(success, "Call failed");

  1. 🔓 Access Control Misconfiguration
    説明: onlyOwner などの制限が漏れている。

再現: 誰でも withdraw(), pause(), setAdmin() できる。

修正: onlyOwner などの修飾子を付与。

modifier onlyOwner {
require(msg.sender == owner, "Not owner");
_;
}

  1. 💵 Incorrect ERC20 Transfer Check
    説明: transfer() などの戻り値を確認しないトークン操作。

再現: token.transfer(...) の戻り値を無視。

修正: 戻り値を require。

require(token.transfer(to, amount), "Transfer failed");

  1. ⏱ Timestamp Dependence
    説明: block.timestamp に依存したゲーム性や制御。

再現: 「24時間経ったら誰でも実行可」など。

修正: 検証環境で warp() を使ってテスト可。経過時間のチェックを厳密に行う。

  1. 🧊 Denial of Service (DoS)
    説明: ガス消費・無限ループ・状態のロックでサービス不能。

再現: for ループで状態を消費者に依存して回す。

修正: Pull方式に変更、limit追加など。

  1. 🧵 Front-running / MEV
    説明: 公開状態で approve(), setPrice() 等が悪用される。

再現: パブリックな setBid(uint price) を任意の誰でも呼べる。

修正: 署名付き注文(EIP-712)、commit-reveal戦略など。

  1. 🪤 Uninitialized Proxy Storage
    説明: プロキシパターンで initialize() が誰でも呼べる。

再現: initialize() に onlyOwner 修飾子なし。

修正: initializer パターンを使い、再実行を防止。

  1. 💥 Selfdestruct Misuse
    説明: selfdestruct() によって強制的にETHを送りつけられる。

再現: selfdestruct(payable(target));

修正: receive() を意図的に設計、不要なETHに対応。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?