solidity – How Does Storage Work with an Array of Length 2^256 – 1 in Solidity?

soliditystorage

If you have a dynamic array that stores bytes32 (called arr) and do arr.length--; (Assuming there is no underflow protection, i.e. pre solc 0.8.0) Then the length of arr would become 2^256 – 1, taking up the entire storage space, thus allowing you to manipulate different state variables indirectly via this array.

Where in the storage would you access the last member of the array?
My guess is the last item of the array would be keccak256(p) – 1 although i am uncertain.

If you change the storage slot that keeps the length of the array (position p) to 500 for example, what would happen to other slots previously designated to the array, e.g. lets say slot 2^256 -1 currently holds 0x10 (padded with 0's to reach 32 bytes) what happens to this slot once the length of arr is changed to 500?

For some context: https://listed.to/@r1oga/13892/ethernaut-levels-19-to-21#AlienCodex

Read the section on level 20 (alien codex).

Best Answer

Your link already answer your question. You would access each element at their "normal" index . If you override/reduce the length to length', the index > length' cannot be accessed anymore (at high level at least) - ie in your example, reducing to 500 means you cannot access array[2^256-1] without assembly, and it reverts.

Proof/test for yourself: The following contract starts with a dynamic array of length 5, you can get element via high or low-level call. Then you can override the array length an try retrieving value via high-level call (array[idx]) -> fail, or low-level (in assembly, get elt stored at index x) -> still present:

pragma solidity ^0.7.6;

contract TestArray {
    
    uint256[] abc;
    
    constructor() {
        abc.push(1);
        abc.push(2);
        abc.push(3);
        abc.push(4);
        abc.push(5); // length = 5
    }
    
    function getLength() external view returns (uint256 lgth) {
        assembly {
            let ptr := abc.slot
            lgth := sload(ptr)
        }
    }
    
    function newLength() external {
        assembly {
            let ptr := abc.slot
            sstore(abc.slot, 2)
        }
    }
    
    function getElt(uint256 idx) external view returns (uint256) {
        return abc[idx];
    }
    
    function getStorage(uint256 idx) external view returns (uint256 res) {
        assembly {
            let ptr := abc.slot
            res := sload(add(keccak256(abc.slot, 32), idx))
        }
    }
}

More details :

The array length insure the requested index is not out of bound. For instance, if you debug the (obviously) failed call to test(4) in this contract :

pragma solidity ^0.7.6;

contract Receiver {
    
    uint256[] abc;
    
    function test(uint256 idx) public view returns (uint256 res) {
        return abc[idx];
    }

}

Here is what happens in assembly and to the stack :

            stack:   0            1      2
113 DUP2    ->                    4      
114 SLOAD   ->        0(=length)  4
115 DUP2    ->        4       0(=length) 4 
116 LT   -> "is 4 lesser than 0" ?

Then revert (via a jumpi etc)

Related Topic