Skip to content

Commit

Permalink
feat: revised exit logic
Browse files Browse the repository at this point in the history
  • Loading branch information
jordaniza committed Sep 12, 2024
1 parent bbe295b commit 81c12cb
Show file tree
Hide file tree
Showing 10 changed files with 139 additions and 36 deletions.
2 changes: 1 addition & 1 deletion src/escrow/increasing/ExitQueue.sol
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ contract ExitQueue is IExitQueue, DaoAuthorizable, UUPSUpgradeable {
uint cooldownExpiry = block.timestamp + cooldown;

// if the next cp is after the cooldown, return the next cp
return nextCP > cooldownExpiry ? nextCP : cooldownExpiry;
return nextCP >= cooldownExpiry ? nextCP : cooldownExpiry;
}

/// @notice Exits the queue for that tokenID.
Expand Down
10 changes: 7 additions & 3 deletions src/escrow/increasing/QuadraticIncreasingEscrow.sol
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,10 @@ contract QuadraticIncreasingEscrow is
return bias.lt((0)) ? uint256(0) : SignedFixedPointMath.fromFP((bias)).toUint256();
}

function previewMaxBias(uint256 amount) external pure returns (uint256) {
return getBias(MAX_TIME, amount);
}

/*//////////////////////////////////////////////////////////////
Warmup
//////////////////////////////////////////////////////////////*/
Expand Down Expand Up @@ -274,7 +278,7 @@ contract QuadraticIncreasingEscrow is
/// @param _newLocked New locked amount / end lock time for the user
function _checkpoint(
uint256 _tokenId,
IVotingEscrow.LockedBalance memory, /* _oldLocked */
IVotingEscrow.LockedBalance memory /* _oldLocked */,
IVotingEscrow.LockedBalance memory _newLocked
) internal {
// this implementation doesn't yet support manual checkpointing
Expand All @@ -293,7 +297,7 @@ contract QuadraticIncreasingEscrow is
// write the new timestamp - in the case of an increasing curve
// we align the checkpoint to the start of the upcoming deposit interval
// to ensure global slope changes can be scheduled
// NOTE: the above global functionality is not implemented in this version of the contracts
// NOTE: the above global functionality is not implemented in this version of the contracts
uNew.ts = _newLocked.start;

// check to see if we have an existing epoch for this token
Expand All @@ -309,7 +313,7 @@ contract QuadraticIncreasingEscrow is

// if the user is exiting, we don't need to set the warmup period
if (!isExiting) {
_userPointWarmup[_tokenId][userEpoch] = block.timestamp + warmupPeriod;
_userPointWarmup[_tokenId][userEpoch] = block.timestamp + warmupPeriod;
}
}

Expand Down
3 changes: 1 addition & 2 deletions src/libs/CurveConstantLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@ pragma solidity ^0.8.0;
/// Below are the shared coefficients for the linear and quadratic terms
library CurveConstantLib {
/// @dev 2 / (7 * 2_weeks) - expressed in fixed point
int256 internal constant SHARED_LINEAR_COEFFICIENT = 236211000720;

int256 internal constant SHARED_LINEAR_COEFFICIENT = 236205593348;
/// @dev 1 / (7 * (2_weeks)^2) - expressed in fixed point
int256 internal constant SHARED_QUADRATIC_COEFFICIENT = 97637;

Expand Down
1 change: 1 addition & 0 deletions src/voting/ISimpleGaugeVoter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ interface IGaugeManagerEvents {
}

interface IGaugeManagerErrors {
error ZeroGauge();
error GaugeActivationUnchanged();
error GaugeExists();
}
Expand Down
1 change: 1 addition & 0 deletions src/voting/SimpleGaugeVoter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@ contract SimpleGaugeVoter is ISimpleGaugeVoter, ReentrancyGuard, Pausable, Plugi
address _gauge,
string calldata _metadata
) external auth(GAUGE_ADMIN_ROLE) nonReentrant returns (address gauge) {
if (_gauge == address(0)) revert ZeroGauge();
if (gaugeExists(_gauge)) revert GaugeExists();

gauges[_gauge] = Gauge(true, block.timestamp, bytes32(abi.encode(_metadata)));
Expand Down
26 changes: 19 additions & 7 deletions test/e2e.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,9 @@ contract TestE2E is Test, IWithdrawalQueueErrors, IGaugeVote, IEscrowCurveUserSt
function _withdraw() internal {
vm.startPrank(user);
{
vm.expectRevert(abi.encodeWithSelector(NotTicketHolder.selector));
uint atm = block.timestamp;

vm.expectRevert(NotTicketHolder.selector);
ve.withdraw(tokenId);

// reset votes to clear
Expand All @@ -117,15 +119,23 @@ contract TestE2E is Test, IWithdrawalQueueErrors, IGaugeVote, IEscrowCurveUserSt
assertEq(ve.balanceOf(address(ve)), 1, "VE should have the NFT");

// wait for 1 day
vm.warp(block.timestamp + 1 days);
vm.warp(atm + 1 days);

vm.expectRevert(abi.encodeWithSelector(CannotExit.selector));
vm.expectRevert(CannotExit.selector);
ve.withdraw(tokenId);

// the cooldown is not enough: we snap to the end of the voting period

// wait for the cooldown
vm.warp(block.timestamp + COOLDOWN - 1 days);
vm.warp(atm + COOLDOWN);

vm.expectRevert(CannotExit.selector);
ve.withdraw(tokenId);

// warp to the end of the voting period
vm.warp(atm + 1 weeks);
ve.withdraw(tokenId);

assertEq(ve.balanceOf(user), 0, "User not should have the token");
assertEq(ve.balanceOf(address(ve)), 0, "VE should not have the NFT");
assertEq(token.balanceOf(user), DEPOSIT, "User should have the tokens");
Expand Down Expand Up @@ -153,10 +163,12 @@ contract TestE2E is Test, IWithdrawalQueueErrors, IGaugeVote, IEscrowCurveUserSt
}
vm.stopPrank();

uint maxDeposit = curve.previewMaxBias(DEPOSIT);

// check, should be 25% of 6*DEPOSIT in the first gauge
// and 75% of 6*DEPOSIT in the second gauge
uint expectedFirst = (6 * DEPOSIT) / 4;
uint expectedSecond = (6 * DEPOSIT) - expectedFirst;
uint expectedFirst = maxDeposit / 4;
uint expectedSecond = maxDeposit - expectedFirst;

assertEq(voter.gaugeVotes(gaugeTheFirst), expectedFirst, "First gauge weight incorrect");
assertEq(voter.gaugeVotes(gaugeTheSecond), expectedSecond, "Second gauge weight incorrect");
Expand Down Expand Up @@ -271,7 +283,7 @@ contract TestE2E is Test, IWithdrawalQueueErrors, IGaugeVote, IEscrowCurveUserSt
vm.warp(start + curve.period() * 5 + 30);
assertEq(
curve.votingPowerAt(tokenId, block.timestamp),
6 * DEPOSIT,
5999967296216703996928,
"Balance incorrect after p6"
);
}
Expand Down
20 changes: 12 additions & 8 deletions test/escrow/curve/QuadraticCurveMath.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,9 @@ contract TestQuadraticIncreasingCurve is QuadraticCurveBase {
// warmup complete
vm.warp(block.timestamp + 1);

// excel: 449.206158900000000000
// solmate: 449.206133622001394300
// excel: 449.206158900000000000
// solmate: 449.206133622001394300
// solmate (optimized): 449.06723257244469756
assertEq(
curve.votingPowerAt(tokenIdFirst, block.timestamp),
449206133622001394300,
Expand All @@ -136,26 +137,29 @@ contract TestQuadraticIncreasingCurve is QuadraticCurveBase {
"Balance incorrect after p1"
);

uint256 expectedMaxI = 2524126241845405204467;
uint256 expectedMaxII = 5999967296216703996928705792;

// warp to the final period
// TECHNICALLY, this should finish at exactly 5 periods but
// 30 seconds off is okay
vm.warp(start + curve.period() * 5 + 30);
// TECHNICALLY, this should finish at exactly 5 periodd and 6 * voting power
// but FP arithmetic has a small rounding error
vm.warp(start + curve.period() * 5);
assertEq(
curve.votingPowerAt(tokenIdFirst, block.timestamp),
6 * depositFirst,
expectedMaxI,
"Balance incorrect after p6"
);
assertEq(
curve.votingPowerAt(tokenIdSecond, block.timestamp),
6 * depositSecond,
expectedMaxII,
"Balance incorrect after p6 II "
);

// warp to the future and balance should be the same
vm.warp(520 weeks);
assertEq(
curve.votingPowerAt(tokenIdFirst, block.timestamp),
6 * depositFirst,
expectedMaxI,
"Balance incorrect after 10 years"
);
}
Expand Down
78 changes: 67 additions & 11 deletions test/escrow/escrow/EscrowWithdraw.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -77,15 +77,15 @@ contract TestWithdraw is EscrowBase, IEscrowCurveUserStorage, IGaugeVote {
escrow.approve(_who, tokenId);

// must wait till end of queue
vm.warp(block.timestamp + queue.cooldown() - 1);
vm.warp(3 weeks - 1);
vm.expectRevert(CannotExit.selector);
vm.prank(_who);
escrow.withdraw(tokenId);

uint fee = queue.calculateFee(tokenId);

// withdraw
vm.warp(block.timestamp + queue.cooldown());
vm.warp(3 weeks);
vm.prank(_who);
vm.expectEmit(true, true, false, true);
emit Withdraw(_who, tokenId, _dep - fee, block.timestamp, 0);
Expand All @@ -97,20 +97,21 @@ contract TestWithdraw is EscrowBase, IEscrowCurveUserStorage, IGaugeVote {
// remainder sent to user
assertEq(token.balanceOf(_who), _dep - fee);

if (_fee == 0) {
bool feeDepTooSmall = uint(_fee) * uint(_dep) < 1e18;

if (_fee == 0 || feeDepTooSmall) {
assertEq(token.balanceOf(_who), _dep);
assertEq(token.balanceOf(address(queue)), 0);
} else {
console.log("fee", fee);
console.log("_fee", _fee);
console.log("_dep", _dep);
assertGt(token.balanceOf(address(queue)), 0);
}

// nft is burned
assertEq(escrow.balanceOf(_who), 0);
assertEq(escrow.balanceOf(address(escrow)), 0);
assertEq(escrow.totalLocked(), 0);

assertEq(escrow.votingPowerForAccount(_who), 0);
}

function testFuzz_enterWithdrawal(uint128 _dep, address _who) public {
Expand Down Expand Up @@ -149,14 +150,69 @@ contract TestWithdraw is EscrowBase, IEscrowCurveUserStorage, IGaugeVote {
assertEq(escrow.balanceOf(_who), 0);
assertEq(escrow.balanceOf(address(escrow)), 1);

// should be zero vp with the nft
assertEq(escrow.votingPower(tokenId), 0);
// voting power should still be there as the cp is still active
assertGt(escrow.votingPower(tokenId), 0);

// account voting power now zero
assertEq(escrow.votingPowerForAccount(_who), 0);
// but we should have written a user point in the future
UserPoint memory up = curve.userPointHistory(tokenId, 2);
assertEq(up.bias, 0);
assertEq(up.ts, 3 weeks);

// should have a ticket expiring in a few days
assertEq(queue.canExit(tokenId), false);
assertEq(queue.queue(tokenId).exitDate, block.timestamp + 3 days);
assertEq(queue.queue(tokenId).exitDate, 3 weeks);

// check the future to see the voting power expired
vm.warp(3 weeks + 1);
assertEq(escrow.votingPower(tokenId), 0);
}

function testCanWithdrawAfterCooldownOnlyIfCrossesWeekBoundary() public {
address _who = address(1);
uint128 _dep = 100e18;

vm.warp(2 weeks + 1);

token.mint(_who, _dep);
uint tokenId;
vm.startPrank(_who);
{
token.approve(address(escrow), _dep);
tokenId = escrow.createLock(_dep);

// voting active after cooldown
vm.warp(block.timestamp + 3 weeks - queue.cooldown() + 1);

// make a vote
voter.vote(tokenId, votes);

escrow.approve(address(escrow), tokenId);
escrow.resetVotesAndBeginWithdrawal(tokenId);
}
vm.stopPrank();

uint _now = block.timestamp;

// must wait till end of cooldown
vm.warp(3 weeks);
vm.expectRevert(CannotExit.selector);
vm.prank(_who);
escrow.withdraw(tokenId);

uint fee = queue.calculateFee(tokenId);

// withdraw
vm.warp(_now + queue.cooldown());
vm.prank(_who);
vm.expectEmit(true, true, false, true);
emit Withdraw(_who, tokenId, _dep - fee, block.timestamp, 0);
escrow.withdraw(tokenId);

// asserts
assertEq(token.balanceOf(address(queue)), fee);
assertEq(token.balanceOf(_who), _dep - fee);
assertEq(escrow.balanceOf(_who), 0);
assertEq(escrow.balanceOf(address(escrow)), 0);
assertEq(escrow.totalLocked(), 0);
}
}
21 changes: 17 additions & 4 deletions test/escrow/queue/ExitQueue.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -106,19 +106,31 @@ contract TestExitQueue is ExitQueueBase {
// test emits a queued event and writes to state
function testFuzz_canQueue(uint256 _tokenId, address _ticketHolder, uint32 _warp) public {
vm.assume(_ticketHolder != address(0));
vm.assume(_warp > 0); // any time other than genesis
vm.warp(_warp);

uint expectedExitDate = block.timestamp + queue.cooldown();
// if there are less than cooldown seconds left, exit date is end of the
// week, else it's now + cooldown

uint expectedExitDate;
uint remainingSecondsBeforeNextCP = 1 weeks - (block.timestamp % 1 weeks);
if (queue.cooldown() < remainingSecondsBeforeNextCP) {
expectedExitDate = block.timestamp + remainingSecondsBeforeNextCP;
} else {
expectedExitDate = block.timestamp + queue.cooldown();
}

vm.expectEmit(true, true, false, true);
emit ExitQueued(_tokenId, _ticketHolder, expectedExitDate);
queue.queueExit(_tokenId, _ticketHolder);
assertEq(queue.ticketHolder(_tokenId), _ticketHolder);
assertEq(queue.queue(_tokenId).exitDate, block.timestamp);
assertEq(queue.queue(_tokenId).exitDate, expectedExitDate);
}

// test can exit updates only after the cooldown period
function testFuzz_canExit(uint216 _cooldown) public {
vm.warp(0);

queue.setCooldown(_cooldown);

uint256 tokenId = 420;
Expand All @@ -129,10 +141,11 @@ contract TestExitQueue is ExitQueueBase {
assert(queue.canExit(tokenId));
} else {
assertFalse(queue.canExit(tokenId));
vm.warp(time + _cooldown - 1);

vm.warp(time + queue.nextExitDate() - 1);
assertFalse(queue.canExit(tokenId));

vm.warp(time + _cooldown);
vm.warp(time + queue.nextExitDate());
assertTrue(queue.canExit(tokenId));
}
}
Expand Down
Loading

0 comments on commit 81c12cb

Please sign in to comment.