Skip to content

Commit

Permalink
Merge pull request #24 from moonshotcollective/week4-cross-user-staking
Browse files Browse the repository at this point in the history
Week4 cross user staking
  • Loading branch information
ghostffcode authored Aug 5, 2022
2 parents facfdf1 + ebf66e2 commit dc01e37
Show file tree
Hide file tree
Showing 10 changed files with 404 additions and 159 deletions.
208 changes: 208 additions & 0 deletions packages/hardhat/contracts/IDStaking.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
//SPDX-License-Identifier: MIT
pragma solidity >=0.8.0 <0.9.0;

import {XStaking} from "./XStaking.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

contract IDStaking is XStaking, Ownable {
uint256 public latestRound;

struct Round {
string meta;
uint256 tvl;
uint256 start;
uint256 duration;
}

mapping(uint256 => Round) rounds;

event roundCreated(uint256 id);
event tokenStaked(uint256 roundId, address staker, uint256 amount);
event xStaked(
uint256 roundId,
address staker,
address user,
uint256 amount,
bool staked
);
event tokenUnstaked(uint256 roundId, address staker, uint256 amount);
event tokenMigrated(address staker, uint256 fromRound, uint256 toRound);

modifier roundExists(uint256 roundId) {
require(
rounds[roundId].start > 0 && roundId > 0,
"Round does not exist"
);
_;
}

constructor(IERC20 _token) payable {
token = _token;
}

function createRound(
uint256 start,
uint256 duration,
string memory meta
) public onlyOwner {
if (latestRound > 0) {
require(
start >
rounds[latestRound].start + rounds[latestRound].duration,
"new rounds have to start after old rounds"
);
}

require(start > block.timestamp, "new rounds should be in the future");

latestRound++;

rounds[latestRound].start = start;
rounds[latestRound].duration = duration;
rounds[latestRound].meta = meta;

emit roundCreated(latestRound);
}

// stake
function stake(uint256 roundId, uint256 amount) public {
require(isActiveRound(roundId), "Can't stake an inactive round");

_stake(roundId, amount);

rounds[roundId].tvl += amount;

emit tokenStaked(roundId, msg.sender, amount);
}

// stakeUser
function stakeUsers(
uint256 roundId,
address[] memory users,
uint256[] memory amounts
) public {
require(isActiveRound(roundId), "Can't stake an inactive round");
require(users.length == amounts.length, "Unequal users and amount");

for (uint256 i = 0; i < users.length; i++) {
require(address(0) != users[i], "can't stake the zero address");
require(
users[i] != msg.sender,
"You can't stake on your address here"
);
_stakeUser(roundId, users[i], amounts[i]);

rounds[roundId].tvl += amounts[i];

emit xStaked(roundId, msg.sender, users[i], amounts[i], true);
}
}

// unstake
function unstake(uint256 roundId, uint256 amount) public {
require(
!isActiveRound(roundId),
"Can't unstake during an active round"
);
require(
stakes[roundId][msg.sender] >= amount,
"Not enough balance to withdraw"
);

rounds[roundId].tvl -= amount;

_unstake(roundId, amount);

emit tokenUnstaked(roundId, msg.sender, amount);
}

// unstakeUser
function unstakeUsers(uint256 roundId, address[] memory users) public {
require(
!isActiveRound(roundId),
"Can't unstake during an active round"
);

for (uint256 i = 0; i < users.length; i++) {
require(address(0) != users[i], "can't stake the zero address");
require(
users[i] != msg.sender,
"You can't stake on your address here"
);

bytes32 stakeId = getStakeId(msg.sender, users[i]);
uint256 unstakeBalance = xStakes[roundId][stakeId];

if (unstakeBalance > 0) {
rounds[roundId].tvl -= unstakeBalance;

_unstakeUser(roundId, users[i], unstakeBalance);

emit xStaked(
roundId,
msg.sender,
users[i],
unstakeBalance,
false
);
}
}
}

// migrateStake
function migrateStake(uint256 fromRound) public {
require(fromRound < latestRound, "Can't migrate from an active round");

uint256 balance = stakes[fromRound][msg.sender];

require(balance > 0, "Not enough balance to migrate");

rounds[fromRound].tvl -= balance;
stakes[fromRound][msg.sender] = 0;
rounds[latestRound].tvl += balance;
stakes[latestRound][msg.sender] = balance;

emit tokenUnstaked(fromRound, msg.sender, balance);
emit tokenStaked(latestRound, msg.sender, balance);
emit tokenMigrated(msg.sender, fromRound, latestRound);
}

// VIEW
function fetchRoundMeta(uint256 roundId)
public
view
roundExists(roundId)
returns (
uint256 start,
uint256 duration,
uint256 tvl
)
{
return (
rounds[roundId].start,
rounds[roundId].duration,
rounds[roundId].tvl
);
}

function isActiveRound(uint256 roundId)
public
view
returns (bool isActive)
{
(uint256 start, uint256 duration, ) = fetchRoundMeta(roundId);
isActive =
start < block.timestamp &&
start + duration > block.timestamp;
}

function getUserStakeForRound(uint256 roundId, address user)
public
view
roundExists(roundId)
returns (uint256)
{
return _getUserStakeForRound(roundId, user);
}
}
137 changes: 9 additions & 128 deletions packages/hardhat/contracts/Staking.sol
Original file line number Diff line number Diff line change
@@ -1,152 +1,33 @@
//SPDX-License-Identifier: MIT
pragma solidity >=0.8.0 <0.9.0;

import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

contract Staking is Ownable {
contract Staking {
IERC20 public token;
uint256 public latestRound;

struct Round {
string meta;
uint256 tvl;
uint256 start;
uint256 duration;
mapping(address => uint256) stakes;
}

mapping(uint256 => Round) rounds;

event roundCreated(uint256 id);
event tokenStaked(uint256 roundId, address staker, uint256 amount);
event tokenUnstaked(uint256 roundId, address staker, uint256 amount);

modifier roundExists(uint256 roundId) {
require(
rounds[roundId].start > 0 && roundId > 0,
"Round does not exist"
);
_;
}

constructor(IERC20 _token) payable {
token = _token;
}

function createRound(
uint256 start,
uint256 duration,
string memory meta
//Removed onlyOwner modifier for testing purposes
) public {

// REMOVING these require statements because they are not necessary to test staking and locking
// if (latestRound > 0) {
// require(
// start >
// rounds[latestRound].start + rounds[latestRound].duration,
// "new rounds have to start after old rounds"
// );
// }

// require(start > block.timestamp, "new rounds should be in the future");

latestRound++;

rounds[latestRound].start = start;
rounds[latestRound].duration = duration;
rounds[latestRound].meta = meta;

emit roundCreated(latestRound);
}
mapping(uint256 => mapping(address => uint256)) public stakes;

// stake
function stake(uint256 roundId, uint256 amount) public {
// require(isActiveRound(roundId), "Can't stake an inactive round");

function _stake(uint256 roundId, uint256 amount) internal {
token.transferFrom(msg.sender, address(this), amount);

rounds[roundId].tvl += amount;

rounds[roundId].stakes[msg.sender] += amount;

emit tokenStaked(roundId, msg.sender, amount);
stakes[roundId][msg.sender] += amount;
}

// unstake
function unstake(uint256 roundId, uint256 amount) public {
// require(
// !isActiveRound(roundId),
// "Can't unstake during an active round"
// );
require(
rounds[roundId].stakes[msg.sender] >= amount,
"Not enough balance to withdraw"
);

rounds[roundId].tvl -= amount;

rounds[roundId].stakes[msg.sender] -= amount;
function _unstake(uint256 roundId, uint256 amount) internal {
stakes[roundId][msg.sender] -= amount;

token.transfer(msg.sender, amount);

emit tokenUnstaked(roundId, msg.sender, amount);
}

// migrateStake
function migrateStake(uint256 fromRound) public {
require(fromRound < latestRound, "Can't migrate from an active round");

uint256 balance = rounds[fromRound].stakes[msg.sender];

require(balance > 0, "Not enough balance to migrate");

rounds[fromRound].tvl -= balance;
rounds[fromRound].stakes[msg.sender] = 0;
rounds[latestRound].tvl += balance;
rounds[latestRound].stakes[msg.sender] = balance;

emit tokenUnstaked(fromRound, msg.sender, balance);
emit tokenStaked(latestRound, msg.sender, balance);
}

// VIEW
function fetchRoundMeta(uint256 roundId)
public
function _getUserStakeForRound(uint256 roundId, address user)
internal
view
roundExists(roundId)
returns (
uint256 start,
uint256 duration,
uint256 tvl
)
{
return (
rounds[roundId].start,
rounds[roundId].duration,
rounds[roundId].tvl
);
}

function isActiveRound(uint256 roundId)
public
view
returns (bool isActive)
{
(uint256 start, uint256 duration, ) = fetchRoundMeta(roundId);
isActive =
start < block.timestamp &&
start + duration > block.timestamp;
}

function getUserStakeForRound(uint256 roundId, address user)
public
view
///roundExists(roundId)
returns (uint256)
{
return rounds[roundId].stakes[user];
return stakes[roundId][user];
}

function getUserStakeFromLatestRound(address user)
Expand Down
Loading

0 comments on commit dc01e37

Please sign in to comment.