-
Notifications
You must be signed in to change notification settings - Fork 9
/
Copy pathRestrictionManager.sol
147 lines (122 loc) · 5.6 KB
/
RestrictionManager.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity 0.8.26;
import {Auth} from "src/Auth.sol";
import {IRoot} from "src/interfaces/IRoot.sol";
import {ITranche} from "src/interfaces/token/ITranche.sol";
import {IHook, HookData} from "src/interfaces/token/IHook.sol";
import {MessagesLib} from "src/libraries/MessagesLib.sol";
import {BitmapLib} from "src/libraries/BitmapLib.sol";
import {BytesLib} from "src/libraries/BytesLib.sol";
import {IERC165} from "src/interfaces/IERC7575.sol";
import {RestrictionUpdate, IRestrictionManager} from "src/interfaces/token/IRestrictionManager.sol";
/// @title Restriction Manager
/// @notice Hook implementation that:
/// * Requires adding accounts to the memberlist before they can receive tokens
/// * Supports freezing accounts which blocks transfers both to and from them
/// * Allows authTransferFrom calls
///
/// @dev The first 8 bytes (uint64) of hookData is used for the memberlist valid until date,
/// the last bit is used to denote whether the account is frozen.
contract RestrictionManager is Auth, IRestrictionManager, IHook {
using BitmapLib for *;
using BytesLib for bytes;
/// @dev Least significant bit
uint8 public constant FREEZE_BIT = 0;
IRoot public immutable root;
constructor(address root_, address deployer) Auth(deployer) {
root = IRoot(root_);
}
// --- Callback from tranche token ---
/// @inheritdoc IHook
function onERC20Transfer(address from, address to, uint256 value, HookData calldata hookData)
external
virtual
returns (bytes4)
{
require(checkERC20Transfer(from, to, value, hookData), "RestrictionManager/transfer-blocked");
return IHook.onERC20Transfer.selector;
}
/// @inheritdoc IHook
function onERC20AuthTransfer(
address, /* sender */
address, /* from */
address, /* to */
uint256, /* value */
HookData calldata /* hookData */
) external pure returns (bytes4) {
return IHook.onERC20AuthTransfer.selector;
}
// --- ERC1404 implementation ---
/// @inheritdoc IHook
function checkERC20Transfer(address from, address to, uint256, /* value */ HookData calldata hookData)
public
view
returns (bool)
{
if (uint128(hookData.from).getBit(FREEZE_BIT) == true && !root.endorsed(from)) {
// Source is frozen and not endorsed
return false;
}
if (root.endorsed(to) || to == address(0)) {
// Destination is endorsed and source was already checked, so the transfer is allowed
return true;
}
uint128 toHookData = uint128(hookData.to);
if (toHookData.getBit(FREEZE_BIT) == true) {
// Destination is frozen
return false;
}
if (toHookData >> 64 < block.timestamp) {
// Destination is not a member
return false;
}
return true;
}
// --- Incoming message handling ---
/// @inheritdoc IHook
function updateRestriction(address token, bytes memory update) external auth {
RestrictionUpdate updateId = RestrictionUpdate(update.toUint8(0));
if (updateId == RestrictionUpdate.UpdateMember) updateMember(token, update.toAddress(1), update.toUint64(33));
else if (updateId == RestrictionUpdate.Freeze) freeze(token, update.toAddress(1));
else if (updateId == RestrictionUpdate.Unfreeze) unfreeze(token, update.toAddress(1));
else revert("RestrictionManager/invalid-update");
}
/// @inheritdoc IRestrictionManager
function freeze(address token, address user) public auth {
require(user != address(0), "RestrictionManager/cannot-freeze-zero-address");
require(!root.endorsed(user), "RestrictionManager/endorsed-user-cannot-be-frozen");
uint128 hookData = uint128(ITranche(token).hookDataOf(user));
ITranche(token).setHookData(user, bytes16(hookData.setBit(FREEZE_BIT, true)));
emit Freeze(token, user);
}
/// @inheritdoc IRestrictionManager
function unfreeze(address token, address user) public auth {
uint128 hookData = uint128(ITranche(token).hookDataOf(user));
ITranche(token).setHookData(user, bytes16(hookData.setBit(FREEZE_BIT, false)));
emit Unfreeze(token, user);
}
/// @inheritdoc IRestrictionManager
function isFrozen(address token, address user) public view returns (bool) {
return uint128(ITranche(token).hookDataOf(user)).getBit(FREEZE_BIT);
}
// --- Managing members ---
/// @inheritdoc IRestrictionManager
function updateMember(address token, address user, uint64 validUntil) public auth {
require(block.timestamp <= validUntil, "RestrictionManager/invalid-valid-until");
require(!root.endorsed(user), "RestrictionManager/endorsed-user-cannot-be-updated");
uint128 hookData = uint128(validUntil) << 64;
hookData.setBit(FREEZE_BIT, isFrozen(token, user));
ITranche(token).setHookData(user, bytes16(hookData));
emit UpdateMember(token, user, validUntil);
}
/// @inheritdoc IRestrictionManager
function isMember(address token, address user) external view returns (bool isValid, uint64 validUntil) {
validUntil = abi.encodePacked(ITranche(token).hookDataOf(user)).toUint64(0);
isValid = validUntil >= block.timestamp;
}
// --- ERC165 support ---
/// @inheritdoc IERC165
function supportsInterface(bytes4 interfaceId) external pure override returns (bool) {
return interfaceId == type(IHook).interfaceId || interfaceId == type(IERC165).interfaceId;
}
}