diff --git a/lib/constants.js b/lib/constants.js index b424d51..46d2898 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -70,10 +70,16 @@ module.exports = { INFINITE: '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' }, TASKS: { + PARAM_NAME_ADDRESS: 'address', + PARAM_NAME_ENTITY: 'entity', PARAM_NAME_FACTORY: 'factory', + PARAM_FLAG_NEW_IMPLEMENTATION: 'implementation', + PARAM_FLAG_LEDGER_WALLET: 'ledger', PARAM_NAME_PROXY_ADDRESS: 'proxy', + PARAM_NAME_ROLE: 'role', PARAM_NAME_SAFE_ADDRESS: 'safe', - PARAM_FLAG_NEW_IMPLEMENTATION: 'implementation', - PARAM_FLAG_LEDGER_WALLET: 'ledger' + PARAM_NAME_TARGET: 'target', + PARAM_NAME_REVOKE: 'revoke', + PARAM_NAME_GRANT: 'grant' } } diff --git a/tasks/acl-permission.js b/tasks/acl-permission.js new file mode 100644 index 0000000..61b271a --- /dev/null +++ b/tasks/acl-permission.js @@ -0,0 +1,73 @@ +const { Confirm } = require('enquirer') +const { task } = require('hardhat/config') + +const { + TASKS: { + PARAM_NAME_ENTITY, + PARAM_NAME_TARGET, + PARAM_NAME_ROLE, + PARAM_NAME_SAFE_ADDRESS, + PARAM_FLAG_LEDGER_WALLET, + PARAM_NAME_REVOKE, + PARAM_NAME_GRANT + } +} = require('../lib/constants') +const { getAllRoles } = require('../lib/roles') +const { getAdapter, proposeTransactionToSafe } = require('../lib/safe') + +const proposeGrantRole = async (_args, _hre) => { + const safeAddress = _args[PARAM_NAME_SAFE_ADDRESS] + const targetAddress = _args[PARAM_NAME_TARGET] + const entity = _args[PARAM_NAME_ENTITY] + const role = _args[PARAM_NAME_ROLE] + const revoke = !_args[PARAM_NAME_GRANT] || _args[PARAM_NAME_REVOKE] + + const roles = getAllRoles(_hre.ethers) + + if (!(role in roles)) throw new Error(`Invalid role ${role}`) + + const contract = await _hre.ethers.getContractAt('AccessControlUpgradeable', targetAddress) + + revoke + ? console.info(`✔ Checking if ${entity} address does not have ${role} on ${targetAddress}...`) + : console.info(`✔ Checking if ${entity} address already has ${role} on ${targetAddress}...`) + if (revoke !== (await contract.hasRole(roles[role], entity))) { + revoke + ? console.info(`✘ Failed: ${entity} does not have role ${role}`) + : console.info(`✘ Failed: ${entity} already has role ${role}`) + return + } + + console.info(`✔ Checking if the safe ${safeAddress} has DEFAULT_ADMIN_ROLE' on ${targetAddress}...`) + if (!(await contract.hasRole(roles.DEFAULT_ADMIN_ROLE, safeAddress))) { + console.info('✘ Failed: SAFE account does not have DEFAULT_ADMIN_ROLE') + return + } + + const actionConfirm = new Confirm({ + message: `${revoke ? 'Revoke' : 'Grant'} ${_args[PARAM_NAME_ROLE]} to ${entity} @ ${targetAddress}?` + }) + if (!(await actionConfirm.run())) { + console.info('Quitting') + return + } + + const transactionData = contract.interface.encodeFunctionData(revoke ? 'revokeRoke' : 'grantRole', [ + roles[role], + entity + ]) + + const adapter = await getAdapter(_hre.ethers, _args[PARAM_FLAG_LEDGER_WALLET]) + await proposeTransactionToSafe(adapter, safeAddress, targetAddress, 0, transactionData) + console.info("✔ Done! Check your safe wallet's transaction queue") +} + +task('permissions:acl-manage', 'Propose grant/revoke role transaction to safe multisig') + .addFlag(PARAM_NAME_REVOKE, 'Revoke a role') + .addFlag(PARAM_NAME_GRANT, 'Grant a role') + .addPositionalParam(PARAM_NAME_ROLE, 'Role name to be managed') + .addPositionalParam(PARAM_NAME_ENTITY, 'Address to which role will be granted/revoked') + .addPositionalParam(PARAM_NAME_TARGET, 'Target contract address') + .addPositionalParam(PARAM_NAME_SAFE_ADDRESS, 'Safe address') + .addFlag(PARAM_FLAG_LEDGER_WALLET, 'Use a Ledger wallet') + .setAction(proposeGrantRole) diff --git a/tasks/index.js b/tasks/index.js index 56d4dec..7def657 100644 --- a/tasks/index.js +++ b/tasks/index.js @@ -1,3 +1,4 @@ +require('./acl-permission') require('./decode-forwarder-metadata') require('./check-all-permissions') require('./check-permissions')