calldata
vs memory
If I had understood correctly so far, by default, the parameters of a function are stored in the memory except for external
functions which are stored in calldata
This used to be true until Solidity 0.5.0, which is very old by now. In newer versions you have to explicitly specify where the parameter is stored.
calldata which I assume is pretty much in the memory but just at a different location.
You could say that calldata
is also in memory but it's not the memory belonging to contract. It does not have an address that you could use with the MLOAD
/MSTORE
opcodes to reach it. When the caller is another contract, calldata is mapped from the memory space of that contract and only it can reach it.
The most important feature of calldata
is that it's read-only. It contains the parameters in the ABI-encoded form. When you declare a parameter as memory
, the compiler inserts extra code to decode the values from calldata and copy them to a new location in memory, which adds some extra cost so it's only necessary if you want to modify the value in place.
What a storage
parameter actually is
Since the parameters of a function are just values that we use and don't really care about them once the function finishes, in what scenario would I want to store parameters in storage?
A storage
parameter does not really mean your data is copied into storage
. All parameters are actually stored on the stack. Since stack in the EVM only has 16 slots accessible at any given moment, storing large amounts of data there is not viable. It works fine for values types like ints or addresses because they always fit in a 32-byte slot but for reference types like arrays or structs, which require more space or can grow, Solidity only puts a reference on the stack and the actual content is elsewhere. It can be in storage, memory or calldata and you need to specify that when you declare the parameter.
So a storage
parameter is just a pointer/reference. Internally it's just a number that indicates the storage position at which the actual content can be found. Note that local variables declared as storage
work the same way - they do not allocate a new area of storage. They can only point at existing storage variables declared at the contract level.
Possible uses for storage
parameters
There are two main use cases for storage
parameters:
- Giving libraries access to storage variables. A library function can potentially could be called by many different contracts having different storage variables and, if it's an external call, these contracts are not known at compilation time. It cannot refer to their variables by their names. Solidity solves this problem by making it possible to pass a reference to a storage variable into a function via a parameter.
- Making your functions generic in regards to storage. Say, your contract stores several instances of a struct and you want to have a function that validates them. You obviously do not want to have to write one function per instance and you can avoid it by having the function accept the struct as a parameter. But if you declare the parameter as
memory
, each call will involve allocating a new instance in memory and copying its data from storage which is costly. A better solution is to declare the parameter as storage
so that your function can accept a reference to it, which is potentially much lighter.