55925 bc insight underpriced supply queries enable cheap cpu dos
Submitted on Oct 7th 2025 at 20:42:31 UTC by @spongebob for Attackathon | VeChain Hayabusa Upgrade
Report ID: #55925
Report Type: Blockchain/DLT
Report severity: Insight
Target: https://github.com/vechain/thor/compare/master...release/hayabusa
Impacts:
Temporary freezing of network transactions by delaying one block by 500% or more of the average block time of the preceding 24 hours beyond standard difficulty adjustments
Description
Underpriced gas in native_totalSupply lets callers trigger three storage decodes and heavy CalcEnergy math while only paying a single SloadGas (200). The native wrapper charges once at https://github.com/vechain/thor/blob/3ad5e1805c778a070e27d0d0293f335a256c235e/builtin/energy_native.go#L22-L29
Yet TotalSupply() pulls initial-supply, growth-stop-time, and issued via State.DecodeStorage plus re-does big-int growth math:
https://github.com/vechain/thor/blob/3ad5e1805c778a070e27d0d0293f335a256c235e/builtin/energy/energy.go#L48-L133
https://github.com/vechain/thor/blob/3ad5e1805c778a070e27d0d0293f335a256c235e/builtin/energy/energy.go#L229-L239
Example of one of the storage decodes:
func (e *Energy) GetEnergyGrowthStopTime() (uint64, error) {
if e.stopTime != 0 {
return e.stopTime, nil
}
var time uint64
if err := e.state.DecodeStorage(e.addr, growthStopTimeKey, func(raw []byte) error {
if len(raw) == 0 {
return nil
}
return rlp.DecodeBytes(raw, &time)The underlying account storage decode is implemented here:
https://github.com/vechain/thor/blob/3ad5e1805c778a070e27d0d0293f335a256c235e/state/account.go#L44-L77
After the first call the trie data is cached, but every subsequent call still re-decodes the RLP payloads and multiplies big integers.
An attacker can loop Energy.totalSupply() thousands of times in one clause, consuming most of a block’s CPU while buying only ≈200 gas per iteration.
native_totalBurned has the same pattern. It bills one SloadGas in: https://github.com/vechain/thor/blob/3ad5e1805c778a070e27d0d0293f335a256c235e/builtin/energy_native.go#L30-L36
Yet TotalBurned() RLP-decodes the aggregated add/sub counters and performs big-int arithmetic. That extra work is effectively free, giving another cheap tight loop primitive (lower impact than totalSupply, but still mis-priced).
Severity rationale: This can let an attacker delay block production by cheaply forcing validators into heavy CPU work.
Recommendation
Proof of Concept
Was this helpful?