Skip to content

Commit

Permalink
Merge pull request #727 from ebtc-protocol/feat/c4-lev-macro-fixes
Browse files Browse the repository at this point in the history
C4 Leverage Macro Fixes
  • Loading branch information
wtj2021 authored Dec 18, 2023
2 parents 2187c97 + 2eabf12 commit be2e0ef
Show file tree
Hide file tree
Showing 4 changed files with 356 additions and 18 deletions.
8 changes: 8 additions & 0 deletions packages/contracts/contracts/Interfaces/ISortedCdps.sol
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,12 @@ interface ISortedCdps {
bytes32 startNodeId,
uint maxNodes
) external view returns (bytes32, bool);

function toCdpId(
address owner,
uint256 blockHeight,
uint256 nonce
) external pure returns (bytes32);

function nextCdpNonce() external view returns (uint256);
}
106 changes: 88 additions & 18 deletions packages/contracts/contracts/LeverageMacroBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ interface ICdpCdps {
/// - Via delegate call (LeverageMacroDelegateTarget)
/// @custom:known-issue Due to slippage some dust amounts for all intermediary tokens can be left, since there's no way to ask to sell all available

contract LeverageMacroBase {
abstract contract LeverageMacroBase {
using SafeERC20 for IERC20;

IBorrowerOperations public immutable borrowerOperations;
Expand Down Expand Up @@ -74,6 +74,7 @@ contract LeverageMacroBase {
}

enum PostOperationCheck {
none,
openCdp,
cdpStats,
isClosed
Expand Down Expand Up @@ -124,6 +125,53 @@ contract LeverageMacroBase {
) external {
_assertOwner();

// Figure out the expected CDP ID using sortedCdps.toCdpId
bytes32 expectedCdpId;
if (
operation.operationType == OperationType.OpenCdpOperation &&
postCheckType != PostOperationCheck.none
) {
expectedCdpId = sortedCdps.toCdpId(
address(this),
block.number,
sortedCdps.nextCdpNonce()
);
} else if (
operation.operationType == OperationType.OpenCdpForOperation &&
postCheckType != PostOperationCheck.none
) {
OpenCdpForOperation memory flData = abi.decode(
operation.OperationData,
(OpenCdpForOperation)
);
// This is used to support permitPositionManagerApproval
expectedCdpId = sortedCdps.toCdpId(
flData.borrower,
block.number,
sortedCdps.nextCdpNonce()
);
}

_doOperation(flType, borrowAmount, operation, postCheckType, checkParams, expectedCdpId);
}

/// @notice Internal function used by derived contracts (i.e. EbtcZapRouter)
/// @param flType flash loan type (eBTC, stETH or None)
/// @param borrowAmount flash loan amount
/// @param operation leverage macro operation
/// @param postCheckType post operation check type
/// @param checkParams post operation check params
/// @param expectedCdpId pre-computed CDP ID used to run post operation checks
/// @dev expectedCdpId is required for OpenCdp and OpenCdpFor, can be set to bytes32(0)
/// for all other operations
function _doOperation(
FlashLoanType flType,
uint256 borrowAmount,
LeverageMacroOperation memory operation,
PostOperationCheck postCheckType,
PostCheckParams memory checkParams,
bytes32 expectedCdpId
) internal {
// Call FL Here, then the stuff below needs to happen inside the FL
if (operation.amountToTransferIn > 0) {
IERC20(operation.tokenToTransferIn).safeTransferFrom(
Expand All @@ -133,16 +181,6 @@ contract LeverageMacroBase {
);
}

/**
* SETUP FOR POST CALL CHECK
*/
uint256 initialCdpIndex;
if (postCheckType == PostOperationCheck.openCdp) {
// How to get owner
// sortedCdps.existCdpOwners(_cdpId);
initialCdpIndex = sortedCdps.cdpCountOf(address(this));
}

// Take eBTC or stETH FlashLoan
if (flType == FlashLoanType.eBTC) {
IERC3156FlashLender(address(borrowerOperations)).flashLoan(
Expand All @@ -167,13 +205,8 @@ contract LeverageMacroBase {
* POST CALL CHECK FOR CREATION
*/
if (postCheckType == PostOperationCheck.openCdp) {
// How to get owner
// sortedCdps.existCdpOwners(_cdpId);
// initialCdpIndex is initialCdpIndex + 1
bytes32 cdpId = sortedCdps.cdpOfOwnerByIndex(address(this), initialCdpIndex);

// Check for param details
ICdpManagerData.Cdp memory cdpInfo = cdpManager.Cdps(cdpId);
ICdpManagerData.Cdp memory cdpInfo = cdpManager.Cdps(expectedCdpId);
_doCheckValueType(checkParams.expectedDebt, cdpInfo.debt);
_doCheckValueType(checkParams.expectedCollateral, cdpInfo.coll);
require(
Expand Down Expand Up @@ -282,9 +315,12 @@ contract LeverageMacroBase {
}

enum OperationType {
None, // Swaps only
OpenCdpOperation,
OpenCdpForOperation,
AdjustCdpOperation,
CloseCdpOperation
CloseCdpOperation,
ClaimSurplusOperation
}

/// @dev Must be memory since we had to decode it
Expand All @@ -297,10 +333,14 @@ contract LeverageMacroBase {
// Based on the type we do stuff
if (operation.operationType == OperationType.OpenCdpOperation) {
_openCdpCallback(operation.OperationData);
} else if (operation.operationType == OperationType.OpenCdpForOperation) {
_openCdpForCallback(operation.OperationData);
} else if (operation.operationType == OperationType.CloseCdpOperation) {
_closeCdpCallback(operation.OperationData);
} else if (operation.operationType == OperationType.AdjustCdpOperation) {
_adjustCdpCallback(operation.OperationData);
} else if (operation.operationType == OperationType.ClaimSurplusOperation) {
_claimSurplusCallback();
}

uint256 afterSwapsLength = operation.swapsAfter.length;
Expand All @@ -318,6 +358,16 @@ contract LeverageMacroBase {
uint256 stETHToDeposit;
}

// Open for
struct OpenCdpForOperation {
// Open CDP For Data
uint256 eBTCToMint;
bytes32 _upperHint;
bytes32 _lowerHint;
uint256 stETHToDeposit;
address borrower;
}

// Change leverage or something
struct AdjustCdpOperation {
bytes32 _cdpId;
Expand Down Expand Up @@ -459,6 +509,7 @@ contract LeverageMacroBase {
/// @dev Must be memory since we had to decode it
function _openCdpCallback(bytes memory data) internal {
OpenCdpOperation memory flData = abi.decode(data, (OpenCdpOperation));

/**
* Open CDP and Emit event
*/
Expand All @@ -470,6 +521,21 @@ contract LeverageMacroBase {
);
}

function _openCdpForCallback(bytes memory data) internal {
OpenCdpForOperation memory flData = abi.decode(data, (OpenCdpForOperation));

/**
* Open CDP and Emit event
*/
bytes32 _cdpId = borrowerOperations.openCdpFor(
flData.eBTCToMint,
flData._upperHint,
flData._lowerHint,
flData.stETHToDeposit,
flData.borrower
);
}

/// @dev Must be memory since we had to decode it
function _closeCdpCallback(bytes memory data) internal {
CloseCdpOperation memory flData = abi.decode(data, (CloseCdpOperation));
Expand All @@ -493,6 +559,10 @@ contract LeverageMacroBase {
);
}

function _claimSurplusCallback() internal {
borrowerOperations.claimSurplusCollShares();
}

/// @dev excessivelySafeCall to perform generic calls without getting gas bombed | useful if you don't care about return value
/// @notice Credits to: https://github.com/nomad-xyz/ExcessivelySafeCall/blob/main/src/ExcessivelySafeCall.sol
function excessivelySafeCall(
Expand Down
22 changes: 22 additions & 0 deletions packages/contracts/contracts/TestContracts/Mock1Inch.sol
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ contract Mock1Inch {
price = _newPrice;
}

// swapExactIn, keeping name for compatibility
function swap(address tokenIn, address tokenOut, uint256 amountIn) external returns (uint256) {
if (tokenIn == address(stETH) && tokenOut == address(eBTCToken)) {
stETH.transferFrom(msg.sender, address(this), amountIn);
Expand All @@ -40,4 +41,25 @@ contract Mock1Inch {

revert("No match");
}

// Needed by EbtcZapRouter with leverage
function swapExactOut(
address tokenIn,
address tokenOut,
uint256 amountOut
) external returns (uint256) {
if (tokenIn == address(stETH) && tokenOut == address(eBTCToken)) {
uint256 amt = (amountOut * 1e18) / price;
stETH.transferFrom(msg.sender, address(this), amt);
eBTCToken.transfer(msg.sender, amountOut);
return amt;
} else if (tokenIn == address(eBTCToken) && tokenOut == address(stETH)) {
uint256 amt = (amountOut * price) / 1e18;
eBTCToken.transferFrom(msg.sender, address(this), amt);
stETH.transfer(msg.sender, amountOut);
return amt;
}

revert("No match");
}
}
Loading

0 comments on commit be2e0ef

Please sign in to comment.