├── .babelrc ├── .env ├── .eslintrc.yaml ├── .gitattributes ├── .gitignore ├── .prettierrc ├── .solcover.js ├── LICENSE ├── README.md ├── abi └── dodoAbi.json ├── audit ├── [Peckshield] dodo_audit_report_2020_16_en_1.0.pdf ├── [Trail of Bits] DODO Summary Report.pdf └── dodo_audit_report_2020_16_en_1.0.pdf ├── contracts ├── DODOEthProxy.sol ├── DODOZoo.sol ├── dodo.sol ├── helper │ ├── BandBNBBUSDPriceOracleProxy.sol │ ├── ChainlinkCOMPUSDCPriceOracleProxy.sol │ ├── ChainlinkEthUSDCPriceOracleProxy.sol │ ├── ChainlinkEthUSDTPriceOracleProxy.sol │ ├── ChainlinkLENDUSDCPriceOracleProxy.sol │ ├── ChainlinkLINKUSDPriceOracleProxy.sol │ ├── ChainlinkSNXUSDPriceOracleProxy.sol │ ├── ChainlinkWBTCUSDCPriceOracleProxy.sol │ ├── ChainlinkYFIUSDCPriceOracleProxy.sol │ ├── CloneFactory.sol │ ├── ConstOracle.sol │ ├── Migrations.sol │ ├── MinimumOracle.sol │ ├── MultiSig.sol │ ├── NaiveOracle.sol │ ├── TestERC20.sol │ ├── TestWETH.sol │ ├── UniswapArbitrageur.sol │ └── UniswapV2.sol ├── impl │ ├── Admin.sol │ ├── DODOLpToken.sol │ ├── LiquidityProvider.sol │ ├── Pricing.sol │ ├── Settlement.sol │ ├── Storage.sol │ └── Trader.sol ├── intf │ ├── IDODO.sol │ ├── IDODOCallee.sol │ ├── IDODOLpToken.sol │ ├── IERC20.sol │ ├── IOracle.sol │ └── IWETH.sol ├── lib │ ├── DODOMath.sol │ ├── DecimalMath.sol │ ├── InitializableOwnable.sol │ ├── Ownable.sol │ ├── ReentrancyGuard.sol │ ├── SafeERC20.sol │ ├── SafeMath.sol │ └── Types.sol └── token │ ├── DODOMine.sol │ ├── DODOMineReader.sol │ ├── DODORewardVault.sol │ ├── DODOToken.sol │ └── LockedTokenVault.sol ├── coverage.json ├── migrations ├── 1_initial_migration.js └── 2_deploy.js ├── package-lock.json ├── package.json ├── test ├── Admin.test.ts ├── Attacks.test.ts ├── DODOEthProxyAsBase.test.ts ├── DODOEthProxyAsQuote.test.ts ├── DODOZoo.test.ts ├── LiquidityProvider.test.ts ├── LongTailTokenlMode.test.ts ├── Mining.test.ts ├── StableCoinMode.test.ts ├── TokenLock.test.ts ├── Trader.test.ts ├── UniswapArbitrageur.test.ts └── utils │ ├── Context.ts │ ├── Contracts.ts │ ├── Converter.ts │ ├── EVM.ts │ ├── Log.ts │ └── SlippageFormula.ts ├── truffle-config.js ├── tsconfig.json ├── tslint.json └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-2", "stage-3"] 3 | } 4 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | RPC_NODE_URI=http://127.0.0.1:8545 2 | RESET_SNAPSHOT_ID=0x2 3 | NETWORK_ID=5777 4 | GAS_PRICE=1 -------------------------------------------------------------------------------- /.eslintrc.yaml: -------------------------------------------------------------------------------- 1 | extends: airbnb-base 2 | parser: babel-eslint 3 | 4 | env: 5 | node: true 6 | es6: true 7 | 8 | globals: 9 | artifacts: true 10 | 11 | rules: 12 | no-use-before-define: 0 13 | class-methods-use-this: 0 14 | no-underscore-dangle: 0 15 | max-len: 16 | - error 17 | - 100 18 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .coverage* 2 | .DS_Store 3 | .env.local 4 | .idea 5 | 6 | build/ 7 | dist/ 8 | node_modules/ 9 | coverage/ 10 | lint/ 11 | script/ 12 | flattered/ 13 | 14 | # VIM 15 | *.swo 16 | *.swp 17 | 18 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "overrides": [ 3 | { 4 | "files": "*.sol", 5 | "options": { 6 | "printWidth": 100, 7 | "tabWidth": 4, 8 | "useTabs": false, 9 | "singleQuote": false, 10 | "bracketSpacing": false, 11 | "explicitTypes": "always" 12 | } 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /.solcover.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | client: require("ganache-cli"), 3 | port: 6545, 4 | testrpcOptions: 5 | "--port 6545 -l 0x1fffffffffffff -i 1002 -g 1 --allowUnlimitedContractSize", 6 | skipFiles: [ 7 | "lib/SafeMath.sol", 8 | "lib/DecimalMath.sol", 9 | "lib/Types.sol", 10 | "lib/ReentrancyGuard.sol", 11 | "lib/Ownable.sol", 12 | "impl/DODOLpToken.sol", 13 | "intf", 14 | "helper", 15 | ], 16 | }; 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DODO:10x better liquidity than uniswap 2 | 3 | ## What is DODO? 4 | 5 | ✍️[Introducing DODO](https://medium.com/@dodo.in.the.zoo/introducing-dodo-10x-better-liquidity-than-uniswap-852ce2137c57) 6 | 7 | - DODO is based on a brand new market maker algorithm with an essential idea of risk neutrality to keep liquidity providers’ portfolio stable. 8 | - Compared with AMMs, DODO will perform 10x better in liquidity. 9 | 10 | ## Who audit DODO? 11 | 12 | [PeckShield Inc.](https://peckshield.cn/en) is a leading blockchain security company with the goal of elevating the security, privacy, and usability of current blockchain ecosystems by offering top-notch, industry-leading services and products. 13 | 14 | You could find the audit report [here](https://raw.githubusercontent.com/DODOEX/dodo-smart-contract/master/audit/dodo_audit_report_2020_16_en_1.0.pdf) 15 | 16 | ## More details and deployment info 17 | 18 | You could find all documents and info about DODO [here](https://dodoex.github.io/docs/docs) 19 | 20 | ## Bug Bounty 💰 21 | 22 | ### Rewards 23 | 24 | Severity of bugs will be assessed under the [CVSS Risk Rating](https://www.first.org/cvss/calculator/3.0) scale, as follows: 25 | 26 | - Critical (9.0-10.0): Up to $50,000 27 | - High (7.0-8.9): Up to $10,000 28 | - Medium (4.0-6.9): Up to $2,000 29 | - Low (0.1-3.9): Up to $1,000 30 | 31 | In addition to assessing severity, rewards will be considered based on the impact of the discovered vulnerability as well as the level of difficulty in discovering such vulnerability. 32 | 33 | ### Disclosure 34 | 35 | Any vulnerability or bug discovered must be reported only to the following email: contact@dodoex.io; must not be disclosed publicly; must not be disclosed to any other person, entity or email address prior to disclosure to the contact@dodoex.io email; and must not be disclosed in any way other than to the contact@dodoex.io email. In addition, disclosure to contact@dodoex.io must be made promptly following discovery of the vulnerability. Please include as much information about the vulnerability as possible, including: 36 | 37 | - The conditions on which reproducing the bug is contingent. 38 | - The steps needed to reproduce the bug or, preferably, a proof of concept. 39 | - The potential implications of the vulnerability being abused. 40 | 41 | A detailed report of a vulnerability increases the likelihood of a reward and may increase the reward amount. 42 | 43 | Anyone who reports a unique, previously-unreported vulnerability that results in a change to the code or a configuration change and who keeps such vulnerability confidential until it has been resolved by our engineers will be recognized publicly for their contribution, if agreed. 44 | 45 | ## Contact Us 46 | 47 | Send E-mail to contact@dodoex.io -------------------------------------------------------------------------------- /audit/[Peckshield] dodo_audit_report_2020_16_en_1.0.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DODOEX/dodo-smart-contract/d983485948a55d0ee846951e02cf911633b08d96/audit/[Peckshield] dodo_audit_report_2020_16_en_1.0.pdf -------------------------------------------------------------------------------- /audit/[Trail of Bits] DODO Summary Report.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DODOEX/dodo-smart-contract/d983485948a55d0ee846951e02cf911633b08d96/audit/[Trail of Bits] DODO Summary Report.pdf -------------------------------------------------------------------------------- /audit/dodo_audit_report_2020_16_en_1.0.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DODOEX/dodo-smart-contract/d983485948a55d0ee846951e02cf911633b08d96/audit/dodo_audit_report_2020_16_en_1.0.pdf -------------------------------------------------------------------------------- /contracts/DODOEthProxy.sol: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2020 DODO ZOO. 4 | SPDX-License-Identifier: Apache-2.0 5 | 6 | */ 7 | 8 | pragma solidity 0.6.9; 9 | pragma experimental ABIEncoderV2; 10 | 11 | import {ReentrancyGuard} from "./lib/ReentrancyGuard.sol"; 12 | import {SafeERC20} from "./lib/SafeERC20.sol"; 13 | import {SafeMath} from "./lib/SafeMath.sol"; 14 | import {IDODO} from "./intf/IDODO.sol"; 15 | import {IERC20} from "./intf/IERC20.sol"; 16 | import {IWETH} from "./intf/IWETH.sol"; 17 | 18 | interface IDODOZoo { 19 | function getDODO(address baseToken, address quoteToken) external view returns (address); 20 | } 21 | 22 | /** 23 | * @title DODO Eth Proxy 24 | * @author DODO Breeder 25 | * 26 | * @notice Handle ETH-WETH converting for users. 27 | */ 28 | contract DODOEthProxy is ReentrancyGuard { 29 | using SafeERC20 for IERC20; 30 | using SafeMath for uint256; 31 | 32 | address public _DODO_ZOO_; 33 | address payable public _WETH_; 34 | 35 | // ============ Events ============ 36 | 37 | event ProxySellEthToToken( 38 | address indexed seller, 39 | address indexed quoteToken, 40 | uint256 payEth, 41 | uint256 receiveToken 42 | ); 43 | 44 | event ProxyBuyEthWithToken( 45 | address indexed buyer, 46 | address indexed quoteToken, 47 | uint256 receiveEth, 48 | uint256 payToken 49 | ); 50 | 51 | event ProxySellTokenToEth( 52 | address indexed seller, 53 | address indexed baseToken, 54 | uint256 payToken, 55 | uint256 receiveEth 56 | ); 57 | 58 | event ProxyBuyTokenWithEth( 59 | address indexed buyer, 60 | address indexed baseToken, 61 | uint256 receiveToken, 62 | uint256 payEth 63 | ); 64 | 65 | event ProxyDepositEthAsBase(address indexed lp, address indexed DODO, uint256 ethAmount); 66 | 67 | event ProxyWithdrawEthAsBase(address indexed lp, address indexed DODO, uint256 ethAmount); 68 | 69 | event ProxyDepositEthAsQuote(address indexed lp, address indexed DODO, uint256 ethAmount); 70 | 71 | event ProxyWithdrawEthAsQuote(address indexed lp, address indexed DODO, uint256 ethAmount); 72 | 73 | // ============ Functions ============ 74 | 75 | constructor(address dodoZoo, address payable weth) public { 76 | _DODO_ZOO_ = dodoZoo; 77 | _WETH_ = weth; 78 | } 79 | 80 | fallback() external payable { 81 | require(msg.sender == _WETH_, "WE_SAVED_YOUR_ETH_:)"); 82 | } 83 | 84 | receive() external payable { 85 | require(msg.sender == _WETH_, "WE_SAVED_YOUR_ETH_:)"); 86 | } 87 | 88 | function sellEthToToken( 89 | address quoteTokenAddress, 90 | uint256 ethAmount, 91 | uint256 minReceiveTokenAmount 92 | ) external payable preventReentrant returns (uint256 receiveTokenAmount) { 93 | require(msg.value == ethAmount, "ETH_AMOUNT_NOT_MATCH"); 94 | address DODO = IDODOZoo(_DODO_ZOO_).getDODO(_WETH_, quoteTokenAddress); 95 | require(DODO != address(0), "DODO_NOT_EXIST"); 96 | IWETH(_WETH_).deposit{value: ethAmount}(); 97 | IWETH(_WETH_).approve(DODO, ethAmount); 98 | receiveTokenAmount = IDODO(DODO).sellBaseToken(ethAmount, minReceiveTokenAmount, ""); 99 | _transferOut(quoteTokenAddress, msg.sender, receiveTokenAmount); 100 | emit ProxySellEthToToken(msg.sender, quoteTokenAddress, ethAmount, receiveTokenAmount); 101 | return receiveTokenAmount; 102 | } 103 | 104 | function buyEthWithToken( 105 | address quoteTokenAddress, 106 | uint256 ethAmount, 107 | uint256 maxPayTokenAmount 108 | ) external preventReentrant returns (uint256 payTokenAmount) { 109 | address DODO = IDODOZoo(_DODO_ZOO_).getDODO(_WETH_, quoteTokenAddress); 110 | require(DODO != address(0), "DODO_NOT_EXIST"); 111 | payTokenAmount = IDODO(DODO).queryBuyBaseToken(ethAmount); 112 | _transferIn(quoteTokenAddress, msg.sender, payTokenAmount); 113 | IERC20(quoteTokenAddress).safeApprove(DODO, payTokenAmount); 114 | IDODO(DODO).buyBaseToken(ethAmount, maxPayTokenAmount, ""); 115 | IWETH(_WETH_).withdraw(ethAmount); 116 | msg.sender.transfer(ethAmount); 117 | emit ProxyBuyEthWithToken(msg.sender, quoteTokenAddress, ethAmount, payTokenAmount); 118 | return payTokenAmount; 119 | } 120 | 121 | function sellTokenToEth( 122 | address baseTokenAddress, 123 | uint256 tokenAmount, 124 | uint256 minReceiveEthAmount 125 | ) external preventReentrant returns (uint256 receiveEthAmount) { 126 | address DODO = IDODOZoo(_DODO_ZOO_).getDODO(baseTokenAddress, _WETH_); 127 | require(DODO != address(0), "DODO_NOT_EXIST"); 128 | IERC20(baseTokenAddress).safeApprove(DODO, tokenAmount); 129 | _transferIn(baseTokenAddress, msg.sender, tokenAmount); 130 | receiveEthAmount = IDODO(DODO).sellBaseToken(tokenAmount, minReceiveEthAmount, ""); 131 | IWETH(_WETH_).withdraw(receiveEthAmount); 132 | msg.sender.transfer(receiveEthAmount); 133 | emit ProxySellTokenToEth(msg.sender, baseTokenAddress, tokenAmount, receiveEthAmount); 134 | return receiveEthAmount; 135 | } 136 | 137 | function buyTokenWithEth( 138 | address baseTokenAddress, 139 | uint256 tokenAmount, 140 | uint256 maxPayEthAmount 141 | ) external payable preventReentrant returns (uint256 payEthAmount) { 142 | require(msg.value == maxPayEthAmount, "ETH_AMOUNT_NOT_MATCH"); 143 | address DODO = IDODOZoo(_DODO_ZOO_).getDODO(baseTokenAddress, _WETH_); 144 | require(DODO != address(0), "DODO_NOT_EXIST"); 145 | payEthAmount = IDODO(DODO).queryBuyBaseToken(tokenAmount); 146 | IWETH(_WETH_).deposit{value: payEthAmount}(); 147 | IWETH(_WETH_).approve(DODO, payEthAmount); 148 | IDODO(DODO).buyBaseToken(tokenAmount, maxPayEthAmount, ""); 149 | _transferOut(baseTokenAddress, msg.sender, tokenAmount); 150 | uint256 refund = maxPayEthAmount.sub(payEthAmount); 151 | if (refund > 0) { 152 | msg.sender.transfer(refund); 153 | } 154 | emit ProxyBuyTokenWithEth(msg.sender, baseTokenAddress, tokenAmount, payEthAmount); 155 | return payEthAmount; 156 | } 157 | 158 | function depositEthAsBase(uint256 ethAmount, address quoteTokenAddress) 159 | external 160 | payable 161 | preventReentrant 162 | { 163 | require(msg.value == ethAmount, "ETH_AMOUNT_NOT_MATCH"); 164 | address DODO = IDODOZoo(_DODO_ZOO_).getDODO(_WETH_, quoteTokenAddress); 165 | require(DODO != address(0), "DODO_NOT_EXIST"); 166 | IWETH(_WETH_).deposit{value: ethAmount}(); 167 | IWETH(_WETH_).approve(DODO, ethAmount); 168 | IDODO(DODO).depositBaseTo(msg.sender, ethAmount); 169 | emit ProxyDepositEthAsBase(msg.sender, DODO, ethAmount); 170 | } 171 | 172 | function withdrawEthAsBase(uint256 ethAmount, address quoteTokenAddress) 173 | external 174 | preventReentrant 175 | returns (uint256 withdrawAmount) 176 | { 177 | address DODO = IDODOZoo(_DODO_ZOO_).getDODO(_WETH_, quoteTokenAddress); 178 | require(DODO != address(0), "DODO_NOT_EXIST"); 179 | address ethLpToken = IDODO(DODO)._BASE_CAPITAL_TOKEN_(); 180 | 181 | // transfer all pool shares to proxy 182 | uint256 lpBalance = IERC20(ethLpToken).balanceOf(msg.sender); 183 | IERC20(ethLpToken).transferFrom(msg.sender, address(this), lpBalance); 184 | IDODO(DODO).withdrawBase(ethAmount); 185 | 186 | // transfer remain shares back to msg.sender 187 | lpBalance = IERC20(ethLpToken).balanceOf(address(this)); 188 | IERC20(ethLpToken).transfer(msg.sender, lpBalance); 189 | 190 | // because of withdraw penalty, withdrawAmount may not equal to ethAmount 191 | // query weth amount first and than transfer ETH to msg.sender 192 | uint256 wethAmount = IERC20(_WETH_).balanceOf(address(this)); 193 | IWETH(_WETH_).withdraw(wethAmount); 194 | msg.sender.transfer(wethAmount); 195 | emit ProxyWithdrawEthAsBase(msg.sender, DODO, wethAmount); 196 | return wethAmount; 197 | } 198 | 199 | function withdrawAllEthAsBase(address quoteTokenAddress) 200 | external 201 | preventReentrant 202 | returns (uint256 withdrawAmount) 203 | { 204 | address DODO = IDODOZoo(_DODO_ZOO_).getDODO(_WETH_, quoteTokenAddress); 205 | require(DODO != address(0), "DODO_NOT_EXIST"); 206 | address ethLpToken = IDODO(DODO)._BASE_CAPITAL_TOKEN_(); 207 | 208 | // transfer all pool shares to proxy 209 | uint256 lpBalance = IERC20(ethLpToken).balanceOf(msg.sender); 210 | IERC20(ethLpToken).transferFrom(msg.sender, address(this), lpBalance); 211 | IDODO(DODO).withdrawAllBase(); 212 | 213 | // because of withdraw penalty, withdrawAmount may not equal to ethAmount 214 | // query weth amount first and than transfer ETH to msg.sender 215 | uint256 wethAmount = IERC20(_WETH_).balanceOf(address(this)); 216 | IWETH(_WETH_).withdraw(wethAmount); 217 | msg.sender.transfer(wethAmount); 218 | emit ProxyWithdrawEthAsBase(msg.sender, DODO, wethAmount); 219 | return wethAmount; 220 | } 221 | 222 | function depositEthAsQuote(uint256 ethAmount, address baseTokenAddress) 223 | external 224 | payable 225 | preventReentrant 226 | { 227 | require(msg.value == ethAmount, "ETH_AMOUNT_NOT_MATCH"); 228 | address DODO = IDODOZoo(_DODO_ZOO_).getDODO(baseTokenAddress, _WETH_); 229 | require(DODO != address(0), "DODO_NOT_EXIST"); 230 | IWETH(_WETH_).deposit{value: ethAmount}(); 231 | IWETH(_WETH_).approve(DODO, ethAmount); 232 | IDODO(DODO).depositQuoteTo(msg.sender, ethAmount); 233 | emit ProxyDepositEthAsQuote(msg.sender, DODO, ethAmount); 234 | } 235 | 236 | function withdrawEthAsQuote(uint256 ethAmount, address baseTokenAddress) 237 | external 238 | preventReentrant 239 | returns (uint256 withdrawAmount) 240 | { 241 | address DODO = IDODOZoo(_DODO_ZOO_).getDODO(baseTokenAddress, _WETH_); 242 | require(DODO != address(0), "DODO_NOT_EXIST"); 243 | address ethLpToken = IDODO(DODO)._QUOTE_CAPITAL_TOKEN_(); 244 | 245 | // transfer all pool shares to proxy 246 | uint256 lpBalance = IERC20(ethLpToken).balanceOf(msg.sender); 247 | IERC20(ethLpToken).transferFrom(msg.sender, address(this), lpBalance); 248 | IDODO(DODO).withdrawQuote(ethAmount); 249 | 250 | // transfer remain shares back to msg.sender 251 | lpBalance = IERC20(ethLpToken).balanceOf(address(this)); 252 | IERC20(ethLpToken).transfer(msg.sender, lpBalance); 253 | 254 | // because of withdraw penalty, withdrawAmount may not equal to ethAmount 255 | // query weth amount first and than transfer ETH to msg.sender 256 | uint256 wethAmount = IERC20(_WETH_).balanceOf(address(this)); 257 | IWETH(_WETH_).withdraw(wethAmount); 258 | msg.sender.transfer(wethAmount); 259 | emit ProxyWithdrawEthAsQuote(msg.sender, DODO, wethAmount); 260 | return wethAmount; 261 | } 262 | 263 | function withdrawAllEthAsQuote(address baseTokenAddress) 264 | external 265 | preventReentrant 266 | returns (uint256 withdrawAmount) 267 | { 268 | address DODO = IDODOZoo(_DODO_ZOO_).getDODO(baseTokenAddress, _WETH_); 269 | require(DODO != address(0), "DODO_NOT_EXIST"); 270 | address ethLpToken = IDODO(DODO)._QUOTE_CAPITAL_TOKEN_(); 271 | 272 | // transfer all pool shares to proxy 273 | uint256 lpBalance = IERC20(ethLpToken).balanceOf(msg.sender); 274 | IERC20(ethLpToken).transferFrom(msg.sender, address(this), lpBalance); 275 | IDODO(DODO).withdrawAllQuote(); 276 | 277 | // because of withdraw penalty, withdrawAmount may not equal to ethAmount 278 | // query weth amount first and than transfer ETH to msg.sender 279 | uint256 wethAmount = IERC20(_WETH_).balanceOf(address(this)); 280 | IWETH(_WETH_).withdraw(wethAmount); 281 | msg.sender.transfer(wethAmount); 282 | emit ProxyWithdrawEthAsQuote(msg.sender, DODO, wethAmount); 283 | return wethAmount; 284 | } 285 | 286 | // ============ Helper Functions ============ 287 | 288 | function _transferIn( 289 | address tokenAddress, 290 | address from, 291 | uint256 amount 292 | ) internal { 293 | IERC20(tokenAddress).safeTransferFrom(from, address(this), amount); 294 | } 295 | 296 | function _transferOut( 297 | address tokenAddress, 298 | address to, 299 | uint256 amount 300 | ) internal { 301 | IERC20(tokenAddress).safeTransfer(to, amount); 302 | } 303 | } 304 | -------------------------------------------------------------------------------- /contracts/DODOZoo.sol: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2020 DODO ZOO. 4 | SPDX-License-Identifier: Apache-2.0 5 | 6 | */ 7 | 8 | pragma solidity 0.6.9; 9 | pragma experimental ABIEncoderV2; 10 | 11 | import {Ownable} from "./lib/Ownable.sol"; 12 | import {IDODO} from "./intf/IDODO.sol"; 13 | import {ICloneFactory} from "./helper/CloneFactory.sol"; 14 | 15 | 16 | /** 17 | * @title DODOZoo 18 | * @author DODO Breeder 19 | * 20 | * @notice Register of All DODO 21 | */ 22 | contract DODOZoo is Ownable { 23 | address public _DODO_LOGIC_; 24 | address public _CLONE_FACTORY_; 25 | 26 | address public _DEFAULT_SUPERVISOR_; 27 | 28 | mapping(address => mapping(address => address)) internal _DODO_REGISTER_; 29 | address[] public _DODOs; 30 | 31 | // ============ Events ============ 32 | 33 | event DODOBirth(address newBorn, address baseToken, address quoteToken); 34 | 35 | // ============ Constructor Function ============ 36 | 37 | constructor( 38 | address _dodoLogic, 39 | address _cloneFactory, 40 | address _defaultSupervisor 41 | ) public { 42 | _DODO_LOGIC_ = _dodoLogic; 43 | _CLONE_FACTORY_ = _cloneFactory; 44 | _DEFAULT_SUPERVISOR_ = _defaultSupervisor; 45 | } 46 | 47 | // ============ Admin Function ============ 48 | 49 | function setDODOLogic(address _dodoLogic) external onlyOwner { 50 | _DODO_LOGIC_ = _dodoLogic; 51 | } 52 | 53 | function setCloneFactory(address _cloneFactory) external onlyOwner { 54 | _CLONE_FACTORY_ = _cloneFactory; 55 | } 56 | 57 | function setDefaultSupervisor(address _defaultSupervisor) external onlyOwner { 58 | _DEFAULT_SUPERVISOR_ = _defaultSupervisor; 59 | } 60 | 61 | function removeDODO(address dodo) external onlyOwner { 62 | address baseToken = IDODO(dodo)._BASE_TOKEN_(); 63 | address quoteToken = IDODO(dodo)._QUOTE_TOKEN_(); 64 | require(isDODORegistered(baseToken, quoteToken), "DODO_NOT_REGISTERED"); 65 | _DODO_REGISTER_[baseToken][quoteToken] = address(0); 66 | for (uint256 i = 0; i <= _DODOs.length - 1; i++) { 67 | if (_DODOs[i] == dodo) { 68 | _DODOs[i] = _DODOs[_DODOs.length - 1]; 69 | _DODOs.pop(); 70 | break; 71 | } 72 | } 73 | } 74 | 75 | function addDODO(address dodo) public onlyOwner { 76 | address baseToken = IDODO(dodo)._BASE_TOKEN_(); 77 | address quoteToken = IDODO(dodo)._QUOTE_TOKEN_(); 78 | require(!isDODORegistered(baseToken, quoteToken), "DODO_REGISTERED"); 79 | _DODO_REGISTER_[baseToken][quoteToken] = dodo; 80 | _DODOs.push(dodo); 81 | } 82 | 83 | // ============ Breed DODO Function ============ 84 | 85 | function breedDODO( 86 | address maintainer, 87 | address baseToken, 88 | address quoteToken, 89 | address oracle, 90 | uint256 lpFeeRate, 91 | uint256 mtFeeRate, 92 | uint256 k, 93 | uint256 gasPriceLimit 94 | ) external onlyOwner returns (address newBornDODO) { 95 | require(!isDODORegistered(baseToken, quoteToken), "DODO_REGISTERED"); 96 | newBornDODO = ICloneFactory(_CLONE_FACTORY_).clone(_DODO_LOGIC_); 97 | IDODO(newBornDODO).init( 98 | _OWNER_, 99 | _DEFAULT_SUPERVISOR_, 100 | maintainer, 101 | baseToken, 102 | quoteToken, 103 | oracle, 104 | lpFeeRate, 105 | mtFeeRate, 106 | k, 107 | gasPriceLimit 108 | ); 109 | addDODO(newBornDODO); 110 | emit DODOBirth(newBornDODO, baseToken, quoteToken); 111 | return newBornDODO; 112 | } 113 | 114 | // ============ View Functions ============ 115 | 116 | function isDODORegistered(address baseToken, address quoteToken) public view returns (bool) { 117 | if ( 118 | _DODO_REGISTER_[baseToken][quoteToken] == address(0) && 119 | _DODO_REGISTER_[quoteToken][baseToken] == address(0) 120 | ) { 121 | return false; 122 | } else { 123 | return true; 124 | } 125 | } 126 | 127 | function getDODO(address baseToken, address quoteToken) external view returns (address) { 128 | return _DODO_REGISTER_[baseToken][quoteToken]; 129 | } 130 | 131 | function getDODOs() external view returns (address[] memory) { 132 | return _DODOs; 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /contracts/dodo.sol: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2020 DODO ZOO. 4 | SPDX-License-Identifier: Apache-2.0 5 | 6 | */ 7 | 8 | pragma solidity 0.6.9; 9 | pragma experimental ABIEncoderV2; 10 | 11 | import {Types} from "./lib/Types.sol"; 12 | import {IERC20} from "./intf/IERC20.sol"; 13 | import {Storage} from "./impl/Storage.sol"; 14 | import {Trader} from "./impl/Trader.sol"; 15 | import {LiquidityProvider} from "./impl/LiquidityProvider.sol"; 16 | import {Admin} from "./impl/Admin.sol"; 17 | import {DODOLpToken} from "./impl/DODOLpToken.sol"; 18 | 19 | 20 | /** 21 | * @title DODO 22 | * @author DODO Breeder 23 | * 24 | * @notice Entrance for users 25 | */ 26 | contract DODO is Admin, Trader, LiquidityProvider { 27 | function init( 28 | address owner, 29 | address supervisor, 30 | address maintainer, 31 | address baseToken, 32 | address quoteToken, 33 | address oracle, 34 | uint256 lpFeeRate, 35 | uint256 mtFeeRate, 36 | uint256 k, 37 | uint256 gasPriceLimit 38 | ) external { 39 | require(!_INITIALIZED_, "DODO_INITIALIZED"); 40 | _INITIALIZED_ = true; 41 | 42 | // constructor 43 | _OWNER_ = owner; 44 | emit OwnershipTransferred(address(0), _OWNER_); 45 | 46 | _SUPERVISOR_ = supervisor; 47 | _MAINTAINER_ = maintainer; 48 | _BASE_TOKEN_ = baseToken; 49 | _QUOTE_TOKEN_ = quoteToken; 50 | _ORACLE_ = oracle; 51 | 52 | _DEPOSIT_BASE_ALLOWED_ = false; 53 | _DEPOSIT_QUOTE_ALLOWED_ = false; 54 | _TRADE_ALLOWED_ = false; 55 | _GAS_PRICE_LIMIT_ = gasPriceLimit; 56 | 57 | // Advanced controls are disabled by default 58 | _BUYING_ALLOWED_ = true; 59 | _SELLING_ALLOWED_ = true; 60 | uint256 MAX_INT = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff; 61 | _BASE_BALANCE_LIMIT_ = MAX_INT; 62 | _QUOTE_BALANCE_LIMIT_ = MAX_INT; 63 | 64 | _LP_FEE_RATE_ = lpFeeRate; 65 | _MT_FEE_RATE_ = mtFeeRate; 66 | _K_ = k; 67 | _R_STATUS_ = Types.RStatus.ONE; 68 | 69 | _BASE_CAPITAL_TOKEN_ = address(new DODOLpToken(_BASE_TOKEN_)); 70 | _QUOTE_CAPITAL_TOKEN_ = address(new DODOLpToken(_QUOTE_TOKEN_)); 71 | 72 | _checkDODOParameters(); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /contracts/helper/BandBNBBUSDPriceOracleProxy.sol: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2020 DODO ZOO. 4 | SPDX-License-Identifier: Apache-2.0 5 | 6 | */ 7 | 8 | pragma solidity 0.6.9; 9 | pragma experimental ABIEncoderV2; 10 | 11 | 12 | interface IBandOracleAggregator { 13 | function getReferenceData(string memory base, string memory quote) 14 | external 15 | view 16 | returns (uint256); 17 | } 18 | 19 | 20 | contract BandBNBBUSDPriceOracleProxy { 21 | IBandOracleAggregator public aggregator; 22 | 23 | constructor(IBandOracleAggregator _aggregator) public { 24 | aggregator = _aggregator; 25 | } 26 | 27 | function getPrice() public view returns (uint256) { 28 | return aggregator.getReferenceData("BNB", "USD"); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /contracts/helper/ChainlinkCOMPUSDCPriceOracleProxy.sol: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2020 DODO ZOO. 4 | SPDX-License-Identifier: Apache-2.0 5 | 6 | */ 7 | 8 | pragma solidity 0.6.9; 9 | pragma experimental ABIEncoderV2; 10 | 11 | 12 | interface IChainlink { 13 | function latestAnswer() external view returns (uint256); 14 | } 15 | 16 | 17 | // for COMP-USDC(decimals=6) price convert 18 | 19 | contract ChainlinkCOMPUSDCPriceOracleProxy { 20 | address public chainlink = 0xdbd020CAeF83eFd542f4De03e3cF0C28A4428bd5; 21 | 22 | function getPrice() external view returns (uint256) { 23 | return IChainlink(chainlink).latestAnswer() / 100; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /contracts/helper/ChainlinkEthUSDCPriceOracleProxy.sol: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2020 DODO ZOO. 4 | SPDX-License-Identifier: Apache-2.0 5 | 6 | */ 7 | 8 | pragma solidity 0.6.9; 9 | pragma experimental ABIEncoderV2; 10 | 11 | 12 | interface IChainlink { 13 | function latestAnswer() external view returns (uint256); 14 | } 15 | 16 | 17 | // for WETH-USDC(decimals=6) price convert 18 | 19 | contract ChainlinkETHPriceOracleProxy { 20 | address public chainlink = 0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419; 21 | 22 | function getPrice() external view returns (uint256) { 23 | return IChainlink(chainlink).latestAnswer() / 100; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /contracts/helper/ChainlinkEthUSDTPriceOracleProxy.sol: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2020 DODO ZOO. 4 | SPDX-License-Identifier: Apache-2.0 5 | 6 | */ 7 | 8 | pragma solidity 0.6.9; 9 | pragma experimental ABIEncoderV2; 10 | 11 | 12 | interface IChainlink { 13 | function latestAnswer() external view returns (uint256); 14 | } 15 | 16 | 17 | // for WETH-USDT(decimals=6) price convert 18 | 19 | contract ChainlinkETHUSDTPriceOracleProxy { 20 | address public chainlink = 0xEe9F2375b4bdF6387aa8265dD4FB8F16512A1d46; 21 | 22 | function getPrice() external view returns (uint256) { 23 | return 10**24 / IChainlink(chainlink).latestAnswer(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /contracts/helper/ChainlinkLENDUSDCPriceOracleProxy.sol: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2020 DODO ZOO. 4 | SPDX-License-Identifier: Apache-2.0 5 | 6 | */ 7 | 8 | pragma solidity 0.6.9; 9 | pragma experimental ABIEncoderV2; 10 | 11 | 12 | interface IChainlink { 13 | function latestAnswer() external view returns (uint256); 14 | } 15 | 16 | 17 | // for LEND-USDC(decimals=6) price convert 18 | 19 | contract ChainlinkLENDUSDCPriceOracleProxy { 20 | address public chainlink = 0x4aB81192BB75474Cf203B56c36D6a13623270A67; 21 | 22 | function getPrice() external view returns (uint256) { 23 | return IChainlink(chainlink).latestAnswer() / 100; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /contracts/helper/ChainlinkLINKUSDPriceOracleProxy.sol: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2020 DODO ZOO. 4 | SPDX-License-Identifier: Apache-2.0 5 | 6 | */ 7 | 8 | pragma solidity 0.6.9; 9 | pragma experimental ABIEncoderV2; 10 | 11 | 12 | interface IChainlink { 13 | function latestAnswer() external view returns (uint256); 14 | } 15 | 16 | 17 | // for LINK-USDC(decimals=6) price convert 18 | 19 | contract ChainlinkLINKUSDCPriceOracleProxy { 20 | address public chainlink = 0x2c1d072e956AFFC0D435Cb7AC38EF18d24d9127c; 21 | 22 | function getPrice() external view returns (uint256) { 23 | return IChainlink(chainlink).latestAnswer() / 100; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /contracts/helper/ChainlinkSNXUSDPriceOracleProxy.sol: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2020 DODO ZOO. 4 | SPDX-License-Identifier: Apache-2.0 5 | 6 | */ 7 | 8 | pragma solidity 0.6.9; 9 | pragma experimental ABIEncoderV2; 10 | 11 | 12 | interface IChainlink { 13 | function latestAnswer() external view returns (uint256); 14 | } 15 | 16 | 17 | // for SNX-USDC(decimals=6) price convert 18 | 19 | contract ChainlinkSNXUSDCPriceOracleProxy { 20 | address public chainlink = 0xDC3EA94CD0AC27d9A86C180091e7f78C683d3699; 21 | 22 | function getPrice() external view returns (uint256) { 23 | return IChainlink(chainlink).latestAnswer() / 100; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /contracts/helper/ChainlinkWBTCUSDCPriceOracleProxy.sol: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2020 DODO ZOO. 4 | SPDX-License-Identifier: Apache-2.0 5 | 6 | */ 7 | 8 | pragma solidity 0.6.9; 9 | pragma experimental ABIEncoderV2; 10 | 11 | 12 | interface IChainlink { 13 | function latestAnswer() external view returns (uint256); 14 | } 15 | 16 | 17 | // for WBTC(decimals=8)-USDC(decimals=6) price convert 18 | 19 | contract ChainlinkWBTCUSDCPriceOracleProxy { 20 | address public chainlink = 0xF4030086522a5bEEa4988F8cA5B36dbC97BeE88c; 21 | 22 | function getPrice() external view returns (uint256) { 23 | return IChainlink(chainlink).latestAnswer() * (10**8); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /contracts/helper/ChainlinkYFIUSDCPriceOracleProxy.sol: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2020 DODO ZOO. 4 | SPDX-License-Identifier: Apache-2.0 5 | 6 | */ 7 | 8 | pragma solidity 0.6.9; 9 | pragma experimental ABIEncoderV2; 10 | 11 | import {SafeMath} from "../lib/SafeMath.sol"; 12 | 13 | 14 | interface IChainlink { 15 | function latestAnswer() external view returns (uint256); 16 | } 17 | 18 | 19 | // for YFI-USDC(decimals=6) price convert 20 | 21 | contract ChainlinkYFIUSDCPriceOracleProxy { 22 | using SafeMath for uint256; 23 | 24 | address public yfiEth = 0x7c5d4F8345e66f68099581Db340cd65B078C41f4; 25 | address public EthUsd = 0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419; 26 | 27 | function getPrice() external view returns (uint256) { 28 | uint256 yfiEthPrice = IChainlink(yfiEth).latestAnswer(); 29 | uint256 EthUsdPrice = IChainlink(EthUsd).latestAnswer(); 30 | return yfiEthPrice.mul(EthUsdPrice).div(10**20); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /contracts/helper/CloneFactory.sol: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2020 DODO ZOO. 4 | SPDX-License-Identifier: Apache-2.0 5 | 6 | */ 7 | 8 | pragma solidity 0.6.9; 9 | pragma experimental ABIEncoderV2; 10 | 11 | interface ICloneFactory { 12 | function clone(address prototype) external returns (address proxy); 13 | } 14 | 15 | // introduction of proxy mode design: https://docs.openzeppelin.com/upgrades/2.8/ 16 | // minimum implementation of transparent proxy: https://eips.ethereum.org/EIPS/eip-1167 17 | 18 | contract CloneFactory is ICloneFactory { 19 | function clone(address prototype) external override returns (address proxy) { 20 | bytes20 targetBytes = bytes20(prototype); 21 | assembly { 22 | let clone := mload(0x40) 23 | mstore(clone, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000) 24 | mstore(add(clone, 0x14), targetBytes) 25 | mstore( 26 | add(clone, 0x28), 27 | 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000 28 | ) 29 | proxy := create(0, clone, 0x37) 30 | } 31 | return proxy; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /contracts/helper/ConstOracle.sol: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2020 DODO ZOO. 4 | SPDX-License-Identifier: Apache-2.0 5 | 6 | */ 7 | 8 | pragma solidity 0.6.9; 9 | pragma experimental ABIEncoderV2; 10 | 11 | 12 | contract ConstOracle { 13 | uint256 public tokenPrice; 14 | 15 | constructor(uint256 _price) public { 16 | tokenPrice = _price; 17 | } 18 | 19 | function getPrice() external view returns (uint256) { 20 | return tokenPrice; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /contracts/helper/Migrations.sol: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2020 DODO ZOO. 4 | SPDX-License-Identifier: Apache-2.0 5 | 6 | */ 7 | 8 | pragma solidity 0.6.9; 9 | pragma experimental ABIEncoderV2; 10 | 11 | 12 | contract Migrations { 13 | address public owner; 14 | uint256 public last_completed_migration; 15 | 16 | modifier restricted() { 17 | if (msg.sender == owner) { 18 | _; 19 | } 20 | } 21 | 22 | constructor() public { 23 | owner = msg.sender; 24 | } 25 | 26 | function setCompleted(uint256 completed) public restricted { 27 | last_completed_migration = completed; 28 | } 29 | 30 | function upgrade(address newAddress) public restricted { 31 | Migrations upgraded = Migrations(newAddress); 32 | upgraded.setCompleted(last_completed_migration); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /contracts/helper/MinimumOracle.sol: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2020 DODO ZOO. 4 | SPDX-License-Identifier: Apache-2.0 5 | 6 | */ 7 | 8 | pragma solidity 0.6.9; 9 | pragma experimental ABIEncoderV2; 10 | 11 | 12 | interface IMinimumOracle { 13 | function getPrice() external view returns (uint256); 14 | 15 | function setPrice(uint256 newPrice) external; 16 | 17 | function transferOwnership(address newOwner) external; 18 | } 19 | 20 | 21 | contract MinimumOracle { 22 | address public _OWNER_; 23 | uint256 public tokenPrice; 24 | 25 | // ============ Events ============ 26 | 27 | event OwnershipTransfer(address indexed previousOwner, address indexed newOwner); 28 | 29 | // ============ Modifiers ============ 30 | 31 | modifier onlyOwner() { 32 | require(msg.sender == _OWNER_, "NOT_OWNER"); 33 | _; 34 | } 35 | 36 | // ============ Functions ============ 37 | 38 | constructor() public { 39 | _OWNER_ = msg.sender; 40 | emit OwnershipTransfer(address(0), _OWNER_); 41 | } 42 | 43 | function transferOwnership(address newOwner) external onlyOwner { 44 | require(newOwner != address(0), "INVALID_OWNER"); 45 | emit OwnershipTransfer(_OWNER_, newOwner); 46 | _OWNER_ = newOwner; 47 | } 48 | 49 | function setPrice(uint256 newPrice) external onlyOwner { 50 | tokenPrice = newPrice; 51 | } 52 | 53 | function getPrice() external view returns (uint256) { 54 | return tokenPrice; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /contracts/helper/NaiveOracle.sol: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2020 DODO ZOO. 4 | SPDX-License-Identifier: Apache-2.0 5 | 6 | */ 7 | 8 | pragma solidity 0.6.9; 9 | pragma experimental ABIEncoderV2; 10 | 11 | import {Ownable} from "../lib/Ownable.sol"; 12 | 13 | 14 | // Oracle only for test 15 | contract NaiveOracle is Ownable { 16 | uint256 public tokenPrice; 17 | 18 | function setPrice(uint256 newPrice) external onlyOwner { 19 | tokenPrice = newPrice; 20 | } 21 | 22 | function getPrice() external view returns (uint256) { 23 | return tokenPrice; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /contracts/helper/TestERC20.sol: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2020 DODO ZOO. 4 | SPDX-License-Identifier: Apache-2.0 5 | 6 | */ 7 | 8 | pragma solidity 0.6.9; 9 | 10 | import {SafeMath} from "../lib/SafeMath.sol"; 11 | 12 | contract TestERC20 { 13 | using SafeMath for uint256; 14 | 15 | string public name; 16 | uint8 public decimals; 17 | 18 | mapping(address => uint256) balances; 19 | mapping(address => mapping(address => uint256)) internal allowed; 20 | 21 | event Transfer(address indexed from, address indexed to, uint256 amount); 22 | event Approval(address indexed owner, address indexed spender, uint256 amount); 23 | 24 | constructor(string memory _name, uint8 _decimals) public { 25 | name = _name; 26 | decimals = _decimals; 27 | } 28 | 29 | function transfer(address to, uint256 amount) public returns (bool) { 30 | require(to != address(0), "TO_ADDRESS_IS_EMPTY"); 31 | require(amount <= balances[msg.sender], "BALANCE_NOT_ENOUGH"); 32 | 33 | balances[msg.sender] = balances[msg.sender].sub(amount); 34 | balances[to] = balances[to].add(amount); 35 | emit Transfer(msg.sender, to, amount); 36 | return true; 37 | } 38 | 39 | function balanceOf(address owner) public view returns (uint256 balance) { 40 | return balances[owner]; 41 | } 42 | 43 | function transferFrom( 44 | address from, 45 | address to, 46 | uint256 amount 47 | ) public returns (bool) { 48 | require(to != address(0), "TO_ADDRESS_IS_EMPTY"); 49 | require(amount <= balances[from], "BALANCE_NOT_ENOUGH"); 50 | require(amount <= allowed[from][msg.sender], "ALLOWANCE_NOT_ENOUGH"); 51 | 52 | balances[from] = balances[from].sub(amount); 53 | balances[to] = balances[to].add(amount); 54 | allowed[from][msg.sender] = allowed[from][msg.sender].sub(amount); 55 | emit Transfer(from, to, amount); 56 | return true; 57 | } 58 | 59 | function approve(address spender, uint256 amount) public returns (bool) { 60 | allowed[msg.sender][spender] = amount; 61 | emit Approval(msg.sender, spender, amount); 62 | return true; 63 | } 64 | 65 | function allowance(address owner, address spender) public view returns (uint256) { 66 | return allowed[owner][spender]; 67 | } 68 | 69 | function mint(address account, uint256 amount) external { 70 | balances[account] = balances[account].add(amount); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /contracts/helper/TestWETH.sol: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2020 DODO ZOO. 4 | SPDX-License-Identifier: Apache-2.0 5 | 6 | */ 7 | 8 | pragma solidity 0.6.9; 9 | 10 | 11 | contract WETH9 { 12 | string public name = "Wrapped Ether"; 13 | string public symbol = "WETH"; 14 | uint8 public decimals = 18; 15 | 16 | event Approval(address indexed src, address indexed guy, uint256 wad); 17 | event Transfer(address indexed src, address indexed dst, uint256 wad); 18 | event Deposit(address indexed dst, uint256 wad); 19 | event Withdrawal(address indexed src, uint256 wad); 20 | 21 | mapping(address => uint256) public balanceOf; 22 | mapping(address => mapping(address => uint256)) public allowance; 23 | 24 | fallback() external payable { 25 | deposit(); 26 | } 27 | 28 | receive() external payable { 29 | deposit(); 30 | } 31 | 32 | function deposit() public payable { 33 | balanceOf[msg.sender] += msg.value; 34 | emit Deposit(msg.sender, msg.value); 35 | } 36 | 37 | function withdraw(uint256 wad) public { 38 | require(balanceOf[msg.sender] >= wad); 39 | balanceOf[msg.sender] -= wad; 40 | msg.sender.transfer(wad); 41 | emit Withdrawal(msg.sender, wad); 42 | } 43 | 44 | function totalSupply() public view returns (uint256) { 45 | return address(this).balance; 46 | } 47 | 48 | function approve(address guy, uint256 wad) public returns (bool) { 49 | allowance[msg.sender][guy] = wad; 50 | emit Approval(msg.sender, guy, wad); 51 | return true; 52 | } 53 | 54 | function transfer(address dst, uint256 wad) public returns (bool) { 55 | return transferFrom(msg.sender, dst, wad); 56 | } 57 | 58 | function transferFrom( 59 | address src, 60 | address dst, 61 | uint256 wad 62 | ) public returns (bool) { 63 | require(balanceOf[src] >= wad); 64 | 65 | if (src != msg.sender && allowance[src][msg.sender] != uint256(-1)) { 66 | require(allowance[src][msg.sender] >= wad); 67 | allowance[src][msg.sender] -= wad; 68 | } 69 | 70 | balanceOf[src] -= wad; 71 | balanceOf[dst] += wad; 72 | 73 | Transfer(src, dst, wad); 74 | 75 | return true; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /contracts/helper/UniswapArbitrageur.sol: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2020 DODO ZOO. 4 | SPDX-License-Identifier: Apache-2.0 5 | 6 | */ 7 | 8 | pragma solidity 0.6.9; 9 | pragma experimental ABIEncoderV2; 10 | 11 | import {Ownable} from "../lib/Ownable.sol"; 12 | import {IDODO} from "../intf/IDODO.sol"; 13 | import {IERC20} from "../intf/IERC20.sol"; 14 | import {SafeERC20} from "../lib/SafeERC20.sol"; 15 | import {SafeMath} from "../lib/SafeMath.sol"; 16 | 17 | interface IUniswapV2Pair { 18 | function token0() external view returns (address); 19 | 20 | function token1() external view returns (address); 21 | 22 | function getReserves() 23 | external 24 | view 25 | returns ( 26 | uint112 reserve0, 27 | uint112 reserve1, 28 | uint32 blockTimestampLast 29 | ); 30 | 31 | function swap( 32 | uint256 amount0Out, 33 | uint256 amount1Out, 34 | address to, 35 | bytes calldata data 36 | ) external; 37 | } 38 | 39 | contract UniswapArbitrageur { 40 | using SafeMath for uint256; 41 | using SafeERC20 for IERC20; 42 | 43 | address public _UNISWAP_; 44 | address public _DODO_; 45 | address public _BASE_; 46 | address public _QUOTE_; 47 | 48 | bool public _REVERSE_; // true if dodo.baseToken=uniswap.token0 49 | 50 | constructor(address _uniswap, address _dodo) public { 51 | _UNISWAP_ = _uniswap; 52 | _DODO_ = _dodo; 53 | 54 | _BASE_ = IDODO(_DODO_)._BASE_TOKEN_(); 55 | _QUOTE_ = IDODO(_DODO_)._QUOTE_TOKEN_(); 56 | 57 | address token0 = IUniswapV2Pair(_UNISWAP_).token0(); 58 | address token1 = IUniswapV2Pair(_UNISWAP_).token1(); 59 | 60 | if (token0 == _BASE_ && token1 == _QUOTE_) { 61 | _REVERSE_ = false; 62 | } else if (token0 == _QUOTE_ && token1 == _BASE_) { 63 | _REVERSE_ = true; 64 | } else { 65 | require(true, "DODO_UNISWAP_NOT_MATCH"); 66 | } 67 | 68 | IERC20(_BASE_).approve(_DODO_, uint256(-1)); 69 | IERC20(_QUOTE_).approve(_DODO_, uint256(-1)); 70 | } 71 | 72 | function executeBuyArbitrage(uint256 baseAmount) external returns (uint256 quoteProfit) { 73 | IDODO(_DODO_).buyBaseToken(baseAmount, uint256(-1), "0xd"); 74 | quoteProfit = IERC20(_QUOTE_).balanceOf(address(this)); 75 | IERC20(_QUOTE_).transfer(msg.sender, quoteProfit); 76 | return quoteProfit; 77 | } 78 | 79 | function executeSellArbitrage(uint256 baseAmount) external returns (uint256 baseProfit) { 80 | IDODO(_DODO_).sellBaseToken(baseAmount, 0, "0xd"); 81 | baseProfit = IERC20(_BASE_).balanceOf(address(this)); 82 | IERC20(_BASE_).transfer(msg.sender, baseProfit); 83 | return baseProfit; 84 | } 85 | 86 | function dodoCall( 87 | bool isDODOBuy, 88 | uint256 baseAmount, 89 | uint256 quoteAmount, 90 | bytes calldata 91 | ) external { 92 | require(msg.sender == _DODO_, "WRONG_DODO"); 93 | if (_REVERSE_) { 94 | _inverseArbitrage(isDODOBuy, baseAmount, quoteAmount); 95 | } else { 96 | _arbitrage(isDODOBuy, baseAmount, quoteAmount); 97 | } 98 | } 99 | 100 | function _inverseArbitrage( 101 | bool isDODOBuy, 102 | uint256 baseAmount, 103 | uint256 quoteAmount 104 | ) internal { 105 | (uint112 _reserve0, uint112 _reserve1, ) = IUniswapV2Pair(_UNISWAP_).getReserves(); 106 | uint256 token0Balance = uint256(_reserve0); 107 | uint256 token1Balance = uint256(_reserve1); 108 | uint256 token0Amount; 109 | uint256 token1Amount; 110 | if (isDODOBuy) { 111 | IERC20(_BASE_).transfer(_UNISWAP_, baseAmount); 112 | // transfer token1 into uniswap 113 | uint256 newToken0Balance = token0Balance.mul(token1Balance).div( 114 | token1Balance.add(baseAmount) 115 | ); 116 | token0Amount = token0Balance.sub(newToken0Balance).mul(9969).div(10000); // mul 0.9969 117 | require(token0Amount > quoteAmount, "NOT_PROFITABLE"); 118 | IUniswapV2Pair(_UNISWAP_).swap(token0Amount, token1Amount, address(this), ""); 119 | } else { 120 | IERC20(_QUOTE_).transfer(_UNISWAP_, quoteAmount); 121 | // transfer token0 into uniswap 122 | uint256 newToken1Balance = token0Balance.mul(token1Balance).div( 123 | token0Balance.add(quoteAmount) 124 | ); 125 | token1Amount = token1Balance.sub(newToken1Balance).mul(9969).div(10000); // mul 0.9969 126 | require(token1Amount > baseAmount, "NOT_PROFITABLE"); 127 | IUniswapV2Pair(_UNISWAP_).swap(token0Amount, token1Amount, address(this), ""); 128 | } 129 | } 130 | 131 | function _arbitrage( 132 | bool isDODOBuy, 133 | uint256 baseAmount, 134 | uint256 quoteAmount 135 | ) internal { 136 | (uint112 _reserve0, uint112 _reserve1, ) = IUniswapV2Pair(_UNISWAP_).getReserves(); 137 | uint256 token0Balance = uint256(_reserve0); 138 | uint256 token1Balance = uint256(_reserve1); 139 | uint256 token0Amount; 140 | uint256 token1Amount; 141 | if (isDODOBuy) { 142 | IERC20(_BASE_).transfer(_UNISWAP_, baseAmount); 143 | // transfer token0 into uniswap 144 | uint256 newToken1Balance = token1Balance.mul(token0Balance).div( 145 | token0Balance.add(baseAmount) 146 | ); 147 | token1Amount = token1Balance.sub(newToken1Balance).mul(9969).div(10000); // mul 0.9969 148 | require(token1Amount > quoteAmount, "NOT_PROFITABLE"); 149 | IUniswapV2Pair(_UNISWAP_).swap(token0Amount, token1Amount, address(this), ""); 150 | } else { 151 | IERC20(_QUOTE_).transfer(_UNISWAP_, quoteAmount); 152 | // transfer token1 into uniswap 153 | uint256 newToken0Balance = token1Balance.mul(token0Balance).div( 154 | token1Balance.add(quoteAmount) 155 | ); 156 | token0Amount = token0Balance.sub(newToken0Balance).mul(9969).div(10000); // mul 0.9969 157 | require(token0Amount > baseAmount, "NOT_PROFITABLE"); 158 | IUniswapV2Pair(_UNISWAP_).swap(token0Amount, token1Amount, address(this), ""); 159 | } 160 | } 161 | 162 | function retrieve(address token, uint256 amount) external { 163 | IERC20(token).safeTransfer(msg.sender, amount); 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /contracts/impl/Admin.sol: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2020 DODO ZOO. 4 | SPDX-License-Identifier: Apache-2.0 5 | 6 | */ 7 | 8 | pragma solidity 0.6.9; 9 | pragma experimental ABIEncoderV2; 10 | 11 | import {Storage} from "./Storage.sol"; 12 | 13 | 14 | /** 15 | * @title Admin 16 | * @author DODO Breeder 17 | * 18 | * @notice Functions for admin operations 19 | */ 20 | contract Admin is Storage { 21 | // ============ Events ============ 22 | 23 | event UpdateGasPriceLimit(uint256 oldGasPriceLimit, uint256 newGasPriceLimit); 24 | 25 | event UpdateLiquidityProviderFeeRate( 26 | uint256 oldLiquidityProviderFeeRate, 27 | uint256 newLiquidityProviderFeeRate 28 | ); 29 | 30 | event UpdateMaintainerFeeRate(uint256 oldMaintainerFeeRate, uint256 newMaintainerFeeRate); 31 | 32 | event UpdateK(uint256 oldK, uint256 newK); 33 | 34 | // ============ Params Setting Functions ============ 35 | 36 | function setOracle(address newOracle) external onlyOwner { 37 | _ORACLE_ = newOracle; 38 | } 39 | 40 | function setSupervisor(address newSupervisor) external onlyOwner { 41 | _SUPERVISOR_ = newSupervisor; 42 | } 43 | 44 | function setMaintainer(address newMaintainer) external onlyOwner { 45 | _MAINTAINER_ = newMaintainer; 46 | } 47 | 48 | function setLiquidityProviderFeeRate(uint256 newLiquidityPorviderFeeRate) external onlyOwner { 49 | emit UpdateLiquidityProviderFeeRate(_LP_FEE_RATE_, newLiquidityPorviderFeeRate); 50 | _LP_FEE_RATE_ = newLiquidityPorviderFeeRate; 51 | _checkDODOParameters(); 52 | } 53 | 54 | function setMaintainerFeeRate(uint256 newMaintainerFeeRate) external onlyOwner { 55 | emit UpdateMaintainerFeeRate(_MT_FEE_RATE_, newMaintainerFeeRate); 56 | _MT_FEE_RATE_ = newMaintainerFeeRate; 57 | _checkDODOParameters(); 58 | } 59 | 60 | function setK(uint256 newK) external onlyOwner { 61 | emit UpdateK(_K_, newK); 62 | _K_ = newK; 63 | _checkDODOParameters(); 64 | } 65 | 66 | function setGasPriceLimit(uint256 newGasPriceLimit) external onlySupervisorOrOwner { 67 | emit UpdateGasPriceLimit(_GAS_PRICE_LIMIT_, newGasPriceLimit); 68 | _GAS_PRICE_LIMIT_ = newGasPriceLimit; 69 | } 70 | 71 | // ============ System Control Functions ============ 72 | 73 | function disableTrading() external onlySupervisorOrOwner { 74 | _TRADE_ALLOWED_ = false; 75 | } 76 | 77 | function enableTrading() external onlyOwner notClosed { 78 | _TRADE_ALLOWED_ = true; 79 | } 80 | 81 | function disableQuoteDeposit() external onlySupervisorOrOwner { 82 | _DEPOSIT_QUOTE_ALLOWED_ = false; 83 | } 84 | 85 | function enableQuoteDeposit() external onlyOwner notClosed { 86 | _DEPOSIT_QUOTE_ALLOWED_ = true; 87 | } 88 | 89 | function disableBaseDeposit() external onlySupervisorOrOwner { 90 | _DEPOSIT_BASE_ALLOWED_ = false; 91 | } 92 | 93 | function enableBaseDeposit() external onlyOwner notClosed { 94 | _DEPOSIT_BASE_ALLOWED_ = true; 95 | } 96 | 97 | // ============ Advanced Control Functions ============ 98 | 99 | function disableBuying() external onlySupervisorOrOwner { 100 | _BUYING_ALLOWED_ = false; 101 | } 102 | 103 | function enableBuying() external onlyOwner notClosed { 104 | _BUYING_ALLOWED_ = true; 105 | } 106 | 107 | function disableSelling() external onlySupervisorOrOwner { 108 | _SELLING_ALLOWED_ = false; 109 | } 110 | 111 | function enableSelling() external onlyOwner notClosed { 112 | _SELLING_ALLOWED_ = true; 113 | } 114 | 115 | function setBaseBalanceLimit(uint256 newBaseBalanceLimit) external onlyOwner notClosed { 116 | _BASE_BALANCE_LIMIT_ = newBaseBalanceLimit; 117 | } 118 | 119 | function setQuoteBalanceLimit(uint256 newQuoteBalanceLimit) external onlyOwner notClosed { 120 | _QUOTE_BALANCE_LIMIT_ = newQuoteBalanceLimit; 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /contracts/impl/DODOLpToken.sol: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2020 DODO ZOO. 4 | SPDX-License-Identifier: Apache-2.0 5 | 6 | */ 7 | 8 | pragma solidity 0.6.9; 9 | pragma experimental ABIEncoderV2; 10 | 11 | import {IERC20} from "../intf/IERC20.sol"; 12 | import {SafeMath} from "../lib/SafeMath.sol"; 13 | import {Ownable} from "../lib/Ownable.sol"; 14 | 15 | /** 16 | * @title DODOLpToken 17 | * @author DODO Breeder 18 | * 19 | * @notice Tokenize liquidity pool assets. An ordinary ERC20 contract with mint and burn functions 20 | */ 21 | contract DODOLpToken is Ownable { 22 | using SafeMath for uint256; 23 | 24 | string public symbol = "DLP"; 25 | address public originToken; 26 | 27 | uint256 public totalSupply; 28 | mapping(address => uint256) internal balances; 29 | mapping(address => mapping(address => uint256)) internal allowed; 30 | 31 | // ============ Events ============ 32 | 33 | event Transfer(address indexed from, address indexed to, uint256 amount); 34 | 35 | event Approval(address indexed owner, address indexed spender, uint256 amount); 36 | 37 | event Mint(address indexed user, uint256 value); 38 | 39 | event Burn(address indexed user, uint256 value); 40 | 41 | // ============ Functions ============ 42 | 43 | constructor(address _originToken) public { 44 | originToken = _originToken; 45 | } 46 | 47 | function name() public view returns (string memory) { 48 | string memory lpTokenSuffix = "_DODO_LP_TOKEN_"; 49 | return string(abi.encodePacked(IERC20(originToken).name(), lpTokenSuffix)); 50 | } 51 | 52 | function decimals() public view returns (uint8) { 53 | return IERC20(originToken).decimals(); 54 | } 55 | 56 | /** 57 | * @dev transfer token for a specified address 58 | * @param to The address to transfer to. 59 | * @param amount The amount to be transferred. 60 | */ 61 | function transfer(address to, uint256 amount) public returns (bool) { 62 | require(amount <= balances[msg.sender], "BALANCE_NOT_ENOUGH"); 63 | 64 | balances[msg.sender] = balances[msg.sender].sub(amount); 65 | balances[to] = balances[to].add(amount); 66 | emit Transfer(msg.sender, to, amount); 67 | return true; 68 | } 69 | 70 | /** 71 | * @dev Gets the balance of the specified address. 72 | * @param owner The address to query the the balance of. 73 | * @return balance An uint256 representing the amount owned by the passed address. 74 | */ 75 | function balanceOf(address owner) external view returns (uint256 balance) { 76 | return balances[owner]; 77 | } 78 | 79 | /** 80 | * @dev Transfer tokens from one address to another 81 | * @param from address The address which you want to send tokens from 82 | * @param to address The address which you want to transfer to 83 | * @param amount uint256 the amount of tokens to be transferred 84 | */ 85 | function transferFrom( 86 | address from, 87 | address to, 88 | uint256 amount 89 | ) public returns (bool) { 90 | require(amount <= balances[from], "BALANCE_NOT_ENOUGH"); 91 | require(amount <= allowed[from][msg.sender], "ALLOWANCE_NOT_ENOUGH"); 92 | 93 | balances[from] = balances[from].sub(amount); 94 | balances[to] = balances[to].add(amount); 95 | allowed[from][msg.sender] = allowed[from][msg.sender].sub(amount); 96 | emit Transfer(from, to, amount); 97 | return true; 98 | } 99 | 100 | /** 101 | * @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender. 102 | * @param spender The address which will spend the funds. 103 | * @param amount The amount of tokens to be spent. 104 | */ 105 | function approve(address spender, uint256 amount) public returns (bool) { 106 | allowed[msg.sender][spender] = amount; 107 | emit Approval(msg.sender, spender, amount); 108 | return true; 109 | } 110 | 111 | /** 112 | * @dev Function to check the amount of tokens that an owner allowed to a spender. 113 | * @param owner address The address which owns the funds. 114 | * @param spender address The address which will spend the funds. 115 | * @return A uint256 specifying the amount of tokens still available for the spender. 116 | */ 117 | function allowance(address owner, address spender) public view returns (uint256) { 118 | return allowed[owner][spender]; 119 | } 120 | 121 | function mint(address user, uint256 value) external onlyOwner { 122 | balances[user] = balances[user].add(value); 123 | totalSupply = totalSupply.add(value); 124 | emit Mint(user, value); 125 | emit Transfer(address(0), user, value); 126 | } 127 | 128 | function burn(address user, uint256 value) external onlyOwner { 129 | balances[user] = balances[user].sub(value); 130 | totalSupply = totalSupply.sub(value); 131 | emit Burn(user, value); 132 | emit Transfer(user, address(0), value); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /contracts/impl/Pricing.sol: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2020 DODO ZOO. 4 | SPDX-License-Identifier: Apache-2.0 5 | 6 | */ 7 | 8 | pragma solidity 0.6.9; 9 | pragma experimental ABIEncoderV2; 10 | 11 | import {SafeMath} from "../lib/SafeMath.sol"; 12 | import {DecimalMath} from "../lib/DecimalMath.sol"; 13 | import {DODOMath} from "../lib/DODOMath.sol"; 14 | import {Types} from "../lib/Types.sol"; 15 | import {Storage} from "./Storage.sol"; 16 | 17 | 18 | /** 19 | * @title Pricing 20 | * @author DODO Breeder 21 | * 22 | * @notice DODO Pricing model 23 | */ 24 | contract Pricing is Storage { 25 | using SafeMath for uint256; 26 | 27 | // ============ R = 1 cases ============ 28 | 29 | function _ROneSellBaseToken(uint256 amount, uint256 targetQuoteTokenAmount) 30 | internal 31 | view 32 | returns (uint256 receiveQuoteToken) 33 | { 34 | uint256 i = getOraclePrice(); 35 | uint256 Q2 = DODOMath._SolveQuadraticFunctionForTrade( 36 | targetQuoteTokenAmount, 37 | targetQuoteTokenAmount, 38 | DecimalMath.mul(i, amount), 39 | false, 40 | _K_ 41 | ); 42 | // in theory Q2 <= targetQuoteTokenAmount 43 | // however when amount is close to 0, precision problems may cause Q2 > targetQuoteTokenAmount 44 | return targetQuoteTokenAmount.sub(Q2); 45 | } 46 | 47 | function _ROneBuyBaseToken(uint256 amount, uint256 targetBaseTokenAmount) 48 | internal 49 | view 50 | returns (uint256 payQuoteToken) 51 | { 52 | require(amount < targetBaseTokenAmount, "DODO_BASE_BALANCE_NOT_ENOUGH"); 53 | uint256 B2 = targetBaseTokenAmount.sub(amount); 54 | payQuoteToken = _RAboveIntegrate(targetBaseTokenAmount, targetBaseTokenAmount, B2); 55 | return payQuoteToken; 56 | } 57 | 58 | // ============ R < 1 cases ============ 59 | 60 | function _RBelowSellBaseToken( 61 | uint256 amount, 62 | uint256 quoteBalance, 63 | uint256 targetQuoteAmount 64 | ) internal view returns (uint256 receieQuoteToken) { 65 | uint256 i = getOraclePrice(); 66 | uint256 Q2 = DODOMath._SolveQuadraticFunctionForTrade( 67 | targetQuoteAmount, 68 | quoteBalance, 69 | DecimalMath.mul(i, amount), 70 | false, 71 | _K_ 72 | ); 73 | return quoteBalance.sub(Q2); 74 | } 75 | 76 | function _RBelowBuyBaseToken( 77 | uint256 amount, 78 | uint256 quoteBalance, 79 | uint256 targetQuoteAmount 80 | ) internal view returns (uint256 payQuoteToken) { 81 | // Here we don't require amount less than some value 82 | // Because it is limited at upper function 83 | // See Trader.queryBuyBaseToken 84 | uint256 i = getOraclePrice(); 85 | uint256 Q2 = DODOMath._SolveQuadraticFunctionForTrade( 86 | targetQuoteAmount, 87 | quoteBalance, 88 | DecimalMath.mulCeil(i, amount), 89 | true, 90 | _K_ 91 | ); 92 | return Q2.sub(quoteBalance); 93 | } 94 | 95 | function _RBelowBackToOne() internal view returns (uint256 payQuoteToken) { 96 | // important: carefully design the system to make sure spareBase always greater than or equal to 0 97 | uint256 spareBase = _BASE_BALANCE_.sub(_TARGET_BASE_TOKEN_AMOUNT_); 98 | uint256 price = getOraclePrice(); 99 | uint256 fairAmount = DecimalMath.mul(spareBase, price); 100 | uint256 newTargetQuote = DODOMath._SolveQuadraticFunctionForTarget( 101 | _QUOTE_BALANCE_, 102 | _K_, 103 | fairAmount 104 | ); 105 | return newTargetQuote.sub(_QUOTE_BALANCE_); 106 | } 107 | 108 | // ============ R > 1 cases ============ 109 | 110 | function _RAboveBuyBaseToken( 111 | uint256 amount, 112 | uint256 baseBalance, 113 | uint256 targetBaseAmount 114 | ) internal view returns (uint256 payQuoteToken) { 115 | require(amount < baseBalance, "DODO_BASE_BALANCE_NOT_ENOUGH"); 116 | uint256 B2 = baseBalance.sub(amount); 117 | return _RAboveIntegrate(targetBaseAmount, baseBalance, B2); 118 | } 119 | 120 | function _RAboveSellBaseToken( 121 | uint256 amount, 122 | uint256 baseBalance, 123 | uint256 targetBaseAmount 124 | ) internal view returns (uint256 receiveQuoteToken) { 125 | // here we don't require B1 <= targetBaseAmount 126 | // Because it is limited at upper function 127 | // See Trader.querySellBaseToken 128 | uint256 B1 = baseBalance.add(amount); 129 | return _RAboveIntegrate(targetBaseAmount, B1, baseBalance); 130 | } 131 | 132 | function _RAboveBackToOne() internal view returns (uint256 payBaseToken) { 133 | // important: carefully design the system to make sure spareBase always greater than or equal to 0 134 | uint256 spareQuote = _QUOTE_BALANCE_.sub(_TARGET_QUOTE_TOKEN_AMOUNT_); 135 | uint256 price = getOraclePrice(); 136 | uint256 fairAmount = DecimalMath.divFloor(spareQuote, price); 137 | uint256 newTargetBase = DODOMath._SolveQuadraticFunctionForTarget( 138 | _BASE_BALANCE_, 139 | _K_, 140 | fairAmount 141 | ); 142 | return newTargetBase.sub(_BASE_BALANCE_); 143 | } 144 | 145 | // ============ Helper functions ============ 146 | 147 | function getExpectedTarget() public view returns (uint256 baseTarget, uint256 quoteTarget) { 148 | uint256 Q = _QUOTE_BALANCE_; 149 | uint256 B = _BASE_BALANCE_; 150 | if (_R_STATUS_ == Types.RStatus.ONE) { 151 | return (_TARGET_BASE_TOKEN_AMOUNT_, _TARGET_QUOTE_TOKEN_AMOUNT_); 152 | } else if (_R_STATUS_ == Types.RStatus.BELOW_ONE) { 153 | uint256 payQuoteToken = _RBelowBackToOne(); 154 | return (_TARGET_BASE_TOKEN_AMOUNT_, Q.add(payQuoteToken)); 155 | } else if (_R_STATUS_ == Types.RStatus.ABOVE_ONE) { 156 | uint256 payBaseToken = _RAboveBackToOne(); 157 | return (B.add(payBaseToken), _TARGET_QUOTE_TOKEN_AMOUNT_); 158 | } 159 | } 160 | 161 | function getMidPrice() public view returns (uint256 midPrice) { 162 | (uint256 baseTarget, uint256 quoteTarget) = getExpectedTarget(); 163 | if (_R_STATUS_ == Types.RStatus.BELOW_ONE) { 164 | uint256 R = DecimalMath.divFloor( 165 | quoteTarget.mul(quoteTarget).div(_QUOTE_BALANCE_), 166 | _QUOTE_BALANCE_ 167 | ); 168 | R = DecimalMath.ONE.sub(_K_).add(DecimalMath.mul(_K_, R)); 169 | return DecimalMath.divFloor(getOraclePrice(), R); 170 | } else { 171 | uint256 R = DecimalMath.divFloor( 172 | baseTarget.mul(baseTarget).div(_BASE_BALANCE_), 173 | _BASE_BALANCE_ 174 | ); 175 | R = DecimalMath.ONE.sub(_K_).add(DecimalMath.mul(_K_, R)); 176 | return DecimalMath.mul(getOraclePrice(), R); 177 | } 178 | } 179 | 180 | function _RAboveIntegrate( 181 | uint256 B0, 182 | uint256 B1, 183 | uint256 B2 184 | ) internal view returns (uint256) { 185 | uint256 i = getOraclePrice(); 186 | return DODOMath._GeneralIntegrate(B0, B1, B2, i, _K_); 187 | } 188 | 189 | // function _RBelowIntegrate( 190 | // uint256 Q0, 191 | // uint256 Q1, 192 | // uint256 Q2 193 | // ) internal view returns (uint256) { 194 | // uint256 i = getOraclePrice(); 195 | // i = DecimalMath.divFloor(DecimalMath.ONE, i); // 1/i 196 | // return DODOMath._GeneralIntegrate(Q0, Q1, Q2, i, _K_); 197 | // } 198 | } 199 | -------------------------------------------------------------------------------- /contracts/impl/Settlement.sol: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2020 DODO ZOO. 4 | SPDX-License-Identifier: Apache-2.0 5 | 6 | */ 7 | 8 | pragma solidity 0.6.9; 9 | pragma experimental ABIEncoderV2; 10 | 11 | import {SafeMath} from "../lib/SafeMath.sol"; 12 | import {SafeERC20} from "../lib/SafeERC20.sol"; 13 | import {DecimalMath} from "../lib/DecimalMath.sol"; 14 | import {Types} from "../lib/Types.sol"; 15 | import {IDODOLpToken} from "../intf/IDODOLpToken.sol"; 16 | import {IERC20} from "../intf/IERC20.sol"; 17 | import {Storage} from "./Storage.sol"; 18 | 19 | 20 | /** 21 | * @title Settlement 22 | * @author DODO Breeder 23 | * 24 | * @notice Functions for assets settlement 25 | */ 26 | contract Settlement is Storage { 27 | using SafeMath for uint256; 28 | using SafeERC20 for IERC20; 29 | 30 | // ============ Events ============ 31 | 32 | event Donate(uint256 amount, bool isBaseToken); 33 | 34 | event ClaimAssets(address indexed user, uint256 baseTokenAmount, uint256 quoteTokenAmount); 35 | 36 | // ============ Assets IN/OUT Functions ============ 37 | 38 | function _baseTokenTransferIn(address from, uint256 amount) internal { 39 | require(_BASE_BALANCE_.add(amount) <= _BASE_BALANCE_LIMIT_, "BASE_BALANCE_LIMIT_EXCEEDED"); 40 | IERC20(_BASE_TOKEN_).safeTransferFrom(from, address(this), amount); 41 | _BASE_BALANCE_ = _BASE_BALANCE_.add(amount); 42 | } 43 | 44 | function _quoteTokenTransferIn(address from, uint256 amount) internal { 45 | require( 46 | _QUOTE_BALANCE_.add(amount) <= _QUOTE_BALANCE_LIMIT_, 47 | "QUOTE_BALANCE_LIMIT_EXCEEDED" 48 | ); 49 | IERC20(_QUOTE_TOKEN_).safeTransferFrom(from, address(this), amount); 50 | _QUOTE_BALANCE_ = _QUOTE_BALANCE_.add(amount); 51 | } 52 | 53 | function _baseTokenTransferOut(address to, uint256 amount) internal { 54 | IERC20(_BASE_TOKEN_).safeTransfer(to, amount); 55 | _BASE_BALANCE_ = _BASE_BALANCE_.sub(amount); 56 | } 57 | 58 | function _quoteTokenTransferOut(address to, uint256 amount) internal { 59 | IERC20(_QUOTE_TOKEN_).safeTransfer(to, amount); 60 | _QUOTE_BALANCE_ = _QUOTE_BALANCE_.sub(amount); 61 | } 62 | 63 | // ============ Donate to Liquidity Pool Functions ============ 64 | 65 | function _donateBaseToken(uint256 amount) internal { 66 | _TARGET_BASE_TOKEN_AMOUNT_ = _TARGET_BASE_TOKEN_AMOUNT_.add(amount); 67 | emit Donate(amount, true); 68 | } 69 | 70 | function _donateQuoteToken(uint256 amount) internal { 71 | _TARGET_QUOTE_TOKEN_AMOUNT_ = _TARGET_QUOTE_TOKEN_AMOUNT_.add(amount); 72 | emit Donate(amount, false); 73 | } 74 | 75 | function donateBaseToken(uint256 amount) external preventReentrant { 76 | _baseTokenTransferIn(msg.sender, amount); 77 | _donateBaseToken(amount); 78 | } 79 | 80 | function donateQuoteToken(uint256 amount) external preventReentrant { 81 | _quoteTokenTransferIn(msg.sender, amount); 82 | _donateQuoteToken(amount); 83 | } 84 | 85 | // ============ Final Settlement Functions ============ 86 | 87 | // last step to shut down dodo 88 | function finalSettlement() external onlyOwner notClosed { 89 | _CLOSED_ = true; 90 | _DEPOSIT_QUOTE_ALLOWED_ = false; 91 | _DEPOSIT_BASE_ALLOWED_ = false; 92 | _TRADE_ALLOWED_ = false; 93 | uint256 totalBaseCapital = getTotalBaseCapital(); 94 | uint256 totalQuoteCapital = getTotalQuoteCapital(); 95 | 96 | if (_QUOTE_BALANCE_ > _TARGET_QUOTE_TOKEN_AMOUNT_) { 97 | uint256 spareQuote = _QUOTE_BALANCE_.sub(_TARGET_QUOTE_TOKEN_AMOUNT_); 98 | _BASE_CAPITAL_RECEIVE_QUOTE_ = DecimalMath.divFloor(spareQuote, totalBaseCapital); 99 | } else { 100 | _TARGET_QUOTE_TOKEN_AMOUNT_ = _QUOTE_BALANCE_; 101 | } 102 | 103 | if (_BASE_BALANCE_ > _TARGET_BASE_TOKEN_AMOUNT_) { 104 | uint256 spareBase = _BASE_BALANCE_.sub(_TARGET_BASE_TOKEN_AMOUNT_); 105 | _QUOTE_CAPITAL_RECEIVE_BASE_ = DecimalMath.divFloor(spareBase, totalQuoteCapital); 106 | } else { 107 | _TARGET_BASE_TOKEN_AMOUNT_ = _BASE_BALANCE_; 108 | } 109 | 110 | _R_STATUS_ = Types.RStatus.ONE; 111 | } 112 | 113 | // claim remaining assets after final settlement 114 | function claimAssets() external preventReentrant { 115 | require(_CLOSED_, "DODO_NOT_CLOSED"); 116 | require(!_CLAIMED_[msg.sender], "ALREADY_CLAIMED"); 117 | _CLAIMED_[msg.sender] = true; 118 | 119 | uint256 quoteCapital = getQuoteCapitalBalanceOf(msg.sender); 120 | uint256 baseCapital = getBaseCapitalBalanceOf(msg.sender); 121 | 122 | uint256 quoteAmount = 0; 123 | if (quoteCapital > 0) { 124 | quoteAmount = _TARGET_QUOTE_TOKEN_AMOUNT_.mul(quoteCapital).div(getTotalQuoteCapital()); 125 | } 126 | uint256 baseAmount = 0; 127 | if (baseCapital > 0) { 128 | baseAmount = _TARGET_BASE_TOKEN_AMOUNT_.mul(baseCapital).div(getTotalBaseCapital()); 129 | } 130 | 131 | _TARGET_QUOTE_TOKEN_AMOUNT_ = _TARGET_QUOTE_TOKEN_AMOUNT_.sub(quoteAmount); 132 | _TARGET_BASE_TOKEN_AMOUNT_ = _TARGET_BASE_TOKEN_AMOUNT_.sub(baseAmount); 133 | 134 | quoteAmount = quoteAmount.add(DecimalMath.mul(baseCapital, _BASE_CAPITAL_RECEIVE_QUOTE_)); 135 | baseAmount = baseAmount.add(DecimalMath.mul(quoteCapital, _QUOTE_CAPITAL_RECEIVE_BASE_)); 136 | 137 | _baseTokenTransferOut(msg.sender, baseAmount); 138 | _quoteTokenTransferOut(msg.sender, quoteAmount); 139 | 140 | IDODOLpToken(_BASE_CAPITAL_TOKEN_).burn(msg.sender, baseCapital); 141 | IDODOLpToken(_QUOTE_CAPITAL_TOKEN_).burn(msg.sender, quoteCapital); 142 | 143 | emit ClaimAssets(msg.sender, baseAmount, quoteAmount); 144 | return; 145 | } 146 | 147 | // in case someone transfer to contract directly 148 | function retrieve(address token, uint256 amount) external onlyOwner { 149 | if (token == _BASE_TOKEN_) { 150 | require( 151 | IERC20(_BASE_TOKEN_).balanceOf(address(this)) >= _BASE_BALANCE_.add(amount), 152 | "DODO_BASE_BALANCE_NOT_ENOUGH" 153 | ); 154 | } 155 | if (token == _QUOTE_TOKEN_) { 156 | require( 157 | IERC20(_QUOTE_TOKEN_).balanceOf(address(this)) >= _QUOTE_BALANCE_.add(amount), 158 | "DODO_QUOTE_BALANCE_NOT_ENOUGH" 159 | ); 160 | } 161 | IERC20(token).safeTransfer(msg.sender, amount); 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /contracts/impl/Storage.sol: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2020 DODO ZOO. 4 | SPDX-License-Identifier: Apache-2.0 5 | 6 | */ 7 | 8 | pragma solidity 0.6.9; 9 | pragma experimental ABIEncoderV2; 10 | 11 | import {InitializableOwnable} from "../lib/InitializableOwnable.sol"; 12 | import {SafeMath} from "../lib/SafeMath.sol"; 13 | import {DecimalMath} from "../lib/DecimalMath.sol"; 14 | import {ReentrancyGuard} from "../lib/ReentrancyGuard.sol"; 15 | import {IOracle} from "../intf/IOracle.sol"; 16 | import {IDODOLpToken} from "../intf/IDODOLpToken.sol"; 17 | import {Types} from "../lib/Types.sol"; 18 | 19 | 20 | /** 21 | * @title Storage 22 | * @author DODO Breeder 23 | * 24 | * @notice Local Variables 25 | */ 26 | contract Storage is InitializableOwnable, ReentrancyGuard { 27 | using SafeMath for uint256; 28 | 29 | // ============ Variables for Control ============ 30 | 31 | bool internal _INITIALIZED_; 32 | bool public _CLOSED_; 33 | bool public _DEPOSIT_QUOTE_ALLOWED_; 34 | bool public _DEPOSIT_BASE_ALLOWED_; 35 | bool public _TRADE_ALLOWED_; 36 | uint256 public _GAS_PRICE_LIMIT_; 37 | 38 | // ============ Advanced Controls ============ 39 | bool public _BUYING_ALLOWED_; 40 | bool public _SELLING_ALLOWED_; 41 | uint256 public _BASE_BALANCE_LIMIT_; 42 | uint256 public _QUOTE_BALANCE_LIMIT_; 43 | 44 | // ============ Core Address ============ 45 | 46 | address public _SUPERVISOR_; // could freeze system in emergency 47 | address public _MAINTAINER_; // collect maintainer fee to buy food for DODO 48 | 49 | address public _BASE_TOKEN_; 50 | address public _QUOTE_TOKEN_; 51 | address public _ORACLE_; 52 | 53 | // ============ Variables for PMM Algorithm ============ 54 | 55 | uint256 public _LP_FEE_RATE_; 56 | uint256 public _MT_FEE_RATE_; 57 | uint256 public _K_; 58 | 59 | Types.RStatus public _R_STATUS_; 60 | uint256 public _TARGET_BASE_TOKEN_AMOUNT_; 61 | uint256 public _TARGET_QUOTE_TOKEN_AMOUNT_; 62 | uint256 public _BASE_BALANCE_; 63 | uint256 public _QUOTE_BALANCE_; 64 | 65 | address public _BASE_CAPITAL_TOKEN_; 66 | address public _QUOTE_CAPITAL_TOKEN_; 67 | 68 | // ============ Variables for Final Settlement ============ 69 | 70 | uint256 public _BASE_CAPITAL_RECEIVE_QUOTE_; 71 | uint256 public _QUOTE_CAPITAL_RECEIVE_BASE_; 72 | mapping(address => bool) public _CLAIMED_; 73 | 74 | // ============ Modifiers ============ 75 | 76 | modifier onlySupervisorOrOwner() { 77 | require(msg.sender == _SUPERVISOR_ || msg.sender == _OWNER_, "NOT_SUPERVISOR_OR_OWNER"); 78 | _; 79 | } 80 | 81 | modifier notClosed() { 82 | require(!_CLOSED_, "DODO_CLOSED"); 83 | _; 84 | } 85 | 86 | // ============ Helper Functions ============ 87 | 88 | function _checkDODOParameters() internal view returns (uint256) { 89 | require(_K_ < DecimalMath.ONE, "K>=1"); 90 | require(_K_ > 0, "K=0"); 91 | require(_LP_FEE_RATE_.add(_MT_FEE_RATE_) < DecimalMath.ONE, "FEE_RATE>=1"); 92 | } 93 | 94 | function getOraclePrice() public view returns (uint256) { 95 | return IOracle(_ORACLE_).getPrice(); 96 | } 97 | 98 | function getBaseCapitalBalanceOf(address lp) public view returns (uint256) { 99 | return IDODOLpToken(_BASE_CAPITAL_TOKEN_).balanceOf(lp); 100 | } 101 | 102 | function getTotalBaseCapital() public view returns (uint256) { 103 | return IDODOLpToken(_BASE_CAPITAL_TOKEN_).totalSupply(); 104 | } 105 | 106 | function getQuoteCapitalBalanceOf(address lp) public view returns (uint256) { 107 | return IDODOLpToken(_QUOTE_CAPITAL_TOKEN_).balanceOf(lp); 108 | } 109 | 110 | function getTotalQuoteCapital() public view returns (uint256) { 111 | return IDODOLpToken(_QUOTE_CAPITAL_TOKEN_).totalSupply(); 112 | } 113 | 114 | // ============ Version Control ============ 115 | function version() external pure returns (uint256) { 116 | return 101; // 1.0.1 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /contracts/impl/Trader.sol: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2020 DODO ZOO. 4 | SPDX-License-Identifier: Apache-2.0 5 | 6 | */ 7 | 8 | pragma solidity 0.6.9; 9 | pragma experimental ABIEncoderV2; 10 | 11 | import {SafeMath} from "../lib/SafeMath.sol"; 12 | import {DecimalMath} from "../lib/DecimalMath.sol"; 13 | import {Types} from "../lib/Types.sol"; 14 | import {IDODOCallee} from "../intf/IDODOCallee.sol"; 15 | import {Storage} from "./Storage.sol"; 16 | import {Pricing} from "./Pricing.sol"; 17 | import {Settlement} from "./Settlement.sol"; 18 | 19 | 20 | /** 21 | * @title Trader 22 | * @author DODO Breeder 23 | * 24 | * @notice Functions for trader operations 25 | */ 26 | contract Trader is Storage, Pricing, Settlement { 27 | using SafeMath for uint256; 28 | 29 | // ============ Events ============ 30 | 31 | event SellBaseToken(address indexed seller, uint256 payBase, uint256 receiveQuote); 32 | 33 | event BuyBaseToken(address indexed buyer, uint256 receiveBase, uint256 payQuote); 34 | 35 | event ChargeMaintainerFee(address indexed maintainer, bool isBaseToken, uint256 amount); 36 | 37 | // ============ Modifiers ============ 38 | 39 | modifier tradeAllowed() { 40 | require(_TRADE_ALLOWED_, "TRADE_NOT_ALLOWED"); 41 | _; 42 | } 43 | 44 | modifier buyingAllowed() { 45 | require(_BUYING_ALLOWED_, "BUYING_NOT_ALLOWED"); 46 | _; 47 | } 48 | 49 | modifier sellingAllowed() { 50 | require(_SELLING_ALLOWED_, "SELLING_NOT_ALLOWED"); 51 | _; 52 | } 53 | 54 | modifier gasPriceLimit() { 55 | require(tx.gasprice <= _GAS_PRICE_LIMIT_, "GAS_PRICE_EXCEED"); 56 | _; 57 | } 58 | 59 | // ============ Trade Functions ============ 60 | 61 | function sellBaseToken( 62 | uint256 amount, 63 | uint256 minReceiveQuote, 64 | bytes calldata data 65 | ) external tradeAllowed sellingAllowed gasPriceLimit preventReentrant returns (uint256) { 66 | // query price 67 | ( 68 | uint256 receiveQuote, 69 | uint256 lpFeeQuote, 70 | uint256 mtFeeQuote, 71 | Types.RStatus newRStatus, 72 | uint256 newQuoteTarget, 73 | uint256 newBaseTarget 74 | ) = _querySellBaseToken(amount); 75 | require(receiveQuote >= minReceiveQuote, "SELL_BASE_RECEIVE_NOT_ENOUGH"); 76 | 77 | // settle assets 78 | _quoteTokenTransferOut(msg.sender, receiveQuote); 79 | if (data.length > 0) { 80 | IDODOCallee(msg.sender).dodoCall(false, amount, receiveQuote, data); 81 | } 82 | _baseTokenTransferIn(msg.sender, amount); 83 | if (mtFeeQuote != 0) { 84 | _quoteTokenTransferOut(_MAINTAINER_, mtFeeQuote); 85 | emit ChargeMaintainerFee(_MAINTAINER_, false, mtFeeQuote); 86 | } 87 | 88 | // update TARGET 89 | if (_TARGET_QUOTE_TOKEN_AMOUNT_ != newQuoteTarget) { 90 | _TARGET_QUOTE_TOKEN_AMOUNT_ = newQuoteTarget; 91 | } 92 | if (_TARGET_BASE_TOKEN_AMOUNT_ != newBaseTarget) { 93 | _TARGET_BASE_TOKEN_AMOUNT_ = newBaseTarget; 94 | } 95 | if (_R_STATUS_ != newRStatus) { 96 | _R_STATUS_ = newRStatus; 97 | } 98 | 99 | _donateQuoteToken(lpFeeQuote); 100 | emit SellBaseToken(msg.sender, amount, receiveQuote); 101 | 102 | return receiveQuote; 103 | } 104 | 105 | function buyBaseToken( 106 | uint256 amount, 107 | uint256 maxPayQuote, 108 | bytes calldata data 109 | ) external tradeAllowed buyingAllowed gasPriceLimit preventReentrant returns (uint256) { 110 | // query price 111 | ( 112 | uint256 payQuote, 113 | uint256 lpFeeBase, 114 | uint256 mtFeeBase, 115 | Types.RStatus newRStatus, 116 | uint256 newQuoteTarget, 117 | uint256 newBaseTarget 118 | ) = _queryBuyBaseToken(amount); 119 | require(payQuote <= maxPayQuote, "BUY_BASE_COST_TOO_MUCH"); 120 | 121 | // settle assets 122 | _baseTokenTransferOut(msg.sender, amount); 123 | if (data.length > 0) { 124 | IDODOCallee(msg.sender).dodoCall(true, amount, payQuote, data); 125 | } 126 | _quoteTokenTransferIn(msg.sender, payQuote); 127 | if (mtFeeBase != 0) { 128 | _baseTokenTransferOut(_MAINTAINER_, mtFeeBase); 129 | emit ChargeMaintainerFee(_MAINTAINER_, true, mtFeeBase); 130 | } 131 | 132 | // update TARGET 133 | if (_TARGET_QUOTE_TOKEN_AMOUNT_ != newQuoteTarget) { 134 | _TARGET_QUOTE_TOKEN_AMOUNT_ = newQuoteTarget; 135 | } 136 | if (_TARGET_BASE_TOKEN_AMOUNT_ != newBaseTarget) { 137 | _TARGET_BASE_TOKEN_AMOUNT_ = newBaseTarget; 138 | } 139 | if (_R_STATUS_ != newRStatus) { 140 | _R_STATUS_ = newRStatus; 141 | } 142 | 143 | _donateBaseToken(lpFeeBase); 144 | emit BuyBaseToken(msg.sender, amount, payQuote); 145 | 146 | return payQuote; 147 | } 148 | 149 | // ============ Query Functions ============ 150 | 151 | function querySellBaseToken(uint256 amount) external view returns (uint256 receiveQuote) { 152 | (receiveQuote, , , , , ) = _querySellBaseToken(amount); 153 | return receiveQuote; 154 | } 155 | 156 | function queryBuyBaseToken(uint256 amount) external view returns (uint256 payQuote) { 157 | (payQuote, , , , , ) = _queryBuyBaseToken(amount); 158 | return payQuote; 159 | } 160 | 161 | function _querySellBaseToken(uint256 amount) 162 | internal 163 | view 164 | returns ( 165 | uint256 receiveQuote, 166 | uint256 lpFeeQuote, 167 | uint256 mtFeeQuote, 168 | Types.RStatus newRStatus, 169 | uint256 newQuoteTarget, 170 | uint256 newBaseTarget 171 | ) 172 | { 173 | (newBaseTarget, newQuoteTarget) = getExpectedTarget(); 174 | 175 | uint256 sellBaseAmount = amount; 176 | 177 | if (_R_STATUS_ == Types.RStatus.ONE) { 178 | // case 1: R=1 179 | // R falls below one 180 | receiveQuote = _ROneSellBaseToken(sellBaseAmount, newQuoteTarget); 181 | newRStatus = Types.RStatus.BELOW_ONE; 182 | } else if (_R_STATUS_ == Types.RStatus.ABOVE_ONE) { 183 | uint256 backToOnePayBase = newBaseTarget.sub(_BASE_BALANCE_); 184 | uint256 backToOneReceiveQuote = _QUOTE_BALANCE_.sub(newQuoteTarget); 185 | // case 2: R>1 186 | // complex case, R status depends on trading amount 187 | if (sellBaseAmount < backToOnePayBase) { 188 | // case 2.1: R status do not change 189 | receiveQuote = _RAboveSellBaseToken(sellBaseAmount, _BASE_BALANCE_, newBaseTarget); 190 | newRStatus = Types.RStatus.ABOVE_ONE; 191 | if (receiveQuote > backToOneReceiveQuote) { 192 | // [Important corner case!] may enter this branch when some precision problem happens. And consequently contribute to negative spare quote amount 193 | // to make sure spare quote>=0, mannually set receiveQuote=backToOneReceiveQuote 194 | receiveQuote = backToOneReceiveQuote; 195 | } 196 | } else if (sellBaseAmount == backToOnePayBase) { 197 | // case 2.2: R status changes to ONE 198 | receiveQuote = backToOneReceiveQuote; 199 | newRStatus = Types.RStatus.ONE; 200 | } else { 201 | // case 2.3: R status changes to BELOW_ONE 202 | receiveQuote = backToOneReceiveQuote.add( 203 | _ROneSellBaseToken(sellBaseAmount.sub(backToOnePayBase), newQuoteTarget) 204 | ); 205 | newRStatus = Types.RStatus.BELOW_ONE; 206 | } 207 | } else { 208 | // _R_STATUS_ == Types.RStatus.BELOW_ONE 209 | // case 3: R<1 210 | receiveQuote = _RBelowSellBaseToken(sellBaseAmount, _QUOTE_BALANCE_, newQuoteTarget); 211 | newRStatus = Types.RStatus.BELOW_ONE; 212 | } 213 | 214 | // count fees 215 | lpFeeQuote = DecimalMath.mul(receiveQuote, _LP_FEE_RATE_); 216 | mtFeeQuote = DecimalMath.mul(receiveQuote, _MT_FEE_RATE_); 217 | receiveQuote = receiveQuote.sub(lpFeeQuote).sub(mtFeeQuote); 218 | 219 | return (receiveQuote, lpFeeQuote, mtFeeQuote, newRStatus, newQuoteTarget, newBaseTarget); 220 | } 221 | 222 | function _queryBuyBaseToken(uint256 amount) 223 | internal 224 | view 225 | returns ( 226 | uint256 payQuote, 227 | uint256 lpFeeBase, 228 | uint256 mtFeeBase, 229 | Types.RStatus newRStatus, 230 | uint256 newQuoteTarget, 231 | uint256 newBaseTarget 232 | ) 233 | { 234 | (newBaseTarget, newQuoteTarget) = getExpectedTarget(); 235 | 236 | // charge fee from user receive amount 237 | lpFeeBase = DecimalMath.mul(amount, _LP_FEE_RATE_); 238 | mtFeeBase = DecimalMath.mul(amount, _MT_FEE_RATE_); 239 | uint256 buyBaseAmount = amount.add(lpFeeBase).add(mtFeeBase); 240 | 241 | if (_R_STATUS_ == Types.RStatus.ONE) { 242 | // case 1: R=1 243 | payQuote = _ROneBuyBaseToken(buyBaseAmount, newBaseTarget); 244 | newRStatus = Types.RStatus.ABOVE_ONE; 245 | } else if (_R_STATUS_ == Types.RStatus.ABOVE_ONE) { 246 | // case 2: R>1 247 | payQuote = _RAboveBuyBaseToken(buyBaseAmount, _BASE_BALANCE_, newBaseTarget); 248 | newRStatus = Types.RStatus.ABOVE_ONE; 249 | } else if (_R_STATUS_ == Types.RStatus.BELOW_ONE) { 250 | uint256 backToOnePayQuote = newQuoteTarget.sub(_QUOTE_BALANCE_); 251 | uint256 backToOneReceiveBase = _BASE_BALANCE_.sub(newBaseTarget); 252 | // case 3: R<1 253 | // complex case, R status may change 254 | if (buyBaseAmount < backToOneReceiveBase) { 255 | // case 3.1: R status do not change 256 | // no need to check payQuote because spare base token must be greater than zero 257 | payQuote = _RBelowBuyBaseToken(buyBaseAmount, _QUOTE_BALANCE_, newQuoteTarget); 258 | newRStatus = Types.RStatus.BELOW_ONE; 259 | } else if (buyBaseAmount == backToOneReceiveBase) { 260 | // case 3.2: R status changes to ONE 261 | payQuote = backToOnePayQuote; 262 | newRStatus = Types.RStatus.ONE; 263 | } else { 264 | // case 3.3: R status changes to ABOVE_ONE 265 | payQuote = backToOnePayQuote.add( 266 | _ROneBuyBaseToken(buyBaseAmount.sub(backToOneReceiveBase), newBaseTarget) 267 | ); 268 | newRStatus = Types.RStatus.ABOVE_ONE; 269 | } 270 | } 271 | 272 | return (payQuote, lpFeeBase, mtFeeBase, newRStatus, newQuoteTarget, newBaseTarget); 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /contracts/intf/IDODO.sol: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2020 DODO ZOO. 4 | SPDX-License-Identifier: Apache-2.0 5 | 6 | */ 7 | 8 | pragma solidity 0.6.9; 9 | pragma experimental ABIEncoderV2; 10 | 11 | 12 | interface IDODO { 13 | function init( 14 | address owner, 15 | address supervisor, 16 | address maintainer, 17 | address baseToken, 18 | address quoteToken, 19 | address oracle, 20 | uint256 lpFeeRate, 21 | uint256 mtFeeRate, 22 | uint256 k, 23 | uint256 gasPriceLimit 24 | ) external; 25 | 26 | function transferOwnership(address newOwner) external; 27 | 28 | function claimOwnership() external; 29 | 30 | function sellBaseToken( 31 | uint256 amount, 32 | uint256 minReceiveQuote, 33 | bytes calldata data 34 | ) external returns (uint256); 35 | 36 | function buyBaseToken( 37 | uint256 amount, 38 | uint256 maxPayQuote, 39 | bytes calldata data 40 | ) external returns (uint256); 41 | 42 | function querySellBaseToken(uint256 amount) external view returns (uint256 receiveQuote); 43 | 44 | function queryBuyBaseToken(uint256 amount) external view returns (uint256 payQuote); 45 | 46 | function getExpectedTarget() external view returns (uint256 baseTarget, uint256 quoteTarget); 47 | 48 | function depositBaseTo(address to, uint256 amount) external returns (uint256); 49 | 50 | function withdrawBase(uint256 amount) external returns (uint256); 51 | 52 | function withdrawAllBase() external returns (uint256); 53 | 54 | function depositQuoteTo(address to, uint256 amount) external returns (uint256); 55 | 56 | function withdrawQuote(uint256 amount) external returns (uint256); 57 | 58 | function withdrawAllQuote() external returns (uint256); 59 | 60 | function _BASE_CAPITAL_TOKEN_() external view returns (address); 61 | 62 | function _QUOTE_CAPITAL_TOKEN_() external view returns (address); 63 | 64 | function _BASE_TOKEN_() external returns (address); 65 | 66 | function _QUOTE_TOKEN_() external returns (address); 67 | } 68 | -------------------------------------------------------------------------------- /contracts/intf/IDODOCallee.sol: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2020 DODO ZOO. 4 | SPDX-License-Identifier: Apache-2.0 5 | 6 | */ 7 | 8 | pragma solidity 0.6.9; 9 | pragma experimental ABIEncoderV2; 10 | 11 | interface IDODOCallee { 12 | function dodoCall( 13 | bool isBuyBaseToken, 14 | uint256 baseAmount, 15 | uint256 quoteAmount, 16 | bytes calldata data 17 | ) external; 18 | } 19 | -------------------------------------------------------------------------------- /contracts/intf/IDODOLpToken.sol: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2020 DODO ZOO. 4 | SPDX-License-Identifier: Apache-2.0 5 | 6 | */ 7 | 8 | pragma solidity ^0.6.9; 9 | pragma experimental ABIEncoderV2; 10 | 11 | 12 | interface IDODOLpToken { 13 | function mint(address user, uint256 value) external; 14 | 15 | function burn(address user, uint256 value) external; 16 | 17 | function balanceOf(address owner) external view returns (uint256); 18 | 19 | function totalSupply() external view returns (uint256); 20 | } 21 | -------------------------------------------------------------------------------- /contracts/intf/IERC20.sol: -------------------------------------------------------------------------------- 1 | // This is a file copied from https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/IERC20.sol 2 | // SPDX-License-Identifier: MIT 3 | 4 | pragma solidity 0.6.9; 5 | pragma experimental ABIEncoderV2; 6 | 7 | /** 8 | * @dev Interface of the ERC20 standard as defined in the EIP. 9 | */ 10 | interface IERC20 { 11 | /** 12 | * @dev Returns the amount of tokens in existence. 13 | */ 14 | function totalSupply() external view returns (uint256); 15 | 16 | function decimals() external view returns (uint8); 17 | 18 | function name() external view returns (string memory); 19 | 20 | /** 21 | * @dev Returns the amount of tokens owned by `account`. 22 | */ 23 | function balanceOf(address account) external view returns (uint256); 24 | 25 | /** 26 | * @dev Moves `amount` tokens from the caller's account to `recipient`. 27 | * 28 | * Returns a boolean value indicating whether the operation succeeded. 29 | * 30 | * Emits a {Transfer} event. 31 | */ 32 | function transfer(address recipient, uint256 amount) external returns (bool); 33 | 34 | /** 35 | * @dev Returns the remaining number of tokens that `spender` will be 36 | * allowed to spend on behalf of `owner` through {transferFrom}. This is 37 | * zero by default. 38 | * 39 | * This value changes when {approve} or {transferFrom} are called. 40 | */ 41 | function allowance(address owner, address spender) external view returns (uint256); 42 | 43 | /** 44 | * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. 45 | * 46 | * Returns a boolean value indicating whether the operation succeeded. 47 | * 48 | * IMPORTANT: Beware that changing an allowance with this method brings the risk 49 | * that someone may use both the old and the new allowance by unfortunate 50 | * transaction ordering. One possible solution to mitigate this race 51 | * condition is to first reduce the spender's allowance to 0 and set the 52 | * desired value afterwards: 53 | * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 54 | * 55 | * Emits an {Approval} event. 56 | */ 57 | function approve(address spender, uint256 amount) external returns (bool); 58 | 59 | /** 60 | * @dev Moves `amount` tokens from `sender` to `recipient` using the 61 | * allowance mechanism. `amount` is then deducted from the caller's 62 | * allowance. 63 | * 64 | * Returns a boolean value indicating whether the operation succeeded. 65 | * 66 | * Emits a {Transfer} event. 67 | */ 68 | function transferFrom( 69 | address sender, 70 | address recipient, 71 | uint256 amount 72 | ) external returns (bool); 73 | } 74 | -------------------------------------------------------------------------------- /contracts/intf/IOracle.sol: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2020 DODO ZOO. 4 | SPDX-License-Identifier: Apache-2.0 5 | 6 | */ 7 | 8 | pragma solidity 0.6.9; 9 | pragma experimental ABIEncoderV2; 10 | 11 | 12 | interface IOracle { 13 | function getPrice() external view returns (uint256); 14 | } 15 | -------------------------------------------------------------------------------- /contracts/intf/IWETH.sol: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2020 DODO ZOO. 4 | SPDX-License-Identifier: Apache-2.0 5 | 6 | */ 7 | 8 | pragma solidity 0.6.9; 9 | pragma experimental ABIEncoderV2; 10 | 11 | 12 | interface IWETH { 13 | function totalSupply() external view returns (uint256); 14 | 15 | function balanceOf(address account) external view returns (uint256); 16 | 17 | function transfer(address recipient, uint256 amount) external returns (bool); 18 | 19 | function allowance(address owner, address spender) external view returns (uint256); 20 | 21 | function approve(address spender, uint256 amount) external returns (bool); 22 | 23 | function transferFrom( 24 | address src, 25 | address dst, 26 | uint256 wad 27 | ) external returns (bool); 28 | 29 | function deposit() external payable; 30 | 31 | function withdraw(uint256 wad) external; 32 | } 33 | -------------------------------------------------------------------------------- /contracts/lib/DODOMath.sol: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2020 DODO ZOO. 4 | SPDX-License-Identifier: Apache-2.0 5 | 6 | */ 7 | 8 | pragma solidity 0.6.9; 9 | pragma experimental ABIEncoderV2; 10 | 11 | import {SafeMath} from "./SafeMath.sol"; 12 | import {DecimalMath} from "./DecimalMath.sol"; 13 | 14 | /** 15 | * @title DODOMath 16 | * @author DODO Breeder 17 | * 18 | * @notice Functions for complex calculating. Including ONE Integration and TWO Quadratic solutions 19 | */ 20 | library DODOMath { 21 | using SafeMath for uint256; 22 | 23 | /* 24 | Integrate dodo curve fron V1 to V2 25 | require V0>=V1>=V2>0 26 | res = (1-k)i(V1-V2)+ikV0*V0(1/V2-1/V1) 27 | let V1-V2=delta 28 | res = i*delta*(1-k+k(V0^2/V1/V2)) 29 | */ 30 | function _GeneralIntegrate( 31 | uint256 V0, 32 | uint256 V1, 33 | uint256 V2, 34 | uint256 i, 35 | uint256 k 36 | ) internal pure returns (uint256) { 37 | uint256 fairAmount = DecimalMath.mul(i, V1.sub(V2)); // i*delta 38 | uint256 V0V0V1V2 = DecimalMath.divCeil(V0.mul(V0).div(V1), V2); 39 | uint256 penalty = DecimalMath.mul(k, V0V0V1V2); // k(V0^2/V1/V2) 40 | return DecimalMath.mul(fairAmount, DecimalMath.ONE.sub(k).add(penalty)); 41 | } 42 | 43 | /* 44 | The same with integration expression above, we have: 45 | i*deltaB = (Q2-Q1)*(1-k+kQ0^2/Q1/Q2) 46 | Given Q1 and deltaB, solve Q2 47 | This is a quadratic function and the standard version is 48 | aQ2^2 + bQ2 + c = 0, where 49 | a=1-k 50 | -b=(1-k)Q1-kQ0^2/Q1+i*deltaB 51 | c=-kQ0^2 52 | and Q2=(-b+sqrt(b^2+4(1-k)kQ0^2))/2(1-k) 53 | note: another root is negative, abondan 54 | if deltaBSig=true, then Q2>Q1 55 | if deltaBSig=false, then Q2= kQ02Q1) { 75 | b = b.sub(kQ02Q1); 76 | minusbSig = true; 77 | } else { 78 | b = kQ02Q1.sub(b); 79 | minusbSig = false; 80 | } 81 | 82 | // calculate sqrt 83 | uint256 squareRoot = DecimalMath.mul( 84 | DecimalMath.ONE.sub(k).mul(4), 85 | DecimalMath.mul(k, Q0).mul(Q0) 86 | ); // 4(1-k)kQ0^2 87 | squareRoot = b.mul(b).add(squareRoot).sqrt(); // sqrt(b*b+4(1-k)kQ0*Q0) 88 | 89 | // final res 90 | uint256 denominator = DecimalMath.ONE.sub(k).mul(2); // 2(1-k) 91 | uint256 numerator; 92 | if (minusbSig) { 93 | numerator = b.add(squareRoot); 94 | } else { 95 | numerator = squareRoot.sub(b); 96 | } 97 | 98 | if (deltaBSig) { 99 | return DecimalMath.divFloor(numerator, denominator); 100 | } else { 101 | return DecimalMath.divCeil(numerator, denominator); 102 | } 103 | } 104 | 105 | /* 106 | Start from the integration function 107 | i*deltaB = (Q2-Q1)*(1-k+kQ0^2/Q1/Q2) 108 | Assume Q2=Q0, Given Q1 and deltaB, solve Q0 109 | let fairAmount = i*deltaB 110 | */ 111 | function _SolveQuadraticFunctionForTarget( 112 | uint256 V1, 113 | uint256 k, 114 | uint256 fairAmount 115 | ) internal pure returns (uint256 V0) { 116 | // V0 = V1+V1*(sqrt-1)/2k 117 | uint256 sqrt = DecimalMath.divCeil(DecimalMath.mul(k, fairAmount).mul(4), V1); 118 | sqrt = sqrt.add(DecimalMath.ONE).mul(DecimalMath.ONE).sqrt(); 119 | uint256 premium = DecimalMath.divCeil(sqrt.sub(DecimalMath.ONE), k.mul(2)); 120 | // V0 is greater than or equal to V1 according to the solution 121 | return DecimalMath.mul(V1, DecimalMath.ONE.add(premium)); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /contracts/lib/DecimalMath.sol: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2020 DODO ZOO. 4 | SPDX-License-Identifier: Apache-2.0 5 | 6 | */ 7 | 8 | pragma solidity 0.6.9; 9 | pragma experimental ABIEncoderV2; 10 | 11 | import {SafeMath} from "./SafeMath.sol"; 12 | 13 | 14 | /** 15 | * @title DecimalMath 16 | * @author DODO Breeder 17 | * 18 | * @notice Functions for fixed point number with 18 decimals 19 | */ 20 | library DecimalMath { 21 | using SafeMath for uint256; 22 | 23 | uint256 constant ONE = 10**18; 24 | 25 | function mul(uint256 target, uint256 d) internal pure returns (uint256) { 26 | return target.mul(d) / ONE; 27 | } 28 | 29 | function mulCeil(uint256 target, uint256 d) internal pure returns (uint256) { 30 | return target.mul(d).divCeil(ONE); 31 | } 32 | 33 | function divFloor(uint256 target, uint256 d) internal pure returns (uint256) { 34 | return target.mul(ONE).div(d); 35 | } 36 | 37 | function divCeil(uint256 target, uint256 d) internal pure returns (uint256) { 38 | return target.mul(ONE).divCeil(d); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /contracts/lib/InitializableOwnable.sol: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2020 DODO ZOO. 4 | SPDX-License-Identifier: Apache-2.0 5 | 6 | */ 7 | 8 | pragma solidity 0.6.9; 9 | pragma experimental ABIEncoderV2; 10 | 11 | /** 12 | * @title Ownable 13 | * @author DODO Breeder 14 | * 15 | * @notice Ownership related functions 16 | */ 17 | contract InitializableOwnable { 18 | address public _OWNER_; 19 | address public _NEW_OWNER_; 20 | 21 | // ============ Events ============ 22 | 23 | event OwnershipTransferPrepared(address indexed previousOwner, address indexed newOwner); 24 | 25 | event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); 26 | 27 | // ============ Modifiers ============ 28 | 29 | modifier onlyOwner() { 30 | require(msg.sender == _OWNER_, "NOT_OWNER"); 31 | _; 32 | } 33 | 34 | // ============ Functions ============ 35 | 36 | function transferOwnership(address newOwner) external onlyOwner { 37 | require(newOwner != address(0), "INVALID_OWNER"); 38 | emit OwnershipTransferPrepared(_OWNER_, newOwner); 39 | _NEW_OWNER_ = newOwner; 40 | } 41 | 42 | function claimOwnership() external { 43 | require(msg.sender == _NEW_OWNER_, "INVALID_CLAIM"); 44 | emit OwnershipTransferred(_OWNER_, _NEW_OWNER_); 45 | _OWNER_ = _NEW_OWNER_; 46 | _NEW_OWNER_ = address(0); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /contracts/lib/Ownable.sol: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2020 DODO ZOO. 4 | SPDX-License-Identifier: Apache-2.0 5 | 6 | */ 7 | 8 | pragma solidity 0.6.9; 9 | pragma experimental ABIEncoderV2; 10 | 11 | /** 12 | * @title Ownable 13 | * @author DODO Breeder 14 | * 15 | * @notice Ownership related functions 16 | */ 17 | contract Ownable { 18 | address public _OWNER_; 19 | address public _NEW_OWNER_; 20 | 21 | // ============ Events ============ 22 | 23 | event OwnershipTransferPrepared(address indexed previousOwner, address indexed newOwner); 24 | 25 | event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); 26 | 27 | // ============ Modifiers ============ 28 | 29 | modifier onlyOwner() { 30 | require(msg.sender == _OWNER_, "NOT_OWNER"); 31 | _; 32 | } 33 | 34 | // ============ Functions ============ 35 | 36 | constructor() internal { 37 | _OWNER_ = msg.sender; 38 | emit OwnershipTransferred(address(0), _OWNER_); 39 | } 40 | 41 | function transferOwnership(address newOwner) external onlyOwner { 42 | require(newOwner != address(0), "INVALID_OWNER"); 43 | emit OwnershipTransferPrepared(_OWNER_, newOwner); 44 | _NEW_OWNER_ = newOwner; 45 | } 46 | 47 | function claimOwnership() external { 48 | require(msg.sender == _NEW_OWNER_, "INVALID_CLAIM"); 49 | emit OwnershipTransferred(_OWNER_, _NEW_OWNER_); 50 | _OWNER_ = _NEW_OWNER_; 51 | _NEW_OWNER_ = address(0); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /contracts/lib/ReentrancyGuard.sol: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2020 DODO ZOO. 4 | SPDX-License-Identifier: Apache-2.0 5 | 6 | */ 7 | 8 | pragma solidity 0.6.9; 9 | pragma experimental ABIEncoderV2; 10 | 11 | /** 12 | * @title ReentrancyGuard 13 | * @author DODO Breeder 14 | * 15 | * @notice Protect functions from Reentrancy Attack 16 | */ 17 | contract ReentrancyGuard { 18 | // https://solidity.readthedocs.io/en/latest/control-structures.html?highlight=zero-state#scoping-and-declarations 19 | // zero-state of _ENTERED_ is false 20 | bool private _ENTERED_; 21 | 22 | modifier preventReentrant() { 23 | require(!_ENTERED_, "REENTRANT"); 24 | _ENTERED_ = true; 25 | _; 26 | _ENTERED_ = false; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /contracts/lib/SafeERC20.sol: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2020 DODO ZOO. 4 | SPDX-License-Identifier: Apache-2.0 5 | This is a simplified version of OpenZepplin's SafeERC20 library 6 | 7 | */ 8 | 9 | pragma solidity 0.6.9; 10 | pragma experimental ABIEncoderV2; 11 | 12 | import {IERC20} from "../intf/IERC20.sol"; 13 | import {SafeMath} from "./SafeMath.sol"; 14 | 15 | 16 | /** 17 | * @title SafeERC20 18 | * @dev Wrappers around ERC20 operations that throw on failure (when the token 19 | * contract returns false). Tokens that return no value (and instead revert or 20 | * throw on failure) are also supported, non-reverting calls are assumed to be 21 | * successful. 22 | * To use this library you can add a `using SafeERC20 for ERC20;` statement to your contract, 23 | * which allows you to call the safe operations as `token.safeTransfer(...)`, etc. 24 | */ 25 | library SafeERC20 { 26 | using SafeMath for uint256; 27 | 28 | function safeTransfer( 29 | IERC20 token, 30 | address to, 31 | uint256 value 32 | ) internal { 33 | _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); 34 | } 35 | 36 | function safeTransferFrom( 37 | IERC20 token, 38 | address from, 39 | address to, 40 | uint256 value 41 | ) internal { 42 | _callOptionalReturn( 43 | token, 44 | abi.encodeWithSelector(token.transferFrom.selector, from, to, value) 45 | ); 46 | } 47 | 48 | function safeApprove( 49 | IERC20 token, 50 | address spender, 51 | uint256 value 52 | ) internal { 53 | // safeApprove should only be called when setting an initial allowance, 54 | // or when resetting it to zero. To increase and decrease it, use 55 | // 'safeIncreaseAllowance' and 'safeDecreaseAllowance' 56 | // solhint-disable-next-line max-line-length 57 | require( 58 | (value == 0) || (token.allowance(address(this), spender) == 0), 59 | "SafeERC20: approve from non-zero to non-zero allowance" 60 | ); 61 | _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value)); 62 | } 63 | 64 | /** 65 | * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement 66 | * on the return value: the return value is optional (but if data is returned, it must not be false). 67 | * @param token The token targeted by the call. 68 | * @param data The call data (encoded using abi.encode or one of its variants). 69 | */ 70 | function _callOptionalReturn(IERC20 token, bytes memory data) private { 71 | // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since 72 | // we're implementing it ourselves. 73 | 74 | // A Solidity high level call has three parts: 75 | // 1. The target address is checked to verify it contains contract code 76 | // 2. The call itself is made, and success asserted 77 | // 3. The return value is decoded, which in turn checks the size of the returned data. 78 | // solhint-disable-next-line max-line-length 79 | 80 | // solhint-disable-next-line avoid-low-level-calls 81 | (bool success, bytes memory returndata) = address(token).call(data); 82 | require(success, "SafeERC20: low-level call failed"); 83 | 84 | if (returndata.length > 0) { 85 | // Return data is optional 86 | // solhint-disable-next-line max-line-length 87 | require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed"); 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /contracts/lib/SafeMath.sol: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2020 DODO ZOO. 4 | SPDX-License-Identifier: Apache-2.0 5 | 6 | */ 7 | 8 | pragma solidity 0.6.9; 9 | pragma experimental ABIEncoderV2; 10 | 11 | 12 | /** 13 | * @title SafeMath 14 | * @author DODO Breeder 15 | * 16 | * @notice Math operations with safety checks that revert on error 17 | */ 18 | library SafeMath { 19 | function mul(uint256 a, uint256 b) internal pure returns (uint256) { 20 | if (a == 0) { 21 | return 0; 22 | } 23 | 24 | uint256 c = a * b; 25 | require(c / a == b, "MUL_ERROR"); 26 | 27 | return c; 28 | } 29 | 30 | function div(uint256 a, uint256 b) internal pure returns (uint256) { 31 | require(b > 0, "DIVIDING_ERROR"); 32 | return a / b; 33 | } 34 | 35 | function divCeil(uint256 a, uint256 b) internal pure returns (uint256) { 36 | uint256 quotient = div(a, b); 37 | uint256 remainder = a - quotient * b; 38 | if (remainder > 0) { 39 | return quotient + 1; 40 | } else { 41 | return quotient; 42 | } 43 | } 44 | 45 | function sub(uint256 a, uint256 b) internal pure returns (uint256) { 46 | require(b <= a, "SUB_ERROR"); 47 | return a - b; 48 | } 49 | 50 | function add(uint256 a, uint256 b) internal pure returns (uint256) { 51 | uint256 c = a + b; 52 | require(c >= a, "ADD_ERROR"); 53 | return c; 54 | } 55 | 56 | function sqrt(uint256 x) internal pure returns (uint256 y) { 57 | uint256 z = x / 2 + 1; 58 | y = x; 59 | while (z < y) { 60 | y = z; 61 | z = (x / z + z) / 2; 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /contracts/lib/Types.sol: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2020 DODO ZOO. 4 | SPDX-License-Identifier: Apache-2.0 5 | 6 | */ 7 | 8 | pragma solidity 0.6.9; 9 | pragma experimental ABIEncoderV2; 10 | 11 | library Types { 12 | enum RStatus {ONE, ABOVE_ONE, BELOW_ONE} 13 | } 14 | -------------------------------------------------------------------------------- /contracts/token/DODOMineReader.sol: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2020 DODO ZOO. 4 | SPDX-License-Identifier: Apache-2.0 5 | 6 | */ 7 | 8 | pragma solidity 0.6.9; 9 | pragma experimental ABIEncoderV2; 10 | 11 | import {IDODO} from "../intf/IDODO.sol"; 12 | import {IERC20} from "../intf/IERC20.sol"; 13 | import {SafeMath} from "../lib/SafeMath.sol"; 14 | 15 | 16 | interface IDODOMine { 17 | function getUserLpBalance(address _lpToken, address _user) external view returns (uint256); 18 | } 19 | 20 | 21 | contract DODOMineReader { 22 | using SafeMath for uint256; 23 | 24 | function getUserStakedBalance( 25 | address _dodoMine, 26 | address _dodo, 27 | address _user 28 | ) external view returns (uint256 baseBalance, uint256 quoteBalance) { 29 | address baseLpToken = IDODO(_dodo)._BASE_CAPITAL_TOKEN_(); 30 | address quoteLpToken = IDODO(_dodo)._QUOTE_CAPITAL_TOKEN_(); 31 | 32 | uint256 baseLpBalance = IDODOMine(_dodoMine).getUserLpBalance(baseLpToken, _user); 33 | uint256 quoteLpBalance = IDODOMine(_dodoMine).getUserLpBalance(quoteLpToken, _user); 34 | 35 | uint256 baseLpTotalSupply = IERC20(baseLpToken).totalSupply(); 36 | uint256 quoteLpTotalSupply = IERC20(quoteLpToken).totalSupply(); 37 | 38 | (uint256 baseTarget, uint256 quoteTarget) = IDODO(_dodo).getExpectedTarget(); 39 | baseBalance = baseTarget.mul(baseLpBalance).div(baseLpTotalSupply); 40 | quoteBalance = quoteTarget.mul(quoteLpBalance).div(quoteLpTotalSupply); 41 | 42 | return (baseBalance, quoteBalance); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /contracts/token/DODORewardVault.sol: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2020 DODO ZOO. 4 | SPDX-License-Identifier: Apache-2.0 5 | 6 | */ 7 | 8 | pragma solidity 0.6.9; 9 | pragma experimental ABIEncoderV2; 10 | 11 | import {Ownable} from "../lib/Ownable.sol"; 12 | import {SafeERC20} from "../lib/SafeERC20.sol"; 13 | import {IERC20} from "../intf/IERC20.sol"; 14 | 15 | 16 | interface IDODORewardVault { 17 | function reward(address to, uint256 amount) external; 18 | } 19 | 20 | 21 | contract DODORewardVault is Ownable { 22 | using SafeERC20 for IERC20; 23 | 24 | address public dodoToken; 25 | 26 | constructor(address _dodoToken) public { 27 | dodoToken = _dodoToken; 28 | } 29 | 30 | function reward(address to, uint256 amount) external onlyOwner { 31 | IERC20(dodoToken).safeTransfer(to, amount); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /contracts/token/DODOToken.sol: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2020 DODO ZOO. 4 | SPDX-License-Identifier: Apache-2.0 5 | 6 | */ 7 | 8 | pragma solidity 0.6.9; 9 | pragma experimental ABIEncoderV2; 10 | 11 | import {SafeMath} from "../lib/SafeMath.sol"; 12 | 13 | 14 | /** 15 | * @title DODO Token 16 | * @author DODO Breeder 17 | */ 18 | contract DODOToken { 19 | using SafeMath for uint256; 20 | 21 | string public symbol = "DODO"; 22 | string public name = "DODO bird"; 23 | 24 | uint256 public decimals = 18; 25 | uint256 public totalSupply = 1000000000 * 10**18; // 1 Billion 26 | 27 | mapping(address => uint256) internal balances; 28 | mapping(address => mapping(address => uint256)) internal allowed; 29 | 30 | // ============ Events ============ 31 | 32 | event Transfer(address indexed from, address indexed to, uint256 amount); 33 | 34 | event Approval(address indexed owner, address indexed spender, uint256 amount); 35 | 36 | // ============ Functions ============ 37 | 38 | constructor() public { 39 | balances[msg.sender] = totalSupply; 40 | } 41 | 42 | /** 43 | * @dev transfer token for a specified address 44 | * @param to The address to transfer to. 45 | * @param amount The amount to be transferred. 46 | */ 47 | function transfer(address to, uint256 amount) public returns (bool) { 48 | require(amount <= balances[msg.sender], "BALANCE_NOT_ENOUGH"); 49 | 50 | balances[msg.sender] = balances[msg.sender].sub(amount); 51 | balances[to] = balances[to].add(amount); 52 | emit Transfer(msg.sender, to, amount); 53 | return true; 54 | } 55 | 56 | /** 57 | * @dev Gets the balance of the specified address. 58 | * @param owner The address to query the the balance of. 59 | * @return balance An uint256 representing the amount owned by the passed address. 60 | */ 61 | function balanceOf(address owner) external view returns (uint256 balance) { 62 | return balances[owner]; 63 | } 64 | 65 | /** 66 | * @dev Transfer tokens from one address to another 67 | * @param from address The address which you want to send tokens from 68 | * @param to address The address which you want to transfer to 69 | * @param amount uint256 the amount of tokens to be transferred 70 | */ 71 | function transferFrom( 72 | address from, 73 | address to, 74 | uint256 amount 75 | ) public returns (bool) { 76 | require(amount <= balances[from], "BALANCE_NOT_ENOUGH"); 77 | require(amount <= allowed[from][msg.sender], "ALLOWANCE_NOT_ENOUGH"); 78 | 79 | balances[from] = balances[from].sub(amount); 80 | balances[to] = balances[to].add(amount); 81 | allowed[from][msg.sender] = allowed[from][msg.sender].sub(amount); 82 | emit Transfer(from, to, amount); 83 | return true; 84 | } 85 | 86 | /** 87 | * @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender. 88 | * @param spender The address which will spend the funds. 89 | * @param amount The amount of tokens to be spent. 90 | */ 91 | function approve(address spender, uint256 amount) public returns (bool) { 92 | allowed[msg.sender][spender] = amount; 93 | emit Approval(msg.sender, spender, amount); 94 | return true; 95 | } 96 | 97 | /** 98 | * @dev Function to check the amount of tokens that an owner allowed to a spender. 99 | * @param owner address The address which owns the funds. 100 | * @param spender address The address which will spend the funds. 101 | * @return A uint256 specifying the amount of tokens still available for the spender. 102 | */ 103 | function allowance(address owner, address spender) public view returns (uint256) { 104 | return allowed[owner][spender]; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /contracts/token/LockedTokenVault.sol: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2020 DODO ZOO. 4 | SPDX-License-Identifier: Apache-2.0 5 | 6 | */ 7 | 8 | pragma solidity 0.6.9; 9 | pragma experimental ABIEncoderV2; 10 | 11 | import {SafeMath} from "../lib/SafeMath.sol"; 12 | import {DecimalMath} from "../lib/DecimalMath.sol"; 13 | import {Ownable} from "../lib/Ownable.sol"; 14 | import {SafeERC20} from "../lib/SafeERC20.sol"; 15 | import {IERC20} from "../intf/IERC20.sol"; 16 | 17 | 18 | /** 19 | * @title LockedTokenVault 20 | * @author DODO Breeder 21 | * 22 | * @notice Lock Token and release it linearly 23 | */ 24 | 25 | contract LockedTokenVault is Ownable { 26 | using SafeMath for uint256; 27 | using SafeERC20 for IERC20; 28 | 29 | address _TOKEN_; 30 | 31 | mapping(address => uint256) internal originBalances; 32 | mapping(address => uint256) internal claimedBalances; 33 | 34 | uint256 public _UNDISTRIBUTED_AMOUNT_; 35 | uint256 public _START_RELEASE_TIME_; 36 | uint256 public _RELEASE_DURATION_; 37 | uint256 public _CLIFF_RATE_; 38 | 39 | bool public _DISTRIBUTE_FINISHED_; 40 | 41 | // ============ Modifiers ============ 42 | 43 | event Claim(address indexed holder, uint256 origin, uint256 claimed, uint256 amount); 44 | 45 | // ============ Modifiers ============ 46 | 47 | modifier beforeStartRelease() { 48 | require(block.timestamp < _START_RELEASE_TIME_, "RELEASE START"); 49 | _; 50 | } 51 | 52 | modifier afterStartRelease() { 53 | require(block.timestamp >= _START_RELEASE_TIME_, "RELEASE NOT START"); 54 | _; 55 | } 56 | 57 | modifier distributeNotFinished() { 58 | require(!_DISTRIBUTE_FINISHED_, "DISTRIBUTE FINISHED"); 59 | _; 60 | } 61 | 62 | // ============ Init Functions ============ 63 | 64 | constructor( 65 | address _token, 66 | uint256 _startReleaseTime, 67 | uint256 _releaseDuration, 68 | uint256 _cliffRate 69 | ) public { 70 | _TOKEN_ = _token; 71 | _START_RELEASE_TIME_ = _startReleaseTime; 72 | _RELEASE_DURATION_ = _releaseDuration; 73 | _CLIFF_RATE_ = _cliffRate; 74 | } 75 | 76 | function deposit(uint256 amount) external onlyOwner { 77 | _tokenTransferIn(_OWNER_, amount); 78 | _UNDISTRIBUTED_AMOUNT_ = _UNDISTRIBUTED_AMOUNT_.add(amount); 79 | } 80 | 81 | function withdraw(uint256 amount) external onlyOwner { 82 | _UNDISTRIBUTED_AMOUNT_ = _UNDISTRIBUTED_AMOUNT_.sub(amount); 83 | _tokenTransferOut(_OWNER_, amount); 84 | } 85 | 86 | function finishDistribute() external onlyOwner { 87 | _DISTRIBUTE_FINISHED_ = true; 88 | } 89 | 90 | // ============ For Owner ============ 91 | 92 | function grant(address[] calldata holderList, uint256[] calldata amountList) 93 | external 94 | onlyOwner 95 | { 96 | require(holderList.length == amountList.length, "batch grant length not match"); 97 | uint256 amount = 0; 98 | for (uint256 i = 0; i < holderList.length; ++i) { 99 | // for saving gas, no event for grant 100 | originBalances[holderList[i]] = originBalances[holderList[i]].add(amountList[i]); 101 | amount = amount.add(amountList[i]); 102 | } 103 | _UNDISTRIBUTED_AMOUNT_ = _UNDISTRIBUTED_AMOUNT_.sub(amount); 104 | } 105 | 106 | function recall(address holder) external onlyOwner distributeNotFinished { 107 | _UNDISTRIBUTED_AMOUNT_ = _UNDISTRIBUTED_AMOUNT_.add(originBalances[holder]).sub( 108 | claimedBalances[holder] 109 | ); 110 | originBalances[holder] = 0; 111 | claimedBalances[holder] = 0; 112 | } 113 | 114 | // ============ For Holder ============ 115 | 116 | function transferLockedToken(address to) external { 117 | originBalances[to] = originBalances[to].add(originBalances[msg.sender]); 118 | claimedBalances[to] = claimedBalances[to].add(claimedBalances[msg.sender]); 119 | 120 | originBalances[msg.sender] = 0; 121 | claimedBalances[msg.sender] = 0; 122 | } 123 | 124 | function claim() external { 125 | uint256 claimableToken = getClaimableBalance(msg.sender); 126 | _tokenTransferOut(msg.sender, claimableToken); 127 | claimedBalances[msg.sender] = claimedBalances[msg.sender].add(claimableToken); 128 | emit Claim( 129 | msg.sender, 130 | originBalances[msg.sender], 131 | claimedBalances[msg.sender], 132 | claimableToken 133 | ); 134 | } 135 | 136 | // ============ View ============ 137 | 138 | function isReleaseStart() external view returns (bool) { 139 | return block.timestamp >= _START_RELEASE_TIME_; 140 | } 141 | 142 | function getOriginBalance(address holder) external view returns (uint256) { 143 | return originBalances[holder]; 144 | } 145 | 146 | function getClaimedBalance(address holder) external view returns (uint256) { 147 | return claimedBalances[holder]; 148 | } 149 | 150 | function getClaimableBalance(address holder) public view returns (uint256) { 151 | uint256 remainingToken = getRemainingBalance(holder); 152 | return originBalances[holder].sub(remainingToken).sub(claimedBalances[holder]); 153 | } 154 | 155 | function getRemainingBalance(address holder) public view returns (uint256) { 156 | uint256 remainingRatio = getRemainingRatio(block.timestamp); 157 | return DecimalMath.mul(originBalances[holder], remainingRatio); 158 | } 159 | 160 | function getRemainingRatio(uint256 timestamp) public view returns (uint256) { 161 | if (timestamp < _START_RELEASE_TIME_) { 162 | return DecimalMath.ONE; 163 | } 164 | uint256 timePast = timestamp.sub(_START_RELEASE_TIME_); 165 | if (timePast < _RELEASE_DURATION_) { 166 | uint256 remainingTime = _RELEASE_DURATION_.sub(timePast); 167 | return DecimalMath.ONE.sub(_CLIFF_RATE_).mul(remainingTime).div(_RELEASE_DURATION_); 168 | } else { 169 | return 0; 170 | } 171 | } 172 | 173 | // ============ Internal Helper ============ 174 | 175 | function _tokenTransferIn(address from, uint256 amount) internal { 176 | IERC20(_TOKEN_).safeTransferFrom(from, address(this), amount); 177 | } 178 | 179 | function _tokenTransferOut(address to, uint256 amount) internal { 180 | IERC20(_TOKEN_).safeTransfer(to, amount); 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | const Migrations = artifacts.require("Migrations"); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(Migrations); 5 | }; 6 | -------------------------------------------------------------------------------- /migrations/2_deploy.js: -------------------------------------------------------------------------------- 1 | const DODOZoo = artifacts.require("DODOZoo"); 2 | 3 | module.exports = async (deployer, network) => {}; 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dodo", 3 | "version": "1.0.0", 4 | "description": "a kind of bird", 5 | "main": "index.js", 6 | "author": "dodo breeder", 7 | "license": "Apache-2.0", 8 | "keywords": [ 9 | "dodo", 10 | "ethereum", 11 | "lmm" 12 | ], 13 | "scripts": { 14 | "prettier": "prettier --write **/*.sol", 15 | "migrate": "truffle migrate", 16 | "compile": "rm -r build && truffle compile", 17 | "coverage": "NETWORK_ID=1002 RPC_NODE_URI=http://127.0.0.1:6545 COVERAGE=true truffle run coverage", 18 | "test": "truffle compile && truffle test", 19 | "test_only": "truffle test", 20 | "deploy": "truffle migrate --network=$NETWORK --reset", 21 | "deploy_kovan": "NETWORK=kovan npm run deploy", 22 | "deploy_mainnet": "NETWORK=mainnet npm run deploy", 23 | "deploy_test": "NETWORK=development npm run deploy", 24 | "node": "ganache-cli --port 8545 -l 0x1fffffffffffff -i 5777 -g 1 --allowUnlimitedContractSize" 25 | }, 26 | "dependencies": { 27 | "@types/chai": "^4.2.11", 28 | "@types/es6-promisify": "^6.0.0", 29 | "@types/mocha": "^7.0.2", 30 | "assert": "^2.0.0", 31 | "axios": "^0.20.0", 32 | "babel-cli": "^6.26.0", 33 | "babel-eslint": "^10.1.0", 34 | "bignumber.js": "^9.0.0", 35 | "chai": "^4.2.0", 36 | "chai-bignumber": "^3.0.0", 37 | "debug": "^4.1.1", 38 | "dotenv-flow": "^3.1.0", 39 | "es6-promisify": "^6.1.1", 40 | "ethereumjs-util": "^7.0.2", 41 | "lodash": "^4.17.20", 42 | "mocha": "^7.2.0", 43 | "solc": "0.6.9", 44 | "truffle-hdwallet-provider": "^1.0.17", 45 | "ts-node": "^8.10.2", 46 | "typescript": "^3.9.5", 47 | "web3": "^1.2.8", 48 | "web3-core-helpers": "^1.2.8", 49 | "web3-eth-contract": "^1.2.8" 50 | }, 51 | "devDependencies": { 52 | "@truffle/hdwallet-provider": "^1.0.36", 53 | "ganache-cli": "^6.9.1", 54 | "prettier": "^2.0.5", 55 | "prettier-plugin-solidity": "^1.0.0-alpha.52", 56 | "solidity-coverage": "^0.7.7" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /test/Attacks.test.ts: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2020 DODO ZOO. 4 | SPDX-License-Identifier: Apache-2.0 5 | 6 | */ 7 | 8 | import { DODOContext, getDODOContext } from './utils/Context'; 9 | import { decimalStr, gweiStr } from './utils/Converter'; 10 | import BigNumber from "bignumber.js"; 11 | import * as assert from "assert" 12 | 13 | let lp1: string 14 | let lp2: string 15 | let trader: string 16 | let hacker: string 17 | 18 | async function init(ctx: DODOContext): Promise { 19 | await ctx.setOraclePrice(decimalStr("100")) 20 | lp1 = ctx.spareAccounts[0] 21 | lp2 = ctx.spareAccounts[1] 22 | trader = ctx.spareAccounts[2] 23 | hacker = ctx.spareAccounts[3] 24 | await ctx.mintTestToken(lp1, decimalStr("100"), decimalStr("10000")) 25 | await ctx.mintTestToken(lp2, decimalStr("100"), decimalStr("10000")) 26 | await ctx.mintTestToken(trader, decimalStr("100"), decimalStr("10000")) 27 | await ctx.mintTestToken(hacker, decimalStr("10000"), decimalStr("1000000")) 28 | await ctx.approveDODO(lp1) 29 | await ctx.approveDODO(lp2) 30 | await ctx.approveDODO(trader) 31 | await ctx.approveDODO(hacker) 32 | } 33 | 34 | describe("Attacks", () => { 35 | 36 | let snapshotId: string 37 | let ctx: DODOContext 38 | 39 | before(async () => { 40 | ctx = await getDODOContext() 41 | await init(ctx); 42 | }) 43 | 44 | beforeEach(async () => { 45 | snapshotId = await ctx.EVM.snapshot(); 46 | }); 47 | 48 | afterEach(async () => { 49 | await ctx.EVM.reset(snapshotId) 50 | }); 51 | 52 | describe("Price offset attack", () => { 53 | /* 54 | attack describe: 55 | 1. hacker deposit a great number of base token 56 | 2. hacker buy base token 57 | 3. hacker withdraw a great number of base token 58 | 4. hacker sell or buy base token to finish the arbitrage loop 59 | 60 | expected: 61 | 1. hacker won't earn any quote token or sell base token with price better than what dodo provides 62 | 2. quote token lp and base token lp have no loss 63 | 64 | Same in quote direction 65 | */ 66 | it("attack on base token", async () => { 67 | await ctx.DODO.methods.depositBase(decimalStr("10")).send(ctx.sendParam(lp1)) 68 | await ctx.DODO.methods.depositQuote(decimalStr("1000")).send(ctx.sendParam(lp1)) 69 | let hackerInitBaseBalance = new BigNumber(await ctx.BASE.methods.balanceOf(hacker).call()) 70 | let hackerInitQuoteBalance = new BigNumber(await ctx.QUOTE.methods.balanceOf(hacker).call()) 71 | // attack step 1 72 | await ctx.DODO.methods.depositBase(decimalStr("5000")).send(ctx.sendParam(hacker)) 73 | // attack step 2 74 | await ctx.DODO.methods.buyBaseToken(decimalStr("9.5"), decimalStr("2000"), "0x").send(ctx.sendParam(hacker)) 75 | // attack step 3 76 | await ctx.DODO.methods.withdrawBase(decimalStr("5000")).send(ctx.sendParam(hacker)) 77 | // attack step 4 78 | let hackerTempBaseBalance = new BigNumber(await ctx.BASE.methods.balanceOf(hacker).call()) 79 | if (hackerTempBaseBalance.isGreaterThan(hackerInitBaseBalance)) { 80 | await ctx.DODO.methods.sellBaseToken(hackerTempBaseBalance.minus(hackerInitBaseBalance).toString(), "0", "0x").send(ctx.sendParam(hacker)) 81 | } else { 82 | await ctx.DODO.methods.buyBaseToken(hackerInitBaseBalance.minus(hackerTempBaseBalance).toString(), decimalStr("5000"), "0x").send(ctx.sendParam(hacker)) 83 | } 84 | 85 | // expected hacker no profit 86 | let hackerBaseBalance = new BigNumber(await ctx.BASE.methods.balanceOf(hacker).call()) 87 | let hackerQuoteBalance = new BigNumber(await ctx.QUOTE.methods.balanceOf(hacker).call()) 88 | 89 | assert.ok(hackerBaseBalance.isLessThanOrEqualTo(hackerInitBaseBalance)) 90 | assert.ok(hackerQuoteBalance.isLessThanOrEqualTo(hackerInitQuoteBalance)) 91 | 92 | // expected lp no loss 93 | let lpBaseBalance = new BigNumber(await ctx.DODO.methods.getLpBaseBalance(lp1).call()) 94 | let lpQuoteBalance = new BigNumber(await ctx.DODO.methods.getLpQuoteBalance(lp1).call()) 95 | 96 | assert.ok(lpBaseBalance.isGreaterThanOrEqualTo(decimalStr("10"))) 97 | assert.ok(lpQuoteBalance.isGreaterThanOrEqualTo(decimalStr("1000"))) 98 | }) 99 | 100 | it("attack on quote token", async () => { 101 | await ctx.DODO.methods.depositBase(decimalStr("10")).send(ctx.sendParam(lp1)) 102 | await ctx.DODO.methods.depositQuote(decimalStr("1000")).send(ctx.sendParam(lp1)) 103 | let hackerInitBaseBalance = new BigNumber(await ctx.BASE.methods.balanceOf(hacker).call()) 104 | let hackerInitQuoteBalance = new BigNumber(await ctx.QUOTE.methods.balanceOf(hacker).call()) 105 | 106 | // attack step 1 107 | await ctx.DODO.methods.depositQuote(decimalStr("100000")).send(ctx.sendParam(hacker)) 108 | // attack step 2 109 | await ctx.DODO.methods.sellBaseToken(decimalStr("9"), decimalStr("500"), "0x").send(ctx.sendParam(hacker)) 110 | // attack step 3 111 | await ctx.DODO.methods.withdrawQuote(decimalStr("100000")).send(ctx.sendParam(hacker)) 112 | // attack step 4 113 | let hackerTempBaseBalance = new BigNumber(await ctx.BASE.methods.balanceOf(hacker).call()) 114 | if (hackerTempBaseBalance.isGreaterThan(hackerInitBaseBalance)) { 115 | await ctx.DODO.methods.sellBaseToken(hackerTempBaseBalance.minus(hackerInitBaseBalance).toString(), "0", "0x").send(ctx.sendParam(hacker)) 116 | } else { 117 | await ctx.DODO.methods.buyBaseToken(hackerInitBaseBalance.minus(hackerTempBaseBalance).toString(), decimalStr("5000"), "0x").send(ctx.sendParam(hacker)) 118 | } 119 | 120 | // expected hacker no profit 121 | let hackerBaseBalance = new BigNumber(await ctx.BASE.methods.balanceOf(hacker).call()) 122 | let hackerQuoteBalance = new BigNumber(await ctx.QUOTE.methods.balanceOf(hacker).call()) 123 | 124 | assert.ok(hackerBaseBalance.isLessThanOrEqualTo(hackerInitBaseBalance)) 125 | assert.ok(hackerQuoteBalance.isLessThanOrEqualTo(hackerInitQuoteBalance)) 126 | 127 | // expected lp no loss 128 | let lpBaseBalance = new BigNumber(await ctx.DODO.methods.getLpBaseBalance(lp1).call()) 129 | let lpQuoteBalance = new BigNumber(await ctx.DODO.methods.getLpQuoteBalance(lp1).call()) 130 | 131 | assert.ok(lpBaseBalance.isGreaterThanOrEqualTo(decimalStr("10"))) 132 | assert.ok(lpQuoteBalance.isGreaterThanOrEqualTo(decimalStr("1000"))) 133 | }) 134 | }) 135 | 136 | describe("Front run attack", () => { 137 | /* 138 | attack describe: 139 | hacker tries to front run oracle updating by sending tx with higher gas price 140 | 141 | expected: 142 | revert tx 143 | */ 144 | it("front run", async () => { 145 | await ctx.DODO.methods.depositBase(decimalStr("10")).send(ctx.sendParam(lp1)) 146 | await ctx.DODO.methods.depositQuote(decimalStr("1000")).send(ctx.sendParam(lp1)) 147 | await assert.rejects( 148 | ctx.DODO.methods.buyBaseToken(decimalStr("1"), decimalStr("200"), "0x").send({ from: trader, gas: 300000, gasPrice: gweiStr("200") }), /GAS_PRICE_EXCEED/ 149 | ) 150 | await assert.rejects( 151 | ctx.DODO.methods.sellBaseToken(decimalStr("1"), decimalStr("200"), "0x").send({ from: trader, gas: 300000, gasPrice: gweiStr("200") }), /GAS_PRICE_EXCEED/ 152 | ) 153 | }) 154 | 155 | }) 156 | 157 | }) -------------------------------------------------------------------------------- /test/DODOEthProxyAsBase.test.ts: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2020 DODO ZOO. 4 | SPDX-License-Identifier: Apache-2.0 5 | 6 | */ 7 | import * as assert from 'assert'; 8 | import { BigNumber } from 'bignumber.js'; 9 | import { TransactionReceipt } from 'web3-core'; 10 | import { Contract } from 'web3-eth-contract'; 11 | 12 | import { 13 | DefaultDODOContextInitConfig, 14 | DODOContext, 15 | getDODOContext, 16 | } from './utils/Context'; 17 | import * as contracts from './utils/Contracts'; 18 | import { decimalStr, MAX_UINT256 } from './utils/Converter'; 19 | import { logGas } from './utils/Log'; 20 | 21 | let lp: string; 22 | let trader: string; 23 | let DODOEthProxy: Contract; 24 | 25 | async function init(ctx: DODOContext): Promise { 26 | // switch ctx to eth proxy mode 27 | const WETH = await contracts.newContract(contracts.WETH_CONTRACT_NAME); 28 | await ctx.DODOZoo.methods 29 | .breedDODO( 30 | ctx.Maintainer, 31 | WETH.options.address, 32 | ctx.QUOTE.options.address, 33 | ctx.ORACLE.options.address, 34 | DefaultDODOContextInitConfig.lpFeeRate, 35 | DefaultDODOContextInitConfig.mtFeeRate, 36 | DefaultDODOContextInitConfig.k, 37 | DefaultDODOContextInitConfig.gasPriceLimit 38 | ) 39 | .send(ctx.sendParam(ctx.Deployer)); 40 | 41 | ctx.DODO = contracts.getContractWithAddress( 42 | contracts.DODO_CONTRACT_NAME, 43 | await ctx.DODOZoo.methods 44 | .getDODO(WETH.options.address, ctx.QUOTE.options.address) 45 | .call() 46 | ); 47 | await ctx.DODO.methods.enableBaseDeposit().send(ctx.sendParam(ctx.Deployer)); 48 | await ctx.DODO.methods.enableQuoteDeposit().send(ctx.sendParam(ctx.Deployer)); 49 | await ctx.DODO.methods.enableTrading().send(ctx.sendParam(ctx.Deployer)); 50 | 51 | ctx.BASE = WETH; 52 | 53 | DODOEthProxy = await contracts.newContract( 54 | contracts.DODO_ETH_PROXY_CONTRACT_NAME, 55 | [ctx.DODOZoo.options.address, WETH.options.address] 56 | ); 57 | 58 | // env 59 | lp = ctx.spareAccounts[0]; 60 | trader = ctx.spareAccounts[1]; 61 | await ctx.setOraclePrice(decimalStr("100")); 62 | await ctx.approveDODO(lp); 63 | await ctx.approveDODO(trader); 64 | 65 | await ctx.QUOTE.methods 66 | .mint(lp, decimalStr("1000")) 67 | .send(ctx.sendParam(ctx.Deployer)); 68 | await ctx.QUOTE.methods 69 | .mint(trader, decimalStr("1000")) 70 | .send(ctx.sendParam(ctx.Deployer)); 71 | await ctx.QUOTE.methods 72 | .approve(DODOEthProxy.options.address, MAX_UINT256) 73 | .send(ctx.sendParam(trader)); 74 | 75 | await ctx.DODO.methods 76 | .depositQuote(decimalStr("1000")) 77 | .send(ctx.sendParam(lp)); 78 | } 79 | 80 | describe("DODO ETH PROXY", () => { 81 | let snapshotId: string; 82 | let ctx: DODOContext; 83 | 84 | before(async () => { 85 | ctx = await getDODOContext(); 86 | await init(ctx); 87 | await ctx.QUOTE.methods 88 | .approve(DODOEthProxy.options.address, MAX_UINT256) 89 | .send(ctx.sendParam(trader)); 90 | }); 91 | 92 | beforeEach(async () => { 93 | snapshotId = await ctx.EVM.snapshot(); 94 | const depositAmount = "10"; 95 | await DODOEthProxy.methods 96 | .depositEthAsBase(decimalStr(depositAmount), ctx.QUOTE.options.address) 97 | .send(ctx.sendParam(lp, depositAmount)); 98 | }); 99 | 100 | afterEach(async () => { 101 | await ctx.EVM.reset(snapshotId); 102 | }); 103 | 104 | describe("buy&sell eth directly", () => { 105 | it("buy", async () => { 106 | const buyAmount = "1"; 107 | await logGas( 108 | DODOEthProxy.methods.buyEthWithToken( 109 | ctx.QUOTE.options.address, 110 | decimalStr(buyAmount), 111 | decimalStr("200") 112 | ), 113 | ctx.sendParam(trader), 114 | "buy ETH with token directly" 115 | ); 116 | assert.strictEqual( 117 | await ctx.DODO.methods._BASE_BALANCE_().call(), 118 | decimalStr("8.999") 119 | ); 120 | assert.strictEqual( 121 | await ctx.QUOTE.methods.balanceOf(trader).call(), 122 | "898581839502056240973" 123 | ); 124 | }); 125 | it("sell", async () => { 126 | const sellAmount = "1"; 127 | await logGas( 128 | DODOEthProxy.methods.sellEthToToken( 129 | ctx.QUOTE.options.address, 130 | decimalStr(sellAmount), 131 | decimalStr("50") 132 | ), 133 | ctx.sendParam(trader, sellAmount), 134 | "sell ETH to token directly" 135 | ); 136 | assert.strictEqual( 137 | await ctx.DODO.methods._BASE_BALANCE_().call(), 138 | decimalStr("11") 139 | ); 140 | assert.strictEqual( 141 | await ctx.QUOTE.methods.balanceOf(trader).call(), 142 | "1098617454226610630663" 143 | ); 144 | }); 145 | }); 146 | 147 | describe("withdraw eth directly", () => { 148 | it("withdraw", async () => { 149 | const withdrawAmount = decimalStr("5"); 150 | const baseLpTokenAddress = await ctx.DODO.methods 151 | ._BASE_CAPITAL_TOKEN_() 152 | .call(); 153 | const baseLpToken = contracts.getContractWithAddress( 154 | contracts.TEST_ERC20_CONTRACT_NAME, 155 | baseLpTokenAddress 156 | ); 157 | await baseLpToken.methods 158 | .approve(DODOEthProxy.options.address, MAX_UINT256) 159 | .send(ctx.sendParam(lp)); 160 | const lpEthBalanceBefore = await ctx.Web3.eth.getBalance(lp); 161 | const txReceipt: TransactionReceipt = await DODOEthProxy.methods 162 | .withdrawEthAsBase(withdrawAmount, ctx.QUOTE.options.address) 163 | .send(ctx.sendParam(lp)); 164 | 165 | assert.strictEqual( 166 | await ctx.DODO.methods.getLpBaseBalance(lp).call(), 167 | withdrawAmount 168 | ); 169 | const tx = await ctx.Web3.eth.getTransaction(txReceipt.transactionHash); 170 | const ethSpentOnGas = new BigNumber(tx.gasPrice).times(txReceipt.gasUsed); 171 | const lpEthBalanceAfter = await ctx.Web3.eth.getBalance(lp); 172 | assert.ok( 173 | new BigNumber(lpEthBalanceBefore) 174 | .plus(withdrawAmount) 175 | .minus(ethSpentOnGas) 176 | .eq(lpEthBalanceAfter) 177 | ); 178 | }); 179 | 180 | it("withdraw all", async () => { 181 | const withdrawAmount = decimalStr("10"); 182 | const baseLpTokenAddress = await ctx.DODO.methods 183 | ._BASE_CAPITAL_TOKEN_() 184 | .call(); 185 | const baseLpToken = contracts.getContractWithAddress( 186 | contracts.TEST_ERC20_CONTRACT_NAME, 187 | baseLpTokenAddress 188 | ); 189 | await baseLpToken.methods 190 | .approve(DODOEthProxy.options.address, MAX_UINT256) 191 | .send(ctx.sendParam(lp)); 192 | const lpEthBalanceBefore = await ctx.Web3.eth.getBalance(lp); 193 | const txReceipt: TransactionReceipt = await DODOEthProxy.methods 194 | .withdrawAllEthAsBase(ctx.QUOTE.options.address) 195 | .send(ctx.sendParam(lp)); 196 | 197 | assert.strictEqual( 198 | await ctx.DODO.methods.getLpBaseBalance(lp).call(), 199 | "0" 200 | ); 201 | const tx = await ctx.Web3.eth.getTransaction(txReceipt.transactionHash); 202 | const ethSpentOnGas = new BigNumber(tx.gasPrice).times(txReceipt.gasUsed); 203 | const lpEthBalanceAfter = await ctx.Web3.eth.getBalance(lp); 204 | assert.ok( 205 | new BigNumber(lpEthBalanceBefore) 206 | .plus(withdrawAmount) 207 | .minus(ethSpentOnGas) 208 | .eq(lpEthBalanceAfter) 209 | ); 210 | }); 211 | }); 212 | 213 | describe("revert cases", () => { 214 | it("value not match", async () => { 215 | await assert.rejects( 216 | DODOEthProxy.methods 217 | .sellEthToToken( 218 | ctx.QUOTE.options.address, 219 | decimalStr("1"), 220 | decimalStr("50") 221 | ) 222 | .send(ctx.sendParam(trader, "2")), 223 | /ETH_AMOUNT_NOT_MATCH/ 224 | ); 225 | await assert.rejects( 226 | DODOEthProxy.methods 227 | .depositEthAsBase(decimalStr("1"), ctx.QUOTE.options.address) 228 | .send(ctx.sendParam(lp, "2")), 229 | /ETH_AMOUNT_NOT_MATCH/ 230 | ); 231 | }); 232 | }); 233 | }); 234 | -------------------------------------------------------------------------------- /test/DODOEthProxyAsQuote.test.ts: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2020 DODO ZOO. 4 | SPDX-License-Identifier: Apache-2.0 5 | 6 | */ 7 | import * as assert from 'assert'; 8 | import BigNumber from 'bignumber.js'; 9 | import { TransactionReceipt } from 'web3-core'; 10 | import { Contract } from 'web3-eth-contract'; 11 | 12 | import { 13 | DefaultDODOContextInitConfig, 14 | DODOContext, 15 | getDODOContext, 16 | } from './utils/Context'; 17 | import * as contracts from './utils/Contracts'; 18 | import { decimalStr, MAX_UINT256 } from './utils/Converter'; 19 | import { logGas } from './utils/Log'; 20 | 21 | let lp: string; 22 | let trader: string; 23 | let DODOEthProxy: Contract; 24 | 25 | async function init(ctx: DODOContext): Promise { 26 | // switch ctx to eth proxy mode 27 | const WETH = await contracts.newContract(contracts.WETH_CONTRACT_NAME); 28 | await ctx.DODOZoo.methods 29 | .breedDODO( 30 | ctx.Maintainer, 31 | ctx.BASE.options.address, 32 | WETH.options.address, 33 | ctx.ORACLE.options.address, 34 | DefaultDODOContextInitConfig.lpFeeRate, 35 | DefaultDODOContextInitConfig.mtFeeRate, 36 | DefaultDODOContextInitConfig.k, 37 | DefaultDODOContextInitConfig.gasPriceLimit 38 | ) 39 | .send(ctx.sendParam(ctx.Deployer)); 40 | 41 | ctx.DODO = contracts.getContractWithAddress( 42 | contracts.DODO_CONTRACT_NAME, 43 | await ctx.DODOZoo.methods 44 | .getDODO(ctx.BASE.options.address, WETH.options.address) 45 | .call() 46 | ); 47 | await ctx.DODO.methods.enableBaseDeposit().send(ctx.sendParam(ctx.Deployer)); 48 | await ctx.DODO.methods.enableQuoteDeposit().send(ctx.sendParam(ctx.Deployer)); 49 | await ctx.DODO.methods.enableTrading().send(ctx.sendParam(ctx.Deployer)); 50 | 51 | ctx.QUOTE = WETH; 52 | 53 | DODOEthProxy = await contracts.newContract( 54 | contracts.DODO_ETH_PROXY_CONTRACT_NAME, 55 | [ctx.DODOZoo.options.address, WETH.options.address] 56 | ); 57 | 58 | // env 59 | lp = ctx.spareAccounts[0]; 60 | trader = ctx.spareAccounts[1]; 61 | await ctx.setOraclePrice(decimalStr("0.01")); 62 | await ctx.approveDODO(lp); 63 | await ctx.approveDODO(trader); 64 | 65 | await ctx.BASE.methods 66 | .mint(lp, decimalStr("1000")) 67 | .send(ctx.sendParam(ctx.Deployer)); 68 | await ctx.BASE.methods 69 | .mint(trader, decimalStr("1000")) 70 | .send(ctx.sendParam(ctx.Deployer)); 71 | await ctx.BASE.methods 72 | .approve(DODOEthProxy.options.address, MAX_UINT256) 73 | .send(ctx.sendParam(trader)); 74 | 75 | await ctx.DODO.methods 76 | .depositBase(decimalStr("1000")) 77 | .send(ctx.sendParam(lp)); 78 | } 79 | 80 | describe("DODO ETH PROXY", () => { 81 | let snapshotId: string; 82 | let ctx: DODOContext; 83 | 84 | before(async () => { 85 | ctx = await getDODOContext(); 86 | await init(ctx); 87 | await ctx.BASE.methods 88 | .approve(DODOEthProxy.options.address, MAX_UINT256) 89 | .send(ctx.sendParam(trader)); 90 | }); 91 | 92 | beforeEach(async () => { 93 | snapshotId = await ctx.EVM.snapshot(); 94 | let depositAmount = "10"; 95 | await DODOEthProxy.methods 96 | .depositEthAsQuote(decimalStr(depositAmount), ctx.BASE.options.address) 97 | .send(ctx.sendParam(lp, depositAmount)); 98 | }); 99 | 100 | afterEach(async () => { 101 | await ctx.EVM.reset(snapshotId); 102 | }); 103 | 104 | describe("buy&sell eth directly", () => { 105 | it("buy", async () => { 106 | const maxPayEthAmount = "2.1"; 107 | const ethInPoolBefore = decimalStr("10"); 108 | const traderEthBalanceBefore = await ctx.Web3.eth.getBalance(trader); 109 | const txReceipt: TransactionReceipt = await logGas( 110 | DODOEthProxy.methods.buyTokenWithEth( 111 | ctx.BASE.options.address, 112 | decimalStr("200"), 113 | decimalStr(maxPayEthAmount) 114 | ), 115 | ctx.sendParam(trader, maxPayEthAmount), 116 | "buy token with ETH directly" 117 | ); 118 | const ethInPoolAfter = "12056338203652739553"; 119 | assert.strictEqual( 120 | await ctx.DODO.methods._QUOTE_BALANCE_().call(), 121 | ethInPoolAfter 122 | ); 123 | assert.strictEqual( 124 | await ctx.BASE.methods.balanceOf(trader).call(), 125 | decimalStr("1200") 126 | ); 127 | const tx = await ctx.Web3.eth.getTransaction(txReceipt.transactionHash); 128 | const ethSpentOnGas = new BigNumber(tx.gasPrice).times(txReceipt.gasUsed); 129 | const traderEthBalanceAfter = await ctx.Web3.eth.getBalance(trader); 130 | 131 | const totalEthBefore = new BigNumber(traderEthBalanceBefore).plus( 132 | ethInPoolBefore 133 | ); 134 | const totalEthAfter = new BigNumber(traderEthBalanceAfter) 135 | .plus(ethSpentOnGas) 136 | .plus(ethInPoolAfter); 137 | assert.ok(totalEthBefore.eq(totalEthAfter)); 138 | }); 139 | it("sell", async () => { 140 | const minReceiveEthAmount = "0.45"; 141 | await logGas( 142 | DODOEthProxy.methods.sellTokenToEth( 143 | ctx.BASE.options.address, 144 | decimalStr("50"), 145 | decimalStr(minReceiveEthAmount) 146 | ), 147 | ctx.sendParam(trader), 148 | "sell token to ETH directly" 149 | ); 150 | assert.strictEqual( 151 | await ctx.DODO.methods._QUOTE_BALANCE_().call(), 152 | "9503598324131652490" 153 | ); 154 | assert.strictEqual( 155 | await ctx.BASE.methods.balanceOf(trader).call(), 156 | decimalStr("950") 157 | ); 158 | }); 159 | }); 160 | 161 | describe("withdraw eth directly", () => { 162 | it("withdraw", async () => { 163 | const withdrawAmount = decimalStr("5"); 164 | const quoteLpTokenAddress = await ctx.DODO.methods 165 | ._QUOTE_CAPITAL_TOKEN_() 166 | .call(); 167 | const quoteLpToken = contracts.getContractWithAddress( 168 | contracts.TEST_ERC20_CONTRACT_NAME, 169 | quoteLpTokenAddress 170 | ); 171 | await quoteLpToken.methods 172 | .approve(DODOEthProxy.options.address, MAX_UINT256) 173 | .send(ctx.sendParam(lp)); 174 | const lpEthBalanceBefore = await ctx.Web3.eth.getBalance(lp); 175 | const txReceipt: TransactionReceipt = await DODOEthProxy.methods 176 | .withdrawEthAsQuote(withdrawAmount, ctx.BASE.options.address) 177 | .send(ctx.sendParam(lp)); 178 | 179 | assert.strictEqual( 180 | await ctx.DODO.methods.getLpQuoteBalance(lp).call(), 181 | withdrawAmount 182 | ); 183 | const tx = await ctx.Web3.eth.getTransaction(txReceipt.transactionHash); 184 | const ethSpentOnGas = new BigNumber(tx.gasPrice).times(txReceipt.gasUsed); 185 | const lpEthBalanceAfter = await ctx.Web3.eth.getBalance(lp); 186 | assert.ok( 187 | new BigNumber(lpEthBalanceBefore) 188 | .plus(withdrawAmount) 189 | .minus(ethSpentOnGas) 190 | .eq(lpEthBalanceAfter) 191 | ); 192 | }); 193 | 194 | it("withdraw all", async () => { 195 | const withdrawAmount = decimalStr("10"); 196 | const quoteLpTokenAddress = await ctx.DODO.methods 197 | ._QUOTE_CAPITAL_TOKEN_() 198 | .call(); 199 | const quoteLpToken = contracts.getContractWithAddress( 200 | contracts.TEST_ERC20_CONTRACT_NAME, 201 | quoteLpTokenAddress 202 | ); 203 | await quoteLpToken.methods 204 | .approve(DODOEthProxy.options.address, MAX_UINT256) 205 | .send(ctx.sendParam(lp)); 206 | const lpEthBalanceBefore = await ctx.Web3.eth.getBalance(lp); 207 | const txReceipt: TransactionReceipt = await DODOEthProxy.methods 208 | .withdrawAllEthAsQuote(ctx.BASE.options.address) 209 | .send(ctx.sendParam(lp)); 210 | 211 | assert.strictEqual( 212 | await ctx.DODO.methods.getLpQuoteBalance(lp).call(), 213 | "0" 214 | ); 215 | const tx = await ctx.Web3.eth.getTransaction(txReceipt.transactionHash); 216 | const ethSpentOnGas = new BigNumber(tx.gasPrice).times(txReceipt.gasUsed); 217 | const lpEthBalanceAfter = await ctx.Web3.eth.getBalance(lp); 218 | assert.ok( 219 | new BigNumber(lpEthBalanceBefore) 220 | .plus(withdrawAmount) 221 | .minus(ethSpentOnGas) 222 | .eq(lpEthBalanceAfter) 223 | ); 224 | }); 225 | }); 226 | 227 | describe("revert cases", () => { 228 | it("value not match", async () => { 229 | await assert.rejects( 230 | DODOEthProxy.methods 231 | .buyTokenWithEth( 232 | ctx.BASE.options.address, 233 | decimalStr("50"), 234 | decimalStr("1") 235 | ) 236 | .send(ctx.sendParam(trader, "2")), 237 | /ETH_AMOUNT_NOT_MATCH/ 238 | ); 239 | await assert.rejects( 240 | DODOEthProxy.methods 241 | .depositEthAsQuote(decimalStr("1"), ctx.BASE.options.address) 242 | .send(ctx.sendParam(lp, "2")), 243 | /ETH_AMOUNT_NOT_MATCH/ 244 | ); 245 | }); 246 | }); 247 | }); 248 | -------------------------------------------------------------------------------- /test/DODOZoo.test.ts: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2020 DODO ZOO. 4 | SPDX-License-Identifier: Apache-2.0 5 | 6 | */ 7 | 8 | import { DODOContext, getDODOContext } from './utils/Context'; 9 | import * as assert from "assert" 10 | import { newContract, TEST_ERC20_CONTRACT_NAME, getContractWithAddress, DODO_CONTRACT_NAME } from './utils/Contracts'; 11 | 12 | 13 | async function init(ctx: DODOContext): Promise { } 14 | 15 | describe("DODO ZOO", () => { 16 | 17 | let snapshotId: string 18 | let ctx: DODOContext 19 | 20 | before(async () => { 21 | ctx = await getDODOContext() 22 | await init(ctx); 23 | }) 24 | 25 | beforeEach(async () => { 26 | snapshotId = await ctx.EVM.snapshot(); 27 | }); 28 | 29 | afterEach(async () => { 30 | await ctx.EVM.reset(snapshotId) 31 | }); 32 | 33 | describe("Breed new dodo", () => { 34 | it("could not deploy the same dodo", async () => { 35 | await assert.rejects( 36 | ctx.DODOZoo.methods.breedDODO(ctx.Maintainer, ctx.BASE.options.address, ctx.QUOTE.options.address, ctx.ORACLE.options.address, "0", "0", "1", "0").send(ctx.sendParam(ctx.Deployer)), 37 | /DODO_REGISTERED/ 38 | ) 39 | 40 | await assert.rejects( 41 | ctx.DODOZoo.methods.breedDODO(ctx.Maintainer, ctx.QUOTE.options.address, ctx.BASE.options.address, ctx.ORACLE.options.address, "0", "0", "1", "0").send(ctx.sendParam(ctx.Deployer)), 42 | /DODO_REGISTERED/ 43 | ) 44 | }) 45 | 46 | it("breed new dodo", async () => { 47 | let newBase = await newContract(TEST_ERC20_CONTRACT_NAME, ["AnotherBase", 18]) 48 | let newQuote = await newContract(TEST_ERC20_CONTRACT_NAME, ["AnotherQuote", 18]) 49 | await assert.rejects( 50 | ctx.DODOZoo.methods.breedDODO(ctx.Maintainer, newBase.options.address, newQuote.options.address, ctx.ORACLE.options.address, "0", "0", "1", "0").send(ctx.sendParam(ctx.Maintainer)), 51 | /NOT_OWNER/ 52 | ) 53 | await ctx.DODOZoo.methods.breedDODO(ctx.Maintainer, newBase.options.address, newQuote.options.address, ctx.ORACLE.options.address, "0", "0", "1", "0").send(ctx.sendParam(ctx.Deployer)) 54 | 55 | let newDODO = getContractWithAddress(DODO_CONTRACT_NAME, await ctx.DODOZoo.methods.getDODO(newBase.options.address, newQuote.options.address).call()) 56 | assert.equal(await newDODO.methods._BASE_TOKEN_().call(), newBase.options.address) 57 | assert.equal(await newDODO.methods._QUOTE_TOKEN_().call(), newQuote.options.address) 58 | 59 | // could not init twice 60 | await assert.rejects( 61 | newDODO.methods.init(ctx.Deployer, ctx.Supervisor, ctx.Maintainer, ctx.QUOTE.options.address, ctx.BASE.options.address, ctx.ORACLE.options.address, "0", "0", "1", "0").send(ctx.sendParam(ctx.Deployer)), 62 | /DODO_INITIALIZED/ 63 | ) 64 | 65 | // console.log(await ctx.DODOZoo.methods.getDODOs().call()) 66 | }) 67 | 68 | // it.only("remove dodo", async () => { 69 | // console.log(await ctx.DODOZoo.methods.getDODOs().call()) 70 | // await ctx.DODOZoo.methods.removeDODO(ctx.DODO.options.address).send(ctx.sendParam(ctx.Deployer)) 71 | // console.log(await ctx.DODOZoo.methods.getDODO(ctx.BASE.options.address, ctx.QUOTE.options.address).call()) 72 | // console.log(await ctx.DODOZoo.methods.getDODOs().call()) 73 | // }) 74 | 75 | it("dodo register control flow", async () => { 76 | await ctx.DODOZoo.methods.removeDODO(ctx.DODO.options.address).send(ctx.sendParam(ctx.Deployer)) 77 | assert.equal(await ctx.DODOZoo.methods.getDODO(ctx.BASE.options.address, ctx.QUOTE.options.address).call(), "0x0000000000000000000000000000000000000000") 78 | await assert.rejects( 79 | ctx.DODOZoo.methods.removeDODO(ctx.DODO.options.address).send(ctx.sendParam(ctx.Deployer)), 80 | /DODO_NOT_REGISTERED/ 81 | ) 82 | await ctx.DODOZoo.methods.addDODO(ctx.DODO.options.address).send(ctx.sendParam(ctx.Deployer)) 83 | assert.equal(await ctx.DODOZoo.methods.getDODO(ctx.BASE.options.address, ctx.QUOTE.options.address).call(), ctx.DODO.options.address) 84 | await assert.rejects( 85 | ctx.DODOZoo.methods.addDODO(ctx.DODO.options.address).send(ctx.sendParam(ctx.Deployer)), 86 | /DODO_REGISTERED/ 87 | ) 88 | }) 89 | 90 | }) 91 | }) -------------------------------------------------------------------------------- /test/LongTailTokenlMode.test.ts: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2020 DODO ZOO. 4 | SPDX-License-Identifier: Apache-2.0 5 | 6 | */ 7 | 8 | import { DODOContext, getDODOContext } from './utils/Context'; 9 | import { decimalStr, gweiStr } from './utils/Converter'; 10 | import * as assert from "assert" 11 | 12 | let lp: string 13 | let trader: string 14 | 15 | async function init(ctx: DODOContext): Promise { 16 | await ctx.setOraclePrice(decimalStr("10")) 17 | 18 | lp = ctx.spareAccounts[0] 19 | trader = ctx.spareAccounts[1] 20 | await ctx.approveDODO(lp) 21 | await ctx.approveDODO(trader) 22 | 23 | await ctx.mintTestToken(lp, decimalStr("10000"), decimalStr("10000000")) 24 | await ctx.mintTestToken(trader, decimalStr("0"), decimalStr("10000000")) 25 | 26 | await ctx.DODO.methods.depositBase(decimalStr("10000")).send(ctx.sendParam(lp)) 27 | } 28 | 29 | describe("Trader", () => { 30 | 31 | let snapshotId: string 32 | let ctx: DODOContext 33 | 34 | before(async () => { 35 | let dodoContextInitConfig = { 36 | lpFeeRate: decimalStr("0"), 37 | mtFeeRate: decimalStr("0"), 38 | k: decimalStr("0.99"), // nearly one 39 | gasPriceLimit: gweiStr("100"), 40 | } 41 | ctx = await getDODOContext(dodoContextInitConfig) 42 | await init(ctx); 43 | }) 44 | 45 | beforeEach(async () => { 46 | snapshotId = await ctx.EVM.snapshot(); 47 | }); 48 | 49 | afterEach(async () => { 50 | await ctx.EVM.reset(snapshotId) 51 | }); 52 | 53 | // price change quickly 54 | describe("Trade long tail coin", () => { 55 | it("price discover", async () => { 56 | // 10% depth 57 | // avg price = 11.137 58 | await ctx.DODO.methods.buyBaseToken(decimalStr("1000"), decimalStr("100000"), "0x").send(ctx.sendParam(trader)) 59 | assert.equal(await ctx.BASE.methods.balanceOf(trader).call(), decimalStr("1000")) 60 | assert.equal(await ctx.QUOTE.methods.balanceOf(trader).call(), "9988900000000000000000000") 61 | 62 | // 20% depth 63 | // avg price = 12.475 64 | await ctx.DODO.methods.buyBaseToken(decimalStr("1000"), decimalStr("100000"), "0x").send(ctx.sendParam(trader)) 65 | assert.equal(await ctx.BASE.methods.balanceOf(trader).call(), decimalStr("2000")) 66 | assert.equal(await ctx.QUOTE.methods.balanceOf(trader).call(), "9975049999999999999970000") 67 | 68 | // 50% depth 69 | // avg price = 19.9 70 | await ctx.DODO.methods.buyBaseToken(decimalStr("3000"), decimalStr("300000"), "0x").send(ctx.sendParam(trader)) 71 | assert.equal(await ctx.BASE.methods.balanceOf(trader).call(), decimalStr("5000")) 72 | assert.equal(await ctx.QUOTE.methods.balanceOf(trader).call(), "9900499999999999999970000") 73 | 74 | // 80% depth 75 | // avg price = 49.6 76 | await ctx.DODO.methods.buyBaseToken(decimalStr("3000"), decimalStr("300000"), "0x").send(ctx.sendParam(trader)) 77 | assert.equal(await ctx.BASE.methods.balanceOf(trader).call(), decimalStr("8000")) 78 | assert.equal(await ctx.QUOTE.methods.balanceOf(trader).call(), "9603199999999999999970000") 79 | }) 80 | 81 | it("user has no pnl if buy and sell immediately", async () => { 82 | // lp buy 83 | await ctx.DODO.methods.buyBaseToken(decimalStr("1000"), decimalStr("100000"), "0x").send(ctx.sendParam(lp)) 84 | 85 | // trader buy and sell 86 | await ctx.DODO.methods.buyBaseToken(decimalStr("1000"), decimalStr("100000"), "0x").send(ctx.sendParam(trader)) 87 | await ctx.DODO.methods.sellBaseToken(decimalStr("1000"), decimalStr("0"), "0x").send(ctx.sendParam(trader)) 88 | 89 | // no profit or loss (may have precision problems) 90 | assert.equal(await ctx.BASE.methods.balanceOf(trader).call(), "0") 91 | assert.equal(await ctx.QUOTE.methods.balanceOf(trader).call(), "9999999999999999999970000") 92 | }) 93 | }) 94 | }) -------------------------------------------------------------------------------- /test/Mining.test.ts: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2020 DODO ZOO. 4 | SPDX-License-Identifier: Apache-2.0 5 | 6 | */ 7 | 8 | import { DODOContext, getDODOContext } from './utils/Context'; 9 | import { decimalStr, MAX_UINT256 } from './utils/Converter'; 10 | // import * as assert from "assert" 11 | import { newContract, DODO_TOKEN_CONTRACT_NAME, DODO_MINE_NAME, TEST_ERC20_CONTRACT_NAME, getContractWithAddress, DODO_MINE_READER_NAME } from './utils/Contracts'; 12 | import { Contract } from 'web3-eth-contract'; 13 | import { assert } from 'chai'; 14 | import { logGas } from './utils/Log'; 15 | 16 | let BaseDLP: Contract 17 | let QuoteDLP: Contract 18 | let DODOToken: Contract 19 | let DODOMine: Contract 20 | let DODOMineReader: Contract 21 | let lp1: string; 22 | let lp2: string; 23 | 24 | async function init(ctx: DODOContext): Promise { 25 | 26 | lp1 = ctx.spareAccounts[0]; 27 | lp2 = ctx.spareAccounts[1]; 28 | await ctx.mintTestToken(lp1, decimalStr("100"), decimalStr("10000")); 29 | await ctx.mintTestToken(lp2, decimalStr("100"), decimalStr("10000")); 30 | 31 | await ctx.approveDODO(lp1); 32 | await ctx.approveDODO(lp2); 33 | 34 | await ctx.DODO.methods.depositBase(decimalStr("100")).send(ctx.sendParam(lp1)) 35 | await ctx.DODO.methods.depositQuote(decimalStr("10000")).send(ctx.sendParam(lp1)) 36 | 37 | await ctx.DODO.methods.depositBase(decimalStr("100")).send(ctx.sendParam(lp2)) 38 | await ctx.DODO.methods.depositQuote(decimalStr("10000")).send(ctx.sendParam(lp2)) 39 | 40 | DODOToken = await newContract(DODO_TOKEN_CONTRACT_NAME) 41 | DODOMine = await newContract(DODO_MINE_NAME, [DODOToken.options.address, (await ctx.Web3.eth.getBlockNumber()).toString()]) 42 | DODOMineReader = await newContract(DODO_MINE_READER_NAME) 43 | 44 | BaseDLP = await getContractWithAddress(TEST_ERC20_CONTRACT_NAME, await ctx.DODO.methods._BASE_CAPITAL_TOKEN_().call()) 45 | QuoteDLP = await getContractWithAddress(TEST_ERC20_CONTRACT_NAME, await ctx.DODO.methods._QUOTE_CAPITAL_TOKEN_().call()) 46 | 47 | await BaseDLP.methods.approve(DODOMine.options.address, MAX_UINT256).send(ctx.sendParam(lp1)) 48 | await QuoteDLP.methods.approve(DODOMine.options.address, MAX_UINT256).send(ctx.sendParam(lp1)) 49 | 50 | await BaseDLP.methods.approve(DODOMine.options.address, MAX_UINT256).send(ctx.sendParam(lp2)) 51 | await QuoteDLP.methods.approve(DODOMine.options.address, MAX_UINT256).send(ctx.sendParam(lp2)) 52 | 53 | await DODOMine.methods.setReward(decimalStr("100"), true).send(ctx.sendParam(ctx.Deployer)) 54 | await DODOMine.methods.addLpToken(BaseDLP.options.address, "1", true).send(ctx.sendParam(ctx.Deployer)) 55 | await DODOMine.methods.addLpToken(QuoteDLP.options.address, "2", true).send(ctx.sendParam(ctx.Deployer)) 56 | 57 | const rewardVault = await DODOMine.methods.dodoRewardVault().call() 58 | await DODOToken.methods.transfer(rewardVault, decimalStr("100000000")).send(ctx.sendParam(ctx.Deployer)) 59 | } 60 | 61 | describe("Lock DODO Token", () => { 62 | 63 | let snapshotId: string 64 | let ctx: DODOContext 65 | 66 | before(async () => { 67 | ctx = await getDODOContext() 68 | await init(ctx); 69 | }) 70 | 71 | beforeEach(async () => { 72 | snapshotId = await ctx.EVM.snapshot(); 73 | }); 74 | 75 | afterEach(async () => { 76 | await ctx.EVM.reset(snapshotId) 77 | }); 78 | 79 | describe("Lp Deposit", () => { 80 | it("single lp deposit", async () => { 81 | await logGas(DODOMine.methods.deposit(BaseDLP.options.address, decimalStr("100")), ctx.sendParam(lp1), "deposit") 82 | await ctx.EVM.fastMove(100) 83 | assert.equal(await DODOMine.methods.getPendingReward(BaseDLP.options.address, lp1).call(), "3333333333333333333300") 84 | assert.equal(await DODOMine.methods.getDlpMiningSpeed(BaseDLP.options.address).call(), "33333333333333333333") 85 | }) 86 | 87 | it("multi lp deposit", async () => { 88 | await DODOMine.methods.deposit(BaseDLP.options.address, decimalStr("100")).send(ctx.sendParam(lp1)) 89 | await ctx.EVM.fastMove(100) 90 | await DODOMine.methods.deposit(BaseDLP.options.address, decimalStr("100")).send(ctx.sendParam(lp2)) 91 | await ctx.EVM.fastMove(100) 92 | assert.equal(await DODOMine.methods.getPendingReward(BaseDLP.options.address, lp1).call(), "5033333333333333333200") 93 | assert.equal(await DODOMine.methods.getPendingReward(BaseDLP.options.address, lp2).call(), "1666666666666666666600") 94 | 95 | await DODOMine.methods.deposit(QuoteDLP.options.address, decimalStr("100")).send(ctx.sendParam(lp1)) 96 | await ctx.EVM.fastMove(100) 97 | await DODOMine.methods.deposit(QuoteDLP.options.address, decimalStr("100")).send(ctx.sendParam(lp2)) 98 | await ctx.EVM.fastMove(100) 99 | assert.equal(await DODOMine.methods.getPendingReward(QuoteDLP.options.address, lp1).call(), "10066666666666666666600") 100 | assert.equal(await DODOMine.methods.getPendingReward(QuoteDLP.options.address, lp2).call(), "3333333333333333333300") 101 | 102 | assert.equal(await DODOMine.methods.getAllPendingReward(lp1).call(), "18466666666666666666500") 103 | assert.equal(await DODOMine.methods.getAllPendingReward(lp2).call(), "8366666666666666666600") 104 | }) 105 | 106 | it.only("lp multi deposit and withdraw", async () => { 107 | await DODOMine.methods.deposit(BaseDLP.options.address, decimalStr("100")).send(ctx.sendParam(lp2)) 108 | await DODOMine.methods.deposit(BaseDLP.options.address, decimalStr("100")).send(ctx.sendParam(lp1)) 109 | await ctx.EVM.fastMove(100) 110 | await logGas(DODOMine.methods.withdraw(BaseDLP.options.address, decimalStr("50")), ctx.sendParam(lp1), "withdraw") 111 | assert.equal(await DODOMine.methods.getAllPendingReward(lp1).call(), "0") 112 | assert.equal(await DODOToken.methods.balanceOf(lp1).call(), "1683333333333333333300") 113 | assert.equal(await DODOMine.methods.getRealizedReward(lp1).call(), "1683333333333333333300") 114 | await ctx.EVM.fastMove(100) 115 | await DODOMine.methods.deposit(BaseDLP.options.address, decimalStr("50")).send(ctx.sendParam(lp1)) 116 | assert.equal(await DODOMine.methods.getAllPendingReward(lp1).call(), "0") 117 | assert.equal(await DODOToken.methods.balanceOf(lp1).call(), "2805555555555555555500") 118 | assert.equal(await DODOMine.methods.getRealizedReward(lp1).call(), "2805555555555555555500") 119 | 120 | var balance = await DODOMineReader.methods.getUserStakedBalance(DODOMine.options.address, ctx.DODO.options.address, lp1).call() 121 | assert.equal(balance.baseBalance, decimalStr("100")) 122 | assert.equal(balance.quoteBalance, decimalStr("0")) 123 | }) 124 | 125 | it("lp claim", async () => { 126 | await DODOMine.methods.deposit(BaseDLP.options.address, decimalStr("100")).send(ctx.sendParam(lp1)) 127 | await DODOMine.methods.deposit(BaseDLP.options.address, decimalStr("100")).send(ctx.sendParam(lp2)) 128 | 129 | await DODOMine.methods.deposit(QuoteDLP.options.address, decimalStr("100")).send(ctx.sendParam(lp1)) 130 | await DODOMine.methods.deposit(QuoteDLP.options.address, decimalStr("100")).send(ctx.sendParam(lp2)) 131 | 132 | await ctx.EVM.fastMove(100) 133 | 134 | await logGas(DODOMine.methods.claim(BaseDLP.options.address), ctx.sendParam(lp1), "claim") 135 | assert.equal(await DODOMine.methods.getPendingReward(BaseDLP.options.address, lp1).call(), "0") 136 | assert.equal(await DODOMine.methods.getAllPendingReward(lp1).call(), "3433333333333333333200") 137 | assert.equal(await DODOMine.methods.getRealizedReward(lp1).call(), "1749999999999999999900") 138 | assert.equal(await DODOToken.methods.balanceOf(lp1).call(), "1749999999999999999900") 139 | 140 | await logGas(DODOMine.methods.claimAll(), ctx.sendParam(lp2), "claim 2 pool") 141 | assert.equal(await DODOMine.methods.getPendingReward(BaseDLP.options.address, lp2).call(), "0") 142 | assert.equal(await DODOMine.methods.getAllPendingReward(lp2).call(), "0") 143 | assert.equal(await DODOMine.methods.getRealizedReward(lp2).call(), "5133333333333333333200") 144 | assert.equal(await DODOToken.methods.balanceOf(lp2).call(), "5133333333333333333200") 145 | }) 146 | 147 | it("lp emergency withdraw", async () => { 148 | await DODOMine.methods.deposit(QuoteDLP.options.address, decimalStr("100")).send(ctx.sendParam(lp1)) 149 | 150 | await ctx.EVM.fastMove(100) 151 | 152 | await DODOMine.methods.emergencyWithdraw(QuoteDLP.options.address).send(ctx.sendParam(lp1)) 153 | 154 | assert.equal(await QuoteDLP.methods.balanceOf(lp1).call(), decimalStr("10000")) 155 | assert.equal(await DODOMine.methods.getPendingReward(QuoteDLP.options.address, lp1).call(), "0") 156 | assert.equal(await DODOMine.methods.getAllPendingReward(lp1).call(), "0") 157 | assert.equal(await DODOMine.methods.getRealizedReward(lp1).call(), "0") 158 | assert.equal(await DODOToken.methods.balanceOf(lp1).call(), "0") 159 | }) 160 | 161 | it("setLpToken", async () => { 162 | await DODOMine.methods.deposit(BaseDLP.options.address, decimalStr("100")).send(ctx.sendParam(lp1)) 163 | await ctx.EVM.fastMove(100) 164 | await DODOMine.methods.setLpToken(BaseDLP.options.address, "2", true).send(ctx.sendParam(ctx.Deployer)) 165 | await ctx.EVM.fastMove(100) 166 | 167 | assert.equal(await DODOMine.methods.getAllPendingReward(lp1).call(), "8366666666666666666600") 168 | }) 169 | 170 | }) 171 | 172 | }) -------------------------------------------------------------------------------- /test/StableCoinMode.test.ts: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2020 DODO ZOO. 4 | SPDX-License-Identifier: Apache-2.0 5 | 6 | */ 7 | 8 | import { DODOContext, getDODOContext } from './utils/Context'; 9 | import { decimalStr, gweiStr } from './utils/Converter'; 10 | import * as assert from "assert" 11 | 12 | let lp: string 13 | let trader: string 14 | 15 | async function init(ctx: DODOContext): Promise { 16 | await ctx.setOraclePrice(decimalStr("1")) 17 | 18 | lp = ctx.spareAccounts[0] 19 | trader = ctx.spareAccounts[1] 20 | await ctx.approveDODO(lp) 21 | await ctx.approveDODO(trader) 22 | 23 | await ctx.mintTestToken(lp, decimalStr("10000"), decimalStr("10000")) 24 | await ctx.mintTestToken(trader, decimalStr("10000"), decimalStr("10000")) 25 | 26 | await ctx.DODO.methods.depositBase(decimalStr("10000")).send(ctx.sendParam(lp)) 27 | await ctx.DODO.methods.depositQuote(decimalStr("10000")).send(ctx.sendParam(lp)) 28 | } 29 | 30 | describe("Trader", () => { 31 | 32 | let snapshotId: string 33 | let ctx: DODOContext 34 | 35 | before(async () => { 36 | let dodoContextInitConfig = { 37 | lpFeeRate: decimalStr("0.0001"), 38 | mtFeeRate: decimalStr("0"), 39 | k: gweiStr("1"), // nearly zero 40 | gasPriceLimit: gweiStr("100"), 41 | } 42 | ctx = await getDODOContext(dodoContextInitConfig) 43 | await init(ctx); 44 | }) 45 | 46 | beforeEach(async () => { 47 | snapshotId = await ctx.EVM.snapshot(); 48 | }); 49 | 50 | afterEach(async () => { 51 | await ctx.EVM.reset(snapshotId) 52 | }); 53 | 54 | describe("Trade stable coin", () => { 55 | it("trade with tiny slippage", async () => { 56 | // 10% depth avg price 1.000100000111135 57 | await ctx.DODO.methods.buyBaseToken(decimalStr("1000"), decimalStr("1001"), "0x").send(ctx.sendParam(trader)) 58 | assert.equal(await ctx.BASE.methods.balanceOf(trader).call(), decimalStr("11000")) 59 | assert.equal(await ctx.QUOTE.methods.balanceOf(trader).call(), "8999899999888865431655") 60 | 61 | // 99.9% depth avg price 1.00010109 62 | await ctx.DODO.methods.buyBaseToken(decimalStr("8990"), decimalStr("10000"), "0x").send(ctx.sendParam(trader)) 63 | assert.equal(await ctx.BASE.methods.balanceOf(trader).call(), decimalStr("19990")) 64 | assert.equal(await ctx.QUOTE.methods.balanceOf(trader).call(), "8990031967806921648") 65 | 66 | // sell to 99.9% depth avg price 0.9999 67 | await ctx.DODO.methods.sellBaseToken(decimalStr("19980"), decimalStr("19970"), "0x").send(ctx.sendParam(trader)) 68 | assert.equal(await ctx.BASE.methods.balanceOf(trader).call(), decimalStr("10")) 69 | assert.equal(await ctx.QUOTE.methods.balanceOf(trader).call(), "19986992950440794518402") 70 | }) 71 | 72 | it("huge sell trading amount", async () => { 73 | // trader could sell any number of base token 74 | // but the price will drop quickly 75 | await ctx.mintTestToken(trader, decimalStr("10000"), decimalStr("0")) 76 | await ctx.DODO.methods.sellBaseToken(decimalStr("20000"), decimalStr("0"), "0x").send(ctx.sendParam(trader)) 77 | 78 | assert.equal(await ctx.BASE.methods.balanceOf(trader).call(), decimalStr("0")) 79 | assert.equal(await ctx.QUOTE.methods.balanceOf(trader).call(), "19998999990001000029997") 80 | }) 81 | 82 | it("huge buy trading amount", async () => { 83 | // could not buy all base balance 84 | await assert.rejects( 85 | ctx.DODO.methods.buyBaseToken(decimalStr("10000"), decimalStr("10010"), "0x").send(ctx.sendParam(trader)), 86 | /DODO_BASE_BALANCE_NOT_ENOUGH/ 87 | ) 88 | 89 | // when buy amount close to base balance, price will increase quickly 90 | await ctx.mintTestToken(trader, decimalStr("0"), decimalStr("10000")) 91 | await ctx.DODO.methods.buyBaseToken(decimalStr("9999"), decimalStr("20000"), "0x").send(ctx.sendParam(trader)) 92 | assert.equal(await ctx.BASE.methods.balanceOf(trader).call(), decimalStr("19999")) 93 | assert.equal(await ctx.QUOTE.methods.balanceOf(trader).call(), "9000000119999999900000") 94 | }) 95 | 96 | it("tiny withdraw penalty", async () => { 97 | await ctx.DODO.methods.buyBaseToken(decimalStr("9990"), decimalStr("10000"), "0x").send(ctx.sendParam(trader)) 98 | 99 | // penalty only 0.2% even if withdraw make pool utilization rate raise to 99.5% 100 | assert.equal(await ctx.DODO.methods.getWithdrawBasePenalty(decimalStr("5")).call(), "9981967500000000") 101 | }) 102 | }) 103 | }) -------------------------------------------------------------------------------- /test/TokenLock.test.ts: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2020 DODO ZOO. 4 | SPDX-License-Identifier: Apache-2.0 5 | 6 | */ 7 | 8 | import { DODOContext, getDODOContext } from './utils/Context'; 9 | import { decimalStr, MAX_UINT256 } from './utils/Converter'; 10 | // import * as assert from "assert" 11 | import { newContract, DODO_TOKEN_CONTRACT_NAME, LOCKED_TOKEN_VAULT_CONTRACT_NAME } from './utils/Contracts'; 12 | import { Contract } from 'web3-eth-contract'; 13 | import * as assert from 'assert'; 14 | import BigNumber from 'bignumber.js'; 15 | import { logGas } from './utils/Log'; 16 | 17 | let DODOToken: Contract 18 | let LockedTokenVault: Contract 19 | let initTime: any 20 | 21 | let u1: string 22 | let u2: string 23 | let u3: string 24 | 25 | async function init(ctx: DODOContext): Promise { 26 | u1 = ctx.spareAccounts[0]; 27 | u2 = ctx.spareAccounts[1]; 28 | u3 = ctx.spareAccounts[2]; 29 | 30 | initTime = (await ctx.Web3.eth.getBlock(await ctx.Web3.eth.getBlockNumber())).timestamp; 31 | DODOToken = await newContract(DODO_TOKEN_CONTRACT_NAME) 32 | 33 | // release after 1 day, cliff 10% and vest in 1 day 34 | LockedTokenVault = await newContract(LOCKED_TOKEN_VAULT_CONTRACT_NAME, [DODOToken.options.address, initTime + 86400, 86400, decimalStr("0.1")]) 35 | 36 | DODOToken.methods.approve(LockedTokenVault.options.address, MAX_UINT256).send(ctx.sendParam(ctx.Deployer)) 37 | LockedTokenVault.methods.deposit(decimalStr("10000")).send(ctx.sendParam(ctx.Deployer)) 38 | } 39 | 40 | describe("Lock DODO Token", () => { 41 | 42 | let snapshotId: string 43 | let ctx: DODOContext 44 | 45 | before(async () => { 46 | ctx = await getDODOContext() 47 | await init(ctx); 48 | }) 49 | 50 | beforeEach(async () => { 51 | snapshotId = await ctx.EVM.snapshot(); 52 | }); 53 | 54 | afterEach(async () => { 55 | await ctx.EVM.reset(snapshotId) 56 | }); 57 | 58 | describe("Lock operations", () => { 59 | it("init states", async () => { 60 | assert.equal(await LockedTokenVault.methods._UNDISTRIBUTED_AMOUNT_().call(), decimalStr("10000")) 61 | await logGas(LockedTokenVault.methods.grant( 62 | [u1], 63 | [decimalStr("100")] 64 | ), ctx.sendParam(ctx.Deployer), "grant 1 address") 65 | }) 66 | 67 | it("grant", async () => { 68 | await logGas(LockedTokenVault.methods.grant( 69 | [u1, u2, u3], 70 | [decimalStr("100"), decimalStr("200"), decimalStr("300")] 71 | ), ctx.sendParam(ctx.Deployer), "grant 3 address") 72 | 73 | assert.equal(await LockedTokenVault.methods._UNDISTRIBUTED_AMOUNT_().call(), decimalStr("9400")) 74 | 75 | assert.equal(await LockedTokenVault.methods.getOriginBalance(u1).call(), decimalStr("100")) 76 | assert.equal(await LockedTokenVault.methods.getOriginBalance(u2).call(), decimalStr("200")) 77 | assert.equal(await LockedTokenVault.methods.getClaimableBalance(u1).call(), "0") 78 | 79 | await ctx.EVM.increaseTime(86400) 80 | assert.ok(approxEqual(await LockedTokenVault.methods.getClaimableBalance(u1).call(), decimalStr("10"))) 81 | 82 | await ctx.EVM.increaseTime(30000) 83 | assert.ok(approxEqual(await LockedTokenVault.methods.getClaimableBalance(u1).call(), decimalStr("41.25"))) 84 | }) 85 | 86 | it("claim", async () => { 87 | await LockedTokenVault.methods.grant( 88 | [u1, u2, u3], 89 | [decimalStr("100"), decimalStr("200"), decimalStr("300")] 90 | ).send(ctx.sendParam(ctx.Deployer)) 91 | 92 | await ctx.EVM.increaseTime(86400) 93 | await LockedTokenVault.methods.claim().send(ctx.sendParam(u1)) 94 | assert.equal(await LockedTokenVault.methods.getOriginBalance(u1).call(), decimalStr("100")) 95 | assert.equal(await LockedTokenVault.methods.getClaimableBalance(u1).call(), "0") 96 | assert.ok(approxEqual(await DODOToken.methods.balanceOf(u1).call(), decimalStr("10"))) 97 | 98 | await ctx.EVM.increaseTime(30000) 99 | await LockedTokenVault.methods.claim().send(ctx.sendParam(u1)) 100 | assert.equal(await LockedTokenVault.methods.getClaimableBalance(u1).call(), "0") 101 | assert.ok(approxEqual(await LockedTokenVault.methods.getRemainingBalance(u1).call(), decimalStr("58.75"))) 102 | assert.ok(approxEqual(await DODOToken.methods.balanceOf(u1).call(), decimalStr("41.25"))) 103 | 104 | await LockedTokenVault.methods.claim().send(ctx.sendParam(u2)) 105 | assert.equal(await LockedTokenVault.methods.getClaimableBalance(u2).call(), "0") 106 | assert.ok(approxEqual(await LockedTokenVault.methods.getRemainingBalance(u2).call(), decimalStr("117.5"))) 107 | assert.ok(approxEqual(await DODOToken.methods.balanceOf(u2).call(), decimalStr("82.5"))) 108 | }) 109 | 110 | it("recall & transfer", async () => { 111 | await LockedTokenVault.methods.grant( 112 | [u1, u2, u3], 113 | [decimalStr("100"), decimalStr("200"), decimalStr("300")] 114 | ).send(ctx.sendParam(ctx.Deployer)) 115 | 116 | // recall u2 117 | await LockedTokenVault.methods.recall(u2).send(ctx.sendParam(ctx.Deployer)) 118 | assert.equal(await LockedTokenVault.methods.getOriginBalance(u2).call(), "0") 119 | 120 | // transfer from u3 to u2 121 | await ctx.EVM.increaseTime(86400 + 30000) 122 | await LockedTokenVault.methods.transferLockedToken(u2).send(ctx.sendParam(u3)) 123 | 124 | await LockedTokenVault.methods.claim().send(ctx.sendParam(u2)) 125 | assert.equal(await LockedTokenVault.methods.getClaimableBalance(u2).call(), "0") 126 | assert.ok(approxEqual(await LockedTokenVault.methods.getRemainingBalance(u2).call(), decimalStr("176.25"))) 127 | assert.ok(approxEqual(await DODOToken.methods.balanceOf(u2).call(), decimalStr("123.75"))) 128 | 129 | // transfer from u2 to u3 130 | await ctx.EVM.increaseTime(30000) 131 | await LockedTokenVault.methods.transferLockedToken(u3).send(ctx.sendParam(u2)) 132 | 133 | await LockedTokenVault.methods.claim().send(ctx.sendParam(u3)) 134 | assert.equal(await LockedTokenVault.methods.getClaimableBalance(u3).call(), "0") 135 | assert.ok(approxEqual(await LockedTokenVault.methods.getRemainingBalance(u3).call(), decimalStr("82.5"))) 136 | assert.ok(approxEqual(await DODOToken.methods.balanceOf(u3).call(), decimalStr("93.75"))) 137 | 138 | // transfer from u3 to u1 139 | await LockedTokenVault.methods.transferLockedToken(u1).send(ctx.sendParam(u3)) 140 | 141 | }) 142 | 143 | it("withdraw", async () => { 144 | await LockedTokenVault.methods.grant( 145 | [u1, u2, u3], 146 | [decimalStr("100"), decimalStr("200"), decimalStr("300")] 147 | ).send(ctx.sendParam(ctx.Deployer)) 148 | 149 | await LockedTokenVault.methods.withdraw(decimalStr("1000")).send(ctx.sendParam(ctx.Deployer)) 150 | assert.equal(await LockedTokenVault.methods._UNDISTRIBUTED_AMOUNT_().call(), decimalStr("8400")) 151 | 152 | await assert.rejects( 153 | LockedTokenVault.methods.withdraw(decimalStr("8500")).send(ctx.sendParam(ctx.Deployer)), 154 | /SUB_ERROR/ 155 | ) 156 | }) 157 | 158 | it("finish distributed", async () => { 159 | await LockedTokenVault.methods.grant( 160 | [u1, u2, u3], 161 | [decimalStr("100"), decimalStr("200"), decimalStr("300")] 162 | ).send(ctx.sendParam(ctx.Deployer)) 163 | await LockedTokenVault.methods.finishDistribute().send(ctx.sendParam(ctx.Deployer)) 164 | 165 | // can not recall 166 | await assert.rejects( 167 | LockedTokenVault.methods.recall(u2).send(ctx.sendParam(ctx.Deployer)), 168 | /DISTRIBUTE FINISHED/ 169 | ) 170 | }) 171 | }) 172 | 173 | }) 174 | 175 | function approxEqual(numStr1: string, numStr2: string) { 176 | let num1 = new BigNumber(numStr1) 177 | let num2 = new BigNumber(numStr2) 178 | let ratio = num1.div(num2).minus(1).abs() 179 | if (ratio.isLessThan(0.0002)) { 180 | return true 181 | } else { 182 | return false 183 | } 184 | } -------------------------------------------------------------------------------- /test/UniswapArbitrageur.test.ts: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2020 DODO ZOO. 4 | SPDX-License-Identifier: Apache-2.0 5 | 6 | */ 7 | 8 | import * as assert from 'assert'; 9 | import { Contract } from 'web3-eth-contract'; 10 | 11 | import { DODOContext, getDODOContext } from './utils/Context'; 12 | import { 13 | newContract, 14 | UNISWAP_ARBITRAGEUR_CONTRACT_NAME, 15 | UNISWAP_CONTRACT_NAME, 16 | } from './utils/Contracts'; 17 | import { decimalStr } from './utils/Converter'; 18 | import { logGas } from './utils/Log'; 19 | 20 | let lp: string; 21 | let keeper: string; 22 | 23 | let Uniswap: Contract; 24 | let UniswapArbitrageur: Contract; 25 | 26 | let UniswapReverse: Contract; 27 | let UniswapArbitrageurReverse: Contract; 28 | 29 | async function init(ctx: DODOContext): Promise { 30 | await ctx.setOraclePrice(decimalStr("100")); 31 | 32 | lp = ctx.spareAccounts[0]; 33 | keeper = ctx.spareAccounts[1]; 34 | await ctx.approveDODO(lp); 35 | 36 | await ctx.mintTestToken(lp, decimalStr("100"), decimalStr("10000")); 37 | 38 | await ctx.DODO.methods.depositBase(decimalStr("10")).send(ctx.sendParam(lp)); 39 | await ctx.DODO.methods 40 | .depositQuote(decimalStr("1000")) 41 | .send(ctx.sendParam(lp)); 42 | 43 | Uniswap = await newContract(UNISWAP_CONTRACT_NAME); 44 | Uniswap.methods 45 | .initialize(ctx.BASE.options.address, ctx.QUOTE.options.address) 46 | .send(ctx.sendParam(ctx.Deployer)); 47 | ctx.BASE.methods 48 | .transfer(Uniswap.options.address, decimalStr("10")) 49 | .send(ctx.sendParam(lp)); 50 | ctx.QUOTE.methods 51 | .transfer(Uniswap.options.address, decimalStr("2000")) 52 | .send(ctx.sendParam(lp)); 53 | Uniswap.methods.sync().send(ctx.sendParam(lp)); 54 | 55 | UniswapArbitrageur = await newContract(UNISWAP_ARBITRAGEUR_CONTRACT_NAME, [ 56 | Uniswap.options.address, 57 | ctx.DODO.options.address, 58 | ]); 59 | 60 | UniswapReverse = await newContract(UNISWAP_CONTRACT_NAME); 61 | UniswapReverse.methods 62 | .initialize(ctx.BASE.options.address, ctx.QUOTE.options.address) 63 | .send(ctx.sendParam(ctx.Deployer)); 64 | ctx.BASE.methods 65 | .transfer(UniswapReverse.options.address, decimalStr("10")) 66 | .send(ctx.sendParam(lp)); 67 | ctx.QUOTE.methods 68 | .transfer(UniswapReverse.options.address, decimalStr("2000")) 69 | .send(ctx.sendParam(lp)); 70 | UniswapReverse.methods.sync().send(ctx.sendParam(lp)); 71 | 72 | UniswapArbitrageurReverse = await newContract( 73 | UNISWAP_ARBITRAGEUR_CONTRACT_NAME, 74 | [UniswapReverse.options.address, ctx.DODO.options.address] 75 | ); 76 | } 77 | 78 | describe("Uniswap Arbitrageur", () => { 79 | let snapshotId: string; 80 | let ctx: DODOContext; 81 | 82 | before(async () => { 83 | ctx = await getDODOContext(); 84 | await init(ctx); 85 | }); 86 | 87 | beforeEach(async () => { 88 | snapshotId = await ctx.EVM.snapshot(); 89 | }); 90 | 91 | afterEach(async () => { 92 | await ctx.EVM.reset(snapshotId); 93 | }); 94 | 95 | describe("arbitrage with not reverse pair", () => { 96 | it("buy at dodo", async () => { 97 | await ctx.setOraclePrice(decimalStr("100")); 98 | // dodo price 100 uniswap price 200 99 | // buy at dodo 100 | <<<<<<< Updated upstream 101 | await logGas( 102 | UniswapArbitrageur.methods.executeBuyArbitrage(decimalStr("1")), 103 | ctx.sendParam(keeper), 104 | "arbitrage buy at dodo not reverse" 105 | ); 106 | assert.equal( 107 | await ctx.QUOTE.methods.balanceOf(keeper).call(), 108 | "79836384956601695518" 109 | ); 110 | }); 111 | ======= 112 | logGas(await UniswapArbitrageur.methods.executeBuyArbitrage(decimalStr("1")), ctx.sendParam(keeper), "arbitrage buy at dodo not reverse") 113 | assert.equal(await ctx.QUOTE.methods.balanceOf(keeper).call(), "79836384956601695518") 114 | }) 115 | >>>>>>> Stashed changes 116 | 117 | it("sell at dodo", async () => { 118 | await ctx.setOraclePrice(decimalStr("300")); 119 | // dodo price 300 uniswap price 200 120 | // sell at dodo 121 | <<<<<<< Updated upstream 122 | await logGas( 123 | UniswapArbitrageur.methods.executeSellArbitrage(decimalStr("1")), 124 | ctx.sendParam(keeper), 125 | "arbitrage sell at dodo not reverse" 126 | ); 127 | assert.equal( 128 | await ctx.BASE.methods.balanceOf(keeper).call(), 129 | "252761069524143743" 130 | ); 131 | }); 132 | }); 133 | ======= 134 | logGas(await UniswapArbitrageur.methods.executeSellArbitrage(decimalStr("1")), ctx.sendParam(keeper), "arbitrage sell at dodo not reverse") 135 | assert.equal(await ctx.BASE.methods.balanceOf(keeper).call(), "252761069524143743") 136 | }) 137 | }) 138 | >>>>>>> Stashed changes 139 | 140 | describe("arbitrage with reverse pair", () => { 141 | it("buy at dodo", async () => { 142 | await ctx.setOraclePrice(decimalStr("100")); 143 | // dodo price 100 uniswap price 200 144 | // buy at dodo 145 | <<<<<<< Updated upstream 146 | await logGas( 147 | UniswapArbitrageurReverse.methods.executeBuyArbitrage(decimalStr("1")), 148 | ctx.sendParam(keeper), 149 | "arbitrage buy at dodo reverse" 150 | ); 151 | assert.equal( 152 | await ctx.QUOTE.methods.balanceOf(keeper).call(), 153 | "79836384956601695518" 154 | ); 155 | }); 156 | ======= 157 | logGas(await UniswapArbitrageurReverse.methods.executeBuyArbitrage(decimalStr("1")), ctx.sendParam(keeper), "arbitrage buy at dodo reverse") 158 | assert.equal(await ctx.QUOTE.methods.balanceOf(keeper).call(), "79836384956601695518") 159 | }) 160 | >>>>>>> Stashed changes 161 | 162 | it("sell at dodo", async () => { 163 | await ctx.setOraclePrice(decimalStr("300")); 164 | // dodo price 300 uniswap price 200 165 | // sell at dodo 166 | <<<<<<< Updated upstream 167 | await logGas( 168 | UniswapArbitrageurReverse.methods.executeSellArbitrage(decimalStr("1")), 169 | ctx.sendParam(keeper), 170 | "arbitrage sell at dodo reverse" 171 | ); 172 | assert.equal( 173 | await ctx.BASE.methods.balanceOf(keeper).call(), 174 | "252761069524143743" 175 | ); 176 | }); 177 | }); 178 | ======= 179 | logGas(await UniswapArbitrageurReverse.methods.executeSellArbitrage(decimalStr("1")), ctx.sendParam(keeper), "arbitrage sell at dodo reverse") 180 | assert.equal(await ctx.BASE.methods.balanceOf(keeper).call(), "252761069524143743") 181 | }) 182 | }) 183 | >>>>>>> Stashed changes 184 | 185 | describe("revert cases", () => { 186 | it("price not match", async () => { 187 | await ctx.setOraclePrice(decimalStr("200")); 188 | await assert.rejects( 189 | UniswapArbitrageurReverse.methods 190 | .executeBuyArbitrage(decimalStr("1")) 191 | .send(ctx.sendParam(keeper)), 192 | /NOT_PROFITABLE/ 193 | ); 194 | await assert.rejects( 195 | UniswapArbitrageurReverse.methods 196 | .executeSellArbitrage(decimalStr("1")) 197 | .send(ctx.sendParam(keeper)), 198 | /NOT_PROFITABLE/ 199 | ); 200 | }); 201 | }); 202 | }); 203 | -------------------------------------------------------------------------------- /test/utils/Context.ts: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2020 DODO ZOO. 4 | SPDX-License-Identifier: Apache-2.0 5 | 6 | */ 7 | 8 | import BigNumber from 'bignumber.js'; 9 | import Web3 from 'web3'; 10 | import { Contract } from 'web3-eth-contract'; 11 | 12 | import * as contracts from './Contracts'; 13 | import { decimalStr, gweiStr, MAX_UINT256 } from './Converter'; 14 | import { EVM, getDefaultWeb3 } from './EVM'; 15 | import * as log from './Log'; 16 | 17 | BigNumber.config({ 18 | EXPONENTIAL_AT: 1000, 19 | DECIMAL_PLACES: 80, 20 | }); 21 | 22 | export interface DODOContextInitConfig { 23 | lpFeeRate: string; 24 | mtFeeRate: string; 25 | k: string; 26 | gasPriceLimit: string; 27 | } 28 | 29 | /* 30 | price curve when k=0.1 31 | +──────────────────────+───────────────+ 32 | | purchase percentage | avg slippage | 33 | +──────────────────────+───────────────+ 34 | | 1% | 0.1% | 35 | | 5% | 0.5% | 36 | | 10% | 1.1% | 37 | | 20% | 2.5% | 38 | | 50% | 10% | 39 | | 70% | 23.3% | 40 | +──────────────────────+───────────────+ 41 | */ 42 | export let DefaultDODOContextInitConfig = { 43 | lpFeeRate: decimalStr("0.002"), 44 | mtFeeRate: decimalStr("0.001"), 45 | k: decimalStr("0.1"), 46 | gasPriceLimit: gweiStr("100"), 47 | }; 48 | 49 | export class DODOContext { 50 | EVM: EVM; 51 | Web3: Web3; 52 | DODO: Contract; 53 | DODOZoo: Contract; 54 | BASE: Contract; 55 | BaseCapital: Contract; 56 | QUOTE: Contract; 57 | QuoteCapital: Contract; 58 | ORACLE: Contract; 59 | Deployer: string; 60 | Supervisor: string; 61 | Maintainer: string; 62 | spareAccounts: string[]; 63 | 64 | constructor() {} 65 | 66 | async init(config: DODOContextInitConfig) { 67 | this.EVM = new EVM(); 68 | this.Web3 = getDefaultWeb3(); 69 | var cloneFactory = await contracts.newContract( 70 | contracts.CLONE_FACTORY_CONTRACT_NAME 71 | ); 72 | 73 | this.BASE = await contracts.newContract( 74 | contracts.TEST_ERC20_CONTRACT_NAME, 75 | ["TestBase", 18] 76 | ); 77 | this.QUOTE = await contracts.newContract( 78 | contracts.TEST_ERC20_CONTRACT_NAME, 79 | ["TestQuote", 18] 80 | ); 81 | this.ORACLE = await contracts.newContract( 82 | contracts.NAIVE_ORACLE_CONTRACT_NAME 83 | ); 84 | 85 | const allAccounts = await this.Web3.eth.getAccounts(); 86 | this.Deployer = allAccounts[0]; 87 | this.Supervisor = allAccounts[1]; 88 | this.Maintainer = allAccounts[2]; 89 | this.spareAccounts = allAccounts.slice(3, 10); 90 | 91 | var DODOTemplate = await contracts.newContract( 92 | contracts.DODO_CONTRACT_NAME 93 | ); 94 | this.DODOZoo = await contracts.newContract( 95 | contracts.DODO_ZOO_CONTRACT_NAME, 96 | [ 97 | DODOTemplate.options.address, 98 | cloneFactory.options.address, 99 | this.Supervisor, 100 | ] 101 | ); 102 | 103 | await this.DODOZoo.methods 104 | .breedDODO( 105 | this.Maintainer, 106 | this.BASE.options.address, 107 | this.QUOTE.options.address, 108 | this.ORACLE.options.address, 109 | config.lpFeeRate, 110 | config.mtFeeRate, 111 | config.k, 112 | config.gasPriceLimit 113 | ) 114 | .send(this.sendParam(this.Deployer)); 115 | 116 | this.DODO = contracts.getContractWithAddress( 117 | contracts.DODO_CONTRACT_NAME, 118 | await this.DODOZoo.methods 119 | .getDODO(this.BASE.options.address, this.QUOTE.options.address) 120 | .call() 121 | ); 122 | await this.DODO.methods 123 | .enableBaseDeposit() 124 | .send(this.sendParam(this.Deployer)); 125 | await this.DODO.methods 126 | .enableQuoteDeposit() 127 | .send(this.sendParam(this.Deployer)); 128 | await this.DODO.methods.enableTrading().send(this.sendParam(this.Deployer)); 129 | 130 | this.BaseCapital = contracts.getContractWithAddress( 131 | contracts.DODO_LP_TOKEN_CONTRACT_NAME, 132 | await this.DODO.methods._BASE_CAPITAL_TOKEN_().call() 133 | ); 134 | this.QuoteCapital = contracts.getContractWithAddress( 135 | contracts.DODO_LP_TOKEN_CONTRACT_NAME, 136 | await this.DODO.methods._QUOTE_CAPITAL_TOKEN_().call() 137 | ); 138 | 139 | console.log(log.blueText("[Init dodo context]")); 140 | } 141 | 142 | sendParam(sender, value = "0") { 143 | return { 144 | from: sender, 145 | gas: process.env["COVERAGE"] ? 10000000000 : 7000000, 146 | gasPrice: process.env.GAS_PRICE, 147 | value: decimalStr(value), 148 | }; 149 | } 150 | 151 | async setOraclePrice(price: string) { 152 | await this.ORACLE.methods 153 | .setPrice(price) 154 | .send(this.sendParam(this.Deployer)); 155 | } 156 | 157 | async mintTestToken(to: string, base: string, quote: string) { 158 | await this.BASE.methods.mint(to, base).send(this.sendParam(this.Deployer)); 159 | await this.QUOTE.methods 160 | .mint(to, quote) 161 | .send(this.sendParam(this.Deployer)); 162 | } 163 | 164 | async approveDODO(account: string) { 165 | await this.BASE.methods 166 | .approve(this.DODO.options.address, MAX_UINT256) 167 | .send(this.sendParam(account)); 168 | await this.QUOTE.methods 169 | .approve(this.DODO.options.address, MAX_UINT256) 170 | .send(this.sendParam(account)); 171 | } 172 | } 173 | 174 | export async function getDODOContext( 175 | config: DODOContextInitConfig = DefaultDODOContextInitConfig 176 | ): Promise { 177 | var context = new DODOContext(); 178 | await context.init(config); 179 | return context; 180 | } 181 | -------------------------------------------------------------------------------- /test/utils/Contracts.ts: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2020 DODO ZOO. 4 | SPDX-License-Identifier: Apache-2.0 5 | 6 | */ 7 | var jsonPath: string = "../../build/contracts/" 8 | if (process.env["COVERAGE"]) { 9 | console.log("[Coverage mode]") 10 | jsonPath = "../../.coverage_artifacts/contracts/" 11 | } 12 | 13 | const CloneFactory = require(`${jsonPath}CloneFactory.json`) 14 | const DODO = require(`${jsonPath}DODO.json`) 15 | const DODOZoo = require(`${jsonPath}DODOZoo.json`) 16 | const DODOEthProxy = require(`${jsonPath}DODOEthProxy.json`) 17 | const WETH = require(`${jsonPath}WETH9.json`) 18 | const TestERC20 = require(`${jsonPath}TestERC20.json`) 19 | const NaiveOracle = require(`${jsonPath}NaiveOracle.json`) 20 | const DODOLpToken = require(`${jsonPath}DODOLpToken.json`) 21 | const Uniswap = require(`${jsonPath}UniswapV2Pair.json`) 22 | const UniswapArbitrageur = require(`${jsonPath}UniswapArbitrageur.json`) 23 | const DODOToken = require(`${jsonPath}DODOToken.json`) 24 | const DODOMine = require(`${jsonPath}DODOMine.json`) 25 | const DODOMineReader = require(`${jsonPath}DODOMineReader.json`) 26 | const LockedTokenVault = require(`${jsonPath}LockedTokenVault.json`) 27 | 28 | import { getDefaultWeb3 } from './EVM'; 29 | import { Contract } from 'web3-eth-contract'; 30 | 31 | export const CLONE_FACTORY_CONTRACT_NAME = "CloneFactory" 32 | export const DODO_CONTRACT_NAME = "DODO" 33 | export const TEST_ERC20_CONTRACT_NAME = "TestERC20" 34 | export const NAIVE_ORACLE_CONTRACT_NAME = "NaiveOracle" 35 | export const DODO_LP_TOKEN_CONTRACT_NAME = "DODOLpToken" 36 | export const DODO_ZOO_CONTRACT_NAME = "DOOZoo" 37 | export const DODO_WILD_CONTRACT_NAME = "DOOWild" 38 | export const DODO_ETH_PROXY_CONTRACT_NAME = "DODOEthProxy" 39 | export const WETH_CONTRACT_NAME = "WETH" 40 | export const UNISWAP_CONTRACT_NAME = "Uniswap" 41 | export const UNISWAP_ARBITRAGEUR_CONTRACT_NAME = "UniswapArbitrageur" 42 | export const DODO_TOKEN_CONTRACT_NAME = "DODOToken" 43 | export const LOCKED_TOKEN_VAULT_CONTRACT_NAME = "LockedTokenVault" 44 | export const DODO_MINE_NAME = "DODOMine" 45 | export const DODO_MINE_READER_NAME = "DODOMineReader" 46 | 47 | var contractMap: { [name: string]: any } = {} 48 | contractMap[CLONE_FACTORY_CONTRACT_NAME] = CloneFactory 49 | contractMap[DODO_CONTRACT_NAME] = DODO 50 | contractMap[TEST_ERC20_CONTRACT_NAME] = TestERC20 51 | contractMap[NAIVE_ORACLE_CONTRACT_NAME] = NaiveOracle 52 | contractMap[DODO_LP_TOKEN_CONTRACT_NAME] = DODOLpToken 53 | contractMap[DODO_ZOO_CONTRACT_NAME] = DODOZoo 54 | contractMap[DODO_ETH_PROXY_CONTRACT_NAME] = DODOEthProxy 55 | contractMap[WETH_CONTRACT_NAME] = WETH 56 | contractMap[UNISWAP_CONTRACT_NAME] = Uniswap 57 | contractMap[UNISWAP_ARBITRAGEUR_CONTRACT_NAME] = UniswapArbitrageur 58 | contractMap[DODO_TOKEN_CONTRACT_NAME] = DODOToken 59 | contractMap[LOCKED_TOKEN_VAULT_CONTRACT_NAME] = LockedTokenVault 60 | contractMap[DODO_MINE_NAME] = DODOMine 61 | contractMap[DODO_MINE_READER_NAME] = DODOMineReader 62 | 63 | interface ContractJson { 64 | abi: any; 65 | networks: { [network: number]: any }; 66 | byteCode: string; 67 | } 68 | 69 | export function getContractJSON(contractName: string): ContractJson { 70 | var info = contractMap[contractName] 71 | return { 72 | abi: info.abi, 73 | networks: info.networks, 74 | byteCode: info.bytecode 75 | } 76 | } 77 | 78 | export function getContractWithAddress(contractName: string, address: string) { 79 | var Json = getContractJSON(contractName) 80 | var web3 = getDefaultWeb3() 81 | return new web3.eth.Contract(Json.abi, address) 82 | } 83 | 84 | export function getDepolyedContract(contractName: string): Contract { 85 | var Json = getContractJSON(contractName) 86 | var networkId = process.env.NETWORK_ID 87 | var deployedAddress = getContractJSON(contractName).networks[networkId].address 88 | var web3 = getDefaultWeb3() 89 | return new web3.eth.Contract(Json.abi, deployedAddress) 90 | } 91 | 92 | export async function newContract(contractName: string, args: any[] = []): Promise { 93 | var web3 = getDefaultWeb3() 94 | var Json = getContractJSON(contractName) 95 | var contract = new web3.eth.Contract(Json.abi) 96 | var adminAccount = (await web3.eth.getAccounts())[0] 97 | let parameter = { 98 | from: adminAccount, 99 | gas: process.env["COVERAGE"] ? 10000000000 : 7000000, 100 | gasPrice: web3.utils.toHex(web3.utils.toWei('1', 'wei')) 101 | } 102 | return await contract.deploy({ data: Json.byteCode, arguments: args }).send(parameter) 103 | } -------------------------------------------------------------------------------- /test/utils/Converter.ts: -------------------------------------------------------------------------------- 1 | import BigNumber from "bignumber.js"; 2 | 3 | export const MAX_UINT256 = "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" 4 | 5 | export function decimalStr(value: string): string { 6 | return new BigNumber(value).multipliedBy(10 ** 18).toFixed(0, BigNumber.ROUND_DOWN) 7 | } 8 | 9 | export function gweiStr(gwei: string): string { 10 | return new BigNumber(gwei).multipliedBy(10 ** 9).toFixed(0, BigNumber.ROUND_DOWN) 11 | } -------------------------------------------------------------------------------- /test/utils/EVM.ts: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2020 DODO ZOO. 4 | SPDX-License-Identifier: Apache-2.0 5 | 6 | */ 7 | 8 | // require('dotenv-flow').config(); 9 | 10 | import { JsonRpcPayload, JsonRpcResponse } from 'web3-core-helpers'; 11 | import Web3 from 'web3'; 12 | 13 | export function getDefaultWeb3() { 14 | return new Web3(process.env.RPC_NODE_URI) 15 | } 16 | 17 | export class EVM { 18 | private provider = new Web3.providers.HttpProvider(process.env.RPC_NODE_URI); 19 | 20 | public async reset(id: string): Promise { 21 | if (!id) { 22 | throw new Error('id must be set'); 23 | } 24 | 25 | await this.callJsonrpcMethod('evm_revert', [id]); 26 | 27 | return this.snapshot(); 28 | } 29 | 30 | public async snapshot(): Promise { 31 | return this.callJsonrpcMethod('evm_snapshot'); 32 | } 33 | 34 | public async evmRevert(id: string): Promise { 35 | return this.callJsonrpcMethod('evm_revert', [id]); 36 | } 37 | 38 | public async stopMining(): Promise { 39 | return this.callJsonrpcMethod('miner_stop'); 40 | } 41 | 42 | public async startMining(): Promise { 43 | return this.callJsonrpcMethod('miner_start'); 44 | } 45 | 46 | public async mineBlock(): Promise { 47 | return this.callJsonrpcMethod('evm_mine'); 48 | } 49 | 50 | public async fastMove(moveBlockNum: number): Promise { 51 | var res: string 52 | for (let i = 0; i < moveBlockNum; i++) { 53 | res = await this.callJsonrpcMethod('evm_mine'); 54 | } 55 | return res 56 | } 57 | 58 | public async increaseTime(duration: number): Promise { 59 | await this.callJsonrpcMethod('evm_increaseTime', [duration]); 60 | return this.callJsonrpcMethod('evm_mine'); 61 | } 62 | 63 | public async callJsonrpcMethod(method: string, params?: (any[])): Promise { 64 | const args: JsonRpcPayload = { 65 | method, 66 | params, 67 | jsonrpc: '2.0', 68 | id: new Date().getTime(), 69 | }; 70 | 71 | const response = await this.send(args); 72 | 73 | return response.result; 74 | } 75 | 76 | private async send(args: JsonRpcPayload): Promise { 77 | return new Promise((resolve, reject) => { 78 | const callback: any = (error: Error, val: JsonRpcResponse): void => { 79 | if (error) { 80 | reject(error); 81 | } else { 82 | resolve(val); 83 | } 84 | }; 85 | 86 | this.provider.send( 87 | args, 88 | callback, 89 | ); 90 | }); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /test/utils/Log.ts: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2020 DODO ZOO. 4 | SPDX-License-Identifier: Apache-2.0 5 | 6 | */ 7 | 8 | export const blueText = x => `\x1b[36m${x}\x1b[0m`; 9 | export const yellowText = x => `\x1b[33m${x}\x1b[0m`; 10 | export const greenText = x => `\x1b[32m${x}\x1b[0m`; 11 | export const redText = x => `\x1b[31m${x}\x1b[0m`; 12 | export const numberWithCommas = x => x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ','); 13 | 14 | export async function logGas(funcCall: any, params: any, desc: string) { 15 | const estimatedGas = await funcCall.estimateGas(params) 16 | const receipt = await funcCall.send(params) 17 | const gasUsed = receipt.gasUsed; 18 | let colorFn; 19 | 20 | if (gasUsed < 80000) { 21 | colorFn = greenText; 22 | } else if (gasUsed < 200000) { 23 | colorFn = yellowText; 24 | } else { 25 | colorFn = redText; 26 | } 27 | 28 | console.log(("Gas estimated:" + numberWithCommas(estimatedGas)).padEnd(60, '.'), blueText(desc) + " ", colorFn(numberWithCommas(gasUsed).padStart(5))); 29 | return receipt 30 | } -------------------------------------------------------------------------------- /test/utils/SlippageFormula.ts: -------------------------------------------------------------------------------- 1 | function calculateSlippage(buyPercentage: number) { 2 | const k = 0.1 3 | console.log(buyPercentage, ":", ((1 / (1 - buyPercentage)) * k - k) * 100, "%") 4 | } 5 | 6 | function calculateLoss(priceGap: number) { 7 | const feeRate = 0.0025 8 | const k = 0.1 9 | let amountPartial = Math.sqrt(priceGap / k + 1) - 1 10 | let loss = amountPartial * (priceGap - feeRate * 2) 11 | console.log(priceGap, ":", loss * 100, "%") 12 | } 13 | 14 | // calculateSlippage(0.01) 15 | // calculateSlippage(0.05) 16 | // calculateSlippage(0.1) 17 | // calculateSlippage(0.2) 18 | // calculateSlippage(0.5) 19 | // calculateSlippage(0.7) 20 | 21 | // calculateLoss(0.006) 22 | // calculateLoss(0.007) 23 | // calculateLoss(0.008) 24 | // calculateLoss(0.009) 25 | // calculateLoss(0.01) 26 | // calculateLoss(0.02) 27 | // calculateLoss(0.03) -------------------------------------------------------------------------------- /truffle-config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Use this file to configure your truffle project. It's seeded with some 3 | * common settings for different networks and features like migrations, 4 | * compilation and testing. Uncomment the ones you need or modify 5 | * them to suit your project as necessary. 6 | * 7 | * More information about configuration can be found at: 8 | * 9 | * truffleframework.com/docs/advanced/configuration 10 | * 11 | * To deploy via Infura you'll need a wallet provider (like @truffle/hdwallet-provider) 12 | * to sign your transactions before they're sent to a remote public node. Infura accounts 13 | * are available for free at: infura.io/register. 14 | * 15 | * You'll also need a mnemonic - the twelve word phrase the wallet uses to generate 16 | * public/private key pairs. If you're publishing your code to GitHub make sure you load this 17 | * phrase from a file you've .gitignored so it doesn't accidentally become public. 18 | * 19 | */ 20 | 21 | // const HDWalletProvider = require('@truffle/hdwallet-provider'); 22 | // const infuraKey = "fj4jll3k....."; 23 | // 24 | // const fs = require('fs'); 25 | // const mnemonic = fs.readFileSync(".secret").toString().trim(); 26 | require("ts-node/register"); // eslint-disable-line 27 | require("dotenv-flow").config(); // eslint-disable-line 28 | 29 | module.exports = { 30 | /** 31 | * Networks define how you connect to your ethereum client and let you set the 32 | * defaults web3 uses to send transactions. If you don't specify one truffle 33 | * will spin up a development blockchain for you on port 9545 when you 34 | * run `develop` or `test`. You can ask a truffle command to use a specific 35 | * network from the command line, e.g 36 | * 37 | * $ truffle test --network 38 | */ 39 | 40 | networks: { 41 | // Useful for testing. The `development` name is special - truffle uses it by default 42 | // if it's defined here and no other network is specified at the command line. 43 | // You should run a client (like ganache-cli, geth or parity) in a separate terminal 44 | // tab if you use this network and you must also set the `host`, `port` and `network_id` 45 | // options below to some value. 46 | // 47 | development: { 48 | host: "127.0.0.1", 49 | port: 8545, 50 | network_id: 5777, 51 | gas: 0xfffffffffff, 52 | gasPrice: 1, 53 | }, 54 | coverage: { 55 | host: "127.0.0.1", 56 | port: 6545, 57 | network_id: 1002, 58 | gas: 0xfffffffffff, 59 | gasPrice: 1, 60 | }, 61 | }, 62 | 63 | // Set default mocha options here, use special reporters etc. 64 | mocha: { 65 | timeout: false, 66 | }, 67 | plugins: ["solidity-coverage"], 68 | // Configure your compilers 69 | compilers: { 70 | solc: { 71 | version: "0.6.9", // Fetch exact version from solc-bin (default: truffle's version) 72 | settings: { 73 | // See the solidity docs for advice about optimization and evmVersion 74 | optimizer: { 75 | enabled: true, 76 | runs: 200, 77 | }, 78 | }, 79 | }, 80 | }, 81 | }; 82 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "lib": ["es2015", "es2016", "es2017", "dom"], 6 | "strict": false, 7 | "sourceMap": true, 8 | "declaration": true, 9 | "downlevelIteration": true, 10 | "noUnusedLocals": true, 11 | "esModuleInterop": true, 12 | "outDir": "dist", 13 | "resolveJsonModule": true, 14 | "allowSyntheticDefaultImports": true, 15 | "typeRoots": ["node_modules/@types"], 16 | "types": ["node", "mocha", "chai"] 17 | }, 18 | "include": ["src", "test", "script"], 19 | "exclude": ["scripts/**/*", "build/**/*", "migrations/**/*"], 20 | "compileOnSave": true 21 | } 22 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["tslint-config-airbnb", "tslint-no-focused-test"], 3 | "rules": { 4 | "import-name": false, 5 | "no-floating-promises": true, 6 | "no-focused-test": true, 7 | "variable-name": [ 8 | true, 9 | "allow-pascal-case", 10 | "ban-keywords" 11 | ] 12 | } 13 | } 14 | --------------------------------------------------------------------------------