@towns-protocol/contracts 0.0.449 → 0.0.451
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -3
- package/scripts/deployments/facets/DeployEntitlementChecker.s.sol +5 -2
- package/src/base/registry/facets/checker/EntitlementChecker.sol +163 -94
- package/src/base/registry/facets/checker/IEntitlementChecker.sol +52 -28
- package/src/base/registry/facets/xchain/IXChain.sol +6 -5
- package/src/base/registry/facets/xchain/XChain.sol +32 -27
- package/src/base/registry/facets/xchain/XChainCheckLib.sol +8 -6
- package/src/base/registry/facets/xchain/XChainLib.sol +11 -4
- package/src/spaces/facets/gated/EntitlementGated.sol +6 -13
- package/src/spaces/facets/gated/EntitlementGatedBase.sol +40 -83
- package/src/spaces/facets/gated/IEntitlementGated.sol +20 -0
- package/src/spaces/facets/membership/join/MembershipJoin.sol +13 -30
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@towns-protocol/contracts",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.451",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"clean": "forge clean",
|
|
6
6
|
"compile": "forge build",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"@layerzerolabs/oapp-evm": "^0.3.2",
|
|
34
34
|
"@openzeppelin/merkle-tree": "^1.0.8",
|
|
35
35
|
"@prb/test": "^0.6.4",
|
|
36
|
-
"@towns-protocol/prettier-config": "^0.0.
|
|
36
|
+
"@towns-protocol/prettier-config": "^0.0.451",
|
|
37
37
|
"@wagmi/cli": "^2.2.0",
|
|
38
38
|
"forge-std": "github:foundry-rs/forge-std#v1.10.0",
|
|
39
39
|
"prettier": "^3.5.3",
|
|
@@ -50,5 +50,5 @@
|
|
|
50
50
|
"publishConfig": {
|
|
51
51
|
"access": "public"
|
|
52
52
|
},
|
|
53
|
-
"gitHead": "
|
|
53
|
+
"gitHead": "ad68c0f2514e44f51293824ebba5ccc2f8a6494c"
|
|
54
54
|
}
|
|
@@ -15,14 +15,17 @@ library DeployEntitlementChecker {
|
|
|
15
15
|
using DynamicArrayLib for DynamicArrayLib.DynamicArray;
|
|
16
16
|
|
|
17
17
|
function selectors() internal pure returns (bytes4[] memory res) {
|
|
18
|
-
DynamicArrayLib.DynamicArray memory arr = DynamicArrayLib.p().reserve(
|
|
18
|
+
DynamicArrayLib.DynamicArray memory arr = DynamicArrayLib.p().reserve(10);
|
|
19
19
|
arr.p(EntitlementChecker.registerNode.selector);
|
|
20
20
|
arr.p(EntitlementChecker.unregisterNode.selector);
|
|
21
21
|
arr.p(EntitlementChecker.isValidNode.selector);
|
|
22
22
|
arr.p(EntitlementChecker.getNodeCount.selector);
|
|
23
23
|
arr.p(EntitlementChecker.getNodeAtIndex.selector);
|
|
24
24
|
arr.p(EntitlementChecker.getRandomNodes.selector);
|
|
25
|
-
|
|
25
|
+
// requestEntitlementCheck V1 (legacy)
|
|
26
|
+
arr.p(bytes4(keccak256("requestEntitlementCheck(address,bytes32,uint256,address[])")));
|
|
27
|
+
// requestEntitlementCheck unified (enum dispatch)
|
|
28
|
+
arr.p(bytes4(keccak256("requestEntitlementCheck(uint8,bytes)")));
|
|
26
29
|
arr.p(EntitlementChecker.requestEntitlementCheckV2.selector);
|
|
27
30
|
arr.p(EntitlementChecker.getNodesByOperator.selector);
|
|
28
31
|
|
|
@@ -2,41 +2,40 @@
|
|
|
2
2
|
pragma solidity ^0.8.23;
|
|
3
3
|
|
|
4
4
|
// interfaces
|
|
5
|
+
import {IEntitlementGatedBase} from "../../../../spaces/facets/gated/IEntitlementGated.sol";
|
|
5
6
|
import {IEntitlementChecker} from "./IEntitlementChecker.sol";
|
|
6
|
-
import {IEntitlementGatedBase} from "src/spaces/facets/gated/IEntitlementGated.sol";
|
|
7
7
|
|
|
8
8
|
// libraries
|
|
9
|
-
|
|
10
|
-
import {EntitlementCheckerStorage} from "./EntitlementCheckerStorage.sol";
|
|
11
9
|
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
10
|
+
import {SafeTransferLib} from "solady/utils/SafeTransferLib.sol";
|
|
11
|
+
import {CustomRevert} from "../../../../utils/libraries/CustomRevert.sol";
|
|
12
|
+
import {CurrencyTransfer} from "../../../../utils/libraries/CurrencyTransfer.sol";
|
|
13
|
+
import {NodeOperatorStatus, NodeOperatorStorage} from "../operator/NodeOperatorStorage.sol";
|
|
14
|
+
import {XChainLib} from "../xchain/XChainLib.sol";
|
|
15
|
+
import {EntitlementCheckerStorage} from "./EntitlementCheckerStorage.sol";
|
|
15
16
|
|
|
16
17
|
// contracts
|
|
17
18
|
import {Facet} from "@towns-protocol/diamond/src/facets/Facet.sol";
|
|
18
19
|
|
|
19
20
|
contract EntitlementChecker is IEntitlementChecker, Facet {
|
|
20
21
|
using EnumerableSet for EnumerableSet.AddressSet;
|
|
21
|
-
using EnumerableSet for EnumerableSet.UintSet;
|
|
22
22
|
using EnumerableSet for EnumerableSet.Bytes32Set;
|
|
23
|
+
using EnumerableSet for EnumerableSet.UintSet;
|
|
23
24
|
using CustomRevert for bytes4;
|
|
24
|
-
|
|
25
|
-
// =============================================================
|
|
26
|
-
// Initializer
|
|
27
|
-
// =============================================================
|
|
25
|
+
using SafeTransferLib for address;
|
|
28
26
|
|
|
29
27
|
function __EntitlementChecker_init() external onlyInitializing {
|
|
30
28
|
_addInterface(type(IEntitlementChecker).interfaceId);
|
|
31
29
|
}
|
|
32
30
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
31
|
+
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
|
|
32
|
+
/* MODIFIERS */
|
|
33
|
+
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
|
|
34
|
+
|
|
35
|
+
modifier onlyNodeOperator(address node) {
|
|
36
|
+
EntitlementCheckerStorage.Layout storage $ = EntitlementCheckerStorage.layout();
|
|
38
37
|
|
|
39
|
-
if (
|
|
38
|
+
if (msg.sender != $.operatorByNode[node]) {
|
|
40
39
|
EntitlementChecker_InvalidNodeOperator.selector.revertWith();
|
|
41
40
|
}
|
|
42
41
|
_;
|
|
@@ -48,63 +47,134 @@ contract EntitlementChecker is IEntitlementChecker, Facet {
|
|
|
48
47
|
if (!nodeOperatorLayout.operators.contains(msg.sender)) {
|
|
49
48
|
EntitlementChecker_InvalidOperator.selector.revertWith();
|
|
50
49
|
}
|
|
51
|
-
_;
|
|
52
|
-
|
|
53
50
|
if (nodeOperatorLayout.statusByOperator[msg.sender] != NodeOperatorStatus.Approved) {
|
|
54
51
|
EntitlementChecker_OperatorNotActive.selector.revertWith();
|
|
55
52
|
}
|
|
53
|
+
_;
|
|
56
54
|
}
|
|
57
55
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
56
|
+
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
|
|
57
|
+
/* ADMIN FUNCTIONS */
|
|
58
|
+
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
|
|
61
59
|
|
|
62
60
|
/// @inheritdoc IEntitlementChecker
|
|
63
61
|
function registerNode(address node) external onlyRegisteredApprovedOperator {
|
|
64
|
-
EntitlementCheckerStorage.Layout storage
|
|
62
|
+
EntitlementCheckerStorage.Layout storage $ = EntitlementCheckerStorage.layout();
|
|
65
63
|
|
|
66
|
-
if (
|
|
64
|
+
if ($.nodes.contains(node)) {
|
|
67
65
|
EntitlementChecker_NodeAlreadyRegistered.selector.revertWith();
|
|
68
66
|
}
|
|
69
67
|
|
|
70
|
-
|
|
71
|
-
|
|
68
|
+
$.nodes.add(node);
|
|
69
|
+
$.operatorByNode[node] = msg.sender;
|
|
72
70
|
|
|
73
71
|
emit NodeRegistered(node);
|
|
74
72
|
}
|
|
75
73
|
|
|
76
74
|
/// @inheritdoc IEntitlementChecker
|
|
77
|
-
function unregisterNode(address node) external onlyNodeOperator(node
|
|
78
|
-
EntitlementCheckerStorage.Layout storage
|
|
75
|
+
function unregisterNode(address node) external onlyNodeOperator(node) {
|
|
76
|
+
EntitlementCheckerStorage.Layout storage $ = EntitlementCheckerStorage.layout();
|
|
79
77
|
|
|
80
|
-
if (
|
|
78
|
+
if (!$.nodes.contains(node)) {
|
|
81
79
|
EntitlementChecker_NodeNotRegistered.selector.revertWith();
|
|
82
80
|
}
|
|
83
81
|
|
|
84
|
-
|
|
85
|
-
delete
|
|
82
|
+
$.nodes.remove(node);
|
|
83
|
+
delete $.operatorByNode[node];
|
|
86
84
|
|
|
87
85
|
emit NodeUnregistered(node);
|
|
88
86
|
}
|
|
89
87
|
|
|
88
|
+
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
|
|
89
|
+
/* ENTITLEMENT */
|
|
90
|
+
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
|
|
91
|
+
|
|
92
|
+
/// @inheritdoc IEntitlementChecker
|
|
93
|
+
function requestEntitlementCheck(
|
|
94
|
+
address receiver,
|
|
95
|
+
bytes32 transactionId,
|
|
96
|
+
uint256 roleId,
|
|
97
|
+
address[] memory nodes
|
|
98
|
+
) external {
|
|
99
|
+
emit EntitlementCheckRequested(receiver, msg.sender, transactionId, roleId, nodes);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/// @inheritdoc IEntitlementChecker
|
|
103
|
+
function requestEntitlementCheckV2(
|
|
104
|
+
address receiver,
|
|
105
|
+
bytes32 transactionId,
|
|
106
|
+
uint256 requestId,
|
|
107
|
+
bytes memory extraData
|
|
108
|
+
) external payable {
|
|
109
|
+
address sender = abi.decode(extraData, (address));
|
|
110
|
+
_requestEntitlementCheck(
|
|
111
|
+
receiver,
|
|
112
|
+
transactionId,
|
|
113
|
+
requestId,
|
|
114
|
+
CurrencyTransfer.NATIVE_TOKEN,
|
|
115
|
+
msg.value,
|
|
116
|
+
sender
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/// @inheritdoc IEntitlementChecker
|
|
121
|
+
function requestEntitlementCheck(CheckType checkType, bytes calldata data) external payable {
|
|
122
|
+
if (checkType == CheckType.V1) {
|
|
123
|
+
if (msg.value != 0) EntitlementChecker_InvalidValue.selector.revertWith();
|
|
124
|
+
(address receiver, bytes32 transactionId, uint256 roleId, address[] memory nodes) = abi
|
|
125
|
+
.decode(data, (address, bytes32, uint256, address[]));
|
|
126
|
+
emit EntitlementCheckRequested(receiver, msg.sender, transactionId, roleId, nodes);
|
|
127
|
+
} else if (checkType == CheckType.V2) {
|
|
128
|
+
(
|
|
129
|
+
address receiver,
|
|
130
|
+
bytes32 transactionId,
|
|
131
|
+
uint256 requestId,
|
|
132
|
+
bytes memory extraData
|
|
133
|
+
) = abi.decode(data, (address, bytes32, uint256, bytes));
|
|
134
|
+
address sender = abi.decode(extraData, (address));
|
|
135
|
+
_requestEntitlementCheck(
|
|
136
|
+
receiver,
|
|
137
|
+
transactionId,
|
|
138
|
+
requestId,
|
|
139
|
+
CurrencyTransfer.NATIVE_TOKEN,
|
|
140
|
+
msg.value,
|
|
141
|
+
sender
|
|
142
|
+
);
|
|
143
|
+
} else if (checkType == CheckType.V3) {
|
|
144
|
+
(
|
|
145
|
+
address receiver,
|
|
146
|
+
bytes32 transactionId,
|
|
147
|
+
uint256 requestId,
|
|
148
|
+
address currency,
|
|
149
|
+
uint256 amount,
|
|
150
|
+
address sender
|
|
151
|
+
) = abi.decode(data, (address, bytes32, uint256, address, uint256, address));
|
|
152
|
+
_requestEntitlementCheck(receiver, transactionId, requestId, currency, amount, sender);
|
|
153
|
+
} else {
|
|
154
|
+
EntitlementChecker_InvalidCheckType.selector.revertWith();
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
|
|
159
|
+
/* GETTERS */
|
|
160
|
+
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
|
|
161
|
+
|
|
90
162
|
/// @inheritdoc IEntitlementChecker
|
|
91
163
|
function isValidNode(address node) external view returns (bool) {
|
|
92
|
-
|
|
93
|
-
return layout.nodes.contains(node);
|
|
164
|
+
return EntitlementCheckerStorage.layout().nodes.contains(node);
|
|
94
165
|
}
|
|
95
166
|
|
|
96
167
|
/// @inheritdoc IEntitlementChecker
|
|
97
168
|
function getNodeCount() external view returns (uint256) {
|
|
98
|
-
|
|
99
|
-
return layout.nodes.length();
|
|
169
|
+
return EntitlementCheckerStorage.layout().nodes.length();
|
|
100
170
|
}
|
|
101
171
|
|
|
102
172
|
/// @inheritdoc IEntitlementChecker
|
|
103
173
|
function getNodeAtIndex(uint256 index) external view returns (address) {
|
|
104
|
-
EntitlementCheckerStorage.Layout storage
|
|
174
|
+
EntitlementCheckerStorage.Layout storage $ = EntitlementCheckerStorage.layout();
|
|
105
175
|
|
|
106
|
-
require(index <
|
|
107
|
-
return
|
|
176
|
+
require(index < $.nodes.length(), "Index out of bounds");
|
|
177
|
+
return $.nodes.at(index);
|
|
108
178
|
}
|
|
109
179
|
|
|
110
180
|
/// @inheritdoc IEntitlementChecker
|
|
@@ -113,51 +183,74 @@ contract EntitlementChecker is IEntitlementChecker, Facet {
|
|
|
113
183
|
}
|
|
114
184
|
|
|
115
185
|
/// @inheritdoc IEntitlementChecker
|
|
116
|
-
function
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
uint256
|
|
120
|
-
address[]
|
|
121
|
-
|
|
122
|
-
|
|
186
|
+
function getNodesByOperator(address operator) external view returns (address[] memory nodes) {
|
|
187
|
+
EntitlementCheckerStorage.Layout storage $ = EntitlementCheckerStorage.layout();
|
|
188
|
+
address[] memory allNodes = $.nodes.values();
|
|
189
|
+
uint256 totalNodeCount = allNodes.length;
|
|
190
|
+
nodes = new address[](totalNodeCount);
|
|
191
|
+
uint256 nodeCount;
|
|
192
|
+
for (uint256 i; i < totalNodeCount; ++i) {
|
|
193
|
+
address node = allNodes[i];
|
|
194
|
+
if ($.operatorByNode[node] == operator) {
|
|
195
|
+
unchecked {
|
|
196
|
+
nodes[nodeCount++] = node;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
assembly ("memory-safe") {
|
|
201
|
+
mstore(nodes, nodeCount)
|
|
202
|
+
}
|
|
123
203
|
}
|
|
124
204
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
205
|
+
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
|
|
206
|
+
/* INTERNAL */
|
|
207
|
+
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
|
|
208
|
+
|
|
209
|
+
function _requestEntitlementCheck(
|
|
210
|
+
address receiver,
|
|
128
211
|
bytes32 transactionId,
|
|
129
212
|
uint256 requestId,
|
|
130
|
-
|
|
131
|
-
|
|
213
|
+
address currency,
|
|
214
|
+
uint256 amount,
|
|
215
|
+
address sender
|
|
216
|
+
) internal {
|
|
132
217
|
address space = msg.sender;
|
|
133
|
-
address senderAddress = abi.decode(extraData, (address));
|
|
134
218
|
|
|
135
219
|
XChainLib.Layout storage layout = XChainLib.layout();
|
|
136
220
|
|
|
137
|
-
layout.requestsBySender[
|
|
221
|
+
layout.requestsBySender[sender].add(transactionId);
|
|
138
222
|
|
|
139
223
|
// Only create the request if it doesn't exist yet
|
|
140
224
|
XChainLib.Request storage request = layout.requests[transactionId];
|
|
141
225
|
if (request.caller == address(0)) {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
EntitlementChecker_InvalidValue.selector.revertWith();
|
|
226
|
+
request.caller = space;
|
|
227
|
+
request.blockNumber = block.number;
|
|
228
|
+
request.value = amount;
|
|
229
|
+
request.receiver = receiver;
|
|
230
|
+
request.currency = currency;
|
|
231
|
+
|
|
232
|
+
if (currency == CurrencyTransfer.NATIVE_TOKEN) {
|
|
233
|
+
if (amount != msg.value) EntitlementChecker_InvalidValue.selector.revertWith();
|
|
234
|
+
} else {
|
|
235
|
+
// ERC20: reject any ETH sent
|
|
236
|
+
if (msg.value != 0) EntitlementChecker_InvalidValue.selector.revertWith();
|
|
237
|
+
// ERC20: pull tokens from Space
|
|
238
|
+
if (amount != 0) currency.safeTransferFrom(space, address(this), amount);
|
|
153
239
|
}
|
|
240
|
+
} else {
|
|
241
|
+
// Request already exists from a previous requestId on this transactionId.
|
|
242
|
+
// Escrow was established on the first request - reject any additional ETH
|
|
243
|
+
// to prevent funds being sent but not tracked.
|
|
244
|
+
if (msg.value != 0) EntitlementChecker_InvalidValue.selector.revertWith();
|
|
154
245
|
}
|
|
155
246
|
|
|
156
247
|
address[] memory randomNodes = _getRandomNodes(5);
|
|
157
248
|
|
|
158
|
-
XChainLib.Check storage check =
|
|
249
|
+
XChainLib.Check storage check = layout.checks[transactionId];
|
|
159
250
|
|
|
160
|
-
check.requestIds.add(requestId)
|
|
251
|
+
if (!check.requestIds.add(requestId)) {
|
|
252
|
+
EntitlementChecker_DuplicateRequestId.selector.revertWith();
|
|
253
|
+
}
|
|
161
254
|
|
|
162
255
|
for (uint256 i; i < randomNodes.length; ++i) {
|
|
163
256
|
check.nodes[requestId].add(randomNodes[i]);
|
|
@@ -170,7 +263,7 @@ contract EntitlementChecker is IEntitlementChecker, Facet {
|
|
|
170
263
|
}
|
|
171
264
|
|
|
172
265
|
emit EntitlementCheckRequestedV2(
|
|
173
|
-
|
|
266
|
+
receiver,
|
|
174
267
|
space,
|
|
175
268
|
address(this),
|
|
176
269
|
transactionId,
|
|
@@ -179,36 +272,12 @@ contract EntitlementChecker is IEntitlementChecker, Facet {
|
|
|
179
272
|
);
|
|
180
273
|
}
|
|
181
274
|
|
|
182
|
-
/// @inheritdoc IEntitlementChecker
|
|
183
|
-
function getNodesByOperator(address operator) external view returns (address[] memory nodes) {
|
|
184
|
-
EntitlementCheckerStorage.Layout storage layout = EntitlementCheckerStorage.layout();
|
|
185
|
-
uint256 totalNodeCount = layout.nodes.length();
|
|
186
|
-
nodes = new address[](totalNodeCount);
|
|
187
|
-
uint256 nodeCount;
|
|
188
|
-
for (uint256 i; i < totalNodeCount; ++i) {
|
|
189
|
-
address node = layout.nodes.at(i);
|
|
190
|
-
if (layout.operatorByNode[node] == operator) {
|
|
191
|
-
unchecked {
|
|
192
|
-
nodes[nodeCount++] = node;
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
assembly ("memory-safe") {
|
|
197
|
-
mstore(nodes, nodeCount) // Update the length of the array
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
// =============================================================
|
|
202
|
-
// Internal
|
|
203
|
-
// =============================================================
|
|
204
275
|
function _getRandomNodes(uint256 count) internal view returns (address[] memory randomNodes) {
|
|
205
|
-
EntitlementCheckerStorage.Layout storage
|
|
276
|
+
EntitlementCheckerStorage.Layout storage $ = EntitlementCheckerStorage.layout();
|
|
206
277
|
|
|
207
|
-
uint256 nodeCount =
|
|
278
|
+
uint256 nodeCount = $.nodes.length();
|
|
208
279
|
|
|
209
|
-
if (count > nodeCount)
|
|
210
|
-
EntitlementChecker_InsufficientNumberOfNodes.selector.revertWith();
|
|
211
|
-
}
|
|
280
|
+
if (count > nodeCount) EntitlementChecker_InsufficientNumberOfNodes.selector.revertWith();
|
|
212
281
|
|
|
213
282
|
randomNodes = new address[](count);
|
|
214
283
|
uint256[] memory indices = new uint256[](nodeCount);
|
|
@@ -221,7 +290,7 @@ contract EntitlementChecker is IEntitlementChecker, Facet {
|
|
|
221
290
|
for (uint256 i; i < count; ++i) {
|
|
222
291
|
// Adjust random function to generate within range 0 to n-1
|
|
223
292
|
uint256 rand = _pseudoRandom(i, nodeCount);
|
|
224
|
-
randomNodes[i] =
|
|
293
|
+
randomNodes[i] = $.nodes.at(indices[rand]);
|
|
225
294
|
// Move the last element to the used slot and reduce the pool size
|
|
226
295
|
indices[rand] = indices[--nodeCount];
|
|
227
296
|
}
|
|
@@ -2,6 +2,24 @@
|
|
|
2
2
|
pragma solidity ^0.8.23;
|
|
3
3
|
|
|
4
4
|
interface IEntitlementCheckerBase {
|
|
5
|
+
/// @notice CheckType enum for unified entitlement check dispatch
|
|
6
|
+
/// @dev To encode data for each type:
|
|
7
|
+
/// switch (checkType) {
|
|
8
|
+
/// case CheckType.V1:
|
|
9
|
+
/// data = abi.encode(receiver, transactionId, roleId, nodes);
|
|
10
|
+
/// case CheckType.V2:
|
|
11
|
+
/// data = abi.encode(receiver, transactionId, requestId, extraData);
|
|
12
|
+
/// // where extraData = abi.encode(sender)
|
|
13
|
+
/// case CheckType.V3:
|
|
14
|
+
/// data = abi.encode(receiver, transactionId, requestId, currency, amount, sender);
|
|
15
|
+
/// }
|
|
16
|
+
enum CheckType {
|
|
17
|
+
V1, // Legacy check with explicit nodes
|
|
18
|
+
V2, // ETH escrow (uses msg.value, currency is always NATIVE_TOKEN)
|
|
19
|
+
V3 // ETH or ERC20 escrow (uses currency + amount)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
error EntitlementChecker_InvalidCheckType();
|
|
5
23
|
error EntitlementChecker_NodeAlreadyRegistered();
|
|
6
24
|
error EntitlementChecker_NodeNotRegistered();
|
|
7
25
|
error EntitlementChecker_InsufficientNumberOfNodes();
|
|
@@ -12,6 +30,7 @@ interface IEntitlementCheckerBase {
|
|
|
12
30
|
error EntitlementChecker_InsufficientFunds();
|
|
13
31
|
error EntitlementChecker_NoRefundsAvailable();
|
|
14
32
|
error EntitlementChecker_InvalidValue();
|
|
33
|
+
error EntitlementChecker_DuplicateRequestId();
|
|
15
34
|
|
|
16
35
|
// Events
|
|
17
36
|
event NodeRegistered(address indexed nodeAddress);
|
|
@@ -19,17 +38,17 @@ interface IEntitlementCheckerBase {
|
|
|
19
38
|
|
|
20
39
|
/// @notice Event emitted when an entitlement check is requested
|
|
21
40
|
event EntitlementCheckRequested(
|
|
22
|
-
address
|
|
23
|
-
address
|
|
41
|
+
address receiver,
|
|
42
|
+
address space,
|
|
24
43
|
bytes32 transactionId,
|
|
25
44
|
uint256 roleId,
|
|
26
45
|
address[] selectedNodes
|
|
27
46
|
);
|
|
28
47
|
|
|
29
48
|
event EntitlementCheckRequestedV2(
|
|
30
|
-
address
|
|
31
|
-
address
|
|
32
|
-
address
|
|
49
|
+
address receiver,
|
|
50
|
+
address space,
|
|
51
|
+
address resolver,
|
|
33
52
|
bytes32 transactionId,
|
|
34
53
|
uint256 roleId,
|
|
35
54
|
address[] selectedNodes
|
|
@@ -45,49 +64,54 @@ interface IEntitlementChecker is IEntitlementCheckerBase {
|
|
|
45
64
|
/// @param node The address of the node to unregister
|
|
46
65
|
function unregisterNode(address node) external;
|
|
47
66
|
|
|
48
|
-
/// @notice Check if a node address is registered and valid
|
|
49
|
-
/// @param node The address of the node to check
|
|
50
|
-
/// @return bool True if the node is valid, false otherwise
|
|
51
|
-
function isValidNode(address node) external view returns (bool);
|
|
52
|
-
|
|
53
|
-
/// @notice Get the total number of registered nodes
|
|
54
|
-
/// @return uint256 The count of registered nodes
|
|
55
|
-
function getNodeCount() external view returns (uint256);
|
|
56
|
-
|
|
57
|
-
/// @notice Get the node address at a specific index
|
|
58
|
-
/// @param index The index of the node to retrieve
|
|
59
|
-
/// @return address The address of the node at the given index
|
|
60
|
-
function getNodeAtIndex(uint256 index) external view returns (address);
|
|
61
|
-
|
|
62
|
-
/// @notice Get a random selection of registered nodes
|
|
63
|
-
/// @param count The number of random nodes to return
|
|
64
|
-
/// @return address[] Array of randomly selected node addresses
|
|
65
|
-
function getRandomNodes(uint256 count) external view returns (address[] memory);
|
|
66
|
-
|
|
67
67
|
/// @notice Request an entitlement check for a transaction
|
|
68
|
-
/// @param
|
|
68
|
+
/// @param receiver The address to check entitlements for (membership recipient)
|
|
69
69
|
/// @param transactionId The unique identifier of the transaction
|
|
70
70
|
/// @param roleId The role ID to check entitlements against
|
|
71
71
|
/// @param nodes Array of node addresses that will perform the check
|
|
72
72
|
function requestEntitlementCheck(
|
|
73
|
-
address
|
|
73
|
+
address receiver,
|
|
74
74
|
bytes32 transactionId,
|
|
75
75
|
uint256 roleId,
|
|
76
76
|
address[] memory nodes
|
|
77
77
|
) external;
|
|
78
78
|
|
|
79
79
|
/// @notice Request an entitlement check with additional data (V2)
|
|
80
|
-
/// @param
|
|
80
|
+
/// @param receiver The address to check entitlements for (membership recipient)
|
|
81
81
|
/// @param transactionId The unique identifier of the transaction
|
|
82
82
|
/// @param requestId The unique identifier for this specific request
|
|
83
83
|
/// @param extraData Additional data required for the check
|
|
84
84
|
function requestEntitlementCheckV2(
|
|
85
|
-
address
|
|
85
|
+
address receiver,
|
|
86
86
|
bytes32 transactionId,
|
|
87
87
|
uint256 requestId,
|
|
88
88
|
bytes memory extraData
|
|
89
89
|
) external payable;
|
|
90
90
|
|
|
91
|
+
/// @notice Unified entitlement check request with enum-based dispatch
|
|
92
|
+
/// @param checkType The type of check to perform (V1, V2, or V3)
|
|
93
|
+
/// @param data Encoded parameters specific to the check type (see CheckType enum docs)
|
|
94
|
+
function requestEntitlementCheck(CheckType checkType, bytes calldata data) external payable;
|
|
95
|
+
|
|
96
|
+
/// @notice Check if a node address is registered and valid
|
|
97
|
+
/// @param node The address of the node to check
|
|
98
|
+
/// @return bool True if the node is valid, false otherwise
|
|
99
|
+
function isValidNode(address node) external view returns (bool);
|
|
100
|
+
|
|
101
|
+
/// @notice Get the total number of registered nodes
|
|
102
|
+
/// @return uint256 The count of registered nodes
|
|
103
|
+
function getNodeCount() external view returns (uint256);
|
|
104
|
+
|
|
105
|
+
/// @notice Get the node address at a specific index
|
|
106
|
+
/// @param index The index of the node to retrieve
|
|
107
|
+
/// @return address The address of the node at the given index
|
|
108
|
+
function getNodeAtIndex(uint256 index) external view returns (address);
|
|
109
|
+
|
|
110
|
+
/// @notice Get a random selection of registered nodes
|
|
111
|
+
/// @param count The number of random nodes to return
|
|
112
|
+
/// @return address[] Array of randomly selected node addresses
|
|
113
|
+
function getRandomNodes(uint256 count) external view returns (address[] memory);
|
|
114
|
+
|
|
91
115
|
/// @notice Get all nodes registered to a specific operator
|
|
92
116
|
/// @param operator The address of the operator
|
|
93
117
|
/// @return address[] Array of node addresses registered to the operator
|
|
@@ -2,20 +2,21 @@
|
|
|
2
2
|
pragma solidity ^0.8.23;
|
|
3
3
|
|
|
4
4
|
// interfaces
|
|
5
|
-
|
|
6
5
|
import {IEntitlementCheckerBase} from "src/base/registry/facets/checker/IEntitlementChecker.sol";
|
|
7
6
|
import {IEntitlementGatedBase} from "src/spaces/facets/gated/IEntitlementGated.sol";
|
|
8
7
|
|
|
9
|
-
// libraries
|
|
10
|
-
|
|
11
|
-
// contracts
|
|
12
|
-
|
|
13
8
|
/// @dev Struct to hold voting context and avoid stack too deep
|
|
9
|
+
/// @param transactionId The unique identifier of the transaction
|
|
10
|
+
/// @param caller The space contract that initiated the request
|
|
11
|
+
/// @param value Amount escrowed (ETH or ERC20)
|
|
12
|
+
/// @param completed Whether the transaction has been finalized
|
|
13
|
+
/// @param currency Token address (NATIVE_TOKEN for ETH)
|
|
14
14
|
struct VotingContext {
|
|
15
15
|
bytes32 transactionId;
|
|
16
16
|
address caller;
|
|
17
17
|
uint256 value;
|
|
18
18
|
bool completed;
|
|
19
|
+
address currency;
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
/// @dev Struct to hold vote counting results
|
|
@@ -2,21 +2,20 @@
|
|
|
2
2
|
pragma solidity ^0.8.23;
|
|
3
3
|
|
|
4
4
|
// interfaces
|
|
5
|
+
import {IEntitlementGated} from "../../../../spaces/facets/gated/IEntitlementGated.sol";
|
|
5
6
|
import {IXChain, VotingContext, VoteResults} from "./IXChain.sol";
|
|
6
|
-
import {IEntitlementGated} from "src/spaces/facets/gated/IEntitlementGated.sol";
|
|
7
7
|
|
|
8
8
|
// libraries
|
|
9
|
-
import {XChainLib} from "./XChainLib.sol";
|
|
10
9
|
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
|
|
11
|
-
import {CurrencyTransfer} from "
|
|
12
|
-
import {CustomRevert} from "
|
|
10
|
+
import {CurrencyTransfer} from "../../../../utils/libraries/CurrencyTransfer.sol";
|
|
11
|
+
import {CustomRevert} from "../../../../utils/libraries/CustomRevert.sol";
|
|
13
12
|
import {XChainCheckLib} from "./XChainCheckLib.sol";
|
|
13
|
+
import {XChainLib} from "./XChainLib.sol";
|
|
14
14
|
|
|
15
15
|
// contracts
|
|
16
16
|
import {Facet} from "@towns-protocol/diamond/src/facets/Facet.sol";
|
|
17
|
-
import {EntitlementGated} from "src/spaces/facets/gated/EntitlementGated.sol";
|
|
18
|
-
import {ReentrancyGuard} from "solady/utils/ReentrancyGuard.sol";
|
|
19
17
|
import {OwnableBase} from "@towns-protocol/diamond/src/facets/ownable/OwnableBase.sol";
|
|
18
|
+
import {ReentrancyGuard} from "solady/utils/ReentrancyGuard.sol";
|
|
20
19
|
|
|
21
20
|
contract XChain is IXChain, ReentrancyGuard, OwnableBase, Facet {
|
|
22
21
|
using EnumerableSet for EnumerableSet.AddressSet;
|
|
@@ -39,11 +38,13 @@ contract XChain is IXChain, ReentrancyGuard, OwnableBase, Facet {
|
|
|
39
38
|
|
|
40
39
|
/// @inheritdoc IXChain
|
|
41
40
|
function provideXChainRefund(address senderAddress, bytes32 transactionId) external onlyOwner {
|
|
42
|
-
|
|
41
|
+
XChainLib.Layout storage layout = XChainLib.layout();
|
|
42
|
+
|
|
43
|
+
if (!layout.requestsBySender[senderAddress].remove(transactionId)) {
|
|
43
44
|
EntitlementGated_TransactionCheckAlreadyCompleted.selector.revertWith();
|
|
44
45
|
}
|
|
45
46
|
|
|
46
|
-
XChainLib.Request storage request =
|
|
47
|
+
XChainLib.Request storage request = layout.requests[transactionId];
|
|
47
48
|
|
|
48
49
|
if (request.completed) {
|
|
49
50
|
EntitlementGated_TransactionCheckAlreadyCompleted.selector.revertWith();
|
|
@@ -55,23 +56,19 @@ contract XChain is IXChain, ReentrancyGuard, OwnableBase, Facet {
|
|
|
55
56
|
|
|
56
57
|
request.completed = true;
|
|
57
58
|
|
|
58
|
-
XChainLib.Check storage check =
|
|
59
|
+
XChainLib.Check storage check = layout.checks[transactionId];
|
|
59
60
|
|
|
60
61
|
// clean up checks if any
|
|
61
|
-
uint256
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
uint256 requestId = check.requestIds.at(i);
|
|
65
|
-
check.voteCompleted[requestId] = true;
|
|
66
|
-
}
|
|
62
|
+
uint256[] memory requestIds = check.requestIds.values();
|
|
63
|
+
for (uint256 i; i < requestIds.length; ++i) {
|
|
64
|
+
check.voteCompleted[requestIds[i]] = true;
|
|
67
65
|
}
|
|
68
66
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
);
|
|
67
|
+
// normalize address(0) to NATIVE_TOKEN for pre-upgrade requests
|
|
68
|
+
address currency = request.currency;
|
|
69
|
+
currency = currency == address(0) ? CurrencyTransfer.NATIVE_TOKEN : currency;
|
|
70
|
+
// refund escrowed amount
|
|
71
|
+
CurrencyTransfer.transferCurrency(currency, address(this), senderAddress, request.value);
|
|
75
72
|
}
|
|
76
73
|
|
|
77
74
|
/// @inheritdoc IXChain
|
|
@@ -80,8 +77,9 @@ contract XChain is IXChain, ReentrancyGuard, OwnableBase, Facet {
|
|
|
80
77
|
uint256 requestId,
|
|
81
78
|
NodeVoteStatus result
|
|
82
79
|
) external nonReentrant {
|
|
83
|
-
XChainLib.
|
|
84
|
-
XChainLib.
|
|
80
|
+
XChainLib.Layout storage layout = XChainLib.layout();
|
|
81
|
+
XChainLib.Request storage request = layout.requests[transactionId];
|
|
82
|
+
XChainLib.Check storage check = layout.checks[transactionId];
|
|
85
83
|
|
|
86
84
|
VotingContext memory context = check.validateVotingEligibility(
|
|
87
85
|
request,
|
|
@@ -129,11 +127,18 @@ contract XChain is IXChain, ReentrancyGuard, OwnableBase, Facet {
|
|
|
129
127
|
NodeVoteStatus finalStatus
|
|
130
128
|
) internal {
|
|
131
129
|
// Mark transaction as completed and clean up
|
|
132
|
-
XChainLib.layout
|
|
133
|
-
|
|
130
|
+
XChainLib.Layout storage layout = XChainLib.layout();
|
|
131
|
+
layout.requests[context.transactionId].completed = true;
|
|
132
|
+
layout.requestsBySender[context.caller].remove(context.transactionId);
|
|
134
133
|
|
|
135
|
-
//
|
|
136
|
-
|
|
134
|
+
// return escrowed funds to Space, then callback
|
|
135
|
+
CurrencyTransfer.transferCurrency(
|
|
136
|
+
context.currency,
|
|
137
|
+
address(this),
|
|
138
|
+
context.caller,
|
|
139
|
+
context.value
|
|
140
|
+
);
|
|
141
|
+
IEntitlementGated(context.caller).postEntitlementCheckResultV2(
|
|
137
142
|
context.transactionId,
|
|
138
143
|
0,
|
|
139
144
|
finalStatus
|
|
@@ -2,15 +2,14 @@
|
|
|
2
2
|
pragma solidity ^0.8.23;
|
|
3
3
|
|
|
4
4
|
// interfaces
|
|
5
|
+
import {IEntitlementGatedBase} from "../../../../spaces/facets/gated/IEntitlementGated.sol";
|
|
6
|
+
import {VoteResults, VotingContext} from "./IXChain.sol";
|
|
5
7
|
|
|
6
8
|
// libraries
|
|
7
|
-
|
|
8
|
-
// contracts
|
|
9
|
-
import {XChainLib} from "./XChainLib.sol";
|
|
10
|
-
import {IEntitlementGatedBase} from "src/spaces/facets/gated/IEntitlementGated.sol";
|
|
11
|
-
import {CustomRevert} from "src/utils/libraries/CustomRevert.sol";
|
|
12
9
|
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
|
|
13
|
-
import {
|
|
10
|
+
import {CurrencyTransfer} from "../../../../utils/libraries/CurrencyTransfer.sol";
|
|
11
|
+
import {CustomRevert} from "../../../../utils/libraries/CustomRevert.sol";
|
|
12
|
+
import {XChainLib} from "./XChainLib.sol";
|
|
14
13
|
|
|
15
14
|
library XChainCheckLib {
|
|
16
15
|
using CustomRevert for bytes4;
|
|
@@ -50,6 +49,9 @@ library XChainCheckLib {
|
|
|
50
49
|
context.caller = request.caller;
|
|
51
50
|
context.value = request.value;
|
|
52
51
|
context.completed = request.completed;
|
|
52
|
+
// normalize address(0) to NATIVE_TOKEN for pre-upgrade requests
|
|
53
|
+
address currency = request.currency;
|
|
54
|
+
context.currency = currency == address(0) ? CurrencyTransfer.NATIVE_TOKEN : currency;
|
|
53
55
|
}
|
|
54
56
|
|
|
55
57
|
function processNodeVote(
|
|
@@ -2,13 +2,12 @@
|
|
|
2
2
|
pragma solidity ^0.8.23;
|
|
3
3
|
|
|
4
4
|
// interfaces
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import {IEntitlementGatedBase} from "../../../../spaces/facets/gated/IEntitlementGated.sol";
|
|
6
|
+
import {IEntitlementChecker} from "../checker/IEntitlementChecker.sol";
|
|
7
|
+
|
|
7
8
|
// libraries
|
|
8
9
|
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
|
|
9
10
|
|
|
10
|
-
// contracts
|
|
11
|
-
|
|
12
11
|
library XChainLib {
|
|
13
12
|
// keccak256(abi.encode(uint256(keccak256("xchain.entitlement.transactions.storage")) - 1)) &
|
|
14
13
|
// ~bytes32(uint256(0xff))
|
|
@@ -22,12 +21,20 @@ library XChainLib {
|
|
|
22
21
|
mapping(uint256 requestId => bool voteCompleted) voteCompleted;
|
|
23
22
|
}
|
|
24
23
|
|
|
24
|
+
/// @dev Stores crosschain entitlement check request data
|
|
25
|
+
/// @param value Amount escrowed (ETH or ERC20)
|
|
26
|
+
/// @param blockNumber Block when request was created
|
|
27
|
+
/// @param caller Space contract that initiated the request
|
|
28
|
+
/// @param completed Whether the request has been finalized
|
|
29
|
+
/// @param receiver Wallet address being checked for entitlement
|
|
30
|
+
/// @param currency Token address (NATIVE_TOKEN for ETH)
|
|
25
31
|
struct Request {
|
|
26
32
|
uint256 value;
|
|
27
33
|
uint256 blockNumber;
|
|
28
34
|
address caller;
|
|
29
35
|
bool completed;
|
|
30
36
|
address receiver;
|
|
37
|
+
address currency;
|
|
31
38
|
}
|
|
32
39
|
|
|
33
40
|
struct Layout {
|
|
@@ -2,16 +2,14 @@
|
|
|
2
2
|
pragma solidity ^0.8.23;
|
|
3
3
|
|
|
4
4
|
// interfaces
|
|
5
|
+
import {IEntitlementChecker} from "../../../base/registry/facets/checker/IEntitlementChecker.sol";
|
|
6
|
+
import {IRuleEntitlement} from "../../entitlements/rule/IRuleEntitlement.sol";
|
|
5
7
|
import {IEntitlementGated} from "./IEntitlementGated.sol";
|
|
6
|
-
import {IEntitlementChecker} from "src/base/registry/facets/checker/IEntitlementChecker.sol";
|
|
7
|
-
import {IRuleEntitlement} from "src/spaces/entitlements/rule/IRuleEntitlement.sol";
|
|
8
|
-
|
|
9
|
-
// libraries
|
|
10
8
|
|
|
11
9
|
// contracts
|
|
12
|
-
import {EntitlementGatedBase} from "./EntitlementGatedBase.sol";
|
|
13
10
|
import {Facet} from "@towns-protocol/diamond/src/facets/Facet.sol";
|
|
14
11
|
import {ReentrancyGuard} from "solady/utils/ReentrancyGuard.sol";
|
|
12
|
+
import {EntitlementGatedBase} from "./EntitlementGatedBase.sol";
|
|
15
13
|
|
|
16
14
|
abstract contract EntitlementGated is
|
|
17
15
|
IEntitlementGated,
|
|
@@ -30,8 +28,7 @@ abstract contract EntitlementGated is
|
|
|
30
28
|
_setEntitlementChecker(entitlementChecker);
|
|
31
29
|
}
|
|
32
30
|
|
|
33
|
-
/// @
|
|
34
|
-
/// @dev the internal function validates the transactionId and the result
|
|
31
|
+
/// @inheritdoc IEntitlementGated
|
|
35
32
|
function postEntitlementCheckResult(
|
|
36
33
|
bytes32 transactionId,
|
|
37
34
|
uint256 roleId,
|
|
@@ -40,11 +37,7 @@ abstract contract EntitlementGated is
|
|
|
40
37
|
_postEntitlementCheckResult(transactionId, roleId, result);
|
|
41
38
|
}
|
|
42
39
|
|
|
43
|
-
/// @
|
|
44
|
-
/// @dev Only the entitlement checker can call this function
|
|
45
|
-
/// @param transactionId The unique identifier for the transaction
|
|
46
|
-
/// @param roleId The role ID for the entitlement check
|
|
47
|
-
/// @param result The result of the entitlement check (PASSED or FAILED)
|
|
40
|
+
/// @inheritdoc IEntitlementGated
|
|
48
41
|
function postEntitlementCheckResultV2(
|
|
49
42
|
bytes32 transactionId,
|
|
50
43
|
uint256 roleId,
|
|
@@ -53,7 +46,7 @@ abstract contract EntitlementGated is
|
|
|
53
46
|
_postEntitlementCheckResultV2(transactionId, roleId, result);
|
|
54
47
|
}
|
|
55
48
|
|
|
56
|
-
/// @
|
|
49
|
+
/// @inheritdoc IEntitlementGated
|
|
57
50
|
function getRuleData(
|
|
58
51
|
bytes32 transactionId,
|
|
59
52
|
uint256 roleId
|
|
@@ -2,23 +2,25 @@
|
|
|
2
2
|
pragma solidity ^0.8.23;
|
|
3
3
|
|
|
4
4
|
// interfaces
|
|
5
|
+
import {IEntitlementChecker, IEntitlementCheckerBase} from "../../../base/registry/facets/checker/IEntitlementChecker.sol";
|
|
6
|
+
import {IImplementationRegistry} from "../../../factory/facets/registry/IImplementationRegistry.sol";
|
|
7
|
+
import {IRuleEntitlement} from "../../entitlements/rule/IRuleEntitlement.sol";
|
|
5
8
|
import {IEntitlementGatedBase} from "./IEntitlementGated.sol";
|
|
6
9
|
|
|
7
|
-
import {IEntitlementChecker} from "src/base/registry/facets/checker/IEntitlementChecker.sol";
|
|
8
|
-
import {IImplementationRegistry} from "src/factory/facets/registry/IImplementationRegistry.sol";
|
|
9
|
-
import {IRuleEntitlement} from "src/spaces/entitlements/rule/IRuleEntitlement.sol";
|
|
10
|
-
|
|
11
10
|
// libraries
|
|
11
|
+
import {SafeTransferLib} from "solady/utils/SafeTransferLib.sol";
|
|
12
|
+
import {CurrencyTransfer} from "../../../utils/libraries/CurrencyTransfer.sol";
|
|
13
|
+
import {CustomRevert} from "../../../utils/libraries/CustomRevert.sol";
|
|
14
|
+
import {MembershipStorage} from "../membership/MembershipStorage.sol";
|
|
12
15
|
import {EntitlementGatedStorage} from "./EntitlementGatedStorage.sol";
|
|
13
|
-
import {MembershipStorage} from "src/spaces/facets/membership/MembershipStorage.sol";
|
|
14
|
-
import {CustomRevert} from "src/utils/libraries/CustomRevert.sol";
|
|
15
16
|
|
|
16
17
|
abstract contract EntitlementGatedBase is IEntitlementGatedBase {
|
|
17
18
|
using CustomRevert for bytes4;
|
|
19
|
+
using SafeTransferLib for address;
|
|
18
20
|
|
|
19
21
|
modifier onlyEntitlementChecker() {
|
|
20
22
|
if (msg.sender != address(EntitlementGatedStorage.layout().entitlementChecker)) {
|
|
21
|
-
|
|
23
|
+
EntitlementGated_OnlyEntitlementChecker.selector.revertWith();
|
|
22
24
|
}
|
|
23
25
|
_;
|
|
24
26
|
}
|
|
@@ -33,9 +35,7 @@ abstract contract EntitlementGatedBase is IEntitlementGatedBase {
|
|
|
33
35
|
IRuleEntitlement entitlement,
|
|
34
36
|
uint256 roleId
|
|
35
37
|
) internal {
|
|
36
|
-
if (callerAddress == address(0))
|
|
37
|
-
CustomRevert.revertWith(EntitlementGated_InvalidAddress.selector);
|
|
38
|
-
}
|
|
38
|
+
if (callerAddress == address(0)) EntitlementGated_InvalidAddress.selector.revertWith();
|
|
39
39
|
|
|
40
40
|
EntitlementGatedStorage.Layout storage ds = EntitlementGatedStorage.layout();
|
|
41
41
|
Transaction storage transaction = ds.transactions[transactionId];
|
|
@@ -43,7 +43,7 @@ abstract contract EntitlementGatedBase is IEntitlementGatedBase {
|
|
|
43
43
|
uint256 _length = transaction.roleIds.length;
|
|
44
44
|
for (uint256 i; i < _length; ++i) {
|
|
45
45
|
if (transaction.roleIds[i] == roleId) {
|
|
46
|
-
|
|
46
|
+
EntitlementGated_TransactionCheckAlreadyRegistered.selector.revertWith();
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
49
|
}
|
|
@@ -155,94 +155,51 @@ abstract contract EntitlementGatedBase is IEntitlementGatedBase {
|
|
|
155
155
|
/* V2 */
|
|
156
156
|
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
|
|
157
157
|
|
|
158
|
-
/// @notice Requests a
|
|
159
|
-
/// @
|
|
160
|
-
///
|
|
161
|
-
|
|
162
|
-
/// @param entitlement The entitlement contract to use for checking
|
|
163
|
-
/// @param requestId The specific request identifier
|
|
164
|
-
/// @param value The amount of ETH to send with the request (if any)
|
|
165
|
-
function _requestEntitlementCheckV2(
|
|
158
|
+
/// @notice Requests a crosschain entitlement check with payment escrow
|
|
159
|
+
/// @dev Stores entitlement reference for RuleData queries, escrows payment with
|
|
160
|
+
/// EntitlementChecker, and emits event for off-chain nodes to process.
|
|
161
|
+
function _requestEntitlementCheck(
|
|
166
162
|
address walletAddress,
|
|
167
163
|
address senderAddress,
|
|
168
164
|
bytes32 transactionId,
|
|
169
165
|
IRuleEntitlement entitlement,
|
|
170
166
|
uint256 requestId,
|
|
171
|
-
|
|
167
|
+
address currency,
|
|
168
|
+
uint256 amount
|
|
172
169
|
) internal {
|
|
173
|
-
// Validate all inputs upfront
|
|
174
|
-
_validateEntitlementCheckInputs(walletAddress, entitlement, value);
|
|
175
|
-
|
|
176
|
-
// Setup transaction state
|
|
177
|
-
EntitlementGatedStorage.Layout storage $ = EntitlementGatedStorage.layout();
|
|
178
|
-
_setupTransaction($.transactions[transactionId], entitlement);
|
|
179
|
-
|
|
180
|
-
// Execute the entitlement check request
|
|
181
|
-
_executeEntitlementCheckRequest(
|
|
182
|
-
$.entitlementChecker,
|
|
183
|
-
walletAddress,
|
|
184
|
-
transactionId,
|
|
185
|
-
requestId,
|
|
186
|
-
value,
|
|
187
|
-
abi.encode(senderAddress)
|
|
188
|
-
);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
/// @notice Validates inputs for entitlement check request
|
|
192
|
-
/// @param walletAddress The wallet address being checked
|
|
193
|
-
/// @param entitlement The entitlement contract
|
|
194
|
-
/// @param value The ETH value being sent
|
|
195
|
-
function _validateEntitlementCheckInputs(
|
|
196
|
-
address walletAddress,
|
|
197
|
-
IRuleEntitlement entitlement,
|
|
198
|
-
uint256 value
|
|
199
|
-
) private view {
|
|
200
|
-
if (value > msg.value) {
|
|
201
|
-
EntitlementGated_InvalidValue.selector.revertWith();
|
|
202
|
-
}
|
|
203
170
|
if (walletAddress == address(0)) {
|
|
204
171
|
EntitlementGated_InvalidAddress.selector.revertWith();
|
|
205
172
|
}
|
|
206
173
|
if (address(entitlement) == address(0)) {
|
|
207
174
|
EntitlementGated_InvalidEntitlement.selector.revertWith();
|
|
208
175
|
}
|
|
209
|
-
}
|
|
210
176
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
transaction.finalized = true;
|
|
219
|
-
transaction.entitlement = entitlement;
|
|
220
|
-
}
|
|
177
|
+
EntitlementGatedStorage.Layout storage $ = EntitlementGatedStorage.layout();
|
|
178
|
+
Transaction storage transaction = $.transactions[transactionId];
|
|
179
|
+
// Only set on first call; subsequent calls for other roleIds reuse existing state
|
|
180
|
+
if (!transaction.finalized) {
|
|
181
|
+
transaction.finalized = true;
|
|
182
|
+
transaction.entitlement = entitlement;
|
|
183
|
+
}
|
|
221
184
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
) private {
|
|
237
|
-
if (value > 0) {
|
|
238
|
-
checker.requestEntitlementCheckV2{value: value}(
|
|
239
|
-
walletAddress,
|
|
240
|
-
transactionId,
|
|
241
|
-
requestId,
|
|
242
|
-
extraData
|
|
185
|
+
IEntitlementChecker checker = $.entitlementChecker;
|
|
186
|
+
bytes memory data = abi.encode(
|
|
187
|
+
walletAddress,
|
|
188
|
+
transactionId,
|
|
189
|
+
requestId,
|
|
190
|
+
currency,
|
|
191
|
+
amount,
|
|
192
|
+
senderAddress
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
if (currency == CurrencyTransfer.NATIVE_TOKEN) {
|
|
196
|
+
checker.requestEntitlementCheck{value: amount}(
|
|
197
|
+
IEntitlementCheckerBase.CheckType.V3,
|
|
198
|
+
data
|
|
243
199
|
);
|
|
244
200
|
} else {
|
|
245
|
-
|
|
201
|
+
if (amount != 0) currency.safeApproveWithRetry(address(checker), amount);
|
|
202
|
+
checker.requestEntitlementCheck(IEntitlementCheckerBase.CheckType.V3, data);
|
|
246
203
|
}
|
|
247
204
|
}
|
|
248
205
|
|
|
@@ -42,12 +42,32 @@ interface IEntitlementGatedBase {
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
interface IEntitlementGated is IEntitlementGatedBase {
|
|
45
|
+
/// @notice Called by the xchain node to post the result of the entitlement check
|
|
46
|
+
/// @param transactionId The unique identifier for the transaction
|
|
47
|
+
/// @param roleId The role ID for the entitlement check
|
|
48
|
+
/// @param result The result of the entitlement check (PASSED or FAILED)
|
|
45
49
|
function postEntitlementCheckResult(
|
|
46
50
|
bytes32 transactionId,
|
|
47
51
|
uint256 roleId,
|
|
48
52
|
NodeVoteStatus result
|
|
49
53
|
) external;
|
|
50
54
|
|
|
55
|
+
/// @notice Post the result of the entitlement check for a specific role
|
|
56
|
+
/// @dev Only the entitlement checker can call this function
|
|
57
|
+
/// @param transactionId The unique identifier for the transaction
|
|
58
|
+
/// @param roleId The role ID for the entitlement check
|
|
59
|
+
/// @param result The result of the entitlement check (PASSED or FAILED)
|
|
60
|
+
function postEntitlementCheckResultV2(
|
|
61
|
+
bytes32 transactionId,
|
|
62
|
+
uint256 roleId,
|
|
63
|
+
NodeVoteStatus result
|
|
64
|
+
) external payable;
|
|
65
|
+
|
|
66
|
+
/// @notice Get the rule data for a specific transaction and role
|
|
67
|
+
/// @dev Deprecated: Use EntitlementDataQueryable.getCrossChainEntitlementData instead
|
|
68
|
+
/// @param transactionId The unique identifier for the transaction
|
|
69
|
+
/// @param roleId The role ID for the entitlement check
|
|
70
|
+
/// @return The rule data for the transaction and role
|
|
51
71
|
function getRuleData(
|
|
52
72
|
bytes32 transactionId,
|
|
53
73
|
uint256 roleId
|
|
@@ -289,13 +289,8 @@ abstract contract MembershipJoin is
|
|
|
289
289
|
return false;
|
|
290
290
|
}
|
|
291
291
|
|
|
292
|
-
/// @
|
|
293
|
-
///
|
|
294
|
-
/// @param sender The address sending the transaction
|
|
295
|
-
/// @param transactionId The transaction identifier
|
|
296
|
-
/// @param requiredAmount The required payment amount
|
|
297
|
-
/// @return isEntitled Whether user is entitled (always false for crosschain)
|
|
298
|
-
/// @return isCrosschainPending Whether crosschain checks are pending
|
|
292
|
+
/// @dev Checks all crosschain entitlements across roles. User passes if any check succeeds.
|
|
293
|
+
/// Payment is escrowed only once (on first check) and returned when any check completes.
|
|
299
294
|
function _checkCrosschainEntitlements(
|
|
300
295
|
IRolesBase.Role[] memory roles,
|
|
301
296
|
address receiver,
|
|
@@ -303,7 +298,7 @@ abstract contract MembershipJoin is
|
|
|
303
298
|
bytes32 transactionId,
|
|
304
299
|
uint256 requiredAmount
|
|
305
300
|
) internal returns (bool isEntitled, bool isCrosschainPending) {
|
|
306
|
-
bool paymentSent
|
|
301
|
+
bool paymentSent;
|
|
307
302
|
|
|
308
303
|
for (uint256 i; i < roles.length; ++i) {
|
|
309
304
|
if (roles[i].disabled) continue;
|
|
@@ -312,28 +307,16 @@ abstract contract MembershipJoin is
|
|
|
312
307
|
IEntitlement entitlement = IEntitlement(roles[i].entitlements[j]);
|
|
313
308
|
|
|
314
309
|
if (entitlement.isCrosschain()) {
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
);
|
|
326
|
-
paymentSent = true;
|
|
327
|
-
} else {
|
|
328
|
-
_requestEntitlementCheckV2(
|
|
329
|
-
receiver,
|
|
330
|
-
sender,
|
|
331
|
-
transactionId,
|
|
332
|
-
IRuleEntitlement(address(entitlement)),
|
|
333
|
-
roles[i].id,
|
|
334
|
-
0
|
|
335
|
-
);
|
|
336
|
-
}
|
|
310
|
+
_requestEntitlementCheck(
|
|
311
|
+
receiver,
|
|
312
|
+
sender,
|
|
313
|
+
transactionId,
|
|
314
|
+
IRuleEntitlement(address(entitlement)),
|
|
315
|
+
roles[i].id,
|
|
316
|
+
_getMembershipCurrency(),
|
|
317
|
+
paymentSent ? 0 : requiredAmount
|
|
318
|
+
);
|
|
319
|
+
paymentSent = true;
|
|
337
320
|
isCrosschainPending = true;
|
|
338
321
|
}
|
|
339
322
|
}
|