0

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
        }
    }
2
  • @PaulRazvanBerg this is absolutely different Commented Jun 19, 2022 at 10:05
  • 1
    Sorry, I have retracted my vote to close this question. Commented Jun 19, 2022 at 10:25

1 Answer 1

3

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.

5
  • thanks, you saved my time :x Good luck getting your PhD Commented Jun 19, 2022 at 14:44
  • I tried to understand the bits with your examples, for example in a 32-bytes struct with 3 slots ` { uint128, uint64, uint64 } ` To delete only the last slot from this struct I use ` slot := and(slot,0xFFFFFFFFFFFFFFFF) sstore(hash,slot) ` But here the last 2 slots are deleted If possible in the first example use a struct of more than 2 slots, thanks Commented Jun 19, 2022 at 16:57
  • that;s only 3 slots ? let me know if that's just a mistake on your side. Can you detail more what you call "last" ? you seem to be using it for both lower and higher bytes... slot := and(slot,0xFFFFFFFFFFFFFFFF) clears everything except the last 16 bytes, maybe you meant slot := and(slot, not(0xFFFFFFFFFFFFFFFF)) to clear only the last 16 bytes ?
    – hroussille
    Commented Jun 19, 2022 at 17:06
  • Sorry I did not understand, for a struct with 3 slots like struct MyStruct {uint128 slot1;uint64 slot2; uint64 slot3;} used this method to clear only the second slot named slot2 ( 64 bit ) slot := and(slot,0xFFFFFFFFFFFFFFFF0000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) Is this code standard, Or is there another simpler or shorter equivalent that only I can delete slot2? thanks Commented Jun 20, 2022 at 4:23
  • This would be equivalent and shorter : s := and(s, not(shl(128, 0xFFFFFFFFFFFFFFFF))) It's basically generating the mask programmatically.
    – hroussille
    Commented Jun 20, 2022 at 7:03

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.