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.19;
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);
function transfer(address to, uint256 amount) external returns (bool);
function totalSupply() external view returns (uint256);
}
interface IERC4626 {
function convertToAssets(uint256 shares) external view returns (uint256 assets);
function convertToShares(uint256 assets) external view returns (uint256 shares);
function deposit(uint256 assets, address receiver) external returns (uint256 shares);
function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets);
}
interface IAavePoolV3 {
function supply(address asset, uint256 amount, address onBehalfOf, uint16 referralCode) external;
function borrow(
address asset,
uint256 amount,
uint256 interestRateMode, // 1 = stable, 2 = variable
uint16 referralCode,
address onBehalfOf
) external;
function getUserAccountData(
address user
)
external
view
returns (
uint256 totalCollateralBase,
uint256 totalDebtBase,
uint256 availableBorrowsBase,
uint256 currentLiquidationThreshold,
uint256 ltv,
uint256 healthFactor
);
}
interface IAaveProtocolDataProviderV3 {
// supplyCap, borrowCap are integers with 0 decimals (whole tokens)
function getReserveCaps(address asset) external view returns (uint256 borrowCap, uint256 supplyCap);
function getReserveTokensAddresses(
address asset
) external view returns (address aTokenAddress, address stableDebtTokenAddress, address variableDebtTokenAddress);
}
interface IAaveAddressesProvider {
function getPool() external view returns (address);
function getPoolDataProvider() external view returns (address);
}
interface IPendleMarketV3 {
function readTokens() external view returns (address SY, address PT, address YT);
}
interface IPYieldToken {
function redeemDueInterestAndRewards(
address user,
bool redeemInterest,
bool redeemRewards
) external returns (uint256 interestOut, uint256[] memory rewardsOut);
function SY() external view returns (address);
}
interface IStandardizedYield {
function redeem(
address receiver,
uint256 amountSharesToRedeem,
address tokenOut,
uint256 minTokenOut,
bool burnFromInternalBalance
) external returns (uint256 amountTokenOut);
}
interface IPMarket {
// minimal interface needed
}
interface IPSwapAggregator {
// minimal interface needed
}
enum SwapType {
NONE,
KYBERSWAP,
ODOS,
// ETH_WETH not used in Aggregator
ETH_WETH,
OKX,
ONE_INCH,
PARASWAP,
RESERVE_2,
RESERVE_3,
RESERVE_4,
RESERVE_5
}
struct SwapData {
SwapType swapType;
address extRouter;
bytes extCalldata;
bool needScale;
}
struct SwapDataExtra {
address tokenIn;
address tokenOut;
uint256 minOut;
SwapData swapData;
}
struct RedeemYtIncomeToTokenStruct {
IPYieldToken yt;
bool doRedeemInterest;
bool doRedeemRewards;
address tokenRedeemSy;
uint256 minTokenRedeemOut;
}
interface IPendleRouterV4 {
struct TokenInput {
address tokenIn; // in
uint256 netTokenIn; // in amount
address tokenMintSy; // in amount for mint SY
address pendleSwap; // 0x0 when mint
SwapData swapData; // none when mint
}
function mintPyFromToken(
address receiver,
address YT,
uint256 minPyOut,
TokenInput calldata input
) external payable returns (uint256 netPyOut, uint256 netSyInterm);
function mintPyFromSy(
address receiver,
address YT,
uint256 netSyIn,
uint256 minPyOut
) external returns (uint256 netPyOut);
}
contract LeveragePtSusdeModule {
uint256 constant MAX_LOOP = 32;
uint256 constant MIN_BORROW = 1e18; // min borrow amount (1 USDe)
uint256 constant SLIPPAGE = 1e13; // 0.001% = 0.00001 * 1e18
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 pendleMarketSusde;
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 minHealthRate;
event Loop(
uint256 loop,
uint256 oldApt,
uint256 borrowAmount,
uint256 susdeReceived,
uint256 ptMinted,
uint256 ptSupplied
);
constructor(
address safe_,
address operator_,
address aaveAddressesProvider_,
address pendleRouter_,
address pendleMarketSusde_,
address usde_,
address susde_,
uint256 minHealthRate_
) {
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(pendleMarketSusde_ != address(0), "Invalid Pendle market");
require(usde_ != address(0), "Invalid USDe");
require(susde_ != address(0), "Invalid sUSDE");
require(minHealthRate_ >= 1e18, "Health rate must be >= 100%");
safe = safe_;
operator = operator_;
aaveAddressesProvider = aaveAddressesProvider_;
aavePool = IAaveAddressesProvider(aaveAddressesProvider_).getPool();
aaveDataProvider = IAaveAddressesProvider(aaveAddressesProvider_).getPoolDataProvider();
pendleRouter = pendleRouter_;
pendleMarketSusde = pendleMarketSusde_;
usde = usde_;
susde = susde_;
(sySusde, ptSusde, ytSusde) = IPendleMarketV3(pendleMarketSusde_).readTokens();
(aptSusde, , ) = IAaveProtocolDataProviderV3(aaveDataProvider).getReserveTokensAddresses(ptSusde);
(, , variableDebtUsde) = IAaveProtocolDataProviderV3(aaveDataProvider).getReserveTokensAddresses(usde);
minHealthRate = minHealthRate_;
}
modifier onlyOperator() {
require(msg.sender == operator, "Not operator");
_;
}
function leverage(uint256 targetApt, uint256 safeLtv) external onlyOperator {
_checkSupplyCap(targetApt);
uint256 sUSDeNav = IERC4626(susde).convertToAssets(1e18);
uint256 rounds = 0;
while (rounds < MAX_LOOP) {
rounds++;
// USDe to borrow
uint256 currentApt = IERC20(aptSusde).balanceOf(safe);
uint256 aPtToGet = targetApt > currentApt ? targetApt - currentApt : 0;
if (aPtToGet < MIN_BORROW) {
break;
}
uint256 borrowAmount = _calculateBorrowAmount(currentApt, aPtToGet, safeLtv);
if (borrowAmount < MIN_BORROW) {
break;
}
_borrow(borrowAmount);
// borrowed USDe + USDe balance => sUSDe
uint256 usdeBalance = IERC20(usde).balanceOf(safe);
uint256 susdeReceived = _stakeUsdeToSusde(usdeBalance, sUSDeNav);
// staked sUSDe + sUSDe balance => PT + YT
uint256 sUSDeBalance = IERC20(susde).balanceOf(safe);
uint256 ptMinted = _mintPtYt(sUSDeBalance, sUSDeNav);
// minted PT + PT balance => aPT
uint256 ptBalance = IERC20(ptSusde).balanceOf(safe);
_supplyPT(ptBalance);
emit Loop(rounds, currentApt, borrowAmount, susdeReceived, ptMinted, ptBalance);
}
_assertFinalHealthRate();
}
function claimYieldRewards() external onlyOperator {
_redeemYtInterest();
uint256 syBalance = IERC20(sySusde).balanceOf(safe);
if (syBalance > 0) {
uint256 ptMinted = _mintPtYtFromSy(syBalance);
if (ptMinted > 0) {
_supplyPT(ptMinted);
}
}
}
function _checkSupplyCap(uint256 targetApt) internal view {
(, uint256 supplyCap) = IAaveProtocolDataProviderV3(aaveDataProvider).getReserveCaps(ptSusde);
if (supplyCap == 0) {
// 0 = no cap
return;
}
uint256 aTokenSupply = IERC20(aptSusde).totalSupply();
uint256 currentApt = IERC20(aptSusde).balanceOf(safe);
uint256 delta = targetApt > currentApt ? targetApt - currentApt : 0;
require(aTokenSupply + delta <= supplyCap * 1e18, "Supply cap exceeded");
}
function _calculateBorrowAmount(
uint256 currentApt,
uint256 aPtToGet,
uint256 safeLtv
) internal view returns (uint256) {
uint256 currentDebt = IERC20(variableDebtUsde).balanceOf(safe);
// assume aPT-sUSDe is in unit of USDe
uint256 maxSafeBorrow = (currentApt * safeLtv) / 1e18;
// min(aPtToGet, max(0, maxSafeBorrow - currentDebt))
uint256 safeBorrowAmount = maxSafeBorrow > currentDebt ? maxSafeBorrow - currentDebt : 0;
return safeBorrowAmount < aPtToGet ? safeBorrowAmount : aPtToGet;
}
function _supplyPT(uint256 amount) internal {
require(IERC20(ptSusde).balanceOf(safe) >= amount, "Insufficient PT");
_approveFromSafe(ptSusde, aavePool, amount);
bytes memory data = abi.encodeWithSelector(
IAavePoolV3.supply.selector,
ptSusde, // asset
amount, // amount
safe, // onBehalfOf
0 // referralCode
);
bool success = IGnosisSafe(safe).execTransactionFromModule(aavePool, 0, data, IGnosisSafe.Operation.Call);
require(success, "Supply failed");
_approveFromSafe(ptSusde, aavePool, 0);
}
function _borrow(uint256 amount) internal {
bytes memory data = abi.encodeWithSelector(
IAavePoolV3.borrow.selector,
usde, // asset
amount, // amount
2, // interestRateMode (variable rate)
0, // referralCode
safe // onBehalfOf
);
bool success = IGnosisSafe(safe).execTransactionFromModule(aavePool, 0, data, IGnosisSafe.Operation.Call);
require(success, "Borrow failed");
}
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 _stakeUsdeToSusde(uint256 usdeAmount, uint256 sUSDeNav) internal returns (uint256 susdeReceived) {
_approveFromSafe(usde, susde, usdeAmount);
// expectedSusde = usdeAmount / sUSDeNav
uint256 expectedSusde = (usdeAmount * 1e18) / sUSDeNav;
uint256 minSusde = (expectedSusde * (1e18 - SLIPPAGE)) / 1e18;
uint256 susdeBefore = IERC20(susde).balanceOf(safe);
bytes memory data = abi.encodeWithSelector(
IERC4626.deposit.selector,
usdeAmount, // assets
safe // receiver
);
bool success = IGnosisSafe(safe).execTransactionFromModule(susde, 0, data, IGnosisSafe.Operation.Call);
require(success, "sUSDE stake failed");
uint256 susdeAfter = IERC20(susde).balanceOf(safe);
susdeReceived = susdeAfter - susdeBefore;
require(susdeReceived >= minSusde, "Slippage too high on sUSDE stake");
_approveFromSafe(usde, susde, 0);
}
function _mintPtYt(uint256 susdeAmount, uint256 sUSDeNav) internal returns (uint256 ptMinted) {
_approveFromSafe(susde, pendleRouter, susdeAmount);
// expectedPt = susdeAmount * sUSDeNav
uint256 expectedPt = (susdeAmount * sUSDeNav) / 1e18;
uint256 minPtOut = (expectedPt * (1e18 - SLIPPAGE)) / 1e18;
IPendleRouterV4.TokenInput memory input = IPendleRouterV4.TokenInput({
tokenIn: susde,
netTokenIn: susdeAmount,
tokenMintSy: susde,
pendleSwap: address(0),
swapData: SwapData({ swapType: SwapType.NONE, extRouter: address(0), extCalldata: "", needScale: false })
});
uint256 ptBefore = IERC20(ptSusde).balanceOf(safe);
bytes memory data = abi.encodeWithSelector(
IPendleRouterV4.mintPyFromToken.selector,
safe, // receiver
ytSusde, // YT
minPtOut, // minPyOut
input // TokenInput
);
bool success = IGnosisSafe(safe).execTransactionFromModule(pendleRouter, 0, data, IGnosisSafe.Operation.Call);
require(success, "PT mint failed");
uint256 ptAfter = IERC20(ptSusde).balanceOf(safe);
ptMinted = ptAfter - ptBefore;
require(ptMinted >= minPtOut, "PT minted less than expected");
_approveFromSafe(susde, pendleRouter, 0);
}
function _assertFinalHealthRate() internal view {
(, , , , , uint256 healthFactor) = IAavePoolV3(aavePool).getUserAccountData(safe);
require(healthFactor >= minHealthRate, "Health factor too low");
}
function _redeemYtInterest() internal {
bytes memory data = abi.encodeWithSelector(
IPYieldToken.redeemDueInterestAndRewards.selector,
safe, // user
true, // doRedeemInterest
false // doRedeemRewards
);
bool success = IGnosisSafe(safe).execTransactionFromModule(ytSusde, 0, data, IGnosisSafe.Operation.Call);
require(success, "YT redeem interest failed");
}
function _mintPtYtFromSy(uint256 syAmount) internal returns (uint256 ptMinted) {
_approveFromSafe(sySusde, pendleRouter, syAmount);
// when minting from SY, we expect 1:1 conversion to PT
uint256 minPtOut = (syAmount * (1e18 - SLIPPAGE)) / 1e18;
uint256 ptBefore = IERC20(ptSusde).balanceOf(safe);
bytes memory data = abi.encodeWithSelector(
IPendleRouterV4.mintPyFromSy.selector,
safe, // receiver
ytSusde, // YT
syAmount, // netSyIn
minPtOut // minPyOut
);
bool success = IGnosisSafe(safe).execTransactionFromModule(pendleRouter, 0, data, IGnosisSafe.Operation.Call);
require(success, "PT mint from SY failed");
uint256 ptAfter = IERC20(ptSusde).balanceOf(safe);
ptMinted = ptAfter - ptBefore;
require(ptMinted >= minPtOut, "PT minted from SY less than expected");
_approveFromSafe(sySusde, pendleRouter, 0);
}
}
Submitted on: 2025-09-24 19:08:25
Comments
Log in to comment.
No comments yet.