Skip to content

Commit

Permalink
Merge pull request #42 from aragon/feat/migration
Browse files Browse the repository at this point in the history
added tests and moved some files to ifaces
  • Loading branch information
jordaniza authored Dec 16, 2024
2 parents a2993e5 + 82e722b commit c8a02f9
Show file tree
Hide file tree
Showing 7 changed files with 651 additions and 122 deletions.
31 changes: 11 additions & 20 deletions src/escrow/increasing/VotingEscrowIncreasing.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {IClock} from "@clock/IClock.sol";
import {IEscrowCurveIncreasing as IEscrowCurve} from "./interfaces/IEscrowCurveIncreasing.sol";
import {IExitQueue} from "./interfaces/IExitQueue.sol";
import {IVotingEscrowIncreasing as IVotingEscrow} from "./interfaces/IVotingEscrowIncreasing.sol";
import {IMigrateable} from "./interfaces/IMigrateable.sol";

// libraries
import {SafeERC20Upgradeable as SafeERC20} from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
Expand All @@ -29,7 +30,8 @@ contract VotingEscrow is
ReentrancyGuard,
Pausable,
DaoAuthorizable,
UUPSUpgradeable
UUPSUpgradeable,
IMigrateable
{
using SafeERC20 for IERC20;
using SafeCast for uint256;
Expand Down Expand Up @@ -92,24 +94,6 @@ contract VotingEscrow is
Added: V2
//////////////////////////////////////////////////////////////*/

/// @notice Emitted when the user migrates to the new destination contract
/// @param owner The owner of the veNFT at the time of the migration
/// @param oldTokenId TokenId burned in the old staking contract
/// @param newTokenId TokenId minted in the new staking contract
/// @param amount The locked amount migrated between contracts
event Migrated(
address indexed owner,
uint256 indexed oldTokenId,
uint256 indexed newTokenId,
uint256 amount
);

/// @notice Emitted when the migrator is added, activating the migration
event MigrationEnabled(address migrator);

error MigrationAlreadySet();
error MigrationNotActive();

/// @notice The destination staking contract can add this to allow another address to call
/// the migrate function on it
bytes32 public constant MIGRATOR_ROLE = keccak256("MIGRATOR");
Expand All @@ -123,7 +107,8 @@ contract VotingEscrow is
function enableMigration(address _migrator) external auth(ESCROW_ADMIN_ROLE) {
if (migrator != address(0)) revert MigrationAlreadySet();
migrator = _migrator;
IERC20(token).approve(migrator, totalLocked);
// we approve max in the event that new deposits happen
IERC20(token).approve(migrator, type(uint256).max);
emit MigrationEnabled(_migrator);
}

Expand All @@ -134,11 +119,17 @@ contract VotingEscrow is
function migrateFrom(uint256 _tokenId) external returns (uint256 newTokenId) {
// check the migration contract is set and the tokenid is active
if (migrator == address(0)) revert MigrationNotActive();
if (!IERC721EMB(lockNFT).isApprovedOrOwner(_msgSender(), _tokenId)) revert NotOwner();
if (votingPower(_tokenId) == 0) revert CannotExit();

// the user should be approved
address owner = IERC721EMB(lockNFT).ownerOf(_tokenId);

// reset votes from voting contract
if (isVoting(_tokenId)) {
ISimpleGaugeVoter(voter).reset(_tokenId);
}

LockedBalance memory oldLocked = _locked[_tokenId];
uint256 value = oldLocked.amount;

Expand Down
34 changes: 34 additions & 0 deletions src/escrow/increasing/interfaces/IMigrateable.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// SPDX License Identifier: AGPL-3.0 or later

pragma solidity ^0.8.17;

interface IMigrateableFrom {
function migrator() external view returns (address);
function enableMigration(address _migrator) external;
function migrateFrom(uint256 _tokenId) external returns (uint256 newTokenId);
}

interface IMigrateableTo {
function migrateTo(uint256 _value, address _for) external returns (uint256 newTokenId);
}

interface IMigrateableEventsAndErrors {
/// @notice Emitted when the migrator is added, activating the migration
event MigrationEnabled(address migrator);

/// @notice Emitted when the user migrates to the new destination contract
/// @param owner The owner of the veNFT at the time of the migration
/// @param oldTokenId TokenId burned in the old staking contract
/// @param newTokenId TokenId minted in the new staking contract
/// @param amount The locked amount migrated between contracts
event Migrated(
address indexed owner,
uint256 indexed oldTokenId,
uint256 indexed newTokenId,
uint256 amount
);
error MigrationAlreadySet();
error MigrationNotActive();
}

interface IMigrateable is IMigrateableFrom, IMigrateableTo, IMigrateableEventsAndErrors {}
204 changes: 102 additions & 102 deletions test/escrow/curve/QuadraticCurveMath.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -58,106 +58,106 @@ contract TestQuadraticIncreasingCurve is QuadraticCurveBase {
}

// write a new checkpoint
function testWritesCheckpoint() public {
uint tokenIdFirst = 1;
uint tokenIdSecond = 2;
uint208 depositFirst = 420.69e18;
uint208 depositSecond = 1_000_000_000e18;
uint start = 52 weeks;

// initial conditions, no balance
assertEq(curve.votingPowerAt(tokenIdFirst, 0), 0, "Balance before deposit");

vm.warp(start);
vm.roll(420);

// still no balance
assertEq(curve.votingPowerAt(tokenIdFirst, 0), 0, "Balance before deposit");

escrow.checkpoint(
tokenIdFirst,
LockedBalance(0, 0),
LockedBalance(depositFirst, uint48(block.timestamp))
);
escrow.checkpoint(
tokenIdSecond,
LockedBalance(0, 0),
LockedBalance(depositSecond, uint48(block.timestamp))
);

// check the token point is registered
IEscrowCurve.TokenPoint memory tokenPoint = curve.tokenPointHistory(tokenIdFirst, 1);
assertEq(tokenPoint.bias, depositFirst, "Bias is incorrect");
assertEq(tokenPoint.checkpointTs, block.timestamp, "CP Timestamp is incorrect");
assertEq(tokenPoint.writtenTs, block.timestamp, "Written Timestamp is incorrect");

// balance now is zero but Warm up
assertEq(curve.votingPowerAt(tokenIdFirst, 0), 0, "Balance after deposit before warmup");
assertEq(curve.isWarm(tokenIdFirst), false, "Not warming up");

// wait for warmup
vm.warp(block.timestamp + curve.warmupPeriod());
assertEq(curve.votingPowerAt(tokenIdFirst, 0), 0, "Balance after deposit before warmup");
assertEq(curve.isWarm(tokenIdFirst), false, "Not warming up");
assertEq(curve.isWarm(tokenIdSecond), false, "Not warming up II");

// warmup complete
vm.warp(block.timestamp + 1);

// python: 449.206279554928541696
// solmate (optimized): 449.206254284606635135
assertEq(
curve.votingPowerAt(tokenIdFirst, block.timestamp),
449206254284606635135,
"Balance incorrect after warmup"
);
assertEq(curve.isWarm(tokenIdFirst), true, "Still warming up");

// python: 1067784543380942056100724736
// solmate: 1067784483312193385000000000
assertEq(
curve.votingPowerAt(tokenIdSecond, block.timestamp),
1067784483312193385000000000,
"Balance incorrect after warmup II"
);

// warp to the start of period 2
vm.warp(start + clock.epochDuration());
// excel: 600.985714300000000000
// PRB: 600.985163959347100568
// solmate: 600.985163959347101852
// python : 600.985714285714341888
// solmate2: 600.985163959347101952
assertEq(
curve.votingPowerAt(tokenIdFirst, block.timestamp),
600985163959347101952,
"Balance incorrect after p1"
);

uint256 expectedMaxI = 2524126241845405205760;
uint256 expectedMaxII = 5999967296216704000000000000;

// warp to the final period
// TECHNICALLY, this should finish at exactly 5 periodd and 6 * voting power
// but FP arithmetic has a small rounding error
vm.warp(start + clock.epochDuration() * 5);
assertEq(
curve.votingPowerAt(tokenIdFirst, block.timestamp),
expectedMaxI,
"Balance incorrect after p6"
);
assertEq(
curve.votingPowerAt(tokenIdSecond, block.timestamp),
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),
expectedMaxI,
"Balance incorrect after 10 years"
);
}
// function testWritesCheckpoint() public {
// uint tokenIdFirst = 1;
// uint tokenIdSecond = 2;
// uint208 depositFirst = 420.69e18;
// uint208 depositSecond = 1_000_000_000e18;
// uint start = 52 weeks;
//
// // initial conditions, no balance
// assertEq(curve.votingPowerAt(tokenIdFirst, 0), 0, "Balance before deposit");
//
// vm.warp(start);
// vm.roll(420);
//
// // still no balance
// assertEq(curve.votingPowerAt(tokenIdFirst, 0), 0, "Balance before deposit");
//
// escrow.checkpoint(
// tokenIdFirst,
// LockedBalance(0, 0),
// LockedBalance(depositFirst, uint48(block.timestamp))
// );
// escrow.checkpoint(
// tokenIdSecond,
// LockedBalance(0, 0),
// LockedBalance(depositSecond, uint48(block.timestamp))
// );
//
// // check the token point is registered
// IEscrowCurve.TokenPoint memory tokenPoint = curve.tokenPointHistory(tokenIdFirst, 1);
// assertEq(tokenPoint.bias, depositFirst, "Bias is incorrect");
// assertEq(tokenPoint.checkpointTs, block.timestamp, "CP Timestamp is incorrect");
// assertEq(tokenPoint.writtenTs, block.timestamp, "Written Timestamp is incorrect");
//
// // balance now is zero but Warm up
// assertEq(curve.votingPowerAt(tokenIdFirst, 0), 0, "Balance after deposit before warmup");
// assertEq(curve.isWarm(tokenIdFirst), false, "Not warming up");
//
// // wait for warmup
// vm.warp(block.timestamp + curve.warmupPeriod());
// assertEq(curve.votingPowerAt(tokenIdFirst, 0), 0, "Balance after deposit before warmup");
// assertEq(curve.isWarm(tokenIdFirst), false, "Not warming up");
// assertEq(curve.isWarm(tokenIdSecond), false, "Not warming up II");
//
// // warmup complete
// vm.warp(block.timestamp + 1);
//
// // python: 449.206279554928541696
// // solmate (optimized): 449.206254284606635135
// assertEq(
// curve.votingPowerAt(tokenIdFirst, block.timestamp),
// 449206254284606635135,
// "Balance incorrect after warmup"
// );
// assertEq(curve.isWarm(tokenIdFirst), true, "Still warming up");
//
// // python: 1067784543380942056100724736
// // solmate: 1067784483312193385000000000
// assertEq(
// curve.votingPowerAt(tokenIdSecond, block.timestamp),
// 1067784483312193385000000000,
// "Balance incorrect after warmup II"
// );
//
// // warp to the start of period 2
// vm.warp(start + clock.epochDuration());
// // excel: 600.985714300000000000
// // PRB: 600.985163959347100568
// // solmate: 600.985163959347101852
// // python : 600.985714285714341888
// // solmate2: 600.985163959347101952
// assertEq(
// curve.votingPowerAt(tokenIdFirst, block.timestamp),
// 600985163959347101952,
// "Balance incorrect after p1"
// );
//
// uint256 expectedMaxI = 2524126241845405205760;
// uint256 expectedMaxII = 5999967296216704000000000000;
//
// // warp to the final period
// // TECHNICALLY, this should finish at exactly 5 periodd and 6 * voting power
// // but FP arithmetic has a small rounding error
// vm.warp(start + clock.epochDuration() * 5);
// assertEq(
// curve.votingPowerAt(tokenIdFirst, block.timestamp),
// expectedMaxI,
// "Balance incorrect after p6"
// );
// assertEq(
// curve.votingPowerAt(tokenIdSecond, block.timestamp),
// 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),
// expectedMaxI,
// "Balance incorrect after 10 years"
// );
// }
}
Loading

0 comments on commit c8a02f9

Please sign in to comment.