3

Why are these variables confounding? The returned result shows that both loops are effecting both bytes variables (second returns the same result).

function mix() public returns (bytes) {  

    bytes first;
    for (uint8 cnt = 0; cnt<8; cnt++) {
        first.push(9)
    }


    bytes second;
    for (uint8 cnt2 = 0; cnt2<8; cnt2++) {
        second.push(8);
    }

    return first;
}

Returned result: '0x09090909090909090808080808080808'

4

This is going to take a few hops to explain.

The code looks innocent enough, but there is a compiler warning about uninitialized storage pointers. The warning shouldn't be ignored.

Written explicitly, it would be

bytes storage first;

Storage pointers can start to look like slight of hand. Consider this:

MyStruct s = structsMap[key];
s.someVal = false;

The storage at structsMap[key] will be set to false because s is a storage pointer (a.k.a. reference variable). Instead of holding the value, it holds a pointer to storage. When you write to s, you are writing to the actual storage at whatever slot s refers to, which it got when it was initialized.

Your bytes first; statement doesn't have anything on the right (of =), so it's not initialized, so it gets the default of slot 0. That's the "uninitialized" part of the warning. bytes second gets the same slot 0. Yikes! Now we have things that look separate stomping on the same storage slot. Here be dragons.

If we declare the storage in the global section, it will work as expected:

contract NoMixing {

    bytes public first;
    bytes public second;

    function mix() public returns (bytes, bytes) {  

        for (uint8 cnt = 0; cnt<8; cnt++) {
            first.push(9);
        }

        for (uint8 cnt2 = 0; cnt2<8; cnt2++) {
            second.push(8);
        }

        return (first,second);
    }

}

I'm not a huge fan of storage pointers because code like yours looks like it should work. See this: https://vessenes.com/solidity-frustrations-references-and-mapping/

Some general safety tips:

  1. Declare storage outside functions.
  2. Use memory explicitly when assigning something from an indexed type ([]). It would make your push method fail but at least you'd be alerted to the issue.
  3. Use extreme caution with x after x = somethingInStorage[index]; so you don't overwrite something you didn't mean to alter.

Hope it helps.

UPDATE

This is such an interesting example, I decided to blog about the issue. Here's an explainer about storage pointers in Solidity with some safety suggestions for avoiding this sort of confusion: https://blog.b9lab.com/storage-pointers-in-solidity-7dcfaa536089

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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