APYOracleV3

Description:

Decentralized Finance (DeFi) protocol contract providing Pausable, Swap, Liquidity, Factory, Oracle functionality.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "src/oracles/APYOracleV3.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import {IAPYOracle} from "../interfaces/IAPYOracle.sol";
import {IAavePool} from "../interfaces/external/IAavePool.sol";
import {ICompoundComet} from "../interfaces/external/ICompoundComet.sol";
import {IVesperPool} from "../interfaces/IVesperPool.sol";

/**
 * @title APYOracleV3
 * @notice Fixed APY Oracle with keeper access control and rate limiting
 * @dev Fixes critical vulnerability that allowed APY manipulation via rapid updates
 * 
 * SECURITY FIXES:
 * 1. Keeper-only updates (no public access)
 * 2. Per-protocol rate limiting (minimum 23 hours between updates)
 * 3. Timestamp-based APY calculation (actual time elapsed, not assumed)
 * 4. Guardian emergency pause capability
 */
contract APYOracleV3 is IAPYOracle {
    // ============ State Variables ============
    
    // Ownership and access control
    address public owner;
    address public guardian; // Can pause updates in emergency
    bool public paused;
    
    // Keeper management (NEW - SECURITY FIX)
    mapping(address => bool) public keepers;
    uint256 public keeperCount;
    
    // Per-protocol rate limiting (NEW - SECURITY FIX)
    mapping(address => uint256) public lastProtocolUpdate;
    uint256 public constant MIN_UPDATE_INTERVAL = 23 hours;
    
    // Dynamic adapter registry
    mapping(address => bool) public registeredAdapters;
    mapping(string => address) public adapterByName;
    mapping(address => string) public nameByAdapter;
    mapping(address => bool) public isDirectAPY;
    
    // Protocol vault addresses (mainnet)
    address public constant MORPHO_VAULT = 0xBEEF01735c132Ada46AA9aA4c54623cAA92A64CB;
    address public constant YEARN_VAULT = 0xa354F35829Ae975e850e23e9615b11Da1B3dC4DE;
    address public constant VESPER_VAULT = 0x0C49066C0808Ee8c673553B7cbd99BCC9ABf113d;
    address public constant AAVE_POOL = 0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2;
    address public constant COMPOUND_COMET = 0xc3d688B66703497DAA19211EEdff47f25384cdc3;
    address public constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
    
    // Constants
    uint256 private constant SECONDS_PER_YEAR = 365 days;
    uint256 private constant PRICE_DECIMALS = 1e18;
    uint256 private constant WEEK_SIZE = 7;
    
    // Enhanced price tracking with timestamps (SECURITY FIX)
    struct PricePoint {
        uint256 price;
        uint256 timestamp;
    }

    address[] public allAdapters;
    mapping(address => uint256) private adapterIndex; // for quick removal
    
    mapping(address => PricePoint[7]) public priceHistory;
    mapping(address => uint256) public currentIndex;
    mapping(address => bool) public initialized;
    
    // Manual override system for testing
    mapping(address => uint256) public manualAPY;
    mapping(address => bool) public useManualAPY;
    
    // Adapter to protocol vault mapping
    mapping(address => address) public adapterToVault;
    
    // Events
    event KeeperAdded(address indexed keeper);
    event KeeperRemoved(address indexed keeper);
    event GuardianSet(address indexed guardian);
    event EmergencyPause(address indexed triggeredBy);
    event EmergencyUnpause(address indexed triggeredBy);
    event AdapterRegistered(string indexed name, address indexed adapter, bool isDirectAPY);
    event AdapterUnregistered(string indexed name, address indexed adapter);
    event VaultMappingSet(address indexed adapter, address indexed vault);
    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
    
    // ============ Modifiers ============
    
    modifier onlyOwner() {
        require(msg.sender == owner, "Not owner");
        _;
    }
    
    modifier onlyKeeper() {
        require(keepers[msg.sender] || msg.sender == owner, "Not authorized keeper");
        _;
    }
    
    modifier whenNotPaused() {
        require(!paused, "Oracle paused");
        _;
    }
    
    modifier rateLimited(address protocol) {
        require(
            block.timestamp >= lastProtocolUpdate[protocol] + MIN_UPDATE_INTERVAL,
            "Update too soon - 23hr minimum"
        );
        _;
    }
    
    // ============ Constructor ============
    
    constructor() {
        owner = msg.sender;
        emit APYOracleInitialized(address(this));
    }
    
    // ============ Access Control Functions ============
    
    /**
     * @notice Add a keeper address that can update prices
     * @param keeper The address to add as keeper
     */
    function addKeeper(address keeper) external onlyOwner {
        require(keeper != address(0), "Invalid keeper");
        require(!keepers[keeper], "Already keeper");
        
        keepers[keeper] = true;
        keeperCount++;
        
        emit KeeperAdded(keeper);
    }
    
    /**
     * @notice Remove a keeper address
     * @param keeper The address to remove
     */
    function removeKeeper(address keeper) external onlyOwner {
        require(keepers[keeper], "Not a keeper");
        
        keepers[keeper] = false;
        keeperCount--;
        
        emit KeeperRemoved(keeper);
    }
    
    /**
     * @notice Set guardian for emergency pause
     * @param _guardian The guardian address
     */
    function setGuardian(address _guardian) external onlyOwner {
        guardian = _guardian;
        emit GuardianSet(_guardian);
    }
    
    /**
     * @notice Emergency pause (guardian or owner)
     */
    function emergencyPause() external {
        require(msg.sender == owner || msg.sender == guardian, "Not authorized");
        paused = true;
        emit EmergencyPause(msg.sender);
    }
    
    /**
     * @notice Unpause (owner only)
     */
    function emergencyUnpause() external onlyOwner {
        paused = false;
        emit EmergencyUnpause(msg.sender);
    }
    
    /**
     * @notice Transfer ownership
     * @param newOwner The new owner address
     */
    function transferOwnership(address newOwner) external onlyOwner {
        require(newOwner != address(0), "Invalid owner");
        address oldOwner = owner;
        owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
    
    // ============ Registry Functions ============
    
    function registerAdapter(
        string memory name, 
        address adapter,
        bool _isDirectAPY
    ) external onlyOwner {
        require(adapter != address(0), "Invalid adapter");
        require(bytes(name).length > 0, "Invalid name");
        require(!registeredAdapters[adapter], "Already registered");

        registeredAdapters[adapter] = true;
        adapterByName[name] = adapter;
        nameByAdapter[adapter] = name;
        isDirectAPY[adapter] = _isDirectAPY;

        // add to dynamic list
        adapterIndex[adapter] = allAdapters.length;
        allAdapters.push(adapter);

        emit AdapterRegistered(name, adapter, _isDirectAPY);
    }
    
    function setAdapterVaultMapping(address adapter, address vault) external onlyOwner {
        require(registeredAdapters[adapter], "Adapter not registered");
        require(vault != address(0), "Invalid vault");

        uint256 testPrice = _getCurrentPrice(vault);
        require(testPrice > 0, "APYOracleV3: unsupported vault");

        adapterToVault[adapter] = vault;
        emit VaultMappingSet(adapter, vault);
    }

    
    function unregisterAdapter(string memory name) external onlyOwner {
        address adapter = adapterByName[name];
        require(adapter != address(0), "Adapter not found");

        delete registeredAdapters[adapter];
        delete adapterByName[name];
        delete nameByAdapter[adapter];
        delete isDirectAPY[adapter];
        delete adapterToVault[adapter];

        // remove from dynamic list (swap & pop)
        uint256 idx = adapterIndex[adapter];
        uint256 lastIdx = allAdapters.length - 1;
        if (idx != lastIdx) {
            address lastAdapter = allAdapters[lastIdx];
            allAdapters[idx] = lastAdapter;
            adapterIndex[lastAdapter] = idx;
        }
        allAdapters.pop();
        delete adapterIndex[adapter];

        emit AdapterUnregistered(name, adapter);
    }

    
    // ============ Core Price Update (SECURED) ============
    
    /**
     * @notice Update price for a protocol vault
     * @dev Now restricted to keepers with rate limiting and timestamp tracking
     * @param protocol The protocol vault address to update
     */
    function updateProtocolPrice(address protocol) 
        external 
        onlyKeeper 
        whenNotPaused 
        rateLimited(protocol) 
    {
        require(protocol != address(0), "Invalid protocol");
        
        uint256 price = _getCurrentPrice(protocol);
        require(price != 0, "Invalid price");
        
        // Update with timestamp tracking
        uint256 newIndex = (currentIndex[protocol] + 1) % WEEK_SIZE;
        priceHistory[protocol][newIndex] = PricePoint({
            price: price,
            timestamp: block.timestamp
        });
        currentIndex[protocol] = newIndex;
        
        // Update rate limit tracker
        lastProtocolUpdate[protocol] = block.timestamp;
        
        // Mark as initialized after full week
        if (!initialized[protocol] && _hasFullWeekData(protocol)) {
            initialized[protocol] = true;
            emit ProtocolInitialized(protocol);
        }
        
        emit PriceUpdated(protocol, price, block.timestamp);
    }
    
    /**
     * @notice Batch update prices for efficiency
     * @param protocols Array of protocol addresses to update
     */
    function batchUpdatePrices(address[] calldata protocols) 
        external 
        onlyKeeper 
        whenNotPaused 
    {
        for (uint256 i = 0; i < protocols.length; i++) {
            if (block.timestamp >= lastProtocolUpdate[protocols[i]] + MIN_UPDATE_INTERVAL) {
                _updatePrice(protocols[i]);
            }
        }
    }
    
    function _updatePrice(address protocol) private {
        uint256 price = _getCurrentPrice(protocol);
        if (price == 0) return;
        
        uint256 newIndex = (currentIndex[protocol] + 1) % WEEK_SIZE;
        priceHistory[protocol][newIndex] = PricePoint({
            price: price,
            timestamp: block.timestamp
        });
        currentIndex[protocol] = newIndex;
        lastProtocolUpdate[protocol] = block.timestamp;
        
        if (!initialized[protocol] && _hasFullWeekData(protocol)) {
            initialized[protocol] = true;
            emit ProtocolInitialized(protocol);
        }
        
        emit PriceUpdated(protocol, price, block.timestamp);
    }
    
    // ============ APY Calculation (FIXED WITH TIMESTAMPS) ============
    
    /**
     * @notice Get APY for an adapter
     * @param adapter The adapter address
     * @return apy The current APY in basis points
     */
    function getAPY(address adapter) external view returns (uint256 apy) {
        require(registeredAdapters[adapter], "Adapter not registered");
        
        if (useManualAPY[adapter]) {
            return manualAPY[adapter];
        }
        
        if (isDirectAPY[adapter]) {
            string memory name = nameByAdapter[adapter];
            if (keccak256(bytes(name)) == keccak256(bytes("Aave"))) {
                return getAaveAPY();
            } else if (keccak256(bytes(name)) == keccak256(bytes("Compound"))) {
                return getCompoundAPY();
            }
        }
        
        address vault = adapterToVault[adapter];
        require(vault != address(0), "No vault mapping");
        
        return getHistoricalAPY(vault);
    }
    
    /**
     * @notice Calculate time-weighted APY from historical data
     * @dev FIXED: Now uses actual timestamps instead of assuming 7-day spacing
     * @param protocol The protocol vault address
     * @return apy The calculated APY in basis points
     */
    function getHistoricalAPY(address protocol) public view returns (uint256 apy) {
        if (!initialized[protocol]) return 0;
        
        uint256 current = currentIndex[protocol];
        uint256 weekAgo = (current + 1) % WEEK_SIZE;
        
        PricePoint memory currentPoint = priceHistory[protocol][current];
        PricePoint memory oldPoint = priceHistory[protocol][weekAgo];
        
        // Ensure we have valid data
        if (oldPoint.price == 0 || currentPoint.price <= oldPoint.price) return 0;
        if (oldPoint.timestamp == 0 || currentPoint.timestamp <= oldPoint.timestamp) return 0;
        
        // Calculate actual time elapsed
        uint256 timeElapsed = currentPoint.timestamp - oldPoint.timestamp;
        
        // Require at least 6 days of data to prevent manipulation
        if (timeElapsed < 6 days) return 0;
        
        // Calculate price change
        uint256 priceChange = currentPoint.price - oldPoint.price;
        
        // Calculate return over the period
        uint256 periodReturn = (priceChange * 10000) / oldPoint.price;
        
        // Annualize based on actual time elapsed
        // APY = (periodReturn * SECONDS_PER_YEAR) / timeElapsed
        apy = (periodReturn * SECONDS_PER_YEAR) / timeElapsed;
        
        return apy;
    }
    
    function getAaveAPY() public view returns (uint256 apy) {
        IAavePool pool = IAavePool(AAVE_POOL);
        IAavePool.ReserveData memory data = pool.getReserveData(USDC);
        return data.currentLiquidityRate / 1e23;
    }
    
    function getCompoundAPY() public view returns (uint256 apy) {
        ICompoundComet comet = ICompoundComet(COMPOUND_COMET);
        uint256 utilization = comet.getUtilization();
        uint256 supplyRate = comet.getSupplyRate(utilization);
        return (supplyRate * SECONDS_PER_YEAR) / 1e14;
    }
    
    // ============ Initialization Functions ============
    
    /**
     * @notice Load historical prices (compatibility function)
     * @param protocol The protocol vault address  
     * @param prices Array of 7 historical prices
     */
    function loadHistoricalPrices(address protocol, uint256[7] memory prices) external onlyOwner {
        require(protocol != address(0), "Invalid protocol");
        require(!initialized[protocol], "Already initialized");
        
        uint256 startTime = block.timestamp - 7 days;
        for (uint256 i = 0; i < WEEK_SIZE; i++) {
            require(prices[i] > 0, "Invalid price");
            priceHistory[protocol][i] = PricePoint({
                price: prices[i],
                timestamp: startTime + (i * 1 days)
            });
        }
        
        currentIndex[protocol] = 6;
        lastProtocolUpdate[protocol] = block.timestamp - 1 days;
        initialized[protocol] = true;
        
        emit HistoricalDataLoaded(protocol, prices);
        emit ProtocolInitialized(protocol);
    }
    
    /**
     * @notice Update daily prices (compatibility function)
     */
    function updateDailyPrices() external onlyKeeper whenNotPaused {
        // Update known protocols if rate limit allows
        if (block.timestamp >= lastProtocolUpdate[MORPHO_VAULT] + MIN_UPDATE_INTERVAL) {
            _updatePrice(MORPHO_VAULT);
        }
        if (block.timestamp >= lastProtocolUpdate[YEARN_VAULT] + MIN_UPDATE_INTERVAL) {
            _updatePrice(YEARN_VAULT);
        }
        if (block.timestamp >= lastProtocolUpdate[VESPER_VAULT] + MIN_UPDATE_INTERVAL) {
            _updatePrice(VESPER_VAULT);
        }
    }
    
    /**
     * @notice Manual price update for testing (owner only)
     * @param protocol The protocol vault address
     * @param price The price to set
     */
    function updatePriceManual(address protocol, uint256 price) external onlyOwner {
        require(protocol != address(0), "Invalid protocol");
        require(price != 0, "Invalid price");
        require(!initialized[protocol], "Already initialized");
        
        uint256 idx = currentIndex[protocol];
        priceHistory[protocol][idx] = PricePoint({
            price: price,
            timestamp: block.timestamp
        });
        currentIndex[protocol] = (idx + 1) % WEEK_SIZE;
        
        // Auto-initialize after 7 manual updates
        bool hasFullData = true;
        for (uint i = 0; i < WEEK_SIZE; i++) {
            if (priceHistory[protocol][i].price == 0) {
                hasFullData = false;
                break;
            }
        }
        
        if (hasFullData && !initialized[protocol]) {
            initialized[protocol] = true;
            emit ProtocolInitialized(protocol);
        }
        
        emit PriceUpdated(protocol, price, block.timestamp);
    }
    
    /**
     * @notice Load historical prices with timestamps (owner only)
     * @param protocol The protocol vault address
     * @param prices Array of 7 historical prices
     * @param timestamps Array of 7 timestamps
     */
    function loadHistoricalPricesWithTimestamps(
        address protocol,
        uint256[7] memory prices,
        uint256[7] memory timestamps
    ) external onlyOwner {
        require(protocol != address(0), "Invalid protocol");
        require(!initialized[protocol], "Already initialized");
        
        // Validate timestamps are increasing
        for (uint256 i = 1; i < WEEK_SIZE; i++) {
            require(timestamps[i] > timestamps[i-1], "Timestamps must increase");
            require(prices[i] > 0, "Invalid price");
        }
        
        // Load data
        for (uint256 i = 0; i < WEEK_SIZE; i++) {
            priceHistory[protocol][i] = PricePoint({
                price: prices[i],
                timestamp: timestamps[i]
            });
        }
        
        currentIndex[protocol] = 6;
        lastProtocolUpdate[protocol] = timestamps[6];
        initialized[protocol] = true;
        
        emit HistoricalDataLoaded(protocol, prices);
        emit ProtocolInitialized(protocol);
    }
    
    /**
     * @notice Set manual APY override for testing
     * @param adapter The adapter address
     * @param apy The manual APY in basis points
     */
    function setManualAPY(address adapter, uint256 apy) external onlyOwner {
        require(registeredAdapters[adapter], "Adapter not registered");
        require(apy <= 100000, "APY too high");
        
        manualAPY[adapter] = apy;
        useManualAPY[adapter] = true;
        
        emit ManualAPYSet(adapter, apy);
        emit ManualAPYToggled(adapter, true);
    }
    
    function setUseManualAPY(address adapter, bool useManual) external onlyOwner {
        require(registeredAdapters[adapter], "Adapter not registered");
        useManualAPY[adapter] = useManual;
        emit ManualAPYToggled(adapter, useManual);
    }
    
    // ============ View Functions ============
    
    function isInitialized(address protocol) external view returns (bool) {
        return initialized[protocol];
    }
    
    function getLastUpdateTime(address protocol) external view returns (uint256) {
        return lastProtocolUpdate[protocol];
    }

    function getRegisteredAdapters() external view returns (address[] memory) {
        return allAdapters;
    }

    
    function canUpdateProtocol(address protocol) external view returns (bool) {
        return block.timestamp >= lastProtocolUpdate[protocol] + MIN_UPDATE_INTERVAL;
    }
    
    function getPriceHistory(address protocol) external view returns (uint256[7] memory prices) {
        for (uint256 i = 0; i < WEEK_SIZE; i++) {
            prices[i] = priceHistory[protocol][i].price;
        }

        return prices;
    }
    
    function getPriceHistoryWithTimestamps(address protocol) external view returns (
        uint256[7] memory prices,
        uint256[7] memory timestamps
    ) {
        for (uint256 i = 0; i < WEEK_SIZE; i++) {
            prices[i] = priceHistory[protocol][i].price;
            timestamps[i] = priceHistory[protocol][i].timestamp;
        }
    }
    
    // ============ Internal Functions ============
    
    function _getCurrentPrice(address protocol) private view returns (uint256) {
        if (protocol == MORPHO_VAULT) {
            return _getMorphoPrice();
        } else if (protocol == YEARN_VAULT) {
            return _getYearnPrice();
        } else if (protocol == VESPER_VAULT) {
            return _getVesperPrice();
        }
        return 0;
    }
    
    function _getMorphoPrice() private view returns (uint256) {
        IMetaMorpho vault = IMetaMorpho(MORPHO_VAULT);
        uint256 totalAssets = vault.totalAssets();
        uint256 totalSupply = vault.totalSupply();
        
        if (totalSupply == 0) return PRICE_DECIMALS;
        
        return (totalAssets * PRICE_DECIMALS) / totalSupply;
    }
    
    function _getYearnPrice() private view returns (uint256) {
        IYearnVault vault = IYearnVault(YEARN_VAULT);
        uint256 pricePerShare = vault.pricePerShare();
        return pricePerShare * 1e12; // Scale from 6 to 18 decimals
    }
    
    function _getVesperPrice() private view returns (uint256) {
        IVesperPool pool = IVesperPool(VESPER_VAULT);
        uint256 pricePerShare = pool.getPricePerShare();
        return pricePerShare * 1e12; // Scale from 6 to 18 decimals
    }
    
    function _hasFullWeekData(address protocol) private view returns (bool) {
        uint256 oldestTimestamp = priceHistory[protocol][0].timestamp;
        uint256 newestTimestamp = priceHistory[protocol][6].timestamp;
        
        // Require at least 6 days of data
        if (newestTimestamp - oldestTimestamp < 6 days) return false;
        
        // All prices must be non-zero
        for (uint256 i = 0; i < WEEK_SIZE; i++) {
            if (priceHistory[protocol][i].price == 0) return false;
        }
        
        return true;
    }
}

// Minimal interfaces
interface IMetaMorpho {
    function totalAssets() external view returns (uint256);
    function totalSupply() external view returns (uint256);
}

interface IYearnVault {
    function pricePerShare() external view returns (uint256);
}"
    },
    "src/interfaces/IAPYOracle.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

/**
 * @title IAPYOracle
 * @notice Interface for the APY Oracle contract that provides APY data for all adapters
 * @dev Supports both direct protocol queries and historical price-based calculations
 */
interface IAPYOracle {
    // Events
    event APYOracleInitialized(address indexed oracle);
    event PriceUpdated(address indexed protocol, uint256 price, uint256 timestamp);
    event ManualAPYSet(address indexed adapter, uint256 apy);
    event ManualAPYToggled(address indexed adapter, bool useManual);
    event HistoricalDataLoaded(address indexed protocol, uint256[7] prices);
    event ProtocolInitialized(address indexed protocol);

    // Errors
    error InvalidAddress();
    error NotInitialized();
    error AlreadyInitialized();
    error UpdateTooSoon();
    error InvalidPrice();
    error InvalidAPY();
    error InvalidProtocol();

    /**
     * @notice Get the current APY for a given adapter
     * @param adapter The adapter address to get APY for
     * @return apy The current APY in basis points (100 = 1%)
     */
    function getAPY(address adapter) external view returns (uint256 apy);

    /**
     * @notice Get Aave protocol APY directly from the pool
     * @return apy The current Aave supply APY in basis points
     */
    function getAaveAPY() external view returns (uint256 apy);

    /**
     * @notice Get Compound protocol APY directly from the comet
     * @return apy The current Compound supply APY in basis points
     */
    function getCompoundAPY() external view returns (uint256 apy);

    /**
     * @notice Calculate APY from historical price data
     * @param protocol The protocol address to calculate APY for
     * @return apy The calculated APY based on 7-day price history
     */
    function getHistoricalAPY(address protocol) external view returns (uint256 apy);

    /**
     * @notice Update price for a protocol (keeper function)
     * @param protocol The protocol address to update price for
     */
    function updateProtocolPrice(address protocol) external;

    /**
     * @notice Manually update price for testing
     * @param protocol The protocol address
     * @param price The new price to set
     */
    function updatePriceManual(address protocol, uint256 price) external;

    /**
     * @notice Load historical prices for initialization
     * @param protocol The protocol address
     * @param prices Array of 7 historical prices (oldest to newest)
     */
    function loadHistoricalPrices(address protocol, uint256[7] memory prices) external;

    /**
     * @notice Set manual APY override for testing
     * @param adapter The adapter address
     * @param apy The manual APY to set
     */
    function setManualAPY(address adapter, uint256 apy) external;

    /**
     * @notice Toggle manual APY override
     * @param adapter The adapter address
     * @param useManual Whether to use manual APY
     */
    function setUseManualAPY(address adapter, bool useManual) external;

    /**
     * @notice Check if a protocol is initialized with price history
     * @param protocol The protocol address to check
     * @return initialized Whether the protocol has been initialized
     */
    function isInitialized(address protocol) external view returns (bool initialized);

    /**
     * @notice Get the last update timestamp for a protocol
     * @param protocol The protocol address
     * @return timestamp The last update timestamp
     */
    function getLastUpdateTime(address protocol) external view returns (uint256 timestamp);

    /**
     * @notice Get the current price history for a protocol
     * @param protocol The protocol address
     * @return prices The 7-day price history array
     */
    function getPriceHistory(address protocol) external view returns (uint256[7] memory prices);

    /**
     * @notice Update prices for all protocols (keeper batch function)
     */
    function updateDailyPrices() external;
}"
    },
    "src/interfaces/external/IAavePool.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;

interface IAavePool {
    struct ReserveData {
        uint256 configuration;
        uint128 liquidityIndex;
        uint128 currentLiquidityRate;
        uint128 variableBorrowIndex;
        uint128 currentVariableBorrowRate;
        uint128 currentStableBorrowRate;
        uint40 lastUpdateTimestamp;
        uint16 id;
        address aTokenAddress;
        address stableDebtTokenAddress;
        address variableDebtTokenAddress;
        address interestRateStrategyAddress;
        uint128 accruedToTreasury;
        uint128 unbacked;
        uint128 isolationModeTotalDebt;
    }
    
    function supply(address asset, uint256 amount, address onBehalfOf, uint16 referralCode) external;
    function withdraw(address asset, uint256 amount, address to) external returns (uint256);
    function getReserveData(address asset) external view returns (ReserveData memory);
}

interface IAToken {
    function balanceOf(address user) external view returns (uint256);
    function scaledBalanceOf(address user) external view returns (uint256);
    function getScaledUserBalanceAndSupply(address user) external view returns (uint256, uint256);
    function totalSupply() external view returns (uint256);
    function transfer(address to, uint256 amount) external returns (bool);
    function transferFrom(address from, address to, uint256 amount) external returns (bool);
    function approve(address spender, uint256 amount) external returns (bool);
    function allowance(address owner, address spender) external view returns (uint256);
}"
    },
    "src/interfaces/external/ICompoundComet.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;

interface ICompoundComet {
    function supply(address asset, uint256 amount) external;
    function withdraw(address asset, uint256 amount) external;
    function balanceOf(address account) external view returns (uint256);
    function getUtilization() external view returns (uint256);
    function getSupplyRate(uint256 utilization) external view returns (uint256);
    function baseTokenPriceFeed() external view returns (address);
    function numAssets() external view returns (uint8);
    function getAssetInfo(uint8 i) external view returns (AssetInfo memory);
    function totalsBasic() external view returns (TotalsBasic memory);
    
    struct AssetInfo {
        uint8 offset;
        address asset;
        address priceFeed;
        uint64 scale;
        uint64 borrowCollateralFactor;
        uint64 liquidateCollateralFactor;
        uint64 liquidationFactor;
        uint128 supplyCap;
    }
    
    struct TotalsBasic {
        uint64 baseSupplyIndex;
        uint64 baseBorrowIndex;
        uint64 trackingSupplyIndex;
        uint64 trackingBorrowIndex;
        uint104 totalSupplyBase;
        uint104 totalBorrowBase;
        uint40 lastAccrualTime;
        uint8 pauseFlags;
    }
}"
    },
    "src/interfaces/IVesperPool.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;

/**
 * @title IVesperPool
 * @notice Interface for Vesper Finance pool contracts
 * @dev Vesper uses 18 decimals internally for all calculations
 */
interface IVesperPool {
    /**
     * @notice Deposit underlying tokens and receive pool shares
     * @param amount Amount of underlying tokens to deposit
     */
    function deposit(uint256 amount) external;

    /**
     * @notice Withdraw by burning pool shares
     * @param shares Amount of pool shares to burn
     * @dev Note: This takes share amount, not underlying amount
     */
    function withdraw(uint256 shares) external;

    /**
     * @notice Get the current price per share
     * @return Price per share in 18 decimals
     */
    function getPricePerShare() external view returns (uint256);

    /**
     * @notice Get the total value of all assets in the pool
     * @return Total value in underlying token decimals
     */
    function totalValue() external view returns (uint256);

    /**
     * @notice Get the total supply of pool shares
     * @return Total supply of shares
     */
    function totalSupply() external view returns (uint256);

    /**
     * @notice Get the balance of pool shares for an account
     * @param account Address to check balance for
     * @return Balance of pool shares
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @notice Convert underlying amount to 18 decimals
     * @param amount Amount in underlying token decimals
     * @return Amount in 18 decimals
     */
    function convertTo18(uint256 amount) external view returns (uint256);

    /**
     * @notice Convert from 18 decimals to underlying decimals
     * @param amount Amount in 18 decimals
     * @return Amount in underlying token decimals
     */
    function convertFrom18(uint256 amount) external view returns (uint256);

    /**
     * @notice Get the underlying token address
     * @return Address of the underlying token
     */
    function token() external view returns (address);
}"
    }
  },
  "settings": {
    "remappings": [
      "forge-std/=lib/forge-std/src/",
      "@openzeppelin/=lib/openzeppelin-contracts/",
      "@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/",
      "erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/",
      "halmos-cheatcodes/=lib/openzeppelin-contracts/lib/halmos-cheatcodes/src/",
      "openzeppelin-contracts/=lib/openzeppelin-contracts/"
    ],
    "optimizer": {
      "enabled": true,
      "runs": 100
    },
    "metadata": {
      "useLiteralContent": false,
      "bytecodeHash": "ipfs",
      "appendCBOR": true
    },
    "outputSelection": {
      "*": {
        "*": [
          "evm.bytecode",
          "evm.deployedBytecode",
          "devdoc",
          "userdoc",
          "metadata",
          "abi"
        ]
      }
    },
    "evmVersion": "cancun",
    "viaIR": true
  }
}}

Tags:
ERC20, DeFi, Pausable, Swap, Liquidity, Factory, Oracle|addr:0x45c597aa7030f7b822b9f1716837d1fc4e521cff|verified:true|block:23603774|tx:0xfc03f71828e02edad00f2557cd29dca1f495ee9c7ba650c7c37715a04aa3c783|first_check:1760784931

Submitted on: 2025-10-18 12:55:33

Comments

Log in to comment.

No comments yet.