UNPKG

tenderly-wizard-v6

Version:

A tool for managing virtual testnets using Tenderly

440 lines (398 loc) 12.4 kB
import SAFE_MASTER_COPY_BASE_ABI from "../contracts/safe_master_copy_v2.json"; import SAFE_MODULE_PROXY_FACTORY_ABI from "../contracts/safe_module_proxy_factory_v2.json"; import ROLES_V2_MASTER_COPY_ABI from "../contracts/roles_v2.json"; import { createMultisendTx, getPreValidatedSignatures, predictRolesModAddress, SALT, } from "../utils/util"; import colors from "colors"; import { deploySafeV2, addSafeSigners } from "./deploy-safe-v2"; // @ts-ignore import { ethers } from "hardhat"; import { ChainId } from "zodiac-roles-sdk"; import { deployViaFactory } from "./EIP2470"; import { MANAGER_ROLE_ID_V2, SAFE_OPERATION_DELEGATECALL, SALTS, SECURITY_ROLE_ID_V2, tx, } from "../utils/constants"; import { ChainConfig, RolesVersion } from "../utils/types"; import { getChainConfig } from "../utils/roles-chain-config"; import { AccessControllerWhitelistV2 } from "../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"; export async function deployRolesV2( owner: string, avatar: string, target: string, proxied: boolean, chainId: ChainId, chainConfig: ChainConfig["v2"] ) { const [caller] = await ethers.getSigners(); if (proxied) { const rolesMaster = new ethers.Contract( chainConfig.ROLES_MASTER_COPY_ADDR, ROLES_V2_MASTER_COPY_ABI, caller ); const abiCoder = ethers.AbiCoder.defaultAbiCoder(); const encoded = abiCoder.encode( ["address", "address", "address"], [owner, avatar, target] ); const initParams = await rolesMaster.setUp.populateTransaction(encoded); const safeModuleProxyFactory = new ethers.Contract( chainConfig.SAFE_MODULE_PROXY_FACTORY_ADDR, SAFE_MODULE_PROXY_FACTORY_ABI, caller ); const predictedRolesAddress = await 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, SALT ); const txReceipt = await deployModTx.wait(); const txData = txReceipt.logs?.find( (x: any) => 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.green( `✅ Roles was deployed via proxy factory to ${rolesModAddress}` ) ); return rolesModAddress; } catch (e: any) { 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 ethers.getContractFactory("Packer"); const packerLibraryAddress = await deployViaFactory( Packer.bytecode, salt, caller, "Packer" ); const Integrity = await ethers.getContractFactory("Integrity"); const integrityLibraryAddress = await deployViaFactory( Integrity.bytecode, salt, caller, "Integrity" ); const Roles = await 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 export async function enableRolesModifier(safeAddr: string, rolesAddr: string) { const [caller] = await ethers.getSigners(); const signature = getPreValidatedSignatures(caller.address); //investment safe const invSafe = new ethers.Contract( safeAddr, SAFE_MASTER_COPY_BASE_ABI, caller ); const enabled = await invSafe.isModuleEnabled(rolesAddr); if (!enabled) { const enable = await invSafe.enableModule.populateTransaction(rolesAddr); const enableTx = await invSafe.execTransaction( safeAddr, tx.zeroValue, enable.data ?? "", tx.operation, tx.avatarTxGas, tx.baseGas, tx.gasPrice, tx.gasToken, tx.refundReceiver, signature ); const txReceipt = await enableTx.wait(); const txData = txReceipt.logs?.find( (x: any) => x.fragment?.name === "EnabledModule" ); const moduleEnabledFromEvent = txData?.args?.module; console.info( colors.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 export async function setRolesMultisend( safeAddr: string, rolesAddr: string, chainConfig: ChainConfig["v2"] ) { const [caller] = await ethers.getSigners(); const roles = new ethers.Contract( rolesAddr, ROLES_V2_MASTER_COPY_ABI, caller ); const multisendOnRecord = await roles.multisend(); //If no MS on record, submit a tx to write one on record if (multisendOnRecord === ethers.constants.AddressZero) { const setMsPopTx = await roles.setMultisend.populateTransaction( chainConfig.MULTISEND_ADDR ); const safe = new ethers.Contract( safeAddr, SAFE_MASTER_COPY_BASE_ABI, caller ); const signature = getPreValidatedSignatures(caller.address); const setMsTx = await safe.execTransaction( rolesAddr, tx.zeroValue, setMsPopTx.data, tx.operation, tx.avatarTxGas, tx.baseGas, tx.gasPrice, tx.gasToken, tx.refundReceiver, signature ); await setMsTx.wait(); console.info( colors.blue( `ℹ️ Multisend has been set to: ${chainConfig.MULTISEND_ADDR}` ) ); } else { console.info( `Multisend has already been previously set to: ${multisendOnRecord}` ); } } export async function setRolesUnwrapper( safeAddr: string, rolesAddr: string, chainConfig: ChainConfig["v2"] ) { const [caller] = await ethers.getSigners(); const roles = new ethers.Contract( rolesAddr, ROLES_V2_MASTER_COPY_ABI, caller ); const setMsPopTx = await roles.setTransactionUnwrapper.populateTransaction( chainConfig.MULTISEND_ADDR, chainConfig.MULTISEND_SELECTOR, chainConfig.DEFAULT_UNWRAPPER_ADDR ); const safe = new ethers.Contract(safeAddr, SAFE_MASTER_COPY_BASE_ABI, caller); const signature = getPreValidatedSignatures(caller.address); const setMsTx = await safe.execTransaction( rolesAddr, tx.zeroValue, setMsPopTx.data, tx.operation, tx.avatarTxGas, tx.baseGas, tx.gasPrice, tx.gasToken, tx.refundReceiver, signature ); await setMsTx.wait(); console.info( colors.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 export async function assignRoles( safeAddr: string, rolesAddr: string, memberAddrs: string[], roleId: `0x${string}`, chainConfig: ChainConfig["v2"] ) { const [caller] = await ethers.getSigners(); // assign manager a role (becomes a member of role:manager_role_id) const roles = new ethers.Contract( rolesAddr, ROLES_V2_MASTER_COPY_ABI, caller ); const signature = getPreValidatedSignatures(caller.address); const acSafe = new ethers.Contract( safeAddr, SAFE_MASTER_COPY_BASE_ABI, caller ); const assignRolesPopTx = await Promise.all( memberAddrs.map(async memberAddr => { return await roles.assignRoles.populateTransaction( memberAddr, [roleId], [true] ); }) ); const metaTxs = createMultisendTx( assignRolesPopTx, chainConfig.MULTISEND_ADDR ); await acSafe.execTransaction( chainConfig.MULTISEND_ADDR, tx.zeroValue, metaTxs.data, SAFE_OPERATION_DELEGATECALL, tx.avatarTxGas, tx.baseGas, tx.gasPrice, tx.gasToken, tx.refundReceiver, signature ); console.info( colors.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 export const deployAccessControlSystemV2 = async ( chainId: ChainId, options: { proxied: boolean; sysAdminAddresses: string[]; acSafeThreshold: number; invSafeThreshold: number; securityEOAs: string[]; managerEOAs: string[]; }, deployed?: { acSafeAddr: string | null; invSafeAddr: string | null; invRolesAddr: string | null; acRolesAddr: string | null; } ) => { // get chain config for multichain deploy const chainConfig = getChainConfig(chainId, "v2"); //Deploy both safes const accessControlSafeAddr = deployed?.acSafeAddr || (await deploySafeV2(chainConfig, SALTS.safes.accessControl)); const investmentSafeAddr = deployed?.invSafeAddr || (await deploySafeV2(chainConfig, 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, SECURITY_ROLE_ID_V2 as `0x${string}`, chainConfig ); // Populate this role for Security so they can call whitelisting related functions on investment roles const [caller] = await ethers.getSigners(); const accessControllerWhitelist = new 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, MANAGER_ROLE_ID_V2 as `0x${string}`, chainConfig ); // // Add signers await addSafeSigners( investmentSafeAddr, options.sysAdminAddresses, chainConfig ); await 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, }; };