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);
}
}
Submitted on: 2025-10-14 12:11:31
Comments
Log in to comment.
No comments yet.