@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.
- package/package.json +3 -3
- package/scripts/deployments/diamonds/DeployAppRegistry.s.sol +27 -1
- package/scripts/deployments/facets/DeployIdentityRegistry.s.sol +64 -0
- package/scripts/deployments/facets/DeployReputationRegistry.s.sol +64 -0
- package/scripts/interactions/InteractAppRegistry.s.sol +60 -26
- package/src/apps/facets/identity/IIdentityRegistry.sol +130 -0
- package/src/apps/facets/identity/IdentityRegistryBase.sol +32 -0
- package/src/apps/facets/identity/IdentityRegistryFacet.sol +135 -0
- package/src/apps/facets/identity/IdentityRegistryStorage.sol +27 -0
- package/src/apps/facets/registry/AppRegistryBase.sol +0 -1
- package/src/apps/facets/reputation/IReputationRegistry.sol +186 -0
- package/src/apps/facets/reputation/ReputationRegistryBase.sol +450 -0
- package/src/apps/facets/reputation/ReputationRegistryFacet.sol +155 -0
- package/src/apps/facets/reputation/ReputationRegistryStorage.sol +38 -0
- package/src/apps/simple/app/ISimpleApp.sol +22 -0
- package/src/apps/simple/app/SimpleAppFacet.sol +20 -1
- package/src/apps/simple/app/SimpleAppStorage.sol +1 -0
|
@@ -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
|
+
}
|