Hello, when I try to deploy this code with upgrades.deployProxy
it is raising the following error:
Error: Contract `contracts/Contracts.sol:ContractA` is not upgrade safe
contracts/Contracts.sol:65: Use of delegatecall is not allowed
https://zpl.in/upgrades/error-002
contracts/Contracts.sol:65: Use of delegatecall is not allowed
https://zpl.in/upgrades/error-002
I know I can ignore it by passing the unsafeAllow: ["delegatecall"]
parameter, but I wanted to double check if this code is really unsecure or not, because from what I understand, the call from ContractA to ContractB is not a delegatecall and even being it, it will need to check for the role. Even if we execute ContracA's implementation contract directly, it will need to check the role anyway (with implementation's storage but won't work anyway). Am I missing something?
Thanks
PS: I see that adding /// @custom:oz-upgrades-unsafe-allow-reachable delegatecall
NatSpec to to ContractB's callContractC also works for ignoring. Should be the way to go?
Code to reproduce
pragma solidity ^0.8.20;
import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
contract ContractA is Initializable, AccessControlUpgradeable, UUPSUpgradeable {
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
function initialize() public initializer {
__AccessControl_init();
__UUPSUpgradeable_init();
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
}
function _authorizeUpgrade(address newImplementation) internal override onlyRole(DEFAULT_ADMIN_ROLE) {}
function callContractC(
address contractC,
address contractB,
uint256 amount
) external onlyRole(DEFAULT_ADMIN_ROLE) {
ContractB(contractB).callContractC(contractC, amount);
}
}
contract ContractB {
address private immutable contractA;
constructor(address contractA_) {
contractA = contractA_;
}
function callContractC(address contractC, uint256 amount) external {
(amount);
require(contractA == msg.sender, "Only contractA can call");
(bool success, ) = contractC.delegatecall(msg.data);
require(success, "Delegate call to ContractC failed");
}
}
contract ContracC {
function callContractC(address contractC, uint256 amount) external pure {
(contractC);
(amount);
}
}
Environment
- hardhat 2.22.3
- @openzeppelin/hardhat-upgrades 3.1.0
- @openzeppelin/contracts-upgradeable 5.0.2