9

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 which I assume is pretty much in the memory but just at a different location.

I have also seen examples where parameters are instructed to store in storage instead.

For eg: function foo(uint[] storage _data, uint _value)

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? Also, since storage is supposed to be persistent, is the value of the parameters somehow going to be available on the next call?

0

2 Answers 2

13

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:

  1. 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.
  2. 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.
9

Cameel's answer is excellent, but to make it less theoretical and a little more concrete, here's a tldr:

  • use calldata when you only need read-only data, avoiding the cost of allocating memory or storage.
  • use memory if you want your parameter itself to be mutable.
  • use storage if your argument passed in will already exist in storage, to prevent copying something into storage over into memory unnecessarily.

So, to clarify, there are times where using storage is more efficient than using memory!

To explain with some code:

contract str {
  uint[] x = [10];
  function useStore(uint[] storage y) internal view returns (uint) {
    return y[0];
  }
  function useMem(uint[] memory y) internal view returns (uint) {
    return y[0];
  }

  function efficient(uint n) external view returns (uint) {
    return useStore(x) + n;
  }
  function inefficient(uint n) external view returns (uint) {
    return useMem(x) + n;
  }  
}

As a user of this contract, you can call either efficient() or inefficient(). They should be functionally identical, but the only difference is that internally, one calls "useStore" and one calls "useMem".

Both of them are being passed x, which is an array that already exists in storage. When we call useStore(x), no new memory is allocated for argument y--it just uses the existing pointer in storage! Meanwhile, useMem(x) has to copy x into memory.

efficient() saves about 400 gas in 0.8.10 over inefficient().

2
  • 1
    Why is it that sometimes you don't need to call any of the variables at all? Like in your efficient and inefficient functions, why doesn't n need to have calldata? Also, when defining an argument that is in the shape of a struct, snould it be calldata or storage? So like fn(MyStruct storage myStruct) {} or fn(MyStruct calldata myStruct) {}
    – wongx
    Commented May 12, 2022 at 18:11
  • 1
    Great question. As per docs, structs & arrays & mappings (so called "reference types") require this as an explicit declaration. In other cases, it is optional.
    – Kyle Baker
    Commented May 13, 2022 at 19:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.