Skip to content

Commit

Permalink
Merge pull request #13 from swamp-thing-sovryn/multiple-LM-tokens-rew…
Browse files Browse the repository at this point in the history
…ards

feat: new liquidity mining that rewards in multiple tokens
  • Loading branch information
swamp-thing-sovryn authored Sep 18, 2021
2 parents 51f8507 + 9e0416b commit 37d79d0
Show file tree
Hide file tree
Showing 38 changed files with 8,594 additions and 2 deletions.
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) public 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;
}
153 changes: 153 additions & 0 deletions contracts/farm/LMV1toLMV2Migrator.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
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
) public 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");
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");

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);
_finishPoolsMigration();
}

/**
* @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] == false) {
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 {
liquidityMiningV1.migrateFunds();
liquidityMiningV2.finishMigration();
_finishFundsMigration();
}
}
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

0 comments on commit 37d79d0

Please sign in to comment.