ETH Price: $1,963.83 (+1.34%)

Contract Diff Checker

Contract Name:
CurvePoolRegistry

Contract Source Code:

<i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>

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

import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";

/**
 *⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
 *⠀⠀⠀⠀⠈⢻⣿⠛⠻⢷⣄⠀⠀ ⣴⡟⠛⠛⣷⠀ ⠘⣿⡿⠛⠛⢿⡇⠀⠀⠀⠀
 *⠀⠀⠀⠀⠀⢸⣿⠀⠀ ⠈⣿⡄⠀⠿⣧⣄⡀ ⠉⠀⠀ ⣿⣧⣀⣀⡀⠀⠀⠀⠀⠀
 *⠀⠀⠀⠀⠀⢸⣿⠀⠀ ⢀⣿⠃ ⣀ ⠈⠉⠻⣷⡄⠀ ⣿⡟⠉⠉⠁⠀⠀⠀⠀⠀
 *⠀⠀⠀⠀⢠⣼⣿⣤⣴⠿⠋⠀ ⠀⢿⣦⣤⣴⡿⠁ ⢠⣿⣷⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
 *
 *      - Defining Successful Future -
 *
 * @title CurvePoolRegistry
 * @author Andrei Averin — CTO dsf.finance
 * @notice Centralized, owner-controlled registry for whitelisting safe and verified Curve pools.
 * @dev
 * - Intended to be consumed by aggregator modules (e.g., CurveDexModule).
 * - Stores:
 *    (1) Per-(tokenA, tokenB) allowlist of pools (strict routing allowlist).
 *    (2) Minimal per-pool call profile for `exchange(...)` (non-underlying branch).
 * - Pair allowlist is keyed by `keccak256(sorted(tokenA, tokenB))`.
 * - Admin (owner) can atomically set/clear allowlists and update per-pool profiles.
 * - The profile exposes three booleans:
 *      `exIndexUint`             : true => `exchange(uint256,uint256,...)`, false => `exchange(int128,int128,...)`
 *      `exHasEthFlag`            : true => non-underlying `exchange(..., bool use_eth)` (5-arg/6-arg forms)
 *      `exHasReceiver`           : true => presence of receiver overloads `exchange(..., address)` / `exchange(..., bool, address)`
 *      `exHasReceiverUnderlying` : true => underlying receiver overloads exist
 * - The registry also provides view-only probe helpers to heuristically detect a profile off-chain.
 */
 
/**
 * @title IPoolRegistry
 * @notice Interface for accessing whitelisted pools and their call profiles.
 */
interface IPoolRegistry {
    /**
     * @notice Minimal call profile for non-underlying `exchange(...)`.
     * @dev
     * - `exIndexUint`             selects indices type: uint256 vs int128.
     * - `exHasEthFlag`            indicates existence of a trailing `bool use_eth` argument in non-underlying exchange.
     * - `exHasReceiver`           indicates presence of receiver overloads (`..., address)` or `(..., bool, address)`.
     * - `exHasReceiverUnderlying` covers `exchange_underlying(..., address)`/`(..., bool, address)` presence.
     */
    struct PoolProfile {
        // exchange signatures (non-underlying branch)
        bool exIndexUint;             // true: exchange(uint256,uint256,...) ; false: exchange(int128,int128,...)
        bool exHasEthFlag;            // true: non-underlying exchange has trailing `bool use_eth`
        bool exHasReceiver;           // true: non-underlying exchange has overloads with `address receiver`
        bool exHasReceiverUnderlying; // underlying: has receiver overloads
    }

    /**
     * @notice Get allowlisted pools for a (tokenA, tokenB) pair.
     * @param  tokenA First token.
     * @param  tokenB Second token.
     * @return Array of allowlisted pool addresses under the canonical pair key.
     */
    function getVerifiedPools(address tokenA, address tokenB) external view returns (address[] memory);
    
    /**
     * @notice Get a saved profile for a pool.
     * @param  pool    Pool address.
     * @return exists  True if a profile is set in the registry.
     * @return profile The stored PoolProfile ({exIndexUint, exHasEthFlag, exHasReceiver}).
     */
    function getPoolProfile(address pool) external view returns (bool exists, PoolProfile memory profile);

    /**
     * @notice Returns stored profile flags for a pool as separate booleans (gas-cheaper in some cases).
     * @param  pool                    Pool address.
     * @return exists                  True if an explicit profile exists.
     * @return exIndexUint             See `PoolProfile.exIndexUint`.
     * @return exHasEthFlag            See `PoolProfile.exHasEthFlag`.
     * @return exHasReceiver           See `PoolProfile.exHasReceiver`.
     * @return exHasReceiverUnderlying See `PoolProfile.exHasReceiverUnderlying`.
     */
    function getPoolProfileFlags(address pool)
        external
        view
        returns (
            bool exists,
            bool exIndexUint,
            bool exHasEthFlag,
            bool exHasReceiver,
            bool exHasReceiverUnderlying
        );
}

/**
 * @title  CurvePoolRegistry
 * @notice Stores and manages the list of officially verified Curve pools for specific token pairs.
 * @dev    Owner is the only actor allowed to mutate storage (allowlists and profiles).
 */
contract CurvePoolRegistry is Ownable, IPoolRegistry {
    // events 
    event VerifiedPoolsSet(bytes32 indexed pairKey, address tokenA, address tokenB, address[] pools);
    event VerifiedPoolsCleared(bytes32 indexed pairKey, address tokenA, address tokenB);
    event PoolProfileSet(
        address indexed pool,
        bool exIndexUint,
        bool exHasEthFlag,
        bool exHasReceiver,
        bool exHasReceiverUnderlying
    );
    event PoolProfileCleared(address indexed pool);
    event DefaultProfileSet(PoolProfile profile);

    // probe constants
    uint256 private constant DX  = 1;
    uint256 private constant MIN = 0;

    // selectors
    // coins(...)
    bytes4 private constant COINS_U256        = bytes4(keccak256("coins(uint256)"));
    bytes4 private constant COINS_I128        = bytes4(keccak256("coins(int128)"));
    // non-underlying exchange base
    bytes4 private constant EX_U256_ETH       = bytes4(keccak256("exchange(uint256,uint256,uint256,uint256,bool)"));
    bytes4 private constant EX_U256           = bytes4(keccak256("exchange(uint256,uint256,uint256,uint256)"));
    bytes4 private constant EX_I128_ETH       = bytes4(keccak256("exchange(int128,int128,uint256,uint256,bool)"));
    bytes4 private constant EX_I128           = bytes4(keccak256("exchange(int128,int128,uint256,uint256)"));
    // non-underlying exchange with receiver
    bytes4 private constant EX_U256_RCV       = bytes4(keccak256("exchange(uint256,uint256,uint256,uint256,address)"));
    bytes4 private constant EX_U256_ETH_RCV   = bytes4(keccak256("exchange(uint256,uint256,uint256,uint256,bool,address)"));
    bytes4 private constant EX_I128_RCV       = bytes4(keccak256("exchange(int128,int128,uint256,uint256,address)"));


    // key: keccak256(sorted(tokenA, tokenB)) => allowlisted pools
    mapping(bytes32 => address[]) public verifiedPools;

    // per-pool profile
    mapping(address => bool) internal exIndexUintOf;     // true => exchange(uint256,...)
    mapping(address => bool) internal exHasEthFlagOf;    // true => has trailing `bool use_eth`
    mapping(address => bool) internal exHasReceiverOf;   // true => has overloads with `address receiver`
    mapping(address => bool) internal exHasReceiverUnderlyingOf;
    mapping(address => bool) internal hasProfile;

    PoolProfile private _defaultProfile = PoolProfile({
        exIndexUint: true,
        exHasEthFlag: true,
        exHasReceiver: false,
        exHasReceiverUnderlying: false
    });

    constructor() Ownable(msg.sender) {}

    /**
     * @inheritdoc IPoolRegistry
     */
    function getVerifiedPools(address tokenA, address tokenB) 
        external 
        view 
        override 
        returns (address[] memory) 
    {
        return verifiedPools[_pairKey(tokenA, tokenB)];
    }

    /**
     * @inheritdoc IPoolRegistry
     */
    function getPoolProfile(address pool)
        external
        view
        override
        returns (bool exists, PoolProfile memory profile)
    {
        if (hasProfile[pool]) {
            profile = PoolProfile({
                exIndexUint:   exIndexUintOf[pool],
                exHasEthFlag:  exHasEthFlagOf[pool],
                exHasReceiver: exHasReceiverOf[pool],
                exHasReceiverUnderlying:   exHasReceiverUnderlyingOf[pool]
            });
            return (true, profile);
        }
        return (false, _defaultProfile);
    }
    
    /**
     * @inheritdoc IPoolRegistry
     */
    function getPoolProfileFlags(address pool)
        external
        view
        override
        returns (
            bool exists,
            bool exIndexUint,
            bool exHasEthFlag,
            bool exHasReceiver,
            bool exHasReceiverUnderlying
        )
    {
        if (hasProfile[pool]) {
            return (
                true,
                exIndexUintOf[pool],
                exHasEthFlagOf[pool],
                exHasReceiverOf[pool],
                exHasReceiverUnderlyingOf[pool]
            );
        }
        IPoolRegistry.PoolProfile memory p = _defaultProfile;
        return (false, p.exIndexUint, p.exHasEthFlag, p.exHasReceiver, p.exHasReceiverUnderlying);
    }

    
    /**
     * @notice Returns the current default profile that is used when a pool has no explicit profile.
     * @return profile The default `PoolProfile`.
     */
    function getDefaultProfile() external view returns (PoolProfile memory) {
        return _defaultProfile;
    }

    /**
     * @notice Add/replace the allowlist of pools for a (tokenA, tokenB) pair.
     * @dev    Replaces the full list atomically.
     * @param  tokenA First token
     * @param  tokenB Second token
     * @param  pools  New full list of allowlisted pools (non-empty)
     */
    function setVerifiedPools(address tokenA, address tokenB, address[] calldata pools)
        external
        onlyOwner
    {
        require(pools.length > 0, "Registry: empty list");
        bytes32 key = _pairKey(tokenA, tokenB);
        verifiedPools[key] = pools;
        emit VerifiedPoolsSet(key, tokenA, tokenB, pools);
    }
    
    /**
     * @notice Clear the allowlist for a (tokenA, tokenB) pair.
     * @param  tokenA First token
     * @param  tokenB Second token
     */
    function clearVerifiedPools(address tokenA, address tokenB)
        external
        onlyOwner
    {
        bytes32 key = _pairKey(tokenA, tokenB);
        delete verifiedPools[key];
        emit VerifiedPoolsCleared(key, tokenA, tokenB);
    }

    /**
     * @notice Set/update a pool profile (three flags).
     * @param  pool                     Pool address (non-zero)
     * @param  exIndexUint              true => `exchange(uint256,...)`; false => `exchange(int128,...)`
     * @param  exHasEthFlag             true => non-underlying has trailing `bool use_eth`
     * @param  exHasReceiver            true => receiver overloads exist (`..., address)` / `(..., bool, address)`
     * @param  exHasReceiverUnderlying  true => underlying receiver overloads exist
     */
    function setPoolProfile(
        address pool,
        bool exIndexUint,
        bool exHasEthFlag,
        bool exHasReceiver,
        bool exHasReceiverUnderlying
    )
        external
        onlyOwner
    {
        require(pool != address(0), "Registry: zero pool");
        exIndexUintOf[pool]               = exIndexUint;
        exHasEthFlagOf[pool]              = exHasEthFlag;
        exHasReceiverOf[pool]             = exHasReceiver;
        exHasReceiverUnderlyingOf[pool]   = exHasReceiverUnderlying;
        hasProfile[pool]                  = true;
        emit PoolProfileSet(pool, exIndexUint, exHasEthFlag, exHasReceiver, exHasReceiverUnderlying);
    }

    /**
     * @notice Batch set/update pool profiles (arrays must be aligned).
     * @param  pools                    Pool addresses
     * @param  exIndexUint              Flags for indices type per pool
     * @param  exHasEthFlag             Flags for `use_eth` presence per pool
     * @param  exHasReceiver            Flags for receiver overloads per pool
     * @param exHasReceiverUnderlying   Flags for presence of receiver overloads per pool (underlying).
     */
    function setPoolProfiles(
        address[] calldata pools,
        bool[]    calldata exIndexUint,
        bool[]    calldata exHasEthFlag,
        bool[]    calldata exHasReceiver,
        bool[]    calldata exHasReceiverUnderlying
    )
        external
        onlyOwner
    {
        uint256 n = pools.length;
        require(n > 0, "Registry: empty arrays");
        require(
            exIndexUint.length == n &&
            exHasEthFlag.length == n &&
            exHasReceiver.length == n &&
            exHasReceiverUnderlying.length == n,
            "Registry: bad arrays"
        );

        for (uint256 i; i < n; ++i) {
            address pool = pools[i];
            require(pool != address(0), "Registry: zero pool");
            exIndexUintOf[pool]             = exIndexUint[i];
            exHasEthFlagOf[pool]            = exHasEthFlag[i];
            exHasReceiverOf[pool]           = exHasReceiver[i];
            exHasReceiverUnderlyingOf[pool] = exHasReceiverUnderlying[i];
            hasProfile[pool]                = true;
            emit PoolProfileSet(pool, exIndexUint[i], exHasEthFlag[i], exHasReceiver[i], exHasReceiverUnderlying[i]);
        }
    }

    /**
     * @notice Delete a saved profile for a pool.
     * @dev    After deletion, calling modules may fall back to defaults or skip routing.
     * @param  pool Pool address
     */
    function clearPoolProfile(address pool) external onlyOwner {
        delete exIndexUintOf[pool];
        delete exHasEthFlagOf[pool];
        delete exHasReceiverOf[pool];
        delete exHasReceiverUnderlyingOf[pool]; // NEW
        delete hasProfile[pool];
        emit PoolProfileCleared(pool);
    }

    /**
     * @notice Sets the global default profile returned when a pool lacks an explicit profile.
     * @dev    Emits {DefaultProfileSet}. Parameter order matches {setPoolProfile} to reduce misconfiguration risk.
     * @param  exIndexUint                true => `exchange(uint256,...)`; false => `exchange(int128,...)`
     * @param  exHasEthFlag               true => non-underlying has trailing `bool use_eth`
     * @param  exHasReceiver              true => non-underlying receiver overloads exist
     * @param  exHasReceiverUnderlying    true => underlying receiver overloads exist
     */
    function setDefaultProfile(
        bool exIndexUint,
        bool exHasEthFlag,
        bool exHasReceiver,
        bool exHasReceiverUnderlying
    ) external onlyOwner {
        _defaultProfile = PoolProfile({
            exIndexUint: exIndexUint,
            exHasEthFlag: exHasEthFlag,
            exHasReceiver: exHasReceiver,
            exHasReceiverUnderlying: exHasReceiverUnderlying
        });
        emit DefaultProfileSet(_defaultProfile);
    }

    /**
     * @notice Directional pair key (order-dependent).
     * @dev    tokenIn -> tokenOut matters:
     *         key(a,b) != key(b,a)
     * @param  a    Token address
     * @param  b    Token address
     * @return Pair key `keccak256(a, b)`
     */
    function _pairKey(address a, address b) internal pure returns (bytes32) {
        return keccak256(abi.encodePacked(a, b));
    }
}

<i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)

pragma solidity ^0.8.20;

import {Context} from "../utils/Context.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * The initial owner is set to the address provided by the deployer. This can
 * later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable is Context {
    address private _owner;

    /**
     * @dev The caller account is not authorized to perform an operation.
     */
    error OwnableUnauthorizedAccount(address account);

    /**
     * @dev The owner is not a valid owner account. (eg. `address(0)`)
     */
    error OwnableInvalidOwner(address owner);

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the address provided by the deployer as the initial owner.
     */
    constructor(address initialOwner) {
        if (initialOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(initialOwner);
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        if (owner() != _msgSender()) {
            revert OwnableUnauthorizedAccount(_msgSender());
        }
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby disabling any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        if (newOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}

<i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)

pragma solidity ^0.8.20;

/**
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }

    function _contextSuffixLength() internal view virtual returns (uint256) {
        return 0;
    }
}

Please enter a contract address above to load the contract details and source code.

Context size (optional):