Ambiguous Evaluation Order¶
Smart contract code must be unequivocally clear regarding the evaluation order of variables. Statements mustn't leave room for ambiguity in the sequence of variable evaluation, as inconsistent outcomes can arise from such uncertainty. The Solidity documentation states the following:
The evaluation order of expressions is not specified (more formally, the order in which the children of one node in the expression tree are evaluated is not specified, but they are of course evaluated before the node itself). It is only guaranteed that statements are executed in order and short-circuiting for boolean expressions is done.
The consequence of this quote is that functions evaluated in Solidity are not evaluated in a fully deterministic way. While the output is deterministic for a particular Solidity compiler version, it may not remain consistent across different versions. This inherent ambiguity can become problematic when multiple functions independently affect shared stateful objects and are invoked within the same statement. Depending on the sequence in which these functions are evaluated, the final outcome of the statement could vary. An example is given by Mikhail Vladimirov on the Ethereum StackExchange:
1 2 3 4 |
|
Adding to the complexity, instructions like addmod
and mulmod
, as well as events, generally diverge from the standard pattern of evaluation order. Consequently, any code that incorporates these instructions might yield unexpected outcomes.
The EthTrust Security Levels Specification provides an example underlining the risk posed by the ambiguous evaluation order: If functions g
and h
manipulate any variable that the outcome of function f
relies on, an invocation like f(g(x), h(y))
cannot be assured to consistently return the same results.
To mitigate this vulnerability, a prevalent strategy involves storing intermediate results in temporary variables. This method ensures that the evaluation order remains unambiguous, regardless of compiler variations or complex functional interactions.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
|
In this example, the deterministicResult
function explicitly sets the evaluation order by storing the result of the firstTransform
function in a temporary variable, firstResult
. It then passes firstResult
into secondTransform
, thereby assuring a predictable sequence of execution, regardless of the side effects within each transformation function.