Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix denial of service borrowing vault #376

Merged

Conversation

pedrovalido
Copy link
Contributor

@pedrovalido pedrovalido commented Mar 15, 2023

This PR addresses H-1 in Macro audit report.

@pedrovalido pedrovalido self-assigned this Mar 15, 2023
@pedrovalido pedrovalido changed the base branch from main to protocol/fix/macro-findings-critical-high March 15, 2023 03:07
@pedrovalido pedrovalido linked an issue Mar 15, 2023 that may be closed by this pull request
@pedrovalido
Copy link
Contributor Author

The issue:
When someone repays the vault's full debt, there's an issue when doing any other vault operation because of the convertDebtToShares function which divides by totalDebt (which is 0).

Chosen option to address this issue
Create a function correctDebt(amount) which borrows the given amount from the current provider. This will lead to an increase in the totalDebt (with precautions) taking advantage of the vault "repayer" and mitigating the issue when the totalDebt is 0.

ps.: There's still the lag between someone repaying the full vaults debt and the function being called. In this timeframe, the vault will not function properly until the correctDebt function is called.

@pedrovalido pedrovalido marked this pull request as ready for review March 29, 2023 03:30
@pedrovalido pedrovalido added the Bug Something isn't working label Mar 29, 2023
*
* @param amount to be borrowed
*/
function correctDebt(uint256 amount) external onlyTimelock {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pedrovalido, if I follow the logic of this function correctDebt() the borrowed amount stays stuck in the vault?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're correct. I'll fix this

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is now fixed. Treasury is passed as an argument in the correctDebt function so funds are sent to it

assertEq(vault.balanceOfDebt(ALICE), 0);

uint256 maxAmount = vault.maxWithdraw(ALICE);
do_withdraw(maxAmount, vault, ALICE);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Going through the test I realized that in fact if some "troublemaker" paybacks the debt of the vault, users are now free to withdraw their assets without burning their debtShares. I think we need to discuss what behavior we expect.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the time in between us calling the troublemaker paying the debt and we calling correctDebt we'll be a problem. I'll come up with some possible solutions so we can discuss them.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed, I paused the withdraw in the withdraw function and it is not very efficient but couldnt find a better way. Issue was added to the optimizations card in the board.
Also, left a //TODO in the correctDebt function to decide if we should unpause there or not. I remember we touched this issue but didn't reach a conclusion. My suggestion: we can unpause inside the correctDebt function as it is only called by timelock so I think it is safe. Let me know if I'm missing something

@pedrovalido pedrovalido marked this pull request as draft April 6, 2023 15:09
@pedrovalido pedrovalido marked this pull request as ready for review April 14, 2023 20:33
Copy link
Contributor

@0xdcota 0xdcota left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @pedrovalido, as discussed lets proceed to remove the amount argument from the 'correctDebt()` function such that by default we re-establish 1 debtShare == 1 unit of debt asset.
We will also exclude that un-pause from the correct debt function, so that this is a seperate action and the "activation of the vault in more controlled steps. In addition the OZ timelock controller contract allows to execute batch actions too. So in case of desiring an "atomic" un-pausing of the withdraw action once debt is corrected it can be done through the timelock.

@pedrovalido
Copy link
Contributor Author

Hey @pedrovalido, as discussed lets proceed to remove the amount argument from the 'correctDebt()` function such that by default we re-establish 1 debtShare == 1 unit of debt asset.
We will also exclude that un-pause from the correct debt function, so that this is a seperate action and the "activation of the vault in more controlled steps. In addition the OZ timelock controller contract allows to execute batch actions too. So in case of desiring an "atomic" un-pausing of the withdraw action once debt is corrected it can be done through the timelock.

Changes have been addressed :)

@0xdcota 0xdcota merged commit 60a0c57 into protocol/fix/macro-findings-critical-high Apr 20, 2023
@0xdcota 0xdcota deleted the protocol/fix/dos-bvault branch April 20, 2023 12:55

if (totalDebt_ == 0 && supply > 0 && supply > totalDebt_) {
_pause(VaultActions.Withdraw);
revert BorrowingVault__withdraw_debtNeedsCorrection();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you please explain what you intend to do here?
I see a pause on top and a revert added just below

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the situation in where some external actor, pays entirely the debt of the vault. By that previously, they will effectively, disable the vault from borrowing operations, if I recall.
Now, if... someone, does pay the vault's debt ever, we created a function to restore debt back, and essentially later decide what to do with the "benefactor" proceeds.
The conditions there, is to avoid withdraws at the moment right after the debt has been paid back.

If you read the conditions: if total debt is zero, meaning... someone paidback the whole debt, and there is debtSharesSupply, meaning.... there were user/s owing funds. Then pause all withdrawals until all gets sort out.

In the case of someone paying off all the debt, we don't want to create "bad debt" position, by allowing users to withdraw the shares and leaving "unbacked" debtShares, once debt is restored.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Though the revert will do effectively the job. I see now, that state for pause will not persist.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep general intention makes sense

Though the revert will do effectively the job. I see now, that state for pause will not persist.

I was more concerned with the above, yep it won't persist

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refer to #536

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug Something isn't working Smart Contracts
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Fix DoS Borrowing Vault
3 participants