// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

import "@openzeppelin/contracts/access/Ownable.sol";

/**
 * @title ChainalysisOracleWrapper — read-only proxy to Chainalysis's free
 *                                    on-chain sanctions oracle
 * @notice Chainalysis publishes a free, public sanctions oracle on Ethereum
 *         mainnet (and other major chains) exposing `isSanctioned(address)`.
 *         AERE wraps that oracle on Chain 2800 by holding the deployed
 *         address as an immutable, providing the same interface, and
 *         letting AERE dApps + institutional integrators query a SINGLE
 *         endpoint without having to know the upstream provider's address.
 *
 *         This contract makes a forwarding STATICCALL to the upstream
 *         oracle. If the upstream rejects (e.g. paused, migrated, doesn't
 *         exist on AERE yet), this contract returns false rather than
 *         reverting — failing closed but not bricking the dApp.
 *
 * @dev    Phase 1 (Q3 2026 ship):
 *           - UPSTREAM_ORACLE is settable ONCE by Foundation owner via
 *             setUpstream() and immutable after `lockUpstream()`.
 *           - Foundation MUST call lockUpstream() before declaring the
 *             registry "production-ready". Pre-lock, the contract is
 *             considered configurable.
 *
 *         Phase 2 (Q1 2027):
 *           - Multiple upstream oracles polled, response aggregated via
 *             "ANY-true" rule (sanctioned by any source ⇒ sanctioned).
 *           - Per-source attestation events for transparency.
 *
 *         FORWARD INTERFACE: bool isSanctioned(address)
 *         (matches Chainalysis canonical SanctionsList contract)
 */
interface IChainalysisUpstream {
    function isSanctioned(address account) external view returns (bool);
}

contract ChainalysisOracleWrapper is Ownable {

    address public upstreamOracle;
    bool public locked;

    event UpstreamSet(address oldOracle, address newOracle);
    event UpstreamLocked(address finalOracle);

    error AlreadyLocked();
    error ZeroAddress();

    function setUpstream(address newOracle) external onlyOwner {
        if (locked) revert AlreadyLocked();
        if (newOracle == address(0)) revert ZeroAddress();
        emit UpstreamSet(upstreamOracle, newOracle);
        upstreamOracle = newOracle;
    }

    function lockUpstream() external onlyOwner {
        if (locked) revert AlreadyLocked();
        if (upstreamOracle == address(0)) revert ZeroAddress();
        locked = true;
        emit UpstreamLocked(upstreamOracle);
    }

    /// @notice Returns true if the upstream Chainalysis oracle reports
    ///         `account` as sanctioned. Returns false on any upstream
    ///         error (failing closed but never reverting the consumer).
    function isSanctioned(address account) external view returns (bool sanctioned) {
        address up = upstreamOracle;
        if (up == address(0)) return false;
        try IChainalysisUpstream(up).isSanctioned(account) returns (bool s) {
            return s;
        } catch {
            return false;
        }
    }
}
