Get OwnableUnauthorizedAccount when upgrade the transparent proxy

I get an anvil blockchain to deploy my contracts and use the address anvil provide to set the owner of my contract.But it get OwnableUnauthorizedAccount(my TokenUpdate contract address)

Its confusing that I set a msg.sender print in my TokenUpdate's run function and even I change the lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/access/Ownable.sol code to revert the error OwnableUnauthorizedAccount2(address account, address account2);

The msg.sender print in my TokenUpdate and the revert msy shows that I used my private_key's account not the address in OwnableUnauthorizedAccount(my TokenUpdate contract address)

Here is my github repo

You can follow the step In my README.md to replicate the error use the env I provide. ->

:1234: Code to reproduce

Here's the code I changed in lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/access/Ownable.sol

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)

pragma solidity ^0.8.20;

import {Context} from "../utils/Context.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * The initial owner is set to the address provided by the deployer. This can
 * later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable is Context {
    address private _owner;

    /**
     * @dev The caller account is not authorized to perform an operation.
     */
    error OwnableUnauthorizedAccount(address account);
    error OwnableUnauthorizedAccount2(address account, address account2);

    /**
     * @dev The owner is not a valid owner account. (eg. `address(0)`)
     */
    error OwnableInvalidOwner(address owner);

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the address provided by the deployer as the initial owner.
     */
    constructor(address initialOwner) {
        if (initialOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(initialOwner);
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        if (owner() != _msgSender()) {
            revert OwnableUnauthorizedAccount2(_msgSender(), owner());
        }
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby disabling any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        if (newOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}

Here is deploy script TokenDeploy.s.sol

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

import {Upgrades} from "openzeppelin-foundry-upgrades/Upgrades.sol";
import {TokenV1} from "src/4_upgradableERC20/TokenV1.sol";
import {TokenV2} from "src/4_upgradableERC20/TokenV2.sol";
import {TokenV2Bank} from "src/4_upgradableERC20/TokenV2Bank.sol";
import {TokenProxy} from "src/4_upgradableERC20/TokenProxy.sol";
import {BaseDeploy} from "script/4_upgradableERC20/BaseDeploy.s.sol";
import {Script, console} from "forge-std/Script.sol";

contract TokenDeploy is BaseDeploy {
    function run() external returns (address _proxy) {
        _proxy =
            Upgrades.deployTransparentProxy("TokenV1.sol:TokenV1", INIT_OWNER, abi.encodeCall(TokenV1.initialize, ()));
    }
}

contract TokenUpdate is BaseDeploy {
    function run() external returns (address _bank) {
        vm.startBroadcast();
        Upgrades.deployTransparentProxy("TokenV1.sol:TokenV1", INIT_OWNER, abi.encodeCall(TokenV1.initialize, ()));
        console.log("Tora sender", msg.sender);
        Upgrades.upgradeProxy(PROXY_ADDRESS, "TokenV2.sol:TokenV2", abi.encodeCall(TokenV2.initialize, ()));
        _bank = address(new TokenV2Bank(PROXY_ADDRESS));
        vm.stopBroadcast();
    }
}

contract Test is BaseDeploy {
    function run() external returns (address _bank) {
        _bank = 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266;
    }
}

TokenV1.sol

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

// import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
// import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";

contract TokenV1 is ERC20Upgradeable {
    uint256 public s_counter;

    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor() {
        _disableInitializers();
    }

    function initialize() external payable initializer {
        __ERC20_init("TORAToken", "ToraT");
        s_counter = 1;
    }

    function add() external {
        s_counter++;
    }

    function addI(uint256 input) external {
        s_counter += input;
    }
}

TokenV2.sol

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

// import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {Address} from "@openzeppelin/contracts/utils/Address.sol";
// import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";

interface TokenRecipient {
    function depositToken(address user, uint256 amount) external returns (bool);
    function withdrawToken(address user, uint256 amount) external returns (bool);
}

/// @custom:oz-upgrades-from src/4_upgradableERC20/TokenV1.sol:TokenV1
contract TokenV2 is ERC20Upgradeable {
    using Address for address;

    error VaultERC20__NoTokensReceived();

    uint256 public s_counter;
    uint256 public s_counter2;

    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor() {
        _disableInitializers();
    }

    function deposit(address s_receipt, uint256 amount) external returns (bool) {
        //Add this function can let allowance reduce by the amount
        // _spendAllowance(msg.sender, s_receipt, amount);
        _transfer(msg.sender, s_receipt, amount);

        bool rv = TokenRecipient(s_receipt).depositToken(msg.sender, amount);
        if (!rv) {
            revert VaultERC20__NoTokensReceived();
        }

        return true;
    }

    function withdraw(address s_receipt, uint256 amount) external returns (bool) {
        //Add this function can let allowance reduce by the amount
        // _spendAllowance(msg.sender, s_receipt, amount);
        _transfer(s_receipt, msg.sender, amount);

        bool rv = TokenRecipient(s_receipt).withdrawToken(msg.sender, amount);
        if (!rv) {
            revert VaultERC20__NoTokensReceived();
        }

        return true;
    }

    function initialize() external payable reinitializer(2) {
        s_counter2 = 1;
    }

    function add3() external {
        s_counter += 3;
    }

    function counter2add3() external {
        s_counter2 += 3;
    }
}

TokenV2Bank.sol

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

import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract TokenV2Bank {
    error callerNotTokenAddress();

    address immutable s_tokenAddress;
    mapping(address => uint256) s_balanceMap;

    constructor(address _tokenAddress) {
        s_tokenAddress = _tokenAddress;
    }

    function depositToken(address user, uint256 amount) external returns (bool) {
        bool returnFlag = false;
        if (msg.sender == s_tokenAddress) {
            s_balanceMap[user] += amount;
            returnFlag = true;
        } else {
            revert callerNotTokenAddress();
        }
        return returnFlag;
    }

    function withdrawToken(address user, uint256 amount) external returns (bool) {
        bool returnFlag = false;
        if (msg.sender == s_tokenAddress) {
            s_balanceMap[user] -= amount;
            returnFlag = true;
        } else {
            revert callerNotTokenAddress();
        }
        return returnFlag;
    }

    function getBalance(address user) public view returns (uint256) {
        return s_balanceMap[user];
    }

    function getBalance() external view returns (uint256) {
        return s_balanceMap[msg.sender];
    }
}

:computer: Environment

forge 0.2.0
OpenZeppelin Contracts v5

Hi, welcome to the community! :wave:

According to the openzeppelin-foundry-upgrades docs:

Important Include the --sender <ADDRESS> flag for the forge script command
when performing upgrades, 
specifying an address that owns the proxy or proxy admin. 
Otherwise, OwnableUnauthorizedAccount errors will occur.
1 Like

Thank you for your response!

I read your advice carefully, found that your advise to add --sender has already done in my Makefile


...

NETWORK_ARGS := --rpc-url http://localhost:8545 --private-key $(PRIVATE_KEY) --broadcast

...

cupdate:

@forge clean && forge script script/4_upgradableERC20/TokenDeploy.s.sol:TokenUpdate $(NETWORK_ARGS)

Where it's pointed out in foundry's documents:


The broadcasting sender is determined by checking the following in order:

If --sender argument was provided, that address is used.

If exactly one signer (e.g. private key, hardware wallet, keystore) is set, that signer is used.

Otherwise, the default Foundry sender (0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38) is attempted to be used.

As is mentioned above I used the --private-key to determin the sender.

But later I found how to solve the problem: I forgot to add startbroadcast in my code :joy:.


vm.startBroadcast();

After broadcast command added, the contract just upgrade as I expected. :tada:

Thank you very much for your reply. Drove me to reread the document and find the solution.

1 Like

Glad you solved your problem.

1 Like