Gas-accounting discrepancy for infinite loops
Description
A loop {} instruction takes approximately 5.2 seconds to reach the LimitExceeded error, despite the system's baseline costing rule being designed for 1μs = 100 cost units (implying ~1 second execution for the default 100m limit).
The issue stems from the WASM instrumentation process generating inefficient gas-accounting code for simple loops:
pub fn fn() {
loop {
std::hint::black_box(());
}
}It generates WASM with the following structure:
loop ;; label = @2
i64.const 3529 // Gas cost per iteration
call 11 // Gas accounting function call
br 0 (;@2;) // Branch back to start
endThe overhead of the gas-accounting function call (call 11) dominates the execution time since the original loop body is empty, making the actual runtime significantly exceed the intended cost limits.
Impact
Transactions can consume up to 5x more wall-clock time than intended by the gas limits, and there is also the potential for transaction-processing delays in blocks.
Recommendations
Several approaches can be taken to address this issue:
Gas cost adjustments
Add a base cost for each gas metering operation. Adjust the gas-cost unit multiplier to better reflect actual execution costs.
Loop optimization
Implement loop unrolling during instrumentation to reduce the frequency of gas-accounting calls. Here is an example transformation:
loop
i64.const 14116 // Combined cost for multiple iterations
call 11
br 0
endGas function optimization
Optimize the gas-accounting host function for the common case.
Remediation
This issue has been acknowledged by Radix Publishing Limited, and a fix was implemented in commit 86828df1↗.