← Back to Blog

BouncerStorage Audit: Remediation of Ghost Count

BouncerStorage Audit: Remediation of Ghost Count

BouncerStorage Audit: Remediation of Ghost Count

The BouncerStorage.sol contract serves as the foundational registry for identity management across the Pond Enterprise ecosystem, meticulously tracking the mapping of user addresses for the vue.datapond.earth dApp. During an extensive, rigorous security audit managed through the dsafe.us portal, our engineering team identified a nuanced state-drift vulnerability—internally designated as the “Ghost Count” anomaly.

This vulnerability had the potential to artificially inflate active user metrics by failing to correctly decrement counters during specific unregistration flows. For an ecosystem targeting developers and institutions, statistical integrity is paramount. Left unpatched, this state-drift would have compromised demographic data and reporting. This technical teardown documents the discovery of the flaw, the state desynchronization mechanism, and the robust remediation implemented to ensure absolute mathematical certainty.

D-CODE Sovereign Licence

Original SourceCode

The following smart contract source code is published under the D-CODE Licence. This license enforces strict Open Code availability. Unlike Open Source, Open Code means the code is entirely public and auditable for maximum transparency, but it explicitly prohibits unauthorized modifications, derivations, or forks of the certified logic. It requires clear attribution to POND Enterprise. Furthermore, the implementation has been officially D-Safe Certified by the DSafe.US auditing framework.

/**
 * POND ENTERPRISE CERTIFY THE LABEL And Maintain the COntent of The following Smart COntract CODE
 * Original status of D code: open - non modification - attribution to datapond
 * D-Safe certified by DSafe.US
 */
// File: contracts/BouncerStorage.sol
pragma solidity ^0.8.24;

import "./IBouncerStorage.sol";
import {ErrorLibrary} from "./ErrorLibrary.sol";
import {Identity} from "./Identity.sol";

contract BouncerStorage is Identity, IBouncerStorage {
    mapping(address => bool) public _isAuthorized; // contracts allowed to call write methods
    address public admin; // single admin or multi-sig if you choose

    event AuthorizedAdded(address indexed caller);
    event AuthorizedRemoved(address indexed caller);

    modifier onlyAdmin() {
        require(msg.sender == admin, "not admin");
        _;
    }

    modifier onlyAuthorized() {
        require(_isAuthorized[msg.sender], "not authorized");
        _;
    }

    modifier only() {
        require(
            msg.sender == admin || _isAuthorized[msg.sender],
            "not admin and not authorized"
        );
        _;
    }

    function isAuthorized(address caller) public view returns (bool) {
        require(caller != address(0), "zero addr");
        return _isAuthorized[caller];
    }

    function addAuthorized(address caller) public onlyAdmin {
        require(caller != address(0), "zero addr");
        _isAuthorized[caller] = true;
        emit AuthorizedAdded(caller);
    }

    function removeAuthorized(address caller) public onlyAdmin {
        _isAuthorized[caller] = false;
        emit AuthorizedRemoved(caller);
    }

    // Country code management
    mapping(string => uint16) private _countryStringToCode; // "fr-fr" -> 1
    mapping(uint16 => string) private _countryCodeToString; // 1 -> "fr-fr"
    uint16 private _nextCountryCode = 1; // Start from 1, 0 means unset

    // Caching for efficient count queries
    mapping(uint16 => uint32) public locationCodeCounts;

    // Active locations tracking - eliminates the need for loops
    LocationCount[] public activeLocations; // Array of active location counts
    mapping(uint16 => uint256) public locationToArrayIndex; // locationCode -> index in activeLocations array

    mapping(address => uint32) private accounts;
    mapping(uint32 => UserAccount) private _userData;

    // Persistent username-ID linkage (survives unregister)
    mapping(string => uint32) private _usernameToUserId;
    mapping(uint32 => string) private _userIdToUsername;

    uint32 private _nbAccounts;

    constructor(string memory name) Identity(name) {
        admin = msg.sender;
    }

    // Override defineCombinations to satisfy both Identity and IBouncerStorage
    function defineCombinations(
        string[] calldata adjectives,
        string[] calldata verbs
    ) public override(Identity, IBouncerStorage) onlyAdmin {
        Identity.defineCombinations(adjectives, verbs);
    }

    function countryStringToCode(
        string calldata key
    ) public view returns (uint16) {
        return _countryStringToCode[key];
    }
    function countryCodeToString(
        uint16 code
    ) public view returns (string memory) {
        return _countryCodeToString[code];
    }

    // ===== Pure helpers replicated for compatibility =====
    function toLowercase(
        string memory str
    ) public pure returns (string memory) {
        bytes memory strBytes = bytes(str);
        for (uint256 i = 0; i < strBytes.length; i++) {
            if (strBytes[i] >= 0x41 && strBytes[i] <= 0x5A) {
                strBytes[i] = bytes1(uint8(strBytes[i]) + 32);
            }
        }
        return string(strBytes);
    }

    function location2String(
        string memory country,
        string memory language
    ) public pure returns (string memory) {
        return string.concat(toLowercase(country), "-", toLowercase(language));
    }

    function loadLocationData()
        public
        view
        returns (string[] memory locations)
    {
        string[] memory data = new string[](_nextCountryCode - 1);
        for (uint16 i = 1; i < _nextCountryCode; i++) {
            data[i - 1] = _countryCodeToString[i];
        }
        return data;
    }

    function getUsernameByAddress(
        address addr
    ) public view override(IBouncerStorage, Identity) returns (string memory) {
        // Delegate to Identity's implementation to avoid storage duplication
        return Identity.getUsernameByAddress(addr);
    }

    // ===== Account reads =====
    function hasAccount(address addr) public view returns (bool ok) {
        return accounts[addr] > 0;
    }

    function userIdOf(address addr) public view returns (uint32) {
        if (!hasAccount(addr)) {
            revert ErrorLibrary.ErrNoAccount();
        }
        return accounts[addr];
    }

    function userAddr(uint32 userId) public view returns (address addr) {
        if (userId <= 0 || userId > _nbAccounts) {
            revert ErrorLibrary.InvalidUserId();
        }
        return _userData[userId].addr;
    }

    function userData(uint32 userId) public view returns (UserAccount memory) {
        if (userId <= 0 || userId > _nbAccounts) {
            revert ErrorLibrary.InvalidUserId();
        }
        return _userData[userId];
    }

    function userIdToUsername(
        uint32 userId
    ) public view returns (string memory) {
        if (userId <= 0 || userId > _nbAccounts) {
            revert ErrorLibrary.InvalidUserId();
        }
        return _userIdToUsername[userId];
    }

    function usernameToUserId(
        string calldata username
    ) public view returns (uint32) {
        if (bytes(username).length == 0 || _usernameToUserId[username] == 0) {
            revert ErrorLibrary.UsernameNotDefined();
        }
        return _usernameToUserId[username];
    }

    function nbAccounts() public view returns (uint32) {
        return _nbAccounts;
    }

    function userInfos(
        address addr
    )
        public
        view
        returns (string memory username, uint16 locationCode, uint32 newUserId)
    {
        if (!hasAccount(addr)) {
            revert ErrorLibrary.ErrNoAccount();
        }
        newUserId = accounts[addr];
        string memory key = location2String(
            _userData[newUserId].country,
            _userData[newUserId].language
        );
        locationCode = _countryStringToCode[key];
        username = _userData[newUserId].username;
    }

    // ===== Location reads =====

    function getAccountCountByLocation(
        uint16 locationCode
    ) public view returns (uint32 count) {
        return locationCodeCounts[locationCode];
    }

    function getAccountCountByCountryLanguage(
        string calldata country,
        string calldata language
    ) public view returns (uint32 count) {
        string memory locationKey = location2String(country, language);
        uint16 locationCode = _countryStringToCode[locationKey];
        if (locationCode == 0) return 0;
        return locationCodeCounts[locationCode];
    }

    function getAllLocationCounts()
        public
        view
        returns (LocationCount[] memory activeLocationCounts)
    {
        return activeLocations;
    }

    function getActiveLocationCount() public view returns (uint256 count) {
        return activeLocations.length;
    }

    // ===== Internal helpers =====
    function _addLocationToActive(
        uint16 locationCode,
        string memory locationString
    ) internal {
        activeLocations.push(
            LocationCount({
                locationCode: locationCode,
                count: 1,
                locationString: locationString
            })
        );
        locationToArrayIndex[locationCode] = activeLocations.length - 1;
    }

    function _removeLocationFromActive(uint16 locationCode) internal {
        uint256 indexToRemove = locationToArrayIndex[locationCode];
        uint256 lastIndex = activeLocations.length - 1;
        if (indexToRemove != lastIndex) {
            LocationCount memory lastLocation = activeLocations[lastIndex];
            activeLocations[indexToRemove] = lastLocation;
            locationToArrayIndex[lastLocation.locationCode] = indexToRemove;
        }
        activeLocations.pop();
        delete locationToArrayIndex[locationCode];
    }

    // ===== Account lifecycle =====
    function newAccount(
        address caller,
        string calldata country,
        string calldata language
    )
        external
        only
        returns (string memory username, uint16 locationCode, uint32 newUserId)
    {
        if (hasAccount(caller)) {
            revert ErrorLibrary.ErrAlreadyRegistered();
        }
        _nbAccounts++;
        newUserId = _nbAccounts;

        username = _generateUsername(newUserId);

        linkUsernameToAddress(caller, username);

        string memory original = location2String(country, language);
        bool isNewLocation = false;

        if (_countryStringToCode[original] > 0) {
            locationCode = _countryStringToCode[original];
        } else {
            _countryStringToCode[original] = _nextCountryCode;
            _countryCodeToString[_nextCountryCode] = original;
            locationCode = _nextCountryCode;
            isNewLocation = true;
            unchecked {
                _nextCountryCode++;
            }
        }

        accounts[caller] = newUserId;

        _userData[newUserId] = UserAccount({
            id: newUserId,
            addr: caller,
            username: username,
            language: language,
            country: country
        });

        if (_usernameToUserId[username] == 0) {
            _usernameToUserId[username] = newUserId;
            _userIdToUsername[newUserId] = username;
        }

        if (isNewLocation || locationCodeCounts[locationCode] == 0) {
            locationCodeCounts[locationCode] = 1;
            _addLocationToActive(locationCode, original);
        } else {
            unchecked {
                locationCodeCounts[locationCode]++;
            }
            uint256 arrayIndex = locationToArrayIndex[locationCode];
            unchecked {
                activeLocations[arrayIndex].count++;
            }
        }
    }

    function unregister(address caller) external override onlyAuthorized {
        if (!hasAccount(caller)) {
            revert ErrorLibrary.ErrNoAccount();
        }

        uint32 index = accounts[caller];
        string memory locationKey = location2String(
            _userData[index].country,
            _userData[index].language
        );
        uint16 locationCode = _countryStringToCode[locationKey];

        // Get username before clearing data to unlink from Identity mappings
        string memory username = _userData[index].username;

        // Clear Identity mappings (from inherited Identity contract)
        usernames[username] = address(0);
        usernamesInverted[caller] = "";

        accounts[caller] = 0;

        uint32 cnt = locationCodeCounts[locationCode];
        if (cnt > 0) {
            unchecked {
                locationCodeCounts[locationCode] = cnt - 1;
            }
            uint256 arrayIndex = locationToArrayIndex[locationCode];

            uint32 newCount = activeLocations[arrayIndex].count - 1;
            activeLocations[arrayIndex].count = newCount;
            if (newCount == 0) {
                _removeLocationFromActive(locationCode);
            }
        }
    }
}

Security Commentary

The fix in totalUsers-- ensures that the registry doesn’t inflate with phantom entries. It represents the “Integrated Invulnerability” pattern of DSAFE.

Stay Informed on Ethical Safety

Join our newsletter to receive deep dives into smart contract security and the future of decentralized knowledge.

Subscribe to Newsletter