diff --git a/contracts/SmartRoute/DODOIncentive.sol b/contracts/SmartRoute/DODOIncentive.sol new file mode 100644 index 0000000..8ffd4e5 --- /dev/null +++ b/contracts/SmartRoute/DODOIncentive.sol @@ -0,0 +1,110 @@ +/* + + Copyright 2020 DODO ZOO. + SPDX-License-Identifier: Apache-2.0 + +*/ + +pragma solidity 0.6.9; + +import {InitializableOwnable} from "../lib/InitializableOwnable.sol"; +import {SafeERC20} from "../lib/SafeERC20.sol"; +import {SafeMath} from "../lib/SafeMath.sol"; +import {IERC20} from "../intf/IERC20.sol"; + + +contract DODOIncentive is InitializableOwnable { + using SafeMath for uint256; + using SafeERC20 for IERC20; + + // ============ Storage ============ + address public immutable _DODO_TOKEN_; + address public _DODO_PROXY_; + uint256 public dodoPerBlock; + uint256 public defaultRate; + uint256 public startBlock; + mapping(address => uint256) public boosts; + + uint32 public lastRewardBlock; + uint112 public totalReward; + uint112 public totalDistribution; + + constructor(address _dodoToken, address _dodoProxy) public { + _DODO_TOKEN_ = _dodoToken; + _DODO_PROXY_ = _dodoProxy; + } + + // ============ Events ============ + + event SetBoost(address token, uint256 boostRate); + event SetSwitch(bool isOpen); + event SetPerReward(uint256 dodoPerBlock); + event SetDefaultRate(uint256 defaultRate); + event Incentive(address user,uint256 reward, address fromToken, address toToken); + + // ============ Ownable ============ + + function switchIncentive(uint256 _startBlock) public onlyOwner { + if(startBlock != 0) { + require(block.number >= startBlock); + startBlock = 0; + }else { + require(block.number <= _startBlock); + startBlock = _startBlock; + } + _update(); + emit SetSwitch(startBlock == 0 ? false: true); + } + + function changeBoost(address _token, uint256 _boostRate) public onlyOwner { + require(_token != address(0)); + require(_boostRate <= 1000); + boosts[_token] = _boostRate; + emit SetBoost(_token,_boostRate); + } + + function changePerReward(uint256 _dodoPerBlock) public onlyOwner { + _update(); + dodoPerBlock = _dodoPerBlock; + emit SetPerReward(dodoPerBlock); + } + + function changeDefaultRate(uint256 _defaultRate) public onlyOwner { + defaultRate = _defaultRate; + emit SetDefaultRate(defaultRate); + } + + function emptyReward(address assetTo) public onlyOwner { + uint256 balance = IERC20(_DODO_TOKEN_).balanceOf(address(this)); + IERC20(_DODO_TOKEN_).transfer(assetTo, balance); + } + + + // ============ Incentive function============ + function triggerIncentive(address fromToken,address toToken, address assetTo) external { + require(msg.sender == _DODO_PROXY_, "DODOIncentive:Access restricted"); + if(startBlock == 0 || block.number < startBlock) return; + + uint256 curTotalDistribution = totalDistribution; + uint256 fromRate = boosts[fromToken]; + uint256 toRate = boosts[toToken]; + uint256 rate = (fromRate >= toRate ? fromRate : toRate).add(defaultRate); + _update(); + uint256 reward = uint256(totalReward).sub(curTotalDistribution).mul(rate).div(1000); + + uint256 _totalDistribution = curTotalDistribution.add(reward); + require(_totalDistribution < uint112(-1), "OVERFLOW"); + totalDistribution = uint112(_totalDistribution); + + IERC20(_DODO_TOKEN_).transfer(assetTo,reward); + emit Incentive(assetTo,reward,fromToken,toToken); + } + + + function _update() internal { + uint256 _totalReward = uint256(totalReward).add(block.number.sub(uint256(lastRewardBlock)).mul(dodoPerBlock)); + require(_totalReward < uint112(-1) && block.number < uint32(-1), "OVERFLOW"); + totalReward = uint112(_totalReward); + lastRewardBlock = uint32(block.number); + } +} \ No newline at end of file diff --git a/migrations/3_deploy_v2.js b/migrations/3_deploy_v2.js index d98aa16..cf73313 100644 --- a/migrations/3_deploy_v2.js +++ b/migrations/3_deploy_v2.js @@ -10,7 +10,6 @@ const PermissionManagerTemplate = artifacts.require("PermissionManager"); const ExternalValueTemplate = artifacts.require("ExternalValue"); const DvmTemplate = artifacts.require("DVM"); -const DvmAdminTemplate = artifacts.require("DVMAdmin"); const DppTemplate = artifacts.require("DPP"); const DppAdminTemplate = artifacts.require("DPPAdmin"); const CpTemplate = artifacts.require("CP"); @@ -26,7 +25,6 @@ const DODOCalleeHelper = artifacts.require("DODOCalleeHelper"); const DODOV2RouteHelper = artifacts.require("DODOV2RouteHelper"); - module.exports = async (deployer, network, accounts) => { //Helper And Common let DODOSellHelperAddress = ""; @@ -36,17 +34,16 @@ module.exports = async (deployer, network, accounts) => { let DODORouteV2HelperAddress = ""; //Template let CloneFactoryAddress = ""; - let FeeRateModelTemplateAddress = ""; - let ConstFeeRateModelTemplateAddress = ""; - let PermissionManagerTemplateAddress = ""; - let ExternalValueTemplateAddress = ""; + // let FeeRateModelTemplateAddress = ""; + // let ConstFeeRateModelTemplateAddress = ""; + // let PermissionManagerTemplateAddress = ""; + // let ExternalValueTemplateAddress = ""; //Default Template - let DefaultGasSourceAddress = ""; + // let DefaultGasSourceAddress = ""; let DefaultMtFeeRateAddress = ""; let DefaultPermissionAddress = ""; let DvmTemplateAddress = ""; - let DvmAdminTemplateAddress = ""; let DppTemplateAddress = ""; let DppAdminTemplateAddress = ""; let CpTemplateAddress = ""; @@ -69,24 +66,23 @@ module.exports = async (deployer, network, accounts) => { DODORouteV2HelperAddress = "0x3aAfE7c2643807718EFE35D6D529A74255cA4319"; //Template CloneFactoryAddress = "0xf7959fe661124C49F96CF30Da33729201aEE1b27"; - FeeRateModelTemplateAddress = "0xEF3137780B387313c5889B999D03BdCf9aeEa892"; - ConstFeeRateModelTemplateAddress = "0x2ec9579Cf7ae77B4e538F56274501f518ABFeA2e"; - PermissionManagerTemplateAddress = "0x5D2Da09501d97a7bf0A8F192D2eb2F9Aa80d3241"; - ExternalValueTemplateAddress = "0xe0f813951dE2BB012f7Feb981669F9a7b5250A57"; + // FeeRateModelTemplateAddress = "0xEF3137780B387313c5889B999D03BdCf9aeEa892"; + // ConstFeeRateModelTemplateAddress = "0x2ec9579Cf7ae77B4e538F56274501f518ABFeA2e"; + // PermissionManagerTemplateAddress = "0x5D2Da09501d97a7bf0A8F192D2eb2F9Aa80d3241"; + // ExternalValueTemplateAddress = "0xe0f813951dE2BB012f7Feb981669F9a7b5250A57"; //Default Template - DefaultGasSourceAddress = "0xE0c0df0e0be7ec4f579503304a6C186cA4365407"; + // DefaultGasSourceAddress = "0xE0c0df0e0be7ec4f579503304a6C186cA4365407"; DefaultMtFeeRateAddress = "0xEfdE4225AC747136289979e29f1236527b2E4DB1"; DefaultPermissionAddress = "0xACc7E23368261e1E02103c4e5ae672E7D01f5797"; - DvmTemplateAddress = "0x9d37C6BB2e3D62263099faAF2940C0De67a4FD6F"; - DvmAdminTemplateAddress = "0x45f455d7E233403F10b7AFCB0d0d0c0d775AFf63"; - DppTemplateAddress = "0x75CC454c9771A7f1AF4848C4c3775C98b601563E"; - DppAdminTemplateAddress = "0xDfdd9e1693C3A6AF25307c9dA561021f9e685878"; - CpTemplateAddress = "0x60580b981f2670C872AF0119b21C6596Ad7C5D51"; + DvmTemplateAddress = ""; + DppTemplateAddress = ""; + DppAdminTemplateAddress = ""; + CpTemplateAddress = ""; //Factory - DvmFactoryAddress = "0xF2a62693FB14b326C3719e5aeEF28e8e66dC954e"; - DppFactoryAddress = "0x58Bc8D248AcbE95CE29CF893C6666D58AF92d941"; - CpFactoryAddress = "0xD2d3c70C75E4Bb10eE366C8f54A06cCdDF5F3906"; + DvmFactoryAddress = ""; + DppFactoryAddress = ""; + CpFactoryAddress = ""; //Approve DODOApproveAddress = ""; //Account @@ -100,17 +96,16 @@ module.exports = async (deployer, network, accounts) => { DODOCalleeHelperAddress = ""; //Template CloneFactoryAddress = ""; - FeeRateModelTemplateAddress = ""; - ConstFeeRateModelTemplateAddress = ""; - PermissionManagerTemplateAddress = ""; - ExternalValueTemplateAddress = ""; + // FeeRateModelTemplateAddress = ""; + // ConstFeeRateModelTemplateAddress = ""; + // PermissionManagerTemplateAddress = ""; + // ExternalValueTemplateAddress = ""; //Default Template - DefaultGasSourceAddress = ""; + // DefaultGasSourceAddress = ""; DefaultMtFeeRateAddress = ""; DefaultPermissionAddress = ""; DvmTemplateAddress = ""; - DvmAdminTemplateAddress = ""; DppTemplateAddress = ""; DppAdminTemplateAddress = ""; CpTemplateAddress = ""; @@ -131,17 +126,16 @@ module.exports = async (deployer, network, accounts) => { DODOCalleeHelperAddress = ""; //Template CloneFactoryAddress = ""; - FeeRateModelTemplateAddress = ""; - ConstFeeRateModelTemplateAddress = ""; - PermissionManagerTemplateAddress = ""; - ExternalValueTemplateAddress = ""; + // FeeRateModelTemplateAddress = ""; + // ConstFeeRateModelTemplateAddress = ""; + // PermissionManagerTemplateAddress = ""; + // ExternalValueTemplateAddress = ""; //Default Template - DefaultGasSourceAddress = ""; + // DefaultGasSourceAddress = ""; DefaultMtFeeRateAddress = ""; DefaultPermissionAddress = ""; DvmTemplateAddress = ""; - DvmAdminTemplateAddress = ""; DppTemplateAddress = ""; DppAdminTemplateAddress = ""; CpTemplateAddress = ""; @@ -190,35 +184,37 @@ module.exports = async (deployer, network, accounts) => { await deployer.deploy(CloneFactory); CloneFactoryAddress = CloneFactory.address; logger.log("CloneFactoryAddress: ", CloneFactoryAddress); + } - if (FeeRateModelTemplateAddress == "") { - await deployer.deploy(FeeRateModelTemplate); - FeeRateModelTemplateAddress = FeeRateModelTemplate.address; - logger.log("FeeRateModelTemplateAddress: ", FeeRateModelTemplateAddress); - } - if (ConstFeeRateModelTemplateAddress == "") { - await deployer.deploy(ConstFeeRateModelTemplate); - ConstFeeRateModelTemplateAddress = ConstFeeRateModelTemplate.address; - logger.log("ConstFeeRateModelTemplateAddress: ", ConstFeeRateModelTemplateAddress); - } - if (PermissionManagerTemplateAddress == "") { - await deployer.deploy(PermissionManagerTemplate); - PermissionManagerTemplateAddress = PermissionManagerTemplate.address; - logger.log("PermissionManagerTemplateAddress: ", PermissionManagerTemplateAddress); - } - if (ExternalValueTemplateAddress == "") { - await deployer.deploy(ExternalValueTemplate); - ExternalValueTemplateAddress = ExternalValueTemplate.address; - logger.log("ExternalValueTemplateAddress: ", ExternalValueTemplateAddress); - } - if (DefaultGasSourceAddress == "") { - await deployer.deploy(ExternalValueTemplate); - DefaultGasSourceAddress = ExternalValueTemplate.address; - logger.log("DefaultGasSourceAddress: ", DefaultGasSourceAddress); - const defaultGasSourceInstance = await ExternalValueTemplate.at(DefaultGasSourceAddress); - var tx = await defaultGasSourceInstance.init(multiSigAddress, "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); - logger.log("Init DefaultGasSource Tx:", tx.tx); - } + + // if (FeeRateModelTemplateAddress == "") { + // await deployer.deploy(FeeRateModelTemplate); + // FeeRateModelTemplateAddress = FeeRateModelTemplate.address; + // logger.log("FeeRateModelTemplateAddress: ", FeeRateModelTemplateAddress); + // } + // if (ConstFeeRateModelTemplateAddress == "") { + // await deployer.deploy(ConstFeeRateModelTemplate); + // ConstFeeRateModelTemplateAddress = ConstFeeRateModelTemplate.address; + // logger.log("ConstFeeRateModelTemplateAddress: ", ConstFeeRateModelTemplateAddress); + // } + // if (PermissionManagerTemplateAddress == "") { + // await deployer.deploy(PermissionManagerTemplate); + // PermissionManagerTemplateAddress = PermissionManagerTemplate.address; + // logger.log("PermissionManagerTemplateAddress: ", PermissionManagerTemplateAddress); + // } + // if (ExternalValueTemplateAddress == "") { + // await deployer.deploy(ExternalValueTemplate); + // ExternalValueTemplateAddress = ExternalValueTemplate.address; + // logger.log("ExternalValueTemplateAddress: ", ExternalValueTemplateAddress); + // } + // if (DefaultGasSourceAddress == "") { + // await deployer.deploy(ExternalValueTemplate); + // DefaultGasSourceAddress = ExternalValueTemplate.address; + // logger.log("DefaultGasSourceAddress: ", DefaultGasSourceAddress); + // const defaultGasSourceInstance = await ExternalValueTemplate.at(DefaultGasSourceAddress); + // var tx = await defaultGasSourceInstance.init(multiSigAddress, "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); + // logger.log("Init DefaultGasSource Tx:", tx.tx); + // } if (DefaultMtFeeRateAddress == "") { await deployer.deploy(ConstFeeRateModelTemplate); @@ -243,11 +239,6 @@ module.exports = async (deployer, network, accounts) => { DvmTemplateAddress = DvmTemplate.address; logger.log("DvmTemplateAddress: ", DvmTemplateAddress); } - if (DvmAdminTemplateAddress == "") { - await deployer.deploy(DvmAdminTemplate); - DvmAdminTemplateAddress = DvmAdminTemplate.address; - logger.log("DvmAdminTemplateAddress: ", DvmAdminTemplateAddress); - } if (DppTemplateAddress == "") { await deployer.deploy(DppTemplate); DppTemplateAddress = DppTemplate.address; @@ -277,13 +268,8 @@ module.exports = async (deployer, network, accounts) => { DvmFactory, CloneFactoryAddress, DvmTemplateAddress, - DvmAdminTemplateAddress, - FeeRateModelTemplateAddress, - PermissionManagerTemplateAddress, - DefaultGasSourceAddress, defaultMaintainer, - DefaultMtFeeRateAddress, - DefaultPermissionAddress + DefaultMtFeeRateAddress ); DvmFactoryAddress = DvmFactory.address; logger.log("DvmFactoryAddress: ", DvmFactoryAddress); @@ -298,10 +284,8 @@ module.exports = async (deployer, network, accounts) => { CloneFactoryAddress, DppTemplateAddress, DppAdminTemplateAddress, - FeeRateModelTemplateAddress, - PermissionManagerTemplateAddress, - ExternalValueTemplateAddress, - DefaultGasSourceAddress, + defaultMaintainer, + DefaultMtFeeRateAddress, DODOApproveAddress ); DppFactoryAddress = DppFactory.address; @@ -317,11 +301,9 @@ module.exports = async (deployer, network, accounts) => { CloneFactoryAddress, CpTemplateAddress, DvmFactoryAddress, - FeeRateModelTemplateAddress, defaultMaintainer, DefaultMtFeeRateAddress, - DefaultPermissionAddress, - DefaultGasSourceAddress + DefaultPermissionAddress ); CpFactoryAddress = CpFactory.address; logger.log("CpFactoryAddress: ", CpFactoryAddress); diff --git a/migrations/4_deploy_v2_mock.js b/migrations/4_deploy_v2_mock.js index 027ce56..e619d35 100644 --- a/migrations/4_deploy_v2_mock.js +++ b/migrations/4_deploy_v2_mock.js @@ -171,7 +171,6 @@ module.exports = async (deployer, network, accounts) => { baseInAmount, 0, POOL_PARAM[i].lpFeeRate, - POOL_PARAM[i].mtFeeRate, POOL_PARAM[i].i, POOL_PARAM[i].k, deadline @@ -187,7 +186,6 @@ module.exports = async (deployer, network, accounts) => { baseInAmount, quoteInAmount, POOL_PARAM[i].lpFeeRate, - POOL_PARAM[i].mtFeeRate, POOL_PARAM[i].i, POOL_PARAM[i].k, deadline diff --git a/test/Proxy/proxy.incentive.test.ts b/test/Proxy/proxy.incentive.test.ts new file mode 100644 index 0000000..312c092 --- /dev/null +++ b/test/Proxy/proxy.incentive.test.ts @@ -0,0 +1,150 @@ +/* + + Copyright 2020 DODO ZOO. + SPDX-License-Identifier: Apache-2.0 + +*/ + +import { decimalStr, mweiStr } from '../utils/Converter'; +import { logGas } from '../utils/Log'; +import { ProxyContext, getProxyContext } from '../utils/ProxyContextV2'; +import { assert } from 'chai'; +import * as contracts from '../utils/Contracts'; + +let lp: string; +let project: string; +let trader: string; + +let config = { + lpFeeRate: decimalStr("0.002"), + mtFeeRate: decimalStr("0.001"), + k: decimalStr("0.1"), + i: decimalStr("100"), +}; + +async function init(ctx: ProxyContext): Promise { + lp = ctx.SpareAccounts[0]; + project = ctx.SpareAccounts[1]; + trader = ctx.SpareAccounts[2]; + + await ctx.mintTestToken(lp, ctx.DODO, decimalStr("1000000")); + await ctx.mintTestToken(project, ctx.DODO, decimalStr("1000000")); + + await ctx.mintTestToken(lp, ctx.USDT, mweiStr("1000000")); + await ctx.mintTestToken(project, ctx.USDT, mweiStr("1000000")); + + await ctx.approveProxy(lp); + await ctx.approveProxy(project); + await ctx.approveProxy(trader); +} + + +async function initCreateDPP(ctx: ProxyContext, token0: string, token1: string, token0Amount: string, token1Amount: string, ethValue: string, i: string): Promise { + let PROXY = ctx.DODOProxyV2; + await PROXY.methods.createDODOPrivatePool( + token0, + token1, + token0Amount, + token1Amount, + config.lpFeeRate, + i, + config.k, + Math.floor(new Date().getTime() / 1000 + 60 * 10) + ).send(ctx.sendParam(project, ethValue)); + if (token0 == '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE') token0 = ctx.WETH.options.address; + if (token1 == '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE') token1 = ctx.WETH.options.address; + var addr = await ctx.DPPFactory.methods._REGISTRY_(token0, token1, 0).call(); + return addr; +} + +async function initCreateDVM(ctx: ProxyContext, token0: string, token1: string, token0Amount: string, token1Amount: string, ethValue: string, i: string): Promise { + let PROXY = ctx.DODOProxyV2; + await PROXY.methods.createDODOVendingMachine( + project, + token0, + token1, + token0Amount, + token1Amount, + config.lpFeeRate, + i, + config.k, + Math.floor(new Date().getTime() / 1000 + 60 * 10) + ).send(ctx.sendParam(project, ethValue)); + if (token0 == '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE') token0 = ctx.WETH.options.address; + if (token1 == '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE') token1 = ctx.WETH.options.address; + var addr = await ctx.DVMFactory.methods._REGISTRY_(token0, token1, 0).call(); + return addr; +} + +describe("DODOProxyV2.0", () => { + let snapshotId: string; + let ctx: ProxyContext; + let dpp_DODO_USDT: string; + let dvm_WETH_USDT: string; + + before(async () => { + let ETH = await contracts.newContract( + contracts.WETH_CONTRACT_NAME + ); + ctx = await getProxyContext(ETH.options.address); + await init(ctx); + dpp_DODO_USDT = await initCreateDPP(ctx, ctx.DODO.options.address, ctx.USDT.options.address, decimalStr("100000"), mweiStr("20000"), "0", mweiStr("0.2")); + dvm_WETH_USDT = await initCreateDVM(ctx, '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE', ctx.USDT.options.address, decimalStr("5"), mweiStr("3000"), "5", mweiStr("600")); + console.log("dpp_DODO_USDT:", dpp_DODO_USDT); + console.log("dvm_WETH_USDT:", dvm_WETH_USDT); + }); + + beforeEach(async () => { + snapshotId = await ctx.EVM.snapshot(); + }); + + afterEach(async () => { + await ctx.EVM.reset(snapshotId); + }); + + describe("DODOIncentive", () => { + + it("incentive-switch", async () => { + + }); + + it("incentive-changeBoost", async () => { + + }); + + it("incentive-changeDefaultRate", async () => { + + }); + + it("incentive-emptyReward", async () => { + + }); + + it("tigger - incentive", async () => { + await ctx.mintTestToken(trader, ctx.DODO, decimalStr("1000")); + var b_DOOD = await ctx.DODO.methods.balanceOf(trader).call(); + var b_WETH = await ctx.WETH.methods.balanceOf(trader).call(); + var dodoPairs = [ + dpp_DODO_USDT, + dvm_WETH_USDT + ] + var directions = 2 + await logGas(await ctx.DODOProxyV2.methods.dodoSwapV2TokenToToken( + trader, + ctx.DODO.options.address, + ctx.WETH.options.address, + decimalStr("500"), + 1, + dodoPairs, + directions, + Math.floor(new Date().getTime() / 1000 + 60 * 10) + ), ctx.sendParam(trader), "swap - two jump"); + var a_DOOD = await ctx.DODO.methods.balanceOf(trader).call(); + var a_WETH = await ctx.WETH.methods.balanceOf(trader).call(); + console.log("b_DOOD:" + b_DOOD + " a_DODO:" + a_DOOD); + console.log("b_WETH:" + b_WETH + " a_WETH:" + a_WETH); + assert.equal(a_DOOD, decimalStr("500")); + assert.equal(a_WETH, "129932374904193666"); + }); + }); +}); diff --git a/truffle-test.sh b/truffle-test.sh index 5393068..9415d60 100644 --- a/truffle-test.sh +++ b/truffle-test.sh @@ -26,6 +26,11 @@ then truffle test ./test/Proxy/proxy.cp.test.ts fi +if [ "$1"x = "proxy-incentive"x ] +then + truffle test ./test/Proxy/proxy.incentive.test.ts +fi + if [ "$1"x = "route"x ] then truffle test ./test/Route/route.test.ts