├── .gitattributes ├── .gitignore ├── .prettierrc ├── LICENSE ├── README.md ├── buidler.config.js ├── contracts ├── BoostGovV2.sol ├── BoostRewardsV2.sol ├── BoostToken.sol ├── IERC20.sol ├── IERC20Burnable.sol ├── ISwapRouter.sol ├── ITreasury.sol ├── IUniswapV2Factory.sol ├── LPTokenWrapper.sol ├── LPTokenWrapperWithSlash.sol ├── SafeMath.sol ├── Treasury.sol ├── V1 │ ├── BoostGov.sol │ └── BoostRewardsPool.sol ├── mock │ ├── Token.sol │ └── WETH.sol └── zeppelin │ ├── Address.sol │ ├── Ownable.sol │ └── SafeERC20.sol ├── package-lock.json ├── package.json ├── scripts ├── defiPools.js ├── internalPoolGov.js ├── mainnet_settings.json └── wave3Distribution.js └── test ├── BoostGov.js ├── BoostRewardsPool.js └── BoostToken.js /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # Sapper build files 58 | __sapper__ 59 | 60 | # dotenv environment variables file 61 | .env 62 | 63 | #Buidler files 64 | bin/ 65 | cache 66 | artifacts 67 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "overrides": [ 3 | { 4 | "files": "*.sol", 5 | "options": { 6 | "printWidth": 99, 7 | "tabWidth": 4, 8 | "useTabs": false, 9 | "singleQuote": false, 10 | "bracketSpacing": false, 11 | "explicitTypes": "always" 12 | } 13 | }, 14 | { 15 | "files": "*.js", 16 | "options": { 17 | "printWidth": 119, 18 | "tabWidth": 2, 19 | "useTabs": false, 20 | "singleQuote": true, 21 | "bracketSpacing": false, 22 | "explicitTypes": "always", 23 | "semi": true 24 | } 25 | } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Boosted Finance 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🚀 Boosted Finance 🚀 2 | 3 | ## The Inspiration 4 | Many of us watched or participated in decentralized financial socio-economic experiments which have failed miserably due to the lack of long-term network effects, over-engineered mechanisms or centralized bias. Yield farmers need two things: maximum yield and fair farming grounds. 5 | 6 | Boosted Finance brings together the best that DeFi and decentralized technology has on offer: equally fair barrier of entry for all participants, democratic governance, perpetual network effects and economic theory. We have been inspired by Yam’s elegant protocol design, taking key principles while omitting the rebasing mechanism and implementing the eighth wonder of the world - compound effects. 7 | 8 | Boosted Finance features: 9 | * A fair distribution mechanism that incentivizes key community members to actively take the reins of governance. 10 | * Fully on-chain governance to enable decentralized control and evolution from Day 1. 11 | * A governable treasury to reward participation from community members to further support stability in the long run. 12 | * An yield-boosting option that works on the compounding effect of money and maintaining long-term prosperity for BOOST holders. 13 | 14 | ## Audits 15 | There have been no official third-party audits for Boost Finance although core contributors have made extensive efforts to secure smart contracts including forking the code bases of notable and established projects. 16 | 17 | ⚠️ We urge all users who engage with staking contracts to self-audit and read through contracts before putting your LP tokens at stake. You will be using this BETA product at your own risk. 18 | 19 | Security is important to us. We invite Trail of Bits, PeckShield, OpenZeppelin, Consensys, Certik, and Quantstamp and more to audit the contracts. We will be submitting a governance proposal to award firms that audit Boosted Finance from the fully governable BOOST treasury. 20 | 21 | If you feel uncomfortable with these premises, don't stake or hold BOOST. If the community votes to fund an audit, there is no assumption that the original devs will be around to implement fixes, and is entirely at their discretion. 22 | 23 | ## BOOST token 24 | BOOST is a governance token for the BOOST treasury. The value of the token will be entirely dependent on the BOOST community who are responsible for the direction via community voting on-chain. 25 | 26 | ## Distribution 27 | **No Pre-Mine. No Founder Allocation. No Venture Capital.** In true DeFi spirit, the initial distribution of BOOST will be evenly spread across 10 genesis staking pools to provide an equal-opportunity staking distributions for yield farmers of all backgrounds. 28 | 29 | There will be **100,000 BOOST tokens to be fully released over the course of 4 weeks** from launch. BOOST will be farmed and allocated every minute based on the proportion of the token staked against the total staked token in the same pool. 30 | 31 | 32 | The initial distribution of 40,000 BOOST will be evenly distributed across ten staking pools composed of the top DeFi tokens over the first week of launch. These pools have been chosen based on covering all facets of DeFi and ones with communities that have strong commitment to decentralized technologies. 33 | 34 | 60,000 BOOST will be equally through two internal BOOST staking pools with duration of 2 weeks each. The first pool will be set up through [Uniswap](https://uniswap.org/) and will be initiated 48 hours after the launch of genesis farming pools. The second pool will be set up through [SushiSwap](https://sushiswap.org/) and will be initiated shortly after liquidity migration has taken place on SushiSwap (the countdown timer to the event can be found [here](http://etherscan.io/block/countdown/10850000)). 35 | 36 | ## Governance 37 | Using a modified YFI governance model, the community will be able to submit governance proposals **48 hours after launch** by locking up 13.37 BOOST in a staking contract for three days. Each proposal will have a **duration of 2 days** where the proposer can submit withdrawal amount and address, if they wish to claim yCRV for the proposal. 38 | 39 | **Once voting for or against in governance proposals, stakers will not be able to switch votes.** 40 | 41 | Proposals can only be resolved after the two day duration, once resolved as either a ‘pass’ or ‘fail, it cannot be resolved again to prevent multiple withdrawals for successful proposals. To discourage bad proposals, if the proposers asks for more than 1000 yCRX and a 5% minimum quorum is not met — the entire stake will be **slashed**, converted to yCRV and transferred to the governance contract. 42 | 43 | Quorums are determined by the total supply at the time of proposal resolution and must hit a minimum of 30% quorum, more votes for than against, to pass. 44 | 45 | ## Development 46 | 47 | ### Building and Tests 48 | This project uses [buidler](https://buidler.dev/). 49 | 50 | Make sure to install required dependencies: 51 | ``` 52 | $ npm install 53 | ``` 54 | 55 | To compile the contracts: 56 | ``` 57 | $ npm run compile 58 | ``` 59 | 60 | To run the tests: 61 | ``` 62 | $ npm run test 63 | ``` 64 | 65 | To run a single test: 66 | ``` 67 | $ npx buidler test test/ 68 | ``` 69 | 70 | ## Attribution 71 | Much of this codebase is modified from existing works, including: 72 | 73 | [Compound](https://compound.finance/) - Jumping off point for token code and governance 74 | 75 | [Synthetix](https://synthetix.io/) - Rewards staking contract 76 | 77 | [YEarn](https://yearn.finance/) - Initial fair distribution implementation 78 | 79 | [YAM](https://yam.finance/) - Design inspiration 80 | 81 | ## Mainnet Contract Addresses 82 | 83 | Boost Token: `0x3e780920601D61cEdb860fe9c4a90c9EA6A35E78` 84 | 85 | Pool Addresses\ 86 | AAVE: `0x383F3Ba9B39011f658e55a4c24c648851A4A8b60`\ 87 | BAND: `0x3080869CF796d944cB4fb3C47D7084f8E8D3d22a`\ 88 | COMP: `0x39CD2Fc7BAc954ABc3C1b6dA1CD467fA44f4f3BD`\ 89 | KNC: `0x90dfbaDDf8f213185004bB200eDbB554E1F13D52`\ 90 | LINK: `0x57fbd512a440CCE6832c62fD63c54A0A9f545F8a`\ 91 | MKR: `0x40aFeF1b846D0a4EEf601Cf2B2117468eF63643C`\ 92 | REN: `0x1dfF57d28C30F094235f0244939194B1223e66e1`\ 93 | SNX: `0xf8Cb70658F7eC2bdC51d65323300b4cd0B5c6301`\ 94 | SUSHI: `0xC7491fcDfc8af10d5a8Bc9C13b60B85209C0dc59`\ 95 | YFI: `0x3EE27441449B2DfC705E3C237FFd75826870120A` 96 | -------------------------------------------------------------------------------- /buidler.config.js: -------------------------------------------------------------------------------- 1 | usePlugin("@nomiclabs/buidler-truffle5"); 2 | usePlugin("@nomiclabs/buidler-web3"); 3 | usePlugin("@nomiclabs/buidler-etherscan"); 4 | 5 | require('./scripts/wave3Distribution'); 6 | require('./scripts/defiPools'); 7 | require('./scripts/internalPoolGov'); 8 | require('dotenv').config(); 9 | 10 | module.exports = { 11 | defaultNetwork: 'buidlerevm', 12 | networks: { 13 | kovan: { 14 | url: `https://kovan.infura.io/v3/${process.env.INFURA_API_KEY}`, 15 | accounts: [process.env.PRIVATE_KEY], 16 | timeout: 20000 17 | }, 18 | mainnet: { 19 | url: `https://mainnet.infura.io/v3/${process.env.INFURA_API_KEY}`, 20 | accounts: [process.env.PRIVATE_KEY], 21 | timeout: 20000 22 | } 23 | }, 24 | solc: { 25 | version: "0.5.17", 26 | optimizer: { 27 | enabled: true, 28 | runs: 10000 29 | } 30 | }, 31 | etherscan: { 32 | url: "https://api-kovan.etherscan.io/api", 33 | apiKey: process.env.ETHERSCAN_API_KEY 34 | }, 35 | paths: { 36 | sources: './contracts', 37 | tests: './test', 38 | }, 39 | mocha: { 40 | enableTimeouts: false, 41 | }, 42 | }; 43 | -------------------------------------------------------------------------------- /contracts/BoostGovV2.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | /* 3 | * MIT License 4 | * =========== 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | */ 23 | 24 | pragma solidity 0.5.17; 25 | 26 | import "./SafeMath.sol"; 27 | import "./zeppelin/SafeERC20.sol"; 28 | import "./IERC20.sol"; 29 | import "./ITreasury.sol"; 30 | import "./ISwapRouter.sol"; 31 | import "./LPTokenWrapperWithSlash.sol"; 32 | 33 | 34 | contract BoostGovV2 is LPTokenWrapperWithSlash { 35 | IERC20 public stablecoin; 36 | ITreasury public treasury; 37 | SwapRouter public swapRouter; 38 | 39 | // 1% = 100 40 | uint256 public constant MIN_QUORUM_PUNISHMENT = 500; // 5% 41 | uint256 public constant MIN_QUORUM_THRESHOLD = 3000; // 30% 42 | uint256 public constant PERCENTAGE_PRECISION = 10000; 43 | uint256 public WITHDRAW_THRESHOLD = 1e21; // 1000 yCRV 44 | 45 | mapping(address => uint256) public voteLock; // timestamp that boost stakes are locked after voting 46 | 47 | struct Proposal { 48 | address proposer; 49 | address withdrawAddress; 50 | uint256 withdrawAmount; 51 | mapping(address => uint256) forVotes; 52 | mapping(address => uint256) againstVotes; 53 | uint256 totalForVotes; 54 | uint256 totalAgainstVotes; 55 | uint256 totalSupply; 56 | uint256 start; // block start; 57 | uint256 end; // start + period 58 | string url; 59 | string title; 60 | } 61 | 62 | mapping (uint256 => Proposal) public proposals; 63 | uint256 public proposalCount; 64 | uint256 public proposalPeriod = 2 days; 65 | uint256 public lockPeriod = 3 days; 66 | uint256 public minimum = 1337e16; // 13.37 BOOST 67 | 68 | constructor(IERC20 _stakeToken, ITreasury _treasury, SwapRouter _swapRouter) 69 | public 70 | LPTokenWrapperWithSlash(_stakeToken) 71 | { 72 | treasury = _treasury; 73 | stablecoin = treasury.defaultToken(); 74 | stablecoin.safeApprove(address(treasury), uint256(-1)); 75 | stakeToken.safeApprove(address(_swapRouter), uint256(-1)); 76 | swapRouter = _swapRouter; 77 | } 78 | 79 | function propose( 80 | string memory _url, 81 | string memory _title, 82 | uint256 _withdrawAmount, 83 | address _withdrawAddress 84 | ) public { 85 | require(balanceOf(msg.sender) > minimum, "stake more boost"); 86 | proposals[proposalCount++] = Proposal({ 87 | proposer: msg.sender, 88 | withdrawAddress: _withdrawAddress, 89 | withdrawAmount: _withdrawAmount, 90 | totalForVotes: 0, 91 | totalAgainstVotes: 0, 92 | totalSupply: 0, 93 | start: block.timestamp, 94 | end: proposalPeriod.add(block.timestamp), 95 | url: _url, 96 | title: _title 97 | }); 98 | voteLock[msg.sender] = lockPeriod.add(block.timestamp); 99 | } 100 | 101 | function voteFor(uint256 id) public { 102 | require(proposals[id].start < block.timestamp , " block.timestamp , ">end"); 104 | require(proposals[id].againstVotes[msg.sender] == 0, "cannot switch votes"); 105 | uint256 userVotes = Math.sqrt(balanceOf(msg.sender)); 106 | uint256 votes = userVotes.sub(proposals[id].forVotes[msg.sender]); 107 | proposals[id].totalForVotes = proposals[id].totalForVotes.add(votes); 108 | proposals[id].forVotes[msg.sender] = userVotes; 109 | 110 | voteLock[msg.sender] = lockPeriod.add(block.timestamp); 111 | } 112 | 113 | function voteAgainst(uint256 id) public { 114 | require(proposals[id].start < block.timestamp , " block.timestamp , ">end"); 116 | require(proposals[id].forVotes[msg.sender] == 0, "cannot switch votes"); 117 | uint256 userVotes = Math.sqrt(balanceOf(msg.sender)); 118 | uint256 votes = userVotes.sub(proposals[id].againstVotes[msg.sender]); 119 | proposals[id].totalAgainstVotes = proposals[id].totalAgainstVotes.add(votes); 120 | proposals[id].againstVotes[msg.sender] = userVotes; 121 | 122 | voteLock[msg.sender] = lockPeriod.add(block.timestamp); 123 | } 124 | 125 | function stake(uint256 amount) public { 126 | super.stake(amount); 127 | } 128 | 129 | function withdraw(uint256 amount) public { 130 | require(voteLock[msg.sender] < block.timestamp, "tokens locked"); 131 | super.withdraw(amount); 132 | } 133 | 134 | function resolveProposal(uint256 id) public { 135 | require(proposals[id].proposer != address(0), "non-existent proposal"); 136 | require(proposals[id].end < block.timestamp , "ongoing proposal"); 137 | require(proposals[id].totalSupply == 0, "already resolved"); 138 | 139 | // update proposal total supply 140 | proposals[id].totalSupply = Math.sqrt(totalSupply()); 141 | 142 | // sum votes, multiply by precision, divide by square rooted total supply 143 | uint256 quorum = 144 | (proposals[id].totalForVotes.add(proposals[id].totalAgainstVotes)) 145 | .mul(PERCENTAGE_PRECISION) 146 | .div(proposals[id].totalSupply); 147 | 148 | if ((quorum < MIN_QUORUM_PUNISHMENT) && proposals[id].withdrawAmount > WITHDRAW_THRESHOLD) { 149 | // user's stake gets slashed, converted to stablecoin and sent to treasury 150 | uint256 amount = slash(proposals[id].proposer); 151 | convertAndSendTreasuryFunds(amount); 152 | } else if ( 153 | (quorum > MIN_QUORUM_THRESHOLD) && 154 | (proposals[id].totalForVotes > proposals[id].totalAgainstVotes) 155 | ) { 156 | // treasury to send funds to proposal 157 | treasury.withdraw( 158 | proposals[id].withdrawAmount, 159 | proposals[id].withdrawAddress 160 | ); 161 | } 162 | } 163 | 164 | function convertAndSendTreasuryFunds(uint256 amount) internal { 165 | address[] memory routeDetails = new address[](3); 166 | routeDetails[0] = address(stakeToken); 167 | routeDetails[1] = swapRouter.WETH(); 168 | routeDetails[2] = address(stablecoin); 169 | uint[] memory amounts = swapRouter.swapExactTokensForTokens( 170 | amount, 171 | 0, 172 | routeDetails, 173 | address(this), 174 | block.timestamp + 100 175 | ); 176 | // 0 = input token amt, 1 = weth output amt, 2 = stablecoin output amt 177 | treasury.deposit(stablecoin, amounts[2]); 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /contracts/BoostRewardsV2.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | /* 3 | * MIT License 4 | * =========== 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | */ 23 | 24 | pragma solidity 0.5.17; 25 | 26 | import "./SafeMath.sol"; 27 | import "./zeppelin/Ownable.sol"; 28 | import "./zeppelin/SafeERC20.sol"; 29 | import "./IERC20Burnable.sol"; 30 | import "./ITreasury.sol"; 31 | import "./ISwapRouter.sol"; 32 | import "./LPTokenWrapper.sol"; 33 | 34 | 35 | contract BoostRewardsV2 is LPTokenWrapper, Ownable { 36 | IERC20 public boostToken; 37 | ITreasury public treasury; 38 | SwapRouter public swapRouter; 39 | IERC20 public stablecoin; 40 | 41 | uint256 public tokenCapAmount; 42 | uint256 public starttime; 43 | uint256 public duration; 44 | uint256 public periodFinish = 0; 45 | uint256 public rewardRate = 0; 46 | uint256 public lastUpdateTime; 47 | uint256 public rewardPerTokenStored; 48 | mapping(address => uint256) public userRewardPerTokenPaid; 49 | mapping(address => uint256) public rewards; 50 | 51 | // booster variables 52 | // variables to keep track of totalSupply and balances (after accounting for multiplier) 53 | uint256 public boostedTotalSupply; 54 | uint256 public lastBoostPurchase; // timestamp of lastBoostPurchase 55 | mapping(address => uint256) public boostedBalances; 56 | mapping(address => uint256) public numBoostersBought; // each booster = 5% increase in stake amt 57 | mapping(address => uint256) public nextBoostPurchaseTime; // timestamp for which user is eligible to purchase another booster 58 | uint256 public globalBoosterPrice = 1e18; 59 | uint256 public boostThreshold = 10; 60 | uint256 public boostScaleFactor = 20; 61 | uint256 public scaleFactor = 320; 62 | 63 | event RewardAdded(uint256 reward); 64 | event RewardPaid(address indexed user, uint256 reward); 65 | 66 | modifier checkStart() { 67 | require(block.timestamp >= starttime,"not start"); 68 | _; 69 | } 70 | 71 | modifier updateReward(address account) { 72 | rewardPerTokenStored = rewardPerToken(); 73 | lastUpdateTime = lastTimeRewardApplicable(); 74 | if (account != address(0)) { 75 | rewards[account] = earned(account); 76 | userRewardPerTokenPaid[account] = rewardPerTokenStored; 77 | } 78 | _; 79 | } 80 | 81 | constructor( 82 | uint256 _tokenCapAmount, 83 | IERC20 _stakeToken, 84 | IERC20 _boostToken, 85 | address _treasury, 86 | SwapRouter _swapRouter, 87 | uint256 _starttime, 88 | uint256 _duration 89 | ) public LPTokenWrapper(_stakeToken) { 90 | tokenCapAmount = _tokenCapAmount; 91 | boostToken = _boostToken; 92 | treasury = ITreasury(_treasury); 93 | stablecoin = treasury.defaultToken(); 94 | swapRouter = _swapRouter; 95 | starttime = _starttime; 96 | lastBoostPurchase = _starttime; 97 | duration = _duration; 98 | boostToken.safeApprove(address(_swapRouter), uint256(-1)); 99 | stablecoin.safeApprove(address(treasury), uint256(-1)); 100 | } 101 | 102 | function lastTimeRewardApplicable() public view returns (uint256) { 103 | return Math.min(block.timestamp, periodFinish); 104 | } 105 | 106 | function rewardPerToken() public view returns (uint256) { 107 | if (boostedTotalSupply == 0) { 108 | return rewardPerTokenStored; 109 | } 110 | return 111 | rewardPerTokenStored.add( 112 | lastTimeRewardApplicable() 113 | .sub(lastUpdateTime) 114 | .mul(rewardRate) 115 | .mul(1e18) 116 | .div(boostedTotalSupply) 117 | ); 118 | } 119 | 120 | function earned(address account) public view returns (uint256) { 121 | return 122 | boostedBalances[account] 123 | .mul(rewardPerToken().sub(userRewardPerTokenPaid[account])) 124 | .div(1e18) 125 | .add(rewards[account]); 126 | } 127 | 128 | function getBoosterPrice(address user) 129 | public view returns (uint256 boosterPrice, uint256 newBoostBalance) 130 | { 131 | if (boostedTotalSupply == 0) return (0,0); 132 | 133 | // 5% increase for each previously user-purchased booster 134 | uint256 boostersBought = numBoostersBought[user]; 135 | boosterPrice = globalBoosterPrice.mul(boostersBought.mul(5).add(100)).div(100); 136 | 137 | // increment boostersBought by 1 138 | boostersBought = boostersBought.add(1); 139 | 140 | // if no. of boosters exceed threshold, increase booster price by boostScaleFactor; 141 | if (boostersBought >= boostThreshold) { 142 | boosterPrice = boosterPrice 143 | .mul((boostersBought.sub(boostThreshold)).mul(boostScaleFactor).add(100)) 144 | .div(100); 145 | } 146 | 147 | // 2.5% decrease for every 2 hour interval since last global boost purchase 148 | boosterPrice = pow(boosterPrice, 975, 1000, (block.timestamp.sub(lastBoostPurchase)).div(2 hours)); 149 | 150 | // adjust price based on expected increase in boost supply 151 | // boostersBought has been incremented by 1 already 152 | newBoostBalance = balanceOf(user) 153 | .mul(boostersBought.mul(5).add(100)) 154 | .div(100); 155 | uint256 boostBalanceIncrease = newBoostBalance.sub(boostedBalances[user]); 156 | boosterPrice = boosterPrice 157 | .mul(boostBalanceIncrease) 158 | .mul(scaleFactor) 159 | .div(boostedTotalSupply); 160 | } 161 | 162 | // stake visibility is public as overriding LPTokenWrapper's stake() function 163 | function stake(uint256 amount) public updateReward(msg.sender) checkStart { 164 | require(amount > 0, "Cannot stake 0"); 165 | super.stake(amount); 166 | 167 | // check user cap 168 | require( 169 | balanceOf(msg.sender) <= tokenCapAmount || block.timestamp >= starttime.add(86400), 170 | "token cap exceeded" 171 | ); 172 | 173 | // boosters do not affect new amounts 174 | boostedBalances[msg.sender] = boostedBalances[msg.sender].add(amount); 175 | boostedTotalSupply = boostedTotalSupply.add(amount); 176 | 177 | _getReward(msg.sender); 178 | 179 | // transfer token last, to follow CEI pattern 180 | stakeToken.safeTransferFrom(msg.sender, address(this), amount); 181 | } 182 | 183 | function withdraw(uint256 amount) public updateReward(msg.sender) checkStart { 184 | require(amount > 0, "Cannot withdraw 0"); 185 | super.withdraw(amount); 186 | 187 | // reset boosts :( 188 | numBoostersBought[msg.sender] = 0; 189 | 190 | // update boosted balance and supply 191 | updateBoostBalanceAndSupply(msg.sender, 0); 192 | 193 | // in case _getReward function fails, continue 194 | (bool success, ) = address(this).call( 195 | abi.encodeWithSignature( 196 | "_getReward(address)", 197 | msg.sender 198 | ) 199 | ); 200 | // to remove compiler warning 201 | success; 202 | 203 | // transfer token last, to follow CEI pattern 204 | stakeToken.safeTransfer(msg.sender, amount); 205 | } 206 | 207 | function getReward() public updateReward(msg.sender) checkStart { 208 | _getReward(msg.sender); 209 | } 210 | 211 | function exit() external { 212 | withdraw(balanceOf(msg.sender)); 213 | } 214 | 215 | function setScaleFactorsAndThreshold( 216 | uint256 _boostThreshold, 217 | uint256 _boostScaleFactor, 218 | uint256 _scaleFactor 219 | ) external onlyOwner 220 | { 221 | boostThreshold = _boostThreshold; 222 | boostScaleFactor = _boostScaleFactor; 223 | scaleFactor = _scaleFactor; 224 | } 225 | 226 | function boost() external updateReward(msg.sender) checkStart { 227 | require( 228 | block.timestamp > nextBoostPurchaseTime[msg.sender], 229 | "early boost purchase" 230 | ); 231 | 232 | // save current booster price, since transfer is done last 233 | // since getBoosterPrice() returns new boost balance, avoid re-calculation 234 | (uint256 boosterAmount, uint256 newBoostBalance) = getBoosterPrice(msg.sender); 235 | // user's balance and boostedSupply will be changed in this function 236 | applyBoost(msg.sender, newBoostBalance); 237 | 238 | _getReward(msg.sender); 239 | 240 | boostToken.safeTransferFrom(msg.sender, address(this), boosterAmount); 241 | 242 | IERC20Burnable burnableBoostToken = IERC20Burnable(address(boostToken)); 243 | 244 | // burn 25% 245 | uint256 burnAmount = boosterAmount.div(4); 246 | burnableBoostToken.burn(burnAmount); 247 | boosterAmount = boosterAmount.sub(burnAmount); 248 | 249 | // swap to stablecoin 250 | address[] memory routeDetails = new address[](3); 251 | routeDetails[0] = address(boostToken); 252 | routeDetails[1] = swapRouter.WETH(); 253 | routeDetails[2] = address(stablecoin); 254 | uint[] memory amounts = swapRouter.swapExactTokensForTokens( 255 | boosterAmount, 256 | 0, 257 | routeDetails, 258 | address(this), 259 | block.timestamp + 100 260 | ); 261 | 262 | // transfer to treasury 263 | // index 2 = final output amt 264 | treasury.deposit(stablecoin, amounts[2]); 265 | } 266 | 267 | function notifyRewardAmount(uint256 reward) 268 | external 269 | onlyOwner 270 | updateReward(address(0)) 271 | { 272 | rewardRate = reward.div(duration); 273 | lastUpdateTime = starttime; 274 | periodFinish = starttime.add(duration); 275 | emit RewardAdded(reward); 276 | } 277 | 278 | function updateBoostBalanceAndSupply(address user, uint256 newBoostBalance) internal { 279 | // subtract existing balance from boostedSupply 280 | boostedTotalSupply = boostedTotalSupply.sub(boostedBalances[user]); 281 | 282 | // when applying boosts, 283 | // newBoostBalance has already been calculated in getBoosterPrice() 284 | if (newBoostBalance == 0) { 285 | // each booster adds 5% to current stake amount 286 | newBoostBalance = balanceOf(user).mul(numBoostersBought[user].mul(5).add(100)).div(100); 287 | } 288 | 289 | // update user's boosted balance 290 | boostedBalances[user] = newBoostBalance; 291 | 292 | // update boostedSupply 293 | boostedTotalSupply = boostedTotalSupply.add(newBoostBalance); 294 | } 295 | 296 | function applyBoost(address user, uint256 newBoostBalance) internal { 297 | // increase no. of boosters bought 298 | numBoostersBought[user] = numBoostersBought[user].add(1); 299 | 300 | updateBoostBalanceAndSupply(user, newBoostBalance); 301 | 302 | // increase next purchase eligibility by an hour 303 | nextBoostPurchaseTime[user] = block.timestamp.add(3600); 304 | 305 | // increase global booster price by 1% 306 | globalBoosterPrice = globalBoosterPrice.mul(101).div(100); 307 | 308 | lastBoostPurchase = block.timestamp; 309 | } 310 | 311 | function _getReward(address user) internal { 312 | uint256 reward = earned(user); 313 | if (reward > 0) { 314 | rewards[user] = 0; 315 | boostToken.safeTransfer(user, reward); 316 | emit RewardPaid(user, reward); 317 | } 318 | } 319 | 320 | /// Imported from: https://forum.openzeppelin.com/t/does-safemath-library-need-a-safe-power-function/871/7 321 | /// Modified so that it takes in 3 arguments for base 322 | /// @return a * (b / c)^exponent 323 | function pow(uint256 a, uint256 b, uint256 c, uint256 exponent) internal pure returns (uint256) { 324 | if (exponent == 0) { 325 | return a; 326 | } 327 | else if (exponent == 1) { 328 | return a.mul(b).div(c); 329 | } 330 | else if (a == 0 && exponent != 0) { 331 | return 0; 332 | } 333 | else { 334 | uint256 z = a.mul(b).div(c); 335 | for (uint256 i = 1; i < exponent; i++) 336 | z = z.mul(b).div(c); 337 | return z; 338 | } 339 | } 340 | } 341 | -------------------------------------------------------------------------------- /contracts/BoostToken.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | /* 3 | * MIT License 4 | * =========== 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | */ 23 | 24 | pragma solidity ^0.5.17; 25 | 26 | interface IERC20 { 27 | function totalSupply() external view returns (uint); 28 | function balanceOf(address account) external view returns (uint); 29 | function transfer(address recipient, uint amount) external returns (bool); 30 | function allowance(address owner, address spender) external view returns (uint); 31 | function approve(address spender, uint amount) external returns (bool); 32 | function transferFrom(address sender, address recipient, uint amount) external returns (bool); 33 | event Transfer(address indexed from, address indexed to, uint value); 34 | event Approval(address indexed owner, address indexed spender, uint value); 35 | } 36 | 37 | contract Context { 38 | constructor () internal { } 39 | // solhint-disable-previous-line no-empty-blocks 40 | 41 | function _msgSender() internal view returns (address payable) { 42 | return msg.sender; 43 | } 44 | } 45 | 46 | contract ERC20 is Context, IERC20 { 47 | using SafeMath for uint; 48 | 49 | mapping (address => uint) private _balances; 50 | 51 | mapping (address => mapping (address => uint)) private _allowances; 52 | 53 | uint private _totalSupply; 54 | function totalSupply() public view returns (uint) { 55 | return _totalSupply; 56 | } 57 | function balanceOf(address account) public view returns (uint) { 58 | return _balances[account]; 59 | } 60 | function transfer(address recipient, uint amount) public returns (bool) { 61 | _transfer(_msgSender(), recipient, amount); 62 | return true; 63 | } 64 | function allowance(address owner, address spender) public view returns (uint) { 65 | return _allowances[owner][spender]; 66 | } 67 | function approve(address spender, uint amount) public returns (bool) { 68 | _approve(_msgSender(), spender, amount); 69 | return true; 70 | } 71 | function transferFrom(address sender, address recipient, uint amount) public returns (bool) { 72 | _transfer(sender, recipient, amount); 73 | _approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, "ERC20: transfer amount exceeds allowance")); 74 | return true; 75 | } 76 | function increaseAllowance(address spender, uint addedValue) public returns (bool) { 77 | _approve(_msgSender(), spender, _allowances[_msgSender()][spender].add(addedValue)); 78 | return true; 79 | } 80 | function decreaseAllowance(address spender, uint subtractedValue) public returns (bool) { 81 | _approve(_msgSender(), spender, _allowances[_msgSender()][spender].sub(subtractedValue, "ERC20: decreased allowance below zero")); 82 | return true; 83 | } 84 | function _transfer(address sender, address recipient, uint amount) internal { 85 | require(sender != address(0), "ERC20: transfer from the zero address"); 86 | require(recipient != address(0), "ERC20: transfer to the zero address"); 87 | 88 | _balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance"); 89 | _balances[recipient] = _balances[recipient].add(amount); 90 | emit Transfer(sender, recipient, amount); 91 | } 92 | function _mint(address account, uint amount) internal { 93 | require(account != address(0), "ERC20: mint to the zero address"); 94 | 95 | _totalSupply = _totalSupply.add(amount); 96 | require(_totalSupply <= 1e23, "_totalSupply exceed hard limit"); 97 | _balances[account] = _balances[account].add(amount); 98 | emit Transfer(address(0), account, amount); 99 | } 100 | function _burn(address account, uint amount) internal { 101 | require(account != address(0), "ERC20: burn from the zero address"); 102 | 103 | _balances[account] = _balances[account].sub(amount, "ERC20: burn amount exceeds balance"); 104 | _totalSupply = _totalSupply.sub(amount); 105 | emit Transfer(account, address(0), amount); 106 | } 107 | function _approve(address owner, address spender, uint amount) internal { 108 | require(owner != address(0), "ERC20: approve from the zero address"); 109 | require(spender != address(0), "ERC20: approve to the zero address"); 110 | 111 | _allowances[owner][spender] = amount; 112 | emit Approval(owner, spender, amount); 113 | } 114 | } 115 | 116 | contract ERC20Detailed is IERC20 { 117 | string private _name; 118 | string private _symbol; 119 | uint8 private _decimals; 120 | 121 | constructor (string memory name, string memory symbol, uint8 decimals) public { 122 | _name = name; 123 | _symbol = symbol; 124 | _decimals = decimals; 125 | } 126 | function name() public view returns (string memory) { 127 | return _name; 128 | } 129 | function symbol() public view returns (string memory) { 130 | return _symbol; 131 | } 132 | function decimals() public view returns (uint8) { 133 | return _decimals; 134 | } 135 | } 136 | 137 | library SafeMath { 138 | function add(uint a, uint b) internal pure returns (uint) { 139 | uint c = a + b; 140 | require(c >= a, "SafeMath: addition overflow"); 141 | 142 | return c; 143 | } 144 | function sub(uint a, uint b) internal pure returns (uint) { 145 | return sub(a, b, "SafeMath: subtraction overflow"); 146 | } 147 | function sub(uint a, uint b, string memory errorMessage) internal pure returns (uint) { 148 | require(b <= a, errorMessage); 149 | uint c = a - b; 150 | 151 | return c; 152 | } 153 | function mul(uint a, uint b) internal pure returns (uint) { 154 | if (a == 0) { 155 | return 0; 156 | } 157 | 158 | uint c = a * b; 159 | require(c / a == b, "SafeMath: multiplication overflow"); 160 | 161 | return c; 162 | } 163 | function div(uint a, uint b) internal pure returns (uint) { 164 | return div(a, b, "SafeMath: division by zero"); 165 | } 166 | function div(uint a, uint b, string memory errorMessage) internal pure returns (uint) { 167 | // Solidity only automatically asserts when dividing by 0 168 | require(b > 0, errorMessage); 169 | uint c = a / b; 170 | 171 | return c; 172 | } 173 | } 174 | 175 | library Address { 176 | function isContract(address account) internal view returns (bool) { 177 | bytes32 codehash; 178 | bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470; 179 | // solhint-disable-next-line no-inline-assembly 180 | assembly { codehash := extcodehash(account) } 181 | return (codehash != 0x0 && codehash != accountHash); 182 | } 183 | } 184 | 185 | library SafeERC20 { 186 | using SafeMath for uint; 187 | using Address for address; 188 | 189 | function safeTransfer(IERC20 token, address to, uint value) internal { 190 | callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); 191 | } 192 | 193 | function safeTransferFrom(IERC20 token, address from, address to, uint value) internal { 194 | callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value)); 195 | } 196 | 197 | function safeApprove(IERC20 token, address spender, uint value) internal { 198 | require((value == 0) || (token.allowance(address(this), spender) == 0), 199 | "SafeERC20: approve from non-zero to non-zero allowance" 200 | ); 201 | callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value)); 202 | } 203 | function callOptionalReturn(IERC20 token, bytes memory data) private { 204 | require(address(token).isContract(), "SafeERC20: call to non-contract"); 205 | 206 | // solhint-disable-next-line avoid-low-level-calls 207 | (bool success, bytes memory returndata) = address(token).call(data); 208 | require(success, "SafeERC20: low-level call failed"); 209 | 210 | if (returndata.length > 0) { // Return data is optional 211 | // solhint-disable-next-line max-line-length 212 | require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed"); 213 | } 214 | } 215 | } 216 | 217 | contract BoostToken is ERC20, ERC20Detailed { 218 | using SafeERC20 for IERC20; 219 | using Address for address; 220 | using SafeMath for uint; 221 | 222 | 223 | address public governance; 224 | mapping (address => bool) public minters; 225 | 226 | constructor () public ERC20Detailed("Boosted Finance", "BOOST", 18) { 227 | governance = msg.sender; 228 | addMinter(governance); 229 | // underlying _mint function has hard limit 230 | mint(governance, 1e23); 231 | } 232 | 233 | function mint(address account, uint amount) public { 234 | require(minters[msg.sender], "!minter"); 235 | _mint(account, amount); 236 | } 237 | 238 | function setGovernance(address _governance) public { 239 | require(msg.sender == governance, "!governance"); 240 | governance = _governance; 241 | } 242 | 243 | function addMinter(address _minter) public { 244 | require(msg.sender == governance, "!governance"); 245 | minters[_minter] = true; 246 | } 247 | 248 | function removeMinter(address _minter) public { 249 | require(msg.sender == governance, "!governance"); 250 | minters[_minter] = false; 251 | } 252 | 253 | function burn(uint256 amount) public { 254 | _burn(msg.sender, amount); 255 | } 256 | } 257 | -------------------------------------------------------------------------------- /contracts/IERC20.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: GPL-3.0-only 2 | 3 | pragma solidity 0.5.17; 4 | 5 | 6 | /** 7 | * @dev Interface of the ERC20 standard as defined in the EIP. Does not include 8 | * the optional functions; to access them see {ERC20Detailed}. 9 | */ 10 | interface IERC20 { 11 | /** 12 | * @dev Returns the amount of tokens in existence. 13 | */ 14 | function totalSupply() external view returns (uint256); 15 | 16 | /** 17 | * @dev Returns the amount of tokens owned by `account`. 18 | */ 19 | function balanceOf(address account) external view returns (uint256); 20 | 21 | /** 22 | * @dev Moves `amount` tokens from the caller's account to `recipient`. 23 | * 24 | * Returns a boolean value indicating whether the operation succeeded. 25 | * 26 | * Emits a {Transfer} event. 27 | */ 28 | function transfer(address recipient, uint256 amount) external returns (bool); 29 | 30 | /** 31 | * @dev Returns the remaining number of tokens that `spender` will be 32 | * allowed to spend on behalf of `owner` through {transferFrom}. This is 33 | * zero by default. 34 | * 35 | * This value changes when {approve} or {transferFrom} are called. 36 | */ 37 | function allowance(address owner, address spender) external view returns (uint256); 38 | 39 | /** 40 | * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. 41 | * 42 | * Returns a boolean value indicating whether the operation succeeded. 43 | * 44 | * IMPORTANT: Beware that changing an allowance with this method brings the risk 45 | * that someone may use both the old and the new allowance by unfortunate 46 | * transaction ordering. One possible solution to mitigate this race 47 | * condition is to first reduce the spender's allowance to 0 and set the 48 | * desired value afterwards: 49 | * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 50 | * 51 | * Emits an {Approval} event. 52 | */ 53 | function approve(address spender, uint256 amount) external returns (bool); 54 | 55 | /** 56 | * @dev Moves `amount` tokens from `sender` to `recipient` using the 57 | * allowance mechanism. `amount` is then deducted from the caller's 58 | * allowance. 59 | * 60 | * Returns a boolean value indicating whether the operation succeeded. 61 | * 62 | * Emits a {Transfer} event. 63 | */ 64 | function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); 65 | 66 | /** 67 | * @dev Emitted when `value` tokens are moved from one account (`from`) to 68 | * another (`to`). 69 | * 70 | * Note that `value` may be zero. 71 | */ 72 | event Transfer(address indexed from, address indexed to, uint256 value); 73 | 74 | /** 75 | * @dev Emitted when the allowance of a `spender` for an `owner` is set by 76 | * a call to {approve}. `value` is the new allowance. 77 | */ 78 | event Approval(address indexed owner, address indexed spender, uint256 value); 79 | } 80 | -------------------------------------------------------------------------------- /contracts/IERC20Burnable.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: GPL-3.0-only 2 | 3 | pragma solidity 0.5.17; 4 | 5 | 6 | interface IERC20Burnable { 7 | function totalSupply() external view returns (uint256); 8 | function balanceOf(address account) external view returns (uint256); 9 | function transfer(address recipient, uint256 amount) external returns (bool); 10 | function allowance(address owner, address spender) external view returns (uint256); 11 | function approve(address spender, uint256 amount) external returns (bool); 12 | function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); 13 | function burn(uint256 amount) external; 14 | event Transfer(address indexed from, address indexed to, uint256 value); 15 | event Approval(address indexed owner, address indexed spender, uint256 value); 16 | } 17 | -------------------------------------------------------------------------------- /contracts/ISwapRouter.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: GPL-3.0-only 2 | 3 | pragma solidity 0.5.17; 4 | 5 | 6 | interface SwapRouter { 7 | function WETH() external pure returns (address); 8 | function swapExactTokensForTokens( 9 | uint amountIn, 10 | uint amountOutMin, 11 | address[] calldata path, 12 | address to, 13 | uint deadline 14 | ) external returns (uint[] memory amounts); 15 | } 16 | -------------------------------------------------------------------------------- /contracts/ITreasury.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | import "./IERC20.sol"; 4 | 5 | 6 | interface ITreasury { 7 | function defaultToken() external view returns (IERC20); 8 | function deposit(IERC20 token, uint256 amount) external; 9 | function withdraw(uint256 amount, address withdrawAddress) external; 10 | } 11 | -------------------------------------------------------------------------------- /contracts/IUniswapV2Factory.sol: -------------------------------------------------------------------------------- 1 | 2 | //SPDX-License-Identifier: GPL-3.0-only 3 | 4 | pragma solidity 0.5.17; 5 | 6 | 7 | interface IUniswapV2Factory { 8 | function getPair(address tokenA, address tokenB) external view returns (address pair); 9 | function createPair(address tokenA, address tokenB) external returns (address pair); 10 | } 11 | -------------------------------------------------------------------------------- /contracts/LPTokenWrapper.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: GPL-3.0-only 2 | 3 | 4 | pragma solidity ^0.5.17; 5 | 6 | import "./SafeMath.sol"; 7 | import "./zeppelin/SafeERC20.sol"; 8 | 9 | 10 | contract LPTokenWrapper { 11 | using SafeMath for uint256; 12 | using SafeERC20 for IERC20; 13 | 14 | IERC20 public stakeToken; 15 | 16 | uint256 private _totalSupply; 17 | mapping(address => uint256) private _balances; 18 | 19 | constructor(IERC20 _stakeToken) public { 20 | stakeToken = _stakeToken; 21 | } 22 | 23 | function totalSupply() public view returns (uint256) { 24 | return _totalSupply; 25 | } 26 | 27 | function balanceOf(address account) public view returns (uint256) { 28 | return _balances[account]; 29 | } 30 | 31 | function stake(uint256 amount) public { 32 | _totalSupply = _totalSupply.add(amount); 33 | _balances[msg.sender] = _balances[msg.sender].add(amount); 34 | // safeTransferFrom shifted to overriden method 35 | } 36 | 37 | function withdraw(uint256 amount) public { 38 | _totalSupply = _totalSupply.sub(amount); 39 | _balances[msg.sender] = _balances[msg.sender].sub(amount); 40 | // safeTransferFrom shifted to overriden method 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /contracts/LPTokenWrapperWithSlash.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | import "./SafeMath.sol"; 4 | import "./zeppelin/SafeERC20.sol"; 5 | 6 | 7 | contract LPTokenWrapperWithSlash { 8 | using SafeMath for uint256; 9 | using SafeERC20 for IERC20; 10 | 11 | IERC20 public stakeToken; 12 | 13 | uint256 private _totalSupply; 14 | mapping(address => uint256) private _balances; 15 | 16 | constructor(IERC20 _stakeToken) public { 17 | stakeToken = _stakeToken; 18 | } 19 | 20 | function totalSupply() public view returns (uint256) { 21 | return _totalSupply; 22 | } 23 | 24 | function balanceOf(address account) public view returns (uint256) { 25 | return _balances[account]; 26 | } 27 | 28 | function stake(uint256 amount) public { 29 | _totalSupply = _totalSupply.add(amount); 30 | _balances[msg.sender] = _balances[msg.sender].add(amount); 31 | // safeTransferFrom shifted to overriden method 32 | } 33 | 34 | function withdraw(uint256 amount) public { 35 | _totalSupply = _totalSupply.sub(amount); 36 | _balances[msg.sender] = _balances[msg.sender].sub(amount); 37 | // safeTransferFrom shifted to overriden method 38 | } 39 | 40 | function slash(address proposer) internal returns (uint256 amount) { 41 | amount = _balances[proposer]; 42 | _totalSupply = _totalSupply.sub(amount); 43 | _balances[proposer] = 0; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /contracts/SafeMath.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | // Note: This file has been modified to include the sqrt function for quadratic voting 4 | /** 5 | * @dev Standard math utilities missing in the Solidity language. 6 | */ 7 | library Math { 8 | /** 9 | * @dev Returns the largest of two numbers. 10 | */ 11 | function max(uint256 a, uint256 b) internal pure returns (uint256) { 12 | return a >= b ? a : b; 13 | } 14 | 15 | /** 16 | * @dev Returns the smallest of two numbers. 17 | */ 18 | function min(uint256 a, uint256 b) internal pure returns (uint256) { 19 | return a < b ? a : b; 20 | } 21 | 22 | /** 23 | * @dev Returns the average of two numbers. The result is rounded towards 24 | * zero. 25 | */ 26 | function average(uint256 a, uint256 b) internal pure returns (uint256) { 27 | // (a + b) / 2 can overflow, so we distribute 28 | return (a / 2) + (b / 2) + ((a % 2 + b % 2) / 2); 29 | } 30 | 31 | /** 32 | * Imported from: https://github.com/alianse777/solidity-standard-library/blob/master/Math.sol 33 | * @dev Compute square root of x 34 | * @return sqrt(x) 35 | */ 36 | function sqrt(uint256 x) internal pure returns (uint256) { 37 | uint256 n = x / 2; 38 | uint256 lstX = 0; 39 | while (n != lstX){ 40 | lstX = n; 41 | n = (n + x/n) / 2; 42 | } 43 | return uint256(n); 44 | } 45 | } 46 | 47 | /** 48 | * @dev Wrappers over Solidity's arithmetic operations with added overflow 49 | * checks. 50 | * 51 | * Arithmetic operations in Solidity wrap on overflow. This can easily result 52 | * in bugs, because programmers usually assume that an overflow raises an 53 | * error, which is the standard behavior in high level programming languages. 54 | * `SafeMath` restores this intuition by reverting the transaction when an 55 | * operation overflows. 56 | * 57 | * Using this library instead of the unchecked operations eliminates an entire 58 | * class of bugs, so it's recommended to use it always. 59 | */ 60 | library SafeMath { 61 | /** 62 | * @dev Returns the addition of two unsigned integers, reverting on 63 | * overflow. 64 | * 65 | * Counterpart to Solidity's `+` operator. 66 | * 67 | * Requirements: 68 | * - Addition cannot overflow. 69 | */ 70 | function add(uint256 a, uint256 b) internal pure returns (uint256) { 71 | uint256 c = a + b; 72 | require(c >= a, "SafeMath: addition overflow"); 73 | 74 | return c; 75 | } 76 | 77 | /** 78 | * @dev Returns the subtraction of two unsigned integers, reverting on 79 | * overflow (when the result is negative). 80 | * 81 | * Counterpart to Solidity's `-` operator. 82 | * 83 | * Requirements: 84 | * - Subtraction cannot overflow. 85 | */ 86 | function sub(uint256 a, uint256 b) internal pure returns (uint256) { 87 | return sub(a, b, "SafeMath: subtraction overflow"); 88 | } 89 | 90 | /** 91 | * @dev Returns the subtraction of two unsigned integers, reverting with custom message on 92 | * overflow (when the result is negative). 93 | * 94 | * Counterpart to Solidity's `-` operator. 95 | * 96 | * Requirements: 97 | * - Subtraction cannot overflow. 98 | * 99 | * _Available since v2.4.0._ 100 | */ 101 | function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { 102 | require(b <= a, errorMessage); 103 | uint256 c = a - b; 104 | 105 | return c; 106 | } 107 | 108 | /** 109 | * @dev Returns the multiplication of two unsigned integers, reverting on 110 | * overflow. 111 | * 112 | * Counterpart to Solidity's `*` operator. 113 | * 114 | * Requirements: 115 | * - Multiplication cannot overflow. 116 | */ 117 | function mul(uint256 a, uint256 b) internal pure returns (uint256) { 118 | // Gas optimization: this is cheaper than requiring 'a' not being zero, but the 119 | // benefit is lost if 'b' is also tested. 120 | // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 121 | if (a == 0) { 122 | return 0; 123 | } 124 | 125 | uint256 c = a * b; 126 | require(c / a == b, "SafeMath: multiplication overflow"); 127 | 128 | return c; 129 | } 130 | 131 | /** 132 | * @dev Returns the integer division of two unsigned integers. Reverts on 133 | * division by zero. The result is rounded towards zero. 134 | * 135 | * Counterpart to Solidity's `/` operator. Note: this function uses a 136 | * `revert` opcode (which leaves remaining gas untouched) while Solidity 137 | * uses an invalid opcode to revert (consuming all remaining gas). 138 | * 139 | * Requirements: 140 | * - The divisor cannot be zero. 141 | */ 142 | function div(uint256 a, uint256 b) internal pure returns (uint256) { 143 | return div(a, b, "SafeMath: division by zero"); 144 | } 145 | 146 | /** 147 | * @dev Returns the integer division of two unsigned integers. Reverts with custom message on 148 | * division by zero. The result is rounded towards zero. 149 | * 150 | * Counterpart to Solidity's `/` operator. Note: this function uses a 151 | * `revert` opcode (which leaves remaining gas untouched) while Solidity 152 | * uses an invalid opcode to revert (consuming all remaining gas). 153 | * 154 | * Requirements: 155 | * - The divisor cannot be zero. 156 | * 157 | * _Available since v2.4.0._ 158 | */ 159 | function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { 160 | // Solidity only automatically asserts when dividing by 0 161 | require(b > 0, errorMessage); 162 | uint256 c = a / b; 163 | // assert(a == b * c + a % b); // There is no case in which this doesn't hold 164 | 165 | return c; 166 | } 167 | 168 | /** 169 | * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), 170 | * Reverts when dividing by zero. 171 | * 172 | * Counterpart to Solidity's `%` operator. This function uses a `revert` 173 | * opcode (which leaves remaining gas untouched) while Solidity uses an 174 | * invalid opcode to revert (consuming all remaining gas). 175 | * 176 | * Requirements: 177 | * - The divisor cannot be zero. 178 | */ 179 | function mod(uint256 a, uint256 b) internal pure returns (uint256) { 180 | return mod(a, b, "SafeMath: modulo by zero"); 181 | } 182 | 183 | /** 184 | * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), 185 | * Reverts with custom message when dividing by zero. 186 | * 187 | * Counterpart to Solidity's `%` operator. This function uses a `revert` 188 | * opcode (which leaves remaining gas untouched) while Solidity uses an 189 | * invalid opcode to revert (consuming all remaining gas). 190 | * 191 | * Requirements: 192 | * - The divisor cannot be zero. 193 | * 194 | * _Available since v2.4.0._ 195 | */ 196 | function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { 197 | require(b != 0, errorMessage); 198 | return a % b; 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /contracts/Treasury.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | /* 3 | * MIT License 4 | * =========== 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | */ 23 | 24 | pragma solidity 0.5.17; 25 | 26 | import "./SafeMath.sol"; 27 | import "./zeppelin/SafeERC20.sol"; 28 | import "./zeppelin/Ownable.sol"; 29 | import "./IERC20.sol"; 30 | import "./ITreasury.sol"; 31 | import "./ISwapRouter.sol"; 32 | 33 | 34 | contract Treasury is Ownable, ITreasury { 35 | using SafeMath for uint256; 36 | using SafeERC20 for IERC20; 37 | 38 | IERC20 public defaultToken; 39 | SwapRouter public swapRouter; 40 | address public ecoFund; 41 | address public gov; 42 | address internal govSetter; 43 | 44 | mapping(address => uint256) public ecoFundAmts; 45 | 46 | // 1% = 100 47 | uint256 public constant MAX_FUND_PERCENTAGE = 1500; // 15% 48 | uint256 public constant PERCENTAGE_PRECISION = 10000; // 100% 49 | uint256 public fundPercentage = 500; // 5% 50 | 51 | 52 | constructor(SwapRouter _swapRouter, IERC20 _defaultToken, address _ecoFund) public { 53 | swapRouter = _swapRouter; 54 | defaultToken = _defaultToken; 55 | ecoFund = _ecoFund; 56 | govSetter = msg.sender; 57 | } 58 | 59 | function setGov(address _gov) external { 60 | require(msg.sender == govSetter, "not authorized"); 61 | gov = _gov; 62 | govSetter = address(0); 63 | } 64 | 65 | function setSwapRouter(SwapRouter _swapRouter) external onlyOwner { 66 | swapRouter = _swapRouter; 67 | } 68 | 69 | function setEcoFund(address _ecoFund) external onlyOwner { 70 | ecoFund = _ecoFund; 71 | } 72 | 73 | function setFundPercentage(uint256 _fundPercentage) external onlyOwner { 74 | require(_fundPercentage <= MAX_FUND_PERCENTAGE, "exceed max percent"); 75 | fundPercentage = _fundPercentage; 76 | } 77 | 78 | function balanceOf(IERC20 token) public view returns (uint256) { 79 | return token.balanceOf(address(this)).sub(ecoFundAmts[address(token)]); 80 | } 81 | 82 | function deposit(IERC20 token, uint256 amount) external { 83 | token.safeTransferFrom(msg.sender, address(this), amount); 84 | // portion allocated to ecoFund 85 | ecoFundAmts[address(token)] = amount.mul(fundPercentage).div(PERCENTAGE_PRECISION); 86 | } 87 | 88 | // only default token withdrawals allowed 89 | function withdraw(uint256 amount, address withdrawAddress) external { 90 | require(msg.sender == gov, "caller not gov"); 91 | require(balanceOf(defaultToken) >= amount, "insufficient funds"); 92 | defaultToken.safeTransfer(withdrawAddress, amount); 93 | } 94 | 95 | function convertToDefaultToken(address[] calldata routeDetails, uint256 amount) external { 96 | require(routeDetails[0] != address(defaultToken), "src can't be defaultToken"); 97 | require(routeDetails[routeDetails.length - 1] == address(defaultToken), "dest not defaultToken"); 98 | IERC20 srcToken = IERC20(routeDetails[0]); 99 | require(balanceOf(srcToken) >= amount, "insufficient funds"); 100 | if (srcToken.allowance(address(this), address(swapRouter)) <= amount) { 101 | srcToken.safeApprove(address(swapRouter), uint256(-1)); 102 | } 103 | swapRouter.swapExactTokensForTokens( 104 | amount, 105 | 0, 106 | routeDetails, 107 | address(this), 108 | block.timestamp + 100 109 | ); 110 | } 111 | 112 | function withdrawEcoFund(IERC20 token, uint256 amount) external { 113 | ecoFundAmts[address(token)] = ecoFundAmts[address(token)].sub(amount); 114 | token.safeTransfer(ecoFund, amount); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /contracts/V1/BoostGov.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | /* 3 | * MIT License 4 | * =========== 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | */ 23 | 24 | pragma solidity ^0.5.17; 25 | 26 | /** 27 | * @dev Wrappers over Solidity's arithmetic operations with added overflow 28 | * checks. 29 | * 30 | * Arithmetic operations in Solidity wrap on overflow. This can easily result 31 | * in bugs, because programmers usually assume that an overflow raises an 32 | * error, which is the standard behavior in high level programming languages. 33 | * `SafeMath` restores this intuition by reverting the transaction when an 34 | * operation overflows. 35 | * 36 | * Using this library instead of the unchecked operations eliminates an entire 37 | * class of bugs, so it's recommended to use it always. 38 | */ 39 | library SafeMath { 40 | /** 41 | * @dev Returns the addition of two unsigned integers, reverting on 42 | * overflow. 43 | * 44 | * Counterpart to Solidity's `+` operator. 45 | * 46 | * Requirements: 47 | * - Addition cannot overflow. 48 | */ 49 | function add(uint256 a, uint256 b) internal pure returns (uint256) { 50 | uint256 c = a + b; 51 | require(c >= a, "SafeMath: addition overflow"); 52 | 53 | return c; 54 | } 55 | 56 | /** 57 | * @dev Returns the subtraction of two unsigned integers, reverting on 58 | * overflow (when the result is negative). 59 | * 60 | * Counterpart to Solidity's `-` operator. 61 | * 62 | * Requirements: 63 | * - Subtraction cannot overflow. 64 | */ 65 | function sub(uint256 a, uint256 b) internal pure returns (uint256) { 66 | return sub(a, b, "SafeMath: subtraction overflow"); 67 | } 68 | 69 | /** 70 | * @dev Returns the subtraction of two unsigned integers, reverting with custom message on 71 | * overflow (when the result is negative). 72 | * 73 | * Counterpart to Solidity's `-` operator. 74 | * 75 | * Requirements: 76 | * - Subtraction cannot overflow. 77 | * 78 | * _Available since v2.4.0._ 79 | */ 80 | function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { 81 | require(b <= a, errorMessage); 82 | uint256 c = a - b; 83 | 84 | return c; 85 | } 86 | 87 | /** 88 | * @dev Returns the multiplication of two unsigned integers, reverting on 89 | * overflow. 90 | * 91 | * Counterpart to Solidity's `*` operator. 92 | * 93 | * Requirements: 94 | * - Multiplication cannot overflow. 95 | */ 96 | function mul(uint256 a, uint256 b) internal pure returns (uint256) { 97 | // Gas optimization: this is cheaper than requiring 'a' not being zero, but the 98 | // benefit is lost if 'b' is also tested. 99 | // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 100 | if (a == 0) { 101 | return 0; 102 | } 103 | 104 | uint256 c = a * b; 105 | require(c / a == b, "SafeMath: multiplication overflow"); 106 | 107 | return c; 108 | } 109 | 110 | /** 111 | * @dev Returns the integer division of two unsigned integers. Reverts on 112 | * division by zero. The result is rounded towards zero. 113 | * 114 | * Counterpart to Solidity's `/` operator. Note: this function uses a 115 | * `revert` opcode (which leaves remaining gas untouched) while Solidity 116 | * uses an invalid opcode to revert (consuming all remaining gas). 117 | * 118 | * Requirements: 119 | * - The divisor cannot be zero. 120 | */ 121 | function div(uint256 a, uint256 b) internal pure returns (uint256) { 122 | return div(a, b, "SafeMath: division by zero"); 123 | } 124 | 125 | /** 126 | * @dev Returns the integer division of two unsigned integers. Reverts with custom message on 127 | * division by zero. The result is rounded towards zero. 128 | * 129 | * Counterpart to Solidity's `/` operator. Note: this function uses a 130 | * `revert` opcode (which leaves remaining gas untouched) while Solidity 131 | * uses an invalid opcode to revert (consuming all remaining gas). 132 | * 133 | * Requirements: 134 | * - The divisor cannot be zero. 135 | * 136 | * _Available since v2.4.0._ 137 | */ 138 | function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { 139 | // Solidity only automatically asserts when dividing by 0 140 | require(b > 0, errorMessage); 141 | uint256 c = a / b; 142 | // assert(a == b * c + a % b); // There is no case in which this doesn't hold 143 | 144 | return c; 145 | } 146 | 147 | /** 148 | * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), 149 | * Reverts when dividing by zero. 150 | * 151 | * Counterpart to Solidity's `%` operator. This function uses a `revert` 152 | * opcode (which leaves remaining gas untouched) while Solidity uses an 153 | * invalid opcode to revert (consuming all remaining gas). 154 | * 155 | * Requirements: 156 | * - The divisor cannot be zero. 157 | */ 158 | function mod(uint256 a, uint256 b) internal pure returns (uint256) { 159 | return mod(a, b, "SafeMath: modulo by zero"); 160 | } 161 | 162 | /** 163 | * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), 164 | * Reverts with custom message when dividing by zero. 165 | * 166 | * Counterpart to Solidity's `%` operator. This function uses a `revert` 167 | * opcode (which leaves remaining gas untouched) while Solidity uses an 168 | * invalid opcode to revert (consuming all remaining gas). 169 | * 170 | * Requirements: 171 | * - The divisor cannot be zero. 172 | * 173 | * _Available since v2.4.0._ 174 | */ 175 | function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { 176 | require(b != 0, errorMessage); 177 | return a % b; 178 | } 179 | } 180 | 181 | 182 | pragma solidity ^0.5.17; 183 | 184 | /** 185 | * @dev Interface of the ERC20 standard as defined in the EIP. Does not include 186 | * the optional functions; to access them see {ERC20Detailed}. 187 | */ 188 | interface IERC20 { 189 | /** 190 | * @dev Returns the amount of tokens in existence. 191 | */ 192 | function totalSupply() external view returns (uint256); 193 | 194 | /** 195 | * @dev Returns the amount of tokens owned by `account`. 196 | */ 197 | function balanceOf(address account) external view returns (uint256); 198 | 199 | /** 200 | * @dev Moves `amount` tokens from the caller's account to `recipient`. 201 | * 202 | * Returns a boolean value indicating whether the operation succeeded. 203 | * 204 | * Emits a {Transfer} event. 205 | */ 206 | function transfer(address recipient, uint256 amount) external returns (bool); 207 | 208 | /** 209 | * @dev Returns the remaining number of tokens that `spender` will be 210 | * allowed to spend on behalf of `owner` through {transferFrom}. This is 211 | * zero by default. 212 | * 213 | * This value changes when {approve} or {transferFrom} are called. 214 | */ 215 | function allowance(address owner, address spender) external view returns (uint256); 216 | 217 | /** 218 | * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. 219 | * 220 | * Returns a boolean value indicating whether the operation succeeded. 221 | * 222 | * IMPORTANT: Beware that changing an allowance with this method brings the risk 223 | * that someone may use both the old and the new allowance by unfortunate 224 | * transaction ordering. One possible solution to mitigate this race 225 | * condition is to first reduce the spender's allowance to 0 and set the 226 | * desired value afterwards: 227 | * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 228 | * 229 | * Emits an {Approval} event. 230 | */ 231 | function approve(address spender, uint256 amount) external returns (bool); 232 | 233 | /** 234 | * @dev Moves `amount` tokens from `sender` to `recipient` using the 235 | * allowance mechanism. `amount` is then deducted from the caller's 236 | * allowance. 237 | * 238 | * Returns a boolean value indicating whether the operation succeeded. 239 | * 240 | * Emits a {Transfer} event. 241 | */ 242 | function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); 243 | 244 | /** 245 | * @dev Emitted when `value` tokens are moved from one account (`from`) to 246 | * another (`to`). 247 | * 248 | * Note that `value` may be zero. 249 | */ 250 | event Transfer(address indexed from, address indexed to, uint256 value); 251 | 252 | /** 253 | * @dev Emitted when the allowance of a `spender` for an `owner` is set by 254 | * a call to {approve}. `value` is the new allowance. 255 | */ 256 | event Approval(address indexed owner, address indexed spender, uint256 value); 257 | } 258 | 259 | // File: @openzeppelin/contracts/utils/Address.sol 260 | 261 | pragma solidity ^0.5.5; 262 | 263 | /** 264 | * @dev Collection of functions related to the address type 265 | */ 266 | library Address { 267 | /** 268 | * @dev Returns true if `account` is a contract. 269 | * 270 | * This test is non-exhaustive, and there may be false-negatives: during the 271 | * execution of a contract's constructor, its address will be reported as 272 | * not containing a contract. 273 | * 274 | * IMPORTANT: It is unsafe to assume that an address for which this 275 | * function returns false is an externally-owned account (EOA) and not a 276 | * contract. 277 | */ 278 | function isContract(address account) internal view returns (bool) { 279 | // This method relies in extcodesize, which returns 0 for contracts in 280 | // construction, since the code is only stored at the end of the 281 | // constructor execution. 282 | 283 | // According to EIP-1052, 0x0 is the value returned for not-yet created accounts 284 | // and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned 285 | // for accounts without code, i.e. `keccak256('')` 286 | bytes32 codehash; 287 | bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470; 288 | // solhint-disable-next-line no-inline-assembly 289 | assembly { codehash := extcodehash(account) } 290 | return (codehash != 0x0 && codehash != accountHash); 291 | } 292 | 293 | /** 294 | * @dev Converts an `address` into `address payable`. Note that this is 295 | * simply a type cast: the actual underlying value is not changed. 296 | * 297 | * _Available since v2.4.0._ 298 | */ 299 | function toPayable(address account) internal pure returns (address payable) { 300 | return address(uint160(account)); 301 | } 302 | 303 | /** 304 | * @dev Replacement for Solidity's `transfer`: sends `amount` wei to 305 | * `recipient`, forwarding all available gas and reverting on errors. 306 | * 307 | * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost 308 | * of certain opcodes, possibly making contracts go over the 2300 gas limit 309 | * imposed by `transfer`, making them unable to receive funds via 310 | * `transfer`. {sendValue} removes this limitation. 311 | * 312 | * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more]. 313 | * 314 | * IMPORTANT: because control is transferred to `recipient`, care must be 315 | * taken to not create reentrancy vulnerabilities. Consider using 316 | * {ReentrancyGuard} or the 317 | * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. 318 | * 319 | * _Available since v2.4.0._ 320 | */ 321 | function sendValue(address payable recipient, uint256 amount) internal { 322 | require(address(this).balance >= amount, "Address: insufficient balance"); 323 | 324 | // solhint-disable-next-line avoid-call-value 325 | (bool success, ) = recipient.call.value(amount)(""); 326 | require(success, "Address: unable to send value, recipient may have reverted"); 327 | } 328 | } 329 | 330 | // File: @openzeppelin/contracts/token/ERC20/SafeERC20.sol 331 | 332 | pragma solidity ^0.5.0; 333 | 334 | 335 | 336 | 337 | /** 338 | * @title SafeERC20 339 | * @dev Wrappers around ERC20 operations that throw on failure (when the token 340 | * contract returns false). Tokens that return no value (and instead revert or 341 | * throw on failure) are also supported, non-reverting calls are assumed to be 342 | * successful. 343 | * To use this library you can add a `using SafeERC20 for ERC20;` statement to your contract, 344 | * which allows you to call the safe operations as `token.safeTransfer(...)`, etc. 345 | */ 346 | library SafeERC20 { 347 | using SafeMath for uint256; 348 | using Address for address; 349 | 350 | function safeTransfer(IERC20 token, address to, uint256 value) internal { 351 | callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); 352 | } 353 | 354 | function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal { 355 | callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value)); 356 | } 357 | 358 | function safeApprove(IERC20 token, address spender, uint256 value) internal { 359 | // safeApprove should only be called when setting an initial allowance, 360 | // or when resetting it to zero. To increase and decrease it, use 361 | // 'safeIncreaseAllowance' and 'safeDecreaseAllowance' 362 | // solhint-disable-next-line max-line-length 363 | require((value == 0) || (token.allowance(address(this), spender) == 0), 364 | "SafeERC20: approve from non-zero to non-zero allowance" 365 | ); 366 | callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value)); 367 | } 368 | 369 | function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal { 370 | uint256 newAllowance = token.allowance(address(this), spender).add(value); 371 | callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); 372 | } 373 | 374 | function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal { 375 | uint256 newAllowance = token.allowance(address(this), spender).sub(value, "SafeERC20: decreased allowance below zero"); 376 | callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); 377 | } 378 | 379 | /** 380 | * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement 381 | * on the return value: the return value is optional (but if data is returned, it must not be false). 382 | * @param token The token targeted by the call. 383 | * @param data The call data (encoded using abi.encode or one of its variants). 384 | */ 385 | function callOptionalReturn(IERC20 token, bytes memory data) private { 386 | // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since 387 | // we're implementing it ourselves. 388 | 389 | // A Solidity high level call has three parts: 390 | // 1. The target address is checked to verify it contains contract code 391 | // 2. The call itself is made, and success asserted 392 | // 3. The return value is decoded, which in turn checks the size of the returned data. 393 | // solhint-disable-next-line max-line-length 394 | require(address(token).isContract(), "SafeERC20: call to non-contract"); 395 | 396 | // solhint-disable-next-line avoid-low-level-calls 397 | (bool success, bytes memory returndata) = address(token).call(data); 398 | require(success, "SafeERC20: low-level call failed"); 399 | 400 | if (returndata.length > 0) { // Return data is optional 401 | // solhint-disable-next-line max-line-length 402 | require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed"); 403 | } 404 | } 405 | } 406 | 407 | pragma solidity ^0.5.0; 408 | 409 | contract LPTokenWrapperWithSlash { 410 | using SafeMath for uint256; 411 | using SafeERC20 for IERC20; 412 | 413 | IERC20 public stakeToken; 414 | 415 | uint256 private _totalSupply; 416 | mapping(address => uint256) private _balances; 417 | 418 | constructor(IERC20 _stakeToken) public { 419 | stakeToken = _stakeToken; 420 | } 421 | 422 | function totalSupply() public view returns (uint256) { 423 | return _totalSupply; 424 | } 425 | 426 | function balanceOf(address account) public view returns (uint256) { 427 | return _balances[account]; 428 | } 429 | 430 | function stake(uint256 amount) public { 431 | _totalSupply = _totalSupply.add(amount); 432 | _balances[msg.sender] = _balances[msg.sender].add(amount); 433 | stakeToken.safeTransferFrom(msg.sender, address(this), amount); 434 | } 435 | 436 | function withdraw(uint256 amount) public { 437 | _totalSupply = _totalSupply.sub(amount); 438 | _balances[msg.sender] = _balances[msg.sender].sub(amount); 439 | stakeToken.safeTransfer(msg.sender, amount); 440 | } 441 | 442 | function slash(address proposer) internal returns (uint256 amount) { 443 | amount = _balances[proposer]; 444 | _totalSupply = _totalSupply.sub(amount); 445 | _balances[proposer] = 0; 446 | } 447 | } 448 | 449 | interface UniswapRouter { 450 | function WETH() external pure returns (address); 451 | function swapExactTokensForTokens( 452 | uint256 amountIn, 453 | uint256 amountOutMin, 454 | address[] calldata path, 455 | address to, 456 | uint256 deadline 457 | ) external returns (uint256[] memory amounts); 458 | } 459 | 460 | interface IGovernance { 461 | function getStablecoin() external view returns (address); 462 | } 463 | 464 | 465 | contract BoostGov is LPTokenWrapperWithSlash, IGovernance { 466 | IERC20 public stablecoin; 467 | UniswapRouter public uniswapRouter; 468 | 469 | // 1% = 100 470 | uint256 public constant MIN_QUORUM_PUNISHMENT = 500; // 5% 471 | uint256 public constant MIN_QUORUM_THRESHOLD = 3000; // 30% 472 | uint256 public constant PERCENTAGE_PRECISION = 10000; 473 | uint256 public WITHDRAW_THRESHOLD = 1e21; // 1000 yCRV 474 | 475 | mapping(address => uint256) public voteLock; // period that your stake it locked to keep it for voting 476 | 477 | struct Proposal { 478 | address proposer; 479 | uint256 withdrawAmount; 480 | address withdrawAddress; 481 | bool hasResolved; 482 | mapping(address => uint256) forVotes; 483 | mapping(address => uint256) againstVotes; 484 | uint256 totalForVotes; 485 | uint256 totalAgainstVotes; 486 | uint256 start; // block start; 487 | uint256 end; // start + period 488 | string url; // url 489 | } 490 | 491 | mapping (uint256 => Proposal) public proposals; 492 | uint256 public proposalCount; 493 | uint256 public proposalPeriod = 2 days; 494 | uint256 public lockPeriod = 3 days; 495 | uint256 public minimum = 1337e16; // 13.37 BOOST 496 | 497 | constructor(IERC20 _stakeToken, IERC20 _stablecoin, UniswapRouter _uniswapRouter) 498 | public 499 | LPTokenWrapperWithSlash(_stakeToken) 500 | { 501 | stablecoin = _stablecoin; 502 | stakeToken.safeApprove(address(_uniswapRouter), uint256(-1)); 503 | uniswapRouter = _uniswapRouter; 504 | } 505 | 506 | function getStablecoin() external view returns (address) { 507 | return address(stablecoin); 508 | } 509 | 510 | function propose( 511 | string memory _url, 512 | uint256 _withdrawAmount, 513 | address _withdrawAddress 514 | ) public { 515 | require(balanceOf(msg.sender) > minimum, "stake more boost"); 516 | proposals[proposalCount++] = Proposal({ 517 | proposer: msg.sender, 518 | withdrawAmount: _withdrawAmount, 519 | withdrawAddress: _withdrawAddress, 520 | hasResolved: false, 521 | totalForVotes: 0, 522 | totalAgainstVotes: 0, 523 | start: block.timestamp, 524 | end: proposalPeriod.add(block.timestamp), 525 | url: _url 526 | }); 527 | voteLock[msg.sender] = lockPeriod.add(block.timestamp); 528 | } 529 | 530 | function voteFor(uint256 id) public { 531 | require(proposals[id].start < block.timestamp , " block.timestamp , ">end"); 533 | require(proposals[id].againstVotes[msg.sender] == 0, "cannot switch votes"); 534 | uint256 votes = balanceOf(msg.sender).sub(proposals[id].forVotes[msg.sender]); 535 | proposals[id].totalForVotes = proposals[id].totalForVotes.add(votes); 536 | proposals[id].forVotes[msg.sender] = balanceOf(msg.sender); 537 | 538 | voteLock[msg.sender] = lockPeriod.add(block.timestamp); 539 | } 540 | 541 | function voteAgainst(uint256 id) public { 542 | require(proposals[id].start < block.timestamp , " block.timestamp , ">end"); 544 | require(proposals[id].forVotes[msg.sender] == 0, "cannot switch votes"); 545 | uint256 votes = balanceOf(msg.sender).sub(proposals[id].againstVotes[msg.sender]); 546 | proposals[id].totalAgainstVotes = proposals[id].totalAgainstVotes.add(votes); 547 | proposals[id].againstVotes[msg.sender] = balanceOf(msg.sender); 548 | 549 | voteLock[msg.sender] = lockPeriod.add(block.timestamp); 550 | } 551 | 552 | function stake(uint256 amount) public { 553 | super.stake(amount); 554 | } 555 | 556 | function withdraw(uint256 amount) public { 557 | require(voteLock[msg.sender] < block.timestamp, "tokens locked"); 558 | super.withdraw(amount); 559 | } 560 | 561 | function resolveProposal(uint256 id) public { 562 | require(proposals[id].proposer != address(0), "non-existent proposal"); 563 | require(proposals[id].end < block.timestamp , "ongoing proposal"); 564 | require(!proposals[id].hasResolved, "already resolved"); 565 | 566 | // sum votes, multiply by precision, divide by total supply 567 | uint256 quorum = 568 | (proposals[id].totalForVotes.add(proposals[id].totalAgainstVotes)) 569 | .mul(PERCENTAGE_PRECISION) 570 | .div(totalSupply()); 571 | 572 | proposals[id].hasResolved = true; 573 | if ((quorum < MIN_QUORUM_PUNISHMENT) && proposals[id].withdrawAmount > WITHDRAW_THRESHOLD) { 574 | // user's stake gets slashed, converted to stablecoin 575 | uint256 amount = slash(proposals[id].proposer); 576 | address[] memory routeDetails = new address[](3); 577 | routeDetails[0] = address(stakeToken); 578 | routeDetails[1] = uniswapRouter.WETH(); 579 | routeDetails[2] = address(stablecoin); 580 | uniswapRouter.swapExactTokensForTokens( 581 | amount, 582 | 0, 583 | routeDetails, 584 | address(this), 585 | block.timestamp + 100 586 | ); 587 | } else if ( 588 | (quorum > MIN_QUORUM_THRESHOLD) && 589 | (proposals[id].totalForVotes > proposals[id].totalAgainstVotes) 590 | ) { 591 | // send funds to withdraw address 592 | if (stablecoin.balanceOf(address(this)) >= proposals[id].withdrawAmount) { 593 | stablecoin.safeTransfer( 594 | proposals[id].withdrawAddress, 595 | proposals[id].withdrawAmount 596 | ); 597 | } 598 | } 599 | } 600 | } 601 | -------------------------------------------------------------------------------- /contracts/V1/BoostRewardsPool.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | /* 3 | * MIT License 4 | * =========== 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | */ 23 | 24 | pragma solidity ^0.5.17; 25 | 26 | /** 27 | * @dev Standard math utilities missing in the Solidity language. 28 | */ 29 | library Math { 30 | /** 31 | * @dev Returns the largest of two numbers. 32 | */ 33 | function max(uint256 a, uint256 b) internal pure returns (uint256) { 34 | return a >= b ? a : b; 35 | } 36 | 37 | /** 38 | * @dev Returns the smallest of two numbers. 39 | */ 40 | function min(uint256 a, uint256 b) internal pure returns (uint256) { 41 | return a < b ? a : b; 42 | } 43 | 44 | /** 45 | * @dev Returns the average of two numbers. The result is rounded towards 46 | * zero. 47 | */ 48 | function average(uint256 a, uint256 b) internal pure returns (uint256) { 49 | // (a + b) / 2 can overflow, so we distribute 50 | return (a / 2) + (b / 2) + ((a % 2 + b % 2) / 2); 51 | } 52 | } 53 | 54 | pragma solidity ^0.5.17; 55 | 56 | /** 57 | * @dev Wrappers over Solidity's arithmetic operations with added overflow 58 | * checks. 59 | * 60 | * Arithmetic operations in Solidity wrap on overflow. This can easily result 61 | * in bugs, because programmers usually assume that an overflow raises an 62 | * error, which is the standard behavior in high level programming languages. 63 | * `SafeMath` restores this intuition by reverting the transaction when an 64 | * operation overflows. 65 | * 66 | * Using this library instead of the unchecked operations eliminates an entire 67 | * class of bugs, so it's recommended to use it always. 68 | */ 69 | library SafeMath { 70 | /** 71 | * @dev Returns the addition of two unsigned integers, reverting on 72 | * overflow. 73 | * 74 | * Counterpart to Solidity's `+` operator. 75 | * 76 | * Requirements: 77 | * - Addition cannot overflow. 78 | */ 79 | function add(uint256 a, uint256 b) internal pure returns (uint256) { 80 | uint256 c = a + b; 81 | require(c >= a, "SafeMath: addition overflow"); 82 | 83 | return c; 84 | } 85 | 86 | /** 87 | * @dev Returns the subtraction of two unsigned integers, reverting on 88 | * overflow (when the result is negative). 89 | * 90 | * Counterpart to Solidity's `-` operator. 91 | * 92 | * Requirements: 93 | * - Subtraction cannot overflow. 94 | */ 95 | function sub(uint256 a, uint256 b) internal pure returns (uint256) { 96 | return sub(a, b, "SafeMath: subtraction overflow"); 97 | } 98 | 99 | /** 100 | * @dev Returns the subtraction of two unsigned integers, reverting with custom message on 101 | * overflow (when the result is negative). 102 | * 103 | * Counterpart to Solidity's `-` operator. 104 | * 105 | * Requirements: 106 | * - Subtraction cannot overflow. 107 | * 108 | * _Available since v2.4.0._ 109 | */ 110 | function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { 111 | require(b <= a, errorMessage); 112 | uint256 c = a - b; 113 | 114 | return c; 115 | } 116 | 117 | /** 118 | * @dev Returns the multiplication of two unsigned integers, reverting on 119 | * overflow. 120 | * 121 | * Counterpart to Solidity's `*` operator. 122 | * 123 | * Requirements: 124 | * - Multiplication cannot overflow. 125 | */ 126 | function mul(uint256 a, uint256 b) internal pure returns (uint256) { 127 | // Gas optimization: this is cheaper than requiring 'a' not being zero, but the 128 | // benefit is lost if 'b' is also tested. 129 | // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 130 | if (a == 0) { 131 | return 0; 132 | } 133 | 134 | uint256 c = a * b; 135 | require(c / a == b, "SafeMath: multiplication overflow"); 136 | 137 | return c; 138 | } 139 | 140 | /** 141 | * @dev Returns the integer division of two unsigned integers. Reverts on 142 | * division by zero. The result is rounded towards zero. 143 | * 144 | * Counterpart to Solidity's `/` operator. Note: this function uses a 145 | * `revert` opcode (which leaves remaining gas untouched) while Solidity 146 | * uses an invalid opcode to revert (consuming all remaining gas). 147 | * 148 | * Requirements: 149 | * - The divisor cannot be zero. 150 | */ 151 | function div(uint256 a, uint256 b) internal pure returns (uint256) { 152 | return div(a, b, "SafeMath: division by zero"); 153 | } 154 | 155 | /** 156 | * @dev Returns the integer division of two unsigned integers. Reverts with custom message on 157 | * division by zero. The result is rounded towards zero. 158 | * 159 | * Counterpart to Solidity's `/` operator. Note: this function uses a 160 | * `revert` opcode (which leaves remaining gas untouched) while Solidity 161 | * uses an invalid opcode to revert (consuming all remaining gas). 162 | * 163 | * Requirements: 164 | * - The divisor cannot be zero. 165 | * 166 | * _Available since v2.4.0._ 167 | */ 168 | function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { 169 | // Solidity only automatically asserts when dividing by 0 170 | require(b > 0, errorMessage); 171 | uint256 c = a / b; 172 | // assert(a == b * c + a % b); // There is no case in which this doesn't hold 173 | 174 | return c; 175 | } 176 | 177 | /** 178 | * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), 179 | * Reverts when dividing by zero. 180 | * 181 | * Counterpart to Solidity's `%` operator. This function uses a `revert` 182 | * opcode (which leaves remaining gas untouched) while Solidity uses an 183 | * invalid opcode to revert (consuming all remaining gas). 184 | * 185 | * Requirements: 186 | * - The divisor cannot be zero. 187 | */ 188 | function mod(uint256 a, uint256 b) internal pure returns (uint256) { 189 | return mod(a, b, "SafeMath: modulo by zero"); 190 | } 191 | 192 | /** 193 | * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), 194 | * Reverts with custom message when dividing by zero. 195 | * 196 | * Counterpart to Solidity's `%` operator. This function uses a `revert` 197 | * opcode (which leaves remaining gas untouched) while Solidity uses an 198 | * invalid opcode to revert (consuming all remaining gas). 199 | * 200 | * Requirements: 201 | * - The divisor cannot be zero. 202 | * 203 | * _Available since v2.4.0._ 204 | */ 205 | function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { 206 | require(b != 0, errorMessage); 207 | return a % b; 208 | } 209 | } 210 | 211 | pragma solidity ^0.5.17; 212 | 213 | /* 214 | * @dev Provides information about the current execution context, including the 215 | * sender of the transaction and its data. While these are generally available 216 | * via msg.sender and msg.data, they should not be accessed in such a direct 217 | * manner, since when dealing with GSN meta-transactions the account sending and 218 | * paying for execution may not be the actual sender (as far as an application 219 | * is concerned). 220 | * 221 | * This contract is only required for intermediate, library-like contracts. 222 | */ 223 | contract Context { 224 | // Empty internal constructor, to prevent people from mistakenly deploying 225 | // an instance of this contract, which should be used via inheritance. 226 | constructor () internal { } 227 | // solhint-disable-previous-line no-empty-blocks 228 | 229 | function _msgSender() internal view returns (address payable) { 230 | return msg.sender; 231 | } 232 | 233 | function _msgData() internal view returns (bytes memory) { 234 | this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691 235 | return msg.data; 236 | } 237 | } 238 | 239 | pragma solidity ^0.5.17; 240 | 241 | /** 242 | * @dev Contract module which provides a basic access control mechanism, where 243 | * there is an account (an owner) that can be granted exclusive access to 244 | * specific functions. 245 | * 246 | * This module is used through inheritance. It will make available the modifier 247 | * `onlyOwner`, which can be applied to your functions to restrict their use to 248 | * the owner. 249 | */ 250 | contract Ownable is Context { 251 | address private _owner; 252 | 253 | event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); 254 | 255 | /** 256 | * @dev Initializes the contract setting the deployer as the initial owner. 257 | */ 258 | constructor () internal { 259 | _owner = _msgSender(); 260 | emit OwnershipTransferred(address(0), _owner); 261 | } 262 | 263 | /** 264 | * @dev Returns the address of the current owner. 265 | */ 266 | function owner() public view returns (address) { 267 | return _owner; 268 | } 269 | 270 | /** 271 | * @dev Throws if called by any account other than the owner. 272 | */ 273 | modifier onlyOwner() { 274 | require(isOwner(), "Ownable: caller is not the owner"); 275 | _; 276 | } 277 | 278 | /** 279 | * @dev Returns true if the caller is the current owner. 280 | */ 281 | function isOwner() public view returns (bool) { 282 | return _msgSender() == _owner; 283 | } 284 | 285 | /** 286 | * @dev Leaves the contract without owner. It will not be possible to call 287 | * `onlyOwner` functions anymore. Can only be called by the current owner. 288 | * 289 | * NOTE: Renouncing ownership will leave the contract without an owner, 290 | * thereby removing any functionality that is only available to the owner. 291 | */ 292 | function renounceOwnership() public onlyOwner { 293 | emit OwnershipTransferred(_owner, address(0)); 294 | _owner = address(0); 295 | } 296 | 297 | /** 298 | * @dev Transfers ownership of the contract to a new account (`newOwner`). 299 | * Can only be called by the current owner. 300 | */ 301 | function transferOwnership(address newOwner) public onlyOwner { 302 | _transferOwnership(newOwner); 303 | } 304 | 305 | /** 306 | * @dev Transfers ownership of the contract to a new account (`newOwner`). 307 | */ 308 | function _transferOwnership(address newOwner) internal { 309 | require(newOwner != address(0), "Ownable: new owner is the zero address"); 310 | emit OwnershipTransferred(_owner, newOwner); 311 | _owner = newOwner; 312 | } 313 | } 314 | 315 | pragma solidity ^0.5.17; 316 | 317 | /** 318 | * @dev Interface of the ERC20 standard as defined in the EIP. Does not include 319 | * the optional functions; to access them see {ERC20Detailed}. 320 | */ 321 | interface IERC20 { 322 | /** 323 | * @dev Returns the amount of tokens in existence. 324 | */ 325 | function totalSupply() external view returns (uint256); 326 | 327 | /** 328 | * @dev Returns the amount of tokens owned by `account`. 329 | */ 330 | function balanceOf(address account) external view returns (uint256); 331 | 332 | /** 333 | * @dev Moves `amount` tokens from the caller's account to `recipient`. 334 | * 335 | * Returns a boolean value indicating whether the operation succeeded. 336 | * 337 | * Emits a {Transfer} event. 338 | */ 339 | function transfer(address recipient, uint256 amount) external returns (bool); 340 | 341 | /** 342 | * @dev Returns the remaining number of tokens that `spender` will be 343 | * allowed to spend on behalf of `owner` through {transferFrom}. This is 344 | * zero by default. 345 | * 346 | * This value changes when {approve} or {transferFrom} are called. 347 | */ 348 | function allowance(address owner, address spender) external view returns (uint256); 349 | 350 | /** 351 | * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. 352 | * 353 | * Returns a boolean value indicating whether the operation succeeded. 354 | * 355 | * IMPORTANT: Beware that changing an allowance with this method brings the risk 356 | * that someone may use both the old and the new allowance by unfortunate 357 | * transaction ordering. One possible solution to mitigate this race 358 | * condition is to first reduce the spender's allowance to 0 and set the 359 | * desired value afterwards: 360 | * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 361 | * 362 | * Emits an {Approval} event. 363 | */ 364 | function approve(address spender, uint256 amount) external returns (bool); 365 | 366 | /** 367 | * @dev Moves `amount` tokens from `sender` to `recipient` using the 368 | * allowance mechanism. `amount` is then deducted from the caller's 369 | * allowance. 370 | * 371 | * Returns a boolean value indicating whether the operation succeeded. 372 | * 373 | * Emits a {Transfer} event. 374 | */ 375 | function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); 376 | 377 | /** 378 | * @dev Emitted when `value` tokens are moved from one account (`from`) to 379 | * another (`to`). 380 | * 381 | * Note that `value` may be zero. 382 | */ 383 | event Transfer(address indexed from, address indexed to, uint256 value); 384 | 385 | /** 386 | * @dev Emitted when the allowance of a `spender` for an `owner` is set by 387 | * a call to {approve}. `value` is the new allowance. 388 | */ 389 | event Approval(address indexed owner, address indexed spender, uint256 value); 390 | } 391 | 392 | interface IERC20Burnable { 393 | function totalSupply() external view returns (uint256); 394 | function balanceOf(address account) external view returns (uint256); 395 | function transfer(address recipient, uint256 amount) external returns (bool); 396 | function allowance(address owner, address spender) external view returns (uint256); 397 | function approve(address spender, uint256 amount) external returns (bool); 398 | function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); 399 | function burn(uint256 amount) external; 400 | event Transfer(address indexed from, address indexed to, uint256 value); 401 | event Approval(address indexed owner, address indexed spender, uint256 value); 402 | } 403 | 404 | pragma solidity ^0.5.17; 405 | 406 | /** 407 | * @dev Collection of functions related to the address type 408 | */ 409 | library Address { 410 | /** 411 | * @dev Returns true if `account` is a contract. 412 | * 413 | * This test is non-exhaustive, and there may be false-negatives: during the 414 | * execution of a contract's constructor, its address will be reported as 415 | * not containing a contract. 416 | * 417 | * IMPORTANT: It is unsafe to assume that an address for which this 418 | * function returns false is an externally-owned account (EOA) and not a 419 | * contract. 420 | */ 421 | function isContract(address account) internal view returns (bool) { 422 | // This method relies in extcodesize, which returns 0 for contracts in 423 | // construction, since the code is only stored at the end of the 424 | // constructor execution. 425 | 426 | // According to EIP-1052, 0x0 is the value returned for not-yet created accounts 427 | // and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned 428 | // for accounts without code, i.e. `keccak256('')` 429 | bytes32 codehash; 430 | bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470; 431 | // solhint-disable-next-line no-inline-assembly 432 | assembly { codehash := extcodehash(account) } 433 | return (codehash != 0x0 && codehash != accountHash); 434 | } 435 | 436 | /** 437 | * @dev Converts an `address` into `address payable`. Note that this is 438 | * simply a type cast: the actual underlying value is not changed. 439 | * 440 | * _Available since v2.4.0._ 441 | */ 442 | function toPayable(address account) internal pure returns (address payable) { 443 | return address(uint160(account)); 444 | } 445 | 446 | /** 447 | * @dev Replacement for Solidity's `transfer`: sends `amount` wei to 448 | * `recipient`, forwarding all available gas and reverting on errors. 449 | * 450 | * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost 451 | * of certain opcodes, possibly making contracts go over the 2300 gas limit 452 | * imposed by `transfer`, making them unable to receive funds via 453 | * `transfer`. {sendValue} removes this limitation. 454 | * 455 | * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more]. 456 | * 457 | * IMPORTANT: because control is transferred to `recipient`, care must be 458 | * taken to not create reentrancy vulnerabilities. Consider using 459 | * {ReentrancyGuard} or the 460 | * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. 461 | * 462 | * _Available since v2.4.0._ 463 | */ 464 | function sendValue(address payable recipient, uint256 amount) internal { 465 | require(address(this).balance >= amount, "Address: insufficient balance"); 466 | 467 | // solhint-disable-next-line avoid-call-value 468 | (bool success, ) = recipient.call.value(amount)(""); 469 | require(success, "Address: unable to send value, recipient may have reverted"); 470 | } 471 | } 472 | 473 | pragma solidity ^0.5.17; 474 | 475 | 476 | /** 477 | * @title SafeERC20 478 | * @dev Wrappers around ERC20 operations that throw on failure (when the token 479 | * contract returns false). Tokens that return no value (and instead revert or 480 | * throw on failure) are also supported, non-reverting calls are assumed to be 481 | * successful. 482 | * To use this library you can add a `using SafeERC20 for ERC20;` statement to your contract, 483 | * which allows you to call the safe operations as `token.safeTransfer(...)`, etc. 484 | */ 485 | library SafeERC20 { 486 | using SafeMath for uint256; 487 | using Address for address; 488 | 489 | function safeTransfer(IERC20 token, address to, uint256 value) internal { 490 | callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); 491 | } 492 | 493 | function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal { 494 | callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value)); 495 | } 496 | 497 | function safeApprove(IERC20 token, address spender, uint256 value) internal { 498 | // safeApprove should only be called when setting an initial allowance, 499 | // or when resetting it to zero. To increase and decrease it, use 500 | // 'safeIncreaseAllowance' and 'safeDecreaseAllowance' 501 | // solhint-disable-next-line max-line-length 502 | require((value == 0) || (token.allowance(address(this), spender) == 0), 503 | "SafeERC20: approve from non-zero to non-zero allowance" 504 | ); 505 | callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value)); 506 | } 507 | 508 | function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal { 509 | uint256 newAllowance = token.allowance(address(this), spender).add(value); 510 | callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); 511 | } 512 | 513 | function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal { 514 | uint256 newAllowance = token.allowance(address(this), spender).sub(value, "SafeERC20: decreased allowance below zero"); 515 | callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); 516 | } 517 | 518 | /** 519 | * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement 520 | * on the return value: the return value is optional (but if data is returned, it must not be false). 521 | * @param token The token targeted by the call. 522 | * @param data The call data (encoded using abi.encode or one of its variants). 523 | */ 524 | function callOptionalReturn(IERC20 token, bytes memory data) private { 525 | // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since 526 | // we're implementing it ourselves. 527 | 528 | // A Solidity high level call has three parts: 529 | // 1. The target address is checked to verify it contains contract code 530 | // 2. The call itself is made, and success asserted 531 | // 3. The return value is decoded, which in turn checks the size of the returned data. 532 | // solhint-disable-next-line max-line-length 533 | require(address(token).isContract(), "SafeERC20: call to non-contract"); 534 | 535 | // solhint-disable-next-line avoid-low-level-calls 536 | (bool success, bytes memory returndata) = address(token).call(data); 537 | require(success, "SafeERC20: low-level call failed"); 538 | 539 | if (returndata.length > 0) { // Return data is optional 540 | // solhint-disable-next-line max-line-length 541 | require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed"); 542 | } 543 | } 544 | } 545 | 546 | pragma solidity ^0.5.17; 547 | 548 | 549 | contract LPTokenWrapper { 550 | using SafeMath for uint256; 551 | using SafeERC20 for IERC20; 552 | 553 | IERC20 public stakeToken; 554 | 555 | uint256 private _totalSupply; 556 | mapping(address => uint256) private _balances; 557 | 558 | constructor(IERC20 _stakeToken) public { 559 | stakeToken = _stakeToken; 560 | } 561 | 562 | function totalSupply() public view returns (uint256) { 563 | return _totalSupply; 564 | } 565 | 566 | function balanceOf(address account) public view returns (uint256) { 567 | return _balances[account]; 568 | } 569 | 570 | function stake(uint256 amount) public { 571 | _totalSupply = _totalSupply.add(amount); 572 | _balances[msg.sender] = _balances[msg.sender].add(amount); 573 | // safeTransferFrom shifted to last line of overridden method 574 | } 575 | 576 | function withdraw(uint256 amount) public { 577 | _totalSupply = _totalSupply.sub(amount); 578 | _balances[msg.sender] = _balances[msg.sender].sub(amount); 579 | // safeTransfer shifted to last line of overridden method 580 | } 581 | } 582 | 583 | interface UniswapRouter { 584 | function WETH() external pure returns (address); 585 | function swapExactTokensForTokens( 586 | uint amountIn, 587 | uint amountOutMin, 588 | address[] calldata path, 589 | address to, 590 | uint deadline 591 | ) external returns (uint[] memory amounts); 592 | } 593 | 594 | interface IGovernance { 595 | function getStablecoin() external view returns (address); 596 | } 597 | 598 | contract BoostRewardsPool is LPTokenWrapper, Ownable { 599 | IERC20 public boostToken; 600 | address public governance; 601 | address public governanceSetter; 602 | UniswapRouter public uniswapRouter; 603 | address public stablecoin; 604 | 605 | uint256 public constant MAX_NUM_BOOSTERS = 5; 606 | uint256 public tokenCapAmount; 607 | uint256 public starttime; 608 | uint256 public duration; 609 | uint256 public periodFinish = 0; 610 | uint256 public rewardRate = 0; 611 | uint256 public lastUpdateTime; 612 | uint256 public rewardPerTokenStored; 613 | mapping(address => uint256) public userRewardPerTokenPaid; 614 | mapping(address => uint256) public rewards; 615 | 616 | // booster variables 617 | // variables to keep track of totalSupply and balances (after accounting for multiplier) 618 | uint256 public boostedTotalSupply; 619 | mapping(address => uint256) public boostedBalances; 620 | mapping(address => uint256) public numBoostersBought; // each booster = 5% increase in stake amt 621 | mapping(address => uint256) public nextBoostPurchaseTime; // timestamp for which user is eligible to purchase another booster 622 | uint256 public boosterPrice = PRECISION; 623 | uint256 internal constant PRECISION = 1e18; 624 | 625 | event RewardAdded(uint256 reward); 626 | event RewardPaid(address indexed user, uint256 reward); 627 | 628 | modifier checkStart() { 629 | require(block.timestamp >= starttime,"not start"); 630 | _; 631 | } 632 | 633 | modifier updateReward(address account) { 634 | rewardPerTokenStored = rewardPerToken(); 635 | lastUpdateTime = lastTimeRewardApplicable(); 636 | if (account != address(0)) { 637 | rewards[account] = earned(account); 638 | userRewardPerTokenPaid[account] = rewardPerTokenStored; 639 | } 640 | _; 641 | } 642 | 643 | constructor( 644 | uint256 _tokenCapAmount, 645 | IERC20 _stakeToken, 646 | IERC20 _boostToken, 647 | address _governanceSetter, 648 | UniswapRouter _uniswapRouter, 649 | uint256 _starttime, 650 | uint256 _duration 651 | ) public LPTokenWrapper(_stakeToken) { 652 | tokenCapAmount = _tokenCapAmount; 653 | boostToken = _boostToken; 654 | boostToken.approve(address(_uniswapRouter), uint256(-1)); 655 | governanceSetter = _governanceSetter; 656 | uniswapRouter = _uniswapRouter; 657 | starttime = _starttime; 658 | duration = _duration; 659 | } 660 | 661 | function lastTimeRewardApplicable() public view returns (uint256) { 662 | return Math.min(block.timestamp, periodFinish); 663 | } 664 | 665 | function rewardPerToken() public view returns (uint256) { 666 | if (boostedTotalSupply == 0) { 667 | return rewardPerTokenStored; 668 | } 669 | return 670 | rewardPerTokenStored.add( 671 | lastTimeRewardApplicable() 672 | .sub(lastUpdateTime) 673 | .mul(rewardRate) 674 | .mul(1e18) 675 | .div(boostedTotalSupply) 676 | ); 677 | } 678 | 679 | function earned(address account) public view returns (uint256) { 680 | return 681 | boostedBalances[account] 682 | .mul(rewardPerToken().sub(userRewardPerTokenPaid[account])) 683 | .div(1e18) 684 | .add(rewards[account]); 685 | } 686 | 687 | // stake visibility is public as overriding LPTokenWrapper's stake() function 688 | function stake(uint256 amount) public updateReward(msg.sender) checkStart { 689 | require(amount > 0, "Cannot stake 0"); 690 | super.stake(amount); 691 | 692 | // check user cap 693 | require( 694 | balanceOf(msg.sender) <= tokenCapAmount || block.timestamp >= starttime.add(86400), 695 | "token cap exceeded" 696 | ); 697 | 698 | // update boosted balance and supply 699 | updateBoostBalanceAndSupply(msg.sender); 700 | 701 | // transfer token last, to follow CEI pattern 702 | stakeToken.safeTransferFrom(msg.sender, address(this), amount); 703 | } 704 | 705 | function withdraw(uint256 amount) public updateReward(msg.sender) checkStart { 706 | require(amount > 0, "Cannot withdraw 0"); 707 | super.withdraw(amount); 708 | 709 | // update boosted balance and supply 710 | updateBoostBalanceAndSupply(msg.sender); 711 | 712 | stakeToken.safeTransfer(msg.sender, amount); 713 | } 714 | 715 | function exit() external { 716 | withdraw(balanceOf(msg.sender)); 717 | getReward(); 718 | } 719 | 720 | function getReward() public updateReward(msg.sender) checkStart { 721 | uint256 reward = earned(msg.sender); 722 | if (reward > 0) { 723 | rewards[msg.sender] = 0; 724 | boostToken.safeTransfer(msg.sender, reward); 725 | emit RewardPaid(msg.sender, reward); 726 | } 727 | } 728 | 729 | function boost() external updateReward(msg.sender) checkStart { 730 | require( 731 | // 2 days after starttime 732 | block.timestamp > starttime.add(172800) && 733 | block.timestamp > nextBoostPurchaseTime[msg.sender], 734 | "early boost purchase" 735 | ); 736 | 737 | // increase next purchase eligibility by an hour 738 | nextBoostPurchaseTime[msg.sender] = block.timestamp.add(3600); 739 | 740 | // increase no. of boosters bought 741 | uint256 booster = numBoostersBought[msg.sender].add(1); 742 | numBoostersBought[msg.sender] = booster; 743 | require(booster <= MAX_NUM_BOOSTERS, "max boosters bought"); 744 | 745 | // save current booster price, since transfer is done last 746 | booster = boosterPrice; 747 | // increase next booster price by 5% 748 | boosterPrice = boosterPrice.mul(105).div(100); 749 | 750 | // update boosted balance and supply 751 | updateBoostBalanceAndSupply(msg.sender); 752 | 753 | boostToken.safeTransferFrom(msg.sender, address(this), booster); 754 | 755 | IERC20Burnable burnableBoostToken = IERC20Burnable(address(boostToken)); 756 | // if governance not set, burn all 757 | if (governance == address(0)) { 758 | burnableBoostToken.burn(booster); 759 | return; 760 | } 761 | 762 | // otherwise, burn 50% 763 | uint256 burnAmount = booster.div(2); 764 | burnableBoostToken.burn(burnAmount); 765 | booster = booster.sub(burnAmount); 766 | 767 | // swap to stablecoin, transferred to governance 768 | address[] memory routeDetails = new address[](3); 769 | routeDetails[0] = address(boostToken); 770 | routeDetails[1] = uniswapRouter.WETH(); 771 | routeDetails[2] = stablecoin; 772 | uniswapRouter.swapExactTokensForTokens( 773 | booster, 774 | 0, 775 | routeDetails, 776 | governance, 777 | block.timestamp + 100 778 | ); 779 | } 780 | 781 | function notifyRewardAmount(uint256 reward) 782 | external 783 | onlyOwner 784 | updateReward(address(0)) 785 | { 786 | rewardRate = reward.div(duration); 787 | lastUpdateTime = starttime; 788 | periodFinish = starttime.add(duration); 789 | emit RewardAdded(reward); 790 | } 791 | 792 | function setGovernance(address _governance) 793 | external 794 | { 795 | require(msg.sender == governanceSetter, "only setter"); 796 | governance = _governance; 797 | stablecoin = IGovernance(governance).getStablecoin(); 798 | governanceSetter = address(0); 799 | } 800 | 801 | function updateBoostBalanceAndSupply(address user) internal { 802 | // subtract existing balance from boostedSupply 803 | boostedTotalSupply = boostedTotalSupply.sub(boostedBalances[user]); 804 | // calculate and update new boosted balance (user's balance has been updated by parent method) 805 | // each booster adds 5% to stake amount 806 | uint256 newBoostBalance = balanceOf(user).mul(numBoostersBought[user].mul(5).add(100)).div(100); 807 | boostedBalances[user] = newBoostBalance; 808 | // update boostedSupply 809 | boostedTotalSupply = boostedTotalSupply.add(newBoostBalance); 810 | } 811 | } 812 | -------------------------------------------------------------------------------- /contracts/mock/Token.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 4 | 5 | 6 | contract Token is ERC20 { 7 | string public name = "Test"; 8 | string public symbol = "TST"; 9 | uint256 public decimals = 18; 10 | 11 | constructor( 12 | string memory _name, 13 | string memory _symbol, 14 | uint256 _decimals 15 | ) public { 16 | name = _name; 17 | symbol = _symbol; 18 | decimals = _decimals; 19 | _mint(msg.sender, 10**(50 + 18)); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /contracts/mock/WETH.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.17; 2 | 3 | 4 | contract WETH9 { 5 | string public name = "Wrapped Ether"; 6 | string public symbol = "WETH"; 7 | uint8 public decimals = 18; 8 | 9 | event Approval(address indexed src, address indexed guy, uint wad); 10 | event Transfer(address indexed src, address indexed dst, uint wad); 11 | event Deposit(address indexed dst, uint wad); 12 | event Withdrawal(address indexed src, uint wad); 13 | 14 | mapping (address => uint) public balanceOf; 15 | mapping (address => mapping (address => uint)) public allowance; 16 | 17 | constructor () public payable { 18 | deposit(); 19 | } 20 | 21 | function deposit() public payable { 22 | balanceOf[msg.sender] += msg.value; 23 | emit Deposit(msg.sender, msg.value); 24 | } 25 | 26 | function withdraw(uint wad) public { 27 | require(balanceOf[msg.sender] >= wad); 28 | balanceOf[msg.sender] -= wad; 29 | msg.sender.transfer(wad); 30 | emit Withdrawal(msg.sender, wad); 31 | } 32 | 33 | function totalSupply() public view returns (uint) { 34 | return address(this).balance; 35 | } 36 | 37 | function approve(address guy, uint wad) public returns (bool) { 38 | allowance[msg.sender][guy] = wad; 39 | emit Approval(msg.sender, guy, wad); 40 | return true; 41 | } 42 | 43 | function transfer(address dst, uint wad) public returns (bool) { 44 | return transferFrom(msg.sender, dst, wad); 45 | } 46 | 47 | function transferFrom(address src, address dst, uint wad) 48 | public 49 | returns (bool) 50 | { 51 | require(balanceOf[src] >= wad); 52 | 53 | if (src != msg.sender && allowance[src][msg.sender] != uint(-1)) { 54 | require(allowance[src][msg.sender] >= wad); 55 | allowance[src][msg.sender] -= wad; 56 | } 57 | 58 | balanceOf[src] -= wad; 59 | balanceOf[dst] += wad; 60 | 61 | emit Transfer(src, dst, wad); 62 | 63 | return true; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /contracts/zeppelin/Address.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | 4 | /** 5 | * @dev Collection of functions related to the address type 6 | */ 7 | library Address { 8 | /** 9 | * @dev Returns true if `account` is a contract. 10 | * 11 | * This test is non-exhaustive, and there may be false-negatives: during the 12 | * execution of a contract's constructor, its address will be reported as 13 | * not containing a contract. 14 | * 15 | * IMPORTANT: It is unsafe to assume that an address for which this 16 | * function returns false is an externally-owned account (EOA) and not a 17 | * contract. 18 | */ 19 | function isContract(address account) internal view returns (bool) { 20 | // This method relies in extcodesize, which returns 0 for contracts in 21 | // construction, since the code is only stored at the end of the 22 | // constructor execution. 23 | 24 | // According to EIP-1052, 0x0 is the value returned for not-yet created accounts 25 | // and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned 26 | // for accounts without code, i.e. `keccak256('')` 27 | bytes32 codehash; 28 | bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470; 29 | // solhint-disable-next-line no-inline-assembly 30 | assembly { codehash := extcodehash(account) } 31 | return (codehash != 0x0 && codehash != accountHash); 32 | } 33 | 34 | /** 35 | * @dev Converts an `address` into `address payable`. Note that this is 36 | * simply a type cast: the actual underlying value is not changed. 37 | * 38 | * _Available since v2.4.0._ 39 | */ 40 | function toPayable(address account) internal pure returns (address payable) { 41 | return address(uint160(account)); 42 | } 43 | 44 | /** 45 | * @dev Replacement for Solidity's `transfer`: sends `amount` wei to 46 | * `recipient`, forwarding all available gas and reverting on errors. 47 | * 48 | * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost 49 | * of certain opcodes, possibly making contracts go over the 2300 gas limit 50 | * imposed by `transfer`, making them unable to receive funds via 51 | * `transfer`. {sendValue} removes this limitation. 52 | * 53 | * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more]. 54 | * 55 | * IMPORTANT: because control is transferred to `recipient`, care must be 56 | * taken to not create reentrancy vulnerabilities. Consider using 57 | * {ReentrancyGuard} or the 58 | * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. 59 | * 60 | * _Available since v2.4.0._ 61 | */ 62 | function sendValue(address payable recipient, uint256 amount) internal { 63 | require(address(this).balance >= amount, "Address: insufficient balance"); 64 | 65 | // solhint-disable-next-line avoid-call-value 66 | (bool success, ) = recipient.call.value(amount)(""); 67 | require(success, "Address: unable to send value, recipient may have reverted"); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /contracts/zeppelin/Ownable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | 4 | /* 5 | * @dev Provides information about the current execution context, including the 6 | * sender of the transaction and its data. While these are generally available 7 | * via msg.sender and msg.data, they should not be accessed in such a direct 8 | * manner, since when dealing with GSN meta-transactions the account sending and 9 | * paying for execution may not be the actual sender (as far as an application 10 | * is concerned). 11 | * 12 | * This contract is only required for intermediate, library-like contracts. 13 | */ 14 | contract Context { 15 | // Empty internal constructor, to prevent people from mistakenly deploying 16 | // an instance of this contract, which should be used via inheritance. 17 | constructor () internal { } 18 | // solhint-disable-previous-line no-empty-blocks 19 | 20 | function _msgSender() internal view returns (address payable) { 21 | return msg.sender; 22 | } 23 | 24 | function _msgData() internal view returns (bytes memory) { 25 | this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691 26 | return msg.data; 27 | } 28 | } 29 | 30 | /** 31 | * @dev Contract module which provides a basic access control mechanism, where 32 | * there is an account (an owner) that can be granted exclusive access to 33 | * specific functions. 34 | * 35 | * This module is used through inheritance. It will make available the modifier 36 | * `onlyOwner`, which can be applied to your functions to restrict their use to 37 | * the owner. 38 | */ 39 | contract Ownable is Context { 40 | address private _owner; 41 | 42 | event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); 43 | 44 | /** 45 | * @dev Initializes the contract setting the deployer as the initial owner. 46 | */ 47 | constructor () internal { 48 | _owner = _msgSender(); 49 | emit OwnershipTransferred(address(0), _owner); 50 | } 51 | 52 | /** 53 | * @dev Returns the address of the current owner. 54 | */ 55 | function owner() public view returns (address) { 56 | return _owner; 57 | } 58 | 59 | /** 60 | * @dev Throws if called by any account other than the owner. 61 | */ 62 | modifier onlyOwner() { 63 | require(isOwner(), "Ownable: caller is not the owner"); 64 | _; 65 | } 66 | 67 | /** 68 | * @dev Returns true if the caller is the current owner. 69 | */ 70 | function isOwner() public view returns (bool) { 71 | return _msgSender() == _owner; 72 | } 73 | 74 | /** 75 | * @dev Leaves the contract without owner. It will not be possible to call 76 | * `onlyOwner` functions anymore. Can only be called by the current owner. 77 | * 78 | * NOTE: Renouncing ownership will leave the contract without an owner, 79 | * thereby removing any functionality that is only available to the owner. 80 | */ 81 | function renounceOwnership() public onlyOwner { 82 | emit OwnershipTransferred(_owner, address(0)); 83 | _owner = address(0); 84 | } 85 | 86 | /** 87 | * @dev Transfers ownership of the contract to a new account (`newOwner`). 88 | * Can only be called by the current owner. 89 | */ 90 | function transferOwnership(address newOwner) public onlyOwner { 91 | _transferOwnership(newOwner); 92 | } 93 | 94 | /** 95 | * @dev Transfers ownership of the contract to a new account (`newOwner`). 96 | */ 97 | function _transferOwnership(address newOwner) internal { 98 | require(newOwner != address(0), "Ownable: new owner is the zero address"); 99 | emit OwnershipTransferred(_owner, newOwner); 100 | _owner = newOwner; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /contracts/zeppelin/SafeERC20.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.17; 2 | 3 | import "../SafeMath.sol"; 4 | import "./Address.sol"; 5 | import "../IERC20.sol"; 6 | 7 | /** 8 | * @title SafeERC20 9 | * @dev Wrappers around ERC20 operations that throw on failure (when the token 10 | * contract returns false). Tokens that return no value (and instead revert or 11 | * throw on failure) are also supported, non-reverting calls are assumed to be 12 | * successful. 13 | * To use this library you can add a `using SafeERC20 for ERC20;` statement to your contract, 14 | * which allows you to call the safe operations as `token.safeTransfer(...)`, etc. 15 | */ 16 | library SafeERC20 { 17 | using SafeMath for uint256; 18 | using Address for address; 19 | 20 | function safeTransfer(IERC20 token, address to, uint256 value) internal { 21 | callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); 22 | } 23 | 24 | function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal { 25 | callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value)); 26 | } 27 | 28 | function safeApprove(IERC20 token, address spender, uint256 value) internal { 29 | // safeApprove should only be called when setting an initial allowance, 30 | // or when resetting it to zero. To increase and decrease it, use 31 | // 'safeIncreaseAllowance' and 'safeDecreaseAllowance' 32 | // solhint-disable-next-line max-line-length 33 | require((value == 0) || (token.allowance(address(this), spender) == 0), 34 | "SafeERC20: approve from non-zero to non-zero allowance" 35 | ); 36 | callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value)); 37 | } 38 | 39 | function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal { 40 | uint256 newAllowance = token.allowance(address(this), spender).add(value); 41 | callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); 42 | } 43 | 44 | function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal { 45 | uint256 newAllowance = token.allowance(address(this), spender).sub(value, "SafeERC20: decreased allowance below zero"); 46 | callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); 47 | } 48 | 49 | /** 50 | * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement 51 | * on the return value: the return value is optional (but if data is returned, it must not be false). 52 | * @param token The token targeted by the call. 53 | * @param data The call data (encoded using abi.encode or one of its variants). 54 | */ 55 | function callOptionalReturn(IERC20 token, bytes memory data) private { 56 | // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since 57 | // we're implementing it ourselves. 58 | 59 | // A Solidity high level call has three parts: 60 | // 1. The target address is checked to verify it contains contract code 61 | // 2. The call itself is made, and success asserted 62 | // 3. The return value is decoded, which in turn checks the size of the returned data. 63 | // solhint-disable-next-line max-line-length 64 | require(address(token).isContract(), "SafeERC20: call to non-contract"); 65 | 66 | // solhint-disable-next-line avoid-low-level-calls 67 | (bool success, bytes memory returndata) = address(token).call(data); 68 | require(success, "SafeERC20: low-level call failed"); 69 | 70 | if (returndata.length > 0) { // Return data is optional 71 | // solhint-disable-next-line max-line-length 72 | require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed"); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "boosted-finance-contracts", 3 | "version": "1.0.0", 4 | "description": "Smart contract repo for Boosted Finance", 5 | "repository": "git@github.com:Boosted-Finance/smart-contracts.git", 6 | "author": "JoaquinLiHua@protonmail.com", 7 | "license": "MIT", 8 | "scripts": { 9 | "compile": "npx buidler compile", 10 | "test": "npx buidler test" 11 | }, 12 | "devDependencies": { 13 | "@nomiclabs/buidler": "^1.4.4", 14 | "@nomiclabs/buidler-ethers": "^2.0.0", 15 | "@nomiclabs/buidler-etherscan": "^2.0.0", 16 | "@nomiclabs/buidler-truffle5": "^1.3.4", 17 | "@nomiclabs/buidler-web3": "^1.3.4", 18 | "@openzeppelin/contracts": "^2.5.1", 19 | "@openzeppelin/test-helpers": "0.5.6", 20 | "@uniswap/v2-core": "^1.0.1", 21 | "@uniswap/v2-periphery": "^1.1.0-beta.0", 22 | "buidler-ethers-v5": "^0.2.2", 23 | "chai": "^4.2.0", 24 | "chai-as-promised": "^7.1.1", 25 | "chai-bn": "^0.2.1", 26 | "ethereum-waffle": "^3.0.2", 27 | "solc": "^0.5.17" 28 | }, 29 | "dependencies": { 30 | "dotenv": "^8.2.0", 31 | "ethers": "^5.0.9", 32 | "prettier": "^2.1.1", 33 | "solhint": "^3.2.0" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /scripts/defiPools.js: -------------------------------------------------------------------------------- 1 | usePlugin('@nomiclabs/buidler-ethers'); 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | const BN = require('ethers').BigNumber; 5 | 6 | let configPath; 7 | 8 | const uniswapRouter = "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D"; 9 | let boostTokenAddress; 10 | let gasPrice = new BN.from(275).mul(new BN.from(10).pow(new BN.from(9))); 11 | let setter; 12 | let starttime; 13 | let duration; 14 | let tokens; 15 | let boostToken; 16 | let boostTokenAmount; 17 | let internalPoolBoostAmount = new BN.from('60000').mul(new BN.from('10').pow(new BN.from('18'))); 18 | let rewardsPool; 19 | 20 | task('deployTokenRewards', 'deploy boost token + reward pools').setAction(async () => { 21 | network = await ethers.provider.getNetwork(); 22 | const [deployer] = await ethers.getSigners(); 23 | let deployerAddress = await deployer.getAddress(); 24 | let BoostToken; 25 | let RewardsPool; 26 | 27 | // use mainnet contracts 28 | BoostToken = await ethers.getContractFactory('BoostToken'); 29 | RewardsPool = await ethers.getContractFactory('BoostRewardsPool'); 30 | // 4000 per pool 31 | boostTokenAmount = new BN.from('4000').mul(new BN.from('10').pow(new BN.from('18'))); 32 | configPath = path.join(__dirname, './mainnet_settings.json'); 33 | 34 | readParams(JSON.parse(fs.readFileSync(configPath, 'utf8'))); 35 | console.log("Deploying token..."); 36 | boostToken = await BoostToken.deploy({gasPrice: gasPrice}); 37 | await boostToken.deployed(); 38 | console.log(`boostToken: ${boostToken.address}`); 39 | 40 | for (let token of tokens) { 41 | console.log(`Deploying ${token.name} rewards pool...`); 42 | rewardsPool = await RewardsPool.deploy( 43 | new BN.from(token.cap), 44 | token.address, 45 | boostToken.address, 46 | setter, 47 | uniswapRouter, 48 | starttime, 49 | duration, 50 | {gasPrice: gasPrice} 51 | ); 52 | await rewardsPool.deployed(); 53 | console.log(`${token.name} pool address: ${rewardsPool.address}`); 54 | console.log('Transferring boost tokens to pool'); 55 | await boostToken.transfer(rewardsPool.address, boostTokenAmount, {gasPrice: gasPrice}); 56 | console.log('Notifying reward amt'); 57 | await rewardsPool.notifyRewardAmount(boostTokenAmount, {gasPrice: gasPrice}); 58 | console.log(`Renouncing ownership of ${token.name} pool`); 59 | await rewardsPool.renounceOwnership({gasPrice: gasPrice}); 60 | } 61 | 62 | await boostToken.transfer(setter, internalPoolBoostAmount); 63 | await boostToken.removeMinter(deployerAddress); 64 | await boostToken.setGovernance(ethers.constants.AddressZero); 65 | process.exit(0); 66 | }); 67 | 68 | function readParams(jsonInput) { 69 | boostTokenAddress = jsonInput.boostToken; 70 | setter = jsonInput.setter; 71 | tokens = jsonInput.tokens; 72 | starttime = jsonInput.starttime; 73 | duration = jsonInput.duration; 74 | } 75 | -------------------------------------------------------------------------------- /scripts/internalPoolGov.js: -------------------------------------------------------------------------------- 1 | usePlugin('@nomiclabs/buidler-ethers'); 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | const BN = require('ethers').BigNumber; 5 | 6 | let configPath; 7 | 8 | let setter; 9 | let stablecoin; 10 | let boostTokenAddress; 11 | let boostToken; 12 | let boostTokenAmount = new BN.from('60000').mul(new BN.from('10').pow(new BN.from('18'))); 13 | let internalPool; 14 | let gov; 15 | 16 | task('internalPoolGov', 'deploy internal pool + gov contract').setAction(async () => { 17 | network = await ethers.provider.getNetwork(); 18 | let InternalPool; 19 | let BoostGov; 20 | let uniswapFactory = await ethers.getContractAt('IUniswapV2Factory', '0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f'); 21 | let uniswapRouter = await ethers.getContractAt('UniswapRouter', '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D'); 22 | if (network == 1) { 23 | // user mainnet contracts 24 | InternalPool = await ethers.getContractFactory('BoostRewardsInternalPool'); 25 | BoostGov = await ethers.getContractFactory('BoostGov'); 26 | configPath = path.join(__dirname, './mainnet_settings.json'); 27 | readParams(JSON.parse(fs.readFileSync(configPath, 'utf8'))); 28 | boostToken = await ethers.getContractAt('BoostToken', boostTokenAddress); 29 | } else { 30 | // use kovan contracts 31 | InternalPool = await ethers.getContractFactory('BoostRewardsInternalPoolKovan'); 32 | BoostGov = await ethers.getContractFactory('BoostGovKovan'); 33 | configPath = path.join(__dirname, './kovan_settings.json'); 34 | readParams(JSON.parse(fs.readFileSync(configPath, 'utf8'))); 35 | boostToken = await ethers.getContractAt('BoostToken', boostTokenAddress); 36 | } 37 | 38 | // create uniswap pool, get address 39 | let weth = await uniswapRouter.WETH(); 40 | await uniswapFactory.createPair(weth, boostTokenAddress); 41 | let uniswapToken = await uniswapFactory.getPair(weth, boostTokenAddress); 42 | console.log(`uniswap LP Token: ${uniswapToken}`); 43 | // internal boost pool: cap to 100 tokens for now 44 | internalPool = await InternalPool.deploy( 45 | new BN.from('100000').mul(new BN.from('10').pow(new BN.from('18'))), 46 | uniswapToken, 47 | boostTokenAddress, 48 | setter 49 | ); 50 | await internalPool.deployed(); 51 | console.log(`${uniswapToken} pool address: ${internalPool.address}`); 52 | await internalPool.notifyRewardAmount(boostTokenAmount); 53 | await internalPool.renounceOwnership(); 54 | 55 | // deploy gov 56 | gov = await BoostGov.deploy(boostTokenAddress, stablecoin); 57 | await gov.deployed(); 58 | console.log(`governance address: ${gov.address}`); 59 | console.log('TODOs:'); 60 | console.log('1) Transfer reward from multisig to internal pool'); 61 | console.log('2) Call `setGovernance` for each pool'); 62 | process.exit(0); 63 | }); 64 | 65 | function readParams(jsonInput) { 66 | setter = jsonInput.setter; 67 | stablecoin = jsonInput.stablecoin; 68 | boostTokenAddress = jsonInput.boostToken; 69 | } 70 | -------------------------------------------------------------------------------- /scripts/mainnet_settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "setter": "", 3 | "stablecoin": "0xdf5e0e81dff6faf3a7e52ba697820c5e32d806a8", 4 | "tokens": [ 5 | { 6 | "name": "COMP", 7 | "address": "0xc00e94cb662c3520282e6f5717214004a7f26888", 8 | "cap": "" 9 | }, 10 | { 11 | "name": "MKR", 12 | "address": "0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2", 13 | "cap": "" 14 | }, 15 | { 16 | "name": "LEND", 17 | "address": "0x80fb784b7ed66730e8b1dbd9820afd29931aab03", 18 | "cap": "" 19 | }, 20 | { 21 | "name": "SNX", 22 | "address": "0xc011a73ee8576fb46f5e1c5751ca3b9fe0af2a6f", 23 | "cap": "" 24 | }, 25 | { 26 | "name": "KNC", 27 | "address": "0xdd974d5c2e2928dea5f71b9825b8b646686bd200", 28 | "cap": "" 29 | }, 30 | { 31 | "name": "REN", 32 | "address": "0x408e41876cccdc0f92210600ef50372656052a38", 33 | "cap": "" 34 | }, 35 | { 36 | "name": "YFI", 37 | "address": "0x0bc529c00c6401aef6d220be8c6ea1667f6ad93e", 38 | "cap": "" 39 | }, 40 | { 41 | "name": "LINK", 42 | "address": "0x514910771af9ca656af840dff83e8264ecf986ca", 43 | "cap": "" 44 | }, 45 | { 46 | "name": "BAND", 47 | "address": "0xba11d00c5f74255f56a5e366f4f77f5a186d7f55", 48 | "cap": "" 49 | }, 50 | { 51 | "name": "WETH", 52 | "address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", 53 | "cap": "" 54 | } 55 | ] 56 | } 57 | -------------------------------------------------------------------------------- /scripts/wave3Distribution.js: -------------------------------------------------------------------------------- 1 | usePlugin('@nomiclabs/buidler-ethers'); 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | const BN = require('ethers').BigNumber; 5 | 6 | let configPath; 7 | 8 | const uniswapRouter = "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D"; 9 | let boostTokenAddress; 10 | let gasPrice = new BN.from(20).mul(new BN.from(10).pow(new BN.from(9))); 11 | let starttime; 12 | let duration; 13 | let uniswapLP; 14 | let multisig; 15 | let tokens; 16 | let internalPoolBoostAmount = new BN.from('15000').mul(new BN.from('10').pow(new BN.from('18'))); 17 | let rewardsPoolBoostAmount = new BN.from('3750').mul(new BN.from('10').pow(new BN.from('18'))); 18 | let rewardsPool; 19 | 20 | task('wave3Deploy', 'deploy wave 3 reward pools').setAction(async () => { 21 | network = await ethers.provider.getNetwork(); 22 | const [deployer] = await ethers.getSigners(); 23 | let deployerAddress = await deployer.getAddress(); 24 | let RewardsPool; 25 | 26 | configPath = path.join(__dirname, './mainnet_settings.json'); 27 | readParams(JSON.parse(fs.readFileSync(configPath, 'utf8'))); 28 | 29 | // deploy treasury 30 | console.log('Deploying treasury'); 31 | Treasury = await ethers.getContractFactory('Treasury'); 32 | treasury = await Treasury.deploy( 33 | uniswapRouter, 34 | stablecoin, 35 | multisig, 36 | {gasPrice: gasPrice} 37 | ); 38 | await treasury.deployed(); 39 | console.log(`treasury address: ${treasury.address}`); 40 | await pressToContinue(); 41 | 42 | // deploy governance 43 | console.log('Deploying gov'); 44 | Gov = await ethers.getContractFactory('BoostGovV2'); 45 | gov = await Gov.deploy( 46 | boostTokenAddress, 47 | treasury.address, 48 | uniswapRouter 49 | ); 50 | await gov.deployed(); 51 | console.log(`gov address: ${gov.address}`); 52 | await pressToContinue(); 53 | 54 | // set governance contract in treasury 55 | console.log('Setting gov in treasury'); 56 | await treasury.setGov(gov.address); 57 | await pressToContinue(); 58 | 59 | // transfer treasury ownership to multisig 60 | console.log('transferring treasury ownership to multisig'); 61 | await treasury.transferOwnership(multisig); 62 | await pressToContinue(); 63 | 64 | // deploy rewards 65 | RewardsPool = await ethers.getContractFactory('BoostRewardsV2'); 66 | for (let token of tokens) { 67 | console.log(`Deploying ${token.name} rewards pool...`); 68 | rewardsPool = await RewardsPool.deploy( 69 | new BN.from(token.cap), 70 | token.address, 71 | boostTokenAddress, 72 | treasury.address, 73 | uniswapRouter, 74 | starttime, 75 | duration, 76 | {gasPrice: gasPrice} 77 | ); 78 | await rewardsPool.deployed(); 79 | console.log(`${token.name} pool address: ${rewardsPool.address}`); 80 | await pressToContinue(); 81 | console.log(`Notifying reward amt: ${rewardsPoolBoostAmount}`); 82 | await rewardsPool.notifyRewardAmount(rewardsPoolBoostAmount, {gasPrice: gasPrice}); 83 | await pressToContinue(); 84 | console.log(`Transferring ownership of ${token.name} pool`); 85 | await rewardsPool.transferOwnership(multisig, {gasPrice: gasPrice}); 86 | await pressToContinue(); 87 | } 88 | 89 | console.log("Deploying uniswap LP pool"); 90 | rewardsPool = await RewardsPool.deploy( 91 | new BN.from(uniswapLP.cap), 92 | uniswapLP.address, 93 | boostTokenAddress, 94 | treasury.address, 95 | uniswapRouter, 96 | starttime, 97 | duration, 98 | {gasPrice: gasPrice} 99 | ); 100 | await rewardsPool.deployed(); 101 | console.log(`${uniswapLP.name} pool address: ${rewardsPool.address}`); 102 | await pressToContinue(); 103 | console.log(`Notifying reward amt: ${internalPoolBoostAmount}`); 104 | await rewardsPool.notifyRewardAmount(internalPoolBoostAmount, {gasPrice: gasPrice}); 105 | await pressToContinue(); 106 | console.log(`Transferring ownership of ${uniswapLP.name} pool`); 107 | await rewardsPool.transferOwnership(multisig, {gasPrice: gasPrice}); 108 | await pressToContinue(); 109 | 110 | process.exit(0); 111 | }); 112 | 113 | function readParams(jsonInput) { 114 | starttime = jsonInput.starttime; 115 | duration = jsonInput.duration; 116 | multisig = jsonInput.multisig; 117 | stablecoin = jsonInput.stablecoin; 118 | boostTokenAddress = jsonInput.boostToken; 119 | uniswapLP = jsonInput.uniswapLP; 120 | tokens = jsonInput.tokens; 121 | } 122 | 123 | async function pressToContinue() { 124 | console.log("Checkpoint... Press any key to continue!"); 125 | await keypress(); 126 | } 127 | 128 | const keypress = async () => { 129 | process.stdin.setRawMode(true) 130 | return new Promise(resolve => process.stdin.once('data', data => { 131 | const byteArray = [...data] 132 | if (byteArray.length > 0 && byteArray[0] === 3) { 133 | console.log('^C') 134 | process.exit(1) 135 | } 136 | process.stdin.setRawMode(false) 137 | resolve() 138 | })) 139 | } -------------------------------------------------------------------------------- /test/BoostGov.js: -------------------------------------------------------------------------------- 1 | const {expectRevert, time} = require('@openzeppelin/test-helpers'); 2 | const {BN} = require('@openzeppelin/test-helpers/src/setup'); 3 | const {expect, assert} = require('chai'); 4 | const UniswapV2FactoryBytecode = require('@uniswap/v2-core/build/UniswapV2Factory.json'); 5 | const UniswapV2Router02Bytecode = require('@uniswap/v2-periphery/build/UniswapV2Router02.json'); 6 | const TruffleContract = require('@truffle/contract'); 7 | const BoostGov = artifacts.require('BoostGov'); 8 | const BoostToken = artifacts.require('BoostToken'); 9 | const TestToken = artifacts.require('Token'); 10 | const WETH = artifacts.require('WETH9'); 11 | require('chai').use(require('chai-as-promised')).use(require('chai-bn')(BN)).should(); 12 | 13 | function getCurrentBlockTime() { 14 | return new Promise(function (fulfill, reject) { 15 | web3.eth.getBlock('latest', false, function (err, result) { 16 | if (err) reject(err); 17 | else fulfill(result.timestamp); 18 | }); 19 | }); 20 | } 21 | 22 | function mineBlockAtTime(timestamp) { 23 | return new Promise((resolve, reject) => { 24 | web3.currentProvider.send.bind(web3.currentProvider)( 25 | { 26 | jsonrpc: '2.0', 27 | method: 'evm_mine', 28 | params: [timestamp], 29 | id: new Date().getTime(), 30 | }, 31 | (err, res) => { 32 | if (err) { 33 | return reject(err); 34 | } 35 | resolve(res); 36 | } 37 | ); 38 | }); 39 | } 40 | 41 | const MAX_UINT256 = new BN(2).pow(new BN(256)).sub(new BN(1)); 42 | 43 | contract('BoostGov', ([governance, alice, bob, carol]) => { 44 | before('init uniswap, tokens, and governance', async () => { 45 | this.boost = await BoostToken.new({from: governance}); 46 | this.weth = await WETH.new({from: governance}); 47 | this.ycrv = await TestToken.new('Curve.fi yDAI/yUSDC/yUSDT/yTUSD', 'yDAI+yUSDC+yUSDT+yTUSD', '18', { 48 | from: governance, 49 | }); 50 | 51 | // Setup Uniswap 52 | const UniswapV2Factory = TruffleContract(UniswapV2FactoryBytecode); 53 | const UniswapV2Router02 = TruffleContract(UniswapV2Router02Bytecode); 54 | UniswapV2Factory.setProvider(web3.currentProvider); 55 | UniswapV2Router02.setProvider(web3.currentProvider); 56 | this.uniswapV2Factory = await UniswapV2Factory.new(governance, {from: governance}); 57 | this.uniswapV2Router = await UniswapV2Router02.new(this.uniswapV2Factory.address, this.weth.address, { 58 | from: governance, 59 | }); 60 | 61 | // Create Uniswap pair 62 | await this.boost.approve(this.uniswapV2Router.address, MAX_UINT256, {from: governance}); 63 | await this.ycrv.approve(this.uniswapV2Router.address, MAX_UINT256, {from: governance}); 64 | await this.uniswapV2Factory.createPair(this.boost.address, this.weth.address, {from: governance}); 65 | await this.uniswapV2Factory.createPair(this.ycrv.address, this.weth.address, {from: governance}); 66 | await this.uniswapV2Router.addLiquidityETH( 67 | this.boost.address, 68 | web3.utils.toWei('10000'), 69 | '0', 70 | '0', 71 | governance, 72 | MAX_UINT256, 73 | {value: web3.utils.toWei('100'), from: governance} 74 | ); 75 | await this.uniswapV2Router.addLiquidityETH( 76 | this.ycrv.address, 77 | web3.utils.toWei('10000'), 78 | '0', 79 | '0', 80 | governance, 81 | MAX_UINT256, 82 | {value: web3.utils.toWei('100'), from: governance} 83 | ); 84 | 85 | // Deploy governance 86 | this.gov = await BoostGov.new(this.boost.address, this.ycrv.address, this.uniswapV2Router.address, { 87 | from: governance, 88 | }); 89 | 90 | // Bootstrap BOOST treasury and user balances 91 | await this.ycrv.transfer(this.gov.address, web3.utils.toWei('10000'), {from: governance}); 92 | await this.boost.transfer(alice, web3.utils.toWei('1000'), {from: governance}); 93 | await this.boost.transfer(bob, web3.utils.toWei('1000'), {from: governance}); 94 | await this.boost.transfer(carol, web3.utils.toWei('10000'), {from: governance}); 95 | 96 | // Set balances and approvals 97 | await this.weth.deposit({value: web3.utils.toWei('300'), from: governance}); 98 | await this.weth.transfer(alice, web3.utils.toWei('100'), {from: governance}); 99 | await this.weth.transfer(bob, web3.utils.toWei('100'), {from: governance}); 100 | await this.weth.transfer(carol, web3.utils.toWei('100'), {from: governance}); 101 | await this.boost.approve(this.gov.address, MAX_UINT256, {from: alice}); 102 | await this.boost.approve(this.gov.address, MAX_UINT256, {from: bob}); 103 | await this.boost.approve(this.gov.address, MAX_UINT256, {from: carol}); 104 | }); 105 | 106 | it('should have correct constants', async () => { 107 | assert.equal((await this.gov.proposalPeriod()).toString(), 2 * 24 * 60 * 60); 108 | assert.equal((await this.gov.lockPeriod()).toString(), 3 * 24 * 60 * 60); 109 | assert.equal(await this.gov.minimum(), web3.utils.toWei('13.37')); 110 | assert.equal(await this.gov.stablecoin(), this.ycrv.address); 111 | }); 112 | 113 | it('should revert when proposing due to not staking BOOST', async () => { 114 | await expectRevert( 115 | this.gov.propose('https://gov.boosted.finance/', web3.utils.toWei('1'), alice, {from: alice}), 116 | 'stake more boost' 117 | ); 118 | }); 119 | 120 | it('should successfully submit a proposal', async () => { 121 | const url = 'https://gov.boosted.finance/'; 122 | const withdrawAmount = web3.utils.toWei('10'); 123 | const withdrawAddress = alice; 124 | 125 | await this.gov.stake(web3.utils.toWei('100'), {from: alice}); 126 | await this.gov.propose(url, withdrawAmount, withdrawAddress, {from: alice}) 127 | 128 | const proposal = await this.gov.proposals(0); 129 | assert.equal((await this.gov.totalSupply()).toString(), web3.utils.toWei('100')); 130 | assert.equal(await this.gov.proposalCount(), 1); 131 | assert.equal(proposal['proposer'], alice); 132 | assert.equal(proposal['url'], url); 133 | assert.equal(proposal['withdrawAmount'], withdrawAmount); 134 | assert.equal(proposal['withdrawAddress'], alice); 135 | }); 136 | 137 | it('should successfully vote for proposal', async () => { 138 | await this.gov.voteFor(0, {from: alice}); 139 | 140 | const proposal = await this.gov.proposals(0); 141 | assert.equal(proposal['totalForVotes'], web3.utils.toWei('100')); 142 | }); 143 | 144 | it('should successfully vote against proposal', async () => { 145 | await this.gov.stake(web3.utils.toWei('10'), {from: bob}); 146 | await this.gov.voteAgainst(0, {from: bob}); 147 | 148 | const proposal = await this.gov.proposals(0); 149 | assert.equal(proposal['totalAgainstVotes'], web3.utils.toWei('10')); 150 | }); 151 | 152 | it('should allow to stake and vote more on earlier chosen side', async () => { 153 | await this.gov.stake(web3.utils.toWei('100'), {from: alice}); 154 | await this.gov.voteFor(0, {from: alice}); 155 | await this.gov.stake(web3.utils.toWei('1'), {from: bob}); 156 | await this.gov.voteAgainst(0, {from: bob}); 157 | 158 | const proposal = await this.gov.proposals(0); 159 | assert.equal(proposal['totalForVotes'], web3.utils.toWei('200')); 160 | assert.equal(proposal['totalAgainstVotes'], web3.utils.toWei('11')); 161 | }); 162 | 163 | it('should revert when trying to switch votes', async () => { 164 | await expectRevert(this.gov.voteAgainst(0, {from: alice}), 'cannot switch votes'); 165 | await expectRevert(this.gov.voteFor(0, {from: bob}), 'cannot switch votes'); 166 | }); 167 | 168 | it('should revert voting for non-existing proposal', async () => { 169 | await expectRevert(this.gov.voteFor('999', {from: alice}), '>end'); 170 | await expectRevert(this.gov.voteAgainst('999', {from: alice}), '>end'); 171 | }); 172 | 173 | it('should revert when trying to withdraw when vote locked', async () => { 174 | await expectRevert(this.gov.withdraw(web3.utils.toWei('10'), {from: alice}), 'tokens locked'); 175 | await expectRevert(this.gov.withdraw(web3.utils.toWei('10'), {from: bob}), 'tokens locked'); 176 | }); 177 | 178 | it('should revert when trying to resolve ongoing proposal', async () => { 179 | await expectRevert(this.gov.resolveProposal(0, {from: alice}), 'ongoing proposal'); 180 | }); 181 | 182 | it('should revert when trying to resolve non-existing proposal', async () => { 183 | await expectRevert(this.gov.resolveProposal(999, {from: alice}), 'non-existent proposal'); 184 | }); 185 | 186 | it('should resolve proposal', async () => { 187 | const proposal = await this.gov.proposals(0); 188 | 189 | // Mine block and move timestamp to beyond proposal period 190 | await mineBlockAtTime(parseInt(proposal['start']) + (2 * 24 * 60 * 60)); 191 | 192 | await this.gov.resolveProposal(0, {from: alice}); 193 | assert.equal(await this.ycrv.balanceOf(alice), web3.utils.toWei('10')); 194 | assert.equal((await this.gov.proposals(0))['hasResolved'], true); 195 | }); 196 | 197 | it('should resolve proposal but not receive funds due to quorum not being met', async () => { 198 | const url = 'https://gov.boosted.finance/'; 199 | const withdrawAmount = web3.utils.toWei('10'); 200 | const withdrawAddress = alice; 201 | 202 | await this.gov.stake(web3.utils.toWei('2000'), {from: carol}); 203 | await this.gov.propose(url, withdrawAmount, withdrawAddress, {from: alice}) 204 | await this.gov.voteFor(1, {from: alice}); 205 | await this.gov.voteAgainst(1, {from: bob}); 206 | 207 | const proposal = await this.gov.proposals(1); 208 | 209 | // Mine block and move timestamp to beyond proposal period 210 | await mineBlockAtTime(parseInt(proposal['start']) + (2 * 24 * 60 * 60)); 211 | 212 | await this.gov.resolveProposal(1, {from: alice}); 213 | assert.equal(await this.ycrv.balanceOf(alice), web3.utils.toWei('10')); // No change in yCRV balance 214 | assert.equal((await this.gov.proposals(1))['hasResolved'], true); 215 | }); 216 | 217 | it('should resolve proposal but not receive funds due to against votes > for votes', async () => { 218 | const url = 'https://gov.boosted.finance/'; 219 | const withdrawAmount = web3.utils.toWei('10'); 220 | const withdrawAddress = alice; 221 | 222 | await this.gov.propose(url, withdrawAmount, withdrawAddress, {from: alice}) 223 | await this.gov.voteFor(2, {from: alice}); 224 | await this.gov.voteFor(2, {from: bob}); 225 | await this.gov.voteAgainst(2, {from: carol}); 226 | 227 | const proposal = await this.gov.proposals(2); 228 | 229 | // Mine block and move timestamp to beyond proposal period 230 | await mineBlockAtTime(parseInt(proposal['start']) + (2 * 24 * 60 * 60)); 231 | 232 | await this.gov.resolveProposal(2, {from: alice}); 233 | assert.equal(await this.ycrv.balanceOf(alice), web3.utils.toWei('10')); // No change in yCRV balance 234 | assert.equal((await this.gov.proposals(2))['hasResolved'], true); 235 | }); 236 | 237 | it('should resolve proposal but proposer gets slashed due to MIN_QUORUM_PUNISHMENT and WITHDRAW_THRESHOLD', async () => { 238 | const url = 'https://gov.boosted.finance/'; 239 | const withdrawAmount = web3.utils.toWei('1001'); 240 | const withdrawAddress = alice; 241 | 242 | await this.gov.stake(web3.utils.toWei('5000'), {from: carol}); 243 | await this.gov.propose(url, withdrawAmount, withdrawAddress, {from: alice}) 244 | await this.gov.voteFor(3, {from: alice}); 245 | 246 | const proposal = await this.gov.proposals(3); 247 | 248 | // Mine block and move timestamp to beyond proposal period 249 | await mineBlockAtTime(parseInt(proposal['start']) + (2 * 24 * 60 * 60)); 250 | 251 | await this.gov.resolveProposal(3, {from: alice}); 252 | assert.equal(await this.gov.balanceOf(alice), 0); // All BOOST balances is slashed 253 | assert.equal((await this.gov.proposals(3))['hasResolved'], true); 254 | }); 255 | 256 | it('should successfully withdraw stake from governance', async () => { 257 | // Mine block and move timestamp to beyond proposal period 258 | await mineBlockAtTime(await getCurrentBlockTime() + (3 * 24 * 60 * 60)); 259 | 260 | await this.gov.withdraw(await this.gov.balanceOf(alice), {from: alice}); 261 | await this.gov.withdraw(await this.gov.balanceOf(bob), {from: bob}); 262 | await this.gov.withdraw(await this.gov.balanceOf(carol), {from: carol}); 263 | 264 | assert.equal(await this.gov.balanceOf(alice), 0); 265 | assert.equal(await this.gov.balanceOf(bob), 0); 266 | assert.equal(await this.gov.balanceOf(carol), 0); 267 | }); 268 | }); 269 | -------------------------------------------------------------------------------- /test/BoostRewardsPool.js: -------------------------------------------------------------------------------- 1 | const {constants, expectRevert, time} = require('@openzeppelin/test-helpers'); 2 | const {BN} = require('@openzeppelin/test-helpers/src/setup'); 3 | const {expect, assert} = require('chai'); 4 | const UniswapV2FactoryBytecode = require('@uniswap/v2-core/build/UniswapV2Factory.json'); 5 | const UniswapV2Router02Bytecode = require('@uniswap/v2-periphery/build/UniswapV2Router02.json'); 6 | const TruffleContract = require('@truffle/contract'); 7 | const BoostRewardsPool = artifacts.require('BoostRewardsPool'); 8 | const BoostGov = artifacts.require('BoostGov'); 9 | const BoostToken = artifacts.require('BoostToken'); 10 | const TestToken = artifacts.require('Token'); 11 | const WETH = artifacts.require('WETH9'); 12 | require('chai').use(require('chai-as-promised')).use(require('chai-bn')(BN)).should(); 13 | 14 | function getCurrentBlock() { 15 | return new Promise(function (fulfill, reject) { 16 | web3.eth.getBlockNumber(function (err, result) { 17 | if (err) reject(err); 18 | else fulfill(result); 19 | }); 20 | }); 21 | } 22 | 23 | function getCurrentBlockTime() { 24 | return new Promise(function (fulfill, reject) { 25 | web3.eth.getBlock('latest', false, function (err, result) { 26 | if (err) reject(err); 27 | else fulfill(result.timestamp); 28 | }); 29 | }); 30 | } 31 | 32 | async function mineBlocks(blocks) { 33 | for (let i = 0; i < blocks; i++) { 34 | await time.advanceBlock(); 35 | } 36 | } 37 | 38 | function mineBlockAtTime(timestamp) { 39 | return new Promise((resolve, reject) => { 40 | web3.currentProvider.send.bind(web3.currentProvider)( 41 | { 42 | jsonrpc: '2.0', 43 | method: 'evm_mine', 44 | params: [timestamp], 45 | id: new Date().getTime(), 46 | }, 47 | (err, res) => { 48 | if (err) { 49 | return reject(err); 50 | } 51 | resolve(res); 52 | } 53 | ); 54 | }); 55 | } 56 | 57 | const DURATION = 7 * 24 * 60 * 60; 58 | const MAX_NUM_BOOSTERS = 5; 59 | const USDT_CAP_AMOUNT = (10000 * 10 ** 6).valueOf(); 60 | const WBTC_CAP_AMOUNT = (1 * 10 ** 8).valueOf(); 61 | const REWARD_AMOUNT = web3.utils.toWei('4000'); 62 | const MAX_UINT256 = new BN(2).pow(new BN(256)).sub(new BN(1)); 63 | let START_TIME; 64 | 65 | contract('BoostRewardsPool', ([governance, minter, alice]) => { 66 | before('init tokens and pools', async () => { 67 | // Set pool start time one day after 68 | START_TIME = await getCurrentBlockTime() + (24 * 60 * 60); 69 | 70 | // Setup tokens 71 | this.boost = await BoostToken.new({from: governance}); 72 | this.usdt = await TestToken.new('Tether USDT', 'USDT', '6', {from: governance}); 73 | this.wbtc = await TestToken.new('Wrapped Bitcoin', 'WBTC', '8', {from: governance}); 74 | this.ycrv = await TestToken.new('Curve.fi yDAI/yUSDC/yUSDT/yTUSD', 'yDAI+yUSDC+yUSDT+yTUSD', '18', { 75 | from: governance, 76 | }); 77 | this.weth = await WETH.new({from: governance}); 78 | 79 | // Setup Uniswap 80 | const UniswapV2Factory = TruffleContract(UniswapV2FactoryBytecode); 81 | const UniswapV2Router02 = TruffleContract(UniswapV2Router02Bytecode); 82 | UniswapV2Factory.setProvider(web3.currentProvider); 83 | UniswapV2Router02.setProvider(web3.currentProvider); 84 | this.uniswapV2Factory = await UniswapV2Factory.new(governance, {from: governance}); 85 | this.uniswapV2Router = await UniswapV2Router02.new(this.uniswapV2Factory.address, this.weth.address, { 86 | from: governance, 87 | }); 88 | 89 | // Create Uniswap pair 90 | await this.boost.approve(this.uniswapV2Router.address, MAX_UINT256, {from: governance}); 91 | await this.ycrv.approve(this.uniswapV2Router.address, MAX_UINT256, {from: governance}); 92 | await this.uniswapV2Factory.createPair(this.boost.address, this.weth.address, {from: governance}); 93 | await this.uniswapV2Factory.createPair(this.ycrv.address, this.weth.address, {from: governance}); 94 | await this.uniswapV2Router.addLiquidityETH( 95 | this.boost.address, 96 | web3.utils.toWei('10000'), 97 | '0', 98 | '0', 99 | governance, 100 | MAX_UINT256, 101 | {value: web3.utils.toWei('100'), from: governance} 102 | ); 103 | await this.uniswapV2Router.addLiquidityETH( 104 | this.ycrv.address, 105 | web3.utils.toWei('10000'), 106 | '0', 107 | '0', 108 | governance, 109 | MAX_UINT256, 110 | {value: web3.utils.toWei('100'), from: governance} 111 | ); 112 | 113 | // Setup pools 114 | this.usdtPool = await BoostRewardsPool.new( 115 | USDT_CAP_AMOUNT, 116 | this.usdt.address, 117 | this.boost.address, 118 | governance, 119 | this.uniswapV2Router.address, 120 | START_TIME, 121 | DURATION, 122 | { 123 | from: governance, 124 | } 125 | ); 126 | await this.boost.transfer(this.usdtPool.address, REWARD_AMOUNT, {from: governance}); 127 | this.wbtcPool = await BoostRewardsPool.new( 128 | WBTC_CAP_AMOUNT, 129 | this.wbtc.address, 130 | this.boost.address, 131 | governance, 132 | this.uniswapV2Router.address, 133 | START_TIME, 134 | DURATION, 135 | { 136 | from: governance, 137 | } 138 | ); 139 | await this.boost.transfer(this.wbtcPool.address, REWARD_AMOUNT, {from: governance}); 140 | 141 | // Set balances and approvals 142 | await this.weth.deposit({value: web3.utils.toWei('100'), from: governance}); 143 | await this.usdt.transfer(alice, 100000 * 10 ** 6, {from: governance}); 144 | await this.wbtc.transfer(alice, 1000 * 10 ** 8, {from: governance}); 145 | await this.weth.transfer(alice, web3.utils.toWei('100'), {from: governance}); 146 | await this.usdt.approve(this.usdtPool.address, MAX_UINT256, {from: alice}); 147 | await this.wbtc.approve(this.wbtcPool.address, MAX_UINT256, {from: alice}); 148 | await this.boost.approve(this.usdtPool.address, MAX_UINT256, {from: alice}); 149 | await this.boost.approve(this.wbtcPool.address, MAX_UINT256, {from: alice}); 150 | 151 | // Deploy governance but don't set yet 152 | this.gov = await BoostGov.new(this.boost.address, this.ycrv.address, this.uniswapV2Router.address, { 153 | from: governance, 154 | }); 155 | }); 156 | 157 | it('should test the rewards pool constants', async () => { 158 | // USDT pool 159 | assert.equal(await this.usdtPool.boostToken(), this.boost.address); 160 | assert.equal(await this.usdtPool.uniswapRouter(), this.uniswapV2Router.address); 161 | assert.equal(await this.usdtPool.tokenCapAmount(), USDT_CAP_AMOUNT); 162 | assert.equal(await this.usdtPool.MAX_NUM_BOOSTERS(), MAX_NUM_BOOSTERS); 163 | assert.equal(await this.usdtPool.duration(), DURATION); 164 | assert.equal(await this.usdtPool.starttime(), START_TIME); 165 | 166 | // WBTC pool 167 | assert.equal(await this.wbtcPool.boostToken(), this.boost.address); 168 | assert.equal(await this.wbtcPool.uniswapRouter(), this.uniswapV2Router.address); 169 | assert.equal(await this.wbtcPool.tokenCapAmount(), WBTC_CAP_AMOUNT); 170 | assert.equal(await this.wbtcPool.MAX_NUM_BOOSTERS(), MAX_NUM_BOOSTERS); 171 | assert.equal(await this.wbtcPool.duration(), DURATION); 172 | assert.equal(await this.wbtcPool.starttime(), START_TIME); 173 | }); 174 | 175 | it('should set the rewards per pool', async () => { 176 | await this.usdtPool.notifyRewardAmount(REWARD_AMOUNT, {from: governance}); 177 | assert.equal((await this.usdtPool.rewardRate()).valueOf(), REWARD_AMOUNT / DURATION - 1); 178 | assert.equal(await this.usdtPool.lastUpdateTime(), START_TIME); 179 | assert.equal(await this.usdtPool.periodFinish(), START_TIME + DURATION); 180 | 181 | await this.wbtcPool.notifyRewardAmount(REWARD_AMOUNT, {from: governance}); 182 | assert.equal((await this.wbtcPool.rewardRate()).valueOf(), REWARD_AMOUNT / DURATION - 1); 183 | assert.equal(await this.wbtcPool.lastUpdateTime(), START_TIME); 184 | assert.equal(await this.wbtcPool.periodFinish(), START_TIME + DURATION); 185 | }); 186 | 187 | it('should test renouncing governanceship per pool', async () => { 188 | await this.usdtPool.renounceOwnership({from: governance}); 189 | assert.equal(await this.usdtPool.governance(), constants.ZERO_ADDRESS); 190 | 191 | await this.wbtcPool.renounceOwnership({from: governance}); 192 | assert.equal(await this.wbtcPool.governance(), constants.ZERO_ADDRESS); 193 | }); 194 | 195 | it('should revert relevant functions if pool has not started yet', async () => { 196 | await expectRevert(this.usdtPool.stake(1000 * 10 ** 6, {from: alice}), 'not start'); 197 | await expectRevert(this.wbtcPool.stake(0.1 * 10 ** 8, {from: alice}), 'not start'); 198 | await expectRevert(this.usdtPool.getReward({from: alice}), 'not start'); 199 | await expectRevert(this.wbtcPool.getReward({from: alice}), 'not start'); 200 | await expectRevert(this.usdtPool.boost({from: alice}), 'not start'); 201 | await expectRevert(this.wbtcPool.boost({from: alice}), 'not start'); 202 | await expectRevert(this.usdtPool.withdraw(1, {from: alice}), 'not start'); 203 | await expectRevert(this.wbtcPool.withdraw(1, {from: alice}), 'not start'); 204 | await expectRevert(this.usdtPool.exit({from: alice}), 'not start'); 205 | await expectRevert(this.wbtcPool.exit({from: alice}), 'not start'); 206 | }); 207 | 208 | it('should test staking at a pool', async () => { 209 | // Mine block and move timestamp to beyond pool start time 210 | await mineBlockAtTime(START_TIME + 15); 211 | 212 | await this.usdtPool.stake(1000 * 10 ** 6, {from: alice}); 213 | assert.equal(await this.usdtPool.balanceOf(alice), 1000 * 10 ** 6); 214 | await this.wbtcPool.stake(0.5 * 10 ** 8, {from: alice}); 215 | assert.equal(await this.wbtcPool.balanceOf(alice), 0.5 * 10 ** 8); 216 | }); 217 | 218 | it('should revert staking at a pool with amount exceeding token within first 24hours', async () => { 219 | await expectRevert(this.usdtPool.stake(20000 * 10 ** 6, {from: alice}), 'token cap exceeded'); 220 | await expectRevert(this.wbtcPool.stake(2 * 10 ** 8, {from: alice}), 'token cap exceeded'); 221 | }); 222 | 223 | it('should revert staking at a pool with amount exceeding token cap', async () => { 224 | // Mine block and move timestamp to beyond 24hour token cap 225 | await mineBlockAtTime(START_TIME + 86400); 226 | 227 | await this.usdtPool.stake(20000 * 10 ** 6, {from: alice}); 228 | assert.equal(await this.usdtPool.balanceOf(alice), 21000 * 10 ** 6); 229 | await this.wbtcPool.stake(2 * 10 ** 8, {from: alice}); 230 | assert.equal(await this.wbtcPool.balanceOf(alice), 2.5 * 10 ** 8); 231 | }); 232 | 233 | it('should test getting rewards from a pool', async () => { 234 | // Mine 1000 blocks 235 | mineBlocks(1000); 236 | 237 | await this.usdtPool.getReward({from: alice}); 238 | await this.wbtcPool.getReward({from: alice}); 239 | 240 | assert((await this.boost.balanceOf(alice)).should.be.a.bignumber.that.is.greaterThan('0')); 241 | }); 242 | 243 | it('should revert purchasing yield boosters before intended start time', async () => { 244 | await expectRevert(this.usdtPool.boost({from: alice}), 'early boost purchase'); 245 | await expectRevert(this.wbtcPool.boost({from: alice}), 'early boost purchase'); 246 | }); 247 | 248 | it('should successfully purchase yield boosters', async () => { 249 | // Mine block and move timestamp to beyond 2 days 250 | await mineBlockAtTime(START_TIME + 172800); 251 | 252 | await this.usdtPool.boost({from: alice}); 253 | await this.wbtcPool.boost({from: alice}); 254 | 255 | const boosterPriceUSDT = await this.usdtPool.boosterPrice(); 256 | const boosterPriceWBTC = await this.wbtcPool.boosterPrice(); 257 | const boostersBoughtUSDT = await this.usdtPool.numBoostersBought(alice); 258 | const boostersBoughtWBTC = await this.wbtcPool.numBoostersBought(alice); 259 | 260 | assert.equal(boosterPriceUSDT, 1 * 10 ** 18 * 1.05); 261 | assert.equal(boosterPriceWBTC, 1 * 10 ** 18 * 1.05); 262 | assert.equal(boostersBoughtUSDT, 1); 263 | assert.equal(boostersBoughtWBTC, 1); 264 | }); 265 | 266 | it('should successfully set governance', async () => { 267 | await this.usdtPool.setGovernance(this.gov.address, {from: governance}); 268 | await this.wbtcPool.setGovernance(this.gov.address, {from: governance}); 269 | 270 | assert.equal(await this.usdtPool.governanceSetter(), constants.ZERO_ADDRESS); 271 | assert.equal(await this.wbtcPool.governanceSetter(), constants.ZERO_ADDRESS); 272 | assert.equal(await this.usdtPool.stablecoin(), this.ycrv.address); 273 | assert.equal(await this.wbtcPool.stablecoin(), this.ycrv.address); 274 | }); 275 | 276 | it('should successfully purchase yield boosters, sending half of BOOST to Boost Governance', async () => { 277 | // Mine block and move timestamp to 1 hour 278 | await mineBlockAtTime((await getCurrentBlockTime()) + 3600); 279 | 280 | await this.usdtPool.boost({from: alice}); 281 | await this.wbtcPool.boost({from: alice}); 282 | 283 | assert((await this.ycrv.balanceOf(this.gov.address)).should.be.a.bignumber.that.is.greaterThan('0')); 284 | }); 285 | 286 | it('should revert purchasing yield boosters consecutively within an hour', async () => { 287 | // Mine block and move timestamp to 1 hour 288 | await mineBlockAtTime((await getCurrentBlockTime()) + 3600); 289 | 290 | await this.usdtPool.boost({from: alice}); 291 | await this.wbtcPool.boost({from: alice}); 292 | await expectRevert(this.usdtPool.boost({from: alice}), 'early boost purchase'); 293 | await expectRevert(this.wbtcPool.boost({from: alice}), 'early boost purchase'); 294 | }); 295 | 296 | it('should revert purchasing more than the max num allowed of boosters', async () => { 297 | for (let i = 3; i < MAX_NUM_BOOSTERS; i++) { 298 | await mineBlockAtTime((await getCurrentBlockTime()) + 3600); 299 | await this.usdtPool.boost({from: alice}); 300 | await this.wbtcPool.boost({from: alice}); 301 | } 302 | 303 | await mineBlockAtTime((await getCurrentBlockTime()) + 3600); 304 | await expectRevert(this.usdtPool.boost({from: alice}), 'max boosters bought'); 305 | await expectRevert(this.wbtcPool.boost({from: alice}), 'max boosters bought'); 306 | }); 307 | 308 | it('should test withdrawing from a pool', async () => { 309 | const balanceUSDT = await this.usdt.balanceOf(alice); 310 | const balanceWBTC = await this.wbtc.balanceOf(alice); 311 | 312 | await this.usdtPool.withdraw((10 * 10 ** 6).valueOf(), {from: alice}); 313 | await this.wbtcPool.withdraw((0.1 * 10 ** 8).valueOf(), {from: alice}); 314 | 315 | assert.equal((await this.usdt.balanceOf(alice)).toString(), balanceUSDT.add(new BN(10 * 10 ** 6))); 316 | assert.equal((await this.wbtc.balanceOf(alice)).toString(), balanceWBTC.add(new BN(0.1 * 10 ** 8))); 317 | }); 318 | 319 | it('should test exiting a pool', async () => { 320 | await this.usdtPool.exit({from: alice}); 321 | await this.wbtcPool.exit({from: alice}); 322 | 323 | assert.equal(await this.usdtPool.balanceOf(alice), 0); 324 | assert.equal(await this.wbtcPool.balanceOf(alice), 0); 325 | }); 326 | }); 327 | -------------------------------------------------------------------------------- /test/BoostToken.js: -------------------------------------------------------------------------------- 1 | const {expectRevert} = require('@openzeppelin/test-helpers'); 2 | const {expect, assert} = require('chai'); 3 | const BoostToken = artifacts.require('BoostToken'); 4 | 5 | contract('BoostToken', ([governance, alice, bob, carol]) => { 6 | beforeEach(async () => { 7 | this.boost = await BoostToken.new({from: governance}); 8 | }); 9 | 10 | it('should have correct name, symbol, decimal, and totalSupply', async () => { 11 | assert.equal(await this.boost.name(), 'Boosted Finance'); 12 | assert.equal(await this.boost.symbol(), 'BOOST'); 13 | assert.equal(await this.boost.decimals(), '18'); 14 | assert.equal(await this.boost.totalSupply(), 10**23); 15 | }); 16 | 17 | it('should perform token transfers properly', async () => { 18 | await this.boost.transfer(alice, '100', {from: governance}); 19 | await this.boost.transfer(bob, '1000', {from: governance}); 20 | await this.boost.transfer(carol, '10', {from: alice}); 21 | await this.boost.transfer(carol, '100', {from: bob}); 22 | 23 | const aliceBal = await this.boost.balanceOf(alice); 24 | const bobBal = await this.boost.balanceOf(bob); 25 | const carolBal = await this.boost.balanceOf(carol); 26 | 27 | assert.equal(aliceBal.valueOf(), '90'); 28 | assert.equal(bobBal.valueOf(), '900'); 29 | assert.equal(carolBal.valueOf(), '110'); 30 | }); 31 | 32 | it('should fail if you try to do bad transfers', async () => { 33 | await this.boost.transfer(alice, '100', {from: governance}); 34 | await expectRevert(this.boost.transfer(carol, '110', {from: alice}), 'ERC20: transfer amount exceeds balance'); 35 | await expectRevert(this.boost.transfer(carol, '1', {from: bob}), 'ERC20: transfer amount exceeds balance'); 36 | }); 37 | 38 | it('should burn tokens', async () => { 39 | await this.boost.transfer(alice, '1000', {from: governance}); 40 | await this.boost.transfer(bob, '1100', {from: governance}); 41 | await this.boost.transfer(carol, '500', {from: governance}); 42 | await this.boost.burn('500', {from: alice}); 43 | await this.boost.burn('100', {from: bob}); 44 | await this.boost.burn('400', {from: carol}); 45 | 46 | const totalSupply = await this.boost.totalSupply(); 47 | const aliceBal = await this.boost.balanceOf(alice); 48 | const bobBal = await this.boost.balanceOf(bob); 49 | const carolBal = await this.boost.balanceOf(carol); 50 | 51 | assert.equal(totalSupply.toString(), (10**23 - 500 - 100 - 400).valueOf()); 52 | assert.equal(aliceBal.valueOf(), '500'); 53 | assert.equal(bobBal.valueOf(), '1000'); 54 | assert.equal(carolBal.valueOf(), '100'); 55 | }); 56 | 57 | it('should not allow burning exceeding total balance of user ', async () => { 58 | await this.boost.transfer(alice, '1000', {from: governance}); 59 | await this.boost.transfer(bob, '1100', {from: governance}); 60 | await this.boost.transfer(carol, '500', {from: governance}); 61 | 62 | await expectRevert(this.boost.burn('1001', {from: alice}), 'ERC20: burn amount exceeds balance'); 63 | await expectRevert(this.boost.burn('1101', {from: bob}), 'ERC20: burn amount exceeds balance'); 64 | await expectRevert(this.boost.burn('501', {from: carol}), 'ERC20: burn amount exceeds balance'); 65 | }); 66 | }); 67 | --------------------------------------------------------------------------------