Back to Blog

深度分析:Truebit事件

Code Auditing
January 14, 2026
11 min read

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

0x0 背景

Truebit通过链下计算和交互式验证为以太坊提供计算服务 [2]。在该协议中,TRU代币作为协调激励(包括质押和与任务相关的支付)的核心经济工具。

该协议公开了两个用于购买和赎回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);// numerator_2 = 200 * totalSupply * amount * _reserve
    v10 = 0x18ef(amount, _reserve);
    v11 = 0x18ef(amount, v10);
    v12 = 0x18ef(100, v11);// numerator_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);// numerator = _reserve * amount
    if (v1 > 0) {
        assert(v1);
        return v4 / v1;// retirePrice = numerator / totalSupply
    } else {
    // ...
}

赎回规则是线性的:

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

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

0x1 漏洞分析

尽管设计意图是“大额购买昂贵”,但_getPurchasePrice()在算术运算中存在整数溢出。由于该合约是用Solidity 0.6.10编译的,uint256上的算术运算可能会在没有显式保护(例如通过SafeMath)的情况下静默溢出并回绕(模2^256)。

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);// numerator_2 = 200 * totalSupply * amount * _reserve
    v10 = 0x18ef(amount, _reserve);
    v11 = 0x18ef(amount, v10);
    v12 = 0x18ef(100, v11);// numerator_1 = 100 * amount**2 * _reserve
    v13 = _SafeDiv(v6, v12 + v9); // purchasePrice = (numerator_1 + numerator_2) / denominator
    return v13;
}

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

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

  • 以低于正常价格(或零价格)购买大量TRU,然后
  • 通过sellTRU()以高得多的有效汇率赎回TRU。

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] 攻击交易

关于BlockSec

BlockSec是全栈区块链安全和加密合规提供商。我们构建产品和服务,帮助客户在协议和平台的全生命周期中进行代码审计(包括智能合约、区块链和钱包)、实时拦截攻击、分析事件、追踪非法资金,并满足AML/CFT要求。

BlockSec在知名会议上发表了多篇区块链安全论文,报告了多个DeFi应用的零日攻击,阻止了多次黑客攻击挽救了超过2000万美元,并保障了数十亿美元的加密货币。

Sign up for the latest updates
The Decentralization Dilemma: Cascading Risk and Emergency Power in the KelpDAO Crisis
Security Insights

The Decentralization Dilemma: Cascading Risk and Emergency Power in the KelpDAO Crisis

This BlockSec deep-dive analyzes the KelpDAO $290M rsETH cross-chain bridge exploit (April 18, 2026), attributed to the Lazarus Group, tracing a causal chain across three layers: how a single-point DVN dependency enabled the attack, how DeFi composability cascaded the damage through Aave V3 lending markets to freeze WETH liquidity exceeding $6.7B across Ethereum, Arbitrum, Base, Mantle, and Linea, and how the crisis forced decentralized governance to exercise centralized emergency powers. The article examines three parameters that shaped the cascade's severity (LTV, pool depth, and cross-chain deployment count) and provides an exclusive technical breakdown of Arbitrum Security Council's forced state transition, an atomic contract upgrade that moved 30,766 ETH without the holder's signature.

Weekly Web3 Security Incident Roundup | Apr 13 – Apr 19, 2026
Security Insights

Weekly Web3 Security Incident Roundup | Apr 13 – Apr 19, 2026

This BlockSec weekly security report covers four attack incidents detected between April 13 and April 19, 2026, across multiple chains such as Ethereum, Unichain, Arbitrum, and NEAR, with total estimated losses of approximately $310M. The highlighted incident is the $290M KelpDAO rsETH bridge exploit, where an attacker poisoned the RPC infrastructure of the sole LayerZero DVN to fabricate a cross-chain message, triggering a cascading WETH freeze across five chains and an Arbitrum Security Council forced state transition that raises questions about the actual trust boundaries of decentralized systems. Other incidents include a $242K MMR proof forgery on Hyperbridge, a $1.5M signed integer abuse on Dango, and an $18.4M circular swap path exploit on Rhea Finance's Burrowland protocol.

Weekly Web3 Security Incident Roundup | Apr 6 – Apr 12, 2026
Security Insights

Weekly Web3 Security Incident Roundup | Apr 6 – Apr 12, 2026

This BlockSec weekly security report covers four DeFi attack incidents detected between April 6 and April 12, 2026, across Linea, BNB Chain, Arbitrum, Optimism, Avalanche, and Base, with total estimated losses of approximately $928.6K. Notable incidents include a $517K approval-related exploit where a user mistakenly approved a permissionless SquidMulticall contract enabling arbitrary external calls, a $193K business logic flaw in the HB token's reward-settlement logic that allowed direct AMM reserve manipulation, a $165.6K exploit in Denaria's perpetual DEX caused by a rounding asymmetry compounded with an unsafe cast, and a $53K access control issue in XBITVault caused by an initialization-dependent check that failed open. The report provides detailed vulnerability analysis and attack transaction breakdowns for each incident.

Best Security Auditor for Web3

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

BlockSec Audit