Environment
Using Defender on Chrome / Rinkeby / Metamask.
Details
I'm currently trying to setup a development flow that is easy to integrate with Defender. Using hardhat, hardhat-deploy and hardhat-upgradeable, I'm able to deploy upgradeable contracts, upgrade them, even prepare them. During the deployment process, I automatically change the Proxy ownership to a multisig that I'm using inside Defender.
Afterwards, it works for upgrades, but I'm unable to run Admin Actions or Pause/Unpause actions from the multisig. There's an rpc error I can see in the logs (with no details).
Code to reproduce
For the contracts
Versioned.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.6;
import '@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol';
contract Versioned {
uint256 version;
mapping(uint256 => bool) private _initialized;
mapping(uint256 => bool) private _initializing;
function getVersion() public view returns (uint256) {
return version;
}
modifier initializer(uint256 _version) {
require(
_initializing[_version] || !_initialized[_version],
'Initializable: contract version is already initialized'
);
bool isTopLevelCall = !_initializing[_version];
if (isTopLevelCall) {
_initializing[_version] = true;
_initialized[_version] = true;
version = _version;
}
_;
if (isTopLevelCall) {
_initializing[_version] = false;
}
}
}
Pausable.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.6;
import '@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol';
contract Pausable {
bool public paused;
address public pauser;
modifier isSystemPaused() {
require(paused == true, 'P1');
_;
}
modifier isSystemNotPaused() {
require(paused == false, 'P1');
_;
}
function pause() external {
require(msg.sender == pauser, 'P2');
paused = true;
}
function unpause() external {
require(msg.sender == pauser, 'P2');
paused = false;
}
function setPauser(address _pauser) internal {
pauser = _pauser;
}
}
GatewayV0.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.6;
import '../Versioned.sol';
import '../Pausable.sol';
import '@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol';
contract GatewayV0 is Versioned, Pausable {
uint256 public test;
function __GatewayV0__constructor(address _pauser) public initializer(0) {
Pausable.setPauser(_pauser);
}
function inc() public {
test += 1;
}
}
and the deployment script
import { HardhatRuntimeEnvironment } from 'hardhat/types';
import { DeployFunction } from 'hardhat-deploy/types';
import { storeProxy } from '../../deploy_utils/storeProxy';
import { getProxy } from '../../deploy_utils/getProxy';
import { isLive } from '../../deploy_utils/isLive';
const deploy: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {
const { deployments, getNamedAccounts, upgrades, ethers } = hre;
const { deploy } = deployments;
const { deployer } = await getNamedAccounts();
const proxyAddress = getProxy('Gateway', hre);
if (!proxyAddress) {
const GatewayV0 = await ethers.getContractFactory('GatewayV0');
const GatewayV0Proxy = await upgrades.deployProxy(GatewayV0, [process.env.OWNER || deployer], {initializer: '__GatewayV0__constructor'})
console.log('deployed GatewayV0', GatewayV0Proxy.address);
if (isLive(hre) && process.env.OWNER) {
console.log('changing owner');
const Admin = await upgrades.admin.getInstance();
await Admin.changeProxyAdmin(GatewayV0Proxy.address, process.env.OWNER, {gasLimit: 1000000});
console.log('changed owner');
}
storeProxy('Gateway', GatewayV0Proxy.address, hre);
} else {
console.log('skipped GatewayV0');
}
};
module.exports = deploy;
module.exports.tags = ['Gateway']
Once added inside Defender, whenever I try to pause I get
Error: Transaction validation failed. This usually means that this proposal would fail to execute given its current parameters.
Make sure all involved accounts have the necessary permissions for the transaction to succeed.
Check pause's code to identify possible revert conditions.
You are proposing to execute function pause through address 0x809c4d1Eb772E72765A9440fbF5A0564E33D1bb9. Make sure that address has enough permissions to execute the function.
And I'm sure the Multisig is the pauser (TBD is the name of the multisig)
Any insights on what might be happening inside Defender ? It's crucial for me to be able to run admin actions (upgradeToAndCall is not available otherwise) and pausing from the multisig.