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

feat(EMP contracts): Subsequent liquidations reset withdrawal timer #1859

Merged
merged 6 commits into from
Aug 20, 2020

Conversation

chrismaree
Copy link
Member

@chrismaree chrismaree commented Aug 17, 2020

Motivation

Issue: #1858

Summary

Very large positions are difficult to liquidate in the current EMP contracts as liquidators are required to pay off all sponsor debt when liquidating. This problem is particularly acute in the request withdrawal case.

This PR implements a change to the EMP contracts that re-sets the requested withdrawal liveness timer on every subsequent liquidation against a position.

Details

Each time a position has a liquidation submitted against the contracts check if there is a pending withdrawal request. If there is one, and it is not expired, then the sponsor's withdrawal request timestamp is re-set to the current time + the liquidation liveness.

Discussion

I don't check pending transfer requests. I think this is fine. Any thoughts on this point?

Signed-off-by: Christopher Maree <christopher.maree@gmail.com>
if (
positionToLiquidate.withdrawalRequestPassTimestamp > 0 && // this is a subsequent liquidation.
positionToLiquidate.withdrawalRequestPassTimestamp < getCurrentTime() && // liveness has not passed yet.
maxTokensToLiquidate.isGreaterThan(minSponsorTokens) // above the minimum threshold.
Copy link
Member

Choose a reason for hiding this comment

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

nit: I think we treat the threshold as a >= threshold rather than a > threshold (look at create() so I think this should be isGreaterThanOrEqualTo. But it doesn't really matter if it really makes the testing more difficult

Copy link
Member

Choose a reason for hiding this comment

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

I agree that we should be consistent here.

Copy link
Member Author

Choose a reason for hiding this comment

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

I have implemented this change. PTAL.

// then re-set the liveness. This enables a liquidation against a withdraw request to be "dragged out" if the position is very large.
if (
positionToLiquidate.withdrawalRequestPassTimestamp > 0 && // this is a subsequent liquidation.
positionToLiquidate.withdrawalRequestPassTimestamp < getCurrentTime() && // liveness has not passed yet.
Copy link
Member

Choose a reason for hiding this comment

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

This should be <= instead of < based on how withdraw() handles comparising the request timestamp to getCurrentTime()

Copy link
Member

Choose a reason for hiding this comment

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

Hmmmm, why would we only do this positions that have not yet passed the liveness? Do we see this being meaningfully helpful to UX? I don't think it necessarily matters that much -- it just seems like it makes the casework more complex and harder to explain, so I think we'd need to have a good reason to add this complexity over just doing it in all withdrawal-based liquidations.

Copy link
Member Author

Choose a reason for hiding this comment

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

My main logic with this was if you had passed the liquidation liveness you should not be able to re-set this again. If this restriction was not added then a passed withdrawal request could have it's liveness re-set. This might actually be desired, in the case that the sponsor is slow to withdraw.

Copy link
Member

Choose a reason for hiding this comment

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

This should be <= instead of < based on how withdraw() handles comparising the request timestamp to getCurrentTime()

Any response to this?

Copy link
Member Author

Choose a reason for hiding this comment

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

This should be <= instead of < based on how withdraw() handles comparising the request timestamp to getCurrentTime()

Any response to this?

Yup. To be strictly consistent I agree with you.

);

// Create a subsequent liquidation partial and check that it also advances the withdrawal request timer
await liquidationContract.setCurrentTime(liquidationTime.add(liquidationLiveness.divn(2)).toString());
Copy link
Member

Choose a reason for hiding this comment

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

Can you add in a couple tests here for:

  • liquidations below minSponsorTokens should not advance the timer
  • once the withdraw request timetsamp has passed liveness, a liquidation above minSponsorTokens should not advance the timer

Copy link
Member Author

Choose a reason for hiding this comment

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

sure. done 1. Have not added one as we might remove this logic. will add a test for it, depending on how @mrice32 's comment is resolved

Copy link
Member

@nicholaspai nicholaspai left a comment

Choose a reason for hiding this comment

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

This is so much cleaner than the withdraw & GCR defense, realyl curious what @daywiss @kendricktan @mrice32 can think of in terms of adverse/unintended consequences

@coveralls
Copy link

coveralls commented Aug 17, 2020

Coverage Status

Coverage increased (+0.02%) to 92.704% when pulling fecf0b3 on chrismaree/liquidation-liveness-reset into 8aae10f on master.

Copy link
Member

@mrice32 mrice32 left a comment

Choose a reason for hiding this comment

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

Looks great! I agree with @nicholaspai that I think this could be a really clean solution to the whale issue. As discussed IRL, I think we probably want team feedback around this feature, especially from non-eng wrt how this might affect usability and ease of explanation for the withdraw request process.

if (
positionToLiquidate.withdrawalRequestPassTimestamp > 0 && // this is a subsequent liquidation.
positionToLiquidate.withdrawalRequestPassTimestamp < getCurrentTime() && // liveness has not passed yet.
maxTokensToLiquidate.isGreaterThan(minSponsorTokens) // above the minimum threshold.
Copy link
Member

Choose a reason for hiding this comment

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

I agree that we should be consistent here.

// then re-set the liveness. This enables a liquidation against a withdraw request to be "dragged out" if the position is very large.
if (
positionToLiquidate.withdrawalRequestPassTimestamp > 0 && // this is a subsequent liquidation.
positionToLiquidate.withdrawalRequestPassTimestamp < getCurrentTime() && // liveness has not passed yet.
Copy link
Member

Choose a reason for hiding this comment

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

Hmmmm, why would we only do this positions that have not yet passed the liveness? Do we see this being meaningfully helpful to UX? I don't think it necessarily matters that much -- it just seems like it makes the casework more complex and harder to explain, so I think we'd need to have a good reason to add this complexity over just doing it in all withdrawal-based liquidations.

@daywiss
Copy link
Contributor

daywiss commented Aug 17, 2020

so if i am understanding the intention behind this change, its that i can initiate a liquidation at a small price, essentially buying myself some more time to get more cash and send another liquidation, and so on, until the entire position can be liquidated?

just as some general thoughts an not with the intention of changing implementation:

  1. i feel like this feature should be capped at a max time limit or time scaled with respect to either the total position size or the new liquidation size. for instance larger positions have longer time limit, or smaller liquidations have shorter time extension. its probably ok to be unbounded because of costs but it does seem strange.

  2. im worried about side effects outside of the griefing one we identified. For instance could it be advantageous for someone to delay a large withdraw in order to get their own through quicker or to eat the capital with some unknown attack? Or maybe to get a better rate for minting? You guys probably have a better idea than me so I will go with your assessment. What helps is the delay is already pretty long without the reset, so most likely any attack could also happen within 2xliveness could happen in the first liveness period.

  3. agree with matt that we need eyes from outside eng to 👍 or 👎

i will defer to others in eng on the technical implementation of this

@nicholaspai
Copy link
Member

nicholaspai commented Aug 17, 2020

im worried about side effects outside of the griefing one we identified. For instance could it be advantageous for someone to delay a large withdraw in order to get their own through quicker or to eat the capital with some unknown attack

This would certainly be a factor if the contract were undercollateralized (i.e. every token is backed by < 100% of its value in collateral), but the contract being > 100% collateralized is a core assumption we make. In this weird state, sponsors would grief each other in order to "race" to get their collateral out of the contract.

i feel like this feature should be capped at a max time limit or time scaled with respect to either the total position size or the new liquidation size. for instance larger positions have longer time limit, or smaller liquidations have shorter time extension. its probably ok to be unbounded because of costs but it does seem strange.

Is the argument for a max time limit to protect sponsors from being griefed? The counter against a max time limit is that it sets a limit on the amount of time you can delay a malicious whale sponsor from withdrawing their collateral. However, we'd need to make sure to prevent the possibility that whales would split their positions amongst many smaller positions to take advantage of shortened extension times.

Independent of that, scaling the time extensions by position size is really interesting and makes sense intuitively (larger positions should get longer time extensions). Finally, should we consider increasing the withdrawal liveness by the existing withdrawal liveness plus some time? This way, malicious whales get penalized more for invalid withdrawals. A simple implementation would set withdrawRequestPassTimestamp = (currentWithdrawRequestPassTimestamp - currentTime) + withdrawLiveness

Signed-off-by: Christopher Maree <christopher.maree@gmail.com>
Signed-off-by: Christopher Maree <christopher.maree@gmail.com>
Signed-off-by: Christopher Maree <christopher.maree@gmail.com>
Copy link
Member

@mrice32 mrice32 left a comment

Choose a reason for hiding this comment

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

This is awesome! Very nice and minimal change :).

Just wanted to note some things we discussed IRL:

  1. We decided that the liveness inequality makes sense. Timer should not reset if the liveness has already passed.
  2. We decided that the min sponsor threshold is slightly inelegant, but makes sense.

I think this is good to go once we add a test for the post-liveness withdraw that we decided we would keep.

@mrice32 mrice32 changed the title feat(EMP contracts): Subsequent liquidations reset withdrawl timer feat(EMP contracts): Subsequent liquidations reset withdrawal timer Aug 18, 2020
@chrismaree chrismaree merged commit 9ac5269 into master Aug 20, 2020
@chrismaree chrismaree deleted the chrismaree/liquidation-liveness-reset branch August 20, 2020 11:43
@pyggie
Copy link

pyggie commented Aug 30, 2020

Each time a position has a liquidation submitted against the contracts check if there is a pending withdrawal request. If there is one, and it is not expired, then the sponsor's withdrawal request timestamp is re-set to the current time + the liquidation liveness.

What happens if the newly extended withdrawal pass timestamp is after expiration? Could repeated liquidation nibbles extend it indefinitely?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants