tenderly-wizard-v6
Version:
A tool for managing virtual testnets using Tenderly
175 lines (174 loc) • 11 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.deployAccessControlSystemV1 = void 0;
exports.deployRoles = deployRoles;
exports.enableRolesModifier = enableRolesModifier;
exports.setRolesMultisend = setRolesMultisend;
exports.assignRoles = assignRoles;
const safe_master_copy_v1_json_1 = __importDefault(require("../contracts/safe_master_copy_v1.json"));
const roles_v1_json_1 = __importDefault(require("../contracts/roles_v1.json"));
const scope_access_controller_v1_1 = require("../whitelist/acs/scope-access-controller-v1");
const colors_1 = __importDefault(require("colors"));
const deploy_safe_v1_1 = require("./deploy-safe-v1");
const constants_1 = require("../utils/constants");
// @ts-ignore
const hardhat_1 = require("hardhat");
const util_1 = require("../utils/util");
const roles_chain_config_1 = require("../utils/roles-chain-config");
const ethers_1 = require("ethers");
const zodiac_1 = require("@gnosis-guild/zodiac");
//@dev note that hardhat struggles with nested contracts. When we call a Safe to interact with Roles, only events from the Safe can be detected.
async function deployRoles(owner, avatar, target, proxied, chainId, chainConfig) {
const [caller] = await hardhat_1.ethers.getSigners();
if (proxied) {
// get expected Module Address and transaction
const { expectedModuleAddress, transaction } = await (0, zodiac_1.deployAndSetUpModule)(zodiac_1.KnownContracts.ROLES_V1, {
types: ["address", "address", "address"],
values: [owner, avatar, target],
}, caller.provider, chainId, util_1.SALT);
const predictedRolesAddress = await (0, util_1.predictRolesModAddress)(caller, owner, avatar, target, "v1");
console.log(`prediected roles address: ${predictedRolesAddress}`);
// check if address is matching predicted address before processing transaction
if (expectedModuleAddress !== predictedRolesAddress) {
throw new Error(`Roles mod address deployment unexpected, expected ${util_1.predictRolesModAddress}, actual: ${expectedModuleAddress}`);
}
// const rolesMaster = new Contract(
// chainConfig.ROLES_MASTER_COPY_ADDR,
// ROLES_V1_MASTER_COPY_ABI,
// caller
// );
// const initParams = await rolesMaster.populateTransaction.setUp(encoded);
// const tsSalt = new Date().getTime(); // salt must be the same
// const safeModuleProxyFactory = new Contract(
// chainConfig.SAFE_MODULE_PROXY_FACTORY_ADDR,
// SAFE_MODULE_PROXY_FACTORY_ABI,
// caller
// );
// const deployModTx = await safeModuleProxyFactory.deployModule(
// chainConfig.ROLES_MASTER_COPY_ADDR,
// initParams.data as string,
// tsSalt
// );
// const txReceipt = await deployModTx.wait();
// const txData = txReceipt.events?.find(
// (x: any) => x.event == "ModuleProxyCreation"
// );
// const rolesModAddress = txData?.args?.proxy;
try {
await caller.sendTransaction(transaction);
console.info(colors_1.default.green(`✅ Roles was deployed via proxy factory to ${expectedModuleAddress}`));
return expectedModuleAddress;
}
catch (e) {
console.error(e);
throw new Error(`Roles mod address deployment failed: ${e}`);
}
}
const Permissions = await hardhat_1.ethers.getContractFactory("Permissions");
const permissions = await Permissions.deploy();
const Roles = await hardhat_1.ethers.getContractFactory("Roles", {
libraries: {
Permissions: permissions.address,
},
});
const roles = await Roles.deploy(owner, avatar, target);
console.info("Modifier deployed to:", roles.address, "\n");
return roles.address;
}
//If the roles module is not already enabled on Safe, enable it
async function enableRolesModifier(safeAddr, rolesAddr) {
const [caller] = await hardhat_1.ethers.getSigners();
const signature = (0, util_1.getPreValidatedSignatures)(caller.address);
const invSafe = new ethers_1.Contract(safeAddr, safe_master_copy_v1_json_1.default, caller);
const enabled = await invSafe.isModuleEnabled(rolesAddr);
console.log(`ℹ️ Roles modifier: ${rolesAddr} is enabled on safe: ${safeAddr} ${enabled}`);
if (!enabled) {
const enable = await invSafe.enableModule.populateTransaction(rolesAddr);
const enableTx = await invSafe.execTransaction(safeAddr, constants_1.tx.zeroValue, enable.data ?? "", constants_1.tx.operation, constants_1.tx.avatarTxGas, constants_1.tx.baseGas, constants_1.tx.gasPrice, constants_1.tx.gasToken, constants_1.tx.refundReceiver, signature);
const txReceipt = await enableTx.wait();
const txData = txReceipt.events?.find((x) => x.event == "EnabledModule");
const moduleEnabledFromEvent = txData?.args?.module;
console.info(colors_1.default.blue(`ℹ️ Roles modifier: ${moduleEnabledFromEvent} has been enabled on safe: ${safeAddr}`));
}
else {
console.info(`Roles modifier: ${rolesAddr} was already enabled on safe: ${safeAddr}`);
}
}
// sets the address of the multisend contract
async function setRolesMultisend(safeAddr, rolesAddr, chainConfig) {
const [caller] = await hardhat_1.ethers.getSigners();
const roles = new ethers_1.Contract(rolesAddr, roles_v1_json_1.default, caller);
const multisendOnRecord = await roles.multisend();
//If no MS on record, submit a tx to write one on record
if (multisendOnRecord === ethers_1.ZeroAddress) {
const setMsPopTx = await roles.setMultisend.populateTransaction(chainConfig.MULTISEND_ADDR);
const safe = new ethers_1.Contract(safeAddr, safe_master_copy_v1_json_1.default, caller);
const signature = (0, util_1.getPreValidatedSignatures)(caller.address);
await safe.execTransaction(rolesAddr, constants_1.tx.zeroValue, setMsPopTx.data, constants_1.tx.operation, constants_1.tx.avatarTxGas, constants_1.tx.baseGas, constants_1.tx.gasPrice, constants_1.tx.gasToken, constants_1.tx.refundReceiver, signature);
console.info(colors_1.default.blue(`ℹ️ Multisend has been set to: ${chainConfig.MULTISEND_ADDR}`));
}
else {
console.info(`Multisend has already been previously set to: ${multisendOnRecord}`);
}
}
// assign a role to a array of members addresses attached to a role id policy
async function assignRoles(safeAddr, rolesAddr, memberAddrs, roleId, chainConfig) {
const [caller] = await hardhat_1.ethers.getSigners();
// assign manager a role (becomes a member of role:manager_role_id)
const roles = new ethers_1.Contract(rolesAddr, roles_v1_json_1.default, caller);
const signature = (0, util_1.getPreValidatedSignatures)(caller.address);
const acSafe = new ethers_1.Contract(safeAddr, safe_master_copy_v1_json_1.default, caller);
const assignRolesPopTx = await Promise.all(memberAddrs.map(async (memberAddr) => {
return await roles.assignRoles.populateTransaction(memberAddr, [roleId], [true]);
}));
const metaTxs = (0, util_1.createMultisendTx)(assignRolesPopTx, chainConfig.MULTISEND_ADDR);
await acSafe.execTransaction(chainConfig.MULTISEND_ADDR, constants_1.tx.zeroValue, metaTxs.data, constants_1.SAFE_OPERATION_DELEGATECALL, constants_1.tx.avatarTxGas, constants_1.tx.baseGas, constants_1.tx.gasPrice, constants_1.tx.gasToken, constants_1.tx.refundReceiver, signature);
console.info(colors_1.default.blue(`Role member: ${memberAddrs.toString()} has been assigned role id: ${roleId}`));
}
// this will deploy the entire system from scratch, WITHOUT any investment manager permissions
const deployAccessControlSystemV1 = async (chainId, options, deployed) => {
// get chain config for multichain deploy
const chainConfig = (0, roles_chain_config_1.getChainConfig)(chainId, "v1");
//Deploy both safes
const accessControlSafeAddr = deployed?.acSafeAddr ||
(await (0, deploy_safe_v1_1.deploySafeV1)(chainConfig, constants_1.SALTS.safes.accessControl));
const investmentSafeAddr = deployed?.invSafeAddr ||
(await (0, deploy_safe_v1_1.deploySafeV1)(chainConfig, constants_1.SALTS.safes.investment));
// //Deploy and enable a Roles modifier on the investment safe
const invRolesAddr = deployed?.invRolesAddr ||
(await deployRoles(accessControlSafeAddr, investmentSafeAddr, investmentSafeAddr, options.proxied, chainId, chainConfig));
await enableRolesModifier(investmentSafeAddr, invRolesAddr);
//Set the multisend address on roles so that manager can send multisend txs later on
await setRolesMultisend(accessControlSafeAddr, invRolesAddr, chainConfig);
//Deploy and enable a Roles modifier on the access control safe
const acRolesAddr = deployed?.acRolesAddr ||
(await deployRoles(accessControlSafeAddr, accessControlSafeAddr, accessControlSafeAddr, options.proxied, chainId, chainConfig));
await enableRolesModifier(accessControlSafeAddr, acRolesAddr);
//Set the multisend address on roles so that manager can send multisend txs later on
await setRolesMultisend(accessControlSafeAddr, acRolesAddr, chainConfig);
//Grant an access controller role to Security EOA's
await assignRoles(accessControlSafeAddr, acRolesAddr, options.securityEOAs, constants_1.SECURITY_ROLE_ID_V1, chainConfig);
// Populate this role for Security so they can call whitelisting related functions on investment roles
const [caller] = await hardhat_1.ethers.getSigners();
const accessControllerWhitelist = new scope_access_controller_v1_1.AccessControllerWhitelistV1(acRolesAddr, caller);
await accessControllerWhitelist.execute(invRolesAddr, accessControlSafeAddr);
//Grant a role to the investment managers EOAs
//the idea would be that each strategy would be transacted on by 1 EOA
await assignRoles(accessControlSafeAddr, invRolesAddr, options.managerEOAs, constants_1.MANAGER_ROLE_ID_V1, chainConfig);
// Add signers
await (0, deploy_safe_v1_1.addSafeSigners)(investmentSafeAddr, options.sysAdminAddresses, chainConfig);
await (0, deploy_safe_v1_1.addSafeSigners)(accessControlSafeAddr, options.sysAdminAddresses, chainConfig);
//Remove the deployer address as owner and rewrite signing threshold
await (0, deploy_safe_v1_1.removeDeployerAsOwner)(investmentSafeAddr, options.invSafeThreshold);
await (0, deploy_safe_v1_1.removeDeployerAsOwner)(accessControlSafeAddr, options.acSafeThreshold);
return {
acSafe: accessControlSafeAddr,
invSafe: investmentSafeAddr,
invRoles: invRolesAddr,
acRoles: acRolesAddr,
};
};
exports.deployAccessControlSystemV1 = deployAccessControlSystemV1;