333 lines
11 KiB
Solidity
333 lines
11 KiB
Solidity
/*
|
|
|
|
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 getLockedvDODO(address account) external returns (uint256);
|
|
}
|
|
|
|
interface IDODOCirculationHelper {
|
|
// vDODO 锁仓不算流通
|
|
function getCirculation() external returns (uint256);
|
|
|
|
function getVDODOWithdrawFeeRatio() external returns (uint256);
|
|
}
|
|
|
|
contract vDODOToken is InitializableOwnable, ReentrancyGuard {
|
|
using SafeMath for uint256;
|
|
|
|
// ============ 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_TOKEN_;
|
|
address immutable _DODO_APPROVE_PROXY_;
|
|
address public _DOOD_GOV_;
|
|
address public _DODO_CIRCULATION_HELPER_;
|
|
|
|
bool public _CAN_TRANSFER_;
|
|
|
|
// staking reward parameters
|
|
uint256 public dodoPerBlock;
|
|
uint256 public constant _SUPERIOR_RATIO_ = 10**17; // 0.1
|
|
uint256 public dodoFeeDestroyRatio;
|
|
|
|
// accounting
|
|
uint256 public alpha = 100 * 10**18; // 100
|
|
uint256 public lastRewardBlock;
|
|
mapping(address => UserInfo) public userInfo;
|
|
|
|
struct UserInfo {
|
|
uint256 VDODOAmount;
|
|
uint256 credit;
|
|
address superior;
|
|
uint256 superiorVDODO;
|
|
}
|
|
|
|
// ============ Events ============
|
|
|
|
event Deposit(address user, address superior, uint256 amount);
|
|
event Withdraw(address user, uint256 amount);
|
|
event SetCantransfer(bool allowed);
|
|
|
|
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 ============
|
|
|
|
modifier canTransfer() {
|
|
require(_CAN_TRANSFER_, "vDODOToken: not allowed transfer");
|
|
_;
|
|
}
|
|
|
|
modifier balanceEnough(address account, uint256 amount) {
|
|
require(availableBalanceOf(account) >= amount, "vDODOToken: available amount not enough");
|
|
_;
|
|
}
|
|
|
|
// ============ Constructor ============
|
|
|
|
constructor(
|
|
// address _dodoGov,
|
|
address _dodoToken,
|
|
address _dodoCirculationHelper,
|
|
address _dodoApproveProxy,
|
|
string memory _name,
|
|
string memory _symbol
|
|
) public {
|
|
initOwner(msg.sender);
|
|
name = _name;
|
|
symbol = _symbol;
|
|
decimals = 18;
|
|
_DODO_APPROVE_PROXY_ = _dodoApproveProxy;
|
|
// _DOOD_GOV_ = _dodoGov;
|
|
_DODO_CIRCULATION_HELPER_ = _dodoCirculationHelper;
|
|
_DODO_TOKEN_ = _dodoToken;
|
|
lastRewardBlock = block.number;
|
|
}
|
|
|
|
// ============ Ownable Functions ============`
|
|
|
|
function setCantransfer(bool _allowed) public onlyOwner {
|
|
_CAN_TRANSFER_ = _allowed;
|
|
emit SetCantransfer(_allowed);
|
|
}
|
|
|
|
function changePerReward(uint256 _dodoPerBlock) public onlyOwner {
|
|
_updateAlpha();
|
|
dodoPerBlock = _dodoPerBlock;
|
|
emit ChangePerReward(dodoPerBlock);
|
|
}
|
|
|
|
function updateDodoFeeDestroyRatio(uint256 _dodoFeeDestroyRatio) public onlyOwner {
|
|
dodoFeeDestroyRatio = _dodoFeeDestroyRatio;
|
|
emit UpdateDodoFeeDestroyRatio(_dodoFeeDestroyRatio);
|
|
}
|
|
|
|
function updateDODOCirculationHelper(address _helper) public onlyOwner {
|
|
_DODO_CIRCULATION_HELPER_ = _helper;
|
|
}
|
|
function updateGovernance(address _governance) public onlyOwner {
|
|
_DOOD_GOV_ = _governance;
|
|
}
|
|
|
|
// ============ Functions ============
|
|
|
|
function mint(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();
|
|
uint256 newVdodoAmount = DecimalMath.divFloor(_dodoAmount, alpha);
|
|
|
|
UserInfo storage user = userInfo[msg.sender];
|
|
_mint(user, newVdodoAmount);
|
|
|
|
uint256 superiorVDODO;
|
|
if (user.superior == address(0) && _superiorAddress != address(0) && _superiorAddress != msg.sender ) {
|
|
require(_superiorAddress != msg.sender, "COULD NOT SET SELF AS SUPERIOR");
|
|
superiorVDODO = DecimalMath.divFloor(user.VDODOAmount, _SUPERIOR_RATIO_);
|
|
user.superior = _superiorAddress;
|
|
} else if (user.superior != address(0)) {
|
|
superiorVDODO = DecimalMath.divFloor(newVdodoAmount, _SUPERIOR_RATIO_);
|
|
}
|
|
|
|
_mintToSuperior(user, superiorVDODO);
|
|
|
|
emit Deposit(msg.sender, _superiorAddress, _dodoAmount);
|
|
}
|
|
|
|
function redeem(uint256 _vDodoAmount)
|
|
public
|
|
preventReentrant
|
|
balanceEnough(msg.sender, _vDodoAmount)
|
|
{
|
|
_updateAlpha();
|
|
|
|
UserInfo storage user = userInfo[msg.sender];
|
|
_redeem(user, _vDodoAmount);
|
|
|
|
if (user.superior != address(0)) {
|
|
uint256 superiorRedeemVDODO = DecimalMath.divFloor(_vDodoAmount, _SUPERIOR_RATIO_);
|
|
_redeemFromSuperior(user, superiorRedeemVDODO);
|
|
}
|
|
|
|
uint256 feeRatio =
|
|
IDODOCirculationHelper(_DODO_CIRCULATION_HELPER_).getVDODOWithdrawFeeRatio();
|
|
uint256 withdrawDodoAmount = DecimalMath.mulFloor(_vDodoAmount, alpha);
|
|
|
|
uint256 withdrawFeeAmount = DecimalMath.mulCeil(withdrawDodoAmount, feeRatio);
|
|
uint256 dodoReceive = withdrawDodoAmount.sub(withdrawFeeAmount);
|
|
|
|
IERC20(_DODO_TOKEN_).transfer(msg.sender, dodoReceive);
|
|
|
|
if (dodoFeeDestroyRatio > 0) {
|
|
uint256 destroyDodoAmount =
|
|
DecimalMath.mulCeil(withdrawDodoAmount, dodoFeeDestroyRatio);
|
|
_transfer(address(this), address(0), destroyDodoAmount);
|
|
withdrawFeeAmount = withdrawFeeAmount.sub(destroyDodoAmount);
|
|
}
|
|
|
|
alpha = alpha.add(DecimalMath.divFloor(withdrawFeeAmount, totalSupply));
|
|
emit Withdraw(msg.sender, _vDodoAmount);
|
|
}
|
|
|
|
// ============ Functions(ERC20) ============
|
|
|
|
function balanceOf(address account) public view returns (uint256 balance) {
|
|
UserInfo memory user = userInfo[account];
|
|
balance = user.VDODOAmount.sub(DecimalMath.divFloor(user.credit, alpha));
|
|
}
|
|
|
|
function availableBalanceOf(address account) public returns (uint256 balance) {
|
|
uint256 lockedBalance = IGovernance(_DOOD_GOV_).getLockedvDODO(account);
|
|
balance = balanceOf(account).sub(lockedBalance);
|
|
}
|
|
|
|
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) {
|
|
_ALLOWED_[msg.sender][spender] = amount;
|
|
emit Approval(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 allowance(address owner, address spender) public view returns (uint256) {
|
|
return _ALLOWED_[owner][spender];
|
|
}
|
|
|
|
// ============ View Functions ============
|
|
|
|
function canWithDraw(address _address) public view returns (uint256 withDrawAmount) {
|
|
UserInfo memory user = userInfo[_address];
|
|
withDrawAmount = user.VDODOAmount.mul(alpha).sub(user.credit);
|
|
}
|
|
|
|
// ============ internal function ============
|
|
|
|
function _updateAlpha() internal {
|
|
uint256 accuDODO = dodoPerBlock * (block.number.sub(lastRewardBlock));
|
|
if (totalSupply > 0) {
|
|
alpha = alpha.add(DecimalMath.divFloor(accuDODO, totalSupply));
|
|
}
|
|
lastRewardBlock = block.number;
|
|
}
|
|
|
|
function _mint(UserInfo storage to, uint256 amount) internal {
|
|
to.VDODOAmount = to.VDODOAmount.add(amount);
|
|
totalSupply = totalSupply.add(amount);
|
|
}
|
|
|
|
function _mintToSuperior(UserInfo storage user, uint256 vdodoAmount) internal {
|
|
if (vdodoAmount > 0) {
|
|
user.superiorVDODO = user.superiorVDODO.add(vdodoAmount);
|
|
UserInfo storage superiorUser = userInfo[user.superior];
|
|
_mint(superiorUser, vdodoAmount);
|
|
superiorUser.credit = superiorUser.credit.add(DecimalMath.mulFloor(vdodoAmount, alpha));
|
|
}
|
|
}
|
|
|
|
function _redeem(UserInfo storage from, uint256 amount) internal {
|
|
from.VDODOAmount = from.VDODOAmount.sub(amount);
|
|
totalSupply = totalSupply.sub(amount);
|
|
}
|
|
|
|
function _redeemFromSuperior(UserInfo storage user, uint256 vdodoAmount) internal {
|
|
if (vdodoAmount > 0) {
|
|
// 最多撤销当时给superior的
|
|
vdodoAmount = user.superiorVDODO <= vdodoAmount ? user.superiorVDODO : vdodoAmount;
|
|
user.superiorVDODO = user.superiorVDODO.sub(vdodoAmount);
|
|
|
|
// 最多撤销superior的全部credit
|
|
UserInfo storage superiorUser = userInfo[user.superior];
|
|
uint256 creditVDODO = DecimalMath.divFloor(superiorUser.credit, alpha);
|
|
|
|
if (vdodoAmount >= creditVDODO) {
|
|
superiorUser.credit = 0;
|
|
_redeem(superiorUser, creditVDODO);
|
|
} else {
|
|
superiorUser.credit = superiorUser.credit.sub(
|
|
DecimalMath.mulFloor(vdodoAmount, alpha)
|
|
);
|
|
_redeem(superiorUser, vdodoAmount);
|
|
}
|
|
}
|
|
}
|
|
|
|
function _transfer(
|
|
address from,
|
|
address to,
|
|
uint256 _amount
|
|
) internal balanceEnough(msg.sender, _amount) {
|
|
require(from != address(0), "transfer from the zero address");
|
|
require(to != address(0), "transfer to the zero address");
|
|
|
|
UserInfo storage fromUser = userInfo[from];
|
|
fromUser.VDODOAmount = fromUser.VDODOAmount.sub(_amount);
|
|
|
|
UserInfo storage toUser = userInfo[to];
|
|
toUser.VDODOAmount = toUser.VDODOAmount.add(_amount);
|
|
|
|
uint256 superiorRedeemVDODO = DecimalMath.divFloor(_amount, _SUPERIOR_RATIO_);
|
|
|
|
address fromSuperiorAddr = fromUser.superior;
|
|
if (fromSuperiorAddr != address(0)) {
|
|
_redeemFromSuperior(fromUser, superiorRedeemVDODO);
|
|
}
|
|
|
|
address toSuperiorAddr = toUser.superior;
|
|
if (toSuperiorAddr != address(0)) {
|
|
_mintToSuperior(toUser, superiorRedeemVDODO);
|
|
}
|
|
|
|
emit Transfer(from, to, _amount);
|
|
}
|
|
|
|
function donate(uint256 amount) public {
|
|
IERC20(_DODO_TOKEN_).transferFrom(msg.sender, address(this), amount);
|
|
alpha = alpha.add(DecimalMath.divFloor(amount, totalSupply));
|
|
}
|
|
}
|