Closed wtj2021 closed 9 months ago
update ISortedCdps interface to expose toCdpId
and nextCdpNonce
so that consuming contracts can deterministically find cpdId of Cdp that is being created.
Is now officially abstract. It was always intended to be inherited and is used this way in practice.
We now allow users to claim surplus as an action in the flashloan fallback of a leverage op.
This fixes issues where positions opened via a LeverageMacro impl could have no way to get out collateral surplus out (unless the implementing contract also included that functionality separately)
fixes https://github.com/code-423n4/2023-10-badger-findings/issues/323
we have a new distinct OperationType
, ClaimSurplusOperation
enum OperationType {
OpenCdpOperation,
AdjustCdpOperation,
CloseCdpOperation,
**ClaimSurplusOperation**
}
it corresponds with a new PostOperationCheck
type, claimSurplus
This allows for confirming the collateral shares that were gained as part of the operation
enum PostOperationCheck {
openCdp,
cdpStats,
isClosed,
**claimSurplus**
}
we piggyback on the PostCheckParams.expectedCollateral
parameter to verify the value of the expected output from the claim operation.
The new flashloan callback is correspondingly simple, no parameters are required. We claim the outstanding surplus that belongs to the contract that has the callback context.
function _claimSurplusCallback() internal {
borrowerOperations.claimSurplusCollShares();
}
For collSurplus post-operation checks, we read the stETH shares in the macro before and after the operation and compare them to ensure our shares increased by at least the expected amount.
we then assume all shares that entered the macro as part of the operation are from this.
The option to sweep all collateral to the caller is retained based on the immutable willSweep
flag
Wdyt about removing the safety checks for targets That way owner can just call w/e target and w/e signature they need
@wtj2021 @dapp-whisperer
In response to https://github.com/code-423n4/2023-10-badger-findings/issues/112, @GalloDaSballo mentions:
I think we wil remove the safety check and add a way to reset allowances
does this imply you support removal of the _ensureNotSystem check to prevent arbitrary calls to the eBTC contracts via _doSwap
?
I was wondering if you could expand further on allowance reset comment @GalloDaSballo?
The reviewer says the following
This will result in the inability to access the borrowerOperations.permitPositionManagerApproval method for authorization
In fact, I think that the restrictions in _ensureNotSystem can be removed. This is a wallet. Any developer can develop a similar wallet according to his own wishes. The restrictions here have little significance for security.
In creating the zaps, we do in fact get around this by wrapping the LeverageMacroBase in a different contract that can call the positionManager functions.
So this is only really relevant for the LeverageMacroReference impl.
Wdyt about removing the safety checks for targets That way owner can just call w/e target and w/e signature they need
@wtj2021 @dapp-whisperer
I'm fine removing it as per previous comment. Was writing mine and didn't see yours :)
Can you remind me of the original security reasoning behind the checks? @GalloDaSballo
If l understood correctly in this pull request the main changes in LeverageMacro are:
On short explanation the function doOperation compare the value of the claimed surplus coll by checking the shares balance of LM before and after performing the claim surplus callback. The concern here is that apart from claiming the surplus coll, the user may want to do also a swap or arbitrary call which may lead to the LM receiving more stETH shares or spending the shares the LM already has. Given this concern, it may be harder to check and compare how much of surplus the LM received.
The question is if this is reasonable concern and should it be fixed???
uint256 collSharesBefore;
if (postCheckType == PostOperationCheck.claimSurplus) {
collSharesBefore = stETH.sharesOf(address(this));
}
/// @dev Must be memory since we had to decode it
function _handleOperation(LeverageMacroOperation memory operation) internal {
uint256 beforeSwapsLength = operation.swapsBefore.length;
if (beforeSwapsLength > 0) {
_doSwaps(operation.swapsBefore);
}
// 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;
if (afterSwapsLength > 0) {
_doSwaps(operation.swapsAfter);
}
}
// Post check type: claim collateral surplus
if (postCheckType == PostOperationCheck.claimSurplus) {
uint256 collSharesClaimed = stETH.sharesOf(address(this)) - collSharesBefore;
_doCheckValueType(checkParams.expectedCollateral, collSharesClaimed);
}
If the reason for removing the safety checks is that the LM can't do arbitrary call to grant a position approval, shouldn't it be better to just create a new feature which does that? Apart from that there could be some wrong outputs e.g if the person decides to open Cdps by using both the swaps and the OpenCdpOperation at the same time, but l do think that's more like an edge case if someone ever do this.
The good news is that by removing the safety checks, the LM can do arbitrary calls to grant position approvals and also make Cdp liquidations.
Ok just to confirm that the issues are fixed:
Am l missing something else?
@CodingNameKiki Yes, that's correct. toCdpId is used to replace cdpOfOwnerByIndex, which can cause issues for large sortedCdp lists.
Will describe the problem in short explanation, take as an example that a user wants to couple of swaps without wanting to do any of the cdp callbacks e.g opening cdp, adjusting cdp and the other...
function _handleOperation(LeverageMacroOperation memory operation) internal {
uint256 beforeSwapsLength = operation.swapsBefore.length;
if (beforeSwapsLength > 0) {
_doSwaps(operation.swapsBefore);
}
// 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;
if (afterSwapsLength > 0) {
_doSwaps(operation.swapsAfter);
}
}
However if we look at enum OperationsType, we can see that there is NO option and on zero value the LeverageMacro will suggest that the user wants to do a open cdp operation.
enum OperationType {
OpenCdpOperation,
OpenCdpForOperation,
AdjustCdpOperation,
CloseCdpOperation,
ClaimSurplusOperation
}
So the Leverage macro will always do an external call to find the unique id and always trigger the open cdp callback and try to open Cdp with empty operation data, which will revert and this might result to a significant gas loss to the user, if the user is doing a list of swaps and arbitrary calls.
bytes32 expectedCdpId;
if (operation.operationType == OperationType.OpenCdpOperation) {
expectedCdpId = sortedCdps.toCdpId(
address(this),
block.number,
sortedCdps.nextCdpNonce()
);
if (operation.operationType == OperationType.OpenCdpOperation) {
_openCdpCallback(operation.OperationData);
function _openCdpCallback(bytes memory data) internal {
OpenCdpOperation memory flData = abi.decode(data, (OpenCdpOperation));
/**
* Open CDP and Emit event
*/
bytes32 _cdpId = borrowerOperations.openCdp(
flData.eBTCToMint,
flData._upperHint,
flData._lowerHint,
flData.stETHToDeposit
);
}
Regardless if there is no cdp operation or the user simply turns off the post check feature, the LM will still make an external call to the function toCdpId in order to find it.
enum OperationType {
OpenCdpOperation,
OpenCdpForOperation,
AdjustCdpOperation,
CloseCdpOperation,
ClaimSurplusOperation
}
You can see that the post operation check is turned off on zero value.
enum PostOperationCheck {
none,
openCdp,
cdpStats,
isClosed
}
Or is the LM designed to always do a cdp operation alongside the arbitrary calls? @wtj2021
Wouldn't it be better to let people do only arbitrary calls if they want, it can be easily fixed by refactoring the enum:
enum OperationType {
None,
OpenCdpOperation,
OpenCdpForOperation,
AdjustCdpOperation,
CloseCdpOperation,
ClaimSurplusOperation
}
Overall am thinking we could add a performArbitraryCall
operation to allow bypassing those checks and allow adding functionality we currently don't have
I would put this as low prio
I'd be interested in a cut version of the macro, that uses the same audited code, but removes some extra pieces as to make it easier to read and to understand
Overall we should look to simplify the code more so than adding a bunch of additional if/elses
That said, the claimCollSurplus is fixed, which means that the macro is usable
I wonder if we forgot about other functionality
Generally speaking the Permission Managers are a bad idea to support in the macro as this is a very advanced piece of tech
If we maintain that we don't want to deploy the macro, then I think we should document these issues and keep as is
I wonder if we forgot about other functionality
By removing the safety checks LeverageMacro will be able to access the above functionalities. Also the LM will be able to open multiple Cdps at once, adjust, close etc and will have access to take both flash loans at once, the design right now only allows one operation at a time. Ideally we should decide what is appropriate for the LM to do and instead of removing the checks add some other operation like Alex mentioned.
@CodingNameKiki that makes sense, I added a None
operation type for doing trades and a check to skip the expectedCdpId if post check type is set to none.
Nice, l think everything looks good now.
@GalloDaSballo @rayeaster
after discussion is ready to merge down to main. If you of you guys can stamp we can merge.
LGTM for merge