How to handle contract unused variables when using upgradeable smart contract?

Hi everyone, I'm wrapping my head around this: let's say I have a mapping called addressToCoin in my contract:

mapping(address => MyCoinStruct) public addressToCoin;

In a later version of the contract I don't use this mapping anymore. What happens? Do I have to leave the variable there? Because I know that in upgradeable smart contracts you need to handle very carefully the variables otherwise there are problems with the smart contract data.
If I want to add a new variable I simply add it under the addressToCoin like so:

mapping(address => MyCoinStruct) public addressToCoin;
uint256 public counter;

is that right?

1 Like

Yes, you should leave it there (in the first slot), but you may as well change it to something like:

mapping(address => MyCoinStruct) private _deprecated;

That is, unless any other entity (onchain contract or offchain dapp) depends on the existence of:

function addressToCoin(address) public view returns (MyCoinStruct)

In which case, you should preserve not only the slot, but also the current naming.

1 Like

Cristal clear, thank you

BTW, here's a gas-saving trick for you.

If that mapping contains a lot of non-zero information, then for each one of your Write functions, you can implement an identical function which takes the exact same input arguments, alongside an additional address key.

This new function should first call the original function, and right before returning, it should execute:

delete addressToCoin[key];

I believe that this would potentially refund your contract users with up to 10K gas per transaction.

Of course, you'll need to preserve the original naming (function addressToCoin), so that your contract users will be able to make sure that a given key is indeed mapped to non-zero information.

Note that if you use OpenZeppelin Upgrades Plugins, renaming a variable will result in an error from changing the storage layout. But you can use an annotation on the variable to mark that as correct:

/// @custom:oz-renamed-from addressToCoin
mapping(address => MyCoinStruct) private addressToCoin_deprecated;
1 Like

What if the mapping is not used at all and it doesn't contain any non-zero information. Is it safe to just delete it or rename it?

For example, if we're having the following storage:

mapping(uint256 => uint256) public data1;
mapping(address => uint256[]) public data2;

Both mappings will occupy 32 bytes, so data1 will be at slot[0] and data2 will be at slot[1]. It's important to note that only the the elements they contain are stored starting at a different storage slot that is computed using a Keccak-256 hash.

Could we change the type and name of both mappings similar to:

uint256 data1;
bool data2;

Since bool data consume only 1 byte of storage, will bool variable data2 be expanded to use 32 bytes as slot 1 was previously using?

Definitely not. Solidity is not aware of what your state variables were previously.

Our recommendation is to never delete variables once they've been deployed and used.

Thanks for your answer. Yes, I'm aware of this good practice, but I'm just thinking hypothetically.

Since Solidity is not aware of what the state variables were previously and we've consumed 2 slots of 32 bytes each in the past, couldn't we pack different variables to sum up again everything in to 2 slots? That's my point.

You can do something like this:

uint256 data1;
bool data2;
uint248 __gap;