0

I've been racking my brain with this for some time, and have been unable to increment and decrement specific fields inside nested structs as per the example below.

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

contract CalendarGasTest {
    struct ExampleNested_1 {
        uint88 e0;
        uint80 e1;
        uint80 e2;
    }

    struct ExampleNested_2 {
        uint80 e3;
        uint72 e4;
        uint72 e5;
    }

    struct Example {
        ExampleNested_1 nested1;
        ExampleNested_2 nested2;
    }

    Example public _example;


    constructor() {}

    function increment() external {
        assembly {
            let slot := _example.slot
            // how do i increment a specific field in my packed structs?
        }
    }

    function decrement() external {
        assembly {
            let slot := _example.slot
            // how do i decrement a specific field in my packed structs?
        }
    }
}

I am able to get the first field of each nested struct to increment/decrement, with something like this:

    function testFirstFieldFirstNestedStruct() external {
      assembly {
        let slot := _example.slot

        sstore(slot, add(sload(slot), 1))
      }
    }

    function testFirstFieldSecondNestedStruct() external {
      assembly {
        let slot := _example.slot

        sstore(add(slot, 0x1), add(sload(add(slot, 0x1)), 1))
      }
    }

which I'm pretty sure is not a good implementation as I was just messing around (not sure why 0x1, 0x2, 0x3...0xn correspond to the nth nested struct).

Regardless of what I do I can't get the behavior I want which is to be able to increment and decrement any field within the nested structs.

2 Answers 2

1

The code below shows an implementation for incrementing:

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

contract CalendarGasTest {
    struct ExampleNested_1 {
        uint88 e0;
        uint80 e1;
        uint80 e2;
    }

    struct ExampleNested_2 {
        uint80 e3;
        uint72 e4;
        uint72 e5;
    }

    struct Example {
        ExampleNested_1 nested1;
        ExampleNested_2 nested2;
    }

    Example public _example;

    constructor() {}

    function inc(uint256 o) external {
        if (o ==  1) { ++_example.nested1.e1; }
    }

    function incAssembly(uint256 o) external {
        assembly {
            switch o
                case 1 {
                    let e2Offset := 88
                    let e2Mask := 0x00000000000000000000ffffffffffffffffffff0000000000000000000000
                    let slot := _example.slot
                    let slotInMem := sload(slot)
                    let tempE2 := and(slotInMem, e2Mask)
                    let rShift := shr(e2Offset, tempE2)
                    let inc := add(rShift, 1)
                    let newE2 := shl(e2Offset, inc)
                    sstore(slot, or(newE2, and(slotInMem, not(e2Mask))))
                }
        }
    }
}

In general, its about shifting the desired value to the right-most bit, masking the other packed fields, doing the addition, shifting it back into its right location and then finally masking this new value with the persisted values for the rest of the nested struct.

Are there any assembly optimizations I could do to make this more performant?

1

Yes, you could definitely perform certain optimizations to make it more efficient.

  • Instead of loading the storage slot into memory slotInMem before performing bitwise operations you should directly perform bitwise operations on storage slot without loading it into memory.
  • This will significantly reduce gas cost incured due to unnecesary memory operations. -Instead of defining (e1Offset and e1Mask) for each field calculate the values inline based on the bit size of the field
e1Offset := 88 and e1Mask := 0xffffffffffffffffffff0000000000000000000000000000000000000000
  • Use the eq opcode to check if o is equal to 1 instead of using switch statement.
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

contract CalendarGasTest {
    struct ExampleNested_1 {
        uint88 e0;
        uint80 e1;
        uint80 e2;
    }

    struct ExampleNested_2 {
        uint80 e3;
        uint72 e4;
        uint72 e5;
    }

    struct Example {
        ExampleNested_1 nested1;
        ExampleNested_2 nested2;
    }

    Example public _example;

    constructor() {}

    function incrementE1() external {
        assembly {
            let slot := _example.slot

            // Calculate offset and mask for e1
            let e1Offset := 88
            let e1Mask := 0xffffffffffffffffffff0000000000000000000000000000000000000000

            // Load the value of the specific field
            let fieldValue := and(sload(slot), e1Mask)

            // Increment the value
            fieldValue := add(fieldValue, 1)

            // Update the storage slot with the new value
            sstore(slot, or(and(sload(slot), not(e1Mask)), fieldValue))
        }
    }

    function decrementE1() external {
        assembly {
            let slot := _example.slot

            // Calculate offset and mask for e1
            let e1Offset := 88
            let e1Mask := 0xffffffffffffffffffff0000000000000000000000000000000000000000

            // Load the value of the specific field
            let fieldValue := and(sload(slot), e1Mask)

            // Decrement the value
            fieldValue := sub(fieldValue, 1)

            // Update the storage slot with the new value
            sstore(slot, or(and(sload(slot), not(e1Mask)), fieldValue))
        }
    }
}

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.