Help me understand how this upgradeable contract's initialize() works?

I am looking at this AdminUpgradeabilityProxy contract: https://bscscan.com/address/0x8cb88701790f650f273c8bb2cc4c5f439cd65219#code

and find that this proxy’s implementation contract’s initialize() function is:

contract BunnyMinterV2 is IBunnyMinterV2, OwnableUpgradeable {
    ...
    function initialize() external initializer {
        WITHDRAWAL_FEE_FREE_PERIOD = 3 days;
        WITHDRAWAL_FEE = 50;
        PERFORMANCE_FEE = 3000;

        bunnyPerProfitBNB = 5e18;
        bunnyPerBunnyBNBFlip = 6e18;

        IBEP20(BUNNY).approve(BUNNY_POOL, uint(- 1));
    }
    ...
}

I notice there is no owner set in this initialize() function, but by checking the events of this contract creation transaction, I see 2 events:

  1. one is the Approval event, it is what the initializer does
  2. the other is OwnershipTransferred(address,address) ( keccak hash is 0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0), I wonder where is this event coming from.

According to my understanding, in order to set owner, we have to call Ownable.initialize(sender); explicitly in the initializer, so how is this owner set?

I have tried to deploy the contract on testnet, and the owner is not set in my deployment, so I wonder how the contract creation transaction I posted above managed to get the owner set.

contract and tx info:

Perhaps it’s in the contract OwnableUpgradeable where the owner is set? The owner is set before the BunnyMinterV2 contract is called.

1 Like

thanks for your reply, it is very likely so since the OwnershipTransferred event happened before the Approval event.

While do you have any idea how can I re-produce this?

I would recreate it exactly as they have it on the mainnet, but using rinkeby instead.

finally understand why, I got the wrong first implementation contract, the right one should be this: https://bscscan.com/address/0xc39c78a5e90a57a030bcffc6ae5fadb14d72d15d#code

and this is it’s initialize method:

function initialize() external initializer {
    __PancakeSwapV2_init();

    WITHDRAWAL_FEE_FREE_PERIOD = 3 days;
    WITHDRAWAL_FEE = 50;
    PERFORMANCE_FEE = 3000;

    bunnyPerProfitBNB = 5e18;
    bunnyPerBunnyBNBFlip = 6e18;

    helper = IStrategyHelper(0xA84c09C1a2cF4918CaEf625682B429398b97A1a0);
    BUNNY_TOKEN.approve(BUNNY_POOL, uint(-1));
}

and code for __PancakeSwapV2_init() is:

function __PancakeSwapV2_init() internal initializer {
    __Ownable_init();
}

and this is where the owner was initialized.

The lesson I learned: once the initialize() method is called, the following implementation contract can change the method to whatever it likes and it makes no change to the proxy contract’s states cause this method won’t be called twice.

1 Like