Data Location in Solidity Explained

You might find it difficult to understand where and how the EVM stores variables. This article will uncover some of the hidden facts about data location in Solidity. 

Data location in Solidity - storage, memory, calldata

There are three “places” in which the EVM stores variables: storage, memory and calldata.

You might have seen them in the declaration of some variables. But you probably noticed that they are usually missing. This doesn’t mean some variables don’t have a location. We know that every declared variable is stored somewhere.

Also, the data location rules differ according to the variable’s type and scope:

  • Type: 
    • Reference type: array (including strings or bytes) mapping, structures
    • Value type: all the others, for example, uint or boolean
  • Scope:
    • Local variable: declared inside a function
    • Global variable: aka state variables, declared outside of all functions

We’ll go through the following concepts of data location in Solidity:

  1. Definitions and Usage
  2. Helpful Facts
  3. Assignment Rules

The information in this post is up-to-date with Solidity version 0.8.7. 


1. Definition and Usage

Each data location in Solidity has a different purpose and we should use them cautiously. Keep in mind that, in some situations, Solidity strictly enforces a default location.

Storage

When placed in storage, a variable is written on the blockchain. Everything that is on the chain, stays there. Every contract has its own storage, so these variables are persistent.

Therefore, you can access storage variables at all times. You can modify their value but their location is permanent. Every change is registered on the blockchain.

Memory

Variables stored in memory are declared inside a function. They are temporary and their ‘lifetime’ is dependent on the runtime of the function they correspond to. They are only accessible inside that method. Their purpose is to assist calculations. 

Moreover, the EVM discards their location after the function execution. You cannot access these variables from anywhere else other than from inside the method.

Calldata

Calldata is also a temporary data location in Solidity. It acts like memory, in terms of its dependence on the function’s execution. The variables stored in calldata are only available inside the function they were declared in.

On top of that, calldata variables are not modifiable. This means that you cannot change their value. 


Facts about data location in Solidity

2. Helpful facts

There are some useful facts about data location in Solidity that might help you along the way. It is best to know them in order to avoid silly mistakes.

Gas

  • Storage is the most expensive data location you can use. Then there’s memory, with the cheapest being calldata.
  • Modifying storage is one of the most expensive operations you can do on Ethereum. To be memory efficient, try to minimize the number of times you alter global (state) variables.

Must do

  • Global (state) variables always stay in storage and you cannot explicitly override that.
  • Function parameters (including the return ones) must be in memory or calldata. 

Prior to version 0.6.9 data location for reference type arguments was limited to calldata in external functions, memory in public functions and either memory or storage in internal and private ones. Now memory and calldata are allowed in all functions regardless of their visibility.

Solidity docs
  • You cannot override the location of local variables of value type. You’d get a Parser Error. By default, they sit in memory.
  • Since v0.5.1, you must specify the location of local variables of reference type. If you don’t you’ll get an error: 
TypeError: Data location must be "storage", "memory" or "calldata" for variable, but none was given.

3. Assignment rules

When assigning a storage variable the value of a memory (or calldata) one, it will create a copy. The other way around is true as well.

pragma solidity 0.8.7;
 
contract Contract {
  uint public age;
 
  constructor(){
     age = 30;
    }
 
  function changeAge() public returns(uint) {
     uint age2 = age; // assign storage -> memory creates copy
     age = 20;
     return age2; // returns 30
  }
 
}

Assignments between value types from the same data location (e.g. storage to storage) will make a copy.

For reference types, when assigning from one memory variable to another memory variable, it will create a reference. They will point to the same data location. So if you modify one of them, it will reflect in both.

pragma solidity 0.8.7;
 
contract Contract {
 
  function vote() public returns(uint[] memory) {
    uint[] memory ages = new uint[](2);
    ages[0] = 2;
    ages[1] = 3;
    
    uint[] memory newages = ages; // assign reference memory variable to a reference memory variable
    ages[0] = 7;
    return newages; // returns (7,3)
  }
}

Assigning from storage to local storage variables will also result in a reference.

pragma solidity 0.8.7;
 
contract Contract {
  uint[] ages;
    
  function vote() public returns(uint[] memory) {
    ages = new uint[](2); // storage
    ages[0] = 2;
    ages[1] = 3;

    uint[] storage newages = ages; // from storage to local storage
    ages[0] = 7;
    return newages; //returns (7,3)
  }
 
}

If you want to read more about Solidity development, check out my blog.

I hope this was helpful! Leave any suggestions in the comments 😁

Leave a Reply