// File: src/WatRegistry.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.16;

import {Auth} from "chronicle-std/auth/Auth.sol";

import {IWatRegistry} from "./IWatRegistry.sol";

/**
 * @title WatRegistry
 *
 * @notice Single source of truth for all of Chronicle Protocol's wat
 *         configurations
 *
 * @dev Note that this registry is built on the assumption that a wat has the
 *      same configuration on each chain it is deployed to with the exception
 *      of wats being deployed on Ethereum which can have less lifted feeds due
 *      to ScribeOptimistic's auto-kicking functionality.
 *
 * @dev Storage use is O(n + m) where n = #wats, m = #deployments
 */
contract WatRegistry is IWatRegistry, Auth {
    struct Config {
        uint8 bar;
        uint feedsBloom;
    }

    /// @dev Ethereum's chain id.
    ///
    ///      Note that this contract is only deployed on Ethereum and respective
    ///      testnets.
    uint internal immutable _CHAINID_ETH;

    /// @dev List of all wats.
    ///
    /// @dev Note that stable ordering is not guaranteed.
    ///
    /// @custom:invariant Does not contain duplicates.
    /// @custom:invariant Does not contain bytes32(0).
    bytes32[] internal _wats;

    /// @dev Mapping of wats to their configurations.
    ///
    /// @custom:invariant If config exists its bar is not zero.
    mapping(bytes32 => Config) internal _configs;

    /// @dev Mapping of wats to the list of chains with a deployment.
    mapping(bytes32 => uint[]) internal _chains;

    /// @dev Mapping of (wats, chain id) to the respective deployment.
    mapping(bytes32 => mapping(uint => address)) internal _deployment;

    // -- Constructor --

    constructor(address initialAuthed) Auth(initialAuthed) {
        _CHAINID_ETH = block.chainid;
    }

    // -- Public Read Functions --

    /// @inheritdoc IWatRegistry
    function wats() external view returns (bytes32[] memory) {
        return _wats;
    }

    /// @inheritdoc IWatRegistry
    function exists(bytes32 wat) public view returns (bool) {
        return _configs[wat].bar != 0
            || _deployment[wat][_CHAINID_ETH] != address(0);
    }

    /// @inheritdoc IWatRegistry
    function config(bytes32 wat) public view returns (uint8, uint) {
        // Revert if wat unknown.
        if (!exists(wat)) {
            revert WatUnknown(wat);
        }

        // Use Ethereum deployment as config's source if possible, otherwise use
        // config stored in _configs storage.
        address src = _deployment[wat][_CHAINID_ETH];
        if (src != address(0)) {
            return (_readBar(src), _readFeedsBloom(src));
        } else {
            Config memory cfg = _configs[wat];

            return (cfg.bar, cfg.feedsBloom);
        }
    }

    /// @inheritdoc IWatRegistry
    function chains(bytes32 wat) public view returns (uint[] memory) {
        // Revert if wat unknown.
        if (!exists(wat)) {
            revert WatUnknown(wat);
        }

        return _chains[wat];
    }

    /// @inheritdoc IWatRegistry
    function deployment(bytes32 wat, uint chainId)
        public
        view
        returns (address)
    {
        // Revert if wat unknown.
        if (!exists(wat)) {
            revert WatUnknown(wat);
        }

        return _deployment[wat][chainId];
    }

    // -- Auth'ed Functionality --

    /// @inheritdoc IWatRegistry
    function embrace(bytes32 wat, address ethDeployment) external auth {
        // Revert if wat invalid.
        require(wat != bytes32(0));

        // Revert if wat exists and ethDeployment does not match.
        if (exists(wat)) {
            require(_deployment[wat][_CHAINID_ETH] == ethDeployment);
            return;
        }

        // Add wat to _wats storage.
        _wats.push(wat);
        emit Embraced(msg.sender, wat);

        // Store Ethereum deployment.
        _chains[wat].push(_CHAINID_ETH);
        _deployment[wat][_CHAINID_ETH] = ethDeployment;
        emit DeploymentUpdated(
            msg.sender, wat, _CHAINID_ETH, address(0), ethDeployment
        );
    }

    /// @inheritdoc IWatRegistry
    function embrace(bytes32 wat, uint8 bar, uint feedsBloom) external auth {
        // Revert if wat invalid.
        require(wat != bytes32(0));

        // Revert if wat exists and configs due not match.
        if (exists(wat)) {
            Config memory cfg = _configs[wat];
            require(cfg.bar == bar);
            require(cfg.feedsBloom == feedsBloom);
            return;
        }

        // Revert if bar is zero.
        require(bar != 0);

        // Add wat to _wats storage.
        _wats.push(wat);
        emit Embraced(msg.sender, wat);

        // Store config.
        _configs[wat] = Config(bar, feedsBloom);
        emit ConfigUpdated(msg.sender, wat, uint8(0), bar, 0, feedsBloom);
    }

    /// @inheritdoc IWatRegistry
    function abandon(bytes32 wat) external auth {
        // Note to be idempotent.
        if (!exists(wat)) {
            return;
        }

        // Delete config.
        delete _configs[wat];

        // Cache chains in memory and delete afterwards.
        uint[] memory chains_ = _chains[wat];
        delete _chains[wat];

        // Delete every deployment.
        for (uint i; i < chains_.length; i++) {
            delete _deployment[wat][chains_[i]];
        }

        // Delete wat.
        uint len = _wats.length;
        for (uint i; i < len; i++) {
            if (_wats[i] == wat) {
                _wats[i] = _wats[len - 1];
                _wats.pop();
                break;
            }
        }

        emit Abandoned(msg.sender, wat);
    }

    /// @inheritdoc IWatRegistry
    function setConfig(bytes32 wat, uint8 bar, uint feedsBloom) external auth {
        require(exists(wat));

        // Revert if bar is zero and no Ethereum deployment exists.
        require(_deployment[wat][_CHAINID_ETH] != address(0) || bar != 0);

        // Load current config from storage.
        Config memory cfg = _configs[wat];
        uint8 oldBar = cfg.bar;
        uint oldFeedsBloom = cfg.feedsBloom;

        if (oldBar != bar || oldFeedsBloom != feedsBloom) {
            _configs[wat] = Config(bar, feedsBloom);

            emit ConfigUpdated(
                msg.sender, wat, oldBar, bar, oldFeedsBloom, feedsBloom
            );
        }
    }

    /// @inheritdoc IWatRegistry
    function setDeployment(bytes32 wat, uint chainId, address deployment_)
        external
        auth
    {
        require(exists(wat));
        require(chainId != 0);

        address oldDeployment = _deployment[wat][chainId];
        if (oldDeployment != deployment_) {
            if (chainId == _CHAINID_ETH) {
                // Revert if Ethereum deployment is being removed while config
                // has bar of zero.
                if (deployment_ == address(0)) {
                    require(_configs[wat].bar != 0);
                }
            }

            // If chain does not exist yet, add it to _chains.
            if (oldDeployment == address(0)) {
                _chains[wat].push(chainId);
            }

            // If deployment is being removed, remove chain from _chains.
            if (deployment_ == address(0)) {
                uint len = _chains[wat].length;
                for (uint i; i < len; i++) {
                    if (_chains[wat][i] == chainId) {
                        _chains[wat][i] = _chains[wat][len - 1];
                        _chains[wat].pop();
                        break;
                    }
                }
            }

            _deployment[wat][chainId] = deployment_;

            emit DeploymentUpdated(
                msg.sender, wat, chainId, oldDeployment, deployment_
            );
        }
    }

    // -- Internal Helpers --

    function _readBar(address src) internal view returns (uint8) {
        return IWatLike(src).bar();
    }

    function _readFeedsBloom(address src) internal view returns (uint) {
        address[] memory feeds = IWatLike(src).feeds();

        uint feedsBloom;
        for (uint i; i < feeds.length; i++) {
            uint8 feedId = uint8(uint(uint160(feeds[i]) >> 152));
            feedsBloom |= (1 << feedId);
        }

        return feedsBloom;
    }
}

interface IWatLike {
    function bar() external view returns (uint8);
    function feeds() external view returns (address[] memory);
}

/**
 * @dev Contract overwrite to deploy contract instances with specific naming.
 *
 *      For more info, see docs/Deployment.md.
 */
contract WatRegistry_2 is WatRegistry {
    constructor(address initialAuthed) WatRegistry(initialAuthed) {}
}

// File: lib/chronicle-std/src/auth/Auth.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.16;

import {IAuth} from "./IAuth.sol";

/**
 * @title Auth Module
 *
 * @dev The `Auth` contract module provides a basic access control mechanism,
 *      where a set of addresses are granted access to protected functions.
 *      These addresses are said to be _auth'ed_.
 *
 *      Initially, the address given as constructor argument is the only address
 *      auth'ed. Through the `rely(address)` and `deny(address)` functions,
 *      auth'ed callers are able to grant/renounce auth to/from addresses.
 *
 *      This module is used through inheritance. It will make available the
 *      modifier `auth`, which can be applied to functions to restrict their
 *      use to only auth'ed callers.
 */
abstract contract Auth is IAuth {
    /// @dev Mapping storing whether address is auth'ed.
    /// @custom:invariant Image of mapping is {0, 1}.
    ///                     ∀x ∊ Address: _wards[x] ∊ {0, 1}
    /// @custom:invariant Only address given as constructor argument is authenticated after deployment.
    ///                     deploy(initialAuthed) → (∀x ∊ Address: _wards[x] == 1 → x == initialAuthed)
    /// @custom:invariant Only functions `rely` and `deny` may mutate the mapping's state.
    ///                     ∀x ∊ Address: preTx(_wards[x]) != postTx(_wards[x])
    ///                                     → (msg.sig == "rely" ∨ msg.sig == "deny")
    /// @custom:invariant Mapping's state may only be mutated by authenticated caller.
    ///                     ∀x ∊ Address: preTx(_wards[x]) != postTx(_wards[x]) → _wards[msg.sender] = 1
    mapping(address => uint) private _wards;

    /// @dev List of addresses possibly being auth'ed.
    /// @dev May contain duplicates.
    /// @dev May contain addresses not being auth'ed anymore.
    /// @custom:invariant Every address being auth'ed once is element of the list.
    ///                     ∀x ∊ Address: authed(x) -> x ∊ _wardsTouched
    address[] private _wardsTouched;

    /// @dev Ensures caller is auth'ed.
    modifier auth() {
        assembly ("memory-safe") {
            // Compute slot of _wards[msg.sender].
            mstore(0x00, caller())
            mstore(0x20, _wards.slot)
            let slot := keccak256(0x00, 0x40)

            // Revert if caller not auth'ed.
            let isAuthed := sload(slot)
            if iszero(isAuthed) {
                // Store selector of `NotAuthorized(address)`.
                mstore(0x00, 0x4a0bfec1)
                // Store msg.sender.
                mstore(0x20, caller())
                // Revert with (offset, size).
                revert(0x1c, 0x24)
            }
        }
        _;
    }

    constructor(address initialAuthed) {
        _wards[initialAuthed] = 1;
        _wardsTouched.push(initialAuthed);

        // Note to use address(0) as caller to indicate address was auth'ed
        // during deployment.
        emit AuthGranted(address(0), initialAuthed);
    }

    /// @inheritdoc IAuth
    function rely(address who) external auth {
        if (_wards[who] == 1) return;

        _wards[who] = 1;
        _wardsTouched.push(who);
        emit AuthGranted(msg.sender, who);
    }

    /// @inheritdoc IAuth
    function deny(address who) external auth {
        if (_wards[who] == 0) return;

        _wards[who] = 0;
        emit AuthRenounced(msg.sender, who);
    }

    /// @inheritdoc IAuth
    function authed(address who) public view returns (bool) {
        return _wards[who] == 1;
    }

    /// @inheritdoc IAuth
    /// @custom:invariant Only contains auth'ed addresses.
    ///                     ∀x ∊ authed(): _wards[x] == 1
    /// @custom:invariant Contains all auth'ed addresses.
    ///                     ∀x ∊ Address: _wards[x] == 1 → x ∊ authed()
    function authed() public view returns (address[] memory) {
        // Initiate array with upper limit length.
        address[] memory wardsList = new address[](_wardsTouched.length);

        // Iterate through all possible auth'ed addresses.
        uint ctr;
        for (uint i; i < wardsList.length; i++) {
            // Add address only if still auth'ed.
            if (_wards[_wardsTouched[i]] == 1) {
                wardsList[ctr++] = _wardsTouched[i];
            }
        }

        // Set length of array to number of auth'ed addresses actually included.
        assembly ("memory-safe") {
            mstore(wardsList, ctr)
        }

        return wardsList;
    }

    /// @inheritdoc IAuth
    function wards(address who) public view returns (uint) {
        return _wards[who];
    }
}

// File: src/IWatRegistry.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.16;

interface IWatRegistry {
    /// @notice Thrown if wat `wat` unknown.
    error WatUnknown(bytes32 wat);

    /// @notice Emitted when wat added to registry.
    /// @param caller The caller's address.
    /// @param wat The wat added.
    event Embraced(address indexed caller, bytes32 indexed wat);

    /// @notice Emitted when wat removed from registry.
    /// @param caller The caller's address.
    /// @param wat The wat removed.
    event Abandoned(address indexed caller, bytes32 indexed wat);

    /// @notice Emitted when a wat's config updated.
    /// @param caller The caller's address.
    /// @param wat The wat updated.
    /// @param oldBar The old bar security parameter.
    /// @param newBar The new bar security parameter.
    /// @param oldFeedsBloom The old feeds encoded via bloom mechanism.
    /// @param newFeedsBloom The new feeds encoded via bloom mechanism.
    event ConfigUpdated(
        address indexed caller,
        bytes32 indexed wat,
        uint8 oldBar,
        uint8 newBar,
        uint oldFeedsBloom,
        uint newFeedsBloom
    );

    /// @notice Emitted when a wat's deployment updated.
    /// @param caller The caller's address.
    /// @param wat The wat updated.
    /// @param chainId The chain id of the deployment.
    /// @param oldDeployment The old deployment address.
    /// @param newDeployment The new deployment address.
    event DeploymentUpdated(
        address indexed caller,
        bytes32 indexed wat,
        uint chainId,
        address oldDeployment,
        address newDeployment
    );

    // -- Public Read Functions --

    /// @notice Returns all of Chronicle Protocol's wat identifiers.
    ///
    /// @return Chronicle Protocol's wat identifiers.
    function wats() external view returns (bytes32[] memory);

    /// @notice Returns whether wat `wat` exists in registry.
    ///
    /// @param wat The wat identifier.
    /// @return Whether wat exists in registry.
    function exists(bytes32 wat) external view returns (bool);

    /// @notice Returns wat `wat`'s configuration.
    ///
    /// @dev Reverts if:
    ///      - Wat unknown
    ///
    /// @dev Config is sourced from the Ethereum deployment if it exists,
    ///      otherwise static config is returned.
    ///
    /// @param wat The wat identifier.
    /// @return The wat's bar security parameter.
    /// @return The wat's lifted feeds encoded via bloom mechanism.
    function config(bytes32 wat) external view returns (uint8, uint);

    /// @notice Returns wat `wat`'s list of chain ids its deployed to.
    ///
    /// @dev Reverts if:
    ///      - Wat unknown
    ///
    /// @param wat The wat identifier.
    /// @return The list of chain ids the wat is deployed to.
    function chains(bytes32 wat) external view returns (uint[] memory);

    /// @notice Returns wat `wat`'s deployment address for chain `chainId`.
    ///
    /// @dev Reverts if:
    ///      - Wat unknown
    ///
    /// @param wat The wat identifier.
    /// @param chainId The chain id.
    /// @return Address of wat's deployment on given chain if it exists, zero
    ///         address otherwise.
    function deployment(bytes32 wat, uint chainId)
        external
        view
        returns (address);

    // -- Auth'ed Functions --

    /// @notice Adds wat `wat` with Ethereum deployment `ethDeployment` to the
    ///         registry.
    ///
    /// @dev Only callable by auth'ed addresses.
    ///
    /// @dev Reverts if:
    ///      - Wat invalid
    ///      - Wat exists already
    ///
    /// @param wat The wat identifier.
    /// @param ethDeployment The wat's deployment on Ethereum.
    function embrace(bytes32 wat, address ethDeployment) external;

    /// @notice Adds wat `wat` with config bar `bar` and feeds encoded via bloom
    ///         mechanism `feedsBloom` to the registry.
    ///
    /// @dev Only callable by auth'ed addresses.
    ///
    /// @dev Reverts if:
    ///      - Wat invalid
    ///      - Wat exists already
    ///      - Bar zero
    ///
    /// @param wat The wat identifier.
    /// @param bar The bar security parameter.
    /// @param feedsBloom The feeds encoded via bloom mechanism.
    function embrace(bytes32 wat, uint8 bar, uint feedsBloom) external;

    /// @notice Removes wat `wat` from the registry.
    ///
    /// @dev Only callable by auth'ed addresses.
    function abandon(bytes32 wat) external;

    /// @notice Updates wat `wat`'s config.
    ///
    /// @dev Only callable by auth'ed addresses.
    ///
    /// @dev Reverts if:
    ///      - Wat unknown
    ///      - Bar zero while no Ethereum deployment exists
    ///
    /// @param wat The wat identifier.
    /// @param bar The bar security parameter.
    /// @param feedsBloom The feeds encoded via bloom mechanism.
    function setConfig(bytes32 wat, uint8 bar, uint feedsBloom) external;

    /// @notice Updates wat `wat`'s deployment for chain `chainId`.
    ///
    /// @dev Only callable by auth'ed addresses.
    ///
    /// @dev Reverts if:
    ///      - Wat unknown
    ///      - Chain id zero
    ///      - Ethereum deployment is set to zero while bar config is zero
    ///
    /// @param wat The wat identifier.
    /// @param chainId The chain id of the deployment.
    /// @param deployment The deployment.
    function setDeployment(bytes32 wat, uint chainId, address deployment)
        external;
}

// File: lib/chronicle-std/src/auth/IAuth.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.16;

interface IAuth {
    /// @notice Thrown by protected function if caller not auth'ed.
    /// @param caller The caller's address.
    error NotAuthorized(address caller);

    /// @notice Emitted when auth granted to address.
    /// @param caller The caller's address.
    /// @param who The address auth got granted to.
    event AuthGranted(address indexed caller, address indexed who);

    /// @notice Emitted when auth renounced from address.
    /// @param caller The caller's address.
    /// @param who The address auth got renounced from.
    event AuthRenounced(address indexed caller, address indexed who);

    /// @notice Grants address `who` auth.
    /// @dev Only callable by auth'ed address.
    /// @param who The address to grant auth.
    function rely(address who) external;

    /// @notice Renounces address `who`'s auth.
    /// @dev Only callable by auth'ed address.
    /// @param who The address to renounce auth.
    function deny(address who) external;

    /// @notice Returns whether address `who` is auth'ed.
    /// @param who The address to check.
    /// @return True if `who` is auth'ed, false otherwise.
    function authed(address who) external view returns (bool);

    /// @notice Returns full list of addresses granted auth.
    /// @dev May contain duplicates.
    /// @return List of addresses granted auth.
    function authed() external view returns (address[] memory);

    /// @notice Returns whether address `who` is auth'ed.
    /// @custom:deprecated Use `authed(address)(bool)` instead.
    /// @param who The address to check.
    /// @return 1 if `who` is auth'ed, 0 otherwise.
    function wards(address who) external view returns (uint);
}