Skip to content
Open
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
6 changes: 3 additions & 3 deletions .github/workflows/node.js.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ jobs:
matrix:
node-version: [12.x, 14.x]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/

env:
NODE_OPTIONS: "--max-old-space-size=8192"
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
Expand All @@ -27,8 +28,7 @@ jobs:
run: npm run lint && npm run prettier-check
- name: Running Test
run: npm run test
# - name: Increasing the memory
# run: export NODE_OPTIONS="--max-old-space-size=7168"

# - name: Code Coverage
# run: npm run coverage
# - name: Coveralls GitHub Action
Expand Down
41 changes: 41 additions & 0 deletions contracts/farm/ERC20TransferLogic.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
pragma solidity 0.5.17;

import "./IRewardTransferLogic.sol";
import "./ERC20TransferLogicStorage.sol";
import "../interfaces/IERC20.sol";
import "../openzeppelin/SafeERC20.sol";

contract ERC20TransferLogic is IRewardTransferLogic, ERC20TransferLogicStorage {
using SafeERC20 for IERC20;

event TokenAddressUpdated(address _newTokenAddress);

/**
* @param _token Reward token to be distributed
*/
function initialize(address _token) external onlyAuthorized {
setTokenAddress(_token);
}

function setTokenAddress(address _token) public onlyAuthorized {
require(_token != address(0), "Invalid token address");
token = IERC20(_token);
emit TokenAddressUpdated(_token);
}

function getRewardTokenAddress() external view returns (address) {
return address(token);
}

function senderToAuthorize() external view returns (address) {
return address(this);
}

function transferReward(
address _to,
uint256 _value,
bool // it doesn't matter if it's a withdrawal or not
) external {
token.safeTransferFrom(msg.sender, _to, _value);
}
}
9 changes: 9 additions & 0 deletions contracts/farm/ERC20TransferLogicStorage.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
pragma solidity 0.5.17;

import "./IRewardTransferLogic.sol";
import "../utils/AdminRole.sol";
import "../interfaces/IERC20.sol";

contract ERC20TransferLogicStorage is IRewardTransferLogic, AdminRole {
IERC20 public token;
}
40 changes: 40 additions & 0 deletions contracts/farm/ILiquidityMiningV1.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
pragma solidity 0.5.17;

interface ILiquidityMiningV1 {
function withdraw(
address _poolToken,
uint256 _amount,
address _user
) external;

function onTokensDeposited(address _user, uint256 _amount) external;

function getUserPoolTokenBalance(address _poolToken, address _user) external view returns (uint256);

function getPoolInfoListArray()
external
view
returns (
address[] memory,
uint96[] memory,
uint256[] memory,
uint256[] memory
);

function getUserInfoListArray(address _user)
external
view
returns (
uint256[] memory,
uint256[] memory,
uint256[] memory
);

function migrateFunds() external;

function finishMigrationGracePeriod() external;

function getTotalUsersBalance() external view returns (uint256);

function getStartBlock() external view returns (uint256);
}
44 changes: 44 additions & 0 deletions contracts/farm/ILiquidityMiningV2.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
pragma solidity 0.5.17;

interface ILiquidityMiningV2 {
function withdraw(
address _poolToken,
uint256 _amount,
address _user
) external;

function onTokensDeposited(address _user, uint256 _amount) external;

function getUserPoolTokenBalance(address _poolToken, address _user) external view returns (uint256);

function setPoolInfoRewardToken(
address _poolToken,
address _rewardToken,
uint256 _lastRewardBlock,
uint256 _accumulatedRewardPerShare
) external;

function setRewardToken(
address _rewardToken,
uint256 _startBlock,
uint256 _totalUsersBalance
) external;

function setUserInfo(
uint256 _poolId,
address _user,
address _rewardToken,
uint256 _amount,
uint256 _rewardDebt,
uint256 _accumulatedReward
) external;

function add(
address _poolToken,
address[] calldata _rewardTokens,
uint96[] calldata _allocationPoints,
bool _withUpdate
) external;

function finishMigration() external;
}
22 changes: 22 additions & 0 deletions contracts/farm/IRewardTransferLogic.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
pragma solidity 0.5.17;

/// @title This interface helps decoupling the Liquidity Mining reward
/// @dev Implement this interface in order to transfer the rewards with different logic. For example:
/// SOV tokens
interface IRewardTransferLogic {
/// @dev Returns the reward token address this contract will transfer
function getRewardTokenAddress() external view returns (address);

/// @notice Transfers will be executed from this address so it must be approved before invoking
function senderToAuthorize() external view returns (address);

/// @notice Transfers the reward amount to the specified address
/// @param _to The address to transfer the reward to
/// @param _value The amount of the reward to transfer
/// @param _isWithdrawal If true, means that the reward and the LP deposited tokens are being compeltely withdrawn
function transferReward(
address _to,
uint256 _value,
bool _isWithdrawal
) external;
}
154 changes: 154 additions & 0 deletions contracts/farm/LMV1toLMV2Migrator.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
pragma solidity 0.5.17;
pragma experimental ABIEncoderV2;

import "../openzeppelin/ERC20.sol";
import "../openzeppelin/SafeERC20.sol";
import "../openzeppelin/SafeMath.sol";
import "../utils/AdminRole.sol";
import "./ILiquidityMiningV1.sol";
import "./ILiquidityMiningV2.sol";

contract LMV1toLMV2Migrator is AdminRole {
using SafeMath for uint256;
using SafeERC20 for IERC20;
enum MigrationStates { MigratingPools, MigratingUsers, MigratingFunds, MigrationFinished }

//represents de migration state from LiquidityMiningV1 to LiquidityMiningV2
MigrationStates public migrationState;

//LiquidityMiningV1 contract address
ILiquidityMiningV1 public liquidityMiningV1;

//LiquidityMiningV2 contract address
ILiquidityMiningV2 public liquidityMiningV2;

/// @dev it is true if the user has been already migrated
mapping(address => bool) public userMigrated;

/// @dev The SOV token
IERC20 public SOV;

event UserMigrated(address indexed user);

/* Modifiers */
modifier onlyPoolsMigrationState() {
require(migrationState == MigrationStates.MigratingPools, "Wrong state: should be MigratingPools");
_;
}

modifier onlyUsersMigrationState() {
require(migrationState == MigrationStates.MigratingUsers, "Wrong state: should be MigratingUsers");
_;
}

modifier onlyFundsMigrationState() {
require(migrationState == MigrationStates.MigratingFunds, "Wrong state: should be MigratingFunds");
_;
}

/**
* @notice Initialize migrator
*
* @param _SOV The SOV token address
* @param _liquidityMiningV1 The LiquidityMiningV1 contract address
* @param _liquidityMiningV2 The LiquidityMiningV2 contract address
*/
function initialize(
IERC20 _SOV,
ILiquidityMiningV1 _liquidityMiningV1,
ILiquidityMiningV2 _liquidityMiningV2
) external onlyAuthorized {
require(address(_SOV) != address(0), "invalid token address");
require(address(_liquidityMiningV1) != address(0), "invalid contract address");
require(address(_liquidityMiningV2) != address(0), "invalid contract address");
require(address(SOV) == address(0), "Already initialized");
liquidityMiningV1 = _liquidityMiningV1;
liquidityMiningV2 = _liquidityMiningV2;
SOV = _SOV;
migrationState = MigrationStates.MigratingPools;
}

function _finishPoolsMigration() internal onlyPoolsMigrationState {
migrationState = MigrationStates.MigratingUsers;
}

function finishUsersMigration() external onlyAuthorized onlyUsersMigrationState {
migrationState = MigrationStates.MigratingFunds;
}

function _finishFundsMigration() internal onlyFundsMigrationState {
migrationState = MigrationStates.MigrationFinished;
}

/**
* @notice read all pools from liquidity mining V1 contract and add them
*/
function migratePools() external onlyAuthorized onlyPoolsMigrationState {
(
address[] memory _poolToken,
uint96[] memory _allocationPoints,
uint256[] memory _lastRewardBlock,
uint256[] memory _accumulatedRewardPerShare
) = liquidityMiningV1.getPoolInfoListArray();

require(_poolToken.length == _allocationPoints.length, "Arrays mismatch");
require(_poolToken.length == _lastRewardBlock.length, "Arrays mismatch");

_finishPoolsMigration();
liquidityMiningV1.finishMigrationGracePeriod();
for (uint256 i = 0; i < _poolToken.length; i++) {
address poolToken = _poolToken[i];
uint96[] memory allocationPoints = new uint96[](1);
allocationPoints[0] = _allocationPoints[i];
uint256 lastRewardBlock = _lastRewardBlock[i];
uint256 accumulatedRewardPerShare = _accumulatedRewardPerShare[i];
address[] memory SOVAddress = new address[](1);
SOVAddress[0] = address(SOV);
//add will revert if poolToken is invalid or if it was already added
liquidityMiningV2.add(poolToken, SOVAddress, allocationPoints, false);
//add pool function put lastRewardBlock with current block number value, so we need to retrieve the original
liquidityMiningV2.setPoolInfoRewardToken(poolToken, address(SOV), lastRewardBlock, accumulatedRewardPerShare);
}
uint256 _startblock = liquidityMiningV1.getStartBlock();
uint256 _totalUsersBalance = liquidityMiningV1.getTotalUsersBalance();
liquidityMiningV2.setRewardToken(address(SOV), _startblock, _totalUsersBalance);
}

/**
* @notice read all users of all the pools from liquidity mining V1 contract and copy their info
* @param _users a list of users to be copied
*/

function migrateUsers(address[] calldata _users) external onlyAuthorized onlyUsersMigrationState {
for (uint256 i = 0; i < _users.length; i++) {
(uint256[] memory _amount, uint256[] memory _rewardDebt, uint256[] memory _accumulatedReward) =
liquidityMiningV1.getUserInfoListArray(_users[i]);

require(_amount.length == _rewardDebt.length, "Arrays mismatch");
require(_amount.length == _accumulatedReward.length, "Arrays mismatch");

address user = _users[i];

if (!userMigrated[user]) {
userMigrated[user] = true;
for (uint256 j = 0; j < _amount.length; j++) {
uint256 poolId = j;
uint256 _userAmount = _amount[j];
uint256 _userRewardDebt = _rewardDebt[j];
uint256 _userAccumulatedReward = _accumulatedReward[j];
liquidityMiningV2.setUserInfo(poolId, user, address(SOV), _userAmount, _userRewardDebt, _userAccumulatedReward);
}
emit UserMigrated(user);
}
}
}

/**
* @notice transfer all funds from liquidity mining V1
*/
function migrateFunds() external onlyAuthorized onlyFundsMigrationState {
_finishFundsMigration();
liquidityMiningV1.migrateFunds();
liquidityMiningV2.finishMigration();
}
}
11 changes: 11 additions & 0 deletions contracts/farm/LiquidityMiningProxyV2.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
pragma solidity ^0.5.17;

import "./LiquidityMiningStorageV2.sol";
import "../proxy/UpgradableProxy.sol";

/**
* @dev LiquidityMining contract should be upgradable, use UpgradableProxy
*/
contract LiquidityMiningProxyV2 is LiquidityMiningStorageV2, UpgradableProxy {

}
18 changes: 18 additions & 0 deletions contracts/farm/LiquidityMiningStorageV1.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
pragma solidity 0.5.17;

import "./LiquidityMiningStorage.sol";

contract LiquidityMiningStorageV1 is LiquidityMiningStorage {
/// @dev Careful when adding new states as there is a < comparison being used in the modifiers
enum MigrationGracePeriodStates {
None,
Started, // users can withdraw funds and rewards but not deposit
Finished // users can't operate with the contract
}

/// @dev Represents migration grace period state
MigrationGracePeriodStates public migrationGracePeriodState;

/// @dev liquidity mining V2 contract address
address public liquidityMiningV2;
}
Loading