mcpField
T
token-antiflash

token-antiflash

github.com/0xlayerghost安全455 次安装2 周前更新
代码仓库

关于 token-antiflash

token-antiflash 是面向 ERC20 代币合约的闪电贷防护设计技能,供智能合约开发者评估代币层威胁并选择防护策略。它要求先讨论威胁模型、策略取舍和参数,再按确认结果实施。

主要功能

  1. 01评估代币威胁模型与攻击路径
  2. 02比较不同防护策略及其取舍
  3. 03涵盖成本追踪、冷却期和交易限额
  4. 04提供转账状态传播设计要点
  5. 05区分 OpenZeppelin 4.x 与 5.x 钩子

适用场景

  1. 01为 ERC20 代币设计闪电贷防护
  2. 02根据攻击路径选择防护组合
  3. 03确定限额、税率或冷却参数
  4. 04检查转账状态传播中的绕过风险

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/0xlayerghost/solidity-agent-kit --skill token-antiflash

安装后在 Claude Code 中输入/token-antiflash确认已加载。

详细介绍

Token 级闪电贷防护设计模式

语言规则

  • 始终使用与用户相同的语言回复。 如果用户使用中文提问,就用中文回复;如果使用英语提问,就用英语回复。

适用范围:适用于需要在代币合约层面防护闪电贷攻击的 ERC20 代币合约。本指南与 defi-security 技能(协议层面)互为补充,介绍代币内部的防御机制。以下所有参数均为设计参考值——实际数值应根据项目需求、代币经济模型和市场状况确定。

工作流规则

触发此技能时,不要直接实现所有策略。请遵循以下工作流:

  1. 评估:识别项目的威胁模型——代币类型(Meme、社区、DeFi 生态系统)、面临风险的价值,以及现实可行的攻击途径
  2. 说明:向开发者展示策略决策矩阵和组合指南。清楚说明各策略的权衡(Gas 成本、用户体验摩擦、实现复杂度)
  3. 让开发者选择:询问开发者希望采用哪种防护级别(Basic / Standard / Advanced / Maximum),或希望采用哪些具体策略。不要擅自假设防护级别
  4. 确认参数:针对每项所选策略,在编写代码前与开发者确认关键设计参数(例如,税费档位、交易量限制百分比、冷却时间粒度)
  5. 实现:只有在开发者确认后,才使用双方商定的参数实现所选策略

例外:如果开发者明确要求“全部实现”或“最高防护”,则跳过步骤 2–4,直接实现 Maximum 组合。如果开发者按编号指定了确切策略,则针对这些策略跳到步骤 4。

版本兼容性

OpenZeppelin 版本要重写的钩子备注
OZ 5.x(Solidity ≥0.8.20)_update(address from, address to, uint256 amount)钩子运行时,状态已更新
OZ 4.x(Solidity <0.8.20)_beforeTokenTransfer(address from, address to, uint256 amount)状态尚未更新;读取的是转账前的状态
// OZ 5.x — preferred for new projects
function _update(address from, address to, uint256 amount) internal override {
    super._update(from, to, amount);
    // ... defense logic here
}

// OZ 4.x — for existing projects
function _beforeTokenTransfer(address from, address to, uint256 amount) internal override {
    super._beforeTokenTransfer(from, to, amount);
    // ... defense logic here (balances not yet changed)
}

核心设计原则

闪电贷攻击依赖单笔交易中的原子执行:借入 → 操纵 → 获利 → 还款。代币层防护通过在代币合约内部引入成本、时间和身份屏障,打破这种原子性。

策略决策矩阵

设计带闪电贷防护的代币时,应根据威胁模型评估要采用哪些策略:

威胁途径推荐策略优先级
同一区块内买入→卖出套利同区块冷却 + 合约地址检查高
零成本代币利用(空投/漏洞利用)成本追踪 + 渐进式卖出税高
大额交易操纵按地址限制交易量 + 基础交易费中
多地址 Sybil 绕过推荐关系绑定 + 转账时状态传播中
Bot 抢先交易 / 三明治攻击抢先交易卖出保护 + 动态费用视情况而定

策略 1:成本追踪

设计思路:追踪每个地址持有代币的取得成本。将已实现利润限制为成本的可配置倍数。零成本取得的代币(空投、漏洞利用、闪电贷)成本为零,这意味着利润上限为零,或出售时销毁代币。

关键设计要点:

  • 维护每个地址的成本状态,并在每次 _transfer / _update 时更新
  • 转账时:成本随代币转移——按比例将成本从发送方传递给接收方
  • 应标记零成本代币,并单独处理(销毁或限制出售)
  • 利润上限倍数应为可配置的所有者参数,并设置合理的上限
  • 考虑维护额外映射所产生的 Gas 成本——测量对转账 Gas 的影响

为何能防御闪电贷:闪电贷借入的代币天然具有零成本。由于利润受成本限制,执行攻击便没有经济动机。

实现:

struct CostBasis {
    uint256 totalCost;    // total acquisition cost in quote token units
    uint256 totalAmount;  // total token amount held
}

mapping(address => CostBasis) private _costBasis;

function _propagateCostBasis(address from, address to, uint256 amount) private {
    CostBasis storage senderCost = _costBasis[from];
    if (senderCost.totalAmount == 0) return;

    // proportional cost transfer
    uint256 transferredCost = (senderCost.totalCost * amount) / senderCost.totalAmount;

    senderCost.totalCost -= transferredCost;
    senderCost.totalAmount -= amount;

    _costBasis[to].totalCost += transferredCost;
    _costBasis[to].totalAmount += amount;
}

// call this when a buy is detected (from == AMM pair)
function _recordBuy(address buyer, uint256 amount, uint256 costInQuote) private {
    _costBasis[buyer].totalCost += costInQuote;
    _costBasis[buyer].totalAmount += amount;
}

策略 2:合约地址检查(兼容 EIP-7702)

设计思路:阻止智能合约(闪电贷合约即属此类)发起交互,同时正确处理 EIP-7702 委托的 EOA。

关键设计要点:

  • EIP-7702 委托标识符具有特定的代码长度(23 字节:0xef0100 + 20 字节地址)
  • 允许代码长度恰好为此值的地址(它们是委托 EOA,而非合约)
  • 阻止代码长度不为零且不符合上述值的地址(普通智能合约)
  • 还可考虑将 msg.sender == tx.origin 作为辅助防护
  • 将必要的合约加入白名单:LP 交易对、路由器、质押合约

为何能防御闪电贷:闪电贷在合约调用链中执行。阻止合约调用方会迫使交互通过 EOA 进行,从而打破原子化执行。

实现:

// EIP-7702 delegation designator format:
//   byte[0..2] = 0xef0100  (magic prefix, 3 bytes)
//   byte[3..22] = target contract address (20 bytes)
//   total: 23 bytes
// Plain EOA has extcodesize = 0.
// Regular contract has extcodesize > 0 (and != 23 for the magic prefix case).
bytes3 private constant EIP7702_MAGIC = 0xef0100;

/// @dev Classifies an address into three types:
///   - size == 0         → plain EOA → return false (allow)
///   - size == 23 AND prefix matches 0xef0100 → EIP-7702 delegated EOA → return false (allow)
///   - anything else     → real deployed contract → return true (block)
///
/// EIP-7702 delegated EOAs are allowed because:
///   1. They are user-controlled accounts (smart wallets), not attack contracts
///   2. They cannot execute atomic flash loan buy→sell (Strategy 3 still blocks same-block ops)
///   3. Blocking them would break smart wallet UX for legitimate holders
///
/// extcodecopy gas note: ~700 gas cold read. Only runs when size == 23, which is rare.
function _isRealContract(address account) internal view returns (bool) {
    uint256 size;
    assembly { size := extcodesize(account) }

    // fast path: most addresses are plain EOA or regular contract
    if (size == 0) return false;   // plain EOA → allow
    if (size != 23) return true;   // regular contract (size > 23 or 1..22) → block

    // slow path: exactly 23 bytes → read prefix to distinguish 7702 from an exotic 23-byte contract
    bytes3 prefix;
    assembly {
        // extcodecopy(addr, destMemPtr, srcOffset, length)
        // write 3 bytes starting at offset 0 of the account's code into `prefix`
        extcodecopy(account, prefix, 0, 3)
    }
    if (prefix == EIP7702_MAGIC) return false; // confirmed EIP-7702 delegated EOA → allow
    return true;                               // exotic 23-byte contract → block
}

/// @dev Call at the top of _update (or _beforeTokenTransfer) to block contract interactions.
///      Whitelisted addresses (LP pair, router, staking) bypass this check entirely.
function _checkNotContract(address addr) internal view {
    if (_isWhitelisted[addr]) return;
    require(!_isRealContract(addr), "AntiFlash: contract caller blocked");
}

策略 3:同区块冷却

设计思路:禁止在同一区块内买入后卖出(或收到代币后卖出)。这是防御闪电贷最直接的方法,因为闪电贷必须在单笔交易(一个区块)内完成。

关键设计要点:

  • 追踪每个地址最近一次收到代币时的区块号
  • 卖出时(转账至 LP 交易对):要求追踪的区块号严格小于当前的 block.number
  • 关键:还要处理转账绕过的情况——阻止在同一区块内执行 A 买入 → A 转账给 B → B 卖出
  • 钱包间转账时:使用 max(lastBlock[to], lastBlock[from]) 传递区块限制,以防止状态洗白
  • 不要使用 block.timestamp 追踪同一区块——block.number 才是正确的指标

为何能防御闪电贷:闪电贷在单笔交易(单一区块)内执行并偿还。同区块冷却可彻底阻止买入→卖出循环。

_update 集成枢纽

所有策略状态的传递都必须整合到单个 _update 重写中。这是最常被忽略的漏洞——只接入部分逻辑会留下可绕过转账的路径。

function _update(address from, address to, uint256 amount) internal override {
    // ── Guard: call super first so balances are updated before our checks ──
    super._update(from, to, amount);

    bool fromPair = _isAMMPair[from]; // buy: tokens flow from pair → user
    bool toPair   = _isAMMPair[to];   // sell: tokens flow from user → pair
    bool isTransfer = !fromPair && !toPair && from != address(0) && to != address(0);

    // ── Strategy 2: Block real contracts ──
    // Only check non-whitelisted, non-pair addresses
    if (from != address(0) && !fromPair) {
        _checkNotContract(from);
    }

    // ── Strategy 3: Same-block cooldown ──
    if (toPair && from != address(0)) {
        // sell path: seller must not have received tokens this block
        require(
            block.number > _lastInteractionBlock[from],
            "AntiFlash: same-block sell"
        );
    }
    if (fromPair) {
        // buy path: record the block so this address cannot sell same-block
        _lastInteractionBlock[to] = block.number;
    }
    if (isTransfer) {
        // ★ transfer path: propagate the stricter (larger) block number
        // using max() ensures restriction only extends, never shortens
        // prevents: buy → transfer to fresh address → sell same-block
        if (_lastInteractionBlock[from] > _lastInteractionBlock[to]) {
            _lastInteractionBlock[to] = _lastInteractionBlock[from];
        }
    }

    // ── Strategy 1: Cost basis propagation ──
    if (useCostTracking && isTransfer) {
        _propagateCostBasis(from, to, amount);
    }
    if (useCostTracking && fromPair) {
        // record buy cost using current price from oracle or passed-in value
        // (exact cost recording depends on your price feed integration)
        _recordBuy(to, amount, _getCurrentCostBasis(amount));
    }

    // ── Strategy 7: Progressive tax timestamp propagation ──
    if (useProgressiveTax && isTransfer) {
        // propagate the older (more restrictive) timestamp so receiver
        // cannot launder holding time by receiving a fresh transfer
        if (_firstBuyTimestamp[from] > 0) {
            if (_firstBuyTimestamp[to] == 0 || _firstBuyTimestamp[from] > _firstBuyTimestamp[to]) {
                _firstBuyTimestamp[to] = _firstBuyTimestamp[from];
            }
        }
    }
    if (useProgressiveTax && fromPair && _firstBuyTimestamp[to] == 0) {
        _firstBuyTimestamp[to] = block.timestamp;
    }

    // ── Strategy 4: Per-address daily volume ──
    if (useDailyLimit && !fromPair && from != address(0)) {
        uint256 today = block.timestamp / 1 days;
        _dailyVolume[from][today] += amount;
        require(_dailyVolume[from][today] <= _dailyLimit, "AntiFlash: daily limit");
    }
}

所有状态传递的关键规则:使用 max() 语义——限制只延长,绝不缩短。直接赋值(state[to] = state[from])可被利用:冷却时间较短的发送方可以覆盖接收方较长的冷却时间。

策略 4:单地址交易量限制

设计思路:从单笔交易和周期两个层面限制个人交易量,降低任何攻击的经济规模。

关键设计要点:

  • 单笔交易限额:可配置为总供应量的一定比例
  • 周期(例如每日)内的单地址累计限额
  • 使用 block.timestamp / period_length 作为映射键来跟踪周期
  • 将 LP 交易对、质押合约和运营地址列入白名单,使其不受限制
  • 参数应允许所有者调整,并设定上限

抵御闪电贷攻击的原理:限制攻击者在单笔交易中可转移的数量。即使其他防御措施被绕过,损失也能受到限制。

策略 5:最低余额保留

设计思路:要求每次转账后,发送方账户中都保留少量最低余额,而不是允许全部提取。

关键设计要点:

  • 定义可配置的最低保留余额(少量尘埃金额)
  • 在 _update / _transfer 中强制执行:balanceOf(from) - amount >= minBalance
  • 特定操作可例外:授权销毁、合约迁移
  • 附带好处:维持持币者数量指标(地址余额不会归零)
  • 最低余额应足够小,不影响正常用户体验

策略 6:抢先交易卖出保护

设计思路:检测到大额卖单时,调整参数或触发保护机制,以缓冲价格冲击。

关键设计要点:

  • 定义触发保护的阈值(例如 LP 储备的一定比例)
  • 可选方案:临时提高卖出手续费、用已收税款回购、调整 LP 参数
  • 最好在业务/路由器层实现,而不是在代币合约内部实现
  • 考虑使用 Uniswap V4 hooks 实现原生 LP 集成
  • 按时间窗口监控卖出与买入的比例;当卖出比例明显偏高时,提高手续费
  • 此策略较为复杂,可能不适用于所有项目

策略 7:递进式卖出税(阶梯税)

设计思路:对近期获得的代币征收较高卖出税,并随着持有时间增加而降低税率。鼓励持有,惩罚快速买卖。

关键设计要点:

  • 跟踪每个地址的首次买入时间戳
  • 定义可配置的税率计划:持有时间最短时税率最高,随后逐步降至基准税率
  • 从卖出金额中扣除税款,并发送至指定的手续费接收方
  • 关键:钱包到钱包转账时,传递时间戳以防止时间戳洗白(如果发送方受到限制,接收方不应获得“全新”的时间戳)
  • 税务参数应允许所有者调整
  • 考虑使用时间分档(小时/天),而非连续公式,以提高燃料效率

抵御闪电贷攻击的原理:闪电借入的代币实际持有时间接近于零 → 适用最高税率 → 消除利润空间。

实现方式:

struct TaxTier {
    uint32 maxAge;  // max holding time in seconds for this tier to apply
    uint16 taxBps;  // tax rate in basis points (e.g. 2000 = 20%)
}

TaxTier[] public sellTaxSchedule;

/// @dev Returns the sell tax rate (bps) for `seller` based on their holding time.
///      Tiers must be ordered ascending by maxAge. Returns BASE_TAX_BPS if all tiers exceeded.
function _getSellTaxRate(address seller) internal view returns (uint256) {
    uint256 holdTime = block.timestamp - _firstBuyTimestamp[seller];
    uint256 len = sellTaxSchedule.length;
    for (uint256 i; i < len; ) {
        if (holdTime < sellTaxSchedule[i].maxAge) return sellTaxSchedule[i].taxBps;
        unchecked { ++i; }
    }
    return BASE_TAX_BPS; // long-term holder baseline rate
}

/// @dev Example schedule — owner should configure via setter with an upper-bound guard
function _initDefaultTaxSchedule() internal {
    sellTaxSchedule.push(TaxTier(1 hours,  4000)); // < 1h  → 40%
    sellTaxSchedule.push(TaxTier(1 days,   2500)); // < 1d  → 25%
    sellTaxSchedule.push(TaxTier(7 days,   1500)); // < 7d  → 15%
    sellTaxSchedule.push(TaxTier(30 days,   500)); // < 30d → 5%
    // > 30d → BASE_TAX_BPS
}

/// @dev Owner setter — enforce upper bound to prevent rug via tax
function setSellTaxSchedule(TaxTier[] calldata tiers) external onlyOwner {
    for (uint256 i; i < tiers.length; ) {
        require(tiers[i].taxBps <= 5000, "AntiFlash: tax exceeds 50%");
        unchecked { ++i; }
    }
    delete sellTaxSchedule;
    for (uint256 i; i < tiers.length; ) {
        sellTaxSchedule.push(tiers[i]);
        unchecked { ++i; }
    }
}

燃料说明:对于 ≤8 个档位,线性扫描即可。如果需要更多档位,请改用二分查找或硬编码的 if-else 链。

策略 8:生态系统收益税

设计思路:额外的卖出税用于累积生态系统价值,例如 NFT 持有者分红、子代币销毁或其他生态系统合约。

关键设计要点:

  • 在基础费用之外额外征收卖出税,并分配给指定的生态系统合约
  • 税款可根据已实现利润计算(需要使用策略 1 的成本跟踪),也可采用固定税率
  • 通过**提取模式(领取)**而非推送模式分发分红,以避免燃料消耗攻击
  • 可与策略 7 结合,构建多层税收体系
  • 确保税款接收合约经过验证且不可变(或受时间锁保护)

策略 9:基础交易手续费

设计思路:对所有买入/卖出交易收取标准基础手续费,并结合 AMM 自然滑点,为任何往返交易设定最低成本门槛。

关键设计要点:

  • 对买入和卖出方向均收取可配置的手续费
  • 手续费分配给:开发基金/注入 LP/销毁(可配置)
  • 与 AMM 滑点结合,为往返交易设定最低成本
  • 单靠这一策略不足以防范闪电贷——它是基础防护层,需与其他策略配合使用
  • 手续费应保持合理,避免阻碍正常交易

策略 10:推荐关系绑定系统

设计思路:要求地址在交易前建立推荐关系,形成匿名闪电贷合约无法绕过的身份门槛。

关键设计要点:

  • 新地址必须先由现有推荐人“激活”,才能进行首次交易
  • 每个地址的推荐关系仅可设置一次,且不可更改
  • 从交易手续费中支付多级推荐佣金(深度和费率可配置)
  • 闪电贷合约没有推荐人 → 无法交易
  • 提供一定程度的 Sybil 抵抗能力(短时间内创建大量已激活地址更加困难)
  • 考虑对用户体验的影响——这可能会给合法新用户带来阻碍

组合指南

根据项目需求选择策略。策略越多,防护越强,但 Gas 成本和实现复杂度也越高:

防护级别策略适用场景
基础3 + 4 + 9有基本防护需求的标准代币
标准2 + 3 + 4 + 5 + 7 + 9有一定价值风险敞口的社区代币
高级1 + 2 + 3 + 4 + 5 + 7 + 8 + 9拥有生态系统的高价值 DeFi 代币
最高所有策略拥有 NFT 生态系统、子代币且 TVL 较高的代币

Gas 优化:结构体打包

启用多种策略时,每个独立的 mapping(address => uint256) 在每次转账时都会对每个字段产生一次 SLOAD/SSTORE。将每个地址的所有状态打包到一个结构体中,可将其减少为一次存储槽读写。

// ❌ Scattered storage — up to 4 separate SLOAD/SSTORE per transfer
mapping(address => uint256) private _lastInteractionBlock; // slot A
mapping(address => uint256) private _firstBuyTimestamp;    // slot B
mapping(address => uint256) private _dailyVolume;          // slot C
mapping(address => uint256) private _cooldownUntil;        // slot D

// ✅ Packed storage — 1 SLOAD + 1 SSTORE covers all four fields
// uint64 block number: safe until block ~1.8×10^19 (far future)
// uint64 timestamp:    safe until year ~584 billion
// uint96 daily volume: up to ~79 billion tokens with 18 decimals
struct UserState {
    uint64  lastBlock;       // Strategy 3: last interaction block
    uint64  firstBuyTime;    // Strategy 7: first buy timestamp
    uint96  dailyVolume;     // Strategy 4: today's traded volume
    uint32  cooldownUntil;   // optional: unix timestamp cooldown end
    // total: 8+8+12+4 = 32 bytes → exactly 1 storage slot
}

mapping(address => UserState) private _userState;

// Example read — one SLOAD fetches all fields
function _getSellTaxRatePacked(address seller) internal view returns (uint256) {
    uint256 holdTime = block.timestamp - uint256(_userState[seller].firstBuyTime);
    // ... same tier lookup as Strategy 7
}

// Example write — one SSTORE updates all touched fields
function _updateOnBuy(address buyer) internal {
    UserState storage s = _userState[buyer];
    s.lastBlock    = uint64(block.number);
    s.firstBuyTime = s.firstBuyTime == 0 ? uint64(block.timestamp) : s.firstBuyTime;
    // dailyVolume and cooldownUntil updated separately as needed
}

适用场景:当启用 3 种以上策略且需要关注转账 Gas 消耗时,启用结构体打包。请测量优化前后的差异——根据存储槽的冷/热状态,每次转账通常可节省 10,000–40,000 Gas。

实现检查清单

部署具有反闪电贷保护的代币前:

  • 所有防护状态(成本基准、区块跟踪、时间戳)均在所有转账路径(包括 _transfer/_update)中正确传递
  • 钱包间转账会传递限制状态——不仅限于与交易对的交互(这是最常被忽略的漏洞)
  • 为以下对象正确配置白名单:LP 交易对、路由器、质押合约、部署者多签钱包
  • 所有可配置参数均设有所有者设置函数,并定义上下限
  • 如果使用合约地址检查,需正确处理 EIP-7702 委托标识(23 字节)
  • 同区块冷却机制同时覆盖直接卖出和先转账再卖出的模式
  • 渐进式税费的时间戳无法通过转账到新地址来重置
  • 所有手续费/税款收取均使用拉取模式(领取),而非推送模式
  • 测量额外跟踪映射带来的 Gas 影响——确保 _transfer 不超过可接受的 Gas 限制
  • 模糊测试覆盖:零成本代币卖出、同区块多跳、地址循环、多地址 Sybil 绕过尝试
  • 如果涉及依赖预言机的定价,应在业务层考虑集成 TWAP(难以直接在代币合约中实现——优先使用路由器/业务合约)

评论

支持 Markdown