From 078fb40f1f50cc98120a2b4abc72d22c36dfae3d Mon Sep 17 00:00:00 2001 From: owen05 Date: Wed, 24 Mar 2021 18:08:20 +0800 Subject: [PATCH 01/18] dodomine v2 --- contracts/DODOToken/DODOMineV2/BaseMine.sol | 216 ++++++++++++++++++ contracts/DODOToken/DODOMineV2/LpMine.sol | 67 ++++++ .../DODOToken/DODOMineV2/RewardVault.sol | 31 +++ contracts/DODOToken/DODOMineV2/vDODOMine.sol | 59 +++++ contracts/DODOToken/Governance.sol | 47 +++- contracts/DODOToken/LockedTokenVault.sol | 2 +- contracts/intf/IDODOApproveProxy.sol | 13 ++ deploy-detail-periphery.txt | 3 +- migrations/4_deploy_periphery.js | 17 ++ truffle-config.js | 3 +- 10 files changed, 451 insertions(+), 7 deletions(-) create mode 100644 contracts/DODOToken/DODOMineV2/BaseMine.sol create mode 100644 contracts/DODOToken/DODOMineV2/LpMine.sol create mode 100644 contracts/DODOToken/DODOMineV2/RewardVault.sol create mode 100644 contracts/DODOToken/DODOMineV2/vDODOMine.sol create mode 100644 contracts/intf/IDODOApproveProxy.sol diff --git a/contracts/DODOToken/DODOMineV2/BaseMine.sol b/contracts/DODOToken/DODOMineV2/BaseMine.sol new file mode 100644 index 0000000..2d1d96b --- /dev/null +++ b/contracts/DODOToken/DODOMineV2/BaseMine.sol @@ -0,0 +1,216 @@ +/* + + Copyright 2020 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; + address rewardDistributor; + + uint256 rewardPerBlock; + uint256 accRewardPerShare; + uint256 lastRewardBlock; + + mapping(address => uint256) userRewardPerTokenPaid; + 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 RewardDistributorChanged(uint256 indexed i, address rewardDistributor); + event NewRewardToken(uint256 indexed i, address rewardToken); + event RemoveRewardToken(address rewardToken); + + // ============ Modifier ========== + + modifier onlyRewardDistributor(uint i) { + require(msg.sender == rewardTokenInfos[i].rewardDistributor, "DODOMineV2: ACCESS_RESTRICTED"); + _; + } + + modifier updateReward(address user) { + uint256 len = rewardTokenInfos.length; + for (uint i = 0; i < len; i++) { + RewardTokenInfo storage rt = rewardTokenInfos[i]; + rt.accRewardPerShare = rewardPerLpToken(i); + rt.lastRewardBlock = lastBlockRewardApplicable(i); + if (user != address(0)) { + rt.userRewards[user] = getPendingReward(i, user); + rt.userRewardPerTokenPaid[user] = rt.accRewardPerShare; + } + } + _; + } + + // ============ View ============ + + function lastBlockRewardApplicable(uint i) public view returns (uint256) { + uint256 startBlock = rewardTokenInfos[i].startBlock; + uint256 endBlock = rewardTokenInfos[i].endBlock; + if(block.number < endBlock) { + if(block.number < startBlock) + return startBlock; + else + return block.number; + }else { + return endBlock; + } + } + + function rewardPerLpToken(uint i) public view returns (uint256) { + RewardTokenInfo memory rt = rewardTokenInfos[i]; + if (totalSupply() == 0) { + return rt.accRewardPerShare; + } + return rt.accRewardPerShare.add( + DecimalMath.divFloor( + lastBlockRewardApplicable(i).sub(rt.lastRewardBlock).mul(rt.rewardPerBlock), + totalSupply() + ) + ); + } + + function getPendingReward(uint i, address user) public view returns (uint256) { + RewardTokenInfo storage rt = rewardTokenInfos[i]; + return DecimalMath.mulFloor( + balanceOf(user), + rewardPerLpToken(i).sub(rt.userRewardPerTokenPaid[user]) + ).add(rt.userRewards[user]); + } + + function totalSupply() public view returns (uint256) { + return _totalSupply; + } + + function balanceOf(address user) public view returns (uint256) { + return _balances[user]; + } + + // ============ Claim ============ + + function getReward(uint i) public updateReward(msg.sender) { + 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 getAllRewards() public { + uint256 len = rewardTokenInfos.length; + for (uint i = 0; i < len; i++) { + getReward(i); + } + } + + // =============== Ownable ================ + + function setRewardDistribution(uint i, address rewardDistributor) external onlyOwner { + RewardTokenInfo storage rt = rewardTokenInfos[i]; + rt.rewardDistributor = rewardDistributor; + emit RewardDistributorChanged(i, rewardDistributor); + } + + function addRewardToken(address rewardToken, address rewardDistributor,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"); + require(rewardDistributor != address(0), "DODOMineV2: REWARD_DISTRIBUTOR_INVALID"); + + uint256 len = rewardTokenInfos.length; + for (uint 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.rewardDistributor = rewardDistributor; + rt.rewardVault = address(new RewardVault(rewardToken)); + + emit NewRewardToken(len, rewardToken); + emit RewardDistributorChanged(len, rewardDistributor); + } + + function removeRewardToken(address rewardToken) external onlyOwner { + uint256 len = rewardTokenInfos.length; + for (uint256 i = 0; i < len; i++) { + if (rewardToken == rewardTokenInfos[i].rewardToken) { + rewardTokenInfos[i] = rewardTokenInfos[len - 1]; + rewardTokenInfos.pop(); + emit RemoveRewardToken(rewardToken); + break; + } + } + } + + function setEndBlock(uint i, uint256 newEndBlock) external onlyRewardDistributor(i) updateReward(address(0)) { + require(block.number < newEndBlock, "DODOMineV2: END_BLOCK_INVALID"); + RewardTokenInfo storage rt = rewardTokenInfos[i]; + require(block.number > rt.startBlock, "DODOMineV2: NOT_START"); + require(block.number < rt.endBlock, "DODOMineV2: ALREADY_CLOSE"); + + //TODO: gas ?需要额外维护 已分发reward变量,总reward 两个storage + // uint256 vaultBalance = IERC20(rt.rewardToken).balanceOf(rt.rewardVault); + // uint256 preReward = DecimalMath.mulFloor(newEndBlock.sub(block.number), rt.rewardPerBlock); + // require(preReward <= vaultBalance, "DODOMineV2: REWARD_NOT_ENOUGH"); + + rt.endBlock = newEndBlock; + rt.lastRewardBlock = block.number; + + emit UpdateEndBlock(i, newEndBlock); + } + + function setReward(uint i, uint256 newRewardPerBlock) external onlyRewardDistributor(i) updateReward(address(0)) { + RewardTokenInfo storage rt = rewardTokenInfos[i]; + uint256 endBlock = rt.endBlock; + + require(block.number < endBlock, "DODOMineV2: ALREADY_FINISHED"); + + //TODO: + // uint256 vaultBalance = IERC20(rt.rewardToken).balanceOf(rt.rewardVault); + // uint256 startBlock = rt.startBlock; + // uint256 preReward; + // if(startBlock > block.number) { + // preReward = DecimalMath.mulFloor(endBlock.sub(startBlock), newRewardPerBlock); + // }else { + // preReward = DecimalMath.mulFloor(endBlock.sub(block.number), newRewardPerBlock); + // } + // require(preReward <= vaultBalance, "DODOMineV2: REWARD_NOT_ENOUGH"); + + rt.rewardPerBlock = newRewardPerBlock; + emit UpdateReward(i, newRewardPerBlock); + } +} diff --git a/contracts/DODOToken/DODOMineV2/LpMine.sol b/contracts/DODOToken/DODOMineV2/LpMine.sol new file mode 100644 index 0000000..489a025 --- /dev/null +++ b/contracts/DODOToken/DODOMineV2/LpMine.sol @@ -0,0 +1,67 @@ +/* + + Copyright 2020 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 {IDODOApproveProxy} from "../../intf/IDODOApproveProxy.sol"; +import {BaseMine} from "./BaseMine.sol"; + + +contract LpMine is BaseMine { + using SafeERC20 for IERC20; + using SafeMath for uint256; + + // ============ Storage ============ + + address public immutable _LP_TOKEN_; + address public immutable _DODO_APPROVE_PROXY_; + + constructor(address lpToken, address dodoApproveProxy) public { + _LP_TOKEN_ = lpToken; + _DODO_APPROVE_PROXY_ = dodoApproveProxy; + } + + // ============ Event ============ + event Deposit(address indexed user, uint256 amount); + event Withdraw(address indexed user, uint256 amount); + + + // ============ Deposit && Withdraw && Exit ============ + + function deposit(uint256 amount) virtual public updateReward(msg.sender) { + require(amount > 0, "DODOMineV2: CANNOT_DEPOSIT_ZERO"); + _totalSupply = _totalSupply.add(amount); + _balances[msg.sender] = _balances[msg.sender].add(amount); + IDODOApproveProxy(_DODO_APPROVE_PROXY_).claimTokens( + _LP_TOKEN_, + msg.sender, + address(this), + amount + ); + emit Deposit(msg.sender, amount); + } + + function withdraw(uint256 amount) virtual public updateReward(msg.sender) { + require(amount > 0, "DODOMineV2: CANNOT_WITHDRAW_ZERO"); + _totalSupply = _totalSupply.sub(amount); + _balances[msg.sender] = _balances[msg.sender].sub(amount); + IERC20(_LP_TOKEN_).safeTransfer(msg.sender, amount); + emit Withdraw(msg.sender, amount); + } + + function withdrawAll() external { + withdraw(balanceOf(msg.sender)); + } + + function exit() external { + withdraw(balanceOf(msg.sender)); + getAllRewards(); + } +} diff --git a/contracts/DODOToken/DODOMineV2/RewardVault.sol b/contracts/DODOToken/DODOMineV2/RewardVault.sol new file mode 100644 index 0000000..33bacf8 --- /dev/null +++ b/contracts/DODOToken/DODOMineV2/RewardVault.sol @@ -0,0 +1,31 @@ +/* + + 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; +} + +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); + } +} diff --git a/contracts/DODOToken/DODOMineV2/vDODOMine.sol b/contracts/DODOToken/DODOMineV2/vDODOMine.sol new file mode 100644 index 0000000..f5dd926 --- /dev/null +++ b/contracts/DODOToken/DODOMineV2/vDODOMine.sol @@ -0,0 +1,59 @@ +/* + + Copyright 2020 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); + + + function deposit(uint256 amount) public { + require(amount > 0, "vDODOMineETH: CANNOT_DEPOSIT_ZERO"); + require(IVDODOToken(_vDODO_TOKEN_).availableBalanceOf(msg.sender) >= amount, "vDODOMineETH: vDODO_NOT_ENOUGH"); + _totalSupply = _totalSupply.add(amount); + _balances[msg.sender] = _balances[msg.sender].add(amount); + emit Deposit(msg.sender, amount); + } + + function withdraw(uint256 amount) public { + require(amount > 0, "DODOMineV2: CANNOT_WITHDRAW_ZERO"); + _totalSupply = _totalSupply.sub(amount); + _balances[msg.sender] = _balances[msg.sender].sub(amount); + emit Withdraw(msg.sender, amount); + } + + function withdrawAll() external { + withdraw(balanceOf(msg.sender)); + } + + function exit() external { + withdraw(balanceOf(msg.sender)); + getAllRewards(); + } +} \ No newline at end of file diff --git a/contracts/DODOToken/Governance.sol b/contracts/DODOToken/Governance.sol index c177d7e..5c86b1e 100644 --- a/contracts/DODOToken/Governance.sol +++ b/contracts/DODOToken/Governance.sol @@ -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; + } + } } } diff --git a/contracts/DODOToken/LockedTokenVault.sol b/contracts/DODOToken/LockedTokenVault.sol index aea1bd3..706204d 100644 --- a/contracts/DODOToken/LockedTokenVault.sol +++ b/contracts/DODOToken/LockedTokenVault.sol @@ -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; diff --git a/contracts/intf/IDODOApproveProxy.sol b/contracts/intf/IDODOApproveProxy.sol new file mode 100644 index 0000000..39e13d8 --- /dev/null +++ b/contracts/intf/IDODOApproveProxy.sol @@ -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; +} diff --git a/deploy-detail-periphery.txt b/deploy-detail-periphery.txt index a9fd245..a942417 100644 --- a/deploy-detail-periphery.txt +++ b/deploy-detail-periphery.txt @@ -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 diff --git a/migrations/4_deploy_periphery.js b/migrations/4_deploy_periphery.js index 48c5fd6..597667e 100644 --- a/migrations/4_deploy_periphery.js +++ b/migrations/4_deploy_periphery.js @@ -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); diff --git a/truffle-config.js b/truffle-config.js index 4249cf0..c88c751 100644 --- a/truffle-config.js +++ b/truffle-config.js @@ -54,7 +54,8 @@ module.exports = { DVM: false, CP: false, CPFactory: false, - MultiCall: false + MultiCall: false, + LockedVault: false }, networks: { From e3e5c4644542f09ed69fa0c41d926ac8c2c233b2 Mon Sep 17 00:00:00 2001 From: owen05 Date: Thu, 25 Mar 2021 16:11:37 +0800 Subject: [PATCH 02/18] update dodomine v2 --- contracts/DODOToken/DODOMineV2/BaseMine.sol | 53 ++++---------------- contracts/DODOToken/DODOMineV2/LpMine.sol | 21 +++----- contracts/DODOToken/DODOMineV2/vDODOMine.sol | 23 +++++++++ 3 files changed, 40 insertions(+), 57 deletions(-) diff --git a/contracts/DODOToken/DODOMineV2/BaseMine.sol b/contracts/DODOToken/DODOMineV2/BaseMine.sol index 2d1d96b..393212a 100644 --- a/contracts/DODOToken/DODOMineV2/BaseMine.sol +++ b/contracts/DODOToken/DODOMineV2/BaseMine.sol @@ -26,7 +26,6 @@ contract BaseMine is InitializableOwnable { uint256 startBlock; uint256 endBlock; address rewardVault; - address rewardDistributor; uint256 rewardPerBlock; uint256 accRewardPerShare; @@ -46,22 +45,16 @@ contract BaseMine is InitializableOwnable { 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 RewardDistributorChanged(uint256 indexed i, address rewardDistributor); event NewRewardToken(uint256 indexed i, address rewardToken); event RemoveRewardToken(address rewardToken); // ============ Modifier ========== - modifier onlyRewardDistributor(uint i) { - require(msg.sender == rewardTokenInfos[i].rewardDistributor, "DODOMineV2: ACCESS_RESTRICTED"); - _; - } - modifier updateReward(address user) { uint256 len = rewardTokenInfos.length; for (uint i = 0; i < len; i++) { RewardTokenInfo storage rt = rewardTokenInfos[i]; - rt.accRewardPerShare = rewardPerLpToken(i); + rt.accRewardPerShare = rewardPerToken(i); rt.lastRewardBlock = lastBlockRewardApplicable(i); if (user != address(0)) { rt.userRewards[user] = getPendingReward(i, user); @@ -86,7 +79,7 @@ contract BaseMine is InitializableOwnable { } } - function rewardPerLpToken(uint i) public view returns (uint256) { + function rewardPerToken(uint i) public view returns (uint256) { RewardTokenInfo memory rt = rewardTokenInfos[i]; if (totalSupply() == 0) { return rt.accRewardPerShare; @@ -103,7 +96,7 @@ contract BaseMine is InitializableOwnable { RewardTokenInfo storage rt = rewardTokenInfos[i]; return DecimalMath.mulFloor( balanceOf(user), - rewardPerLpToken(i).sub(rt.userRewardPerTokenPaid[user]) + rewardPerToken(i).sub(rt.userRewardPerTokenPaid[user]) ).add(rt.userRewards[user]); } @@ -115,6 +108,11 @@ contract BaseMine is InitializableOwnable { return _balances[user]; } + function getRewardTokenByIdx(uint i) public view returns (address) { + RewardTokenInfo memory rt = rewardTokenInfos[i]; + return rt.rewardToken; + } + // ============ Claim ============ function getReward(uint i) public updateReward(msg.sender) { @@ -136,17 +134,10 @@ contract BaseMine is InitializableOwnable { // =============== Ownable ================ - function setRewardDistribution(uint i, address rewardDistributor) external onlyOwner { - RewardTokenInfo storage rt = rewardTokenInfos[i]; - rt.rewardDistributor = rewardDistributor; - emit RewardDistributorChanged(i, rewardDistributor); - } - - function addRewardToken(address rewardToken, address rewardDistributor,uint256 startBlock, uint256 endBlock) external onlyOwner { + function addRewardToken(address rewardToken, 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"); - require(rewardDistributor != address(0), "DODOMineV2: REWARD_DISTRIBUTOR_INVALID"); uint256 len = rewardTokenInfos.length; for (uint i = 0; i < len; i++) { @@ -157,11 +148,9 @@ contract BaseMine is InitializableOwnable { rt.rewardToken = rewardToken; rt.startBlock = startBlock; rt.endBlock = endBlock; - rt.rewardDistributor = rewardDistributor; rt.rewardVault = address(new RewardVault(rewardToken)); emit NewRewardToken(len, rewardToken); - emit RewardDistributorChanged(len, rewardDistributor); } function removeRewardToken(address rewardToken) external onlyOwner { @@ -176,40 +165,20 @@ contract BaseMine is InitializableOwnable { } } - function setEndBlock(uint i, uint256 newEndBlock) external onlyRewardDistributor(i) updateReward(address(0)) { + function setEndBlock(uint i, uint256 newEndBlock) external onlyOwner updateReward(address(0)) { require(block.number < newEndBlock, "DODOMineV2: END_BLOCK_INVALID"); RewardTokenInfo storage rt = rewardTokenInfos[i]; require(block.number > rt.startBlock, "DODOMineV2: NOT_START"); require(block.number < rt.endBlock, "DODOMineV2: ALREADY_CLOSE"); - - //TODO: gas ?需要额外维护 已分发reward变量,总reward 两个storage - // uint256 vaultBalance = IERC20(rt.rewardToken).balanceOf(rt.rewardVault); - // uint256 preReward = DecimalMath.mulFloor(newEndBlock.sub(block.number), rt.rewardPerBlock); - // require(preReward <= vaultBalance, "DODOMineV2: REWARD_NOT_ENOUGH"); - rt.endBlock = newEndBlock; rt.lastRewardBlock = block.number; - emit UpdateEndBlock(i, newEndBlock); } - function setReward(uint i, uint256 newRewardPerBlock) external onlyRewardDistributor(i) updateReward(address(0)) { + function setReward(uint i, uint256 newRewardPerBlock) external onlyOwner updateReward(address(0)) { RewardTokenInfo storage rt = rewardTokenInfos[i]; uint256 endBlock = rt.endBlock; - require(block.number < endBlock, "DODOMineV2: ALREADY_FINISHED"); - - //TODO: - // uint256 vaultBalance = IERC20(rt.rewardToken).balanceOf(rt.rewardVault); - // uint256 startBlock = rt.startBlock; - // uint256 preReward; - // if(startBlock > block.number) { - // preReward = DecimalMath.mulFloor(endBlock.sub(startBlock), newRewardPerBlock); - // }else { - // preReward = DecimalMath.mulFloor(endBlock.sub(block.number), newRewardPerBlock); - // } - // require(preReward <= vaultBalance, "DODOMineV2: REWARD_NOT_ENOUGH"); - rt.rewardPerBlock = newRewardPerBlock; emit UpdateReward(i, newRewardPerBlock); } diff --git a/contracts/DODOToken/DODOMineV2/LpMine.sol b/contracts/DODOToken/DODOMineV2/LpMine.sol index 489a025..099cd16 100644 --- a/contracts/DODOToken/DODOMineV2/LpMine.sol +++ b/contracts/DODOToken/DODOMineV2/LpMine.sol @@ -10,22 +10,18 @@ pragma experimental ABIEncoderV2; import {SafeERC20} from "../../lib/SafeERC20.sol"; import {IERC20} from "../../intf/IERC20.sol"; import {SafeMath} from "../../lib/SafeMath.sol"; -import {IDODOApproveProxy} from "../../intf/IDODOApproveProxy.sol"; import {BaseMine} from "./BaseMine.sol"; - -contract LpMine is BaseMine { +contract ERC20Mine is BaseMine { using SafeERC20 for IERC20; using SafeMath for uint256; // ============ Storage ============ - address public immutable _LP_TOKEN_; - address public immutable _DODO_APPROVE_PROXY_; + address public immutable _TOKEN_; - constructor(address lpToken, address dodoApproveProxy) public { - _LP_TOKEN_ = lpToken; - _DODO_APPROVE_PROXY_ = dodoApproveProxy; + constructor(address token) public { + _TOKEN_ = token; } // ============ Event ============ @@ -39,12 +35,7 @@ contract LpMine is BaseMine { require(amount > 0, "DODOMineV2: CANNOT_DEPOSIT_ZERO"); _totalSupply = _totalSupply.add(amount); _balances[msg.sender] = _balances[msg.sender].add(amount); - IDODOApproveProxy(_DODO_APPROVE_PROXY_).claimTokens( - _LP_TOKEN_, - msg.sender, - address(this), - amount - ); + IERC20(_TOKEN_).safeTransferFrom(msg.sender, address(this), amount); emit Deposit(msg.sender, amount); } @@ -52,7 +43,7 @@ contract LpMine is BaseMine { require(amount > 0, "DODOMineV2: CANNOT_WITHDRAW_ZERO"); _totalSupply = _totalSupply.sub(amount); _balances[msg.sender] = _balances[msg.sender].sub(amount); - IERC20(_LP_TOKEN_).safeTransfer(msg.sender, amount); + IERC20(_TOKEN_).safeTransfer(msg.sender, amount); emit Withdraw(msg.sender, amount); } diff --git a/contracts/DODOToken/DODOMineV2/vDODOMine.sol b/contracts/DODOToken/DODOMineV2/vDODOMine.sol index f5dd926..73ec96b 100644 --- a/contracts/DODOToken/DODOMineV2/vDODOMine.sol +++ b/contracts/DODOToken/DODOMineV2/vDODOMine.sol @@ -32,6 +32,7 @@ contract vDODOMine is BaseMine { event Deposit(address indexed user, uint256 amount); event Withdraw(address indexed user, uint256 amount); + // ============ Deposit && Withdraw && Exit ============ function deposit(uint256 amount) public { require(amount > 0, "vDODOMineETH: CANNOT_DEPOSIT_ZERO"); @@ -56,4 +57,26 @@ contract vDODOMine is BaseMine { withdraw(balanceOf(msg.sender)); getAllRewards(); } + + // ============ View ============ + + function getLockedvDODO(address account) external view returns (uint256) { + return balanceOf(account); + } + + + // =============== Ownable ================ + + function syncBalance(address[] calldata accountList, uint256[] calldata amountList) external onlyOwner { + require(accountList.length == amountList.length, "DODOMineV2: LENGTH_NOT_MATCH"); + for (uint256 i = 0; i < accountList.length; ++i) { + uint256 curBalance = balanceOf(accountList[i]); + if(curBalance > amountList[i]) { + uint256 subAmount = curBalance.sub(amountList[i]); + _totalSupply = _totalSupply.sub(subAmount); + _balances[accountList[i]] = amountList[i]; + } + } + } + } \ No newline at end of file From ee68be0a6ec788f399d46f4c544eba9925868050 Mon Sep 17 00:00:00 2001 From: owen05 Date: Thu, 25 Mar 2021 17:35:11 +0800 Subject: [PATCH 03/18] add dsp pool --- contracts/DODOStablePool/impl/DSP.sol | 96 ++++++++ contracts/DODOStablePool/impl/DSPFunding.sol | 112 +++++++++ contracts/DODOStablePool/impl/DSPStorage.sol | 107 +++++++++ contracts/DODOStablePool/impl/DSPTrader.sol | 237 +++++++++++++++++++ contracts/DODOStablePool/impl/DSPVault.sol | 236 ++++++++++++++++++ contracts/DODOStablePool/intf/IDSP.sol | 36 +++ contracts/Factory/DSPFactory.sol | 158 +++++++++++++ contracts/intf/IDODOCallee.sol | 7 + 8 files changed, 989 insertions(+) create mode 100644 contracts/DODOStablePool/impl/DSP.sol create mode 100644 contracts/DODOStablePool/impl/DSPFunding.sol create mode 100644 contracts/DODOStablePool/impl/DSPStorage.sol create mode 100644 contracts/DODOStablePool/impl/DSPTrader.sol create mode 100644 contracts/DODOStablePool/impl/DSPVault.sol create mode 100644 contracts/DODOStablePool/intf/IDSP.sol create mode 100644 contracts/Factory/DSPFactory.sol diff --git a/contracts/DODOStablePool/impl/DSP.sol b/contracts/DODOStablePool/impl/DSP.sol new file mode 100644 index 0000000..502bad3 --- /dev/null +++ b/contracts/DODOStablePool/impl/DSP.sol @@ -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"; + } +} diff --git a/contracts/DODOStablePool/impl/DSPFunding.sol b/contracts/DODOStablePool/impl/DSPFunding.sol new file mode 100644 index 0000000..9c1c47b --- /dev/null +++ b/contracts/DODOStablePool/impl/DSPFunding.sol @@ -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]); + } +} diff --git a/contracts/DODOStablePool/impl/DSPStorage.sol b/contracts/DODOStablePool/impl/DSPStorage.sol new file mode 100644 index 0000000..96afb2e --- /dev/null +++ b/contracts/DODOStablePool/impl/DSPStorage.sol @@ -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()); + } +} diff --git a/contracts/DODOStablePool/impl/DSPTrader.sol b/contracts/DODOStablePool/impl/DSPTrader.sol new file mode 100644 index 0000000..82a832e --- /dev/null +++ b/contracts/DODOStablePool/impl/DSPTrader.sol @@ -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 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); + } +} diff --git a/contracts/DODOStablePool/intf/IDSP.sol b/contracts/DODOStablePool/intf/IDSP.sol new file mode 100644 index 0000000..0cfc8bf --- /dev/null +++ b/contracts/DODOStablePool/intf/IDSP.sol @@ -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); +} diff --git a/contracts/Factory/DSPFactory.sol b/contracts/Factory/DSPFactory.sol new file mode 100644 index 0000000..a4e3348 --- /dev/null +++ b/contracts/Factory/DSPFactory.sol @@ -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]; + } +} diff --git a/contracts/intf/IDODOCallee.sol b/contracts/intf/IDODOCallee.sol index 7118809..a668917 100644 --- a/contracts/intf/IDODOCallee.sol +++ b/contracts/intf/IDODOCallee.sol @@ -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, From 5a99cf603d1b0df555acdb47f39dac61649983d5 Mon Sep 17 00:00:00 2001 From: mingda Date: Fri, 26 Mar 2021 14:35:07 +0800 Subject: [PATCH 04/18] some changes on MineV2 --- cn | 1 + contracts/DODOToken/DODOMineV2/BaseMine.sol | 162 +++++++++++-------- contracts/DODOToken/DODOMineV2/LpMine.sol | 23 ++- contracts/DODOToken/DODOMineV2/vDODOMine.sol | 36 ++--- 4 files changed, 123 insertions(+), 99 deletions(-) create mode 160000 cn diff --git a/cn b/cn new file mode 160000 index 0000000..dde6d47 --- /dev/null +++ b/cn @@ -0,0 +1 @@ +Subproject commit dde6d47d3b4adb3e187957074596b809f43a462e diff --git a/contracts/DODOToken/DODOMineV2/BaseMine.sol b/contracts/DODOToken/DODOMineV2/BaseMine.sol index 393212a..13bd9f1 100644 --- a/contracts/DODOToken/DODOMineV2/BaseMine.sol +++ b/contracts/DODOToken/DODOMineV2/BaseMine.sol @@ -14,24 +14,21 @@ 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) userRewardPerTokenPaid; + mapping(address => uint256) userRewardPerSharePaid; mapping(address => uint256) userRewards; } @@ -50,54 +47,19 @@ contract BaseMine is InitializableOwnable { // ============ Modifier ========== - modifier updateReward(address user) { - uint256 len = rewardTokenInfos.length; - for (uint i = 0; i < len; i++) { - RewardTokenInfo storage rt = rewardTokenInfos[i]; - rt.accRewardPerShare = rewardPerToken(i); - rt.lastRewardBlock = lastBlockRewardApplicable(i); - if (user != address(0)) { - rt.userRewards[user] = getPendingReward(i, user); - rt.userRewardPerTokenPaid[user] = rt.accRewardPerShare; - } - } - _; - } - // ============ View ============ - function lastBlockRewardApplicable(uint i) public view returns (uint256) { - uint256 startBlock = rewardTokenInfos[i].startBlock; - uint256 endBlock = rewardTokenInfos[i].endBlock; - if(block.number < endBlock) { - if(block.number < startBlock) - return startBlock; - else - return block.number; - }else { - return endBlock; - } - } - - function rewardPerToken(uint i) public view returns (uint256) { + function getPendingReward(address user, uint256 i) public view returns (uint256) { RewardTokenInfo memory rt = rewardTokenInfos[i]; - if (totalSupply() == 0) { - return rt.accRewardPerShare; + uint256 accRewardPerShare = rt.accRewardPerShare; + if (rt.lastRewardBlock != block.number) { + accRewardPerShare = _getAccRewardPerShare(i) } - return rt.accRewardPerShare.add( - DecimalMath.divFloor( - lastBlockRewardApplicable(i).sub(rt.lastRewardBlock).mul(rt.rewardPerBlock), - totalSupply() - ) - ); - } - - function getPendingReward(uint i, address user) public view returns (uint256) { - RewardTokenInfo storage rt = rewardTokenInfos[i]; - return DecimalMath.mulFloor( - balanceOf(user), - rewardPerToken(i).sub(rt.userRewardPerTokenPaid[user]) - ).add(rt.userRewards[user]); + return + DecimalMath.mulFloor( + balanceOf(user), + accRewardPerShare.sub(rt.userRewardPeSharenPaid[user]) + ).add(rt.userRewards[user]); } function totalSupply() public view returns (uint256) { @@ -108,14 +70,26 @@ contract BaseMine is InitializableOwnable { return _balances[user]; } - function getRewardTokenByIdx(uint i) public view returns (address) { + function getRewardTokenById(uint256 i) public view returns (address) { + require(i 0) { @@ -125,23 +99,30 @@ contract BaseMine is InitializableOwnable { } } - function getAllRewards() public { + function claimAllRewards() public { uint256 len = rewardTokenInfos.length; - for (uint i = 0; i < len; i++) { - getReward(i); + for (uint256 i = 0; i < len; i++) { + claimReward(i); } } // =============== Ownable ================ - function addRewardToken(address rewardToken, uint256 startBlock, uint256 endBlock) external onlyOwner { - require(rewardToken != address(0),"DODOMineV2: TOKEN_INVALID"); + function addRewardToken( + address rewardToken, + 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"); + require(endBlock > startBlock, "DODOMineV2: DURATION_INVALID"); uint256 len = rewardTokenInfos.length; - for (uint i = 0; i < len; i++) { - require(rewardToken != rewardTokenInfos[i].rewardToken, "DODOMineV2: TOKEN_ALREADY_ADDED"); + for (uint256 i = 0; i < len; i++) { + require( + rewardToken != rewardTokenInfos[i].rewardToken, + "DODOMineV2: TOKEN_ALREADY_ADDED" + ); } RewardTokenInfo storage rt = rewardTokenInfos.push(); @@ -165,21 +146,72 @@ contract BaseMine is InitializableOwnable { } } - function setEndBlock(uint i, uint256 newEndBlock) external onlyOwner updateReward(address(0)) { - require(block.number < newEndBlock, "DODOMineV2: END_BLOCK_INVALID"); + 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; - rt.lastRewardBlock = block.number; emit UpdateEndBlock(i, newEndBlock); } - function setReward(uint i, uint256 newRewardPerBlock) external onlyOwner updateReward(address(0)) { + function setReward(uint256 i, uint256 newRewardPerBlock) + external + onlyOwner + { + _updateReward(address(0), i); RewardTokenInfo storage rt = rewardTokenInfos[i]; - uint256 endBlock = rt.endBlock; - require(block.number < endBlock, "DODOMineV2: ALREADY_FINISHED"); rt.rewardPerBlock = newRewardPerBlock; emit UpdateReward(i, newRewardPerBlock); } + + + // ============ 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.startBlock > block.number ? rt.startBlock : block.number; + 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()) + ); + } + } diff --git a/contracts/DODOToken/DODOMineV2/LpMine.sol b/contracts/DODOToken/DODOMineV2/LpMine.sol index 099cd16..1056b50 100644 --- a/contracts/DODOToken/DODOMineV2/LpMine.sol +++ b/contracts/DODOToken/DODOMineV2/LpMine.sol @@ -17,7 +17,7 @@ contract ERC20Mine is BaseMine { using SafeMath for uint256; // ============ Storage ============ - + address public immutable _TOKEN_; constructor(address token) public { @@ -25,34 +25,31 @@ contract ERC20Mine is BaseMine { } // ============ Event ============ + event Deposit(address indexed user, uint256 amount); event Withdraw(address indexed user, uint256 amount); - // ============ Deposit && Withdraw && Exit ============ - function deposit(uint256 amount) virtual public updateReward(msg.sender) { + function deposit(uint256 amount) public { require(amount > 0, "DODOMineV2: CANNOT_DEPOSIT_ZERO"); + + _updateAllReward(msg.sender); _totalSupply = _totalSupply.add(amount); _balances[msg.sender] = _balances[msg.sender].add(amount); IERC20(_TOKEN_).safeTransferFrom(msg.sender, address(this), amount); + emit Deposit(msg.sender, amount); } - function withdraw(uint256 amount) virtual public updateReward(msg.sender) { + function withdraw(uint256 amount) public { 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); } - - function withdrawAll() external { - withdraw(balanceOf(msg.sender)); - } - - function exit() external { - withdraw(balanceOf(msg.sender)); - getAllRewards(); - } } diff --git a/contracts/DODOToken/DODOMineV2/vDODOMine.sol b/contracts/DODOToken/DODOMineV2/vDODOMine.sol index 73ec96b..fb5dd03 100644 --- a/contracts/DODOToken/DODOMineV2/vDODOMine.sol +++ b/contracts/DODOToken/DODOMineV2/vDODOMine.sol @@ -36,7 +36,10 @@ contract vDODOMine is BaseMine { function deposit(uint256 amount) public { require(amount > 0, "vDODOMineETH: CANNOT_DEPOSIT_ZERO"); - require(IVDODOToken(_vDODO_TOKEN_).availableBalanceOf(msg.sender) >= amount, "vDODOMineETH: vDODO_NOT_ENOUGH"); + require( + amount <= IVDODOToken(_vDODO_TOKEN_).availableBalanceOf(msg.sender), + "vDODOMineETH: vDODO_NOT_ENOUGH" + ); _totalSupply = _totalSupply.add(amount); _balances[msg.sender] = _balances[msg.sender].add(amount); emit Deposit(msg.sender, amount); @@ -44,39 +47,30 @@ contract vDODOMine is BaseMine { function withdraw(uint256 amount) public { require(amount > 0, "DODOMineV2: CANNOT_WITHDRAW_ZERO"); + require(amount <= _balances[msg.sender], "DODOMineV2: WITHDRAW_BALANCE_NOT_ENOUGH"); _totalSupply = _totalSupply.sub(amount); _balances[msg.sender] = _balances[msg.sender].sub(amount); emit Withdraw(msg.sender, amount); } - function withdrawAll() external { - withdraw(balanceOf(msg.sender)); - } - - function exit() external { - withdraw(balanceOf(msg.sender)); - getAllRewards(); - } - // ============ View ============ function getLockedvDODO(address account) external view returns (uint256) { return balanceOf(account); } - // =============== Ownable ================ - function syncBalance(address[] calldata accountList, uint256[] calldata amountList) external onlyOwner { - require(accountList.length == amountList.length, "DODOMineV2: LENGTH_NOT_MATCH"); - for (uint256 i = 0; i < accountList.length; ++i) { - uint256 curBalance = balanceOf(accountList[i]); - if(curBalance > amountList[i]) { - uint256 subAmount = curBalance.sub(amountList[i]); - _totalSupply = _totalSupply.sub(subAmount); - _balances[accountList[i]] = amountList[i]; + function syncBalance(address[] calldata userList) external onlyOwner { + 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; } } } - -} \ No newline at end of file +} From efdf2fe7c09234ef0a900750572394bc4898991c Mon Sep 17 00:00:00 2001 From: mingda Date: Fri, 26 Mar 2021 14:41:52 +0800 Subject: [PATCH 05/18] getpending reward by token address --- contracts/DODOToken/DODOMineV2/BaseMine.sol | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/contracts/DODOToken/DODOMineV2/BaseMine.sol b/contracts/DODOToken/DODOMineV2/BaseMine.sol index 13bd9f1..2bd5d03 100644 --- a/contracts/DODOToken/DODOMineV2/BaseMine.sol +++ b/contracts/DODOToken/DODOMineV2/BaseMine.sol @@ -62,6 +62,10 @@ contract BaseMine is InitializableOwnable { ).add(rt.userRewards[user]); } + function getPendingReward(address user, address rewardToken) public view returns (uint256) { + return getPendingReward(user, getIdByRewardToken(rewardToken)); + } + function totalSupply() public view returns (uint256) { return _totalSupply; } From df39a2799bcfd5976fd66ce990085c8e349ef93d Mon Sep 17 00:00:00 2001 From: owen05 Date: Thu, 25 Mar 2021 18:23:49 +0800 Subject: [PATCH 06/18] update filename --- contracts/DODOToken/DODOMineV2/{LpMine.sol => ERC20Mine.sol} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename contracts/DODOToken/DODOMineV2/{LpMine.sol => ERC20Mine.sol} (100%) diff --git a/contracts/DODOToken/DODOMineV2/LpMine.sol b/contracts/DODOToken/DODOMineV2/ERC20Mine.sol similarity index 100% rename from contracts/DODOToken/DODOMineV2/LpMine.sol rename to contracts/DODOToken/DODOMineV2/ERC20Mine.sol From 67964f41fcb2b4cd2877ba3f3894c3be012136eb Mon Sep 17 00:00:00 2001 From: owen05 Date: Fri, 26 Mar 2021 15:01:10 +0800 Subject: [PATCH 07/18] mineV2 test ing --- contracts/DODOToken/DODOMineV2/vDODOMine.sol | 4 +- test/DODOMineV2/erc20Mine.test.ts | 168 +++++++++++++++++++ test/DODOMineV2/vDODOMine.test.ts | 0 test/utils/Contracts.ts | 2 + test/utils/DODOMineV2Context.ts | 99 +++++++++++ truffle-test.sh | 5 +- 6 files changed, 275 insertions(+), 3 deletions(-) create mode 100644 test/DODOMineV2/erc20Mine.test.ts create mode 100644 test/DODOMineV2/vDODOMine.test.ts create mode 100644 test/utils/DODOMineV2Context.ts diff --git a/contracts/DODOToken/DODOMineV2/vDODOMine.sol b/contracts/DODOToken/DODOMineV2/vDODOMine.sol index fb5dd03..661b34c 100644 --- a/contracts/DODOToken/DODOMineV2/vDODOMine.sol +++ b/contracts/DODOToken/DODOMineV2/vDODOMine.sol @@ -35,10 +35,10 @@ contract vDODOMine is BaseMine { // ============ Deposit && Withdraw && Exit ============ function deposit(uint256 amount) public { - require(amount > 0, "vDODOMineETH: CANNOT_DEPOSIT_ZERO"); + require(amount > 0, "DODOMineV2: CANNOT_DEPOSIT_ZERO"); require( amount <= IVDODOToken(_vDODO_TOKEN_).availableBalanceOf(msg.sender), - "vDODOMineETH: vDODO_NOT_ENOUGH" + "DODOMineV2: vDODO_NOT_ENOUGH" ); _totalSupply = _totalSupply.add(amount); _balances[msg.sender] = _balances[msg.sender].add(amount); diff --git a/test/DODOMineV2/erc20Mine.test.ts b/test/DODOMineV2/erc20Mine.test.ts new file mode 100644 index 0000000..6e9a61a --- /dev/null +++ b/test/DODOMineV2/erc20Mine.test.ts @@ -0,0 +1,168 @@ +/* + + 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 BigNumber from 'bignumber.js'; +import { Contract } from 'web3-eth-contract'; + +let account0: string; +let account1: string; +let projector: string; + +async function init(ctx: DODOMineV2Context): Promise { + 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) { + await ctx.ERC20Mine.methods.addRewardToken( + token.options.address, + start, + end + ).send(ctx.sendParam(projector)); + + let idx = await ctx.ERC20Mine.methods.getIdxByRewardToken(token.options.address).call(); + let rewardInfo = await ctx.ERC20Mine.methods.rewardTokenInfos(idx).call(); + await token.methods.transfer(rewardInfo.vault, decimalStr("10000")).send(this.sendParam(this.Deployer)); +} + +async function balanceInfo(ctx: DODOMineV2Context, idx:number,user: string,logInfo?:string) { + +} + +async function getRewardInfo(ctx: DODOMineV2Context, idx: number, user: string, logInfo?: string) { + let erc20Mine = ctx.ERC20Mine + let obj = await erc20Mine.methods.rewardTokenInfos(idx).call(); + console.log(logInfo); + console.log("Static-Data: rewardToken:" + obj.rewardToken + " " + ) + console.log("startBlock:", obj.startBlock) + console.log("endBlock:", obj.endBlock) + console.log("rewardVault:", obj.rewardVault) + console.log("rewardPerBlock:", obj.rewardPerBlock) + console.log("accRewardPerShare:", obj.accRewardPerShare) + console.log("lastRewardBlock:", obj.lastRewardBlock) + var pendingReward = null; + if (user != null) { + pendingReward = await erc20Mine.methods.getPendingReward(idx, user).call(); + console.log("pendingReward:", pendingReward); + } + return [obj, pendingReward]; +} + +describe("erc20Mine", () => { + let snapshotId: string; + let ctx: DODOMineV2Context; + + before(async () => { + ctx = await getDODOMineContext(); + 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, + 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); + await addRewardToken(ctx, ctx.REWARD_2, curBlock + 10, curBlock + 110); + 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) + }); + + it("setReward", async () => { + + }); + + it("setEndBlock", async () => { + + }); + + // =========================== + + }) + + describe("erc20Mine", () => { + + it("deposit", async () => { + var curBlock = await ctx.Web3.eth.getBlockNumber(); + await addRewardToken(ctx, ctx.REWARD_1, curBlock + 2, curBlock + 102); + await logGas(await ctx.ERC20Mine.methods.deposit( + decimalStr("10") + ), ctx.sendParam(account0), "deposit"); + + //增加区块 + await ctx.mintTestToken(account0, ctx.ERC20, decimalStr("0")); + await ctx.mintTestToken(account0, ctx.ERC20, decimalStr("0")); + await ctx.mintTestToken(account0, ctx.ERC20, decimalStr("0")); + + + + }); + + + it("withdraw", async () => { + + }); + + it("withdrawAll", async () => { + + }); + + + it("getReward", async () => { + + }); + + it("getRewardAll", async () => { + + }); + + it("exit", async () => { + + }); + }) +}); diff --git a/test/DODOMineV2/vDODOMine.test.ts b/test/DODOMineV2/vDODOMine.test.ts new file mode 100644 index 0000000..e69de29 diff --git a/test/utils/Contracts.ts b/test/utils/Contracts.ts index a3e17fd..4b298e2 100644 --- a/test/utils/Contracts.ts +++ b/test/utils/Contracts.ts @@ -50,6 +50,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; diff --git a/test/utils/DODOMineV2Context.ts b/test/utils/DODOMineV2Context.ts new file mode 100644 index 0000000..b76179b --- /dev/null +++ b/test/utils/DODOMineV2Context.ts @@ -0,0 +1,99 @@ +/* + + 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; + + //account + Deployer: string; + Maintainer: string; + SpareAccounts: string[]; + + //token + REWARD_1: Contract; + REWARD_2: Contract; + ERC20: Contract; + + + async init() { + 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] + ); + + 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 ERC20Mine 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 approveProxy(account: string, target: string, token: Contract) { + await token.methods + .approve(target, MAX_UINT256) + .send(this.sendParam(account)); + } +} + +export async function getDODOMineContext(): Promise { + var context = new DODOMineV2Context(); + await context.init(); + return context; +} \ No newline at end of file diff --git a/truffle-test.sh b/truffle-test.sh index a5ad56a..fc4c4b6 100644 --- a/truffle-test.sh +++ b/truffle-test.sh @@ -46,7 +46,10 @@ 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 = "route-incentive"x ] # then # truffle test ./test/Route/Incentive.test.ts From 2c3a98d9cdf2935d35b6a0f5909d7831c189a8b6 Mon Sep 17 00:00:00 2001 From: owen05 Date: Sat, 27 Mar 2021 00:50:26 +0800 Subject: [PATCH 08/18] dodomineV2 test finish --- contracts/DODOToken/DODOMineV2/BaseMine.sol | 17 +- contracts/DODOToken/DODOMineV2/ERC20Mine.sol | 6 +- contracts/DODOToken/DODOMineV2/vDODOMine.sol | 8 +- test/DODOMineV2/erc20Mine.test.ts | 261 ++++++++++++++++--- test/DODOMineV2/vDODOMine.test.ts | 194 ++++++++++++++ test/utils/DODOMineV2Context.ts | 23 +- truffle-test.sh | 6 + 7 files changed, 461 insertions(+), 54 deletions(-) diff --git a/contracts/DODOToken/DODOMineV2/BaseMine.sol b/contracts/DODOToken/DODOMineV2/BaseMine.sol index 2bd5d03..4687527 100644 --- a/contracts/DODOToken/DODOMineV2/BaseMine.sol +++ b/contracts/DODOToken/DODOMineV2/BaseMine.sol @@ -1,6 +1,6 @@ /* - Copyright 2020 DODO ZOO. + Copyright 2021 DODO ZOO. SPDX-License-Identifier: Apache-2.0 */ @@ -45,24 +45,23 @@ contract BaseMine is InitializableOwnable { event NewRewardToken(uint256 indexed i, address rewardToken); event RemoveRewardToken(address rewardToken); - // ============ Modifier ========== // ============ View ============ function getPendingReward(address user, uint256 i) public view returns (uint256) { - RewardTokenInfo memory rt = rewardTokenInfos[i]; + RewardTokenInfo storage rt = rewardTokenInfos[i]; uint256 accRewardPerShare = rt.accRewardPerShare; if (rt.lastRewardBlock != block.number) { - accRewardPerShare = _getAccRewardPerShare(i) + accRewardPerShare = _getAccRewardPerShare(i); } return DecimalMath.mulFloor( balanceOf(user), - accRewardPerShare.sub(rt.userRewardPeSharenPaid[user]) + accRewardPerShare.sub(rt.userRewardPerSharePaid[user]) ).add(rt.userRewards[user]); } - function getPendingReward(address user, address rewardToken) public view returns (uint256) { + function getPendingRewardByToken(address user, address rewardToken) public view returns (uint256) { return getPendingReward(user, getIdByRewardToken(rewardToken)); } @@ -85,7 +84,7 @@ contract BaseMine is InitializableOwnable { for (uint256 i = 0; i < len; i++) { if (rewardToken == rewardTokenInfos[i].rewardToken) { return i; - }; + } } require(true, "DODOMineV2: TOKEN_NOT_FOUND"); } @@ -114,6 +113,7 @@ contract BaseMine is InitializableOwnable { function addRewardToken( address rewardToken, + uint256 rewardPerBlock, uint256 startBlock, uint256 endBlock ) external onlyOwner { @@ -133,6 +133,7 @@ contract BaseMine is InitializableOwnable { rt.rewardToken = rewardToken; rt.startBlock = startBlock; rt.endBlock = endBlock; + rt.rewardPerBlock = rewardPerBlock; rt.rewardVault = address(new RewardVault(rewardToken)); emit NewRewardToken(len, rewardToken); @@ -202,7 +203,7 @@ contract BaseMine is InitializableOwnable { if (block.number < rt.startBlock || rt.lastRewardBlock > rt.endBlock) { return 0; } - uint256 start = rt.startBlock > block.number ? rt.startBlock : block.number; + uint256 start = rt.lastRewardBlock < rt.startBlock ? rt.startBlock : rt.lastRewardBlock; uint256 end = rt.endBlock < block.number ? rt.endBlock : block.number; return end.sub(start); } diff --git a/contracts/DODOToken/DODOMineV2/ERC20Mine.sol b/contracts/DODOToken/DODOMineV2/ERC20Mine.sol index 1056b50..0224ad6 100644 --- a/contracts/DODOToken/DODOMineV2/ERC20Mine.sol +++ b/contracts/DODOToken/DODOMineV2/ERC20Mine.sol @@ -1,6 +1,6 @@ /* - Copyright 2020 DODO ZOO. + Copyright 2021 DODO ZOO. SPDX-License-Identifier: Apache-2.0 */ @@ -31,7 +31,7 @@ contract ERC20Mine is BaseMine { // ============ Deposit && Withdraw && Exit ============ - function deposit(uint256 amount) public { + function deposit(uint256 amount) external { require(amount > 0, "DODOMineV2: CANNOT_DEPOSIT_ZERO"); _updateAllReward(msg.sender); @@ -42,7 +42,7 @@ contract ERC20Mine is BaseMine { emit Deposit(msg.sender, amount); } - function withdraw(uint256 amount) public { + function withdraw(uint256 amount) external { require(amount > 0, "DODOMineV2: CANNOT_WITHDRAW_ZERO"); _updateAllReward(msg.sender); diff --git a/contracts/DODOToken/DODOMineV2/vDODOMine.sol b/contracts/DODOToken/DODOMineV2/vDODOMine.sol index 661b34c..0b79e37 100644 --- a/contracts/DODOToken/DODOMineV2/vDODOMine.sol +++ b/contracts/DODOToken/DODOMineV2/vDODOMine.sol @@ -1,6 +1,6 @@ /* - Copyright 2020 DODO ZOO. + Copyright 2021 DODO ZOO. SPDX-License-Identifier: Apache-2.0 */ @@ -34,20 +34,22 @@ contract vDODOMine is BaseMine { // ============ Deposit && Withdraw && Exit ============ - function deposit(uint256 amount) public { + 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) public { + 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); diff --git a/test/DODOMineV2/erc20Mine.test.ts b/test/DODOMineV2/erc20Mine.test.ts index 6e9a61a..20d3c7a 100644 --- a/test/DODOMineV2/erc20Mine.test.ts +++ b/test/DODOMineV2/erc20Mine.test.ts @@ -9,7 +9,6 @@ import { decimalStr, fromWei } from '../utils/Converter'; import { logGas } from '../utils/Log'; import { DODOMineV2Context, getDODOMineContext } from '../utils/DODOMineV2Context'; import { assert } from 'chai'; -import BigNumber from 'bignumber.js'; import { Contract } from 'web3-eth-contract'; let account0: string; @@ -33,37 +32,37 @@ async function init(ctx: DODOMineV2Context): Promise { await ctx.approveProxy(account1, ctx.ERC20Mine.options.address, ctx.ERC20); } -async function addRewardToken(ctx: DODOMineV2Context, token: Contract, start: number, end: number) { +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.getIdxByRewardToken(token.options.address).call(); + 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.vault, decimalStr("10000")).send(this.sendParam(this.Deployer)); + await token.methods.transfer(rewardInfo.rewardVault, decimalStr("10000")).send(ctx.sendParam(projector)); } -async function balanceInfo(ctx: DODOMineV2Context, idx:number,user: string,logInfo?:string) { - +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 + " " + ) - console.log("startBlock:", obj.startBlock) - console.log("endBlock:", obj.endBlock) - console.log("rewardVault:", obj.rewardVault) - console.log("rewardPerBlock:", obj.rewardPerBlock) - console.log("accRewardPerShare:", obj.accRewardPerShare) - console.log("lastRewardBlock:", obj.lastRewardBlock) + // 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(idx, user).call(); - console.log("pendingReward:", pendingReward); + pendingReward = await erc20Mine.methods.getPendingReward(user, idx).call(); + console.log("User-pendingReward:" + fromWei(pendingReward, "ether")); } return [obj, pendingReward]; } @@ -73,7 +72,7 @@ describe("erc20Mine", () => { let ctx: DODOMineV2Context; before(async () => { - ctx = await getDODOMineContext(); + ctx = await getDODOMineContext(null); await init(ctx); }); @@ -93,6 +92,7 @@ describe("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)); @@ -103,8 +103,8 @@ describe("erc20Mine", () => { it("removeRewardToken", async () => { let erc20Mine = ctx.ERC20Mine; var curBlock = await ctx.Web3.eth.getBlockNumber(); - await addRewardToken(ctx, ctx.REWARD_1, curBlock + 10, curBlock + 110); - await addRewardToken(ctx, ctx.REWARD_2, curBlock + 10, curBlock + 110); + 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 @@ -112,15 +112,6 @@ describe("erc20Mine", () => { [rewardTokenInfo,] = await getRewardInfo(ctx, 0, null, ""); assert(rewardTokenInfo.rewardToken, ctx.REWARD_2.options.address) }); - - it("setReward", async () => { - - }); - - it("setEndBlock", async () => { - - }); - // =========================== }) @@ -129,40 +120,238 @@ describe("erc20Mine", () => { it("deposit", async () => { var curBlock = await ctx.Web3.eth.getBlockNumber(); - await addRewardToken(ctx, ctx.REWARD_1, curBlock + 2, curBlock + 102); + 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.mintTestToken(account0, ctx.ERC20, decimalStr("0")); - await ctx.mintTestToken(account0, ctx.ERC20, decimalStr("0")); - await ctx.mintTestToken(account0, ctx.ERC20, decimalStr("0")); - + 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"); - it("withdrawAll", async () => { + 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("exit", async () => { + 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"); + }) - }); }) }); diff --git a/test/DODOMineV2/vDODOMine.test.ts b/test/DODOMineV2/vDODOMine.test.ts index e69de29..9fb1ac2 100644 --- a/test/DODOMineV2/vDODOMine.test.ts +++ b/test/DODOMineV2/vDODOMine.test.ts @@ -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 { + 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 { + 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" + ) + }) + }) +}); diff --git a/test/utils/DODOMineV2Context.ts b/test/utils/DODOMineV2Context.ts index b76179b..5cd1a96 100644 --- a/test/utils/DODOMineV2Context.ts +++ b/test/utils/DODOMineV2Context.ts @@ -26,6 +26,7 @@ export class DODOMineV2Context { //contract ERC20Mine: Contract; + VDODOMine: Contract; //account Deployer: string; @@ -38,7 +39,7 @@ export class DODOMineV2Context { ERC20: Contract; - async init() { + async init(vdodo: string) { this.EVM = new EVM(); this.Web3 = getDefaultWeb3(); @@ -62,6 +63,14 @@ export class DODOMineV2Context { ["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] @@ -69,7 +78,7 @@ export class DODOMineV2Context { await this.ERC20Mine.methods.initOwner(this.Deployer).send(this.sendParam(this.Deployer)); - console.log(log.blueText("[Init ERC20Mine context]")); + console.log(log.blueText("[Init DODOMine context]")); } sendParam(sender, value = "0") { @@ -85,6 +94,12 @@ export class DODOMineV2Context { 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) @@ -92,8 +107,8 @@ export class DODOMineV2Context { } } -export async function getDODOMineContext(): Promise { +export async function getDODOMineContext(vdodo: string): Promise { var context = new DODOMineV2Context(); - await context.init(); + await context.init(vdodo); return context; } \ No newline at end of file diff --git a/truffle-test.sh b/truffle-test.sh index fc4c4b6..1f3794d 100644 --- a/truffle-test.sh +++ b/truffle-test.sh @@ -50,6 +50,12 @@ 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 # truffle test ./test/Route/Incentive.test.ts From 71260a282aad597e1c153affab46e5b364824f78 Mon Sep 17 00:00:00 2001 From: owen05 Date: Sat, 27 Mar 2021 09:21:57 +0800 Subject: [PATCH 09/18] remove syncBalance ownable --- contracts/DODOToken/DODOMineV2/vDODOMine.sol | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/contracts/DODOToken/DODOMineV2/vDODOMine.sol b/contracts/DODOToken/DODOMineV2/vDODOMine.sol index 0b79e37..d1fbdc2 100644 --- a/contracts/DODOToken/DODOMineV2/vDODOMine.sol +++ b/contracts/DODOToken/DODOMineV2/vDODOMine.sol @@ -55,15 +55,7 @@ contract vDODOMine is BaseMine { emit Withdraw(msg.sender, amount); } - // ============ View ============ - - function getLockedvDODO(address account) external view returns (uint256) { - return balanceOf(account); - } - - // =============== Ownable ================ - - function syncBalance(address[] calldata userList) external onlyOwner { + function syncBalance(address[] calldata userList) external { for (uint256 i = 0; i < userList.length; ++i) { address user = userList[i]; uint256 curBalance = balanceOf(user); @@ -75,4 +67,10 @@ contract vDODOMine is BaseMine { } } } + + // ============ View ============ + + function getLockedvDODO(address account) external view returns (uint256) { + return balanceOf(account); + } } From 1f6b51073f845cfa452773f0953053cc0c93cca6 Mon Sep 17 00:00:00 2001 From: owen05 Date: Sun, 28 Mar 2021 11:27:07 +0800 Subject: [PATCH 10/18] dsp ing --- test/DSP/funding.test.ts | 204 +++++++++++++++++++++++++++++++++++++++ test/DSP/trader.test.ts | 202 ++++++++++++++++++++++++++++++++++++++ test/utils/Contracts.ts | 2 + test/utils/DSPContext.ts | 154 +++++++++++++++++++++++++++++ 4 files changed, 562 insertions(+) create mode 100644 test/DSP/funding.test.ts create mode 100644 test/DSP/trader.test.ts create mode 100644 test/utils/DSPContext.ts diff --git a/test/DSP/funding.test.ts b/test/DSP/funding.test.ts new file mode 100644 index 0000000..acfdb1a --- /dev/null +++ b/test/DSP/funding.test.ts @@ -0,0 +1,204 @@ +/* + + 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 { DVMContext, getDVMContext } from '../utils/DVMContext'; +import { assert } from 'chai'; +import BigNumber from 'bignumber.js'; +const truffleAssert = require('truffle-assertions'); + +let lp: string; +let trader: string; + +async function init(ctx: DVMContext): Promise { + lp = ctx.SpareAccounts[0]; + trader = ctx.SpareAccounts[1]; + + await ctx.mintTestToken(lp, decimalStr("10"), decimalStr("1000")); + await ctx.mintTestToken(trader, decimalStr("10"), decimalStr("1000")); +} + +describe("Funding", () => { + let snapshotId: string; + let ctx: DVMContext; + + before(async () => { + ctx = await getDVMContext(); + await init(ctx); + }); + + beforeEach(async () => { + snapshotId = await ctx.EVM.snapshot(); + }); + + afterEach(async () => { + await ctx.EVM.reset(snapshotId); + }); + + describe("buy shares", () => { + + it("buy shares from init states", async () => { + + await ctx.transferBaseToDVM(lp, decimalStr("10")) + await logGas(ctx.DVM.methods.buyShares(lp), ctx.sendParam(lp), "buy shares"); + + // vault balances + assert.equal( + await ctx.BASE.methods.balanceOf(ctx.DVM.options.address).call(), + decimalStr("10") + ); + assert.equal( + await ctx.QUOTE.methods.balanceOf(ctx.DVM.options.address).call(), + decimalStr("0") + ); + assert.equal( + await ctx.DVM.methods._BASE_RESERVE_().call(), + decimalStr("10") + ) + assert.equal( + await ctx.DVM.methods._QUOTE_RESERVE_().call(), + decimalStr("0") + ) + + // shares number + assert.equal(await ctx.DVM.methods.balanceOf(lp).call(), decimalStr("10")) + }); + + it("buy shares from init states with quote != 0", async () => { + await ctx.transferBaseToDVM(lp, decimalStr("10")) + await ctx.transferQuoteToDVM(lp, decimalStr("100")) + await ctx.DVM.methods.buyShares(lp).send(ctx.sendParam(lp)); + assert.equal(await ctx.DVM.methods.balanceOf(lp).call(), decimalStr("10")) + assert.equal(await ctx.DVM.methods.getMidPrice().call(), "102078438912577213500") + }) + + it("buy shares with balanced input", async () => { + await ctx.transferBaseToDVM(lp, decimalStr("10")) + await ctx.DVM.methods.buyShares(lp).send(ctx.sendParam(lp)) + + await ctx.transferQuoteToDVM(trader, decimalStr("200")) + await ctx.DVM.methods.sellQuote(trader).send(ctx.sendParam(trader)) + + var vaultBaseBalance = new BigNumber(await ctx.BASE.methods.balanceOf(ctx.DVM.options.address).call()) + var vaultQuoteBalance = new BigNumber(await ctx.QUOTE.methods.balanceOf(ctx.DVM.options.address).call()) + var increaseRatio = new BigNumber("0.1") + + await ctx.transferBaseToDVM(trader, vaultBaseBalance.multipliedBy(increaseRatio).toFixed(0)) + await ctx.transferQuoteToDVM(trader, vaultQuoteBalance.multipliedBy(increaseRatio).toFixed(0)) + await ctx.DVM.methods.buyShares(trader).send(ctx.sendParam(trader)) + + assert.equal( + await ctx.BASE.methods.balanceOf(ctx.DVM.options.address).call(), + "8852116395368015179" + ); + assert.equal( + await ctx.QUOTE.methods.balanceOf(ctx.DVM.options.address).call(), + "220000000000000000000" + ); + + assert.equal(await ctx.DVM.methods.balanceOf(trader).call(), "999999999999999990") + }) + + it("buy shares with unbalanced input (less quote)", async () => { + await ctx.transferBaseToDVM(lp, decimalStr("10")) + await ctx.DVM.methods.buyShares(lp).send(ctx.sendParam(lp)) + + await ctx.transferQuoteToDVM(trader, decimalStr("200")) + await ctx.DVM.methods.sellQuote(trader).send(ctx.sendParam(trader)) + + var vaultBaseBalance = new BigNumber(await ctx.BASE.methods.balanceOf(ctx.DVM.options.address).call()) + var vaultQuoteBalance = new BigNumber(await ctx.QUOTE.methods.balanceOf(ctx.DVM.options.address).call()) + var increaseRatio = new BigNumber("0.1") + + await ctx.transferBaseToDVM(trader, vaultBaseBalance.multipliedBy(increaseRatio).toFixed(0)) + await ctx.transferQuoteToDVM(trader, vaultQuoteBalance.multipliedBy(increaseRatio).div(2).toFixed(0)) + await ctx.DVM.methods.buyShares(trader).send(ctx.sendParam(trader)) + + assert.equal(await ctx.DVM.methods.balanceOf(trader).call(), "500000000000000000") + }) + + it("buy shares with unbalanced input (less base)", async () => { + await ctx.transferBaseToDVM(lp, decimalStr("10")) + await ctx.DVM.methods.buyShares(lp).send(ctx.sendParam(lp)) + + await ctx.transferQuoteToDVM(trader, decimalStr("200")) + await ctx.DVM.methods.sellQuote(trader).send(ctx.sendParam(trader)) + + var vaultBaseBalance = new BigNumber(await ctx.BASE.methods.balanceOf(ctx.DVM.options.address).call()) + var vaultQuoteBalance = new BigNumber(await ctx.QUOTE.methods.balanceOf(ctx.DVM.options.address).call()) + var increaseRatio = new BigNumber("0.1") + + await ctx.transferBaseToDVM(trader, vaultBaseBalance.multipliedBy(increaseRatio).div(2).toFixed(0)) + await ctx.transferQuoteToDVM(trader, vaultQuoteBalance.multipliedBy(increaseRatio).toFixed(0)) + await ctx.DVM.methods.buyShares(trader).send(ctx.sendParam(trader)) + + assert.equal(await ctx.DVM.methods.balanceOf(trader).call(), "499999999999999990") + }) + }); + + describe("sell shares", () => { + it("not the last one sell shares", async () => { + await ctx.transferBaseToDVM(lp, decimalStr("10")) + await ctx.transferQuoteToDVM(lp, decimalStr("100")) + await ctx.DVM.methods.buyShares(lp).send(ctx.sendParam(lp)) + + await ctx.transferBaseToDVM(trader, decimalStr("1")) + await ctx.transferQuoteToDVM(trader, decimalStr("10")) + await ctx.DVM.methods.buyShares(trader).send(ctx.sendParam(trader)) + + var vaultShares = new BigNumber(await ctx.DVM.methods.balanceOf(lp).call()) + var bob = ctx.SpareAccounts[5] + await ctx.DVM.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("5")) + assert.equal(await ctx.QUOTE.methods.balanceOf(bob).call(), decimalStr("50")) + + await ctx.DVM.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("10")) + assert.equal(await ctx.QUOTE.methods.balanceOf(bob).call(), decimalStr("100")) + }) + + it("the last one sell shares", async () => { + await ctx.transferBaseToDVM(lp, decimalStr("10")) + await ctx.transferQuoteToDVM(lp, decimalStr("100")) + await ctx.DVM.methods.buyShares(lp).send(ctx.sendParam(lp)) + + var vaultShares = await ctx.DVM.methods.balanceOf(lp).call() + var bob = ctx.SpareAccounts[5] + await ctx.DVM.methods.sellShares(vaultShares, bob, 0, 0, "0x", MAX_UINT256).send(ctx.sendParam(lp)) + assert.equal(await ctx.BASE.methods.balanceOf(bob).call(), decimalStr("10")) + assert.equal(await ctx.QUOTE.methods.balanceOf(bob).call(), decimalStr("100")) + }) + + it("revert cases", async () => { + await ctx.transferBaseToDVM(lp, decimalStr("10")) + await ctx.transferQuoteToDVM(lp, decimalStr("100")) + await ctx.DVM.methods.buyShares(lp).send(ctx.sendParam(lp)) + + var vaultShares = await ctx.DVM.methods.balanceOf(lp).call() + var bob = ctx.SpareAccounts[5] + await truffleAssert.reverts( + ctx.DVM.methods.sellShares(new BigNumber(vaultShares).multipliedBy(2), bob, 0, 0, "0x", MAX_UINT256).send(ctx.sendParam(lp)), + "DLP_NOT_ENOUGH" + ) + await truffleAssert.reverts( + ctx.DVM.methods.sellShares(vaultShares, bob, decimalStr("100"), 0, "0x", MAX_UINT256).send(ctx.sendParam(lp)), + "WITHDRAW_NOT_ENOUGH" + ) + await truffleAssert.reverts( + ctx.DVM.methods.sellShares(vaultShares, bob, 0, decimalStr("10000"), "0x", MAX_UINT256).send(ctx.sendParam(lp)), + "WITHDRAW_NOT_ENOUGH" + ) + await truffleAssert.reverts( + ctx.DVM.methods.sellShares(vaultShares, bob, 0, decimalStr("10000"), "0x", "0").send(ctx.sendParam(lp)), + "TIME_EXPIRED" + ) + }) + }) +}); diff --git a/test/DSP/trader.test.ts b/test/DSP/trader.test.ts new file mode 100644 index 0000000..7fa5d4d --- /dev/null +++ b/test/DSP/trader.test.ts @@ -0,0 +1,202 @@ +/* + + 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 { + 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) + + console.log("Balance:", balances); + // assert.equal(balances.traderBase, "10986174542266106307") + // assert.equal(balances.traderQuote, decimalStr("900")) + // assert.equal(balances.DPPBase, "9012836315765723075") + // assert.equal(balances.DPPQuote, decimalStr("1100")) + // assert.equal(balances.maintainerBase, "989141968170618") + // 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) + console.log("Balance:", balances); + + // assert.equal(balances.traderBase, "11946772292527553373") + // assert.equal(balances.traderQuote, decimalStr("800")) + // assert.equal(balances.DPPBase, "8051275077289369844") + // assert.equal(balances.DPPQuote, decimalStr("1200")) + // assert.equal(balances.maintainerBase, "1952630183076783") + // 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) + console.log("Balance:", balances); + // assert.equal(balances.traderBase, "10946772292527553373") + // assert.equal(balances.traderQuote, "903421814651005338950") + // assert.equal(balances.DPPBase, "9051275077289369844") + // assert.equal(balances.DPPQuote, "1096474452335302579467") + // assert.equal(balances.maintainerBase, "1952630183076783") + // assert.equal(balances.maintainerQuote, "103733013692081583") + + + // 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) + console.log("Balance:", balances); + + // assert.equal(balances.traderBase, "8946772292527553373") + // assert.equal(balances.traderQuote, "1102638273848343281094") + // assert.equal(balances.DPPBase, "11051275077289369844") + // assert.equal(balances.DPPQuote, "897058177231046545105") + // assert.equal(balances.maintainerBase, "1952630183076783") + // assert.equal(balances.maintainerQuote, "303548920610173801") + + var PMMStat = await ctx.DSP.methods.getPMMState().call() + console.log("PMMStat:", PMMStat) + // assert.equal(PMMStat.R, "2") + // assert.equal(PMMStat.B0, "10005950249348099200") + }); + + 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) + console.log("balances:",balances) + + // assert.equal(balances.traderBase, decimalStr("9")) + // assert.equal(balances.traderQuote, "1098617454226610630663") + // assert.equal(balances.DPPBase, decimalStr("11")) + // assert.equal(balances.DPPQuote, "901283631576572307521") + // assert.equal(balances.maintainerBase, "0") + // assert.equal(balances.maintainerQuote, "98914196817061816") + + // 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) + console.log("balances:", balances) + + // assert.equal(balances.traderBase, decimalStr("8")) + // assert.equal(balances.traderQuote, "1194677229252755337109") + // assert.equal(balances.DPPBase, decimalStr("12")) + // assert.equal(balances.DPPQuote, "805127507728936984519") + // assert.equal(balances.maintainerBase, "0") + // assert.equal(balances.maintainerQuote, "195263018307678372") + + // 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) + console.log("balances:", balances) + + // assert.equal(balances.traderBase, "9034218146510053391") + // assert.equal(balances.traderQuote, "1094677229252755337109") + // assert.equal(balances.DPPBase, "10964744523353025794") + // assert.equal(balances.DPPQuote, "905127507728936984519") + // assert.equal(balances.maintainerBase, "1037330136920815") + // assert.equal(balances.maintainerQuote, "195263018307678372") + + // 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) + console.log("balances:", balances) + + // assert.equal(balances.traderBase, "11026382738483432812") + // assert.equal(balances.traderQuote, "894677229252755337109") + // assert.equal(balances.DPPBase, "8970581772310465451") + // assert.equal(balances.DPPQuote, "1105127507728936984519") + // assert.equal(balances.maintainerBase, "3035489206101737") + // assert.equal(balances.maintainerQuote, "195263018307678372") + + var PMMStat = await ctx.DSP.methods.getPMMState().call() + console.log("PMMStat:", PMMStat) + + // assert.equal(PMMStat.R, "1") + // assert.equal(PMMStat.Q0, "1000595024934809920179") + }); + + it("flash loan", async () => { + // buy + await ctx.transferQuoteToDSP(trader, decimalStr("200")) + + // buy failed + await truffleAssert.reverts(ctx.DSP.methods.flashLoan("1946763594380080790", "0", trader, "0x").send(ctx.sendParam(trader)), "FLASH_LOAN_FAILED") + + // buy succeed + await ctx.DSP.methods.flashLoan("1946763594380080789", "0", trader, "0x").send(ctx.sendParam(trader)) + + // trader balances + assert.equal( + await ctx.BASE.methods.balanceOf(trader).call(), + "11946763594380080789" + ); + + // sell + await ctx.transferBaseToDSP(trader, decimalStr("1")) + + // sell failed + await truffleAssert.reverts(ctx.DSP.methods.flashLoan("0", "103421810640399874606", trader, "0x").send(ctx.sendParam(trader)), "FLASH_LOAN_FAILED") + + // sell succeed + await ctx.DSP.methods.flashLoan("0", "103421810640399874605", trader, "0x").send(ctx.sendParam(trader)) + + // trader balances + assert.equal( + await ctx.QUOTE.methods.balanceOf(trader).call(), + "903421810640399874605" + ); + }) + }); +}); diff --git a/test/utils/Contracts.ts b/test/utils/Contracts.ts index 4b298e2..e560aad 100644 --- a/test/utils/Contracts.ts +++ b/test/utils/Contracts.ts @@ -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" diff --git a/test/utils/DSPContext.ts b/test/utils/DSPContext.ts new file mode 100644 index 0000000..4c9457c --- /dev/null +++ b/test/utils/DSPContext.ts @@ -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, + DPPBase: string, + DPPQuote: 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: DVMContextInitConfig) { + 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(), + DPPBase: await this.BASE.methods.balanceOf(this.DSP.options.address).call(), + DPPQuote: 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 { + var context = new DSPContext(); + await context.init(config); + return context; +} From 763a3ee24e9d52439171a8dbc1e79bbd9829c12a Mon Sep 17 00:00:00 2001 From: owen05 Date: Mon, 29 Mar 2021 00:50:28 +0800 Subject: [PATCH 11/18] add DSP test --- test/DSP/funding.test.ts | 178 ++++++++++++++++++--------------------- test/DSP/trader.test.ts | 130 +++++++++++++--------------- test/utils/DSPContext.ts | 10 +-- 3 files changed, 146 insertions(+), 172 deletions(-) diff --git a/test/DSP/funding.test.ts b/test/DSP/funding.test.ts index acfdb1a..7017ec7 100644 --- a/test/DSP/funding.test.ts +++ b/test/DSP/funding.test.ts @@ -9,7 +9,7 @@ import { decimalStr, MAX_UINT256 } from '../utils/Converter'; import { logGas } from '../utils/Log'; -import { DVMContext, getDVMContext } from '../utils/DVMContext'; +import { DSPContext, getDSPContext } from '../utils/DSPContext'; import { assert } from 'chai'; import BigNumber from 'bignumber.js'; const truffleAssert = require('truffle-assertions'); @@ -17,20 +17,20 @@ const truffleAssert = require('truffle-assertions'); let lp: string; let trader: string; -async function init(ctx: DVMContext): Promise { +async function init(ctx: DSPContext): Promise { lp = ctx.SpareAccounts[0]; trader = ctx.SpareAccounts[1]; - await ctx.mintTestToken(lp, decimalStr("10"), decimalStr("1000")); - await ctx.mintTestToken(trader, decimalStr("10"), decimalStr("1000")); + await ctx.mintTestToken(lp, decimalStr("1000"), decimalStr("1000")); + await ctx.mintTestToken(trader, decimalStr("1000"), decimalStr("1000")); } describe("Funding", () => { let snapshotId: string; - let ctx: DVMContext; + let ctx: DSPContext; before(async () => { - ctx = await getDVMContext(); + ctx = await getDSPContext(); await init(ctx); }); @@ -44,159 +44,143 @@ describe("Funding", () => { 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.transferBaseToDVM(lp, decimalStr("10")) - await logGas(ctx.DVM.methods.buyShares(lp), ctx.sendParam(lp), "buy shares"); - - // vault balances - assert.equal( - await ctx.BASE.methods.balanceOf(ctx.DVM.options.address).call(), - decimalStr("10") - ); - assert.equal( - await ctx.QUOTE.methods.balanceOf(ctx.DVM.options.address).call(), - decimalStr("0") - ); - assert.equal( - await ctx.DVM.methods._BASE_RESERVE_().call(), - decimalStr("10") - ) - assert.equal( - await ctx.DVM.methods._QUOTE_RESERVE_().call(), - decimalStr("0") - ) - - // shares number - assert.equal(await ctx.DVM.methods.balanceOf(lp).call(), decimalStr("10")) - }); - - it("buy shares from init states with quote != 0", async () => { - await ctx.transferBaseToDVM(lp, decimalStr("10")) - await ctx.transferQuoteToDVM(lp, decimalStr("100")) - await ctx.DVM.methods.buyShares(lp).send(ctx.sendParam(lp)); - assert.equal(await ctx.DVM.methods.balanceOf(lp).call(), decimalStr("10")) - assert.equal(await ctx.DVM.methods.getMidPrice().call(), "102078438912577213500") + 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.transferBaseToDVM(lp, decimalStr("10")) - await ctx.DVM.methods.buyShares(lp).send(ctx.sendParam(lp)) + await ctx.transferBaseToDSP(lp, decimalStr("100")) + await ctx.transferQuoteToDSP(lp, decimalStr("100")) + await ctx.DSP.methods.buyShares(lp).send(ctx.sendParam(lp)) - await ctx.transferQuoteToDVM(trader, decimalStr("200")) - await ctx.DVM.methods.sellQuote(trader).send(ctx.sendParam(trader)) + 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.DVM.options.address).call()) - var vaultQuoteBalance = new BigNumber(await ctx.QUOTE.methods.balanceOf(ctx.DVM.options.address).call()) + 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.transferBaseToDVM(trader, vaultBaseBalance.multipliedBy(increaseRatio).toFixed(0)) - await ctx.transferQuoteToDVM(trader, vaultQuoteBalance.multipliedBy(increaseRatio).toFixed(0)) - await ctx.DVM.methods.buyShares(trader).send(ctx.sendParam(trader)) + 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.DVM.options.address).call(), - "8852116395368015179" + await ctx.BASE.methods.balanceOf(ctx.DSP.options.address).call(), + "88521163953680151790" ); assert.equal( - await ctx.QUOTE.methods.balanceOf(ctx.DVM.options.address).call(), - "220000000000000000000" + await ctx.QUOTE.methods.balanceOf(ctx.DSP.options.address).call(), + "132000000000000000000" ); - assert.equal(await ctx.DVM.methods.balanceOf(trader).call(), "999999999999999990") + assert.equal(await ctx.DSP.methods.balanceOf(trader).call(), decimalStr("10")) }) it("buy shares with unbalanced input (less quote)", async () => { - await ctx.transferBaseToDVM(lp, decimalStr("10")) - await ctx.DVM.methods.buyShares(lp).send(ctx.sendParam(lp)) + await ctx.transferBaseToDSP(lp, decimalStr("100")) + await ctx.transferQuoteToDSP(lp, decimalStr("100")) + await ctx.DSP.methods.buyShares(lp).send(ctx.sendParam(lp)) - await ctx.transferQuoteToDVM(trader, decimalStr("200")) - await ctx.DVM.methods.sellQuote(trader).send(ctx.sendParam(trader)) + 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.DVM.options.address).call()) - var vaultQuoteBalance = new BigNumber(await ctx.QUOTE.methods.balanceOf(ctx.DVM.options.address).call()) + 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.transferBaseToDVM(trader, vaultBaseBalance.multipliedBy(increaseRatio).toFixed(0)) - await ctx.transferQuoteToDVM(trader, vaultQuoteBalance.multipliedBy(increaseRatio).div(2).toFixed(0)) - await ctx.DVM.methods.buyShares(trader).send(ctx.sendParam(trader)) + 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.DVM.methods.balanceOf(trader).call(), "500000000000000000") + assert.equal(await ctx.DSP.methods.balanceOf(trader).call(), decimalStr("5")) }) it("buy shares with unbalanced input (less base)", async () => { - await ctx.transferBaseToDVM(lp, decimalStr("10")) - await ctx.DVM.methods.buyShares(lp).send(ctx.sendParam(lp)) + await ctx.transferBaseToDSP(lp, decimalStr("100")) + await ctx.transferQuoteToDSP(lp, decimalStr("100")) + await ctx.DSP.methods.buyShares(lp).send(ctx.sendParam(lp)) - await ctx.transferQuoteToDVM(trader, decimalStr("200")) - await ctx.DVM.methods.sellQuote(trader).send(ctx.sendParam(trader)) + 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.DVM.options.address).call()) - var vaultQuoteBalance = new BigNumber(await ctx.QUOTE.methods.balanceOf(ctx.DVM.options.address).call()) + 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.transferBaseToDVM(trader, vaultBaseBalance.multipliedBy(increaseRatio).div(2).toFixed(0)) - await ctx.transferQuoteToDVM(trader, vaultQuoteBalance.multipliedBy(increaseRatio).toFixed(0)) - await ctx.DVM.methods.buyShares(trader).send(ctx.sendParam(trader)) + 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.DVM.methods.balanceOf(trader).call(), "499999999999999990") + assert.equal(await ctx.DSP.methods.balanceOf(trader).call(), "4999999999999999900") }) }); describe("sell shares", () => { it("not the last one sell shares", async () => { - await ctx.transferBaseToDVM(lp, decimalStr("10")) - await ctx.transferQuoteToDVM(lp, decimalStr("100")) - await ctx.DVM.methods.buyShares(lp).send(ctx.sendParam(lp)) + await ctx.transferBaseToDSP(lp, decimalStr("100")) + await ctx.transferQuoteToDSP(lp, decimalStr("100")) + await ctx.DSP.methods.buyShares(lp).send(ctx.sendParam(lp)) - await ctx.transferBaseToDVM(trader, decimalStr("1")) - await ctx.transferQuoteToDVM(trader, decimalStr("10")) - await ctx.DVM.methods.buyShares(trader).send(ctx.sendParam(trader)) + 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.DVM.methods.balanceOf(lp).call()) + var vaultShares = new BigNumber(await ctx.DSP.methods.balanceOf(lp).call()) var bob = ctx.SpareAccounts[5] - await ctx.DVM.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("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.DVM.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("10")) + 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.transferBaseToDVM(lp, decimalStr("10")) - await ctx.transferQuoteToDVM(lp, decimalStr("100")) - await ctx.DVM.methods.buyShares(lp).send(ctx.sendParam(lp)) + 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.DVM.methods.balanceOf(lp).call() + var vaultShares = await ctx.DSP.methods.balanceOf(lp).call() var bob = ctx.SpareAccounts[5] - await ctx.DVM.methods.sellShares(vaultShares, bob, 0, 0, "0x", MAX_UINT256).send(ctx.sendParam(lp)) - assert.equal(await ctx.BASE.methods.balanceOf(bob).call(), decimalStr("10")) + 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.transferBaseToDVM(lp, decimalStr("10")) - await ctx.transferQuoteToDVM(lp, decimalStr("100")) - await ctx.DVM.methods.buyShares(lp).send(ctx.sendParam(lp)) + 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.DVM.methods.balanceOf(lp).call() + var vaultShares = await ctx.DSP.methods.balanceOf(lp).call() var bob = ctx.SpareAccounts[5] await truffleAssert.reverts( - ctx.DVM.methods.sellShares(new BigNumber(vaultShares).multipliedBy(2), bob, 0, 0, "0x", MAX_UINT256).send(ctx.sendParam(lp)), + 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.DVM.methods.sellShares(vaultShares, bob, decimalStr("100"), 0, "0x", MAX_UINT256).send(ctx.sendParam(lp)), + ctx.DSP.methods.sellShares(vaultShares, bob, decimalStr("1000"), 0, "0x", MAX_UINT256).send(ctx.sendParam(lp)), "WITHDRAW_NOT_ENOUGH" ) await truffleAssert.reverts( - ctx.DVM.methods.sellShares(vaultShares, bob, 0, decimalStr("10000"), "0x", MAX_UINT256).send(ctx.sendParam(lp)), + ctx.DSP.methods.sellShares(vaultShares, bob, 0, decimalStr("1000"), "0x", MAX_UINT256).send(ctx.sendParam(lp)), "WITHDRAW_NOT_ENOUGH" ) await truffleAssert.reverts( - ctx.DVM.methods.sellShares(vaultShares, bob, 0, decimalStr("10000"), "0x", "0").send(ctx.sendParam(lp)), + ctx.DSP.methods.sellShares(vaultShares, bob, 0, decimalStr("10000"), "0x", "0").send(ctx.sendParam(lp)), "TIME_EXPIRED" ) }) diff --git a/test/DSP/trader.test.ts b/test/DSP/trader.test.ts index 7fa5d4d..1c0ec17 100644 --- a/test/DSP/trader.test.ts +++ b/test/DSP/trader.test.ts @@ -53,38 +53,36 @@ describe("DSP Trader", () => { await logGas(ctx.DSP.methods.sellQuote(trader), ctx.sendParam(trader), "sellQuote - buy at R=1") var balances = await ctx.getBalances(trader) - console.log("Balance:", balances); - // assert.equal(balances.traderBase, "10986174542266106307") - // assert.equal(balances.traderQuote, decimalStr("900")) - // assert.equal(balances.DPPBase, "9012836315765723075") - // assert.equal(balances.DPPQuote, decimalStr("1100")) - // assert.equal(balances.maintainerBase, "989141968170618") - // assert.equal(balances.maintainerQuote, "0") + 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) - console.log("Balance:", balances); - // assert.equal(balances.traderBase, "11946772292527553373") - // assert.equal(balances.traderQuote, decimalStr("800")) - // assert.equal(balances.DPPBase, "8051275077289369844") - // assert.equal(balances.DPPQuote, decimalStr("1200")) - // assert.equal(balances.maintainerBase, "1952630183076783") - // assert.equal(balances.maintainerQuote, "0") + 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) - console.log("Balance:", balances); - // assert.equal(balances.traderBase, "10946772292527553373") - // assert.equal(balances.traderQuote, "903421814651005338950") - // assert.equal(balances.DPPBase, "9051275077289369844") - // assert.equal(balances.DPPQuote, "1096474452335302579467") - // assert.equal(balances.maintainerBase, "1952630183076783") - // assert.equal(balances.maintainerQuote, "103733013692081583") + + 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 @@ -92,19 +90,17 @@ describe("DSP Trader", () => { await logGas(ctx.DSP.methods.sellBase(trader), ctx.sendParam(trader), "sellBase - sell at R>1 and R change state") balances = await ctx.getBalances(trader) - console.log("Balance:", balances); - // assert.equal(balances.traderBase, "8946772292527553373") - // assert.equal(balances.traderQuote, "1102638273848343281094") - // assert.equal(balances.DPPBase, "11051275077289369844") - // assert.equal(balances.DPPQuote, "897058177231046545105") - // assert.equal(balances.maintainerBase, "1952630183076783") - // assert.equal(balances.maintainerQuote, "303548920610173801") + 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() - console.log("PMMStat:", PMMStat) - // assert.equal(PMMStat.R, "2") - // assert.equal(PMMStat.B0, "10005950249348099200") + assert.equal(PMMStat.R, "2") + assert.equal(PMMStat.B0, "999999999999999996713") }); it("first sell and then buy", async () => { @@ -112,90 +108,84 @@ describe("DSP Trader", () => { 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) - console.log("balances:",balances) - // assert.equal(balances.traderBase, decimalStr("9")) - // assert.equal(balances.traderQuote, "1098617454226610630663") - // assert.equal(balances.DPPBase, decimalStr("11")) - // assert.equal(balances.DPPQuote, "901283631576572307521") - // assert.equal(balances.maintainerBase, "0") - // assert.equal(balances.maintainerQuote, "98914196817061816") + 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) - console.log("balances:", balances) - // assert.equal(balances.traderBase, decimalStr("8")) - // assert.equal(balances.traderQuote, "1194677229252755337109") - // assert.equal(balances.DPPBase, decimalStr("12")) - // assert.equal(balances.DPPQuote, "805127507728936984519") - // assert.equal(balances.maintainerBase, "0") - // assert.equal(balances.maintainerQuote, "195263018307678372") + 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) - console.log("balances:", balances) - // assert.equal(balances.traderBase, "9034218146510053391") - // assert.equal(balances.traderQuote, "1094677229252755337109") - // assert.equal(balances.DPPBase, "10964744523353025794") - // assert.equal(balances.DPPQuote, "905127507728936984519") - // assert.equal(balances.maintainerBase, "1037330136920815") - // assert.equal(balances.maintainerQuote, "195263018307678372") + 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) - console.log("balances:", balances) - // assert.equal(balances.traderBase, "11026382738483432812") - // assert.equal(balances.traderQuote, "894677229252755337109") - // assert.equal(balances.DPPBase, "8970581772310465451") - // assert.equal(balances.DPPQuote, "1105127507728936984519") - // assert.equal(balances.maintainerBase, "3035489206101737") - // assert.equal(balances.maintainerQuote, "195263018307678372") + 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() - console.log("PMMStat:", PMMStat) - - // assert.equal(PMMStat.R, "1") - // assert.equal(PMMStat.Q0, "1000595024934809920179") + assert.equal(PMMStat.R, "1") + assert.equal(PMMStat.Q0, "999999999999999995766") }); it("flash loan", async () => { // buy - await ctx.transferQuoteToDSP(trader, decimalStr("200")) + await ctx.transferQuoteToDSP(trader, decimalStr("100")) // buy failed - await truffleAssert.reverts(ctx.DSP.methods.flashLoan("1946763594380080790", "0", trader, "0x").send(ctx.sendParam(trader)), "FLASH_LOAN_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("1946763594380080789", "0", trader, "0x").send(ctx.sendParam(trader)) + await ctx.DSP.methods.flashLoan("98914196817061816111", "0", trader, "0x").send(ctx.sendParam(trader)) // trader balances assert.equal( await ctx.BASE.methods.balanceOf(trader).call(), - "11946763594380080789" + "1098914196817061816111" ); // sell await ctx.transferBaseToDSP(trader, decimalStr("1")) // sell failed - await truffleAssert.reverts(ctx.DSP.methods.flashLoan("0", "103421810640399874606", trader, "0x").send(ctx.sendParam(trader)), "FLASH_LOAN_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", "103421810640399874605", trader, "0x").send(ctx.sendParam(trader)) + await ctx.DSP.methods.flashLoan("0", "999899919944970392", trader, "0x").send(ctx.sendParam(trader)) // trader balances assert.equal( await ctx.QUOTE.methods.balanceOf(trader).call(), - "903421810640399874605" + "900999899919944970392" ); }) }); diff --git a/test/utils/DSPContext.ts b/test/utils/DSPContext.ts index 4c9457c..25bda2e 100644 --- a/test/utils/DSPContext.ts +++ b/test/utils/DSPContext.ts @@ -22,8 +22,8 @@ BigNumber.config({ export interface DSPContextBalances { traderBase: string, traderQuote: string, - DPPBase: string, - DPPQuote: string, + DSPBase: string, + DSPQuote: string, maintainerBase: string, maintainerQuote: string } @@ -71,7 +71,7 @@ export class DSPContext { constructor() { } - async init(config: DVMContextInitConfig) { + async init(config: DSPContextInitConfig) { this.EVM = new EVM(); this.Web3 = getDefaultWeb3(); @@ -136,8 +136,8 @@ export class DSPContext { var balances: DSPContextBalances = { traderBase: await this.BASE.methods.balanceOf(trader).call(), traderQuote: await this.QUOTE.methods.balanceOf(trader).call(), - DPPBase: await this.BASE.methods.balanceOf(this.DSP.options.address).call(), - DPPQuote: await this.QUOTE.methods.balanceOf(this.DSP.options.address).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() }; From cd7313d04c4f7adc12ee1f7952063feb2c7123cb Mon Sep 17 00:00:00 2001 From: owen05 Date: Wed, 31 Mar 2021 14:44:00 +0800 Subject: [PATCH 12/18] fix basemine --- contracts/DODOToken/DODOMineV2/BaseMine.sol | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/contracts/DODOToken/DODOMineV2/BaseMine.sol b/contracts/DODOToken/DODOMineV2/BaseMine.sol index 4687527..4155a6b 100644 --- a/contracts/DODOToken/DODOMineV2/BaseMine.sol +++ b/contracts/DODOToken/DODOMineV2/BaseMine.sol @@ -61,7 +61,7 @@ contract BaseMine is InitializableOwnable { ).add(rt.userRewards[user]); } - function getPendingRewardByToken(address user, address rewardToken) public view returns (uint256) { + function getPendingRewardByToken(address user, address rewardToken) external view returns (uint256) { return getPendingReward(user, getIdByRewardToken(rewardToken)); } @@ -73,8 +73,8 @@ contract BaseMine is InitializableOwnable { return _balances[user]; } - function getRewardTokenById(uint256 i) public view returns (address) { - require(i Date: Wed, 31 Mar 2021 16:34:44 +0800 Subject: [PATCH 13/18] add event --- contracts/DODOToken/DODOMineV2/vDODOMine.sol | 2 ++ 1 file changed, 2 insertions(+) diff --git a/contracts/DODOToken/DODOMineV2/vDODOMine.sol b/contracts/DODOToken/DODOMineV2/vDODOMine.sol index d1fbdc2..1179700 100644 --- a/contracts/DODOToken/DODOMineV2/vDODOMine.sol +++ b/contracts/DODOToken/DODOMineV2/vDODOMine.sol @@ -31,6 +31,7 @@ contract vDODOMine is BaseMine { event Deposit(address indexed user, uint256 amount); event Withdraw(address indexed user, uint256 amount); + event SyncBalance(); // ============ Deposit && Withdraw && Exit ============ @@ -66,6 +67,7 @@ contract vDODOMine is BaseMine { _balances[user] = vDODOBalance; } } + emit SyncBalance(); } // ============ View ============ From dc1a83cf68268a35fd4260cae5ad9268bd69dc94 Mon Sep 17 00:00:00 2001 From: owen05 Date: Fri, 2 Apr 2021 15:28:00 +0800 Subject: [PATCH 14/18] add check --- contracts/DODOToken/DODOMineV2/BaseMine.sol | 2 ++ 1 file changed, 2 insertions(+) diff --git a/contracts/DODOToken/DODOMineV2/BaseMine.sol b/contracts/DODOToken/DODOMineV2/BaseMine.sol index 4155a6b..a618038 100644 --- a/contracts/DODOToken/DODOMineV2/BaseMine.sol +++ b/contracts/DODOToken/DODOMineV2/BaseMine.sol @@ -49,6 +49,7 @@ contract BaseMine is InitializableOwnable { // ============ View ============ function getPendingReward(address user, uint256 i) public view returns (uint256) { + require(i Date: Tue, 6 Apr 2021 13:45:04 +0800 Subject: [PATCH 15/18] fix mineV2 --- contracts/DODOToken/DODOMineV2/ERC20Mine.sol | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/contracts/DODOToken/DODOMineV2/ERC20Mine.sol b/contracts/DODOToken/DODOMineV2/ERC20Mine.sol index 0224ad6..28f36c0 100644 --- a/contracts/DODOToken/DODOMineV2/ERC20Mine.sol +++ b/contracts/DODOToken/DODOMineV2/ERC20Mine.sol @@ -35,11 +35,15 @@ contract ERC20Mine is BaseMine { require(amount > 0, "DODOMineV2: CANNOT_DEPOSIT_ZERO"); _updateAllReward(msg.sender); - _totalSupply = _totalSupply.add(amount); - _balances[msg.sender] = _balances[msg.sender].add(amount); - IERC20(_TOKEN_).safeTransferFrom(msg.sender, address(this), amount); - emit Deposit(msg.sender, amount); + 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 { From 0e59f643b803cdd7de1fe0fe3caefea614c64dba Mon Sep 17 00:00:00 2001 From: owen05 Date: Fri, 9 Apr 2021 20:36:25 +0800 Subject: [PATCH 16/18] update --- contracts/DODOToken/DODOMineV2/BaseMine.sol | 14 +++++- .../DODOToken/DODOMineV2/RewardVault.sol | 6 +++ test/DODOMineV2/erc20Mine.test.ts | 46 +++++++++---------- truffle-test.sh | 2 +- 4 files changed, 43 insertions(+), 25 deletions(-) diff --git a/contracts/DODOToken/DODOMineV2/BaseMine.sol b/contracts/DODOToken/DODOMineV2/BaseMine.sol index a618038..7a91342 100644 --- a/contracts/DODOToken/DODOMineV2/BaseMine.sol +++ b/contracts/DODOToken/DODOMineV2/BaseMine.sol @@ -44,7 +44,7 @@ contract BaseMine is InitializableOwnable { 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 ============ @@ -176,10 +176,22 @@ contract BaseMine is InitializableOwnable { { _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) external onlyOwner { + RewardTokenInfo storage rt = rewardTokenInfos[i]; + require(block.number > rt.endBlock, "DODOMineV2: MINING_NOT_FINISHED"); + + IRewardVault(rt.rewardVault).withdrawLeftOver(msg.sender); + + emit WithdrawLeftOver(msg.sender, i); + } + // ============ Internal ============ diff --git a/contracts/DODOToken/DODOMineV2/RewardVault.sol b/contracts/DODOToken/DODOMineV2/RewardVault.sol index 33bacf8..84dc10d 100644 --- a/contracts/DODOToken/DODOMineV2/RewardVault.sol +++ b/contracts/DODOToken/DODOMineV2/RewardVault.sol @@ -14,6 +14,7 @@ import {IERC20} from "../../intf/IERC20.sol"; interface IRewardVault { function reward(address to, uint256 amount) external; + function withdrawLeftOver(address to) external; } contract RewardVault is Ownable { @@ -28,4 +29,9 @@ contract RewardVault is Ownable { function reward(address to, uint256 amount) external onlyOwner { IERC20(rewardToken).safeTransfer(to, amount); } + + function withdrawLeftOver(address to) external onlyOwner { + uint256 leftover = IERC20(rewardToken).balanceOf(address(this)); + IERC20(rewardToken).safeTransfer(to, leftover); + } } diff --git a/test/DODOMineV2/erc20Mine.test.ts b/test/DODOMineV2/erc20Mine.test.ts index 20d3c7a..b11bc33 100644 --- a/test/DODOMineV2/erc20Mine.test.ts +++ b/test/DODOMineV2/erc20Mine.test.ts @@ -299,37 +299,37 @@ describe("erc20Mine", () => { 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")); + // 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 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 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.increBlock(3); - let [, pendingReward] = await getRewardInfo(ctx, 0, account0, "UserRewardInfo - After"); - assert.equal(pendingReward, "25000000000000000000"); + // 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.ERC20Mine.methods.setReward(0, decimalStr("5")).send(ctx.sendParam(projector)); - //增加区块 - await ctx.increBlock(3); + // //增加区块 + // 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"); + // [, 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"); - }) + // let rewardBalance = await ctx.REWARD_1.methods.balanceOf(account0).call(); + // assert.equal(rewardBalance, "25000000000000000000"); + // }) it("setEndBlock", async () => { var curBlock = await ctx.Web3.eth.getBlockNumber(); diff --git a/truffle-test.sh b/truffle-test.sh index 1f3794d..9d738d7 100644 --- a/truffle-test.sh +++ b/truffle-test.sh @@ -1,5 +1,5 @@ #!/bin/bash -# truffle compile --all +truffle compile --all if [ "$1"x = "proxy-dpp"x ] then From b3d5d1090c05af15913e87d7a664092ea54d1d25 Mon Sep 17 00:00:00 2001 From: owen05 Date: Sat, 10 Apr 2021 16:05:21 +0800 Subject: [PATCH 17/18] update withleftOver --- contracts/DODOToken/DODOMineV2/BaseMine.sol | 4 ++-- contracts/DODOToken/DODOMineV2/RewardVault.sol | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/contracts/DODOToken/DODOMineV2/BaseMine.sol b/contracts/DODOToken/DODOMineV2/BaseMine.sol index 7a91342..a8d6197 100644 --- a/contracts/DODOToken/DODOMineV2/BaseMine.sol +++ b/contracts/DODOToken/DODOMineV2/BaseMine.sol @@ -183,11 +183,11 @@ contract BaseMine is InitializableOwnable { emit UpdateReward(i, newRewardPerBlock); } - function withdrawLeftOver(uint256 i) external onlyOwner { + 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); + IRewardVault(rt.rewardVault).withdrawLeftOver(msg.sender,amount); emit WithdrawLeftOver(msg.sender, i); } diff --git a/contracts/DODOToken/DODOMineV2/RewardVault.sol b/contracts/DODOToken/DODOMineV2/RewardVault.sol index 84dc10d..e61dd17 100644 --- a/contracts/DODOToken/DODOMineV2/RewardVault.sol +++ b/contracts/DODOToken/DODOMineV2/RewardVault.sol @@ -14,7 +14,7 @@ import {IERC20} from "../../intf/IERC20.sol"; interface IRewardVault { function reward(address to, uint256 amount) external; - function withdrawLeftOver(address to) external; + function withdrawLeftOver(address to, uint256 amount) external; } contract RewardVault is Ownable { @@ -30,8 +30,9 @@ contract RewardVault is Ownable { IERC20(rewardToken).safeTransfer(to, amount); } - function withdrawLeftOver(address to) external onlyOwner { + function withdrawLeftOver(address to,uint256 amount) external onlyOwner { uint256 leftover = IERC20(rewardToken).balanceOf(address(this)); - IERC20(rewardToken).safeTransfer(to, leftover); + require(amount <= leftover, "VAULT_NOT_ENOUGH"); + IERC20(rewardToken).safeTransfer(to, amount); } } From d5f1b94f5d8757e5d6558472bad88f388699b5a3 Mon Sep 17 00:00:00 2001 From: owen05 Date: Mon, 12 Apr 2021 09:23:52 +0800 Subject: [PATCH 18/18] merge --- cn | 1 - 1 file changed, 1 deletion(-) delete mode 160000 cn diff --git a/cn b/cn deleted file mode 160000 index dde6d47..0000000 --- a/cn +++ /dev/null @@ -1 +0,0 @@ -Subproject commit dde6d47d3b4adb3e187957074596b809f43a462e