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:];
}
}
Submitted on: 2025-09-30 14:15:17
Comments
Log in to comment.
No comments yet.