Skip to content

Commit fd927a1

Browse files
committed
Merge remote-tracking branch 'origin/main' into feat_v2-cleanings-cfg-mint
2 parents aac102d + 60b51f6 commit fd927a1

File tree

8 files changed

+189
-59
lines changed

8 files changed

+189
-59
lines changed

env/connections/testnet.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@
1010
},
1111
{
1212
"chains": [["hyper-evm-testnet"], "ALL"],
13-
"adapters": [],
14-
"threshold": 0
13+
"adapters": ["layerZero"],
14+
"threshold": 1
1515
}
1616
]
1717
}

env/hyper-evm-testnet.json

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,24 @@
77
"opsAdmin": "0xc1A929CBc122Ddb8794287D05Bf890E41f23c8cb",
88
"baseRpcUrl": "https://hyperliquid-testnet.g.alchemy.com/v2/"
99
},
10+
"adapters": {
11+
"layerZero": {
12+
"endpoint": "0xf9e1815F151024bDE4B7C10BAC10e8Ba9F6b53E1",
13+
"layerZeroEid": 40362,
14+
"deploy": true,
15+
"blockConfirmations": 5,
16+
"DVNs": [
17+
"0x91e698871030d0e1b6c9268c20bb57e2720618dd"
18+
]
19+
}
20+
},
1021
"contracts": {
22+
"layerZeroAdapter": {
23+
"address": "0x24f3192d46869609F8F6605e662b8146CA4240d3",
24+
"blockNumber": 47348915,
25+
"txHash": "0xb7cf5653c3244134455117a3ef2b190102eeb32fd83e8196362f57d65f8be8f1",
26+
"version": "v3.1"
27+
},
1128
"root": {
1229
"address": "0x1Fa8a2fe2eAE2373B03Fa313E016605EB86cBae8",
1330
"blockNumber": 43580306,
@@ -263,7 +280,7 @@
263280
},
264281
"deploymentInfo": {
265282
"deploy:protocol": {
266-
"gitCommit": "e0294708",
283+
"gitCommit": "24032569",
267284
"timestamp": "2026-01-21T07:45:00Z",
268285
"suffix": "rev2",
269286
"startBlock": 43580227

script/PoolHooks.s.sol

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,9 @@ contract PoolHooks is BaseDeployer {
3838

3939
uint16 public centrifugeId;
4040

41-
address public freelyTransferableHook;
41+
address public contractUpdater;
4242
address public fullRestrictionsHook;
43+
address public freelyTransferableHook;
4344
Root public root;
4445
Spoke public spoke;
4546
BalanceSheet public balanceSheet;
@@ -48,14 +49,15 @@ contract PoolHooks is BaseDeployer {
4849
function run() external {
4950
EnvConfig memory config = Env.load(vm.envString("NETWORK"));
5051

52+
centrifugeId = config.network.centrifugeId;
53+
5154
root = Root(config.contracts.root);
5255
spoke = Spoke(config.contracts.spoke);
5356
balanceSheet = BalanceSheet(config.contracts.balanceSheet);
5457
poolEscrowFactory = IPoolEscrowProvider(config.contracts.poolEscrowFactory);
5558
freelyTransferableHook = config.contracts.freelyTransferableHook;
5659
fullRestrictionsHook = config.contracts.fullRestrictionsHook;
57-
58-
centrifugeId = config.network.centrifugeId;
60+
contractUpdater = config.contracts.contractUpdater;
5961

6062
GraphQLQuery graphQL = new GraphQLQuery(config.network.graphQLApi());
6163

@@ -88,7 +90,7 @@ contract PoolHooks is BaseDeployer {
8890
string memory params = string.concat(
8991
"limit: 1000,"
9092
"where: {"
91-
" centrifugeId: ", vm.toString(centrifugeId),
93+
" centrifugeId: ", vm.toString(centrifugeId).asJsonString(),
9294
"}"
9395
);
9496

@@ -222,6 +224,7 @@ contract PoolHooks is BaseDeployer {
222224

223225
hook.rely(address(root));
224226
hook.rely(address(spoke));
227+
hook.rely(address(contractUpdater));
225228
hook.deny(msg.sender);
226229
}
227230

script/spell/V2Cleanings.s.sol

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
// SPDX-License-Identifier: BUSL-1.1
22
pragma solidity 0.8.28;
33

4+
import {Root} from "../../src/admin/Root.sol";
5+
46
import "forge-std/Script.sol";
57

68
import {V2CleaningsSpell} from "../../src/spell/V2CleaningsSpell.sol";
9+
import {EnvConfig, Env, prettyEnvString} from "../utils/EnvConfig.s.sol";
710

811
contract V2CleaningsDeployer is Script {
912
function run() external {
@@ -20,16 +23,19 @@ contract V2CleaningsExecutor is Script {
2023
address deployer;
2124

2225
function run(V2CleaningsSpell spell) external {
26+
EnvConfig memory config = Env.load(prettyEnvString("NETWORK"));
27+
Root rootV3 = Root(config.contracts.root);
28+
2329
vm.startBroadcast();
2430

25-
migrate(spell);
31+
migrate(spell, rootV3);
2632

2733
vm.stopBroadcast();
2834
}
2935

30-
function migrate(V2CleaningsSpell spell) public {
36+
function migrate(V2CleaningsSpell spell, Root rootV3) public {
3137
vm.label(address(spell), "V2CleaningsSpell");
3238

33-
spell.cast();
39+
spell.cast(rootV3);
3440
}
3541
}

src/deployment/ActionBatchers.sol

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,10 @@ contract NonCoreActionBatcher {
354354
// Rely contractUpdater
355355
report.syncManager.rely(address(report.core.contractUpdater));
356356
report.asyncRequestManager.rely(address(report.core.contractUpdater));
357+
report.freezeOnlyHook.rely(address(report.core.contractUpdater));
358+
report.fullRestrictionsHook.rely(address(report.core.contractUpdater));
359+
report.freelyTransferableHook.rely(address(report.core.contractUpdater));
360+
report.redemptionRestrictionsHook.rely(address(report.core.contractUpdater));
357361

358362
// Rely hub
359363
report.batchRequestManager.rely(address(report.core.hub));

src/spell/V2CleaningsSpell.sol

Lines changed: 45 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,12 @@ import {IERC20} from "../misc/interfaces/IERC20.sol";
77
import {Root} from "../admin/Root.sol";
88

99
Root constant ROOT_V2 = Root(0x0C1fDfd6a1331a875EA013F3897fc8a76ada5DfC);
10-
Root constant ROOT_V3 = Root(0x7Ed48C31f2fdC40d37407cBaBf0870B2b688368f);
10+
11+
address constant CONTRACT_UPDATER = 0x3B150B19245D2C366bc8f18c775b725DFB298F71;
12+
address constant FREEZE_ONLY_HOOK = 0xd5B243F05b2906F1f6C80c6096945faADa0731C1;
13+
address constant FULL_RESTRICTIONS_HOOK = 0x8E680873b4C77e6088b4Ba0aBD59d100c3D224a4;
14+
address constant FREELY_TRANSFERABLE_HOOK = 0x2a9B9C14851Baf7AD19f26607C9171CA1E7a1A61;
15+
address constant REDEMPTION_RESTRICTIONS_HOOK = 0xE5423eD8602Fa0F263e17b6212d88Efe42317f06;
1116

1217
address constant CFG = 0xcccCCCcCCC33D538DBC2EE4fEab0a7A1FF4e8A94;
1318
address constant WCFG = 0xc221b7E65FfC80DE234bbB6667aBDd46593D34F0;
@@ -51,46 +56,53 @@ contract V2CleaningsSpell {
5156
bool public done;
5257
string public constant description = "Pending cleanings from V2";
5358

54-
function cast() external {
59+
function cast(Root rootV3) external {
5560
require(!done, "Spell already executed");
5661
done = true;
5762

58-
_updateCFGWards();
59-
_disableRootV2FromShareTokensV2();
63+
_updateCFGWards(rootV3);
64+
_disableRootV2FromShareTokensV2(rootV3);
6065
_moveFundsFromEscrowToTreasury();
61-
_mintCFGToTreasury();
66+
_relyContractUpdaterOnHooks(rootV3);
67+
_mintCFGToTreasury(rootV3);
6268

63-
ROOT_V2.deny(address(this));
64-
ROOT_V3.deny(address(this));
69+
if (address(ROOT_V2).code.length > 0) {
70+
ROOT_V2.deny(address(this));
71+
}
72+
rootV3.deny(address(this));
6573
}
6674

67-
function _updateCFGWards() internal {
75+
function _updateCFGWards(Root rootV3) internal {
6876
// Check if CFG exists
6977
if (CFG.code.length > 0) {
7078
// Mainnet CFG only has the v2 root relied, need to replace with v3 root
7179
if (block.chainid == ETHEREUM_CHAIN_ID) {
72-
ROOT_V2.relyContract(CFG, address(ROOT_V3));
73-
ROOT_V2.relyContract(CFG, CFG_MINTER);
74-
ROOT_V3.denyContract(CFG, IOU_CFG);
75-
ROOT_V3.denyContract(CFG, address(ROOT_V2));
80+
if (address(ROOT_V2).code.length > 0) {
81+
ROOT_V2.relyContract(CFG, address(rootV3));
82+
ROOT_V2.relyContract(CFG, CFG_MINTER);
83+
rootV3.denyContract(CFG, address(ROOT_V2));
84+
}
85+
rootV3.denyContract(CFG, IOU_CFG);
7686
}
7787

7888
// Deny CREATE3 proxy on new chains
7989
if (block.chainid != ETHEREUM_CHAIN_ID) {
80-
ROOT_V3.denyContract(CFG, CREATE3_PROXY);
90+
rootV3.denyContract(CFG, CREATE3_PROXY);
8191
}
8292
}
8393

8494
// Check if WCFG exists (only in Ethereum)
8595
if (WCFG.code.length > 0) {
86-
Root(ROOT_V2).relyContract(WCFG, address(ROOT_V3));
87-
ROOT_V3.denyContract(WCFG, WCFG_MULTISIG);
88-
ROOT_V3.denyContract(WCFG, CHAINBRIDGE_ERC20_HANDLER);
89-
ROOT_V3.denyContract(WCFG, address(ROOT_V2));
96+
if (address(ROOT_V2).code.length > 0) {
97+
Root(ROOT_V2).relyContract(WCFG, address(rootV3));
98+
rootV3.denyContract(WCFG, address(ROOT_V2));
99+
}
100+
rootV3.denyContract(WCFG, WCFG_MULTISIG);
101+
rootV3.denyContract(WCFG, CHAINBRIDGE_ERC20_HANDLER);
90102
}
91103
}
92104

93-
function _disableRootV2FromShareTokensV2() internal {
105+
function _disableRootV2FromShareTokensV2(Root rootV3) internal {
94106
address[] memory shareTokens = new address[](2);
95107
shareTokens[0] = TRANCHE_JTRSY;
96108
shareTokens[1] = TRANCHE_JAAA;
@@ -101,11 +113,11 @@ contract V2CleaningsSpell {
101113
// forgefmt: disable-next-item
102114
if (address(shareTokenV2).code.length > 0 &&
103115
shareTokenV2.wards(address(ROOT_V2)) == 1 &&
104-
shareTokenV2.wards(address(ROOT_V3)) == 1
116+
shareTokenV2.wards(address(rootV3)) == 1
105117
) {
106-
ROOT_V3.relyContract(address(shareTokenV2), address(this));
118+
rootV3.relyContract(address(shareTokenV2), address(this));
107119
shareTokenV2.deny(address(ROOT_V2));
108-
ROOT_V3.denyContract(address(shareTokenV2), address(this));
120+
rootV3.denyContract(address(shareTokenV2), address(this));
109121
}
110122
}
111123
}
@@ -119,6 +131,8 @@ contract V2CleaningsSpell {
119131
usdc = USDC_BASE;
120132
} else if (block.chainid == ARBITRUM_CHAIN_ID) {
121133
usdc = USDC_ARBITRUM;
134+
} else {
135+
return;
122136
}
123137

124138
ROOT_V2.relyContract(address(ESCROW_V2), address(this));
@@ -130,14 +144,21 @@ contract V2CleaningsSpell {
130144
}
131145
}
132146

133-
function _mintCFGToTreasury() internal {
147+
function _relyContractUpdaterOnHooks(Root rootV3) internal {
148+
rootV3.relyContract(FREEZE_ONLY_HOOK, CONTRACT_UPDATER);
149+
rootV3.relyContract(FULL_RESTRICTIONS_HOOK, CONTRACT_UPDATER);
150+
rootV3.relyContract(FREELY_TRANSFERABLE_HOOK, CONTRACT_UPDATER);
151+
rootV3.relyContract(REDEMPTION_RESTRICTIONS_HOOK, CONTRACT_UPDATER);
152+
}
153+
154+
function _mintCFGToTreasury(Root rootV3) internal {
134155
if (block.chainid == ETHEREUM_CHAIN_ID) {
135156
// Subtract wCFG balance held by the IOU_CFG contract, since those were already
136157
// redeemed 1:1 for CFG and the wCFG total supply was not reduced upon redemption.
137158
uint256 amount = IERC20(WCFG).totalSupply() - IERC20(WCFG).balanceOf(IOU_CFG) + CENTRIFUGE_CHAIN_CFG_AMOUNT;
138-
ROOT_V3.relyContract(CFG, address(this));
159+
rootV3.relyContract(CFG, address(this));
139160
CFGTokenLike(CFG).mint(CNF_TREASURY_WALLET, amount);
140-
ROOT_V3.denyContract(CFG, address(this));
161+
rootV3.denyContract(CFG, address(this));
141162
}
142163
}
143164
}

test/integration/Deployer.t.sol

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -554,9 +554,11 @@ contract FullDeploymentTestNonCore is FullDeploymentConfigTest {
554554
// permissions set correctly
555555
vm.assume(nonWard != address(root));
556556
vm.assume(nonWard != address(spoke));
557+
vm.assume(nonWard != address(contractUpdater));
557558

558559
assertEq(freezeOnlyHook.wards(address(root)), 1);
559560
assertEq(freezeOnlyHook.wards(address(spoke)), 1);
561+
assertEq(freezeOnlyHook.wards(address(contractUpdater)), 1);
560562
assertEq(freezeOnlyHook.wards(nonWard), 0);
561563

562564
// dependencies set correctly
@@ -567,9 +569,11 @@ contract FullDeploymentTestNonCore is FullDeploymentConfigTest {
567569
// permissions set correctly
568570
vm.assume(nonWard != address(root));
569571
vm.assume(nonWard != address(spoke));
572+
vm.assume(nonWard != address(contractUpdater));
570573

571574
assertEq(redemptionRestrictionsHook.wards(address(root)), 1);
572575
assertEq(redemptionRestrictionsHook.wards(address(spoke)), 1);
576+
assertEq(redemptionRestrictionsHook.wards(address(contractUpdater)), 1);
573577
assertEq(redemptionRestrictionsHook.wards(nonWard), 0);
574578

575579
// dependencies set correctly
@@ -580,9 +584,11 @@ contract FullDeploymentTestNonCore is FullDeploymentConfigTest {
580584
// permissions set correctly
581585
vm.assume(nonWard != address(root));
582586
vm.assume(nonWard != address(spoke));
587+
vm.assume(nonWard != address(contractUpdater));
583588

584589
assertEq(freelyTransferableHook.wards(address(root)), 1);
585590
assertEq(freelyTransferableHook.wards(address(spoke)), 1);
591+
assertEq(freelyTransferableHook.wards(address(contractUpdater)), 1);
586592
assertEq(freelyTransferableHook.wards(nonWard), 0);
587593

588594
// dependencies set correctly
@@ -593,9 +599,11 @@ contract FullDeploymentTestNonCore is FullDeploymentConfigTest {
593599
// permissions set correctly
594600
vm.assume(nonWard != address(root));
595601
vm.assume(nonWard != address(spoke));
602+
vm.assume(nonWard != address(contractUpdater));
596603

597604
assertEq(fullRestrictionsHook.wards(address(root)), 1);
598605
assertEq(fullRestrictionsHook.wards(address(spoke)), 1);
606+
assertEq(fullRestrictionsHook.wards(address(contractUpdater)), 1);
599607
assertEq(fullRestrictionsHook.wards(nonWard), 0);
600608

601609
// dependencies set correctly

0 commit comments

Comments
 (0)