Bläddra i källkod

Chore(contract-manager) Executor Contract Type (#2838)

* added EvmExecutorContract.json

* temp_list

* deploy_evm_executor script

* deployed Executor on Ethereum Sepolia

* updat

* update

* update

* update

* chore(executor)-Deployed Executor on Ethereal Testnet

* update
Aditya Arora 4 månader sedan
förälder
incheckning
4bdfb72748

+ 26 - 1
contract_manager/scripts/common.ts

@@ -6,7 +6,11 @@ import { Contract } from "web3-eth-contract";
 import { InferredOptionType } from "yargs";
 import { PrivateKey, getDefaultDeploymentConfig } from "../src/core/base";
 import { EvmChain } from "../src/core/chains";
-import { EvmEntropyContract, EvmWormholeContract } from "../src/core/contracts";
+import {
+  EvmEntropyContract,
+  EvmExecutorContract,
+  EvmWormholeContract,
+} from "../src/core/contracts";
 
 export interface BaseDeployConfig {
   gasMultiplier: number;
@@ -237,6 +241,27 @@ export function findWormholeContract(
   }
 }
 
+/**
+ * Finds the executor contract for a given EVM chain.
+ * @param {EvmChain} chain The EVM chain to find the executor contract for.
+ * @returns If found, the executor contract for the given EVM chain. Else, undefined
+ */
+export function findExecutorContract(
+  chain: EvmChain,
+): EvmExecutorContract | undefined {
+  for (const contract of Object.values(DefaultStore.executor_contracts)) {
+    if (
+      contract instanceof EvmExecutorContract &&
+      contract.chain.getId() === chain.getId()
+    ) {
+      console.log(
+        `Found executor contract for ${chain.getId()} at ${contract.address}`,
+      );
+      return contract;
+    }
+  }
+}
+
 export interface DeployWormholeReceiverContractsConfig
   extends BaseDeployConfig {
   saveContract: boolean;

+ 3 - 41
contract_manager/scripts/deploy_evm_entropy_contracts.ts

@@ -9,7 +9,6 @@ import {
 } from "../src/core/contracts/evm";
 import {
   DeploymentType,
-  getDefaultDeploymentConfig,
   toDeploymentType,
   toPrivateKey,
 } from "../src/core/base";
@@ -22,6 +21,7 @@ import {
   topupAccountsIfNecessary,
   DefaultAddresses,
 } from "./common";
+import { getOrDeployExecutorContract } from "./deploy_evm_executor";
 
 interface DeploymentConfig extends BaseDeployConfig {
   type: DeploymentType;
@@ -44,44 +44,6 @@ const parser = yargs(hideBin(process.argv))
     },
   });
 
-async function deployExecutorContracts(
-  chain: EvmChain,
-  config: DeploymentConfig,
-  wormholeAddr: string,
-): Promise<string> {
-  const executorImplAddr = await deployIfNotCached(
-    CACHE_FILE,
-    chain,
-    config,
-    "ExecutorUpgradable",
-    [],
-  );
-
-  // Craft the init data for the proxy contract
-  const { governanceDataSource } = getDefaultDeploymentConfig(config.type);
-
-  const executorImplContract = getWeb3Contract(
-    config.jsonOutputDir,
-    "ExecutorUpgradable",
-    executorImplAddr,
-  );
-
-  const executorInitData = executorImplContract.methods
-    .initialize(
-      wormholeAddr,
-      0, // lastExecutedSequence,
-      chain.getWormholeChainId(),
-      governanceDataSource.emitterChain,
-      `0x${governanceDataSource.emitterAddress}`,
-    )
-    .encodeABI();
-
-  return await deployIfNotCached(CACHE_FILE, chain, config, "ERC1967Proxy", [
-    executorImplAddr,
-    executorInitData,
-  ]);
-}
-
 async function deployEntropyContracts(
   chain: EvmChain,
   config: DeploymentConfig,
@@ -166,7 +128,7 @@ async function main() {
 
   console.log(`Deploying entropy contracts on ${chain.getId()}...`);
 
-  const executorAddr = await deployExecutorContracts(
+  const executorContract = await getOrDeployExecutorContract(
     chain,
     deploymentConfig,
     wormholeContract.address,
@@ -174,7 +136,7 @@ async function main() {
   const entropyAddr = await deployEntropyContracts(
     chain,
     deploymentConfig,
-    executorAddr,
+    executorContract.address,
   );
 
   if (deploymentConfig.saveContract) {

+ 147 - 0
contract_manager/scripts/deploy_evm_executor.ts

@@ -0,0 +1,147 @@
+import yargs from "yargs";
+import { hideBin } from "yargs/helpers";
+import { EvmChain } from "../src/core/chains";
+import {
+  BaseDeployConfig,
+  COMMON_DEPLOY_OPTIONS,
+  deployIfNotCached,
+  findExecutorContract,
+  getOrDeployWormholeContract,
+  getWeb3Contract,
+} from "./common";
+import {
+  DeploymentType,
+  getDefaultDeploymentConfig,
+  toDeploymentType,
+  toPrivateKey,
+} from "../src/core/base";
+import { DefaultStore } from "../src/node/utils/store";
+import { EvmExecutorContract } from "../src/core/contracts/evm";
+
+const CACHE_FILE = ".cache-deploy-evm-executor";
+
+const parser = yargs(hideBin(process.argv))
+  .scriptName("deploy_evm_executor.ts")
+  .usage(
+    "Usage: $0 --std-output-dir <path/to/std-output-dir/> --private-key <private-key> --chain <chain>",
+  )
+  .options({
+    ...COMMON_DEPLOY_OPTIONS,
+    chain: {
+      type: "string",
+      demandOption: true,
+      desc: "Chain to upload the contract on. Can be one of the evm chains available in the store",
+    },
+  });
+
+interface DeploymentConfig extends BaseDeployConfig {
+  type: DeploymentType;
+  saveContract: boolean;
+}
+
+export async function getOrDeployExecutorContract(
+  chain: EvmChain,
+  config: DeploymentConfig,
+  wormholeAddr: string,
+): Promise<EvmExecutorContract> {
+  return (
+    findExecutorContract(chain) ??
+    (await deployExecutorContracts(chain, config, wormholeAddr))
+  );
+}
+
+/**
+ * Deploys the executor contracts for a given EVM chain.
+ * @param {EvmChain} chain The EVM chain to deploy the executor contracts for.
+ * @param {DeploymentConfig} config The deployment configuration.
+ * @param {string} wormholeAddr The address of the wormhole contract.
+ * @returns {Promise<string>} The address of the deployed executor contract.
+ */
+export async function deployExecutorContracts(
+  chain: EvmChain,
+  config: DeploymentConfig,
+  wormholeAddr: string,
+): Promise<EvmExecutorContract> {
+  const executorImplAddr = await deployIfNotCached(
+    CACHE_FILE,
+    chain,
+    config,
+    "ExecutorUpgradable",
+    [],
+  );
+
+  // Craft the init data for the proxy contract
+  const { governanceDataSource } = getDefaultDeploymentConfig(config.type);
+
+  const executorImplContract = getWeb3Contract(
+    config.jsonOutputDir,
+    "ExecutorUpgradable",
+    executorImplAddr,
+  );
+
+  const executorInitData = executorImplContract.methods
+    .initialize(
+      wormholeAddr,
+      0, // lastExecutedSequence,
+      chain.getWormholeChainId(),
+      governanceDataSource.emitterChain,
+      `0x${governanceDataSource.emitterAddress}`,
+    )
+    .encodeABI();
+
+  const executorAddr = await deployIfNotCached(
+    CACHE_FILE,
+    chain,
+    config,
+    "ERC1967Proxy",
+    [executorImplAddr, executorInitData],
+  );
+
+  return new EvmExecutorContract(chain, executorAddr);
+}
+
+export async function main() {
+  const argv = await parser.argv;
+
+  const chain = DefaultStore.getChainOrThrow(argv.chain, EvmChain);
+
+  const deploymentConfig: DeploymentConfig = {
+    type: toDeploymentType(argv.deploymentType),
+    gasMultiplier: argv.gasMultiplier,
+    gasPriceMultiplier: argv.gasPriceMultiplier,
+    privateKey: toPrivateKey(argv.privateKey),
+    jsonOutputDir: argv.stdOutputDir,
+    saveContract: argv.saveContract,
+  };
+
+  const wormholeContract = await getOrDeployWormholeContract(
+    chain,
+    deploymentConfig,
+    CACHE_FILE,
+  );
+
+  console.log(
+    `Deployment config: ${JSON.stringify(deploymentConfig, null, 2)}\n`,
+  );
+
+  console.log(`Deploying executor contracts on ${chain.getId()}...`);
+
+  const executorContract = await getOrDeployExecutorContract(
+    chain,
+    deploymentConfig,
+    wormholeContract.address,
+  );
+
+  if (deploymentConfig.saveContract) {
+    console.log("Saving the contract in the store...");
+    DefaultStore.executor_contracts[executorContract.getId()] =
+      executorContract;
+    DefaultStore.saveAllContracts();
+  }
+
+  console.log(
+    `✅ Executor contract on ${chain.getId()} at ${executorContract.address}\n\n`,
+  );
+}
+
+main();

+ 29 - 2
contract_manager/src/core/contracts/evm.ts

@@ -412,16 +412,32 @@ export class EvmEntropyContract extends Storable {
   }
 }
 
-export class EvmExecutorContract {
+export class EvmExecutorContract extends Storable {
+  static type = "EvmExecutorContract";
+
   constructor(
     public chain: EvmChain,
     public address: string,
-  ) {}
+  ) {
+    super();
+  }
 
   getId(): string {
     return `${this.chain.getId()}_${this.address}`;
   }
 
+  getType(): string {
+    return EvmExecutorContract.type;
+  }
+
+  toJson() {
+    return {
+      chain: this.chain.getId(),
+      address: this.address,
+      type: EvmExecutorContract.type,
+    };
+  }
+
   async getWormholeContract(): Promise<EvmWormholeContract> {
     const web3 = this.chain.getWeb3();
     //Unfortunately, there is no public method to get the wormhole address
@@ -431,6 +447,17 @@ export class EvmExecutorContract {
     return new EvmWormholeContract(this.chain, address);
   }
 
+  static fromJson(
+    chain: Chain,
+    parsed: { type: string; address: string },
+  ): EvmExecutorContract {
+    if (parsed.type !== EvmExecutorContract.type)
+      throw new Error("Invalid type");
+    if (!(chain instanceof EvmChain))
+      throw new Error(`Wrong chain type ${chain}`);
+    return new EvmExecutorContract(chain, parsed.address);
+  }
+
   getContract() {
     const web3 = this.chain.getWeb3();
     return new web3.eth.Contract(EXECUTOR_ABI, this.address);

+ 8 - 1
contract_manager/src/node/utils/store.ts

@@ -29,6 +29,7 @@ import {
   IotaWormholeContract,
   IotaPriceFeedContract,
   EvmPulseContract,
+  EvmExecutorContract,
 } from "../../core/contracts";
 import { Token } from "../../core/token";
 import { PriceFeedContract, Storable } from "../../core/base";
@@ -46,6 +47,7 @@ import {
 export class Store {
   public chains: Record<string, Chain> = { global: new GlobalChain() };
   public contracts: Record<string, PriceFeedContract> = {};
+  public executor_contracts: Record<string, EvmExecutorContract> = {};
   public entropy_contracts: Record<string, EvmEntropyContract> = {};
   public pulse_contracts: Record<string, EvmPulseContract> = {};
   public wormhole_contracts: Record<string, WormholeContract> = {};
@@ -118,6 +120,7 @@ export class Store {
     const contracts: Storable[] = Object.values(this.contracts);
     contracts.push(...Object.values(this.entropy_contracts));
     contracts.push(...Object.values(this.wormhole_contracts));
+    contracts.push(...Object.values(this.executor_contracts));
     for (const contract of contracts) {
       if (!contractsByType[contract.getType()]) {
         contractsByType[contract.getType()] = [];
@@ -167,6 +170,7 @@ export class Store {
       [AptosWormholeContract.type]: AptosWormholeContract,
       [EvmEntropyContract.type]: EvmEntropyContract,
       [EvmWormholeContract.type]: EvmWormholeContract,
+      [EvmExecutorContract.type]: EvmExecutorContract,
       [FuelPriceFeedContract.type]: FuelPriceFeedContract,
       [FuelWormholeContract.type]: FuelWormholeContract,
       [StarknetPriceFeedContract.type]: StarknetPriceFeedContract,
@@ -192,7 +196,8 @@ export class Store {
         if (
           this.contracts[chainContract.getId()] ||
           this.entropy_contracts[chainContract.getId()] ||
-          this.wormhole_contracts[chainContract.getId()]
+          this.wormhole_contracts[chainContract.getId()] ||
+          this.executor_contracts[chainContract.getId()]
         )
           throw new Error(
             `Multiple contracts with id ${chainContract.getId()} found`,
@@ -201,6 +206,8 @@ export class Store {
           this.entropy_contracts[chainContract.getId()] = chainContract;
         } else if (chainContract instanceof WormholeContract) {
           this.wormhole_contracts[chainContract.getId()] = chainContract;
+        } else if (chainContract instanceof EvmExecutorContract) {
+          this.executor_contracts[chainContract.getId()] = chainContract;
         } else {
           this.contracts[chainContract.getId()] = chainContract;
         }

+ 8 - 1
contract_manager/store/chains/EvmChains.json

@@ -401,7 +401,7 @@
   {
     "id": "sepolia",
     "mainnet": false,
-    "rpcUrl": "https://eth-sepolia.blastapi.io/$ENV_BLAST_API_KEY",
+    "rpcUrl": "https://eth-sepolia.public.blastapi.io",
     "networkId": 11155111,
     "type": "EvmChain"
   },
@@ -1265,5 +1265,12 @@
     "rpcUrl": "https://k8s.testnet.json-rpc.injective.network/",
     "networkId": 1439,
     "type": "EvmChain"
+  },
+  {
+    "id": "ethereal_testnet",
+    "mainnet": false,
+    "rpcUrl": "https://rpc-ethereal-testnet.t.conduit.xyz",
+    "networkId": 657468,
+    "type": "EvmChain"
   }
 ]

+ 1 - 1
contract_manager/store/contracts/EvmEntropyContracts.json

@@ -189,4 +189,4 @@
     "address": "0x23f0e8FAeE7bbb405E7A7C3d60138FCfd43d7509",
     "type": "EvmEntropyContract"
   }
-]
+]

+ 202 - 0
contract_manager/store/contracts/EvmExecutorContracts.json

@@ -0,0 +1,202 @@
+[
+  {
+    "chain": "arbitrum_sepolia",
+    "address": "0x9DF02366A266D79DA5E978aDBEe8B8502117ee1a",
+    "type": "EvmExecutorContract"
+  },
+  {
+    "chain": "blast_s2_testnet",
+    "address": "0x5f3c61944CEb01B3eAef861251Fb1E0f14b848fb",
+    "type": "EvmExecutorContract"
+  },
+  {
+    "chain": "base_sepolia",
+    "address": "0x23f0e8FAeE7bbb405E7A7C3d60138FCfd43d7509",
+    "type": "EvmExecutorContract"
+  },
+  {
+    "chain": "optimism_sepolia",
+    "address": "0x549Ebba8036Ab746611B4fFA1423eb0A4Df61440",
+    "type": "EvmExecutorContract"
+  },
+  {
+    "chain": "zetachain_testnet",
+    "address": "0xfA25E653b44586dBbe27eE9d252192F0e4956683",
+    "type": "EvmExecutorContract"
+  },
+  {
+    "chain": "etherlink_testnet",
+    "address": "0x98046Bd286715D3B0BC227Dd7a956b83D8978603",
+    "type": "EvmExecutorContract"
+  },
+  {
+    "chain": "sei_evm_testnet",
+    "address": "0x5744Cbf430D99456a0A8771208b674F27f8EF0Fb",
+    "type": "EvmExecutorContract"
+  },
+  {
+    "chain": "kaia_testnet",
+    "address": "0x5744Cbf430D99456a0A8771208b674F27f8EF0Fb",
+    "type": "EvmExecutorContract"
+  },
+  {
+    "chain": "b3_testnet",
+    "address": "0x87047526937246727E4869C5f76A347160e08672",
+    "type": "EvmExecutorContract"
+  },
+  {
+    "chain": "apechain_testnet",
+    "address": "0x98046Bd286715D3B0BC227Dd7a956b83D8978603",
+    "type": "EvmExecutorContract"
+  },
+  {
+    "chain": "soneium_minato_testnet",
+    "address": "0x98046Bd286715D3B0BC227Dd7a956b83D8978603",
+    "type": "EvmExecutorContract"
+  },
+  {
+    "chain": "sanko_testnet",
+    "address": "0x87047526937246727E4869C5f76A347160e08672",
+    "type": "EvmExecutorContract"
+  },
+  {
+    "chain": "abstract_testnet",
+    "address": "0x0d8B7FE8598e2BcEcAf1E60f51B4b8B8E4453BA5",
+    "type": "EvmExecutorContract"
+  },
+  {
+    "chain": "sonic_blaze_testnet",
+    "address": "0x8D254a21b3C86D32F7179855531CE99164721933",
+    "type": "EvmExecutorContract"
+  },
+  {
+    "chain": "unichain_sepolia",
+    "address": "0x36825bf3Fbdf5a29E2d5148bfe7Dcf7B5639e320",
+    "type": "EvmExecutorContract"
+  },
+  {
+    "chain": "tabi_testnet",
+    "address": "0x8D254a21b3C86D32F7179855531CE99164721933",
+    "type": "EvmExecutorContract"
+  },
+  {
+    "chain": "monad_testnet",
+    "address": "0x5744Cbf430D99456a0A8771208b674F27f8EF0Fb",
+    "type": "EvmExecutorContract"
+  },
+  {
+    "chain": "story_testnet",
+    "address": "0x87047526937246727E4869C5f76A347160e08672",
+    "type": "EvmExecutorContract"
+  },
+  {
+    "chain": "berachain_bepolia",
+    "address": "0x5744Cbf430D99456a0A8771208b674F27f8EF0Fb",
+    "type": "EvmExecutorContract"
+  },
+  {
+    "chain": "hyperevm_testnet",
+    "address": "0x98046Bd286715D3B0BC227Dd7a956b83D8978603",
+    "type": "EvmExecutorContract"
+  },
+  {
+    "chain": "arbitrum",
+    "address": "0x24654078A8E043e8985D962a5100CDfA2026f92C",
+    "type": "EvmExecutorContract"
+  },
+  {
+    "chain": "optimism",
+    "address": "0x6E7D74FA7d5c90FEF9F0512987605a6d546181Bb",
+    "type": "EvmExecutorContract"
+  },
+  {
+    "chain": "blast",
+    "address": "0x87047526937246727E4869C5f76A347160e08672",
+    "type": "EvmExecutorContract"
+  },
+  {
+    "chain": "zetachain",
+    "address": "0x5744Cbf430D99456a0A8771208b674F27f8EF0Fb",
+    "type": "EvmExecutorContract"
+  },
+  {
+    "chain": "base",
+    "address": "0xf0a1b566B55e0A0CB5BeF52Eb2a57142617Bee67",
+    "type": "EvmExecutorContract"
+  },
+  {
+    "chain": "sei_evm_mainnet",
+    "address": "0x74f09cb3c7e2A01865f424FD14F6dc9A14E3e94E",
+    "type": "EvmExecutorContract"
+  },
+  {
+    "chain": "etherlink",
+    "address": "0x98046Bd286715D3B0BC227Dd7a956b83D8978603",
+    "type": "EvmExecutorContract"
+  },
+  {
+    "chain": "kaia",
+    "address": "0x5744Cbf430D99456a0A8771208b674F27f8EF0Fb",
+    "type": "EvmExecutorContract"
+  },
+  {
+    "chain": "b3_mainnet",
+    "address": "0x87047526937246727E4869C5f76A347160e08672",
+    "type": "EvmExecutorContract"
+  },
+  {
+    "chain": "apechain_mainnet",
+    "address": "0x5744Cbf430D99456a0A8771208b674F27f8EF0Fb",
+    "type": "EvmExecutorContract"
+  },
+  {
+    "chain": "sanko",
+    "address": "0x87047526937246727E4869C5f76A347160e08672",
+    "type": "EvmExecutorContract"
+  },
+  {
+    "chain": "unichain",
+    "address": "0x5744Cbf430D99456a0A8771208b674F27f8EF0Fb",
+    "type": "EvmExecutorContract"
+  },
+  {
+    "chain": "abstract",
+    "address": "0x6650bBd680A4cAdcB30AFfa0Ec78ca0811Db0B85",
+    "type": "EvmExecutorContract"
+  },
+  {
+    "chain": "fantom_sonic_mainnet",
+    "address": "0x5744Cbf430D99456a0A8771208b674F27f8EF0Fb",
+    "type": "EvmExecutorContract"
+  },
+  {
+    "chain": "berachain_mainnet",
+    "address": "0x5744Cbf430D99456a0A8771208b674F27f8EF0Fb",
+    "type": "EvmExecutorContract"
+  },
+  {
+    "chain": "hyperevm",
+    "address": "0x5D289Ad1CE59fCC25b6892e7A303dfFf3a9f7167",
+    "type": "EvmExecutorContract"
+  },
+  {
+    "chain": "story",
+    "address": "0x6E7D74FA7d5c90FEF9F0512987605a6d546181Bb",
+    "type": "EvmExecutorContract"
+  },
+  {
+    "chain": "soneium",
+    "address": "0x41c9e39574F40Ad34c79f1C99B66A45eFB830d4c",
+    "type": "EvmExecutorContract"
+  },
+  {
+    "chain": "sepolia",
+    "address": "0xB0D4a9640aDdd415551B6A4fe75403c9f73A7C49",
+    "type": "EvmExecutorContract"
+  },
+  {
+    "chain": "ethereal_testnet",
+    "address": "0xD458261E832415CFd3BAE5E416FdF3230ce6F134",
+    "type": "EvmExecutorContract"
+  }
+]

+ 5 - 0
contract_manager/store/contracts/EvmWormholeContracts.json

@@ -838,5 +838,10 @@
     "chain": "injective_evm_testnet",
     "address": "0x23f0e8FAeE7bbb405E7A7C3d60138FCfd43d7509",
     "type": "EvmWormholeContract"
+  },
+  {
+    "chain": "ethereal_testnet",
+    "address": "0x74f09cb3c7e2A01865f424FD14F6dc9A14E3e94E",
+    "type": "EvmWormholeContract"
   }
 ]

+ 1 - 0
governance/xc_admin/packages/xc_admin_common/src/chains.ts

@@ -246,6 +246,7 @@ export const RECEIVER_CHAINS = {
   mezo_testnet: 50124,
   hemi_testnet: 50125,
   injective_evm_testnet: 50126,
+  ethereal_testnet: 50127,
 };
 
 // If there is any overlapping value the receiver chain will replace the wormhole

+ 36 - 0
lazer/contracts/evm/script/PythLazerChangeOwnership.s.sol

@@ -0,0 +1,36 @@
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.13;
+
+import {Script, console} from "forge-std/Script.sol";
+import {PythLazer} from "../src/PythLazer.sol";
+import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
+import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol";
+
+contract PythLazerChangeOwnership is Script {
+    address public constant LAZER_PROXY_ADDRESS =
+        address(0xACeA761c27A909d4D3895128EBe6370FDE2dF481);
+
+    uint256 public OLD_OWNER_PRIVATE_KEY = vm.envUint("DEPLOYER_PRIVATE_KEY");
+    address public OLD_OWNER = vm.addr(OLD_OWNER_PRIVATE_KEY);
+    // EVM Executor Contract
+    address public NEW_OWNER = vm.envAddress("NEW_OWNER");
+
+    function run() public {
+        console.log("Old owner: %s", OLD_OWNER);
+        console.log("New owner: %s", NEW_OWNER);
+        console.log("Lazer proxy address: %s", LAZER_PROXY_ADDRESS);
+        console.log("Lazer owner: %s", PythLazer(LAZER_PROXY_ADDRESS).owner());
+        console.log("Moving ownership from %s to %s", OLD_OWNER, NEW_OWNER);
+
+        PythLazer lazer = PythLazer(LAZER_PROXY_ADDRESS);
+        vm.startBroadcast(OLD_OWNER_PRIVATE_KEY);
+        require(lazer.owner() == OLD_OWNER, "Old owner mismatch");
+        lazer.transferOwnership(NEW_OWNER);
+        console.log("Ownership transferred");
+        console.log(
+            "New Lazer owner: %s",
+            PythLazer(LAZER_PROXY_ADDRESS).owner()
+        );
+        vm.stopBroadcast();
+    }
+}