Bytes & Strings are left aligned, while numbers (i.e., uintX) are right aligned.
- bytes32(bytes1(0xFF)) -> 0xFF0...00
- uint256(uint8(0xFF)) -> 0x00...0FF
Leading to such behavior :
function test() public view returns (bytes1) {
uint256 value = 0xFF00000000000000000000000000000000000000000000000000000000000000;
bytes1 rvalue;
assembly {
rvalue := value
}
return rvalue; // 0xFF
}
SLOAD returns a uint256, when trying to put this value in a bytes1 you are actually getting the 32th byte of the uint (Throughout this answer, byte 32 is far left, byte 0 is far right), not the 1st. With bytes12, you get the range 32 to 20 instead of 12 to 0.
This is exactly what happens with :
// clears bytes 32 to 1 and puts byte 32 into a
// because a is byte1 : left aligned
a := and(w, 0xff)
You could work directly on ints and only convert before returning outside the assembly block like so :
function retrieve(uint id) public view returns (bytes1, bytes12) {
turmite storage data = turmites[id];
uint96 b;
uint8 a;
assembly {
let w := sload(data.slot)
a := w
b := shr(8, w)
}
return (bytes1(a), bytes12(b));
}
Or stick with bytesX and shift your values left as this is where they will be picked up :
function retrieve(uint id) public view returns (bytes1, bytes12) {
turmite storage data = turmites[id];
bytes12 b;
bytes1 a;
assembly {
let w := sload(data.slot)
// Shift 152 bits (19 bytes) left to bring data.baserule at byte 32
b := shl(152, w)
// shift 248 bits (31 bytes) left to bring data.state at byte 32
a := shl(248, w)
}
return (a, b);
}