Write struct with solidity assembly ( understand assembly )

assemblysolidity

In the first example, I tried to change the second slot of the struct but failed

 mapping(uint256 => MyStruct) public myStruct;
    struct MyStruct {
        uint128 slot1;
        uint128 slot2; //fixed 32-bytes, 256bit
    }
    function writeTest(uint256 key) external {
        assembly {
            mstore(0x0, key)
            mstore(0x20, myStruct.slot)
            let hash := keccak256(0, 0x40)
            let s:= sload(hash)
            sstore(
                hash,
                or(
                    and(s, not(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)),
                    and(12, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
                )
            ) // clear first 128 bit and write new 128 bit (12)
            let slot2 := div(
                and(
                    s,
                    0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000000
                ),
                0x100000000000000000000000000000000
            ) 
            // or
            slot2 := shr(128, s)
            // how i can clean and rewrite bit in slot 2
        }
    }

In the second example, I tried to create a 32-byte struct in memory and then save it in storage, in this case only the first slot of the struct changes.

  function writeTest2(uint256 key) external {
        MyStruct memory test2 = MyStruct(10, 20); // 32 bytes
        assembly {
            mstore(0x0, key)
            mstore(0x20, myStruct.slot)
            let hash := keccak256(0, 0x40)
            sstore(hash, mload(test2))
            // check map and get me wrong value, just slot 1 changed
        }
    }

how I can write, read or shift a,b,c in mstore for one 32-byte pack

  function testPackingMemory(uint64 a,uint64 b,uint128 c) external {
        assembly {
            mstore(0x40,some_value) // how i can write or read a,b,c in mstore for one 32byte
        }
    }

Best Answer

First example

If you intend to update both fields, there is no need to do an sstore just after updating the first field, it's better to do all the required updates locally (in stack / memory) and issue only a single sstore as it will cost less gas.

Your code is unnecessarily complicated for such operations and it seems that you are using div for bit shifting... I'd advise against it (even if they are equivalent with powers of 2) because it makes the code less readable. If you are dealing with bits, use bit operators and functions. If you ever need a reminder for bit setting / clearing / etc... you can check this answer.

Here is a commented example for your first question :

function writeTest(uint256 key) external {
        assembly {
            mstore(0x0, key)
            mstore(0x20, myStruct.slot)
            let hash := keccak256(0, 0x40)
            let s:= sload(hash)

            // Clear the first 128 bits
            // Equivalent to and(s, 0x0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000000)
            s := and(s, not(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF))

            // Update the first 128 bits with value 12
            s := or(s, and(12, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF))

            // Clear the last 128 bits
            s:= and(s, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)

            // Update the last 128 bits with value 24
            s := or(s, shl(128, and(24, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)))

            sstore(hash, s)
        }
    }

Of course, if you are updating all the bits from the 32 bytes storage variable, you can also completely skip the read step since you will update everything anyway but at least with this example you can see the whole process.

Second example

Your idea is good, but there are some caveats, as you can see in the documentation. There is no in memory packing in solidity (except for bytes and string) so you need to pack the values yourself before writing it with sstore.

Here is a commented example for your second question :

function writeTest2(uint256 key) external {
        MyStruct memory test2 = MyStruct(10, 20); // 32 bytes

        assembly {
            mstore(0x0, key)
            mstore(0x20, myStruct.slot)
            let hash := keccak256(0, 0x40)

            // There is no packing with in memory variables except for bytes and strings
            // The first 32 bytes of test2 encode slot1
            // The second 32 bytes of test2 encode slot2

            // Read slot1 on 32 bytes
            let memory_slot1 := mload(test2)

            // Read slot2 on 32 bytes and shift it left by 128 bits
            let memory_slot2 := shl(128, mload(add(test2, 0x20)))

            // slot1 value is the first 128 bits of the 256 bits variable memory_slot1
            // slot2 value is the last 128 bits of the 256 bits variable memory_slot2
            // OR them to get a single 256 bit variable with the following layout:
            // bits [0: 128] slot1 value
            // bits [128: 256] slot2 value
            let combined := or(memory_slot1, memory_slot2)

            // Store a single 32 bytes value : slot1 and slot2 value packed on 32 bytes
            sstore(hash, combined)
        }
    }

Third example

The issue is similar to your second question. The values are once again encoded on 32 bytes as per the abi encoding. You also need to do the packing yourself...

Here is a commented example for your third question, returning a bytes32 : just for display.

 function testPackingMemory(uint64 a,uint64 b,uint128 c) external pure returns (bytes32 value) {
        assembly {
            // Let's assume that you want to get the following layout on 256 bits
            // bits [0: 64] a value
            // bits [64: 128] b value
            // bits [128: 256] c value
            value := or(value, a)
            value := or(value, shl(64, b))
            value := or(value, shl(128, c))
        }
    }

For any of the example, just make sure to modify the shift values accordingly to your underlying types if you were to change them.

I hope this answers your questions, and don't hesitate to ask for more precision if anything is left unclear.

Related Topic