Skip to content

Commit 4790648

Browse files
feat: additional migration feature and stake factory event with minimum changes (#56)
* feat: add function to deploy a new module * feat: include deployment methods and utility functions * build: deploy new migration contracts * ci: remove CODEOWNERS to unblock code review process * feat: include NewHoprNodeStakeModuleForSafe in every safe-module pair deployment * build: deploy new StakeFactory and bump the version * fix: various typos * fix: lint * fix: attempt to fix multi version of hopr-bindings * fix: more lint and remove unnecessary dependency on the internal type to avoid circular dependency * fix: remove unnecessary hopr-crypto-types dependency * refactor: improve error handling * refactor: remove unwrap() s * chore: Cleanup unused env_logger * docs: add code comments * feat: remove extra features to first unblock blokli tests * chore(owners): return codeowners --------- Co-authored-by: Tibor Csóka <csokat@gmail.com>
1 parent 51967d7 commit 4790648

File tree

9 files changed

+274
-24
lines changed

9 files changed

+274
-24
lines changed

Cargo.lock

Lines changed: 18 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,17 @@ alloy = { version = "=1.0.42", default-features = false, features = [
1515
] }
1616
anyhow = "1.0.100"
1717
const_format = "0.2.35"
18+
hex-literal = "1.1.0"
1819
serde = { version = "1.0.228", features = ["derive"] }
1920
serde_with = { version = "3.16.1" }
2021
serde_json = { version = "1.0.145" }
22+
thiserror = "2.0.17"
23+
tokio = { version = "1.48.0", features = [
24+
"rt-multi-thread",
25+
"macros",
26+
"tracing",
27+
] }
28+
tracing = { version = "0.1.43" }
2129

2230
hopr-bindings = { path = "ethereum/bindings", default-features = false }
2331

ethereum/bindings/Cargo.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "hopr-bindings"
3-
version = "4.2.0"
3+
version = "4.3.0"
44
description = "This package only exists to automate the export and generation of Rust bindings into the HOPR project"
55
edition = "2021"
66
homepage = "https://hoprnet.org/"
@@ -16,5 +16,9 @@ const_format = { workspace = true }
1616
alloy = { workspace = true, default-features = false, features = ["contract"] }
1717
anyhow = { workspace = true }
1818
serde = { workspace = true, features = ["derive"] }
19+
hex-literal = { workspace = true }
1920
serde_with = { workspace = true }
2021
serde_json = { workspace = true }
22+
thiserror = { workspace = true }
23+
tokio = { workspace = true }
24+
tracing = { workspace = true }

ethereum/bindings/contracts-addresses.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@
2121
"announcements": "0x5afe60f4f74159f8F68dadCE6202b938cDa80Dad",
2222
"channels": "0x5bD809bDBAa8D3d5f37743Ae86BFef766dEE56b6",
2323
"module_implementation": "0x1167FB204298799B0B9E98896D58958cAEd164B0",
24-
"node_safe_migration": "0xe95b481AA95e1d071a4B250Ea5F9dD498A19646b",
24+
"node_safe_migration": "0x74dfCdF50340b772696cAeF3F3Bc4cD776D37E8A",
2525
"node_safe_registry": "0x0e4e1a2D851663462523bF38Ca56259aceCCBc76",
26-
"node_stake_factory": "0x878Ea9591726E70AA06f820c3bA5142A0C8ab58B",
26+
"node_stake_factory": "0xE9A9cf50534Eb0817F9fBb115E00Bf8463E973Db",
2727
"ticket_price_oracle": "0x147899Ca57111d9081df125C2bcbd839981f04c2",
2828
"token": "0xD4fdec44DB9D44B8f2b6d529620f9C0C7066A2c1",
2929
"winning_probability_oracle": "0x74329f8153cBb33aABdEd79Ee84748fE8923c5E3"
@@ -37,9 +37,9 @@
3737
"announcements": "0x191Ee0D1494eB159A5F758bC7c05E434cFaFF6e1",
3838
"channels": "0x7a33eb3900DB3e02A4C149E49dBc1F0359921B16",
3939
"module_implementation": "0x3b008Cb90D252b731ceB8952a6Ed78B84Ab31Ea3",
40-
"node_safe_migration": "0x06E01A5CEbAD283C0F00f857c06fbd804B054910",
40+
"node_safe_migration": "0x593eA8942E8b1D36F7714a15D43b5914DEF7B449",
4141
"node_safe_registry": "0x7b8E16ADa4720EB87b8000B9D5700EebbB6b1b5C",
42-
"node_stake_factory": "0x63e44a4e1349D1c3f0cfC52C4Dae7710A6345761",
42+
"node_stake_factory": "0x8E9e7BEDAe175D57054D15e8870E00E5bc66d954",
4343
"ticket_price_oracle": "0x95566eFB62b7D1e95a9BA05E1E43042a95Da1e42",
4444
"token": "0xD4fdec44DB9D44B8f2b6d529620f9C0C7066A2c1",
4545
"winning_probability_oracle": "0x3C5CbDfc873a6a52093c1d3801E29c2D78b26c7f"

ethereum/bindings/src/constants.rs

Lines changed: 158 additions & 0 deletions
Large diffs are not rendered by default.

ethereum/bindings/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
mod codegen;
88

99
pub mod config;
10+
pub mod constants;
1011

1112
#[cfg_attr(rustfmt, rustfmt_skip)]
1213
pub use codegen::*;

ethereum/contracts/src/node-stake/NodeStakeFactory.sol

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,7 @@ contract HoprNodeStakeFactory is HoprNodeStakeFactoryEvents, Ownable2Step, IERC7
426426
_prepareSafeTx(safeProxyAddr, swapOwnerData);
427427
}
428428

429+
emit NewHoprNodeStakeModuleForSafe(moduleProxy, safeProxyAddr);
429430
return (moduleProxy, safeProxyAddr);
430431
}
431432

@@ -493,6 +494,7 @@ contract HoprNodeStakeFactory is HoprNodeStakeFactoryEvents, Ownable2Step, IERC7
493494
_prepareSafeTx(safeProxyAddr, swapOwnerData);
494495
}
495496

497+
emit NewHoprNodeStakeModuleForSafe(moduleProxy, safeProxyAddr);
496498
return (moduleProxy, safeProxyAddr);
497499
}
498500

ethereum/contracts/src/node-stake/migration/NodeSafeMigration.sol

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ abstract contract HoprNodeSafeMigrationEvents {
2626
*/
2727
event SafeAndModuleMigrationCompleted(address safeProxy, address oldModuleProxy, address newModuleProxy);
2828
event ChangedModuleImplementation(address moduleProxy);
29+
event DeployedNewV4Module(address newModuleProxy);
2930
}
3031

3132
/**
@@ -159,4 +160,25 @@ contract HoprNodeSafeMigration is HoprNodeSafeMigrationEvents, SafeMigration, Ex
159160

160161
emit SafeAndModuleMigrationCompleted(address(this), oldModuleProxy, newModuleProxy);
161162
}
163+
164+
/**
165+
* @notice Deploy a new v4 module and enable it in the Safe, without removing the old module.
166+
* @param defaultTarget Default target address for the new module
167+
* @param nonce Nonce for the new module deployment
168+
* @param nodes List of node addresses to be included in the new module
169+
* @return newModuleProxy Address of the newly deployed module proxy
170+
*/
171+
function deployNewV4Module(bytes32 defaultTarget, uint256 nonce, address[] memory nodes)
172+
public
173+
onlyDelegateCall
174+
returns (address newModuleProxy)
175+
{
176+
// deploy a new module contract
177+
newModuleProxy = IHoprNodeStakeFactory(FACTORY_ADDRESS).deployModule(address(this), defaultTarget, nonce);
178+
// add all the nodes to the new module
179+
IHoprNodeManagementModule(newModuleProxy).includeNodes(nodes);
180+
// enable the newly deployed module
181+
IAvatar(address(this)).enableModule(newModuleProxy);
182+
emit DeployedNewV4Module(newModuleProxy);
183+
}
162184
}

ethereum/contracts/test/node-stake/migration/NodeSafeMigration.t.sol

Lines changed: 56 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -191,12 +191,6 @@ contract NodeSafeMigrationTest is
191191

192192
// migrate the module singleton to the new version via delegatecall from the safe, using delegatecall
193193
vm.prank(caller);
194-
// vm.expectEmit(false, false, false, true, safeAddress);
195-
// emit SafeAndModuleMigrationCompleted(
196-
// safeAddress,
197-
// oldModuleProxy,
198-
// newModuleProxyPrediction
199-
//);
200194
_helperSafeTxnDelegateCall(address(migrationContract), IAvatar(safeAddress), callerPrivateKey, data);
201195

202196
// check the module is now upgraded to the new singleton
@@ -270,12 +264,6 @@ contract NodeSafeMigrationTest is
270264

271265
// migrate the module singleton to the new version via delegatecall from the safe, using delegatecall
272266
vm.prank(caller);
273-
// vm.expectEmit(false, false, false, true, safeAddress);
274-
// emit SafeAndModuleMigrationCompleted(
275-
// safeAddress,
276-
// oldModuleProxy,
277-
// newModuleProxyPrediction
278-
//);
279267
_helperSafeTxnDelegateCall(address(migrationContract), IAvatar(safeAddress), callerPrivateKey, data);
280268

281269
// check the module is now upgraded to the new singleton
@@ -292,6 +280,62 @@ contract NodeSafeMigrationTest is
292280
vm.clearMockedCalls();
293281
}
294282

283+
function test_DeployNewV4Module() public mockTokenChannel {
284+
uint256 callerPrivateKey = 0xCAFE;
285+
address caller = vm.addr(callerPrivateKey);
286+
address[] memory admins = new address[](1);
287+
admins[0] = caller;
288+
address[] memory nodes = new address[](3);
289+
nodes[0] = address(0xCAB1);
290+
nodes[1] = address(0xCAB2);
291+
nodes[2] = address(0xCAB3);
292+
uint256 nonce = 999;
293+
uint256 newNonce = nonce + 1;
294+
295+
// create a safe via the old factory
296+
vm.prank(caller);
297+
(address oldModuleProxy, address payable safeAddress) = oldFactory.clone(nonce, OLD_DEFAULT_TARGET, admins);
298+
// check the safe is created with the old module
299+
assertTrue(IAvatar(safeAddress).isModuleEnabled(address(oldModuleProxy)));
300+
// module proxy uses old implementation, at the implementation slot
301+
assertEq(
302+
vm.load(address(oldModuleProxy), _IMPLEMENTATION_SLOT),
303+
bytes32(uint256(uint160(address(oldModuleSingleton))))
304+
);
305+
// check the safe is owned by the old factory
306+
assertEq(IAvatar(safeAddress).getOwners().length, 1);
307+
assertEq(IAvatar(safeAddress).getOwners()[0], address(caller));
308+
// check the module is owned by the safe
309+
assertEq(IOwner(address(oldModuleProxy)).owner(), safeAddress);
310+
311+
// prepare safe transaction data for migration. Caller should call
312+
// deployNewV4Module(bytes32,uint256,address[] memory)
313+
// on the migration contract via delegatecall from the safe
314+
bytes memory data =
315+
abi.encodeWithSelector(migrationContract.deployNewV4Module.selector, NEW_DEFAULT_TARGET, newNonce, nodes);
316+
// predict the new module deployment address
317+
address newModuleProxyPrediction =
318+
newFactory.predictModuleAddress(safeAddress, newNonce, safeAddress, NEW_DEFAULT_TARGET);
319+
320+
// migrate the module singleton to the new version via delegatecall from the safe, using delegatecall
321+
vm.prank(caller);
322+
_helperSafeTxnDelegateCall(address(migrationContract), IAvatar(safeAddress), callerPrivateKey, data);
323+
324+
// check the module is now upgraded to the new singleton
325+
// new module is now owned by the safe
326+
assertEq(IOwner(address(newModuleProxyPrediction)).owner(), safeAddress);
327+
// new module is enabled in the safe
328+
assertTrue(IAvatar(safeAddress).isModuleEnabled(address(newModuleProxyPrediction)));
329+
// old module is STILL enabled in the safe
330+
assertTrue(IAvatar(safeAddress).isModuleEnabled(address(oldModuleProxy)));
331+
assertEq(
332+
vm.load(address(newModuleProxyPrediction), _IMPLEMENTATION_SLOT),
333+
bytes32(uint256(uint160(address(newModuleSingleton))))
334+
);
335+
336+
vm.clearMockedCalls();
337+
}
338+
295339
function _helperSafeTxnDelegateCall(address to, IAvatar safeInstance, uint256 senderPrivateKey, bytes memory data)
296340
private
297341
{

0 commit comments

Comments
 (0)