56513 bc insight during the call to native issuance there s a missing gas charge before call to calculaterewards

Submitted on Oct 17th 2025 at 06:55:44 UTC by @emarai for Attackathon | VeChain Hayabusa Upgrade

  • Report ID: #56513

  • Report Type: Blockchain/DLT

  • Report severity: Insight

  • Target: https://github.com/vechain/thor/compare/master...release/hayabusa

  • Impacts: Modification of transaction fees outside of design parameters

Description

Brief/Intro

The call to staker_native::native_issuance is followed by a call to Energy.Native(env.State(), env.BlockContext().Time).CalculateRewards(staker), which reads from storage. Any read from storage should be preceded by charger.Charge(thor.SloadGas); otherwise the call to native_issuance is cheaper than it actually needs to be.

Vulnerability Details

In builtin/staker_native.go line 445 the native_issuance implementation calls CalculateRewards without a prior gas charge:

{"native_issuance", func(env *xenv.Environment) ([]any, error) {
    charger := gascharger.New(env)

    staker := Staker.NativeMetered(env.State(), charger)
@>    issuance, err := Energy.Native(env.State(), env.BlockContext().Time).CalculateRewards(staker)
    if err != nil {
        return nil, err
    }
    return []any{issuance}, nil
}}

The CalculateRewards function in builtin/energy/energy.go (around line 341) performs storage reads such as e.params.Get(thor.KeyCurveFactor) and staker.LockedStake(). Each of these storage reads should incur thor.SloadGas. Because these reads are not preceded by a gas charge in the native call, the native call is undercharged. The gas charged should be 400 instead of 200 (see PoC).

Relevant snippet from CalculateRewards:

Impact Details

Gas for native calls is cheaper than it should be; the native call is exposed via the generated staker.sol contract's issuance() function:

This means external callers (or contracts) invoking issuance() may pay less gas than the protocol's intended accounting for storage reads, altering transaction-fee behavior relative to design.

References

  • https://github.com/vechain/thor/blob/706bee9e6693244a6ddac17f883c7b09c6c63852/builtin/staker_native.go#L445

  • https://github.com/vechain/thor/blob/release/hayabusa/builtin/energy/energy.go#L341

  • https://github.com/vechain/thor/blob/release/hayabusa/builtin/gen/staker.sol#L283

Proof of Concept

PoC: modify test to demonstrate missing gas charge

Change the following line in builtin/staker_native_gas_test.go, then run the test:

Command: go test -timeout 30s -run ^TestStakerNativeGasCosts$ github.com/vechain/thor/v2/builtin

Patch:

The test will fail because the code did not charge for the additional storage read (e.params.Get(thor.KeyCurveFactor)), demonstrating the undercharge.

Was this helpful?