Skip to content

Commit 4b00d2e

Browse files
authored
Merge pull request #269 from hopperlabsxyz/feat/blacklist
Feat/blacklist
2 parents 2f20042 + 09b1538 commit 4b00d2e

15 files changed

Lines changed: 362 additions & 52 deletions

File tree

.github/workflows/run-tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -462,4 +462,4 @@ jobs:
462462
path: .
463463
- run: forge test -vvv
464464
env:
465-
PROXY: false
465+
PROXY: false

script/safe_scripts/updateDefaultLogic.s.sol

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,11 @@ import {
66
ProxyAdmin
77
} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
88

9-
import {BeaconProxyFactory} from "@src/protocol-v1/BeaconProxyFactory.sol";
10-
import {OptinProxyFactory} from "@src/protocol-v2/OptinProxyFactory.sol";
11-
import {LogicRegistry, ProtocolRegistry} from "@src/protocol-v2/ProtocolRegistry.sol";
12-
import {Script, console} from "forge-std/Script.sol";
9+
import {LogicRegistry} from "@src/protocol-v2/ProtocolRegistry.sol";
1310

1411
// import {Vault} from "../src/v0.5.0/Vault.sol";
1512

1613
import {BatchScript} from "../tools/BatchScript.sol";
17-
import {Options, Upgrades} from "openzeppelin-foundry-upgrades/Upgrades.sol";
18-
19-
/*
20-
This script will deploy the OptinProxyFactory, propose safe txs to:
21-
- update the fee registry with the logicRegistry
22-
- update the default implementation in the logic registry
23-
- upgrade the set of signers of the DAO multisig
24-
*/
2514

2615
contract UpgradeProtocolRegistry is BatchScript {
2716
address registry;

src/v0.6.0/Whitelistable.sol

Lines changed: 99 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,46 +2,50 @@
22
pragma solidity 0.8.26;
33

44
import {Roles} from "./Roles.sol";
5+
import {SanctionsList} from "./interfaces/SanctionsList.sol";
56
import {RolesLib} from "./libraries/RolesLib.sol";
67
import {WhitelistableLib} from "./libraries/WhitelistableLib.sol";
8+
import {AccessMode} from "./primitives/Enums.sol";
79

810
abstract contract Whitelistable is Roles {
911
/// @custom:storage-definition erc7201:hopper.storage.Whitelistable
1012
/// @param isWhitelisted The mapping of whitelisted addresses.
1113
/// @param isActivated The flag to check if the whitelist is activated.
1214
struct WhitelistableStorage {
1315
mapping(address => bool) isWhitelisted;
14-
bool isActivated;
16+
// in v0.6.0, we replace the bool isActivated with a enum AccessMode
17+
// bool isActivated; --> AccessMode accessMode;
18+
AccessMode accessMode;
19+
// added in v0.6.0
20+
mapping(address => bool) isBlacklisted;
21+
SanctionsList externalSanctionList;
1522
}
1623

1724
/// @dev Initializes the whitelist.
18-
/// @param activate if the whitelist should be activated.
25+
/// @param accessMode the access mode of the whitelist.
1926
// solhint-disable-next-line func-name-mixedcase
2027
function __Whitelistable_init(
21-
bool activate
28+
AccessMode accessMode,
29+
address externalSanctionsList
2230
) internal onlyInitializing {
23-
if (activate) {
24-
WhitelistableStorage storage $ = WhitelistableLib._getWhitelistableStorage();
25-
$.isActivated = true;
26-
}
31+
WhitelistableLib.switchAccessMode(accessMode);
32+
WhitelistableLib.setExternalSanctionsList(SanctionsList(externalSanctionsList));
2733
}
2834

29-
/// @notice Deactivates the whitelist
30-
function disableWhitelist() public onlyOwner {
31-
WhitelistableLib.disableWhitelist();
35+
function switchAccessMode(
36+
AccessMode newMode
37+
) public onlyOwner {
38+
WhitelistableLib.switchAccessMode(newMode);
3239
}
3340

34-
/// @notice Checks if an account is whitelisted
41+
/// @notice Checks if an account is whitelisted or blacklisted
42+
/// @dev In v0.6.0, this function is extended to also enforce blacklist checks.
3543
/// @param account The address of the account to check
36-
/// @return True if the account is whitelisted, false otherwise
44+
/// @return True if the account is whitelisted or not blacklisted, false otherwise
3745
function isWhitelisted(
3846
address account
3947
) public view returns (bool) {
40-
WhitelistableStorage storage $ = WhitelistableLib._getWhitelistableStorage();
41-
if (RolesLib._getRolesStorage().feeRegistry.protocolFeeReceiver() == account) {
42-
return true;
43-
}
44-
return $.isActivated ? $.isWhitelisted[account] : true;
48+
return WhitelistableLib.isWhitelisted(account);
4549
}
4650

4751
/// @notice Adds multiple accounts to the whitelist
@@ -58,4 +62,82 @@ abstract contract Whitelistable is Roles {
5862
) external onlyWhitelistManager {
5963
WhitelistableLib.revokeFromWhitelist(accounts);
6064
}
65+
66+
/// @notice Adds multiple accounts to the blacklist
67+
function addToBlacklist(
68+
address[] memory accounts
69+
) external onlyWhitelistManager {
70+
WhitelistableLib.addToBlacklist(accounts);
71+
}
72+
73+
/// @notice Removes multiple accounts from the blacklist
74+
function revokeFromBlacklist(
75+
address[] memory accounts
76+
) external onlyWhitelistManager {
77+
WhitelistableLib.revokeFromBlacklist(accounts);
78+
}
79+
80+
/// @notice Sets the external sanctions list
81+
function setExternalSanctionsList(
82+
SanctionsList sanctionsList
83+
) external onlyWhitelistManager {
84+
WhitelistableLib.setExternalSanctionsList(sanctionsList);
85+
}
6186
}
87+
88+
// v0.6.0 storage layout changes:
89+
90+
// ==================== ORIGINAL STORAGE LAYOUT ====================
91+
// slot 0: isWhitelisted (mapping pointer)
92+
// Type: mapping(address => bool)
93+
// Description: Pointer to the mapping of whitelisted addresses
94+
// Visual representation (bytes32):
95+
// 0x0000000000000000000000000000000000000000000000000000000000000000
96+
// | 32 bytes |
97+
98+
// slot 1: isActivated
99+
// Type: bool
100+
// Description: Activation flag (only 1 byte used, right-aligned)
101+
// Visual representation (bytes32):
102+
// isActivated
103+
// 0x0000000000000000000000000000000000000000000000000000000000000000
104+
// | 31 bytes unused |xx|
105+
// (1 byte)
106+
107+
// ==================== NEW STORAGE LAYOUT ====================
108+
// slot 0: isWhitelisted (mapping pointer)
109+
// Type: mapping(address => bool)
110+
// Description: Pointer to the mapping of whitelisted addresses
111+
// Visual representation (bytes32):
112+
// 0x0000000000000000000000000000000000000000000000000000000000000000
113+
// | 32 bytes |
114+
// Note: The actual mapping data is stored at keccak256(key . slot)
115+
116+
// slot 1: accessMode
117+
// Type: enum AccessMode (uint8)
118+
// Description: Access mode enum value (only 1 byte used, right-aligned)
119+
// Possible values: 0x00 (BlacklistMode), 0x01 (WhitelistMode), 0x02 (Deactivated), etc.
120+
// Visual representation (bytes32):
121+
// accessMode
122+
// 0x0000000000000000000000000000000000000000000000000000000000000001
123+
// | 31 bytes unused |xx|
124+
// (1 byte)
125+
126+
// slot 2: isBlacklisted (mapping pointer)
127+
// Type: mapping(address => bool)
128+
// Description: Pointer to the mapping of blacklisted addresses
129+
// Visual representation (bytes32):
130+
// 0x0000000000000000000000000000000000000000000000000000000000000000
131+
// | 32 bytes |
132+
// Note: The actual mapping data is stored at keccak256(key . slot)
133+
134+
// Upgrades scenario:
135+
// we upgrade the whitelistable contract from v0.5.0 to v0.6.0
136+
// 1) isActivated is 0 (deactivated)
137+
// 1.1) accessMode is 0 (blacklist)
138+
// --> vault remains as accessible as before the upgrade
139+
// if the admin wants to disable the blacklist, he can call the disableWhitelist function
140+
// 2) isActivated is 1 (activated)
141+
// 2.1) accessMode is 1 (whitelist)
142+
// --> vault remains in whitelist mode
143+
// if the admin wants to disable the whitelist, he can call the disableWhitelist function
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity 0.8.26;
3+
4+
interface SanctionsList {
5+
function isSanctioned(
6+
address addr
7+
) external view returns (bool);
8+
}

src/v0.6.0/libraries/WhitelistableLib.sol

Lines changed: 99 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,16 @@
22
pragma solidity 0.8.26;
33

44
import {Whitelistable} from "../Whitelistable.sol";
5-
import {WhitelistDisabled, WhitelistUpdated} from "../primitives/Events.sol";
5+
import {SanctionsList} from "../interfaces/SanctionsList.sol";
6+
import {AccessMode} from "../primitives/Enums.sol";
7+
import {
8+
AccessModeUpdated,
9+
BlacklistUpdated,
10+
ExternalSanctionsListUpdated,
11+
WhitelistDisabled,
12+
WhitelistUpdated
13+
} from "../primitives/Events.sol";
14+
import {RolesLib} from "./RolesLib.sol";
615

716
library WhitelistableLib {
817
// keccak256(abi.encode(uint256(keccak256("hopper.storage.Whitelistable")) - 1)) & ~bytes32(uint256(0xff))
@@ -23,16 +32,23 @@ library WhitelistableLib {
2332
}
2433
}
2534

35+
/// @notice Adds multiple accounts to the whitelist
2636
function addToWhitelist(
2737
address[] memory accounts
2838
) public {
2939
Whitelistable.WhitelistableStorage storage $ = _getWhitelistableStorage();
30-
for (uint256 i = 0; i < accounts.length; i++) {
40+
uint256 i = 0;
41+
for (; i < accounts.length;) {
3142
$.isWhitelisted[accounts[i]] = true;
3243
emit WhitelistUpdated(accounts[i], true);
44+
// solhint-disable-next-line no-inline-assembly
45+
unchecked {
46+
++i;
47+
}
3348
}
3449
}
3550

51+
/// @notice Removes multiple accounts from the whitelist
3652
function revokeFromWhitelist(
3753
address[] memory accounts
3854
) public {
@@ -48,9 +64,87 @@ library WhitelistableLib {
4864
}
4965
}
5066

51-
function disableWhitelist() public {
67+
/// @notice Adds multiple accounts to the blacklist
68+
function addToBlacklist(
69+
address[] memory accounts
70+
) public {
5271
Whitelistable.WhitelistableStorage storage $ = _getWhitelistableStorage();
53-
$.isActivated = false;
54-
emit WhitelistDisabled();
72+
uint256 i = 0;
73+
for (; i < accounts.length;) {
74+
$.isBlacklisted[accounts[i]] = true;
75+
emit BlacklistUpdated(accounts[i], true);
76+
// solhint-disable-next-line no-inline-assembly
77+
unchecked {
78+
++i;
79+
}
80+
}
81+
}
82+
83+
/// @notice Removes multiple accounts from the blacklist
84+
function revokeFromBlacklist(
85+
address[] memory accounts
86+
) public {
87+
Whitelistable.WhitelistableStorage storage $ = _getWhitelistableStorage();
88+
uint256 i = 0;
89+
for (; i < accounts.length;) {
90+
$.isBlacklisted[accounts[i]] = false;
91+
emit BlacklistUpdated(accounts[i], false);
92+
// solhint-disable-next-line no-inline-assembly
93+
unchecked {
94+
++i;
95+
}
96+
}
97+
}
98+
99+
/// @notice Switches the access mode
100+
/// @param newMode The new access mode
101+
/// @dev Emits an AccessModeUpdated event with the new mode
102+
function switchAccessMode(
103+
AccessMode newMode
104+
) public {
105+
Whitelistable.WhitelistableStorage storage $ = _getWhitelistableStorage();
106+
107+
$.accessMode = newMode;
108+
emit AccessModeUpdated(newMode);
109+
}
110+
111+
/// @notice Sets the external sanctions list
112+
function setExternalSanctionsList(
113+
SanctionsList externalSanctionList
114+
) public {
115+
Whitelistable.WhitelistableStorage storage $ = _getWhitelistableStorage();
116+
emit ExternalSanctionsListUpdated(address($.externalSanctionList), address(externalSanctionList));
117+
$.externalSanctionList = externalSanctionList;
118+
}
119+
120+
/// @notice Checks if an account is whitelisted or blacklisted
121+
/// @dev In v0.6.0, this function is extended to also enforce blacklist checks.
122+
/// @param account The address of the account to check
123+
/// @return True if the account is whitelisted or not blacklisted, false otherwise
124+
function isWhitelisted(
125+
address account
126+
) public view returns (bool) {
127+
Whitelistable.WhitelistableStorage storage $ = _getWhitelistableStorage();
128+
AccessMode _accessMode = $.accessMode;
129+
130+
if (RolesLib._getRolesStorage().feeRegistry.protocolFeeReceiver() == account) {
131+
// if the account is the protocol fee receiver, it is always whitelisted
132+
return true;
133+
}
134+
// if the whitelist is active, we check if the account is whitelisted
135+
// if the whitelist is in blacklist mode and the account is blacklisted we return false
136+
bool internalListApproval =
137+
_accessMode == AccessMode.Whitelist ? $.isWhitelisted[account] : !$.isBlacklisted[account];
138+
139+
// by default, we consider that the external sanctions list is not set, so we set it to true
140+
bool externalListApproval = true;
141+
142+
// if the external sanctions list is set, we check if the account is not sanctioned
143+
if ($.externalSanctionList != SanctionsList(address(0))) {
144+
externalListApproval = !$.externalSanctionList.isSanctioned(account);
145+
}
146+
147+
// if the account is whitelisted and not sanctioned, we return true
148+
return internalListApproval && externalListApproval;
55149
}
56150
}

src/v0.6.0/primitives/Enums.sol

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ enum State {
99
Closed // The vault is closed; settlement are locked; withdrawals are guaranteed at fixed price per share
1010
}
1111

12+
// ********************* WHITELISTABLE ********************* //
13+
enum AccessMode {
14+
Blacklist,
15+
Whitelist
16+
}
1217
// ********************* FEE MANAGER ********************* //
1318

1419
enum FeeType {

src/v0.6.0/primitives/Events.sol

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// SPDX-License-Identifier: BUSL-1.1
22
pragma solidity 0.8.26;
33

4-
import {FeeType, State} from "./Enums.sol";
4+
import {AccessMode, FeeType, State} from "./Enums.sol";
55
import {Rates} from "./Struct.sol";
66

77
// ********************* VAULT ********************* //
@@ -46,9 +46,19 @@ event RootUpdated(bytes32 indexed root);
4646
/// @param authorized Indicates whether the account is authorized (true) or not (false).
4747
event WhitelistUpdated(address indexed account, bool authorized);
4848

49+
/// @notice Emitted when a blacklist entry is updated.
50+
/// @param account The address of the account being updated.
51+
/// @param blacklisted Indicates whether the account is blacklisted (true) or not (false).
52+
event BlacklistUpdated(address indexed account, bool blacklisted);
53+
4954
/// @notice Emitted when the whitelist is disabled.
5055
event WhitelistDisabled();
5156

57+
/// @notice Emitted when the external sanctions list is updated.
58+
/// @param oldExternalSanctionList The old external sanctions list.
59+
/// @param newExternalSanctionList The new external sanctions list.
60+
event ExternalSanctionsListUpdated(address oldExternalSanctionList, address newExternalSanctionList);
61+
5262
// ********************* ROLES ********************* //
5363

5464
/// @notice Emitted when the whitelist manager role is updated.
@@ -129,6 +139,10 @@ event TotalAssetsLifespanUpdated(uint128 oldLifespan, uint128 newLifespan);
129139
/// @param shares Amount of shares minted to owner
130140
event DepositSync(address indexed sender, address indexed owner, uint256 assets, uint256 shares);
131141

142+
/// @notice Emitted when the access mode is updated.
143+
/// @param newMode The new access mode (Blacklist or Whitelist).
144+
event AccessModeUpdated(AccessMode newMode);
145+
132146
/// @notice Emitted when the max cap is updated.
133147
/// @param previousMaxCap The previous max cap.
134148
/// @param maxCap The new max cap.

0 commit comments

Comments
 (0)