Readonly storage references

Hey, Iโ€™m thinking about a pattern that could be made safer by having a readonly keyword.

When you have a Struct in storage, and you need to read from a couple of attributes of the Struct but not all, you have a couple of alternatives to write your function.

See example below:

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.7;

contract Packer {
    struct Box {
        uint256 x;
        uint256 y;
        uint256 z;
    }

    mapping(uint256 => Box) public boxes;
    bool flagged;

    constructor() {
        boxes[0] = Box(1, 0, 0);
        boxes[1] = Box(1, 1, 0);
        boxes[2] = Box(0, 0, 1);
    }

    function somethingRef(uint256 n) public {
        Box storage box = boxes[n];
        if (box.x == 1 && box.y == 1) {
            flagged = true;
        }
    }

    function somethingVerbose(uint256 n) public {
        if (boxes[n].x == 1 && boxes[n].y == 1) {
            flagged = true;
        }
    }

    function somethingStructMemory(uint256 n) public {
        Box memory box = boxes[n];
        if (box.x == 1 && box.y == 1) {
            flagged = true;
        }
    }

    function somethingLocalMemory(uint256 n) public {
        uint256 x = boxes[n].x;
        uint256 y = boxes[n].y;
        if (x == 1 && y == 1) {
            flagged = true;
        }
    }
}

I like option somethingRef() because itโ€™s shorter, consumes less gas and you can clearly see you are operating on the Struct attributes. The tradeoff I see is that it could be dangerous to inadvertently do box.x = <something> and assign a value by mistake.

If we had a way to declare it like Box readonly storage box = boxes[n]; and the compiler to warn about any unwanted assignment we could make it safer to use.

Gas usage:

ยท--------------------------------------|---------------------------|-------------|-----------------------------ยท
|         Solc version: 0.8.7          ยท  Optimizer enabled: true  ยท  Runs: 200  ยท  Block limit: 12000000 gas  โ”‚
ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท|ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท|ยทยทยทยทยทยทยทยทยทยทยทยทยท|ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท
|  Methods                                                                                                     โ”‚
ยทยทยทยทยทยทยทยทยทยทยทยทยท|ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท|ยทยทยทยทยทยทยทยทยทยทยทยทยท|ยทยทยทยทยทยทยทยทยทยทยทยทยท|ยทยทยทยทยทยทยทยทยทยทยทยทยท|ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท|ยทยทยทยทยทยทยทยทยทยทยทยทยทยท
|  Contract  ยท  Method                 ยท  Min        ยท  Max        ยท  Avg        ยท  # calls      ยท  usd (avg)  โ”‚
ยทยทยทยทยทยทยทยทยทยทยทยทยท|ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท|ยทยทยทยทยทยทยทยทยทยทยทยทยท|ยทยทยทยทยทยทยทยทยทยทยทยทยท|ยทยทยทยทยทยทยทยทยทยทยทยทยท|ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท|ยทยทยทยทยทยทยทยทยทยทยทยทยทยท
|  Packer    ยท  somethingLocalMemory   ยท          -  ยท          -  ยท      25754  ยท            1  ยท          -  โ”‚
ยทยทยทยทยทยทยทยทยทยทยทยทยท|ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท|ยทยทยทยทยทยทยทยทยทยทยทยทยท|ยทยทยทยทยทยทยทยทยทยทยทยทยท|ยทยทยทยทยทยทยทยทยทยทยทยทยท|ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท|ยทยทยทยทยทยทยทยทยทยทยทยทยทยท
|  Packer    ยท  somethingRef           ยท          -  ยท          -  ยท      25730  ยท            1  ยท          -  โ”‚
ยทยทยทยทยทยทยทยทยทยทยทยทยท|ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท|ยทยทยทยทยทยทยทยทยทยทยทยทยท|ยทยทยทยทยทยทยทยทยทยทยทยทยท|ยทยทยทยทยทยทยทยทยทยทยทยทยท|ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท|ยทยทยทยทยทยทยทยทยทยทยทยทยทยท
|  Packer    ยท  somethingStructMemory  ยท          -  ยท          -  ยท      28009  ยท            1  ยท          -  โ”‚
ยทยทยทยทยทยทยทยทยทยทยทยทยท|ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท|ยทยทยทยทยทยทยทยทยทยทยทยทยท|ยทยทยทยทยทยทยทยทยทยทยทยทยท|ยทยทยทยทยทยทยทยทยทยทยทยทยท|ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท|ยทยทยทยทยทยทยทยทยทยทยทยทยทยท
|  Packer    ยท  somethingVerbose       ยท          -  ยท          -  ยท      25885  ยท            1  ยท          -  โ”‚
2 Likes

Good idea. For shortness it could be just view.

As an alternative it could be always readonly. And in the case of writing it should be specified explicitly as mutable.

I think that read-only references would be very useful and Iโ€™d like to see something like that in the language eventually. It needs a more precise specification than just declaring that they cannot be modified though. We need to know how they would interact with existing features like for example overriding and overloading functions.

You can have storage references not only as local variables but also as parameters and return parameters of functions and this makes the impact of this change much bigger. On the other hand, I think that marking a storage parameter read-only in a library function might actually be one of the more compelling use cases for this.

So, first of all, should it be limited to storage references or should it apply to memory references too? I think that users will naturally request that if we introduce it for storage references. It needs to be well thought out though because memory variables do not have the same exact limitations. For example you can have them in public and external functions.

So hereโ€™s some of the stuff that needs to be considered:

  1. What happens if you assign a read-only reference to a mutable reference-type variable? Is the value copied or is it an error? What if you assign to an actual state variable?
  2. We need to decide which built-ins that work with references should accept or return read-only ones and which should only work with mutable references.
  3. Should there be a mechanism to convert a read-only reference a mutable one? Something like const_cast<> in C++?
  4. Can you override a virtual function having a mutable reference parameter with one that has a read-only one? Similar to how you can for example override a view function with pure one.
  5. Can you overload functions on read-only parameters? I.e. can you define both foo(uint[] storage readonly x) and foo(uint[] storage x) and have the compiler choose the right one?
  6. Can you do using ... for uint[] readonly to get a library function attached only to read-only values?
  7. Is a function with a read-only parameter implicitly convertible to a function type with a mutable one? Or the other way around?
  8. Should we also allow read-only memory references?
    • If so, should the mutability be part of the function signature and be visible in the ABI?
    • Can you assign a read-only memory reference to a mutable state variable or reference? A read-only storage reference to a mutable memory variable?
1 Like

As an alternative it could be always readonly. And in the case of writing it should be specified explicitly as mutable.

I very much like this idea, but Iโ€™m not sure if doing it only in this one specific case and having everything else in the language mutable by default would be a good idea. It would also be a breaking change while introducing a keyword for read-only stuff not necessarily.