├── .gitignore ├── .sonarcloud.properties ├── .travis.yml ├── .travis_install_jdk.sh ├── LICENSE ├── LIST_OF_FEATURE_IDEAS.md ├── README.md ├── config.properties ├── pom.xml └── src ├── main ├── java │ ├── META-INF │ │ └── MANIFEST.MF │ └── de │ │ └── devgao │ │ └── defi │ │ ├── Main.java │ │ ├── compounddai │ │ ├── CompoundDai.java │ │ └── CompoundDaiContract.java │ │ ├── compoundeth │ │ ├── CompoundEth.java │ │ └── CompoundEthContract.java │ │ ├── contractneedsprovider │ │ ├── CircuitBreaker.java │ │ ├── ContractNeedsProvider.java │ │ ├── Permissions.java │ │ ├── Wallet.java │ │ └── Web3jProvider.java │ │ ├── contractuserutil │ │ └── AddressMethod.java │ │ ├── contractutil │ │ ├── Account.java │ │ ├── AccountMethod.java │ │ ├── Approval.java │ │ ├── ApprovalMethod.java │ │ └── ContractValidationUtil.java │ │ ├── dai │ │ ├── Dai.java │ │ └── DaiContract.java │ │ ├── flipper │ │ ├── Auction.java │ │ ├── Flipper.java │ │ └── FlipperContract.java │ │ ├── gasprovider │ │ ├── ArrayListUtil.java │ │ ├── ETHGasStation.java │ │ ├── Etherchain.java │ │ ├── GasPriceException.java │ │ └── GasProvider.java │ │ ├── maker │ │ ├── Maker.java │ │ └── MakerContract.java │ │ ├── medianizer │ │ ├── MedianException.java │ │ ├── Medianizer.java │ │ └── MedianizerContract.java │ │ ├── numberutil │ │ ├── NumberUtil.java │ │ ├── NumberWrapper.java │ │ ├── Rad45.java │ │ ├── Sth28.java │ │ └── Wad18.java │ │ ├── oasis │ │ ├── DaiOrWethMissingException.java │ │ ├── Oasis.java │ │ ├── OasisContract.java │ │ └── OasisOffer.java │ │ ├── oasisdex │ │ ├── DaiOrWethMissingException.java │ │ ├── OasisDex.java │ │ ├── OasisDexContract.java │ │ └── OasisDexOffer.java │ │ ├── uniswap │ │ ├── EthToTokenSwapInput.java │ │ ├── TokenToEthSwapInput.java │ │ ├── Uniswap.java │ │ ├── UniswapContract.java │ │ └── UniswapOffer.java │ │ ├── util │ │ ├── Balances.java │ │ ├── BigNumberUtil.java │ │ ├── ContractUser.java │ │ ├── DirectoryUtil.java │ │ ├── Ethereum.java │ │ ├── IContract.java │ │ ├── JavaProperties.java │ │ ├── NumberUtil.java │ │ ├── ProfitCalculator.java │ │ └── TransactionUtil.java │ │ └── weth │ │ ├── Weth.java │ │ └── WethContract.java └── resources │ └── logback.xml └── test ├── java └── de │ └── devgao │ └── defi │ ├── CompoundDaiIT.java │ ├── compounddai │ └── CompoundDaiIT.java │ ├── contractutil │ └── ContractValidationUtilIT.java │ ├── dai │ └── DaiIT.java │ ├── flipper │ ├── AuctionIT.java │ ├── AuctionTest.java │ └── FlipperIT.java │ ├── gasprovider │ ├── ETHGasStationIT.java │ ├── EtherchainIT.java │ └── GasProviderIT.java │ ├── maker │ └── MakerIT.java │ ├── medianizer │ ├── MedianizerIT.java │ └── MedianizerTest.java │ ├── numberutil │ ├── INumberWrapperTest.java │ ├── NumberUtilTest.java │ ├── NumberWrapperTest.java │ └── Wad18Test.java │ ├── oasis │ └── OasisIT.java │ ├── oasisdex │ └── OasisDexIT.java │ ├── uniswap │ └── UniswapIT.java │ ├── util │ ├── BalancesIT.java │ ├── BigNumberUtilTest.java │ ├── ContractUserIT.java │ ├── JavaPropertiesTest.java │ ├── NumberUtilTest.java │ └── TransactionUtilTest.java │ └── weth │ └── WethIT.java └── resources └── logback-test.xml /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | jedibot.iml 3 | logs/* 4 | 5 | # Maven.gitignore https://github.com/github/gitignore/blob/master/Maven.gitignore 6 | target/ 7 | pom.xml.tag 8 | pom.xml.releaseBackup 9 | pom.xml.versionsBackup 10 | pom.xml.next 11 | release.properties 12 | dependency-reduced-pom.xml 13 | buildNumber.properties 14 | .mvn/timing.properties 15 | 16 | # https://github.com/takari/maven-wrapper#usage-without-binary-jar 17 | .mvn/wrapper/maven-wrapper.jar 18 | /config.properties 19 | -------------------------------------------------------------------------------- /.sonarcloud.properties: -------------------------------------------------------------------------------- 1 | # Path to sources 2 | #sonar.sources=. 3 | sonar.exclusions=**/*Contract.java 4 | #sonar.inclusions= 5 | 6 | # Path to tests 7 | #sonar.tests= 8 | sonar.test.exclusions=**/*Contract.java 9 | #sonar.test.inclusions= 10 | sonar.coverage.exclusions=**/*Contract.java 11 | 12 | # Source encoding 13 | #sonar.sourceEncoding=UTF-8 14 | 15 | # Exclusions for copy-paste detection 16 | sonar.cpd.exclusions=**/*Contract.java 17 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | sudo: false 3 | script: mvn clean org.jacoco:jacoco-maven-plugin:prepare-agent install sonar:sonar 4 | -Dsonar.projectKey=DevelGao_jedibot -Pcoverage 5 | before_install: 6 | ########## 7 | # TEMPORARY hack to get around Travis not installing Java 8 | # Remove this and the ".travis_install_jdk.sh" file once Travis installs java properly again! 9 | # Copied from the output of the "install_jdk" task in our last successful build 10 | - export JAVA_HOME=~/openjdk13 11 | - export PATH="$JAVA_HOME/bin:$PATH" 12 | - ./.travis_install_jdk.sh --target "/home/travis/openjdk13" --workspace "/home/travis/.cache/install-jdk" --feature "13" --license "GPL" --cacerts 13 | ########## 14 | jdk: oraclejdk13 15 | addons: 16 | sonarcloud: 17 | organization: develgao-github 18 | token: 19 | secure: E3tGQ3qIXYQxjszr9MfoP5i3hSeLxn6ofgVykKSeeg0ZcFmR2vwCTcjGETog1zUavRqVW0CjXROtqbTXftjnxArH8ZwAAbhpXU2vjr+0CI34Zh4DzJJUgh7uvZq0tj4YNocHyXPdU4ffNHC5njFemPkWEjr3Pa49dL4xFf3AZiRL8FAjsDY4URPelxvAU++XcYtWza9V0M4ADwXT1BefpJKK+8/+tUz1CInwYvINjn+s0PCjaF8ZiBUj1iIpYZPMfnBEMuflKeWhx34iRKa7HxfiCw4JTIQtEQx1HD7BlYPFsVzCzt+rNFrKrCpxPLD64dBQZcUo+O7CGLoPWBxVvU7fzVPh23Pxm6BF1CCMBNwzRXfwGeVvqeI4S4WZhkdvtsTsAx/SANDynDWWFYqQm64KtUcpWs90VOL6QIdAjcLSCTlYXZ6OuyEgykPJk01KZ9l+BkP5JoDzactzt+SypPd5zdG9gTmsUYxUnF/d9H4TWoPG9mfs+T7p84G2U1FuqfB95OB95u8fQKERaUvXwnRO9GRN9cDvpKgPg9X2waXtNw7mYKQBK7nHcLs7lj3auyIPneI+faysbO+uhJKQ4X4SVchtqiuzs86PnZBHipq3RaXRDHqbFxQDHFZo6m75uo9h9O2n/TTsXv3NsJMxOdrSj696xXdVnRV7V8I8RyU= 20 | env: 21 | global: 22 | - secure: IvDPsmLhROaOyAyWTCi+sgTeQZFw+4ZK3SM3VjXv/vC8JiPE0HlMmKLCDXOXhkAxtLFw3Hj/W5rqD8G0vWBxHPUfxj72BRN0C1qP7BMi735AMuH/1cBRgzcoAnKDRbGzmH72ptyISuuiTTpuJq2pt1mgKaTLm9HUFAi+vdNMKYSgWMlUZJeF8+jQEdl5UwqlEd18+FmC7Vbtx2Oh7I6LNv14Y9gOqfH2fcwdDSIAF2cepX6waPtLkiXKJ1kZwZZk9JyZou77kLd1KxjHeYW7OJMjFpgUXg1+9B9zkagYIbX2GUt4xd1dUArTBBXJLLvMVdhqPwgobpfG1FkEpdpMq2fjQsurNFcYXi/xBFutOmDi4/2vi/Z3zBqaCO2V4YtQVM7WOLPMRHNEDIAbmjVuK7ufAIKcthYR2H2TUbhE3fHy+8GIOA1YZ/DH6mgywibW9vtnCWfLUvkdeipAhgT3hFdM372wOjgNDaiREPPoyDLPfGgFDgFAvziJ9gMCcTK7s/zF/FTb9ojCWyiHVl4NJggLrwGXcQlXjr8vwJNkKIGw/8riXnS2a7dk2mwb/O9rO1z5ILpkq278G7AuqlcekItGr8s9hSEcz/2nQM5PnEyCraixwLJVjZA8Mk5U1YD3B3Of8EqitsHCOYXq4e51vSoaGybZAuSrMIGbIU2qK08= 23 | - secure: Y5TGGrwjuV+CQrYNOb11JZvX4PUer+AmqSmtUA3ioZuDIJ4aGuxXHT7IDOPmvQq75HH3cYRH/ftS1hlhsMpC4ZTr1Lko9HNz8JNmedaG33wy/lqS5c4bPcvtwAZVbck9siK2Lc1ZH4mJ8Y7mdR5DhqsQapgKBtNIwi+bxpWfmWMd2eGLU8aeqI6bpRTuLR5hGbU1jDe+mQ4kPitqiL+fizjSSpWiSrfAJWz7JZoFxDqgFAvifOUGIj3IaWMRGmONeIvtYZvgCyl5zE5U/oxPCq1iIiIVcPcnJsvJVrcKikrnZrck6XRXxErTcrO/Jp8mlz/XzufBOBANNVEL3Eh38ut6lZGp3jkG48nWb7coXTF+QjOSs9T3/8QzJxSCol5fhN9sZTOZhet7rSmj0K0fQoiPkgVFtFMYFl4QR1pJsIpmjXvWnnatRntEAGwiOtFBa9D3ghEeRACaaxcWHWL47wOI+jJvXgALEasEua5izNN84iMAn4lxc95guA1RLyAxMy/Qrj/XRCO0Q3hok4kLt1YkOQyAd02hdhlV098TJ28TM1qQZ5PtxQE5NNA7k/XT72SMCsDYHCsXnVDKnw8EvL9b/FAifLQ7bhBdVXhsO2fUJKcfNifrnzLe6IQAQvEBInowKeTuRoFtffzV9VyT+B3Moxxa6dGOmIwCDFmQH7s= 24 | - TRAVIS_WALLET="{\"version\":3,\"id\":\"47c29513-bbe5-4b2f-ac7e-586605a685d4\",\"address\":\"f037c0e91f4c7c474eda535f2adb119aff6c98af\",\"crypto\":{\"ciphertext\":\"553bafe702347ab3dc4189e88cc9e30c557f9e2fc2c426506b0820c10a13e253\",\"cipherparams\":{\"iv\":\"9a1b9879a6599fbe7d47863b70d5a2a6\"},\"cipher\":\"aes-128-ctr\",\"kdf\":\"scrypt\",\"kdfparams\":{\"dklen\":32,\"salt\":\"f807cde0d0a84fe9827425ccd7180ad1f2dc2cc7c7c66c0a95cd6e7fa38f796f\",\"n\":131072,\"r\":8,\"p\":1},\"mac\":\"8dcd858960411dfb923d372c940ecf5e60c9d700575e01905b976016926a6ea3\"}}" 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Fabian Schüssler 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LIST_OF_FEATURE_IDEAS.md: -------------------------------------------------------------------------------- 1 | # Bug fixes 2 | - Have a look at MakerDao Auction Keep and their API usage about Price Feeds 3 | 4 | # New Features 5 | - Check if selling ETH to participate in DAI auction is profitable 6 | - Add deal for flip auctions 7 | - Add [Uniswap V2](https://uniswap.org/blog/uniswap-v2/) 8 | - Move wait time to config? private static final int WAIT_TIME = 60 * 60 * 1000; // 60 minutes 9 | - Compare oasis and uniswap profit in Main 10 | - **Add maker.borrowAndLendDai(balances)** 11 | - **Add dydx.lendDai()** 12 | - Add eth gas estimation per transaction 13 | - Add dydx buy and sell dai 14 | - [Protocol](https://docs.dydx.exchange/#/protocol) 15 | - [Contracts](https://docs.dydx.exchange/#/contracts) 16 | - Add maker.borrowAndSellDai(balances) 17 | - Add maker.borrowDai(balances) ? 18 | - Add manage maker debt 19 | - Add maker.paybackBorrowedDai(balances) 20 | - Add priority order of profit operations/ financial models 21 | - Add compound.borrowAndLendDai(balances) 22 | - Add compound.paybackBorrowedDai(balances) 23 | - Add checking for internet, if no internet then wait for a period of time 24 | - Add checking for global/emergency shutdown 25 | - Add option to use Geth instead of Infura 26 | - Add fallback option for web3j connectivity 27 | - Improve method getGasLimit() in class GasProvider 28 | - Use ```web3j.ethEstimateGas(t).send();``` to estimate gas or 29 | - Add a differentiation between contracts to method getGasLimit() in class GasProvider 30 | - Use ```web3j.ethEstimateGas(t).send();``` to estimate gas for calculateGasPriceAsAPercentageOfProfit() instead of constant 31 | - Add checking next Oasis Offer if current offer amount too low 32 | - Add support for multithreading: Multiple application with different web3j objects 33 | - Add a timeout to the transaction confirmation in the command line 34 | - Add do nothing during pending transactions 35 | - Add unstuck pending transactions org.web3j.protocol.exceptions.TransactionException: Transaction receipt was not generated after 600 seconds for transaction: 0x71ac56e8dba69d789a8d5e80e081740a7a75fd1c887f4a8d28fd93f47fd69261 36 | - **Add DSProxy** 37 | - [Defi Saver: Introduction to DSProxy](https://medium.com/defi-saver/a-short-introduction-to-makers-dsproxy-and-why-we-l-it-c88932595be) 38 | - [MakerDAO: DSProxy](https://docs.makerdao.com/daiUser.js/advanced-configuration/using-ds-proxy) 39 | - [DSProxy](https://github.com/dapphub/ds-proxy) 40 | - [Developer Guide: DSProxy](https://github.com/makerdao/developerguides/blob/master/devtools/working-with-dsproxy/working-with-dsproxy.md) 41 | - [Add feeds to Medianizer ](https://www.reddit.com/r/MakerDAO/comments/b96kbg/what_is_the_external_source_of_the_dai_usd_peg/) 42 | - Coinbase 43 | - Gemini 44 | - Bitstamp 45 | - Add polling mode if there is no wallet in the config 46 | - Add flashloans 47 | - For fast unwinding vaults 48 | - Add (maybe) PoolTogether 49 | - Add an Infura class that used the Infura API and the probability of future transactions to manage the requests to Infura 50 | - Add information about interest earned 51 | 52 | 53 | # Performance Improvements 54 | - Analyse why lose bids 55 | - dont bid on single stuff, but general 56 | - medianizer/gasprice 57 | - dxproxy 58 | - Have a look at the Median and the GasPrice update frequency to increase performance and make them dependent on their volatility 59 | - Move Medianizer.PRICE_UPDATE_INTERVAL to config.properties or make it variable 60 | 61 | # Refactoring 62 | - **Add test coverage to Uniswap and Oasis** 63 | - **Fix bug profit provider bug** 64 | - Use Mockito to mock balances and test methods 65 | - Have a look at both uniswap profitable methods, maybe refactor into one? 66 | 67 | ``` 68 | 12:02:55.317 TRACE Uniswap - Profit 2.1924 69 | 12:02:55.318 INFO CompoundDai - CDAI CONVERSION NOT NECESSARY 70 | 12:02:55.545 TRACE Etherchain - ETHERCHAIN SUGGESTS GP 15 GWEI 71 | 12:02:55.755 TRACE ETHGasStation - ETHERGASSTATION SUGGESTS GP 150 GWEI 72 | 12:02:55.755 TRACE GasProvider - GP PERCENTAGE OF PROFIT 0.1 73 | 12:02:55.755 TRACE GasProvider - EST. TRANSACTION FEE 20.3935 DAI 74 | 12:02:55.755 TRACE GasProvider - PROFIT SUGGESTS GP 31006.81311057 GWEI 75 | ``` 76 | - Create TimeUtils and put all the unixTime and timeZone stuff into it 77 | - Fix all to dos 78 | - Implement all empty tests 79 | - Use rules/extension to avoid code duplication in the setup of tests 80 | - Refactor profitable methods to make them more readable 81 | - Test profitable methods 82 | - Fix all sonarlints (log4j2) 83 | - Fix handling of gas price too low: 84 | - Check if Uniswap 0.3% fee is taken into account 85 | - Check existing price feeds for new APIs 86 | - Add telegram notifications about trades and balances 87 | - [Add logging layout](http://logback.qos.ch/manual/layouts.html) 88 | 89 | # Think about 90 | - Check out web3j beginners page: event feed 91 | - Converting data classes to records: https://dzone.com/articles/introducing-java-record, https://blog.jetbrains.com/idea/2020/03/java-14-and-intellij-idea/ 92 | - [Using Quiknode](https://www.quiknode.io/) 93 | - [Running own Ethereum node](https://docs.ethhub.io/using-ethereum/running-an-ethereum-node/) 94 | - [Buy own server to run Ethereum node](https://medium.com/coinmonks/running-ethereum-full-nodes-a-guide-for-the-barely-motivated-a8a13e7a0d31) 95 | - **Convert project to Kotlin** 96 | 97 | # Current limitations 98 | - Where do you run it (AWS or Raspberry Pi)? 99 | - How to you connect to the Ethereum Nethwork (Infura or Geth)? 100 | - How do you get price feeds (Need API keys)? 101 | 102 | # Open Questions 103 | - Will the new OasisDex contract expire like the last one? 104 | 105 | # Others 106 | - write peak total usd balance into java properties and date of it 107 | - redeploy and check for errors/ null pointer exception 108 | - java properties: TOTAL ARBITRAGE P&L, TOTAL MISSED PROFITS, TOTAL P&L DURING EXECUTION -55.15 USD 109 | - interest wins or loses by compound, can get transaction costs out of transaction receipts and compare in and output of daiUser into compound 110 | - partial redeem of compound 111 | - cdai trading on compound 112 | - examine in and out of compound 113 | - CompoundEth.checkBorrowDaiOpportunity(); 114 | - logg all transaction meta data: amount + eth/daiUser 115 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jedibot [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Build Status](https://travis-ci.com/DevelGao/jedibot.svg?branch=master)](https://travis-ci.com/DevelGao/jedibot) [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=DevelGao_jedibot&metric=coverage)](https://sonarcloud.io/dashboard?id=DevelGao_jedibot) 2 | 3 | jedibot is an application that aims to do beneficial actions on the Ethereum blockchain for the [DeFi](https://defipulse.com/) ecosystem while earning a profit for the user. These actions include maintaining the DAI peg, providing liquidity and liquidating undercollateralized assets. 4 | 5 | ## Getting started 6 | 7 | ### Prerequisites 8 | 9 | - Java such as [Oracle Java](https://www.oracle.com/de/java/technologies/javase-downloads.html) 10 | ``` 11 | $ echo $JAVA_HOME 12 | C:\Program Files\Java\jdk-14.0.1 13 | ``` 14 | - Connection to Ethereum Network 15 | - [Infura Project ID](https://infura.io/) 16 | 17 | Alternatives: 18 | - [own Ethereum Node](https://docs.ethhub.io/using-ethereum/running-an-ethereum-node/) 19 | - [Quiknode](https://www.quiknode.io/) 20 | - AWS 21 | 22 | - Ethereum Wallet as keystore file including password from services such as [MyEtherWallet](https://www.myetherwallet.com/) 23 | - [Maven](https://maven.apache.org/download.cgi) 24 | ``` 25 | $ mvn -version 26 | Apache Maven 3.6.3 27 | ``` 28 | ### Installing 29 | 30 | - ```mvn install``` 31 | 32 | ### Configuration 33 | 34 | - Update the configuration file ```./config.properties``` 35 | 36 | ``` 37 | infuraProjectId= 38 | password= 39 | wallet= 40 | transactionsRequireConfirmation=true 41 | playSoundOnTransaction=true 42 | uniswapBuyProfitPercentage=0.5 43 | uniswapSellProfitPercentage=0.5 44 | minimumEthereumReserveUpperLimit=0.20 45 | minimumEthereumReserveLowerLimit=0.10 46 | minimumEthereumNecessaryForSale=1.0 47 | minimumDaiNecessaryForSaleAndLending=250.0 48 | minimumFlipAuctionProfit=50.0 49 | minimumGasPrice=1000000000 50 | maximumGasPrice=30000000000 51 | testProperty=true 52 | ``` 53 | 54 | __Make sure to never commit your ```config.properties``` file!__ 55 | 56 | - Make git stop tracking your config file ```git update-index --skip-worktree ./config.properties``` 57 | - Make git start tracking your config file again ```git update-index --no-skip-worktree ./config.properties``` 58 | 59 | ### Compile 60 | 61 | - ```mvn clean compile assembly:single``` 62 | 63 | ### Running the tests 64 | 65 | - All Tests ```mvn clean test``` 66 | - Unit Tests ```mvn clean test -DskipITs``` 67 | - Integration Tests ```mvn clean failsafe:integration-test``` 68 | 69 | ### Run 70 | 71 | - Either just run it in the IDE of your choice 72 | - or execute the compiled application ```java -jar jedibot-0.1-jar-with-dependencies.jar``` and make sure it has access to the ```config.properties``` file 73 | 74 | ### Logs 75 | 76 | You will find the logs in ```./logs```. 77 | 78 | ## Current features 79 | 80 | - sells DAI, if DAI > $1.00 on Oasis ```oasis.checkIfSellDaiIsProfitableThenDoIt(balances);``` 81 | - buys DAI, if DAI < $1.00 on Oasis ```oasis.checkIfBuyDaiIsProfitableThenDoIt(balances);``` 82 | - sells DAI, if DAI > $1.00 on Uniswap ```uniswap.checkIfSellDaiIsProfitableThenDoIt(balances);``` 83 | - buys DAI, if DAI < $1.00 on Uniswap ```uniswap.checkIfBuyDaiIsProfitableThenDoIt(balances);``` 84 | - earns interest on Compound, if there has been no market action for a while ```compoundDai.lendDai(balances);``` 85 | - bids on flip auctions, if the auctions offer cheaper ETH```flipper.checkIfThereAreProfitableFlipAuctions(balances);``` 86 | 87 | ## Contribute 88 | 89 | Feel free to open merge requests. 90 | 91 | ## Developer Guide 92 | 93 | - Code Style: [Google Java Format](https://github.com/google/google-java-format/blob/master/README.md) 94 | - Linting: [Sonarlint](https://www.sonarlint.org/intellij/) 95 | - Add new smart contracts: [web3j](https://github.com/web3j/web3j) 96 | - Logging: SLF4J + logback is used as defined in ```./src/main/resources/logback.xml``` 97 | - Update Maven Dependencies: ```mvn versions:use-latest-versions``` 98 | - Show updatable dependencies: ```mvn versions:display-dependency-updates``` 99 | - Show unused dependencies: ```mvn dependency:analyze``` 100 | - [MakerDAO Documentation](https://docs.makerdao.com/) 101 | - [Web3j Documentation](https://docs.web3j.io/) 102 | - [Compound Documentation](https://compound.finance/docs) 103 | - [DAI Stats](https://daistats.com) 104 | - [ETH Gas Station](https://ethgasstation.info) 105 | - [DAI Peg: DAI Descipher](http://dai.descipher.io) 106 | - [DAI Peg: DAI Stablecoin](https://dai.stablecoin.science/) 107 | - [Loanscan](https://loanscan.io/) 108 | - [Flip auctions](https://daiauctions.com/flip) 109 | 110 | ## License 111 | 112 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details 113 | -------------------------------------------------------------------------------- /config.properties: -------------------------------------------------------------------------------- 1 | infuraProjectId=yourInfuraId 2 | password=yourPassword 3 | wallet=yourWallet 4 | transactionsRequireConfirmation=true 5 | playSoundOnTransaction=true 6 | uniswapBuyProfitPercentage=0.75 7 | uniswapSellProfitPercentage=0.75 8 | minimumEthereumReserveUpperLimit=0.20 9 | minimumEthereumReserveLowerLimit=0.10 10 | minimumEthereumNecessaryForSale=1.0 11 | minimumDaiNecessaryForSaleAndLending=250.0 12 | minimumFlipAuctionProfit=10.0 13 | # 1_000000000 = 1 GWEI 14 | minimumGasPrice=1000000000 15 | # 30_000000000L = 30 GWEI 16 | maximumGasPrice=100000000000 17 | # Just for testing 18 | testProperty=true 19 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 4.0.0 5 | devgao.io 6 | jedibot 7 | 0.1 8 | jar 9 | 10 | 11 | UTF-8 12 | UTF-8 13 | 14 | 15 | 16 | 17 | 18 | org.web3j 19 | core 20 | 5.0.0 21 | 22 | 23 | org.apache.commons 24 | commons-lang3 25 | 3.10 26 | 27 | 28 | 29 | commons-configuration 30 | commons-configuration 31 | 1.10 32 | 33 | 34 | 35 | org.junit.jupiter 36 | junit-jupiter-api 37 | 5.7.0-M1 38 | test 39 | 40 | 41 | org.mockito 42 | mockito-core 43 | 3.3.3 44 | test 45 | 46 | 47 | org.mockito 48 | mockito-junit-jupiter 49 | 3.3.3 50 | test 51 | 52 | 53 | 54 | org.web3j 55 | abi 56 | 5.0.0 57 | 58 | 59 | 60 | org.web3j 61 | tuples 62 | 5.0.0 63 | 64 | 65 | 66 | org.web3j 67 | crypto 68 | 5.0.0 69 | 70 | 71 | 72 | org.web3j 73 | utils 74 | 5.0.0 75 | 76 | 77 | 78 | org.jetbrains 79 | annotations 80 | 19.0.0 81 | 82 | 83 | 84 | io.reactivex.rxjava2 85 | rxjava 86 | 2.2.19 87 | 88 | 89 | org.slf4j 90 | slf4j-api 91 | 2.0.0-alpha1 92 | 93 | 94 | ch.qos.logback 95 | logback-classic 96 | 1.3.0-alpha5 97 | runtime 98 | 99 | 100 | 101 | org.hamcrest 102 | java-hamcrest 103 | 2.0.0.0 104 | test 105 | 106 | 107 | org.junit-pioneer 108 | junit-pioneer 109 | 0.6.0 110 | test 111 | 112 | 113 | 114 | 115 | 116 | 117 | org.apache.maven.plugins 118 | maven-compiler-plugin 119 | 3.8.0 120 | 121 | 13 122 | 13 123 | 13 124 | 125 | 126 | 127 | org.apache.maven.plugins 128 | maven-surefire-plugin 129 | 3.0.0-M4 130 | 131 | 132 | 133 | 134 | org.apache.maven.plugins 135 | maven-failsafe-plugin 136 | 3.0.0-M4 137 | 138 | 139 | 140 | integration-test 141 | verify 142 | 143 | 144 | 145 | 146 | 147 | maven-assembly-plugin 148 | 149 | 150 | 151 | devgao.io.Main 152 | 153 | 154 | 155 | jar-with-dependencies 156 | 157 | 158 | 159 | 160 | ${basedir}/src/main/java 161 | ${basedir}/src/test/java 162 | 163 | 164 | 165 | 166 | coverage 167 | 168 | 169 | 170 | org.jacoco 171 | jacoco-maven-plugin 172 | 0.8.5 173 | 174 | 175 | prepare-agent 176 | 177 | prepare-agent 178 | 179 | 180 | 181 | report 182 | 183 | report 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | -------------------------------------------------------------------------------- /src/main/java/META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Main-Class: devgao.io.Main 3 | 4 | -------------------------------------------------------------------------------- /src/main/java/de/devgao/defi/Main.java: -------------------------------------------------------------------------------- 1 | package devgao.io; 2 | 3 | import devgao.io.compounddai.CompoundDai; 4 | import devgao.io.contractneedsprovider.*; 5 | import devgao.io.dai.Dai; 6 | import devgao.io.flipper.Flipper; 7 | import devgao.io.gasprovider.GasProvider; 8 | import devgao.io.medianizer.Medianizer; 9 | import devgao.io.numberutil.Wad18; 10 | import devgao.io.oasis.Oasis; 11 | import devgao.io.uniswap.Uniswap; 12 | import devgao.io.util.Balances; 13 | import devgao.io.util.Ethereum; 14 | import devgao.io.util.JavaProperties; 15 | import devgao.io.weth.Weth; 16 | import org.slf4j.LoggerFactory; 17 | import org.web3j.crypto.Credentials; 18 | import org.web3j.protocol.Web3j; 19 | 20 | import java.lang.invoke.MethodHandles; 21 | import java.math.BigInteger; 22 | import java.util.List; 23 | import java.util.concurrent.TimeUnit; 24 | 25 | public class Main { 26 | private static final org.slf4j.Logger logger = 27 | LoggerFactory.getLogger(MethodHandles.lookup().lookupClass().getSimpleName()); 28 | private static final boolean IS_DEVELOPMENT_ENVIRONMENT = true; 29 | private static Web3j web3j; 30 | 31 | public static void main(String[] args) { 32 | logger.trace("NEW START"); 33 | JavaProperties javaProperties = new JavaProperties(IS_DEVELOPMENT_ENVIRONMENT); 34 | String password = javaProperties.getValue("password"); 35 | String infuraProjectId = javaProperties.getValue("infuraProjectId"); 36 | String wallet = javaProperties.getValue("wallet"); 37 | boolean playSoundOnTransaction = 38 | Boolean.parseBoolean(javaProperties.getValue("playSoundOnTransaction")); 39 | boolean transactionsRequireConfirmation = 40 | Boolean.parseBoolean(javaProperties.getValue("transactionsRequireConfirmation")); 41 | 42 | CircuitBreaker circuitBreaker = new CircuitBreaker(); 43 | web3j = new Web3jProvider(infuraProjectId).web3j; 44 | Credentials credentials = new Wallet(password, wallet).getCredentials(); 45 | GasProvider gasProvider = 46 | new GasProvider( 47 | web3j, 48 | new Wad18(BigInteger.valueOf(Long.parseLong(javaProperties.getValue("minimumGasPrice")))), 49 | new Wad18(BigInteger.valueOf(Long.parseLong(javaProperties.getValue("maximumGasPrice"))))); 50 | Permissions permissions = 51 | new Permissions(transactionsRequireConfirmation, playSoundOnTransaction); 52 | ContractNeedsProvider contractNeedsProvider = 53 | new ContractNeedsProvider(web3j, credentials, gasProvider, permissions, circuitBreaker); 54 | 55 | Medianizer.setMedianizerContract(contractNeedsProvider); 56 | Dai dai = 57 | new Dai( 58 | contractNeedsProvider, 59 | Double.parseDouble(javaProperties.getValue("minimumDaiNecessaryForSaleAndLending"))); 60 | Weth weth = new Weth(contractNeedsProvider); 61 | CompoundDai compoundDai = new CompoundDai(contractNeedsProvider); 62 | Ethereum ethereum = 63 | new Ethereum( 64 | contractNeedsProvider, 65 | Double.parseDouble(javaProperties.getValue("minimumEthereumReserveUpperLimit")), 66 | Double.parseDouble(javaProperties.getValue("minimumEthereumReserveLowerLimit")), 67 | Double.parseDouble(javaProperties.getValue("minimumEthereumNecessaryForSale"))); 68 | 69 | Balances balances = new Balances(dai, weth, compoundDai, ethereum); 70 | 71 | Oasis oasis = new Oasis(contractNeedsProvider, compoundDai, weth); 72 | Uniswap uniswap = new Uniswap(contractNeedsProvider, javaProperties, compoundDai, weth); 73 | Flipper flipper = 74 | new Flipper( 75 | contractNeedsProvider, 76 | Double.parseDouble(javaProperties.getValue("minimumFlipAuctionProfit"))); 77 | 78 | dai.getApproval().check(uniswap); 79 | dai.getApproval().check(oasis); 80 | dai.getApproval().check(compoundDai); 81 | weth.getApproval().check(oasis); 82 | 83 | while (circuitBreaker.getContinueRunning()) { 84 | try { 85 | balances.updateBalance(60); 86 | if (circuitBreaker.isAllowingOperations(3)) { 87 | // TODO: if infura backoff exception, then backoff 88 | balances.checkEnoughEthereumForGas(ethereum); 89 | oasis.checkIfSellDaiIsProfitableThenDoIt(balances); 90 | oasis.checkIfBuyDaiIsProfitableThenDoIt(balances); 91 | uniswap.checkIfSellDaiIsProfitableThenDoIt(balances); 92 | uniswap.checkIfBuyDaiIsProfitableThenDoIt(balances); 93 | compoundDai.lendDai(balances); 94 | flipper.checkIfThereAreProfitableFlipAuctions(balances); 95 | } 96 | } catch (Exception e) { 97 | logger.error("Exception", e); 98 | shutdown(); 99 | } 100 | 101 | List failedTransactions = circuitBreaker.getFailedTransactions(); 102 | if (!failedTransactions.isEmpty()) { 103 | circuitBreaker.update(); 104 | gasProvider.updateFailedTransactions(failedTransactions); 105 | } 106 | 107 | try { 108 | TimeUnit.MILLISECONDS.sleep(8500); 109 | } catch (InterruptedException e) { 110 | logger.error("Exception", e); 111 | Thread.currentThread().interrupt(); 112 | } 113 | } 114 | shutdown(); 115 | } 116 | 117 | public static void shutdown() { 118 | logger.trace("EXIT"); 119 | web3j.shutdown(); 120 | System.exit(0); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/main/java/de/devgao/defi/compoundeth/CompoundEth.java: -------------------------------------------------------------------------------- 1 | package devgao.io.compoundeth; 2 | 3 | import devgao.io.contractneedsprovider.ContractNeedsProvider; 4 | import devgao.io.contractneedsprovider.Permissions; 5 | import devgao.io.numberutil.Wad18; 6 | import devgao.io.util.Balances; 7 | import org.jetbrains.annotations.NotNull; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import java.lang.invoke.MethodHandles; 11 | import java.math.BigInteger; 12 | 13 | /** 14 | * @deprecated This class is currently unused. It can be used to borrow DAI against WETH, but 15 | * currently MakerDAO is cheaper. 16 | */ 17 | @Deprecated(since = "0.0.1", forRemoval = false) 18 | public class CompoundEth { 19 | public static final String ADDRESS = "0x4Ddc2D193948926D02f9B1fE9e1daa0718270ED5"; 20 | public static final BigInteger gasLimit = 21 | BigInteger.valueOf(100000); // https://compound.finance/developers#gas-costs 22 | private static final org.slf4j.Logger logger = 23 | LoggerFactory.getLogger(MethodHandles.lookup().lookupClass().getSimpleName()); 24 | private static final String EXCEPTION = "Exception"; 25 | private static CompoundEthContract contract; 26 | private final Permissions permissions; 27 | 28 | public CompoundEth(@NotNull ContractNeedsProvider contractNeedsProvider) { 29 | this.permissions = contractNeedsProvider.getPermissions(); 30 | } 31 | 32 | static Wad18 getExchangeRate() { 33 | try { 34 | return new Wad18(contract.exchangeRateStored().send()); 35 | } catch (Exception e) { 36 | logger.error(EXCEPTION, e); 37 | } 38 | return Wad18.ZERO; 39 | } 40 | 41 | void borrow(Wad18 borrowAmount) { 42 | if (permissions.check("COMPOUND ETH BORROW " + borrowAmount)) { 43 | try { 44 | contract.borrow(borrowAmount.toBigInteger()).send(); 45 | logger.warn("BORROW ETH {}", borrowAmount); 46 | } catch (Exception e) { 47 | logger.error(EXCEPTION, e); 48 | } 49 | } 50 | } 51 | 52 | void repayBorrow(Wad18 repayAmount) { 53 | if (permissions.check("COMPOUND ETH REPAY " + repayAmount)) { 54 | try { 55 | contract.repayBorrow(repayAmount.toBigInteger()).send(); 56 | logger.warn("REPAY ETH {}", repayAmount); 57 | } catch (Exception e) { 58 | logger.error(EXCEPTION, e); 59 | } 60 | } 61 | } 62 | 63 | Wad18 getCTokenBalance(String ethereumAddress) { 64 | try { 65 | return new Wad18(contract.balanceOf(ethereumAddress).send()); 66 | } catch (Exception e) { 67 | logger.error(EXCEPTION, e); 68 | } 69 | return Wad18.ZERO; 70 | } 71 | 72 | Wad18 getBalance(String ethereumAddress) { 73 | return getExchangeRate().multiply(getCTokenBalance(ethereumAddress)); 74 | } 75 | 76 | // borrow dai against weth and sell it, if dai price is high 77 | void checkBorrowDaiOpportunity(@NotNull Balances balances) { 78 | if (balances.isThereTooFewEthAndWethForSaleAndLending(balances.ethereum)) { 79 | logger.warn("NOT ENOUGH ETH OR WETH TO BORROW DAI ON COMPOUND"); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/de/devgao/defi/contractneedsprovider/CircuitBreaker.java: -------------------------------------------------------------------------------- 1 | package devgao.io.contractneedsprovider; 2 | 3 | import org.slf4j.LoggerFactory; 4 | 5 | import java.lang.invoke.MethodHandles; 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | public class CircuitBreaker { 10 | private static final org.slf4j.Logger logger = 11 | LoggerFactory.getLogger(MethodHandles.lookup().lookupClass().getSimpleName()); 12 | final ArrayList failedTransactionsWithinTheLastTenMinutesForErrorBlockingArrayList; 13 | private boolean continueRunning = true; 14 | 15 | public CircuitBreaker() { 16 | failedTransactionsWithinTheLastTenMinutesForErrorBlockingArrayList = new ArrayList<>(); 17 | } 18 | 19 | public boolean getContinueRunning() { 20 | return continueRunning; 21 | } 22 | 23 | public void stopRunning() { 24 | continueRunning = false; 25 | } 26 | 27 | public boolean isAllowingOperations(int number) { 28 | boolean isAllowingOperations = 29 | failedTransactionsWithinTheLastTenMinutesForErrorBlockingArrayList.size() < number; 30 | if (!isAllowingOperations) { 31 | logger.trace( 32 | "ALL TRANSACTIONS ARE CURRENTLY NOT ALLOWED BECAUSE THERE HAVE BEEN TOO MANY FAILED TRANSACTIONS RECENTLY"); 33 | } 34 | return isAllowingOperations; 35 | } 36 | 37 | public void addTransactionFailedNow() { 38 | logger.trace("ADD A FAILED TRANSACTION"); 39 | failedTransactionsWithinTheLastTenMinutesForErrorBlockingArrayList.add( 40 | System.currentTimeMillis()); 41 | } 42 | 43 | public void update() { 44 | if (System.currentTimeMillis() 45 | >= failedTransactionsWithinTheLastTenMinutesForErrorBlockingArrayList.get(0) 46 | + 10 * 60 * 1000) { 47 | logger.trace("REMOVE FAILED TRANSACTION: 10 MINUTES"); 48 | failedTransactionsWithinTheLastTenMinutesForErrorBlockingArrayList.remove(0); 49 | } 50 | } 51 | 52 | public List getFailedTransactions() { 53 | return failedTransactionsWithinTheLastTenMinutesForErrorBlockingArrayList; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/de/devgao/defi/contractneedsprovider/ContractNeedsProvider.java: -------------------------------------------------------------------------------- 1 | package devgao.io.contractneedsprovider; 2 | 3 | import devgao.io.gasprovider.GasProvider; 4 | import org.web3j.crypto.Credentials; 5 | import org.web3j.protocol.Web3j; 6 | 7 | public class ContractNeedsProvider { 8 | private final Web3j web3j; 9 | private final Credentials credentials; 10 | private final GasProvider gasProvider; 11 | private final Permissions permissions; 12 | private final CircuitBreaker circuitBreaker; 13 | 14 | public ContractNeedsProvider( 15 | Web3j web3j, 16 | Credentials credentials, 17 | GasProvider gasProvider, 18 | Permissions permissions, 19 | CircuitBreaker circuitBreaker) { 20 | this.web3j = web3j; 21 | this.credentials = credentials; 22 | this.gasProvider = gasProvider; 23 | this.permissions = permissions; 24 | this.circuitBreaker = circuitBreaker; 25 | } 26 | 27 | public Web3j getWeb3j() { 28 | return web3j; 29 | } 30 | 31 | public Credentials getCredentials() { 32 | return credentials; 33 | } 34 | 35 | public GasProvider getGasProvider() { 36 | return gasProvider; 37 | } 38 | 39 | public Permissions getPermissions() { 40 | return permissions; 41 | } 42 | 43 | public CircuitBreaker getCircuitBreaker() { 44 | return circuitBreaker; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/de/devgao/defi/contractneedsprovider/Permissions.java: -------------------------------------------------------------------------------- 1 | package devgao.io.contractneedsprovider; 2 | 3 | import org.slf4j.LoggerFactory; 4 | 5 | import java.awt.*; 6 | import java.io.BufferedReader; 7 | import java.io.IOException; 8 | import java.io.InputStreamReader; 9 | import java.lang.invoke.MethodHandles; 10 | 11 | public class Permissions { 12 | private static final org.slf4j.Logger logger = 13 | LoggerFactory.getLogger(MethodHandles.lookup().lookupClass().getSimpleName()); 14 | 15 | private final boolean playSoundOnTransaction; 16 | private final boolean transactionRequiresConfirmation; 17 | 18 | public Permissions(boolean transactionRequiresConfirmation, boolean playSoundOnTransaction) { 19 | this.transactionRequiresConfirmation = transactionRequiresConfirmation; 20 | this.playSoundOnTransaction = playSoundOnTransaction; 21 | } 22 | 23 | public boolean check(String transactionInformation) { 24 | if (playSoundOnTransaction) { 25 | final Runnable runnable = 26 | (Runnable) Toolkit.getDefaultToolkit().getDesktopProperty("win.sound.exclamation"); 27 | if (runnable != null) runnable.run(); 28 | } 29 | 30 | if (transactionRequiresConfirmation) { 31 | logger.warn(transactionInformation); 32 | 33 | BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); 34 | logger.warn("PLEASE CONFIRM {}{}", transactionInformation, " TRANSACTION WITH [confirm] "); 35 | 36 | String isConfirmed = null; 37 | try { 38 | isConfirmed = reader.readLine(); 39 | } catch (IOException e) { 40 | logger.error("IOException", e); 41 | } 42 | assert isConfirmed != null; 43 | if (isConfirmed.equals("confirm")) { 44 | logger.info("TRANSACTION PERMITTED."); 45 | return true; 46 | } else { 47 | logger.info("TRANSACTION DENIED."); 48 | return false; 49 | } 50 | } else { 51 | logger.trace("TRANSACTION {} DOES NOT REQUIRE CONFIRMATION", transactionInformation); 52 | } 53 | return true; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/de/devgao/defi/contractneedsprovider/Wallet.java: -------------------------------------------------------------------------------- 1 | package devgao.io.contractneedsprovider; 2 | 3 | import org.slf4j.LoggerFactory; 4 | import org.web3j.crypto.Credentials; 5 | import org.web3j.crypto.WalletUtils; 6 | 7 | import java.lang.invoke.MethodHandles; 8 | 9 | public class Wallet { 10 | private static final org.slf4j.Logger logger = 11 | LoggerFactory.getLogger(MethodHandles.lookup().lookupClass().getSimpleName()); 12 | 13 | private Credentials credentials; 14 | 15 | public Wallet(String password, String wallet) { 16 | if (wallet.equals("") || password.equals("")) { 17 | logger.error("WALLET OR PASSWORD IS EMPTY"); 18 | } 19 | 20 | try { 21 | credentials = WalletUtils.loadJsonCredentials(password, wallet); 22 | logger.trace("Credentials loaded"); 23 | } catch (Exception e) { 24 | logger.error("Exception", e); 25 | } 26 | } 27 | 28 | public Credentials getCredentials() { 29 | return credentials; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/de/devgao/defi/contractneedsprovider/Web3jProvider.java: -------------------------------------------------------------------------------- 1 | package devgao.io.contractneedsprovider; 2 | 3 | import org.slf4j.LoggerFactory; 4 | import org.web3j.protocol.Web3j; 5 | import org.web3j.protocol.exceptions.ClientConnectionException; 6 | import org.web3j.protocol.http.HttpService; 7 | 8 | import java.lang.invoke.MethodHandles; 9 | 10 | public class Web3jProvider { 11 | private static final org.slf4j.Logger logger = 12 | LoggerFactory.getLogger(MethodHandles.lookup().lookupClass().getSimpleName()); 13 | 14 | public final Web3j web3j; 15 | 16 | public Web3jProvider(String infuraProjectId) { 17 | this.web3j = Web3j.build(new HttpService("https://mainnet.infura.io/v3/" + infuraProjectId)); 18 | try { 19 | logger.trace( 20 | "Connected to Ethereum client version: {}", 21 | web3j.web3ClientVersion().send().getWeb3ClientVersion()); 22 | } catch (Exception e) { 23 | if (e instanceof ClientConnectionException) { 24 | logger.error("Check your infuraProjectId in the config.properties file."); 25 | } 26 | logger.error("Exception -> Exit", e); 27 | System.exit(0); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/de/devgao/defi/contractuserutil/AddressMethod.java: -------------------------------------------------------------------------------- 1 | package devgao.io.contractuserutil; 2 | 3 | public interface AddressMethod { 4 | String getAddress(); 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/de/devgao/defi/contractutil/Account.java: -------------------------------------------------------------------------------- 1 | package devgao.io.contractutil; 2 | 3 | import devgao.io.numberutil.Wad18; 4 | import org.slf4j.LoggerFactory; 5 | import org.web3j.crypto.Credentials; 6 | 7 | import java.lang.invoke.MethodHandles; 8 | 9 | public class Account { 10 | private static final org.slf4j.Logger logger = 11 | LoggerFactory.getLogger(MethodHandles.lookup().lookupClass().getSimpleName()); 12 | private static final String EXCEPTION = "Exception"; 13 | private final AccountMethod contract; 14 | private final Credentials credentials; 15 | private final String name; 16 | 17 | private Wad18 balance; 18 | 19 | public Account(AccountMethod contract, Credentials credentials, String name) { 20 | this.contract = contract; 21 | this.credentials = credentials; 22 | this.name = name; 23 | update(); 24 | } 25 | 26 | public Wad18 getBalance() { 27 | if (balance.compareTo(Wad18.ZERO) != 0) logger.trace("{} BALANCE {} {}", name, balance, name); 28 | return balance; 29 | } 30 | 31 | public void update() { 32 | Wad18 oldBalance = balance; 33 | try { 34 | balance = new Wad18(contract.balanceOf(credentials.getAddress()).send()); 35 | } catch (Exception e) { 36 | logger.error(EXCEPTION, e); 37 | balance = Wad18.ZERO; 38 | } 39 | if (oldBalance != null && oldBalance.compareTo(balance) != 0) { 40 | logger.trace("OLD BALANCE {} {}", oldBalance, name); 41 | logger.trace("UPDATED BALANCE {} {}", balance, name); 42 | } else if (oldBalance == null) { 43 | logger.trace("{} BALANCE {} {}", name, balance, name); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/de/devgao/defi/contractutil/AccountMethod.java: -------------------------------------------------------------------------------- 1 | package devgao.io.contractutil; 2 | 3 | import org.web3j.protocol.core.RemoteFunctionCall; 4 | 5 | import java.math.BigInteger; 6 | 7 | public interface AccountMethod { 8 | RemoteFunctionCall balanceOf(String param0); 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/de/devgao/defi/contractutil/Approval.java: -------------------------------------------------------------------------------- 1 | package devgao.io.contractutil; 2 | 3 | import devgao.io.contractneedsprovider.ContractNeedsProvider; 4 | import devgao.io.contractuserutil.AddressMethod; 5 | import devgao.io.numberutil.Wad18; 6 | import org.slf4j.LoggerFactory; 7 | 8 | import java.lang.invoke.MethodHandles; 9 | 10 | import static devgao.io.numberutil.NumberUtil.MINIMUM_APPROVAL_ALLOWANCE; 11 | import static devgao.io.numberutil.NumberUtil.UINT_MAX; 12 | 13 | public class Approval { 14 | private static final org.slf4j.Logger logger = 15 | LoggerFactory.getLogger(MethodHandles.lookup().lookupClass().getSimpleName()); 16 | private static final String EXCEPTION = "Exception"; 17 | private final ApprovalMethod contract; 18 | private final ContractNeedsProvider contractNeedsProvider; 19 | 20 | public Approval(ApprovalMethod contract, ContractNeedsProvider contractNeedsProvider) { 21 | this.contract = contract; 22 | this.contractNeedsProvider = contractNeedsProvider; 23 | } 24 | 25 | private void approve(String address, String name) { 26 | if (contractNeedsProvider.getPermissions().check("DAI UNLOCK " + name)) { 27 | try { 28 | contractNeedsProvider.getGasProvider().updateSlowGasPrice(); 29 | contract.approve(address, UINT_MAX).send(); 30 | logger.debug("{} UNLOCK DAI", name); 31 | } catch (Exception e) { 32 | logger.error(EXCEPTION, e); 33 | contractNeedsProvider.getCircuitBreaker().addTransactionFailedNow(); 34 | } 35 | } 36 | } 37 | 38 | public void check(AddressMethod toAllowContract) { 39 | try { 40 | String address = toAllowContract.getAddress(); 41 | Wad18 allowance = 42 | new Wad18( 43 | contract 44 | .allowance(contractNeedsProvider.getCredentials().getAddress(), address) 45 | .send()); 46 | if (allowance.toBigInteger().compareTo(MINIMUM_APPROVAL_ALLOWANCE) < 0) { 47 | logger.warn("DAI ALLOWANCE IS TOO LOW {}", allowance); 48 | approve(address, toAllowContract.getClass().getName()); 49 | } 50 | } catch (Exception e) { 51 | logger.error(EXCEPTION, e); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/de/devgao/defi/contractutil/ApprovalMethod.java: -------------------------------------------------------------------------------- 1 | package devgao.io.contractutil; 2 | 3 | import org.web3j.protocol.core.RemoteFunctionCall; 4 | import org.web3j.protocol.core.methods.response.TransactionReceipt; 5 | 6 | import java.math.BigInteger; 7 | 8 | public interface ApprovalMethod { 9 | RemoteFunctionCall approve(String guy, BigInteger wad); 10 | 11 | RemoteFunctionCall allowance(String param0, String param1); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/de/devgao/defi/contractutil/ContractValidationUtil.java: -------------------------------------------------------------------------------- 1 | package devgao.io.contractutil; 2 | 3 | import devgao.io.contractneedsprovider.CircuitBreaker; 4 | import org.slf4j.LoggerFactory; 5 | import org.web3j.tx.Contract; 6 | 7 | import java.lang.invoke.MethodHandles; 8 | 9 | public class ContractValidationUtil { 10 | private static final org.slf4j.Logger logger = 11 | LoggerFactory.getLogger(MethodHandles.lookup().lookupClass().getSimpleName()); 12 | private static final String EXCEPTION = "Exception"; 13 | 14 | private ContractValidationUtil() { 15 | throw new IllegalStateException("Utility class"); 16 | } 17 | 18 | public static void isContractValid(Contract contract, CircuitBreaker circuitBreaker) { 19 | try { 20 | if (contract.isValid()) { 21 | logger.trace("{} CONTRACT IS VALID", contract.getClass()); 22 | } else { 23 | logger.trace("{} CONTRACT IS INVALID", contract.getClass()); 24 | circuitBreaker.stopRunning(); 25 | } 26 | } catch (Exception e) { 27 | circuitBreaker.stopRunning(); 28 | logger.error(EXCEPTION, e); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/de/devgao/defi/dai/Dai.java: -------------------------------------------------------------------------------- 1 | package devgao.io.dai; 2 | 3 | import devgao.io.contractneedsprovider.ContractNeedsProvider; 4 | import devgao.io.contractutil.Account; 5 | import devgao.io.contractutil.Approval; 6 | import devgao.io.gasprovider.GasProvider; 7 | import devgao.io.numberutil.Wad18; 8 | import org.jetbrains.annotations.NotNull; 9 | import org.slf4j.LoggerFactory; 10 | import org.web3j.crypto.Credentials; 11 | import org.web3j.protocol.Web3j; 12 | import org.web3j.protocol.core.methods.response.TransactionReceipt; 13 | 14 | import java.lang.invoke.MethodHandles; 15 | 16 | import static devgao.io.numberutil.NumberUtil.UINT_MAX; 17 | import static devgao.io.numberutil.NumberUtil.getMachineReadable; 18 | 19 | public class Dai { 20 | public static final String ADDRESS = "0x6b175474e89094c44da98b954eedeac495271d0f"; 21 | private static final org.slf4j.Logger logger = 22 | LoggerFactory.getLogger(MethodHandles.lookup().lookupClass().getSimpleName()); 23 | private static final String CDP_ADDRESS = "0x0000000000000000000000000000000000000000"; 24 | private static final String EXCEPTION = "Exception"; 25 | public final Wad18 minimumDaiNecessaryForSaleAndLending; 26 | private final DaiContract daiContract; 27 | private final Account account; 28 | private final Approval approval; 29 | 30 | public Dai( 31 | @NotNull ContractNeedsProvider contractNeedsProvider, 32 | double minimumDaiNecessaryForSaleAndLending) { 33 | Web3j web3j = contractNeedsProvider.getWeb3j(); 34 | Credentials credentials = contractNeedsProvider.getCredentials(); 35 | GasProvider gasProvider = contractNeedsProvider.getGasProvider(); 36 | this.minimumDaiNecessaryForSaleAndLending = 37 | new Wad18(getMachineReadable(minimumDaiNecessaryForSaleAndLending)); 38 | daiContract = DaiContract.load(ADDRESS, web3j, credentials, gasProvider); 39 | account = new Account(daiContract, credentials, "DAI"); 40 | approval = new Approval(daiContract, contractNeedsProvider); 41 | } 42 | 43 | /** @deprecated DO NOT USE CDP ADDRESS THIS IS WRONG ADDRESS */ 44 | @Deprecated(since = "0.0.1", forRemoval = false) 45 | private void cdpUnlockDai() { 46 | try { 47 | TransactionReceipt transferReceipt = daiContract.approve(CDP_ADDRESS, UINT_MAX).send(); 48 | 49 | logger.debug( 50 | "Transaction complete, view it at https://etherscan.io/tx/{}", 51 | transferReceipt.getTransactionHash()); 52 | 53 | } catch (Exception e) { 54 | logger.error(EXCEPTION, e); 55 | } 56 | } 57 | 58 | public Account getAccount() { 59 | return account; 60 | } 61 | 62 | public boolean isThereEnoughDaiForLending() { 63 | return account.getBalance().compareTo(minimumDaiNecessaryForSaleAndLending) > 0; 64 | } 65 | 66 | public Approval getApproval() { 67 | return approval; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/de/devgao/defi/flipper/Auction.java: -------------------------------------------------------------------------------- 1 | package devgao.io.flipper; 2 | 3 | import devgao.io.numberutil.Rad45; 4 | import devgao.io.numberutil.Wad18; 5 | import org.jetbrains.annotations.NotNull; 6 | import org.slf4j.LoggerFactory; 7 | import org.web3j.crypto.Credentials; 8 | import org.web3j.tuples.generated.Tuple8; 9 | 10 | import java.lang.invoke.MethodHandles; 11 | import java.math.BigInteger; 12 | import java.time.Instant; 13 | import java.time.ZoneId; 14 | import java.time.format.DateTimeFormatter; 15 | import java.util.TimeZone; 16 | 17 | public class Auction { 18 | public static final DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy_MM_dd HH_mm_ss"); 19 | private static final org.slf4j.Logger logger = 20 | LoggerFactory.getLogger(MethodHandles.lookup().lookupClass().getSimpleName()); 21 | public final Rad45 bidAmountInDai; // bid 22 | public final Wad18 collateralForSale; // lot 23 | public final String highestBidder; // guy 24 | public final Wad18 bidExpiry; // tic 25 | public final Wad18 maxAuctionDuration; // end 26 | public final String addressOfAuctionedVault; // usr 27 | public final String recipientOfAuctionIncome; // gal 28 | public final Rad45 totalDaiWanted; // tab 29 | public final BigInteger id; // id 30 | 31 | Auction( 32 | BigInteger id, 33 | @NotNull 34 | Tuple8 35 | auctionTuple) { 36 | this.id = id; 37 | this.bidAmountInDai = new Rad45(auctionTuple.component1()); 38 | this.collateralForSale = new Wad18(auctionTuple.component2()); 39 | this.highestBidder = auctionTuple.component3(); 40 | this.bidExpiry = new Wad18(auctionTuple.component4()); 41 | this.maxAuctionDuration = new Wad18(auctionTuple.component5()); 42 | this.addressOfAuctionedVault = auctionTuple.component6(); 43 | this.recipientOfAuctionIncome = auctionTuple.component7(); 44 | this.totalDaiWanted = new Rad45(auctionTuple.component8()); 45 | logger.trace("AUCTION CONSTRUCTED {}", this); 46 | } 47 | 48 | boolean isDent(Wad18 minimumBidIncrease) { 49 | return totalDaiWanted.compareTo(bidAmountInDai.multiply(minimumBidIncrease)) <= 0; 50 | } 51 | 52 | boolean isInDefinedBiddingPhase(BigInteger biddingPeriod, boolean isDent) { 53 | long currentUnixTime = System.currentTimeMillis() / 1000L; 54 | if (isDent) { 55 | return !isCompleted() 56 | && bidExpiry.min(maxAuctionDuration).longValue() - biddingPeriod.longValue() 57 | < currentUnixTime; 58 | } 59 | return !isCompleted() 60 | && maxAuctionDuration.longValue() - biddingPeriod.longValue() < currentUnixTime; 61 | } 62 | 63 | boolean amIHighestBidder(@NotNull Credentials credentials) { 64 | return highestBidder.equalsIgnoreCase(credentials.getAddress()); 65 | } 66 | 67 | Wad18 getPotentialProfit(Wad18 minimumBidIncrease, @NotNull Wad18 median) { 68 | Wad18 marketPrice = median.multiply(collateralForSale); // todo: test this properly 69 | Wad18 auctionPrice = bidAmountInDai.multiply(minimumBidIncrease); 70 | Wad18 potentialProfit = marketPrice.subtract(auctionPrice); 71 | logger.trace("AUCTION {} HAS POTENTIAL PROFIT {} DAI", id, potentialProfit); 72 | return potentialProfit; 73 | } 74 | 75 | boolean isAffordable(Wad18 minimumBidIncrease, @NotNull Wad18 val) { 76 | Wad18 auctionPrice = bidAmountInDai.multiply(minimumBidIncrease); 77 | boolean isAffordable = auctionPrice.compareTo(val) < 0; 78 | logger.trace("AUCTION IS AFFORDABLE {}", isAffordable); 79 | return isAffordable; 80 | } 81 | 82 | boolean isActive() { 83 | return !isEmpty() && !isCompleted(); 84 | } 85 | 86 | boolean isEmpty() { 87 | String burnAddress = "0x0000000000000000000000000000000000000000"; 88 | boolean isEmpty = 89 | bidAmountInDai.compareTo(Rad45.ZERO) == 0 90 | && collateralForSale.compareTo(Wad18.ZERO) == 0 91 | && highestBidder.equalsIgnoreCase(burnAddress) 92 | && bidExpiry.compareTo(Wad18.ZERO) == 0 93 | && maxAuctionDuration.compareTo(Wad18.ZERO) == 0 94 | && addressOfAuctionedVault.equalsIgnoreCase(burnAddress) 95 | && recipientOfAuctionIncome.equalsIgnoreCase(burnAddress) 96 | && totalDaiWanted.compareTo(Rad45.ZERO) == 0; 97 | logger.trace("AUCTION IS EMPTY {}", isEmpty); 98 | return isEmpty; 99 | } 100 | 101 | boolean isCompleted() { 102 | // TODO: auction is also completed if tic is in the past if isDent 103 | String timeZone = TimeZone.getDefault().getID(); 104 | String formattedBidExpiry = 105 | Instant.ofEpochSecond(maxAuctionDuration.longValue()) 106 | .atZone(ZoneId.of(timeZone)) 107 | .format(dtf); 108 | long currentUnixTime = System.currentTimeMillis() / 1000L; 109 | logger.trace("END OF AUCTION {}", formattedBidExpiry); 110 | boolean isCompleted = maxAuctionDuration.longValue() < currentUnixTime; 111 | logger.trace("AUCTION IS COMPLETED {}", isCompleted); 112 | return isCompleted; 113 | } 114 | 115 | @Override 116 | public String toString() { 117 | String timeZone = TimeZone.getDefault().getID(); 118 | return "Auction{" 119 | + "id=" 120 | + id 121 | + ", bidAmountInDai=" 122 | + bidAmountInDai 123 | + ", collateralForSale=" 124 | + collateralForSale 125 | + ", highestBidder='" 126 | + highestBidder 127 | + ", bidExpiry=" 128 | + Instant.ofEpochSecond(bidExpiry.longValue()).atZone(ZoneId.of(timeZone)).format(dtf) 129 | + ", maxAuctionDuration=" 130 | + Instant.ofEpochSecond(maxAuctionDuration.longValue()) 131 | .atZone(ZoneId.of(timeZone)) 132 | .format(dtf) 133 | + ", addressOfAuctionedVault='" 134 | + addressOfAuctionedVault 135 | + ", recipientOfAuctionIncome='" 136 | + recipientOfAuctionIncome 137 | + ", totalDaiWanted=" 138 | + totalDaiWanted 139 | + '}'; 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/main/java/de/devgao/defi/gasprovider/ArrayListUtil.java: -------------------------------------------------------------------------------- 1 | package devgao.io.gasprovider; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | public class ArrayListUtil { 9 | private ArrayListUtil() { 10 | throw new IllegalStateException("Utility class"); 11 | } 12 | 13 | @NotNull 14 | public static List removeDuplicates(@NotNull List list) { 15 | ArrayList newList = new ArrayList<>(); 16 | for (T element : list) { 17 | if (!newList.contains(element)) { 18 | newList.add(element); 19 | } 20 | } 21 | return newList; 22 | } 23 | 24 | public static String toString(@NotNull List list) { 25 | StringBuilder sb = new StringBuilder(); 26 | for (T s : list) { 27 | sb.append(s.toString()); 28 | sb.append("\t"); 29 | } 30 | return sb.toString(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/de/devgao/defi/gasprovider/ETHGasStation.java: -------------------------------------------------------------------------------- 1 | package devgao.io.gasprovider; 2 | 3 | import devgao.io.numberutil.Wad18; 4 | import org.jetbrains.annotations.NotNull; 5 | import org.slf4j.LoggerFactory; 6 | import org.web3j.utils.Convert; 7 | 8 | import java.io.IOException; 9 | import java.lang.invoke.MethodHandles; 10 | import java.math.BigInteger; 11 | import java.net.URL; 12 | import java.net.URLConnection; 13 | import java.nio.charset.StandardCharsets; 14 | import java.util.Scanner; 15 | 16 | public class ETHGasStation { 17 | private static final org.slf4j.Logger logger = 18 | LoggerFactory.getLogger(MethodHandles.lookup().lookupClass().getSimpleName()); 19 | 20 | private ETHGasStation() { 21 | throw new IllegalStateException("Utility class"); 22 | } 23 | 24 | @NotNull 25 | static Wad18 getFastestGasPrice() throws GasPriceException { 26 | Wad18 gasPrice = getGasPrice(4); 27 | logger.trace( 28 | "ETHERGASSTATION SUGGESTS GP {}{}", 29 | Convert.fromWei(gasPrice.toBigDecimal(), Convert.Unit.GWEI), 30 | " GWEI"); 31 | return gasPrice; 32 | } 33 | 34 | @NotNull 35 | static Wad18 getSafeLowGasPrice() throws GasPriceException { 36 | Wad18 gasPrice = getGasPrice(6); 37 | logger.trace( 38 | "ETHERGASSTATION SUGGESTS GP {}{}", 39 | Convert.fromWei(gasPrice.toBigDecimal(), Convert.Unit.GWEI), 40 | " GWEI"); 41 | return gasPrice; 42 | } 43 | 44 | private static Wad18 getGasPrice(int i) throws GasPriceException { 45 | try { 46 | URL url = new URL("https://ethgasstation.info/json/ethgasAPI.json"); 47 | URLConnection hc = url.openConnection(); 48 | hc.setRequestProperty( 49 | "User-Agent", 50 | "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.4; en-US; rv:1.9.2.2) Gecko/20100316 Firefox/3.6.2"); 51 | String out = 52 | new Scanner(hc.getInputStream(), StandardCharsets.UTF_8).useDelimiter("\\A").next(); 53 | String[] parts = out.split("\""); 54 | String clean = parts[i].replaceAll("[^\\d.]", ""); 55 | return new Wad18( 56 | Convert.toWei(clean, Convert.Unit.GWEI).toBigInteger().divide(BigInteger.TEN)); 57 | } catch (IOException e) { 58 | logger.error("IOException ", e); 59 | throw new GasPriceException("ETHGasStationException"); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/de/devgao/defi/gasprovider/Etherchain.java: -------------------------------------------------------------------------------- 1 | package devgao.io.gasprovider; 2 | 3 | import devgao.io.numberutil.Wad18; 4 | import org.jetbrains.annotations.NotNull; 5 | import org.slf4j.LoggerFactory; 6 | import org.web3j.utils.Convert; 7 | 8 | import java.io.IOException; 9 | import java.lang.invoke.MethodHandles; 10 | import java.net.URL; 11 | import java.net.URLConnection; 12 | import java.nio.charset.StandardCharsets; 13 | import java.util.Scanner; 14 | 15 | public class Etherchain { 16 | private static final org.slf4j.Logger logger = 17 | LoggerFactory.getLogger(MethodHandles.lookup().lookupClass().getSimpleName()); 18 | 19 | private Etherchain() { 20 | throw new IllegalStateException("Utility class"); 21 | } 22 | 23 | @NotNull 24 | static Wad18 getFastestGasPrice() throws GasPriceException { 25 | try { 26 | URL url = new URL("https://www.etherchain.org/api/gasPriceOracle"); 27 | URLConnection hc = url.openConnection(); 28 | hc.setRequestProperty( 29 | "User-Agent", 30 | "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.4; en-US; rv:1.9.2.2) Gecko/20100316 Firefox/3.6.2"); 31 | String out = 32 | new Scanner(hc.getInputStream(), StandardCharsets.UTF_8).useDelimiter("\\A").next(); 33 | String[] parts = out.split("\""); 34 | Wad18 gasPrice = new Wad18(Convert.toWei(parts[15], Convert.Unit.GWEI).toBigInteger()); 35 | 36 | logger.trace( 37 | "ETHERCHAIN SUGGESTS GP {}{}", 38 | Convert.fromWei(gasPrice.toBigDecimal(), Convert.Unit.GWEI), 39 | " GWEI"); 40 | return gasPrice; 41 | } catch (IOException e) { 42 | logger.error("IOException", e); 43 | throw new GasPriceException("EtherchainException"); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/de/devgao/defi/gasprovider/GasPriceException.java: -------------------------------------------------------------------------------- 1 | package devgao.io.gasprovider; 2 | 3 | public class GasPriceException extends Exception { 4 | public GasPriceException(String message) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/de/devgao/defi/gasprovider/GasProvider.java: -------------------------------------------------------------------------------- 1 | package devgao.io.gasprovider; 2 | 3 | import devgao.io.compounddai.CompoundDaiContract; 4 | import devgao.io.dai.DaiContract; 5 | import devgao.io.flipper.FlipperContract; 6 | import devgao.io.numberutil.Wad18; 7 | import devgao.io.oasis.OasisContract; 8 | import devgao.io.uniswap.UniswapContract; 9 | import devgao.io.weth.WethContract; 10 | import org.jetbrains.annotations.NotNull; 11 | import org.slf4j.LoggerFactory; 12 | import org.web3j.protocol.Web3j; 13 | import org.web3j.tx.gas.ContractGasProvider; 14 | import org.web3j.utils.Convert; 15 | 16 | import java.io.IOException; 17 | import java.lang.invoke.MethodHandles; 18 | import java.math.BigInteger; 19 | import java.util.ArrayList; 20 | import java.util.List; 21 | 22 | import static devgao.io.numberutil.NumberUtil.getMachineReadable; 23 | 24 | public class GasProvider implements ContractGasProvider { 25 | static final String GWEI = " GWEI"; 26 | private static final String GAS_PRICE_EXCEPTION = "GasPriceException"; 27 | private static final org.slf4j.Logger logger = 28 | LoggerFactory.getLogger(MethodHandles.lookup().lookupClass().getSimpleName()); 29 | final Wad18 minimumGasPrice; 30 | final Wad18 maximumGasPrice; 31 | final Web3j web3j; 32 | Wad18 gasPrice; 33 | private List failedTransactionsWithinTheLastTwelveHoursForGasPriceArrayList = 34 | new ArrayList<>(); 35 | 36 | public GasProvider(Web3j web3j, Wad18 minimumGasPrice, Wad18 maximumGasPrice) { 37 | this.web3j = web3j; 38 | this.minimumGasPrice = minimumGasPrice; 39 | this.maximumGasPrice = maximumGasPrice; 40 | this.gasPrice = new Wad18(BigInteger.valueOf(1_000000000)); 41 | } 42 | 43 | public void updateFailedTransactions(List list) { 44 | failedTransactionsWithinTheLastTwelveHoursForGasPriceArrayList.addAll(list); 45 | setFailedTransactionsWithinTheLastTwelveHoursForGasPriceArrayList( 46 | ArrayListUtil.removeDuplicates( 47 | failedTransactionsWithinTheLastTwelveHoursForGasPriceArrayList)); 48 | if (System.currentTimeMillis() 49 | >= failedTransactionsWithinTheLastTwelveHoursForGasPriceArrayList.get(0) 50 | + 12 * 60 * 60 * 1000) { // 12 hours 51 | logger.trace("FAILED TRANSACTION REMOVED 12 HOURS"); 52 | failedTransactionsWithinTheLastTwelveHoursForGasPriceArrayList.remove(0); 53 | } 54 | } 55 | 56 | @Override 57 | public BigInteger getGasLimit(@NotNull String contractFunc) { 58 | switch (contractFunc) { 59 | case CompoundDaiContract.FUNC_MINT: 60 | case CompoundDaiContract.FUNC_REDEEM: 61 | return BigInteger.valueOf(400_000); 62 | case UniswapContract.FUNC_TOKENTOETHSWAPINPUT: 63 | case UniswapContract.FUNC_ETHTOTOKENSWAPINPUT: 64 | return BigInteger.valueOf(65_000); 65 | case OasisContract.FUNC_BUY: 66 | return BigInteger.valueOf(300_000); 67 | case WethContract.FUNC_WITHDRAW: 68 | case WethContract.FUNC_DEPOSIT: 69 | case DaiContract.FUNC_APPROVE: 70 | return BigInteger.valueOf(50_000); 71 | case FlipperContract.FUNC_TEND: 72 | case FlipperContract.FUNC_DENT: 73 | return BigInteger.valueOf(150_000); 74 | default: 75 | logger.trace("{} FUNCTION HAS NO CUSTOMIZED GAS LIMIT YET", contractFunc); 76 | return BigInteger.valueOf(500_000); 77 | } 78 | } 79 | 80 | @Override 81 | public BigInteger getGasPrice(String contractFunc) { 82 | return gasPrice.toBigInteger(); 83 | } 84 | 85 | public Wad18 updateFastGasPrice(Wad18 medianEthereumPrice, Wad18 potentialProfit) { 86 | Wad18 fastGasPrice = minimumGasPrice; 87 | try { 88 | Wad18 etherchainResult = Etherchain.getFastestGasPrice(); 89 | fastGasPrice = fastGasPrice.max(etherchainResult); 90 | } catch (GasPriceException e) { 91 | logger.error(GAS_PRICE_EXCEPTION, e); 92 | } 93 | try { 94 | Wad18 ethGasStationResult = ETHGasStation.getFastestGasPrice(); 95 | fastGasPrice = fastGasPrice.max(ethGasStationResult); 96 | } catch (GasPriceException e) { 97 | logger.error(GAS_PRICE_EXCEPTION, e); 98 | } 99 | try { 100 | double percentageOfProfitAsFee = 101 | getPercentageOfProfitAsFee( 102 | failedTransactionsWithinTheLastTwelveHoursForGasPriceArrayList.size()); 103 | Wad18 gasPriceBasedOnProfit = 104 | calculateGasPriceAsAPercentageOfProfit( 105 | medianEthereumPrice, potentialProfit, 300000.0, percentageOfProfitAsFee); 106 | // instead of fixed 107 | fastGasPrice = fastGasPrice.max(gasPriceBasedOnProfit); 108 | } catch (GasPriceException e) { 109 | logger.error(GAS_PRICE_EXCEPTION, e); 110 | } 111 | gasPrice = fastGasPrice.min(maximumGasPrice); 112 | logger.trace( 113 | "GAS PRICE {}{}", Convert.fromWei(gasPrice.toBigDecimal(), Convert.Unit.GWEI), GWEI); 114 | return gasPrice; 115 | } 116 | 117 | public Wad18 updateSlowGasPrice() { 118 | Wad18 slowGasPrice = maximumGasPrice; 119 | try { 120 | Wad18 ethGasStationResult = ETHGasStation.getSafeLowGasPrice(); 121 | slowGasPrice = slowGasPrice.min(ethGasStationResult); 122 | } catch (GasPriceException e) { 123 | logger.error(GAS_PRICE_EXCEPTION, e); 124 | } 125 | try { 126 | Wad18 web3jResult = new Wad18(web3j.ethGasPrice().send().getGasPrice()); 127 | logger.trace( 128 | "WEB3J SUGGESTS GP {}{}", 129 | Convert.fromWei(web3jResult.toBigDecimal(), Convert.Unit.GWEI), 130 | GWEI); 131 | slowGasPrice = slowGasPrice.min(web3jResult); 132 | } catch (IOException e) { 133 | logger.error("IOException", e); 134 | } 135 | gasPrice = slowGasPrice; 136 | logger.trace( 137 | "GAS PRICE {}{}", Convert.fromWei(gasPrice.toBigDecimal(), Convert.Unit.GWEI), GWEI); 138 | return gasPrice; 139 | } 140 | 141 | public double getPercentageOfProfitAsFee( 142 | int failedTransactionsWithinTheLastTwelveHoursForGasPriceArrayListSize) { 143 | double percentageOfProfitAsFee = 144 | Math.min( 145 | 0.35, 146 | ((double) failedTransactionsWithinTheLastTwelveHoursForGasPriceArrayListSize * 5.0 147 | + 10.0) 148 | / 100.0); 149 | logger.trace("GAS PRICE IS AT LEAST {}% OF POTENTIAL PROFIT", percentageOfProfitAsFee * 100); 150 | 151 | return percentageOfProfitAsFee; 152 | } 153 | 154 | Wad18 calculateGasPriceAsAPercentageOfProfit( 155 | @NotNull Wad18 medianEthereumPrice, 156 | Wad18 potentialProfit, 157 | double gasLimit, 158 | double percentageOfProfitAsFee) 159 | throws GasPriceException { 160 | if (medianEthereumPrice.compareTo(Wad18.ZERO) == 0 161 | || potentialProfit.compareTo(Wad18.ZERO) == 0 162 | || gasLimit == 0.0) 163 | throw new GasPriceException("calculateGasPriceAsAPercentageOfProfit Exception"); 164 | // TODO: debug this method call 165 | Wad18 feeInEth = 166 | potentialProfit 167 | .multiply(new Wad18(getMachineReadable(percentageOfProfitAsFee))) 168 | .divide(medianEthereumPrice); // 0.049307620043223397 0.04930762004 169 | logger.trace("EST. TRANSACTION FEE {}{}", feeInEth.multiply(medianEthereumPrice), " DAI"); 170 | 171 | Wad18 gasPriceBasedOnProfit = feeInEth.divide(new Wad18(getMachineReadable(gasLimit))); 172 | logger.trace( 173 | "PROFIT SUGGESTS GP {}{}", 174 | Convert.fromWei(gasPriceBasedOnProfit.toBigDecimal(), Convert.Unit.GWEI), 175 | GWEI); 176 | 177 | return gasPriceBasedOnProfit; 178 | } 179 | 180 | /** @deprecated in the interface       */ 181 | @Override 182 | @Deprecated(since = "0.0.1") 183 | public BigInteger getGasPrice() { 184 | return null; 185 | } 186 | 187 | /** @deprecated in the interface       */ 188 | @Override 189 | @Deprecated(since = "0.0.1") 190 | public BigInteger getGasLimit() { 191 | return null; 192 | } 193 | 194 | public int getFailedTransactionsWithinTheLastTwelveHoursForGasPriceArrayList() { 195 | return failedTransactionsWithinTheLastTwelveHoursForGasPriceArrayList.size(); 196 | } 197 | 198 | public void setFailedTransactionsWithinTheLastTwelveHoursForGasPriceArrayList( 199 | List failedTransactionsWithinTheLastTwelveHoursForGasPriceArrayList) { 200 | this.failedTransactionsWithinTheLastTwelveHoursForGasPriceArrayList = 201 | failedTransactionsWithinTheLastTwelveHoursForGasPriceArrayList; 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /src/main/java/de/devgao/defi/maker/Maker.java: -------------------------------------------------------------------------------- 1 | package devgao.io.maker; 2 | 3 | import devgao.io.contractneedsprovider.ContractNeedsProvider; 4 | import devgao.io.gasprovider.GasProvider; 5 | import devgao.io.numberutil.Wad18; 6 | import devgao.io.util.Balances; 7 | import org.slf4j.LoggerFactory; 8 | import org.web3j.crypto.Credentials; 9 | import org.web3j.protocol.Web3j; 10 | import org.web3j.tuples.generated.Tuple4; 11 | import org.web3j.utils.Numeric; 12 | 13 | import java.lang.invoke.MethodHandles; 14 | import java.math.BigInteger; 15 | 16 | import static devgao.io.numberutil.NumberUtil.getMachineReadable; 17 | 18 | /** 19 | * @deprecated This class is currently unused. It was created for SCD and has to be updated to MCD. 20 | */ 21 | @Deprecated(since = "0.0.1") 22 | public class Maker { 23 | private static final org.slf4j.Logger logger = 24 | LoggerFactory.getLogger(MethodHandles.lookup().lookupClass().getSimpleName()); 25 | private static final String ADDRESS = "0x448a5065aeBB8E423F0896E6c5D525C040f59af3"; 26 | private static final Wad18 liquidationRatio = new Wad18(getMachineReadable(1.5)); 27 | private static final String CDP_ID = 28 | "0x0000000000000000000000000000000000000000000000000000000000000000"; // TODO: Get own cdp ID 29 | private static final Wad18 MAX_LIQUIDATION_PRICE = new Wad18(getMachineReadable(100.0)); 30 | private final MakerContract makerContract; 31 | private Wad18 drawableDai; 32 | 33 | public Maker(ContractNeedsProvider contractNeedsProvider) { 34 | drawableDai = Wad18.ZERO; 35 | Web3j web3j = contractNeedsProvider.getWeb3j(); 36 | Credentials credentials = contractNeedsProvider.getCredentials(); 37 | GasProvider gasProvider = contractNeedsProvider.getGasProvider(); 38 | makerContract = MakerContract.load(ADDRESS, web3j, credentials, gasProvider); 39 | } 40 | 41 | private void updateCDPInformation(Balances balances) throws Exception { 42 | Tuple4 cupsResult = 43 | makerContract.cups(Numeric.hexStringToByteArray(CDP_ID)).send(); 44 | 45 | Wad18 lockedEth = new Wad18(cupsResult.component2()); // ink is the locked ETH 46 | Wad18 outstandingDaiDebt = new Wad18(cupsResult.component3()); // art is outstanding dai debt 47 | Wad18 collateralLessFees = new Wad18(cupsResult.component4()); // ire is collateral - fees 48 | 49 | // (Stability Debt * Liquidation Ratio) / Collateral = Liquidation Price 50 | Wad18 currentLiquidationPrice = 51 | outstandingDaiDebt.multiply(liquidationRatio).multiply(lockedEth); 52 | 53 | // INFO: drawable dai does not include owned eth 54 | if (MAX_LIQUIDATION_PRICE.compareTo(currentLiquidationPrice) > 0) { 55 | drawableDai = MAX_LIQUIDATION_PRICE.multiply(lockedEth); 56 | drawableDai = drawableDai.divide(liquidationRatio); 57 | drawableDai = drawableDai.subtract(outstandingDaiDebt); 58 | } else { 59 | drawableDai = Wad18.ZERO; 60 | } 61 | logger.trace("CDP INFORMATION"); 62 | logger.trace("LOCKED ETH (INK) {}", lockedEth); 63 | logger.trace("OUTSTANDING DAI DEBT (ART) {}", outstandingDaiDebt); 64 | logger.trace("COLLATERAL-FEES (IRE) {}", collateralLessFees); 65 | logger.trace("LIQUIDATION PRICE {}", currentLiquidationPrice.toString(5)); 66 | logger.trace("DRAWABLE DAI {}", drawableDai); 67 | Wad18 potentialDai = drawableDai.add(balances.dai.getAccount().getBalance()); 68 | logger.trace("POTENTIAL DAI {}", potentialDai); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/de/devgao/defi/medianizer/MedianException.java: -------------------------------------------------------------------------------- 1 | package devgao.io.medianizer; 2 | 3 | public class MedianException extends Exception { 4 | public MedianException(String message) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/de/devgao/defi/medianizer/Medianizer.java: -------------------------------------------------------------------------------- 1 | package devgao.io.medianizer; 2 | 3 | import devgao.io.contractneedsprovider.ContractNeedsProvider; 4 | import devgao.io.gasprovider.GasProvider; 5 | import devgao.io.numberutil.Wad18; 6 | import org.jetbrains.annotations.NotNull; 7 | import org.slf4j.LoggerFactory; 8 | import org.web3j.crypto.Credentials; 9 | import org.web3j.protocol.Web3j; 10 | 11 | import java.lang.invoke.MethodHandles; 12 | import java.math.BigInteger; 13 | import java.net.URL; 14 | import java.nio.charset.StandardCharsets; 15 | import java.util.Arrays; 16 | import java.util.Scanner; 17 | 18 | import static devgao.io.numberutil.NumberUtil.getMachineReadable; 19 | 20 | public class Medianizer { 21 | static final int PRICE_UPDATE_INTERVAL = 8 * 1000; 22 | private static final org.slf4j.Logger logger = 23 | LoggerFactory.getLogger(MethodHandles.lookup().lookupClass().getSimpleName()); 24 | private static final String ETH_USD = " ETH/USD"; 25 | private static final String ETH_DAI = " ETH/DAI"; 26 | private static final String ADDRESS = "0x729D19f657BD0614b4985Cf1D82531c67569197B"; 27 | private static final String EXCEPTION = "Exception"; 28 | private static MedianizerContract medianizerContract; 29 | private static Wad18 median; 30 | private static long pastTimeMedian; 31 | 32 | private Medianizer() { 33 | throw new IllegalStateException("Utility class"); 34 | } 35 | 36 | public static void setMedianizerContract(@NotNull ContractNeedsProvider contractNeedsProvider) { 37 | Web3j web3j = contractNeedsProvider.getWeb3j(); 38 | Credentials credentials = contractNeedsProvider.getCredentials(); 39 | GasProvider gasProvider = contractNeedsProvider.getGasProvider(); 40 | Medianizer.medianizerContract = 41 | MedianizerContract.load(ADDRESS, web3j, credentials, gasProvider); 42 | } 43 | 44 | public static Wad18 getPrice() throws MedianException { 45 | long currentTime = System.currentTimeMillis(); 46 | // BigInteger currentBlock = getCurrentBlock(); 47 | 48 | if (currentTime >= pastTimeMedian + PRICE_UPDATE_INTERVAL) { 49 | logger.trace("MEDIAN: TIME"); 50 | pastTimeMedian = currentTime; 51 | updateMedian(); 52 | } 53 | // if (!pastBlock.equals(currentBlock)) { 54 | // logger.trace("MEDIAN: BLOCK"); 55 | // pastBlock = currentBlock; 56 | // updateMedian(); 57 | // } 58 | logger.trace("MEDIAN {}{}", median, ETH_DAI); 59 | return median; 60 | } 61 | 62 | private static void updateMedian() throws MedianException { 63 | Wad18 newMedian = 64 | getMedian( 65 | new Wad18[] {getKrakenEthPrice(), getMakerDAOEthPrice(), getCoinbaseProEthPrice()}); 66 | if (newMedian.equals(Wad18.ZERO)) throw new MedianException("MEDIAN IS ZERO EXCEPTION"); 67 | median = newMedian; 68 | } 69 | 70 | static Wad18 getMedian(Wad18[] array) throws MedianException { 71 | array = 72 | Arrays.stream(array) 73 | .filter(n -> n.compareTo(Wad18.ZERO) > 0) 74 | .sorted() 75 | .toArray(Wad18[]::new); 76 | 77 | logger.trace("MEDIAN {}", Arrays.toString(array)); 78 | if (array.length == 0) throw new MedianException("ARRAY IS EMPTY"); 79 | if (array.length == 1) throw new MedianException("TOO FEW PRICE FEEDS"); 80 | if (array.length % 2 == 0) 81 | return array[array.length / 2] 82 | .add(array[array.length / 2 - 1]) 83 | .divide(getMachineReadable(2.0)); 84 | else return array[array.length / 2]; 85 | } 86 | 87 | private static Wad18 getMakerDAOEthPrice() { 88 | Wad18 ethPrice = Wad18.ZERO; 89 | try { 90 | byte[] result = medianizerContract.read().send(); 91 | ethPrice = new Wad18(new BigInteger(result)); 92 | } catch (Exception e) { 93 | logger.error(EXCEPTION, e); 94 | } 95 | logger.trace("MAKERDAO {}{}", ethPrice, ETH_DAI); 96 | return ethPrice; 97 | } 98 | 99 | /** 100 | * successful input example: {"USD":268.35} 101 | * 102 | *

input rate limit error example: {"Response":"Error","Message":"You are over your rate limit 103 | * please upgrade your 104 | * account!","HasWarning":false,"Type":99,"RateLimit":{"calls_made":{"second":1,"minute":1,"hour":76,"day":20003,"month":57172,"total_calls":57172},"max_calls":{"second":20,"minute":300,"hour":3000,"day":20000,"month":180000}},"Data":{}} 105 | * 106 | * @return BigInteger 107 | */ 108 | private static Wad18 getCryptocompareEthPrice() { 109 | Wad18 ethPrice = Wad18.ZERO; 110 | try (Scanner scanner = 111 | new Scanner( 112 | new URL("https://min-api.cryptocompare.com/data/price?fsym=ETH&tsyms=USD").openStream(), 113 | StandardCharsets.UTF_8)) { 114 | String input = scanner.useDelimiter("\\A").next(); 115 | String[] parts = input.split("\""); 116 | ethPrice = 117 | new Wad18( 118 | getMachineReadable(Double.valueOf(parts[2].substring(1, parts[2].length() - 1)))); 119 | } catch (Exception e) { 120 | logger.error(EXCEPTION, e); 121 | } 122 | logger.trace("CRYPTOCOMPARE {}{}", ethPrice, ETH_USD); 123 | return ethPrice; 124 | } 125 | 126 | /** 127 | * input example: 128 | * {"error":[],"result":{"XETHZUSD":{"a":["251.29000","4","4.000"],"b":["250.99000","6","6.000"],"c":["251.04000","2.00000000"],"v":["25489.49597316","35276.40048677"],"p":["249.36880","248.99603"],"t":[3542,4754],"l":["245.85000","245.85000"],"h":["252.58000","253.35000"],"o":"252.05000"}}} 129 | * 130 | * @return BigInteger 131 | */ 132 | private static Wad18 getKrakenEthPrice() { 133 | Wad18 ethPrice = Wad18.ZERO; 134 | try (Scanner scanner = 135 | new Scanner( 136 | new URL("https://api.kraken.com/0/public/Ticker?pair=ETHUSD").openStream(), 137 | StandardCharsets.UTF_8)) { 138 | String input = scanner.useDelimiter("\\A").next(); 139 | String[] parts = input.split("\""); 140 | BigInteger bid = getMachineReadable(Double.valueOf(parts[9])); 141 | BigInteger ask = getMachineReadable(Double.valueOf(parts[17])); 142 | ethPrice = new Wad18(bid.add(ask).divide(BigInteger.valueOf(2))); 143 | } catch (Exception e) { 144 | logger.error(EXCEPTION, e); 145 | } 146 | logger.trace("KRAKEN {}{}", ethPrice, ETH_USD); 147 | return ethPrice; 148 | } 149 | 150 | /** 151 | * input example: 152 | * {"sequence":6998278453,"bids":[["237.49","3.61796708",1]],"asks":[["237.64","48.303",2]]} 153 | * 154 | * @return BigInteger 155 | */ 156 | static Wad18 getCoinbaseProEthPrice() { 157 | Wad18 ethPrice = Wad18.ZERO; 158 | try (Scanner scanner = 159 | new Scanner( 160 | new URL("https://api.coinbase.com/v2/prices/ETH-USD/spot").openStream(), 161 | StandardCharsets.UTF_8)) { 162 | String input = scanner.useDelimiter("\\A").next(); 163 | String[] parts = input.split("\""); 164 | ethPrice = new Wad18(getMachineReadable(Double.valueOf(parts[13]))); 165 | } catch (Exception e) { 166 | logger.error(EXCEPTION, e); 167 | } 168 | logger.trace("COINBASE {}{}", ethPrice, ETH_USD); 169 | return ethPrice; 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /src/main/java/de/devgao/defi/numberutil/NumberUtil.java: -------------------------------------------------------------------------------- 1 | package devgao.io.numberutil; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | 5 | import java.math.BigDecimal; 6 | import java.math.BigInteger; 7 | 8 | public final class NumberUtil { 9 | public static final BigInteger UINT_MAX = 10 | new BigInteger( 11 | "115792089237316195423570985008687907853269984665640564039457584007913129639935"); // 2^256-1 12 | /** 13 | * MINIMUM_APPROVAL_ALLOWANCE seems to be the maximal returned value instead of UINT_MAX TODO: ask 14 | * on github if this is necessary because of an web3j bug 15 | */ 16 | public static final BigInteger MINIMUM_APPROVAL_ALLOWANCE = 17 | new BigInteger("115792089237316195423570985008687907853269"); 18 | 19 | private NumberUtil() {} 20 | 21 | @NotNull 22 | public static BigInteger getMachineReadable(Double smallNumber) { 23 | if (smallNumber == 0.0) return BigInteger.ZERO; 24 | return BigDecimal.valueOf(smallNumber * Math.pow(10, 18)).toBigInteger(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/de/devgao/defi/numberutil/NumberWrapper.java: -------------------------------------------------------------------------------- 1 | package devgao.io.numberutil; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | import org.web3j.utils.Convert; 5 | 6 | import java.math.BigDecimal; 7 | import java.math.BigInteger; 8 | import java.math.RoundingMode; 9 | 10 | public class NumberWrapper implements Comparable { 11 | final BigDecimal bigDecimal; 12 | final int decimals; 13 | 14 | public NumberWrapper(String number, int decimals) { 15 | this.bigDecimal = new BigDecimal(number); 16 | this.decimals = decimals; 17 | } 18 | 19 | public NumberWrapper(int number, int decimals) { 20 | this.bigDecimal = BigDecimal.valueOf(number); 21 | this.decimals = decimals; 22 | } 23 | 24 | public NumberWrapper(long number, int decimals) { 25 | this.bigDecimal = BigDecimal.valueOf(number); 26 | this.decimals = decimals; 27 | } 28 | 29 | public NumberWrapper(int decimals) { 30 | this.bigDecimal = new BigDecimal(BigInteger.ZERO); 31 | this.decimals = decimals; 32 | } 33 | 34 | public NumberWrapper(BigInteger bigInteger, int decimals) { 35 | this.bigDecimal = new BigDecimal(bigInteger); 36 | this.decimals = decimals; 37 | } 38 | 39 | public NumberWrapper(BigDecimal bigDecimal, int decimals) { 40 | this.bigDecimal = bigDecimal; 41 | this.decimals = decimals; 42 | } 43 | 44 | public NumberWrapper divide(@NotNull BigInteger divisorWrapper) { 45 | return this.divide(new Wad18(divisorWrapper)); 46 | } 47 | 48 | public NumberWrapper divide(@NotNull NumberWrapper divisorWrapper) { 49 | if (decimals != divisorWrapper.decimals) 50 | throw new IllegalArgumentException("Not yet implemented"); 51 | BigDecimal divisor = divisorWrapper.toBigDecimal(); 52 | if (divisor.compareTo(BigDecimal.ZERO) == 0) 53 | throw new IllegalArgumentException("Argument 'divisor' is 0"); 54 | return new NumberWrapper( 55 | bigDecimal 56 | .multiply(BigDecimal.valueOf(Math.pow(10, decimals))) 57 | .divide(divisor, 0, RoundingMode.DOWN) 58 | .stripTrailingZeros(), 59 | decimals); 60 | } 61 | 62 | public NumberWrapper multiply(@NotNull NumberWrapper multiplicand) { 63 | if (decimals != multiplicand.decimals) 64 | throw new IllegalArgumentException("Not yet implemented"); 65 | return new NumberWrapper( 66 | bigDecimal 67 | .multiply(multiplicand.toBigDecimal()) 68 | .divide(BigDecimal.valueOf(Math.pow(10, decimals)), 0, RoundingMode.DOWN), 69 | decimals); 70 | } 71 | 72 | public BigInteger toBigInteger() { 73 | return bigDecimal.toBigInteger(); 74 | } 75 | 76 | public BigDecimal toBigDecimal() { 77 | return bigDecimal; 78 | } 79 | 80 | @Override 81 | public String toString() { 82 | if (decimals == 18) { 83 | return Convert.fromWei(bigDecimal, Convert.Unit.ETHER) 84 | .setScale(decimals, RoundingMode.DOWN) 85 | .toPlainString(); 86 | } else if (decimals > 18) { 87 | return Convert.fromWei( 88 | bigDecimal.divide( 89 | BigDecimal.valueOf(Math.pow(10, decimals - 18.0)), RoundingMode.DOWN), 90 | Convert.Unit.ETHER) 91 | .setScale(decimals, RoundingMode.DOWN) 92 | .toPlainString(); 93 | } 94 | throw new IllegalArgumentException("todo"); // todo 95 | } 96 | 97 | public String toString(int decimals) { 98 | // todo: only works for decimal == 18 99 | return Convert.fromWei(bigDecimal, Convert.Unit.ETHER) 100 | .setScale(decimals, RoundingMode.DOWN) 101 | .toPlainString(); 102 | } 103 | 104 | public NumberWrapper add(@NotNull NumberWrapper augend) { 105 | return new Wad18(bigDecimal.add(augend.toBigDecimal())); 106 | } 107 | 108 | public NumberWrapper subtract(@NotNull NumberWrapper subtrahend) { 109 | return new Wad18(bigDecimal.subtract(subtrahend.toBigDecimal())); 110 | } 111 | 112 | @Override 113 | public int compareTo(@NotNull NumberWrapper val) { 114 | // TODO: check if this can be better 115 | BigDecimal bigDecimal1 = bigDecimal.multiply(BigDecimal.valueOf(Math.pow(10, val.decimals))); 116 | BigDecimal bigDecimal2 = 117 | val.toBigDecimal().multiply(BigDecimal.valueOf(Math.pow(10, decimals))); 118 | return bigDecimal1.compareTo(bigDecimal2); 119 | } 120 | 121 | @Override 122 | public int hashCode() { 123 | return bigDecimal.intValue(); // INFO: check if this is a good idea 124 | } 125 | 126 | @Override 127 | public boolean equals(Object compareObject) { 128 | if (compareObject instanceof NumberWrapper) { 129 | NumberWrapper numberWrapper = (NumberWrapper) compareObject; 130 | return bigDecimal.compareTo(numberWrapper.toBigDecimal()) == 0; 131 | } 132 | return false; 133 | } // todo: hashcode, null? 134 | 135 | public NumberWrapper min(@NotNull NumberWrapper compareObject) { 136 | return new NumberWrapper(bigDecimal.min(compareObject.toBigDecimal()), this.decimals); 137 | } 138 | 139 | public NumberWrapper max(@NotNull NumberWrapper compareObject) { 140 | return new NumberWrapper(bigDecimal.max(compareObject.toBigDecimal()), this.decimals); 141 | } 142 | 143 | public long longValue() { 144 | return bigDecimal.longValue(); 145 | } 146 | 147 | public double doubleValue() { 148 | return bigDecimal.doubleValue(); 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/main/java/de/devgao/defi/numberutil/Rad45.java: -------------------------------------------------------------------------------- 1 | package devgao.io.numberutil; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | 5 | import java.math.BigDecimal; 6 | import java.math.BigInteger; 7 | import java.math.RoundingMode; 8 | 9 | public class Rad45 extends NumberWrapper { 10 | public static final Rad45 ZERO = new Rad45(0); 11 | public static final Rad45 ONE = new Rad45(1); 12 | private static final int RAD45_DECIMALS = 45; 13 | 14 | public Rad45() { 15 | super(RAD45_DECIMALS); 16 | } 17 | 18 | public Rad45(int number) { 19 | super(number, RAD45_DECIMALS); 20 | } 21 | 22 | public Rad45(String number) { 23 | super(number, RAD45_DECIMALS); 24 | } 25 | 26 | public Rad45(long number) { 27 | super(number, RAD45_DECIMALS); 28 | } 29 | 30 | public Rad45(BigInteger bigInteger) { 31 | super(bigInteger, RAD45_DECIMALS); 32 | } 33 | 34 | public Rad45(BigDecimal bigDecimal) { 35 | super(bigDecimal, RAD45_DECIMALS); 36 | } 37 | 38 | @Override 39 | public Wad18 divide(@NotNull BigInteger divisorWrapper) { 40 | return this.divide(new Rad45(divisorWrapper)); 41 | } 42 | 43 | @Override 44 | public Wad18 divide(@NotNull NumberWrapper divisorWrapper) { 45 | BigDecimal divisor = divisorWrapper.toBigDecimal(); 46 | if (divisor.compareTo(BigDecimal.ZERO) == 0) 47 | throw new IllegalArgumentException("Argument 'divisor' is 0"); 48 | return new Wad18( 49 | bigDecimal 50 | .multiply(BigDecimal.valueOf(Math.pow(10, RAD45_DECIMALS))) 51 | .divide(divisor, 0, RoundingMode.DOWN) 52 | .stripTrailingZeros()); 53 | } 54 | 55 | @Override 56 | public Wad18 multiply(@NotNull NumberWrapper multiplicand) { 57 | return new Wad18( 58 | bigDecimal 59 | .multiply(multiplicand.toBigDecimal()) 60 | .divide(BigDecimal.valueOf(Math.pow(10, RAD45_DECIMALS)), 0, RoundingMode.DOWN)); 61 | } 62 | 63 | @Override 64 | public Wad18 add(@NotNull NumberWrapper augend) { 65 | return new Wad18(bigDecimal.add(augend.toBigDecimal())); 66 | } 67 | 68 | @Override 69 | public Wad18 subtract(@NotNull NumberWrapper subtrahend) { 70 | return new Wad18(bigDecimal.subtract(subtrahend.toBigDecimal())); 71 | } 72 | 73 | @Override 74 | public Rad45 min(@NotNull NumberWrapper compareObject) { 75 | return new Rad45(bigDecimal.min(compareObject.toBigDecimal())); 76 | } 77 | 78 | @Override 79 | public Rad45 max(@NotNull NumberWrapper compareObject) { 80 | return new Rad45(bigDecimal.max(compareObject.toBigDecimal())); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/de/devgao/defi/numberutil/Sth28.java: -------------------------------------------------------------------------------- 1 | package devgao.io.numberutil; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | 5 | import java.math.BigDecimal; 6 | import java.math.BigInteger; 7 | 8 | public class Sth28 extends NumberWrapper { 9 | public static final Sth28 ZERO = new Sth28(0); 10 | public static final Sth28 ONE = new Sth28(1); 11 | private static final int STH32_DECIMALS = 28; 12 | 13 | public Sth28() { 14 | super(STH32_DECIMALS); 15 | } 16 | 17 | public Sth28(int number) { 18 | super(number, STH32_DECIMALS); 19 | } 20 | 21 | public Sth28(String number) { 22 | super(number, STH32_DECIMALS); 23 | } 24 | 25 | public Sth28(long number) { 26 | super(number, STH32_DECIMALS); 27 | } 28 | 29 | public Sth28(BigInteger bigInteger) { 30 | super(bigInteger, STH32_DECIMALS); 31 | } 32 | 33 | public Sth28(BigDecimal bigDecimal) { 34 | super(bigDecimal, STH32_DECIMALS); 35 | } 36 | 37 | @Override 38 | public Sth28 add(@NotNull NumberWrapper augend) { 39 | return new Sth28(bigDecimal.add(augend.toBigDecimal())); 40 | } 41 | 42 | @Override 43 | public Sth28 subtract(@NotNull NumberWrapper subtrahend) { 44 | return new Sth28(bigDecimal.subtract(subtrahend.toBigDecimal())); 45 | } 46 | 47 | @Override 48 | public Sth28 min(@NotNull NumberWrapper compareObject) { 49 | return new Sth28(bigDecimal.min(compareObject.toBigDecimal())); 50 | } 51 | 52 | @Override 53 | public Sth28 max(@NotNull NumberWrapper compareObject) { 54 | return new Sth28(bigDecimal.max(compareObject.toBigDecimal())); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/de/devgao/defi/numberutil/Wad18.java: -------------------------------------------------------------------------------- 1 | package devgao.io.numberutil; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | 5 | import java.math.BigDecimal; 6 | import java.math.BigInteger; 7 | import java.math.RoundingMode; 8 | 9 | /** 10 | * Immutable 11 | */ 12 | public class Wad18 extends NumberWrapper { 13 | public static final Wad18 ZERO = new Wad18(0); 14 | public static final Wad18 ONE = new Wad18(1); 15 | private static final int WAD18_DECIMALS = 18; 16 | 17 | public Wad18() { 18 | super(WAD18_DECIMALS); 19 | } 20 | 21 | public Wad18(int number) { 22 | super(number, WAD18_DECIMALS); 23 | } 24 | 25 | public Wad18(String number) { 26 | super(number, WAD18_DECIMALS); 27 | } 28 | 29 | public Wad18(long number) { 30 | super(number, WAD18_DECIMALS); 31 | } 32 | 33 | public Wad18(BigInteger bigInteger) { 34 | super(bigInteger, WAD18_DECIMALS); 35 | } 36 | 37 | public Wad18(BigDecimal bigDecimal) { 38 | super(bigDecimal, WAD18_DECIMALS); 39 | } 40 | 41 | @Override 42 | public Wad18 divide(@NotNull BigInteger divisorWrapper) { 43 | return this.divide(new Wad18(divisorWrapper)); 44 | } 45 | 46 | @Override 47 | public Wad18 divide(@NotNull NumberWrapper divisorWrapper) { 48 | BigDecimal divisor = divisorWrapper.toBigDecimal(); 49 | if (divisor.compareTo(BigDecimal.ZERO) == 0) 50 | throw new IllegalArgumentException("Argument 'divisor' is 0"); 51 | return new Wad18( 52 | bigDecimal 53 | .multiply(BigDecimal.valueOf(Math.pow(10, WAD18_DECIMALS))) 54 | .divide(divisor, 0, RoundingMode.DOWN) 55 | .stripTrailingZeros()); 56 | } 57 | 58 | @Override 59 | public Wad18 multiply(@NotNull NumberWrapper multiplicandNumberWrapper) { 60 | Wad18 multiplicand = new Wad18(multiplicandNumberWrapper.toBigDecimal()); 61 | int decimalsDifference = multiplicandNumberWrapper.decimals - decimals; 62 | if (decimalsDifference > 0) { 63 | multiplicand = 64 | new Wad18( 65 | multiplicandNumberWrapper 66 | .toBigDecimal() 67 | .divide( 68 | BigDecimal.valueOf(Math.pow(10, decimalsDifference)), 69 | RoundingMode.HALF_EVEN)); 70 | } else if (decimalsDifference < 0) { 71 | throw new IllegalArgumentException("Not yet implemented"); 72 | } 73 | return multiply(multiplicand); 74 | } 75 | 76 | public Wad18 multiply(@NotNull Wad18 multiplicand) { 77 | return new Wad18( 78 | bigDecimal 79 | .multiply(multiplicand.toBigDecimal()) 80 | .divide(BigDecimal.valueOf(Math.pow(10, WAD18_DECIMALS)), 0, RoundingMode.DOWN)); 81 | } 82 | 83 | @Override 84 | public Wad18 add(@NotNull NumberWrapper augend) { 85 | return new Wad18(bigDecimal.add(augend.toBigDecimal())); 86 | } 87 | 88 | @Override 89 | public Wad18 subtract(@NotNull NumberWrapper subtrahend) { 90 | return new Wad18(bigDecimal.subtract(subtrahend.toBigDecimal())); 91 | } 92 | 93 | @Override 94 | public Wad18 min(@NotNull NumberWrapper compareObject) { 95 | return new Wad18(bigDecimal.min(compareObject.toBigDecimal())); 96 | } 97 | 98 | @Override 99 | public Wad18 max(@NotNull NumberWrapper compareObject) { 100 | return new Wad18(bigDecimal.max(compareObject.toBigDecimal())); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/main/java/de/devgao/defi/oasis/DaiOrWethMissingException.java: -------------------------------------------------------------------------------- 1 | package devgao.io.oasis; 2 | 3 | public class DaiOrWethMissingException extends Exception { 4 | public DaiOrWethMissingException(String message) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/de/devgao/defi/oasis/OasisOffer.java: -------------------------------------------------------------------------------- 1 | package devgao.io.oasis; 2 | 3 | import devgao.io.numberutil.Wad18; 4 | 5 | import java.math.BigInteger; 6 | import java.util.Map; 7 | 8 | public class OasisOffer { 9 | public final BigInteger offerId; 10 | public final Map offerValues; 11 | public final Wad18 bestOfferDaiPerEth; 12 | public final Wad18 profit; 13 | 14 | public OasisOffer( 15 | BigInteger offerId, Map offerValues, Wad18 bestOfferDaiPerEth, Wad18 profit) { 16 | this.offerId = offerId; 17 | this.offerValues = offerValues; 18 | this.bestOfferDaiPerEth = bestOfferDaiPerEth; 19 | this.profit = profit; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/de/devgao/defi/oasisdex/DaiOrWethMissingException.java: -------------------------------------------------------------------------------- 1 | package devgao.io.oasisdex; 2 | 3 | public class DaiOrWethMissingException extends Exception { 4 | public DaiOrWethMissingException(String message) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/de/devgao/defi/oasisdex/OasisDexOffer.java: -------------------------------------------------------------------------------- 1 | package devgao.io.oasisdex; 2 | 3 | import java.math.BigDecimal; 4 | import java.math.BigInteger; 5 | import java.util.Map; 6 | 7 | public class OasisDexOffer { 8 | public final BigInteger offerId; 9 | public final Map offerValues; 10 | public final BigDecimal bestOfferDaiPerEth; 11 | public final BigDecimal profit; 12 | 13 | public OasisDexOffer( 14 | BigInteger offerId, 15 | Map offerValues, 16 | BigDecimal bestOfferDaiPerEth, 17 | BigDecimal profit) { 18 | this.offerId = offerId; 19 | this.offerValues = offerValues; 20 | this.bestOfferDaiPerEth = bestOfferDaiPerEth; 21 | this.profit = profit; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/de/devgao/defi/uniswap/EthToTokenSwapInput.java: -------------------------------------------------------------------------------- 1 | package devgao.io.uniswap; 2 | 3 | import devgao.io.numberutil.Wad18; 4 | 5 | public class EthToTokenSwapInput { 6 | public final Wad18 minTokens; 7 | public final Wad18 deadline; 8 | public final Wad18 ethSold; 9 | public final Wad18 potentialProfit; 10 | 11 | public EthToTokenSwapInput( 12 | Wad18 minTokens, Wad18 deadline, Wad18 ethSold, Wad18 potentialProfit) { 13 | this.minTokens = minTokens; 14 | this.deadline = deadline; 15 | this.ethSold = ethSold; 16 | this.potentialProfit = potentialProfit; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/de/devgao/defi/uniswap/TokenToEthSwapInput.java: -------------------------------------------------------------------------------- 1 | package devgao.io.uniswap; 2 | 3 | import devgao.io.numberutil.Wad18; 4 | 5 | public class TokenToEthSwapInput { 6 | public final Wad18 minEth; 7 | public final Wad18 deadline; 8 | public final Wad18 tokenSold; 9 | public final Wad18 potentialProfit; 10 | 11 | public TokenToEthSwapInput(Wad18 minEth, Wad18 deadline, Wad18 tokenSold, Wad18 potentialProfit) { 12 | this.minEth = minEth; 13 | this.deadline = deadline; 14 | this.tokenSold = tokenSold; 15 | this.potentialProfit = potentialProfit; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/de/devgao/defi/uniswap/UniswapOffer.java: -------------------------------------------------------------------------------- 1 | package devgao.io.uniswap; 2 | 3 | import devgao.io.numberutil.Wad18; 4 | 5 | public class UniswapOffer { 6 | public final Wad18 buyableAmount; 7 | public final Wad18 profit; 8 | 9 | public UniswapOffer(Wad18 buyableAmount, Wad18 profit) { 10 | this.buyableAmount = buyableAmount; 11 | this.profit = profit; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/de/devgao/defi/util/BigNumberUtil.java: -------------------------------------------------------------------------------- 1 | package devgao.io.util; 2 | 3 | import org.apache.commons.lang3.StringUtils; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | import java.math.BigDecimal; 7 | import java.math.BigInteger; 8 | import java.math.RoundingMode; 9 | 10 | public class BigNumberUtil { 11 | public static final BigInteger BIGGEST_NUMBER = 12 | new BigInteger("115792089237316195423570985008687907853269984665640564039457"); 13 | 14 | private BigNumberUtil() { 15 | throw new IllegalStateException("Utility class"); 16 | } 17 | 18 | @NotNull 19 | public static BigDecimal makeDoubleMachineReadable(Double smallNumber) { 20 | if (smallNumber == 0.0) return BigDecimal.ZERO; 21 | return BigDecimal.valueOf(smallNumber * Math.pow(10, 18)); 22 | } 23 | 24 | public static BigInteger multiply(@NotNull BigInteger multiplicand1, BigInteger multiplicand2) { 25 | return multiplicand1.multiply(multiplicand2).divide(BigInteger.valueOf(1000000000000000000L)); 26 | } 27 | 28 | public static BigDecimal multiply(@NotNull BigDecimal multiplicand1, BigDecimal multiplicand2) { 29 | return multiplicand1 30 | .multiply(multiplicand2) 31 | .divide(BigDecimal.valueOf(Math.pow(10, 18)), RoundingMode.DOWN); 32 | } 33 | 34 | public static BigInteger divide(@NotNull BigInteger dividend, BigInteger divisor) { 35 | return dividend.multiply(BigInteger.valueOf(1000000000000000000L)).divide(divisor); 36 | } 37 | 38 | public static BigDecimal divide(@NotNull BigDecimal dividend, BigDecimal divisor) { 39 | return dividend 40 | .multiply(BigDecimal.valueOf(Math.pow(10, 18))) 41 | .divide(divisor, 0, RoundingMode.DOWN) 42 | .stripTrailingZeros(); 43 | } 44 | 45 | public static double makeBigNumberCurrencyHumanReadable(@NotNull BigDecimal bigNumber) { 46 | return Math.round((bigNumber.floatValue() / Math.pow(10, 18)) * 100.0) / 100.0; 47 | } 48 | 49 | public static double makeBigNumberCurrencyHumanReadable(@NotNull BigInteger bigNumber) { 50 | return Math.round((bigNumber.floatValue() / Math.pow(10, 18)) * 100.0) / 100.0; 51 | } 52 | 53 | public static double makeBigNumberHumanReadable(@NotNull BigDecimal bigNumber) { 54 | return Math.round((bigNumber.floatValue() / Math.pow(10, 18)) * 10000.0) / 10000.0; 55 | } 56 | 57 | public static double makeBigNumberHumanReadable(@NotNull BigInteger bigNumber) { 58 | return Math.round((bigNumber.floatValue() / Math.pow(10, 18)) * 10000.0) / 10000.0; 59 | } 60 | 61 | @NotNull 62 | public static String makeBigNumberHumanReadableFullPrecision(@NotNull BigDecimal bigNumber) { 63 | // INFO: rethink this method 64 | String number = StringUtils.leftPad(bigNumber.toPlainString(), 18, "0"); 65 | if (number.length() == 18) { 66 | if (new BigDecimal(number).compareTo(BigDecimal.ZERO) == 0) return "0.000000000000000000"; 67 | return "0." + number; 68 | } else { 69 | String[] splitString = number.split("\\."); 70 | if (splitString.length == 2) { 71 | String decimal = StringUtils.rightPad(splitString[1], 18, "0"); 72 | decimal = decimal.substring(0, 18); 73 | return splitString[0] + "." + decimal; 74 | } 75 | return number.substring(0, number.length() - 18) 76 | + "." 77 | + number.substring(number.length() - 18); 78 | } 79 | } 80 | 81 | @NotNull 82 | public static String makeBigNumberHumanReadableFullPrecision(@NotNull BigInteger bigNumber) { 83 | String number = StringUtils.leftPad(bigNumber.toString(), 18, "0"); 84 | if (number.length() == 18) { 85 | return "0." + number; 86 | } else { 87 | return number.substring(0, number.length() - 18) 88 | + "." 89 | + number.substring(number.length() - 18); // todo: test if .toPlainString() is necessary 90 | } 91 | } 92 | 93 | @NotNull 94 | public static String makeBigNumberHumanReadableFullPrecision( 95 | @NotNull BigInteger bigNumber, int decimals) { 96 | String number = StringUtils.leftPad(bigNumber.toString(), decimals, "0"); 97 | if (number.length() == decimals) { 98 | return "0." + number; 99 | } else { 100 | // TODO: fix 15401073595382400000000. 101 | return number.substring(0, number.length() - decimals) 102 | + "." 103 | + number.substring(number.length() - decimals); 104 | } 105 | } 106 | 107 | // todo: add tests 108 | @NotNull 109 | public static BigInteger convertUint256toBigInteger(@NotNull BigInteger bigNumber) { 110 | return bigNumber.divide(BigDecimal.valueOf(Math.pow(10, 27)).toBigInteger()); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/main/java/de/devgao/defi/util/ContractUser.java: -------------------------------------------------------------------------------- 1 | package devgao.io.util; 2 | 3 | import devgao.io.contractneedsprovider.CircuitBreaker; 4 | import org.jetbrains.annotations.NotNull; 5 | import org.slf4j.LoggerFactory; 6 | import org.web3j.tx.Contract; 7 | 8 | import java.lang.invoke.MethodHandles; 9 | 10 | public class ContractUser { 11 | private static final org.slf4j.Logger logger = 12 | LoggerFactory.getLogger(MethodHandles.lookup().lookupClass().getSimpleName()); 13 | private static final String EXCEPTION = "Exception"; 14 | 15 | protected static void isContractValid(@NotNull Contract contract, CircuitBreaker circuitBreaker) { 16 | try { 17 | if (contract.isValid()) { 18 | logger.trace("{} CONTRACT IS VALID", contract.getClass()); 19 | } else { 20 | logger.trace("{} CONTRACT IS INVALID", contract.getClass()); 21 | circuitBreaker.stopRunning(); 22 | } 23 | } catch (Exception e) { 24 | circuitBreaker.stopRunning(); 25 | logger.error(EXCEPTION, e); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/de/devgao/defi/util/DirectoryUtil.java: -------------------------------------------------------------------------------- 1 | package devgao.io.util; 2 | 3 | import devgao.io.Main; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import java.io.File; 7 | import java.lang.invoke.MethodHandles; 8 | import java.net.URISyntaxException; 9 | 10 | public class DirectoryUtil { 11 | private static final org.slf4j.Logger logger = 12 | LoggerFactory.getLogger(MethodHandles.lookup().lookupClass().getSimpleName()); 13 | 14 | private DirectoryUtil() { 15 | throw new IllegalStateException("Utility class"); 16 | } 17 | 18 | public static String getCurrentDirectory(boolean isDevelopmentEnvironment) { 19 | String currentDirectory = ""; 20 | try { 21 | File currentFile = 22 | new File(Main.class.getProtectionDomain().getCodeSource().getLocation().toURI()) 23 | .getParentFile(); 24 | if (isDevelopmentEnvironment) { 25 | logger.trace("TESTING TRUE"); // weird bug with caching testing = false 26 | currentDirectory = currentFile.getParentFile().getPath(); 27 | } else { 28 | logger.trace("TESTING FALSE"); // weird bug with caching testing = false 29 | currentDirectory = currentFile.getPath(); 30 | } 31 | } catch (URISyntaxException e) { 32 | logger.error("URISyntaxException", e); 33 | } 34 | return currentDirectory; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/de/devgao/defi/util/Ethereum.java: -------------------------------------------------------------------------------- 1 | package devgao.io.util; 2 | 3 | import devgao.io.contractneedsprovider.ContractNeedsProvider; 4 | import devgao.io.contractneedsprovider.Permissions; 5 | import devgao.io.medianizer.MedianException; 6 | import devgao.io.numberutil.Wad18; 7 | import org.jetbrains.annotations.NotNull; 8 | import org.slf4j.LoggerFactory; 9 | import org.web3j.crypto.Credentials; 10 | import org.web3j.protocol.Web3j; 11 | import org.web3j.protocol.core.DefaultBlockParameterName; 12 | import org.web3j.protocol.core.methods.response.TransactionReceipt; 13 | import org.web3j.tx.Transfer; 14 | import org.web3j.utils.Convert; 15 | 16 | import java.io.IOException; 17 | import java.lang.invoke.MethodHandles; 18 | import java.math.BigDecimal; 19 | import java.math.BigInteger; 20 | 21 | import static devgao.io.numberutil.NumberUtil.getMachineReadable; 22 | 23 | public class Ethereum { 24 | private static final org.slf4j.Logger logger = 25 | LoggerFactory.getLogger(MethodHandles.lookup().lookupClass().getSimpleName()); 26 | 27 | public final Wad18 minimumEthereumReserveUpperLimit; 28 | public final Wad18 minimumEthereumReserveLowerLimit; 29 | public final Wad18 minimumEthereumNecessaryForSale; 30 | 31 | private final Web3j web3j; 32 | private final Credentials credentials; 33 | private final Permissions permissions; 34 | 35 | private Wad18 balance; 36 | 37 | public Ethereum( 38 | @NotNull ContractNeedsProvider contractNeedsProvider, 39 | double minimumEthereumReserveUpperLimit, 40 | double minimumEthereumReserveLowerLimit, 41 | double minimumEthereumNecessaryForSale) { 42 | web3j = contractNeedsProvider.getWeb3j(); 43 | permissions = contractNeedsProvider.getPermissions(); 44 | credentials = contractNeedsProvider.getCredentials(); 45 | this.minimumEthereumReserveUpperLimit = 46 | new Wad18(getMachineReadable(minimumEthereumReserveUpperLimit)); 47 | this.minimumEthereumReserveLowerLimit = 48 | new Wad18(getMachineReadable(minimumEthereumReserveLowerLimit)); 49 | this.minimumEthereumNecessaryForSale = 50 | new Wad18(getMachineReadable(minimumEthereumNecessaryForSale)); 51 | balance = Wad18.ZERO; 52 | } 53 | 54 | public void sendTransaction() throws Exception { 55 | if (permissions.check("SEND TRANSACTION")) { 56 | logger.trace("Sending 1 Wei ({} Ether)", Convert.fromWei("1", Convert.Unit.ETHER)); 57 | 58 | TransactionReceipt transferReceipt = 59 | Transfer.sendFunds( 60 | web3j, 61 | credentials, 62 | credentials.getAddress(), // you can put any address here 63 | BigDecimal.ONE, 64 | Convert.Unit.WEI) // 1 wei = 10^-18 Ether, this is the amount sent 65 | .send(); 66 | 67 | logger.trace( 68 | "Transaction complete, view it at https://etherscan.io/tx/{}", 69 | transferReceipt.getTransactionHash()); 70 | logger.trace("end run()"); 71 | } 72 | } 73 | 74 | public void updateBalance() { 75 | Wad18 oldBalance = balance; // todo: use account 76 | try { 77 | balance = 78 | new Wad18( 79 | web3j 80 | .ethGetBalance(getAddress(), DefaultBlockParameterName.LATEST) 81 | .send() 82 | .getBalance()); 83 | } catch (IOException e) { 84 | logger.error("IOException", e); 85 | balance = Wad18.ZERO; // todo: if timeout, then shutdown -> BETTER: retry getting eth balance 86 | } 87 | if (oldBalance != null && oldBalance.compareTo(balance) != 0) { 88 | logger.trace("OLD BALANCE {} ETH", oldBalance); 89 | logger.trace("UPDATED BALANCE {} ETH", balance); 90 | } else if (oldBalance == null) { 91 | logger.trace("ETH BALANCE {} ETH", balance); 92 | } 93 | } 94 | 95 | public String getAddress() { 96 | return credentials.getAddress(); 97 | } 98 | 99 | public Wad18 getBalance() { 100 | if (balance.compareTo(Wad18.ZERO) != 0) logger.trace("ETH BALANCE {}{}", balance, " ETH"); 101 | return balance; 102 | } 103 | 104 | public Wad18 getBalanceWithoutMinimumEthereumReserveUpperLimit() { 105 | Wad18 balanceWithoutMinimumEthereumReserveUpperLimit = 106 | Wad18.ZERO.max(balance.subtract(minimumEthereumReserveUpperLimit)); 107 | if (balanceWithoutMinimumEthereumReserveUpperLimit.compareTo(Wad18.ZERO) != 0) 108 | logger.trace( 109 | "ETH BALANCE WITHOUT MINIMUM ETHEREUM RESERVER UPPER LIMIT {}{}", 110 | balanceWithoutMinimumEthereumReserveUpperLimit, 111 | " ETH"); 112 | return balanceWithoutMinimumEthereumReserveUpperLimit; 113 | } 114 | 115 | public BigInteger getCurrentBlock() throws MedianException { 116 | try { 117 | return web3j.ethBlockNumber().send().getBlockNumber(); 118 | } catch (Exception e) { 119 | logger.error("Exception", e); 120 | throw new MedianException("CAN'T GET CURRENT BLOCK"); 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/main/java/de/devgao/defi/util/IContract.java: -------------------------------------------------------------------------------- 1 | package devgao.io.util; 2 | 3 | public interface IContract { 4 | String getAddress(); 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/de/devgao/defi/util/JavaProperties.java: -------------------------------------------------------------------------------- 1 | package devgao.io.util; 2 | 3 | import org.apache.commons.configuration.PropertiesConfiguration; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import java.io.FileInputStream; 7 | import java.io.IOException; 8 | import java.io.InputStream; 9 | import java.lang.invoke.MethodHandles; 10 | import java.util.Properties; 11 | 12 | public class JavaProperties { 13 | private static final org.slf4j.Logger logger = 14 | LoggerFactory.getLogger(MethodHandles.lookup().lookupClass().getSimpleName()); 15 | private static final String CONFIG_FILE_NAME = "/config.properties"; 16 | private final String currentDirectory; 17 | 18 | public JavaProperties(boolean isDevelopmentEnvironment) { 19 | this.currentDirectory = DirectoryUtil.getCurrentDirectory(isDevelopmentEnvironment); 20 | logger.trace("{}{}", currentDirectory, CONFIG_FILE_NAME); 21 | } 22 | 23 | public String getValue(String key) { 24 | try (InputStream propertiesFile = new FileInputStream(currentDirectory + CONFIG_FILE_NAME)) { 25 | Properties properties = new Properties(); 26 | properties.load(propertiesFile); 27 | String propertiesValue = properties.getProperty(key); 28 | logger.trace("READ KEY {}", key); 29 | 30 | String loggedValue; 31 | switch (key) { 32 | case "password": 33 | loggedValue = "*password is not logged*"; 34 | break; 35 | case "infuraProjectId": 36 | loggedValue = "*infuraProjectId is not logged*"; 37 | break; 38 | case "wallet": 39 | loggedValue = "*wallet is not logged*"; 40 | break; 41 | default: 42 | loggedValue = propertiesValue; 43 | } 44 | 45 | logger.trace("READ VALUE {}", loggedValue); 46 | 47 | return propertiesValue; 48 | } catch (IOException e) { 49 | logger.error("IOException ", e); 50 | } 51 | logger.warn("READ VALUE EXCEPTION"); 52 | return ""; 53 | } 54 | 55 | public void setValue(String key, String value) { 56 | try { 57 | PropertiesConfiguration config = 58 | new PropertiesConfiguration(currentDirectory + CONFIG_FILE_NAME); 59 | logger.trace("OLD KEY VALUE {}", config.getProperty(key)); 60 | config.setProperty(key, value); 61 | logger.trace("WROTE KEY VALUE {} {}", key, value); 62 | config.save(); 63 | } catch (Exception e) { 64 | logger.error("Exception", e); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/de/devgao/defi/util/NumberUtil.java: -------------------------------------------------------------------------------- 1 | package devgao.io.util; 2 | 3 | import org.apache.commons.lang3.StringUtils; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | import java.math.BigDecimal; 7 | import java.math.BigInteger; 8 | import java.math.RoundingMode; 9 | 10 | public final class NumberUtil { 11 | public static final BigInteger UINT_MAX = 12 | new BigInteger( 13 | "115792089237316195423570985008687907853269984665640564039457584007913129639935"); // 2^256-1 14 | /** 15 | * MINIMUM_APPROVAL_ALLOWANCE seems to be the maximal returned value instead of UINT_MAX TODO: ask 16 | * on github if this is necessary because of an web3j bug 17 | */ 18 | public static final BigInteger MINIMUM_APPROVAL_ALLOWANCE = 19 | new BigInteger("115792089237316195423570985008687907853269"); 20 | 21 | private NumberUtil() {} 22 | 23 | @NotNull 24 | public static BigInteger getMachineReadable(Double smallNumber) { 25 | if (smallNumber == 0.0) return BigInteger.ZERO; 26 | return BigDecimal.valueOf(smallNumber * Math.pow(10, 18)).toBigInteger(); 27 | } 28 | 29 | public static BigInteger multiply(@NotNull BigInteger multiplicand1, BigInteger multiplicand2) { 30 | return multiplicand1.multiply(multiplicand2).divide(BigInteger.valueOf(1000000000000000000L)); 31 | } 32 | 33 | public static BigDecimal multiply(@NotNull BigDecimal multiplicand1, BigDecimal multiplicand2) { 34 | return multiplicand1 35 | .multiply(multiplicand2) 36 | .divide(BigDecimal.valueOf(Math.pow(10, 18)), RoundingMode.DOWN); 37 | } 38 | 39 | public static BigInteger divide(@NotNull BigInteger dividend, @NotNull BigInteger divisor) { 40 | if (divisor.compareTo(BigInteger.ZERO) == 0) 41 | throw new IllegalArgumentException("Argument 'divisor' is 0"); // todo: test exception 42 | return dividend.multiply(BigInteger.valueOf(1000000000000000000L)).divide(divisor); 43 | } 44 | 45 | public static BigDecimal divide(@NotNull BigDecimal dividend, @NotNull BigDecimal divisor) { 46 | if (divisor.compareTo(BigDecimal.ZERO) == 0) 47 | throw new IllegalArgumentException("Argument 'divisor' is 0"); 48 | return dividend 49 | .multiply(BigDecimal.valueOf(Math.pow(10, 18))) 50 | .divide(divisor, 0, RoundingMode.DOWN) 51 | .stripTrailingZeros(); 52 | } 53 | 54 | public static double getCurrency(@NotNull BigDecimal bigNumber) { 55 | return Math.round((bigNumber.floatValue() / Math.pow(10, 18)) * 100.0) / 100.0; 56 | } 57 | 58 | public static @NotNull String getCurrency(@NotNull BigInteger bigNumber) { 59 | return getPrecision(bigNumber, 2); 60 | } 61 | 62 | public static double getHumanReadable(@NotNull BigDecimal bigNumber) { 63 | return Math.round((bigNumber.floatValue() / Math.pow(10, 18)) * 10000.0) / 10000.0; 64 | } 65 | 66 | public static @NotNull String getHumanReadable(@NotNull BigInteger bigNumber) { 67 | return getPrecision(bigNumber, 4); 68 | } 69 | 70 | private static @NotNull String getPrecision(@NotNull BigInteger bigNumber, int decimals) { 71 | return String.valueOf( 72 | Math.round((bigNumber.floatValue() / Math.pow(10, 18)) * Math.pow(10, decimals)) 73 | / Math.pow(10, decimals)); 74 | } 75 | 76 | // INFO: have a look at the method and its different usages, maybe refactor this method 77 | @NotNull 78 | public static String getFullPrecision(@NotNull BigDecimal bigNumber) { 79 | String number = StringUtils.leftPad(bigNumber.toPlainString(), 18, "0"); 80 | if (number.length() == 18) { 81 | if (new BigDecimal(number).compareTo(BigDecimal.ZERO) == 0) return "0.000000000000000000"; 82 | return "0." + number; 83 | } else { 84 | String[] splitString = number.split("\\."); 85 | if (splitString.length == 2) { 86 | String decimal = StringUtils.rightPad(splitString[1], 18, "0"); 87 | decimal = decimal.substring(0, 18); 88 | return splitString[0] + "." + decimal; 89 | } 90 | return number.substring(0, number.length() - 18) 91 | + "." 92 | + number.substring(number.length() - 18); 93 | } 94 | } 95 | 96 | // TODO: test this method 97 | @NotNull 98 | public static String getFullPrecision(@NotNull BigInteger bigNumber) { 99 | String number = StringUtils.leftPad(bigNumber.toString(), 18, "0"); 100 | if (number.length() == 18) { 101 | return "0." + number; 102 | } else { 103 | return number.substring(0, number.length() - 18) 104 | + "." 105 | + number.substring(number.length() - 18); 106 | } 107 | } 108 | 109 | @NotNull 110 | public static String getFullPrecision(@NotNull BigInteger bigNumber, int decimals) { 111 | String number = StringUtils.leftPad(bigNumber.toString(), decimals, "0"); 112 | if (number.length() == decimals) { 113 | return "0." + number; 114 | } else { 115 | // TODO: test this method with following input 15401073595382400000000. 116 | return number.substring(0, number.length() - decimals) 117 | + "." 118 | + number.substring(number.length() - decimals); 119 | } 120 | } 121 | 122 | // TODO: test this method 123 | @NotNull 124 | public static BigInteger convertUint256toBigInteger(@NotNull BigInteger bigNumber) { 125 | return bigNumber.divide(BigDecimal.valueOf(Math.pow(10, 27)).toBigInteger()); // todo: might round down here... 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/main/java/de/devgao/defi/util/ProfitCalculator.java: -------------------------------------------------------------------------------- 1 | package devgao.io.util; 2 | 3 | import devgao.io.numberutil.Wad18; 4 | import org.jetbrains.annotations.NotNull; 5 | import org.slf4j.LoggerFactory; 6 | 7 | import java.lang.invoke.MethodHandles; 8 | 9 | import static devgao.io.numberutil.NumberUtil.getMachineReadable; 10 | 11 | public class ProfitCalculator { 12 | private static final org.slf4j.Logger logger = 13 | LoggerFactory.getLogger(MethodHandles.lookup().lookupClass().getSimpleName()); 14 | 15 | private ProfitCalculator() { 16 | throw new IllegalStateException("Utility class"); 17 | } 18 | 19 | @NotNull // todo: think about moving these methods into the classes uniswapoffer, 20 | // ethtokenswapinput and 21 | // tokentoethswapinput 22 | public static Wad18 getPotentialProfit( 23 | Wad18 bestOfferMedianRatio, Wad18 toSellInDAI, double percentageOfProfitAsFee) { 24 | Wad18 potentialProfitBeforeCosts = 25 | new Wad18(getMachineReadable(1.0)).subtract(bestOfferMedianRatio).multiply(toSellInDAI); 26 | logger.trace( 27 | "POTENTIAL PROFIT BEFORE COSTS {}{}", potentialProfitBeforeCosts.toString(5), " DAI"); 28 | Wad18 maxTransactionCosts = 29 | new Wad18(getMachineReadable(0.50)) 30 | .max( 31 | potentialProfitBeforeCosts.multiply( 32 | new Wad18(getMachineReadable(percentageOfProfitAsFee)))); 33 | Wad18 potentialProfitAfterCosts = potentialProfitBeforeCosts.subtract(maxTransactionCosts); 34 | 35 | if (potentialProfitAfterCosts.compareTo(Wad18.ZERO) > 0) { 36 | logger.trace("POTENTIAL PROFIT +{}{}", potentialProfitAfterCosts.toString(2), " DAI"); 37 | } else { 38 | logger.trace("POTENTIAL PROFIT {}{}", potentialProfitAfterCosts.toString(2), " DAI"); 39 | } 40 | 41 | return potentialProfitAfterCosts; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/de/devgao/defi/util/TransactionUtil.java: -------------------------------------------------------------------------------- 1 | package devgao.io.util; 2 | 3 | import devgao.io.numberutil.Wad18; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | import java.math.BigInteger; 7 | 8 | import static devgao.io.numberutil.NumberUtil.getMachineReadable; 9 | 10 | public class TransactionUtil { 11 | private TransactionUtil() { 12 | throw new IllegalStateException("Utility class"); 13 | } 14 | 15 | public static Wad18 getTransactionCosts( 16 | @NotNull Wad18 slowGasPrice, Wad18 medianEthereumPrice, @NotNull BigInteger gasLimit, int times) { 17 | return new Wad18(getMachineReadable((double) times)) 18 | .multiply(new Wad18(getMachineReadable(gasLimit.doubleValue()))) 19 | .multiply(slowGasPrice.multiply(medianEthereumPrice)); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/de/devgao/defi/weth/Weth.java: -------------------------------------------------------------------------------- 1 | package devgao.io.weth; 2 | 3 | import devgao.io.contractneedsprovider.CircuitBreaker; 4 | import devgao.io.contractneedsprovider.ContractNeedsProvider; 5 | import devgao.io.contractneedsprovider.Permissions; 6 | import devgao.io.contractutil.Account; 7 | import devgao.io.contractutil.Approval; 8 | import devgao.io.contractutil.ContractValidationUtil; 9 | import devgao.io.gasprovider.GasProvider; 10 | import devgao.io.numberutil.Wad18; 11 | import devgao.io.util.Balances; 12 | import org.jetbrains.annotations.NotNull; 13 | import org.slf4j.LoggerFactory; 14 | import org.web3j.crypto.Credentials; 15 | import org.web3j.protocol.Web3j; 16 | import org.web3j.protocol.core.methods.response.TransactionReceipt; 17 | 18 | import java.lang.invoke.MethodHandles; 19 | import java.util.concurrent.TimeUnit; 20 | 21 | public class Weth { 22 | public static final String ADDRESS = "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"; 23 | private static final String EXCEPTION = "Exception"; 24 | private static final org.slf4j.Logger logger = 25 | LoggerFactory.getLogger(MethodHandles.lookup().lookupClass().getSimpleName()); 26 | private final WethContract wethContract; 27 | private final GasProvider gasProvider; 28 | private final Permissions permissions; 29 | private final CircuitBreaker circuitBreaker; 30 | 31 | private final Account account; 32 | private final Approval approval; 33 | 34 | public Weth(@NotNull ContractNeedsProvider contractNeedsProvider) { 35 | Web3j web3j = contractNeedsProvider.getWeb3j(); 36 | gasProvider = contractNeedsProvider.getGasProvider(); 37 | permissions = contractNeedsProvider.getPermissions(); 38 | Credentials credentials = contractNeedsProvider.getCredentials(); 39 | circuitBreaker = contractNeedsProvider.getCircuitBreaker(); 40 | wethContract = WethContract.load(ADDRESS, web3j, credentials, gasProvider); 41 | ContractValidationUtil.isContractValid(wethContract, circuitBreaker); 42 | account = new Account(wethContract, credentials, "WETH"); 43 | approval = new Approval(wethContract, contractNeedsProvider); 44 | } 45 | 46 | public void weth2Eth( 47 | Balances balances, 48 | Wad18 potentialProfit, 49 | Wad18 medianEthereumPrice, 50 | Wad18 amountOfWethToUnwrap) { 51 | if (permissions.check("WETH2ETH")) { 52 | try { 53 | gasProvider.updateFastGasPrice(medianEthereumPrice, potentialProfit); 54 | Wad18 wethBalance = balances.weth.getAccount().getBalance(); 55 | if (amountOfWethToUnwrap.compareTo(wethBalance) > 0) { 56 | amountOfWethToUnwrap = wethBalance; 57 | logger.warn("WETH AMOUNT TO UNWRAP WAS TOO BIG {}", amountOfWethToUnwrap); 58 | } 59 | 60 | logger.warn("CONVERT {} WETH TO ETH", amountOfWethToUnwrap); 61 | TransactionReceipt transferReceipt = 62 | wethContract.withdraw(amountOfWethToUnwrap.toBigInteger()).send(); 63 | TimeUnit.SECONDS.sleep( 64 | 1); // for balances to update, otherwise same (buy/sell) type of transaction happens, 65 | // although not enough balance weth/dai 66 | logger.trace( 67 | "Transaction complete, view it at https://etherscan.io/tx/{}", 68 | transferReceipt.getTransactionHash()); 69 | 70 | balances.updateBalanceInformation(medianEthereumPrice); // not really necessary? 71 | } catch (Exception e) { 72 | logger.error(EXCEPTION, e); 73 | circuitBreaker.addTransactionFailedNow(); 74 | } 75 | } 76 | } 77 | 78 | public void eth2Weth( 79 | Wad18 amountOfEthToWrap, 80 | Wad18 potentialProfit, 81 | Wad18 medianEthereumPrice, 82 | Balances balances) { 83 | if (permissions.check("ETH2WETH")) { 84 | try { 85 | gasProvider.updateFastGasPrice(medianEthereumPrice, potentialProfit); 86 | if (amountOfEthToWrap.compareTo(balances.ethereum.getBalance()) > 0) { 87 | amountOfEthToWrap = balances.ethereum.getBalanceWithoutMinimumEthereumReserveUpperLimit(); 88 | logger.warn("ETH AMOUNT TO WRAP WAS TOO BIG {}", amountOfEthToWrap); 89 | } 90 | 91 | logger.warn("CONVERT {} ETH TO WETH", amountOfEthToWrap); 92 | TransactionReceipt transferReceipt = 93 | wethContract.deposit(amountOfEthToWrap.toBigInteger()).send(); 94 | TimeUnit.SECONDS.sleep(1); 95 | logger.trace( 96 | "Transaction complete, view it at https://etherscan.io/tx/{}", 97 | transferReceipt.getTransactionHash()); 98 | balances.updateBalanceInformation(medianEthereumPrice); // not really necessary? 99 | } catch (Exception e) { 100 | logger.error(EXCEPTION, e); 101 | circuitBreaker.addTransactionFailedNow(); 102 | } 103 | } 104 | } 105 | 106 | public Approval getApproval() { 107 | return approval; 108 | } 109 | 110 | public Account getAccount() { 111 | return account; 112 | } 113 | 114 | public void checkIfWeth2EthConversionNecessaryThenDoIt( 115 | Wad18 requiredBalance, Balances balances, Wad18 potentialProfit, Wad18 medianEthereumPrice) { 116 | if (account.getBalance().compareTo(requiredBalance) < 0) { 117 | logger.trace("WETH 2 ETH CONVERSION NECESSARY"); 118 | weth2Eth( 119 | balances, 120 | potentialProfit, 121 | medianEthereumPrice, 122 | account.getBalance().subtract(requiredBalance)); 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | System.out 4 | 5 | %d{HH:mm:ss.SSS} %-5level %-20logger{20} %msg%n 6 | 7 | 8 | 9 | 10 | logs/%d{yyyy_MM_dd}.%i.log 11 | 200MB 12 | 365 13 | 5GB 14 | 15 | 16 | %d{HH:mm:ss.SSS} %-5level %-20logger{20} %msg%n 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/test/java/de/devgao/defi/CompoundDaiIT.java: -------------------------------------------------------------------------------- 1 | // package devgao.io; 2 | // 3 | // import devgao.io.exceptions.MedianIsZeroException; 4 | // import devgao.io.util.GasPriceManager; 5 | // import devgao.io.util.JavaProperties; 6 | // import devgao.io.util.Logger; 7 | // import devgao.io.util.Balances; 8 | // import devgao.io.users.CompoundDai; 9 | // import devgao.io.users.MedianEthereumPrice; 10 | // import org.junit.jupiter.api.BeforeAll; 11 | // import org.junit.jupiter.api.Test; 12 | // import org.web3j.protocol.Web3j; 13 | // import org.web3j.protocol.http.HttpService; 14 | // 15 | // import java.math.BigDecimal; 16 | // import java.math.BigInteger; 17 | // 18 | // import static devgao.io.util.Logger.getLogFilePath; 19 | // import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; 20 | // import static org.junit.jupiter.api.Assertions.assertEquals; 21 | // 22 | // class CompoundDaiIT { 23 | // @Test 24 | // void mintAndRedeemTest() { 25 | // assertDoesNotThrow(() -> { 26 | // assertEquals(BigDecimal.ZERO, Balances.getDaiInCompound()); 27 | // CompoundDai.mint(new BigInteger("5000000000000000000"), 28 | // MedianEthereumPrice.getPrice()); 29 | // // org.opentest4j.AssertionFailedError: Unexpected exception thrown: 30 | // java.lang.AssertionError: expected:<5000000000000000000> but was:<4999999999977162310> 31 | // assertEquals(new BigDecimal("5000000000000000000"), Balances.getDaiInCompound()); 32 | // CompoundDai.redeem(new BigInteger("5000000000000000000"), BigDecimal.ZERO, 33 | // MedianEthereumPrice.getPrice()); 34 | // assertEquals(BigDecimal.ZERO, Balances.getDaiInCompound()); 35 | // }); 36 | // } 37 | // 38 | // @Test 39 | // void redeemTest() throws MedianIsZeroException { 40 | // CompoundDai.redeem(new BigInteger("5000000000000000000"), BigDecimal.ZERO, 41 | // MedianEthereumPrice.getPrice()); // too much? 0.000000024445745446 for 244.45745446 42 | // assertEquals(BigDecimal.ZERO, Balances.getDaiInCompound()); 43 | // } 44 | // 45 | // @Test 46 | // void redeemAllTest() throws MedianIsZeroException { 47 | // CompoundDai.redeemAll(BigDecimal.ZERO, MedianEthereumPrice.getPrice()); 48 | // assertEquals(BigInteger.ZERO, Balances.getCdai()); 49 | // } 50 | // 51 | // @Test 52 | // void getCDaiBalanceTest() { 53 | // BigInteger cDaiBalance = CompoundDai.getCDaiBalance(); 54 | // assertEquals(BigInteger.ZERO, cDaiBalance); 55 | // } 56 | // 57 | // @BeforeAll 58 | // static void init() throws Exception { 59 | // // set log file path 60 | // String currentDirectory = getLogFilePath(); 61 | // 62 | // // create a new web3j instance to connect to remote nodes on the network 63 | // GasPriceManager.web3j = Web3j.build(new 64 | // HttpService("https://mainnet.infura.io/v3/8f77571f778f4c7a95f735857a5a340f")); 65 | // Logger.log("Connected to Ethereum client version: " + 66 | // GasPriceManager.web3j.web3ClientVersion().send().getWeb3ClientVersion()); 67 | // 68 | // // load Environment variables 69 | // JavaProperties javaProperties = new JavaProperties(currentDirectory); 70 | // 71 | // // initialize persistent scripts 72 | // GasPriceManager.initializePersistentScripts(); 73 | // 74 | // // check if interacting with persistent scripts is allowed 75 | // GasPriceManager.checkAllowances(); 76 | // } 77 | // } 78 | -------------------------------------------------------------------------------- /src/test/java/de/devgao/defi/compounddai/CompoundDaiIT.java: -------------------------------------------------------------------------------- 1 | package devgao.io.compounddai; 2 | 3 | import devgao.io.contractneedsprovider.*; 4 | import devgao.io.gasprovider.GasProvider; 5 | import devgao.io.numberutil.Sth28; 6 | import devgao.io.numberutil.Wad18; 7 | import devgao.io.util.JavaProperties; 8 | import org.junit.jupiter.api.BeforeEach; 9 | import org.junit.jupiter.api.Test; 10 | import org.web3j.crypto.Credentials; 11 | import org.web3j.protocol.Web3j; 12 | 13 | import static org.junit.jupiter.api.Assertions.assertTrue; 14 | 15 | class CompoundDaiIT { 16 | private static final String TRAVIS_INFURA_PROJECT_ID = "TRAVIS_INFURA_PROJECT_ID"; 17 | private static final String TRAVIS_WALLET = "TRAVIS_WALLET"; 18 | private static final String TRAVIS_PASSWORD = "TRAVIS_PASSWORD"; 19 | 20 | private static final String TOO_HIGH = "Error, value is too high"; 21 | private static final String TOO_LOW = "Error, value is too low"; 22 | 23 | private static final Wad18 MINIMUM_GAS_PRICE = new Wad18(1_000000000); 24 | private static final Wad18 MAXIMUM_GAS_PRICE = new Wad18(200_000000000L); 25 | CompoundDai compoundDai; 26 | 27 | @BeforeEach 28 | void setUp() { 29 | String infuraProjectId; 30 | String password; 31 | String wallet; 32 | 33 | JavaProperties javaProperties = new JavaProperties(true); 34 | 35 | if ("true".equals(System.getenv().get("TRAVIS"))) { 36 | infuraProjectId = System.getenv().get(TRAVIS_INFURA_PROJECT_ID); 37 | wallet = System.getenv().get(TRAVIS_WALLET); 38 | password = System.getenv().get(TRAVIS_PASSWORD); 39 | } else { 40 | infuraProjectId = javaProperties.getValue("infuraProjectId"); 41 | wallet = javaProperties.getValue("wallet"); 42 | password = javaProperties.getValue("password"); 43 | } 44 | 45 | CircuitBreaker circuitBreaker = new CircuitBreaker(); 46 | Web3j web3j = new Web3jProvider(infuraProjectId).web3j; 47 | Credentials credentials = new Wallet(password, wallet).getCredentials(); 48 | GasProvider gasProvider = new GasProvider(web3j, MINIMUM_GAS_PRICE, MAXIMUM_GAS_PRICE); 49 | Permissions permissions = new Permissions(true, true); 50 | ContractNeedsProvider contractNeedsProvider = 51 | new ContractNeedsProvider(web3j, credentials, gasProvider, permissions, circuitBreaker); 52 | compoundDai = new CompoundDai(contractNeedsProvider); 53 | } 54 | 55 | @Test 56 | void getExchangeRate_isBiggerThanHistoricRate_true() { 57 | Sth28 actual = compoundDai.getExchangeRate(); 58 | Sth28 expected = new Sth28("204721828221871910000000000"); 59 | assertTrue(actual.compareTo(expected) > 0); 60 | } 61 | 62 | @Test 63 | void getSupplyRate_betweenRealisticBounds_true() { 64 | Wad18 actual = compoundDai.getSupplyRate(); 65 | Wad18 lowerBound = new Wad18("100000000000000"); // 0.001% = 0.0001 66 | Wad18 higherBound = new Wad18("500000000000000000"); // 50% = 0.5 67 | assertTrue(actual.compareTo(higherBound) < 0, TOO_LOW); 68 | assertTrue(actual.compareTo(lowerBound) > 0, TOO_HIGH); 69 | } 70 | 71 | @Test 72 | void getDailyInterest_betweenRealisticBounds_true() { 73 | Wad18 daiSupplied = new Wad18("5000000000000000000000"); 74 | Wad18 actual = compoundDai.getDailyInterest(daiSupplied); 75 | Wad18 lowerBound = new Wad18("1360000000000000"); // 0.001% = 0.0001 / 365 * 5000 76 | Wad18 higherBound = new Wad18("6840000000000000000"); // 50% = 0.5 / 365 * 5000 77 | assertTrue(actual.compareTo(higherBound) < 0, TOO_LOW); 78 | assertTrue(actual.compareTo(lowerBound) > 0, TOO_HIGH); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/test/java/de/devgao/defi/contractutil/ContractValidationUtilIT.java: -------------------------------------------------------------------------------- 1 | package devgao.io.contractutil; 2 | 3 | import devgao.io.contractneedsprovider.CircuitBreaker; 4 | import devgao.io.contractneedsprovider.Wallet; 5 | import devgao.io.contractneedsprovider.Web3jProvider; 6 | import devgao.io.dai.DaiContract; 7 | import devgao.io.gasprovider.GasProvider; 8 | import devgao.io.numberutil.Wad18; 9 | import devgao.io.util.JavaProperties; 10 | import devgao.io.weth.WethContract; 11 | import org.junit.jupiter.api.BeforeEach; 12 | import org.junit.jupiter.api.Test; 13 | import org.web3j.crypto.Credentials; 14 | import org.web3j.protocol.Web3j; 15 | 16 | import static org.junit.jupiter.api.Assertions.*; 17 | 18 | class ContractValidationUtilIT { 19 | private static final String TRAVIS_INFURA_PROJECT_ID = "TRAVIS_INFURA_PROJECT_ID"; 20 | private static final String TRAVIS_WALLET = "TRAVIS_WALLET"; 21 | private static final String TRAVIS_PASSWORD = "TRAVIS_PASSWORD"; 22 | 23 | private static final String EXCEPTION = "Exception"; 24 | 25 | private static final Wad18 minimumGasPrice = new Wad18(1_000000000); 26 | private static final Wad18 maximumGasPrice = new Wad18(200_000000000L); 27 | 28 | Web3j web3j; 29 | Credentials credentials; 30 | GasProvider gasProvider; 31 | 32 | @BeforeEach 33 | void setUp() { 34 | String infuraProjectId; 35 | String password; 36 | String wallet; 37 | 38 | JavaProperties javaProperties = new JavaProperties(true); 39 | 40 | if ("true".equals(System.getenv().get("TRAVIS"))) { 41 | infuraProjectId = System.getenv().get(TRAVIS_INFURA_PROJECT_ID); 42 | wallet = System.getenv().get(TRAVIS_WALLET); 43 | password = System.getenv().get(TRAVIS_PASSWORD); 44 | } else { 45 | infuraProjectId = javaProperties.getValue("infuraProjectId"); 46 | wallet = javaProperties.getValue("wallet"); 47 | password = javaProperties.getValue("password"); 48 | } 49 | 50 | web3j = new Web3jProvider(infuraProjectId).web3j; 51 | credentials = new Wallet(password, wallet).getCredentials(); 52 | gasProvider = new GasProvider(web3j, minimumGasPrice, maximumGasPrice); 53 | } 54 | 55 | @Test 56 | void isContractValid_isValid_continueRunning() { 57 | WethContract contract = 58 | WethContract.load( 59 | "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", web3j, credentials, gasProvider); 60 | CircuitBreaker circuitBreaker = new CircuitBreaker(); 61 | assertDoesNotThrow(() -> ContractValidationUtil.isContractValid(contract, circuitBreaker)); 62 | assertTrue(circuitBreaker.getContinueRunning()); 63 | } 64 | 65 | @Test 66 | void isContractValid_isNotValid_stopRunning() { 67 | DaiContract contract = 68 | DaiContract.load( 69 | "0x0000000000000000000000000000000000000000", web3j, credentials, gasProvider); 70 | CircuitBreaker circuitBreaker = new CircuitBreaker(); 71 | assertDoesNotThrow(() -> ContractValidationUtil.isContractValid(contract, circuitBreaker)); 72 | assertFalse(circuitBreaker.getContinueRunning()); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/test/java/de/devgao/defi/dai/DaiIT.java: -------------------------------------------------------------------------------- 1 | package devgao.io.dai; 2 | 3 | import devgao.io.contractneedsprovider.*; 4 | import devgao.io.gasprovider.GasProvider; 5 | import devgao.io.numberutil.Wad18; 6 | import devgao.io.util.JavaProperties; 7 | import org.junit.jupiter.api.BeforeEach; 8 | import org.web3j.crypto.Credentials; 9 | import org.web3j.protocol.Web3j; 10 | 11 | class DaiIT { 12 | private static final String TRAVIS_INFURA_PROJECT_ID = "TRAVIS_INFURA_PROJECT_ID"; 13 | private static final String TRAVIS_WALLET = "TRAVIS_WALLET"; 14 | private static final String TRAVIS_PASSWORD = "TRAVIS_PASSWORD"; 15 | 16 | private static final Wad18 minimumGasPrice = new Wad18(1_000000000); 17 | private static final Wad18 maximumGasPrice = new Wad18(200_000000000L); 18 | Dai dai; 19 | JavaProperties javaProperties; 20 | ContractNeedsProvider contractNeedsProvider; 21 | 22 | @BeforeEach 23 | void setUp() { 24 | String infuraProjectId; 25 | String password; 26 | String wallet; 27 | 28 | javaProperties = new JavaProperties(true); 29 | 30 | if ("true".equals(System.getenv().get("TRAVIS"))) { 31 | infuraProjectId = System.getenv().get(TRAVIS_INFURA_PROJECT_ID); 32 | wallet = System.getenv().get(TRAVIS_WALLET); 33 | password = System.getenv().get(TRAVIS_PASSWORD); 34 | } else { 35 | infuraProjectId = javaProperties.getValue("infuraProjectId"); 36 | wallet = javaProperties.getValue("wallet"); 37 | password = javaProperties.getValue("password"); 38 | } 39 | 40 | CircuitBreaker circuitBreaker = new CircuitBreaker(); 41 | Web3j web3j = new Web3jProvider(infuraProjectId).web3j; 42 | Credentials credentials = new Wallet(password, wallet).getCredentials(); 43 | GasProvider gasProvider = new GasProvider(web3j, minimumGasPrice, maximumGasPrice); 44 | Permissions permissions = new Permissions(true, true); 45 | contractNeedsProvider = 46 | new ContractNeedsProvider(web3j, credentials, gasProvider, permissions, circuitBreaker); 47 | dai = 48 | new Dai( 49 | contractNeedsProvider, 50 | Double.parseDouble(javaProperties.getValue("minimumDaiNecessaryForSale"))); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/test/java/de/devgao/defi/flipper/AuctionIT.java: -------------------------------------------------------------------------------- 1 | package devgao.io.flipper; 2 | 3 | import devgao.io.compounddai.CompoundDai; 4 | import devgao.io.contractneedsprovider.*; 5 | import devgao.io.dai.Dai; 6 | import devgao.io.gasprovider.GasProvider; 7 | import devgao.io.numberutil.Wad18; 8 | import devgao.io.uniswap.Uniswap; 9 | import devgao.io.util.Balances; 10 | import devgao.io.util.Ethereum; 11 | import devgao.io.util.JavaProperties; 12 | import devgao.io.weth.Weth; 13 | import org.junit.jupiter.api.BeforeEach; 14 | import org.junit.jupiter.api.Test; 15 | import org.web3j.crypto.Credentials; 16 | import org.web3j.protocol.Web3j; 17 | import org.web3j.tuples.generated.Tuple8; 18 | 19 | import java.math.BigInteger; 20 | 21 | import static devgao.io.numberutil.NumberUtil.getMachineReadable; 22 | import static org.junit.jupiter.api.Assertions.assertFalse; 23 | import static org.junit.jupiter.api.Assertions.assertTrue; 24 | 25 | class AuctionIT { 26 | private static final String TRAVIS_INFURA_PROJECT_ID = "TRAVIS_INFURA_PROJECT_ID"; 27 | private static final String TRAVIS_WALLET = "TRAVIS_WALLET"; 28 | private static final String TRAVIS_PASSWORD = "TRAVIS_PASSWORD"; 29 | 30 | Uniswap uniswap; 31 | Balances balances; 32 | ContractNeedsProvider contractNeedsProvider; 33 | JavaProperties javaProperties; 34 | Weth weth; 35 | Ethereum ethereum; 36 | CompoundDai compoundDai; 37 | Dai dai; 38 | Credentials credentials; 39 | 40 | @BeforeEach 41 | void setUp() { 42 | javaProperties = new JavaProperties(true); 43 | 44 | String infuraProjectId; 45 | String password; 46 | String wallet; 47 | 48 | Permissions permissions = 49 | new Permissions( 50 | Boolean.parseBoolean(javaProperties.getValue("transactionsRequireConfirmation")), 51 | Boolean.parseBoolean(javaProperties.getValue("playSoundOnTransaction"))); 52 | 53 | if ("true".equals(System.getenv().get("TRAVIS"))) { 54 | infuraProjectId = System.getenv().get(TRAVIS_INFURA_PROJECT_ID); 55 | wallet = System.getenv().get(TRAVIS_WALLET); 56 | password = System.getenv().get(TRAVIS_PASSWORD); 57 | } else { 58 | infuraProjectId = javaProperties.getValue("infuraProjectId"); 59 | wallet = javaProperties.getValue("wallet"); 60 | password = javaProperties.getValue("password"); 61 | } 62 | 63 | Web3j web3j = new Web3jProvider(infuraProjectId).web3j; 64 | GasProvider gasProvider = 65 | new GasProvider( 66 | web3j, new Wad18(1_000000000), new Wad18(1000_000000000L)); 67 | credentials = new Wallet(password, wallet).getCredentials(); 68 | CircuitBreaker circuitBreaker = new CircuitBreaker(); 69 | contractNeedsProvider = 70 | new ContractNeedsProvider(web3j, credentials, gasProvider, permissions, circuitBreaker); 71 | 72 | dai = 73 | new Dai( 74 | contractNeedsProvider, 75 | Double.parseDouble(javaProperties.getValue("minimumDaiNecessaryForSaleAndLending"))); 76 | compoundDai = new CompoundDai(contractNeedsProvider); 77 | weth = new Weth(contractNeedsProvider); 78 | ethereum = 79 | new Ethereum( 80 | contractNeedsProvider, 81 | Double.parseDouble(javaProperties.getValue("minimumEthereumReserveUpperLimit")), 82 | Double.parseDouble(javaProperties.getValue("minimumEthereumReserveLowerLimit")), 83 | Double.parseDouble(javaProperties.getValue("minimumEthereumNecessaryForSale"))); 84 | 85 | balances = new Balances(dai, weth, compoundDai, ethereum); 86 | uniswap = new Uniswap(contractNeedsProvider, javaProperties, compoundDai, weth); 87 | } 88 | 89 | @Test 90 | void isAffordable_maxDaiOwnedBiggerThanAuctionPrice_true() { 91 | Tuple8 92 | auctionTuple = 93 | new Tuple8<>( 94 | new BigInteger("200000000000000000000000000000000000000000000000"), 95 | new BigInteger("10000000000000000000"), 96 | "0x04bB161C4e7583CDAaDEe93A8b8E6125FD661E57", 97 | new BigInteger("1588287896"), 98 | new BigInteger("1588266341"), 99 | "0x42A142cc082255CaEE58E3f30dc6d4Fc3056b6A7", 100 | "0xA950524441892A31ebddF91d3cEEFa04Bf454466", 101 | new BigInteger("37299123089429162514476831876850683361693243730")); 102 | Auction auction = new Auction(BigInteger.ONE, auctionTuple); 103 | Wad18 minimumBidIncrease = new Wad18(getMachineReadable(1.0)); 104 | assertTrue(auction.isAffordable(minimumBidIncrease, new Wad18(getMachineReadable(201.0)))); 105 | } 106 | 107 | @Test 108 | void isAffordable_maxDaiOwnedEqualAuctionPrice_false() { 109 | Tuple8 110 | auctionTuple = 111 | new Tuple8<>( 112 | new BigInteger("200000000000000000000000000000000000000000000000"), 113 | new BigInteger("10000000000000000000"), 114 | "0x04bB161C4e7583CDAaDEe93A8b8E6125FD661E57", 115 | new BigInteger("1588287896"), 116 | new BigInteger("1588266341"), 117 | "0x42A142cc082255CaEE58E3f30dc6d4Fc3056b6A7", 118 | "0xA950524441892A31ebddF91d3cEEFa04Bf454466", 119 | new BigInteger("37299123089429162514476831876850683361693243730")); 120 | Auction auction = new Auction(BigInteger.ONE, auctionTuple); 121 | Wad18 minimumBidIncrease = new Wad18(getMachineReadable(1.0)); 122 | assertFalse(auction.isAffordable(minimumBidIncrease, new Wad18(getMachineReadable(200.0)))); 123 | } 124 | 125 | @Test 126 | void isAffordable_maxDaiOwnedSmallerThanAuctionPrice_false() { 127 | Tuple8 128 | auctionTuple = 129 | new Tuple8<>( 130 | new BigInteger("200000000000000000000000000000000000000000000000"), 131 | new BigInteger("10000000000000000000"), 132 | "0x04bB161C4e7583CDAaDEe93A8b8E6125FD661E57", 133 | new BigInteger("1588287896"), 134 | new BigInteger("1588266341"), 135 | "0x42A142cc082255CaEE58E3f30dc6d4Fc3056b6A7", 136 | "0xA950524441892A31ebddF91d3cEEFa04Bf454466", 137 | new BigInteger("37299123089429162514476831876850683361693243730")); 138 | Auction auction = new Auction(BigInteger.ONE, auctionTuple); 139 | Wad18 minimumBidIncrease = new Wad18(getMachineReadable(1.0)); 140 | assertFalse(auction.isAffordable(minimumBidIncrease, new Wad18(getMachineReadable(199.0)))); 141 | } 142 | 143 | @Test 144 | void isAffordable_minimumBidMakesAuctionTooExpensive_false() { 145 | Tuple8 146 | auctionTuple = 147 | new Tuple8<>( 148 | new BigInteger("100000000000000000000000000000000000000000000000"), 149 | new BigInteger("10000000000000000000"), 150 | "0x04bB161C4e7583CDAaDEe93A8b8E6125FD661E57", 151 | new BigInteger("1588287896"), 152 | new BigInteger("1588266341"), 153 | "0x42A142cc082255CaEE58E3f30dc6d4Fc3056b6A7", 154 | "0xA950524441892A31ebddF91d3cEEFa04Bf454466", 155 | new BigInteger("37299123089429162514476831876850683361693243730")); 156 | Auction auction = new Auction(BigInteger.ONE, auctionTuple); 157 | Wad18 minimumBidIncrease = new Wad18(getMachineReadable(1.05)); 158 | assertFalse(auction.isAffordable(minimumBidIncrease, new Wad18(getMachineReadable(105.0)))); 159 | } 160 | 161 | @Test 162 | void isHighestBidderNotMe_meHighestBidder_True() { 163 | Tuple8 164 | auctionTuple = 165 | new Tuple8<>( 166 | new BigInteger("37299123089429162514476831876850683361693243730"), 167 | new BigInteger("175927491330994700"), 168 | credentials.getAddress(), 169 | new BigInteger("1588287896"), 170 | new BigInteger("1588266341"), 171 | "0x42A142cc082255CaEE58E3f30dc6d4Fc3056b6A7", 172 | "0xA950524441892A31ebddF91d3cEEFa04Bf454466", 173 | new BigInteger("37299123089429162514476831876850683361693243730")); 174 | Auction auction = new Auction(BigInteger.ONE, auctionTuple); 175 | assertTrue(auction.amIHighestBidder(credentials)); 176 | } 177 | 178 | @Test 179 | void isHighestBidderNotMe_someoneElseHighestBidder_False() { 180 | Tuple8 181 | auctionTuple = 182 | new Tuple8<>( 183 | new BigInteger("37299123089429162514476831876850683361693243730"), 184 | new BigInteger("175927491330994700"), 185 | "0x04bB161C4e7583CDAaDEe93A8b8E6125FD661E57", 186 | new BigInteger("1588287896"), 187 | new BigInteger("1588266341"), 188 | "0x42A142cc082255CaEE58E3f30dc6d4Fc3056b6A7", 189 | "0xA950524441892A31ebddF91d3cEEFa04Bf454466", 190 | new BigInteger("37299123089429162514476831876850683361693243730")); 191 | Auction auction = new Auction(BigInteger.ONE, auctionTuple); 192 | assertFalse(auction.amIHighestBidder(credentials)); 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /src/test/java/de/devgao/defi/flipper/FlipperIT.java: -------------------------------------------------------------------------------- 1 | package devgao.io.flipper; 2 | 3 | import devgao.io.compounddai.CompoundDai; 4 | import devgao.io.contractneedsprovider.*; 5 | import devgao.io.dai.Dai; 6 | import devgao.io.gasprovider.GasProvider; 7 | import devgao.io.numberutil.Rad45; 8 | import devgao.io.numberutil.Wad18; 9 | import devgao.io.util.Balances; 10 | import devgao.io.util.Ethereum; 11 | import devgao.io.util.JavaProperties; 12 | import devgao.io.weth.Weth; 13 | import org.junit.jupiter.api.BeforeEach; 14 | import org.junit.jupiter.api.Test; 15 | import org.web3j.crypto.Credentials; 16 | import org.web3j.protocol.Web3j; 17 | 18 | import java.math.BigInteger; 19 | import java.util.ArrayList; 20 | 21 | import static devgao.io.numberutil.NumberUtil.getMachineReadable; 22 | import static org.junit.jupiter.api.Assertions.*; 23 | 24 | class FlipperIT { 25 | private static final String TRAVIS_INFURA_PROJECT_ID = "TRAVIS_INFURA_PROJECT_ID"; 26 | private static final String TRAVIS_WALLET = "TRAVIS_WALLET"; 27 | private static final String TRAVIS_PASSWORD = "TRAVIS_PASSWORD"; 28 | 29 | private static final Wad18 minimumGasPrice = new Wad18(1_000000000); 30 | private static final Wad18 maximumGasPrice = new Wad18(200_000000000L); 31 | Flipper flipper; 32 | Balances balances; 33 | 34 | @BeforeEach 35 | void setUp() { 36 | String infuraProjectId; 37 | String password; 38 | String wallet; 39 | 40 | JavaProperties javaProperties = new JavaProperties(true); 41 | 42 | if ("true".equals(System.getenv().get("TRAVIS"))) { 43 | infuraProjectId = System.getenv().get(TRAVIS_INFURA_PROJECT_ID); 44 | wallet = System.getenv().get(TRAVIS_WALLET); 45 | password = System.getenv().get(TRAVIS_PASSWORD); 46 | } else { 47 | infuraProjectId = javaProperties.getValue("infuraProjectId"); 48 | wallet = javaProperties.getValue("wallet"); 49 | password = javaProperties.getValue("password"); 50 | } 51 | 52 | CircuitBreaker circuitBreaker = new CircuitBreaker(); 53 | Web3j web3j = new Web3jProvider(infuraProjectId).web3j; 54 | Credentials credentials = new Wallet(password, wallet).getCredentials(); 55 | GasProvider gasProvider = new GasProvider(web3j, minimumGasPrice, maximumGasPrice); 56 | Permissions permissions = new Permissions(true, true); 57 | ContractNeedsProvider contractNeedsProvider = 58 | new ContractNeedsProvider(web3j, credentials, gasProvider, permissions, circuitBreaker); 59 | flipper = 60 | new Flipper( 61 | contractNeedsProvider, 62 | Double.parseDouble(javaProperties.getValue("minimumFlipAuctionProfit"))); 63 | 64 | Dai dai = 65 | new Dai( 66 | contractNeedsProvider, 67 | Double.parseDouble(javaProperties.getValue("minimumDaiNecessaryForSaleAndLending"))); 68 | Weth weth = new Weth(contractNeedsProvider); 69 | CompoundDai compoundDai = new CompoundDai(contractNeedsProvider); 70 | Ethereum ethereum = 71 | new Ethereum( 72 | contractNeedsProvider, 73 | Double.parseDouble(javaProperties.getValue("minimumEthereumReserveUpperLimit")), 74 | Double.parseDouble(javaProperties.getValue("minimumEthereumReserveLowerLimit")), 75 | Double.parseDouble(javaProperties.getValue("minimumEthereumNecessaryForSale"))); 76 | 77 | balances = new Balances(dai, weth, compoundDai, ethereum); 78 | } 79 | 80 | @Test 81 | void getTotalAuctionCount_noParameters_biggerThan4888() { 82 | BigInteger actualValue = flipper.getTotalAuctionCount(); 83 | assertTrue(actualValue.compareTo(new BigInteger("4888")) >= 0); 84 | } 85 | 86 | @Test 87 | void getAuction_4885_attributesAreCorrect() { 88 | Auction auction = flipper.getAuction(new BigInteger("4885")); 89 | assertEquals( 90 | 0, 91 | auction.bidAmountInDai.compareTo( 92 | new Rad45("37299123089429162514476831876850683361693243730"))); 93 | assertEquals(0, auction.collateralForSale.compareTo(new Wad18("175927491330994700"))); 94 | assertTrue( 95 | auction.highestBidder.equalsIgnoreCase("0x04bB161C4e7583CDAaDEe93A8b8E6125FD661E57")); 96 | assertEquals(0, auction.bidExpiry.compareTo(new Wad18("1588287896"))); 97 | assertEquals(0, auction.maxAuctionDuration.compareTo(new Wad18("1588266341"))); 98 | assertTrue( 99 | auction.addressOfAuctionedVault.equalsIgnoreCase( 100 | "0x42A142cc082255CaEE58E3f30dc6d4Fc3056b6A7")); 101 | assertTrue( 102 | auction.recipientOfAuctionIncome.equalsIgnoreCase( 103 | "0xA950524441892A31ebddF91d3cEEFa04Bf454466")); 104 | assertEquals( 105 | 0, 106 | auction.totalDaiWanted.compareTo( 107 | new Rad45("37299123089429162514476831876850683361693243730"))); 108 | } 109 | 110 | @Test 111 | void getActiveAuctionList_noParameter_noException() { 112 | BigInteger actualValue = flipper.getTotalAuctionCount(); 113 | ArrayList auctionList = flipper.getActiveAffordableAuctionList(actualValue, balances); 114 | Wad18 minimumBidIncrease = flipper.getMinimumBidIncrease(); 115 | for (Auction auction : auctionList) { 116 | assertTrue(auction.isActive()); 117 | assertTrue(auction.isAffordable(minimumBidIncrease, balances.getMaxDaiToSell())); 118 | } 119 | } 120 | 121 | @Test 122 | void getMinimumBidIncrease_noParameter_noException() { 123 | Wad18 actualValue = flipper.getMinimumBidIncrease(); 124 | assertEquals(new Wad18(getMachineReadable(1.03)), actualValue); 125 | assertDoesNotThrow(() -> flipper.getMinimumBidIncrease()); 126 | } 127 | 128 | @Test 129 | void getBidLength_noParameter_noException() { 130 | BigInteger actualValue = flipper.getBidDuration(); 131 | assertEquals(BigInteger.valueOf(21600L), actualValue); 132 | assertDoesNotThrow(() -> flipper.getBidDuration()); 133 | } 134 | 135 | @Test 136 | void getAuctionLength_noParameter_noException() { 137 | BigInteger actualValue = flipper.getAuctionLength(); 138 | assertEquals(BigInteger.valueOf(21600L), actualValue); 139 | assertDoesNotThrow(() -> flipper.getAuctionLength()); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/test/java/de/devgao/defi/gasprovider/ETHGasStationIT.java: -------------------------------------------------------------------------------- 1 | package devgao.io.gasprovider; 2 | 3 | import devgao.io.numberutil.Wad18; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import static org.junit.jupiter.api.Assertions.assertTrue; 7 | 8 | class ETHGasStationIT { 9 | private static final String TOO_HIGH = "Error, value is too high"; 10 | private static final String TOO_LOW = "Error, value is too low"; 11 | private static final Wad18 MINIMUM_GAS_PRICE = new Wad18(1_000000000); 12 | private static final Wad18 MAXIMUM_GAS_PRICE = new Wad18(100_000000000L); 13 | 14 | @Test 15 | void getFastestGasPrice_currentGasPrice_withinReasonableBoundaries() throws GasPriceException { 16 | Wad18 result = ETHGasStation.getFastestGasPrice(); 17 | assertTrue(MINIMUM_GAS_PRICE.compareTo(result) < 0, TOO_LOW); 18 | assertTrue(MAXIMUM_GAS_PRICE.compareTo(result) > 0, TOO_HIGH); 19 | } 20 | 21 | @Test 22 | void getSafeLowGasPrice_currentGasPrice_withinReasonableBoundaries() throws GasPriceException { 23 | Wad18 result = ETHGasStation.getSafeLowGasPrice(); 24 | assertTrue(MINIMUM_GAS_PRICE.compareTo(result) < 0, TOO_LOW); 25 | assertTrue(MAXIMUM_GAS_PRICE.compareTo(result) > 0, TOO_HIGH); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/test/java/de/devgao/defi/gasprovider/EtherchainIT.java: -------------------------------------------------------------------------------- 1 | package devgao.io.gasprovider; 2 | 3 | import devgao.io.numberutil.Wad18; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import java.math.BigInteger; 7 | 8 | import static org.hamcrest.MatcherAssert.assertThat; 9 | import static org.hamcrest.Matchers.is; 10 | import static org.hamcrest.Matchers.lessThan; 11 | import static org.junit.jupiter.api.Assertions.assertTrue; 12 | 13 | class EtherchainIT { 14 | private static final String TOO_HIGH = "Error, value is too high"; 15 | private static final String TOO_LOW = "Error, value is too low"; 16 | private static final Wad18 MINIMUM_GAS_PRICE = new Wad18(1_000000000); 17 | private static final Wad18 MAXIMUM_GAS_PRICE = new Wad18(100_000000000L); 18 | 19 | @Test 20 | void getFastestGasPrice_currentGasPrice_GasPriceWithinBoundaries() throws GasPriceException { 21 | Wad18 result = Etherchain.getFastestGasPrice(); 22 | assertTrue(MINIMUM_GAS_PRICE.compareTo(result) < 0, TOO_LOW); 23 | assertTrue(MAXIMUM_GAS_PRICE.compareTo(result) > 0, TOO_HIGH); 24 | } 25 | 26 | @Test // todo: this is a bad test 27 | void getFastestGasPrice_currentGasPriceDifference_GasPriceDifferenceIsReasonable() 28 | throws GasPriceException { 29 | Wad18 etherchainResult = Etherchain.getFastestGasPrice(); 30 | Wad18 ethGasStationResult = ETHGasStation.getFastestGasPrice(); 31 | Wad18 difference = ethGasStationResult.subtract(etherchainResult); 32 | assertThat(difference.toBigInteger(), is(lessThan(BigInteger.valueOf(20_000000000L)))); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/test/java/de/devgao/defi/gasprovider/GasProviderIT.java: -------------------------------------------------------------------------------- 1 | package devgao.io.gasprovider; 2 | 3 | import devgao.io.contractneedsprovider.Web3jProvider; 4 | import devgao.io.numberutil.Wad18; 5 | import devgao.io.oasis.OasisContract; 6 | import devgao.io.util.JavaProperties; 7 | import org.junit.jupiter.api.BeforeEach; 8 | import org.junit.jupiter.api.Test; 9 | import org.web3j.protocol.Web3j; 10 | 11 | import java.math.BigInteger; 12 | 13 | import static org.hamcrest.MatcherAssert.assertThat; 14 | import static org.hamcrest.Matchers.*; 15 | import static org.junit.jupiter.api.Assertions.*; 16 | 17 | class GasProviderIT { 18 | private static final String TRAVIS_INFURA_PROJECT_ID = "TRAVIS_INFURA_PROJECT_ID"; 19 | 20 | private static final String TOO_HIGH = "Error, value is too high"; 21 | private static final String TOO_LOW = "Error, value is too low"; 22 | private static final String EXCEPTION = "calculateGasPriceAsAPercentageOfProfit Exception"; 23 | private static final Wad18 MINIMUM_GAS_PRICE = new Wad18(1_000000000); 24 | private static final Wad18 MAXIMUM_GAS_PRICE = new Wad18(100_000000000L); 25 | private static Web3j web3j; 26 | 27 | @BeforeEach 28 | void setUp() { 29 | JavaProperties javaProperties = new JavaProperties(true); 30 | 31 | String infuraProjectId; 32 | 33 | if ("true".equals(System.getenv().get("TRAVIS"))) { 34 | infuraProjectId = System.getenv().get(TRAVIS_INFURA_PROJECT_ID); 35 | } else { 36 | infuraProjectId = javaProperties.getValue("infuraProjectId"); 37 | } 38 | 39 | web3j = new Web3jProvider(infuraProjectId).web3j; 40 | } 41 | 42 | @Test 43 | void updateSlowGasPrice_currentGasPrice_GasPriceWithinBoundaries() { 44 | GasProvider gasProvider = new GasProvider(web3j, MINIMUM_GAS_PRICE, MAXIMUM_GAS_PRICE); 45 | gasProvider.updateSlowGasPrice(); 46 | assertTrue(MINIMUM_GAS_PRICE.compareTo(gasProvider.gasPrice) <= 0, TOO_LOW); 47 | assertTrue(MAXIMUM_GAS_PRICE.compareTo(gasProvider.gasPrice) >= 0, TOO_HIGH); 48 | } 49 | 50 | @Test 51 | void updateFastGasPrice_ZeroMedianZeroProfit_GasPriceWithinBoundaries() { 52 | GasProvider gasProvider = new GasProvider(web3j, MINIMUM_GAS_PRICE, MAXIMUM_GAS_PRICE); 53 | gasProvider.updateFastGasPrice(Wad18.ZERO, Wad18.ZERO); 54 | assertTrue(MINIMUM_GAS_PRICE.compareTo(gasProvider.gasPrice) <= 0, TOO_LOW); 55 | assertTrue(MAXIMUM_GAS_PRICE.compareTo(gasProvider.gasPrice) >= 0, TOO_HIGH); 56 | } 57 | 58 | @Test 59 | void updateFastGasPrice_RealMedianAndProfit_GasPriceWithinBoundaries() { 60 | GasProvider gasProvider = new GasProvider(web3j, MINIMUM_GAS_PRICE, MAXIMUM_GAS_PRICE); 61 | gasProvider.updateFastGasPrice( 62 | new Wad18("200000000000000000000"), new Wad18("10000000000000000000")); 63 | assertTrue(MINIMUM_GAS_PRICE.compareTo(gasProvider.gasPrice) <= 0, TOO_LOW); 64 | assertTrue(MAXIMUM_GAS_PRICE.compareTo(gasProvider.gasPrice) >= 0, TOO_HIGH); 65 | } 66 | 67 | @Test 68 | void getGasLimit_someFunction_returnInRealisticBounds() { 69 | GasProvider gasProvider = new GasProvider(web3j, MINIMUM_GAS_PRICE, MAXIMUM_GAS_PRICE); 70 | assertEquals(BigInteger.valueOf(300_000), gasProvider.getGasLimit(OasisContract.FUNC_BUY)); 71 | } 72 | 73 | @Test 74 | void getGasPrice_someFunction_returnInRealisticBounds() { 75 | GasProvider gasProvider = new GasProvider(web3j, MINIMUM_GAS_PRICE, MAXIMUM_GAS_PRICE); 76 | BigInteger actual = gasProvider.getGasPrice(OasisContract.FUNC_BUY); 77 | assertTrue(MINIMUM_GAS_PRICE.toBigInteger().compareTo(actual) <= 0, TOO_LOW); 78 | assertTrue(MAXIMUM_GAS_PRICE.toBigInteger().compareTo(actual) >= 0, TOO_HIGH); 79 | } 80 | 81 | @Test 82 | void getPercentageOfProfitAsFee_zeroTransactions_returnCalculation() { 83 | assertThat(2, allOf(greaterThanOrEqualTo(1), lessThanOrEqualTo(2))); 84 | 85 | GasProvider gasProvider = new GasProvider(web3j, MINIMUM_GAS_PRICE, MAXIMUM_GAS_PRICE); 86 | assertEquals(0.1, gasProvider.getPercentageOfProfitAsFee(0)); 87 | } 88 | 89 | @Test 90 | void getPercentageOfProfitAsFee_tenTransactions_returnCalculation() { 91 | GasProvider gasProvider = new GasProvider(web3j, MINIMUM_GAS_PRICE, MAXIMUM_GAS_PRICE); 92 | assertEquals(0.35, gasProvider.getPercentageOfProfitAsFee(10)); 93 | } 94 | 95 | @Test 96 | void calculateGasPriceAsAPercentageOfProfit_zeroMedian_throwException() { 97 | GasProvider gasProvider = new GasProvider(web3j, MINIMUM_GAS_PRICE, MAXIMUM_GAS_PRICE); 98 | Exception exception = 99 | assertThrows( 100 | GasPriceException.class, 101 | () -> 102 | gasProvider.calculateGasPriceAsAPercentageOfProfit( 103 | Wad18.ZERO, Wad18.ONE, 20.0, 0.1)); 104 | String actualMessage = exception.getMessage(); 105 | assertTrue(actualMessage.contains(EXCEPTION)); 106 | } 107 | 108 | @Test 109 | void calculateGasPriceAsAPercentageOfProfit_zeroProfit_throwException() { 110 | GasProvider gasProvider = new GasProvider(web3j, MINIMUM_GAS_PRICE, MAXIMUM_GAS_PRICE); 111 | Exception exception = 112 | assertThrows( 113 | GasPriceException.class, 114 | () -> 115 | gasProvider.calculateGasPriceAsAPercentageOfProfit( 116 | Wad18.ONE, Wad18.ZERO, 20.0, 0.1)); 117 | String actualMessage = exception.getMessage(); 118 | assertTrue(actualMessage.contains(EXCEPTION)); 119 | } 120 | 121 | @Test 122 | void calculateGasPriceAsAPercentageOfProfit_zeroGasLimit_throwException() { 123 | GasProvider gasProvider = new GasProvider(web3j, MINIMUM_GAS_PRICE, MAXIMUM_GAS_PRICE); 124 | Exception exception = 125 | assertThrows( 126 | GasPriceException.class, 127 | () -> 128 | gasProvider.calculateGasPriceAsAPercentageOfProfit(Wad18.ONE, Wad18.ONE, 0.0, 0.1)); 129 | String actualMessage = exception.getMessage(); 130 | assertTrue(actualMessage.contains(EXCEPTION)); 131 | } 132 | 133 | @Test 134 | void calculateGasPriceAsAPercentageOfProfit_someNumbers_returnCalculation() 135 | throws GasPriceException { 136 | GasProvider gasProvider = new GasProvider(web3j, MINIMUM_GAS_PRICE, MAXIMUM_GAS_PRICE); 137 | Wad18 gasPrice = 138 | gasProvider.calculateGasPriceAsAPercentageOfProfit( 139 | new Wad18("200000000000000000000"), new Wad18("10000000000000000000"), 300000.0, 0.1); 140 | assertEquals("0.000000016666666666", gasPrice.toString()); 141 | } 142 | 143 | @Test 144 | void calculateGasPriceAsAPercentageOfProfit_someRealNumbers_returnCalculation() 145 | throws GasPriceException { 146 | GasProvider gasProvider = new GasProvider(web3j, MINIMUM_GAS_PRICE, MAXIMUM_GAS_PRICE); 147 | Wad18 gasPrice = 148 | gasProvider.calculateGasPriceAsAPercentageOfProfit( 149 | new Wad18("124730000000000000000"), new Wad18("1772900000000000000"), 5300000, 0.35); 150 | assertEquals("0.000000000938653907", gasPrice.toString()); 151 | } 152 | 153 | // TODO: have another look at this test 154 | @Test 155 | void calculateGasPriceAsAPercentageOfProfit_someRealNumbers2_returnCalculation() 156 | throws GasPriceException { 157 | GasProvider gasProvider = new GasProvider(web3j, MINIMUM_GAS_PRICE, MAXIMUM_GAS_PRICE); 158 | Wad18 gasPrice = 159 | gasProvider.calculateGasPriceAsAPercentageOfProfit( 160 | new Wad18("190775000000000000000"), new Wad18("39335200000000000000"), 300000, 0.15); 161 | assertEquals("0.000000103093172585", gasPrice.toString()); 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/test/java/de/devgao/defi/maker/MakerIT.java: -------------------------------------------------------------------------------- 1 | package devgao.io.maker; 2 | 3 | import devgao.io.contractneedsprovider.*; 4 | import devgao.io.gasprovider.GasProvider; 5 | import devgao.io.numberutil.Wad18; 6 | import devgao.io.util.JavaProperties; 7 | import org.junit.jupiter.api.BeforeEach; 8 | import org.web3j.crypto.Credentials; 9 | import org.web3j.protocol.Web3j; 10 | 11 | class MakerIT { 12 | private static final String TRAVIS_INFURA_PROJECT_ID = "TRAVIS_INFURA_PROJECT_ID"; 13 | private static final String TRAVIS_WALLET = "TRAVIS_WALLET"; 14 | private static final String TRAVIS_PASSWORD = "TRAVIS_PASSWORD"; 15 | 16 | private static final Wad18 minimumGasPrice = new Wad18(1_000000000); 17 | private static final Wad18 maximumGasPrice = new Wad18(200_000000000L); 18 | 19 | Maker maker; 20 | 21 | @BeforeEach 22 | void setUp() { 23 | String infuraProjectId; 24 | String password; 25 | String wallet; 26 | 27 | JavaProperties javaProperties = new JavaProperties(true); 28 | 29 | if ("true".equals(System.getenv().get("TRAVIS"))) { 30 | infuraProjectId = System.getenv().get(TRAVIS_INFURA_PROJECT_ID); 31 | wallet = System.getenv().get(TRAVIS_WALLET); 32 | password = System.getenv().get(TRAVIS_PASSWORD); 33 | } else { 34 | infuraProjectId = javaProperties.getValue("infuraProjectId"); 35 | wallet = javaProperties.getValue("wallet"); 36 | password = javaProperties.getValue("password"); 37 | } 38 | 39 | CircuitBreaker circuitBreaker = new CircuitBreaker(); 40 | Web3j web3j = new Web3jProvider(infuraProjectId).web3j; 41 | Credentials credentials = new Wallet(password, wallet).getCredentials(); 42 | GasProvider gasProvider = new GasProvider(web3j, minimumGasPrice, maximumGasPrice); 43 | Permissions permissions = new Permissions(true, true); 44 | ContractNeedsProvider contractNeedsProvider = 45 | new ContractNeedsProvider(web3j, credentials, gasProvider, permissions, circuitBreaker); 46 | maker = new Maker(contractNeedsProvider); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/test/java/de/devgao/defi/medianizer/MedianizerIT.java: -------------------------------------------------------------------------------- 1 | package devgao.io.medianizer; 2 | 3 | import devgao.io.contractneedsprovider.*; 4 | import devgao.io.gasprovider.GasProvider; 5 | import devgao.io.numberutil.Wad18; 6 | import devgao.io.util.JavaProperties; 7 | import org.junit.jupiter.api.BeforeEach; 8 | import org.junit.jupiter.api.Test; 9 | import org.web3j.crypto.Credentials; 10 | import org.web3j.protocol.Web3j; 11 | 12 | import static org.junit.jupiter.api.Assertions.assertEquals; 13 | import static org.junit.jupiter.api.Assertions.assertTrue; 14 | 15 | class MedianizerIT { 16 | private static final String TRAVIS_INFURA_PROJECT_ID = "TRAVIS_INFURA_PROJECT_ID"; 17 | private static final String TRAVIS_WALLET = "TRAVIS_WALLET"; 18 | private static final String TRAVIS_PASSWORD = "TRAVIS_PASSWORD"; 19 | 20 | private static final String TOO_HIGH = "Error, value is too high"; 21 | private static final String TOO_LOW = "Error, value is too low"; 22 | private static final Wad18 MINIMUM_ETH_PRICE = new Wad18("100000000000000000000"); 23 | private static final Wad18 MAXIMUM_ETH_PRICE = new Wad18("500000000000000000000"); 24 | static ContractNeedsProvider contractNeedsProvider; 25 | 26 | @BeforeEach 27 | void setUp() { 28 | String infuraProjectId; 29 | String password; 30 | String wallet; 31 | 32 | JavaProperties javaProperties = new JavaProperties(true); 33 | 34 | if ("true".equals(System.getenv().get("TRAVIS"))) { 35 | infuraProjectId = System.getenv().get(TRAVIS_INFURA_PROJECT_ID); 36 | wallet = System.getenv().get(TRAVIS_WALLET); 37 | password = System.getenv().get(TRAVIS_PASSWORD); 38 | } else { 39 | infuraProjectId = javaProperties.getValue("infuraProjectId"); 40 | wallet = javaProperties.getValue("wallet"); 41 | password = javaProperties.getValue("password"); 42 | } 43 | 44 | Web3j web3j = new Web3jProvider(infuraProjectId).web3j; 45 | Credentials credentials = new Wallet(password, wallet).getCredentials(); 46 | GasProvider gasProvider = 47 | new GasProvider(web3j, new Wad18(1_000000000), new Wad18(200_000000000L)); 48 | Permissions permissions = 49 | new Permissions( 50 | Boolean.parseBoolean(javaProperties.getValue("transactionRequiresConfirmation")), 51 | Boolean.parseBoolean(javaProperties.getValue("playSoundOnTransaction"))); 52 | CircuitBreaker circuitBreaker = new CircuitBreaker(); 53 | contractNeedsProvider = 54 | new ContractNeedsProvider(web3j, credentials, gasProvider, permissions, circuitBreaker); 55 | } 56 | 57 | @Test 58 | void getCoinbaseProEthPrice_simpleGet_returnPrice() { 59 | Medianizer.setMedianizerContract(contractNeedsProvider); 60 | Wad18 coinbaseEthPrice = Medianizer.getCoinbaseProEthPrice(); 61 | assertTrue(MINIMUM_ETH_PRICE.compareTo(coinbaseEthPrice) <= 0, TOO_LOW); 62 | assertTrue(MAXIMUM_ETH_PRICE.compareTo(coinbaseEthPrice) >= 0, TOO_HIGH); 63 | } 64 | 65 | @Test 66 | void getPrice_oneExecution_priceIsWithinReasonableBounds() throws MedianException { 67 | Medianizer.setMedianizerContract(contractNeedsProvider); 68 | Wad18 median = Medianizer.getPrice(); 69 | assertTrue(MINIMUM_ETH_PRICE.compareTo(median) <= 0, TOO_LOW); 70 | assertTrue(MAXIMUM_ETH_PRICE.compareTo(median) >= 0, TOO_HIGH); 71 | } 72 | 73 | @Test 74 | void getPrice_twoExecutionsWithinPriceUpdateInterval_priceIsEqual() 75 | throws MedianException { 76 | Medianizer.setMedianizerContract(contractNeedsProvider); 77 | Wad18 firstMedian = Medianizer.getPrice(); 78 | Wad18 secondMedian = Medianizer.getPrice(); 79 | 80 | assertEquals(firstMedian, secondMedian); 81 | 82 | assertTrue(MINIMUM_ETH_PRICE.compareTo(firstMedian) <= 0, TOO_LOW); 83 | assertTrue(MAXIMUM_ETH_PRICE.compareTo(firstMedian) >= 0, TOO_HIGH); 84 | assertTrue(MINIMUM_ETH_PRICE.compareTo(secondMedian) <= 0, TOO_LOW); 85 | assertTrue(MAXIMUM_ETH_PRICE.compareTo(secondMedian) >= 0, TOO_HIGH); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/test/java/de/devgao/defi/medianizer/MedianizerTest.java: -------------------------------------------------------------------------------- 1 | package devgao.io.medianizer; 2 | 3 | import devgao.io.numberutil.Wad18; 4 | import org.hamcrest.Matchers; 5 | import org.junit.jupiter.api.Assertions; 6 | import org.junit.jupiter.api.Test; 7 | import org.slf4j.LoggerFactory; 8 | 9 | import java.lang.invoke.MethodHandles; 10 | import java.math.BigInteger; 11 | 12 | import static org.hamcrest.MatcherAssert.assertThat; 13 | 14 | class MedianizerTest { 15 | private static final org.slf4j.Logger logger = 16 | LoggerFactory.getLogger(MethodHandles.lookup().lookupClass().getSimpleName()); 17 | 18 | @Test 19 | void getMedian_arrayWithRandomOrderAndZerosAndEvenLength_returnCalculation() 20 | throws MedianException { 21 | BigInteger expected = BigInteger.valueOf((11)); 22 | BigInteger actual = 23 | Medianizer.getMedian( 24 | new Wad18[]{ 25 | new Wad18(80), 26 | new Wad18(1), 27 | new Wad18(0), 28 | new Wad18(10), 29 | new Wad18(2), 30 | new Wad18(12), 31 | new Wad18(100) 32 | }).toBigInteger(); 33 | assertThat(actual, Matchers.comparesEqualTo(expected)); 34 | } 35 | 36 | @Test 37 | void getMedian_arrayWithUnevenLength_returnCalculation() throws MedianException { 38 | BigInteger expected = BigInteger.TEN; 39 | BigInteger actual = 40 | Medianizer.getMedian(new Wad18[]{new Wad18(10), new Wad18(1), new Wad18(10)}).toBigInteger(); 41 | assertThat(actual, Matchers.comparesEqualTo(expected)); 42 | } 43 | 44 | @Test 45 | void getMedian_arrayWithEvenLength_returnCalculation() throws MedianException { 46 | BigInteger expected = BigInteger.valueOf(5); 47 | BigInteger actual = 48 | Medianizer.getMedian( 49 | new Wad18[]{new Wad18(10), new Wad18(1), new Wad18(1), new Wad18(10)}).toBigInteger(); 50 | assertThat(actual, Matchers.comparesEqualTo(expected)); 51 | } 52 | 53 | @Test 54 | void getMedian_arrayWithAFewZeros_returnCalculation() throws MedianException { 55 | BigInteger expected = BigInteger.TEN; 56 | BigInteger actual = 57 | Medianizer.getMedian( 58 | new Wad18[]{new Wad18(10), new Wad18(0), new Wad18(0), new Wad18(10)}).toBigInteger(); 59 | assertThat(actual, Matchers.comparesEqualTo(expected)); 60 | } 61 | 62 | @Test 63 | void getMedian_arrayWithOnlyZeros_throwMedianException() { 64 | Wad18[] array = {new Wad18(0), new Wad18(0), new Wad18(0), new Wad18(0)}; 65 | Assertions.assertThrows(MedianException.class, () -> Medianizer.getMedian(array)); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/test/java/de/devgao/defi/numberutil/INumberWrapperTest.java: -------------------------------------------------------------------------------- 1 | package devgao.io.numberutil; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.math.BigInteger; 6 | 7 | import static devgao.io.numberutil.NumberUtil.getMachineReadable; 8 | import static org.junit.jupiter.api.Assertions.assertEquals; 9 | 10 | public class INumberWrapperTest { 11 | 12 | @Test 13 | public void divide() { 14 | Wad18 wad18 = new Wad18(BigInteger.ONE); 15 | Rad45 rad45 = new Rad45(BigInteger.ONE); 16 | Wad18 actual = wad18.divide(rad45); 17 | Wad18 expected = new Wad18(getMachineReadable(1.0)); 18 | assertEquals(0, expected.compareTo(actual)); 19 | } 20 | 21 | @Test 22 | public void multiply() { 23 | } 24 | 25 | @Test 26 | public void toBigInteger() { 27 | } 28 | 29 | @Test 30 | public void toBigDecimal() { 31 | } 32 | 33 | @Test 34 | public void testToString() { 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/test/java/de/devgao/defi/numberutil/NumberUtilTest.java: -------------------------------------------------------------------------------- 1 | package devgao.io.numberutil; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.math.BigDecimal; 6 | import java.math.BigInteger; 7 | 8 | import static devgao.io.numberutil.NumberUtil.UINT_MAX; 9 | import static org.junit.jupiter.api.Assertions.assertEquals; 10 | 11 | class NumberUtilTest { 12 | @Test 13 | void UINTMAX_getUINTMAX_equals() { 14 | BigInteger expected = new BigInteger("2").pow(256).subtract(BigInteger.ONE); 15 | assertEquals(0, expected.compareTo(UINT_MAX)); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/test/java/de/devgao/defi/numberutil/NumberWrapperTest.java: -------------------------------------------------------------------------------- 1 | package devgao.io.numberutil; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.math.BigInteger; 6 | 7 | import static devgao.io.numberutil.NumberUtil.getMachineReadable; 8 | import static org.junit.jupiter.api.Assertions.assertEquals; 9 | 10 | class NumberWrapperTest { 11 | 12 | @Test 13 | void divide() { 14 | Wad18 wad18 = new Wad18(BigInteger.ONE); 15 | Rad45 rad45 = new Rad45(BigInteger.ONE); 16 | Wad18 actual = wad18.divide(rad45); 17 | Wad18 expected = new Wad18(getMachineReadable(1.0)); 18 | assertEquals(0, expected.compareTo(actual)); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/test/java/de/devgao/defi/numberutil/Wad18Test.java: -------------------------------------------------------------------------------- 1 | package devgao.io.numberutil; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.math.BigDecimal; 6 | import java.math.BigInteger; 7 | 8 | import static org.junit.jupiter.api.Assertions.*; 9 | 10 | class Wad18Test { 11 | 12 | @Test 13 | void divide_TwoDividedByOne_Two() { 14 | Wad18 two = new Wad18(new BigInteger("2000000000000000000")); 15 | Wad18 one = new Wad18(new BigInteger("1000000000000000000")); 16 | BigDecimal expected = new BigDecimal("2000000000000000000"); 17 | BigDecimal actual = two.divide(one).toBigDecimal(); 18 | assertEquals(0, expected.compareTo(actual)); 19 | } 20 | 21 | @Test 22 | void divide_TwoWeiDividedByOneWei_Two() { 23 | Wad18 two = new Wad18(new BigInteger("2")); 24 | Wad18 one = new Wad18(new BigInteger("1")); 25 | BigDecimal expected = new BigDecimal("2000000000000000000"); 26 | BigDecimal actual = two.divide(one).toBigDecimal(); 27 | assertEquals(0, expected.compareTo(actual)); 28 | } 29 | 30 | @Test 31 | void divide_TwoDividedByTwoWei_One() { 32 | Wad18 two = new Wad18(new BigInteger("2000000000000000000")); 33 | Wad18 twoWei = new Wad18(new BigInteger("2")); 34 | BigDecimal expected = new BigDecimal("1000000000000000000000000000000000000"); 35 | BigDecimal actual = two.divide(twoWei).toBigDecimal(); 36 | assertEquals(0, expected.compareTo(actual)); 37 | } 38 | 39 | @Test 40 | void divide_MedianDividedByMedian_AboutOne() { 41 | Wad18 biggerMedian = new Wad18(new BigInteger("204120000000000000000")); 42 | Wad18 smallerMedian = new Wad18(new BigInteger("201120000000000000000")); 43 | BigDecimal expected = new BigDecimal("1014916467780429594"); 44 | BigDecimal actual = biggerMedian.divide(smallerMedian).toBigDecimal(); 45 | assertEquals(0, expected.compareTo(actual)); 46 | } 47 | 48 | @Test 49 | void divide_DivideByZero_IllegalArgumentException() { 50 | Wad18 wad18 = new Wad18(BigDecimal.ONE); 51 | Exception exception = 52 | assertThrows( 53 | IllegalArgumentException.class, 54 | () -> { 55 | wad18.divide(Wad18.ZERO); 56 | }); 57 | String expectedMessage = "Argument 'divisor' is 0"; 58 | String actualMessage = exception.getMessage(); 59 | assertTrue(actualMessage.contains(expectedMessage)); 60 | } 61 | 62 | @Test 63 | void multiply_TwoMultipliedByOne_Two() { 64 | Wad18 two = new Wad18(new BigInteger("2000000000000000000")); 65 | Wad18 one = new Wad18(new BigInteger("1000000000000000000")); 66 | BigDecimal expected = new BigDecimal("2000000000000000000"); 67 | BigDecimal actual = two.multiply(one).toBigDecimal(); 68 | assertEquals(0, expected.compareTo(actual)); 69 | } 70 | 71 | @Test 72 | void multiply_TwoWeiMultipliedByOneWei_Zero() { 73 | Wad18 two = new Wad18(new BigInteger("2")); 74 | Wad18 one = new Wad18(new BigInteger("1")); 75 | BigDecimal expected = new BigDecimal("0"); 76 | BigDecimal actual = two.multiply(one).toBigDecimal(); 77 | assertEquals(0, expected.compareTo(actual)); 78 | } 79 | 80 | @Test 81 | void multiply_TwoMultipliedByTwoWei_FourWei() { 82 | Wad18 bigTwo = new Wad18(new BigInteger("2000000000000000000")); 83 | Wad18 smallTwo = new Wad18(new BigInteger("2")); 84 | BigDecimal expected = new BigDecimal("4"); 85 | BigDecimal actual = bigTwo.multiply(smallTwo).toBigDecimal(); 86 | assertEquals(0, expected.compareTo(actual)); 87 | } 88 | 89 | @Test 90 | void multiply_MedianMultipliedByMedian_AboutOne() { 91 | Wad18 biggerMedian = new Wad18(new BigInteger("204120000000000000000")); 92 | Wad18 smallerMedian = new Wad18(new BigInteger("201120000000000000000")); 93 | BigDecimal expected = new BigDecimal("41052614400000000000000"); 94 | BigDecimal actual = biggerMedian.multiply(smallerMedian).toBigDecimal(); 95 | assertEquals(0, expected.compareTo(actual)); 96 | } 97 | 98 | @Test 99 | void multiply_Wad18MultpliedBySth28_Result() { 100 | Wad18 wad18 = new Wad18(new BigInteger("10000000000000000000")); // 10.000000000000000000 101 | Sth28 sth28 = new Sth28(new BigInteger("204721618847438310000000000")); // 0.0204721618847438310000000000 102 | BigDecimal expected = new BigDecimal("204721618847438310"); 103 | BigDecimal actual = wad18.multiply(sth28).toBigDecimal(); 104 | assertEquals(0, expected.compareTo(actual)); 105 | } 106 | 107 | @Test 108 | void toBigInteger() { 109 | BigInteger expected = new BigInteger("204120000000000000000"); 110 | BigInteger actual = new Wad18(expected).toBigInteger(); 111 | assertEquals(expected, actual); 112 | } 113 | 114 | @Test 115 | void toBigDecimal() { 116 | BigDecimal expected = new BigDecimal("204120000000000000000"); 117 | BigDecimal actual = new Wad18(expected).toBigDecimal(); 118 | assertEquals(expected, actual); 119 | } 120 | 121 | @Test 122 | void toString_median_expectedString() { 123 | BigDecimal number = new BigDecimal("204120000000000000000"); 124 | String actual = new Wad18(number).toString(); 125 | assertEquals("204.120000000000000000", actual); 126 | } 127 | 128 | @Test 129 | void toString_zero_expectedString() { 130 | BigDecimal number = new BigDecimal("0"); 131 | String actual = new Wad18(number).toString(); 132 | assertEquals("0.000000000000000000", actual); 133 | } 134 | 135 | @Test 136 | void toString_oneWei_expectedString() { 137 | BigDecimal number = new BigDecimal("1"); 138 | String actual = new Wad18(number).toString(); 139 | assertEquals("0.000000000000000001", actual); 140 | } 141 | 142 | @Test 143 | void toString_oneWeiAndDecimal_expectedString() { 144 | BigDecimal number = new BigDecimal("1.1"); 145 | String actual = new Wad18(number).toString(); 146 | assertEquals("0.000000000000000001", actual); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/test/java/de/devgao/defi/oasis/OasisIT.java: -------------------------------------------------------------------------------- 1 | package devgao.io.oasis; 2 | 3 | import devgao.io.compounddai.CompoundDai; 4 | import devgao.io.contractneedsprovider.*; 5 | import devgao.io.dai.Dai; 6 | import devgao.io.gasprovider.GasProvider; 7 | import devgao.io.numberutil.Wad18; 8 | import devgao.io.util.JavaProperties; 9 | import devgao.io.weth.Weth; 10 | import org.junit.jupiter.api.BeforeEach; 11 | import org.junit.jupiter.api.Test; 12 | import org.web3j.crypto.Credentials; 13 | import org.web3j.protocol.Web3j; 14 | 15 | import java.math.BigInteger; 16 | 17 | import static org.junit.jupiter.api.Assertions.*; 18 | 19 | class OasisIT { 20 | private static final String TRAVIS_INFURA_PROJECT_ID = "TRAVIS_INFURA_PROJECT_ID"; 21 | private static final String TRAVIS_WALLET = "TRAVIS_WALLET"; 22 | private static final String TRAVIS_PASSWORD = "TRAVIS_PASSWORD"; 23 | 24 | private static final Wad18 minimumGasPrice = new Wad18(1_000000000); 25 | private static final Wad18 maximumGasPrice = new Wad18(200_000000000L); 26 | 27 | Oasis oasis; 28 | 29 | @BeforeEach 30 | void setUp() { 31 | String infuraProjectId; 32 | String password; 33 | String wallet; 34 | 35 | JavaProperties javaProperties = new JavaProperties(true); 36 | 37 | if ("true".equals(System.getenv().get("TRAVIS"))) { 38 | infuraProjectId = System.getenv().get(TRAVIS_INFURA_PROJECT_ID); 39 | wallet = System.getenv().get(TRAVIS_WALLET); 40 | password = System.getenv().get(TRAVIS_PASSWORD); 41 | } else { 42 | infuraProjectId = javaProperties.getValue("infuraProjectId"); 43 | wallet = javaProperties.getValue("wallet"); 44 | password = javaProperties.getValue("password"); 45 | } 46 | 47 | Credentials credentials = new Wallet(password, wallet).getCredentials(); 48 | Web3j web3j = new Web3jProvider(infuraProjectId).web3j; 49 | GasProvider gasProvider = new GasProvider(web3j, minimumGasPrice, maximumGasPrice); 50 | Permissions permissions = new Permissions(true, true); 51 | CircuitBreaker circuitBreaker = new CircuitBreaker(); 52 | ContractNeedsProvider contractNeedsProvider = 53 | new ContractNeedsProvider(web3j, credentials, gasProvider, permissions, circuitBreaker); 54 | Weth weth = new Weth(contractNeedsProvider); 55 | CompoundDai compoundDai = new CompoundDai(contractNeedsProvider); 56 | oasis = new Oasis(contractNeedsProvider, compoundDai, weth); 57 | } 58 | 59 | @Test 60 | void getOffer_nonExistingOffer_DaiOrWethMissingException() { 61 | Exception exception = 62 | assertThrows(DaiOrWethMissingException.class, () -> oasis.getOffer(BigInteger.ZERO)); 63 | String actualMessage = exception.getMessage(); 64 | assertTrue(actualMessage.contains("BOTH DAI AND WETH NEED TO BE PRESENT ONCE.")); 65 | } 66 | 67 | @Test 68 | void getBestOffer_buyDai_returnOffer() { 69 | BigInteger actual = oasis.getBestOffer(Dai.ADDRESS, Weth.ADDRESS); 70 | assertNotEquals(BigInteger.ZERO, actual); 71 | assertNotNull(actual); 72 | } 73 | 74 | @Test 75 | void getBestOffer_sellDai_returnOffer() { 76 | BigInteger actual = oasis.getBestOffer(Weth.ADDRESS, Dai.ADDRESS); 77 | assertNotEquals(BigInteger.ZERO, actual); 78 | assertNotNull(actual); 79 | } 80 | 81 | @Test 82 | void buyDaiSellWethIsProfitable_triggerException_emptyOasisDexOffer() { 83 | // oasisDex.buyDaiSellWethIsProfitable(); 84 | } 85 | 86 | @Test 87 | void buyDaiSellWethIsProfitable_realValues_OasisDexOffer() {} 88 | 89 | @Test 90 | void sellDaiBuyWethIsProfitable_triggerException_emptyOasisDexOffer() {} 91 | 92 | @Test 93 | void sellDaiBuyWethIsProfitable_realValues_OasisDexOffer() {} 94 | } 95 | -------------------------------------------------------------------------------- /src/test/java/de/devgao/defi/oasisdex/OasisDexIT.java: -------------------------------------------------------------------------------- 1 | package devgao.io.oasisdex; 2 | 3 | import devgao.io.compounddai.CompoundDai; 4 | import devgao.io.contractneedsprovider.*; 5 | import devgao.io.dai.Dai; 6 | import devgao.io.gasprovider.GasProvider; 7 | import devgao.io.util.JavaProperties; 8 | import devgao.io.weth.Weth; 9 | import org.junit.jupiter.api.BeforeEach; 10 | import org.junit.jupiter.api.Test; 11 | import org.web3j.crypto.Credentials; 12 | import org.web3j.protocol.Web3j; 13 | 14 | import java.math.BigInteger; 15 | 16 | import static org.junit.jupiter.api.Assertions.*; 17 | 18 | public class OasisDexIT { 19 | private static final BigInteger minimumGasPrice = BigInteger.valueOf(1_000000000); 20 | private static final BigInteger maximumGasPrice = BigInteger.valueOf(200_000000000L); 21 | OasisDex oasisDex; 22 | 23 | @BeforeEach 24 | public void setUp() { 25 | JavaProperties javaProperties = new JavaProperties(true); 26 | String ethereumAddress = javaProperties.getValue("myEthereumAddress"); 27 | String password = javaProperties.getValue("password"); 28 | String infuraProjectId = javaProperties.getValue("infuraProjectId"); 29 | CircuitBreaker circuitBreaker = new CircuitBreaker(); 30 | Web3j web3j = new Web3jProvider(infuraProjectId).web3j; 31 | Credentials credentials = new Wallet(password, ethereumAddress, true).getCredentials(); 32 | GasProvider gasProvider = new GasProvider(web3j, minimumGasPrice, maximumGasPrice); 33 | Permissions permissions = new Permissions(true, true); 34 | ContractNeedsProvider contractNeedsProvider = 35 | new ContractNeedsProvider(web3j, credentials, gasProvider, permissions, circuitBreaker); 36 | Weth weth = new Weth(contractNeedsProvider); 37 | CompoundDai compoundDai = new CompoundDai(contractNeedsProvider); 38 | oasisDex = new OasisDex(contractNeedsProvider, compoundDai, weth); 39 | } 40 | 41 | @Test 42 | public void isContractValid_isValid_continueRunning() { 43 | oasisDex.isContractValid(); 44 | } 45 | 46 | @Test 47 | public void isContractValid_isNotValid_stopRunning() {} 48 | 49 | @Test 50 | public void getOffer_nonExistingOffer_DaiOrWethMissingException() { 51 | Exception exception = 52 | assertThrows(DaiOrWethMissingException.class, () -> oasisDex.getOffer(BigInteger.ZERO)); 53 | String actualMessage = exception.getMessage(); 54 | assertTrue(actualMessage.contains("BOTH DAI AND WETH NEED TO BE PRESENT ONCE.")); 55 | } 56 | 57 | @Test 58 | public void getBestOffer_buyDai_returnOffer() { 59 | BigInteger actual = oasisDex.getBestOffer(Dai.ADDRESS, Weth.ADDRESS); 60 | assertNotEquals(BigInteger.ZERO, actual); 61 | assertNotNull(actual); 62 | } 63 | 64 | @Test 65 | public void getBestOffer_sellDai_returnOffer() { 66 | BigInteger actual = oasisDex.getBestOffer(Weth.ADDRESS, Dai.ADDRESS); 67 | assertNotEquals(BigInteger.ZERO, actual); 68 | assertNotNull(actual); 69 | } 70 | 71 | @Test 72 | public void buyDaiSellWethIsProfitable_triggerException_emptyOasisDexOffer() { 73 | // oasisDex.buyDaiSellWethIsProfitable(); 74 | } 75 | 76 | @Test 77 | public void buyDaiSellWethIsProfitable_realValues_OasisDexOffer() {} 78 | 79 | @Test 80 | public void sellDaiBuyWethIsProfitable_triggerException_emptyOasisDexOffer() {} 81 | 82 | @Test 83 | public void sellDaiBuyWethIsProfitable_realValues_OasisDexOffer() {} 84 | } 85 | -------------------------------------------------------------------------------- /src/test/java/de/devgao/defi/uniswap/UniswapIT.java: -------------------------------------------------------------------------------- 1 | package devgao.io.uniswap; 2 | 3 | import devgao.io.compounddai.CompoundDai; 4 | import devgao.io.contractneedsprovider.*; 5 | import devgao.io.dai.Dai; 6 | import devgao.io.gasprovider.GasProvider; 7 | import devgao.io.numberutil.Wad18; 8 | import devgao.io.util.Balances; 9 | import devgao.io.util.Ethereum; 10 | import devgao.io.util.JavaProperties; 11 | import devgao.io.weth.Weth; 12 | import org.junit.jupiter.api.BeforeEach; 13 | import org.junit.jupiter.api.Test; 14 | import org.web3j.crypto.Credentials; 15 | import org.web3j.protocol.Web3j; 16 | 17 | import java.io.IOException; 18 | 19 | import static org.junit.jupiter.api.Assertions.assertEquals; 20 | import static org.junit.jupiter.api.Assertions.assertNull; 21 | 22 | class UniswapIT { 23 | private static final String TRAVIS_INFURA_PROJECT_ID = "TRAVIS_INFURA_PROJECT_ID"; 24 | private static final String TRAVIS_WALLET = "TRAVIS_WALLET"; 25 | private static final String TRAVIS_PASSWORD = "TRAVIS_PASSWORD"; 26 | 27 | Uniswap uniswap; 28 | Balances balances; 29 | ContractNeedsProvider contractNeedsProvider; 30 | JavaProperties javaProperties; 31 | Weth weth; 32 | Ethereum ethereum; 33 | CompoundDai compoundDai; 34 | Dai dai; 35 | 36 | @BeforeEach 37 | void setUp() { 38 | javaProperties = new JavaProperties(true); 39 | 40 | String infuraProjectId; 41 | String password; 42 | String wallet; 43 | 44 | Permissions permissions = 45 | new Permissions( 46 | Boolean.parseBoolean(javaProperties.getValue("transactionsRequireConfirmation")), 47 | Boolean.parseBoolean(javaProperties.getValue("playSoundOnTransaction"))); 48 | 49 | if ("true".equals(System.getenv().get("TRAVIS"))) { 50 | infuraProjectId = System.getenv().get(TRAVIS_INFURA_PROJECT_ID); 51 | wallet = System.getenv().get(TRAVIS_WALLET); 52 | password = System.getenv().get(TRAVIS_PASSWORD); 53 | } else { 54 | infuraProjectId = javaProperties.getValue("infuraProjectId"); 55 | wallet = javaProperties.getValue("wallet"); 56 | password = javaProperties.getValue("password"); 57 | } 58 | 59 | Web3j web3j = new Web3jProvider(infuraProjectId).web3j; 60 | GasProvider gasProvider = 61 | new GasProvider( 62 | web3j, new Wad18(1_000000000), new Wad18(1000_000000000L)); 63 | Credentials credentials = new Wallet(password, wallet).getCredentials(); 64 | CircuitBreaker circuitBreaker = new CircuitBreaker(); 65 | contractNeedsProvider = 66 | new ContractNeedsProvider(web3j, credentials, gasProvider, permissions, circuitBreaker); 67 | 68 | dai = 69 | new Dai( 70 | contractNeedsProvider, 71 | Double.parseDouble(javaProperties.getValue("minimumDaiNecessaryForSaleAndLending"))); 72 | compoundDai = new CompoundDai(contractNeedsProvider); 73 | weth = new Weth(contractNeedsProvider); 74 | ethereum = 75 | new Ethereum( 76 | contractNeedsProvider, 77 | Double.parseDouble(javaProperties.getValue("minimumEthereumReserveUpperLimit")), 78 | Double.parseDouble(javaProperties.getValue("minimumEthereumReserveLowerLimit")), 79 | Double.parseDouble(javaProperties.getValue("minimumEthereumNecessaryForSale"))); 80 | 81 | balances = new Balances(dai, weth, compoundDai, ethereum); 82 | uniswap = new Uniswap(contractNeedsProvider, javaProperties, compoundDai, weth); 83 | } 84 | 85 | @Test 86 | void getProfitableBuyDaiOffer_someRealNumbers_returnExpectedCalculation() { 87 | Wad18 buyableDaiAmount = 88 | new Wad18("4533813969247998520957"); // 4533.813969247998520957 89 | Wad18 medianEthereumPrice = 90 | new Wad18("231690000000000000000"); // 231.690000000000000000 91 | Wad18 ethToSell = new Wad18("19439031735500000000"); // 19.439031735500000000 92 | UniswapOffer offer = 93 | uniswap.getProfitableBuyDaiOffer( 94 | buyableDaiAmount, ethToSell, balances, medianEthereumPrice, 0.35); 95 | assertEquals(new Wad18("19490059192502288879"), offer.profit); 96 | } 97 | 98 | @Test 99 | void getBuyDaiParameters_buyableAmountIsZero_Null() throws Exception { 100 | Wad18 medianEthereumPrice = new Wad18("231690000000000000000"); 101 | EthToTokenSwapInput ethToTokenSwapInput = 102 | uniswap.getBuyDaiParameters(balances, medianEthereumPrice); 103 | assertNull(ethToTokenSwapInput); 104 | } 105 | 106 | @Test 107 | void 108 | getBuyDaiParameters_buyableAmountIsBiggerThanZero_allEthToTokenSwapInputAttributesNonZero() 109 | throws Exception { 110 | Wad18 medianEthereumPrice = new Wad18("231690000000000000000"); 111 | EthToTokenSwapInput ethToTokenSwapInput = 112 | uniswap.getBuyDaiParameters(balances, medianEthereumPrice); 113 | assertNull(ethToTokenSwapInput); 114 | } 115 | 116 | @Test 117 | void getSellDaiParameters_buyableAmountIsZero_Null() throws IOException { 118 | // TODO: test should not depend on real balances 119 | Wad18 medianEthereumPrice = new Wad18("231690000000000000000"); 120 | TokenToEthSwapInput tokenToEthSwapInput = 121 | uniswap.getSellDaiParameters(balances, medianEthereumPrice); 122 | assertNull(tokenToEthSwapInput); 123 | } 124 | 125 | // TODO: use Mockito set eth and weth balances to non-zero and do the following tests 126 | 127 | @Test 128 | void 129 | getSellDaiParameters_buyableAmountIsBiggerThanZero_allTokenToEthSwapInputAttributesNonZero() {} 130 | 131 | @Test 132 | void getProfitableBuyDaiOffer_triggerException_uniswapOfferZeroZero() {} 133 | 134 | @Test 135 | void getProfitableBuyDaiOffer_lowerThanMinimumProfit_uniswapOfferZeroNonZero() {} 136 | 137 | @Test 138 | void getProfitableBuyDaiOffer_higherThanMinimumProfit_uniswapOfferNonZeroNonZero() {} 139 | 140 | @Test 141 | void getProfitableSellDaiOffer_triggerException_uniswapOfferZeroZero() {} 142 | 143 | @Test 144 | void getProfitableSellDaiOffer_lowerThanMinimumProfit_uniswapOfferZeroNonZero() {} 145 | 146 | @Test 147 | void getProfitableSellDaiOffer_higherThanMinimumProfit_uniswapOfferNonZeroNonZero() {} 148 | } 149 | -------------------------------------------------------------------------------- /src/test/java/de/devgao/defi/util/BalancesIT.java: -------------------------------------------------------------------------------- 1 | package devgao.io.util; 2 | 3 | import devgao.io.compounddai.CompoundDai; 4 | import devgao.io.contractneedsprovider.*; 5 | import devgao.io.dai.Dai; 6 | import devgao.io.gasprovider.GasProvider; 7 | import devgao.io.medianizer.Medianizer; 8 | import devgao.io.numberutil.Wad18; 9 | import devgao.io.weth.Weth; 10 | import org.junit.jupiter.api.BeforeEach; 11 | import org.junit.jupiter.api.Test; 12 | import org.web3j.crypto.Credentials; 13 | import org.web3j.protocol.Web3j; 14 | 15 | import java.util.concurrent.TimeUnit; 16 | 17 | import static org.junit.jupiter.api.Assertions.assertTrue; 18 | 19 | class BalancesIT { 20 | private static final String TRAVIS_INFURA_PROJECT_ID = "TRAVIS_INFURA_PROJECT_ID"; 21 | private static final String TRAVIS_WALLET = "TRAVIS_WALLET"; 22 | private static final String TRAVIS_PASSWORD = "TRAVIS_PASSWORD"; 23 | 24 | private static final String TOO_HIGH = "Error, value is too high"; 25 | private static final String TOO_LOW = "Error, value is too low"; 26 | 27 | private static final Wad18 MINIMUM_GAS_PRICE = new Wad18(1_000000000); 28 | private static final Wad18 MAXIMUM_GAS_PRICE = new Wad18(200_000000000L); 29 | 30 | Balances balances; 31 | 32 | @BeforeEach 33 | void setUp() { 34 | String infuraProjectId; 35 | String password; 36 | String wallet; 37 | 38 | JavaProperties javaProperties = new JavaProperties(true); 39 | 40 | if ("true".equals(System.getenv().get("TRAVIS"))) { 41 | infuraProjectId = System.getenv().get(TRAVIS_INFURA_PROJECT_ID); 42 | wallet = System.getenv().get(TRAVIS_WALLET); 43 | password = System.getenv().get(TRAVIS_PASSWORD); 44 | } else { 45 | infuraProjectId = javaProperties.getValue("infuraProjectId"); 46 | wallet = javaProperties.getValue("wallet"); 47 | password = javaProperties.getValue("password"); 48 | } 49 | 50 | CircuitBreaker circuitBreaker = new CircuitBreaker(); 51 | Web3j web3j = new Web3jProvider(infuraProjectId).web3j; 52 | Credentials credentials = new Wallet(password, wallet).getCredentials(); 53 | GasProvider gasProvider = new GasProvider(web3j, MINIMUM_GAS_PRICE, MAXIMUM_GAS_PRICE); 54 | Permissions permissions = new Permissions(true, true); 55 | ContractNeedsProvider contractNeedsProvider = 56 | new ContractNeedsProvider(web3j, credentials, gasProvider, permissions, circuitBreaker); 57 | 58 | Medianizer.setMedianizerContract(contractNeedsProvider); 59 | Dai dai = 60 | new Dai( 61 | contractNeedsProvider, 62 | Double.parseDouble(javaProperties.getValue("minimumDaiNecessaryForSaleAndLending"))); 63 | Weth weth = new Weth(contractNeedsProvider); 64 | CompoundDai compoundDai = new CompoundDai(contractNeedsProvider); 65 | Ethereum ethereum = 66 | new Ethereum( 67 | contractNeedsProvider, 68 | Double.parseDouble(javaProperties.getValue("minimumEthereumReserveUpperLimit")), 69 | Double.parseDouble(javaProperties.getValue("minimumEthereumReserveLowerLimit")), 70 | Double.parseDouble(javaProperties.getValue("minimumEthereumNecessaryForSale"))); 71 | 72 | balances = new Balances(dai, weth, compoundDai, ethereum); 73 | } 74 | 75 | @Test 76 | void currentOwnershipRatio_zeroDai_zero() throws InterruptedException { 77 | Wad18 medianEthereumPrice = new Wad18("200000000000000000000"); 78 | Wad18 ethBalance = new Wad18("10000000000000000000"); 79 | Wad18 daiBalance = Wad18.ZERO; 80 | Wad18 wethBalance = Wad18.ZERO; 81 | Wad18 cdaiBalance = Wad18.ZERO; 82 | balances.usd = 83 | ethBalance 84 | .multiply(medianEthereumPrice) 85 | .add(wethBalance.multiply(medianEthereumPrice)) 86 | .add(daiBalance) 87 | .add(cdaiBalance); 88 | balances.currentOwnershipRatio(medianEthereumPrice, ethBalance, daiBalance, wethBalance); 89 | TimeUnit.MILLISECONDS.sleep(1000); 90 | double currentOwnershipRatio = 91 | balances.currentOwnershipRatio(medianEthereumPrice, ethBalance, daiBalance, wethBalance); 92 | assertTrue(currentOwnershipRatio == 0.0); 93 | } 94 | 95 | @Test 96 | void currentOwnershipRatio_zeroEth_one() throws InterruptedException { 97 | Wad18 medianEthereumPrice = new Wad18("200000000000000000000"); 98 | Wad18 ethBalance = Wad18.ZERO; 99 | Wad18 daiBalance = new Wad18("10000000000000000000"); 100 | Wad18 wethBalance = Wad18.ZERO; 101 | Wad18 cdaiBalance = Wad18.ZERO; 102 | balances.usd = 103 | ethBalance 104 | .multiply(medianEthereumPrice) 105 | .add(wethBalance.multiply(medianEthereumPrice)) 106 | .add(daiBalance) 107 | .add(cdaiBalance); 108 | balances.currentOwnershipRatio(medianEthereumPrice, ethBalance, daiBalance, wethBalance); 109 | TimeUnit.MILLISECONDS.sleep(1000); 110 | double currentOwnershipRatio = 111 | balances.currentOwnershipRatio(medianEthereumPrice, ethBalance, daiBalance, wethBalance); 112 | System.out.println(currentOwnershipRatio); 113 | assertTrue(currentOwnershipRatio == 100.0); 114 | } 115 | 116 | @Test 117 | void currentOwnershipRatio_halfEth_zeroPointFive() throws InterruptedException { 118 | Wad18 medianEthereumPrice = new Wad18("200000000000000000000"); // 200 USD 119 | Wad18 ethBalance = new Wad18("10000000000000000000"); // 10 ETH 120 | Wad18 daiBalance = new Wad18("2000000000000000000000"); // 2000 DAI 121 | Wad18 wethBalance = Wad18.ZERO; 122 | Wad18 cdaiBalance = Wad18.ZERO; 123 | balances.usd = 124 | ethBalance 125 | .multiply(medianEthereumPrice) 126 | .add(wethBalance.multiply(medianEthereumPrice)) 127 | .add(daiBalance) 128 | .add(cdaiBalance); 129 | balances.currentOwnershipRatio(medianEthereumPrice, ethBalance, daiBalance, wethBalance); 130 | TimeUnit.MILLISECONDS.sleep(1000); 131 | double currentOwnershipRatio = 132 | balances.currentOwnershipRatio(medianEthereumPrice, ethBalance, daiBalance, wethBalance); 133 | System.out.println(currentOwnershipRatio); 134 | assertTrue(currentOwnershipRatio == 50.0); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/test/java/de/devgao/defi/util/BigNumberUtilTest.java: -------------------------------------------------------------------------------- 1 | package devgao.io.util; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.math.BigDecimal; 6 | 7 | import static devgao.io.util.BigNumberUtil.*; 8 | import static org.junit.jupiter.api.Assertions.assertEquals; 9 | 10 | public class BigNumberUtilTest { 11 | private static final BigDecimal SOME_NUMBER = new BigDecimal("1454141938282760506"); 12 | 13 | @Test 14 | public void makeBigNumberHumanReadableFullPrecision_numbersAsString_returnNumber() { 15 | String expected = "98.765432109876543210"; 16 | String actual = 17 | makeBigNumberHumanReadableFullPrecision(new BigDecimal("098765432109876543210")); 18 | assertEquals(expected, actual); 19 | } 20 | 21 | @Test 22 | public void makeBigNumberHumanReadableFullPrecision_zeroAsString_returnZero() { 23 | String expected = "0.000000000000000000"; 24 | String actual = makeBigNumberHumanReadableFullPrecision(new BigDecimal("000000000000000000")); 25 | assertEquals(expected, actual); 26 | } 27 | 28 | @Test 29 | public void makeBigNumberHumanReadableFullPrecision_bigNumberAsString_ReturnBigNumber() { 30 | String expected = "99999.000000000000000001"; 31 | String actual = 32 | makeBigNumberHumanReadableFullPrecision(new BigDecimal("99999000000000000000001")); 33 | assertEquals(expected, actual); 34 | } 35 | 36 | @Test 37 | public void makeBigNumberHumanReadableFullPrecision_zeroAsStringWithDot_returnZero() { 38 | String expected = "0.000000000000000000"; 39 | String actual = makeBigNumberHumanReadableFullPrecision(new BigDecimal("0.0000000000000000")); 40 | assertEquals(expected, actual); 41 | } 42 | 43 | @Test 44 | public void makeBigNumberHumanReadableFullPrecision_zeroAsStringWithDot_returnZero2() { 45 | String expected = "201.000000000000000000"; 46 | String actual = 47 | makeBigNumberHumanReadableFullPrecision(new BigDecimal("201.0000000000000000000")); 48 | assertEquals(expected, actual); 49 | } 50 | 51 | @Test 52 | public void multiply_twoNumbers_returnMultiplication() { 53 | BigDecimal expected = new BigDecimal("145414193828276050"); 54 | BigDecimal actual = multiply(new BigDecimal("100000000000000000"), SOME_NUMBER); 55 | assertEquals(expected, actual); 56 | } 57 | 58 | @Test 59 | public void multiply_subOneNumber_returnMultiplication() { 60 | BigDecimal expected = new BigDecimal("0.1"); 61 | BigDecimal actual = multiply(new BigDecimal("0.1"), SOME_NUMBER); 62 | assertEquals(expected, actual); 63 | } 64 | 65 | @Test 66 | public void divide_twoNumbers_returnDivision() { 67 | BigDecimal expected = new BigDecimal("655846084377936"); 68 | BigDecimal actual = 69 | divide(new BigDecimal("145414193828276050"), new BigDecimal("221720000000000000000")); 70 | assertEquals(expected, actual); 71 | } 72 | 73 | @Test 74 | public void multiply_divide_twoNumbers_returnResult() { 75 | BigDecimal expected = new BigDecimal("655846084377936"); 76 | BigDecimal actual = 77 | divide( 78 | multiply(new BigDecimal("100000000000000000"), SOME_NUMBER), 79 | new BigDecimal("221720000000000000000")); 80 | assertEquals(expected, actual); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/test/java/de/devgao/defi/util/ContractUserIT.java: -------------------------------------------------------------------------------- 1 | package devgao.io.util; 2 | 3 | import devgao.io.contractneedsprovider.CircuitBreaker; 4 | import devgao.io.contractneedsprovider.Wallet; 5 | import devgao.io.contractneedsprovider.Web3jProvider; 6 | import devgao.io.dai.DaiContract; 7 | import devgao.io.gasprovider.GasProvider; 8 | import devgao.io.weth.WethContract; 9 | import org.junit.jupiter.api.BeforeEach; 10 | import org.junit.jupiter.api.Test; 11 | import org.web3j.crypto.Credentials; 12 | import org.web3j.protocol.Web3j; 13 | 14 | import java.math.BigInteger; 15 | 16 | import static org.junit.jupiter.api.Assertions.*; 17 | 18 | class ContractUserIT { 19 | private static final String TRAVIS_INFURA_PROJECT_ID = "TRAVIS_INFURA_PROJECT_ID"; 20 | private static final String TRAVIS_WALLET = "TRAVIS_WALLET"; 21 | private static final String TRAVIS_PASSWORD = "TRAVIS_PASSWORD"; 22 | 23 | private static final String EXCEPTION = "Exception"; 24 | 25 | private static final BigInteger minimumGasPrice = BigInteger.valueOf(1_000000000); 26 | private static final BigInteger maximumGasPrice = BigInteger.valueOf(200_000000000L); 27 | 28 | Web3j web3j; 29 | Credentials credentials; 30 | GasProvider gasProvider; 31 | 32 | @BeforeEach 33 | void setUp() { 34 | String infuraProjectId; 35 | String password; 36 | String wallet; 37 | 38 | JavaProperties javaProperties = new JavaProperties(true); 39 | 40 | if ("true".equals(System.getenv().get("TRAVIS"))) { 41 | infuraProjectId = System.getenv().get(TRAVIS_INFURA_PROJECT_ID); 42 | wallet = System.getenv().get(TRAVIS_WALLET); 43 | password = System.getenv().get(TRAVIS_PASSWORD); 44 | } else { 45 | infuraProjectId = javaProperties.getValue("infuraProjectId"); 46 | wallet = javaProperties.getValue("wallet"); 47 | password = javaProperties.getValue("password"); 48 | } 49 | 50 | web3j = new Web3jProvider(infuraProjectId).web3j; 51 | credentials = new Wallet(password, wallet).getCredentials(); 52 | gasProvider = new GasProvider(web3j, minimumGasPrice, maximumGasPrice); 53 | } 54 | 55 | @Test 56 | public void isContractValid_isValid_continueRunning() { 57 | WethContract contract = 58 | WethContract.load( 59 | "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", web3j, credentials, gasProvider); 60 | CircuitBreaker circuitBreaker = new CircuitBreaker(); 61 | assertDoesNotThrow(() -> ContractUser.isContractValid(contract, circuitBreaker)); 62 | assertTrue(circuitBreaker.getContinueRunning()); 63 | } 64 | 65 | @Test 66 | public void isContractValid_isNotValid_stopRunning() { 67 | DaiContract contract = 68 | DaiContract.load( 69 | "0x0000000000000000000000000000000000000000", web3j, credentials, gasProvider); 70 | CircuitBreaker circuitBreaker = new CircuitBreaker(); 71 | assertDoesNotThrow(() -> ContractUser.isContractValid(contract, circuitBreaker)); 72 | assertFalse(circuitBreaker.getContinueRunning()); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/test/java/de/devgao/defi/util/JavaPropertiesTest.java: -------------------------------------------------------------------------------- 1 | package devgao.io.util; 2 | 3 | import org.junit.jupiter.api.BeforeAll; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import static org.junit.jupiter.api.Assertions.assertEquals; 7 | import static org.junit.jupiter.api.Assertions.assertFalse; 8 | 9 | class JavaPropertiesTest { 10 | private static final String TEST_PROPERTY = "testProperty"; 11 | private static JavaProperties javaProperties; 12 | 13 | @BeforeAll 14 | static void setUp() { 15 | javaProperties = new JavaProperties(true); 16 | } 17 | 18 | @Test 19 | void getValue_getTestProperty_true() { 20 | String testProperty = javaProperties.getValue(TEST_PROPERTY); 21 | assertEquals("true", testProperty); 22 | } 23 | 24 | @Test 25 | void setValue_setTestProperty_falseThenTrue() { 26 | javaProperties.setValue(TEST_PROPERTY, "false"); 27 | String testProperty = javaProperties.getValue(TEST_PROPERTY); 28 | assertEquals("false", testProperty); 29 | javaProperties.setValue(TEST_PROPERTY, "true"); 30 | testProperty = javaProperties.getValue(TEST_PROPERTY); 31 | assertEquals("true", testProperty); 32 | } 33 | 34 | @Test 35 | void getValue_checkAllImportantProperties_AllNotEmpty() { 36 | assertFalse(javaProperties.getValue("infuraProjectId").isEmpty()); 37 | assertFalse(javaProperties.getValue("password").isEmpty()); 38 | assertFalse(javaProperties.getValue("transactionsRequireConfirmation").isEmpty()); 39 | assertFalse(javaProperties.getValue("playSoundOnTransaction").isEmpty()); 40 | assertFalse(javaProperties.getValue("uniswapBuyProfitPercentage").isEmpty()); 41 | assertFalse(javaProperties.getValue("uniswapSellProfitPercentage").isEmpty()); 42 | assertFalse(javaProperties.getValue("wallet").isEmpty()); 43 | assertFalse(javaProperties.getValue("minimumEthereumReserveUpperLimit").isEmpty()); 44 | assertFalse(javaProperties.getValue("minimumEthereumReserveLowerLimit").isEmpty()); 45 | assertFalse(javaProperties.getValue("minimumEthereumNecessaryForSale").isEmpty()); 46 | assertFalse(javaProperties.getValue("minimumDaiNecessaryForSaleAndLending").isEmpty()); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/test/java/de/devgao/defi/util/NumberUtilTest.java: -------------------------------------------------------------------------------- 1 | package devgao.io.util; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.math.BigDecimal; 6 | import java.math.BigInteger; 7 | 8 | import static devgao.io.util.NumberUtil.*; 9 | import static org.junit.jupiter.api.Assertions.assertEquals; 10 | 11 | public class NumberUtilTest { 12 | private static final BigDecimal SOME_NUMBER = new BigDecimal("1454141938282760506"); 13 | 14 | @Test 15 | public void makeBigNumberHumanReadableFullPrecision_numbersAsString_returnNumber() { 16 | String expected = "98.765432109876543210"; 17 | String actual = getFullPrecision(new BigDecimal("098765432109876543210")); 18 | assertEquals(expected, actual); 19 | } 20 | 21 | @Test 22 | public void makeBigNumberHumanReadableFullPrecision_zeroAsString_returnZero() { 23 | String expected = "0.000000000000000000"; 24 | String actual = getFullPrecision(new BigDecimal("000000000000000000")); 25 | assertEquals(expected, actual); 26 | } 27 | 28 | @Test 29 | public void makeBigNumberHumanReadableFullPrecision_bigNumberAsString_ReturnBigNumber() { 30 | String expected = "99999.000000000000000001"; 31 | String actual = getFullPrecision(new BigDecimal("99999000000000000000001")); 32 | assertEquals(expected, actual); 33 | } 34 | 35 | @Test 36 | public void makeBigNumberHumanReadableFullPrecision_zeroAsStringWithDot_returnZero() { 37 | String expected = "0.000000000000000000"; 38 | String actual = getFullPrecision(new BigDecimal("0.0000000000000000")); 39 | assertEquals(expected, actual); 40 | } 41 | 42 | @Test 43 | public void makeBigNumberHumanReadableFullPrecision_zeroAsStringWithDot_returnZero2() { 44 | String expected = "201.000000000000000000"; 45 | String actual = getFullPrecision(new BigDecimal("201.0000000000000000000")); 46 | assertEquals(expected, actual); 47 | } 48 | 49 | @Test 50 | public void multiply_twoNumbers_returnMultiplication() { 51 | BigDecimal expected = new BigDecimal("145414193828276050"); 52 | BigDecimal actual = multiply(new BigDecimal("100000000000000000"), SOME_NUMBER); 53 | assertEquals(expected, actual); 54 | } 55 | 56 | @Test 57 | public void multiply_subOneNumber_returnMultiplication() { 58 | BigDecimal expected = new BigDecimal("0.1"); 59 | BigDecimal actual = multiply(new BigDecimal("0.1"), SOME_NUMBER); 60 | assertEquals(expected, actual); 61 | } 62 | 63 | @Test 64 | public void divide_twoNumbers_returnDivision() { 65 | BigDecimal expected = new BigDecimal("655846084377936"); 66 | BigDecimal actual = 67 | divide(new BigDecimal("145414193828276050"), new BigDecimal("221720000000000000000")); 68 | assertEquals(expected, actual); 69 | } 70 | 71 | @Test 72 | public void multiply_divide_twoNumbers_returnResult() { 73 | BigDecimal expected = new BigDecimal("655846084377936"); 74 | BigDecimal actual = 75 | divide( 76 | multiply(new BigDecimal("100000000000000000"), SOME_NUMBER), 77 | new BigDecimal("221720000000000000000")); 78 | assertEquals(expected, actual); 79 | } 80 | 81 | @Test 82 | public void UINTMAX_getUINTMAX_equals() { 83 | BigInteger expected = new BigInteger("2").pow(256).subtract(BigInteger.ONE); 84 | assertEquals(0, expected.compareTo(UINT_MAX)); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/test/java/de/devgao/defi/util/TransactionUtilTest.java: -------------------------------------------------------------------------------- 1 | package devgao.io.util; 2 | 3 | import devgao.io.numberutil.Wad18; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import java.math.BigInteger; 7 | 8 | import static devgao.io.numberutil.NumberUtil.getMachineReadable; 9 | import static devgao.io.util.TransactionUtil.getTransactionCosts; 10 | import static org.junit.jupiter.api.Assertions.assertTrue; 11 | 12 | class TransactionUtilTest { 13 | 14 | @Test 15 | void getTransactionCosts_realNumbers_true() { 16 | // 2 * 222.53 * 300,000 * 0.00000001 = 1.33518 17 | Wad18 gasPrice = new Wad18("10000000000"); // 10 GWEI 18 | Wad18 medianEthereumPrice = new Wad18(getMachineReadable(222.53)); 19 | BigInteger gasLimit = BigInteger.valueOf(300000); 20 | Wad18 wad18 = getTransactionCosts(gasPrice, medianEthereumPrice, gasLimit, 2); 21 | System.out.println(wad18); 22 | assertTrue(wad18.compareTo(new Wad18(getMachineReadable(1.33518))) == 0); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/test/java/de/devgao/defi/weth/WethIT.java: -------------------------------------------------------------------------------- 1 | package devgao.io.weth; 2 | 3 | import devgao.io.contractneedsprovider.*; 4 | import devgao.io.gasprovider.GasProvider; 5 | import devgao.io.numberutil.Wad18; 6 | import devgao.io.util.JavaProperties; 7 | import org.junit.jupiter.api.BeforeEach; 8 | import org.web3j.crypto.Credentials; 9 | import org.web3j.protocol.Web3j; 10 | 11 | class WethIT { 12 | private static final String TRAVIS_INFURA_PROJECT_ID = "TRAVIS_INFURA_PROJECT_ID"; 13 | private static final String TRAVIS_WALLET = "TRAVIS_WALLET"; 14 | private static final String TRAVIS_PASSWORD = "TRAVIS_PASSWORD"; 15 | 16 | private static final Wad18 minimumGasPrice = new Wad18(1_000000000); 17 | private static final Wad18 maximumGasPrice = new Wad18(200_000000000L); 18 | 19 | Weth weth; 20 | 21 | @BeforeEach 22 | void setUp() { 23 | String infuraProjectId; 24 | String password; 25 | String wallet; 26 | 27 | JavaProperties javaProperties = new JavaProperties(true); 28 | 29 | if ("true".equals(System.getenv().get("TRAVIS"))) { 30 | infuraProjectId = System.getenv().get(TRAVIS_INFURA_PROJECT_ID); 31 | wallet = System.getenv().get(TRAVIS_WALLET); 32 | password = System.getenv().get(TRAVIS_PASSWORD); 33 | } else { 34 | infuraProjectId = javaProperties.getValue("infuraProjectId"); 35 | wallet = javaProperties.getValue("wallet"); 36 | password = javaProperties.getValue("password"); 37 | } 38 | 39 | CircuitBreaker circuitBreaker = new CircuitBreaker(); 40 | Web3j web3j = new Web3jProvider(infuraProjectId).web3j; 41 | Credentials credentials = new Wallet(password, wallet).getCredentials(); 42 | GasProvider gasProvider = new GasProvider(web3j, minimumGasPrice, maximumGasPrice); 43 | Permissions permissions = new Permissions(true, true); 44 | ContractNeedsProvider contractNeedsProvider = 45 | new ContractNeedsProvider(web3j, credentials, gasProvider, permissions, circuitBreaker); 46 | weth = new Weth(contractNeedsProvider); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/test/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | System.out 4 | 5 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 6 | 7 | 8 | 9 | 10 | logs/%d{yyyy_MM_dd}.%i.log 11 | 200MB 12 | 365 13 | 5GB 14 | 15 | 16 | %d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | --------------------------------------------------------------------------------