From 7cf3d68230d49f40d180399c462e1c2e330bfec5 Mon Sep 17 00:00:00 2001 From: jordaniza Date: Fri, 13 Dec 2024 17:51:13 +0400 Subject: [PATCH 1/2] feat: migration functionality --- .../increasing/VotingEscrowIncreasing.sol | 81 +++++++++++++++++++ src/libs/CurveConstantLib.sol | 15 ++-- 2 files changed, 86 insertions(+), 10 deletions(-) diff --git a/src/escrow/increasing/VotingEscrowIncreasing.sol b/src/escrow/increasing/VotingEscrowIncreasing.sol index 5da0f60..ff06b9e 100644 --- a/src/escrow/increasing/VotingEscrowIncreasing.sol +++ b/src/escrow/increasing/VotingEscrowIncreasing.sol @@ -88,6 +88,87 @@ contract VotingEscrow is bool private _lockNFTSet; + /*////////////////////////////////////////////////////////////// + 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"); + + /// @notice destination migration contract + address public migrator; + + /// @notice The Escrow Admin can enable migrations by setting a destination migration contract. + /// @dev This function also approves all tokens in this contract to be transferred to the new migrator. + /// @param _migrator The address of the destination migration contract + function enableMigration(address _migrator) external auth(ESCROW_ADMIN_ROLE) { + if (migrator != address(0)) revert MigrationAlreadySet(); + migrator = _migrator; + IERC20(token).approve(migrator, totalLocked); + emit MigrationEnabled(_migrator); + } + + /// @notice Defined on the staking contract being exited from - burn the tokenId and mint a new one. + /// @dev Skips withdrawal queue logic and vote resets + /// @param _tokenId veNFT to migrate from + /// @return newTokenId veNFT created during the migrationg + 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 (votingPower(_tokenId) == 0) revert CannotExit(); + + // the user should be approved + address owner = IERC721EMB(lockNFT).ownerOf(_tokenId); + + LockedBalance memory oldLocked = _locked[_tokenId]; + uint256 value = oldLocked.amount; + + // burn the current veNFT and write a zero checkpoint. + _locked[_tokenId] = LockedBalance(0, 0); + totalLocked -= value; + _checkpointClear(_tokenId); + IERC721EMB(lockNFT).burn(_tokenId); + + // createLockFor on the new contract for the owner of the veNFT + newTokenId = VotingEscrow(migrator).migrateTo(value, owner); + + // emit the migrated event + emit Migrated(owner, _tokenId, newTokenId, value); + + return newTokenId; + } + + /// @notice Defined on the destination staking contract. Creates a new veNFT with the old params + /// @dev Skips validations like pause, allowing migration ahead of general release. + /// @param _value The amount of underlying token to be migrated. + /// @param _for The original owner of the lock + /// @return newTokenId the veNFT on the destination staking contract + function migrateTo( + uint256 _value, + address _for + ) external nonReentrant auth(MIGRATOR_ROLE) returns (uint256 newTokenId) { + return _createLockFor(_value, _for); + } + /*////////////////////////////////////////////////////////////// Initialization //////////////////////////////////////////////////////////////*/ diff --git a/src/libs/CurveConstantLib.sol b/src/libs/CurveConstantLib.sol index 9f4fbde..e25dcf0 100644 --- a/src/libs/CurveConstantLib.sol +++ b/src/libs/CurveConstantLib.sol @@ -3,19 +3,14 @@ pragma solidity ^0.8.0; /// @title CurveConstantLib /// @notice Precomputed coefficients for escrow curve -/// This curve implementation is a quadratic curve of the form y = (1/7)t^2 + (2/7)t + 1 -/// Which is a transformation of the quadratic curve y = (x^2 + 6)/7 -/// That starts with 1 unit of voting in period 1, and max 6 in period 6. -/// To use this in zero indexed time, with a per-second rate of increase, -/// we transform this to the polynomial y = (1/7)t^2 + (2/7)t + 1 -/// where t = timestamp / 2_weeks (2 weeks is one period) -/// Below are the shared coefficients for the linear and quadratic terms +/// That starts with 1 unit of voting in period 1, and max 6 in period 6, increasing linearly library CurveConstantLib { int256 internal constant SHARED_CONSTANT_COEFFICIENT = 1e18; - /// @dev 2 / (7 * 2_weeks) - expressed in fixed point + /// @dev rate of increase per second expressed in fixed point int256 internal constant SHARED_LINEAR_COEFFICIENT = 236205593348; - /// @dev 1 / (7 * (2_weeks)^2) - expressed in fixed point - int256 internal constant SHARED_QUADRATIC_COEFFICIENT = 97637; + + /// @dev linear curve with zero quadratic term + int256 internal constant SHARED_QUADRATIC_COEFFICIENT = 0; /// @dev the maxiumum number of epochs the cure can keep increasing uint256 internal constant MAX_EPOCHS = 5; From 585d9b8a3512847c75da13e712cb0a3b86f44184 Mon Sep 17 00:00:00 2001 From: jordaniza Date: Fri, 13 Dec 2024 17:56:47 +0400 Subject: [PATCH 2/2] update curve coeff and storage gap --- src/escrow/increasing/VotingEscrowIncreasing.sol | 3 ++- src/libs/CurveConstantLib.sol | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/escrow/increasing/VotingEscrowIncreasing.sol b/src/escrow/increasing/VotingEscrowIncreasing.sol index ff06b9e..e0a34cb 100644 --- a/src/escrow/increasing/VotingEscrowIncreasing.sol +++ b/src/escrow/increasing/VotingEscrowIncreasing.sol @@ -489,5 +489,6 @@ contract VotingEscrow is function _authorizeUpgrade(address) internal virtual override auth(ESCROW_ADMIN_ROLE) {} /// @dev Reserved storage space to allow for layout changes in the future. - uint256[39] private __gap; + /// @dev V2: -1 slot for migrator contract + uint256[38] private __gap; } diff --git a/src/libs/CurveConstantLib.sol b/src/libs/CurveConstantLib.sol index e25dcf0..fedafb5 100644 --- a/src/libs/CurveConstantLib.sol +++ b/src/libs/CurveConstantLib.sol @@ -7,7 +7,7 @@ pragma solidity ^0.8.0; library CurveConstantLib { int256 internal constant SHARED_CONSTANT_COEFFICIENT = 1e18; /// @dev rate of increase per second expressed in fixed point - int256 internal constant SHARED_LINEAR_COEFFICIENT = 236205593348; + int256 internal constant SHARED_LINEAR_COEFFICIENT = 826719576719; /// @dev linear curve with zero quadratic term int256 internal constant SHARED_QUADRATIC_COEFFICIENT = 0;