When looking at sample contracts, sometimes arrays are declared in methods with "memory" and sometimes they aren't. What's the difference?
Without the memory keyword, Solidity tries to declare variables in storage.
Lead Solidity dev chriseth: “You can think of storage as a large array that has a virtual structure… a structure you cannot change at runtime - it is determined by the state variables in your contract”.
That is, the structure of storage is set in stone at the time of contract creation based on your contract-level variable declarations and cannot be changed by future method calls. BUT -- the contents of that storage can be changed with sendTransaction calls. Such calls change “state” which is why contract-level variables are called “state variables”. So a variable uint8 storage var;
declared at the contract level can be changed to any valid value of uint8 (0-255) but that “slot” for a value of type uint8 will always be there.
If you declare variables in functions without the memory keyword, then solidity will try to use the storage structure, which currently compiles, but can produce unexpected results. memory tells solidity to create a chunk of space for the variable at method runtime, guaranteeing its size and structure for future use in that method.
memory cannot be used at the contract level. Only in methods.
See the the entry "What is the memory keyword? What does it do?" in the FAQ. I quote it here:
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.
For almost all types, you cannot specify where they should be stored, because they are copied everytime they are used.
The types where the so-called storage location is important are structs and arrays. 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 means that you can modify their content in the called function and these modifications will still be visible in the caller.
There are defaults for the storage location depending on which type of variable it concerns:
- state variables are always in storage
- function arguments are always in memory
- local variables of struct, array or mapping type reference storage by default
- local variables of value type (i.e. neither array, nor struct nor mapping) are stored in the stack
memory
keyword before a function param? If Memory is ephemeral then what's the reason for using it? And how can a contract still call those functions and therefore modify memory once it's already deployed? –
Jospeh calldata
? –
Trichosis Storage holds data between function calls. It is like a computer hard drive. State variables are storage data. These state variables reside in the smart contract data section on the blockchain. Writing variables into storage is very expensive because each node that runs the transaction has to do the same operation, it makes the transaction more expensive and causes the blockchain bigger.
Memory is a temporary place to store data, like RAM. Function args and local variables in functions are memory data. (if the function is external, args will be stored in the stack (calldata)) Ethereum virtual machine has limited space for memory so values stored here are erased between function calls.
The cost of global storage is 20,000 wei for writing the first time, 5,000 wei for updating the same storage location, and 200 wei for reading the storage. It is to be noted that these costs are per 32 bytes of storage. For example, reading 64 bytes will cost 2 * 200 wei, that is, 400 wei.
The cost of memory storage for both reading and writing 32 bytes of data is 2 wei. The cost of memory is way cheaper than global storage.
As you know accessing data inside a database is more expensive than accessing data inside the memory (session,cache).
Let's say we want to modify the top-level state variable inside a function.
this inside the function int[] public numbers
function Numbers()public{
numbers.push(5)
numbers.push(10)
int[] storage myArray=numbers
// numbers[0] will also be changed to 1
myArray[0]=1
//Imagine you have an NFT contract and store the user's purchased nfts in a state variable on top-level
// now inside a function maybe you need to delete one of the NFT's, since user sold it
// so you will be modifying that list, inside a function using "storage"
}
int[] storage myArray=numbers
in this case myArray will point to the same address as "numbers" (it is similar to how referencing objects behave in javascript). In the function I added 5, then 10 to "numbers" which is placed into Storage. But if you deploy the code on remix and get numbers[0]
, you will get 1 because of myArray[0]=1
If you define myArray
as memory it will be a different story.
// state variables are placed in Storage
int[] public numbers
function Numbers() public{
numbers.push(5)
numbers.push(10)
// we are telling Solidity make numbers local variable using "memory"
// That reduces gas cost of your contract
int[] memory myArray=numbers
myArray[0]=1
// Now, this time maybe you want to user's NFT's where price is less than 100 $
// so you create an array stored in "memory" INSIDE the function
// You loop through user's Nft's and push the ones that price<100
// then return the memory variable
// so, after you return the memory variable, it will be deleted from the memory
}
In this case, "numbers" array is copied into Memory, and myArray now references a memory address which is different from the "numbers" address. If you deploy this code and reach numbers[0]
you will get 5.
- by copying the storage variables onto the memory, we are preventing our state variables from unwanted change. Everytime client calls the public function would modify the storage variables and imagine if thousands of clients call the same function how were you keep track of state variables
I showed the difference on a simple function so it can be easily tested on Remix
int[] storage myArray
is only a pointer to the numbers variable and no space in storage is reserved for myArray. What's the gas cost for myArray being assigned to numbers ? –
Increscent memory
keyword means 2 things: (1) copy by value. (2) declare a variable as a pointer to the new allocated-copied value. storage
means: (1) do not copy by value; copy the reference. (2) declare a variable as a pointer to the new allocated-not-copied value. –
Mutation memory
defines one of the data locations in Solidity that can hold the value temporarily during runtime. memory
variables in Solidity can only be declared within methods and are usually used in method parameters. It's a short term variable that cannot be saved on the blockchain; it holds the value only during the execution of a function and its value is destroyed after execution.
Take a look at example function f()
in which I declared a pointer using the memory
keyword. It will not alter the value of variable User
, whereas if it was declared using storage
it will change the value of variable User
stored on the blockchain and the value will not be destroyed...
struct User {
string name;
}
User[] users;
function f() external {
User memory user = users[0]; // create a pointer
user.name = "example name" // can't change the value of struct User
}
When people talk about Storage and Memory in Solidity, they can actually be referring to two different uses of these words. And this causes a lot of confusion.
The two uses are:
- Where a Solidity contract store data
- How Solidity variables store values
Examples of each:
1. Where a Solidity contract store data: As Yilmaz correctly points out, in the first usage storage and memory can be thought of as similar to a hard drive (long-term, persistent storage) and RAM (temporary) respectively.
For example:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
contract StorageMemory1{
uint storageVariable;
constructor() {
}
function assignToValue(uint memoryVariable) public {
storageVariable = memoryVariable;
}
}
In the example above the value of 'storageVariable' will be saved even as we execute different functions over time. However, 'memoryVariable' is created when the 'assignToValue' function is called and then disappears forever after the function is complete.
2. How Solidity variables store values: If you see an error that says something like 'Data location must be "storage", "memory" or "calldata" for variable, but none was given.' then this is what it is referring to. This is best understood using an example.
For example:
You would get the above error with the following code:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
contract StorageMemory2 {
uint[] public values;
function doSomething() public
{
values.push(5);
values.push(10);
uint[] newArray = values; // The error will show here
}
}
But if you add the word 'memory':
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import 'hardhat/console.sol'; // to use console.log
contract StorageMemory2 {
uint[] public values;
function doSomething() public
{
values.push(5);
values.push(10);
console.log(values[0]); // it will log: 5
uint[] storage newArray = values; // 'newArray' references/points to 'values'
newArray[0] = 8888;
console.log(values[0]); // it will log: 8888
console.log(newArray[0]); // it will also log: 8888
}
}
Notice what adding the word 'storage' does: it makes the 'newArray' variable reference (or point to) the 'values' variable, and modifying 'newArray' also modifies 'values'.
However, if we instead use 'memory', notice what gets logged:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import 'hardhat/console.sol'; // to use console.log
contract StorageMemory2 {
uint[] public values;
function doSomething() public
{
values.push(5);
values.push(10);
console.log(values[0]); // it will log: 5
uint[] memory newArray = values; // 'newArray' is a separate copy of 'values'
newArray[0] = 8888;
console.log(values[0]); // it will log: 5
console.log(newArray[0]); // it will log: 8888
}
}
Using memory creates a copy variable, which does not reference the 'values' array.
And in case you are interested, 'calldata' can be used to pass a variable as read-only:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
contract CallDataExample {
uint[] public values;
function doSomething() public
{
values.push(5);
values.push(10);
modifyArray(values);
}
function modifyArray(uint[] calldata arrayToModify) pure private {
arrayToModify[0] = 8888; // you will get an error saying the array is read only
}
}
© 2022 - 2024 — McMap. All rights reserved.