智能合约安全第六部分:保护以太坊智能合约免受矿工操纵

本文探讨了以太坊智能合约中矿工操纵攻击的风险,详细解释了攻击原理和常见手段,并通过一个易受攻击的 lottery 合约实例进行了演示。为了应对这种风险,文章建议采用 Chainlink VRF 等安全随机数生成方案,以及 commit-reveal 等机制来提高合约的安全性,并避免使用矿工可控的变量。

第六部分:智胜区块生产者——保护以太坊智能合约免受矿工操纵

导言:区块生产者的隐秘影响

想象一下在以太坊上启动一个去中心化的彩票 dApp,玩家投入 ETH 以获得改变人生的奖金的机会。你的智能合约承诺公平,使用链上数据来选择一个随机的获胜者。但是当抽奖发生时,一个验证者巧妙地调整了一个区块变量,操纵结果使之对自己有利。获胜者不是随机的——而是验证者或他们的同伙。玩家失去信任,你的 dApp 的声誉崩溃,你的项目面临审查。这就是矿工操纵的阴险威胁,区块生产者利用他们对 block.timestampblock.number 等变量的控制来扭曲合约逻辑。

欢迎来到智能合约安全:Solodit 检查清单系列的第六章,我们将讨论 Solodit 检查清单中定义的 SOL-AM-MinerManipulation:矿工控制的变量。矿工操纵利用了以太坊的区块元数据,验证者可以在协议限制范围内调整这些元数据,从而影响时间敏感或依赖随机性的合约。截至 2025 年 7 月,以太坊的权益证明 (PoS) 系统具有约 3000 万 gas 的区块 gas 限制和 12 秒的平均区块时间,这些攻击可能会扰乱彩票、拍卖、质押池或治理系统。

在本开发者友好的指南中,我们将深入探讨矿工操纵的工作原理,分析一个易受攻击的彩票合约,并提供使用 Chainlink VRF、提交-揭示方案和其他强大防御措施的安全实现。我们将包括详细的用户和攻击者工作流程、全面的代码片段、高级安全模式和实用的测试策略,同时将此威胁与之前的漏洞(如阻断 (第五部分)、抢跑 (第四部分) 和状态膨胀 (第二部分))联系起来。无论你是构建彩票、拍卖还是 DeFi 协议,本文都将使你能够智胜操纵性验证者并确保你的 dApp 保持公平、安全和无需信任。

为什么矿工操纵是一种沉默的威胁

在以太坊的 PoS 系统中,验证者提出并证明区块,取代了工作量证明 (PoW) 时代的矿工。虽然 PoS 减少了与 PoW 相比的个人控制,但验证者仍然会影响:

  • 区块时间戳 ( block.timestamp ):可在 ~15 秒的窗口内调整(±12–15 秒),以与网络共识规则对齐,只要它大于前一个区块的时间戳。
  • 区块编号 ( block.number ):顺序的,但验证者可以通过战略性地确定优先级来影响时序或交易包含。
  • 区块哈希 ( blockhash ):从区块数据派生而来,在一个区块内可预测,并且可以通过调整其他变量(如 block.timestamp)来操纵。

这些操纵虽然受到限制,但会显着影响依赖这些变量的合约,以用于:

  • 随机数生成:用于彩票、游戏或 NFT 铸造的伪随机数。
  • 时间敏感逻辑:拍卖、投票或质押奖励分配的截止日期。
  • 结果确定:选择获胜者、触发付款或解决争议。

风险包括:

  • 不公平的结果:验证者扭曲随机性以偏袒自己或同伙,如早期以太坊彩票中所见。
  • 财务损失:由于被操纵的逻辑,合法用户失去奖品或机会。
  • 声誉损害:受损的公平性会侵蚀信任,如 2016 年的 GovernMental 庞氏骗局中所见,其中时间戳操纵加剧了漏洞。
  • 系统性偏差:随着时间的推移,重复的操纵会加剧,在高风险系统中扭曲长期结果。

Solodit 检查清单的 SOL-AM-MinerManipulation 强调安全的随机性来源(例如,Chainlink VRF),避免矿工控制的变量,以及实施公平机制,如提交-揭示方案。让我们探讨一下这些攻击是如何展开的,以及如何消除它们。

矿工操纵是如何工作的

矿工操纵利用了验证者对区块元数据的有限控制来影响合约结果。在以太坊的 PoS 系统中,验证者:

  • 在 ~15 秒的窗口内调整 block.timestamp,确保它大于前一个区块的时间戳并与网络时间对齐。
  • 优先处理他们提议的区块中的交易,类似于抢跑策略(第四部分)。
  • 通过调整时间戳或交易集来影响区块数据(例如,blockhash)。

常见的攻击媒介:

  • 随机性操纵:扭曲伪随机数生成(例如,keccak256(block.timestamp, block.number))以选择有利的结果。
  • 基于时间的漏洞利用:调整 block.timestamp 以触发或绕过截止日期(例如,提前结束拍卖或延迟付款)。
  • 交易优先级:包括他们自己的交易以利用时间敏感的逻辑,例如在闪购或彩票中。

这些攻击很微妙,因为验证者在协议规则内操作,如果没有链下监控,很难检测到。

真实案例:2016 GovernMental 庞氏骗局

在 2016 年,GovernMental 庞氏骗局使用 block.timestamp 来安排付款,使其容易受到矿工操纵。矿工可以调整时间戳以影响付款时间,加剧该骗局的崩溃并锁定 110 万美元的 ETH。此事件突出了依赖矿工控制变量的危险,推动了 Chainlink VRF 等安全随机性解决方案的采用。

易受攻击的代码:一个成熟的彩票,容易被操纵

下面是一个彩票合约,它使用 block.timestampblock.number 来生成伪随机数,从而选择一个获胜者,使其成为操纵的容易目标。

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

contract VulnerableLottery {
    address public winner;
    uint256 public entryFee = 0.1 ether;
    address[] public players;
    bool public drawingActive;

    // 进入彩票
    function enter() external payable {
        require(msg.value == entryFee, "Incorrect entry fee");
        require(drawingActive, "Drawing not active");
        players.push(msg.sender);
        emit PlayerEntered(msg.sender);
    }

    // 开始抽奖期
    function startDrawing() external {
        require(!drawingActive, "Drawing already active");
        drawingActive = true;
        emit DrawingStarted(block.timestamp);
    }

    // 抽取获胜者
    function drawWinner() external {
        require(drawingActive, "Drawing not active");
        require(players.length > 0, "No players");
        uint256 random = uint256(keccak256(abi.encodePacked(block.timestamp, block.number)));
        winner = players[random % players.length];
        drawingActive = false;
        emit WinnerSelected(winner);
    }

    event PlayerEntered(address indexed player);
    event DrawingStarted(uint256 startTime);
    event WinnerSelected(address indexed winner);
}

攻击场景:扭曲抽奖

以下是恶意验证者如何利用此合约:

  1. 合法参与:用户调用 enter() 加入彩票,每人支付 0.1 ETH。players 数组增长(例如,100 名玩家)。
  2. 抽奖触发:管理员或用户在 startDrawing() 之后调用 drawWinner(),使用 block.timestampblock.number 生成伪随机索引。
  3. 攻击者的行动:验证者(或他们的同伙)是一名玩家,并提议下一个区块。他们:
  • 在 ~15 秒的窗口内调整 block.timestamp,以生成选择其地址的哈希。
  • 使用脚本离线测试多个时间戳,以找到有利的结果:
const Web3 = require('web3');
const web3 = new Web3('https://mainnet.infura.io/v3/YOUR_PROJECT_ID');
const hash = web3.utils.keccak256(`${timestamp}${blockNumber}`);
const index = BigInt(hash) % BigInt(players.length);
  • 包含他们自己的交易以确保它以被操纵的时间戳处理。

4. 结果:随机值选择验证者的地址作为获胜者,声明奖金池(例如,100 名玩家的 10 ETH)。合法玩家失去机会,没有意识到操纵。

在你的收件箱中获取 codebyankita 的故事

免费加入 Medium 以获取这位作家的更新。

5. 影响:彩票的公平性受到损害,用户失去信任,dApp 面临声誉和潜在的法律风险。

为什么它很危险

该合约依赖于 block.timestampblock.number,它们在协议约束范围内可以被操纵。验证者可以离线预测和测试哈希结果,确保他们或同伙获胜。在一个拥有数百万 ETH 的高风险彩票中,这可能导致重大的财务损失和监管审查。对 drawWinner 调用的缺乏限制也允许重复尝试,从而放大了阻断风险(第五部分)。

补救措施:安全随机性和稳健的设计

Solodit 检查清单建议使用安全的随机性来源(如 Chainlink VRF),避免矿工控制的变量,以及实施公平机制(如提交-揭示方案)。以下是一个安全的彩票合约,它结合了这些原则,以及高级功能,如批量加入、时间锁和熔断器。

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

import "@chainlink/contracts/src/v0.8/interfaces/VRFCoordinatorV2Interface.sol";
import "@chainlink/contracts/src/v0.8/VRFConsumerBaseV2.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";

contract SecureLottery is VRFConsumerBaseV2, ReentrancyGuard {
    VRFCoordinatorV2Interface COORDINATOR;
    address public winner;
    address public admin;
    bool public paused;
    bool public drawingActive;
    uint256 public entryFee = 0.1 ether;
    uint64 public subscriptionId; // Chainlink VRF 订阅 ID
    bytes32 public keyHash; // Chainlink VRF 密钥哈希
    uint32 public callbackGasLimit = 100000; // VRF 回调的 Gas 限制
    uint16 public requestConfirmations = 3; // 最小确认数
    uint32 public numWords = 1; // 随机词数量
    address[] public players;
    mapping(address => bytes32) public commitments; // 用于提交-揭示
    mapping(uint256 => address) public requestIdToSender; // 跟踪 VRF 请求
    uint256 public constant MAX_PLAYERS = 1000; // 限制状态增长
    uint256 public constant COMMIT_WINDOW = 1 hours; // 提交-揭示窗口
    uint256 public constant DRAWING_WINDOW = 7 days; // 抽奖期
    uint256 public lotteryStart;

    modifier onlyAdmin() {
        require(msg.sender == admin, "Not admin");
        _;
    }

    modifier whenNotPaused() {
        require(!paused, "Contract paused");
        _;
    }

    modifier withinDrawingWindow() {
        require(block.timestamp <= lotteryStart + DRAWING_WINDOW, "Drawing closed");
        _;
    }

    constructor(address vrfCoordinator, bytes32 _keyHash, uint64 _subscriptionId) VRFConsumerBaseV2(vrfCoordinator) {
        COORDINATOR = VRFCoordinatorV2Interface(vrfCoordinator);
        keyHash = _keyHash;
        subscriptionId = _subscriptionId;
        admin = msg.sender;
        lotteryStart = block.timestamp;
    }

    // 开始彩票
    function startDrawing() external onlyAdmin whenNotPaused {
        require(!drawingActive, "Drawing already active");
        drawingActive = true;
        lotteryStart = block.timestamp;
        delete players; // 重置玩家数组
        emit DrawingStarted(block.timestamp);
    }

    // 提交条目以隐藏地址
    function commitEntry(bytes32 commitment) external payable whenNotPaused withinDrawingWindow nonReentrant {
        require(msg.value == entryFee, "Incorrect entry fee");
        require(players.length < MAX_PLAYERS, "Player limit reached");
        require(commitments[msg.sender] == bytes32(0), "Already committed");
        commitments[msg.sender] = commitment;
        emit EntryCommitted(msg.sender, commitment);
    }

    // 揭示条目并加入彩票
    function revealEntry(uint256 nonce) external whenNotPaused withinDrawingWindow nonReentrant {
        require(commitments[msg.sender] != bytes32(0), "No commitment");
        require(block.timestamp <= lotteryStart + COMMIT_WINDOW, "Commit window expired");
        require(keccak256(abi.encodePacked(msg.sender, nonce)) == commitments[msg.sender], "Invalid commitment");
        delete commitments[msg.sender];
        players.push(msg.sender);
        emit PlayerEntered(msg.sender);
    }

    // 多个玩家批量加入
    function batchEnter(address[] calldata entrants, bytes32[] calldata commitments) external payable whenNotPaused withinDrawingWindow nonReentrant {
        require(entrants.length == commitments.length, "Mismatched arrays");
        require(msg.value == entryFee * entrants.length, "Incorrect entry fee");
        require(players.length + entrants.length <= MAX_PLAYERS, "Player limit exceeded");
        for (uint256 i = 0; i < entrants.length; i++) {
            if (commitments[entrants[i]] == bytes32(0)) {
                commitments[entrants[i]] = commitments[i];
                emit EntryCommitted(entrants[i], commitments[i]);
            }
        }
    }

    // 从 Chainlink VRF 请求随机数
    function drawWinner() external onlyAdmin whenNotPaused withinDrawingWindow {
        require(drawingActive, "Drawing not active");
        require(players.length > 0, "No players");
        uint256 requestId = COORDINATOR.requestRandomWords(
            keyHash,
            subscriptionId,
            requestConfirmations,
            callbackGasLimit,
            numWords
        );
        requestIdToSender[requestId] = msg.sender;
        drawingActive = false;
        emit RandomnessRequested(requestId);
    }

    // Chainlink VRF 回调
    function fulfillRandomWords(uint256 requestId, uint256[] memory randomWords) internal override {
        require(players.length > 0, "No players");
        winner = players[randomWords[0] % players.length];
        emit WinnerSelected(winner, requestId);
    }

    // 领取奖品
    function claimPrize() external whenNotPaused nonReentrant {
        require(msg.sender == winner, "Not winner");
        uint256 prize = address(this).balance;
        winner = address(0);
        (bool success, ) = msg.sender.call{value: prize}("");
        require(success, "Prize claim failed");
        emit PrizeClaimed(msg.sender, prize);
    }

    // 在遭受攻击时暂停合约
    function pause() external onlyAdmin {
        paused = true;
        emit Paused();
    }

    // 取消暂停合约
    function unpause() external onlyAdmin {
        paused = false;
        emit Unpaused();
    }

    // 拒绝直接 ETH 存款
    receive() external payable {
        revert("Direct ETH deposits not allowed");
    }

    event EntryCommitted(address indexed player, bytes32 commitment);
    event PlayerEntered(address indexed player);
    event DrawingStarted(uint256 startTime);
    event RandomnessRequested(uint256 indexed requestChid);
    event WinnerSelected(address indexed winner, uint256 requestId);
    event PrizeClaimed(address indexed winner, uint256 amount);
    event Paused();
    event Unpaused();
}

它是如何工作的

  • Chainlink VRF:使用 Chainlink 的可验证随机函数 (VRF) v2 进行密码学安全随机数,免疫于矿工操纵。
  • 提交-揭示方案:玩家提交哈希条目 (commitEntry) 以将地址隐藏在内存池中,然后在 1 小时的窗口内揭示它们 (revealEntry),从而防止抢跑(第四部分)和阻断(第五部分)。
  • 批量加入:batchEnter** 函数允许多个玩家高效加入,跳过重复项以减少 gas 浪费。
  • 状态上限:MAX_PLAYERS 常量 (1000) 限制了 players 数组,从而防止状态膨胀(第二部分)。
  • 时间锁:COMMIT_WINDOW (1 小时) 和 DRAWING_WINDOW (7 天) 限制了加入和抽奖期,从而减少了攻击面。
  • 熔断器:paused 标志和 whenNotPaused 修饰符会在怀疑遭受攻击时停止操作。
  • 访问控制:onlyAdmin 修饰符将 startDrawing、drawWinner、pause 和 unpause** 限制为管理员。
  • 重入保护:OpenZeppelin 的 ReentrancyGuard** 防止 ETH 转移期间的重入攻击。
  • 显式 ETH 处理:拒绝意外的 ETH 存款以应对捐赠攻击(第三部分)。
  • 事件日志记录:Gas 高效的事件跟踪提交、加入、随机性请求和获胜者选择。

安全的工作流程

合法用户

  • 通过 commitEntry** 提交哈希条目,隐藏他们的地址。
  • 通过 revealEntry 揭示条目,如果有效,则加入 players 数组。
  • 管理员调用 drawWinner**,从 Chainlink VRF 请求一个随机数。
  • VRF 回调使用安全随机数选择一个获胜者。
  • 获胜者通过 claimPrize** 领取奖品。

攻击者(验证者)

  • 尝试在 drawWinner 期间操纵 block.timestamp 或 block.number**。
  • Chainlink VRF 使用链下随机数,并在链上验证,从而使操纵无效。
  • 尝试垃圾邮件 commitEntry 或 revealEntry,但由于提交检查或 MAX_PLAYERS** 而提前恢复。
  • 结果:彩票保持公平,攻击者的操纵失败。

安全优势

  • 安全随机数:Chainlink VRF 确保密码学安全随机数,免疫于矿工操纵。
  • 内存池隐私:提交-揭示隐藏玩家地址,从而减少抢跑(第四部分)和阻断(第五部分)风险。
  • Gas 效率:早期验证、批量处理和固定 gas 限制最大限度地减少了浪费。
  • DoS 保护:熔断器、时间锁和状态上限限制了攻击影响。
  • 重入安全:防止奖金领取期间的重入。
  • 稳健的设计:访问控制、显式 ETH 处理和高效的事件确保可靠性。

工作流程比较(易受攻击 vs. 安全)

  • 红色框:由于矿工操纵,易受攻击的合约中出现不公平的结果。
  • 黄色框:安全合约中提前恢复,从而节省 gas。
  • 绿色框:使用安全随机数和安全奖金领取获得公平的结果。
  • 关键区别:安全合约使用 Chainlink VRF 和提交-揭示,绕过矿工控制的变量。

高级防御:替代随机数来源

如果 Chainlink VRF 的成本过高或不可用,请考虑以下替代方案:

1. 用于随机数的提交-揭示

用户提交一个随机值的哈希,然后揭示它。合约聚合揭示的值以获得随机数:

mapping(address => bytes32) public randomCommits;
mapping(address => uint256) public randomValues;

function commitRandom(bytes32 commitment) external whenNotPaused {
    require(randomCommits[msg.sender] == bytes32(0), "Already committed");
    randomCommits[msg.sender] = commitment;
    emit RandomCommitted(msg.sender, commitment);
}

function revealRandom(uint256 value, uint256 nonce) external whenNotPaused {
    require(randomCommits[msg.sender] != bytes32(0), "No commitment");
    require(keccak256(abi.encodePacked(value, nonce)) == randomCommits[msg.sender], "Invalid commitment");
    randomValues[msg.sender] = value;
    emit RandomRevealed(msg.sender, value);
}

function drawWinnerWithCommits() external onlyAdmin whenNotPaused {
    uint256 random = 0;
    for (uint256 i = 0; i < players.length; i++) {
        random ^= randomValues[players[i]];
    }
    winner = players[random % players.length];
    emit WinnerSelected(winner, 0);
}

event RandomCommitted(address indexed player, bytes32 commitment);
event RandomRevealed(address indexed player, uint256 value);
  • 优点:Gas 高效,去中心化。
  • 缺点:需要两次交易,容易受到非揭示阻断(第五部分)的影响。

2. 未来区块哈希

使用 blockhash(block.number + n) 获得随机数,延迟对可操纵数据的依赖:

function drawWinnerWithFutureBlock(uint256 futureBlock) external onlyAdmin {
    require(block.number < futureBlock, "Block already mined");
    require(players.length > 0, "No players");
    uint256 random = uint256(blockhash(futureBlock));
    winner = players[random % players.length];
    emit WinnerSelected(winner, 0);
}
  • 优点:简单,链上。
  • 缺点:如果 futureBlock** 太近,仍然可以被操纵,需要延迟。

3. 链下预言机

使用自定义预言机获得随机数,并在链上验证。

  • 优点:灵活,具有成本效益。
  • 缺点:与 Chainlink VRF 相比,可验证性较低,需要信任预言机。

与先前部分的集成:全面的防御

矿工操纵可以放大先前部分的漏洞:

  • 阻断攻击(第五部分):攻击者可能会通过垃圾邮件发送失败的条目来阻塞内存池,从而延迟合法条目并启用时间戳操纵。
  • 抢跑(第四部分):验证者可以通过优先处理他们自己的条目来抢跑 drawWinner**,从而放大操纵风险。
  • 捐赠攻击(第三部分):强制 ETH 存款可能会扰乱奖金池的计算,并因操纵付款时间而加剧。
  • 状态膨胀(第二部分):过多的条目可能会膨胀 players 数组,从而增加了 drawWinner 的 gas 成本。
  • 无界循环(第一部分):获胜者选择中的循环可能会达到 gas 限制,并因垃圾邮件或操纵而加剧。

组合防御:

  • 拉取胜于推送(第一部分):使用 claimPrize** 来隔离奖金分配。
  • 状态上限(第二部分):使用 MAX_PLAYERS** 限制玩家。
  • 显式 ETH 跟踪(第三部分):使用 receive()** 拒绝意外的 ETH。
  • 提交-揭示(第四部分):使用 commitEntry 和 revealEntry 隐藏玩家地址。
  • Gas 高效的设计(第五部分):使用批量处理和早期验证来对抗阻断。

防止矿工操纵的最佳实践

为了与 Solodit 检查清单和行业标准保持一致,请采用以下实践:

  1. 使用 Chainlink VRF 获得随机数
COORDINATOR.requestRandomWords(keyHash, subscriptionId, requestConfirmations, callbackGasLimit, numWords);

2. 实施提交-揭示方案

function commitEntry(bytes32 commitment) external payable {
    commitments[msg.sender] = commitment;
}

3. 避免矿工控制的变量

// 坏
uint256 random = uint256(keccak256(abi.encodePacked(block.timestamp)));
// 好
uint256 random = randomWords[0]; // 来自 Chainlink VRF

4. 使用时间锁

require(block.timestamp <= lotteryStart + COMMIT_WINDOW, "Commit window expired");

5. 实施熔断器

function pause() external onlyAdmin {
    paused = true;
    emit Paused();
}

6. 限制访问

function drawWinner() external onlyAdmin whenNotPaused {
    // ...
}

7. 限制状态增长

require(players.length < MAX_PLAYERS, "Player limit reached");

8. 使用批量处理

function batchEnter(address[] calldata entrants, bytes32[] calldata commitments) external payable {
    // ...
}

9. 监控随机数和交易:使用 Forta 检测随机数请求或验证者行为中的异常。

10. 全面测试

  • 使用 Foundry 模拟时间戳操纵:
await network.provider.send("evm_setNextBlockTimestamp", [newTimestamp]);
  • 使用 Chainlink 的测试网协调器测试 VRF 集成。
  • 使用 Echidna 进行模糊随机输入。
  • 使用 Hardhat 分叉主网,在真实条件下进行测试。

用于预防矿工操纵的高级工具(2025 年更新)

截至 2025 年 7 月,以下工具增强了矿工操纵预防:

  • Slither (0.10.x):检测对矿工控制变量的依赖 (slither — detect timestamp-dependency)。
  • MythX:分析随机数漏洞和外部调用风险。
  • Foundry:使用 evm_setNextBlockTimestamp 和差分模糊模拟时间戳和区块编号操纵。
  • Forta:实时监控随机数模式或验证者行为中的可疑行为。
  • OpenZeppelin Defender:自动响应异常获胜者选择或交易模式。
  • Tenderly:模拟区块变量操纵并可视化结果。
  • Chainlink VRF 测试网:在 Sepolia 或其他测试网上测试随机数集成。
  • MEV-Geth:模拟矿工可提取价值 (MEV) 场景以测试操纵影响。

测试矿工操纵弹性

为了确保你的合约能够抵抗矿工操纵:

  1. 单元测试
it("prevents timestamp manipulation", async () => {
    await lottery.commitEntry(commitment, { value: entryFee, from: user });
    await lottery.revealEntry(nnonce, { from: user });
    await network.provider.send("evm_setNextBlockTimestamp", [newTimestamp]);
    await lottery.drawWinner({ from: admin });
    // Verify winner is selected via VRF, not timestamp
    expect(await lottery.winner()).to.not.be.influencedBy(newTimestamp);
});

it("processes valid entries only", async () => {
    await lottery.commitEntry(commitment, { value: entryFee, from: user });
    await expect(lottery.revealEntry(invalidNonce, { from: user }))
        .to.be.revertedWith("Invalid commitment");
});

it("handles batch entries efficiently", async () => {
    const entrants = [user1, user2];
    const commitments = [commitment1, commitment2];
    await lottery.batchEnter(entrants, commitments, { value: entryFee * 2 });
    expect(await lottery.commitments(user1)).to.equal(commitment1);
    expect(await lottery.commitments(user2)).to.equal(commitment2);
});

2. 模糊测试:使用 Echidna 模拟随机时间戳、区块编号和无效提交。

3. 内存池模拟:使用 Foundry 测试交易排序和内存池动态。

4. 主网分叉:使用 Hardhat 分叉以太坊主网以测试 VRF 集成和区块变量操纵。

5. 随机数监控:使用 Forta 检测随机数请求或获胜者选择中的异常。

真实案例研究:2016 GovernMental 和早期 DeFi 彩票

2016 年的 GovernMental 庞氏骗局依赖于 block.timestamp 来确定支付时间,这使得矿工可以操纵时间表并加剧该骗局的崩溃,从而锁定了价值 110 万美元的 ETH。同样,像 PoolTogether(VRF 之前)这样的早期 DeFi 彩票面临着基于时间戳的随机性的风险,促使在以后的版本中采用 Chainlink VRF 和提交-揭示方案。这些事件突出表明需要安全的随机性和强大的设计。

结论:构建矿工证明的智能合约

矿工操纵就像魔术师操纵抽奖——验证者可以稍微调整一下赔率,破坏公平性。通过使用 Chainlink VRF 获得安全随机数,实施提交-揭示方案,避免受矿工控制的变量,并采用 gas 高效的设计,正如 Solodit 检查清单 ( SOL-AM-MinerManipulation) 建议的那样,开发人员可以确保公平的结果。 Slither、Foundry、Forta 和 Tenderly 等工具,再加上严格的测试和实时监控,可以使合约对操纵性验证者具有弹性。

本文是智能合约安全:Solodit 检查清单系列的一部分。接下来,我们将讨论预言机操纵,探索攻击者如何利用价格馈送,并实施与 Chainlink 的安全预言机集成。无论你是构建彩票、拍卖平台、质押系统还是 DeFi 协议,掌握矿工操纵防御对于创建公平、安全和无需信任的去中心化应用程序至关重要。

  • 原文链接: medium.com/@ankitacode11...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
ankitacode11
ankitacode11
江湖只有他的大名,没有他的介绍。