0

Have been working on a function allow users to edit specific types of state using Solidity assembly, and ran into some unexpected behavior.

According to the documentation, for a storage array arr located at position p, the value at p (i.e. p + 0) is the length of the array (i.e. does not store data):

For dynamic arrays, this slot stores the number of elements in the array.

And yet, when I attempt to mutate the value stored at position 0 (p + 0) using sstore, in fact it is the first element of the array arr[0] which is affected. I would have expected to need to add an additional offset (i.e. p + 1) to reach location arr[0].

Did I misunderstand the documentation? Does Solidity assembly know enough about the underlying storage structure to prevent me from mutating the array length directly?


Here is the function used to determine the location to be mutated. The idea is that you begin with a base storage location of a mapping, and then process a sequence of mapping keys or offsets to get the final storage location for your variable.

  bool constant MAPPING = false;
  bool constant OFFSET = true;

  function changeState(
    bytes32 baseSlot,
    bool[] memory mask,
    bytes32[] memory keys,
    bytes32 value
  )
    public
  {
    require(mask.length == keys.length, "bad-num-keys");
    bytes32 slot = baseSlot;

    for (uint256 i; i < keys.length; i++) {
      if (mask[i] == MAPPING) {
        slot = keccak256(abi.encode(keys[i], slot));
      }
      if (mask[i] == OFFSET) {
        slot = bytes32(add(uint256(keys[i]), uint256(slot)));
        if (i != keys.length - 1) { // If not last offset
          slot = keccak256(abi.encode(slot));
        }
      }

    assembly {
      sstore(slot, value)
    }
  }

And here is the storage structure I am working with:

struct MyStruct {
 address addr;
 uint256 val1;
 uint256 val2;
 uint256[] otherVals;
}

mapping(uint256 => MyStruct) myStructs;

Using this code, I was expecting to pass the following to edit the first value of the otherVals storage array, located in the myStructs mapping with a key of 0:

const baseSlot = myStructs_slot;
const mask = [F, T, T];
const keys = [0, 3, 1];

But found that the following worked:

const baseSlot = myStructs_slot;
const mask = [F, T, T];
const keys = [0, 3, 0];
  • 2
    So how exactly do you "attempt to mutate the value stored at position 0"? – goodvibration Dec 31 '19 at 17:58
  • Using sstore(p, value) – kronosapiens Dec 31 '19 at 17:59
  • 2
    I meant, show your entire code. Need to know how p is defined. – goodvibration Dec 31 '19 at 17:59
2

Here's an example of how to set a storage array's length using sstore:

pragma solidity ^0.6.0;

contract Tata
{
    uint256[] public testArray;

    function getLength() external view returns (uint256)
    {
        return testArray.length;
    }
    function setLength(uint256 newLength) external
    {
        assembly { sstore(testArray_slot, newLength) }
    }
}

You can use the _slot suffix to get the storage address of a storage array.

I wonder what you're doing differently. Can you show your code?

1

Ah I think I figured it out!

My error -- if p is the "location" of the array, then keccak(p) is the location of the data, so keccak(p) + 0 would give the first data value. I incorrectly interpreted keccak(p) as the place where the array length is stored (i.e. [length, val0, val1, ...]), while in reality we have something more along the lines of length at position p and [val0, val1, ...] at a completely different keccak(p).

0

I Think the problem is that P+0 is represented by either:

1) ~p meaning the array does not exist and has not been created. as a result the value cannot be manipulated.

or

2) is p(0) which becomes p(0+0)=0 unfortunately that is only the case when you take machine code and evaluate it using the human language to interpret it.

index 0 is item 1 within the array. it's not treated as nothing.

so p (the array of an unknown number of indexes) is just p, but p(0) is actually p(1) or p of x = (p*x) p*1 = p

when p is at array 0 it is just p (an empty set of 1 that is all that makes up p (which is not an array, but a value.)

p(1) is actually P (in and of itself) plus (p of 1) or p and the item that is separate from it but part of it.

0

Length of dynamic array is stored separately from its elements, so, if p is address of array length, then keccak256 (p) + i is the address of i-th element of the array. So, your core should look like this:

if (mask[i] == OFFSET) {
  slot = bytes32(add(uint256(keys[i]), uint256(keccak256(abi.encode(slot)))));
}

See documentation for details.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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