Arrays in Solidity

Written by aventus | Published 2018/07/20
Tech Story Tags: programming | solidity | ethereum | ticketing | arrays-in-solidity

TLDRvia the TL;DR App

This is the second Aventus technical blog on Solidity from Alex Pinto, a recent addition to our blockchain engineering team. You can read his first post on Working with Strings in Solidity.

There are many occasions when we want to pass to a function a group of similar data that may, or may not, be limited in number. The most basic data type for this situation is an array (and in several cases, this can be used to implement more advanced data structures). We can pass and return arrays without problems, as the following illustrates.

The above uses arrays of uint, which represents a 256-bit integer, of unlimited size. That means I can pass any array of the correct type into the function. It also means I have to initialise the return array in getArrayMultipliedByScalar before I can use it, since at the time outArray_ is declared it does not allocate any memory for its elements (it could have any size).

For comparison; if I used fixed-size arrays, as below, two things happen:

  • I no longer need to initialise the outgoing array.
  • The compiler returns an error if the function receives an array with any other size but 3.

We can make arrays of other types, like bool and address - but what about multi-dimensional arrays?

We can pass bi-dimensional arrays of fixed size:

Sadly, things are more difficult with dynamic arrays.

Some languages, like BASIC and Pascal, index bi-dimensional arrays by a tuple of indices. In these languages, arrays are genuine (rectangular) matrices. But in C-derived languages, multi-dimensional arrays are arrays-of-arrays, instead of matrices. That is the case with Solidity as well, and it pays to take some time to understand what this type declaration means: uint[2][4] should be read as (uint[2])[4], that is, 4 arrays each of size 2.

This is important when we consider dynamic arrays. We could have both of these kinds:

The first example above is a fixed-size array which has 3 elements, each of which is a dynamic array. In the second case, we have a dynamic array outright, but its elements are arrays of fixed size.

I discuss below how to initialise fixedSizeArray, which is the most interesting case of the two. Regarding dynamicArray, because it is a dynamic array, we first must allocate memory for it using new and then we can access the fixed-size elements. The example below works:

Initialisation of multi-dimensional dynamic arrays

Let’s explore an example similar to the above in more detail:

TypeError: Type uint256[3] memory is not implicitly convertible to expected type uint256[3] storage pointer.

The arrays fixedSizeArray and dynamicArray are declared as state variables of the contract, and so are by necessity storage references. Storage arrays can not be initialised from newexpressions, as these are of type memory. Nevertheless, we can initialise each of the arrays inside fixedSizeArray using memory-array expressions, as shown above.

For comparison, I included also two cases where I try to assign a memory array to an explicit storage one. In the constructor, this works, but not in the second function. Why?

This is because the types of storageArray and of localStorageArray are not exactly the same. The former is a state variable of the contract, and when it is referred inside the constructor, its type is uint256[3] _storage ref_ (to see this, change the assignment’s right value to something illegal, such as 7, and the error message will show you the types involved). In comparison, the type of localStorageArray is uint256[3] _storage pointer_. Subtle difference. In the first case, we have a reference to a location in storage, and the assignment copies the memory array to that storage. In the second case, we try to assign to a local variable which according to the documentation just creates a new reference to a previous pointer:

Assignments to local storage variables only assign a reference though, and this reference always points to the state variable even if the latter is changed in the meantime.

Excerpt from Solidity documentation

In the above example, y is a pointer to the same location known as x, and modifying one causes changes in the other. But in our case, we are trying to assign a memory array to a storage variable which, being of a different type, cannot produce a pointer to that memory location.

On the other hand, when we initialise fixedSizeArray, we are actually referring to a storage reference. In this case we can assign from a memory array, which has the effect of completely copying the source over the target, erasing all of its previous contents.

Can we pass multi-dimensional arrays to functions?

It depends!

We can use Solidity’s polymorphism to write four functions with the same name, and different signatures, exploring all combinations of dynamic and fixed-size bi-dimensional arrays.

Two of these functions are illegal, only because their particular array type cannot be passed to a function. Illegal is a bit of a strong word: the error says the type can be used, but only with the new experimental ABI encoder; and that in order to use it, it is necessary to include pragma experimental ABIEncoderV2;. However, we then would get a warning saying that it should not be used in production code.

This restriction will likely be waived in the future, as new versions of Solidity come along, but for now, I just won’t use these features and will look for workarounds.

The common feature between these two types is that the inner type of the array — that is the type of its elements — is dynamic, of unknown size. These types cannot be passed into nor returned from a function.

I will finalise this post with another example:

The last two functions are illegal. The reason why is very consistent with everything that has been said before: string and bytes are dynamic types. Specifically, they are arrays: respectively, of UTF-8 characters, and of bytes. For that reason, the above return types are not really simple uni-dimensional arrays like those of getInts and getAddresses, but are instead bi-dimensional arrays with a dynamic inner type. And because of that, they cannot be passed into nor returned from functions at the current stage of Solidity.

About the Author

Alex is a software engineer at Aventus, working on the blockchain engineering team. He has 20 years of experience working in technology, completing a PhD in Computer Science as well as a post-doctorate in Cryptography. As part of his research, Alex has published papers on Kolmogorov Complexity, Cryptography, Database Anonymization and Code Obfuscation.

Alex also spent seven years lecturing at the University Institute of Maia, including directing the degree programmes for BSc Computer Science and Information Systems and Software.


Published by HackerNoon on 2018/07/20