DelevPtUnstakeSusdeModule

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 IStakedUSDeV2 {
    struct UserCooldown {
        uint104 cooldownEnd;
        uint152 underlyingAmount;
    }

    function cooldowns(address user) external view returns (uint104 cooldownEnd, uint152 underlyingAmount);
    function cooldownShares(uint256 shares) external returns (uint256 assets);
    function cooldownAssets(uint256 assets) external returns (uint256 shares);
    function unstake(address receiver) external;
    function cooldownDuration() external view returns (uint24);
}

contract DelevPtUnstakeSusdeModule {
    uint256 private constant REDEEM_SLIPPAGE = 1e13; // 0.001%

    address public immutable safe;
    address public immutable operator;
    uint256 public immutable minHealthFactor;
    address public immutable aaveAddressesProvider;
    address public immutable aavePool;
    address public immutable aaveDataProvider;
    address public immutable pendleRouter;
    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;

    event UnstakeInitiated(uint256 susdeAmount, uint256 expectedUsdeAmount);
    event UnstakeCompleted(uint256 usdeAmount, uint256 debtRepaid);

    constructor(
        address safe_,
        address operator_,
        address aaveAddressesProvider_,
        address pendleRouter_,
        address pendleMarket_,
        address usde_,
        address susde_,
        uint256 minHealthFactor_
    ) {
        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(pendleMarket_ != address(0), "Invalid Pendle market");
        require(usde_ != address(0), "Invalid USDe");
        require(susde_ != address(0), "Invalid sUSDe");
        require(minHealthFactor_ >= 1e18, "Invalid min health factor");
        safe = safe_;
        operator = operator_;
        minHealthFactor = minHealthFactor_;
        aaveAddressesProvider = aaveAddressesProvider_;
        aavePool = IAaveAddressesProvider(aaveAddressesProvider_).getPool();
        aaveDataProvider = IAaveAddressesProvider(aaveAddressesProvider_).getPoolDataProvider();
        pendleRouter = pendleRouter_;
        pendleMarket = pendleMarket_;
        usde = usde_;
        susde = susde_;
        (sySusde, ptSusde, ytSusde) = IPendleMarketV3(pendleMarket_).readTokens();
        (aPtSusde, , ) = IAaveDataProvider(aaveDataProvider).getReserveTokensAddresses(ptSusde);
        (, , variableDebtUsde) = IAaveDataProvider(aaveDataProvider).getReserveTokensAddresses(usde);
    }

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

    // withdrawAmount can be -1
    function initiateUnstake(uint256 withdrawAmount) external onlyOperator {
        // get sUSDe
        require(withdrawAmount > 0, "Withdraw amount must be positive");
        uint256 actualWithdrawn = _withdrawFromAave(ptSusde, withdrawAmount);
        uint256 susdeReceived = _redeemPtToSusde(actualWithdrawn);
        require(susdeReceived > 0, "No sUSDe received from redemption");
        // unstake
        uint256 expectedUsde = IERC4626(susde).previewRedeem(susdeReceived);
        _cooldownSusde(susdeReceived);
        // verify
        (, , , , , uint256 healthFactor) = IAavePoolV3(aavePool).getUserAccountData(safe);
        require(healthFactor >= minHealthFactor, "Health factor below minimum after withdrawal");
        emit UnstakeInitiated(susdeReceived, expectedUsde);
    }

    function completeUnstake() external onlyOperator {
        (uint104 cooldownEnd, uint152 underlyingAmount) = IStakedUSDeV2(susde).cooldowns(safe);
        require(underlyingAmount > 0, "No USDe to unstake");
        require(block.timestamp >= cooldownEnd, "Cooldown period not finished");
        (, , uint256 currentDebt, , , , , , ) = IAaveDataProvider(aaveDataProvider).getUserReserveData(usde, safe);
        uint256 usdeReceived = _unstakeSusde();
        require(usdeReceived > 0, "No USDe received");
        uint256 repaidAmount = 0;
        if (currentDebt > 0 && usdeReceived > 0) {
            repaidAmount = usdeReceived <= currentDebt ? usdeReceived : currentDebt;
            _repayDebt(repaidAmount);
        }
        emit UnstakeCompleted(usdeReceived, repaidAmount);
    }

    function getCooldownInfo() external view returns (uint104 cooldownEnd, uint152 pendingUsdeAmount) {
        return IStakedUSDeV2(susde).cooldowns(safe);
    }

    function getCooldownDuration() external view returns (uint256) {
        return IStakedUSDeV2(susde).cooldownDuration();
    }

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

    function _cooldownSusde(uint256 susdeAmount) internal {
        bytes memory data = abi.encodeWithSelector(
            IStakedUSDeV2.cooldownShares.selector,
            susdeAmount // shares
        );
        bool success = IGnosisSafe(safe).execTransactionFromModule(susde, 0, data, IGnosisSafe.Operation.Call);
        require(success, "Cooldown initiation failed");
    }

    function _unstakeSusde() internal returns (uint256 usdeReceived) {
        uint256 usdeBalanceBefore = IERC20(usde).balanceOf(safe);
        bytes memory data = abi.encodeWithSelector(
            IStakedUSDeV2.unstake.selector,
            safe // receiver
        );
        bool success = IGnosisSafe(safe).execTransactionFromModule(susde, 0, data, IGnosisSafe.Operation.Call);
        require(success, "Unstake failed");
        uint256 usdeBalanceAfter = IERC20(usde).balanceOf(safe);
        require(usdeBalanceAfter >= usdeBalanceBefore, "USDe balance decreased");
        usdeReceived = usdeBalanceAfter - usdeBalanceBefore;
    }

    function _withdrawFromAave(address asset, uint256 amount) internal returns (uint256 actualWithdrawn) {
        uint256 balanceBefore = IERC20(asset).balanceOf(safe);
        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");
        uint256 balanceAfter = IERC20(asset).balanceOf(safe);
        require(balanceAfter >= balanceBefore, "Balance decreased");
        actualWithdrawn = balanceAfter - balanceBefore;
    }

    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);
        require(susdeAfter >= susdeBefore, "sUSDe balance decreased");
        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);
    }
}

Tags:
Proxy, Swap, Liquidity, Upgradeable|addr:0x5e413fbc37b3e4b231b2b28cb8427d772b290266|verified:true|block:23575071|tx:0xe0874ee9f65df00b7cd734c5f506f903f07bffc9e1a1947a73004424c6eb09ec|first_check:1760436690

Submitted on: 2025-10-14 12:11:31

Comments

Log in to comment.

No comments yet.