MENU

Everything about Solidity Dynamic Array

0
648
0

Basic

Due to their unpredictable size, dynamically-sized array types cannot be stored “in between” the state variables preceding and following them. Instead, they are considered to occupy only 32 bytes and the elements they contain are stored starting at a different storage slot that is computed using a Keccak-256 hash.

Assume the storage location of the array ends up being a slot p after applying the storage layout rules. For dynamic arrays, this slot stores the number of elements in the array (byte arrays and strings are an exception).

Array data is located starting at keccak256(p) and it is laid out in the same way as statically-sized array data would: One element after the other, potentially sharing storage slots if the elements are not longer than 16 bytes.

Array Members

length: Arrays have a length member that contains their number of elements. The length of memory arrays is fixed (but dynamic, i.e. it can depend on runtime parameters) once they are created.

push(): Dynamic storage arrays and bytes (not string) have a member function called push() that you can use to append a zero-initialised element at the end of the array. It returns a reference to the element, so that it can be used like x.push().t = 2 or x.push() = b.

push(x): Dynamic storage arrays and bytes (not string) have a member function called push(x) that you can use to append a given element at the end of the array. The function returns nothing.

pop: Dynamic storage arrays and bytes (not string) have a member function called pop that you can use to remove an element from the end of the array. This also implicitly calls delete on the removed element.

More about length

Solidity v0.6.0 Breaking Changes

Member-access to length of arrays is now always read-only, even for storage arrays. It is no longer possible to resize storage arrays assigning a new value to their length. Use push(), push(value) or pop() instead, or assign a full array, which will of course overwrite existing content. The reason behind this is to prevent storage collisions by gigantic storage arrays.

Solidity v0.5.0

Arrays have a length member that contains their number of elements. The length of memory arrays is fixed (but dynamic, i.e. it can depend on runtime parameters) once they are created. For dynamically-sized arrays (only available for storage), this member can be assigned to resize the array. Accessing elements outside the current length does not automatically resize the array and instead causes a failing assertion. Increasing the length adds new zero-initialised elements to the array. Reducing the length performs an implicit delete on each of the removed elements.

If you use .length-- on an empty array, it causes an underflow and thus sets the length to 2**256-1.

Resizing Array

  • Until v0.5.17
uint[] a;

a.length = x; // resize through length directly
  • Since v0.6.0
uint[] a;

assembly {
  let length := sload(a.slot) // retrieve array length
  sstore(a.slot, x) // resize array
}

Gas Refund in deleting elements

Ethereum refunds gas when a storage slot is overwritten from non-zero value to zero, to the caller of that transaction.

NOTE: Gas refund will be removed since London fork.

So, since v0.6.0, resizing array by using assembly, there is no gas refund, because you didn’t clear the removal elements both implicitly or explicitly.

Here is the solution:

    assembly {
      let capacity_ := sload(a.slot)
      if gt(
        number(),
        0xC5D488 /* @dev FIXME London Fork Block Number */
      ) {
        sstore(a.slot, length_)
        /// @dev No gas refunds since London Fork, thus just returns
        return(0, 0)
      }
      mstore(0, a.slot)
      let offset_ := keccak256(0, 0x20) // never greater than 2**256 - 1
      /// @notice calculating storage slot of array element can overflow, since storage has 2**256 slots.
      /// i.e. let end := add(offset, capacity) // can be greater than 2**256 - 1 aka. overflow
      /// Thus, it cannot use slot number as the loop iterator.
      /// Due to the nature, seemgly inefficient loop here ?
      let index_ := capacity_
      for {

      } gt(index_, length_) {
        index_ := sub(index_, 1)
      } {
        /// @notice Provability of overflow/underflow, but this is designed as storage is continuum.
        let slot := add(offset_, sub(index_, 1))
        sstore(slot, 0)
      }
      sstore(a.slot, index_)
    }

You can find the full source code here:

https://github.com/maAPPsDEV/DynamicArray

Sorry, the comment form is closed at this time.