Master smart contract security best practices to prevent common vulnerabilities and implement secure Solidity patterns. Use this when writing smart contracts, auditing existing contracts, or implementing security measures for blockchain applications.
English | 日本語
スマートコントラクトセキュリティのベストプラクティス、脆弱性防止、セキュアなSolidity開発パターンをマスターします。
攻撃者が状態が更新される前にあなたのコントラクトにコールバックします。
脆弱なコード:
// 再入攻撃に対して脆弱
contract VulnerableBank {
mapping(address => uint256) public balances;
function withdraw() public {
uint256 amount = balances[msg.sender];
// 危険: 状態更新前の外部呼び出し
(bool success, ) = msg.sender.call{value: amount}("");
require(success);
balances[msg.sender] = 0; // 遅すぎる!
}
}
セキュアなパターン(Checks-Effects-Interactions):
contract SecureBank {
mapping(address => uint256) public balances;
function withdraw() public {
uint256 amount = balances[msg.sender];
require(amount > 0, "Insufficient balance");
// EFFECTS: 外部呼び出し前に状態を更新
balances[msg.sender] = 0;
// INTERACTIONS: 外部呼び出しは最後
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "Transfer failed");
}
}
代替案: ReentrancyGuard
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract SecureBank is ReentrancyGuard {
mapping(address => uint256) public balances;
function withdraw() public nonReentrant {
uint256 amount = balances[msg.sender];
require(amount > 0, "Insufficient balance");
balances[msg.sender] = 0;
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "Transfer failed");
}
}
脆弱なコード(Solidity < 0.8.0):
// 脆弱
contract VulnerableToken {
mapping(address => uint256) public balances;
function transfer(address to, uint256 amount) public {
// オーバーフローチェックなし - ラップアラウンド可能
balances[msg.sender] -= amount; // アンダーフロー可能!
balances[to] += amount; // オーバーフロー可能!
}
}
セキュアなパターン(Solidity >= 0.8.0):
// Solidity 0.8以降はオーバーフロー/アンダーフローチェックが組み込み
contract SecureToken {
mapping(address => uint256) public balances;
function transfer(address to, uint256 amount) public {
// オーバーフロー/アンダーフローで自動的にrevert
balances[msg.sender] -= amount;
balances[to] += amount;
}
}
Solidity < 0.8.0の場合、SafeMathを使用:
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
contract SecureToken {
using SafeMath for uint256;
mapping(address => uint256) public balances;
function transfer(address to, uint256 amount) public {
balances[msg.sender] = balances[msg.sender].sub(amount);
balances[to] = balances[to].add(amount);
}
}
脆弱なコード:
// 脆弱: 誰でも重要な機能を呼び出せる
contract VulnerableContract {
address public owner;
function withdraw(uint256 amount) public {
// アクセス制御なし!
payable(msg.sender).transfer(amount);
}
}
セキュアなパターン:
import "@openzeppelin/contracts/access/Ownable.sol";
contract SecureContract is Ownable {
function withdraw(uint256 amount) public onlyOwner {
payable(owner()).transfer(amount);
}
}
// またはカスタムロールベースアクセスを実装
contract RoleBasedContract {
mapping(address => bool) public admins;
modifier onlyAdmin() {
require(admins[msg.sender], "Not an admin");
_;
}
function criticalFunction() public onlyAdmin {
// 保護された機能
}
}
脆弱:
// フロントランニングに対して脆弱
contract VulnerableDEX {
function swap(uint256 amount, uint256 minOutput) public {
// 攻撃者がmempoolでこれを見てフロントランニング
uint256 output = calculateOutput(amount);
require(output >= minOutput, "Slippage too high");
// スワップを実行
}
}
緩和策:
contract SecureDEX {
mapping(bytes32 => bool) public usedCommitments;
// ステップ1: トレードにコミット
function commitTrade(bytes32 commitment) public {
usedCommitments[commitment] = true;
}
// ステップ2: トレードを明らかにする(次のブロック)
function revealTrade(
uint256 amount,
uint256 minOutput,
bytes32 secret
) public {
bytes32 commitment = keccak256(abi.encodePacked(
msg.sender, amount, minOutput, secret
));
require(usedCommitments[commitment], "Invalid commitment");
// スワップを実行
}
}
contract SecurePattern {
mapping(address => uint256) public balances;
function withdraw(uint256 amount) public {
// 1. CHECKS: 条件を検証
require(amount <= balances[msg.sender], "Insufficient balance");
require(amount > 0, "Amount must be positive");
// 2. EFFECTS: 状態を更新
balances[msg.sender] -= amount;
// 3. INTERACTIONS: 外部呼び出しは最後
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "Transfer failed");
}
}
// これを推奨(pull)
contract SecurePayment {
mapping(address => uint256) public pendingWithdrawals;
function recordPayment(address recipient, uint256 amount) internal {
pendingWithdrawals[recipient] += amount;
}
function withdraw() public {
uint256 amount = pendingWithdrawals[msg.sender];
require(amount > 0, "Nothing to withdraw");
pendingWithdrawals[msg.sender] = 0;
payable(msg.sender).transfer(amount);
}
}
// これよりも(push)
contract RiskyPayment {
function distributePayments(address[] memory recipients, uint256[] memory amounts) public {
for (uint i = 0; i < recipients.length; i++) {
// 転送が失敗するとバッチ全体が失敗
payable(recipients[i]).transfer(amounts[i]);
}
}
}
contract SecureContract {
function transfer(address to, uint256 amount) public {
// 入力を検証
require(to != address(0), "Invalid recipient");
require(to != address(this), "Cannot send to contract");
require(amount > 0, "Amount must be positive");
require(amount <= balances[msg.sender], "Insufficient balance");
// 転送を実行
balances[msg.sender] -= amount;
balances[to] += amount;
}
}
import "@openzeppelin/contracts/security/Pausable.sol";
contract EmergencyStop is Pausable, Ownable {
function criticalFunction() public whenNotPaused {
// 機能のロジック
}
function emergencyStop() public onlyOwner {
_pause();
}
function resume() public onlyOwner {
_unpause();
}
}
uint256を使用// ガス効率が良い
contract GasEfficient {
uint256 public value; // 最適
function set(uint256 _value) public {
value = _value;
}
}
// 非効率
contract GasInefficient {
uint8 public value; // 依然として256ビットスロットを使用
function set(uint8 _value) public {
value = _value; // 型変換のための追加ガス
}
}
// ガス効率的(1スロットに3変数)
contract PackedStorage {
uint128 public a; // スロット0
uint64 public b; // スロット0
uint64 public c; // スロット0
uint256 public d; // スロット1
}
// ガス非効率(各変数が別スロット)
contract UnpackedStorage {
uint256 public a; // スロット0
uint256 public b; // スロット1
uint256 public c; // スロット2
uint256 public d; // スロット3
}
memoryの代わりにcalldataを使用contract GasOptimized {
// よりガス効率的
function processData(uint256[] calldata data) public pure returns (uint256) {
return data[0];
}
// 非効率
function processDataMemory(uint256[] memory data) public pure returns (uint256) {
return data[0];
}
}
contract EventStorage {
// イベント発行はストレージより安価
event DataStored(address indexed user, uint256 indexed id, bytes data);
function storeData(uint256 id, bytes calldata data) public {
emit DataStored(msg.sender, id, data);
// 必要でない限りコントラクトストレージに保存しない
}
}
// セキュリティチェックリストコントラクト
contract SecurityChecklist {
/**
* [ ] 再入攻撃防止(ReentrancyGuardまたはCEIパターン)
* [ ] 整数オーバーフロー/アンダーフロー(Solidity 0.8以降またはSafeMath)
* [ ] アクセス制御(Ownable、ロール、modifier)
* [ ] 入力検証(requireステートメント)
* [ ] フロントランニング緩和(該当する場合はcommit-reveal)
* [ ] ガス最適化(パックドストレージ、calldata)
* [ ] 緊急停止メカニズム(Pausable)
* [ ] 支払いにpull over pushパターン
* [ ] 信頼されていないコントラクトへのdelegatecallなし
* [ ] 認証にtx.originを使用しない(msg.senderを使用)
* [ ] 適切なイベント発行
* [ ] 関数の最後に外部呼び出し
* [ ] 外部呼び出しの戻り値をチェック
* [ ] ハードコードされたアドレスなし
* [ ] アップグレードメカニズム(プロキシパターンの場合)
*/
}
// Hardhatテスト例
const { expect } = require("chai");
const { ethers } = require("hardhat");
describe("Security Tests", function () {
it("Should prevent reentrancy attack", async function () {
const [attacker] = await ethers.getSigners();
const VictimBank = await ethers.getContractFactory("SecureBank");
const bank = await VictimBank.deploy();
const Attacker = await ethers.getContractFactory("ReentrancyAttacker");
const attackerContract = await Attacker.deploy(bank.address);
// 資金を預金
await bank.deposit({value: ethers.utils.parseEther("10")});
// 再入攻撃を試行
await expect(
attackerContract.attack({value: ethers.utils.parseEther("1")})
).to.be.revertedWith("ReentrancyGuard: reentrant call");
});
it("Should prevent integer overflow", async function () {
const Token = await ethers.getContractFactory("SecureToken");
const token = await Token.deploy();
// オーバーフローを試行
await expect(
token.transfer(attacker.address, ethers.constants.MaxUint256)
).to.be.reverted;
});
it("Should enforce access control", async function () {
const [owner, attacker] = await ethers.getSigners();
const Contract = await ethers.getContractFactory("SecureContract");
const contract = await Contract.deploy();
// 不正な出金を試行
await expect(
contract.connect(attacker).withdraw(100)
).to.be.revertedWith("Ownable: caller is not the owner");
});
});
contract WellDocumentedContract {
/**
* @title Well Documented Contract
* @dev 監査のための適切なドキュメントの例
* @notice このコントラクトはユーザーの預金と出金を処理します
*/
/// @notice ユーザー残高のマッピング
mapping(address => uint256) public balances;
/**
* @dev コントラクトにETHを預金
* @notice 誰でも資金を預けられます
*/
function deposit() public payable {
require(msg.value > 0, "Must send ETH");
balances[msg.sender] += msg.value;
}
/**
* @dev ユーザーの残高を出金
* @notice 再入攻撃を防ぐためにCEIパターンに従います
* @param amount 出金するwei単位の金額
*/
function withdraw(uint256 amount) public {
// CHECKS
require(amount <= balances[msg.sender], "Insufficient balance");
// EFFECTS
balances[msg.sender] -= amount;
// INTERACTIONS
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "Transfer failed");
}
}
tx.originを使用: 代わりにmsg.senderを使用Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer