tenderly-wizard-v6
Version:
A tool for managing virtual testnets using Tenderly
179 lines (178 loc) • 12.4 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.deployAccessControlSystemV2 = void 0;
exports.deployRolesV2 = deployRolesV2;
exports.enableRolesModifier = enableRolesModifier;
exports.setRolesMultisend = setRolesMultisend;
exports.setRolesUnwrapper = setRolesUnwrapper;
exports.assignRoles = assignRoles;
const safe_master_copy_v2_json_1 = __importDefault(require("../contracts/safe_master_copy_v2.json"));
const safe_module_proxy_factory_v2_json_1 = __importDefault(require("../contracts/safe_module_proxy_factory_v2.json"));
const roles_v2_json_1 = __importDefault(require("../contracts/roles_v2.json"));
const util_1 = require("../utils/util");
const colors_1 = __importDefault(require("colors"));
const deploy_safe_v2_1 = require("./deploy-safe-v2");
// @ts-ignore
const hardhat_1 = require("hardhat");
const EIP2470_1 = require("./EIP2470");
const constants_1 = require("../utils/constants");
const roles_chain_config_1 = require("../utils/roles-chain-config");
const scope_access_controller_v2_1 = require("../whitelist/acs/scope-access-controller-v2");
//@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.
const ZeroHash = "0x0000000000000000000000000000000000000000000000000000000000000000";
const SaltZero = "0x0000000000000000000000000000000000000000000000000000000000000000";
async function deployRolesV2(owner, avatar, target, proxied, chainId, chainConfig) {
const [caller] = await hardhat_1.ethers.getSigners();
if (proxied) {
const rolesMaster = new hardhat_1.ethers.Contract(chainConfig.ROLES_MASTER_COPY_ADDR, roles_v2_json_1.default, caller);
const abiCoder = hardhat_1.ethers.AbiCoder.defaultAbiCoder();
const encoded = abiCoder.encode(["address", "address", "address"], [owner, avatar, target]);
const initParams = await rolesMaster.setUp.populateTransaction(encoded);
const safeModuleProxyFactory = new hardhat_1.ethers.Contract(chainConfig.SAFE_MODULE_PROXY_FACTORY_ADDR, safe_module_proxy_factory_v2_json_1.default, caller);
const predictedRolesAddress = await (0, util_1.predictRolesModAddress)(caller, owner, avatar, target, "v2");
console.log(`predicted roles address: ${predictedRolesAddress}`);
try {
const deployModTx = await safeModuleProxyFactory.deployModule(chainConfig.ROLES_MASTER_COPY_ADDR, initParams.data, util_1.SALT);
const txReceipt = await deployModTx.wait();
const txData = txReceipt.logs?.find((x) => x.fragment?.name === "ModuleProxyCreation");
const rolesModAddress = txData?.args?.proxy;
if (rolesModAddress !== predictedRolesAddress) {
throw new Error(`Roles mod address deployment unexpected, expected ${predictedRolesAddress}, actual: ${rolesModAddress}`);
}
console.info(colors_1.default.green(`✅ Roles was deployed via proxy factory to ${rolesModAddress}`));
return rolesModAddress;
}
catch (e) {
console.error(e);
throw new Error(`Roles mod address deployment failed: ${e}`);
}
}
// const Permissions = await ethers.getContractFactory("Permissions");
// const permissions = await Permissions.deploy();
const salt = ZeroHash;
const Packer = await hardhat_1.ethers.getContractFactory("Packer");
const packerLibraryAddress = await (0, EIP2470_1.deployViaFactory)(Packer.bytecode, salt, caller, "Packer");
const Integrity = await hardhat_1.ethers.getContractFactory("Integrity");
const integrityLibraryAddress = await (0, EIP2470_1.deployViaFactory)(Integrity.bytecode, salt, caller, "Integrity");
const Roles = await hardhat_1.ethers.getContractFactory("Roles", {
libraries: {
Integrity: integrityLibraryAddress,
Packer: packerLibraryAddress,
},
});
const roles = await Roles.deploy(owner, avatar, target);
await roles.connect(caller).waitForDeployment();
// const rolesAddress = await deployRolesV2(owner, avatar, target, proxied);
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);
//investment safe
const invSafe = new hardhat_1.ethers.Contract(safeAddr, safe_master_copy_v2_json_1.default, caller);
const enabled = await invSafe.isModuleEnabled(rolesAddr);
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.logs?.find((x) => x.fragment?.name === "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 hardhat_1.ethers.Contract(rolesAddr, roles_v2_json_1.default, caller);
const multisendOnRecord = await roles.multisend();
//If no MS on record, submit a tx to write one on record
if (multisendOnRecord === hardhat_1.ethers.constants.AddressZero) {
const setMsPopTx = await roles.setMultisend.populateTransaction(chainConfig.MULTISEND_ADDR);
const safe = new hardhat_1.ethers.Contract(safeAddr, safe_master_copy_v2_json_1.default, caller);
const signature = (0, util_1.getPreValidatedSignatures)(caller.address);
const setMsTx = 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);
await setMsTx.wait();
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}`);
}
}
async function setRolesUnwrapper(safeAddr, rolesAddr, chainConfig) {
const [caller] = await hardhat_1.ethers.getSigners();
const roles = new hardhat_1.ethers.Contract(rolesAddr, roles_v2_json_1.default, caller);
const setMsPopTx = await roles.setTransactionUnwrapper.populateTransaction(chainConfig.MULTISEND_ADDR, chainConfig.MULTISEND_SELECTOR, chainConfig.DEFAULT_UNWRAPPER_ADDR);
const safe = new hardhat_1.ethers.Contract(safeAddr, safe_master_copy_v2_json_1.default, caller);
const signature = (0, util_1.getPreValidatedSignatures)(caller.address);
const setMsTx = 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);
await setMsTx.wait();
console.info(colors_1.default.blue(`ℹ️ Transaction unwrapper has been set to: ${chainConfig.DEFAULT_UNWRAPPER_ADDR}`));
}
// 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 hardhat_1.ethers.Contract(rolesAddr, roles_v2_json_1.default, caller);
const signature = (0, util_1.getPreValidatedSignatures)(caller.address);
const acSafe = new hardhat_1.ethers.Contract(safeAddr, safe_master_copy_v2_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} (default)`));
}
// this will deploy the entire system from scratch, WITHOUT any investment manager permissions
const deployAccessControlSystemV2 = async (chainId, options, deployed) => {
// get chain config for multichain deploy
const chainConfig = (0, roles_chain_config_1.getChainConfig)(chainId, "v2");
//Deploy both safes
const accessControlSafeAddr = deployed?.acSafeAddr ||
(await (0, deploy_safe_v2_1.deploySafeV2)(chainConfig, constants_1.SALTS.safes.accessControl));
const investmentSafeAddr = deployed?.invSafeAddr ||
(await (0, deploy_safe_v2_1.deploySafeV2)(chainConfig, constants_1.SALTS.safes.investment));
// //Deploy and enable a Roles modifier on the investment safe
const invRolesAddr = deployed?.invRolesAddr ||
(await deployRolesV2(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);
await setRolesUnwrapper(accessControlSafeAddr, invRolesAddr, chainConfig);
//Deploy and enable a Roles modifier on the access control safe
const acRolesAddr = deployed?.acRolesAddr ||
(await deployRolesV2(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);
await setRolesUnwrapper(accessControlSafeAddr, acRolesAddr, chainConfig);
//Grant an access controller role to Security EOA's
await assignRoles(accessControlSafeAddr, acRolesAddr, options.securityEOAs, constants_1.SECURITY_ROLE_ID_V2, 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_v2_1.AccessControllerWhitelistV2(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_V2, chainConfig);
// // Add signers
await (0, deploy_safe_v2_1.addSafeSigners)(investmentSafeAddr, options.sysAdminAddresses, chainConfig);
await (0, deploy_safe_v2_1.addSafeSigners)(accessControlSafeAddr, options.sysAdminAddresses, chainConfig);
// //Remove the deployer address as owner and rewrite signing threshold
// await removeDeployerAsOwner(investmentSafeAddr, 0);
// await removeDeployerAsOwner(accessControlSafeAddr, 0);
return {
acSafe: accessControlSafeAddr,
invSafe: investmentSafeAddr,
invRoles: invRolesAddr,
acRoles: acRolesAddr,
};
};
exports.deployAccessControlSystemV2 = deployAccessControlSystemV2;