Skip to content

Instantly share code, notes, and snippets.

@k06a
Last active August 6, 2019 09:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save k06a/2f4f7477bc1af06906f6daaf0278c9fd to your computer and use it in GitHub Desktop.
Save k06a/2f4f7477bc1af06906f6daaf0278c9fd to your computer and use it in GitHub Desktop.
pragma solidity ^0.5.0;
import "github.com/OpenZeppelin/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
import "github.com/OpenZeppelin/openzeppelin-contracts/contracts/math/SafeMath.sol";
interface IValidatorSet {
function isValidator(address candidate) external view returns(bool);
}
contract BasePool {
using SafeMath for uint256;
uint256 private _totalShares;
mapping(address => uint256) private _shares;
function totalShares() public view returns(uint256) {
return _totalShares;
}
function shares(address user) public view returns(uint256) {
return _shares[user];
}
function amountFromShare(uint256 share, uint256 balance) public view returns(uint256 amount) {
return share.mul(balance).div(_totalShares);
}
function shareFromAmount(uint256 amount, uint256 balance) public view returns(uint256 share) {
if (_totalShares > 0) {
share = amount.mul(_totalShares).div(balance);
} else {
share = amount;
}
}
function _mintShareByAmount(address user, uint256 amount, uint256 balance) internal returns(uint256) {
uint256 share = shareFromAmount(amount, balance);
_shares[user] = _shares[user].add(share);
_totalShares = _totalShares.add(share);
return share;
}
function _burnShare(address user, uint256 share, uint256 balance) internal returns(uint256) {
uint256 amount = amountFromShare(share, balance);
_totalShares = _totalShares.sub(share);
_shares[user] = _shares[user].sub(share);
return amount;
}
}
//
contract SubPool is BasePool {
IERC20 public token;
StakingPool public parent;
modifier onlyParent {
require(msg.sender == address(parent));
_;
}
constructor(IERC20 _token) public {
token = _token;
parent = StakingPool(msg.sender);
token.approve(address(parent), uint(-1));
}
// Deposit to incoming subpool of active candidate's pool
function depositFor(address user, uint256 amount) public onlyParent returns(uint256 share) {
uint256 nonStakedBalance = token.balanceOf(address(this));
share = _mintShareByAmount(user, amount, nonStakedBalance);
}
// Withdraw from leaved subpool
function withdrawFor(address user, uint256 share) public onlyParent returns(uint256 amount) {
uint256 nonStakedBalance = token.balanceOf(address(this));
amount = _burnShare(user, share, nonStakedBalance);
token.transfer(user, amount);
}
// Adding share to leaving pool
function addShareFor(address user, uint256 amount, uint256 balance) public onlyParent returns(uint256 retShare) {
return _mintShareByAmount(user, amount, balance);
}
// Removing share from one of subpools during moving to leave pool
function removeShareFor(address user, uint256 share) public onlyParent returns(uint256 amount) {
uint256 stakedBalance = parent.balanceOf(address(this));
return _burnShare(user, share, stakedBalance);
}
}
//
contract StakingPool is BasePool {
IERC20 public token;
address public candidate;
IValidatorSet public validatorSet;
address public stakingContract;
SubPool public comingPool;
SubPool public leavingPool;
constructor(IERC20 _token, IValidatorSet _validatorSet, address _stakingContract) public {
token = _token;
candidate = msg.sender;
validatorSet = _validatorSet;
stakingContract = _stakingContract;
comingPool = new SubPool(token);
leavingPool = new SubPool(token);
}
function balanceOf(address user) public view returns(uint256) {
return token.balanceOf(address(this))
.mul(shares(user))
.div(totalShares());
}
// Use for rewards
function onTokenTransfer(address from, uint256 amount, bytes memory data) public returns (bool) {
require(msg.sender == address(token));
require(from == stakingContract, "Can be sent by staking contract at the end of EPOCH");
require(totalShares() > 0);
// End of epoch
if (shares(address(leavingPool)) > 0) {
_withdraw(address(leavingPool), shares(address(leavingPool)), SubPool(0));
leavingPool = new SubPool(token);
}
if (token.balanceOf(address(comingPool)) > 0) {
_deposit(address(comingPool), token.balanceOf(address(comingPool)));
comingPool = new SubPool(token);
}
return true;
}
function deposit(uint256 amount) public {
_deposit(msg.sender, amount);
}
function withdraw(uint256 share, SubPool optionalSubpool) public {
_withdraw(msg.sender, share, optionalSubpool);
}
function _deposit(address user, uint256 amount) internal returns(uint256 share) {
// Deposit
if (!validatorSet.isValidator(candidate)) {
// to this
uint256 balance = token.balanceOf(address(this));
share = _mintShareByAmount(user, amount, balance);
token.transferFrom(user, address(this), amount);
} else {
// to coming pool
share = comingPool.depositFor(user, amount);
token.transferFrom(user, address(comingPool), amount);
}
}
function _withdraw(address user, uint256 share, SubPool optionalSubpool) internal returns(uint256 amount) {
require(optionalSubpool != leavingPool, "Can't withdraw from leaving subpool");
uint256 balance = token.balanceOf(address(this));
// Remove share
if (optionalSubpool == SubPool(0)) {
// From pool
amount = _burnShare(user, share, balance);
}
else {
if (shares(address(optionalSubpool)) == 0) {
// From already leaved pool (detached)
return optionalSubpool.withdrawFor(user, share);
}
// From subpool
amount = optionalSubpool.removeShareFor(user, share);
uint256 subpoolShare = amount.mul(totalShares()).div(balance);
_burnShare(address(optionalSubpool), subpoolShare, balance);
}
// Move funds
if (!validatorSet.isValidator(candidate)) {
// to user
token.transfer(user, amount);
} else {
// to leaving pool
uint256 poolBalance = balance.sub(amount)
.mul(shares(address(leavingPool)))
.div(token.balanceOf(address(this)));
leavingPool.addShareFor(user, amount, poolBalance);
_mintShareByAmount(address(leavingPool), amount, balance.sub(amount));
}
}
}
//
contract StakingPool30 is StakingPool {
uint256 public constant CANDIDATE_MIN_PERCENT = 30;
uint256 public candidateTotalShare;
function onTokenTransfer(address from, uint256 amount, bytes memory data) public returns (bool) {
// Ensure 30% to candidate
uint256 candidatePercent = candidateTotalShare.mul(100).div(totalShares());
if (candidatePercent < CANDIDATE_MIN_PERCENT) {
uint256 percent = CANDIDATE_MIN_PERCENT.sub(candidatePercent);
uint256 candidateReward = amount.mul(percent).div(100);
uint256 balance = token.balanceOf(address(this));
_mintShareByAmount(address(candidate), candidateReward, balance.sub(candidateReward));
}
return super.onTokenTransfer(from, amount, data);
}
function _deposit(address user, uint256 amount) internal returns(uint256 share) {
share = super._deposit(user, amount);
bool isActiveValidator = validatorSet.isValidator(candidate);
if (user == address(candidate) && !isActiveValidator) {
// Calc share in this pool
candidateTotalShare = candidateTotalShare.add(share);
}
if (user == address(comingPool)) {
// Calc share in coming pool
candidateTotalShare = candidateTotalShare.add(
shares(address(comingPool))
.mul(comingPool.shares(address(candidate)))
.div(comingPool.totalShares())
);
}
}
function _withdraw(address user, uint256 share, SubPool optionalSubpool) internal returns(uint256 amount) {
bool isActiveValidator = validatorSet.isValidator(candidate);
if (user == address(candidate) && !isActiveValidator) {
// Removing stake
if (optionalSubpool == SubPool(0)) {
// With this pool
candidateTotalShare = candidateTotalShare.sub(share);
} else {
// With subpool
candidateTotalShare = candidateTotalShare.sub(
share
.mul(optionalSubpool.shares(address(candidate)))
.div(optionalSubpool.totalShares())
);
}
}
if (user == address(leavingPool)) {
// Removing stake with leaving pool
candidateTotalShare = candidateTotalShare.sub(
shares(address(leavingPool))
.mul(leavingPool.shares(address(candidate)))
.div(leavingPool.totalShares())
);
}
return super._withdraw(msg.sender, share, optionalSubpool);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment