📄 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