Back to Blog

深入分析:Truebit 事件

Code Auditing
January 14, 2026

2026年1月8日,以太坊上的Truebit协议遭到攻击,导致约2600万美元的损失 [1]。根本原因是TRU购买定价逻辑中的整数溢出。由于合约使用Solidity v0.6.10编译,该版本默认不强制执行溢出检查,导致购买成本计算中的一个中间大值环绕成一个非常小的数字。因此,攻击者能够以极低的甚至零ETH购买大量的TRU,然后立即以有利的汇率将获得的TRU卖回给合约换取ETH,从而耗尽协议储备。

0x0 背景

Truebit通过链下计算和交互式验证为以太坊提供计算服务 [2]。该协议使用原生代币TRU,并提供两个公开的交易函数:

  • buyTRU():执行TRU购买。所需的ETH成本由内部定价函数计算,该函数也用于getPurchasePrice(),因此getPurchasePrice()反映了购买执行期间适用的确切链上定价逻辑。

  • sellTRU():执行TRU销售(赎回)。预期的ETH支付可以通过getRetirePrice()查询。

关键的设计方面是定价不对称:

  • 购买使用凸形定价曲线(边际价格随供应量增加而增加)。
  • 销售使用线性赎回规则(与储备金成比例)。

由于合约源代码不公开,以下分析基于反编译的字节码。

购买逻辑

buyTRU()函数(以及getPurchasePrice()函数)将定价委托给内部函数_getPurchasePrice(),该函数计算购买amount TRU所需的ETH。

function buyTRU(uint256 amount) public payable {
    require(msg.data.length - 4 >= 32);
    v0 = _getPurchasePrice(amount); // 获取购买价格
    require(msg.value == v0, Error('ETH payment does not match TRU order'));
    v1 = 0x18ef(100 - _setParameters, msg.value);
    v2 = _SafeDiv(100, v1);
    v3 = _SafeAdd(v2, _reserve);
    _reserve = v3;
    require(bool(stor_97_0_19.code.size));
    v4 = stor_97_0_19.mint(msg.sender, amount).gas(msg.gas);
    require(bool(v4), 0, RETURNDATASIZE()); // 检查调用状态,错误时传播错误数据
    return msg.value;
}

function getPurchasePrice(uint256 amount) public nonPayable {
    require(msg.data.length - 4 >= 32);
    v0 = _getPurchasePrice(amount); // 获取购买价格
    return v0;
}

function _getPurchasePrice(uint256 amount) private { 
    require(bool(stor_97_0_19.code.size));
    v0, /* uint256 */ v1 = stor_97_0_19.totalSupply().gas(msg.gas);
    require(bool(v0), 0, RETURNDATASIZE()); // 检查调用状态,错误时传播错误数据
    require(RETURNDATASIZE() >= 32);
    v2 = 0x18ef(v1, v1)
    v3 = 0x18ef(_setParameters, v2);
    v4 = 0x18ef(v1, v1);
    v5 = 0x18ef(100, v4);
    v6 = _SafeSub(v3, v5);// 分母 = 100 * totalSupply**2 - _setParameters * totalSupply**2 
    v7 = 0x18ef(amount, _reserve);
    v8 = 0x18ef(v1, v7);
    v9 = 0x18ef(200, v8);// 分子_2 = 200 * totalSupply * amount * _reserve
    v10 = 0x18ef(amount, _reserve);
    v11 = 0x18ef(amount, v10);
    v12 = 0x18ef(100, v11);// 分子_1 = 100 * amount**2 * _reserve
    v13 = _SafeDiv(v6, v12 + v9); // purchasePrice = (numerator_1 + numerator_2) / denominator
    return v13;
}

从反编译的逻辑来看,购买价格可以表示为关于以下内容的定价曲线风格函数:

其中,

  • amount:要购买的TRU数量
  • reserve_reserve):合约的以太坊储备金
  • totalSupply:TRU的总供应量
  • θ_setParameters):一个系数,固定为75

这条曲线旨在使大额购买成本越来越高(成本增长呈凸形),以阻止投机并减少即时买入方面的操纵。

销售逻辑

sellTRU()函数(以及getRetirePrice()函数)使用内部函数_getRetirePrice()来计算赎回TRU时支付的ETH。

function sellTRU(uint256 amount) public nonPayable {
    require(msg.data.length - 4 >= 32);
    require(bool(stor_97_0_19.code.size));
    v0, /* uint256 */ v1 = stor_97_0_19.allowance(msg.sender, address(this)).gas(msg.gas);
    require(bool(v0), 0, RETURNDATASIZE()); // 检查调用状态,错误时传播错误数据
    require(RETURNDATASIZE() >= 32);
    require(v1 >= amount, Error('Insufficient TRU allowance'));
    v2 = _getRetirePrice(amount); // 获取赎回价格
    v3 = _SafeSub(v2, _reserve);
    _reserve = v3;
    require(bool(stor_97_0_19.code.size));
    v4, /* uint256 */ v5 = stor_97_0_19.transferFrom(msg.sender, address(this), amount).gas(msg.gas);
    require(bool(v4), 0, RETURNDATASIZE()); // 检查调用状态,错误时传播错误数据
    require(RETURNDATASIZE() >= 32);
    require(bool(stor_97_0_19.code.size));
    v6 = stor_97_0_19.burn(amount).gas(msg.gas);
    require(bool(v6), 0, RETURNDATASIZE()); // 检查调用状态,错误时传播错误数据
    v7 = msg.sender.call().value(v2).gas(!v2 * 2300);
    require(bool(v7), 0, RETURNDATASIZE()); // 检查调用状态,错误时传播错误数据
    return v2;
}

function getRetirePrice(uint256 amount) public nonPayable {
    require(msg.data.length - 4 >= 32);
    v0 = _getRetirePrice(amount); // 获取赎回价格
    return v0;
}

function _getRetirePrice(uint256 amount) private { 
    require(bool(stor_97_0_19.code.size));
    v0, /* uint256 */ v1 = stor_97_0_19.totalSupply().gas(msg.gas);
    require(bool(v0), 0, RETURNDATASIZE()); // 检查调用状态,错误时传播错误数据
    require(RETURNDATASIZE() >= 32);
    v1 = v2.length;
    v3 = v2.data;
    v4 = 0x18ef(_reserve, amount);// 分子 = _reserve * amount
    if (v1 > 0) {
        assert(v1);
        return v4 / v1;// retirePrice = numerator / totalSupply
    } else {
    // ...
}

赎回规则是线性的:

赎回价格与被赎回的总供应量分数(即amount / totalSupply)乘以reserve成正比。

这种故意的不对称性造成了巨大的价差:购买是凸形的(大规模时昂贵),而销售是线性的(只赎回储备金的比例部分)。在正常情况下,这种价差使得即时买入→卖出套利不具吸引力。

0x1 漏洞分析

尽管有“大额购买昂贵”的设计意图,但_getPurchasePrice()在其算术运算中包含了一个整数溢出。由于合约使用Solidity 0.6.10编译,uint256上的算术运算会静默溢出并以2^256取模环绕,除非有明确的保护(例如通过SafeMath)。

function _getPurchasePrice(uint256 amount) private { 
    require(bool(stor_97_0_19.code.size));
    v0, /* uint256 */ v1 = stor_97_0_19.totalSupply().gas(msg.gas);
    require(bool(v0), 0, RETURNDATASIZE()); // 检查调用状态,错误时传播错误数据
    require(RETURNDATASIZE() >= 32);
    v2 = 0x18ef(v1, v1)
    v3 = 0x18ef(_setParameters, v2);
    v4 = 0x18ef(v1, v1);
    v5 = 0x18ef(100, v4);
    v6 = _SafeSub(v3, v5);// 分母 = 100 * totalSupply**2 - _setParameters * totalSupply**2 
    v7 = 0x18ef(amount, _reserve);
    v8 = 0x18ef(v1, v7);
    v9 = 0x18ef(200, v8);// 分子_2 = 200 * totalSupply * amount * _reserve
    v10 = 0x18ef(amount, _reserve);
    v11 = 0x18ef(amount, v10);
    v12 = 0x18ef(100, v11);// 分子_1 = 100 * amount**2 * _reserve
    v13 = _SafeDiv(v6, v12 + v9); // purchasePrice = (numerator_1 + numerator_2) / denominator
    return v13;
}

_getPurchasePrice()中,足够大的amount会在两个大分子项(反编译片段中的v12 + v9)的加法过程中触发溢出。当发生此溢出时,分子环绕成一个很小的值,导致最终除法返回一个人为偏低的购买价格,可能为零。

至关重要的是,溢出仅影响买入方定价。卖出方函数保持线性并按预期运行,因此攻击者可以:

  • 以低于成本价(或零成本)购买大量TRU,然后
  • 通过sellTRU()以高得多的有效汇率将其赎回为ETH。

0x2 攻击分析

攻击者在一次交易中执行了多轮套利 [3],重复了以下过程:

                               `getPurchasePrice()` -> `buyTRU()` -> `sellTRU()`

第一轮:零成本购买,然后获利卖出

通过提供一个精心选择的购买金额(240,442,509.453,545,333,947,284,131),攻击者触发了_getPurchasePrice()中的溢出,将计算出的购买价格降低到0 ETH,并允许以零成本获得约2.4亿TRU。

下面的Python代码检查说明了分子超过2^256,并且在环绕后,计算出的购买价格变成了一个非常小的分数,转换为整数时截断为零。

>>> _reserve = 0x1ceec1aef842e54d9ee
>>> totalSupply = 161753242367424992669183203
>>> amount = 240442509453545333947284131
>>> numerator = int(100 * amount * _reserve * (amount + 2 * totalSupply))
>>> numerator > 2**256
True
>>> denominator = (100 - 75) * totalSupply**2
>>> purchasePrice = (numerator - 2**256) / denominator
>>> purchasePrice
0.00025775798757211426
>>> int(purchasePrice)
0

然后,攻击者立即调用sellTRU(),从协议储备金中赎回TRU,获得5,105 ETH。

后续轮次:低成本购买,然后获利卖出

攻击者重复了这个循环多次。后续的购买并不总是严格零成本,但溢出继续使购买价格远低于相应的卖出回报。

在这些轮次中,攻击者提取了大量的ETH,我们的调查表明,在第一轮之后可能仍然存在额外的零成本购买,尽管攻击者选择进行一些非零成本轮次的原因尚不清楚。

总而言之,攻击者从Truebit的储备金中耗尽了8,535 ETH。

0x3 总结

此事件最终是由Truebit买入方定价逻辑中一个未经验证的整数溢出引起的。尽管该协议的买入/卖出定价模式不对称,旨在抵御投机,但使用没有系统性溢出保护的旧版Solidity(0.8版之前)进行编译,破坏了设计并导致了储备金耗尽。

对于任何仍在使用0.8版以下Solidity的生产合约,开发者应:

  • 对每个相关操作应用防溢出算术(例如,SafeMath或等效检查),或
  • 最好迁移到Solidity 0.8+以利用默认的溢出检查。

参考

[1] https://x.com/Truebitprotocol/status/2009328032813850839

[2] https://docs.truebit.io/v1docs

[3] 攻击交易

Sign up for the latest updates
Drift Protocol Incident: Multisig Governance Compromise via Durable Nonce Exploitation
Security Insights

Drift Protocol Incident: Multisig Governance Compromise via Durable Nonce Exploitation

On April 1, 2026 (UTC), Drift Protocol on Solana suffered a $285.3M loss after an attacker exploited Solana's durable nonce mechanism to delay the execution of phished multisig approvals, ultimately transferring administrative control of the protocol's 2-of-5 Squads governance with zero timelock. With full admin privileges, the attacker created a malicious collateral market (CVT), inflated its oracle price, relaxed withdrawal protections, and drained USDC, JLP, SOL, cbBTC, and other assets through 31 rapid withdrawals in approximately 12 minutes. This incident highlights how durable nonce-based delayed execution can decouple signer intent from on-chain execution, bypassing the temporal assumptions that multisig security implicitly relies on.

Weekly Web3 Security Incident Roundup | Mar 23 – Mar 29, 2026
Security Insights

Weekly Web3 Security Incident Roundup | Mar 23 – Mar 29, 2026

This BlockSec weekly security report covers eight DeFi attack incidents detected between March 23 and March 29, 2026, across Ethereum and BNB Chain, with total estimated losses of approximately $1.53M. Incidents include a $679K flawed burn mechanism exploit on the BCE token, a $512K spot-price manipulation attack on Cyrus Finance's PancakeSwap V3 liquidity withdrawal, a $133.5K flash-loan-driven referral reward manipulation on a TUR staking contract, and multiple integer overflow, reentrancy, and accounting error vulnerabilities in DeFi protocols. The report provides detailed vulnerability analysis and attack transaction breakdowns for each incident.

Newsletter -  March 2026
Security Insights

Newsletter - March 2026

In March 2026, the DeFi ecosystem experienced three major security incidents. Resolv Protocol lost ~$80M due to compromised privileged infrastructure keys, BitcoinReserveOffering suffered ~$2.7M from a double-minting logic flaw, and Venus Protocol incurred ~$2.15M following a donation attack combined with market manipulation.

Best Security Auditor for Web3

Validate design, code, and business logic before launch. Aligned with the highest industry security standards.

BlockSec Audit