Assign a variable inside the initialize function does not set the value of the variables

When I deploy an upgreadable contract the owner the implementation is always set to 0x000...000000 and I don't know why. I'm using the Openzeppelin hardhat plugin.
This is the code of the contract

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.2;

import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20BurnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/utils/math/SafeMathUpgradeable.sol";

contract Promethium is
    Initializable,
    ERC20Upgradeable,
    ERC20BurnableUpgradeable,
    PausableUpgradeable
{
    using SafeMathUpgradeable for uint256;

    uint128 private _MAX_NUMBER_OF_TOKENS_MINTABLE;
    uint8 private _DECIMALS;
    address private _addressOfTheNFTContract;
    address private _owner;

    mapping(bytes32 => bool) private _hashBook;

    modifier onlyOwner() {
        require(_msgSender() == _owner, "The caller must be the owner");
        _;
    }

    function initialize(address newOwner) public initializer {
        __ERC20_init("Promethium", "PRM");
        __ERC20Burnable_init();
        __Pausable_init();
        _DECIMALS = 6;
        _MAX_NUMBER_OF_TOKENS_MINTABLE = 21000000000000;
        _owner = newOwner;
        //Delete the following lines for next upgrades
        _mint(newOwner, 100000 * 10**decimals());
    }

    function setAddressOfNFTContract(address addr) public onlyOwner() {
        _addressOfTheNFTContract = addr;
    }

    function pause() public onlyOwner {
        _pause();
    }

    function unpause() public onlyOwner {
        _unpause();
    }

    function mint(address to, uint256 amount) public onlyOwner {
        _mint(to, amount);
    }

    function _beforeTokenTransfer(
        address from,
        address to,
        uint256 amount
    ) internal override whenNotPaused {
        super._beforeTokenTransfer(from, to, amount);
    }

    function decimals() public view virtual override returns (uint8) {
        return _DECIMALS;
    }

    /*
        The string in input is generated as follows:
            - a random number is generated between 0 and 10000000000 in a server hosted in google Cloud
            - that number is hashed with kekka256 algorithm
            - after that I manipulate the strings, a better explanation in the next points
            - the last digit tells how many characters to read from the string to exctract the number of tokens
              that can be minted.
              Example: 
              This hash here was signed by the owner: 0xb4ac5fc7d8e5be4271b275abb9a4bb49fc57e1fc7a1906f612e9e08610000007
              As you can see the last digit is "7" so the characters that must be read from the string are the next 7 characters
              starting from the end of the string, those are the tokens that will be minted in this mint, in this case
              the tokens are: 1000000.
            - From my backend I sign the message with the private key of the owner to validate this mint
     */
    function extractNumberOfTokensFromHash(string memory str)
        public
        pure
        returns (uint256)
    {
        bytes memory strBytes = bytes(str);
        uint256 startIndex = strBytes.length - 1;
        uint256 endIndex = strBytes.length;
        bytes memory result = new bytes(endIndex - startIndex);
        for (uint256 i = startIndex; i < endIndex; i++) {
            result[i - startIndex] = strBytes[i];
        }
        string memory resultToConvert = string(result);
        uint256 charactersToReadFromHash = st2num(resultToConvert);

        startIndex = strBytes.length - 1 - charactersToReadFromHash;
        endIndex = strBytes.length - 1;
        bytes memory numberOfTokensToMint = new bytes(endIndex - startIndex);
        for (uint256 i = startIndex; i < endIndex; i++) {
            numberOfTokensToMint[i - startIndex] = strBytes[i];
        }
        resultToConvert = string(numberOfTokensToMint);
        uint256 tokens = st2num(resultToConvert);

        return tokens;
    }

    function isCodeValid(bytes32 _hash) public view returns (bool) {
        return _hashBook[_hash];
    }

    /*
        This is a utility function used to convert strings to integers
     */
    function st2num(string memory numString) public pure returns (uint256) {
        uint256 numberExtractFromString = exctractNumberFromHash(numString);
        return numberExtractFromString;
    }

    function exctractNumberFromHash(string memory numString)
        private
        pure
        returns (uint256)
    {
        uint256 val = 0;
        if (areEquals(numString, "A")) {
            return 10;
        } else if (areEquals(numString, "B")) {
            return 11;
        } else if (areEquals(numString, "C")) {
            return 12;
        } else if (areEquals(numString, "D")) {
            return 13;
        } else {
            bytes memory stringBytes = bytes(numString);
            for (uint256 i = 0; i < stringBytes.length; i++) {
                uint256 exp = stringBytes.length - i;
                bytes1 ival = stringBytes[i];
                uint8 uval = uint8(ival);
                uint256 jval = uval - uint256(0x30);

                val += (uint256(jval) * (10**(exp - 1)));
            }
        }
        return val;
    }

    function areEquals(string memory firstString, string memory secondString)
        private
        pure
        returns (bool)
    {
        return
            keccak256(abi.encode(firstString)) ==
            keccak256(abi.encode(secondString));
    }

    /*
        The idea is to process 16 bytes at once using binary operations.
        The toHex16 function converts a sequence of 16 bytes represented 
        as a bytes16 value into a sequence of 32 hexadecimal digits represented 
        as a bytes32 value. The toHex function splits a bytes32 value into two bytes16 chunks, 
        converts each chunk to hexadecimal representation via the toHex16 function, and finally 
        concatenates the 0x prefix with the converted chunks using abi.encodePacked function.
        For a better explanation: https://stackoverflow.com/questions/67893318/solidity-how-to-represent-bytes32-as-string
     */
    function toHex(bytes32 data) public pure returns (string memory) {
        return
            string(
                abi.encodePacked(
                    "0x",
                    toHex16(bytes16(data)),
                    toHex16(bytes16(data << 128))
                )
            );
    }

    function toHex16(bytes16 data) internal pure returns (bytes32 result) {
        result =
            (bytes32(data) &
                0xFFFFFFFFFFFFFFFF000000000000000000000000000000000000000000000000) |
            ((bytes32(data) &
                0x0000000000000000FFFFFFFFFFFFFFFF00000000000000000000000000000000) >>
                64);
        result =
            (result &
                0xFFFFFFFF000000000000000000000000FFFFFFFF000000000000000000000000) |
            ((result &
                0x00000000FFFFFFFF000000000000000000000000FFFFFFFF0000000000000000) >>
                32);
        result =
            (result &
                0xFFFF000000000000FFFF000000000000FFFF000000000000FFFF000000000000) |
            ((result &
                0x0000FFFF000000000000FFFF000000000000FFFF000000000000FFFF00000000) >>
                16);
        result =
            (result &
                0xFF000000FF000000FF000000FF000000FF000000FF000000FF000000FF000000) |
            ((result &
                0x00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000) >>
                8);
        result =
            ((result &
                0xF000F000F000F000F000F000F000F000F000F000F000F000F000F000F000F000) >>
                4) |
            ((result &
                0x0F000F000F000F000F000F000F000F000F000F000F000F000F000F000F000F00) >>
                8);
        result = bytes32(
            0x3030303030303030303030303030303030303030303030303030303030303030 +
                uint256(result) +
                (((uint256(result) +
                    0x0606060606060606060606060606060606060606060606060606060606060606) >>
                    4) &
                    0x0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F) *
                7
        );
    }

    modifier onlyNFTContract() {
        require(
            _msgSender() == _addressOfTheNFTContract,
            "This function can only be called by the smart contract of the NFTs"
        );
        _;
    }

    function receiveTokensFromNFTMint(address origin, uint256 value)
        public
        onlyNFTContract
    {
        _balances[origin] = _balances[origin].sub(value);
        _balances[_owner] = _balances[_owner].add(value);
    }

    function owner() public view returns(address) {
        return _owner;
    }

    function setOwner(address newOwner) public onlyOwner() {
        _owner = newOwner;
    }
}

This is crazy, I even pass the address of the owner as parameter to the initializer function so that's super strange. Even if I use the ownable upgradeable the behaviour is the same.
How can I solve this?

This is the script I'm using to deploy the contract

// scripts/deploy.js
//last deploy address: 
async function main() {
    const Promethium = await ethers.getContractFactory("Promethium");
    console.log("Deploying Promethium...");
    const promethium = await upgrades.deployProxy(Promethium, ["0xeac9852225Aa941Fa8EA2E949e733e2329f42195"],{ initializer: 'initialize' });
    console.log("Promethium deployed to:", promethium.address);
  }
  
  main()
    .then(() => process.exit(0))
    .catch(error => {
      console.error(error);
      process.exit(1);
    });

I even tried to hard code the address of the owner inside the initializer function like this:

function initialize() public initializer {
        __ERC20_init("Promethium", "PRM");
        __ERC20Burnable_init();
        __Pausable_init();
        _DECIMALS = 6;
        _MAX_NUMBER_OF_TOKENS_MINTABLE = 21000000000000;
        _owner = 0xeac9852225Aa941Fa8EA2E949e733e2329f42195;
        //Delete the following lines for next upgrades
        _mint(_owner, 100000 * 10**decimals());
    }

But as you can see from https://rinkeby.etherscan.io/address/0xecab3042f58da0d8bc32936f034225c5f8a0de18#readContract it sets the owner as 0x0000000..0000!
I don't know what to do at this point, I hope for someone from OpenZeppelin to come out
So this is worse than I thought because all the variables put inside the initialize function are not set!

The url you posted is for the implementation, all values should be zeros. When using a proxy, storage is done on the proxy, not the implementation. Have you checked the proxy: https://rinkeby.etherscan.io/tx/0x293e52d77bd7626d21c338cf2d0219f6e8f0e78fa393a2ae8a87647bd7a639a2?

1 Like

Wow you saved my day!
So from now on I will always have to call the functions using the address of my proxy’s contract right? Because all the calls are delegated from the proxy to the current implementation. Is this right?

Correct. With upgradable contracts, all interactions need to happen with the proxy. When a contract uses delegatecall, it executes the implementation code as if it was it's own code. The execution context (storage, balance, token approvals) is the proxy

1 Like