Summary
The legacy code generator encodes every generated public mapping getter key with uint256 memory-store semantics before hashing the storage slot. On a 64-byte Hyperion target, this changes the canonical key encoding for wider or differently aligned value types.
The affected call is in ExpressionCompiler::appendStateVariableAccessor(). It calls CompilerUtils::storeInMemory(0), whose implementation always uses TypeProvider::uint256().
This source shape is present at upstream main commit f570687a and the current QRL 64-byte feature head 6f862206.
Minimal reproduction
contract C {
mapping(bytes32 => uint8) public byBytes32;
mapping(address => uint8) public byAddress;
mapping(uint512 => uint8) public byUint512;
mapping(int256 => uint8) public byInt256;
function setBytes32(bytes32 key, uint8 value) public { byBytes32[key] = value; }
function setAddress(address key, uint8 value) public { byAddress[key] = value; }
function setUint512(uint512 key, uint8 value) public { byUint512[key] = value; }
function setInt256(int256 key, uint8 value) public { byInt256[key] = value; }
}
Representative keys that expose the mismatch:
address: a full 64-byte value with nonzero upper and lower 32-byte halves
bytes32: a nonzero 32-byte value, whose ABI alignment differs from uint256
uint512: any value above 2^256 - 1
int256: -1, which requires signed canonicalization
With legacy codegen, the generated getters return zero for entries written under these exact keys. Observed coercions include a left-aligned bytes32 becoming zero, a 64-byte address losing its upper 32 bytes, uint512(2^256 + 42) becoming 42, and int256(-1) losing canonical 512-bit sign extension. The via-IR accessor path does not use this legacy helper.
Expected behavior
A generated getter must hash the exact canonical encoding for its declared mapping key type. It must retrieve the same slot used by an explicit mapping read for every supported key type.
Root cause
CompilerUtils::storeInMemory(unsigned) calls:
prepareMemoryStore(*TypeProvider::uint256(), true)
The generated accessor therefore applies uint256 cleanup and alignment regardless of the declared key type.
Suggested fix
Add a typed memory-store overload and pass *paramTypes[i] from appendStateVariableAccessor(). Keep the existing untyped helper for callers that intentionally store uint256.
A semantic regression should cover bytes32, a full-width address, uint512 > 2^256, and negative int256, with both legacy and via-IR compilation plus default and optimized execution. A local typed-store fix produced the expected distinct results in all four modes: the exact keys returned 7, 5, 9, and 10, while zero, low-only 0xab, and 42 comparison keys returned zero.
Impact
This is a correctness issue for generated public getters on 64-byte targets. Contract state can be written correctly while the generated getter hashes a different key encoding and returns the wrong slot or zero.
Summary
The legacy code generator encodes every generated public mapping getter key with
uint256memory-store semantics before hashing the storage slot. On a 64-byte Hyperion target, this changes the canonical key encoding for wider or differently aligned value types.The affected call is in
ExpressionCompiler::appendStateVariableAccessor(). It callsCompilerUtils::storeInMemory(0), whose implementation always usesTypeProvider::uint256().This source shape is present at upstream
maincommitf570687aand the current QRL 64-byte feature head6f862206.Minimal reproduction
Representative keys that expose the mismatch:
address: a full 64-byte value with nonzero upper and lower 32-byte halvesbytes32: a nonzero 32-byte value, whose ABI alignment differs fromuint256uint512: any value above2^256 - 1int256:-1, which requires signed canonicalizationWith legacy codegen, the generated getters return zero for entries written under these exact keys. Observed coercions include a left-aligned
bytes32becoming zero, a 64-byte address losing its upper 32 bytes,uint512(2^256 + 42)becoming42, andint256(-1)losing canonical 512-bit sign extension. The via-IR accessor path does not use this legacy helper.Expected behavior
A generated getter must hash the exact canonical encoding for its declared mapping key type. It must retrieve the same slot used by an explicit mapping read for every supported key type.
Root cause
CompilerUtils::storeInMemory(unsigned)calls:The generated accessor therefore applies
uint256cleanup and alignment regardless of the declared key type.Suggested fix
Add a typed memory-store overload and pass
*paramTypes[i]fromappendStateVariableAccessor(). Keep the existing untyped helper for callers that intentionally storeuint256.A semantic regression should cover
bytes32, a full-widthaddress,uint512 > 2^256, and negativeint256, with both legacy and via-IR compilation plus default and optimized execution. A local typed-store fix produced the expected distinct results in all four modes: the exact keys returned7,5,9, and10, while zero, low-only0xab, and42comparison keys returned zero.Impact
This is a correctness issue for generated public getters on 64-byte targets. Contract state can be written correctly while the generated getter hashes a different key encoding and returns the wrong slot or zero.