• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

NexusMutual / smart-contracts / #1329

19 Aug 2025 03:57PM UTC coverage: 53.568% (+0.04%) from 53.527%
#1329

Pull #1428

shark0der
test: fix governance test after cap introduction
Pull Request #1428: feat: v3 solidity todos

824 of 1662 branches covered (49.58%)

Branch coverage included in aggregate %.

6 of 6 new or added lines in 3 files covered. (100.0%)

21 existing lines in 3 files now uncovered.

1608 of 2878 relevant lines covered (55.87%)

13.36 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

84.89
/contracts/modules/governance/Registry.sol
1
// SPDX-License-Identifier: GPL-3.0-only
2

3
pragma solidity ^0.8.28;
4

5
import "../../abstract/EIP712.sol";
6
import "../../abstract/RegistryAware.sol";
7
import "../../interfaces/INXMMaster.sol";
8
import "../../interfaces/IRegistry.sol";
9
import "../../interfaces/ITokenController.sol";
10
import "../../libraries/SafeUintCast.sol";
11
import "./UpgradeableProxy.sol";
12

13
contract Registry is IRegistry, EIP712 {
14
  using SafeUintCast for uint;
15

16
  // contracts
17
  mapping(uint index => Contract) internal contracts;
18
  mapping(address contractAddress => uint index) internal contractIndexes;
19

20
  // membership
21
  MembersMeta internal membersMeta; // 1 slot
22
  mapping(uint memberId => address member) internal members;
23
  mapping(address member => uint memberId) internal memberIds;
24
  mapping(address member => bool used) internal wasAddressUsedForJoining;
25

26
  // advisory board seat assignment
27
  mapping(uint seat => uint memberId) internal seatToMember;
28
  mapping(uint memberId => uint seat) internal memberToSeat;
29

30
  // emergency pause
31
  mapping(address => bool) public isEmergencyAdmin;
32
  SystemPause internal systemPause; // 3 slots
33

34
  modifier onlyGovernor() {
35
    address governor = contracts[C_GOVERNOR].addr;
99✔
36
    require(msg.sender == governor, OnlyGovernor());
99✔
37
    _;
91✔
38
  }
39

40
  modifier onlyEmergencyAdmin() {
41
    require(isEmergencyAdmin[msg.sender], OnlyEmergencyAdmin());
35✔
42
    _;
33✔
43
  }
44

45
  modifier whenNotPaused(uint mask) {
46
    uint config = systemPause.config;
70✔
47
    uint maskWithGlobal = mask | PAUSE_GLOBAL;
70✔
48
    require(config & maskWithGlobal == 0, Paused(config, mask));
70✔
49
    _;
62✔
50
  }
51

52
  uint public constant ADVISORY_BOARD_SEATS = 5;
53
  uint public constant JOIN_FEE = 0.002 ether; // no more, no less
54
  bytes32 private constant JOIN_TYPEHASH = keccak256("Join(address member)");
55

56
  INXMMaster public immutable master;
57

58
  constructor(
59
    address _verifyingAddress,
60
    address _master
61
  ) EIP712("NexusMutualRegistry", "1.0.0", _verifyingAddress) {
62
    master = INXMMaster(_master);
4✔
63
  }
64

65
  /* == EMERGENCY PAUSE == */
66

67
  function setEmergencyAdmin(address _emergencyAdmin, bool enabled) external onlyGovernor {
6✔
68
    isEmergencyAdmin[_emergencyAdmin] = enabled;
4✔
69
    emit EmergencyAdminSet(_emergencyAdmin, enabled);
4✔
70
  }
71

72
  function proposePauseConfig(uint config) external onlyEmergencyAdmin {
18✔
73
    systemPause.proposedConfig = config.toUint48();
17✔
74
    systemPause.proposer = msg.sender;
16✔
75
    emit PauseConfigProposed(config, msg.sender);
16✔
76
  }
77

78
  function confirmPauseConfig(uint config) external onlyEmergencyAdmin {
17✔
79
    require(systemPause.proposer != address(0), NoConfigProposed());
16✔
80
    require(systemPause.proposer != msg.sender, ProposerCannotConfirmPause());
15✔
81
    require(systemPause.proposedConfig == uint48(config), PauseConfigMismatch());
14✔
82
    systemPause.config = uint48(config);
13✔
83
    delete systemPause.proposedConfig;
13✔
84
    delete systemPause.proposer;
13✔
85
    emit PauseConfigConfirmed(config, msg.sender);
13✔
86
  }
87

88
  function getSystemPause() external view returns (SystemPause memory) {
89
    return systemPause;
4✔
90
  }
91

92
  function getPauseConfig() external view returns (uint config) {
93
    return systemPause.config;
5✔
94
  }
95

96
  function isPaused(uint mask) external view returns (bool) {
97
    return systemPause.config & mask != 0;
13✔
98
  }
99

100
  /* == MEMBERSHIP MANAGEMENT == */
101

102
  function isMember(address member) external view returns (bool) {
103
    return memberIds[member] != 0;
16✔
104
  }
105

106
  function getMemberId(address member) external view returns (uint) {
107
    return memberIds[member];
14✔
108
  }
109

110
  function getMemberAddress(uint memberId) external view returns (address) {
111
    return members[memberId];
3✔
112
  }
113

114
  function getMemberCount() external view returns (uint) {
115
    return membersMeta.memberCount;
6✔
116
  }
117

118
  function getLastMemberId() external view returns (uint) {
119
    return membersMeta.lastMemberId;
4✔
120
  }
121

122
  function join(address member, bytes memory signature) external payable whenNotPaused(PAUSE_MEMBERSHIP) {
49✔
123
    require(memberIds[member] == 0, AlreadyMember());
47✔
124
    require(wasAddressUsedForJoining[member] == false, AddressAlreadyUsedForJoining());
46✔
125
    require(msg.value == JOIN_FEE, InvalidJoinFee());
44✔
126

127
    (bool success, ) = payable(contracts[C_POOL].addr).call{value: JOIN_FEE}("");
41✔
128
    require(success, FeeTransferFailed());
41✔
129

130
    bytes memory message = abi.encode(JOIN_TYPEHASH, member);
40✔
131
    address signer = recoverSigner(message, signature);
40✔
132
    require(membersMeta.kycAuthAddress == signer, InvalidSignature());
38✔
133
    wasAddressUsedForJoining[member] = true;
31✔
134

135
    uint memberId = ++membersMeta.lastMemberId;
31✔
136
    ++membersMeta.memberCount;
31✔
137
    memberIds[member] = memberId;
31✔
138
    members[memberId] = member;
31✔
139

140
    ITokenController(contracts[C_TOKEN_CONTROLLER].addr).addToWhitelist(member);
31✔
141

142
    emit MembershipChanged(memberId, address(0), member);
31✔
143
  }
144

145
  function switchTo(address to) external whenNotPaused(PAUSE_MEMBERSHIP) {
9✔
146
    _switch(msg.sender, to, true);
7✔
147
  }
148

149
  function switchFor(address from, address to) external whenNotPaused(PAUSE_MEMBERSHIP) {
5✔
150
    require(master.getLatestAddress("MR") == msg.sender, NotMemberRoles());
3✔
151
    _switch(from, to, false);
2✔
152
  }
153

154
  function _switch(address from, address to, bool includeNxmTokens) internal {
155
    uint memberId = memberIds[from];
9✔
156
    require(memberId != 0, NotMember());
9✔
157
    require(memberIds[to] == 0, AlreadyMember());
8✔
158

159
    delete memberIds[from];
6✔
160
    memberIds[to] = memberId;
6✔
161
    members[memberId] = to;
6✔
162

163
    ITokenController(contracts[C_TOKEN_CONTROLLER].addr).switchMembership(from, to, includeNxmTokens);
6✔
164

165
    emit MembershipChanged(memberId, from, to);
6✔
166
  }
167

168
  function leave() external whenNotPaused(PAUSE_MEMBERSHIP) {
7✔
169

170
    uint memberId = memberIds[msg.sender];
5✔
171
    require(memberId != 0, NotMember());
5✔
172
    require(memberToSeat[memberId] == 0, AdvisoryBoardMemberCannotLeave());
4✔
173

174
    // todo:
175
    // address[] memory pools = TC.getManagerStakingPools(memberId)
176
    // require(pools.length == 0, StakingPoolManagersCannotLeave());
177

178
    delete members[memberId];
3✔
179
    delete memberIds[msg.sender];
3✔
180
    --membersMeta.memberCount;
3✔
181

182
    ITokenController(contracts[C_TOKEN_CONTROLLER].addr).removeFromWhitelist(msg.sender);
3✔
183

184
    emit MembershipChanged(memberId, msg.sender, address(0));
3✔
185
  }
186

187
  function setKycAuthAddress(address _kycAuthAddress) external onlyGovernor {
2!
188
    membersMeta.kycAuthAddress = _kycAuthAddress;
2✔
189
  }
190

191
  /* == ADVISORY BOARD MANAGEMENT == */
192
  function isAdvisoryBoardMember(address member) external view returns (bool) {
193
    uint memberId = memberIds[member];
10✔
194
    return memberToSeat[memberId] != 0;
10✔
195
  }
196

197
  function getAdvisoryBoardSeat(address member) external view returns (uint) {
198
    uint memberId = memberIds[member];
8✔
199
    uint seat = memberToSeat[memberId];
8✔
200
    require(seat != 0, NotAdvisoryBoardMember());
8✔
201
    return seat;
7✔
202
  }
203

204
  function getMemberIdBySeat(uint seat) external view returns (uint) {
205
    require(seat != 0 && seat <= ADVISORY_BOARD_SEATS, InvalidSeat());
9✔
206
    return seatToMember[seat];
7✔
207
  }
208

209
  function getMemberAddressBySeat(uint seat) external view returns (address) {
210
    require(seat != 0 && seat <= ADVISORY_BOARD_SEATS, InvalidSeat());
7✔
211
    return members[seatToMember[seat]];
5✔
212
  }
213

214
  function swapAdvisoryBoardMember(uint from, uint to) external onlyGovernor {
8✔
215
    require(from != 0, NotMember());
6✔
216
    require(to != 0, NotMember());
5✔
217
    require(members[to] != address(0), NotMember());
4✔
218

219
    require(memberToSeat[from] != 0, NotAdvisoryBoardMember());
3✔
220
    require(memberToSeat[to] == 0, AlreadyAdvisoryBoardMember());
2✔
221

222
    uint seat = memberToSeat[from];
1✔
223
    memberToSeat[from] = 0;
1✔
224
    memberToSeat[to] = seat;
1✔
225
    seatToMember[seat] = to;
1✔
226

227
    emit AdvisoryBoardMemberSwapped(seat, from, to);
1✔
228
  }
229

230
  /* == CONTRACT MANAGEMENT == */
231

232
  function isValidContractIndex(uint index) public pure returns (bool) {
233
    // cheap validation that only one bit is set (i.e. it's a power of two)
234
    unchecked { return index & (index - 1) == 0 && index > 0; }
1,456✔
235
  }
236

237
  function isProxyContract(uint index) external view returns (bool) {
238
    require(isValidContractIndex(index), InvalidContractIndex());
523✔
239
    return contracts[index].isProxy;
522✔
240
  }
241

242
  function getContractAddressByIndex(uint index) external view returns (address payable) {
243
    require(isValidContractIndex(index), InvalidContractIndex());
597✔
244
    return payable(contracts[index].addr);
596✔
245
  }
246

247
  function getContractIndexByAddress(address contractAddress) external view returns (uint) {
248
    return contractIndexes[contractAddress];
292✔
249
  }
250

251
  function getContracts(uint[] memory indexes) external view returns (Contract[] memory _contracts) {
252
    _contracts = new Contract[](indexes.length);
5✔
253
    for (uint i = 0; i < indexes.length; i++) {
5✔
254
      require(isValidContractIndex(indexes[i]), InvalidContractIndex());
8✔
255
      _contracts[i] = contracts[indexes[i]];
7✔
256
    }
257
  }
258

259
  function deployContract(uint index, bytes32 salt, address implementation) external onlyGovernor {
25✔
260
    _deployContract(index, salt, implementation);
24✔
261
  }
262

263
  function _deployContract(uint index, bytes32 salt, address implementation) internal {
264
    require(isValidContractIndex(index), InvalidContractIndex());
25✔
265
    require(contracts[index].addr == address(0), ContractAlreadyExists());
24✔
266

267
    UpgradeableProxy proxy = new UpgradeableProxy{salt: bytes32(salt)}();
22✔
268
    proxy.upgradeTo(implementation);
22✔
269

270
    contracts[index] = Contract({ addr: address(proxy), isProxy: true });
22✔
271
    contractIndexes[address(proxy)] = index;
22✔
272

273
    emit ContractDeployed(index, address(proxy), implementation);
22✔
274
  }
275

276
  function upgradeContract(uint index, address implementation) external onlyGovernor {
22✔
277
    Contract memory _contract = contracts[index];
21✔
278
    require(_contract.addr != address(0), ContractDoesNotExist());
21✔
279
    require(_contract.isProxy, ContractIsNotProxy());
20✔
280

281
    UpgradeableProxy proxy = UpgradeableProxy(payable(_contract.addr));
19✔
282
    proxy.upgradeTo(implementation);
19✔
283

284
    emit ContractUpgraded(index, address(proxy), implementation);
19✔
285
  }
286

287
  function addContract(uint index, address contractAddress, bool isProxy) external onlyGovernor {
25✔
288
    _addContract(index, contractAddress, isProxy);
24✔
289
  }
290

291
  function _addContract(uint index, address contractAddress, bool isProxy) internal {
292
    require(isValidContractIndex(index), InvalidContractIndex());
24✔
293
    require(contractAddress != address(0), InvalidContractAddress());
23✔
294
    require(contracts[index].addr == address(0), ContractAlreadyExists());
22✔
295
    require(!isProxy || UpgradeableProxy(payable(contractAddress)).proxyOwner() == address(this), NotProxyOwner());
21✔
296

297
    contracts[index] = Contract({addr: contractAddress, isProxy: isProxy});
20✔
298
    contractIndexes[contractAddress] = index;
20✔
299

300
    emit ContractAdded(index, contractAddress, isProxy);
20✔
301
  }
302

303
  function removeContract(uint index) external onlyGovernor {
11✔
304
    Contract memory _contract = contracts[index];
10✔
305
    require(_contract.addr != address(0), ContractDoesNotExist());
10✔
306

307
    contractIndexes[_contract.addr] = 0;
9✔
308
    delete contracts[index];
9✔
309

310
    emit ContractRemoved(index, _contract.addr, _contract.isProxy);
9✔
311
  }
312

313
  function migrate(
314
    address governorImplementation,
315
    address coverNFT,
316
    address stakingNFT,
317
    address token,
318
    bytes32 governorSalt,
319
    bytes32 poolSalt,
320
    bytes32 swapOperatorSalt,
321
    bytes32 assessmentSalt,
322
    bytes32 claimsSalt
323
  ) external {
324

UNCOV
325
    require(contracts[C_REGISTRY].addr == address(0), 'Registry: Already migrated');
×
UNCOV
326
    require(master.getLatestAddress("GV") == msg.sender, 'Registry: Not Governance');
×
327

328
    // all codes: SP CO AS CP CI ST TC RA PC P1 MR MC GV LO MS
329
    // copy over: SP CO    CP    ST TC RA                LO
330
    // drop:                              PC    MR MC       MS
331
    // redeploy:        AS    CI             P1       GV
332
    // add new:                                                SO
333

334
    // registry is marked as non proxy because registry is not its own owner
335
    _addContract(C_REGISTRY, address(this), false);
×
336

337
    _addContract(C_TOKEN, token, false);
×
338
    _addContract(C_COVER_NFT, coverNFT, false);
×
339
    _addContract(C_STAKING_NFT, stakingNFT, false);
×
340

341
    _addContract(C_STAKING_PRODUCTS, master.getLatestAddress("SP"), true);
×
342
    _addContract(C_COVER, master.getLatestAddress("CO"), true);
×
343
    _addContract(C_COVER_PRODUCTS, master.getLatestAddress("CP"), true);
×
UNCOV
344
    _addContract(C_SAFE_TRACKER, master.getLatestAddress("ST"), true);
×
345
    _addContract(C_TOKEN_CONTROLLER, master.getLatestAddress("TC"), true);
×
346
    _addContract(C_RAMM, master.getLatestAddress("RA"), true);
×
347
    _addContract(C_LIMIT_ORDERS, master.getLatestAddress("LO"), true);
×
348

349
    _deployContract(C_GOVERNOR, governorSalt, governorImplementation);
×
UNCOV
350
    _deployContract(C_POOL, poolSalt, address(0));
×
UNCOV
351
    _deployContract(C_SWAP_OPERATOR, swapOperatorSalt, address(0));
×
UNCOV
352
    _deployContract(C_ASSESSMENT, assessmentSalt, address(0));
×
UNCOV
353
    _deployContract(C_CLAIMS, claimsSalt, address(0));
×
354
  }
355

356
  function migrateMembers(address[] calldata membersToMigrate) external {
357

358
    require(master.getLatestAddress("MR") == msg.sender, 'Registry: Not MemberRoles');
×
359

UNCOV
360
    uint count = membersToMigrate.length;
×
361

362
    for (uint i = 0; i < count; i++) {
×
UNCOV
363
      address member = membersToMigrate[i];
×
364

365
      if (memberIds[member] != 0) {
×
366
        continue;
×
367
      }
368

UNCOV
369
      uint memberId = ++membersMeta.lastMemberId;
×
370
      ++membersMeta.memberCount;
×
UNCOV
371
      memberIds[member] = memberId;
×
UNCOV
372
      members[memberId] = member;
×
373

UNCOV
374
      emit MembershipChanged(memberId, address(0), member);
×
375
    }
376
  }
377

378
  function migrateAdvisoryBoardMembers(address[] calldata abMembers) external {
379

380
    require(master.getLatestAddress("MR") == msg.sender, 'Registry: Not MemberRoles');
2!
381

382
    uint count = abMembers.length;
2✔
383
    require(count == ADVISORY_BOARD_SEATS, 'Registry: Invalid advisory board count');
2!
384

385
    for (uint i = 0; i < count; i++) {
2✔
386
      address member = abMembers[i];
10✔
387
      uint memberId = memberIds[member];
10✔
388
      require(memberId != 0, NotMember());
10!
389

390
      uint seat = i + 1;
10✔
391
      require(seatToMember[seat] == 0, 'Registry: AB seat already taken');
10!
392

393
      memberToSeat[memberIds[member]] = seat;
10✔
394
      seatToMember[seat] = memberId;
10✔
395
    }
396
  }
397

398
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc