Writing Upgradeable Contracts initialize function Bug or Not?

I created simple upgradeable ERC20 contract so I need to use function token as recommended by the documentations, It is deployed and working using proxy contract but I just noticed that initialize function can still be executed again so it will create another token into the contract address.

Deploying 'MyContract': 0x12FC65cc9D1c06BB94D88CB785e8987867769E53 // has new token due to successful initialize function execution
ProxyAdmin: 0x7a1bc6dd98Cc126cE454fad687242ddcE05e7E52
TransparentUpgradeableProxy: 0x246F162B9f7A082Bd49F805fbbE32cD730975cb9

:1234: Code to reproduce

ontract MyContract is IERC20Upgradeable, OwnableUpgradeable, ERC20BurnableUpgradeable, StakingRewards {
    using SafeMathUpgradeable for uint256;
    using SafeERC20Upgradeable for IERC20Upgradeable;

    bool private initialized;

    function initialize(string memory name, string memory symbol, uint256 initialSupply) public initializer {
        require(!initialized, "Contract instance has already been initialized");
        initialized = true;

        require(owner() != address(this), "Owner must be set");

        // create initial token values
        __ERC20_init(name, symbol);
        __ERC20Burnable_init();
        __Ownable_init();
        _mint(_msgSender(), initialSupply);

        periodFinish = 0;
        rewardRate = 0;
        rewardsDuration = 7 days;

        rewardsToken = IERC20Upgradeable(_msgSender());
        stakingToken = IERC20Upgradeable(_msgSender());
        rewardsDistribution = _msgSender();

        _status = _NOT_ENTERED;
    }
} 

contract StakingRewards is IStakingRewards, RewardsDistributionRecipient, ReentrancyGuard, Pausable {
    using SafeMathUpgradeable for uint256;
    using SafeERC20Upgradeable for IERC20Upgradeable;

    // /* ========== STATE VARIABLES ========== */

    IERC20Upgradeable public rewardsToken;
    IERC20Upgradeable public stakingToken;
    uint256 public periodFinish;
    uint256 public rewardRate;
    uint256 public rewardsDuration;
    uint256 public lastUpdateTime;
    uint256 public rewardPerTokenStored;
    uint256 private _totalSupply;

    mapping(address => uint256) public userRewardPerTokenPaid;
    mapping(address => uint256) public rewards;
    mapping(address => uint256) private _balances;

    // /* ========== VIEWS ========== */
    function lastTimeRewardApplicable() public override view returns (uint256) {
        return block.timestamp < periodFinish ? block.timestamp : periodFinish;
    }

    function rewardPerToken() public override view returns (uint256) {
        if (_totalSupply == 0) {
            return rewardPerTokenStored;
        }
        return
            rewardPerTokenStored.add(
                lastTimeRewardApplicable().sub(lastUpdateTime).mul(rewardRate).mul(1e18).div(_totalSupply)
            );
    }

    function earned(address account) public override view returns (uint256) {
        return _balances[account].mul(rewardPerToken().sub(userRewardPerTokenPaid[account])).div(1e18).add(rewards[account]);
    }

    function getRewardForDuration() external override view returns (uint256) {
        return rewardRate.mul(rewardsDuration);
    }

    /* ========== MUTATIVE FUNCTIONS ========== */

    function stake(uint256 amount) external override nonReentrant notPaused updateReward(msg.sender) {
        require(amount > 0, "Cannot stake 0");
        _totalSupply = _totalSupply.add(amount);
        _balances[msg.sender] = _balances[msg.sender].add(amount);
        stakingToken.safeTransferFrom(msg.sender, address(this), amount);
        emit Staked(msg.sender, amount);
    }

    function withdraw(uint256 amount) public override nonReentrant updateReward(msg.sender) {
        require(amount > 0, "Cannot withdraw 0");
        _totalSupply = _totalSupply.sub(amount);
        _balances[msg.sender] = _balances[msg.sender].sub(amount);
        stakingToken.safeTransfer(msg.sender, amount);
        emit Withdrawn(msg.sender, amount);
    }

    function getReward() public override nonReentrant updateReward(msg.sender) {
        uint256 reward = rewards[msg.sender];
        if (reward > 0) {
            rewards[msg.sender] = 0;
            rewardsToken.safeTransfer(msg.sender, reward);
            emit RewardPaid(msg.sender, reward);
        }
    }

    function exit() external override {
        withdraw(_balances[msg.sender]);
        getReward();
    }

    /* ========== RESTRICTED FUNCTIONS ========== */

    function notifyRewardAmount(uint256 reward) external override onlyRewardsDistribution updateReward(address(0)) {
        if (block.timestamp >= periodFinish) {
            rewardRate = reward.div(rewardsDuration);
        } else {
            uint256 remaining = periodFinish.sub(block.timestamp);
            uint256 leftover = remaining.mul(rewardRate);
            rewardRate = reward.add(leftover).div(rewardsDuration);
        }

        // Ensure the provided reward amount is not more than the balance in the contract.
        // This keeps the reward rate in the right range, preventing overflows due to
        // very high values of rewardRate in the earned and rewardsPerToken functions;
        // Reward + leftover must be less than 2^256 / 10^18 to avoid overflow.
        uint balance = rewardsToken.balanceOf(address(this));
        require(rewardRate <= balance.div(rewardsDuration), "Provided reward too high");

        lastUpdateTime = block.timestamp;
        periodFinish = block.timestamp.add(rewardsDuration);
        emit RewardAdded(reward);
    }

    // Added to support recovering LP Rewards from other systems such as BAL to be distributed to holders
    function recoverERC20(address tokenAddress, uint256 tokenAmount) external onlyOwner {
        require(tokenAddress != address(stakingToken), "Cannot withdraw the staking token");
        IERC20Upgradeable(tokenAddress).safeTransfer(owner(), tokenAmount);
        emit Recovered(tokenAddress, tokenAmount);
    }

    function setRewardsDuration(uint256 _rewardsDuration) external onlyOwner {
        require(
            block.timestamp > periodFinish,
            "Previous rewards period must be complete before changing the duration for the new period"
        );
        rewardsDuration = _rewardsDuration;
        emit RewardsDurationUpdated(rewardsDuration);
    }

    /* ========== MODIFIERS ========== */

    modifier updateReward(address account) {
        rewardPerTokenStored = rewardPerToken();
        lastUpdateTime = lastTimeRewardApplicable();
        if (account != address(0)) {
            rewards[account] = earned(account);
            userRewardPerTokenPaid[account] = rewardPerTokenStored;
        }
        _;
    }
}

:computer: Environment

Truffle v5.4.17 (core: 5.4.17)
Solidity - 0.8.0 (solc-js)
Node v14.18.1
Web3.js v1.5.3

Can you rephrase the question?

I created ERC20 token using upgradeable smartcontract it will create the token I intended. Though I noticed that if i will execute the initialize action even it is already deployed it will still initialize new token into the contract that executes the action. I thought that initialize action can only be executed once on deploy process.

This are the addresses created on upgradeable contract.

Token contract address:      "0x12FC65cc9D1c06BB94D88CB785e8987867769E53"
ProxyAdmin contract address: "0x7a1bc6dd98Cc126cE454fad687242ddcE05e7E52"
TransparentUpgradeableProxy: "0x246F162B9f7A082Bd49F805fbbE32cD730975cb9"

I believe this is due to proxying the contract or something and I cant find the exact explanation if this is how it works.

PS: I tested it on non upgradable smartcontract so it uses constructor{} instead of initialize function and it run only once on deployment process.

Hi @frangio, heres the rephrased question your asking,

You can only execute initialize once.