UNPKG

tenderly-wizard-v6

Version:

A tool for managing virtual testnets using Tenderly

119 lines (108 loc) 4.18 kB
import SAFE_MASTER_COPY_V2_ABI from "../../contracts/safe_master_copy_v2.json"; import ROLES_V2_ABI from "../../contracts/roles_v2.json"; import { Whitelist } from "../whitelist-class"; import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"; import { LedgerSigner } from "@anders-t/ethers-ledger"; // @ts-ignore import { ethers } from "hardhat"; import { createMultisendTx, getPreValidatedSignatures, scopeTargetsV2, } from "../../utils/util"; import { GAS_LIMIT, SAFE_OPERATION_DELEGATECALL, SECURITY_ROLE_ID_V2, tx, } from "../../utils/constants"; import { ChainConfig } from "../../utils/types"; import { ChainId } from "zodiac-roles-sdk/."; import { getChainConfig } from "../../utils/roles-chain-config"; import config from "../../env-config"; import { RolesV2 } from "@gnosis-guild/zodiac"; import { Interface, Contract, hexlify, ContractRunner, Provider } from "ethers"; // Helper to ensure hex string type function asHexString(value: string): `0x${string}` { return value as `0x${string}`; } // Make LedgerSigner compatible with ContractRunner by ensuring it has a provider type CompatibleSigner = SignerWithAddress | (LedgerSigner & { provider: Provider | null }); const ROLES_FUNCTIONS_ALLOWED = [ "revokeTarget", "scopeTarget", "allowFunction", "revokeFunction", "scopeFunction", "allowTarget", ] as const; // Create an ethers Interface from the ABI to get function signatures const rolesInterface = new Interface(ROLES_V2_ABI); // this whitelisting class is used in the roles deployment so that security has the ability to scope functions export class AccessControllerWhitelistV2 extends Whitelist { chainConfig: ChainConfig["v2"]; constructor(acRolesAddr: string, caller: CompatibleSigner) { super(acRolesAddr, "v2", caller); const chainId = config.TENDERLY_FORK_ID; this.chainConfig = getChainConfig(chainId as ChainId, "v2"); } // Allow the security team to call all the functions listed in `ROLES_FUNCTIONS_ALLOWED`on the investment roles modifier async getFullScope(invRolesAddr: string) { // Nested roles usage here can be confusing. The invRoles is the target that is scoped on the acRoles // Must scopeTarget before roles.allowFunction can be called const roleId = asHexString(hexlify(SECURITY_ROLE_ID_V2).padEnd(66, '0')); const getScopedTargetTxs = await scopeTargetsV2( [invRolesAddr], roleId, this.roles as Contract & RolesV2 ); // Get the sighashs that need to be whitelisted const functionSigs = ROLES_FUNCTIONS_ALLOWED.map(func => { // Find the function fragment in the ABI const fragment = rolesInterface.getFunction(func); if (!fragment) { throw new Error(`Function ${func} not found in ABI`); } return fragment.selector; }); const getScopedAllowFunctionTxs = await this.scopeAllowFunctionsV2( invRolesAddr, functionSigs, roleId ); const txs = [...getScopedTargetTxs, ...getScopedAllowFunctionTxs]; return createMultisendTx(txs, this.chainConfig.MULTISEND_ADDR); } async build(invRolesAddr: string, accessControlSafeAddr: string) { const metaTx = await this.getFullScope(invRolesAddr); // Use any for now since the contract type is complex const acSafe = new Contract( accessControlSafeAddr, SAFE_MASTER_COPY_V2_ABI, this.caller as any ) as any; const signature = getPreValidatedSignatures(await this.caller.getAddress()); return await acSafe.execTransaction.populateTransaction( this.chainConfig.MULTISEND_ADDR, tx.zeroValue, metaTx.data, SAFE_OPERATION_DELEGATECALL.toString(), tx.avatarTxGas, tx.baseGas, tx.gasPrice, tx.gasToken, tx.refundReceiver, signature ); } async execute(invRolesAddr: string, accessControlSafeAddr: string) { const populatedTx = await this.build(invRolesAddr, accessControlSafeAddr); const tx = await this.caller.sendTransaction({ ...populatedTx, gasLimit: GAS_LIMIT, }); console.log( "Successfully executed Security's access control related whitelisting" ); } }