Description:
Multi-signature wallet contract requiring multiple confirmations for transaction execution.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"contracts/swap/CurvePoolRegistry.sol": {
"content": "// SPDX-License-Identifier: MIT\r
pragma solidity ^0.8.30;\r
\r
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";\r
\r
/**\r
*⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀\r
*⠀⠀⠀⠀⠈⢻⣿⠛⠻⢷⣄⠀⠀ ⣴⡟⠛⠛⣷⠀ ⠘⣿⡿⠛⠛⢿⡇⠀⠀⠀⠀\r
*⠀⠀⠀⠀⠀⢸⣿⠀⠀ ⠈⣿⡄⠀⠿⣧⣄⡀ ⠉⠀⠀ ⣿⣧⣀⣀⡀⠀⠀⠀⠀⠀\r
*⠀⠀⠀⠀⠀⢸⣿⠀⠀ ⢀⣿⠃ ⣀ ⠈⠉⠻⣷⡄⠀ ⣿⡟⠉⠉⠁⠀⠀⠀⠀⠀\r
*⠀⠀⠀⠀⢠⣼⣿⣤⣴⠿⠋⠀ ⠀⢿⣦⣤⣴⡿⠁ ⢠⣿⣷⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀\r
*\r
* - Defining Successful Future -\r
*\r
* @title CurvePoolRegistry\r
* @author Andrei Averin — CTO dsf.finance\r
* @notice Centralized, owner-controlled registry for whitelisting safe and verified Curve pools.\r
* @dev\r
* This registry is designed to be consumed by aggregator modules (e.g. CurveDexModule).\r
* It stores:\r
* - a per-(tokenA,tokenB) whitelist of pools (strict routing allowlist), and\r
* - a minimal call profile for each pool describing how to call its exchange method.\r
*\r
* Pair whitelist:\r
* - Pools are stored under a canonical key: keccak256(sorted(tokenA, tokenB)).\r
* - Use {setVerifiedPools} to atomically set the full allowlisted list for a pair.\r
* - Use {clearVerifiedPools} to remove all allowlisted pools for a pair.\r
*\r
* Pool profile:\r
* - Minimal shape : {exIndexUint, exHasEthFlag}.\r
* exIndexUint : true => exchange(uint256,uint256,...) ; false => exchange(int128,int128,...)\r
* exHasEthFlag : true => non-underlying exchange has a trailing `bool use_eth` flag\r
* - Use {setPoolProfile}/{setPoolProfiles} to set/update profiles.\r
* - Use {clearPoolProfile} to delete a profile (caller modules may fall back to a modern default).\r
*\r
* Access control:\r
* - All mutating methods are restricted to the contract owner.\r
*/\r
\r
/**\r
* @title IPoolRegistry\r
* @notice Interface for accessing whitelisted Curve pools.\r
*/\r
interface IPoolRegistry {\r
struct PoolProfile {\r
// exchange signatures\r
bool exIndexUint; // true: exchange(uint256,uint256,...) ; false: exchange(int128,int128,...)\r
bool exHasEthFlag; // true: у non-underlying exchange есть bool use_eth\r
}\r
\r
function getVerifiedPools(address tokenA, address tokenB) external view returns (address[] memory);\r
function getPoolProfile(address pool) external view returns (bool exists, PoolProfile memory profile);\r
}\r
\r
/**\r
* @title CurvePoolRegistry\r
* @notice Stores and manages the list of officially verified Curve pools for specific token pairs.\r
*/\r
contract CurvePoolRegistry is Ownable, IPoolRegistry {\r
/* ───────────────────────────── Events ───────────────────────────── */\r
\r
event VerifiedPoolsSet(bytes32 indexed pairKey, address[] pools);\r
event VerifiedPoolsCleared(bytes32 indexed pairKey);\r
event PoolAppended(bytes32 indexed pairKey, address pool);\r
event PoolRemoved(bytes32 indexed pairKey, address pool);\r
\r
event PoolProfileSet(address indexed pool, bool exIndexUint, bool exHasEthFlag);\r
event PoolProfileCleared(address indexed pool);\r
\r
/* ──────────────────────────── Selectors ─────────────────────────── */\r
\r
bytes4 private constant COINS_U256 = bytes4(keccak256("coins(uint256)"));\r
bytes4 private constant COINS_I128 = bytes4(keccak256("coins(int128)"));\r
bytes4 private constant EX_U256_ETH = bytes4(keccak256("exchange(uint256,uint256,uint256,uint256,bool)"));\r
bytes4 private constant EX_U256 = bytes4(keccak256("exchange(uint256,uint256,uint256,uint256)"));\r
bytes4 private constant EX_I128_ETH = bytes4(keccak256("exchange(int128,int128,uint256,uint256,bool)"));\r
bytes4 private constant EX_I128 = bytes4(keccak256("exchange(int128,int128,uint256,uint256)"));\r
\r
/* ───────────────────────────── Storage ──────────────────────────── */\r
\r
// key: keccak256(sorted(tokenA, tokenB)) => allowlisted pools\r
mapping(bytes32 => address[]) public verifiedPools;\r
\r
// per-pool profile\r
mapping(address => PoolProfile) private _profiles;\r
mapping(address => bool) private _hasProfile;\r
\r
constructor() Ownable(msg.sender) {}\r
\r
/**\r
* @notice Returns the list of verified Curve pools for a token pair.\r
* @param tokenA The first token in the pair.\r
* @param tokenB The second token in the pair.\r
* @return List of verified pools found.\r
*/\r
function getVerifiedPools(address tokenA, address tokenB) \r
external \r
view \r
override \r
returns (address[] memory) \r
{\r
return verifiedPools[_pairKey(tokenA, tokenB)];\r
}\r
\r
/**\r
* @notice Returns the call profile for a specific pool, if specified.\r
* @dev If the profile is not found, `exists` will be `false` and `profile` will be empty (default).\r
* Use `exists` to check for the presence of a custom profile and select the signature\r
* `exchange(...)` in the module.\r
* @param pool Curve pool address.\r
* @return exists Flag indicating the existence of a saved profile in the registry.\r
* @return profile Structure { exIndexUint, exHasEthFlag } for selecting the correct signature.\r
*/\r
function getPoolProfile(address pool)\r
external\r
view\r
override\r
returns (bool exists, PoolProfile memory profile)\r
{\r
if (_hasProfile[pool]) {\r
return (true, _profiles[pool]);\r
}\r
return (false, profile);\r
}\r
\r
/**\r
* @notice Flat version of the profile: separately, without tuple\r
* @return exists Is there a saved profile\r
* @return exIndexUint true => exchange(uint256,...), false => exchange(int128,...)\r
* @return exHasEthFlag true => non-underlying exchange has trailing bool use_eth\r
*/\r
function getPoolProfileFlat(address pool)\r
external\r
view\r
returns (bool exists, bool exIndexUint, bool exHasEthFlag)\r
{\r
if (_hasProfile[pool]) {\r
IPoolRegistry.PoolProfile memory p = _profiles[pool];\r
return (true, p.exIndexUint, p.exHasEthFlag);\r
}\r
return (false, false, false);\r
}\r
\r
/* ──────────────────────── Mutations (pairs) ─────────────────────── */\r
\r
/**\r
* @notice Adds or replaces the list of whitelisted Curve pools for a token pair.\r
* @param tokenA The first token in the pair.\r
* @param tokenB The second token in the pair.\r
* @param pools The list of Curve pools that are allowed to be used for exchanges.\r
*/\r
function setVerifiedPools(address tokenA, address tokenB, address[] calldata pools)\r
external\r
onlyOwner\r
{\r
require(pools.length > 0, "Registry: empty list");\r
bytes32 key = _pairKey(tokenA, tokenB);\r
\r
for (uint256 i; i < pools.length; ) {\r
require(pools[i] != address(0), "Registry: zero pool");\r
unchecked { ++i; }\r
}\r
verifiedPools[key] = pools;\r
emit VerifiedPoolsSet(key, pools);\r
}\r
\r
/**\r
* @notice Removes all verified pools for a token pair.\r
*/\r
function clearVerifiedPools(address tokenA, address tokenB)\r
external\r
onlyOwner\r
{\r
bytes32 key = _pairKey(tokenA, tokenB);\r
delete verifiedPools[key];\r
emit VerifiedPoolsCleared(key);\r
}\r
\r
/**\r
* @notice Adds one Curve pool to the allowlist for the pair (tokenA, tokenB).\r
* @dev\r
* - The order of addresses in the pair is not important: the canonical key `_pairKey` is used internally.\r
* - The function is idempotent: if the pool is already in the list, we simply exit without making any changes.\r
* - Emits the {PoolAppended} event only when actually added.\r
*\r
* Details/gas:\r
* - Duplicate checking is performed by a linear pass through the array (O(n)).\r
* - The pool address cannot be null.\r
*\r
* @param tokenA The first token in the pair.\r
* @param tokenB The second token in the pair.\r
* @param pool The address of the Curve pool to be added to the allowlist.\r
*/\r
function appendVerifiedPool(address tokenA, address tokenB, address pool)\r
external\r
onlyOwner\r
{\r
require(pool != address(0), "Registry: zero pool");\r
bytes32 key = _pairKey(tokenA, tokenB);\r
address[] storage arr = verifiedPools[key];\r
\r
for (uint256 i; i < arr.length; ) {\r
if (arr[i] == pool) return;\r
unchecked { ++i; }\r
}\r
arr.push(pool);\r
emit PoolAppended(key, pool);\r
}\r
\r
/**\r
* @notice Removes one Curve pool from the allowlist for the pair (tokenA, tokenB).\r
* @dev\r
* - The order of addresses in the pair is not important: the canonical key `_pairKey` is used.\r
* - If the pool is not in the list — no-op (without revert), we simply exit.\r
* - Removal is performed by swap-remove (moving the last element to the place of the removed one) without preserving the order.\r
* - Emits the {PoolRemoved} event only upon actual removal.\r
*\r
* Details/gas:\r
* - Element search is linear (O(n)).\r
* - The order of pools in the array may change after deletion.\r
*\r
* @param tokenA The first token of the pair.\r
* @param tokenB The second token of the pair.\r
* @param pool The address of the Curve pool to be removed from the allowlist.\r
*/\r
function removeVerifiedPool(address tokenA, address tokenB, address pool)\r
external\r
onlyOwner\r
{\r
bytes32 key = _pairKey(tokenA, tokenB);\r
address[] storage arr = verifiedPools[key];\r
uint256 n = arr.length;\r
for (uint256 i; i < n; ) {\r
if (arr[i] == pool) {\r
arr[i] = arr[n - 1];\r
arr.pop();\r
emit PoolRemoved(key, pool);\r
return;\r
}\r
unchecked { ++i; }\r
}\r
}\r
\r
/* ────────────────────── Mutations (profiles) ────────────────────── */\r
\r
/**\r
* @notice Sets/updates the profile for a single pool.\r
* @dev Only available to the owner. Overwrites the existing profile\r
* if it has already been set.\r
* @param pool Curve pool address (cannot be `address(0)`).\r
* @param p Call profile: \r
* - `exIndexUint` : true => exchange(uint256,uint256,...), false => exchange(int128,int128,...)\r
* - `exHasEthFlag` : true => non-underlying exchange has trailing `bool use_eth`\r
*/\r
function setPoolProfile(address pool, PoolProfile calldata p) external onlyOwner {\r
require(pool != address(0), "Registry: zero pool");\r
_profiles[pool] = p;\r
_hasProfile[pool] = true;\r
emit PoolProfileSet(pool, p.exIndexUint, p.exHasEthFlag);\r
}\r
\r
/**\r
* @notice Batch installation/update of profiles for multiple pools.\r
* @dev Only available to the owner. Array lengths must match and be > 0.\r
* Each element `pools[i]` receives profile `ps[i]`. Existing profiles\r
* are overwritten.\r
* @param pools Array of Curve pool addresses.\r
* @param ps Array of profiles for the corresponding pools:\r
* - `exIndexUint` : true => exchange(uint256,uint256,...), false => exchange(int128,int128,...)\r
* - `exHasEthFlag` : true => non-underlying exchange has trailing `bool use_eth`\r
*/\r
function setPoolProfiles(address[] calldata pools, PoolProfile[] calldata ps) external onlyOwner {\r
uint256 len = pools.length;\r
require(len == ps.length && len > 0, "Registry: bad arrays");\r
for (uint256 i; i < len; ) {\r
address pool = pools[i];\r
require(pool != address(0), "Registry: zero pool");\r
_profiles[pool] = ps[i];\r
_hasProfile[pool] = true;\r
emit PoolProfileSet(pool, ps[i].exIndexUint, ps[i].exHasEthFlag);\r
unchecked { ++i; }\r
}\r
}\r
\r
/**\r
* @notice Deletes the saved profile for the pool.\r
* @dev Only available to the owner. After deletion, modules may\r
* switch to the “modern” default or refuse routing —\r
* depending on your logic.\r
* @param pool Address of the Curve pool for which you want to delete the profile.\r
*/\r
function clearPoolProfile(address pool) external onlyOwner {\r
delete _profiles[pool];\r
delete _hasProfile[pool];\r
emit PoolProfileCleared(pool);\r
}\r
\r
/**\r
* @notice Detect Curve pool exchange profile (indices type + presence of `use_eth` flag).\r
* @dev Heuristic:\r
* 1) Check `coins(uint256)` / `coins(int128)` as a hint.\r
* 2) Confirm by probing corresponding `exchange(...)` forms via staticcall.\r
* If both hints are ambiguous, both exchange branches are probed.\r
* Reverts if no known form is supported.\r
* @param pool Curve pool address to probe (must be nonzero).\r
* @return exIndexUint True => pool uses `exchange(uint256,...)`; False => `exchange(int128,...)`.\r
* @return exHasEthFlag True => non-underlying exchange has trailing `bool use_eth` flag.\r
*/\r
function detectProfile(address pool) external view returns (bool exIndexUint, bool exHasEthFlag) {\r
require(pool != address(0), "probe: zero pool");\r
\r
bool coinsU = _supportsCoinsU256(pool);\r
bool coinsI = _supportsCoinsI128(pool);\r
\r
// First, we focus on coins(...)\r
if (coinsU && !coinsI) {\r
(bool hasEth, bool okAny) = _probeExchangeUint(pool);\r
if (okAny) return (true, hasEth);\r
// fallback: suddenly coins were introduced, but the exchange is old — let's check int128\r
(hasEth, okAny) = _probeExchangeI128(pool);\r
if (okAny) return (false, hasEth);\r
} else if (!coinsU && coinsI) {\r
(bool hasEth, bool okAny) = _probeExchangeI128(pool);\r
if (okAny) return (false, hasEth);\r
(hasEth, okAny) = _probeExchangeUint(pool);\r
if (okAny) return (true, hasEth);\r
} else {\r
// Unclear about coins — let's try both exchange branches\r
(bool hasEthU, bool okU) = _probeExchangeUint(pool);\r
if (okU) return (true, hasEthU);\r
(bool hasEthI, bool okI) = _probeExchangeI128(pool);\r
if (okI) return (false, hasEthI);\r
}\r
\r
revert("probe: cannot detect exchange signature");\r
}\r
\r
/* ──────────────────────── Internal helpers ──────────────────────── */\r
\r
/**\r
* @notice Hashes a pair of tokens into a unique key, independent of the order.\r
*/\r
function _pairKey(address a, address b) internal pure returns (bytes32) {\r
return a < b ? keccak256(abi.encodePacked(a, b))\r
: keccak256(abi.encodePacked(b, a));\r
}\r
\r
/**\r
* @notice Quick capability check for `coins(uint256)` on a given pool.\r
* @dev Uses low-level staticcall and returns true if the signature exists (does NOT validate return value).\r
* @param pool Curve pool address to probe.\r
* @return ok True if `coins(uint256)` staticcall did not revert.\r
*/\r
function _supportsCoinsU256(address pool) internal view returns (bool ok) {\r
(ok,) = pool.staticcall(abi.encodeWithSelector(COINS_U256, uint256(0)));\r
}\r
\r
/**\r
* @notice Quick capability check for `coins(int128)` on a given pool.\r
* @dev Uses low-level staticcall and returns true if the signature exists (does NOT validate return value).\r
* @param pool Curve pool address to probe.\r
* @return ok True if `coins(int128)` staticcall did not revert.\r
*/\r
function _supportsCoinsI128(address pool) internal view returns (bool ok) {\r
(ok,) = pool.staticcall(abi.encodeWithSelector(COINS_I128, int128(0)));\r
}\r
\r
/**\r
* @notice Probe non-underlying exchange signatures that use uint256 indices.\r
* @dev Tries 5-arg form with trailing `bool use_eth` first, then classic 4-arg form.\r
* Calls are made with dummy values and checked only for revert/non-revert.\r
* @param pool Curve pool address to probe.\r
* @return hasEthFlag True if pool accepts 5-arg form with `use_eth`.\r
* @return okAny True if at least one uint256-index exchange signature is supported.\r
*/\r
function _probeExchangeUint(address pool) internal view returns (bool hasEthFlag, bool okAny) {\r
{\r
(bool ok,) = pool.staticcall(abi.encodeWithSelector(EX_U256_ETH, uint256(0), uint256(1), uint256(0), uint256(0), false));\r
if (ok) return (true, true);\r
}\r
{\r
(bool ok,) = pool.staticcall(abi.encodeWithSelector(EX_U256, uint256(0), uint256(1), uint256(0), uint256(0)));\r
if (ok) return (false, true);\r
}\r
return (false, false);\r
}\r
\r
/**\r
* @notice Probe non-underlying exchange signatures that use int128 indices.\r
* @dev Tries 5-arg form with trailing `bool use_eth` first, then classic 4-arg form.\r
* Calls are made with dummy values and checked only for revert/non-revert.\r
* @param pool Curve pool address to probe.\r
* @return hasEthFlag True if pool accepts 5-arg form with `use_eth`.\r
* @return okAny True if at least one int128-index exchange signature is supported.\r
*/\r
function _probeExchangeI128(address pool) internal view returns (bool hasEthFlag, bool okAny) {\r
{\r
(bool ok,) = pool.staticcall(abi.encodeWithSelector(EX_I128_ETH, int128(0), int128(1), uint256(0), uint256(0), false));\r
if (ok) return (true, true);\r
}\r
{\r
(bool ok,) = pool.staticcall(abi.encodeWithSelector(EX_I128, int128(0), int128(1), uint256(0), uint256(0)));\r
if (ok) return (false, true);\r
}\r
return (false, false);\r
}\r
}"
},
"@openzeppelin/contracts/access/Ownable.sol": {
"content": "// 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);
}
}
"
},
"@openzeppelin/contracts/utils/Context.sol": {
"content": "// 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;
}
}
"
}
},
"settings": {
"optimizer": {
"enabled": true,
"runs": 200
},
"viaIR": true,
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"devdoc",
"userdoc",
"metadata",
"abi"
]
}
},
"remappings": []
}
}}
Submitted on: 2025-10-31 11:19:33
Comments
Log in to comment.
No comments yet.