1

I'm very new to Solidity and running into this issue.

enter image description here

As you can see in this picture, I'm trying to set up a scenario where there exists 1 state variable called text. Then, I want to run a transaction that calls doStuff. doStuff creates a copy of text and stores it in testString. I then want to modify this testString in another function, so I indicate a "pass by reference" using the storage keyword next to the parameter called _text.

Obviously this doesn't work because I'm getting an error that I can't implicitly cast a string memory to a string storage. So my question is: how do I create a copy (by using memory), and then pass that copy by reference to be modified (using storage)? What is the best practice/workaround here? Any comments about how to properly use memory vs storage would also be greatly appreciated.

Here is the code:

// SPDX-License-Identifier: GPL-3.0

pragma solidity >=0.7.0 <0.9.0;

contract Jacob {
    string text;
    
    function doStuff() public view returns (string memory) {
        string memory testString = text;
        
        updateTestString(testString);
    
        return (testString);
    }
    
    function updateTestString(string storage _text) private view {
        _text = "Hello, World!";
    }
}
4
  • 3
    Hi, @tsnakejake. Could you provide the code in text format to make it easier to copy and debug? Aug 16, 2021 at 13:10
  • Hi, I just added it @scorpion9979. Thanks! :)
    – tsnakejake
    Aug 17, 2021 at 2:41
  • I can see many issues above. 1- you're passing a memory string to updateTestString, which takes a storage reference of a string, and 2- you can't set a a string literal ("Hello, World!") to a storage reference of a string, you'll need assembly for that low-level control. Aug 17, 2021 at 12:38
  • If you wanted to pass the storage reference of text, just do it directly. The following line only results in the copying of the string data from storage to memory, it's copying the string value, not the storage reference: string memory testString = text; Aug 17, 2021 at 12:39

3 Answers 3

1

I think this is what you want.

I took the liberty of tidying up and making it closer to best practice. "Do Stuff" can't be view or pure because deep down you want to update the state. You can assign a memory variable to storage but you can't convert the way you were trying to do it. The public function makes the state variable readable.

// SPDX-License-Identifier: GPL-3.0

pragma solidity 0.8.7;

contract Jacob {
    
    string public text; // makes it readable
    
    event Updated(address sender, string text);
    
    function doStuff(string memory _text) public returns (string memory) {
        updateTestString(_text);
        return text;
    }
    
    function updateTestString(string memory _text) private {
        text = _text;
        emit Updated(msg.sender, _text); // best practice, because the state has changed
    }
}

Hope it helps.

0

storage and memory parameters are both references. And in each case they can only refer to something stored in that particular location. updateTestString() expects a storage reference so if you want to give it a string, the data belonging to that string has to actually reside somewhere in storage. Otherwise there's no way for the reference to point at it. The most obvious way to do it would be by defining a storage variable (outside of the function; local storage variables are also references!) and assigning the string to it.

Unfortunately there's no way for the compiler to generate a single function that handles both memory and storage arguments in a uniform way because completely different EVM code is required to access each location. At least not without including conditional code handling both and that would cost extra gas. You need two functions.

To avoid code duplication you could extract common parts into helper functions. If you're fine with creating a copy of the string, you could have a function handling one location and another that's a wrapper simply copying it and calling the other version. In that case I'd recommend to choose memory for the function because memory access is much cheaper and you can dynamically allocate memory objects (unlike storage variables, which must be declared upfront).

The compiler could support this better by letting you create a function with a wildcard location and just generating two different functions under the hood. This will likely be supported in the future through templates/generics.

0
contract Jacob {
    // this is placed into Storage
    string text;
    function doStuff() public view returns (string memory) {
        // you are making copy from Storage-to-Memory
        string memory testString = text;
        //You are passing the arg from memory
        updateTestString(testString);
    
        return (testString);
    }
    // you expect this func gets an arg from storage but u pass from memory
    function updateTestString(string storage _text) private view {
        _text = "Hello, World!";
    }
}


Here is the solution

// SPDX-License-Identifier: GPL-3.0

pragma solidity >=0.7.0 <0.9.0;

contract Jacob {
    string text;
    
    function doStuff() public view returns (string memory) {
        string memory testString = text;
        updateTestString(testString);
        return (testString);
    }
    // instead of "view" I used "pure"
    function updateTestString(string memory _text) private pure{
        _text = "Hello, World!";
    }
}

Notice that I also changed from "view" to "pure". Using "view" returns a warning, but you could still deploy the contract

Your Answer

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

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