56454 bc insight gas undercharging threatens hayabusa network upgrade
Submitted on Oct 16th 2025 at 08:25:34 UTC by @Angry_Mustache_Man for Attackathon | VeChain Hayabusa Upgrade
Report ID: #56454
Report Type: Blockchain/DLT
Report severity: Insight
Target: https://github.com/vechain/thor/compare/master...release/hayabusa
Impacts:
Network not being able to confirm new transactions (total network shutdown)
Description
Vulnerability Details
During the HAYABUSA upgrade, the critical transition of PoA→PoS invokes the native_isEndorsed function. The native_isEndorsed function (in-scope for the attackathon) undercharges gas. See implementation:
https://github.com/vechain/thor/blob/b4c914fe573ed6141daa159fa293e9193a96d74f/builtin/authority_native.go#L105-L137
{"native_isEndorsed", func(env *xenv.Environment) []any {
var nodeMaster common.Address
env.ParseArgs(&nodeMaster)
env.UseGas(thor.SloadGas * 2)
listed, endorsor, _, _, err := Authority.Native(env.State()).Get(thor.Address(nodeMaster))
if err != nil {
panic(err)
}
if !listed {
return []any{false}
}
env.UseGas(thor.SloadGas)
endorsement, err := Params.Native(env.State()).Get(thor.KeyProposerEndorsement)
if err != nil {
panic(err)
}
// Use staker Transition Period logic
// to ensure that transitioning validators are marked as endorsed
env.UseGas(thor.GetBalanceGas)
isEndorsed, err := Staker.Native(env.State()).TransitionPeriodBalanceCheck(
env.ForkConfig(),
env.BlockContext().Number,
endorsement,
)(thor.Address(nodeMaster), endorsor)
if err != nil {
panic(err)
}
return []any{isEndorsed}
}},
}Current gas charges in native_isEndorsed:
Total charged GAS: 1000 gas.
However, TransitionPeriodBalanceCheck (called next) performs additional operations that are not charged here:
https://github.com/vechain/thor/blob/b4c914fe573ed6141daa159fa293e9193a96d74f/builtin/staker/transition.go#L72-L98
The code comment notes: "// before HAYABUSA fork, we only check the account balance". After HAYABUSA, GetValidation is invoked and performs extra storage reads (SLOADs) that are not accounted for in native_isEndorsed.
So the actual gas to be charged after HAYABUSA should be >= 1200 GAS: thor.SloadGas * 2 + thor.SloadGas + thor.GetBalanceGas + (Gas for the GetValidation call)
The Missing Gas Charge is that s.validationService.GetValidation(validator) performs:
Every block validation during the HAYABUSA transition calls native_isEndorsed. The missing charge per call creates economic incentives for exploitation: each call saves >=200 gas, causing economic depletion of the protocol and potentially leading to network shutdown.
Impact Details
Every validator check triggers the missing gas charge. Undercharging (~>=16.7%) per call can be exploited repeatedly, causing economic depletion and risking a full network shutdown (new transactions cannot be confirmed).
Proof of Concept
Create a new test file named thor/builtin/authority_native_test.go with the following tests to (a) statically verify that native_isEndorsed does not charge an SLOAD before calling TransitionPeriodBalanceCheck, and (b) measure gas consumed by GetValidation:
Run the tests:
go test -v ./builtin -run Test_POC
go test -v ./builtin -run Test_GasConsumedAtGetValidation
Was this helpful?