For the complete documentation index, see llms.txt. This page is also available as Markdown.

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

Charge for each underlying storage fetch (for example, 3 * thor.SloadGas for totalSupply) and add a fixed compute premium to cover the big-int math in both getters.

Proof of Concept

Was this helpful?