Skip to content

Commit

Permalink
✨ debt-manager: support permit2 for leverage
Browse files Browse the repository at this point in the history
  • Loading branch information
cruzdanilo authored and santichez committed Jun 26, 2023
1 parent 7f7bcde commit 000693d
Show file tree
Hide file tree
Showing 5 changed files with 337 additions and 106 deletions.
87 changes: 45 additions & 42 deletions .gas-snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -10,48 +10,51 @@ AuditorTest:testEnableMarketAuditorMismatch() (gas: 24805)
AuditorTest:testEnableMarketShouldRevertWithInvalidPriceFeed() (gas: 149281)
AuditorTest:testEnterExitMarket() (gas: 178761)
AuditorTest:testExitMarketOwning() (gas: 177445)
DebtManagerTest:testApproveMaliciousMarket() (gas: 29117)
DebtManagerTest:testApproveMarket() (gas: 61608)
DebtManagerTest:testAvailableLiquidity() (gas: 100500)
DebtManagerTest:testBalancerFlashloanCallFromDifferentOrigin() (gas: 65633)
DebtManagerTest:testCallReceiveFlashLoanFromAnyAddress() (gas: 33487)
DebtManagerTest:testDeleverage() (gas: 461764)
DebtManagerTest:testDeleverageHalfBorrowPosition() (gas: 500887)
DebtManagerTest:testFixedDeleverage() (gas: 451787)
DebtManagerTest:testFixedRoll() (gas: 525758)
DebtManagerTest:testFixedRollSameMaturityWithThreeLoops() (gas: 400516)
DebtManagerTest:testFixedRollWithAccurateBorrowSlippage() (gas: 796127)
DebtManagerTest:testFixedRollWithAccurateBorrowSlippageWithThreeLoops() (gas: 1130239)
DebtManagerTest:testFixedRollWithAccurateRepaySlippage() (gas: 796087)
DebtManagerTest:testFixedRollWithAccurateRepaySlippageWithThreeLoops() (gas: 1124999)
DebtManagerTest:testFixedToFloatingRoll() (gas: 480272)
DebtManagerTest:testFixedToFloatingRollHigherThanAvailableLiquidity() (gas: 542894)
DebtManagerTest:testFixedToFloatingRollHigherThanAvailableLiquidityWithSlippage() (gas: 833202)
DebtManagerTest:testFixedToFloatingRollHigherThanAvailableLiquidityWithSlippageWithThreeLoops() (gas: 1004802)
DebtManagerTest:testFixedToFloatingRollWithAccurateSlippage() (gas: 680799)
DebtManagerTest:testFlashloanFeeGreaterThanZero() (gas: 429690)
DebtManagerTest:testFloatingToFixedRoll() (gas: 516123)
DebtManagerTest:testFloatingToFixedRollHigherThanAvailableLiquidity() (gas: 601753)
DebtManagerTest:testFloatingToFixedRollHigherThanAvailableLiquidityWithSlippage() (gas: 999625)
DebtManagerTest:testFloatingToFixedRollHigherThanAvailableLiquidityWithSlippageWithThreePools() (gas: 1208041)
DebtManagerTest:testFloatingToFixedRollWithAccurateSlippage() (gas: 806522)
DebtManagerTest:testFloatingToFixedRollWithAccurateSlippageWithPreviousPosition() (gas: 757420)
DebtManagerTest:testLateFixedDeleverage() (gas: 484933)
DebtManagerTest:testLateFixedRoll() (gas: 536460)
DebtManagerTest:testLateFixedRollWithThreeLoops() (gas: 720031)
DebtManagerTest:testLateFixedToFloatingRoll() (gas: 487661)
DebtManagerTest:testLateFixedToFloatingRollWithThreeLoops() (gas: 649169)
DebtManagerTest:testLeverage() (gas: 375539)
DebtManagerTest:testLeverageShouldFailWhenHealthFactorNearOne() (gas: 751905)
DebtManagerTest:testLeverageWithAlreadyDepositedAmount() (gas: 403912)
DebtManagerTest:testLeverageWithInvalidBalancerVault() (gas: 2936191)
DebtManagerTest:testMockBalancerVault() (gas: 4289437)
DebtManagerTest:testPartialFixedDeleverage() (gas: 525791)
DebtManagerTest:testPartialFixedRoll() (gas: 589035)
DebtManagerTest:testPartialFixedToFloatingRoll() (gas: 553142)
DebtManagerTest:testPartialLateFixedRoll() (gas: 581414)
DebtManagerTest:testPartialLateFixedToFloatingRoll() (gas: 551840)
DebtManagerTest:testPermitAndRollFloatingToFixed() (gas: 597947)
DebtManagerTest:testApproveMaliciousMarket() (gas: 29065)
DebtManagerTest:testApproveMarket() (gas: 61556)
DebtManagerTest:testAvailableLiquidity() (gas: 100522)
DebtManagerTest:testBalancerFlashloanCallFromDifferentOrigin() (gas: 65744)
DebtManagerTest:testCallReceiveFlashLoanFromAnyAddress() (gas: 33553)
DebtManagerTest:testDeleverage() (gas: 463698)
DebtManagerTest:testDeleverageHalfBorrowPosition() (gas: 502876)
DebtManagerTest:testDeleverageWithWithdraw() (gas: 496289)
DebtManagerTest:testFixedDeleverage() (gas: 452060)
DebtManagerTest:testFixedRoll() (gas: 525951)
DebtManagerTest:testFixedRollSameMaturityWithThreeLoops() (gas: 400560)
DebtManagerTest:testFixedRollWithAccurateBorrowSlippage() (gas: 796447)
DebtManagerTest:testFixedRollWithAccurateBorrowSlippageWithThreeLoops() (gas: 1130759)
DebtManagerTest:testFixedRollWithAccurateRepaySlippage() (gas: 796385)
DebtManagerTest:testFixedRollWithAccurateRepaySlippageWithThreeLoops() (gas: 1125497)
DebtManagerTest:testFixedToFloatingRoll() (gas: 480375)
DebtManagerTest:testFixedToFloatingRollHigherThanAvailableLiquidity() (gas: 543110)
DebtManagerTest:testFixedToFloatingRollHigherThanAvailableLiquidityWithSlippage() (gas: 833500)
DebtManagerTest:testFixedToFloatingRollHigherThanAvailableLiquidityWithSlippageWithThreeLoops() (gas: 1005251)
DebtManagerTest:testFixedToFloatingRollWithAccurateSlippage() (gas: 680969)
DebtManagerTest:testFlashloanFeeGreaterThanZero() (gas: 431305)
DebtManagerTest:testFloatingToFixedRoll() (gas: 516351)
DebtManagerTest:testFloatingToFixedRollHigherThanAvailableLiquidity() (gas: 602076)
DebtManagerTest:testFloatingToFixedRollHigherThanAvailableLiquidityWithSlippage() (gas: 1000204)
DebtManagerTest:testFloatingToFixedRollHigherThanAvailableLiquidityWithSlippageWithThreePools() (gas: 1208787)
DebtManagerTest:testFloatingToFixedRollWithAccurateSlippage() (gas: 806911)
DebtManagerTest:testFloatingToFixedRollWithAccurateSlippageWithPreviousPosition() (gas: 757786)
DebtManagerTest:testLateFixedDeleverage() (gas: 485172)
DebtManagerTest:testLateFixedRoll() (gas: 536631)
DebtManagerTest:testLateFixedRollWithThreeLoops() (gas: 720302)
DebtManagerTest:testLateFixedToFloatingRoll() (gas: 487764)
DebtManagerTest:testLateFixedToFloatingRollWithThreeLoops() (gas: 649360)
DebtManagerTest:testLeverage() (gas: 377213)
DebtManagerTest:testLeverageShouldFailWhenHealthFactorNearOne() (gas: 755217)
DebtManagerTest:testLeverageWithAlreadyDepositedAmount() (gas: 403958)
DebtManagerTest:testLeverageWithInvalidBalancerVault() (gas: 3209983)
DebtManagerTest:testMockBalancerVault() (gas: 4563902)
DebtManagerTest:testPartialFixedDeleverage() (gas: 526046)
DebtManagerTest:testPartialFixedRoll() (gas: 589206)
DebtManagerTest:testPartialFixedToFloatingRoll() (gas: 553249)
DebtManagerTest:testPartialLateFixedRoll() (gas: 581585)
DebtManagerTest:testPartialLateFixedToFloatingRoll() (gas: 551947)
DebtManagerTest:testPermit2AndLeverage() (gas: 530215)
DebtManagerTest:testPermitAndDeleverage() (gas: 532307)
DebtManagerTest:testPermitAndRollFloatingToFixed() (gas: 600065)
InterestRateModelTest:testFixedBorrowRate() (gas: 8089)
InterestRateModelTest:testFloatingBorrowRate() (gas: 6236)
InterestRateModelTest:testMinFixedRate() (gas: 7610)
Expand Down
159 changes: 136 additions & 23 deletions contracts/periphery/DebtManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { SafeTransferLib } from "solmate/src/utils/SafeTransferLib.sol";
import { FixedPointMathLib } from "solmate/src/utils/FixedPointMathLib.sol";
import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import { AddressUpgradeable as Address } from "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol";
import { Market, ERC20, ERC4626, FixedLib, Disagreement } from "../Market.sol";
import { Market, ERC20, FixedLib, Disagreement } from "../Market.sol";
import { Auditor, MarketNotListed } from "../Auditor.sol";

/// @title DebtManager
Expand All @@ -20,13 +20,17 @@ contract DebtManager is Initializable {
/// @notice Auditor contract that lists the markets that can be leveraged.
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
Auditor public immutable auditor;
/// @notice Permit2 contract to be used to transfer assets from accounts.
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
IPermit2 public immutable permit2;
/// @notice Balancer's vault contract that is used to take flash loans.
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
IBalancerVault public immutable balancerVault;

/// @custom:oz-upgrades-unsafe-allow constructor
constructor(Auditor auditor_, IBalancerVault balancerVault_) {
constructor(Auditor auditor_, IPermit2 permit2_, IBalancerVault balancerVault_) {
auditor = auditor_;
permit2 = permit2_;
balancerVault = balancerVault_;

_disableInitializers();
Expand All @@ -44,23 +48,32 @@ contract DebtManager is Initializable {
/// @notice Leverages the floating position of `msg.sender` to match `targetHealthFactor` by taking a flash loan
/// from Balancer's vault.
/// @param market The Market to leverage the position in.
/// @param principal The amount of assets to deposit or deposited.
/// @param principal The amount of assets to leverage.
/// @param deposit The amount of assets to deposit.
/// @param targetHealthFactor The desired target health factor that the account will be leveraged to.
function leverage(Market market, uint256 principal, uint256 deposit, uint256 targetHealthFactor) external {
if (deposit != 0) market.asset().safeTransferFrom(msg.sender, address(this), deposit);

noTransferLeverage(market, principal, deposit, targetHealthFactor);
}

/// @notice Leverages the floating position of `msg.sender` to match `targetHealthFactor` by taking a flash loan
/// from Balancer's vault.
/// @param market The Market to leverage the position in.
/// @param principal The amount of assets to leverage.
/// @param deposit The amount of assets to deposit.
/// @param targetHealthFactor The desired target health factor that the account will be leveraged to.
/// @param deposit True if the principal is being deposited, false if the principal is already deposited.
function leverage(Market market, uint256 principal, uint256 targetHealthFactor, bool deposit) external {
function noTransferLeverage(Market market, uint256 principal, uint256 deposit, uint256 targetHealthFactor) internal {
uint256[] memory amounts = new uint256[](1);
ERC20[] memory tokens = new ERC20[](1);
bytes[] memory calls = new bytes[](2);
ERC20 asset = market.asset();

if (deposit) asset.safeTransferFrom(msg.sender, address(this), principal);

(uint256 adjustFactor, , , , ) = auditor.markets(market);
uint256 factor = adjustFactor.mulWadDown(adjustFactor).divWadDown(targetHealthFactor);
tokens[0] = asset;
tokens[0] = market.asset();
amounts[0] = principal.mulWadDown(factor).divWadDown(1e18 - factor);
calls[0] = abi.encodeCall(ERC4626.deposit, (amounts[0] + (deposit ? principal : 0), msg.sender));
calls[1] = abi.encodeCall(Market.borrow, (amounts[0], address(balancerVault), msg.sender));
calls[0] = abi.encodeCall(market.deposit, (amounts[0] + deposit, msg.sender));
calls[1] = abi.encodeCall(market.borrow, (amounts[0], address(balancerVault), msg.sender));

balancerVault.flashLoan(address(this), tokens, amounts, call(abi.encode(market, calls)));
}
Expand All @@ -69,24 +82,32 @@ contract DebtManager is Initializable {
/// Balancer's vault to repay the borrow.
/// @param market The Market to deleverage the position out.
/// @param maturity The maturity of the fixed pool that the position is being deleveraged out of, `0` if floating.
/// @param maxAssets Max amount of fixed debt that the sender is willing to accept.
/// @param maxRepayAssets Max amount of fixed debt that the sender is willing to accept.
/// @param percentage The percentage of the borrow that will be repaid, represented with 18 decimals.
function deleverage(Market market, uint256 maturity, uint256 maxAssets, uint256 percentage) external {
/// @param withdraw The amount of assets that will be withdrawn to `msg.sender`.
function deleverage(
Market market,
uint256 maturity,
uint256 maxRepayAssets,
uint256 percentage,
uint256 withdraw
) public {
uint256[] memory amounts = new uint256[](1);
ERC20[] memory tokens = new ERC20[](1);
bytes[] memory calls = new bytes[](2);
bytes[] memory calls = new bytes[](withdraw == 0 ? 2 : 3);
tokens[0] = market.asset();

if (maturity == 0) {
(, , uint256 floatingBorrowShares) = market.accounts(msg.sender);
amounts[0] = market.previewRefund(floatingBorrowShares.mulWadDown(percentage));
calls[0] = abi.encodeCall(Market.repay, (amounts[0], msg.sender));
calls[0] = abi.encodeCall(market.repay, (amounts[0], msg.sender));
} else {
uint256 positionAssets;
(amounts[0], positionAssets) = repayAtMaturityAssets(market, maturity, percentage);
calls[0] = abi.encodeCall(Market.repayAtMaturity, (maturity, positionAssets, maxAssets, msg.sender));
calls[0] = abi.encodeCall(market.repayAtMaturity, (maturity, positionAssets, maxRepayAssets, msg.sender));
}
calls[1] = abi.encodeCall(Market.withdraw, (amounts[0], address(balancerVault), msg.sender));
calls[1] = abi.encodeCall(market.withdraw, (amounts[0], address(balancerVault), msg.sender));
if (withdraw > 0) calls[2] = abi.encodeCall(market.withdraw, (withdraw, msg.sender, msg.sender));

balancerVault.flashLoan(address(this), tokens, amounts, call(abi.encode(market, calls)));
}
Expand Down Expand Up @@ -119,11 +140,11 @@ contract DebtManager is Initializable {
calls = new bytes[](2 * r.loopCount);
for (r.i = 0; r.i < r.loopCount; ) {
calls[r.callIndex++] = abi.encodeCall(
Market.repay,
market.repay,
(r.i == 0 ? amounts[0] : r.repayAssets / r.loopCount, msg.sender)
);
calls[r.callIndex++] = abi.encodeCall(
Market.borrowAtMaturity,
market.borrowAtMaturity,
(
borrowMaturity,
r.i + 1 == r.loopCount ? amounts[0] : r.repayAssets / r.loopCount,
Expand Down Expand Up @@ -171,11 +192,11 @@ contract DebtManager is Initializable {
calls = new bytes[](2 * r.loopCount);
for (r.i = 0; r.i < r.loopCount; ) {
calls[r.callIndex++] = abi.encodeCall(
Market.repayAtMaturity,
market.repayAtMaturity,
(repayMaturity, positionAssets, type(uint256).max, msg.sender)
);
calls[r.callIndex++] = abi.encodeCall(
Market.borrow,
market.borrow,
(amounts[0], r.i + 1 == r.loopCount ? address(balancerVault) : address(this), msg.sender)
);
unchecked {
Expand Down Expand Up @@ -221,11 +242,11 @@ contract DebtManager is Initializable {
calls = new bytes[](2 * r.loopCount);
for (r.i = 0; r.i < r.loopCount; ) {
calls[r.callIndex++] = abi.encodeCall(
Market.repayAtMaturity,
market.repayAtMaturity,
(repayMaturity, r.positionAssets, type(uint256).max, msg.sender)
);
calls[r.callIndex++] = abi.encodeCall(
Market.borrowAtMaturity,
market.borrowAtMaturity,
(
borrowMaturity,
amounts[0],
Expand Down Expand Up @@ -328,6 +349,70 @@ contract DebtManager is Initializable {
_;
}

/// @notice Calls `permit2.permitTransferFrom` to transfer `msg.sender` assets.
/// @param token The `ERC20` to transfer from `msg.sender` to this contract.
/// @param assets The amount of assets to transfer from `msg.sender`.
/// @param deadline The deadline for the permit call.
/// @param signature The signature for the permit call.
modifier permitTransfer(
ERC20 token,
uint256 assets,
uint256 deadline,
bytes calldata signature
) {
permit2.permitTransferFrom(
IPermit2.PermitTransferFrom(
IPermit2.TokenPermissions(address(token), assets),
uint256(keccak256(abi.encode(msg.sender, token, assets, deadline))),
deadline
),
IPermit2.SignatureTransferDetails(address(this), assets),
msg.sender,
signature
);
_;
}

/// @notice Leverages the floating position of `msg.sender` to match `targetHealthFactor` by taking a flash loan
/// from Balancer's vault.
/// @param market The Market to leverage the position in.
/// @param principal The amount of assets to leverage.
/// @param deposit The amount of assets to deposit.
/// @param targetHealthFactor The desired target health factor that the account will be leveraged to.
/// @param deadline The deadline for the permit call.
/// @param signature The signature for the permit call.
function leverage(
Market market,
uint256 principal,
uint256 deposit,
uint256 targetHealthFactor,
uint256 deadline,
bytes calldata signature
) external permitTransfer(market.asset(), deposit, deadline, signature) {
noTransferLeverage(market, principal, deposit, targetHealthFactor);
}

/// @notice Deleverages the position of `msg.sender` a certain `percentage` by taking a flash loan from
/// Balancer's vault to repay the borrow.
/// @param market The Market to deleverage the position out.
/// @param maturity The maturity of the fixed pool that the position is being deleveraged out of, `0` if floating.
/// @param maxRepayAssets Max amount of fixed debt that the sender is willing to accept.
/// @param percentage The percentage of the borrow that will be repaid, represented with 18 decimals.
/// @param withdraw The amount of assets that will be withdrawn to `msg.sender`.
/// @param permitAssets The amount of assets to allow this contract to withdraw on behalf of `msg.sender`.
/// @param p Arguments for the permit call to `market` on behalf of `permit.account`.
function deleverage(
Market market,
uint256 maturity,
uint256 maxRepayAssets,
uint256 percentage,
uint256 withdraw,
uint256 permitAssets,
Permit calldata p
) external permit(market, permitAssets, p) {
deleverage(market, maturity, maxRepayAssets, percentage, withdraw);
}

/// @notice Rolls a percentage of the floating position of `msg.sender` to a fixed position
/// after calling `market.permit`.
/// @param market The Market to roll the position in.
Expand Down Expand Up @@ -438,3 +523,31 @@ interface IBalancerVault {
bytes memory userData
) external;
}

interface IPermit2 {
struct TokenPermissions {
address token;
uint256 amount;
}

struct PermitTransferFrom {
TokenPermissions permitted;
uint256 nonce;
uint256 deadline;
}

struct SignatureTransferDetails {
address to;
uint256 requestedAmount;
}

function permitTransferFrom(
PermitTransferFrom memory permit,
SignatureTransferDetails calldata transferDetails,
address owner,
bytes calldata signature
) external;

// solhint-disable-next-line func-name-mixedcase
function DOMAIN_SEPARATOR() external view returns (bytes32);
}

0 comments on commit 000693d

Please sign in to comment.