Repository: moondance-labs/tanssi-symbiotic
Pull request #89: [MD-970] Testing upgrades in fork + [MD-1086] Fix fork tests in Sepolia
Author: login: steven2308
Labels: name: D3-trivial
description: PR contains trivial changes in a runtime directory that do not require an audit
name: not-breaking
description: Does not need to be mentioned in breaking changes
Created: 2025-05-08T16:48:41Z | Merged: 2025-05-14T18:53:04Z

There is a line out of coverage as the result of fixing the fork test in sepolia, I'm working on that. The problem is that there is no Default Collateral Factory on Sepolia, so in that specific case we need to deploy one just like in local test chain, but this is only used on fork test, which are ignored on coverage.

For the upgrade test I'm using a bit of a hack. I'm mocking Benny's key to give admin permissions to a test account so it can run the upgrades on real deployed contracts in Sepolia. An alternative could be to directly modify storage to give admin rights, but I feel current approach is cleaner.


Diff:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index e1fc808d..eae3e36b 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -61,7 +61,7 @@ jobs:
     env:
       # Safe to put here since they are default anvil private keys
       ETH_RPC_URL: "https://eth.llamarpc.com"
-      HOLESKY_RPC_URL: ${{ secrets.ETH_HOLESKY_URL }}
+      SEPOLIA_RPC_URL: ${{ secrets.ETH_SEPOLIA_URL }}
       OWNER_PRIVATE_KEY: "0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6"
       NETWORK_PRIVATE_KEY: "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d"
       RESOLVER_PRIVATE_KEY: "0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a"
@@ -100,18 +100,18 @@ jobs:
 
       - name: Run Forge tests
         run: |
-          forge test --nmp test/fork/* -vvv
+          forge test --nmp "test/fork/*" -vvv
         id: test
 
       - name: Run Forge fork tests
         run: |
-          forge test --fork-url $HOLESKY_RPC_URL --mp test/fork/* -vvvv
+          forge test --fork-url $SEPOLIA_RPC_URL --mp "test/fork/*" -vvv
         id: test-fork
 
       - name: Run Forge coverage
         run: |
           mkdir -p coverage
-          forge coverage --report lcov -r coverage/current.lcov --nmp test/fork/*
+          forge coverage --report lcov -r coverage/current.lcov --nmp "test/fork/*"
         id: coverage
       - name: Retrieve master coverage
         run: |
diff --git a/Makefile b/Makefile
index a6a2f32a..372ca485 100644
--- a/Makefile
+++ b/Makefile
@@ -41,15 +41,15 @@ test :; forge test
 
 testv :; forge test -vvvv
 
-coverage :; forge coverage --nmp test/fork/*
+coverage :; forge coverage --nmp "test/fork/*"
 
-coverage-fork :; forge coverage --mp test/fork/* --fork-url ${FORK_RPC_URL}
+coverage-fork :; forge coverage --mp "test/fork/*" --fork-url ${FORK_RPC_URL}
 
-dcoverage :; forge coverage --nmp test/fork/* --report debug > coverage.txt
+dcoverage :; forge coverage --nmp "test/fork/*" --report debug > coverage.txt
 
-hcoverage:; forge coverage  --nmp test/fork/* --report lcov && genhtml lcov.info -o report --branch-coverage
+hcoverage:; forge coverage  --nmp "test/fork/*" --report lcov && genhtml lcov.info -o report --branch-coverage
 
-snapshot :; forge snapshot --nmp test/fork/*
+snapshot :; forge snapshot --nmp "test/fork/*"
 
 format :; forge fmt
 
diff --git a/demos/DeployTanssiEcosystemDemo.s.sol b/demos/DeployTanssiEcosystemDemo.s.sol
index 87b6cbe6..988d2efc 100644
--- a/demos/DeployTanssiEcosystemDemo.s.sol
+++ b/demos/DeployTanssiEcosystemDemo.s.sol
@@ -30,8 +30,6 @@ import {Vault} from "@symbiotic/contracts/vault/Vault.sol";
 import {INetworkRestakeDelegator} from "@symbiotic/interfaces/delegator/INetworkRestakeDelegator.sol";
 import {IFullRestakeDelegator} from "@symbiotic/interfaces/delegator/IFullRestakeDelegator.sol";
 import {Subnetwork} from "@symbiotic/contracts/libraries/Subnetwork.sol";
-import {IDefaultCollateralFactory} from
-    "@symbiotic-collateral/interfaces/defaultCollateral/IDefaultCollateralFactory.sol";
 
 //**************************************************************************************************
 //                                      CHAINLINK
@@ -184,7 +182,7 @@ contract DeployTanssiEcosystem is Script {
         }
 
         // On real scenario we want to deploy only the slashable vault. TBD
-        if (isTest || block.chainid == 31_337 || block.chainid == 11_155_111) {
+        if (isTest || block.chainid == 31_337) {
             (vaultAddresses.vault, vaultAddresses.delegator, vaultAddresses.slasher) =
                 contractScripts.deployVault.createBaseVault(params);
             console2.log("Vault Collateral: ", IVault(vaultAddresses.vault).collateral());
@@ -194,7 +192,7 @@ contract DeployTanssiEcosystem is Script {
             console2.log(" ");
         }
 
-        if (block.chainid == 31_337 || block.chainid == 11_155_111) {
+        if (block.chainid == 31_337) {
             params.collateral = address(tokensAddresses.rETHToken);
         }
         (vaultAddresses.vaultSlashable, vaultAddresses.delegatorSlashable, vaultAddresses.slasherSlashable) =
@@ -205,9 +203,9 @@ contract DeployTanssiEcosystem is Script {
         console2.log("SlasherSlashable: ", vaultAddresses.slasherSlashable);
         console2.log(" ");
 
-        if (isTest || block.chainid == 31_337 || block.chainid == 11_155_111) {
+        if (isTest || block.chainid == 31_337) {
             params.delegatorIndex = VaultManager.DelegatorType.FULL_RESTAKE;
-            if (block.chainid == 31_337 || block.chainid == 11_155_111) {
+            if (block.chainid == 31_337) {
                 params.collateral = address(tokensAddresses.wBTCToken);
             }
             (vaultAddresses.vaultVetoed, vaultAddresses.delegatorVetoed, vaultAddresses.slasherVetoed) =
@@ -226,7 +224,7 @@ contract DeployTanssiEcosystem is Script {
     }
 
     function _setDelegatorConfigs() public {
-        if (block.chainid == 31_337 || block.chainid == 11_155_111 || isTest) {
+        if (block.chainid == 31_337 || isTest) {
             INetworkRestakeDelegator(vaultAddresses.delegator).setMaxNetworkLimit(0, MAX_NETWORK_LIMIT);
             INetworkRestakeDelegator(vaultAddresses.delegatorVetoed).setMaxNetworkLimit(0, MAX_NETWORK_LIMIT);
 
@@ -271,17 +269,10 @@ contract DeployTanssiEcosystem is Script {
             address operatorNetworkOptInServiceAddress,
             address operatorVaultOptInServiceAddress,
             address networkMiddlewareServiceAddress,
-            address defaultCollateralFactoryAddress,
+            ,
             address stETHAddress,
         ) = contractScripts.helperConfig.activeNetworkConfig();
 
-        IDefaultCollateralFactory defaultCollateralFactory;
-        if (block.chainid != 31_337 && block.chainid != 11_155_111) {
-            defaultCollateralFactory = IDefaultCollateralFactory(defaultCollateralFactoryAddress);
-            ecosystemEntities.defaultCollateralAddress =
-                defaultCollateralFactory.create(address(stETHAddress), 10_000 ether, address(0));
-        }
-
         ecosystemEntities.vaultConfigurator = IVaultConfigurator(vaultConfiguratorAddress);
 
         INetworkRegistry networkRegistry = INetworkRegistry(networkRegistryAddress);
@@ -291,7 +282,7 @@ contract DeployTanssiEcosystem is Script {
         IOptInService operatorVaultOptInService = IOptInService(operatorVaultOptInServiceAddress);
         MockV3Aggregator collateralOracle = new MockV3Aggregator(ORACLE_DECIMALS, ORACLE_CONVERSION_TOKEN);
 
-        if (block.chainid == 31_337 || block.chainid == 11_155_111) {
+        if (block.chainid == 31_337) {
             // Deploy simple ERC20 collateral tokens
             deployTokens();
         } else {
@@ -402,7 +393,6 @@ contract DeployTanssiEcosystem is Script {
         console2.log("NetworkMiddlewareService: ", address(networkMiddlewareService));
         console2.log("OperatorNetworkOptInService: ", address(operatorNetworkOptInService));
         console2.log("OperatorVaultOptInService: ", address(operatorVaultOptInService));
-        console2.log("DefaultCollateralFactory: ", address(defaultCollateralFactory));
         console2.log("DefaultCollateral: ", ecosystemEntities.defaultCollateralAddress);
         console2.log("Middleware: ", address(ecosystemEntities.middleware));
         console2.log("Vault: ", vaultAddresses.vault);
diff --git a/script/DeployTanssiEcosystem.s.sol b/script/DeployTanssiEcosystem.s.sol
index f86c4f88..05ad3550 100644
--- a/script/DeployTanssiEcosystem.s.sol
+++ b/script/DeployTanssiEcosystem.s.sol
@@ -31,6 +31,7 @@ import {IFullRestakeDelegator} from "@symbiotic/interfaces/delegator/IFullRestak
 import {Subnetwork} from "@symbiotic/contracts/libraries/Subnetwork.sol";
 import {IDefaultCollateralFactory} from
     "@symbiotic-collateral/interfaces/defaultCollateral/IDefaultCollateralFactory.sol";
+import {DefaultCollateralFactory} from "@symbiotic-collateral/contracts/defaultCollateral/DefaultCollateralFactory.sol";
 import {VaultFactory} from "@symbiotic/contracts/VaultFactory.sol";
 
 import {ODefaultOperatorRewards} from "src/contracts/rewarder/ODefaultOperatorRewards.sol";
@@ -114,10 +115,6 @@ contract DeployTanssiEcosystem is Script {
     function deployTokens(
         address owner
     ) public returns (address, address, address) {
-        // if (!isTest) {
-        //     vm.stopBroadcast();
-        // }
-
         address stETH = contractScripts.deployCollateral.deployCollateral("stETH");
         console2.log(" ");
         address rETH = contractScripts.deployCollateral.deployCollateral("rETH");
@@ -133,13 +130,7 @@ contract DeployTanssiEcosystem is Script {
         tokensAddresses.rETHToken.mint(owner, 10_000 ether);
         tokensAddresses.wBTCToken.mint(owner, 10_000 ether);
 
-        // vm.stopBroadcast();
-
-        // if (!isTest) {
-        //     vm.startBroadcast(ownerPrivateKey);
-        // }
         return (stETH, rETH, wBTC);
-        // return (stETH, address(0), address(0));
     }
 
     function deployVaults() public returns (VaultAddresses memory) {
@@ -251,11 +242,13 @@ contract DeployTanssiEcosystem is Script {
         (,,,,,,, address defaultCollateralFactoryAddress, address stETHAddress,) =
             contractScripts.helperConfig.activeNetworkConfig();
 
-        IDefaultCollateralFactory defaultCollateralFactory;
         if (block.chainid != 31_337) {
-            defaultCollateralFactory = IDefaultCollateralFactory(defaultCollateralFactoryAddress);
-            ecosystemEntities.defaultCollateralAddress =
-                defaultCollateralFactory.create(address(stETHAddress), 10_000 ether, address(0));
+            if (defaultCollateralFactoryAddress == address(0)) {
+                defaultCollateralFactoryAddress = address(new DefaultCollateralFactory());
+            }
+
+            ecosystemEntities.defaultCollateralAddress = IDefaultCollateralFactory(defaultCollateralFactoryAddress)
+                .create(address(stETHAddress), 10_000 ether, address(0));
         }
 
         if (!isTest) {
diff --git a/script/chain_data.json b/script/chain_data.json
index 720ac374..2c8e45f8 100644
--- a/script/chain_data.json
+++ b/script/chain_data.json
@@ -45,7 +45,7 @@
     "operatorVaultOptInService": "0x1234567890123456789012345678901234567890"
   },
   "11155111": {
-    "defaultCollateralFactory": "0x7224eeF9f38E9240beA197970367E0A8CBDFDD8B",
+    "defaultCollateralFactory": "0x0000000000000000000000000000000000000000",
     "vaultConfigurator": "0xD2191FE92987171691d552C219b8caEf186eb9cA",
     "vaultFactory": "0x407A039D94948484D356eFB765b3c74382A050B4",
     "delegatorFactory": "0x890CA3f95E0f40a79885B7400926544B2214B03f",
@@ -60,6 +60,9 @@
     "defaultStakerRewardsFactory": "0x70C618a13D1A57f7234c0b893b9e28C5cA8E7f37",
     "defaultOperatorRewardsFactory": "0x8D6C873cb7ffa6BE615cE1D55801a9417Ed55f9B",
     "stETH": "0x3e3FE7dBc6B4C189E7128855dD526361c49b40Af",
-    "readHelper": "0x0000000000000000000000000000000000000000"
+    "readHelper": "0x0000000000000000000000000000000000000000",
+    "middleware": "0xdbc3804a9d3164a438ec222a222e8d8b2dffa0de",
+    "operatorRewards": "0x5A1A8ce9f4D294AA1835252B5FE397d607d615C8",
+    "stakerRewards": "0x14453f57A92881bA788d0CaF29AD6CCE1F9028F1"
   }
 }
diff --git a/test/fork/Upgrades.t.sol b/test/fork/Upgrades.t.sol
new file mode 100644
index 00000000..7afe3824
--- /dev/null
+++ b/test/fork/Upgrades.t.sol
@@ -0,0 +1,119 @@
+//SPDX-License-Identifier: GPL-3.0-or-later
+
+// Copyright (C) Moondance Labs Ltd.
+// This file is part of Tanssi.
+// Tanssi is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+// Tanssi is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+// You should have received a copy of the GNU General Public License
+// along with Tanssi.  If not, see <http://www.gnu.org/licenses/>
+pragma solidity 0.8.25;
+
+import {Test} from "forge-std/Test.sol";
+
+import {Middleware} from "src/contracts/middleware/Middleware.sol";
+import {IOBaseMiddlewareReader} from "src/interfaces/middleware/IOBaseMiddlewareReader.sol";
+import {DeployRewards} from "script/DeployRewards.s.sol";
+import {DeployTanssiEcosystem} from "script/DeployTanssiEcosystem.s.sol";
+import {ODefaultOperatorRewards} from "src/contracts/rewarder/ODefaultOperatorRewards.sol";
+import {ODefaultStakerRewards} from "src/contracts/rewarder/ODefaultStakerRewards.sol";
+
+contract MiddlewareTest is Test {
+    Middleware middleware;
+    ODefaultOperatorRewards operatorRewards;
+    ODefaultStakerRewards stakerRewards;
+    DeployTanssiEcosystem deployTanssiEcosystem;
+    DeployRewards deployRewards;
+    address tanssi;
+    address admin;
+
+    function setUp() public {
+        string memory root = vm.projectRoot();
+        string memory path = string.concat(root, "/script/chain_data.json");
+        string memory json = vm.readFile(path);
+
+        uint256 chainId = block.chainid;
+        string memory jsonPath = string.concat("$.", vm.toString(chainId));
+
+        address middlewareAddress = abi.decode(vm.parseJson(json, string.concat(jsonPath, ".middleware")), (address));
+        address operatorRewardsAddress =
+            abi.decode(vm.parseJson(json, string.concat(jsonPath, ".operatorRewards")), (address));
+        address stakerRewardsAddress =
+            abi.decode(vm.parseJson(json, string.concat(jsonPath, ".stakerRewards")), (address));
+
+        middleware = Middleware(middlewareAddress);
+        operatorRewards = ODefaultOperatorRewards(operatorRewardsAddress);
+        stakerRewards = ODefaultStakerRewards(stakerRewardsAddress);
+
+        deployRewards = new DeployRewards();
+        deployRewards.setIsTest(false);
+        deployTanssiEcosystem = new DeployTanssiEcosystem();
+        tanssi = IOBaseMiddlewareReader(address(middleware)).NETWORK();
+        admin = deployTanssiEcosystem.tanssi(); // Loaded from the env OWNER_PRIVATE_KEY
+
+        // Actual admin in the 3 contracts, we use its account to set the admin role to our admin which will run using broadcast
+        vm.startPrank(0x8f7b28C2A36E805F4024c1AE1e96a4B75E50A512);
+        operatorRewards.grantRole(operatorRewards.DEFAULT_ADMIN_ROLE(), admin);
+        stakerRewards.grantRole(stakerRewards.DEFAULT_ADMIN_ROLE(), admin);
+        middleware.grantRole(middleware.DEFAULT_ADMIN_ROLE(), admin);
+        vm.stopPrank();
+    }
+
+    function testUpgradeMiddleware() public {
+        address newOperatorRewardsAddress = makeAddr("newOperatorRewardsAddress");
+        address newStakerRewardsFactoryAddress = makeAddr("newStakerRewardsFactoryAddress");
+
+        IOBaseMiddlewareReader reader = IOBaseMiddlewareReader(address(middleware));
+
+        uint48 currentEpoch = reader.getCurrentEpoch();
+        address network = reader.NETWORK();
+        uint256 operatorsLength = reader.operatorsLength();
+
+        deployTanssiEcosystem.upgradeMiddlewareBroadcast(
+            address(middleware), 1, newOperatorRewardsAddress, newStakerRewardsFactoryAddress
+        );
+
+        assertEq(middleware.i_operatorRewards(), newOperatorRewardsAddress);
+        assertEq(middleware.i_stakerRewardsFactory(), newStakerRewardsFactoryAddress);
+        assertEq(reader.getCurrentEpoch(), currentEpoch);
+        assertEq(reader.NETWORK(), network);
+        assertEq(reader.operatorsLength(), operatorsLength);
+    }
+
+    function testUpgradeMiddlewareFailsIfUnexpectedVersion() public {
+        address newOperatorRewardsAddress = makeAddr("newOperatorRewardsAddress");
+        address newStakerRewardsFactoryAddress = makeAddr("newStakerRewardsFactoryAddress");
+        vm.expectRevert("Middleware version is not expected, cannot upgrade");
+        deployTanssiEcosystem.upgradeMiddleware(
+            address(middleware), 2, newOperatorRewardsAddress, newStakerRewardsFactoryAddress, address(0)
+        );
+    }
+
+    function testUpgradeRewardsOperatorWithBroadcast() public {
+        address networkMiddlewareService = operatorRewards.i_networkMiddlewareService();
+        uint48 operatorShare = operatorRewards.operatorShare();
+
+        deployRewards.setIsTest(false);
+        deployRewards.upgradeOperatorRewards(address(operatorRewards), tanssi, networkMiddlewareService);
+
+        assertEq(operatorRewards.operatorShare(), operatorShare);
+        assertEq(operatorRewards.i_networkMiddlewareService(), networkMiddlewareService);
+    }
+
+    function testUpgradeStakerRewardsWithBroadcast() public {
+        address vault = stakerRewards.i_vault();
+        address network = stakerRewards.i_network();
+        address networkMiddlewareService = stakerRewards.i_networkMiddlewareService();
+
+        deployRewards.upgradeStakerRewards(address(stakerRewards), networkMiddlewareService, vault, network);
+
+        assertEq(stakerRewards.i_vault(), vault);
+        assertEq(stakerRewards.i_network(), network);
+        assertEq(stakerRewards.i_networkMiddlewareService(), networkMiddlewareService);
+    }
+}
diff --git a/test/unit/Deploy.t.sol b/test/unit/Deploy.t.sol
index fb3d1e5c..666279de 100644
--- a/test/unit/Deploy.t.sol
+++ b/test/unit/Deploy.t.sol
@@ -32,8 +32,6 @@ import {IBaseDelegator} from "@symbiotic/interfaces/delegator/IBaseDelegator.sol
 import {IBaseSlasher} from "@symbiotic/interfaces/slasher/IBaseSlasher.sol";
 import {IVetoSlasher} from "@symbiotic/interfaces/slasher/IVetoSlasher.sol";
 
-import {IDefaultCollateralFactory} from
-    "@symbiotic-collateral/interfaces/defaultCollateral/IDefaultCollateralFactory.sol";
 import {Subnetwork} from "@symbiotic/contracts/libraries/Subnetwork.sol";
 import {EpochCapture} from "@symbiotic-middleware/extensions/managers/capture-timestamps/EpochCapture.sol";
 import {IODefaultStakerRewards} from "src/interfaces/rewarder/IODefaultStakerRewards.sol";
diff --git a/test/unit/Rewards.t.sol b/test/unit/Rewards.t.sol
index f27f2740..654c44f0 100644
--- a/test/unit/Rewards.t.sol
+++ b/test/unit/Rewards.t.sol
@@ -1564,7 +1564,7 @@ contract RewardsTest is Test {
     //                                      UPGRADE
     //**************************************************************************************************
 
-    function testUpgradeAndMigrateStakerRewards() public {
+    function testUpgradeStakerRewardsWithScript() public {
         vm.warp(NETWORK_EPOCH_DURATION);
 
         deployRewards.upgradeStakerRewards(
@@ -1576,7 +1576,7 @@ contract RewardsTest is Test {
         assertEq(stakerRewards.i_networkMiddlewareService(), address(networkMiddlewareService));
     }
 
-    function testUpgradeAndMigrateStakerRewardsWithBroadcast() public {
+    function testUpgradeStakerRewardsWithBroadcast() public {
         vm.startPrank(tanssi);
         // On not testing mode, the owner of the contract to upgrade is this, so we need to grant the admin role to it
         address ownerForUpgrade = vm.addr(deployRewards.ownerPrivateKey());


Reviews:
user: login: NindoK
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-14T18:21:58Z
user: login: NindoK
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-14T18:29:20Z
user: login: NindoK
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-14T18:35:38Z

Review comments:
diff_hunk: @@ -0,0 +1,119 @@
+//SPDX-License-Identifier: GPL-3.0-or-later
+
+// Copyright (C) Moondance Labs Ltd.
+// This file is part of Tanssi.
+// Tanssi is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+// Tanssi is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+// You should have received a copy of the GNU General Public License
+// along with Tanssi.  If not, see <http://www.gnu.org/licenses/>
+pragma solidity 0.8.25;
+
+import {Test} from "forge-std/Test.sol";
+
+import {Middleware} from "src/contracts/middleware/Middleware.sol";
+import {IOBaseMiddlewareReader} from "src/interfaces/middleware/IOBaseMiddlewareReader.sol";
+import {DeployRewards} from "script/DeployRewards.s.sol";
+import {DeployTanssiEcosystem} from "script/DeployTanssiEcosystem.s.sol";
+import {ODefaultOperatorRewards} from "src/contracts/rewarder/ODefaultOperatorRewards.sol";
+import {ODefaultStakerRewards} from "src/contracts/rewarder/ODefaultStakerRewards.sol";
+
+contract MiddlewareTest is Test {
path: test/fork/Upgrades.t.sol
user: login: NindoK
body: `UpgradesTest` name
created_at: 2025-05-14T18:21:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,119 @@
+//SPDX-License-Identifier: GPL-3.0-or-later
+
+// Copyright (C) Moondance Labs Ltd.
+// This file is part of Tanssi.
+// Tanssi is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+// Tanssi is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+// You should have received a copy of the GNU General Public License
+// along with Tanssi.  If not, see <http://www.gnu.org/licenses/>
+pragma solidity 0.8.25;
+
+import {Test} from "forge-std/Test.sol";
+
+import {Middleware} from "src/contracts/middleware/Middleware.sol";
+import {IOBaseMiddlewareReader} from "src/interfaces/middleware/IOBaseMiddlewareReader.sol";
+import {DeployRewards} from "script/DeployRewards.s.sol";
+import {DeployTanssiEcosystem} from "script/DeployTanssiEcosystem.s.sol";
+import {ODefaultOperatorRewards} from "src/contracts/rewarder/ODefaultOperatorRewards.sol";
+import {ODefaultStakerRewards} from "src/contracts/rewarder/ODefaultStakerRewards.sol";
+
+contract MiddlewareTest is Test {
+    Middleware middleware;
+    ODefaultOperatorRewards operatorRewards;
+    ODefaultStakerRewards stakerRewards;
+    DeployTanssiEcosystem deployTanssiEcosystem;
+    DeployRewards deployRewards;
+    address tanssi;
+    address admin;
+
+    function setUp() public {
+        string memory root = vm.projectRoot();
+        string memory path = string.concat(root, "/script/chain_data.json");
+        string memory json = vm.readFile(path);
+
+        uint256 chainId = block.chainid;
+        string memory jsonPath = string.concat("$.", vm.toString(chainId));
+
+        address middlewareAddress = abi.decode(vm.parseJson(json, string.concat(jsonPath, ".middleware")), (address));
+        address operatorRewardsAddress =
+            abi.decode(vm.parseJson(json, string.concat(jsonPath, ".operatorRewards")), (address));
+        address stakerRewardsAddress =
+            abi.decode(vm.parseJson(json, string.concat(jsonPath, ".stakerRewards")), (address));
+
+        middleware = Middleware(middlewareAddress);
+        operatorRewards = ODefaultOperatorRewards(operatorRewardsAddress);
+        stakerRewards = ODefaultStakerRewards(stakerRewardsAddress);
+
+        deployRewards = new DeployRewards();
+        deployRewards.setIsTest(false);
+        deployTanssiEcosystem = new DeployTanssiEcosystem();
+        tanssi = IOBaseMiddlewareReader(address(middleware)).NETWORK();
+        admin = deployTanssiEcosystem.tanssi(); // Loaded from the env OWNER_PRIVATE_KEY
+
+        // Actual admin in the 3 contracts, we use its account to set the admin role to our admin which will run using broadcast
+        vm.startPrank(0x8f7b28C2A36E805F4024c1AE1e96a4B75E50A512);
path: test/fork/Upgrades.t.sol
user: login: NindoK
body: I'd just put owner and take it from the `chain_data.json` and use it here
created_at: 2025-05-14T18:29:19Z
author_association: CONTRIBUTOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-05-12T19:40:34Z
body: <h2>> Coverage generated</h2>Coverage after merging <b>feat/970-test-fork-upgrade</b> into <b>staging</b> will be<table><tbody><tr><th>99.91%</th><th>▾ -0.09%</th></tr></tbody></table>

<details><summary>Coverage Report</summary><table><tbody><tr><th>File</th><th>Stmts</th><th>Branches</th><th>Funcs</th><th>Lines</th><th>Uncovered Lines</th></tr><tr><td colspan='6'><b>script</b></td></tr><tr><td>&nbsp; &nbsp;<a href='https://github.com/moondance-labs/tanssi-symbiotic/blob/38b0f37f3411bde55769068015ec81c3c19c8601/script/DeployCollateral.s.sol'>DeployCollateral.s.sol</a></td><td>100%</td><td>100%</td><td>100%</td><td>100%</td><td></td></tr><tr><td>&nbsp; &nbsp;<a href='https://github.com/moondance-labs/tanssi-symbiotic/blob/38b0f37f3411bde55769068015ec81c3c19c8601/script/DeployRewards.s.sol'>DeployRewards.s.sol</a></td><td>100%</td><td>100%</td><td>100%</td><td>100%</td><td></td></tr><tr><td>&nbsp; &nbsp;<a href='https://github.com/moondance-labs/tanssi-symbiotic/blob/38b0f37f3411bde55769068015ec81c3c19c8601/script/DeploySymbiotic.s.sol'>DeploySymbiotic.s.sol</a></td><td>100%</td><td>100%</td><td>100%</td><td>100%</td><td></td></tr><tr><td>&nbsp; &nbsp;<a href='https://github.com/moondance-labs/tanssi-symbiotic/blob/38b0f37f3411bde55769068015ec81c3c19c8601/script/DeployTanssiEcosystem.s.sol'>DeployTanssiEcosystem.s.sol</a></td><td><b>99.09%</b></td><td><b>95.65%</b></td><td>100%</td><td><b>99.44%</b></td><td><a href='https://github.com/moondance-labs/tanssi-symbiotic/blob/38b0f37f3411bde55769068015ec81c3c19c8601/script/DeployTanssiEcosystem.s.sol#L246-L247'>246&ndash;247</a></td></tr><tr><td>&nbsp; &nbsp;<a href='https://github.com/moondance-labs/tanssi-symbiotic/blob/38b0f37f3411bde55769068015ec81c3c19c8601/script/DeployVault.s.sol'>DeployVault.s.sol</a></td><td>100%</td><td>100%</td><td>100%</td><td>100%</td><td></td></tr><tr><td>&nbsp; &nbsp;<a href='https://github.com/moondance-labs/tanssi-symbiotic/blob/38b0f37f3411bde55769068015ec81c3c19c8601/script/HelperConfig.s.sol'>HelperConfig.s.sol</a></td><td>100%</td><td>100%</td><td>100%</td><td>100%</td><td></td></tr><tr><td colspan='6'><b>src/contracts/extensions</b></td></tr><tr><td>&nbsp; &nbsp;<a href='https://github.com/moondance-labs/tanssi-symbiotic/blob/38b0f37f3411bde55769068015ec81c3c19c8601/src/contracts/extensions/OSharedVaults.sol'>OSharedVaults.sol</a></td><td>100%</td><td>100%</td><td>100%</td><td>100%</td><td></td></tr><tr><td colspan='6'><b>src/contracts/libraries</b></td></tr><tr><td>&nbsp; &nbsp;<a href='https://github.com/moondance-labs/tanssi-symbiotic/blob/38b0f37f3411bde55769068015ec81c3c19c8601/src/contracts/libraries/QuickSort.sol'>QuickSort.sol</a></td><td>100%</td><td>100%</td><td>100%</td><td>100%</td><td></td></tr><tr><td colspan='6'><b>src/contracts/middleware</b></td></tr><tr><td>&nbsp; &nbsp;<a href='https://github.com/moondance-labs/tanssi-symbiotic/blob/38b0f37f3411bde55769068015ec81c3c19c8601/src/contracts/middleware/Middleware.sol'>Middleware.sol</a></td><td>100%</td><td>100%</td><td>100%</td><td>100%</td><td></td></tr><tr><td>&nbsp; &nbsp;<a href='https://github.com/moondance-labs/tanssi-symbiotic/blob/38b0f37f3411bde55769068015ec81c3c19c8601/src/contracts/middleware/MiddlewareStorage.sol'>MiddlewareStorage.sol</a></td><td>100%</td><td>100%</td><td>100%</td><td>100%</td><td></td></tr><tr><td>&nbsp; &nbsp;<a href='https://github.com/moondance-labs/tanssi-symbiotic/blob/38b0f37f3411bde55769068015ec81c3c19c8601/src/contracts/middleware/OBaseMiddlewareReader.sol'>OBaseMiddlewareReader.sol</a></td><td>100%</td><td>100%</td><td>100%</td><td>100%</td><td></td></tr><tr><td colspan='6'><b>src/contracts/rewarder</b></td></tr><tr><td>&nbsp; &nbsp;<a href='https://github.com/moondance-labs/tanssi-symbiotic/blob/38b0f37f3411bde55769068015ec81c3c19c8601/src/contracts/rewarder/ODefaultOperatorRewards.sol'>ODefaultOperatorRewards.sol</a></td><td>100%</td><td>100%</td><td>100%</td><td>100%</td><td></td></tr><tr><td>&nbsp; &nbsp;<a href='https://github.com/moondance-labs/tanssi-symbiotic/blob/38b0f37f3411bde55769068015ec81c3c19c8601/src/contracts/rewarder/ODefaultStakerRewards.sol'>ODefaultStakerRewards.sol</a></td><td>100%</td><td>100%</td><td>100%</td><td>100%</td><td></td></tr><tr><td>&nbsp; &nbsp;<a href='https://github.com/moondance-labs/tanssi-symbiotic/blob/38b0f37f3411bde55769068015ec81c3c19c8601/src/contracts/rewarder/ODefaultStakerRewardsFactory.sol'>ODefaultStakerRewardsFactory.sol</a></td><td>100%</td><td>100%</td><td>100%</td><td>100%</td><td></td></tr><tr><td colspan='6'><b>test/mocks</b></td></tr><tr><td>&nbsp; &nbsp;<a href='https://github.com/moondance-labs/tanssi-symbiotic/blob/38b0f37f3411bde55769068015ec81c3c19c8601/test/mocks/FeeToken.sol'>FeeToken.sol</a></td><td>100%</td><td>100%</td><td>100%</td><td>100%</td><td></td></tr><tr><td>&nbsp; &nbsp;<a href='https://github.com/moondance-labs/tanssi-symbiotic/blob/38b0f37f3411bde55769068015ec81c3c19c8601/test/mocks/Token.sol'>Token.sol</a></td><td>100%</td><td>100%</td><td>100%</td><td>100%</td><td></td></tr><tr><td colspan='6'><b>test/mocks/symbiotic</b></td></tr><tr><td>&nbsp; &nbsp;<a href='https://github.com/moondance-labs/tanssi-symbiotic/blob/38b0f37f3411bde55769068015ec81c3c19c8601/test/mocks/symbiotic/DelegatorMock.sol'>DelegatorMock.sol</a></td><td>100%</td><td>100%</td><td>100%</td><td>100%</td><td></td></tr><tr><td>&nbsp; &nbsp;<a href='https://github.com/moondance-labs/tanssi-symbiotic/blob/38b0f37f3411bde55769068015ec81c3c19c8601/test/mocks/symbiotic/RegistryMock.sol'>RegistryMock.sol</a></td><td>100%</td><td>100%</td><td>100%</td><td>100%</td><td></td></tr><tr><td>&nbsp; &nbsp;<a href='https://github.com/moondance-labs/tanssi-symbiotic/blob/38b0f37f3411bde55769068015ec81c3c19c8601/test/mocks/symbiotic/SharedVaultMock.sol'>SharedVaultMock.sol</a></td><td>100%</td><td>100%</td><td>100%</td><td>100%</td><td></td></tr><tr><td colspan='6'><b>test/unit/utils</b></td></tr><tr><td>&nbsp; &nbsp;<a href='https://github.com/moondance-labs/tanssi-symbiotic/blob/38b0f37f3411bde55769068015ec81c3c19c8601/test/unit/utils/MiddlewareV2.sol'>MiddlewareV2.sol</a></td><td>100%</td><td>100%</td><td>100%</td><td>100%</td><td></td></tr><tr><td>&nbsp; &nbsp;<a href='https://github.com/moondance-labs/tanssi-symbiotic/blob/38b0f37f3411bde55769068015ec81c3c19c8601/test/unit/utils/MiddlewareV3.sol'>MiddlewareV3.sol</a></td><td>100%</td><td>100%</td><td>100%</td><td>100%</td><td></td></tr></tbody></table></details>
author_association: NONE