Files
dodo-contractV2/contracts/DODOToken/vDODOToken.sol
2021-01-31 23:17:01 +08:00

331 lines
12 KiB
Solidity
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
Copyright 2020 DODO ZOO.
SPDX-License-Identifier: Apache-2.0
*/
pragma solidity 0.6.9;
pragma experimental ABIEncoderV2;
import {IERC20} from "../intf/IERC20.sol";
import {SafeMath} from "../lib/SafeMath.sol";
import {DecimalMath} from "../lib/DecimalMath.sol";
import {InitializableOwnable} from "../lib/InitializableOwnable.sol";
import {SafeERC20} from "../lib/SafeERC20.sol";
import {ReentrancyGuard} from "../lib/ReentrancyGuard.sol";
import {IDODOApproveProxy} from "../SmartRoute/DODOApproveProxy.sol";
interface IGovernance {
function governanceCall(address account, uint256 amount,bytes calldata data) external returns (bool);
}
interface IDODOLockedHelper {
function getDodoLockedAmount() external returns (uint256);
}
contract vDODOToken is InitializableOwnable ,ReentrancyGuard{
using SafeMath for uint256;
using SafeERC20 for IERC20;
// ============ Storage(ERC20) ============
string public name;
string public symbol;
uint8 public decimals;
uint256 public totalSupply;
mapping(address => mapping(address => uint256)) internal _ALLOWED_;
// ============ Storage ============
address immutable _DODO_LOCKED_HELPER_;
address immutable _DODO_TOKEN_;
address immutable _DODO_APPROVE_PROXY_;
address public _DOOD_GOV_;
bool cantransfer;
uint256 public dodoPerBlock;
uint256 constant public _MAG_SP_AMOUNT_ = 10; // 0.1
uint256 constant public _MIN_X_ = 10**18;
uint256 constant public _MAX_X_ = 10 * 10**18;
uint256 constant public _MIN_Y_ = 5 * 10**18;
uint256 constant public _MAX_Y_ = 15 * 10**18;
uint256 public alpha = 100;
uint256 public lastRewardBlock;
uint256 public dodoFeeDestroyRatio;
mapping(address => bool) public operater;
mapping(address => UserInfo) public userInfo;
struct UserInfo {
address superior;
uint256 vdodoAmount;
uint256 overdraft;
bool hasParticipateGov; //是否正在参与治理,是的话就不可以提币
}
// ============ Events ============
event ParticipatoryGov(address user, uint256 amount);
event Deposit(address user,address superior, uint256 amount);
event Withdraw(address user, uint256 amount);
event SetCantransfer(bool allowed);
event RemoveOperation(address operater);
event AddOperation(address operater);
event ChangePerReward(uint256 dodoPerBlock);
event UpdateDodoFeeDestroyRatio(uint256 dodoFeeDestroyRatio);
event Transfer(address indexed from, address indexed to, uint256 amount);
event Approval(address indexed owner, address indexed spender, uint256 amount);
// ============ Modifiers ============
//TODO: 是否需要operator的白名单设计
modifier onlyOperater() {
require(cantransfer || operater[msg.sender] , "vDODOToken: not allowed transfer");
_;
}
constructor(
address _dodoGov,
address _dodoToken,
address _dodoLockedHelper,
address _dodoApproveProxy,
string memory _name,
string memory _symbol)
public {
name = _name;
symbol = _symbol;
decimals = 18;
_DODO_APPROVE_PROXY_ = _dodoApproveProxy;
_DOOD_GOV_ = _dodoGov;
_DODO_LOCKED_HELPER_ = _dodoLockedHelper;
_DODO_TOKEN_ = _dodoToken;
lastRewardBlock = block.number;
}
// ============ Ownable Functions ============`
function setCantransfer(bool _allowed) public onlyOwner {
cantransfer = _allowed;
emit SetCantransfer(_allowed);
}
function addOperationAddress(address _operater) public onlyOwner {
operater[_operater] = true;
emit AddOperation(_operater);
}
function removeOperation(address _operater) public onlyOwner {
operater[_operater] = false;
emit RemoveOperation(_operater);
}
function changePerReward(uint256 _dodoPerBlock) public onlyOwner {
_updateAlpha();
dodoPerBlock = _dodoPerBlock;
emit ChangePerReward(dodoPerBlock);
}
function updateDodoFeeDestroyRatio(uint256 _dodoFeeDestroyRatio) public onlyOwner {
dodoFeeDestroyRatio = _dodoFeeDestroyRatio;
emit UpdateDodoFeeDestroyRatio(_dodoFeeDestroyRatio);
}
// ============ Functions ============
function participatoryGov(
uint256 _amount,
bytes calldata _data
) external preventReentrant {
UserInfo storage user = userInfo[msg.sender];
require(user.vdodoAmount > _amount, "vDODOToken: no enough vdodo");
if (_data.length > 0)
IGovernance(_DOOD_GOV_).governanceCall(msg.sender, _amount, _data);
user.vdodoAmount = user.vdodoAmount.sub(_amount);
user.hasParticipateGov = true;
//TODO: 是否减掉总量?
totalSupply = totalSupply.sub(_amount);
emit ParticipatoryGov(msg.sender, _amount);
}
function deposit(uint256 _dodoAmount,address _superiorAddress) public preventReentrant {
require(_dodoAmount > 0, "vDODOToken: must deposit greater than 0");
IDODOApproveProxy(_DODO_APPROVE_PROXY_).claimTokens(
_DODO_TOKEN_,
msg.sender,
address(this),
_dodoAmount
);
_updateAlpha();
UserInfo storage user = userInfo[msg.sender];
user.vdodoAmount = user.vdodoAmount.add(_dodoAmount.div(alpha));
if(user.superior == address(0) && _superiorAddress != address(0) && _superiorAddress != msg.sender){
user.superior = _superiorAddress;
}
uint256 _dodoAmountDivAlpha = DecimalMath.divFloor(_dodoAmount, alpha);
if(user.superior != address(0)){
UserInfo storage superiorUser = userInfo[user.superior];
superiorUser.vdodoAmount = superiorUser.vdodoAmount.add(_dodoAmountDivAlpha.mul(_MAG_SP_AMOUNT_).div(100));
superiorUser.overdraft = superiorUser.overdraft.add(_dodoAmount.mul(_MAG_SP_AMOUNT_).div(100));
totalSupply = totalSupply.add(_dodoAmountDivAlpha.mul(_MAG_SP_AMOUNT_ + 100).div(100));
}else {
totalSupply = totalSupply.add(_dodoAmountDivAlpha);
}
emit Deposit(msg.sender, _superiorAddress, _dodoAmount);
}
function withdraw(uint256 _vDodoAmount) public preventReentrant {
UserInfo storage user = userInfo[msg.sender];
uint256 userAmount = user.vdodoAmount;
require(userAmount >= _vDodoAmount, "vDODOToken: no enough vdodo token");
require(!user.hasParticipateGov, "vDODOToken: hasParticipateGov");
_updateAlpha();
user.vdodoAmount = userAmount.sub(_vDodoAmount);
if(user.superior != address(0)) {
UserInfo storage superiorUser = userInfo[user.superior];
superiorUser.vdodoAmount = superiorUser.vdodoAmount.sub(_vDodoAmount.mul(_MAG_SP_AMOUNT_).div(100));
uint256 _overdraft = _vDodoAmount.mul(alpha).mul(_MAG_SP_AMOUNT_).div(100);
superiorUser.overdraft = superiorUser.overdraft.sub(_overdraft);
totalSupply = totalSupply.sub(_vDodoAmount.mul(_MAG_SP_AMOUNT_ + 100).div(100));
} else {
totalSupply = totalSupply.sub(_vDodoAmount);
}
uint256 feeRatio = _checkReward();
uint256 withdrawDodoAmount = alpha.mul(_vDodoAmount);
uint256 withdrawFeeAmount = DecimalMath.mulCeil(withdrawDodoAmount,feeRatio).div(100);
uint256 dodoReceive = withdrawDodoAmount.sub(withdrawFeeAmount);
IERC20(_DODO_TOKEN_).transfer(msg.sender, dodoReceive);
if(dodoFeeDestroyRatio > 0){
uint256 destroyDodoAmount = DecimalMath.mulCeil(withdrawDodoAmount,dodoFeeDestroyRatio).div(100);
_transfer(address(this), address(0), destroyDodoAmount);
withdrawFeeAmount = withdrawFeeAmount.sub(destroyDodoAmount);
}
alpha = alpha.add(withdrawFeeAmount.div(totalSupply));
emit Withdraw(msg.sender, _vDodoAmount);
}
// ============ Functions(ERC20) ============
function balanceOf(address _address) public view returns (uint256 balance) {
UserInfo memory user = userInfo[_address];
balance = user.vdodoAmount.sub(user.overdraft.div(alpha));
}
function transfer(address to, uint256 amount) public returns (bool) {
_transfer(msg.sender, to, amount);
return true;
}
function approve(address spender, uint256 amount) public returns (bool) {
_approve(msg.sender, spender, amount);
return true;
}
function transferFrom(address from, address to, uint256 amount) public returns (bool) {
require(amount <= _ALLOWED_[from][msg.sender], "ALLOWANCE_NOT_ENOUGH");
_transfer(from, to, amount);
_ALLOWED_[from][msg.sender] = _ALLOWED_[from][msg.sender].sub(amount);
emit Transfer(from, to, amount);
return true;
}
function _approve(
address owner,
address spender,
uint256 amount
) private onlyOperater {
_ALLOWED_[owner][spender] = amount;
emit Approval(owner, spender, amount);
}
function allowance(address owner, address spender) public view returns (uint256) {
return _ALLOWED_[owner][spender];
}
function _transfer(address from, address to, uint256 _amount) internal onlyOperater {
require(from != address(0), " transfer from the zero address");
require(to != address(0), " transfer to the zero address");
require(balanceOf(from) >= _amount,"no enough to transfer");
UserInfo storage user = userInfo[from];
user.vdodoAmount= user.vdodoAmount.sub(_amount);
address fromSuperiorAddr = user.superior;
if(fromSuperiorAddr != address(0)) {
UserInfo storage fromSuperior = userInfo[fromSuperiorAddr];
fromSuperior.vdodoAmount = fromSuperior.vdodoAmount.sub(_amount.mul(_MAG_SP_AMOUNT_).div(100));
}
UserInfo storage toUser = userInfo[to];
toUser.vdodoAmount = toUser.vdodoAmount.add(_amount);
address toSuperiorAddr = toUser.superior;
if(toSuperiorAddr != address(0)) {
UserInfo storage toSuperior = userInfo[toSuperiorAddr];
toUser.vdodoAmount =toSuperior.vdodoAmount.add(_amount.mul(_MAG_SP_AMOUNT_).div(100));
}
emit Transfer(from, to, _amount);
}
// ============ View Functions ============
function canWithDraw(address _address) public view returns (uint256 withDrawAmount) {
UserInfo memory user = userInfo[_address];
withDrawAmount = user.vdodoAmount.mul(alpha).sub(user.overdraft);
}
// ============ internal function ============
function _updateAlpha() internal {
uint256 accuDODO = dodoPerBlock * (block.number.sub(lastRewardBlock));
if(totalSupply > 0){
alpha = alpha.add(accuDODO.div(totalSupply));
}
lastRewardBlock = block.number;
}
// ============= Helper and calculation function ===============
function _checkReward() internal returns (uint256) {
uint256 dodoTotalLockedAmout = IDODOLockedHelper(_DODO_LOCKED_HELPER_).getDodoLockedAmount();
// (x - 1)^2 / 81 + (y - 15)^2 / 100 = 1 ==> y = sqrt(100* (x*x +2x ) / 81)) +15
// y = 5 (x ≤ 1)
// y = 15 (x ≥ 10)
uint256 x = DecimalMath.divCeil(dodoTotalLockedAmout,totalSupply);
if( x <= _MIN_X_){
return _MIN_Y_;
}else if(x >= _MAX_X_){
return _MAX_Y_;
}else{
uint256 xSubOne = x.sub(10**18);
uint256 rewardAmount = uint256(81 * 10**18).sub(xSubOne.mul(xSubOne)).mul(100).div(81).sqrt().add(15);
return rewardAmount;
}
}
}
//TODO: donate function?