@@ -24,248 +24,3 @@ interface IReputationOracle {
2424 */
2525 function isActive () external view returns (bool isActive );
2626}
27-
28- // SPDX-License-Identifier: MIT
29- pragma solidity ^ 0.8.20 ;
30-
31- import "@openzeppelin/contracts/access/AccessControl.sol " ;
32- import "./IReputationOracle.sol " ;
33-
34- /**
35- * @title ReputationSnapshot
36- * @notice Creates snapshots of reputation data for cross-chain bridging
37- * @dev Generates Merkle trees from reputation data for efficient verification
38- */
39- contract ReputationSnapshot is AccessControl {
40- bytes32 public constant SNAPSHOT_ROLE = keccak256 ("SNAPSHOT_ROLE " );
41-
42- struct ReputationData {
43- address user;
44- uint256 score;
45- uint256 timestamp;
46- uint256 blockNumber;
47- }
48-
49- // Snapshot storage
50- mapping (uint256 => ReputationData[]) public snapshots;
51- mapping (uint256 => bytes32 ) public snapshotRoots; // Merkle roots
52-
53- // Events
54- event SnapshotCreated (uint256 indexed snapshotId , uint256 userCount , bytes32 root );
55- event ReputationBridged (address indexed user , uint256 snapshotId , uint256 destinationChainId );
56-
57- // Errors
58- error UserNotInSnapshot ();
59- error InvalidSnapshot ();
60-
61- constructor (address admin ) {
62- _grantRole (DEFAULT_ADMIN_ROLE, admin);
63- _grantRole (SNAPSHOT_ROLE, admin);
64- }
65-
66- /**
67- * @notice Create a snapshot of reputation scores for given users
68- * @param users Array of user addresses to include in snapshot
69- * @param oracle The reputation oracle to query scores from
70- * @return snapshotId The ID of the created snapshot (timestamp-based)
71- */
72- function createSnapshot (
73- address [] calldata users ,
74- IReputationOracle oracle
75- ) external onlyRole (SNAPSHOT_ROLE) returns (uint256 snapshotId ) {
76- snapshotId = block .timestamp ;
77- uint256 length = users.length ;
78-
79- for (uint256 i = 0 ; i < length; i++ ) {
80- address user = users[i];
81- uint256 score = oracle.getReputationScore (user);
82-
83- snapshots[snapshotId].push (ReputationData ({
84- user: user,
85- score: score,
86- timestamp: block .timestamp ,
87- blockNumber: block .number
88- }));
89- }
90-
91- // Generate Merkle root
92- bytes32 [] memory leaves = new bytes32 [](length);
93- for (uint256 i = 0 ; i < length; i++ ) {
94- leaves[i] = keccak256 (abi.encodePacked (
95- snapshots[snapshotId][i].user,
96- snapshots[snapshotId][i].score,
97- snapshots[snapshotId][i].timestamp
98- ));
99- }
100-
101- snapshotRoots[snapshotId] = _computeMerkleRoot (leaves);
102-
103- emit SnapshotCreated (snapshotId, length, snapshotRoots[snapshotId]);
104- }
105-
106- /**
107- * @notice Get Merkle proof for a user's reputation in a snapshot
108- * @param snapshotId The snapshot ID
109- * @param user The user address
110- * @return proof The Merkle proof
111- * @return index The index of the user in the snapshot
112- */
113- function getMerkleProof (
114- uint256 snapshotId ,
115- address user
116- ) external view returns (bytes32 [] memory proof , uint256 index ) {
117- if (snapshotRoots[snapshotId] == bytes32 (0 )) revert InvalidSnapshot ();
118-
119- ReputationData[] storage data = snapshots[snapshotId];
120- uint256 length = data.length ;
121-
122- for (uint256 i = 0 ; i < length; i++ ) {
123- if (data[i].user == user) {
124- return (_generateProof (snapshotId, i), i);
125- }
126- }
127- revert UserNotInSnapshot ();
128- }
129-
130- /**
131- * @notice Get reputation data for a user in a snapshot
132- * @param snapshotId The snapshot ID
133- * @param user The user address
134- * @return data The reputation data
135- */
136- function getSnapshotData (
137- uint256 snapshotId ,
138- address user
139- ) external view returns (ReputationData memory data ) {
140- ReputationData[] storage snapshotData = snapshots[snapshotId];
141- uint256 length = snapshotData.length ;
142-
143- for (uint256 i = 0 ; i < length; i++ ) {
144- if (snapshotData[i].user == user) {
145- return snapshotData[i];
146- }
147- }
148- revert UserNotInSnapshot ();
149- }
150-
151- /**
152- * @notice Get the number of users in a snapshot
153- * @param snapshotId The snapshot ID
154- * @return The number of users
155- */
156- function getSnapshotLength (uint256 snapshotId ) external view returns (uint256 ) {
157- return snapshots[snapshotId].length ;
158- }
159-
160- // ============ Internal Functions ============
161-
162- /**
163- * @dev Compute Merkle root from leaves
164- * @param leaves Array of leaf hashes
165- * @return The Merkle root
166- */
167- function _computeMerkleRoot (bytes32 [] memory leaves ) internal pure returns (bytes32 ) {
168- uint256 length = leaves.length ;
169- if (length == 0 ) return bytes32 (0 );
170- if (length == 1 ) return leaves[0 ];
171-
172- // Create a working copy that we can modify
173- bytes32 [] memory currentLevel = new bytes32 [](length);
174- for (uint256 i = 0 ; i < length; i++ ) {
175- currentLevel[i] = leaves[i];
176- }
177-
178- uint256 currentLength = length;
179-
180- while (currentLength > 1 ) {
181- // If odd number of nodes, duplicate the last one
182- if (currentLength % 2 != 0 ) {
183- // Create a new level with one extra slot for the duplicate
184- bytes32 [] memory nextLevel = new bytes32 []((currentLength + 1 ) / 2 );
185-
186- // Process pairs
187- for (uint256 i = 0 ; i < currentLength; i += 2 ) {
188- bytes32 left = currentLevel[i];
189- bytes32 right = (i + 1 < currentLength) ? currentLevel[i + 1 ] : currentLevel[i]; // Duplicate last if odd
190- nextLevel[i / 2 ] = keccak256 (abi.encodePacked (left, right));
191- }
192-
193- currentLevel = nextLevel;
194- currentLength = (currentLength + 1 ) / 2 ;
195- } else {
196- // Even number of nodes
197- bytes32 [] memory nextLevel = new bytes32 [](currentLength / 2 );
198-
199- for (uint256 i = 0 ; i < currentLength; i += 2 ) {
200- nextLevel[i / 2 ] = keccak256 (abi.encodePacked (currentLevel[i], currentLevel[i + 1 ]));
201- }
202-
203- currentLevel = nextLevel;
204- currentLength = currentLength / 2 ;
205- }
206- }
207-
208- return currentLevel[0 ];
209- }
210-
211- /**
212- * @dev Generate Merkle proof for a leaf at given index
213- * @param snapshotId The snapshot ID
214- * @param index The index of the leaf
215- * @return proof Array of proof hashes
216- */
217- function _generateProof (
218- uint256 snapshotId ,
219- uint256 index
220- ) internal view returns (bytes32 [] memory proof ) {
221- ReputationData[] storage data = snapshots[snapshotId];
222- uint256 length = data.length ;
223-
224- // Calculate number of levels needed
225- uint256 levels = 0 ;
226- uint256 tempLength = length;
227- while (tempLength > 1 ) {
228- if (tempLength % 2 != 0 ) tempLength++ ;
229- tempLength /= 2 ;
230- levels++ ;
231- }
232-
233- proof = new bytes32 [](levels);
234-
235- uint256 currentIndex = index;
236- uint256 currentLength = length;
237-
238- for (uint256 level = 0 ; level < levels; level++ ) {
239- uint256 pairIndex;
240- if (currentIndex % 2 == 0 ) {
241- pairIndex = currentIndex + 1 ;
242- } else {
243- pairIndex = currentIndex - 1 ;
244- }
245-
246- if (pairIndex < currentLength) {
247- // Get the hash from the current level
248- bytes32 hash = keccak256 (abi.encodePacked (
249- data[pairIndex].user,
250- data[pairIndex].score,
251- data[pairIndex].timestamp
252- ));
253- proof[level] = hash;
254- } else {
255- // If no pair exists, use the same hash (duplicate)
256- bytes32 hash = keccak256 (abi.encodePacked (
257- data[currentIndex].user,
258- data[currentIndex].score,
259- data[currentIndex].timestamp
260- ));
261- proof[level] = hash;
262- }
263-
264- currentIndex /= 2 ;
265- if (currentLength % 2 != 0 ) currentLength++ ;
266- currentLength /= 2 ;
267- }
268-
269- return proof;
270- }
271- }
0 commit comments