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
~$15.9M Lost: Trusted Volumes & More | BlockSec Weekly
Security Insights

~$15.9M Lost: Trusted Volumes & More | BlockSec Weekly

This BlockSec bi-weekly security report covers 11 notable attack incidents identified between April 27 and May 10, 2026, across Sui, Ethereum, BNB Chain, Base, Blast, and Berachain, with total estimated losses of approximately $15.9M. Three incidents are analyzed in detail: the highlighted $1.14M Aftermath Finance exploit on Sui, where a signed/unsigned semantic mismatch in the builder-fee validation allowed an attacker to inject a negative fee that was converted into positive collateral during settlement; the $5.87M Trusted Volumes RFQ authorization mismatch on Ethereum; and the $5.7M Wasabi Protocol infrastructure-to-contract-control compromise across multiple EVM chains.

Newsletter - April 2026
Security Insights

Newsletter - April 2026

In April 2026, the DeFi ecosystem experienced three major security incidents. KelpDAO lost ~$290M due to an insecure 1-of-1 DVN bridge configuration exploited via RPC infrastructure compromise, Drift Protocol suffered ~$285M from a multisig governance takeover leveraging Solana's durable nonce mechanism, and Rhea Finance incurred ~$18.4M following a business logic flaw in its margin-trading module that allowed circular swap path manipulatio

~$7.04M Lost: GiddyDefi, Volo Vault & More | BlockSec Weekly
Security Insights

~$7.04M Lost: GiddyDefi, Volo Vault & More | BlockSec Weekly

This BlockSec weekly security report covers eight attack incidents detected between April 20 and April 26, 2026, across Ethereum, Avalanche, Sui, Base, HyperLiquid, and MegaETH, with total estimated losses of approximately $7.04M. The highlighted incident is the $1.3M GiddyDefi exploit, where the attacker did not break any cryptography or use a flash loan but simply replayed an existing on-chain EIP-712 signature with the unsigned `aggregator` and `fromToken` fields swapped out for a malicious contract, demonstrating how partial signature coverage turns any historical signature into a generic permit. Other incidents include a $3.5M Volo Vault operator key compromise on Sui, a $1.5M Purrlend privileged-role takeover, a $413K SingularityFinance oracle misconfiguration, a $142.7K Scallop cross-pool index injection, a $72.35K Kipseli Router decimal mismatch, a $50.7K REVLoans (Juicebox) accounting pollution, and a $64K Custom Rebalancer arbitrary-call exploit.

Best Security Auditor for Web3

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

BlockSec Audit