Ethereum Solidity: Memory vs Storage & When to Use Them
Someone asked me on Github whether we should use storage
or memory
keyword in the following simplified code snippet:
For getUsingStorage
and getUsingMemory
, I tried both storage
and memory
and my unit tests were able to pass in both cases. So what’s the difference between storage
and memory
anyway and when should we use them?
According to Solidity documentation, these two keywords are used for Reference Types
where
Complex types, i.e. types which do not always fit into 256 bits have to be handled more carefully than the value-types we have already seen. Since copying them can be quite expensive, we have to think about whether we want them to be stored in memory (which is not persisting) or storage(where the state variables are held).
…
Every complex type, i.e. arrays and structs, has an additional annotation, the “data location”, about whether it is stored in memory or in storage.
It is now important to look at where EVM (Ethereum Virtual Machine) stores data:
The Ethereum Virtual Machine has three areas where it can store items.
The first is “storage”, where all the contract state variables reside. Every contract has its own storage and it is persistent between function calls and quite expensive to use.
The second is “memory”, this is used to hold temporary values. It is erased between (external) function calls and is cheaper to use.
The third one is the stack, which is used to hold small local variables. It is almost free to use, but can only hold a limited amount of values.
Most importantly,
If you e.g. pass such variables in function calls, their data is not copied if it can stay in memory or stay in storage.
This is where it gets confusing:
- The
storage
andmemory
keywords are used to reference data in storage and memory respectively. - Contract storage is pre-allocated during contract construction and cannot be created in function call. After all, it makes little sense to create new variable in storage in a function if it is to be persisted.
- Memory cannot be allocated during contract construction but rather created in function execution. Contract state variable is always declared in storage. Again, it makes little sense to have state variable that cannot persist.
- When assigning a
memory
referenced data to astorage
referenced variable, we are copying data from memory to storage. No new storage is created. - When assigning a
storage
reference data to amemory
referenced variable, we are copying data from storage to memory. New memory is allocated. - When a
storage
variable is created in function locally by look up, it simply reference data already allocated on Storage. No new storage is created.
To recap, refer back to the documentation:
Forced data location:
* parameters (not return) of external functions: calldata
* state variables: storage
Default data location:
* parameters (also return) of functions: memory
* all other local variables: storage
We can change data location
only for parameters of functions
and local variables
in function. Whenever a storage
reference is casted to memory
, a copy is made, and further modification on the object does not propagate back to contract state. memory
reference can only be “assigned” to storage
reference if memory data can be copied to a pre-allocated state variable.
Back to our illustrative contract above, for getters:
function getUsingStorage(uint _itemIdx)
public
// set to non-view to estimate gas
// view
returns (uint)
{
Item storage item = items[_itemIdx];
return item.units;
}function getUsingMemory(uint _itemIdx)
public
// set to non-view to estimate gas
// view
returns (uint)
{
Item memory item = items[_itemIdx];
return item.units;
}
Both functions return the same result, except in the getUsingMemory
a new variable is created and resulting in more gas used:
// getUsingStorage
"gasUsed": 21849// getUsingMemory
"gasUsed": 22149,
On the other hand, for setters:
function addItemUsingStorage(uint _itemIdx, uint _units)
public
{
Item storage item = items[_itemIdx];
item.units += _units;
}
function addItemUsingMemory(uint _itemIdx, uint _units)
public
// set to non-view to estimate gas
// view
{
Item memory item = items[_itemIdx];
item.units += _units;
}
Only addItemUsingStorage
modified the state variable (consuming more gas):
// addItemUsingStorage
// `units` changes in `items`
"gasUsed": 27053,// addItemUsingMemory
// `units` does not change in `items`
"gasUsed": 22287,
So to close, the takeaways are:
memory
andstorage
specifies whichdata location
the variable refers tostorage
cannot be newly created in a function. Anystorage
referenced variable in a function always refers a piece of data pre-allocated on the contract storage (state variable). Any mutation persists after function call.memory
can only be newly created in a function. It can either be newly instantiated complex types like array/struct (e.g. vianew int[...]
) or copied from astorage
referenced variable.- As references are passed internally through function parameters, remember they default to
memory
and if the variable was onstorage
it would create a copy and any modification would not persist.
References:
Join Coinmonks Telegram Channel and Youtube Channel get daily Crypto News
Also, Read
- Crypto Telegram Signals | Crypto Trading Bot
- Copy Trading | Crypto Tax Software
- Grid Trading | Crypto Hardware Wallet
- Best Crypto Exchange | Best Crypto Exchange in India
- Best Crypto APIs for Developers
- Best Crypto Lending Platform
- An ultimate guide to Leveraged Token
- Best VPNs for Crypto Trading
- Best Crypto Analytics or On-Chain Data | Bexplus Review
- 10 Biggest NFT MarketPlaces to Mint a Collection
- AscendEx Staking | Bot Ocean Review | Best Bitcoin Wallets
- Bitget Review | Gemini vs BlockFi | OKEx Futures Trading