Last active
August 6, 2019 09:13
-
-
Save k06a/2f4f7477bc1af06906f6daaf0278c9fd to your computer and use it in GitHub Desktop.
DeepStakingPool - https://drive.google.com/file/d/1u14gqk3acnebjwu-yt4c8z_2qLuuLm9T/view
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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