Merge pull request #12 from DODOEX/feature/mineV2AndDSP
Feature/mine v2 and dsp
This commit is contained in:
96
contracts/DODOStablePool/impl/DSP.sol
Normal file
96
contracts/DODOStablePool/impl/DSP.sol
Normal file
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
|
||||
Copyright 2020 DODO ZOO.
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity 0.6.9;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import {IFeeRateModel} from "../../lib/FeeRateModel.sol";
|
||||
import {IERC20} from "../../intf/IERC20.sol";
|
||||
import {DSPTrader} from "./DSPTrader.sol";
|
||||
import {DSPFunding} from "./DSPFunding.sol";
|
||||
import {DSPVault} from "./DSPVault.sol";
|
||||
|
||||
/**
|
||||
* @title DODO StablePool
|
||||
* @author DODO Breeder
|
||||
*
|
||||
* @notice DODOStablePool initialization
|
||||
*/
|
||||
contract DSP is DSPTrader, DSPFunding {
|
||||
function init(
|
||||
address maintainer,
|
||||
address baseTokenAddress,
|
||||
address quoteTokenAddress,
|
||||
uint256 lpFeeRate,
|
||||
address mtFeeRateModel,
|
||||
uint256 i,
|
||||
uint256 k,
|
||||
bool isOpenTWAP
|
||||
) external {
|
||||
require(!_DSP_INITIALIZED_, "DSP_INITIALIZED");
|
||||
_DSP_INITIALIZED_ = true;
|
||||
|
||||
require(baseTokenAddress != quoteTokenAddress, "BASE_QUOTE_CAN_NOT_BE_SAME");
|
||||
_BASE_TOKEN_ = IERC20(baseTokenAddress);
|
||||
_QUOTE_TOKEN_ = IERC20(quoteTokenAddress);
|
||||
|
||||
require(i > 0 && i <= 10**36);
|
||||
_I_ = i;
|
||||
|
||||
require(k <= 10**18);
|
||||
_K_ = k;
|
||||
|
||||
_LP_FEE_RATE_ = lpFeeRate;
|
||||
_MT_FEE_RATE_MODEL_ = IFeeRateModel(mtFeeRateModel);
|
||||
_MAINTAINER_ = maintainer;
|
||||
|
||||
_IS_OPEN_TWAP_ = isOpenTWAP;
|
||||
if (isOpenTWAP) _BLOCK_TIMESTAMP_LAST_ = uint32(block.timestamp % 2**32);
|
||||
|
||||
string memory connect = "_";
|
||||
string memory suffix = "DLP";
|
||||
|
||||
name = string(abi.encodePacked(suffix, connect, addressToShortString(address(this))));
|
||||
symbol = "DLP";
|
||||
decimals = _BASE_TOKEN_.decimals();
|
||||
|
||||
// ============================== Permit ====================================
|
||||
uint256 chainId;
|
||||
assembly {
|
||||
chainId := chainid()
|
||||
}
|
||||
DOMAIN_SEPARATOR = keccak256(
|
||||
abi.encode(
|
||||
// keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'),
|
||||
0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f,
|
||||
keccak256(bytes(name)),
|
||||
keccak256(bytes("1")),
|
||||
chainId,
|
||||
address(this)
|
||||
)
|
||||
);
|
||||
// ==========================================================================
|
||||
}
|
||||
|
||||
function addressToShortString(address _addr) public pure returns (string memory) {
|
||||
bytes32 value = bytes32(uint256(_addr));
|
||||
bytes memory alphabet = "0123456789abcdef";
|
||||
|
||||
bytes memory str = new bytes(8);
|
||||
for (uint256 i = 0; i < 4; i++) {
|
||||
str[i * 2] = alphabet[uint8(value[i + 12] >> 4)];
|
||||
str[1 + i * 2] = alphabet[uint8(value[i + 12] & 0x0f)];
|
||||
}
|
||||
return string(str);
|
||||
}
|
||||
|
||||
// ============ Version Control ============
|
||||
|
||||
function version() external pure returns (string memory) {
|
||||
return "DSP 1.0.0";
|
||||
}
|
||||
}
|
||||
112
contracts/DODOStablePool/impl/DSPFunding.sol
Normal file
112
contracts/DODOStablePool/impl/DSPFunding.sol
Normal file
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
|
||||
Copyright 2020 DODO ZOO.
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity 0.6.9;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import {DSPVault} from "./DSPVault.sol";
|
||||
import {DecimalMath} from "../../lib/DecimalMath.sol";
|
||||
import {IDODOCallee} from "../../intf/IDODOCallee.sol";
|
||||
|
||||
contract DSPFunding is DSPVault {
|
||||
// ============ Events ============
|
||||
|
||||
event BuyShares(address to, uint256 increaseShares, uint256 totalShares);
|
||||
|
||||
event SellShares(address payer, address to, uint256 decreaseShares, uint256 totalShares);
|
||||
|
||||
// ============ Buy & Sell Shares ============
|
||||
|
||||
// buy shares [round down]
|
||||
function buyShares(address to)
|
||||
external
|
||||
preventReentrant
|
||||
returns (
|
||||
uint256 shares,
|
||||
uint256 baseInput,
|
||||
uint256 quoteInput
|
||||
)
|
||||
{
|
||||
uint256 baseBalance = _BASE_TOKEN_.balanceOf(address(this));
|
||||
uint256 quoteBalance = _QUOTE_TOKEN_.balanceOf(address(this));
|
||||
uint256 baseReserve = _BASE_RESERVE_;
|
||||
uint256 quoteReserve = _QUOTE_RESERVE_;
|
||||
|
||||
baseInput = baseBalance.sub(baseReserve);
|
||||
quoteInput = quoteBalance.sub(quoteReserve);
|
||||
require(baseInput > 0, "NO_BASE_INPUT");
|
||||
|
||||
// Round down when withdrawing. Therefore, never be a situation occuring balance is 0 but totalsupply is not 0
|
||||
// But May Happen,reserve >0 But totalSupply = 0
|
||||
if (totalSupply == 0) {
|
||||
// case 1. initial supply
|
||||
shares = quoteBalance < DecimalMath.mulFloor(baseBalance, _I_)
|
||||
? DecimalMath.divFloor(quoteBalance, _I_)
|
||||
: baseBalance;
|
||||
_BASE_TARGET_ = uint112(shares);
|
||||
_QUOTE_TARGET_ = uint112(DecimalMath.mulFloor(shares, _I_));
|
||||
} else if (baseReserve > 0 && quoteReserve > 0) {
|
||||
// case 2. normal case
|
||||
uint256 baseInputRatio = DecimalMath.divFloor(baseInput, baseReserve);
|
||||
uint256 quoteInputRatio = DecimalMath.divFloor(quoteInput, quoteReserve);
|
||||
uint256 mintRatio = quoteInputRatio < baseInputRatio ? quoteInputRatio : baseInputRatio;
|
||||
shares = DecimalMath.mulFloor(totalSupply, mintRatio);
|
||||
|
||||
_BASE_TARGET_ = uint112(uint256(_BASE_TARGET_).add(DecimalMath.mulFloor(uint256(_BASE_TARGET_), mintRatio)));
|
||||
_QUOTE_TARGET_ = uint112(uint256(_QUOTE_TARGET_).add(DecimalMath.mulFloor(uint256(_QUOTE_TARGET_), mintRatio)));
|
||||
}
|
||||
_mint(to, shares);
|
||||
_setReserve(baseBalance, quoteBalance);
|
||||
emit BuyShares(to, shares, _SHARES_[to]);
|
||||
}
|
||||
|
||||
// sell shares [round down]
|
||||
function sellShares(
|
||||
uint256 shareAmount,
|
||||
address to,
|
||||
uint256 baseMinAmount,
|
||||
uint256 quoteMinAmount,
|
||||
bytes calldata data,
|
||||
uint256 deadline
|
||||
) external preventReentrant returns (uint256 baseAmount, uint256 quoteAmount) {
|
||||
require(deadline >= block.timestamp, "TIME_EXPIRED");
|
||||
require(shareAmount <= _SHARES_[msg.sender], "DLP_NOT_ENOUGH");
|
||||
|
||||
uint256 baseBalance = _BASE_TOKEN_.balanceOf(address(this));
|
||||
uint256 quoteBalance = _QUOTE_TOKEN_.balanceOf(address(this));
|
||||
uint256 totalShares = totalSupply;
|
||||
|
||||
baseAmount = baseBalance.mul(shareAmount).div(totalShares);
|
||||
quoteAmount = quoteBalance.mul(shareAmount).div(totalShares);
|
||||
|
||||
_BASE_TARGET_ = uint112(uint256(_BASE_TARGET_).sub(uint256(_BASE_TARGET_).mul(shareAmount).divCeil(totalShares)));
|
||||
_QUOTE_TARGET_ = uint112(uint256(_QUOTE_TARGET_).sub(uint256(_QUOTE_TARGET_).mul(shareAmount).divCeil(totalShares)));
|
||||
|
||||
require(
|
||||
baseAmount >= baseMinAmount && quoteAmount >= quoteMinAmount,
|
||||
"WITHDRAW_NOT_ENOUGH"
|
||||
);
|
||||
|
||||
_burn(msg.sender, shareAmount);
|
||||
_transferBaseOut(to, baseAmount);
|
||||
_transferQuoteOut(to, quoteAmount);
|
||||
_sync();
|
||||
|
||||
if (data.length > 0) {
|
||||
//Same as DVM
|
||||
IDODOCallee(to).DVMSellShareCall(
|
||||
msg.sender,
|
||||
shareAmount,
|
||||
baseAmount,
|
||||
quoteAmount,
|
||||
data
|
||||
);
|
||||
}
|
||||
|
||||
emit SellShares(msg.sender, to, shareAmount, _SHARES_[msg.sender]);
|
||||
}
|
||||
}
|
||||
107
contracts/DODOStablePool/impl/DSPStorage.sol
Normal file
107
contracts/DODOStablePool/impl/DSPStorage.sol
Normal file
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
|
||||
Copyright 2020 DODO ZOO.
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity 0.6.9;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import {ReentrancyGuard} from "../../lib/ReentrancyGuard.sol";
|
||||
import {SafeMath} from "../../lib/SafeMath.sol";
|
||||
import {DODOMath} from "../../lib/DODOMath.sol";
|
||||
import {DecimalMath} from "../../lib/DecimalMath.sol";
|
||||
import {IFeeRateModel} from "../../lib/FeeRateModel.sol";
|
||||
import {IERC20} from "../../intf/IERC20.sol";
|
||||
import {PMMPricing} from "../../lib/PMMPricing.sol";
|
||||
|
||||
contract DSPStorage is ReentrancyGuard {
|
||||
using SafeMath for uint256;
|
||||
|
||||
bool internal _DSP_INITIALIZED_;
|
||||
bool public _IS_OPEN_TWAP_ = false;
|
||||
|
||||
// ============ Core Address ============
|
||||
|
||||
address public _MAINTAINER_;
|
||||
|
||||
IERC20 public _BASE_TOKEN_;
|
||||
IERC20 public _QUOTE_TOKEN_;
|
||||
|
||||
uint112 public _BASE_RESERVE_;
|
||||
uint112 public _QUOTE_RESERVE_;
|
||||
uint32 public _BLOCK_TIMESTAMP_LAST_;
|
||||
|
||||
uint256 public _BASE_PRICE_CUMULATIVE_LAST_;
|
||||
|
||||
uint112 public _BASE_TARGET_;
|
||||
uint112 public _QUOTE_TARGET_;
|
||||
uint32 public _RState_;
|
||||
|
||||
// ============ Shares (ERC20) ============
|
||||
|
||||
string public symbol;
|
||||
uint8 public decimals;
|
||||
string public name;
|
||||
|
||||
uint256 public totalSupply;
|
||||
mapping(address => uint256) internal _SHARES_;
|
||||
mapping(address => mapping(address => uint256)) internal _ALLOWED_;
|
||||
|
||||
// ================= Permit ======================
|
||||
|
||||
bytes32 public DOMAIN_SEPARATOR;
|
||||
// keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
|
||||
bytes32 public constant PERMIT_TYPEHASH =
|
||||
0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;
|
||||
mapping(address => uint256) public nonces;
|
||||
|
||||
// ============ Variables for Pricing ============
|
||||
|
||||
IFeeRateModel public _MT_FEE_RATE_MODEL_;
|
||||
|
||||
uint256 public _LP_FEE_RATE_;
|
||||
uint256 public _K_;
|
||||
uint256 public _I_;
|
||||
|
||||
// ============ Helper Functions ============
|
||||
|
||||
function getPMMState() public view returns (PMMPricing.PMMState memory state) {
|
||||
state.i = _I_;
|
||||
state.K = _K_;
|
||||
state.B = _BASE_RESERVE_;
|
||||
state.Q = _QUOTE_RESERVE_;
|
||||
state.B0 = _BASE_TARGET_; // will be calculated in adjustedTarget
|
||||
state.Q0 = _QUOTE_TARGET_;
|
||||
state.R = PMMPricing.RState(_RState_);
|
||||
PMMPricing.adjustedTarget(state);
|
||||
}
|
||||
|
||||
function getPMMStateForCall()
|
||||
external
|
||||
view
|
||||
returns (
|
||||
uint256 i,
|
||||
uint256 K,
|
||||
uint256 B,
|
||||
uint256 Q,
|
||||
uint256 B0,
|
||||
uint256 Q0,
|
||||
uint256 R
|
||||
)
|
||||
{
|
||||
PMMPricing.PMMState memory state = getPMMState();
|
||||
i = state.i;
|
||||
K = state.K;
|
||||
B = state.B;
|
||||
Q = state.Q;
|
||||
B0 = state.B0;
|
||||
Q0 = state.Q0;
|
||||
R = uint256(state.R);
|
||||
}
|
||||
|
||||
function getMidPrice() public view returns (uint256 midPrice) {
|
||||
return PMMPricing.getMidPrice(getPMMState());
|
||||
}
|
||||
}
|
||||
237
contracts/DODOStablePool/impl/DSPTrader.sol
Normal file
237
contracts/DODOStablePool/impl/DSPTrader.sol
Normal file
@@ -0,0 +1,237 @@
|
||||
/*
|
||||
|
||||
Copyright 2020 DODO ZOO.
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity 0.6.9;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import {DSPVault} from "./DSPVault.sol";
|
||||
import {SafeMath} from "../../lib/SafeMath.sol";
|
||||
import {DecimalMath} from "../../lib/DecimalMath.sol";
|
||||
import {PMMPricing} from "../../lib/PMMPricing.sol";
|
||||
import {IDODOCallee} from "../../intf/IDODOCallee.sol";
|
||||
|
||||
contract DSPTrader is DSPVault {
|
||||
using SafeMath for uint256;
|
||||
|
||||
// ============ Events ============
|
||||
|
||||
event DODOSwap(
|
||||
address fromToken,
|
||||
address toToken,
|
||||
uint256 fromAmount,
|
||||
uint256 toAmount,
|
||||
address trader,
|
||||
address receiver
|
||||
);
|
||||
|
||||
event DODOFlashLoan(address borrower, address assetTo, uint256 baseAmount, uint256 quoteAmount);
|
||||
|
||||
event RChange(PMMPricing.RState newRState);
|
||||
|
||||
// ============ Trade Functions ============
|
||||
|
||||
function sellBase(address to) external preventReentrant returns (uint256 receiveQuoteAmount) {
|
||||
uint256 baseBalance = _BASE_TOKEN_.balanceOf(address(this));
|
||||
uint256 baseInput = baseBalance.sub(uint256(_BASE_RESERVE_));
|
||||
uint256 mtFee;
|
||||
uint256 newBaseTarget;
|
||||
PMMPricing.RState newRState;
|
||||
(receiveQuoteAmount, mtFee, newRState, newBaseTarget) = querySellBase(tx.origin, baseInput);
|
||||
|
||||
_transferQuoteOut(to, receiveQuoteAmount);
|
||||
_transferQuoteOut(_MAINTAINER_, mtFee);
|
||||
|
||||
// update TARGET
|
||||
if (_RState_ != uint32(newRState)) {
|
||||
require(newBaseTarget <= uint112(-1), "OVERFLOW");
|
||||
_BASE_TARGET_ = uint112(newBaseTarget);
|
||||
_RState_ = uint32(newRState);
|
||||
emit RChange(newRState);
|
||||
}
|
||||
|
||||
_setReserve(baseBalance, _QUOTE_TOKEN_.balanceOf(address(this)));
|
||||
|
||||
emit DODOSwap(
|
||||
address(_BASE_TOKEN_),
|
||||
address(_QUOTE_TOKEN_),
|
||||
baseInput,
|
||||
receiveQuoteAmount,
|
||||
msg.sender,
|
||||
to
|
||||
);
|
||||
}
|
||||
|
||||
function sellQuote(address to) external preventReentrant returns (uint256 receiveBaseAmount) {
|
||||
uint256 quoteBalance = _QUOTE_TOKEN_.balanceOf(address(this));
|
||||
uint256 quoteInput = quoteBalance.sub(uint256(_QUOTE_RESERVE_));
|
||||
uint256 mtFee;
|
||||
uint256 newQuoteTarget;
|
||||
PMMPricing.RState newRState;
|
||||
(receiveBaseAmount, mtFee, newRState, newQuoteTarget) = querySellQuote(
|
||||
tx.origin,
|
||||
quoteInput
|
||||
);
|
||||
|
||||
_transferBaseOut(to, receiveBaseAmount);
|
||||
_transferBaseOut(_MAINTAINER_, mtFee);
|
||||
|
||||
// update TARGET
|
||||
if (_RState_ != uint32(newRState)) {
|
||||
require(newQuoteTarget <= uint112(-1), "OVERFLOW");
|
||||
_QUOTE_TARGET_ = uint112(newQuoteTarget);
|
||||
_RState_ = uint32(newRState);
|
||||
emit RChange(newRState);
|
||||
}
|
||||
|
||||
_setReserve(_BASE_TOKEN_.balanceOf(address(this)), quoteBalance);
|
||||
|
||||
emit DODOSwap(
|
||||
address(_QUOTE_TOKEN_),
|
||||
address(_BASE_TOKEN_),
|
||||
quoteInput,
|
||||
receiveBaseAmount,
|
||||
msg.sender,
|
||||
to
|
||||
);
|
||||
}
|
||||
|
||||
function flashLoan(
|
||||
uint256 baseAmount,
|
||||
uint256 quoteAmount,
|
||||
address assetTo,
|
||||
bytes calldata data
|
||||
) external preventReentrant {
|
||||
_transferBaseOut(assetTo, baseAmount);
|
||||
_transferQuoteOut(assetTo, quoteAmount);
|
||||
|
||||
if (data.length > 0)
|
||||
IDODOCallee(assetTo).DSPFlashLoanCall(msg.sender, baseAmount, quoteAmount, data);
|
||||
|
||||
uint256 baseBalance = _BASE_TOKEN_.balanceOf(address(this));
|
||||
uint256 quoteBalance = _QUOTE_TOKEN_.balanceOf(address(this));
|
||||
|
||||
// no input -> pure loss
|
||||
require(
|
||||
baseBalance >= _BASE_RESERVE_ || quoteBalance >= _QUOTE_RESERVE_,
|
||||
"FLASH_LOAN_FAILED"
|
||||
);
|
||||
|
||||
// sell quote case
|
||||
// quote input + base output
|
||||
if (baseBalance < _BASE_RESERVE_) {
|
||||
uint256 quoteInput = quoteBalance.sub(uint256(_QUOTE_RESERVE_));
|
||||
(
|
||||
uint256 receiveBaseAmount,
|
||||
uint256 mtFee,
|
||||
PMMPricing.RState newRState,
|
||||
uint256 newQuoteTarget
|
||||
) = querySellQuote(tx.origin, quoteInput); // revert if quoteBalance<quoteReserve
|
||||
require(
|
||||
uint256(_BASE_RESERVE_).sub(baseBalance) <= receiveBaseAmount,
|
||||
"FLASH_LOAN_FAILED"
|
||||
);
|
||||
|
||||
_transferBaseOut(_MAINTAINER_, mtFee);
|
||||
if (_RState_ != uint32(newRState)) {
|
||||
require(newQuoteTarget <= uint112(-1), "OVERFLOW");
|
||||
_QUOTE_TARGET_ = uint112(newQuoteTarget);
|
||||
_RState_ = uint32(newRState);
|
||||
emit RChange(newRState);
|
||||
}
|
||||
emit DODOSwap(
|
||||
address(_QUOTE_TOKEN_),
|
||||
address(_BASE_TOKEN_),
|
||||
quoteInput,
|
||||
receiveBaseAmount,
|
||||
msg.sender,
|
||||
assetTo
|
||||
);
|
||||
}
|
||||
|
||||
// sell base case
|
||||
// base input + quote output
|
||||
if (quoteBalance < _QUOTE_RESERVE_) {
|
||||
uint256 baseInput = baseBalance.sub(uint256(_BASE_RESERVE_));
|
||||
(
|
||||
uint256 receiveQuoteAmount,
|
||||
uint256 mtFee,
|
||||
PMMPricing.RState newRState,
|
||||
uint256 newBaseTarget
|
||||
) = querySellBase(tx.origin, baseInput); // revert if baseBalance<baseReserve
|
||||
require(
|
||||
uint256(_QUOTE_RESERVE_).sub(quoteBalance) <= receiveQuoteAmount,
|
||||
"FLASH_LOAN_FAILED"
|
||||
);
|
||||
|
||||
_transferQuoteOut(_MAINTAINER_, mtFee);
|
||||
if (_RState_ != uint32(newRState)) {
|
||||
require(newBaseTarget <= uint112(-1), "OVERFLOW");
|
||||
_BASE_TARGET_ = uint112(newBaseTarget);
|
||||
_RState_ = uint32(newRState);
|
||||
emit RChange(newRState);
|
||||
}
|
||||
emit DODOSwap(
|
||||
address(_BASE_TOKEN_),
|
||||
address(_QUOTE_TOKEN_),
|
||||
baseInput,
|
||||
receiveQuoteAmount,
|
||||
msg.sender,
|
||||
assetTo
|
||||
);
|
||||
}
|
||||
|
||||
_sync();
|
||||
|
||||
emit DODOFlashLoan(msg.sender, assetTo, baseAmount, quoteAmount);
|
||||
}
|
||||
|
||||
// ============ Query Functions ============
|
||||
|
||||
function querySellBase(address trader, uint256 payBaseAmount)
|
||||
public
|
||||
view
|
||||
returns (
|
||||
uint256 receiveQuoteAmount,
|
||||
uint256 mtFee,
|
||||
PMMPricing.RState newRState,
|
||||
uint256 newBaseTarget
|
||||
)
|
||||
{
|
||||
PMMPricing.PMMState memory state = getPMMState();
|
||||
(receiveQuoteAmount, newRState) = PMMPricing.sellBaseToken(state, payBaseAmount);
|
||||
|
||||
uint256 lpFeeRate = _LP_FEE_RATE_;
|
||||
uint256 mtFeeRate = _MT_FEE_RATE_MODEL_.getFeeRate(trader);
|
||||
mtFee = DecimalMath.mulFloor(receiveQuoteAmount, mtFeeRate);
|
||||
receiveQuoteAmount = receiveQuoteAmount
|
||||
.sub(DecimalMath.mulFloor(receiveQuoteAmount, lpFeeRate))
|
||||
.sub(mtFee);
|
||||
newBaseTarget = state.B0;
|
||||
}
|
||||
|
||||
function querySellQuote(address trader, uint256 payQuoteAmount)
|
||||
public
|
||||
view
|
||||
returns (
|
||||
uint256 receiveBaseAmount,
|
||||
uint256 mtFee,
|
||||
PMMPricing.RState newRState,
|
||||
uint256 newQuoteTarget
|
||||
)
|
||||
{
|
||||
PMMPricing.PMMState memory state = getPMMState();
|
||||
(receiveBaseAmount, newRState) = PMMPricing.sellQuoteToken(state, payQuoteAmount);
|
||||
|
||||
uint256 lpFeeRate = _LP_FEE_RATE_;
|
||||
uint256 mtFeeRate = _MT_FEE_RATE_MODEL_.getFeeRate(trader);
|
||||
mtFee = DecimalMath.mulFloor(receiveBaseAmount, mtFeeRate);
|
||||
receiveBaseAmount = receiveBaseAmount
|
||||
.sub(DecimalMath.mulFloor(receiveBaseAmount, lpFeeRate))
|
||||
.sub(mtFee);
|
||||
newQuoteTarget = state.Q0;
|
||||
}
|
||||
}
|
||||
236
contracts/DODOStablePool/impl/DSPVault.sol
Normal file
236
contracts/DODOStablePool/impl/DSPVault.sol
Normal file
@@ -0,0 +1,236 @@
|
||||
/*
|
||||
|
||||
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 {SafeERC20} from "../../lib/SafeERC20.sol";
|
||||
import {DSPStorage} from "./DSPStorage.sol";
|
||||
|
||||
contract DSPVault is DSPStorage {
|
||||
using SafeMath for uint256;
|
||||
using SafeERC20 for IERC20;
|
||||
|
||||
// ============ Events ============
|
||||
|
||||
event Transfer(address indexed from, address indexed to, uint256 amount);
|
||||
|
||||
event Approval(address indexed owner, address indexed spender, uint256 amount);
|
||||
|
||||
event Mint(address indexed user, uint256 value);
|
||||
|
||||
event Burn(address indexed user, uint256 value);
|
||||
|
||||
// ============ View Functions ============
|
||||
|
||||
function getVaultReserve() external view returns (uint256 baseReserve, uint256 quoteReserve) {
|
||||
baseReserve = _BASE_RESERVE_;
|
||||
quoteReserve = _QUOTE_RESERVE_;
|
||||
}
|
||||
|
||||
function getUserFeeRate(address user)
|
||||
external
|
||||
view
|
||||
returns (uint256 lpFeeRate, uint256 mtFeeRate)
|
||||
{
|
||||
lpFeeRate = _LP_FEE_RATE_;
|
||||
mtFeeRate = _MT_FEE_RATE_MODEL_.getFeeRate(user);
|
||||
}
|
||||
|
||||
// ============ Asset In ============
|
||||
|
||||
function getBaseInput() public view returns (uint256 input) {
|
||||
return _BASE_TOKEN_.balanceOf(address(this)).sub(uint256(_BASE_RESERVE_));
|
||||
}
|
||||
|
||||
function getQuoteInput() public view returns (uint256 input) {
|
||||
return _QUOTE_TOKEN_.balanceOf(address(this)).sub(uint256(_QUOTE_RESERVE_));
|
||||
}
|
||||
|
||||
// ============ TWAP UPDATE ===========
|
||||
|
||||
function _twapUpdate() internal {
|
||||
uint32 blockTimestamp = uint32(block.timestamp % 2**32);
|
||||
uint32 timeElapsed = blockTimestamp - _BLOCK_TIMESTAMP_LAST_;
|
||||
if (timeElapsed > 0 && _BASE_RESERVE_ != 0 && _QUOTE_RESERVE_ != 0) {
|
||||
_BASE_PRICE_CUMULATIVE_LAST_ += getMidPrice() * timeElapsed;
|
||||
}
|
||||
_BLOCK_TIMESTAMP_LAST_ = blockTimestamp;
|
||||
}
|
||||
|
||||
// ============ Set States ============
|
||||
|
||||
function _setReserve(uint256 baseReserve, uint256 quoteReserve) internal {
|
||||
require(baseReserve <= uint112(-1) && quoteReserve <= uint112(-1), "OVERFLOW");
|
||||
_BASE_RESERVE_ = uint112(baseReserve);
|
||||
_QUOTE_RESERVE_ = uint112(quoteReserve);
|
||||
|
||||
if (_IS_OPEN_TWAP_) _twapUpdate();
|
||||
}
|
||||
|
||||
function _sync() internal {
|
||||
uint256 baseBalance = _BASE_TOKEN_.balanceOf(address(this));
|
||||
uint256 quoteBalance = _QUOTE_TOKEN_.balanceOf(address(this));
|
||||
require(baseBalance <= uint112(-1) && quoteBalance <= uint112(-1), "OVERFLOW");
|
||||
if (baseBalance != _BASE_RESERVE_) {
|
||||
_BASE_RESERVE_ = uint112(baseBalance);
|
||||
}
|
||||
if (quoteBalance != _QUOTE_RESERVE_) {
|
||||
_QUOTE_RESERVE_ = uint112(quoteBalance);
|
||||
}
|
||||
|
||||
if (_IS_OPEN_TWAP_) _twapUpdate();
|
||||
}
|
||||
|
||||
function sync() external preventReentrant {
|
||||
_sync();
|
||||
}
|
||||
|
||||
// ============ Asset Out ============
|
||||
|
||||
function _transferBaseOut(address to, uint256 amount) internal {
|
||||
if (amount > 0) {
|
||||
_BASE_TOKEN_.safeTransfer(to, amount);
|
||||
}
|
||||
}
|
||||
|
||||
function _transferQuoteOut(address to, uint256 amount) internal {
|
||||
if (amount > 0) {
|
||||
_QUOTE_TOKEN_.safeTransfer(to, amount);
|
||||
}
|
||||
}
|
||||
|
||||
// ============ Shares (ERC20) ============
|
||||
|
||||
/**
|
||||
* @dev transfer token for a specified address
|
||||
* @param to The address to transfer to.
|
||||
* @param amount The amount to be transferred.
|
||||
*/
|
||||
function transfer(address to, uint256 amount) public returns (bool) {
|
||||
require(amount <= _SHARES_[msg.sender], "BALANCE_NOT_ENOUGH");
|
||||
|
||||
_SHARES_[msg.sender] = _SHARES_[msg.sender].sub(amount);
|
||||
_SHARES_[to] = _SHARES_[to].add(amount);
|
||||
emit Transfer(msg.sender, to, amount);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Gets the balance of the specified address.
|
||||
* @param owner The address to query the the balance of.
|
||||
* @return balance An uint256 representing the amount owned by the passed address.
|
||||
*/
|
||||
function balanceOf(address owner) external view returns (uint256 balance) {
|
||||
return _SHARES_[owner];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Transfer tokens from one address to another
|
||||
* @param from address The address which you want to send tokens from
|
||||
* @param to address The address which you want to transfer to
|
||||
* @param amount uint256 the amount of tokens to be transferred
|
||||
*/
|
||||
function transferFrom(
|
||||
address from,
|
||||
address to,
|
||||
uint256 amount
|
||||
) public returns (bool) {
|
||||
require(amount <= _SHARES_[from], "BALANCE_NOT_ENOUGH");
|
||||
require(amount <= _ALLOWED_[from][msg.sender], "ALLOWANCE_NOT_ENOUGH");
|
||||
|
||||
_SHARES_[from] = _SHARES_[from].sub(amount);
|
||||
_SHARES_[to] = _SHARES_[to].add(amount);
|
||||
_ALLOWED_[from][msg.sender] = _ALLOWED_[from][msg.sender].sub(amount);
|
||||
emit Transfer(from, to, amount);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender.
|
||||
* @param spender The address which will spend the funds.
|
||||
* @param amount The amount of tokens to be spent.
|
||||
*/
|
||||
function approve(address spender, uint256 amount) public returns (bool) {
|
||||
_approve(msg.sender, spender, amount);
|
||||
return true;
|
||||
}
|
||||
|
||||
function _approve(
|
||||
address owner,
|
||||
address spender,
|
||||
uint256 amount
|
||||
) private {
|
||||
_ALLOWED_[owner][spender] = amount;
|
||||
emit Approval(owner, spender, amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Function to check the amount of tokens that an owner _ALLOWED_ to a spender.
|
||||
* @param owner address The address which owns the funds.
|
||||
* @param spender address The address which will spend the funds.
|
||||
* @return A uint256 specifying the amount of tokens still available for the spender.
|
||||
*/
|
||||
function allowance(address owner, address spender) public view returns (uint256) {
|
||||
return _ALLOWED_[owner][spender];
|
||||
}
|
||||
|
||||
function _mint(address user, uint256 value) internal {
|
||||
require(value > 1000, "MINT_AMOUNT_NOT_ENOUGH");
|
||||
_SHARES_[user] = _SHARES_[user].add(value);
|
||||
totalSupply = totalSupply.add(value);
|
||||
emit Mint(user, value);
|
||||
emit Transfer(address(0), user, value);
|
||||
}
|
||||
|
||||
function _burn(address user, uint256 value) internal {
|
||||
_SHARES_[user] = _SHARES_[user].sub(value);
|
||||
totalSupply = totalSupply.sub(value);
|
||||
emit Burn(user, value);
|
||||
emit Transfer(user, address(0), value);
|
||||
}
|
||||
|
||||
// ============================ Permit ======================================
|
||||
|
||||
function permit(
|
||||
address owner,
|
||||
address spender,
|
||||
uint256 value,
|
||||
uint256 deadline,
|
||||
uint8 v,
|
||||
bytes32 r,
|
||||
bytes32 s
|
||||
) external {
|
||||
require(deadline >= block.timestamp, "DODO_DSP_LP: EXPIRED");
|
||||
bytes32 digest =
|
||||
keccak256(
|
||||
abi.encodePacked(
|
||||
"\x19\x01",
|
||||
DOMAIN_SEPARATOR,
|
||||
keccak256(
|
||||
abi.encode(
|
||||
PERMIT_TYPEHASH,
|
||||
owner,
|
||||
spender,
|
||||
value,
|
||||
nonces[owner]++,
|
||||
deadline
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
address recoveredAddress = ecrecover(digest, v, r, s);
|
||||
require(
|
||||
recoveredAddress != address(0) && recoveredAddress == owner,
|
||||
"DODO_DSP_LP: INVALID_SIGNATURE"
|
||||
);
|
||||
_approve(owner, spender, value);
|
||||
}
|
||||
}
|
||||
36
contracts/DODOStablePool/intf/IDSP.sol
Normal file
36
contracts/DODOStablePool/intf/IDSP.sol
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
|
||||
Copyright 2020 DODO ZOO.
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity 0.6.9;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
interface IDSP {
|
||||
function init(
|
||||
address maintainer,
|
||||
address baseTokenAddress,
|
||||
address quoteTokenAddress,
|
||||
uint256 lpFeeRate,
|
||||
address mtFeeRateModel,
|
||||
uint256 i,
|
||||
uint256 k,
|
||||
bool isOpenTWAP
|
||||
) external;
|
||||
|
||||
function _BASE_TOKEN_() external returns (address);
|
||||
|
||||
function _QUOTE_TOKEN_() external returns (address);
|
||||
|
||||
function _MT_FEE_RATE_MODEL_() external returns (address);
|
||||
|
||||
function getVaultReserve() external returns (uint256 baseReserve, uint256 quoteReserve);
|
||||
|
||||
function sellBase(address to) external returns (uint256);
|
||||
|
||||
function sellQuote(address to) external returns (uint256);
|
||||
|
||||
function buyShares(address to) external returns (uint256);
|
||||
}
|
||||
238
contracts/DODOToken/DODOMineV2/BaseMine.sol
Normal file
238
contracts/DODOToken/DODOMineV2/BaseMine.sol
Normal file
@@ -0,0 +1,238 @@
|
||||
/*
|
||||
|
||||
Copyright 2021 DODO ZOO.
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
*/
|
||||
pragma solidity 0.6.9;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import {SafeERC20} from "../../lib/SafeERC20.sol";
|
||||
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 {IRewardVault, RewardVault} from "./RewardVault.sol";
|
||||
|
||||
contract BaseMine is InitializableOwnable {
|
||||
using SafeERC20 for IERC20;
|
||||
using SafeMath for uint256;
|
||||
|
||||
// ============ Storage ============
|
||||
|
||||
struct RewardTokenInfo {
|
||||
address rewardToken;
|
||||
uint256 startBlock;
|
||||
uint256 endBlock;
|
||||
address rewardVault;
|
||||
uint256 rewardPerBlock;
|
||||
uint256 accRewardPerShare;
|
||||
uint256 lastRewardBlock;
|
||||
mapping(address => uint256) userRewardPerSharePaid;
|
||||
mapping(address => uint256) userRewards;
|
||||
}
|
||||
|
||||
RewardTokenInfo[] public rewardTokenInfos;
|
||||
|
||||
uint256 internal _totalSupply;
|
||||
mapping(address => uint256) internal _balances;
|
||||
|
||||
// ============ Event =============
|
||||
|
||||
event Claim(uint256 indexed i, address indexed user, uint256 reward);
|
||||
event UpdateReward(uint256 indexed i, uint256 rewardPerBlock);
|
||||
event UpdateEndBlock(uint256 indexed i, uint256 endBlock);
|
||||
event NewRewardToken(uint256 indexed i, address rewardToken);
|
||||
event RemoveRewardToken(address rewardToken);
|
||||
event WithdrawLeftOver(address owner, uint256 i);
|
||||
|
||||
// ============ View ============
|
||||
|
||||
function getPendingReward(address user, uint256 i) public view returns (uint256) {
|
||||
require(i<rewardTokenInfos.length, "DODOMineV2: REWARD_ID_NOT_FOUND");
|
||||
RewardTokenInfo storage rt = rewardTokenInfos[i];
|
||||
uint256 accRewardPerShare = rt.accRewardPerShare;
|
||||
if (rt.lastRewardBlock != block.number) {
|
||||
accRewardPerShare = _getAccRewardPerShare(i);
|
||||
}
|
||||
return
|
||||
DecimalMath.mulFloor(
|
||||
balanceOf(user),
|
||||
accRewardPerShare.sub(rt.userRewardPerSharePaid[user])
|
||||
).add(rt.userRewards[user]);
|
||||
}
|
||||
|
||||
function getPendingRewardByToken(address user, address rewardToken) external view returns (uint256) {
|
||||
return getPendingReward(user, getIdByRewardToken(rewardToken));
|
||||
}
|
||||
|
||||
function totalSupply() public view returns (uint256) {
|
||||
return _totalSupply;
|
||||
}
|
||||
|
||||
function balanceOf(address user) public view returns (uint256) {
|
||||
return _balances[user];
|
||||
}
|
||||
|
||||
function getRewardTokenById(uint256 i) external view returns (address) {
|
||||
require(i<rewardTokenInfos.length, "DODOMineV2: REWARD_ID_NOT_FOUND");
|
||||
RewardTokenInfo memory rt = rewardTokenInfos[i];
|
||||
return rt.rewardToken;
|
||||
}
|
||||
|
||||
function getIdByRewardToken(address rewardToken) public view returns(uint256) {
|
||||
uint256 len = rewardTokenInfos.length;
|
||||
for (uint256 i = 0; i < len; i++) {
|
||||
if (rewardToken == rewardTokenInfos[i].rewardToken) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
require(false, "DODOMineV2: TOKEN_NOT_FOUND");
|
||||
}
|
||||
|
||||
// ============ Claim ============
|
||||
|
||||
function claimReward(uint256 i) public {
|
||||
require(i<rewardTokenInfos.length, "DODOMineV2: REWARD_ID_NOT_FOUND");
|
||||
_updateReward(msg.sender, i);
|
||||
RewardTokenInfo storage rt = rewardTokenInfos[i];
|
||||
uint256 reward = rt.userRewards[msg.sender];
|
||||
if (reward > 0) {
|
||||
rt.userRewards[msg.sender] = 0;
|
||||
IRewardVault(rt.rewardVault).reward(msg.sender, reward);
|
||||
emit Claim(i, msg.sender, reward);
|
||||
}
|
||||
}
|
||||
|
||||
function claimAllRewards() external {
|
||||
uint256 len = rewardTokenInfos.length;
|
||||
for (uint256 i = 0; i < len; i++) {
|
||||
claimReward(i);
|
||||
}
|
||||
}
|
||||
|
||||
// =============== Ownable ================
|
||||
|
||||
function addRewardToken(
|
||||
address rewardToken,
|
||||
uint256 rewardPerBlock,
|
||||
uint256 startBlock,
|
||||
uint256 endBlock
|
||||
) external onlyOwner {
|
||||
require(rewardToken != address(0), "DODOMineV2: TOKEN_INVALID");
|
||||
require(startBlock > block.number, "DODOMineV2: START_BLOCK_INVALID");
|
||||
require(endBlock > startBlock, "DODOMineV2: DURATION_INVALID");
|
||||
|
||||
uint256 len = rewardTokenInfos.length;
|
||||
for (uint256 i = 0; i < len; i++) {
|
||||
require(
|
||||
rewardToken != rewardTokenInfos[i].rewardToken,
|
||||
"DODOMineV2: TOKEN_ALREADY_ADDED"
|
||||
);
|
||||
}
|
||||
|
||||
RewardTokenInfo storage rt = rewardTokenInfos.push();
|
||||
rt.rewardToken = rewardToken;
|
||||
rt.startBlock = startBlock;
|
||||
rt.endBlock = endBlock;
|
||||
rt.rewardPerBlock = rewardPerBlock;
|
||||
rt.rewardVault = address(new RewardVault(rewardToken));
|
||||
|
||||
emit NewRewardToken(len, rewardToken);
|
||||
}
|
||||
|
||||
function removeRewardToken(address rewardToken) external onlyOwner {
|
||||
uint256 len = rewardTokenInfos.length;
|
||||
for (uint256 i = 0; i < len; i++) {
|
||||
if (rewardToken == rewardTokenInfos[i].rewardToken) {
|
||||
if(i != len - 1) {
|
||||
rewardTokenInfos[i] = rewardTokenInfos[len - 1];
|
||||
}
|
||||
rewardTokenInfos.pop();
|
||||
emit RemoveRewardToken(rewardToken);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setEndBlock(uint256 i, uint256 newEndBlock)
|
||||
external
|
||||
onlyOwner
|
||||
{
|
||||
_updateReward(address(0), i);
|
||||
RewardTokenInfo storage rt = rewardTokenInfos[i];
|
||||
|
||||
require(block.number < newEndBlock, "DODOMineV2: END_BLOCK_INVALID");
|
||||
require(block.number > rt.startBlock, "DODOMineV2: NOT_START");
|
||||
require(block.number < rt.endBlock, "DODOMineV2: ALREADY_CLOSE");
|
||||
|
||||
rt.endBlock = newEndBlock;
|
||||
emit UpdateEndBlock(i, newEndBlock);
|
||||
}
|
||||
|
||||
function setReward(uint256 i, uint256 newRewardPerBlock)
|
||||
external
|
||||
onlyOwner
|
||||
{
|
||||
_updateReward(address(0), i);
|
||||
RewardTokenInfo storage rt = rewardTokenInfos[i];
|
||||
|
||||
require(block.number < rt.endBlock, "DODOMineV2: ALREADY_CLOSE");
|
||||
|
||||
rt.rewardPerBlock = newRewardPerBlock;
|
||||
emit UpdateReward(i, newRewardPerBlock);
|
||||
}
|
||||
|
||||
function withdrawLeftOver(uint256 i, uint256 amount) external onlyOwner {
|
||||
RewardTokenInfo storage rt = rewardTokenInfos[i];
|
||||
require(block.number > rt.endBlock, "DODOMineV2: MINING_NOT_FINISHED");
|
||||
|
||||
IRewardVault(rt.rewardVault).withdrawLeftOver(msg.sender,amount);
|
||||
|
||||
emit WithdrawLeftOver(msg.sender, i);
|
||||
}
|
||||
|
||||
|
||||
// ============ Internal ============
|
||||
|
||||
function _updateReward(address user, uint256 i) internal {
|
||||
RewardTokenInfo storage rt = rewardTokenInfos[i];
|
||||
if (rt.lastRewardBlock != block.number){
|
||||
rt.accRewardPerShare = _getAccRewardPerShare(i);
|
||||
rt.lastRewardBlock = block.number;
|
||||
}
|
||||
if (user != address(0)) {
|
||||
rt.userRewards[user] = getPendingReward(user, i);
|
||||
rt.userRewardPerSharePaid[user] = rt.accRewardPerShare;
|
||||
}
|
||||
}
|
||||
|
||||
function _updateAllReward(address user) internal {
|
||||
uint256 len = rewardTokenInfos.length;
|
||||
for (uint256 i = 0; i < len; i++) {
|
||||
_updateReward(user, i);
|
||||
}
|
||||
}
|
||||
|
||||
function _getUnrewardBlockNum(uint256 i) internal view returns (uint256) {
|
||||
RewardTokenInfo memory rt = rewardTokenInfos[i];
|
||||
if (block.number < rt.startBlock || rt.lastRewardBlock > rt.endBlock) {
|
||||
return 0;
|
||||
}
|
||||
uint256 start = rt.lastRewardBlock < rt.startBlock ? rt.startBlock : rt.lastRewardBlock;
|
||||
uint256 end = rt.endBlock < block.number ? rt.endBlock : block.number;
|
||||
return end.sub(start);
|
||||
}
|
||||
|
||||
function _getAccRewardPerShare(uint256 i) internal view returns (uint256) {
|
||||
RewardTokenInfo memory rt = rewardTokenInfos[i];
|
||||
if (totalSupply() == 0) {
|
||||
return rt.accRewardPerShare;
|
||||
}
|
||||
return
|
||||
rt.accRewardPerShare.add(
|
||||
DecimalMath.divFloor(_getUnrewardBlockNum(i).mul(rt.rewardPerBlock), totalSupply())
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
59
contracts/DODOToken/DODOMineV2/ERC20Mine.sol
Normal file
59
contracts/DODOToken/DODOMineV2/ERC20Mine.sol
Normal file
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
|
||||
Copyright 2021 DODO ZOO.
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
*/
|
||||
pragma solidity 0.6.9;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import {SafeERC20} from "../../lib/SafeERC20.sol";
|
||||
import {IERC20} from "../../intf/IERC20.sol";
|
||||
import {SafeMath} from "../../lib/SafeMath.sol";
|
||||
import {BaseMine} from "./BaseMine.sol";
|
||||
|
||||
contract ERC20Mine is BaseMine {
|
||||
using SafeERC20 for IERC20;
|
||||
using SafeMath for uint256;
|
||||
|
||||
// ============ Storage ============
|
||||
|
||||
address public immutable _TOKEN_;
|
||||
|
||||
constructor(address token) public {
|
||||
_TOKEN_ = token;
|
||||
}
|
||||
|
||||
// ============ Event ============
|
||||
|
||||
event Deposit(address indexed user, uint256 amount);
|
||||
event Withdraw(address indexed user, uint256 amount);
|
||||
|
||||
// ============ Deposit && Withdraw && Exit ============
|
||||
|
||||
function deposit(uint256 amount) external {
|
||||
require(amount > 0, "DODOMineV2: CANNOT_DEPOSIT_ZERO");
|
||||
|
||||
_updateAllReward(msg.sender);
|
||||
|
||||
uint256 erc20OriginBalance = IERC20(_TOKEN_).balanceOf(address(this));
|
||||
IERC20(_TOKEN_).safeTransferFrom(msg.sender, address(this), amount);
|
||||
uint256 actualStakeAmount = IERC20(_TOKEN_).balanceOf(address(this)).sub(erc20OriginBalance);
|
||||
|
||||
_totalSupply = _totalSupply.add(actualStakeAmount);
|
||||
_balances[msg.sender] = _balances[msg.sender].add(actualStakeAmount);
|
||||
|
||||
emit Deposit(msg.sender, actualStakeAmount);
|
||||
}
|
||||
|
||||
function withdraw(uint256 amount) external {
|
||||
require(amount > 0, "DODOMineV2: CANNOT_WITHDRAW_ZERO");
|
||||
|
||||
_updateAllReward(msg.sender);
|
||||
_totalSupply = _totalSupply.sub(amount);
|
||||
_balances[msg.sender] = _balances[msg.sender].sub(amount);
|
||||
IERC20(_TOKEN_).safeTransfer(msg.sender, amount);
|
||||
|
||||
emit Withdraw(msg.sender, amount);
|
||||
}
|
||||
}
|
||||
38
contracts/DODOToken/DODOMineV2/RewardVault.sol
Normal file
38
contracts/DODOToken/DODOMineV2/RewardVault.sol
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
|
||||
Copyright 2020 DODO ZOO.
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity 0.6.9;
|
||||
|
||||
import {Ownable} from "../../lib/Ownable.sol";
|
||||
import {SafeERC20} from "../../lib/SafeERC20.sol";
|
||||
import {IERC20} from "../../intf/IERC20.sol";
|
||||
|
||||
|
||||
interface IRewardVault {
|
||||
function reward(address to, uint256 amount) external;
|
||||
function withdrawLeftOver(address to, uint256 amount) external;
|
||||
}
|
||||
|
||||
contract RewardVault is Ownable {
|
||||
using SafeERC20 for IERC20;
|
||||
|
||||
address public rewardToken;
|
||||
|
||||
constructor(address _rewardToken) public {
|
||||
rewardToken = _rewardToken;
|
||||
}
|
||||
|
||||
function reward(address to, uint256 amount) external onlyOwner {
|
||||
IERC20(rewardToken).safeTransfer(to, amount);
|
||||
}
|
||||
|
||||
function withdrawLeftOver(address to,uint256 amount) external onlyOwner {
|
||||
uint256 leftover = IERC20(rewardToken).balanceOf(address(this));
|
||||
require(amount <= leftover, "VAULT_NOT_ENOUGH");
|
||||
IERC20(rewardToken).safeTransfer(to, amount);
|
||||
}
|
||||
}
|
||||
78
contracts/DODOToken/DODOMineV2/vDODOMine.sol
Normal file
78
contracts/DODOToken/DODOMineV2/vDODOMine.sol
Normal file
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
|
||||
Copyright 2021 DODO ZOO.
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity 0.6.9;
|
||||
|
||||
import {SafeERC20} from "../../lib/SafeERC20.sol";
|
||||
import {IERC20} from "../../intf/IERC20.sol";
|
||||
import {SafeMath} from "../../lib/SafeMath.sol";
|
||||
import {BaseMine} from "./BaseMine.sol";
|
||||
|
||||
interface IVDODOToken {
|
||||
function availableBalanceOf(address account) external view returns (uint256);
|
||||
}
|
||||
|
||||
contract vDODOMine is BaseMine {
|
||||
using SafeERC20 for IERC20;
|
||||
using SafeMath for uint256;
|
||||
|
||||
// ============ Storage ============
|
||||
address public immutable _vDODO_TOKEN_;
|
||||
|
||||
constructor(address vDODOToken) public {
|
||||
_vDODO_TOKEN_ = vDODOToken;
|
||||
}
|
||||
|
||||
// ============ Event =============
|
||||
|
||||
event Deposit(address indexed user, uint256 amount);
|
||||
event Withdraw(address indexed user, uint256 amount);
|
||||
event SyncBalance();
|
||||
|
||||
// ============ Deposit && Withdraw && Exit ============
|
||||
|
||||
function deposit(uint256 amount) external {
|
||||
require(amount > 0, "DODOMineV2: CANNOT_DEPOSIT_ZERO");
|
||||
require(
|
||||
amount <= IVDODOToken(_vDODO_TOKEN_).availableBalanceOf(msg.sender),
|
||||
"DODOMineV2: vDODO_NOT_ENOUGH"
|
||||
);
|
||||
_updateAllReward(msg.sender);
|
||||
_totalSupply = _totalSupply.add(amount);
|
||||
_balances[msg.sender] = _balances[msg.sender].add(amount);
|
||||
emit Deposit(msg.sender, amount);
|
||||
}
|
||||
|
||||
function withdraw(uint256 amount) external {
|
||||
require(amount > 0, "DODOMineV2: CANNOT_WITHDRAW_ZERO");
|
||||
require(amount <= _balances[msg.sender], "DODOMineV2: WITHDRAW_BALANCE_NOT_ENOUGH");
|
||||
_updateAllReward(msg.sender);
|
||||
_totalSupply = _totalSupply.sub(amount);
|
||||
_balances[msg.sender] = _balances[msg.sender].sub(amount);
|
||||
emit Withdraw(msg.sender, amount);
|
||||
}
|
||||
|
||||
function syncBalance(address[] calldata userList) external {
|
||||
for (uint256 i = 0; i < userList.length; ++i) {
|
||||
address user = userList[i];
|
||||
uint256 curBalance = balanceOf(user);
|
||||
uint256 vDODOBalance = IERC20(_vDODO_TOKEN_).balanceOf(user);
|
||||
if (curBalance > vDODOBalance) {
|
||||
_updateAllReward(user);
|
||||
_totalSupply = _totalSupply.add(vDODOBalance).sub(curBalance);
|
||||
_balances[user] = vDODOBalance;
|
||||
}
|
||||
}
|
||||
emit SyncBalance();
|
||||
}
|
||||
|
||||
// ============ View ============
|
||||
|
||||
function getLockedvDODO(address account) external view returns (uint256) {
|
||||
return balanceOf(account);
|
||||
}
|
||||
}
|
||||
@@ -5,14 +5,53 @@
|
||||
|
||||
*/
|
||||
pragma solidity 0.6.9;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import {InitializableOwnable} from "../lib/InitializableOwnable.sol";
|
||||
import {SafeMath} from "../lib/SafeMath.sol";
|
||||
|
||||
interface IVDODOMine {
|
||||
function balanceOf(address account) external view returns (uint256);
|
||||
}
|
||||
|
||||
//todo
|
||||
contract Governance is InitializableOwnable {
|
||||
using SafeMath for uint256;
|
||||
|
||||
function getLockedvDODO(address account) external pure returns (uint256 lockedvDODO) {
|
||||
lockedvDODO = 0;//todo for test
|
||||
// ============ Storage ============
|
||||
address[] public _VDODO_MINE_LIST_;
|
||||
|
||||
|
||||
// ============ Event =============
|
||||
event AddMineContract(address mineContract);
|
||||
event RemoveMineContract(address mineContract);
|
||||
|
||||
|
||||
function getLockedvDODO(address account) external view returns (uint256 lockedvDODO) {
|
||||
uint256 len = _VDODO_MINE_LIST_.length;
|
||||
for(uint i = 0; i < len; i++){
|
||||
uint256 curLocked = IVDODOMine(_VDODO_MINE_LIST_[i]).balanceOf(account);
|
||||
lockedvDODO = lockedvDODO.add(curLocked);
|
||||
}
|
||||
}
|
||||
|
||||
// =============== Ownable ================
|
||||
|
||||
function addMineContract(address[] memory mineContracts) external onlyOwner {
|
||||
for(uint i = 0; i < mineContracts.length; i++){
|
||||
require(mineContracts[i] != address(0),"ADDRESS_INVALID");
|
||||
_VDODO_MINE_LIST_.push(mineContracts[i]);
|
||||
emit AddMineContract(mineContracts[i]);
|
||||
}
|
||||
}
|
||||
|
||||
function removeMineContract(address mineContract) external onlyOwner {
|
||||
uint256 len = _VDODO_MINE_LIST_.length;
|
||||
for (uint256 i = 0; i < len; i++) {
|
||||
if (mineContract == _VDODO_MINE_LIST_[i]) {
|
||||
_VDODO_MINE_LIST_[i] = _VDODO_MINE_LIST_[len - 1];
|
||||
_VDODO_MINE_LIST_.pop();
|
||||
emit RemoveMineContract(mineContract);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ contract LockedTokenVault is Ownable {
|
||||
using SafeMath for uint256;
|
||||
using SafeERC20 for IERC20;
|
||||
|
||||
address _TOKEN_;
|
||||
address public immutable _TOKEN_;
|
||||
|
||||
mapping(address => uint256) internal originBalances;
|
||||
mapping(address => uint256) internal claimedBalances;
|
||||
|
||||
158
contracts/Factory/DSPFactory.sol
Normal file
158
contracts/Factory/DSPFactory.sol
Normal file
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
|
||||
Copyright 2020 DODO ZOO.
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity 0.6.9;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
import {InitializableOwnable} from "../lib/InitializableOwnable.sol";
|
||||
import {ICloneFactory} from "../lib/CloneFactory.sol";
|
||||
import {IDSP} from "../DODOStablePool/intf/IDSP.sol";
|
||||
|
||||
interface IDSPFactory {
|
||||
function createDODOStablePool(
|
||||
address baseToken,
|
||||
address quoteToken,
|
||||
uint256 lpFeeRate,
|
||||
uint256 i,
|
||||
uint256 k,
|
||||
bool isOpenTWAP
|
||||
) external returns (address newStablePool);
|
||||
}
|
||||
|
||||
/**
|
||||
* @title DODO StablePool Factory
|
||||
* @author DODO Breeder
|
||||
*
|
||||
* @notice Create And Register DSP Pools
|
||||
*/
|
||||
contract DSPFactory is InitializableOwnable {
|
||||
// ============ Templates ============
|
||||
|
||||
address public immutable _CLONE_FACTORY_;
|
||||
address public immutable _DEFAULT_MAINTAINER_;
|
||||
address public immutable _DEFAULT_MT_FEE_RATE_MODEL_;
|
||||
address public _DSP_TEMPLATE_;
|
||||
|
||||
// ============ Registry ============
|
||||
|
||||
// base -> quote -> DSP address list
|
||||
mapping(address => mapping(address => address[])) public _REGISTRY_;
|
||||
// creator -> DSP address list
|
||||
mapping(address => address[]) public _USER_REGISTRY_;
|
||||
|
||||
// ============ Events ============
|
||||
|
||||
event NewDSP(address baseToken, address quoteToken, address creator, address DSP);
|
||||
|
||||
event RemoveDSP(address DSP);
|
||||
|
||||
// ============ Functions ============
|
||||
|
||||
constructor(
|
||||
address cloneFactory,
|
||||
address DSPTemplate,
|
||||
address defaultMaintainer,
|
||||
address defaultMtFeeRateModel
|
||||
) public {
|
||||
_CLONE_FACTORY_ = cloneFactory;
|
||||
_DSP_TEMPLATE_ = DSPTemplate;
|
||||
_DEFAULT_MAINTAINER_ = defaultMaintainer;
|
||||
_DEFAULT_MT_FEE_RATE_MODEL_ = defaultMtFeeRateModel;
|
||||
}
|
||||
|
||||
function createDODOStablePool(
|
||||
address baseToken,
|
||||
address quoteToken,
|
||||
uint256 lpFeeRate,
|
||||
uint256 i,
|
||||
uint256 k,
|
||||
bool isOpenTWAP
|
||||
) external returns (address newStablePool) {
|
||||
newStablePool = ICloneFactory(_CLONE_FACTORY_).clone(_DSP_TEMPLATE_);
|
||||
{
|
||||
IDSP(newStablePool).init(
|
||||
_DEFAULT_MAINTAINER_,
|
||||
baseToken,
|
||||
quoteToken,
|
||||
lpFeeRate,
|
||||
_DEFAULT_MT_FEE_RATE_MODEL_,
|
||||
i,
|
||||
k,
|
||||
isOpenTWAP
|
||||
);
|
||||
}
|
||||
_REGISTRY_[baseToken][quoteToken].push(newStablePool);
|
||||
_USER_REGISTRY_[tx.origin].push(newStablePool);
|
||||
emit NewDSP(baseToken, quoteToken, tx.origin, newStablePool);
|
||||
}
|
||||
|
||||
// ============ Admin Operation Functions ============
|
||||
|
||||
function updateDSPTemplate(address _newDSPTemplate) external onlyOwner {
|
||||
_DSP_TEMPLATE_ = _newDSPTemplate;
|
||||
}
|
||||
|
||||
function addPoolByAdmin(
|
||||
address creator,
|
||||
address baseToken,
|
||||
address quoteToken,
|
||||
address pool
|
||||
) external onlyOwner {
|
||||
_REGISTRY_[baseToken][quoteToken].push(pool);
|
||||
_USER_REGISTRY_[creator].push(pool);
|
||||
emit NewDSP(baseToken, quoteToken, creator, pool);
|
||||
}
|
||||
|
||||
function removePoolByAdmin(
|
||||
address creator,
|
||||
address baseToken,
|
||||
address quoteToken,
|
||||
address pool
|
||||
) external onlyOwner {
|
||||
address[] memory registryList = _REGISTRY_[baseToken][quoteToken];
|
||||
for (uint256 i = 0; i < registryList.length; i++) {
|
||||
if (registryList[i] == pool) {
|
||||
registryList[i] = registryList[registryList.length - 1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
_REGISTRY_[baseToken][quoteToken] = registryList;
|
||||
_REGISTRY_[baseToken][quoteToken].pop();
|
||||
address[] memory userRegistryList = _USER_REGISTRY_[creator];
|
||||
for (uint256 i = 0; i < userRegistryList.length; i++) {
|
||||
if (userRegistryList[i] == pool) {
|
||||
userRegistryList[i] = userRegistryList[userRegistryList.length - 1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
_USER_REGISTRY_[creator] = userRegistryList;
|
||||
_USER_REGISTRY_[creator].pop();
|
||||
emit RemoveDSP(pool);
|
||||
}
|
||||
|
||||
// ============ View Functions ============
|
||||
|
||||
function getDODOPool(address baseToken, address quoteToken)
|
||||
external
|
||||
view
|
||||
returns (address[] memory machines)
|
||||
{
|
||||
return _REGISTRY_[baseToken][quoteToken];
|
||||
}
|
||||
|
||||
function getDODOPoolBidirection(address token0, address token1)
|
||||
external
|
||||
view
|
||||
returns (address[] memory baseToken0Machines, address[] memory baseToken1Machines)
|
||||
{
|
||||
return (_REGISTRY_[token0][token1], _REGISTRY_[token1][token0]);
|
||||
}
|
||||
|
||||
function getDODOPoolByUser(address user) external view returns (address[] memory machines) {
|
||||
return _USER_REGISTRY_[user];
|
||||
}
|
||||
}
|
||||
13
contracts/intf/IDODOApproveProxy.sol
Normal file
13
contracts/intf/IDODOApproveProxy.sol
Normal file
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
|
||||
Copyright 2020 DODO ZOO.
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity 0.6.9;
|
||||
|
||||
interface IDODOApproveProxy {
|
||||
function isAllowedProxy(address _proxy) external view returns (bool);
|
||||
function claimTokens(address token,address who,address dest,uint256 amount) external;
|
||||
}
|
||||
@@ -31,6 +31,13 @@ interface IDODOCallee {
|
||||
bytes calldata data
|
||||
) external;
|
||||
|
||||
function DSPFlashLoanCall(
|
||||
address sender,
|
||||
uint256 baseAmount,
|
||||
uint256 quoteAmount,
|
||||
bytes calldata data
|
||||
) external;
|
||||
|
||||
function CPCancelCall(
|
||||
address sender,
|
||||
uint256 amount,
|
||||
|
||||
@@ -197,7 +197,8 @@ network type: kovan
|
||||
Deploy time: 2021/3/10 下午11:27:00
|
||||
Deploy type: UpCrowdPoolingFactory
|
||||
UpCrowdPoolingFactory address: 0xe1C4300B47ccE8B162D8d036Db356c563a904757
|
||||
Init UpCpFactory Tx: 0x97ad372abbb2321e24e3e388585d47f0b1120980566abc3268c8305b04dc61f7====================================================
|
||||
Init UpCpFactory Tx: 0x97ad372abbb2321e24e3e388585d47f0b1120980566abc3268c8305b04dc61f7
|
||||
====================================================
|
||||
network type: bsclive
|
||||
Deploy time: 2021/3/13 下午11:36:30
|
||||
Deploy type: UpCrowdPoolingFactory
|
||||
|
||||
@@ -16,6 +16,7 @@ const DODOToken = artifacts.require("DODOToken");
|
||||
const UpCrowdPoolingFactory = artifacts.require("UpCrowdPoolingFactory");
|
||||
const CpFactory = artifacts.require("CrowdPoolingFactory");
|
||||
const MultiCall = artifacts.require("Multicall");
|
||||
const LockedTokenVault = artifacts.require("LockedTokenVault");
|
||||
|
||||
module.exports = async (deployer, network, accounts) => {
|
||||
let CONFIG = GetConfig(network, accounts)
|
||||
@@ -39,6 +40,22 @@ module.exports = async (deployer, network, accounts) => {
|
||||
let multiSigAddress = CONFIG.multiSigAddress;
|
||||
let defaultMaintainer = CONFIG.defaultMaintainer;
|
||||
|
||||
if(deploySwitch.LockedVault) {
|
||||
logger.log("====================================================");
|
||||
logger.log("network type: " + network);
|
||||
logger.log("Deploy time: " + new Date().toLocaleString());
|
||||
logger.log("Deploy type: LockedVault");
|
||||
await deployer.deploy(
|
||||
LockedTokenVault,
|
||||
"0xd8C30a4E866B188F16aD266dC3333BD47F34ebaE",
|
||||
1616468400,
|
||||
2592000,
|
||||
"100000000000000000"
|
||||
);
|
||||
logger.log("LockedVault address: ", LockedTokenVault.address);
|
||||
//TODO: approve && deposit
|
||||
}
|
||||
|
||||
if (deploySwitch.UpCP) {
|
||||
logger.log("====================================================");
|
||||
logger.log("network type: " + network);
|
||||
|
||||
357
test/DODOMineV2/erc20Mine.test.ts
Normal file
357
test/DODOMineV2/erc20Mine.test.ts
Normal file
@@ -0,0 +1,357 @@
|
||||
/*
|
||||
|
||||
Copyright 2021 DODO ZOO.
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
*/
|
||||
|
||||
import { decimalStr, fromWei } from '../utils/Converter';
|
||||
import { logGas } from '../utils/Log';
|
||||
import { DODOMineV2Context, getDODOMineContext } from '../utils/DODOMineV2Context';
|
||||
import { assert } from 'chai';
|
||||
import { Contract } from 'web3-eth-contract';
|
||||
|
||||
let account0: string;
|
||||
let account1: string;
|
||||
let projector: string;
|
||||
|
||||
async function init(ctx: DODOMineV2Context): Promise<void> {
|
||||
projector = ctx.Deployer;
|
||||
account0 = ctx.SpareAccounts[0];
|
||||
account1 = ctx.SpareAccounts[1];
|
||||
|
||||
//For User
|
||||
await ctx.mintTestToken(account0, ctx.ERC20, decimalStr("1000"));
|
||||
await ctx.mintTestToken(account1, ctx.ERC20, decimalStr("500"));
|
||||
|
||||
//For Project
|
||||
await ctx.mintTestToken(projector, ctx.REWARD_1, decimalStr("1000000"));
|
||||
await ctx.mintTestToken(projector, ctx.REWARD_2, decimalStr("1000000"));
|
||||
|
||||
await ctx.approveProxy(account0, ctx.ERC20Mine.options.address, ctx.ERC20);
|
||||
await ctx.approveProxy(account1, ctx.ERC20Mine.options.address, ctx.ERC20);
|
||||
}
|
||||
|
||||
async function addRewardToken(ctx: DODOMineV2Context, token: Contract, start: number, end: number, rewardPerBlock: string) {
|
||||
await ctx.ERC20Mine.methods.addRewardToken(
|
||||
token.options.address,
|
||||
rewardPerBlock,
|
||||
start,
|
||||
end
|
||||
).send(ctx.sendParam(projector));
|
||||
|
||||
let idx = await ctx.ERC20Mine.methods.getIdByRewardToken(token.options.address).call();
|
||||
let rewardInfo = await ctx.ERC20Mine.methods.rewardTokenInfos(idx).call();
|
||||
await token.methods.transfer(rewardInfo.rewardVault, decimalStr("10000")).send(ctx.sendParam(projector));
|
||||
}
|
||||
|
||||
async function stakeInfo(ctx: DODOMineV2Context, user: string, logInfo?: string) {
|
||||
console.log(logInfo)
|
||||
let totalSupply = await ctx.ERC20Mine.methods.totalSupply().call();
|
||||
let balance = await ctx.ERC20Mine.methods.balanceOf(user).call();
|
||||
console.log("totalSupply:" + fromWei(totalSupply, "ether") + " balance:" + fromWei(balance, "ether"));
|
||||
}
|
||||
|
||||
async function getRewardInfo(ctx: DODOMineV2Context, idx: number, user: string, logInfo?: string) {
|
||||
let erc20Mine = ctx.ERC20Mine
|
||||
let obj = await erc20Mine.methods.rewardTokenInfos(idx).call();
|
||||
let curBlock = await ctx.Web3.eth.getBlockNumber();
|
||||
console.log(logInfo);
|
||||
// console.log("Static-Data: rewardToken:" + obj.rewardToken + " rewardVault:" + obj.rewardVault + " rewardPerBlock:" + fromWei(obj.rewardPerBlock, "ether"));
|
||||
console.log("Dynamic-Data: start:" + obj.startBlock + " end:" + obj.endBlock + " accRewardPerShare:" + fromWei(obj.accRewardPerShare, "ether") + " lastRewardBlock:" + obj.lastRewardBlock + " curBlock:" + curBlock);
|
||||
var pendingReward = null;
|
||||
if (user != null) {
|
||||
pendingReward = await erc20Mine.methods.getPendingReward(user, idx).call();
|
||||
console.log("User-pendingReward:" + fromWei(pendingReward, "ether"));
|
||||
}
|
||||
return [obj, pendingReward];
|
||||
}
|
||||
|
||||
describe("erc20Mine", () => {
|
||||
let snapshotId: string;
|
||||
let ctx: DODOMineV2Context;
|
||||
|
||||
before(async () => {
|
||||
ctx = await getDODOMineContext(null);
|
||||
await init(ctx);
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
snapshotId = await ctx.EVM.snapshot();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await ctx.EVM.reset(snapshotId);
|
||||
});
|
||||
|
||||
|
||||
describe("baseMine", () => {
|
||||
// ======= Ownable =========
|
||||
it("addRewardToken", async () => {
|
||||
let erc20Mine = ctx.ERC20Mine;
|
||||
var curBlock = await ctx.Web3.eth.getBlockNumber();
|
||||
await erc20Mine.methods.addRewardToken(
|
||||
ctx.REWARD_1.options.address,
|
||||
decimalStr("0"),
|
||||
curBlock + 2,
|
||||
curBlock + 1000
|
||||
).send(ctx.sendParam(projector));
|
||||
let [rewardTokenInfo,] = await getRewardInfo(ctx, 0, null, "");
|
||||
assert(rewardTokenInfo.rewardPerBlock, decimalStr("0"))
|
||||
});
|
||||
|
||||
it("removeRewardToken", async () => {
|
||||
let erc20Mine = ctx.ERC20Mine;
|
||||
var curBlock = await ctx.Web3.eth.getBlockNumber();
|
||||
await addRewardToken(ctx, ctx.REWARD_1, curBlock + 10, curBlock + 110, decimalStr("0"));
|
||||
await addRewardToken(ctx, ctx.REWARD_2, curBlock + 10, curBlock + 110, decimalStr("0"));
|
||||
let [rewardTokenInfo,] = await getRewardInfo(ctx, 0, null, "");
|
||||
await erc20Mine.methods.removeRewardToken(
|
||||
rewardTokenInfo.rewardToken
|
||||
).send(ctx.sendParam(projector));
|
||||
[rewardTokenInfo,] = await getRewardInfo(ctx, 0, null, "");
|
||||
assert(rewardTokenInfo.rewardToken, ctx.REWARD_2.options.address)
|
||||
});
|
||||
// ===========================
|
||||
|
||||
})
|
||||
|
||||
describe("erc20Mine", () => {
|
||||
|
||||
it("deposit", async () => {
|
||||
var curBlock = await ctx.Web3.eth.getBlockNumber();
|
||||
await addRewardToken(ctx, ctx.REWARD_1, curBlock + 2, curBlock + 102, decimalStr("10"));
|
||||
await stakeInfo(ctx, account0, "UserStakeInfo - Before");
|
||||
await getRewardInfo(ctx, 0, account0, "UserRewardInfo - Before");
|
||||
//增加区块
|
||||
await ctx.increBlock(3);
|
||||
|
||||
// curBlock = await ctx.Web3.eth.getBlockNumber();
|
||||
// console.log("deposit curBlock:", curBlock)
|
||||
await logGas(await ctx.ERC20Mine.methods.deposit(
|
||||
decimalStr("5")
|
||||
), ctx.sendParam(account0), "deposit");
|
||||
await logGas(await ctx.ERC20Mine.methods.deposit(
|
||||
decimalStr("10")
|
||||
), ctx.sendParam(account0), "deposit");
|
||||
await logGas(await ctx.ERC20Mine.methods.deposit(
|
||||
decimalStr("5")
|
||||
), ctx.sendParam(account0), "deposit");
|
||||
|
||||
await stakeInfo(ctx, account0, "UserStakeInfo - After");
|
||||
await getRewardInfo(ctx, 0, account0, "UserRewardInfo - After - 1");
|
||||
//增加区块
|
||||
await ctx.increBlock(3);
|
||||
|
||||
let [obj, pendingReward] = await getRewardInfo(ctx, 0, account0, "UserRewardInfo - After - 2");
|
||||
|
||||
assert.equal(obj.accRewardPerShare, "2666666666666666666");
|
||||
assert.equal(pendingReward, "49999999999999999990");
|
||||
});
|
||||
|
||||
|
||||
it("withdraw", async () => {
|
||||
var curBlock = await ctx.Web3.eth.getBlockNumber();
|
||||
await addRewardToken(ctx, ctx.REWARD_1, curBlock + 2, curBlock + 102, decimalStr("10"));
|
||||
// await stakeInfo(ctx, account0, "UserStakeInfo - Before");
|
||||
// await getRewardInfo(ctx, 0, account0, "UserRewardInfo - Before");
|
||||
//增加区块
|
||||
await ctx.increBlock(3);
|
||||
|
||||
await logGas(await ctx.ERC20Mine.methods.deposit(
|
||||
decimalStr("20")
|
||||
), ctx.sendParam(account0), "deposit - account0");
|
||||
await logGas(await ctx.ERC20Mine.methods.deposit(
|
||||
decimalStr("10")
|
||||
), ctx.sendParam(account1), "deposit - account1");
|
||||
|
||||
await getRewardInfo(ctx, 0, account0, "UserRewardInfo - After - 1");
|
||||
await ctx.increBlock(3);
|
||||
await getRewardInfo(ctx, 0, account0, "UserRewardInfo - After - 2");
|
||||
|
||||
await logGas(await ctx.ERC20Mine.methods.withdraw(
|
||||
decimalStr("10")
|
||||
), ctx.sendParam(account0), "withdraw");
|
||||
await getRewardInfo(ctx, 0, account0, "UserRewardInfo - After - 3");
|
||||
await logGas(await ctx.ERC20Mine.methods.withdraw(
|
||||
decimalStr("10")
|
||||
), ctx.sendParam(account0), "withdraw");
|
||||
|
||||
//增加区块
|
||||
await ctx.increBlock(3);
|
||||
let [obj, pendingReward] = await getRewardInfo(ctx, 0, account0, "UserRewardInfo - After - 4");
|
||||
|
||||
assert.equal(obj.accRewardPerShare, "2333333333333333333");
|
||||
assert.equal(pendingReward, "41666666666666666660");
|
||||
});
|
||||
|
||||
|
||||
it("getReward", async () => {
|
||||
var curBlock = await ctx.Web3.eth.getBlockNumber();
|
||||
await addRewardToken(ctx, ctx.REWARD_1, curBlock + 2, curBlock + 102, decimalStr("10"));
|
||||
await stakeInfo(ctx, account0, "UserStakeInfo - Before");
|
||||
await getRewardInfo(ctx, 0, account0, "UserRewardInfo - Before");
|
||||
|
||||
//增加区块
|
||||
await ctx.increBlock(3);
|
||||
|
||||
await logGas(await ctx.ERC20Mine.methods.deposit(
|
||||
decimalStr("10")
|
||||
), ctx.sendParam(account0), "deposit");
|
||||
await logGas(await ctx.ERC20Mine.methods.deposit(
|
||||
decimalStr("10")
|
||||
), ctx.sendParam(account1), "deposit");
|
||||
|
||||
//增加区块
|
||||
await ctx.increBlock(3);
|
||||
|
||||
await getRewardInfo(ctx, 0, account0, "UserRewardInfo - After");
|
||||
await logGas(await ctx.ERC20Mine.methods.claimReward(0), ctx.sendParam(account0), "claimReward - 0");
|
||||
|
||||
let rewardBalance = await ctx.REWARD_1.methods.balanceOf(account0).call();
|
||||
assert.equal(rewardBalance, "30000000000000000000");
|
||||
|
||||
});
|
||||
|
||||
it("getRewardAll", async () => {
|
||||
var curBlock = await ctx.Web3.eth.getBlockNumber();
|
||||
await addRewardToken(ctx, ctx.REWARD_1, curBlock + 5, curBlock + 103, decimalStr("10"));
|
||||
await addRewardToken(ctx, ctx.REWARD_2, curBlock + 5, curBlock + 103, decimalStr("5"));
|
||||
await stakeInfo(ctx, account0, "UserStakeInfo - Before");
|
||||
await getRewardInfo(ctx, 0, account0, "UserRewardInfo - Before");
|
||||
|
||||
//增加区块
|
||||
await ctx.increBlock(10);
|
||||
|
||||
await logGas(await ctx.ERC20Mine.methods.deposit(
|
||||
decimalStr("10")
|
||||
), ctx.sendParam(account0), "deposit");
|
||||
await logGas(await ctx.ERC20Mine.methods.deposit(
|
||||
decimalStr("10")
|
||||
), ctx.sendParam(account1), "deposit");
|
||||
|
||||
//增加区块
|
||||
await ctx.increBlock(3);
|
||||
|
||||
await getRewardInfo(ctx, 0, account0, "UserRewardInfo - After");
|
||||
await logGas(await ctx.ERC20Mine.methods.claimAllRewards(), ctx.sendParam(account0), "claimReward - 0");
|
||||
|
||||
let rewardBalance0 = await ctx.REWARD_1.methods.balanceOf(account0).call();
|
||||
let rewardBalance1 = await ctx.REWARD_2.methods.balanceOf(account0).call();
|
||||
assert.equal(rewardBalance0, "30000000000000000000");
|
||||
assert.equal(rewardBalance1, "15000000000000000000");
|
||||
});
|
||||
|
||||
it("setReward - beforeStart", async () => {
|
||||
var curBlock = await ctx.Web3.eth.getBlockNumber();
|
||||
await addRewardToken(ctx, ctx.REWARD_1, curBlock + 10, curBlock + 100, decimalStr("10"));
|
||||
await ctx.ERC20Mine.methods.setReward(0, decimalStr("5")).send(ctx.sendParam(projector));
|
||||
|
||||
//增加区块
|
||||
await ctx.increBlock(10);
|
||||
|
||||
await logGas(await ctx.ERC20Mine.methods.deposit(
|
||||
decimalStr("10")
|
||||
), ctx.sendParam(account0), "deposit");
|
||||
await logGas(await ctx.ERC20Mine.methods.deposit(
|
||||
decimalStr("10")
|
||||
), ctx.sendParam(account1), "deposit");
|
||||
|
||||
//增加区块
|
||||
await ctx.increBlock(3);
|
||||
|
||||
await getRewardInfo(ctx, 0, account0, "UserRewardInfo - After");
|
||||
await logGas(await ctx.ERC20Mine.methods.claimReward(0), ctx.sendParam(account0), "claimReward - 0");
|
||||
|
||||
let rewardBalance = await ctx.REWARD_1.methods.balanceOf(account0).call();
|
||||
assert.equal(rewardBalance, "15000000000000000000");
|
||||
})
|
||||
|
||||
it("setReward - ing", async () => {
|
||||
var curBlock = await ctx.Web3.eth.getBlockNumber();
|
||||
await addRewardToken(ctx, ctx.REWARD_1, curBlock + 2, curBlock + 100, decimalStr("10"));
|
||||
|
||||
//增加区块
|
||||
await ctx.increBlock(3);
|
||||
|
||||
await logGas(await ctx.ERC20Mine.methods.deposit(
|
||||
decimalStr("10")
|
||||
), ctx.sendParam(account0), "deposit");
|
||||
await logGas(await ctx.ERC20Mine.methods.deposit(
|
||||
decimalStr("10")
|
||||
), ctx.sendParam(account1), "deposit");
|
||||
|
||||
//增加区块
|
||||
await ctx.increBlock(3);
|
||||
|
||||
let [, pendingReward] = await getRewardInfo(ctx, 0, account0, "UserRewardInfo - After");
|
||||
assert.equal(pendingReward, "25000000000000000000");
|
||||
|
||||
await ctx.ERC20Mine.methods.setReward(0, decimalStr("5")).send(ctx.sendParam(projector));
|
||||
|
||||
//增加区块
|
||||
await ctx.increBlock(3);
|
||||
|
||||
[, pendingReward] = await getRewardInfo(ctx, 0, account0, "UserRewardInfo - After");
|
||||
await logGas(await ctx.ERC20Mine.methods.claimReward(0), ctx.sendParam(account0), "claimReward - 0");
|
||||
|
||||
let rewardBalance = await ctx.REWARD_1.methods.balanceOf(account0).call();
|
||||
assert.equal(rewardBalance, "40000000000000000000");
|
||||
})
|
||||
|
||||
// it("setReward - after", async () => {
|
||||
// var curBlock = await ctx.Web3.eth.getBlockNumber();
|
||||
// await addRewardToken(ctx, ctx.REWARD_1, curBlock + 2, curBlock + 10, decimalStr("10"));
|
||||
|
||||
// //增加区块
|
||||
// await ctx.increBlock(3);
|
||||
|
||||
// await logGas(await ctx.ERC20Mine.methods.deposit(
|
||||
// decimalStr("10")
|
||||
// ), ctx.sendParam(account0), "deposit");
|
||||
// await logGas(await ctx.ERC20Mine.methods.deposit(
|
||||
// decimalStr("10")
|
||||
// ), ctx.sendParam(account1), "deposit");
|
||||
|
||||
// //增加区块
|
||||
// await ctx.increBlock(3);
|
||||
|
||||
// let [, pendingReward] = await getRewardInfo(ctx, 0, account0, "UserRewardInfo - After");
|
||||
// assert.equal(pendingReward, "25000000000000000000");
|
||||
|
||||
// await ctx.ERC20Mine.methods.setReward(0, decimalStr("5")).send(ctx.sendParam(projector));
|
||||
|
||||
// //增加区块
|
||||
// await ctx.increBlock(3);
|
||||
|
||||
// [, pendingReward] = await getRewardInfo(ctx, 0, account0, "UserRewardInfo - After");
|
||||
// await logGas(await ctx.ERC20Mine.methods.claimReward(0), ctx.sendParam(account0), "claimReward - 0");
|
||||
|
||||
// let rewardBalance = await ctx.REWARD_1.methods.balanceOf(account0).call();
|
||||
// assert.equal(rewardBalance, "25000000000000000000");
|
||||
// })
|
||||
|
||||
it("setEndBlock", async () => {
|
||||
var curBlock = await ctx.Web3.eth.getBlockNumber();
|
||||
await addRewardToken(ctx, ctx.REWARD_1, curBlock + 2, curBlock + 100, decimalStr("10"));
|
||||
|
||||
//增加区块
|
||||
await ctx.increBlock(3);
|
||||
|
||||
await logGas(await ctx.ERC20Mine.methods.deposit(
|
||||
decimalStr("10")
|
||||
), ctx.sendParam(account0), "deposit");
|
||||
await logGas(await ctx.ERC20Mine.methods.deposit(
|
||||
decimalStr("10")
|
||||
), ctx.sendParam(account1), "deposit");
|
||||
|
||||
//增加区块
|
||||
await ctx.increBlock(3);
|
||||
|
||||
await ctx.ERC20Mine.methods.setEndBlock(0, curBlock + 120).send(ctx.sendParam(projector));
|
||||
let [obj,] = await getRewardInfo(ctx, 0, account0, "UserRewardInfo - After");
|
||||
assert(obj.endBlock - curBlock - 100, "20");
|
||||
})
|
||||
|
||||
})
|
||||
});
|
||||
194
test/DODOMineV2/vDODOMine.test.ts
Normal file
194
test/DODOMineV2/vDODOMine.test.ts
Normal file
@@ -0,0 +1,194 @@
|
||||
/*
|
||||
|
||||
Copyright 2021 DODO ZOO.
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
*/
|
||||
|
||||
import { decimalStr, fromWei } from '../utils/Converter';
|
||||
import { logGas } from '../utils/Log';
|
||||
import { DODOMineV2Context, getDODOMineContext } from '../utils/DODOMineV2Context';
|
||||
import { VDODOContext, getVDODOContext } from '../utils/VDODOContext';
|
||||
import { assert } from 'chai';
|
||||
import { Contract } from 'web3-eth-contract';
|
||||
const truffleAssert = require('truffle-assertions');
|
||||
|
||||
let account0: string;
|
||||
let account1: string;
|
||||
let projector: string;
|
||||
let dodoTeam: string;
|
||||
|
||||
async function init(ctx: DODOMineV2Context): Promise<void> {
|
||||
projector = ctx.Deployer;
|
||||
account0 = ctx.SpareAccounts[0];
|
||||
account1 = ctx.SpareAccounts[1];
|
||||
|
||||
//For Project
|
||||
await ctx.mintTestToken(projector, ctx.REWARD_1, decimalStr("1000000"));
|
||||
await ctx.mintTestToken(projector, ctx.REWARD_2, decimalStr("1000000"));
|
||||
|
||||
await ctx.approveProxy(account0, ctx.VDODOMine.options.address, ctx.ERC20);
|
||||
await ctx.approveProxy(account1, ctx.VDODOMine.options.address, ctx.ERC20);
|
||||
}
|
||||
|
||||
async function initVdodo(ctx: VDODOContext): Promise<void> {
|
||||
dodoTeam = ctx.Deployer;
|
||||
await ctx.mintTestToken(account0, decimalStr("10000"));
|
||||
await ctx.mintTestToken(account1, decimalStr("10000"));
|
||||
|
||||
await ctx.approveProxy(account0);
|
||||
await ctx.approveProxy(account1);
|
||||
}
|
||||
|
||||
async function mint(ctx: VDODOContext, user: string, mintAmount: string, superior: string) {
|
||||
await ctx.VDODO.methods.mint(
|
||||
mintAmount,
|
||||
superior
|
||||
).send(ctx.sendParam(user));
|
||||
}
|
||||
|
||||
async function addRewardToken(ctx: DODOMineV2Context, token: Contract, start: number, end: number, rewardPerBlock: string) {
|
||||
await ctx.VDODOMine.methods.addRewardToken(
|
||||
token.options.address,
|
||||
rewardPerBlock,
|
||||
start,
|
||||
end
|
||||
).send(ctx.sendParam(projector));
|
||||
|
||||
let idx = await ctx.VDODOMine.methods.getIdByRewardToken(token.options.address).call();
|
||||
let rewardInfo = await ctx.VDODOMine.methods.rewardTokenInfos(idx).call();
|
||||
await token.methods.transfer(rewardInfo.rewardVault, decimalStr("10000")).send(ctx.sendParam(projector));
|
||||
}
|
||||
|
||||
async function stakeInfo(ctx: DODOMineV2Context, user: string, logInfo?: string) {
|
||||
console.log(logInfo)
|
||||
let totalSupply = await ctx.VDODOMine.methods.totalSupply().call();
|
||||
let balance = await ctx.VDODOMine.methods.balanceOf(user).call();
|
||||
console.log("totalSupply:" + fromWei(totalSupply, "ether") + " balance:" + fromWei(balance, "ether"));
|
||||
}
|
||||
|
||||
async function vdodoBalance(ctx: VDODOContext, user: string, logInfo?: string) {
|
||||
console.log(logInfo)
|
||||
let dodoBalance = await ctx.VDODO.methods.dodoBalanceOf(user).call();
|
||||
let availableBalance = await ctx.VDODO.methods.availableBalanceOf(user).call();
|
||||
console.log("dodoBalance:" + fromWei(dodoBalance, "ether") + " availableBalance:" + fromWei(availableBalance, "ether"));
|
||||
return [dodoBalance, availableBalance]
|
||||
}
|
||||
|
||||
async function getRewardInfo(ctx: DODOMineV2Context, idx: number, user: string, logInfo?: string) {
|
||||
let VDODOMine = ctx.VDODOMine
|
||||
let obj = await VDODOMine.methods.rewardTokenInfos(idx).call();
|
||||
let curBlock = await ctx.Web3.eth.getBlockNumber();
|
||||
console.log(logInfo);
|
||||
// console.log("Static-Data: rewardToken:" + obj.rewardToken + " rewardVault:" + obj.rewardVault + " rewardPerBlock:" + fromWei(obj.rewardPerBlock, "ether"));
|
||||
console.log("Dynamic-Data: start:" + obj.startBlock + " end:" + obj.endBlock + " accRewardPerShare:" + fromWei(obj.accRewardPerShare, "ether") + " lastRewardBlock:" + obj.lastRewardBlock + " curBlock:" + curBlock);
|
||||
var pendingReward = null;
|
||||
if (user != null) {
|
||||
pendingReward = await VDODOMine.methods.getPendingReward(user, idx).call();
|
||||
console.log("User-pendingReward:" + fromWei(pendingReward, "ether"));
|
||||
}
|
||||
return [obj, pendingReward];
|
||||
}
|
||||
|
||||
describe("VDODOMine", () => {
|
||||
let snapshotId: string;
|
||||
let ctx: DODOMineV2Context;
|
||||
let ctxVdodo: VDODOContext;
|
||||
|
||||
before(async () => {
|
||||
ctxVdodo = await getVDODOContext();
|
||||
ctx = await getDODOMineContext(ctxVdodo.VDODO.options.address);
|
||||
await init(ctx);
|
||||
await initVdodo(ctxVdodo);
|
||||
await ctxVdodo.VDODO.methods.updateGovernance(ctx.VDODOMine.options.address).send(ctxVdodo.sendParam(ctxVdodo.Deployer));
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
snapshotId = await ctx.EVM.snapshot();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await ctx.EVM.reset(snapshotId);
|
||||
});
|
||||
|
||||
describe("VDODOMine", () => {
|
||||
|
||||
it("deposit", async () => {
|
||||
await mint(ctxVdodo, account0, decimalStr("10000"), dodoTeam);
|
||||
await vdodoBalance(ctxVdodo, account0, "vDODOBalance - before");
|
||||
|
||||
var curBlock = await ctx.Web3.eth.getBlockNumber();
|
||||
await addRewardToken(ctx, ctx.REWARD_1, curBlock + 2, curBlock + 102, decimalStr("10"));
|
||||
await stakeInfo(ctx, account0, "UserStakeInfo - Before");
|
||||
await getRewardInfo(ctx, 0, account0, "UserRewardInfo - Before");
|
||||
|
||||
//增加区块
|
||||
await ctx.increBlock(3);
|
||||
|
||||
await logGas(await ctx.VDODOMine.methods.deposit(
|
||||
decimalStr("5")
|
||||
), ctx.sendParam(account0), "deposit - 0");
|
||||
|
||||
await logGas(await ctx.VDODOMine.methods.deposit(
|
||||
decimalStr("5")
|
||||
), ctx.sendParam(account0), "deposit - 1");
|
||||
|
||||
await stakeInfo(ctx, account0, "UserStakeInfo - After");
|
||||
let [, pendingReward] = await getRewardInfo(ctx, 0, account0, "UserRewardInfo - After - 1");
|
||||
let [, availableBalance] = await vdodoBalance(ctxVdodo, account0, "vDODOBalance - after");
|
||||
|
||||
assert.equal(pendingReward, decimalStr("10"));
|
||||
assert.equal(availableBalance, "90063636363636363600");
|
||||
});
|
||||
|
||||
|
||||
it("withdraw", async () => {
|
||||
await mint(ctxVdodo, account0, decimalStr("10000"), dodoTeam);
|
||||
await vdodoBalance(ctxVdodo, account0, "vDODOBalance - before");
|
||||
|
||||
var curBlock = await ctx.Web3.eth.getBlockNumber();
|
||||
await addRewardToken(ctx, ctx.REWARD_1, curBlock + 2, curBlock + 102, decimalStr("10"));
|
||||
await stakeInfo(ctx, account0, "UserStakeInfo - Before");
|
||||
await getRewardInfo(ctx, 0, account0, "UserRewardInfo - Before");
|
||||
|
||||
await ctx.increBlock(3);
|
||||
|
||||
await logGas(await ctx.VDODOMine.methods.deposit(
|
||||
decimalStr("10")
|
||||
), ctx.sendParam(account0), "deposit");
|
||||
|
||||
await ctx.increBlock(3);
|
||||
|
||||
await logGas(await ctx.VDODOMine.methods.withdraw(
|
||||
decimalStr("5")
|
||||
), ctx.sendParam(account0), "withdraw");
|
||||
|
||||
await stakeInfo(ctx, account0, "UserStakeInfo - After");
|
||||
let [, pendingReward] = await getRewardInfo(ctx, 0, account0, "UserRewardInfo - After");
|
||||
let [, availableBalance] = await vdodoBalance(ctxVdodo, account0, "vDODOBalance - after");
|
||||
|
||||
assert.equal(pendingReward, decimalStr("40"));
|
||||
assert.equal(availableBalance, "95090909090909090900");
|
||||
});
|
||||
|
||||
|
||||
it("revert case", async () => {
|
||||
await mint(ctxVdodo, account0, decimalStr("10000"), dodoTeam);
|
||||
|
||||
var curBlock = await ctx.Web3.eth.getBlockNumber();
|
||||
await addRewardToken(ctx, ctx.REWARD_1, curBlock + 2, curBlock + 102, decimalStr("10"));
|
||||
|
||||
//增加区块
|
||||
await ctx.increBlock(3);
|
||||
|
||||
await logGas(await ctx.VDODOMine.methods.deposit(
|
||||
decimalStr("10")
|
||||
), ctx.sendParam(account0), "deposit - 0");
|
||||
|
||||
await truffleAssert.reverts(
|
||||
ctxVdodo.VDODO.methods.redeem(decimalStr("95"), false).send(ctxVdodo.sendParam(account0)),
|
||||
"vDODOToken: available amount not enough"
|
||||
)
|
||||
})
|
||||
})
|
||||
});
|
||||
188
test/DSP/funding.test.ts
Normal file
188
test/DSP/funding.test.ts
Normal file
@@ -0,0 +1,188 @@
|
||||
/*
|
||||
|
||||
Copyright 2020 DODO ZOO.
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
*/
|
||||
|
||||
// import * as assert from 'assert';
|
||||
|
||||
import { decimalStr, MAX_UINT256 } from '../utils/Converter';
|
||||
import { logGas } from '../utils/Log';
|
||||
import { DSPContext, getDSPContext } from '../utils/DSPContext';
|
||||
import { assert } from 'chai';
|
||||
import BigNumber from 'bignumber.js';
|
||||
const truffleAssert = require('truffle-assertions');
|
||||
|
||||
let lp: string;
|
||||
let trader: string;
|
||||
|
||||
async function init(ctx: DSPContext): Promise<void> {
|
||||
lp = ctx.SpareAccounts[0];
|
||||
trader = ctx.SpareAccounts[1];
|
||||
|
||||
await ctx.mintTestToken(lp, decimalStr("1000"), decimalStr("1000"));
|
||||
await ctx.mintTestToken(trader, decimalStr("1000"), decimalStr("1000"));
|
||||
}
|
||||
|
||||
describe("Funding", () => {
|
||||
let snapshotId: string;
|
||||
let ctx: DSPContext;
|
||||
|
||||
before(async () => {
|
||||
ctx = await getDSPContext();
|
||||
await init(ctx);
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
snapshotId = await ctx.EVM.snapshot();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await ctx.EVM.reset(snapshotId);
|
||||
});
|
||||
|
||||
describe("buy shares", () => {
|
||||
|
||||
it("revert cases", async () => {
|
||||
await ctx.transferBaseToDSP(lp, decimalStr("10"))
|
||||
await truffleAssert.reverts(
|
||||
ctx.DSP.methods.buyShares(lp).send(ctx.sendParam(lp)),
|
||||
"MINT_AMOUNT_NOT_ENOUGH"
|
||||
)
|
||||
})
|
||||
|
||||
it("buy shares from init states", async () => {
|
||||
await ctx.transferBaseToDSP(lp, decimalStr("100"))
|
||||
await ctx.transferQuoteToDSP(lp, decimalStr("100"))
|
||||
await ctx.DSP.methods.buyShares(lp).send(ctx.sendParam(lp));
|
||||
assert.equal(await ctx.DSP.methods.balanceOf(lp).call(), decimalStr("100"))
|
||||
assert.equal(await ctx.DSP.methods.getMidPrice().call(), decimalStr("1"))
|
||||
})
|
||||
|
||||
it("buy shares with balanced input", async () => {
|
||||
await ctx.transferBaseToDSP(lp, decimalStr("100"))
|
||||
await ctx.transferQuoteToDSP(lp, decimalStr("100"))
|
||||
await ctx.DSP.methods.buyShares(lp).send(ctx.sendParam(lp))
|
||||
|
||||
await ctx.transferQuoteToDSP(trader, decimalStr("20"))
|
||||
await ctx.DSP.methods.sellQuote(trader).send(ctx.sendParam(trader))
|
||||
|
||||
var vaultBaseBalance = new BigNumber(await ctx.BASE.methods.balanceOf(ctx.DSP.options.address).call())
|
||||
var vaultQuoteBalance = new BigNumber(await ctx.QUOTE.methods.balanceOf(ctx.DSP.options.address).call())
|
||||
var increaseRatio = new BigNumber("0.1")
|
||||
|
||||
await ctx.transferBaseToDSP(trader, vaultBaseBalance.multipliedBy(increaseRatio).toFixed(0))
|
||||
await ctx.transferQuoteToDSP(trader, vaultQuoteBalance.multipliedBy(increaseRatio).toFixed(0))
|
||||
await ctx.DSP.methods.buyShares(trader).send(ctx.sendParam(trader))
|
||||
|
||||
assert.equal(
|
||||
await ctx.BASE.methods.balanceOf(ctx.DSP.options.address).call(),
|
||||
"88521163953680151790"
|
||||
);
|
||||
assert.equal(
|
||||
await ctx.QUOTE.methods.balanceOf(ctx.DSP.options.address).call(),
|
||||
"132000000000000000000"
|
||||
);
|
||||
|
||||
assert.equal(await ctx.DSP.methods.balanceOf(trader).call(), decimalStr("10"))
|
||||
})
|
||||
|
||||
it("buy shares with unbalanced input (less quote)", async () => {
|
||||
await ctx.transferBaseToDSP(lp, decimalStr("100"))
|
||||
await ctx.transferQuoteToDSP(lp, decimalStr("100"))
|
||||
await ctx.DSP.methods.buyShares(lp).send(ctx.sendParam(lp))
|
||||
|
||||
await ctx.transferQuoteToDSP(trader, decimalStr("20"))
|
||||
await ctx.DSP.methods.sellQuote(trader).send(ctx.sendParam(trader))
|
||||
|
||||
var vaultBaseBalance = new BigNumber(await ctx.BASE.methods.balanceOf(ctx.DSP.options.address).call())
|
||||
var vaultQuoteBalance = new BigNumber(await ctx.QUOTE.methods.balanceOf(ctx.DSP.options.address).call())
|
||||
var increaseRatio = new BigNumber("0.1")
|
||||
|
||||
await ctx.transferBaseToDSP(trader, vaultBaseBalance.multipliedBy(increaseRatio).toFixed(0))
|
||||
await ctx.transferQuoteToDSP(trader, vaultQuoteBalance.multipliedBy(increaseRatio).div(2).toFixed(0))
|
||||
await ctx.DSP.methods.buyShares(trader).send(ctx.sendParam(trader))
|
||||
|
||||
assert.equal(await ctx.DSP.methods.balanceOf(trader).call(), decimalStr("5"))
|
||||
})
|
||||
|
||||
it("buy shares with unbalanced input (less base)", async () => {
|
||||
await ctx.transferBaseToDSP(lp, decimalStr("100"))
|
||||
await ctx.transferQuoteToDSP(lp, decimalStr("100"))
|
||||
await ctx.DSP.methods.buyShares(lp).send(ctx.sendParam(lp))
|
||||
|
||||
await ctx.transferQuoteToDSP(trader, decimalStr("20"))
|
||||
await ctx.DSP.methods.sellQuote(trader).send(ctx.sendParam(trader))
|
||||
|
||||
var vaultBaseBalance = new BigNumber(await ctx.BASE.methods.balanceOf(ctx.DSP.options.address).call())
|
||||
var vaultQuoteBalance = new BigNumber(await ctx.QUOTE.methods.balanceOf(ctx.DSP.options.address).call())
|
||||
var increaseRatio = new BigNumber("0.1")
|
||||
|
||||
await ctx.transferBaseToDSP(trader, vaultBaseBalance.multipliedBy(increaseRatio).div(2).toFixed(0))
|
||||
await ctx.transferQuoteToDSP(trader, vaultQuoteBalance.multipliedBy(increaseRatio).toFixed(0))
|
||||
await ctx.DSP.methods.buyShares(trader).send(ctx.sendParam(trader))
|
||||
|
||||
assert.equal(await ctx.DSP.methods.balanceOf(trader).call(), "4999999999999999900")
|
||||
})
|
||||
});
|
||||
|
||||
describe("sell shares", () => {
|
||||
it("not the last one sell shares", async () => {
|
||||
await ctx.transferBaseToDSP(lp, decimalStr("100"))
|
||||
await ctx.transferQuoteToDSP(lp, decimalStr("100"))
|
||||
await ctx.DSP.methods.buyShares(lp).send(ctx.sendParam(lp))
|
||||
|
||||
await ctx.transferBaseToDSP(trader, decimalStr("10"))
|
||||
await ctx.transferQuoteToDSP(trader, decimalStr("10"))
|
||||
await ctx.DSP.methods.buyShares(trader).send(ctx.sendParam(trader))
|
||||
|
||||
var vaultShares = new BigNumber(await ctx.DSP.methods.balanceOf(lp).call())
|
||||
var bob = ctx.SpareAccounts[5]
|
||||
await ctx.DSP.methods.sellShares(vaultShares.div(2).toFixed(0), bob, 0, 0, "0x", MAX_UINT256).send(ctx.sendParam(lp))
|
||||
assert.equal(await ctx.BASE.methods.balanceOf(bob).call(), decimalStr("50"))
|
||||
assert.equal(await ctx.QUOTE.methods.balanceOf(bob).call(), decimalStr("50"))
|
||||
|
||||
await ctx.DSP.methods.sellShares(vaultShares.div(2).toFixed(0), bob, 0, 0, "0x", MAX_UINT256).send(ctx.sendParam(lp))
|
||||
assert.equal(await ctx.BASE.methods.balanceOf(bob).call(), decimalStr("100"))
|
||||
assert.equal(await ctx.QUOTE.methods.balanceOf(bob).call(), decimalStr("100"))
|
||||
})
|
||||
|
||||
it("the last one sell shares", async () => {
|
||||
await ctx.transferBaseToDSP(lp, decimalStr("100"))
|
||||
await ctx.transferQuoteToDSP(lp, decimalStr("100"))
|
||||
await ctx.DSP.methods.buyShares(lp).send(ctx.sendParam(lp))
|
||||
|
||||
var vaultShares = await ctx.DSP.methods.balanceOf(lp).call()
|
||||
var bob = ctx.SpareAccounts[5]
|
||||
await ctx.DSP.methods.sellShares(vaultShares, bob, 0, 0, "0x", MAX_UINT256).send(ctx.sendParam(lp))
|
||||
assert.equal(await ctx.BASE.methods.balanceOf(bob).call(), decimalStr("100"))
|
||||
assert.equal(await ctx.QUOTE.methods.balanceOf(bob).call(), decimalStr("100"))
|
||||
})
|
||||
|
||||
it("revert cases", async () => {
|
||||
await ctx.transferBaseToDSP(lp, decimalStr("100"))
|
||||
await ctx.transferQuoteToDSP(lp, decimalStr("100"))
|
||||
await ctx.DSP.methods.buyShares(lp).send(ctx.sendParam(lp))
|
||||
|
||||
var vaultShares = await ctx.DSP.methods.balanceOf(lp).call()
|
||||
var bob = ctx.SpareAccounts[5]
|
||||
await truffleAssert.reverts(
|
||||
ctx.DSP.methods.sellShares(new BigNumber(vaultShares).multipliedBy(2), bob, 0, 0, "0x", MAX_UINT256).send(ctx.sendParam(lp)),
|
||||
"DLP_NOT_ENOUGH"
|
||||
)
|
||||
await truffleAssert.reverts(
|
||||
ctx.DSP.methods.sellShares(vaultShares, bob, decimalStr("1000"), 0, "0x", MAX_UINT256).send(ctx.sendParam(lp)),
|
||||
"WITHDRAW_NOT_ENOUGH"
|
||||
)
|
||||
await truffleAssert.reverts(
|
||||
ctx.DSP.methods.sellShares(vaultShares, bob, 0, decimalStr("1000"), "0x", MAX_UINT256).send(ctx.sendParam(lp)),
|
||||
"WITHDRAW_NOT_ENOUGH"
|
||||
)
|
||||
await truffleAssert.reverts(
|
||||
ctx.DSP.methods.sellShares(vaultShares, bob, 0, decimalStr("10000"), "0x", "0").send(ctx.sendParam(lp)),
|
||||
"TIME_EXPIRED"
|
||||
)
|
||||
})
|
||||
})
|
||||
});
|
||||
192
test/DSP/trader.test.ts
Normal file
192
test/DSP/trader.test.ts
Normal file
@@ -0,0 +1,192 @@
|
||||
/*
|
||||
|
||||
Copyright 2020 DODO ZOO.
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
*/
|
||||
|
||||
// import * as assert from 'assert';
|
||||
|
||||
import { decimalStr, gweiStr } from '../utils/Converter';
|
||||
import { logGas } from '../utils/Log';
|
||||
import { DSPContext, getDSPContext } from '../utils/DSPContext';
|
||||
import { assert } from 'chai';
|
||||
const truffleAssert = require('truffle-assertions');
|
||||
|
||||
let lp: string;
|
||||
let trader: string;
|
||||
|
||||
async function init(ctx: DSPContext): Promise<void> {
|
||||
lp = ctx.Deployer
|
||||
trader = ctx.SpareAccounts[1];
|
||||
|
||||
await ctx.mintTestToken(lp, decimalStr("1000"), decimalStr("1000"));
|
||||
await ctx.mintTestToken(trader, decimalStr("1000"), decimalStr("1000"));
|
||||
|
||||
await ctx.transferBaseToDSP(lp, decimalStr("1000"))
|
||||
await ctx.transferQuoteToDSP(lp, decimalStr("1000"))
|
||||
await ctx.DSP.methods.buyShares(lp).send(ctx.sendParam(lp))
|
||||
}
|
||||
|
||||
describe("DSP Trader", () => {
|
||||
let snapshotId: string;
|
||||
let ctx: DSPContext;
|
||||
|
||||
before(async () => {
|
||||
ctx = await getDSPContext();
|
||||
await init(ctx);
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
snapshotId = await ctx.EVM.snapshot();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await ctx.EVM.reset(snapshotId);
|
||||
});
|
||||
|
||||
describe("trade", () => {
|
||||
|
||||
it("first buy and then sell", async () => {
|
||||
// buy at R=1
|
||||
await ctx.transferQuoteToDSP(trader, decimalStr("100"))
|
||||
await logGas(ctx.DSP.methods.sellQuote(trader), ctx.sendParam(trader), "sellQuote - buy at R=1")
|
||||
var balances = await ctx.getBalances(trader)
|
||||
|
||||
assert.equal(balances.traderBase, "1098914196817061816111")
|
||||
assert.equal(balances.traderQuote, decimalStr("900"))
|
||||
assert.equal(balances.DSPBase, "901085803182938183889")
|
||||
assert.equal(balances.DSPQuote, decimalStr("1100"))
|
||||
assert.equal(balances.maintainerBase, "0")
|
||||
assert.equal(balances.maintainerQuote, "0")
|
||||
|
||||
// buy at R>1
|
||||
await ctx.transferQuoteToDSP(trader, decimalStr("100"))
|
||||
await logGas(ctx.DSP.methods.sellQuote(trader), ctx.sendParam(trader), "sellQuote - buy at R>1")
|
||||
balances = await ctx.getBalances(trader)
|
||||
|
||||
assert.equal(balances.traderBase, "1195262145875634983260")
|
||||
assert.equal(balances.traderQuote, decimalStr("800"))
|
||||
assert.equal(balances.DSPBase, "804737854124365016740")
|
||||
assert.equal(balances.DSPQuote, decimalStr("1200"))
|
||||
assert.equal(balances.maintainerBase, "0")
|
||||
assert.equal(balances.maintainerQuote, "0")
|
||||
|
||||
// sell at R>1 and R not change state
|
||||
await ctx.transferBaseToDSP(trader, decimalStr("100"))
|
||||
await logGas(ctx.DSP.methods.sellBase(trader), ctx.sendParam(trader), "sellBase - sell at R>1 and R not change state")
|
||||
balances = await ctx.getBalances(trader)
|
||||
|
||||
assert.equal(balances.traderBase, "1095262145875634983260")
|
||||
assert.equal(balances.traderQuote, "903734814802481693100")
|
||||
assert.equal(balances.DSPBase, "904737854124365016740")
|
||||
assert.equal(balances.DSPQuote, "1096265185197518306900")
|
||||
assert.equal(balances.maintainerBase, "0")
|
||||
assert.equal(balances.maintainerQuote, "0")
|
||||
|
||||
|
||||
// sell at R>1 and R change state
|
||||
await ctx.transferBaseToDSP(trader, decimalStr("200"))
|
||||
|
||||
await logGas(ctx.DSP.methods.sellBase(trader), ctx.sendParam(trader), "sellBase - sell at R>1 and R change state")
|
||||
balances = await ctx.getBalances(trader)
|
||||
|
||||
assert.equal(balances.traderBase, "895262145875634983260")
|
||||
assert.equal(balances.traderQuote, "1103541932946094354686")
|
||||
assert.equal(balances.DSPBase, "1104737854124365016740")
|
||||
assert.equal(balances.DSPQuote, "896458067053905645314")
|
||||
assert.equal(balances.maintainerBase, "0")
|
||||
assert.equal(balances.maintainerQuote, "0")
|
||||
|
||||
var PMMStat = await ctx.DSP.methods.getPMMState().call()
|
||||
assert.equal(PMMStat.R, "2")
|
||||
assert.equal(PMMStat.B0, "999999999999999996713")
|
||||
});
|
||||
|
||||
it("first sell and then buy", async () => {
|
||||
// sell at R=1
|
||||
await ctx.transferBaseToDSP(trader, decimalStr("1"))
|
||||
await logGas(ctx.DSP.methods.sellBase(trader), ctx.sendParam(trader), "sellBase - sell at R=1")
|
||||
var balances = await ctx.getBalances(trader)
|
||||
|
||||
assert.equal(balances.traderBase, decimalStr("999"))
|
||||
assert.equal(balances.traderQuote, "1000999899919944970392")
|
||||
assert.equal(balances.DSPBase, decimalStr("1001"))
|
||||
assert.equal(balances.DSPQuote, "999000100080055029608")
|
||||
assert.equal(balances.maintainerBase, "0")
|
||||
assert.equal(balances.maintainerQuote, "0")
|
||||
|
||||
// buy at R>1
|
||||
await ctx.transferBaseToDSP(trader, decimalStr("1"))
|
||||
await logGas(ctx.DSP.methods.sellBase(trader), ctx.sendParam(trader), "sellBase - buy at R>1")
|
||||
balances = await ctx.getBalances(trader)
|
||||
|
||||
assert.equal(balances.traderBase, decimalStr("998"))
|
||||
assert.equal(balances.traderQuote, "1001999599359119051790")
|
||||
assert.equal(balances.DSPBase, decimalStr("1002"))
|
||||
assert.equal(balances.DSPQuote, "998000400640880948210")
|
||||
assert.equal(balances.maintainerBase, "0")
|
||||
assert.equal(balances.maintainerQuote, "0")
|
||||
|
||||
// sell at R>1 and R not change state
|
||||
await ctx.transferQuoteToDSP(trader, decimalStr("1"))
|
||||
await logGas(ctx.DSP.methods.sellQuote(trader), ctx.sendParam(trader), "sell at R>1 and R not change state")
|
||||
balances = await ctx.getBalances(trader)
|
||||
|
||||
assert.equal(balances.traderBase, "999000300621013276966")
|
||||
assert.equal(balances.traderQuote, "1000999599359119051790")
|
||||
assert.equal(balances.DSPBase, "1000999699378986723034")
|
||||
assert.equal(balances.DSPQuote, "999000400640880948210")
|
||||
assert.equal(balances.maintainerBase, "0")
|
||||
assert.equal(balances.maintainerQuote, "0")
|
||||
|
||||
// sell at R>1 and R change state
|
||||
await ctx.transferQuoteToDSP(trader, decimalStr("2"))
|
||||
await logGas(ctx.DSP.methods.sellQuote(trader), ctx.sendParam(trader), "sell at R>1 and R change state")
|
||||
balances = await ctx.getBalances(trader)
|
||||
|
||||
assert.equal(balances.traderBase, "1001000300480585414741")
|
||||
assert.equal(balances.traderQuote, "998999599359119051790")
|
||||
assert.equal(balances.DSPBase, "998999699519414585259")
|
||||
assert.equal(balances.DSPQuote, "1001000400640880948210")
|
||||
assert.equal(balances.maintainerBase, "0")
|
||||
assert.equal(balances.maintainerQuote, "0")
|
||||
|
||||
var PMMStat = await ctx.DSP.methods.getPMMState().call()
|
||||
assert.equal(PMMStat.R, "1")
|
||||
assert.equal(PMMStat.Q0, "999999999999999995766")
|
||||
});
|
||||
|
||||
it("flash loan", async () => {
|
||||
// buy
|
||||
await ctx.transferQuoteToDSP(trader, decimalStr("100"))
|
||||
|
||||
// buy failed
|
||||
await truffleAssert.reverts(ctx.DSP.methods.flashLoan("901085803182938100000", decimalStr("101"), trader, "0x").send(ctx.sendParam(trader)), "FLASH_LOAN_FAILED")
|
||||
|
||||
// buy succeed
|
||||
await ctx.DSP.methods.flashLoan("98914196817061816111", "0", trader, "0x").send(ctx.sendParam(trader))
|
||||
|
||||
// trader balances
|
||||
assert.equal(
|
||||
await ctx.BASE.methods.balanceOf(trader).call(),
|
||||
"1098914196817061816111"
|
||||
);
|
||||
|
||||
// sell
|
||||
await ctx.transferBaseToDSP(trader, decimalStr("1"))
|
||||
|
||||
// sell failed
|
||||
await truffleAssert.reverts(ctx.DSP.methods.flashLoan(decimalStr("2"), "1", trader, "0x").send(ctx.sendParam(trader)), "FLASH_LOAN_FAILED")
|
||||
|
||||
// sell succeed
|
||||
await ctx.DSP.methods.flashLoan("0", "999899919944970392", trader, "0x").send(ctx.sendParam(trader))
|
||||
|
||||
// trader balances
|
||||
assert.equal(
|
||||
await ctx.QUOTE.methods.balanceOf(trader).call(),
|
||||
"900999899919944970392"
|
||||
);
|
||||
})
|
||||
});
|
||||
});
|
||||
@@ -38,6 +38,8 @@ export const EXTERNAL_VALUE_NAME = "ExternalValue"
|
||||
export const FEE_RATE_MODEL_NAME = "FeeRateModel"
|
||||
export const DPP_NAME = "DPP"
|
||||
export const DPP_FACTORY_NAME = "DPPFactory"
|
||||
export const DSP_NAME = "DSP"
|
||||
export const DSP_FACTORY_NAME = "DSPFactory"
|
||||
export const SMART_APPROVE = "DODOApprove"
|
||||
export const SMART_APPROVE_PROXY = "DODOApproveProxy"
|
||||
export const DODO_SELL_HELPER = "DODOSellHelper"
|
||||
@@ -50,6 +52,8 @@ export const VDODO_NAME = "vDODOToken"
|
||||
export const DODO_CULATION_HELPER = "DODOCirculationHelper"
|
||||
export const DODO_GOVERNANCE = "Governance"
|
||||
export const DODO_PROXY_NAME = "DODOV2Proxy02"
|
||||
export const ERC20_MINE = "ERC20Mine"
|
||||
export const VDODO_MINE = "vDODOMine"
|
||||
|
||||
interface ContractJson {
|
||||
abi: any;
|
||||
|
||||
114
test/utils/DODOMineV2Context.ts
Normal file
114
test/utils/DODOMineV2Context.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
|
||||
Copyright 2020 DODO ZOO.
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
*/
|
||||
|
||||
import BigNumber from 'bignumber.js';
|
||||
import Web3 from 'web3';
|
||||
import { Contract } from 'web3-eth-contract';
|
||||
|
||||
import * as contracts from './Contracts';
|
||||
import { decimalStr, mweiStr, MAX_UINT256 } from './Converter';
|
||||
import { EVM, getDefaultWeb3 } from './EVM';
|
||||
import * as log from './Log';
|
||||
|
||||
BigNumber.config({
|
||||
EXPONENTIAL_AT: 1000,
|
||||
DECIMAL_PLACES: 80,
|
||||
});
|
||||
|
||||
|
||||
export class DODOMineV2Context {
|
||||
EVM: EVM;
|
||||
Web3: Web3;
|
||||
|
||||
//contract
|
||||
ERC20Mine: Contract;
|
||||
VDODOMine: Contract;
|
||||
|
||||
//account
|
||||
Deployer: string;
|
||||
Maintainer: string;
|
||||
SpareAccounts: string[];
|
||||
|
||||
//token
|
||||
REWARD_1: Contract;
|
||||
REWARD_2: Contract;
|
||||
ERC20: Contract;
|
||||
|
||||
|
||||
async init(vdodo: string) {
|
||||
this.EVM = new EVM();
|
||||
this.Web3 = getDefaultWeb3();
|
||||
|
||||
const allAccounts = await this.Web3.eth.getAccounts();
|
||||
this.Deployer = allAccounts[0];
|
||||
this.Maintainer = allAccounts[1];
|
||||
this.SpareAccounts = allAccounts.slice(2, 10);
|
||||
|
||||
this.ERC20 = await contracts.newContract(
|
||||
contracts.MINTABLE_ERC20_CONTRACT_NAME,
|
||||
["ERC20 Token", "ERC20", 18]
|
||||
);
|
||||
|
||||
this.REWARD_1 = await contracts.newContract(
|
||||
contracts.MINTABLE_ERC20_CONTRACT_NAME,
|
||||
["REWARD_1 Token", "REWARD_1", 18]
|
||||
);
|
||||
|
||||
this.REWARD_2 = await contracts.newContract(
|
||||
contracts.MINTABLE_ERC20_CONTRACT_NAME,
|
||||
["REWARD_2 Token", "REWARD_2", 18]
|
||||
);
|
||||
|
||||
if (vdodo != null) {
|
||||
this.VDODOMine = await contracts.newContract(
|
||||
contracts.VDODO_MINE,
|
||||
[vdodo]
|
||||
);
|
||||
await this.VDODOMine.methods.initOwner(this.Deployer).send(this.sendParam(this.Deployer));
|
||||
}
|
||||
|
||||
this.ERC20Mine = await contracts.newContract(
|
||||
contracts.ERC20_MINE,
|
||||
[this.ERC20.options.address]
|
||||
);
|
||||
|
||||
await this.ERC20Mine.methods.initOwner(this.Deployer).send(this.sendParam(this.Deployer));
|
||||
|
||||
console.log(log.blueText("[Init DODOMine context]"));
|
||||
}
|
||||
|
||||
sendParam(sender, value = "0") {
|
||||
return {
|
||||
from: sender,
|
||||
gas: process.env["COVERAGE"] ? 10000000000 : 7000000,
|
||||
gasPrice: mweiStr("1000"),
|
||||
value: decimalStr(value),
|
||||
};
|
||||
}
|
||||
|
||||
async mintTestToken(to: string, token: Contract, amount: string) {
|
||||
await token.methods.mint(to, amount).send(this.sendParam(this.Deployer));
|
||||
}
|
||||
|
||||
async increBlock(num: number) {
|
||||
for (let i = 0; i < num; i++) {
|
||||
await this.mintTestToken(this.Deployer, this.ERC20, decimalStr("0"));
|
||||
}
|
||||
}
|
||||
|
||||
async approveProxy(account: string, target: string, token: Contract) {
|
||||
await token.methods
|
||||
.approve(target, MAX_UINT256)
|
||||
.send(this.sendParam(account));
|
||||
}
|
||||
}
|
||||
|
||||
export async function getDODOMineContext(vdodo: string): Promise<DODOMineV2Context> {
|
||||
var context = new DODOMineV2Context();
|
||||
await context.init(vdodo);
|
||||
return context;
|
||||
}
|
||||
154
test/utils/DSPContext.ts
Normal file
154
test/utils/DSPContext.ts
Normal file
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
|
||||
Copyright 2020 DODO ZOO.
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
*/
|
||||
|
||||
import BigNumber from 'bignumber.js';
|
||||
import Web3 from 'web3';
|
||||
import { Contract } from 'web3-eth-contract';
|
||||
|
||||
import * as contracts from './Contracts';
|
||||
import { decimalStr, MAX_UINT256 } from './Converter';
|
||||
import { EVM, getDefaultWeb3 } from './EVM';
|
||||
import * as log from './Log';
|
||||
|
||||
BigNumber.config({
|
||||
EXPONENTIAL_AT: 1000,
|
||||
DECIMAL_PLACES: 80,
|
||||
});
|
||||
|
||||
export interface DSPContextBalances {
|
||||
traderBase: string,
|
||||
traderQuote: string,
|
||||
DSPBase: string,
|
||||
DSPQuote: string,
|
||||
maintainerBase: string,
|
||||
maintainerQuote: string
|
||||
}
|
||||
|
||||
export interface DSPContextInitConfig {
|
||||
lpFeeRate: string;
|
||||
mtFeeRate: string;
|
||||
k: string;
|
||||
i: string;
|
||||
}
|
||||
|
||||
/*
|
||||
price curve when k=0.1
|
||||
+──────────────────────+───────────────+
|
||||
| purchase percentage | avg slippage |
|
||||
+──────────────────────+───────────────+
|
||||
| 1% | 0.1% |
|
||||
| 5% | 0.5% |
|
||||
| 10% | 1.1% |
|
||||
| 20% | 2.5% |
|
||||
| 50% | 10% |
|
||||
| 70% | 23.3% |
|
||||
+──────────────────────+───────────────+
|
||||
*/
|
||||
export let DefaultDSPContextInitConfig = {
|
||||
lpFeeRate: decimalStr("0.002"),
|
||||
mtFeeRate: decimalStr("0.001"),
|
||||
k: decimalStr("0.1"),
|
||||
i: decimalStr("1"),
|
||||
};
|
||||
|
||||
export class DSPContext {
|
||||
EVM: EVM;
|
||||
Web3: Web3;
|
||||
DSP: Contract;
|
||||
BASE: Contract;
|
||||
QUOTE: Contract;
|
||||
Deployer: string;
|
||||
Maintainer: string;
|
||||
MtFeeRate: string;
|
||||
SpareAccounts: string[];
|
||||
|
||||
mtFeeRateModel: Contract;
|
||||
|
||||
|
||||
constructor() { }
|
||||
|
||||
async init(config: DSPContextInitConfig) {
|
||||
this.EVM = new EVM();
|
||||
this.Web3 = getDefaultWeb3();
|
||||
|
||||
this.DSP = await contracts.newContract(contracts.DSP_NAME)
|
||||
var mtFeeRateModel = await contracts.newContract(contracts.FEE_RATE_MODEL_NAME)
|
||||
this.mtFeeRateModel = mtFeeRateModel;
|
||||
this.MtFeeRate = mtFeeRateModel.options.address
|
||||
|
||||
this.BASE = await contracts.newContract(
|
||||
contracts.MINTABLE_ERC20_CONTRACT_NAME,
|
||||
["TestBase", "BASE", 18]
|
||||
);
|
||||
this.QUOTE = await contracts.newContract(
|
||||
contracts.MINTABLE_ERC20_CONTRACT_NAME,
|
||||
["TestQuote", "QUOTE", 18]
|
||||
);
|
||||
|
||||
const allAccounts = await this.Web3.eth.getAccounts();
|
||||
this.Deployer = allAccounts[0];
|
||||
this.Maintainer = allAccounts[1];
|
||||
this.SpareAccounts = allAccounts.slice(2, 10);
|
||||
|
||||
await this.DSP.methods.init(
|
||||
this.Maintainer,
|
||||
this.BASE.options.address,
|
||||
this.QUOTE.options.address,
|
||||
0,
|
||||
mtFeeRateModel.options.address,
|
||||
config.i,
|
||||
config.k,
|
||||
true
|
||||
).send(this.sendParam(this.Deployer))
|
||||
|
||||
console.log(log.blueText("[Init DSP context]"));
|
||||
}
|
||||
|
||||
sendParam(sender, value = "0") {
|
||||
return {
|
||||
from: sender,
|
||||
gas: process.env["COVERAGE"] ? 10000000000 : 7000000,
|
||||
gasPrice: process.env.GAS_PRICE,
|
||||
value: decimalStr(value),
|
||||
};
|
||||
}
|
||||
|
||||
async mintTestToken(to: string, base: string, quote: string) {
|
||||
await this.BASE.methods.mint(to, base).send(this.sendParam(this.Deployer));
|
||||
await this.QUOTE.methods
|
||||
.mint(to, quote)
|
||||
.send(this.sendParam(this.Deployer));
|
||||
}
|
||||
|
||||
async transferBaseToDSP(account: string, amount: string) {
|
||||
await this.BASE.methods.transfer(this.DSP.options.address, amount).send(this.sendParam(account))
|
||||
}
|
||||
|
||||
async transferQuoteToDSP(account: string, amount: string) {
|
||||
await this.QUOTE.methods.transfer(this.DSP.options.address, amount).send(this.sendParam(account))
|
||||
}
|
||||
|
||||
async getBalances(trader: string) {
|
||||
var balances: DSPContextBalances = {
|
||||
traderBase: await this.BASE.methods.balanceOf(trader).call(),
|
||||
traderQuote: await this.QUOTE.methods.balanceOf(trader).call(),
|
||||
DSPBase: await this.BASE.methods.balanceOf(this.DSP.options.address).call(),
|
||||
DSPQuote: await this.QUOTE.methods.balanceOf(this.DSP.options.address).call(),
|
||||
maintainerBase: await this.BASE.methods.balanceOf(this.Maintainer).call(),
|
||||
maintainerQuote: await this.QUOTE.methods.balanceOf(this.Maintainer).call()
|
||||
};
|
||||
return balances;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getDSPContext(
|
||||
config: DSPContextInitConfig = DefaultDSPContextInitConfig
|
||||
): Promise<DSPContext> {
|
||||
var context = new DSPContext();
|
||||
await context.init(config);
|
||||
return context;
|
||||
}
|
||||
@@ -54,7 +54,8 @@ module.exports = {
|
||||
DVM: false,
|
||||
CP: false,
|
||||
CPFactory: false,
|
||||
MultiCall: false
|
||||
MultiCall: false,
|
||||
LockedVault: false
|
||||
},
|
||||
|
||||
networks: {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/bin/bash
|
||||
# truffle compile --all
|
||||
truffle compile --all
|
||||
|
||||
if [ "$1"x = "proxy-dpp"x ]
|
||||
then
|
||||
@@ -46,6 +46,15 @@ then
|
||||
truffle test ./test/vDODO/mintRedeem.test.ts
|
||||
fi
|
||||
|
||||
if [ "$1"x = "erc20-mine"x ]
|
||||
then
|
||||
truffle test ./test/DODOMineV2/erc20Mine.test.ts
|
||||
fi
|
||||
|
||||
if [ "$1"x = "vdodo-mine"x ]
|
||||
then
|
||||
truffle test ./test/DODOMineV2/vDODOMine.test.ts
|
||||
fi
|
||||
|
||||
# if [ "$1"x = "route-incentive"x ]
|
||||
# then
|
||||
|
||||
Reference in New Issue
Block a user