📄 BackupStorage.sol
D-OPEN SOVEREIGN
1/*
2 * Copyright (c) 2026 DataPond D-Library Pty Ltd. ("The Sanctuary")
3 * Non-Profit Public Library — The World Library
4 *
5 * D-SAFE Certification issued by POND Enterprise.
6 * Published under the D-Open Code Sovereign Licence v1.0 framework
7 * DataPond D-Library All exclusive Right reserved on modifiying the code - CC Attribution to data pond, Non modifiable, Non Commercial.
8 * https://registry.world.bibliotech.com/licence
9 * Source code donated to DataPond D-Library by it's author: data pond.
10 *
11 * Technical Guardian ("The Shield"): Pond Enterprise Pty Ltd. (ACN 694 747 987)
12 * All liability claims about D-SAFE certification issues coming from the published D-Safe direction must be addressed with Pond Enterprise Pty Ltd.
13 * Code Modification automatically voids the certified liability protection provided by Pond Enterprise.
14 */
15
16// SPDX-License-Identifier: Apache-2.0
17pragma solidity ^0.8.24;
18
19import {IBackup} from "./IBackup.sol";
20import {ErrorLibrary} from "./ErrorLibrary.sol";
21
22contract BackupStorage is IBackup {
23 // Storage
24 mapping(uint32 => uint256[]) private _userData;
25 // UserId => User Level
26 mapping(uint32 => uint8) private _levels;
27 // UserId => Level => creationTime
28 mapping(uint32 => mapping(uint8 => uint256)) private _levelHistory;
29 // userId => Storage Index => string
30 mapping(uint32 => mapping(uint32 => string)) private _stringStorage;
31 // userId => Storage Indexes that contain strings
32 mapping(uint32 => uint256[]) private _stringIndexes;
33 // Access control (ScientistStorage-like)
34 mapping(address => bool) public _isAuthorized;
35
36 address public admin;
37
38 event AuthorizedAdded(address indexed caller);
39 event AuthorizedRemoved(address indexed caller);
40
41 modifier onlyAdmin() {
42 require(msg.sender == admin, "not admin");
43 _;
44 }
45
46 modifier onlyAuthorized() {
47 require(_isAuthorized[msg.sender], "not authorized");
48 _;
49 }
50
51 constructor() {
52 admin = msg.sender;
53 // Optionally: isAuthorized[admin] = true;
54 }
55
56 // Admin API
57 function isAuthorized(address caller) external view returns (bool) {
58 require(caller != address(0), "zero addr");
59 return _isAuthorized[caller];
60 }
61
62 function addAuthorized(address caller) external override onlyAdmin {
63 require(caller != address(0), "zero addr");
64 _isAuthorized[caller] = true;
65 emit AuthorizedAdded(caller);
66 }
67
68 function removeAuthorized(address caller) external override onlyAdmin {
69 _isAuthorized[caller] = false;
70 emit AuthorizedRemoved(caller);
71 }
72
73 // Writes (authorized)
74 function storeBatchActions(
75 uint32 userId,
76 uint256[] calldata patches,
77 uint8 level,
78 uint16[] calldata textIndex,
79 string[] calldata textData
80 ) external override onlyAuthorized {
81 require(textIndex.length == textData.length, "textIndex/textData length mismatch");
82
83 uint256 currentLength = _userData[userId].length;
84 for (uint32 i = 0; i < patches.length; i++) {
85 _userData[userId].push(patches[i]);
86 }
87
88 for (uint16 i = 0; i < textIndex.length; i++) {
89 uint16 relativePatchIndex = textIndex[i];
90 // Fix Bug 2: Use the absolute history index (existing length + relative index within new patches)
91 uint256 absoluteIndex = currentLength + relativePatchIndex;
92 _stringStorage[userId][uint32(absoluteIndex)] = textData[i];
93 _stringIndexes[userId].push(absoluteIndex);
94 }
95
96 _levels[userId] = level;
97 _levelHistory[userId][level] = block.timestamp;
98 }
99
100 function userLevel(uint32 userId) external view override returns (uint8) {
101 if (userId <= 0) {
102 revert ErrorLibrary.InvalidUserId();
103 }
104 if (_levels[userId] == 0) {
105 return 1;
106 }
107 return _levels[userId];
108 }
109
110 // Reads
111 function loadActions(
112 uint32 userId
113 )
114 external
115 view
116 override
117 returns (
118 uint256[] memory compressedActions,
119 uint256[] memory indexes,
120 string[] memory stringIndexes
121 )
122 {
123 uint256[] storage sIndexesStorage = _stringIndexes[userId];
124 uint256[] memory sIndexes = new uint256[](sIndexesStorage.length);
125 string[] memory sData = new string[](sIndexesStorage.length);
126
127 for (uint256 i = 0; i < sIndexesStorage.length; i++) {
128 sIndexes[i] = sIndexesStorage[i];
129 sData[i] = _stringStorage[userId][uint32(sIndexesStorage[i])];
130 }
131
132 return (_userData[userId], sIndexes, sData);
133 }
134
135 function loadActionsSince(
136 uint32 userId,
137 uint16 index
138 )
139 external
140 view
141 override
142 returns (
143 uint256[] memory compressedActions,
144 uint256[] memory indexes,
145 string[] memory stringIndexes
146 )
147 {
148 uint256 totalActions = _userData[userId].length;
149 if (index >= totalActions) {
150 return (new uint256[](0), new uint256[](0), new string[](0));
151 }
152
153 uint256 count = totalActions - index;
154 uint256[] memory actions = new uint256[](count);
155 for (uint256 i = 0; i < count; i++) {
156 actions[i] = _userData[userId][index + i];
157 }
158
159 // Filter metadata that belongs to the "since" range
160 uint256[] storage allSIndexes = _stringIndexes[userId];
161 uint256 sinceCount = 0;
162 for (uint256 i = 0; i < allSIndexes.length; i++) {
163 if (allSIndexes[i] >= index) {
164 sinceCount++;
165 }
166 }
167
168 uint256[] memory sIndexes = new uint256[](sinceCount);
169 string[] memory sData = new string[](sinceCount);
170 uint256 current = 0;
171 for (uint256 i = 0; i < allSIndexes.length; i++) {
172 if (allSIndexes[i] >= index) {
173 sIndexes[current] = allSIndexes[i] - index;
174 sData[current] = _stringStorage[userId][uint32(allSIndexes[i])];
175 current++;
176 }
177 }
178
179 return (actions, sIndexes, sData);
180 }
181
182 function nbWrites(uint32 userId) external view override returns (uint256) {
183 return _userData[userId].length;
184 }
185}
186