@towns-protocol/contracts 0.0.413 → 0.0.415

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.
@@ -0,0 +1,27 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.23;
3
+
4
+ // interfaces
5
+
6
+ // libraries
7
+
8
+ // contracts
9
+
10
+ library IdentityRegistryStorage {
11
+ // keccak256(abi.encode(uint256(keccak256("apps.facets.identity.storage")) - 1)) & ~bytes32(uint256(0xff))
12
+ bytes32 internal constant STORAGE_SLOT =
13
+ 0x66991193de0d3d5cfb03e1f2f26d7b691100aae98b32c0c50fcb3839feebe100;
14
+
15
+ struct Layout {
16
+ // agentId => key => value
17
+ mapping(uint256 => mapping(string => bytes)) metadata;
18
+ // agent uri
19
+ mapping(uint256 => string) agentUri;
20
+ }
21
+
22
+ function getLayout() internal pure returns (Layout storage $) {
23
+ assembly {
24
+ $.slot := STORAGE_SLOT
25
+ }
26
+ }
27
+ }
@@ -16,7 +16,6 @@ import {IAppAccount} from "../../../spaces/facets/account/IAppAccount.sol";
16
16
  import {CustomRevert} from "../../../utils/libraries/CustomRevert.sol";
17
17
  import {BasisPoints} from "../../../utils/libraries/BasisPoints.sol";
18
18
  import {AppRegistryStorage, ClientInfo, AppInfo} from "./AppRegistryStorage.sol";
19
- import {LibClone} from "solady/utils/LibClone.sol";
20
19
  import {FixedPointMathLib} from "solady/utils/FixedPointMathLib.sol";
21
20
  import {CurrencyTransfer} from "../../../utils/libraries/CurrencyTransfer.sol";
22
21
  import {LibAppRegistry} from "./LibAppRegistry.sol";
@@ -0,0 +1,186 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.23;
3
+
4
+ // interfaces
5
+
6
+ // libraries
7
+
8
+ // contracts
9
+
10
+ /// @title Reputation Registry Base - ERC-8004 Data Structures
11
+ /// @notice Core data structures and events for the ERC-8004 reputation system
12
+ interface IReputationRegistryBase {
13
+ struct Feedback {
14
+ uint8 rating;
15
+ bytes32 tag1;
16
+ bytes32 tag2;
17
+ }
18
+
19
+ struct FeedbackAuth {
20
+ uint256 agentId;
21
+ address reviewerAddress;
22
+ uint64 indexLimit;
23
+ uint256 expiry;
24
+ uint256 chainId;
25
+ address identityRegistry;
26
+ address signerAddress;
27
+ }
28
+
29
+ event NewFeedback(
30
+ uint256 indexed agentId,
31
+ address indexed reviewerAddress,
32
+ uint8 score,
33
+ bytes32 indexed tag1,
34
+ bytes32 tag2,
35
+ string feedbackUri,
36
+ bytes32 feedbackHash
37
+ );
38
+
39
+ event FeedbackRevoked(
40
+ uint256 indexed agentId,
41
+ address indexed reviewerAddress,
42
+ uint64 indexed feedbackIndex
43
+ );
44
+
45
+ event ResponseAppended(
46
+ uint256 indexed agentId,
47
+ address indexed reviewerAddress,
48
+ uint64 feedbackIndex,
49
+ address indexed responder,
50
+ string responseUri,
51
+ bytes32 responseHash
52
+ );
53
+ }
54
+
55
+ /// @title Reputation Registry - ERC-8004 Implementation
56
+ /// @notice A reputation system for AI agents enabling feedback collection, scoring, and trust establishment
57
+ /// @dev This contract implements the ERC-8004 Reputation Registry specification, which enables
58
+ /// discovering and trusting agents across organizational boundaries through reputation signals.
59
+ interface IReputationRegistry is IReputationRegistryBase {
60
+ /// @notice Returns the address of the identity registry where agents are registered as NFTs
61
+ /// @dev The identity registry is an ERC-721 contract where each agent has a unique token ID.
62
+ /// This registry links reputation data to agent identities defined in the ERC-8004 Identity Registry.
63
+ /// @return The address of the identity registry contract
64
+ function getIdentityRegistry() external view returns (address);
65
+
66
+ /// @notice Posts feedback for an agent after completing a task or interaction
67
+ /// @param agentId The NFT token ID of the agent being reviewed
68
+ /// @param score The rating from 0 (worst) to 100 (best)
69
+ /// @param tag1 First tag for categorization and filtering (use bytes32(0) for none)
70
+ /// @param tag2 Second tag for categorization and filtering (use bytes32(0) for none)
71
+ /// @param feedbackUri URI pointing to detailed off-chain feedback data (IPFS recommended)
72
+ /// @param feedbackHash KECCAK-256 hash of off-chain data for integrity verification
73
+ function giveFeedback(
74
+ uint256 agentId,
75
+ uint8 score,
76
+ bytes32 tag1,
77
+ bytes32 tag2,
78
+ string calldata feedbackUri,
79
+ bytes32 feedbackHash
80
+ ) external;
81
+
82
+ /// @notice Revokes previously submitted feedback
83
+ /// @dev Only the original reviewer can revoke their feedback. Revoked feedback is excluded
84
+ /// from getSummary() calculations but remains visible in readAllFeedback() with the
85
+ /// revoked status flag. This maintains audit trail integrity per ERC-8004 principles.
86
+ /// @param agentId The NFT token ID of the agent
87
+ /// @param feedbackIndex The 1-based index of the feedback to revoke (must be > 0)
88
+ function revokeFeedback(uint256 agentId, uint64 feedbackIndex) external;
89
+
90
+ /// @notice Appends a response to existing feedback
91
+ /// @param agentId The NFT token ID of the agent
92
+ /// @param reviewerAddress The address that gave the original feedback
93
+ /// @param feedbackIndex The 1-based index of the feedback being responded to
94
+ /// @param responseUri URI pointing to the response content (IPFS recommended)
95
+ /// @param responseHash KECCAK-256 hash of response data for integrity verification
96
+ function appendResponse(
97
+ uint256 agentId,
98
+ address reviewerAddress,
99
+ uint64 feedbackIndex,
100
+ string calldata responseUri,
101
+ bytes32 responseHash
102
+ ) external;
103
+
104
+ /// @notice Retrieves aggregated reputation statistics for an agent with optional filtering
105
+ /// @param agentId The NFT token ID of the agent
106
+ /// @param clientAddresses Optional filter for specific reviewers (empty = all reviewers)
107
+ /// @param tag1 Optional filter for first tag (bytes32(0) = no filter)
108
+ /// @param tag2 Optional filter for second tag (bytes32(0) = no filter)
109
+ /// @return count Number of matching feedback entries (excludes revoked)
110
+ /// @return averageScore Mean score 0-100 (returns 0 if no feedback matches)
111
+ function getSummary(
112
+ uint256 agentId,
113
+ address[] calldata clientAddresses,
114
+ bytes32 tag1,
115
+ bytes32 tag2
116
+ ) external view returns (uint64 count, uint8 averageScore);
117
+
118
+ /// @notice Reads a specific feedback entry by agent, reviewer, and index
119
+ /// @param agentId The NFT token ID of the agent
120
+ /// @param reviewerAddress The address that gave the feedback
121
+ /// @param index The 1-based feedback index (must be > 0 and <= lastIndex)
122
+ /// @return score The rating from 0 to 100
123
+ /// @return tag1 The first categorization tag
124
+ /// @return tag2 The second categorization tag
125
+ /// @return isRevoked True if feedback has been revoked, false otherwise
126
+ function readFeedback(
127
+ uint256 agentId,
128
+ address reviewerAddress,
129
+ uint64 index
130
+ ) external view returns (uint8 score, bytes32 tag1, bytes32 tag2, bool isRevoked);
131
+
132
+ /// @notice Reads all feedback for an agent with flexible filtering options
133
+ /// @param agentId The NFT token ID of the agent
134
+ /// @param clientAddresses Optional filter for specific reviewers (empty = all reviewers)
135
+ /// @param tag1 Optional filter for first tag (bytes32(0) = no filter)
136
+ /// @param tag2 Optional filter for second tag (bytes32(0) = no filter)
137
+ /// @param includeRevoked Whether to include revoked feedback in results
138
+ /// @return clients Array of reviewer addresses (one per feedback entry)
139
+ /// @return scores Array of ratings 0-100 (parallel to clients array)
140
+ /// @return tag1s Array of first tags (parallel to clients array)
141
+ /// @return tag2s Array of second tags (parallel to clients array)
142
+ /// @return revokedStatuses Array of revocation flags (parallel to clients array)
143
+ function readAllFeedback(
144
+ uint256 agentId,
145
+ address[] calldata clientAddresses,
146
+ bytes32 tag1,
147
+ bytes32 tag2,
148
+ bool includeRevoked
149
+ )
150
+ external
151
+ view
152
+ returns (
153
+ address[] memory clients,
154
+ uint8[] memory scores,
155
+ bytes32[] memory tag1s,
156
+ bytes32[] memory tag2s,
157
+ bool[] memory revokedStatuses
158
+ );
159
+
160
+ /// @notice Gets the total count of responses with flexible filtering
161
+ /// @param agentId The NFT token ID of the agent
162
+ /// @param reviewerAddress Filter by reviewer (address(0) = all reviewers)
163
+ /// @param feedbackIndex Filter by specific feedback index (0 = all feedback)
164
+ /// @param responders Filter by specific responders (empty = all responders)
165
+ /// @return Total count of responses matching the filter criteria
166
+ function getResponseCount(
167
+ uint256 agentId,
168
+ address reviewerAddress,
169
+ uint64 feedbackIndex,
170
+ address[] calldata responders
171
+ ) external view returns (uint256);
172
+
173
+ /// @notice Returns all addresses that have given feedback to this agent
174
+ /// @param agentId The NFT token ID of the agent
175
+ /// @return clients Array of all reviewer addresses (in order of first feedback)
176
+ function getClients(uint256 agentId) external view returns (address[] memory clients);
177
+
178
+ /// @notice Gets the highest feedback index for a specific reviewer-agent pair
179
+ /// @param agentId The NFT token ID of the agent
180
+ /// @param reviewerAddress The address of the reviewer
181
+ /// @return lastIndex The highest assigned index (0 if no feedback exists)
182
+ function getLastIndex(
183
+ uint256 agentId,
184
+ address reviewerAddress
185
+ ) external view returns (uint64 lastIndex);
186
+ }
@@ -0,0 +1,450 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.29;
3
+
4
+ // interfaces
5
+ import {IReputationRegistryBase} from "./IReputationRegistry.sol";
6
+
7
+ // libraries
8
+ import {CustomRevert} from "../../../utils/libraries/CustomRevert.sol";
9
+ import {ReputationRegistryStorage} from "./ReputationRegistryStorage.sol";
10
+ import {SignatureCheckerLib} from "solady/utils/SignatureCheckerLib.sol";
11
+ import {AttestationRequest, RevocationRequestData, Attestation} from "@ethereum-attestation-service/eas-contracts/IEAS.sol";
12
+ import {EMPTY_UID} from "@ethereum-attestation-service/eas-contracts/Common.sol";
13
+ import {EnumerableSetLib} from "solady/utils/EnumerableSetLib.sol";
14
+
15
+ // contracts
16
+ import {ERC721ABase} from "../../../diamond/facets/token/ERC721A/ERC721ABase.sol";
17
+ import {AttestationBase} from "../attest/AttestationBase.sol";
18
+
19
+ abstract contract ReputationRegistryBase is IReputationRegistryBase, ERC721ABase, AttestationBase {
20
+ using CustomRevert for bytes4;
21
+ using ReputationRegistryStorage for ReputationRegistryStorage.Layout;
22
+ using SignatureCheckerLib for address;
23
+ using SignatureCheckerLib for bytes32;
24
+ using EnumerableSetLib for EnumerableSetLib.AddressSet;
25
+
26
+ error Reputation__InvalidScore();
27
+ error Reputation__AgentNotExists();
28
+ error Reputation__SelfFeedbackNotAllowed();
29
+ error Reputation__InvalidSignature();
30
+ error Reputation__InvalidSignerAddress();
31
+ error Reputation__InvalidFeedbackIndex();
32
+
33
+ bytes32 constant EMPTY_TAG = bytes32(0);
34
+
35
+ struct CachedFeedback {
36
+ address client;
37
+ uint8 rating;
38
+ bool revoked;
39
+ bytes32 tag1;
40
+ bytes32 tag2;
41
+ }
42
+
43
+ function _giveFeedback(uint256 agentId, Feedback memory feedback) internal {
44
+ if (feedback.rating > 100) Reputation__InvalidScore.selector.revertWith();
45
+ if (!_exists(agentId)) Reputation__AgentNotExists.selector.revertWith();
46
+
47
+ address agentAddress = _ownerOf(agentId);
48
+ _verifyNotSelfFeedback(agentAddress, agentId);
49
+
50
+ ReputationRegistryStorage.Layout storage $ = ReputationRegistryStorage.getLayout();
51
+ uint64 currentIndex = $.lastIndex[agentId][msg.sender] + 1;
52
+ $.lastIndex[agentId][msg.sender] = currentIndex;
53
+ $.clients[agentId].add(msg.sender);
54
+
55
+ bytes32 attestationId = _createAttestation($.feedbackSchemaId, agentAddress, feedback);
56
+ $.feedback[agentId][msg.sender][currentIndex] = attestationId;
57
+ }
58
+
59
+ /// @notice Revokes a feedback
60
+ /// @param agentId The ID of the agent
61
+ /// @param feedbackIndex The index of the feedback
62
+ function _revokeFeedback(uint256 agentId, uint64 feedbackIndex) internal {
63
+ if (feedbackIndex == 0) Reputation__InvalidFeedbackIndex.selector.revertWith();
64
+ ReputationRegistryStorage.Layout storage $ = ReputationRegistryStorage.getLayout();
65
+ if (feedbackIndex > $.lastIndex[agentId][msg.sender])
66
+ Reputation__InvalidFeedbackIndex.selector.revertWith();
67
+
68
+ bytes32 attestationId = $.feedback[agentId][msg.sender][feedbackIndex];
69
+ if (attestationId == EMPTY_UID) Reputation__InvalidFeedbackIndex.selector.revertWith();
70
+
71
+ RevocationRequestData memory request;
72
+ request.uid = attestationId;
73
+
74
+ _revoke($.feedbackSchemaId, request, msg.sender, 0, true);
75
+
76
+ emit FeedbackRevoked(agentId, msg.sender, feedbackIndex);
77
+ }
78
+
79
+ /// @notice Appends a response to a feedback
80
+ /// @param agentId The ID of the agent
81
+ /// @param reviewerAddress The address of the feedback reviewer
82
+ /// @param feedbackIndex The index of the feedback
83
+ /// @param comment The comment of the response
84
+ /// @param commentHash The hash of the comment
85
+ function _appendResponse(
86
+ uint256 agentId,
87
+ address reviewerAddress,
88
+ uint64 feedbackIndex,
89
+ string calldata comment,
90
+ bytes32 commentHash
91
+ ) internal {
92
+ if (feedbackIndex == 0) Reputation__InvalidFeedbackIndex.selector.revertWith();
93
+
94
+ ReputationRegistryStorage.Layout storage $ = ReputationRegistryStorage.getLayout();
95
+ if (feedbackIndex > $.lastIndex[agentId][reviewerAddress])
96
+ Reputation__InvalidFeedbackIndex.selector.revertWith();
97
+
98
+ bytes32 attestationId = $.feedback[agentId][reviewerAddress][feedbackIndex];
99
+ if (attestationId == EMPTY_UID) Reputation__InvalidFeedbackIndex.selector.revertWith();
100
+
101
+ $.responders[agentId][reviewerAddress][feedbackIndex].add(msg.sender);
102
+ $.responseCount[agentId][reviewerAddress][feedbackIndex][msg.sender]++;
103
+
104
+ emit ResponseAppended(
105
+ agentId,
106
+ reviewerAddress,
107
+ feedbackIndex,
108
+ msg.sender,
109
+ comment,
110
+ commentHash
111
+ );
112
+ }
113
+
114
+ function _getLastIndex(
115
+ uint256 agentId,
116
+ address reviewerAddress
117
+ ) internal view returns (uint64) {
118
+ return ReputationRegistryStorage.getLayout().lastIndex[agentId][reviewerAddress];
119
+ }
120
+
121
+ function _readFeedback(
122
+ uint256 agentId,
123
+ address reviewerAddress,
124
+ uint64 feedbackIndex
125
+ ) internal view returns (Feedback memory feedback, bool isRevoked) {
126
+ if (feedbackIndex == 0) Reputation__InvalidFeedbackIndex.selector.revertWith();
127
+
128
+ ReputationRegistryStorage.Layout storage $ = ReputationRegistryStorage.getLayout();
129
+ if (feedbackIndex > $.lastIndex[agentId][reviewerAddress])
130
+ Reputation__InvalidFeedbackIndex.selector.revertWith();
131
+
132
+ bytes32 attestationId = $.feedback[agentId][reviewerAddress][feedbackIndex];
133
+ if (attestationId == EMPTY_UID) Reputation__InvalidFeedbackIndex.selector.revertWith();
134
+
135
+ Attestation memory attestation = _getAttestation(attestationId);
136
+ feedback = abi.decode(attestation.data, (Feedback));
137
+ return (feedback, attestation.revocationTime != 0);
138
+ }
139
+
140
+ function _getSummary(
141
+ uint256 agentId,
142
+ address[] calldata reviewers,
143
+ bytes32 tag1,
144
+ bytes32 tag2
145
+ ) internal view returns (uint64 count, uint8 averageRating) {
146
+ address[] memory clients;
147
+
148
+ ReputationRegistryStorage.Layout storage $ = ReputationRegistryStorage.getLayout();
149
+
150
+ if (reviewers.length > 0) clients = reviewers;
151
+ else clients = $.clients[agentId].values();
152
+
153
+ uint256 totalRating;
154
+ for (uint256 i; i < clients.length; ++i) {
155
+ uint64 lastIndex = $.lastIndex[agentId][clients[i]];
156
+ for (uint64 j = 1; j <= lastIndex; ++j) {
157
+ bytes32 attestationId = $.feedback[agentId][clients[i]][j];
158
+ if (attestationId == EMPTY_UID) continue;
159
+ Attestation memory attestation = _getAttestation(attestationId);
160
+ if (attestation.revocationTime != 0) continue;
161
+
162
+ Feedback memory feedback = abi.decode(attestation.data, (Feedback));
163
+ if (tag1 != EMPTY_TAG && feedback.tag1 != tag1) continue;
164
+ if (tag2 != EMPTY_TAG && feedback.tag2 != tag2) continue;
165
+ totalRating += feedback.rating;
166
+ count++;
167
+ }
168
+ }
169
+
170
+ averageRating = count > 0 ? uint8(totalRating / count) : 0;
171
+ }
172
+
173
+ struct AllFeedback {
174
+ address[] clients;
175
+ uint8[] scores;
176
+ bytes32[] tag1s;
177
+ bytes32[] tag2s;
178
+ bool[] revokedStatuses;
179
+ }
180
+
181
+ function _readAllFeedback(
182
+ uint256 agentId,
183
+ address[] calldata reviewers,
184
+ bytes32 tag1,
185
+ bytes32 tag2,
186
+ bool includeRevoked
187
+ ) internal view returns (AllFeedback memory allFeedback) {
188
+ ReputationRegistryStorage.Layout storage $ = ReputationRegistryStorage.getLayout();
189
+ address[] memory clientList = reviewers.length > 0
190
+ ? reviewers
191
+ : $.clients[agentId].values();
192
+
193
+ uint256 maxCount = _estimateMaxFeedback(agentId, clientList, $);
194
+ CachedFeedback[] memory cache = new CachedFeedback[](maxCount);
195
+
196
+ uint256 totalCount = _populateCache(
197
+ cache,
198
+ agentId,
199
+ clientList,
200
+ tag1,
201
+ tag2,
202
+ includeRevoked,
203
+ $
204
+ );
205
+
206
+ allFeedback.clients = new address[](totalCount);
207
+ allFeedback.scores = new uint8[](totalCount);
208
+ allFeedback.tag1s = new bytes32[](totalCount);
209
+ allFeedback.tag2s = new bytes32[](totalCount);
210
+ allFeedback.revokedStatuses = new bool[](totalCount);
211
+
212
+ for (uint256 i; i < totalCount; ++i) {
213
+ allFeedback.clients[i] = cache[i].client;
214
+ allFeedback.scores[i] = cache[i].rating;
215
+ allFeedback.tag1s[i] = cache[i].tag1;
216
+ allFeedback.tag2s[i] = cache[i].tag2;
217
+ allFeedback.revokedStatuses[i] = cache[i].revoked;
218
+ }
219
+ }
220
+
221
+ function _populateCache(
222
+ CachedFeedback[] memory cache,
223
+ uint256 agentId,
224
+ address[] memory clientList,
225
+ bytes32 tag1,
226
+ bytes32 tag2,
227
+ bool includeRevoked,
228
+ ReputationRegistryStorage.Layout storage $
229
+ ) private view returns (uint256 count) {
230
+ uint256 clientListLength = clientList.length;
231
+
232
+ for (uint256 i; i < clientListLength; ++i) {
233
+ count = _processFeedbackForClient(
234
+ cache,
235
+ count,
236
+ agentId,
237
+ clientList[i],
238
+ tag1,
239
+ tag2,
240
+ includeRevoked,
241
+ $
242
+ );
243
+ }
244
+ }
245
+
246
+ function _processFeedbackForClient(
247
+ CachedFeedback[] memory cache,
248
+ uint256 currentCount,
249
+ uint256 agentId,
250
+ address client,
251
+ bytes32 tag1,
252
+ bytes32 tag2,
253
+ bool includeRevoked,
254
+ ReputationRegistryStorage.Layout storage $
255
+ ) private view returns (uint256 newCount) {
256
+ newCount = currentCount;
257
+ uint64 lastIndex = $.lastIndex[agentId][client];
258
+
259
+ for (uint64 j = 1; j <= lastIndex; ++j) {
260
+ bytes32 attestationId = $.feedback[agentId][client][j];
261
+
262
+ if (attestationId != EMPTY_UID) {
263
+ Attestation memory att = _getAttestation(attestationId);
264
+
265
+ if (includeRevoked || att.revocationTime == 0) {
266
+ Feedback memory fdbk = abi.decode(att.data, (Feedback));
267
+
268
+ if (
269
+ (tag1 == EMPTY_TAG || fdbk.tag1 == tag1) &&
270
+ (tag2 == EMPTY_TAG || fdbk.tag2 == tag2)
271
+ ) {
272
+ cache[newCount] = CachedFeedback({
273
+ client: client,
274
+ rating: fdbk.rating,
275
+ tag1: fdbk.tag1,
276
+ tag2: fdbk.tag2,
277
+ revoked: att.revocationTime != 0
278
+ });
279
+ ++newCount;
280
+ }
281
+ }
282
+ }
283
+ }
284
+ }
285
+
286
+ function _getResponseCount(
287
+ uint256 agentId,
288
+ address reviewerAddress,
289
+ uint64 feedbackIndex,
290
+ address[] calldata responders
291
+ ) internal view returns (uint256 count) {
292
+ ReputationRegistryStorage.Layout storage $ = ReputationRegistryStorage.getLayout();
293
+
294
+ if (reviewerAddress == address(0)) {
295
+ return _countAllClientsResponses(agentId, responders, $);
296
+ }
297
+
298
+ if (feedbackIndex == 0) {
299
+ return _countReviewerAllFeedback(agentId, reviewerAddress, responders, $);
300
+ }
301
+
302
+ return _countSingleFeedback(agentId, reviewerAddress, feedbackIndex, responders, $);
303
+ }
304
+
305
+ function _countAllClientsResponses(
306
+ uint256 agentId,
307
+ address[] calldata responders,
308
+ ReputationRegistryStorage.Layout storage $
309
+ ) private view returns (uint256 count) {
310
+ address[] memory clients = $.clients[agentId].values();
311
+ uint256 clientsLength = clients.length;
312
+
313
+ if (responders.length == 0) {
314
+ for (uint256 i; i < clientsLength; ++i) {
315
+ count += _countClientAllResponses(agentId, clients[i], $);
316
+ }
317
+ } else {
318
+ for (uint256 i; i < clientsLength; ++i) {
319
+ count += _countClientSpecificResponders(agentId, clients[i], responders, $);
320
+ }
321
+ }
322
+ }
323
+
324
+ function _countClientAllResponses(
325
+ uint256 agentId,
326
+ address client,
327
+ ReputationRegistryStorage.Layout storage $
328
+ ) private view returns (uint256 count) {
329
+ uint64 lastIdx = $.lastIndex[agentId][client];
330
+
331
+ for (uint64 j = 1; j <= lastIdx; ++j) {
332
+ address[] memory allResponders = $.responders[agentId][client][j].values();
333
+ uint256 respondersLength = allResponders.length;
334
+
335
+ for (uint256 k; k < respondersLength; ++k) {
336
+ count += $.responseCount[agentId][client][j][allResponders[k]];
337
+ }
338
+ }
339
+ }
340
+
341
+ function _countClientSpecificResponders(
342
+ uint256 agentId,
343
+ address client,
344
+ address[] calldata responders,
345
+ ReputationRegistryStorage.Layout storage $
346
+ ) private view returns (uint256 count) {
347
+ uint64 lastIdx = $.lastIndex[agentId][client];
348
+ uint256 respondersLength = responders.length;
349
+
350
+ for (uint64 j = 1; j <= lastIdx; ++j) {
351
+ for (uint256 k; k < respondersLength; ++k) {
352
+ count += $.responseCount[agentId][client][j][responders[k]];
353
+ }
354
+ }
355
+ }
356
+
357
+ function _countReviewerAllFeedback(
358
+ uint256 agentId,
359
+ address reviewerAddress,
360
+ address[] calldata responders,
361
+ ReputationRegistryStorage.Layout storage $
362
+ ) private view returns (uint256 count) {
363
+ uint64 lastIdx = $.lastIndex[agentId][reviewerAddress];
364
+
365
+ if (responders.length == 0) {
366
+ for (uint64 j = 1; j <= lastIdx; ++j) {
367
+ address[] memory allResponders = $.responders[agentId][reviewerAddress][j].values();
368
+ uint256 respondersLength = allResponders.length;
369
+
370
+ for (uint256 k; k < respondersLength; ++k) {
371
+ count += $.responseCount[agentId][reviewerAddress][j][allResponders[k]];
372
+ }
373
+ }
374
+ } else {
375
+ uint256 respondersLength = responders.length;
376
+
377
+ for (uint64 j = 1; j <= lastIdx; ++j) {
378
+ for (uint256 k; k < respondersLength; ++k) {
379
+ count += $.responseCount[agentId][reviewerAddress][j][responders[k]];
380
+ }
381
+ }
382
+ }
383
+ }
384
+
385
+ function _countSingleFeedback(
386
+ uint256 agentId,
387
+ address reviewerAddress,
388
+ uint64 feedbackIndex,
389
+ address[] calldata responders,
390
+ ReputationRegistryStorage.Layout storage $
391
+ ) private view returns (uint256 count) {
392
+ if (responders.length == 0) {
393
+ address[] memory allResponders = $
394
+ .responders[agentId][reviewerAddress][feedbackIndex].values();
395
+ uint256 respondersLength = allResponders.length;
396
+
397
+ for (uint256 k; k < respondersLength; ++k) {
398
+ count += $.responseCount[agentId][reviewerAddress][feedbackIndex][allResponders[k]];
399
+ }
400
+ } else {
401
+ uint256 respondersLength = responders.length;
402
+
403
+ for (uint256 k; k < respondersLength; ++k) {
404
+ count += $.responseCount[agentId][reviewerAddress][feedbackIndex][responders[k]];
405
+ }
406
+ }
407
+ }
408
+
409
+ function _getClients(uint256 agentId) internal view returns (address[] memory) {
410
+ return ReputationRegistryStorage.getLayout().clients[agentId].values();
411
+ }
412
+
413
+ /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
414
+ /* Utility Functions */
415
+ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
416
+
417
+ function _createAttestation(
418
+ bytes32 schemaId,
419
+ address recipient,
420
+ Feedback memory feedback
421
+ ) internal returns (bytes32 attestationId) {
422
+ AttestationRequest memory request;
423
+ request.schema = schemaId;
424
+ request.data.recipient = recipient;
425
+ request.data.revocable = true;
426
+ request.data.refUID = EMPTY_UID;
427
+ request.data.data = abi.encode(feedback);
428
+ return _attest(msg.sender, 0, request).uid;
429
+ }
430
+
431
+ function _estimateMaxFeedback(
432
+ uint256 agentId,
433
+ address[] memory clientList,
434
+ ReputationRegistryStorage.Layout storage $
435
+ ) private view returns (uint256 maxCount) {
436
+ for (uint256 i; i < clientList.length; ++i) {
437
+ maxCount += $.lastIndex[agentId][clientList[i]];
438
+ }
439
+ }
440
+
441
+ function _verifyNotSelfFeedback(address agent, uint256 agentId) internal view {
442
+ if (
443
+ msg.sender == agent ||
444
+ _isApprovedForAll(agent, msg.sender) ||
445
+ _getApproved(agentId) == msg.sender
446
+ ) {
447
+ Reputation__SelfFeedbackNotAllowed.selector.revertWith();
448
+ }
449
+ }
450
+ }