WTF Solidity: 5. Data Storage and Scope
Recently, I have been relearning Solidity, consolidating the finer details, and also writing a "WTF Solidity Tutorial" for newbies to learn. Lectures are updated 1~3 times weekly.
Everyone is welcomed to follow my Twitter: @0xAA_Science
WTF Academy Discord: Link
All codebase and tutorial notes are open source and available on GitHub (At 1024 repo stars, course certification is unlocked. At 2048 repo stars, community NFT is unlocked.): github.com/AmazingAng/WTFSolidity
Reference types in Solidity
Reference Type: Reference types differ from value types in that they do not store values directly on their own. Instead, reference types store the address/pointer of the data’s location and do not directly share the data. You can modify the underlying data with different variable names. Reference types array
, struct
and mapping
, which take up a lot of storage space. We need to deal with the location of the data storage when using them.
Data location
There are three types of data storage locations in solidity: storage
, memory
and calldata
. Gas costs are different for different storage locations.
The data of a storage
variable is stored on-chain, similar to the hard disk of a computer, and consumes a lot of gas
; while the data of memory
and calldata
variables are temporarily stored in memory, consumes less gas
.
General usage:
storage
: The state variables arestorage
by default, which are stored on-chain.memory
: The parameters and temporary variables in the function generally usememory
label, which is stored in memory and not on-chain.calldata
: Similar tomemory
, stored in memory, not on-chain. The difference frommemory
is thatcalldata
variables cannot be modified, and is generally used for function parameters. Example:
function fCalldata(uint[] calldata _x) public pure returns(uint[] calldata){
//The parameter is the calldata array, which cannot be modified.
// _x[0] = 0 //This modification will report an error.
return(_x);
}
Example:
Data location and assignment behaviour
Data locations are not only relevant for persistency of data, but also for the semantics of assignments:
- When
storage
(a state variable of the contract) is assigned to the localstorage
(in a function), a reference will be created, and changing value of the new variable will affect the original one. Example:
uint[] x = [1,2,3]; // state variable: array x
function fStorage() public{
//Declare a storage variable xStorage, pointing to x. Modifying xStorage will also affect x
uint[] storage xStorage = x;
xStorage[0] = 100;
}
Example:
- Assigning
storage
tomemory
creates independent copies, and changes to one will not affect the other; and vice versa. Example:
uint[] x = [1,2,3]; // state variable: array x
function fMemory() public view{
//Declare a variable xMemory of Memory, copy x. Modifying xMemory will not affect x
uint[] memory xMemory = x;
xMemory[0] = 100;
}
Example:
Assigning
memory
tomemory
will create a reference, and changing the new variable will affect the original variable.Otherwise, assigning a variable to
storage
will create independent copies, and modifying one will not affect the other.
Variable scope
There are three types of variables in Solidity
according to their scope: state variables, local variables, and global variables.
1. State variables
State variables are variables whose data is stored on-chain and can be accessed by in-contract functions, but their gas
consumption is high.
State variables are declared inside the contract and outside the functions:
contract Variables {
uint public x = 1;
uint public y;
string public z;
We can change the value of the state variable in a function:
function foo() external{
// You can change the value of the state variable in the function
x = 5;
y = 2;
z = "0xAA";
}
2. Local variable
Local variables are variables that are only valid during function execution; they are invalid after function exit. The data of local variables are stored in memory, not on-chain, and their gas
consumption is low.
Local variables are declared inside a function:
function bar() external pure returns(uint){
uint xx = 1;
uint yy = 3;
uint zz = xx + yy;
return(zz);
}
3. Global variable
Global variables are variables that work in the global scope and are reserved keywords for solidity
. They can be used directly in functions without declaring them:
function global() external view returns(address, uint, bytes memory){
address sender = msg.sender;
uint blockNum = block.number;
bytes memory data = msg.data;
return(sender, blockNum, data);
}
In the above example, we use three global variables: msg.sender
, block.number
and msg.data
, which represent the sender of the message (current call), current block height, and complete calldata.
Below are some commonly used global variables:
blockhash(uint blockNumber)
: (bytes32
) The hash of the given block - only applies to the 256 most recent block.block.coinbase
: (address payable
) The address of the current block minerblock.gaslimit
: (uint
) The gaslimit of the current blockblock.number
: (uint
) Current block numberblock.timestamp
: (uint
) The timestamp of the current block, in seconds since the unix epochgasleft()
: (uint256
) Remaining gasmsg.data
: (bytes calldata
) Complete calldatamsg.sender
: (address payable
) Message sender (current caller)msg.sig
: (bytes4
) first four bytes of the calldata (i.e. function identifier)msg.value
: (bytes4
) number of wei sent with the message
Example:
Summary
In this chapter, we introduced reference types, data storage locations and variable scopes in solidity
. There are three types of data storage locations: storage
, memory
and calldata
. Gas costs are different for different storage locations. The variable scope include state variables, local variables and global variables.