Hello there,
I'm using AccessManager with upgradeable contracts. Some feedback/questions on this:
- Inheriting from
AccessManagerUpgradeable
, if you want to write an admin method with other role than the default admin, you have to resort to using hasRole()
in a way similar to AccessControl
onlyAuthorized
is limited to a fixed set of methods defined in AccessManager, and it's not designed to be extended.
restricted
is not available, since it is in AccessManaged
- What is the recommended way of doing ACL in
UUPSUpgradeable
_authorizeUpgrade
? It's an internal method, so restricted
is not recommended. Selector for upgradeToAndCall
can't be restricted
, since it's onlyProxy
.
Again, we can use hasRole
in a similar way AccessControl
has onlyRole
, but it's awkward since restricted
roles are wired outside of the correspondent AccessManaged
contracts code... instead of having 1 consistent paradigm for ACL we have to use 2.
hasRole
does not take into account execution delays, which was the intention of using AccessManager.
_checkCanCall(address caller, bytes calldata data)
does take into account delays, but I would have to fake a selector, since internal methods don't really have one, I'm not sure if that could have some interactions. EDIT: can't fake a selector, because data is calldata
and I can only pass memory
inside _checkCanCall
- This issue
Thanks for your time!
1 Like
Mind you, adding restricted
to authorizeUpgrade
will work because it will default to ask for protocol admin method as I explain in the GH issue.
The problem is I need a separate role since I want to assign an execution delay to upgrades independent of other admin methods.
Fun thing is that we were discussing this very same thing today before you posted it.
The AccessManager has essentiall 2 ways of authorizing:
- A set of admin rules for interactions with the manager itself.
- A set of roles and permissions for external contracts.
This means that a pattern like Transparent Proxy may work. Though, if you want to have the ProxyAdmin restricted by the manager itself that's another conversation.
In regards to UUPSUpgradeable
, I think it's possible to restrict the _authorizeUpgrade
just by checking if the caller is the manager itself:
function _authorizeUpgrade(address) internal {
require(address(this) === msg.sender);
}
Then you would add add an upgrader role:
setTargetFunctionRole(
address(this), // AccessManager address
["0x00..00"] // upgradeToAndCall selector
uint64(420) // Any role
)
In this way, you can relay the upgrade using the execute
function, so that the manager calls itself after being instructed by a role holder. Operations through execute need to be scheduled through schedule
first if the operation requires a delay .
Again, we can use hasRole
in a similar way AccessControl
has onlyRole
, but it's awkward since restricted
roles are wired outside of the correspondent AccessManaged
contracts code... instead of having 1 consistent paradigm for ACL we have to use 2.
The consistent paradigm here is that the manager is the source of truth in both cases. There's no point in keeping the restricted
roles within the AccessManaged since it fragments the permissions and not doing so is one of the goals of the AccessManager.
In case you want to keep the roles in each corresponding contract, you can still use the manager. See Using with Access Control.
2 Likes
Thanks! The example you provide serves to upgrade the AccessManager itself.
What about other UUPSUpgradeable contracts? Example taken from the Wizard
// SPDX-License-Identifier: MIT
// Compatible with OpenZeppelin Contracts ^5.0.0
pragma solidity ^0.8.20;
import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PermitUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/manager/AccessManagedUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
contract MyToken is Initializable, ERC20Upgradeable, ERC20PermitUpgradeable, AccessManagedUpgradeable, UUPSUpgradeable {
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
function initialize(address initialAuthority) initializer public {
__ERC20_init("MyToken", "MTK");
__ERC20Permit_init("MyToken");
__AccessManaged_init(initialAuthority);
__UUPSUpgradeable_init();
}
function _authorizeUpgrade(address newImplementation)
internal
restricted
override
{}
}
What is the recommended way to configure the role
UPGRADER_ROLE` with a delay to upgrade MyToken?
//What do I put in ?????
accessManager.setTargetFunctionRole(address(mytoken), ?????, ProtocolAdmin.UPGRADER_ROLE, execDelay);
accessManager.grantRole(multisig, ProtocolAdmin.UPGRADER_ROLE);
The Wizard example is well configured, though.
You should set bytes4(keccak256('upgradeToAndCall(address,bytes)'))
. Given you already have an execDelay
for the upgradeToAndCall
function, then the only way to execute it is by scheduling an upgrade operaton using schedule
and then execute
from the manager.
1 Like
Nevermind @ernestognw , I found the example in the demo you mentioned, thanks!
1 Like