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;
}
}