
Analysis of Solidity Compiler Vulnerability: Flaws in ABI Re-encoding
TechFlow Selected TechFlow Selected

Analysis of Solidity Compiler Vulnerability: Flaws in ABI Re-encoding
The process itself does not have significant logical issues, but when combined with Solidity's cleanup mechanism, a vulnerability arises due to an oversight in the Solidity compiler code.
Overview
This article provides a detailed analysis of a vulnerability in the Solidity compiler (0.5.8 <= version < 0.8.16) at the source code level, caused by incorrect handling of fixed-length uint and bytes32 array types during the ABI re-encoding process. It also proposes relevant solutions and mitigation measures.
Vulnerability Details
The ABI encoding format is the standard method used when users or contracts invoke functions on other contracts and pass parameters. For more details, refer to Solidity's official documentation on ABI Encoding.
In smart contract development, data is often retrieved from calldata sent by users or other contracts, and may later be forwarded or emitted via events. Since all EVM opcodes operate only on memory, stack, and storage, any ABI encoding operation in Solidity requires copying data from calldata into memory according to the ABI format and in the new parameter order.
This process itself does not have significant logical flaws. However, when combined with Solidity’s cleanup mechanism, a vulnerability arises due to an oversight in the Solidity compiler's implementation.
According to ABI encoding rules, after removing the function selector, encoded data consists of two parts: head and tail. When the data type is a fixed-length uint or bytes32 array, ABI stores such data entirely in the head section. Meanwhile, Solidity’s cleanup mechanism clears the next memory slot after the current one is used, to prevent dirty data from affecting future memory operations. Importantly, when Solidity performs ABI encoding for multiple parameters, it encodes them strictly from left to right!
To better understand the vulnerability, consider the following contract code:
contract Eocene {
event VerifyABI(bytes[], uint[2]);
function verifyABI(bytes[] calldata a, uint[2] calldata b) public {
emit VerifyABI(a, b); // Event data will be ABI-encoded and stored on-chain
}
}
The function verifyABI in contract Eocene simply emits the input parameters: a dynamically sized bytes[] array `a` and a fixed-size uint[2] array `b`.
Note that event emissions also trigger ABI encoding. Parameters `a` and `b` are encoded in ABI format before being stored on-chain.
We compile this contract using Solidity v0.8.14, deploy it via Remix, and call it with verifyABI(['0xaaaaaa','0xbbbbbb'], [0x11111, 0x22222]).
First, let’s examine the correct ABI encoding format for verifyABI(['0xaaaaaa','0xbbbbbb'], [0x11111, 0x22222]):
0x52cd1a9c // bytes4(keccak256("verifyABI(bytes[],uint[2])"))
0000000000000000000000000000000000000000000000000000000000000060 // offset of a
0000000000000000000000000000000000000000000000000000000000011111 // b[0]
0000000000000000000000000000000000000000000000000000000000022222 // b[1]
0000000000000000000000000000000000000000000000000000000000000002 // length of a
0000000000000000000000000000000000000000000000000000000000000040 // offset of a[0]
0000000000000000000000000000000000000000000000000000000000000080 // offset of a[1]
0000000000000000000000000000000000000000000000000000000000000003 // length of a[0]
aaaaaa00000000000000000000000000000000000000000000000000000000 // a[0]
0000000000000000000000000000000000000000000000000000000000000003 // length of a[1]
bbbbbb00000000000000000000000000000000000000000000000000000000 // a[1]
If the Solidity compiler worked correctly, the on-chain logged data should match exactly what was sent. Let’s test this by calling the contract and inspecting the on-chain logs. You can view the transaction yourself here: TX.
After successful execution, the event log shows:
!! Shocking — the value storing the length of parameter `a`, immediately following `b[1]`, has been incorrectly zeroed out !!
0000000000000000000000000000000000000000000000000000000000000060 // offset of a
0000000000000000000000000000000000000000000000000000000000011111 // b[0]
0000000000000000000000000000000000000000000000000000000000022222 // b[1]
0000000000000000000000000000000000000000000000000000000000000000 // length of a?? Why is it now 0??
0000000000000000000000000000000000000000000000000000000000000040 // offset of a[0]
0000000000000000000000000000000000000000000000000000000000000080 // offset of a[1]
0000000000000000000000000000000000000000000000000000000000000003 // length of a[0]
aaaaaa00000000000000000000000000000000000000000000000000000000 // a[0]
0000000000000000000000000000000000000000000000000000000000000003 // length of a[1]
bbbbbb00000000000000000000000000000000000000000000000000000000 // a[1]
Why does this happen?
As previously stated, when Solidity processes a series of parameters requiring ABI encoding, it does so from left to right. The specific encoding logic for `a` and `b` is as follows:
-
Solidity first encodes `a`. According to ABI rules, the offset of `a` is placed in the head, while its element count and actual values are stored in the tail.
-
Next, it processes `b`. Since `b` is a fixed-size uint[2] array, its values are stored directly in the head. However, due to Solidity’s cleanup mechanism, after writing `b[1]` into memory, the next memory slot — which holds the length of `a` — is inadvertently cleared to zero.
-
The ABI encoding completes, but the corrupted data is now permanently stored on-chain. This is the manifestation of vulnerability SOL-2022-6.
At the source code level, the flaw is clear: when copying fixed-length bytes32 or uint arrays from calldata to memory, Solidity always zeroes out the next memory slot after the copy. Combined with the left-to-right ABI encoding order and the separation between head and tail sections, this results in overwriting critical data in the tail.
The vulnerable Solidity compiler code path is as follows:
When the source location is Calldata, and the type is ByteArray, String, or an array with base type uint or bytes32, the compiler enters ABIFunctions::abiEncodingFunctionCalldataArrayWithoutCleanup()

Inside this function, fromArrayType.isDynamicallySized() checks whether the array is fixed-size. Only fixed-size arrays meet the vulnerability condition.
The result of isByteArrayOrString() is passed to YulUtilFunctions::copyToMemoryFunction(), determining whether to perform cleanup (zeroing the next slot) after calldatacopy.
These conditions together mean the vulnerability is triggered only when a fixed-length uint or bytes32 array from calldata is copied to memory. This explains the root cause of the vulnerability constraints.

Since ABI encoding proceeds strictly from left to right, we must recognize the exploitation prerequisites: there must be a dynamically sized type before the fixed-length uint or bytes32 array, such that its data resides in the tail section of the ABI encoding. Additionally, the fixed-length uint or bytes32 array must be the last parameter to be encoded.
The reason is straightforward: if the fixed-length array isn’t last, the subsequent parameter will overwrite the zeroed slot, negating any damage. If no prior dynamic data exists that uses the tail, then zeroing the next slot is harmless because that slot isn't used by ABI encoding.
Furthermore, it is important to note that all implicit or explicit ABI operations, including any Tuple (grouped data) matching this pattern, are affected by this vulnerability.
Specific operations impacted include:
-
event
-
error
-
abi.encode*
-
returns // the return of function
-
struct // user-defined struct
-
all external calls
Solutions
-
When using any of the affected operations in your contract, ensure the last parameter is not a fixed-length uint or bytes32 array.
-
Use a Solidity compiler version not affected by this vulnerability (i.e., ≥ 0.8.16).
-
Seek assistance from professional security auditors to conduct a thorough security review of your contract.
Join TechFlow official community to stay tuned
Telegram:https://t.me/TechFlowDaily
X (Twitter):https://x.com/TechFlowPost
X (Twitter) EN:https://x.com/BlockFlow_News














