Skip to content

Commit

Permalink
Merge pull request #2 from SuperFarmDAO/lp-incentive-program-contracts
Browse files Browse the repository at this point in the history
  • Loading branch information
TimTinkers authored Apr 14, 2021
2 parents 78d6a59 + df3db11 commit 56cee87
Show file tree
Hide file tree
Showing 8 changed files with 309 additions and 8 deletions.
22 changes: 16 additions & 6 deletions contracts/Fee1155NFTLockable.sol
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,7 @@ contract Fee1155NFTLockable is ERC1155, Ownable {
using SafeMath for uint256;

/// A version number for this fee-bearing 1155 item contract's interface.
uint256 public version = 2;

/// @dev A mask for isolating an item's group ID.
uint256 constant GROUP_MASK = uint256(uint128(~0)) << 128;
uint256 public version = 1;

/// The ERC-1155 URI for looking up item metadata using {id} substitution.
string public metadataUri;
Expand All @@ -49,6 +46,12 @@ contract Fee1155NFTLockable is ERC1155, Ownable {
/// Specifically whitelist an OpenSea proxy registry address.
address public proxyRegistryAddress;

/// A counter to enforce unique IDs for each item group minted.
uint256 public nextItemGroupId;

/// This mapping tracks the number of unique items within each item group.
mapping (uint256 => uint256) public itemGroupSizes;

/// Whether or not the item collection has been locked to further minting.
bool public locked;

Expand All @@ -67,6 +70,7 @@ contract Fee1155NFTLockable is ERC1155, Ownable {
metadataUri = _uri;
feeOwner = _feeOwner;
proxyRegistryAddress = _proxyRegistryAddress;
nextItemGroupId = 0;
locked = false;
}

Expand Down Expand Up @@ -112,18 +116,24 @@ contract Fee1155NFTLockable is ERC1155, Ownable {
@param amounts The amount of each corresponding item ID to create.
@param data Any associated data to use on items minted in this transaction.
*/
function createNFT(address recipient, uint256[] calldata ids, uint256[] calldata amounts, bytes calldata data) external onlyOwner {
function createNFT(address recipient, uint256[] calldata ids, uint256[] calldata amounts, bytes calldata data) external onlyOwner returns (uint256) {
require(!locked,
"You cannot create more NFTs on a locked collection.");
require(ids.length > 0,
"You cannot create an empty item group.");
require(ids.length == amounts.length,
"IDs length cannot be mismatched with amounts length.");

// Create an item group of requested size using the next available ID.
uint256 shiftedGroupId = nextItemGroupId << 128;
itemGroupSizes[shiftedGroupId] = ids.length;

// Mint the entire batch of items.
_mintBatch(recipient, ids, amounts, data);

// Increment our next item group ID and return our created item group ID.
emit ItemGroupCreated(ids[0] & GROUP_MASK, ids.length, msg.sender);
nextItemGroupId = nextItemGroupId.add(1);
emit ItemGroupCreated(shiftedGroupId, ids.length, msg.sender);
return shiftedGroupId;
}
}
22 changes: 22 additions & 0 deletions contracts/ISuperStaking.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
pragma solidity 0.7.3;

interface ISuperStaking {
// Views
function lastTimeRewardApplicable() external view returns (uint256);

function rewardPerToken() external view returns (uint256);

function earned(address account) external view returns (uint256);

function balanceOf(address account) external view returns (uint256);

// Mutative

function stake(uint256 amount) external;

function withdraw(uint256 amount) external;

function getReward() external;

function exit() external;
}
141 changes: 141 additions & 0 deletions contracts/SuperStaking.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
pragma solidity 0.7.3;

import "@openzeppelin/contracts/math/Math.sol";
import "@openzeppelin/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

import './ISuperStaking.sol';

contract SuperStaking is ISuperStaking, Ownable, ReentrancyGuard {
using SafeMath for uint256;
using SafeERC20 for IERC20;

uint256 public periodFinish;
uint256 public rewardRate;
uint256 public rewardsDuration;
uint256 public lastUpdateTime;
uint256 public rewardPerTokenStored;
uint256 public lastBalance;
uint256 public totalSupply;

IERC20 public rewardsToken;
IERC20 public stakingToken;

mapping(address => uint256) private userRewardPerTokenPaid;
mapping(address => uint256) private rewards;
mapping(address => uint256) private _balances;

constructor(
address _owner,
address _rewardsToken,
address _stakingToken
) {
rewardsToken = IERC20(_rewardsToken);
stakingToken = IERC20(_stakingToken);
}

function balanceOf(address account) external override view returns (uint256) {
return _balances[account];
}

function lastTimeRewardApplicable() public override view returns (uint256) {
return Math.min(block.timestamp, periodFinish);
}

function rewardPerToken() public override view returns (uint256) {
if (totalSupply == 0) {
return rewardPerTokenStored;
}
return rewardPerTokenStored.add(lastTimeRewardApplicable().sub(lastUpdateTime).mul(rewardRate).mul(1e18).div(totalSupply));
}

function earned(address account) public override view returns (uint256) {
return _balances[account].mul(rewardPerToken().sub(userRewardPerTokenPaid[account])).div(1e18).add(rewards[account]);
}

/* ========== MUTATIVE FUNCTIONS ========== */

function stake(uint256 amount) external override nonReentrant updateReward(msg.sender) {
require(amount > 0, 'Cannot stake 0');
totalSupply = totalSupply.add(amount);
_balances[msg.sender] = _balances[msg.sender].add(amount);
stakingToken.safeTransferFrom(msg.sender, address(this), amount);
emit Staked(msg.sender, amount);
}

function withdraw(uint256 amount) public override nonReentrant updateReward(msg.sender) {
require(amount > 0, 'Cannot withdraw 0');
totalSupply = totalSupply.sub(amount);
_balances[msg.sender] = _balances[msg.sender].sub(amount);
stakingToken.safeTransfer(msg.sender, amount);
emit Withdrawn(msg.sender, amount);
}

function getReward() public override nonReentrant updateReward(msg.sender) {
uint256 reward = rewards[msg.sender];
if (reward > 0) {
rewards[msg.sender] = 0;
rewardsToken.safeTransfer(msg.sender, reward);
emit RewardPaid(msg.sender, reward);
}
}

function exit() external override {
withdraw(_balances[msg.sender]);
getReward();
}

/* ========== RESTRICTED FUNCTIONS ========== */

function notifyRewardAmount(uint256 reward) external onlyOwner updateReward(address(0)) {
require(rewardRate > 0, 'Reward Rate is not yet set');
if (block.timestamp >= periodFinish) {
rewardsDuration = reward.div(rewardRate);
} else {
uint256 remaining = periodFinish.sub(block.timestamp);
uint256 leftover = remaining.mul(rewardRate);
rewardsDuration = reward.add(leftover).div(rewardRate);
}

// Ensure the provided reward amount is not more than the balance in the contract.
// This keeps the reward rate in the right range, preventing overflows due to
// very high values of rewardRate in the earned and rewardsPerToken functions;
// Reward + leftover must be less than 2^256 / 10^18 to avoid overflow.
uint256 balance = rewardsToken.balanceOf(address(this));
require(rewardRate <= balance.div(rewardsDuration), 'Provided reward too high');

lastUpdateTime = block.timestamp;
periodFinish = block.timestamp.add(rewardsDuration);
emit RewardAdded(reward);
}

function setRewardRate(uint256 rewardsPerInterval, uint256 interval) external onlyOwner {
require(rewardsPerInterval > 0 && interval > 0, 'rewardsPerInterval and interval should be greater than 0');
require(block.timestamp > periodFinish, 'Previous rewards period must be complete before changing the reward rate');
rewardRate = rewardsPerInterval.div(interval);

RewardRateUpdated(rewardsPerInterval, interval, rewardRate);
}

/* ========== MODIFIERS ========== */

modifier updateReward(address account) {
rewardPerTokenStored = rewardPerToken();
lastUpdateTime = lastTimeRewardApplicable();
if (account != address(0)) {
rewards[account] = earned(account);
userRewardPerTokenPaid[account] = rewardPerTokenStored;
}
_;
}

/* ========== EVENTS ========== */

event RewardAdded(uint256 reward);
event Staked(address indexed user, uint256 amount);
event Withdrawn(address indexed user, uint256 amount);
event RewardPaid(address indexed user, uint256 reward);
event RewardRateUpdated(uint256 rewardsPerInterval, uint256 interval, uint256 rewardRate);
}
10 changes: 10 additions & 0 deletions hardhat-ganache-tests.config.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
'use strict';

// Configure environment variables.
require('dotenv').config();

Expand Down Expand Up @@ -33,6 +35,14 @@ module.exports = {
enabled: true
}
}
},
{
version: '0.7.3',
settings: {
optimizer: {
enabled: true
}
}
}
]
},
Expand Down
10 changes: 10 additions & 0 deletions hardhat.config.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
'use strict';

// Configure environment variables.
require('dotenv').config();

Expand Down Expand Up @@ -37,6 +39,14 @@ module.exports = {
enabled: true
}
}
},
{
version: '0.7.3',
settings: {
optimizer: {
enabled: true
}
}
}
]
},
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
},
"homepage": "https://github.com/SuperFarmDAO/SuperFarm-Contracts#readme",
"scripts": {
"test": "npx hardhat test; echo 'Executing test cases which require Ganache ...'; npx hardhat --config hardhat-ganache-tests.config.js --network ganache test;",
"test": "npx hardhat test; echo 'Executing test cases which require Ganache ...';",
"lint": "npx eslint ./",
"validate": "npm-run-all --parallel test lint",
"contract-size": "npx hardhat size-contracts"
Expand All @@ -35,6 +35,7 @@
"dependencies": {
"@uniswap/lib": "^1.1.4",
"@uniswap/v2-core": "^1.0.1",
"@uniswap/v2-periphery": "^1.1.0-beta.0",
"dotenv": "^8.2.0",
"limiter": "^1.1.5"
},
Expand Down
2 changes: 1 addition & 1 deletion test/ShopPlatformLaunchpad1155.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -630,7 +630,7 @@ describe('ShopPlatformLaunchpad1155', function () {
name: 'Test Pool',
startBlock: currentBlockNumber,
endBlock: currentBlockNumber + 100,
purchaseLimit: 1,
purchaseLimit: 4,
requirement: {
requiredType: 0,
requiredAsset: ethers.constants.AddressZero,
Expand Down
Loading

0 comments on commit 56cee87

Please sign in to comment.