@@ -37,6 +37,10 @@ contract WakuRlnV2 is Initializable, OwnableUpgradeable, UUPSUpgradeable, Member
3737 /// @notice The Merkle tree that stores rate commitments of memberships
3838 LazyIMTData public merkleTree;
3939
40+ /// @notice Emitted whenever a new Merkle tree root is stored
41+ /// @param newRoot The newly stored Merkle tree root
42+ event RootStored (uint256 newRoot );
43+
4044 /// @notice Сheck if the idCommitment is valid
4145 /// @param idCommitment The idCommitment of the membership
4246 modifier onlyValidIdCommitment (uint256 idCommitment ) {
@@ -57,6 +61,21 @@ contract WakuRlnV2 is Initializable, OwnableUpgradeable, UUPSUpgradeable, Member
5761 _;
5862 }
5963
64+ // Fixed-size circular buffer for recent roots
65+ uint8 public constant HISTORY_SIZE = 5 ;
66+
67+ /// @notice Fixed-size circular buffer storing the most recent HISTORY_SIZE roots
68+ /// @dev Organized as a ring buffer where rootIndex points to the next write position
69+ uint256 [HISTORY_SIZE] private recentRoots;
70+
71+ /// @notice Index pointing to the next slot to write in the circular buffer
72+ /// @dev Wraps around using modulo HISTORY_SIZE arithmetic
73+ uint8 private rootIndex;
74+
75+ /// @notice Flag indicating if any roots have been stored yet
76+ /// @dev Used to distinguish empty buffer from buffer with zero values
77+ bool private rootsInitialized;
78+
6079 constructor () {
6180 _disableInitializers ();
6281 }
@@ -178,6 +197,51 @@ contract WakuRlnV2 is Initializable, OwnableUpgradeable, UUPSUpgradeable, Member
178197 emit MembershipRegistered (idCommitment, rateLimit, index);
179198 }
180199
200+ /// @notice Adds a new root to the history (called after tree update)
201+ function _storeNewRoot (uint256 newRoot ) internal {
202+ // Initialize buffer on first call
203+ if (! rootsInitialized) {
204+ recentRoots[0 ] = newRoot;
205+ rootIndex = 1 ;
206+ rootsInitialized = true ;
207+ } else {
208+ recentRoots[rootIndex] = newRoot;
209+ rootIndex = (rootIndex + 1 ) % HISTORY_SIZE;
210+ }
211+
212+ emit RootStored (newRoot);
213+ }
214+
215+ /// @notice Returns the list of recent roots, newest first
216+ function getRecentRoots () external view returns (uint256 [HISTORY_SIZE] memory ordered ) {
217+ // Returns empty array if no roots have been stored yet
218+ if (! rootsInitialized) {
219+ return ordered;
220+ }
221+
222+ uint8 index = rootIndex;
223+ for (uint8 i = 0 ; i < HISTORY_SIZE; i++ ) {
224+ // Traverse backwards from most recent
225+ uint8 idx = (index + HISTORY_SIZE - 1 - i) % HISTORY_SIZE;
226+ ordered[i] = recentRoots[idx];
227+ }
228+ }
229+
230+ /// @notice Get the root at a specific position (0 = newest)
231+ function getRootAt (uint8 position ) external view returns (uint256 ) {
232+ require (position < HISTORY_SIZE, "Out of range " );
233+ require (rootsInitialized, "No roots yet " );
234+
235+ uint8 index = (rootIndex + HISTORY_SIZE - 1 - position) % HISTORY_SIZE;
236+ return recentRoots[index];
237+ }
238+
239+ /// @notice Returns the root of the Merkle tree that stores rate commitments of memberships
240+ /// @return The root of the Merkle tree that stores rate commitments of memberships
241+ function root () public view returns (uint256 ) {
242+ return LazyIMT.root (merkleTree, MERKLE_TREE_DEPTH);
243+ }
244+
181245 /// @dev Register a membership (internal function)
182246 /// @param idCommitment The idCommitment of the membership
183247 /// @param rateLimit The rate limit of the membership
@@ -189,12 +253,9 @@ contract WakuRlnV2 is Initializable, OwnableUpgradeable, UUPSUpgradeable, Member
189253 LazyIMT.insert (merkleTree, rateCommitment);
190254 nextFreeIndex += 1 ;
191255 }
192- }
193256
194- /// @notice Returns the root of the Merkle tree that stores rate commitments of memberships
195- /// @return The root of the Merkle tree that stores rate commitments of memberships
196- function root () external view returns (uint256 ) {
197- return LazyIMT.root (merkleTree, MERKLE_TREE_DEPTH);
257+ // ✅ After updating or inserting, capture and store the new root
258+ _storeNewRoot (root ());
198259 }
199260
200261 /// @notice Returns the Merkle proof that a given membership is in the membership set
@@ -247,15 +308,21 @@ contract WakuRlnV2 is Initializable, OwnableUpgradeable, UUPSUpgradeable, Member
247308 // eraseFromMembershipSet == false means lazy erasure.
248309 // Only erase memberships from the memberships array (consume less gas).
249310 // Merkle tree data will be overwritten when the correspondind index is reused.
311+ bool treeModified = false ;
250312 for (uint256 i = 0 ; i < idCommitmentsToErase.length ; i++ ) {
251313 // Erase the membership from the memberships array in contract storage
252314 uint32 indexToErase = _eraseMembershipLazily (_msgSender (), idCommitmentsToErase[i]);
253315 // Optionally, also erase the rate commitment data from the membership set.
254316 // This does not affect the total rate limit control, or index reusal for new membership registrations.
255317 if (eraseFromMembershipSet) {
256318 LazyIMT.update (merkleTree, 0 , indexToErase);
319+ treeModified = true ;
257320 }
258321 }
322+ // Record only the final root once per batch full clean-up call to avoid duplicates.
323+ if (treeModified) {
324+ _storeNewRoot (root ());
325+ }
259326 }
260327
261328 /// @notice Withdraw any available deposit balance in tokens after a membership is erased
0 commit comments