Search code examples
ethereumsolidity

Storage compatibility: Solidity upgradable smart contract using "proxy" design pattern and changing the mapping type


Anyone used the "proxy" pattern to write upgradable smart contracts? I am planning to upgrade/extend my old smart contract, originally, my code looks like this:

mapping(address => uint256)  balance;

I am planning to write it like this:

struct PackedBalance {
    uint256 balance;
    uint256 locked_balance;    // to support a new feature.
}
mapping(address => PackedBalance) balance;

My concern is if the "storage" is compatible to the "old version smart contract".

I just read the "Layout of State Variables in Storage". As I understand it, it is compatible. But I am a newbie, so I would like to seek help from some experts.

"compatible" means: I can still read correct "balance" of the existing storage slots. And I can write "locked_balance" without breaking anything(like overwritten).


Solution

  • Yes, it's safe to expand the uint256 within a mapping to the struct within the same mapping.

    Lets debug these two example contracts:

    pragma solidity ^0.8;
    
    contract MyContract {
        mapping(address => uint256) balance;
        
        function setBalanceForAddress(address _address, uint256 _balance) external {
            balance[_address] = _balance;
        }
    }
    
    pragma solidity ^0.8;
    
    contract MyContract {
        struct PackedBalance {
            uint256 balance;
            uint256 locked_balance;
        }
    
        mapping(address => PackedBalance) balance;
        
        function setBalanceForAddress(address _address, uint256 _balance, uint256 _lockedBalance) external {
            balance[_address] = PackedBalance(_balance, _lockedBalance);
        }
    }
    

    The address is always going to be 0x1231231231231231231231231231231231231231 (for debugging purposes).

    The first contract stores the uint256 field to storage slot 0xf100689cc6bb188feb3c0ef4658bee6e8042e58e79daebcd84870d7f336a8422

    Tx data:

    0x4fa1007300000000000000000000000012312312312312312312312312312312312312310000000000000000000000000000000000000000000000000000000000000064
    

    Remix debugger screenshot:

    Remix debugger screenshot


    The second contract stores the struct to slots 0xf100689cc6bb188feb3c0ef4658bee6e8042e58e79daebcd84870d7f336a8422 (same as the first contract) and 0xf100689cc6bb188feb3c0ef4658bee6e8042e58e79daebcd84870d7f336a8423 (right next to it).

    Tx data:

    0x6f7223d9000000000000000000000000123123123123123123123123123123123123123100000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000064
    

    Remix debugger screenshot:

    Remix debugger screenshot