@aixyz/erc-8004 0.0.6
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/README.md +145 -0
- package/contracts/IdentityRegistryUpgradeable.sol +214 -0
- package/contracts/ReputationRegistryUpgradeable.sol +386 -0
- package/contracts/ValidationRegistryUpgradeable.sol +198 -0
- package/dist/abis/IdentityRegistry_V1.d.ts +802 -0
- package/dist/abis/IdentityRegistry_V1.js +1047 -0
- package/dist/abis/IdentityRegistry_V2.d.ts +802 -0
- package/dist/abis/IdentityRegistry_V2.js +1047 -0
- package/dist/abis/ReputationRegistry_V1.d.ts +214 -0
- package/dist/abis/ReputationRegistry_V1.js +275 -0
- package/dist/abis/ReputationRegistry_V2.d.ts +567 -0
- package/dist/abis/ReputationRegistry_V2.js +730 -0
- package/dist/abis/ReputationRegistry_V3.d.ts +567 -0
- package/dist/abis/ReputationRegistry_V3.js +730 -0
- package/dist/abis/ValidationRegistry_V1.d.ts +391 -0
- package/dist/abis/ValidationRegistry_V1.js +508 -0
- package/dist/index.js +4379 -0
- package/dist/src/index.d.ts +101 -0
- package/dist/src/index.js +173 -0
- package/dist/src/schemas/feedback.d.ts +190 -0
- package/dist/src/schemas/feedback.js +146 -0
- package/dist/src/schemas/registration.d.ts +219 -0
- package/dist/src/schemas/registration.js +125 -0
- package/package.json +41 -0
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
// As copied from https://github.com/erc-8004/erc-8004-contracts/blob/093d7b91eb9c22048d411896ed397d695742a5f8/contracts/ReputationRegistryUpgradeable.sol
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
pragma solidity ^0.8.20;
|
|
4
|
+
|
|
5
|
+
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
|
|
6
|
+
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
|
|
7
|
+
|
|
8
|
+
interface IIdentityRegistry {
|
|
9
|
+
function isAuthorizedOrOwner(address spender, uint256 agentId) external view returns (bool);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
contract ReputationRegistryUpgradeable is OwnableUpgradeable, UUPSUpgradeable {
|
|
13
|
+
|
|
14
|
+
int128 private constant MAX_ABS_VALUE = 1e38;
|
|
15
|
+
|
|
16
|
+
event NewFeedback(
|
|
17
|
+
uint256 indexed agentId,
|
|
18
|
+
address indexed clientAddress,
|
|
19
|
+
uint64 feedbackIndex,
|
|
20
|
+
int128 value,
|
|
21
|
+
uint8 valueDecimals,
|
|
22
|
+
string indexed indexedTag1,
|
|
23
|
+
string tag1,
|
|
24
|
+
string tag2,
|
|
25
|
+
string endpoint,
|
|
26
|
+
string feedbackURI,
|
|
27
|
+
bytes32 feedbackHash
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
event FeedbackRevoked(
|
|
31
|
+
uint256 indexed agentId,
|
|
32
|
+
address indexed clientAddress,
|
|
33
|
+
uint64 indexed feedbackIndex
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
event ResponseAppended(
|
|
37
|
+
uint256 indexed agentId,
|
|
38
|
+
address indexed clientAddress,
|
|
39
|
+
uint64 feedbackIndex,
|
|
40
|
+
address indexed responder,
|
|
41
|
+
string responseURI,
|
|
42
|
+
bytes32 responseHash
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
struct Feedback {
|
|
46
|
+
int128 value; // 16 bytes
|
|
47
|
+
uint8 valueDecimals; // 1 byte (packed with value + isRevoked)
|
|
48
|
+
bool isRevoked; // 1 byte (packed with value + valueDecimals)
|
|
49
|
+
string tag1;
|
|
50
|
+
string tag2;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/// @dev Identity registry address stored at slot 0 (matches MinimalUUPS)
|
|
54
|
+
address private _identityRegistry;
|
|
55
|
+
|
|
56
|
+
/// @custom:storage-location erc7201:erc8004.reputation.registry
|
|
57
|
+
struct ReputationRegistryStorage {
|
|
58
|
+
// agentId => clientAddress => feedbackIndex => Feedback (1-indexed)
|
|
59
|
+
mapping(uint256 => mapping(address => mapping(uint64 => Feedback))) _feedback;
|
|
60
|
+
// agentId => clientAddress => last feedback index
|
|
61
|
+
mapping(uint256 => mapping(address => uint64)) _lastIndex;
|
|
62
|
+
// agentId => clientAddress => feedbackIndex => responder => response count
|
|
63
|
+
mapping(uint256 => mapping(address => mapping(uint64 => mapping(address => uint64)))) _responseCount;
|
|
64
|
+
// Track all unique responders for each feedback
|
|
65
|
+
mapping(uint256 => mapping(address => mapping(uint64 => address[]))) _responders;
|
|
66
|
+
mapping(uint256 => mapping(address => mapping(uint64 => mapping(address => bool)))) _responderExists;
|
|
67
|
+
// Track all unique clients that have given feedback for each agent
|
|
68
|
+
mapping(uint256 => address[]) _clients;
|
|
69
|
+
mapping(uint256 => mapping(address => bool)) _clientExists;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// keccak256(abi.encode(uint256(keccak256("erc8004.reputation.registry.2")) - 1)) & ~bytes32(uint256(0xff))
|
|
73
|
+
bytes32 private constant REPUTATION_REGISTRY_STORAGE_LOCATION =
|
|
74
|
+
0xa03d7693f2b3746b2d03f163c788147b71aa82854399a21fdf4de143ba778300;
|
|
75
|
+
|
|
76
|
+
function _getReputationRegistryStorage() private pure returns (ReputationRegistryStorage storage $) {
|
|
77
|
+
assembly {
|
|
78
|
+
$.slot := REPUTATION_REGISTRY_STORAGE_LOCATION
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/// @custom:oz-upgrades-unsafe-allow constructor
|
|
83
|
+
constructor() {
|
|
84
|
+
_disableInitializers();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function initialize(address identityRegistry_) public reinitializer(2) onlyOwner {
|
|
88
|
+
require(identityRegistry_ != address(0), "bad identity");
|
|
89
|
+
_identityRegistry = identityRegistry_;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function getIdentityRegistry() external view returns (address) {
|
|
93
|
+
return _identityRegistry;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function giveFeedback(
|
|
97
|
+
uint256 agentId,
|
|
98
|
+
int128 value,
|
|
99
|
+
uint8 valueDecimals,
|
|
100
|
+
string calldata tag1,
|
|
101
|
+
string calldata tag2,
|
|
102
|
+
string calldata endpoint,
|
|
103
|
+
string calldata feedbackURI,
|
|
104
|
+
bytes32 feedbackHash
|
|
105
|
+
) external {
|
|
106
|
+
require(valueDecimals <= 18, "too many decimals");
|
|
107
|
+
require(value >= -MAX_ABS_VALUE && value <= MAX_ABS_VALUE, "value too large");
|
|
108
|
+
|
|
109
|
+
// SECURITY: Prevent self-feedback from owner and operators
|
|
110
|
+
// Also reverts with ERC721NonexistentToken if agent doesn't exist
|
|
111
|
+
require(!IIdentityRegistry(_identityRegistry).isAuthorizedOrOwner(msg.sender, agentId), "Self-feedback not allowed");
|
|
112
|
+
|
|
113
|
+
ReputationRegistryStorage storage $ = _getReputationRegistryStorage();
|
|
114
|
+
|
|
115
|
+
// Increment and get current index (1-indexed)
|
|
116
|
+
uint64 currentIndex = ++$._lastIndex[agentId][msg.sender];
|
|
117
|
+
|
|
118
|
+
// Store feedback
|
|
119
|
+
$._feedback[agentId][msg.sender][currentIndex] = Feedback({
|
|
120
|
+
value: value,
|
|
121
|
+
valueDecimals: valueDecimals,
|
|
122
|
+
tag1: tag1,
|
|
123
|
+
tag2: tag2,
|
|
124
|
+
isRevoked: false
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// track new client
|
|
128
|
+
if (!$._clientExists[agentId][msg.sender]) {
|
|
129
|
+
$._clients[agentId].push(msg.sender);
|
|
130
|
+
$._clientExists[agentId][msg.sender] = true;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
emit NewFeedback(agentId, msg.sender, currentIndex, value, valueDecimals, tag1, tag1, tag2, endpoint, feedbackURI, feedbackHash);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function revokeFeedback(uint256 agentId, uint64 feedbackIndex) external {
|
|
137
|
+
require(feedbackIndex > 0, "index must be > 0");
|
|
138
|
+
ReputationRegistryStorage storage $ = _getReputationRegistryStorage();
|
|
139
|
+
require(feedbackIndex <= $._lastIndex[agentId][msg.sender], "index out of bounds");
|
|
140
|
+
require(!$._feedback[agentId][msg.sender][feedbackIndex].isRevoked, "Already revoked");
|
|
141
|
+
|
|
142
|
+
$._feedback[agentId][msg.sender][feedbackIndex].isRevoked = true;
|
|
143
|
+
emit FeedbackRevoked(agentId, msg.sender, feedbackIndex);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function appendResponse(
|
|
147
|
+
uint256 agentId,
|
|
148
|
+
address clientAddress,
|
|
149
|
+
uint64 feedbackIndex,
|
|
150
|
+
string calldata responseURI,
|
|
151
|
+
bytes32 responseHash
|
|
152
|
+
) external {
|
|
153
|
+
require(feedbackIndex > 0, "index must be > 0");
|
|
154
|
+
require(bytes(responseURI).length > 0, "Empty URI");
|
|
155
|
+
ReputationRegistryStorage storage $ = _getReputationRegistryStorage();
|
|
156
|
+
require(feedbackIndex <= $._lastIndex[agentId][clientAddress], "index out of bounds");
|
|
157
|
+
|
|
158
|
+
// Track new responder
|
|
159
|
+
if (!$._responderExists[agentId][clientAddress][feedbackIndex][msg.sender]) {
|
|
160
|
+
$._responders[agentId][clientAddress][feedbackIndex].push(msg.sender);
|
|
161
|
+
$._responderExists[agentId][clientAddress][feedbackIndex][msg.sender] = true;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Increment response count for this responder
|
|
165
|
+
$._responseCount[agentId][clientAddress][feedbackIndex][msg.sender]++;
|
|
166
|
+
|
|
167
|
+
emit ResponseAppended(agentId, clientAddress, feedbackIndex, msg.sender, responseURI, responseHash);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function getLastIndex(uint256 agentId, address clientAddress) external view returns (uint64) {
|
|
171
|
+
ReputationRegistryStorage storage $ = _getReputationRegistryStorage();
|
|
172
|
+
return $._lastIndex[agentId][clientAddress];
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function readFeedback(uint256 agentId, address clientAddress, uint64 feedbackIndex)
|
|
176
|
+
external
|
|
177
|
+
view
|
|
178
|
+
returns (int128 value, uint8 valueDecimals, string memory tag1, string memory tag2, bool isRevoked)
|
|
179
|
+
{
|
|
180
|
+
ReputationRegistryStorage storage $ = _getReputationRegistryStorage();
|
|
181
|
+
require(feedbackIndex > 0, "index must be > 0");
|
|
182
|
+
require(feedbackIndex <= $._lastIndex[agentId][clientAddress], "index out of bounds");
|
|
183
|
+
Feedback storage f = $._feedback[agentId][clientAddress][feedbackIndex];
|
|
184
|
+
return (f.value, f.valueDecimals, f.tag1, f.tag2, f.isRevoked);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function getSummary(
|
|
188
|
+
uint256 agentId,
|
|
189
|
+
address[] calldata clientAddresses,
|
|
190
|
+
string calldata tag1,
|
|
191
|
+
string calldata tag2
|
|
192
|
+
) external view returns (uint64 count, int128 summaryValue, uint8 summaryValueDecimals) {
|
|
193
|
+
|
|
194
|
+
ReputationRegistryStorage storage $ = _getReputationRegistryStorage();
|
|
195
|
+
address[] memory clientList;
|
|
196
|
+
if (clientAddresses.length > 0) {
|
|
197
|
+
clientList = clientAddresses;
|
|
198
|
+
} else {
|
|
199
|
+
revert("clientAddresses required");
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
bytes32 emptyHash = keccak256(bytes(""));
|
|
203
|
+
bytes32 tag1Hash = keccak256(bytes(tag1));
|
|
204
|
+
bytes32 tag2Hash = keccak256(bytes(tag2));
|
|
205
|
+
|
|
206
|
+
// WAD: 18 decimal fixed-point precision for internal math
|
|
207
|
+
int256 sum;
|
|
208
|
+
|
|
209
|
+
// Track frequency of each valueDecimals (0-18, anything >18 treated as 18)
|
|
210
|
+
uint64[19] memory decimalCounts;
|
|
211
|
+
|
|
212
|
+
for (uint256 i; i < clientList.length; i++) {
|
|
213
|
+
uint64 lastIdx = $._lastIndex[agentId][clientList[i]];
|
|
214
|
+
for (uint64 j = 1; j <= lastIdx; j++) {
|
|
215
|
+
Feedback storage fb = $._feedback[agentId][clientList[i]][j];
|
|
216
|
+
if (fb.isRevoked) continue;
|
|
217
|
+
if (emptyHash != tag1Hash &&
|
|
218
|
+
tag1Hash != keccak256(bytes(fb.tag1))) continue;
|
|
219
|
+
if (emptyHash != tag2Hash &&
|
|
220
|
+
tag2Hash != keccak256(bytes(fb.tag2))) continue;
|
|
221
|
+
|
|
222
|
+
// Normalize to 18 decimals (WAD)
|
|
223
|
+
// `valueDecimals` is bounded to <= 18 on write; keep math signed.
|
|
224
|
+
int256 factor = int256(10 ** uint256(18 - fb.valueDecimals));
|
|
225
|
+
int256 normalized = fb.value * factor;
|
|
226
|
+
decimalCounts[fb.valueDecimals]++;
|
|
227
|
+
|
|
228
|
+
sum += normalized;
|
|
229
|
+
count++;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (count == 0) {
|
|
234
|
+
return (0, 0, 0);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Find mode (most frequent valueDecimals)
|
|
238
|
+
uint8 modeDecimals;
|
|
239
|
+
uint64 maxCount;
|
|
240
|
+
for (uint8 d; d <= 18; d++) {
|
|
241
|
+
if (decimalCounts[d] > maxCount) {
|
|
242
|
+
maxCount = decimalCounts[d];
|
|
243
|
+
modeDecimals = d;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Calculate average in WAD, then scale to mode precision
|
|
248
|
+
int256 avgWad = sum / int256(uint256(count));
|
|
249
|
+
summaryValue = int128(avgWad / int256(10 ** uint256(18 - modeDecimals)));
|
|
250
|
+
summaryValueDecimals = modeDecimals;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function readAllFeedback(
|
|
254
|
+
uint256 agentId,
|
|
255
|
+
address[] calldata clientAddresses,
|
|
256
|
+
string calldata tag1,
|
|
257
|
+
string calldata tag2,
|
|
258
|
+
bool includeRevoked
|
|
259
|
+
) external view returns (
|
|
260
|
+
address[] memory clients,
|
|
261
|
+
uint64[] memory feedbackIndexes,
|
|
262
|
+
int128[] memory values,
|
|
263
|
+
uint8[] memory valueDecimals,
|
|
264
|
+
string[] memory tag1s,
|
|
265
|
+
string[] memory tag2s,
|
|
266
|
+
bool[] memory revokedStatuses
|
|
267
|
+
) {
|
|
268
|
+
ReputationRegistryStorage storage $ = _getReputationRegistryStorage();
|
|
269
|
+
address[] memory clientList;
|
|
270
|
+
if (clientAddresses.length > 0) {
|
|
271
|
+
clientList = clientAddresses;
|
|
272
|
+
} else {
|
|
273
|
+
clientList = $._clients[agentId];
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// First pass: count matching feedback
|
|
277
|
+
bytes32 emptyHash = keccak256(bytes(""));
|
|
278
|
+
bytes32 tag1Hash = keccak256(bytes(tag1));
|
|
279
|
+
bytes32 tag2Hash = keccak256(bytes(tag2));
|
|
280
|
+
uint256 totalCount;
|
|
281
|
+
for (uint256 i; i < clientList.length; i++) {
|
|
282
|
+
uint64 lastIdx = $._lastIndex[agentId][clientList[i]];
|
|
283
|
+
for (uint64 j = 1; j <= lastIdx; j++) {
|
|
284
|
+
Feedback storage fb = $._feedback[agentId][clientList[i]][j];
|
|
285
|
+
if (!includeRevoked && fb.isRevoked) continue;
|
|
286
|
+
if (emptyHash != tag1Hash &&
|
|
287
|
+
tag1Hash != keccak256(bytes(fb.tag1))) continue;
|
|
288
|
+
if (emptyHash != tag2Hash &&
|
|
289
|
+
tag2Hash != keccak256(bytes(fb.tag2))) continue;
|
|
290
|
+
totalCount++;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Initialize arrays
|
|
295
|
+
clients = new address[](totalCount);
|
|
296
|
+
feedbackIndexes = new uint64[](totalCount);
|
|
297
|
+
values = new int128[](totalCount);
|
|
298
|
+
valueDecimals = new uint8[](totalCount);
|
|
299
|
+
tag1s = new string[](totalCount);
|
|
300
|
+
tag2s = new string[](totalCount);
|
|
301
|
+
revokedStatuses = new bool[](totalCount);
|
|
302
|
+
|
|
303
|
+
// Second pass: populate arrays
|
|
304
|
+
uint256 idx;
|
|
305
|
+
for (uint256 i; i < clientList.length; i++) {
|
|
306
|
+
uint64 lastIdx = $._lastIndex[agentId][clientList[i]];
|
|
307
|
+
for (uint64 j = 1; j <= lastIdx; j++) {
|
|
308
|
+
Feedback storage fb = $._feedback[agentId][clientList[i]][j];
|
|
309
|
+
if (!includeRevoked && fb.isRevoked) continue;
|
|
310
|
+
if (emptyHash != tag1Hash &&
|
|
311
|
+
tag1Hash != keccak256(bytes(fb.tag1))) continue;
|
|
312
|
+
if (emptyHash != tag2Hash &&
|
|
313
|
+
tag2Hash != keccak256(bytes(fb.tag2))) continue;
|
|
314
|
+
|
|
315
|
+
clients[idx] = clientList[i];
|
|
316
|
+
feedbackIndexes[idx] = j;
|
|
317
|
+
values[idx] = fb.value;
|
|
318
|
+
valueDecimals[idx] = fb.valueDecimals;
|
|
319
|
+
tag1s[idx] = fb.tag1;
|
|
320
|
+
tag2s[idx] = fb.tag2;
|
|
321
|
+
revokedStatuses[idx] = fb.isRevoked;
|
|
322
|
+
idx++;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function getResponseCount(
|
|
328
|
+
uint256 agentId,
|
|
329
|
+
address clientAddress,
|
|
330
|
+
uint64 feedbackIndex,
|
|
331
|
+
address[] calldata responders
|
|
332
|
+
) external view returns (uint64 count) {
|
|
333
|
+
ReputationRegistryStorage storage $ = _getReputationRegistryStorage();
|
|
334
|
+
if (clientAddress == address(0)) {
|
|
335
|
+
// Count all responses for all clients
|
|
336
|
+
address[] memory clients = $._clients[agentId];
|
|
337
|
+
for (uint256 i; i < clients.length; i++) {
|
|
338
|
+
uint64 lastIdx = $._lastIndex[agentId][clients[i]];
|
|
339
|
+
for (uint64 j = 1; j <= lastIdx; j++) {
|
|
340
|
+
count += _countResponses(agentId, clients[i], j, responders);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
} else if (feedbackIndex == 0) {
|
|
344
|
+
// Count all responses for specific clientAddress
|
|
345
|
+
uint64 lastIdx = $._lastIndex[agentId][clientAddress];
|
|
346
|
+
for (uint64 j = 1; j <= lastIdx; j++) {
|
|
347
|
+
count += _countResponses(agentId, clientAddress, j, responders);
|
|
348
|
+
}
|
|
349
|
+
} else {
|
|
350
|
+
// Count responses for specific clientAddress and feedbackIndex
|
|
351
|
+
count = _countResponses(agentId, clientAddress, feedbackIndex, responders);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function _countResponses(
|
|
356
|
+
uint256 agentId,
|
|
357
|
+
address clientAddress,
|
|
358
|
+
uint64 feedbackIndex,
|
|
359
|
+
address[] calldata responders
|
|
360
|
+
) internal view returns (uint64 count) {
|
|
361
|
+
ReputationRegistryStorage storage $ = _getReputationRegistryStorage();
|
|
362
|
+
if (responders.length == 0) {
|
|
363
|
+
// Count from all responders
|
|
364
|
+
address[] memory allResponders = $._responders[agentId][clientAddress][feedbackIndex];
|
|
365
|
+
for (uint256 k; k < allResponders.length; k++) {
|
|
366
|
+
count += $._responseCount[agentId][clientAddress][feedbackIndex][allResponders[k]];
|
|
367
|
+
}
|
|
368
|
+
} else {
|
|
369
|
+
// Count from specified responders
|
|
370
|
+
for (uint256 k; k < responders.length; k++) {
|
|
371
|
+
count += $._responseCount[agentId][clientAddress][feedbackIndex][responders[k]];
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function getClients(uint256 agentId) external view returns (address[] memory) {
|
|
377
|
+
ReputationRegistryStorage storage $ = _getReputationRegistryStorage();
|
|
378
|
+
return $._clients[agentId];
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}
|
|
382
|
+
|
|
383
|
+
function getVersion() external pure returns (string memory) {
|
|
384
|
+
return "2.0.0";
|
|
385
|
+
}
|
|
386
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
// As copied from https://github.com/erc-8004/erc-8004-contracts/blob/093d7b91eb9c22048d411896ed397d695742a5f8/contracts/ValidationRegistryUpgradeable.sol
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
pragma solidity ^0.8.20;
|
|
4
|
+
|
|
5
|
+
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
|
|
6
|
+
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
|
|
7
|
+
|
|
8
|
+
interface IIdentityRegistry {
|
|
9
|
+
function ownerOf(uint256 tokenId) external view returns (address);
|
|
10
|
+
function getApproved(uint256 tokenId) external view returns (address);
|
|
11
|
+
function isApprovedForAll(address owner, address operator) external view returns (bool);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
contract ValidationRegistryUpgradeable is OwnableUpgradeable, UUPSUpgradeable {
|
|
15
|
+
event ValidationRequest(
|
|
16
|
+
address indexed validatorAddress,
|
|
17
|
+
uint256 indexed agentId,
|
|
18
|
+
string requestURI,
|
|
19
|
+
bytes32 indexed requestHash
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
event ValidationResponse(
|
|
23
|
+
address indexed validatorAddress,
|
|
24
|
+
uint256 indexed agentId,
|
|
25
|
+
bytes32 indexed requestHash,
|
|
26
|
+
uint8 response,
|
|
27
|
+
string responseURI,
|
|
28
|
+
bytes32 responseHash,
|
|
29
|
+
string tag
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
struct ValidationStatus {
|
|
33
|
+
address validatorAddress;
|
|
34
|
+
uint256 agentId;
|
|
35
|
+
uint8 response; // 0..100
|
|
36
|
+
bytes32 responseHash;
|
|
37
|
+
string tag;
|
|
38
|
+
uint256 lastUpdate;
|
|
39
|
+
bool hasResponse;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/// @dev Identity registry address stored at slot 0 (matches MinimalUUPS)
|
|
43
|
+
address private _identityRegistry;
|
|
44
|
+
|
|
45
|
+
/// @custom:storage-location erc7201:erc8004.validation.registry
|
|
46
|
+
struct ValidationRegistryStorage {
|
|
47
|
+
// requestHash => validation status
|
|
48
|
+
mapping(bytes32 => ValidationStatus) validations;
|
|
49
|
+
// agentId => list of requestHashes
|
|
50
|
+
mapping(uint256 => bytes32[]) _agentValidations;
|
|
51
|
+
// validatorAddress => list of requestHashes
|
|
52
|
+
mapping(address => bytes32[]) _validatorRequests;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// keccak256(abi.encode(uint256(keccak256("erc8004.validation.registry")) - 1)) & ~bytes32(uint256(0xff))
|
|
56
|
+
bytes32 private constant VALIDATION_REGISTRY_STORAGE_LOCATION =
|
|
57
|
+
0x21543a2dd0df813994fbf82c69c61d1aafcdce183d68d2ef40068bdce1481100;
|
|
58
|
+
|
|
59
|
+
function _getValidationRegistryStorage() private pure returns (ValidationRegistryStorage storage $) {
|
|
60
|
+
assembly {
|
|
61
|
+
$.slot := VALIDATION_REGISTRY_STORAGE_LOCATION
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/// @custom:oz-upgrades-unsafe-allow constructor
|
|
66
|
+
constructor() {
|
|
67
|
+
_disableInitializers();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function initialize(address identityRegistry_) public reinitializer(2) onlyOwner {
|
|
71
|
+
require(identityRegistry_ != address(0), "bad identity");
|
|
72
|
+
_identityRegistry = identityRegistry_;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function getIdentityRegistry() external view returns (address) {
|
|
76
|
+
return _identityRegistry;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function validationRequest(
|
|
80
|
+
address validatorAddress,
|
|
81
|
+
uint256 agentId,
|
|
82
|
+
string calldata requestURI,
|
|
83
|
+
bytes32 requestHash
|
|
84
|
+
) external {
|
|
85
|
+
ValidationRegistryStorage storage $ = _getValidationRegistryStorage();
|
|
86
|
+
require(validatorAddress != address(0), "bad validator");
|
|
87
|
+
require($.validations[requestHash].validatorAddress == address(0), "exists");
|
|
88
|
+
|
|
89
|
+
// Check permission: caller must be owner or approved operator
|
|
90
|
+
IIdentityRegistry registry = IIdentityRegistry(_identityRegistry);
|
|
91
|
+
address owner = registry.ownerOf(agentId);
|
|
92
|
+
require(
|
|
93
|
+
msg.sender == owner ||
|
|
94
|
+
registry.isApprovedForAll(owner, msg.sender) ||
|
|
95
|
+
registry.getApproved(agentId) == msg.sender,
|
|
96
|
+
"Not authorized"
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
$.validations[requestHash] = ValidationStatus({
|
|
100
|
+
validatorAddress: validatorAddress,
|
|
101
|
+
agentId: agentId,
|
|
102
|
+
response: 0,
|
|
103
|
+
responseHash: bytes32(0),
|
|
104
|
+
tag: "",
|
|
105
|
+
lastUpdate: block.timestamp,
|
|
106
|
+
hasResponse: false
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// Track for lookups
|
|
110
|
+
$._agentValidations[agentId].push(requestHash);
|
|
111
|
+
$._validatorRequests[validatorAddress].push(requestHash);
|
|
112
|
+
|
|
113
|
+
emit ValidationRequest(validatorAddress, agentId, requestURI, requestHash);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function validationResponse(
|
|
117
|
+
bytes32 requestHash,
|
|
118
|
+
uint8 response,
|
|
119
|
+
string calldata responseURI,
|
|
120
|
+
bytes32 responseHash,
|
|
121
|
+
string calldata tag
|
|
122
|
+
) external {
|
|
123
|
+
ValidationRegistryStorage storage $ = _getValidationRegistryStorage();
|
|
124
|
+
ValidationStatus storage s = $.validations[requestHash];
|
|
125
|
+
require(s.validatorAddress != address(0), "unknown");
|
|
126
|
+
require(msg.sender == s.validatorAddress, "not validator");
|
|
127
|
+
require(response <= 100, "resp>100");
|
|
128
|
+
s.response = response;
|
|
129
|
+
s.responseHash = responseHash;
|
|
130
|
+
s.tag = tag;
|
|
131
|
+
s.lastUpdate = block.timestamp;
|
|
132
|
+
s.hasResponse = true;
|
|
133
|
+
emit ValidationResponse(s.validatorAddress, s.agentId, requestHash, response, responseURI, responseHash, tag);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function getValidationStatus(bytes32 requestHash)
|
|
137
|
+
external
|
|
138
|
+
view
|
|
139
|
+
returns (address validatorAddress, uint256 agentId, uint8 response, bytes32 responseHash, string memory tag, uint256 lastUpdate)
|
|
140
|
+
{
|
|
141
|
+
ValidationRegistryStorage storage $ = _getValidationRegistryStorage();
|
|
142
|
+
ValidationStatus memory s = $.validations[requestHash];
|
|
143
|
+
require(s.validatorAddress != address(0), "unknown");
|
|
144
|
+
return (s.validatorAddress, s.agentId, s.response, s.responseHash, s.tag, s.lastUpdate);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function getSummary(
|
|
148
|
+
uint256 agentId,
|
|
149
|
+
address[] calldata validatorAddresses,
|
|
150
|
+
string calldata tag
|
|
151
|
+
) external view returns (uint64 count, uint8 avgResponse) {
|
|
152
|
+
ValidationRegistryStorage storage $ = _getValidationRegistryStorage();
|
|
153
|
+
uint256 totalResponse;
|
|
154
|
+
|
|
155
|
+
bytes32[] storage requestHashes = $._agentValidations[agentId];
|
|
156
|
+
|
|
157
|
+
for (uint256 i; i < requestHashes.length; i++) {
|
|
158
|
+
ValidationStatus storage s = $.validations[requestHashes[i]];
|
|
159
|
+
|
|
160
|
+
// Filter by validator if specified
|
|
161
|
+
bool matchValidator = (validatorAddresses.length == 0);
|
|
162
|
+
if (!matchValidator) {
|
|
163
|
+
for (uint256 j; j < validatorAddresses.length; j++) {
|
|
164
|
+
if (s.validatorAddress == validatorAddresses[j]) {
|
|
165
|
+
matchValidator = true;
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Filter by tag (empty string means no filter)
|
|
172
|
+
bool matchTag = (bytes(tag).length == 0) || (keccak256(bytes(s.tag)) == keccak256(bytes(tag)));
|
|
173
|
+
|
|
174
|
+
if (matchValidator && matchTag && s.hasResponse) {
|
|
175
|
+
totalResponse += s.response;
|
|
176
|
+
count++;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
avgResponse = count > 0 ? uint8(totalResponse / count) : 0;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function getAgentValidations(uint256 agentId) external view returns (bytes32[] memory) {
|
|
184
|
+
ValidationRegistryStorage storage $ = _getValidationRegistryStorage();
|
|
185
|
+
return $._agentValidations[agentId];
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function getValidatorRequests(address validatorAddress) external view returns (bytes32[] memory) {
|
|
189
|
+
ValidationRegistryStorage storage $ = _getValidationRegistryStorage();
|
|
190
|
+
return $._validatorRequests[validatorAddress];
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}
|
|
194
|
+
|
|
195
|
+
function getVersion() external pure returns (string memory) {
|
|
196
|
+
return "2.0.0";
|
|
197
|
+
}
|
|
198
|
+
}
|