0

Solidity does not cause any compile errors for this code although the storage variable data is declared with no reference to actually storage (initially)

contract Test {
  struct Data {
    uint256 first;
    uint256 second;
  }
  address public someAddress = 0x04068DA6C83AFCFA0e13ba15A6696662335D5B75;
  mapping(address => Data) public datas;

  constructor() {}

  function test(address[] memory users) external view {
    Data storage data;
    for (uint256 i = 0; i < users.length; i++) {
        if (users[i] == someAddress) {
            data = datas[someAddress];
            break;
        }
    }
  }
}

Can such declaration cause any problems?

1 Answer 1

0

This is an uninitialized storage pointer which points to slot 0. I used 0.8.14 (version is important here). No warning was raised. Previous compilers did indeed warn about this.

Data storage data

someAddress is in slot 0 because it was the first storage variable declared. So, if you write to it, the contract will scribble over someAddress. Yes ... very dangerous.

Why does it exist?

Storage pointers in functions are very useful for brevity and readability. Consider a moderately complex structure with a mapping or array inside a struct nested inside another mapping of structs. Keep in mind this example is very contrived and not useful. It just contemplates nested structs and some verbose syntax to get around.

You might get something like:

// SPDX-License-Identifier: UNLICENSED

pragma solidity 0.8.14;

contract Test {

    enum GameState {UNDEFINED, PENDING, STARTED, CONCLUDED}

    struct Player {
        string name;
        uint[] games;
    }

    struct Game {
        GameState gameState;
        Player player1;
        Player player2;
        uint8 player1Score;
        uint8 player2Score;
    }

    mapping(address => Player) players;
    // simplist game ID by row number
    Game[] games;

    // fish a game out of the player history
    function getPlayerGameAtIndex(address player, uint gameIndex) public view returns (Game memory game) {
        // this expression gets you there
        return games[players[player].games[gameIndex]];
    }

    // what if you want to write to it? 
    function changePlayer(address player, uint gameIndex, address newPlayer) public {
        // this is safe because g is initialized to point at a specific instance (slot)
        Games storage g = games[players[player].games[gameIndex]];
        if(g.player1 = msg.sender) g.player1 = newPlayer;
        if(g.player2 = msg.sender) g.player2 = newPlayer;

        /*
        using g (storage pointer) is easier to understand and more gas-efficient than:

        if(games[players[player].games[gameIndex]].player1 = msg.sender) games[players[player].games[gameIndex]].player1 = newPlayer;
        if(games[players[player].games[gameIndex]].player2 = msg.sender) games[players[player].games[gameIndex]].player2 = newPlayer;
        */ 

    }

}

Here's a little explainer about storage pointers. I use them frequently for someone who says he doesn't like them. My concern is that misuse can be disastrous and correct use is not obvious to people who are new to the language.

https://blog.b9lab.com/storage-pointers-in-solidity-7dcfaa536089

Hope it helps.

1
  • Thank you for very detailed explanation , Rob
    – kirgol
    Jun 23 at 9:14

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.