DEV Community

Ruud Christopher Saimplice
Ruud Christopher Saimplice

Posted on • Edited on

Advance Solidity Assembly: Bit Shifting and Storage Offsets

Bit Shifting Overview

Bit shifting is an important notion in Solidity that enables for efficient binary data manipulation. In this blog post, we will look into bit shifting in Solidity and how it may be used. It can be used to transform data kinds, extract individual bits, and set specific bits. Understanding bit shifting is critical for producing efficient and optimized code.

Bit shifting is the process of shifting the bits of a binary number to the left or right. This procedure can be used to manipulate binary data, such as converting between data types, extracting certain bits, or setting specific bits.

We can execute bit shifting in Solidity by using the and >> operators. The operator shifts a number's bits to the left, whereas the >> operator shifts the bits to the right.

Packed Variables

You will occasionally need to access or write variables while using assembly.

There is a specific instance in which numerous variables are packed.

Because the EVM has 256 bits (32bytes) slots, you can't directly access the packed variables; instead, you'll access the storage slot and find the offset of said variable.

Then, shift the bits right until you reach the offset where the variable was stored inside the slot.

If necessary, mask some bits so that it returns the expected value.


uint128 public A = 2;
uint104 public D = 6;
uint16  public E = 4;
uint8   public G = 1;

function readBySlot(uint slot) external view returns(bytes32 value){
    assembly{
        value := sload(slot)
    }
} 
// return 0x0100040000000000000000000000000600000000000000000000000000000002

Enter fullscreen mode Exit fullscreen mode

All of the variables are crammed into the same 32-byte slot.Variables will always be packed from the bottom to the top.

How can we get the value of the variable E ? To do so, we need to know the variable offset, which is the location of the variable in bytes from right to left.

function offsetE() external pure returns(uint256 _off){
    assembly{
        // return 29 bytes left
        _off := E.offset
    }
}
Enter fullscreen mode Exit fullscreen mode

Now that we have everything we require, we will begin the shiffting process.We need to move the E value 29 bytes to the right in order to return it. We'll need to use shr to determine the number of bits to shift right. Because we have bytes, we must multiply that amount by 8:

function shiftE() external view returns(bytes32 e){
    assembly{
        let slot  := sload(E.slot)
        e := shr(mul(E.offset, 8), slot)
    }
}
Enter fullscreen mode Exit fullscreen mode

return 0x0000000000000000000000000000000000000000000000000000000000010004

We're getting there; we now have E in the last byte, but there's something unusual going on. We still have the G variable 1.We shall now begin masking.

function shiftE() external view returns(bytes32 e,uint256 v){
    assembly{
        let slot  := sload(E.slot)
        e := shr(mul(E.offset, 8), slot)
        v := and(0xffff,e)
    }
}
Enter fullscreen mode Exit fullscreen mode

return
1:0x0000000000000000000000000000000000000000000000000000000000010004.
2:4

Write to storage without causing any problems with the code

Because the EVM can only write in 32-byte increments, writing can be challenging.We're going to write to a packed variable, say E, which is uint16.Let's put something in storage! Change the E value to 4.

function setE(uint16 newE)external  returns(bytes32 value,bytes32 clearedE, bytes32 newV, bytes32 shifted){
    assembly{

        value:= sload(E.slot)
        //VALUE -> 0x0100040000000000000000000000000600000000000000000000000000000002

        //WE WANT TO DELETE E
        clearedE := and(0xff0000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, value)
        //CLEARED E -> 0x0100000000000000000000000000000600000000000000000000000000000002

        shifted := shl(mul(E.offset,8),newE)
        //0x0000030000000000000000000000000000000000000000000000000000000000

        newV := or(shifted,clearedE)
        //NEW VALUE -> 0x0100030000000000000000000000000600000000000000000000000000000002
        sstore(E.slot,newV)
    }
}
Enter fullscreen mode Exit fullscreen mode

Follow me on Twitter and wait for the next serie !!
https://twitter.com/web3_ruud

Iā€™m on @buymeacoffee. If you like my work, you can buy me a and share your thoughts šŸŽ‰ā˜• https://www.buymeacoffee.com/officialsa4

Top comments (1)

Collapse
 
lemonr profile image
Lemonr

great