# Storage

#### Storage Basics

* `.slot` - returns the slot location (in uint256) that the variable is located in. Determined at compile-time and doesn’t change
* `sstore` has no consideration for existing variables already declared or stored in the contract, so using it may override values that are already there

```solidity
contract YulStorage {
	uint x = 2;  // slot 0 
	uint y = 5;  // slot 1 
	uint z = 10; // slot 2 
 
	function getXYul() external view returns (uint256 ret) {
        assembly {
            ret := sload(x.slot)
        }
    }

	function getVarYul(uint256 slot) external view returns (bytes32 ret) {
        assembly {
            ret := sload(slot)
        }
    }

		// risky, shouldn't be used unless you know what you're doing 
    function setVarYul(uint256 slot, uint256 value) external {
        assembly {
            sstore(slot, value)
        }
    }
} 
```

#### Storage Offsets & Bitshifting

* Multiple variables can be stored within a slot

```solidity
contract StorageBits {
    uint128 public C = 4;
    uint96 public D = 6;
    uint16 public E = 8;
    uint8 public F = 1;

 
    function readBySlot(uint256 slot) external view returns (bytes32 value) {
        assembly {
            value := sload(slot)
        }
    }
}
```

* Calling readBySlot returns the below bytes32 data, where all variables C to F are stored in slot 0:

```solidity
0x00
01 // F  
0008 // E 
000000000000000000000006 // D 
00000000000000000000000000000004 // C 
```

* To derive the specific values with Yul, we have to use the `.offset` helper
* `.offset` is the number of bytes to the left in a bytes32 object that we have to look in order to find the location of the variable we want

```solidity
function getOffsetE() external pure returns (uint256 slot, uint256 offset) {
        assembly {
            slot := E.slot // returns 0 
            offset := E.offset // returns 28 -> shift 28 bytes to the left 
        }
    }
```

* To obtain the value of E:
* `shr(x, y)` - shift value y right by x number of bits

```solidity
function readE() external view returns (uint256 e) {
        assembly {
            let value := sload(E.slot) // must load in 32 byte increments
            // E.offset = 28
            let shifted := shr(mul(E.offset, 8), value)
            // 0x0000000000000000000000000000000000000000000000000000000000010008
            // equivalent to
            // 0x000000000000000000000000000000000000000000000000000000000000ffff
            e := and(0xffff, shifted)
        }
    }
```

* `and` is a bitwise operation that compares values bit by bit
  * 1 and 0 returns 0
  * 0 and F returns 0
  * 0 and 8 returns 8 → we get `E = 8`
* In order to write to specific values in a slot (while not overriding all the values in the slot), we need to use bit masking & bit shifting

```solidity
// masks can be hardcoded because variable storage slot and offsets are fixed
    // V and 00 = 00
    // V and FF = V
    // V or  00 = V
    // function arguments are always 32 bytes long under the hood
    function writeToE(uint16 newE) external {
        assembly {
            // newE = 0x000000000000000000000000000000000000000000000000000000000000000a - decimal 10 
            let c := sload(E.slot) // slot 0
            // c = 0x0001000800000000000000000000000600000000000000000000000000000004
            let clearedE := and(
                c,
                0xffff0000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff
            )
            // mask     = 0xffff0000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff
            // c        = 0x0001000800000000000000000000000600000000000000000000000000000004
            // clearedE = 0x0001000000000000000000000000000600000000000000000000000000000004
            let shiftedNewE := shl(mul(E.offset, 8), newE)
            // shiftedNewE = 0x0000000a00000000000000000000000000000000000000000000000000000000
            let newVal := or(shiftedNewE, clearedE)
            // shiftedNewE = 0x0000000a00000000000000000000000000000000000000000000000000000000
            // clearedE    = 0x0001000000000000000000000000000600000000000000000000000000000004
            // newVal      = 0x0001000a00000000000000000000000600000000000000000000000000000004
            sstore(C.slot, newVal)
        }
    }
```

* first load the slot that contains E
* selectively delete the bytes that represent E by using bitwise `and` to conduct masking
* shift the new E value by 28 bytes and conduct bitwise `or` to combine the E value and current slot values together
* call `sstore` to set the new value into the storage slot

#### Storage of Arrays & Mappings

* For fixed arrays, essentially the same as declaring 3 `uint256` variables that will take up slots 0, 1 and 2.

```solidity
contract StorageComplex {
    uint256[3] fixedArray;

	constructor() {
	        fixedArray = [99, 999, 9999];
	    }
	
		function fixedArrayView(uint256 index) external view returns (uint256 ret) {
        assembly {
            ret := sload(add(fixedArray.slot, index))
						// 0 - 99 
						// 1 - 999 
						// 2 - 9999 
        }
    }
} 
```

* For dynamic arrays, the data that’s being stored in the storage slot is the length of the array.
* Items in a dynamic array will not be stored sequentially down the slots, like in a fixed array, because the array could overrun and clash with other variables in the following slots

```solidity
contract StorageComplex {
	 uint256[] bigArray;

	constructor() {
	        bigArray = [10, 20, 30, 40];
	    }

	// returns 4 
	function bigArrayLength() external view returns (uint256 ret) {
        assembly {
            ret := sload(bigArray.slot)
        }
    }
} 
```

* Solidity stores values of dynamic arrays by taking the `keccak256` hash of the array’s length, then adding the index of the value to the hash

```solidity
function readBigArrayLocation(uint256 index)
        external
        view
        returns (uint256 ret)
    {
        uint256 slot;
        assembly {
            slot := bigArray.slot
        }
        bytes32 location = keccak256(abi.encode(slot));

        assembly {
            ret := sload(add(location, index))
        }
    }
```

* For mappings, Solidity takes the hash of the mapping’s storage slot, and concatenates it with the mapping key, then stores the value in the corresponding slot location

```solidity
function getMapping(uint256 key) external view returns (uint256 ret) {
        uint256 slot;
        assembly {
            slot := myMapping.slot
        }

        bytes32 location = keccak256(abi.encode(key, uint256(slot)));

        assembly {
            ret := sload(location)
        }
    }
```

* Nested mappings: hashes of hashes
* Keys have to be hashed in order of operation

```solidity
mapping(uint256 => mapping(uint256 => uint256)) public nestedMapping;

constructor {
	nestedMapping[2][4] = 7;
 ******}****** 

function getNestedMapping(uint256 key1, uint256 key2) external view returns (uint256 ret) {
        uint256 slot;
        assembly {
            slot := nestedMapping.slot
        }

        bytes32 location = keccak256(
            abi.encode(
                uint256(key2),
                keccak256(abi.encode(uint256(key1), uint256(slot)))
            )
        );
        assembly {
            ret := sload(location)
        }
    }
```
