A significant vulnerability in the Solidity code generator has been brought to light following a responsible disclosure by developer Shaheen Fazim through the Ethereum Foundation’s bug bounty program. Discovered on August 14, 2026, the defect involves how the EVM assembly pipeline handles the delete operator when applied to individual elements of memory-based bytes and string arrays. Instead of safely clearing a single byte as intended, the generated bytecode writes an entire 32-byte word of zeroes starting at the targeted index. This unexpected behavior risks overwriting adjacent elements within the same array or bleeding past the array’s boundaries to corrupt subsequent memory allocations.
Assigned a "low/medium" severity rating on the internal scale maintained by the Solidity team, the issue impacts all historical versions of the language up to and including version 0.8.36. The code path responsible for the faulty emission actually predates the very first official release of Solidity. Fortunately, the bug is strictly confined to the legacy evmasm pipeline and does not affect the modern intermediate representation (IR) pipeline. Furthermore, the optimizer is not required to trigger the defect, meaning even unoptimized builds compiled under default configurations remain vulnerable if they execute the specific instruction pattern.
Which Contracts Are Affected?
To determine whether a specific smart contract is vulnerable to this memory-corruption issue, developers must evaluate a combination of compiler settings and source code patterns. A contract is only impacted if several precise conditions align during compilation and execution. First, the compilation must utilize the default evmasm code generation pipeline rather than the IR pipeline. Second, the source code must explicitly invoke the delete operator on an indexed element belonging to a bytes or string data structure residing in memory.
While the first two conditions can be readily verified by inspecting project configuration files and source code repositories, evaluating the third condition requires a deep understanding of the memory layout generated by the compiler. Because memory layout intricacies can be difficult to predict manually, developers who confirm the first two criteria are generally advised to treat their contracts as potentially affected rather than attempting to definitively rule out memory overlap.
Crucially, contracts that never apply the delete operator to elements of memory-based byte arrays or strings remain entirely unaffected. Neither the activation of the optimizer nor the target Ethereum Virtual Machine (EVM) version alters whether the bug manifests. The defect lies squarely within the instructions emitted during the code generation stage, and the optimizer merely preserves these faulty semantics.
Several technical nuances dictate whether a particular construct falls into the danger zone. Only individual element deletions are affected; executing a blanket delete b on an entire array routes through a completely separate code path and functions properly. Additionally, the array must reside in memory. Byte arrays stored in persistent storage utilize an entirely different handling mechanism and are immune, while delete cannot be applied to calldata arrays at all. Arrays whose individual elements naturally occupy a full 32-byte word in memory—such as bytes1[] memory—are likewise unaffected because only dynamic bytes and string types pack their underlying elements tightly back-to-back.
String values are also vulnerable by extension. Although direct index deletion like delete s[i] fails to compile for a string memory variable, developers frequently cast strings to bytes type to manipulate them. Executing delete bytes(s)[i] on such a cast triggers the exact same underlying bug. Conversely, fixed-size byte arrays like bytes32 are unaffected because indexing them returns an isolated copy of the byte rather than a mutable reference pointing into the underlying memory structure.
Technical Details
To understand the root cause of the bug, one must examine how Solidity manages memory allocation and data representation. Most values residing in memory occupy full 32-byte words, but bytes arrays serve as a notable exception. A memory byte array consists of an initial 32-byte length field immediately followed by its raw data bytes, packed closely together without internal gaps.
Solidity allocates memory sequentially by advancing the free memory pointer, ensuring that consecutive allocations sit directly flush against one another, separated only by necessary padding. Consequently, any write operation that extends past the intended boundary of a data structure immediately spills into whatever memory space was allocated next.
For standard arrays created via new bytes(n) or initialized from string and byte literals, the compiler appends up to 31 zero bytes of padding so that subsequent allocations always begin neatly on a fresh word boundary. These are known as padded arrays. However, the legacy evmasm pipeline omits this padding for the results of bytes.concat, string.concat, and various abi.encode functions, including abi.encodePacked, abi.encodeWithSelector, abi.encodeWithSignature, and abi.encodeCall. In these unpadded arrays, the data ends precisely where the next allocation begins. Relying on these padding behaviors is discouraged, as internal memory management remains an implementation detail that can vary across compiler pipelines and future releases.
When a value narrower than 32 bytes is stored in memory, its placement within the word depends strictly on its type. Left-aligned values, such as bytesN types, occupy the initial bytes of the word. Conversely, right-aligned values—including standard integers, addresses, memory pointers, and the length headers of dynamic arrays—sit flush against the end of the word, preceded by leading zero bytes.
Because byte arrays pack individual elements consecutively, writing to them diverges from standard memory operations where every variable occupies a full word. The compiler normally handles this by issuing a single-byte store instruction (mstore8) whenever it recognizes the element width. However, a critical discrepancy existed within the specific code path handling element deletion.

When executing delete x, the compiler aims to assign the default zero value associated with that variable’s type. Logically, deleting an array element via delete b[i] should compile down to the exact same machine instructions as an explicit assignment like b[i] = 0. While both operations ultimately conclude by storing a value at the element’s designated location, the legacy evmasm pipeline routed them through entirely separate store implementations.
While assignment correctly utilized the single-byte store instruction to clear only the targeted index, the delete path mistakenly triggered a full 32-byte word store. Because this write began directly at the index of the deleted element rather than aligning with the start of a 32-byte word boundary, it spilled over, clearing up to 31 additional bytes forward. The array’s length field, situated safely before the data, remained untouched, meaning the array retained its original length indicator. However, any subsequent computations relying on the array’s contents—such as hashing via keccak256, ABI encoding, or returning data to an external caller—would quietly process the corrupted memory. Meanwhile, the modern IR pipeline correctly utilized the unified store mechanism and remained immune to the flaw.
The extent of the memory corruption depends heavily on where the deleted element is positioned within the array and how that array was originally allocated. For padded arrays, the trailing padding absorbs a portion of the write. Deletions affecting elements far down the array can push past the boundary, but by no more than 31 bytes minus the existing padding length. Arrays whose lengths are exact multiples of 32 possess zero padding, meaning deletions targeting any of the final 31 elements will inevitably spill into whatever memory allocation follows them.
When the spilled write overwrites subsequent memory, the nature of the damage depends on the alignment of the adjacent data. For left-aligned types, the most significant bytes are wiped out. For right-aligned types like integers or memory pointers, leading zero bytes or upper bits are cleared, potentially transforming large numerical values or corrupting critical references. For unpadded arrays, because there is no safety buffer, deleting any of the final 31 elements will always breach the adjacent allocation. For arrays totaling 31 bytes or fewer, this vulnerability applies to every single index, including the very first element.
Example
To demonstrate how both internal and external memory corruption manifest in practice, security researchers constructed a minimalist test contract that relies on the compiler’s sequential memory allocation order:
contract C
struct S uint256 x;
// Corruption inside the array.
function clearElement() public pure returns (bytes memory)
bytes memory d = hex"0102030405";
delete d[0];
return d;
// Corruption beyond the array.
function clearLastElement() public pure returns (uint256)
bytes memory b = new bytes(32);
S memory s = S(type(uint256).max);
delete b[31];
return s.x;
When compiled using default settings via the evmasm pipeline, invoking these functions yields unexpected results. In the clearElement function, the compact five-byte array occupies a portion of a single padded word. Executing the delete operation clears that entire word starting from index zero, inadvertently wiping out the remaining four bytes of the array alongside the target. Instead of returning the expected array with only the first byte cleared, the function returns a string of all zeroes.
In the clearLastElement function, the 32-byte array occupies a complete word without padding. Deleting the final element at index 31 issues a 32-byte zero write that spills directly into the memory allocated for the struct s sitting immediately afterward. Because integer values are stored right-aligned with their most significant bytes placed first, the overwritten bytes correspond to the upper range of the maximum uint256 value. Consequently, instead of returning the maximum possible integer, the function returns 255 because only the lowest byte survived the spill. Replacing these delete instructions with direct assignments successfully circumvents the bug and produces the expected outputs across all tested compiler versions.
Severity Assessment and the Fix
Although the bug can be triggered without relying on inline assembly, the practical likelihood of encountering it in active deployments is low. The defect remained unnoticed throughout the language’s extensive history. A comprehensive scan of approximately 870,000 verified smart contract compilations within the Sourcify database identified the triggering source pattern in only two distinct deployed functions. Further analysis revealed that neither function was actually vulnerable in practice; in both cases, the overwritten bytes within the array were either immediately discarded or written to unallocated memory spaces.
The vulnerability carries no compiler warnings, and the generated bytecode does not trigger runtime reverts. Instead, it manifests quietly as corrupted data values, complicating debugging efforts. However, intra-array corruption—the more frequent manifestation—is readily detectable by standard unit tests that inspect the resulting byte arrays under the evmasm pipeline.
The Ethereum Foundation and Solidity core developers have addressed the underlying issue in version 0.8.37. The fix modifies the compiler’s internal delete path so that it queries element widths in the exact same manner as standard assignment operations. When clearing an element belonging to a packed byte array, the compiler now correctly emits a single-byte store instruction rather than a full-word store, ensuring that delete b[i] and b[i] = 0 behave identically across all compilation pipelines.
Developers maintaining projects reliant on Solidity versions up to 0.8.36 are strongly encouraged to update their toolchains to version 0.8.37 or later. Projects that cannot immediately upgrade should audit their codebases to identify instances where the delete operator is applied to indexed elements of memory-based bytes or string structures, replacing them with explicit zero assignments where appropriate.

