📄 BouncerStorage.sol
D-OPEN SOVEREIGN

Documentation & Insights

@dev Directly imports a user account, bypassing all normal validation.
Called exclusively by off-chain migration scripts when redeploying storage.
Handles location code mapping and activeLocations bookkeeping.
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 "./IBouncerStorage.sol";
20import {ErrorLibrary} from "./ErrorLibrary.sol";
21import {Identity} from "./Identity.sol";
22
23contract BouncerStorage is Identity, IBouncerStorage {
24    mapping(address => bool) public _isAuthorized; // contracts allowed to call write methods
25    address public admin; // single admin or multi-sig if you choose
26
27    event AuthorizedAdded(address indexed caller);
28    event AuthorizedRemoved(address indexed caller);
29
30    modifier onlyAdmin() {
31        require(msg.sender == admin, "not admin");
32        _;
33    }
34
35    modifier onlyAuthorized() {
36        require(_isAuthorized[msg.sender], "not authorized");
37        _;
38    }
39
40    modifier only() {
41        require(
42            msg.sender == admin || _isAuthorized[msg.sender],
43            "not admin and not authorized"
44        );
45        _;
46    }
47
48    function isAuthorized(address caller) public view returns (bool) {
49        require(caller != address(0), "zero addr");
50        return _isAuthorized[caller];
51    }
52
53    function addAuthorized(address caller) public onlyAdmin {
54        require(caller != address(0), "zero addr");
55        _isAuthorized[caller] = true;
56        emit AuthorizedAdded(caller);
57    }
58
59    function removeAuthorized(address caller) public onlyAdmin {
60        _isAuthorized[caller] = false;
61        emit AuthorizedRemoved(caller);
62    }
63
64    // Deterministic location counts
65    mapping(bytes32 => uint32) public locationCounts;
66    mapping(bytes32 => uint256) private _locationIndex;
67
68    // Active locations tracking - eliminates the need for loops
69    LocationCount[] public activeLocations; // Array of active location counts
70
71    mapping(address => uint32) private accounts;
72    mapping(uint32 => UserAccount) private _userData;
73
74    // Persistent username-ID linkage (survives unregister)
75    mapping(string => uint32) private _usernameToUserId;
76    mapping(uint32 => string) private _userIdToUsername;
77
78    uint32 private _nbAccounts;
79
80    constructor() Identity() {
81        admin = msg.sender;
82    }
83
84    // Override defineCombinations to satisfy both Identity and IBouncerStorage
85    function defineCombinations(
86        string[] calldata adjectives,
87        string[] calldata verbs
88    ) public override(Identity, IBouncerStorage) onlyAdmin {
89        Identity.defineCombinations(adjectives, verbs);
90    }
91
92
93
94    function _locationKey(string memory country, string memory language) internal pure returns (bytes32) {
95        return keccak256(abi.encodePacked(toLowercase(country), "-", toLowercase(language)));
96    }
97
98    function locationToId(string calldata country, string calldata language) external view returns (uint16) {
99        return uint16(_locationIndex[_locationKey(country, language)]);
100    }
101
102    // ===== Pure helpers replicated for compatibility =====
103    function toLowercase(
104        string memory str
105    ) public pure returns (string memory) {
106        bytes memory strBytes = bytes(str);
107        for (uint256 i = 0; i < strBytes.length; i++) {
108            if (strBytes[i] >= 0x41 && strBytes[i] <= 0x5A) {
109                strBytes[i] = bytes1(uint8(strBytes[i]) + 32);
110            }
111        }
112        return string(strBytes);
113    }
114
115
116
117
118    function getUsernameByAddress(
119        address addr
120    ) public view override(IBouncerStorage, Identity) returns (string memory) {
121        // Delegate to Identity's implementation to avoid storage duplication
122        return Identity.getUsernameByAddress(addr);
123    }
124
125    // ===== Account reads =====
126    function hasAccount(address addr) public view returns (bool ok) {
127        return accounts[addr] > 0;
128    }
129
130    function userIdOf(address addr) public view returns (uint32) {
131        if (!hasAccount(addr)) {
132            revert ErrorLibrary.ErrNoAccount();
133        }
134        return accounts[addr];
135    }
136
137    function userAddr(uint32 userId) public view returns (address addr) {
138        if (userId <= 0 || userId > _nbAccounts) {
139            revert ErrorLibrary.InvalidUserId();
140        }
141        return _userData[userId].addr;
142    }
143
144    function userData(uint32 userId) public view returns (UserAccount memory) {
145        if (userId <= 0 || userId > _nbAccounts) {
146            revert ErrorLibrary.InvalidUserId();
147        }
148        return _userData[userId];
149    }
150
151    function userIdToUsername(
152        uint32 userId
153    ) public view returns (string memory) {
154        if (userId <= 0 || userId > _nbAccounts) {
155            revert ErrorLibrary.InvalidUserId();
156        }
157        return _userIdToUsername[userId];
158    }
159
160    function usernameToUserId(
161        string calldata username
162    ) public view returns (uint32) {
163        return _usernameToUserId[username]; // 0 if not found
164    }
165
166    function nbAccounts() public view returns (uint32) {
167        return _nbAccounts;
168    }
169
170    function userInfos(
171        address addr
172    )
173        public
174        view
175        returns (string memory username, string memory country, string memory language, uint32 newUserId)
176    {
177        if (!hasAccount(addr)) {
178            revert ErrorLibrary.ErrNoAccount();
179        }
180        newUserId = accounts[addr];
181        username = _userData[newUserId].username;
182        country = _userData[newUserId].country;
183        language = _userData[newUserId].language;
184    }
185
186    // ===== Location reads =====
187
188
189
190    function getAccountCountByCountryLanguage(
191        string calldata country,
192        string calldata language
193    ) public view returns (uint32 count) {
194        bytes32 key = _locationKey(country, language);
195        return locationCounts[key];
196    }
197
198    function getAllLocationCounts()
199        public
200        view
201        returns (LocationCount[] memory activeLocationCounts)
202    {
203        return activeLocations;
204    }
205
206    function getActiveLocationCount() public view returns (uint256 count) {
207        return activeLocations.length;
208    }
209
210    // ===== Internal helpers =====
211    function _addLocationToActive(
212        bytes32 key,
213        string memory country,
214        string memory language
215    ) internal {
216        activeLocations.push(
217            LocationCount({
218                country: country,
219                language: language,
220                count: 1
221            })
222        );
223        _locationIndex[key] = activeLocations.length; // 1-based index (0 means not found)
224    }
225
226    function _removeLocationFromActive(bytes32 key) internal {
227        uint256 indexToRemove = _locationIndex[key] - 1; // back to 0-based
228        uint256 lastIndex = activeLocations.length - 1;
229        if (indexToRemove != lastIndex) {
230            LocationCount memory lastLocation = activeLocations[lastIndex];
231            activeLocations[indexToRemove] = lastLocation;
232            bytes32 lastKey = _locationKey(lastLocation.country, lastLocation.language);
233            _locationIndex[lastKey] = indexToRemove + 1; // Update index to 1-based
234        }
235        activeLocations.pop();
236        delete _locationIndex[key];
237    }
238
239    // ===== Account lifecycle =====
240    function newAccount(
241        address caller,
242        string calldata country,
243        string calldata language,
244        uint32 referredBy
245    )
246        external
247        only
248        returns (string memory username, string memory countryStr, string memory languageStr, uint32 newUserId)
249    {
250        if (hasAccount(caller)) {
251            revert ErrorLibrary.ErrAlreadyRegistered();
252        }
253        _nbAccounts++;
254        newUserId = _nbAccounts;
255
256        username = _generateUsername(newUserId);
257        linkUsernameToAddress(caller, username);
258
259        accounts[caller] = newUserId;
260
261        _userData[newUserId] = UserAccount({
262            id: newUserId,
263            addr: caller,
264            username: username,
265            language: language,
266            country: country,
267            registeredAt: uint64(block.timestamp),
268            referredBy: referredBy
269        });
270
271        if (_usernameToUserId[username] == 0) {
272            _usernameToUserId[username] = newUserId;
273            _userIdToUsername[newUserId] = username;
274        }
275
276        bytes32 key = _locationKey(country, language);
277        locationCounts[key]++;
278
279        uint256 sentinel = _locationIndex[key];
280        if (sentinel == 0) {
281            _addLocationToActive(key, country, language);
282        } else {
283            activeLocations[sentinel - 1].count++;
284        }
285
286        return (username, country, language, newUserId);
287    }
288
289    function unregister(address caller) external override onlyAuthorized {
290        if (!hasAccount(caller)) {
291            revert ErrorLibrary.ErrNoAccount();
292        }
293
294        uint32 index = accounts[caller];
295        string memory country = _userData[index].country;
296        string memory language = _userData[index].language;
297        bytes32 key = _locationKey(country, language);
298
299        // Get username before clearing data to unlink from Identity mappings
300        string memory username = _userData[index].username;
301
302        // Clear Identity mappings (from inherited Identity contract)
303        usernames[username] = address(0);
304        usernamesInverted[caller] = "";
305
306        accounts[caller] = 0;
307
308        uint32 cnt = locationCounts[key];
309        if (cnt > 0) {
310            unchecked {
311                locationCounts[key] = cnt - 1;
312            }
313            uint256 sentinel = _locationIndex[key];
314            if (sentinel > 0) {
315                uint32 newCount = activeLocations[sentinel - 1].count - 1;
316                activeLocations[sentinel - 1].count = newCount;
317                if (newCount == 0) {
318                    _removeLocationFromActive(key);
319                }
320            }
321        }
322    }
323
324    // ===== Admin bulk-import (migration scripts only) =====
325
326    /**
327     * @dev Directly imports a user account, bypassing all normal validation.
328     * Called exclusively by off-chain migration scripts when redeploying storage.
329     * Handles location code mapping and activeLocations bookkeeping.
330     */
331    function importAccount(
332        uint32 userId,
333        address addr,
334        string calldata username,
335        string calldata country,
336        string calldata language,
337        uint64 registeredAt,
338        uint32 referredBy
339    ) external onlyAdmin {
340        require(addr != address(0), "zero addr");
341        require(userId > 0, "invalid userId");
342
343        // Account address → userId
344        accounts[addr] = userId;
345
346        // User data
347        _userData[userId] = UserAccount({
348            id: userId,
349            addr: addr,
350            username: username,
351            language: language,
352            country: country,
353            registeredAt: registeredAt,
354            referredBy: referredBy
355        });
356
357        // Username linkage (persistent)
358        if (_usernameToUserId[username] == 0) {
359            _usernameToUserId[username] = userId;
360            _userIdToUsername[userId] = username;
361        }
362
363        // Identity mappings (inherited)
364        linkUsernameToAddress(addr, username);
365
366        // Location counts
367        bytes32 key = _locationKey(country, language);
368        locationCounts[key]++;
369
370        uint256 sentinel = _locationIndex[key];
371        if (sentinel == 0) {
372            _addLocationToActive(key, country, language);
373        } else {
374            activeLocations[sentinel - 1].count++;
375        }
376    }
377
378    /// @dev Sets _nbAccounts directly. Call after all importAccount() calls.
379    function setNbAccounts(uint32 count) external onlyAdmin {
380        _nbAccounts = count;
381    }
382
383
384}
385