Files
dodo-contractV2/contracts/DODOToken/vDODOToken.sol

362 lines
12 KiB
Solidity
Raw Normal View History

2021-01-29 16:27:05 +08:00
/*
Copyright 2020 DODO ZOO.
SPDX-License-Identifier: Apache-2.0
*/
2021-01-31 21:19:47 +08:00
pragma solidity 0.6.9;
2021-01-31 23:16:27 +08:00
pragma experimental ABIEncoderV2;
2021-01-31 21:19:47 +08:00
2021-01-31 16:24:37 +08:00
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";
2021-01-31 23:16:27 +08:00
import {IDODOApproveProxy} from "../SmartRoute/DODOApproveProxy.sol";
2021-01-31 21:19:47 +08:00
2021-01-31 16:24:37 +08:00
interface IGovernance {
2021-02-01 18:35:00 +08:00
function getLockedvDODO(address account) external view returns (uint256);
}
2021-02-01 11:53:22 +08:00
interface IDODOCirculationHelper {
2021-02-04 10:47:31 +08:00
// Locked vDOOD not counted in circulation
2021-02-01 18:35:00 +08:00
function getCirculation() external view returns (uint256);
2021-01-31 21:19:47 +08:00
2021-02-01 18:35:00 +08:00
function getVDODOWithdrawFeeRatio() external view returns (uint256);
}
2021-01-31 21:19:47 +08:00
2021-02-04 15:32:05 +08:00
contract vDODOToken is InitializableOwnable {
using SafeMath for uint256;
2021-01-31 21:19:47 +08:00
// ============ Storage(ERC20) ============
2021-01-31 16:24:37 +08:00
2021-01-31 21:19:47 +08:00
string public name;
string public symbol;
uint8 public decimals;
uint256 public totalSupply;
mapping(address => mapping(address => uint256)) internal _ALLOWED_;
2021-01-31 16:24:37 +08:00
2021-01-31 21:19:47 +08:00
// ============ Storage ============
2021-02-01 11:53:22 +08:00
2021-01-31 21:19:47 +08:00
address immutable _DODO_TOKEN_;
address immutable _DODO_APPROVE_PROXY_;
address public _DOOD_GOV_;
2021-02-01 11:53:22 +08:00
address public _DODO_CIRCULATION_HELPER_;
bool public _CAN_TRANSFER_;
// staking reward parameters
uint256 public dodoPerBlock;
uint256 public constant _SUPERIOR_RATIO_ = 10**17; // 0.1
2021-02-01 18:35:00 +08:00
uint256 public dodoFeeBurnRation;
2021-02-01 11:53:22 +08:00
// accounting
2021-02-02 14:35:05 +08:00
uint128 public alpha = 100 * 10**18; // 100
uint128 public lastRewardBlock;
2021-01-31 16:24:37 +08:00
mapping(address => UserInfo) public userInfo;
2021-02-01 11:53:22 +08:00
2021-01-29 16:27:05 +08:00
struct UserInfo {
2021-02-02 14:35:05 +08:00
uint128 VDODOAmount;
uint128 superiorVDODO;
2021-01-31 16:24:37 +08:00
address superior;
2021-02-02 14:35:05 +08:00
uint256 credit;
2021-01-29 16:27:05 +08:00
}
2021-01-31 16:24:37 +08:00
// ============ Events ============
2021-02-01 11:53:22 +08:00
2021-02-02 14:35:05 +08:00
event MintVDODO(address user, address superior, uint256 amount);
event RedeemVDODO(address user, uint256 amount);
2021-01-29 16:27:05 +08:00
event SetCantransfer(bool allowed);
2021-02-01 11:53:22 +08:00
2021-01-31 16:24:37 +08:00
event ChangePerReward(uint256 dodoPerBlock);
2021-02-01 18:35:00 +08:00
event UpdatedodoFeeBurnRation(uint256 dodoFeeBurnRation);
2021-01-31 21:19:47 +08:00
2021-01-31 16:24:37 +08:00
event Transfer(address indexed from, address indexed to, uint256 amount);
event Approval(address indexed owner, address indexed spender, uint256 amount);
2021-01-31 21:19:47 +08:00
2021-01-31 16:24:37 +08:00
// ============ Modifiers ============
2021-02-01 11:53:22 +08:00
modifier canTransfer() {
require(_CAN_TRANSFER_, "vDODOToken: not allowed transfer");
_;
}
modifier balanceEnough(address account, uint256 amount) {
require(availableBalanceOf(account) >= amount, "vDODOToken: available amount not enough");
2021-01-31 16:24:37 +08:00
_;
}
2021-01-29 16:27:05 +08:00
2021-02-01 11:53:22 +08:00
// ============ Constructor ============
2021-01-29 16:27:05 +08:00
constructor(
2021-02-01 18:35:00 +08:00
address dodoGov,
address dodoToken,
address dodoCirculationHelper,
address dodoApproveProxy,
2021-02-02 14:35:05 +08:00
string memory _name,
string memory _symbol
2021-02-01 11:53:22 +08:00
) public {
2021-02-02 14:35:05 +08:00
name = _name;
symbol = _symbol;
2021-01-31 21:19:47 +08:00
decimals = 18;
2021-02-01 18:35:00 +08:00
_DODO_APPROVE_PROXY_ = dodoApproveProxy;
_DOOD_GOV_ = dodoGov;
_DODO_CIRCULATION_HELPER_ = dodoCirculationHelper;
_DODO_TOKEN_ = dodoToken;
2021-02-02 14:35:05 +08:00
lastRewardBlock = uint128(block.number);
2021-01-29 16:27:05 +08:00
}
2021-01-31 16:24:37 +08:00
2021-01-31 23:16:27 +08:00
// ============ Ownable Functions ============`
2021-01-31 16:24:37 +08:00
2021-02-01 18:35:00 +08:00
function setCantransfer(bool allowed) public onlyOwner {
_CAN_TRANSFER_ = allowed;
emit SetCantransfer(allowed);
2021-01-29 16:27:05 +08:00
}
2021-01-31 21:19:47 +08:00
2021-02-02 14:35:05 +08:00
function changePerReward(uint256 _dodoPerBlock) public onlyOwner {
_updateAlpha();
dodoPerBlock = _dodoPerBlock;
emit ChangePerReward(_dodoPerBlock);
2021-01-31 16:24:37 +08:00
}
2021-01-31 21:19:47 +08:00
2021-02-02 14:35:05 +08:00
function updatedodoFeeBurnRation(uint256 _dodoFeeBurnRation) public onlyOwner {
dodoFeeBurnRation = _dodoFeeBurnRation;
emit UpdatedodoFeeBurnRation(_dodoFeeBurnRation);
2021-01-31 16:24:37 +08:00
}
2021-02-01 18:35:00 +08:00
function updateDODOCirculationHelper(address helper) public onlyOwner {
_DODO_CIRCULATION_HELPER_ = helper;
2021-01-29 16:27:05 +08:00
}
2021-02-01 18:40:44 +08:00
2021-02-02 14:35:05 +08:00
function updateGovernance(address governance) public onlyOwner {
_DOOD_GOV_ = governance;
2021-02-01 17:27:44 +08:00
}
2021-02-04 15:32:05 +08:00
function emergencyWithdraw() public onlyOwner {
uint256 dodoBalance = IERC20(_DODO_TOKEN_).balanceOf(address(this));
IERC20(_DODO_TOKEN_).transfer(_OWNER_, dodoBalance);
}
2021-01-29 16:27:05 +08:00
2021-02-01 11:53:22 +08:00
// ============ Functions ============
2021-01-31 21:19:47 +08:00
2021-02-04 15:32:05 +08:00
function mint(uint256 dodoAmount, address superiorAddress) public {
2021-02-02 14:35:05 +08:00
require(superiorAddress != address(0) && superiorAddress != msg.sender, "vDODOToken: Superior INVALID");
2021-02-01 18:35:00 +08:00
require(dodoAmount > 0, "vDODOToken: must mint greater than 0");
2021-02-02 14:35:05 +08:00
_updateAlpha();
2021-01-31 23:16:27 +08:00
IDODOApproveProxy(_DODO_APPROVE_PROXY_).claimTokens(
_DODO_TOKEN_,
2021-01-31 21:19:47 +08:00
msg.sender,
address(this),
2021-02-01 18:35:00 +08:00
dodoAmount
2021-01-31 21:19:47 +08:00
);
2021-02-02 14:35:05 +08:00
uint256 newVdodoAmount = DecimalMath.divFloor(dodoAmount, alpha);
2021-01-29 16:27:05 +08:00
2021-01-31 23:16:27 +08:00
UserInfo storage user = userInfo[msg.sender];
2021-02-01 11:53:22 +08:00
_mint(user, newVdodoAmount);
2021-02-02 14:35:05 +08:00
uint256 increSuperiorVDODO = DecimalMath.mulFloor(newVdodoAmount, _SUPERIOR_RATIO_);
if (user.superior == address(0)) {
2021-02-01 18:35:00 +08:00
user.superior = superiorAddress;
}
2021-01-31 16:24:37 +08:00
2021-02-02 14:35:05 +08:00
_mintToSuperior(user, increSuperiorVDODO);
2021-02-01 18:35:00 +08:00
2021-02-02 14:35:05 +08:00
emit MintVDODO(msg.sender, superiorAddress, dodoAmount);
2021-01-29 16:27:05 +08:00
}
2021-02-01 18:35:00 +08:00
function redeem(uint256 vDodoAmount)
2021-02-01 11:53:22 +08:00
public
2021-02-01 18:35:00 +08:00
balanceEnough(msg.sender, vDodoAmount)
2021-02-01 11:53:22 +08:00
{
2021-02-02 14:35:05 +08:00
_updateAlpha();
2021-01-29 16:27:05 +08:00
2021-02-01 11:53:22 +08:00
UserInfo storage user = userInfo[msg.sender];
2021-02-01 18:35:00 +08:00
_redeem(user, vDodoAmount);
2021-01-29 16:27:05 +08:00
2021-02-01 11:53:22 +08:00
if (user.superior != address(0)) {
2021-02-01 18:35:00 +08:00
uint256 superiorRedeemVDODO = DecimalMath.mulFloor(vDodoAmount, _SUPERIOR_RATIO_);
2021-02-01 11:53:22 +08:00
_redeemFromSuperior(user, superiorRedeemVDODO);
2021-01-31 23:16:27 +08:00
}
2021-01-29 16:27:05 +08:00
2021-02-01 18:35:00 +08:00
(uint256 dodoReceive, uint256 burnDodoAmount, uint256 withdrawFeeDodoAmount) = getWithdrawAmount(vDodoAmount);
2021-02-01 16:23:45 +08:00
2021-02-02 14:35:05 +08:00
IERC20(_DODO_TOKEN_).transfer(msg.sender, dodoReceive);
2021-01-29 16:27:05 +08:00
2021-02-02 14:35:05 +08:00
if(burnDodoAmount > 0){
_transfer(address(this), address(0), burnDodoAmount);
}
if(withdrawFeeDodoAmount > 0) {
alpha = uint128(uint256(alpha).add(DecimalMath.divFloor(withdrawFeeDodoAmount, totalSupply)));
}
2021-01-29 16:27:05 +08:00
2021-02-02 14:35:05 +08:00
emit RedeemVDODO(msg.sender, vDodoAmount);
2021-02-01 18:35:00 +08:00
}
2021-02-02 14:35:05 +08:00
2021-02-01 18:35:00 +08:00
function donate(uint256 dodoAmount) public {
IDODOApproveProxy(_DODO_APPROVE_PROXY_).claimTokens(
_DODO_TOKEN_,
msg.sender,
address(this),
dodoAmount
);
2021-02-02 14:35:05 +08:00
alpha = uint128(uint256(alpha).add(DecimalMath.divFloor(dodoAmount, totalSupply)));
}
2021-01-29 16:27:05 +08:00
2021-01-31 23:16:27 +08:00
// ============ Functions(ERC20) ============
2021-02-01 11:53:22 +08:00
function balanceOf(address account) public view returns (uint256 balance) {
UserInfo memory user = userInfo[account];
2021-02-02 14:35:05 +08:00
balance = uint256(user.VDODOAmount).sub(DecimalMath.divFloor(user.credit, getLatestAlpha()));
2021-02-01 11:53:22 +08:00
}
2021-02-02 01:32:53 +08:00
function availableBalanceOf(address account) public view returns (uint256 balance) {
2021-02-01 11:53:22 +08:00
uint256 lockedBalance = IGovernance(_DOOD_GOV_).getLockedvDODO(account);
balance = balanceOf(account).sub(lockedBalance);
2021-01-29 16:27:05 +08:00
}
2021-01-31 23:16:27 +08:00
function transfer(address to, uint256 amount) public returns (bool) {
2021-02-02 14:35:05 +08:00
_updateAlpha();
2021-01-31 23:16:27 +08:00
_transfer(msg.sender, to, amount);
2021-01-31 16:24:37 +08:00
return true;
}
2021-01-31 23:16:27 +08:00
function approve(address spender, uint256 amount) public returns (bool) {
2021-02-01 11:53:22 +08:00
_ALLOWED_[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
2021-01-29 16:27:05 +08:00
return true;
}
2021-01-31 23:16:27 +08:00
2021-02-01 11:53:22 +08:00
function transferFrom(
address from,
address to,
uint256 amount
) public returns (bool) {
2021-01-31 23:16:27 +08:00
require(amount <= _ALLOWED_[from][msg.sender], "ALLOWANCE_NOT_ENOUGH");
2021-02-02 14:35:05 +08:00
_updateAlpha();
2021-01-31 23:16:27 +08:00
_transfer(from, to, amount);
_ALLOWED_[from][msg.sender] = _ALLOWED_[from][msg.sender].sub(amount);
emit Transfer(from, to, amount);
2021-01-31 16:24:37 +08:00
return true;
}
2021-01-31 23:16:27 +08:00
function allowance(address owner, address spender) public view returns (uint256) {
2021-01-31 16:24:37 +08:00
return _ALLOWED_[owner][spender];
}
2021-01-29 16:27:05 +08:00
2021-01-31 23:16:27 +08:00
// ============ View Functions ============
2021-02-01 11:53:22 +08:00
2021-02-04 15:32:05 +08:00
function dodoBalanceOf(address account) public view returns (uint256 dodoAmount) {
2021-02-01 18:35:00 +08:00
UserInfo memory user = userInfo[account];
2021-02-02 14:35:05 +08:00
dodoAmount = DecimalMath.mulFloor(uint256(user.VDODOAmount),getLatestAlpha()).sub(user.credit);
2021-01-29 16:27:05 +08:00
}
2021-02-01 18:35:00 +08:00
function getLatestAlpha() public view returns(uint256) {
2021-02-03 00:05:45 +08:00
uint256 accuDODO = dodoPerBlock * (block.number - lastRewardBlock);
2021-02-01 11:53:22 +08:00
if (totalSupply > 0) {
2021-02-02 14:35:05 +08:00
return uint256(alpha).add(DecimalMath.divFloor(accuDODO, totalSupply));
2021-02-01 18:35:00 +08:00
} else {
return alpha;
}
}
function getWithdrawAmount(uint256 vDodoAmount) public view returns(uint256 dodoReceive, uint256 burnDodoAmount, uint256 withdrawFeeDodoAmount) {
uint256 feeRatio = IDODOCirculationHelper(_DODO_CIRCULATION_HELPER_).getVDODOWithdrawFeeRatio();
uint256 newAlpha = getLatestAlpha();
uint256 withdrawDodoAmount = DecimalMath.mulFloor(vDodoAmount, newAlpha);
withdrawFeeDodoAmount = DecimalMath.mulCeil(withdrawDodoAmount, feeRatio);
dodoReceive = withdrawDodoAmount.sub(withdrawFeeDodoAmount);
if(dodoFeeBurnRation > 0){
burnDodoAmount = DecimalMath.mulFloor(withdrawFeeDodoAmount,dodoFeeBurnRation);
withdrawFeeDodoAmount = withdrawFeeDodoAmount.sub(burnDodoAmount);
}else {
burnDodoAmount = 0;
2021-01-31 16:24:37 +08:00
}
2021-02-01 18:35:00 +08:00
}
// ============ Internal Functions ============
2021-02-02 14:35:05 +08:00
function _updateAlpha() internal {
uint256 newAlpha = getLatestAlpha();
require(newAlpha <= uint128(-1), "OVERFLOW");
alpha = uint128(newAlpha);
lastRewardBlock = uint128(block.number);
2021-01-29 16:27:05 +08:00
}
2021-02-01 18:35:00 +08:00
function _mint(UserInfo storage to, uint256 vdodoAmount) internal {
2021-02-02 14:35:05 +08:00
require(vdodoAmount <= uint128(-1), "OVERFLOW");
to.VDODOAmount = uint128(uint256(to.VDODOAmount).add(vdodoAmount));
2021-02-01 18:35:00 +08:00
totalSupply = totalSupply.add(vdodoAmount);
2021-02-01 11:53:22 +08:00
}
function _mintToSuperior(UserInfo storage user, uint256 vdodoAmount) internal {
if (vdodoAmount > 0) {
2021-02-02 14:35:05 +08:00
user.superiorVDODO = uint128(uint256(user.superiorVDODO).add(vdodoAmount));
2021-02-01 11:53:22 +08:00
UserInfo storage superiorUser = userInfo[user.superior];
_mint(superiorUser, vdodoAmount);
2021-02-03 00:05:45 +08:00
uint256 dodoAmount = DecimalMath.mulCeil(vdodoAmount, alpha);
superiorUser.credit = superiorUser.credit.add(dodoAmount);
2021-01-31 16:24:37 +08:00
}
}
2021-01-29 16:27:05 +08:00
2021-02-01 18:35:00 +08:00
function _redeem(UserInfo storage from, uint256 vdodoAmount) internal {
2021-02-02 14:35:05 +08:00
from.VDODOAmount = uint128(uint256(from.VDODOAmount).sub(vdodoAmount));
2021-02-01 18:35:00 +08:00
totalSupply = totalSupply.sub(vdodoAmount);
2021-02-01 11:53:22 +08:00
}
function _redeemFromSuperior(UserInfo storage user, uint256 vdodoAmount) internal {
if (vdodoAmount > 0) {
vdodoAmount = user.superiorVDODO <= vdodoAmount ? user.superiorVDODO : vdodoAmount;
2021-02-02 14:35:05 +08:00
user.superiorVDODO = uint128(uint256(user.superiorVDODO).sub(vdodoAmount));
2021-01-31 16:24:37 +08:00
2021-02-01 11:53:22 +08:00
UserInfo storage superiorUser = userInfo[user.superior];
2021-02-02 14:35:05 +08:00
uint256 creditVDODO = DecimalMath.divFloor(superiorUser.credit, alpha);
2021-02-01 11:53:22 +08:00
if (vdodoAmount >= creditVDODO) {
superiorUser.credit = 0;
_redeem(superiorUser, creditVDODO);
} else {
superiorUser.credit = superiorUser.credit.sub(
2021-02-02 14:35:05 +08:00
DecimalMath.mulFloor(vdodoAmount, alpha)
2021-02-01 11:53:22 +08:00
);
_redeem(superiorUser, vdodoAmount);
}
}
}
function _transfer(
address from,
address to,
uint256 _amount
2021-02-03 00:13:45 +08:00
) internal balanceEnough(from, _amount) canTransfer {
2021-02-01 11:53:22 +08:00
require(from != address(0), "transfer from the zero address");
require(to != address(0), "transfer to the zero address");
UserInfo storage fromUser = userInfo[from];
2021-02-02 14:35:05 +08:00
fromUser.VDODOAmount = uint128(uint256(fromUser.VDODOAmount).sub(_amount));
2021-02-01 11:53:22 +08:00
UserInfo storage toUser = userInfo[to];
2021-02-02 14:35:05 +08:00
toUser.VDODOAmount = uint128(uint256(toUser.VDODOAmount).add(_amount));
2021-02-01 11:53:22 +08:00
2021-02-01 18:35:00 +08:00
uint256 superiorRedeemVDODO = DecimalMath.mulFloor(_amount, _SUPERIOR_RATIO_);
2021-02-01 11:53:22 +08:00
2021-02-01 18:35:00 +08:00
if (fromUser.superior != address(0)) {
2021-02-01 11:53:22 +08:00
_redeemFromSuperior(fromUser, superiorRedeemVDODO);
2021-02-01 18:35:00 +08:00
}
2021-02-01 11:53:22 +08:00
2021-02-01 18:35:00 +08:00
if (toUser.superior != address(0)) {
2021-02-01 11:53:22 +08:00
_mintToSuperior(toUser, superiorRedeemVDODO);
}
emit Transfer(from, to, _amount);
}
}