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
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,17 @@ contract Liquidatable is PricelessPositionManager {
})
);

// If this liquidation is a subsequent liquidation on the position, and the liquidation size is larger than
// minSponsorTokens, 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.
maxTokensToLiquidate.isGreaterThanOrEqual(minSponsorTokens) // above the minimum threshold.
) {
positionToLiquidate.withdrawalRequestPassTimestamp = getCurrentTime().add(liquidationLiveness);
}

emit LiquidationCreated(
sponsor,
msg.sender,
Expand Down
94 changes: 94 additions & 0 deletions packages/core/test/financial-templates/Liquidatable.js
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,100 @@ contract("Liquidatable", function(accounts) {
)
);
});
it("Multiple partial liquidations re-set liveness timer on withdrawal requests", async () => {
// Request a withdrawal.
const withdrawalAmount = amountOfSynthetic.divn(5);
await liquidationContract.requestWithdrawal({ rawValue: withdrawalAmount.toString() }, { from: sponsor });

const startingTime = await liquidationContract.getCurrentTime();
let expectedTimestamp = toBN(startingTime)
.add(liquidationLiveness)
.toString();

assert(
expectedTimestamp,
(await liquidationContract.positions(sponsor)).withdrawalRequestPassTimestamp.toString()
);

// Advance time by half of the liveness duration.
await liquidationContract.setCurrentTime(startingTime.add(liquidationLiveness.divn(2)).toString());

await liquidationContract.createLiquidation(
sponsor,
{ rawValue: "0" },
{ rawValue: pricePerToken.toString() },
{ rawValue: amountOfSynthetic.divn(5).toString() },
unreachableDeadline,
{ from: liquidator }
);

// After the liquidation the liveness timer on the withdrawl request should be re-set to the current time +
// the liquidation liveness. This opens the position up to having a subsequent liquidation, if need be.
const liquidation1Time = await liquidationContract.getCurrentTime();
assert(
liquidation1Time.add(liquidationLiveness).toString(),
(await liquidationContract.positions(sponsor)).withdrawalRequestPassTimestamp.toString()
);

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

await liquidationContract.createLiquidation(
sponsor,
{ rawValue: "0" },
{ rawValue: pricePerToken.toString() },
{ rawValue: amountOfSynthetic.divn(5).toString() },
unreachableDeadline,
{ from: liquidator }
);

// Again, verify this is offset correctly.
const liquidation2Time = await liquidationContract.getCurrentTime();
const expectedWithdrawalRequestPassTimestamp = liquidation2Time.add(liquidationLiveness).toString();
assert(
expectedWithdrawalRequestPassTimestamp,
(await liquidationContract.positions(sponsor)).withdrawalRequestPassTimestamp.toString()
);

// Submitting a liquidation less than the minimum sponsor size should not advance the timer. Start by advancing
// time by half of the liquidation liveness.
await liquidationContract.setCurrentTime(liquidation2Time.add(liquidationLiveness.divn(2)).toString());
await liquidationContract.createLiquidation(
sponsor,
{ rawValue: "0" },
{ rawValue: pricePerToken.toString() },
{ rawValue: minSponsorTokens.divn(2).toString() }, // half of the min size. Should not increment timer.
unreachableDeadline,
{ from: liquidator }
);

// Check that the timer has not re-set. expectedWithdrawalRequestPassTimestamp was set after the previous
// liquidation (before incrementing the time).

assert(
expectedWithdrawalRequestPassTimestamp,
(await liquidationContract.positions(sponsor)).withdrawalRequestPassTimestamp.toString()
);

// Advance timer again to place time after liquidation liveness.
await liquidationContract.setCurrentTime(liquidation2Time.add(liquidationLiveness).toString());

// Now, submitting a withdrawal request should NOT reset liveness (sponsor has passed liveness duration).
await liquidationContract.createLiquidation(
sponsor,
{ rawValue: "0" },
{ rawValue: pricePerToken.toString() },
{ rawValue: amountOfSynthetic.divn(5).toString() },
unreachableDeadline,
{ from: liquidator }
);

// Check that the time has not advanced.
assert(
expectedWithdrawalRequestPassTimestamp,
(await liquidationContract.positions(sponsor)).withdrawalRequestPassTimestamp.toString()
);
});
});

describe("Full liquidation has been created", () => {
Expand Down