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
end
The 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
end
Gas 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↗.