DelevPtSusdeModule

Description:

Proxy contract enabling upgradeable smart contract patterns. Delegates calls to an implementation contract.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

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

interface IGnosisSafe {
    enum Operation {
        Call,
        DelegateCall
    }

    function execTransactionFromModule(
        address to,
        uint256 value,
        bytes calldata data,
        Operation operation
    ) external returns (bool success);

    function enableModule(address module) external;
}

interface IERC20 {
    function balanceOf(address account) external view returns (uint256);
    function approve(address spender, uint256 amount) external returns (bool);
}

interface IERC4626 {
    function convertToAssets(uint256 shares) external view returns (uint256);
    function previewRedeem(uint256 shares) external view returns (uint256);
}

interface IAavePoolV3 {
    function supply(address asset, uint256 amount, address onBehalfOf, uint16 referralCode) external;
    function withdraw(address asset, uint256 amount, address to) external returns (uint256);
    function repay(
        address asset,
        uint256 amount,
        uint256 interestRateMode,
        address onBehalfOf
    ) external returns (uint256);

    function getUserAccountData(
        address user
    )
        external
        view
        returns (
            uint256 totalCollateralBase,
            uint256 totalDebtBase,
            uint256 availableBorrowsBase,
            uint256 currentLiquidationThreshold,
            uint256 ltv,
            uint256 healthFactor
        );
}

interface IAaveAddressesProvider {
    function getPool() external view returns (address);
    function getPoolDataProvider() external view returns (address);
}

interface IAaveDataProvider {
    function getUserReserveData(
        address asset,
        address user
    )
        external
        view
        returns (
            uint256 currentATokenBalance,
            uint256 currentStableDebt,
            uint256 currentVariableDebt,
            uint256 principalStableDebt,
            uint256 scaledVariableDebt,
            uint256 stableBorrowRate,
            uint256 liquidityRate,
            uint40 stableRateLastUpdated,
            bool usageAsCollateralEnabled
        );

    function getReserveTokensAddresses(
        address asset
    ) external view returns (address aTokenAddress, address stableDebtTokenAddress, address variableDebtTokenAddress);
}

interface IPendleMarketV3 {
    function readTokens() external view returns (address SY, address PT, address YT);
}

interface IStandardizedYield {
    function exchangeRate() external view returns (uint256);
}

interface IPendleRouterV4 {
    struct TokenOutput {
        address tokenOut;
        uint256 minTokenOut;
        address tokenRedeemSy;
        address pendleSwap;
        SwapData swapData;
    }

    struct SwapData {
        uint8 swapType;
        address extRouter;
        bytes extCalldata;
        bool needScale;
    }

    function redeemPyToToken(
        address receiver,
        address YT,
        uint256 netPyIn,
        TokenOutput calldata output
    ) external returns (uint256 netTokenOut, uint256 netSyFee);
}

interface IOdos {
    struct swapTokenInfo {
        address inputToken;
        uint256 inputAmount;
        address inputReceiver;
        address outputToken;
        uint256 outputQuote;
        uint256 outputMin;
        address outputReceiver;
    }

    function swap(
        swapTokenInfo memory tokenInfo,
        bytes calldata pathDefinition,
        address executor,
        uint32 referralCode
    ) external;
}

contract DelevPtSusdeModule {
    // constants
    uint256 private constant MAX_ROUNDS = 32;
    uint256 private constant REDEEM_SLIPPAGE = 1e13; // 0.001%

    // immutables
    address public immutable safe;
    address public immutable operator;
    address public immutable aaveAddressesProvider;
    address public immutable aavePool;
    address public immutable aaveDataProvider;
    address public immutable pendleRouter;
    address public immutable odosRouter;
    address public immutable pendleMarket;
    address public immutable usde;
    address public immutable susde;
    address public immutable ptSusde;
    address public immutable ytSusde;
    address public immutable sySusde;
    address public immutable aPtSusde;
    address public immutable variableDebtUsde;
    uint256 public immutable swapSlippage;

    constructor(
        address safe_,
        address operator_,
        address aaveAddressesProvider_,
        address pendleRouter_,
        address odosRouter_,
        address pendleMarket_,
        address usde_,
        address susde_,
        uint256 swapSlippage_
    ) {
        require(safe_ != address(0), "Invalid Safe");
        require(operator_ != address(0), "Invalid operator");
        require(aaveAddressesProvider_ != address(0), "Invalid Aave addresses provider");
        require(pendleRouter_ != address(0), "Invalid Pendle router");
        require(odosRouter_ != address(0), "Invalid Odos router");
        require(pendleMarket_ != address(0), "Invalid Pendle market");
        require(usde_ != address(0), "Invalid USDe");
        require(susde_ != address(0), "Invalid sUSDe");
        require(swapSlippage_ > 0 && swapSlippage_ < 1e18, "Invalid swap slippage");
        safe = safe_;
        operator = operator_;
        aaveAddressesProvider = aaveAddressesProvider_;
        aavePool = IAaveAddressesProvider(aaveAddressesProvider_).getPool();
        aaveDataProvider = IAaveAddressesProvider(aaveAddressesProvider_).getPoolDataProvider();
        pendleRouter = pendleRouter_;
        odosRouter = odosRouter_;
        pendleMarket = pendleMarket_;
        usde = usde_;
        susde = susde_;
        (sySusde, ptSusde, ytSusde) = IPendleMarketV3(pendleMarket_).readTokens();
        (aPtSusde, , ) = IAaveDataProvider(aaveDataProvider).getReserveTokensAddresses(ptSusde);
        (, , variableDebtUsde) = IAaveDataProvider(aaveDataProvider).getReserveTokensAddresses(usde);
        swapSlippage = swapSlippage_;
    }

    modifier onlyOperator() {
        require(msg.sender == operator, "Only operator");
        _;
    }

    // no slippage
    function delevToSusde(uint256 safeLtv) external onlyOperator {
        (, , , , , uint256 initialHealthFactor) = IAavePoolV3(aavePool).getUserAccountData(safe);
        for (uint256 round = 0; round < MAX_ROUNDS; round++) {
            uint256 currentApt = IERC20(aPtSusde).balanceOf(safe);
            if (currentApt == 0) {
                break;
            }
            // withdraw min(maxWithdraw, currentApt)
            (uint256 maxWithdraw, ) = getSafeWithdrawAmounts(safeLtv);
            require(maxWithdraw > 0, "No safe withdrawal possible");
            uint256 w = maxWithdraw < currentApt ? maxWithdraw : currentApt;
            _withdrawFromAave(ptSusde, w);
            // swap and supply
            uint256 susdeReceived = _redeemPtToSusde(w);
            _supplyToAave(susde, susdeReceived);
        }
        // verify health factor increases
        (, , , , , uint256 finalHealthFactor) = IAavePoolV3(aavePool).getUserAccountData(safe);
        require(finalHealthFactor >= initialHealthFactor, "Health factor decreased");
    }

    // has slippage
    function delevToUsde(uint256 withdrawAPt, uint256 safeLtv, bytes calldata odosSwapData) external onlyOperator {
        (, , , , , uint256 initialHealthFactor) = IAavePoolV3(aavePool).getUserAccountData(safe);
        (, , uint256 currentDebt, , , , , , ) = IAaveDataProvider(aaveDataProvider).getUserReserveData(usde, safe);
        // withdraw
        (uint256 maxWithdrawAmount, ) = getSafeWithdrawAmounts(safeLtv);
        if (withdrawAPt == type(uint256).max) {
            // -1 means withdraw all possible. note that aPT is always increasing
            withdrawAPt = maxWithdrawAmount;
        } else {
            require(withdrawAPt <= maxWithdrawAmount, "Withdraw amount exceeds safe limit");
        }
        if (withdrawAPt > 0) {
            _withdrawFromAave(ptSusde, withdrawAPt);
            // swap to sUSDe
            _redeemPtToSusde(withdrawAPt);
        }
        // swap to USDe (skip if empty calldata)
        uint256 usdeReceived = 0;
        if (odosSwapData.length > 0) {
            IOdos.swapTokenInfo memory tokenInfo = _validateOdosSwap(odosSwapData);
            usdeReceived = _executeOdosSwap(odosSwapData, tokenInfo);
        }
        // repay min(debt, swapped)
        if (currentDebt > 0 && usdeReceived > 0) {
            uint256 repayAmount = usdeReceived < currentDebt ? usdeReceived : currentDebt;
            _repayDebt(repayAmount);
        }
        // verify health factor increases
        (, , , , , uint256 finalHealthFactor) = IAavePoolV3(aavePool).getUserAccountData(safe);
        require(finalHealthFactor >= initialHealthFactor, "Health factor decreased");
    }

    function getSafeWithdrawAmounts(uint256 safeLtv) public view returns (uint256 aptAmount, uint256 susdeAmount) {
        // collateral = aPT-sUSDe + asUSDe * sUSDePrice
        (uint256 currentApt, , , , , , , , ) = IAaveDataProvider(aaveDataProvider).getUserReserveData(ptSusde, safe);
        (uint256 currentASusde, , , , , , , , ) = IAaveDataProvider(aaveDataProvider).getUserReserveData(susde, safe);
        (, , uint256 currentDebt, , , , , , ) = IAaveDataProvider(aaveDataProvider).getUserReserveData(usde, safe);
        uint256 exchangeRate = IStandardizedYield(sySusde).exchangeRate();
        uint256 totalCollateralValue = currentApt + (currentASusde * exchangeRate) / 1e18;
        // (collateral - withdrawing) * safeLtv >= debt
        if (currentDebt > 0) {
            uint256 minCollateralRequired = (currentDebt * 1e18) / safeLtv;
            if (totalCollateralValue >= minCollateralRequired) {
                // can withdraw up to the excess collateral, but limited by currentApt
                uint256 excessCollateral = totalCollateralValue - minCollateralRequired;
                aptAmount = excessCollateral < currentApt ? excessCollateral : currentApt;
            } else {
                aptAmount = 0; // position too risky
            }
        } else {
            aptAmount = currentApt;
        }
        // to sUSDe
        susdeAmount = (aptAmount * 1e18) / exchangeRate;
    }

    function _approveFromSafe(address token, address spender, uint256 amount) internal {
        bytes memory data = abi.encodeWithSelector(
            IERC20.approve.selector,
            spender, // spender
            amount // amount
        );
        bool success = IGnosisSafe(safe).execTransactionFromModule(token, 0, data, IGnosisSafe.Operation.Call);
        require(success, "Approve failed");
    }

    function _withdrawFromAave(address asset, uint256 amount) internal {
        bytes memory data = abi.encodeWithSelector(
            IAavePoolV3.withdraw.selector,
            asset, // asset
            amount, // amount
            safe // to
        );
        bool success = IGnosisSafe(safe).execTransactionFromModule(aavePool, 0, data, IGnosisSafe.Operation.Call);
        require(success, "Withdraw failed");
    }

    function _supplyToAave(address asset, uint256 amount) internal {
        require(IERC20(asset).balanceOf(safe) >= amount, "Insufficient balance");
        _approveFromSafe(asset, aavePool, amount);
        bytes memory data = abi.encodeWithSelector(
            IAavePoolV3.supply.selector,
            asset, // asset
            amount, // amount
            safe, // onBehalfOf
            0 // referralCode
        );
        bool success = IGnosisSafe(safe).execTransactionFromModule(aavePool, 0, data, IGnosisSafe.Operation.Call);
        require(success, "Supply failed");
        _approveFromSafe(asset, aavePool, 0);
    }

    function _redeemPtToSusde(uint256 amount) internal returns (uint256 tokenOut) {
        require(IERC20(ptSusde).balanceOf(safe) >= amount, "Insufficient PT balance");
        require(IERC20(ytSusde).balanceOf(safe) >= amount, "Insufficient YT balance");
        _approveFromSafe(ptSusde, pendleRouter, amount);
        _approveFromSafe(ytSusde, pendleRouter, amount);
        uint256 exchangeRate = IStandardizedYield(sySusde).exchangeRate();
        uint256 expectedSusde = (amount * 1e18) / exchangeRate;
        uint256 minTokenOut = (expectedSusde * (1e18 - REDEEM_SLIPPAGE)) / 1e18;
        IPendleRouterV4.TokenOutput memory output = IPendleRouterV4.TokenOutput({
            tokenOut: susde,
            minTokenOut: minTokenOut,
            tokenRedeemSy: susde,
            pendleSwap: address(0),
            swapData: IPendleRouterV4.SwapData({
                swapType: 0,
                extRouter: address(0),
                extCalldata: "",
                needScale: false
            })
        });
        uint256 susdeBefore = IERC20(susde).balanceOf(safe);
        bytes memory data = abi.encodeWithSelector(
            IPendleRouterV4.redeemPyToToken.selector,
            safe, // receiver
            ytSusde, // YT
            amount, // netPyIn
            output // output
        );
        bool success = IGnosisSafe(safe).execTransactionFromModule(pendleRouter, 0, data, IGnosisSafe.Operation.Call);
        require(success, "Redeem failed");
        _approveFromSafe(ptSusde, pendleRouter, 0);
        _approveFromSafe(ytSusde, pendleRouter, 0);
        uint256 susdeAfter = IERC20(susde).balanceOf(safe);
        tokenOut = susdeAfter - susdeBefore;
        require(tokenOut >= minTokenOut, "Insufficient sUSDe received");
    }

    function _repayDebt(uint256 amount) internal {
        require(IERC20(usde).balanceOf(safe) >= amount, "Insufficient USDe balance");
        _approveFromSafe(usde, aavePool, amount);
        bytes memory data = abi.encodeWithSelector(
            IAavePoolV3.repay.selector,
            usde, // asset
            amount, // amount
            2, // interestRateMode (variable)
            safe // onBehalfOf
        );
        bool success = IGnosisSafe(safe).execTransactionFromModule(aavePool, 0, data, IGnosisSafe.Operation.Call);
        require(success, "Repay failed");
        _approveFromSafe(usde, aavePool, 0);
    }

    function _executeOdosSwap(
        bytes calldata odosSwapData,
        IOdos.swapTokenInfo memory tokenInfo
    ) internal returns (uint256 outputAmount) {
        require(IERC20(susde).balanceOf(safe) >= tokenInfo.inputAmount, "Insufficient sUSDe balance");
        uint256 expectedUsde = IERC4626(susde).previewRedeem(tokenInfo.inputAmount);
        uint256 minOut = (expectedUsde * (1e18 - swapSlippage)) / 1e18;
        _approveFromSafe(susde, odosRouter, tokenInfo.inputAmount);
        uint256 usdeBefore = IERC20(usde).balanceOf(safe);
        bool success = IGnosisSafe(safe).execTransactionFromModule(
            odosRouter, // to
            0, // value
            odosSwapData,
            IGnosisSafe.Operation.Call
        );
        require(success, "Odos swap failed");
        _approveFromSafe(susde, odosRouter, 0);
        uint256 usdeAfter = IERC20(usde).balanceOf(safe);
        outputAmount = usdeAfter - usdeBefore;
        require(outputAmount >= minOut, "Output below minimum acceptable");
    }

    function _validateOdosSwap(bytes calldata swapData) internal view returns (IOdos.swapTokenInfo memory tokenInfo) {
        (bytes4 selector, bytes memory dataWithoutSelector) = _splitCallData(swapData);
        require(selector == IOdos.swap.selector, "Invalid swap selector");
        (tokenInfo, , , ) = abi.decode(dataWithoutSelector, (IOdos.swapTokenInfo, bytes, address, uint32));
        require(tokenInfo.inputToken == susde, "Invalid input token");
        require(tokenInfo.outputToken == usde, "Invalid output token");
        require(tokenInfo.outputReceiver == safe, "Invalid output receiver");
    }

    function _splitCallData(
        bytes calldata data
    ) internal pure returns (bytes4 selector, bytes memory dataWithoutSelector) {
        selector = bytes4(data[:4]);
        dataWithoutSelector = data[4:];
    }
}

Tags:
Proxy, Swap, Liquidity, Upgradeable|addr:0xb02a06d48ddbefd0c02e72b04e411c7490e46aec|verified:true|block:23475441|tx:0x96a7ae806e8061fe6888bf88ac24a4fbafd65b90429c4dd05217e149eb2da9b4|first_check:1759234517

Submitted on: 2025-09-30 14:15:17

Comments

Log in to comment.

No comments yet.