Solidity

Solidity Reference Types

Solidity Reference Types Main Tips

  • Complex types do not always fit into 256 bytes and have to be handled more carefully than the value types.
  • These types can be demanding, so you must decide carefully, whether you want them in memory, which is not permanent, or in storage, along with the state variables.

Solidity Reference Types Data Location

Each complex type, such as arrays and structs, has an extra annotation, the data location, which specifies whether it is stored in storage or in memory. According to the context, a default is always there, but by appending either storage or memory to the type it can be overridden. The function parameters default (includes return parameters) is memory, and storage is the default for local variables, and for state variables the location is forced to storage.

Additionally, there is also the data location called calldata, that is a non-persistent, non-modifiable area in which function arguments get stored. Function parameters (not return parameters) belonging to external functions are forced to calldata and behave mostly like memory.

Data locations have great importance as they change the behavior of assignments: assignments between memory and storage and also to a state variable (from other state variable as well) always create a copy that is independent. Assignments to local storage variables will only assign a reference though, in addition to that, this reference will always point to the state variable even when the latter has been changed in the meantime. However, assignments from a reference type stored in memory to another memory-stored reference type are not going to create a copy.

Example

pragma solidity ^0.4.0;

contract cont {
    uint[] x; // data of x is located inside storage

    // data of memoryArray is located inside memory
    function func1(uint[] memoryArray) {
        x = memoryArray; // works, copies the whole array into storage
        var y = x; // works, assigns a pointer, y's data location is storage
        y[7]; // fine, will return the 8th element
        y.length = 2; // fine, will modify x through y
        delete x; // fine, will clear the array and modify y
        // The following line of code does not work, since it would need to create a temporary
        // unnamed array inside of the storage, however, storage is allocated "statically":
        // y = memoryArray;
        // The following line of Soidity code will not work either, since it would "reset" the pointer, but there
        // but there is no viable location it could point to.
        // delete y;
        func2(x); // calls func2, thus handing over the reference to x
        func3(x); // calls func3, creates an independent, temporary duplicate in memory
    }

    function func2(uint[] storage storageArray) internal {}
    function func3(uint[] memoryArray) {}
}

 

Try on Remix Try live on Hosting

Overall, now we know that:

    • Forced data location includes:

      • parameters (not returned) of external functions are located in calldata
      • state variables are located in storage

Default data location includes:

    • parameters (also return) of functions are located in memory
    • all other local variables are located in storage

Solidity Reference Types Arrays

Arrays may have either a fixed size compile-time or be dynamic. In the case of storage arrays, this element type may be arbitrary (i.e. other mappings, arrays or structs as well). As for memory arrays, it cannot be a mapping and must be an ABI type in case it is an argument of a function that is publicly visible.

A fixed size array k with the element type T is written as T[k], a dynamic size array as T[ ]. For example, an array including 5 arrays with dynamic size of uint is written as uint[ ][5] (keep in mind that the notation is reversed in comparison to certain other languages). In order to access the second uint inside the third dynamic array, you need to use x[2][1] (indices are zero-based, so access works in the opposite direction of the declaration, for example, x[2] cuts off a single level in the type from the right).

Variables bytes type and string are special arrays. A bytes is just like byte[ ], but it is rightly packed in calldata. string is equal to bytes, however, it does not permit length or index access (for now).

So bytes should be preferred over byte[ ] all the time, as it is cheaper.

Arrays can be marked public and have a getter created by Solidity. The numeric index would then become a required parameter for the getter.

Note: If you would like to access the byte-representation of the string str, try bytes(str).length / bytes(str)[8] = ‘x’; . However, it should be noted, that you would be accessing the low-level bytes of the UTF-8 representation, instead of the individual characters.


Solidity Reference Types Allocating Memory Arrays

Variable length array creation in memory can be done with the new keyword. differently from storage arrays, you cannot change the size of memory arrays by assigning values to the member .length.

Example

pragma solidity ^0.4.0;

contract cont {
    function func(uint len) {
        uint[] memory x = new uint[](7);
        bytes memory y = new bytes(len);
        // Here we have x.length == 7 and y.length == len
        x[6] = 8;
    }
}

 

Try on Remix Try live on Hosting


Solidity Reference Types Array Literals / Inline Arrays

Arrays which are written like an expression and do not get assigned to a variable right away are referred to as array literals.

Example

pragma solidity ^0.4.0;

contract cont {
    function func1() {
        func2([uint(1), 2, 3]);
    }
    function func2(uint[3] _data) {
        // ...
    }
}

 

Try on Remix Try live on Hosting

The type of an array literal is a memory array that has a fixed size, the base type of which is the common type of the given elements. The type of [1, 2, 3] would be uint8[3] memory, since the type of every constant here is uint8. For that reason, converting the first element in the example above to uint was necessary. Keep in mind, that right now, you cannot assign fixed size memory arrays to dynamic size memory arrays, for example, the following is not going to work:

Example

// This does not compile.

pragma solidity ^0.4.0;

contract cont {
    function func() {
        // The following line will create a type error since
        // uint[3] memory is inconvertable to uint[] memory.
        uint[] x = [uint(1), 3, 4];
    }
}

 

Try on Remix Try live on Hosting

Note: This restriction is planned for removal in the future but right now it creates some complications because of how arrays get passed in the ABI.


Solidity Reference Types Members

length:
Arrays possess a length member, which holds the number of elements they have. Dynamic sized arrays may be resized in storage (but not in memory) by modifying the .length member. This would not happen automatically when trying to access elements external to the current length. The memory array size is fixed (but dynamic, as it can depend on the runtime parameters) once they are created.
push:
Dynamic storage arrays, as well as bytes (not string) have a member function named push,which may be used for appending an element at the array’s end. The function would return the new length.

Warning: Currently it is not possible to use arrays of arrays in external functions.

Warning: Because of limitations of the EVM, you cannot return dynamic content from external function calls. The function f inside contract cont { function func() returns (uint[ ] ) { … } } is going to return something in case it is called from web3.js, but not if called from Solidity.

The only viable workaround, right now, is using large static size arrays.

Example

pragma solidity ^0.4.0;

contract contractArray {
    uint[2**20] m_IntegersLots;
    // Keep in mind that the following is not a pair of dynamic sized arrays but a
    // dynamic array filled with pairs instead (for example, of fixed size arrays of length two).
    bool[2][] m_FlagPairs;
    // newPairs will be stored inside memory - the default used for the function arguments' data location

    function setAllFlagPairs(bool[2][] newPairs) {
        // assigning to a storage array will replace the whole array
        m_FlagPairs = newPairs;
    }

    function setPairOfFlags(uint index, bool flagA, bool flagB) {
        // access to a non-existing index will throw an exception
        m_FlagPairs[index][0] = flagA;
        m_FlagPairs[index][1] = flagB;
    }

    function modifyFlagArraySize(uint newSize) {
        // if the newly assigned size is smaller, arrays that we removed, will be cleared
        m_FlagPairs.length = newSize;
    }

    function clearArray() {
        // arrays get completely cleared
        delete m_FlagPairs;
        delete m_IntegersLots;
        // same effect using this line
        m_FlagPairs.length = 0;
    }

    bytes m_bytesData;

    function bytesArrays(bytes data) {
        // byte arrays ("bytes") are not the same since they do not get stored with padding,
        // but may be treated identically to "uint8[]"
        m_bytesData = data;
        m_bytesData.length += 7;
        m_bytesData[3] = 8;
        delete m_bytesData[2];
    }

    function addFlag(bool[2] flag) returns (uint) {
        return m_FlagPairs.push(flag);
    }

    function generateMemoryArray(uint size) returns (bytes) {
        // Dynamic memory arrays are generated using new:
        uint[2][] memory pairArray = new uint[2][](size);
        // generates a dynamic sized byte array:
        bytes memory b = new bytes(200);
        for (uint i = 0; i < b.length; i++)
            b[i] = byte(i);
        return b;
    }
}

 

Try on Remix Try live on Hosting


Solidity Reference Types Structs

In Solidity, there is a way of defining new types in the form of structs, that is shown in the following example:

Example

pragma solidity ^0.4.11;

contract crowdFund {
    // Defines a new struct type that has two fields.
    struct Backer {
        address addr;
        uint fundAmount;
    }

    struct crowdFundingCampaign {
        address beneficiaryAddress;
        uint fundGoal;
        uint numBacker;
        uint fundAmount;
        mapping (uint => Backer) backers;
    }

    uint numCrowdFundingCampaigns;
    mapping (uint => crowdFundingCampaign) crowdFundingCampaigns;

    function newCrowdFundingCampaign(address beneficiaryAddress, uint goal) returns (uint crowdFundingCampaignID) {
        crowdFundingCampaignID = numCrowdFundingCampaigns++; // crowdFundingCampaignID is a return variable
        // Create new struct and save in storage, leaving out the mapping type.
        crowdFundingCampaigns[crowdFundingCampaignID] = crowdFundingCampaign(beneficiaryAddress, goal, 0, 0);
    }

    function contribute(uint crowdFundingCampaignID) payable {
        crowdFundingCampaign storage c = crowdFundingCampaigns[crowdFundingCampaignID];
        // Create a new temporary memory struct, initialise with values that are given
        // and copy it over to storage.
        // Note that you can also use Backer(msg.sender, msg.value) for initializing.
        c.backers[c.numBacker++] = Backer({addr: msg.sender, fundAmount: msg.value});
        c.fundAmount += msg.value;
    }

    function checkGoalReach(uint crowdFundingCampaignID) returns (bool reached) {
        crowdFundingCampaign storage c = crowdFundingCampaigns[crowdFundingCampaignID];
        if (c.fundAmount < c.fundGoal)
            return false;
        uint fundAmount = c.fundAmount;
        c.fundAmount = 0;
        c.beneficiaryAddress.transfer(fundAmount);
        return true;
    }
}

 

Try on Remix Try live on Hosting 

This contract will not provide the complete functionality of a crowdfunding contract, however, it contains the basic concepts needed to understand structs. Struct types may be used inside mappings and arrays and they can contain arrays and mappings themselves.

It is impossible for a struct to contain a member of its own type, however, the struct itself may be the value type of a mapping member. This restriction is needed, since the size of the struct must be finite.

Notice how in each function, a struct type gets assigned to the local variable (of the default storage data location). This will not copy the struct, only storing a reference instead, so assignments to members of the local variable actually write to the state.

Of course, it is possible to directly access the struct’s members without assigning it to a local variable.

Read previous post:
Solidity Value Types

Solidity Value Types Main Tips Solidity is a programming language that is typed statically, meaning that every variable type must...

Close