├── .babelrc ├── .eslintrc.js ├── .gitattributes ├── .gitignore ├── .nvmrc ├── .openzeppelin └── project.json ├── .prettierrc ├── .solcover.js ├── .solhint.json ├── .soliumignore ├── .soliumrc.json ├── LICENSE ├── README.md ├── buidler.config.js ├── contracts └── BondingCurve │ ├── BondingCurve.sol │ ├── BondingCurveBase.sol │ ├── BondingCurveEther.sol │ ├── curve │ ├── BancorCurveLogic.sol │ ├── BancorCurveService.sol │ ├── StaticCurveLogic.sol │ └── bancor-formula │ │ ├── BancorFormula.sol │ │ └── Power.sol │ ├── dividend │ ├── PaymentPool.sol │ └── RewardsDistributor.sol │ ├── factory │ └── BondingCurveFactory.sol │ ├── interface │ ├── IBondingCurve.sol │ ├── IBondingCurveERC20.sol │ ├── IBondingCurveEther.sol │ └── ICurveLogic.sol │ └── token │ ├── BondedToken.sol │ ├── BondedTokenBase.sol │ └── BondedTokenEther.sol ├── docs ├── Basics.md ├── BondingCurve.md ├── DAICO.md ├── Development.md ├── DividendToken.md ├── Research.md ├── Roadmap.md └── diagrams │ ├── out │ ├── admin_scheme_architecture.png │ ├── bancor_curve_examples.png │ ├── bonding_curve_architecture.png │ ├── bonding_curve_architecture_buy.png │ ├── bonding_curve_architecture_pay.png │ ├── bonding_curve_buy_flow.png │ ├── bonding_curve_merkle_flow.png │ ├── bonding_curve_pay_flow.png │ ├── bonding_curve_sell_flow.png │ └── invest_scheme_architecture.png │ └── src │ ├── admin_scheme_architecture.drawio │ ├── bonding_curve_architecture_buy.drawio │ ├── bonding_curve_architecture_pay.drawio │ ├── bonding_curve_distributions.drawio │ ├── invest_scheme_architecture.drawio │ └── open_raise.drawio ├── ecosystem.deploy.sh ├── ecosystem.initialize.js ├── index.js ├── migrations └── .gitkeep ├── package.json ├── scripts ├── deployBondingCurve.js ├── deployCurve.ts └── helpers.ts ├── solhint.json ├── test ├── appDeploy.js ├── behaviors │ ├── BondingCurve.behavior.js │ ├── ERC20Burnable.behavior.js │ ├── bondingCurveAdmin.js │ ├── bondingCurveBuySell.js │ ├── bondingCurveBuySellEther.js │ ├── bondingCurveDeploy.js │ └── bondingCurvePayment.js ├── constants │ ├── bancorValues.js │ └── contractConstants.js ├── expectEvent.js ├── helpers │ ├── CurveEcosystem.js │ ├── CurveEcosystemConfig.js │ ├── ecosystemConfigs.js │ └── utils.js ├── integration │ └── e2e.js ├── setup.js └── unit │ ├── bancorCurveLogic.spec.js │ ├── bancorFormula.spec.js │ ├── bondedToken.spec.js │ ├── bondingCurve.spec.js │ ├── bondingCurveFactory.spec.js │ ├── rewardDistributor.spec.js │ └── staticCurveLogic.spec.js ├── truffle-config.js ├── utils ├── cli │ ├── index.js │ └── lib │ │ └── inquirer.js └── deployer.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env"] 3 | } 4 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author: Dan 3 | * @dev Lints our code and autoformats it using ESLint and Prettier 4 | * @dev Inclues `globals` that are typically injected by Web3 or Truffle 5 | * @dev See https://blog.echobind.com/integrating-prettier-eslint-airbnb-style-guide-in-vscode-47f07b5d7d6a 6 | */ 7 | module.exports = { 8 | env: { 9 | mocha: true, 10 | es6: true 11 | }, 12 | plugins: ['mocha', 'prettier'], 13 | extends: ['airbnb-base', 'prettier'], 14 | globals: { 15 | Atomics: 'readonly', 16 | SharedArrayBuffer: 'readonly', 17 | web3: false, 18 | artifacts: true, 19 | assert: false, 20 | contract: false 21 | }, 22 | parserOptions: { 23 | ecmaVersion: 2018, 24 | sourceType: 'module' 25 | }, 26 | rules: { 27 | 'mocha/no-exclusive-tests': 'error', 28 | 'prettier/prettier': 'error', 29 | 'prefer-template': 'off', 30 | 'no-unused-vars': 'off' 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity 2 | -------------------------------------------------------------------------------- /.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 | coverage.json 20 | 21 | # nyc test coverage 22 | .nyc_output 23 | 24 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 25 | .grunt 26 | 27 | # Bower dependency directory (https://bower.io/) 28 | bower_components 29 | 30 | # node-waf configuration 31 | .lock-wscript 32 | 33 | # Compiled binary addons (https://nodejs.org/api/addons.html) 34 | build/Release 35 | 36 | # Dependency directories 37 | node_modules/ 38 | jspm_packages/ 39 | 40 | # TypeScript v1 declaration files 41 | typings/ 42 | 43 | # Optional npm cache directory 44 | .npm 45 | 46 | # Optional eslint cache 47 | .eslintcache 48 | 49 | # Optional REPL history 50 | .node_repl_history 51 | 52 | # Output of 'npm pack' 53 | *.tgz 54 | 55 | # Yarn Integrity file 56 | .yarn-integrity 57 | 58 | # dotenv environment variables file 59 | .env 60 | 61 | # next.js build output 62 | .next 63 | 64 | # zos sessions 65 | zos.dev-* 66 | .zos.session 67 | .openzeppelin/.session 68 | .openzeppelin/dev-* 69 | 70 | # contracts build 71 | build 72 | 73 | .DS_Store 74 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v10.16.3 -------------------------------------------------------------------------------- /.openzeppelin/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifestVersion": "2.2", 3 | "name": "@dorgtech/open-raise", 4 | "version": "0.0.1-alpha", 5 | "publish": false, 6 | "contracts": { 7 | "StaticCurveLogic": "StaticCurveLogic", 8 | "BancorCurveLogic": "BancorCurveLogic", 9 | "BondedToken": "BondedToken", 10 | "RewardsDistributor": "RewardsDistributor", 11 | "BondingCurve": "BondingCurve", 12 | "BondingCurveEther": "BondingCurveEther", 13 | "BondingCurveFactory": "BondingCurveFactory", 14 | "BancorCurveService": "BancorCurveService" 15 | }, 16 | "dependencies": { 17 | "@openzeppelin/contracts-ethereum-package": "^2.2.3" 18 | }, 19 | "compiler": { 20 | "manager": "openzeppelin", 21 | "solcVersion": "0.5.10", 22 | "compilerSettings": { 23 | "optimizer": { 24 | "enabled": false, 25 | "runs": "200" 26 | } 27 | } 28 | }, 29 | "telemetryOptIn": false 30 | } -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "singleQuote": true 4 | } 5 | -------------------------------------------------------------------------------- /.solcover.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | norpc: false, 3 | skipFiles: [ 4 | "BondingCurve/curve/bancor-formula/BancorFormula.sol", 5 | "BondingCurve/curve/bancor-formula/Power.sol" 6 | ], 7 | copyPackages: ["openzeppelin-test-helpers"] 8 | }; 9 | -------------------------------------------------------------------------------- /.solhint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["solhint:recommended"], 3 | "rules": { 4 | "prettier/prettier": "error" 5 | }, 6 | "plugins": ["prettier"] 7 | } 8 | -------------------------------------------------------------------------------- /.soliumignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | contracts/Migrations.sol 3 | contracts/BondingCurve/curve/bancor-formula -------------------------------------------------------------------------------- /.soliumrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "solium:recommended", 3 | "plugins": ["security"], 4 | "rules": { 5 | "quotes": ["error", "double"], 6 | "indentation": ["error", 4], 7 | "linebreak-style": ["error", "unix"] 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Zeppelin Solutions 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 | # OpenRaise 2 | 3 | > "Accountable fundraising for the next generation of decentralized organizations" 4 | 5 | ⚠️ These contracts are in beta and have not been audited, do not use these contracts in production. 6 | 7 | Blockchain, and Ethereum in particular, promises to reshape capital formation. However, the ICO craze of 2017 demonstrated the need for accountability, giving rise to new models for fundraising such as Continuous Organizations and DAICOs. Open Raise is a modular library of smart contracts and UI components that bring these ideas to life and make it easy for organizations to run accountable fundraising campaigns. The first release will support curve bonded token sales with self-enforcing dividend claims. The roadmap (see below) consists of adding DAICO support and modularizing the code before integrating with popular DAO frameworks and supporting additional functionality. 8 | 9 | ## Fundraising Mechanisms 10 | 11 | - [Bonding Curves](./docs/BondingCurve.md) 12 | - [DAICO](./docs/DAICO.md) 13 | 14 | ## Token Features 15 | 16 | - [Dividend Claims](./docs/DividendToken.md) 17 | -------------------------------------------------------------------------------- /buidler.config.js: -------------------------------------------------------------------------------- 1 | usePlugin("@nomiclabs/buidler-truffle5"); 2 | 3 | // This is a sample Buidler task. To learn how to create your own go to 4 | // https://buidler.dev/guides/create-task.html 5 | task("accounts", "Prints the list of accounts", async () => { 6 | const accounts = await web3.eth.getAccounts(); 7 | 8 | for (const account of accounts) { 9 | console.log(account); 10 | } 11 | }); 12 | 13 | module.exports = {}; 14 | -------------------------------------------------------------------------------- /contracts/BondingCurve/BondingCurve.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.7; 2 | 3 | import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/IERC20.sol"; 4 | import "@openzeppelin/contracts-ethereum-package/contracts/math/SafeMath.sol"; 5 | import "@openzeppelin/contracts-ethereum-package/contracts/ownership/Ownable.sol"; 6 | import "@openzeppelin/contracts-ethereum-package/contracts/lifecycle/Pausable.sol"; 7 | import "@openzeppelin/upgrades/contracts/Initializable.sol"; 8 | import "contracts/BondingCurve/BondingCurveBase.sol"; 9 | import "contracts/BondingCurve/interface/IBondingCurveERC20.sol"; 10 | 11 | /// @title A bonding curve implementation for buying a selling bonding curve tokens. 12 | /// @author dOrg 13 | /// @notice Uses a defined ERC20 token as reserve currency 14 | contract BondingCurve is Initializable, BondingCurveBase, IBondingCurveERC20 { 15 | using SafeMath for uint256; 16 | 17 | IERC20 internal _collateralToken; 18 | 19 | /// @dev Initialize contract 20 | /// @param owner Contract owner, can conduct administrative functions. 21 | /// @param beneficiary Recieves a proportion of incoming tokens on buy() and pay() operations. 22 | /// @param collateralToken Token accepted as collateral by the curve. (e.g. WETH or DAI) 23 | /// @param bondedToken Token native to the curve. The bondingCurve contract has exclusive rights to mint and burn tokens. 24 | /// @param buyCurve Curve logic for buy curve. 25 | /// @param reservePercentage Percentage of incoming collateralTokens distributed to beneficiary on buys. (The remainder is sent to reserve for sells) 26 | /// @param dividendPercentage Percentage of incoming collateralTokens distributed to beneficiary on payments. The remainder being distributed among current bondedToken holders. Divided by precision value. 27 | function initialize( 28 | address owner, 29 | address beneficiary, 30 | IERC20 collateralToken, 31 | BondedToken bondedToken, 32 | ICurveLogic buyCurve, 33 | uint256 reservePercentage, 34 | uint256 dividendPercentage 35 | ) public initializer { 36 | BondingCurveBase.initialize( 37 | owner, 38 | beneficiary, 39 | bondedToken, 40 | buyCurve, 41 | reservePercentage, 42 | dividendPercentage 43 | ); 44 | _collateralToken = collateralToken; 45 | } 46 | 47 | function buy( 48 | uint256 amount, 49 | uint256 maxPrice, 50 | address recipient 51 | ) public whenNotPaused returns ( 52 | uint256 collateralSent 53 | ) { 54 | 55 | (uint256 buyPrice, uint256 toReserve, uint256 toBeneficiary) = _preBuy(amount, maxPrice); 56 | 57 | require( 58 | _collateralToken.transferFrom(msg.sender, address(this), buyPrice), 59 | TRANSFER_FROM_FAILED 60 | ); 61 | _collateralToken.transfer(_beneficiary, toBeneficiary); 62 | 63 | _postBuy(msg.sender, recipient, amount, buyPrice, toReserve, toBeneficiary); 64 | return buyPrice; 65 | } 66 | 67 | /// @dev Sell a given number of bondedTokens for a number of collateralTokens determined by the current rate from the sell curve. 68 | /// @param amount The number of bondedTokens to sell 69 | /// @param minReturn Minimum total price allowable to receive in collateralTokens 70 | /// @param recipient Address to send the new bondedTokens to 71 | function sell( 72 | uint256 amount, 73 | uint256 minReturn, 74 | address recipient 75 | ) public whenNotPaused returns ( 76 | uint256 collateralReceived 77 | ) { 78 | require(amount > 0, REQUIRE_NON_ZERO_NUM_TOKENS); 79 | require(_bondedToken.balanceOf(msg.sender) >= amount, INSUFFICENT_TOKENS); 80 | 81 | uint256 burnReward = rewardForSell(amount); 82 | require(burnReward >= minReturn, PRICE_BELOW_MIN); 83 | 84 | _reserveBalance = _reserveBalance.sub(burnReward); 85 | 86 | _bondedToken.burn(msg.sender, amount); 87 | _collateralToken.transfer(recipient, burnReward); 88 | 89 | emit Sell(msg.sender, recipient, amount, burnReward); 90 | return burnReward; 91 | } 92 | 93 | /// @notice Pay the DAO in the specified payment token. They will be distributed between the DAO beneficiary and bonded token holders 94 | /// @dev Does not currently support arbitrary token payments 95 | /// @param amount The number of tokens to pay the DAO 96 | function pay(uint256 amount) public { 97 | require(amount > MICRO_PAYMENT_THRESHOLD, NO_MICRO_PAYMENTS); 98 | 99 | IERC20 paymentToken = _collateralToken; 100 | 101 | uint256 tokensToDividendHolders = (amount.mul(_dividendPercentage)).div(MAX_PERCENTAGE); 102 | uint256 tokensToBeneficiary = amount.sub(tokensToDividendHolders); 103 | 104 | // incoming funds 105 | require(paymentToken.transferFrom(msg.sender, address(this), amount), TRANSFER_FROM_FAILED); 106 | 107 | // outgoing funds to Beneficiary 108 | paymentToken.transfer(_beneficiary, tokensToBeneficiary); 109 | 110 | // outgoing funds to token holders as dividends (stored by BondedToken) 111 | paymentToken.approve(address(_bondedToken), tokensToDividendHolders); 112 | _bondedToken.distribute(address(this), tokensToDividendHolders); 113 | 114 | emit Pay( 115 | msg.sender, 116 | address(paymentToken), 117 | amount, 118 | tokensToBeneficiary, 119 | tokensToDividendHolders 120 | ); 121 | } 122 | 123 | /* 124 | Getter Functions 125 | */ 126 | 127 | /// @notice Get reserve token contract 128 | function collateralToken() public view returns (IERC20) { 129 | return _collateralToken; 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /contracts/BondingCurve/BondingCurveBase.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.7; 2 | 3 | import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/IERC20.sol"; 4 | import "@openzeppelin/contracts-ethereum-package/contracts/math/SafeMath.sol"; 5 | import "@openzeppelin/contracts-ethereum-package/contracts/ownership/Ownable.sol"; 6 | import "@openzeppelin/contracts-ethereum-package/contracts/lifecycle/Pausable.sol"; 7 | import "@openzeppelin/upgrades/contracts/Initializable.sol"; 8 | import "contracts/BondingCurve/token/BondedToken.sol"; 9 | import "contracts/BondingCurve/interface/IBondingCurve.sol"; 10 | import "contracts/BondingCurve/interface/ICurveLogic.sol"; 11 | 12 | /// @title A bonding curve implementation for buying a selling bonding curve tokens. 13 | /// @author dOrg 14 | /// @notice Uses a defined ERC20 token as reserve currency 15 | contract BondingCurveBase is IBondingCurve, Initializable, Ownable, Pausable { 16 | using SafeMath for uint256; 17 | 18 | BondedToken internal _bondedToken; 19 | 20 | ICurveLogic internal _buyCurve; 21 | address internal _beneficiary; 22 | 23 | uint256 internal _reserveBalance; 24 | uint256 internal _reservePercentage; 25 | uint256 internal _dividendPercentage; 26 | 27 | uint256 internal constant MAX_PERCENTAGE = 100; 28 | uint256 internal constant MICRO_PAYMENT_THRESHOLD = 100; 29 | 30 | string internal constant TRANSFER_FROM_FAILED = "Transfer of collateralTokens from sender failed"; 31 | string internal constant TOKEN_MINTING_FAILED = "bondedToken minting failed"; 32 | string internal constant TRANSFER_TO_BENEFICIARY_FAILED = "Tranfer of collateralTokens to beneficiary failed"; 33 | string internal constant INSUFFICENT_TOKENS = "Insufficent tokens"; 34 | string internal constant MAX_PRICE_EXCEEDED = "Current price exceedes maximum specified"; 35 | string internal constant PRICE_BELOW_MIN = "Current price is below minimum specified"; 36 | string internal constant REQUIRE_NON_ZERO_NUM_TOKENS = "Must specify a non-zero amount of bondedTokens"; 37 | string internal constant SELL_CURVE_LARGER = "Buy curve value must be greater than Sell curve value"; 38 | string internal constant SPLIT_ON_PAY_INVALID = "dividendPercentage must be a valid percentage"; 39 | string internal constant SPLIT_ON_BUY_INVALID = "reservePercentage must be a valid percentage"; 40 | string internal constant SPLIT_ON_PAY_MATH_ERROR = "dividendPercentage splits returned a greater token value than input value"; 41 | string internal constant NO_MICRO_PAYMENTS = "Payment amount must be greater than 100 'units' for calculations to work correctly"; 42 | string internal constant TOKEN_BURN_FAILED = "bondedToken burn failed"; 43 | string internal constant TRANSFER_TO_RECIPIENT_FAILED = "Transfer to recipient failed"; 44 | 45 | event BeneficiarySet(address beneficiary); 46 | event BuyCurveSet(address buyCurve); 47 | event SellCurveSet(address sellCurve); 48 | event DividendPercentageSet(uint256 dividendPercentage); 49 | event ReservePercentageSet(uint256 reservePercentage); 50 | 51 | event Buy( 52 | address indexed buyer, 53 | address indexed recipient, 54 | uint256 amount, 55 | uint256 price, 56 | uint256 reserveAmount, 57 | uint256 beneficiaryAmount 58 | ); 59 | event Sell(address indexed seller, address indexed recipient, uint256 amount, uint256 reward); 60 | event Pay( 61 | address indexed from, 62 | address indexed token, 63 | uint256 amount, 64 | uint256 beneficiaryAmount, 65 | uint256 dividendAmount 66 | ); 67 | 68 | /// @dev Initialize contract 69 | /// @param owner Contract owner, can conduct administrative functions. 70 | /// @param beneficiary Recieves a proportion of incoming tokens on buy() and pay() operations. 71 | /// @param bondedToken Token native to the curve. The bondingCurve contract has exclusive rights to mint and burn tokens. 72 | /// @param buyCurve Curve logic for buy curve. 73 | /// @param reservePercentage Percentage of incoming collateralTokens distributed to beneficiary on buys. (The remainder is sent to reserve for sells) 74 | /// @param dividendPercentage Percentage of incoming collateralTokens distributed to beneficiary on payments. The remainder being distributed among current bondedToken holders. Divided by precision value. 75 | function initialize( 76 | address owner, 77 | address beneficiary, 78 | BondedToken bondedToken, 79 | ICurveLogic buyCurve, 80 | uint256 reservePercentage, 81 | uint256 dividendPercentage 82 | ) public initializer { 83 | _isValiddividendPercentage(reservePercentage); 84 | _isValidreservePercentage(dividendPercentage); 85 | 86 | Ownable.initialize(owner); 87 | Pausable.initialize(owner); 88 | 89 | _beneficiary = beneficiary; 90 | 91 | _buyCurve = buyCurve; 92 | _bondedToken = bondedToken; 93 | 94 | _reservePercentage = reservePercentage; 95 | _dividendPercentage = dividendPercentage; 96 | 97 | emit BuyCurveSet(address(_buyCurve)); 98 | emit BeneficiarySet(_beneficiary); 99 | emit ReservePercentageSet(_reservePercentage); 100 | emit DividendPercentageSet(_dividendPercentage); 101 | } 102 | 103 | function _isValidreservePercentage(uint256 reservePercentage) internal view { 104 | require(reservePercentage <= MAX_PERCENTAGE, SPLIT_ON_BUY_INVALID); 105 | } 106 | 107 | function _isValiddividendPercentage(uint256 dividendPercentage) internal view { 108 | require(dividendPercentage <= MAX_PERCENTAGE, SPLIT_ON_PAY_INVALID); 109 | } 110 | 111 | /// @notice Get the price in ether to mint tokens 112 | /// @param numTokens The number of tokens to calculate price for 113 | function priceToBuy(uint256 numTokens) public view returns (uint256) { 114 | return _buyCurve.calcMintPrice(_bondedToken.totalSupply(), _reserveBalance, numTokens); 115 | } 116 | 117 | /// @notice Get the reward in ether to burn tokens 118 | /// @param numTokens The number of tokens to calculate reward for 119 | function rewardForSell(uint256 numTokens) public view returns (uint256) { 120 | uint256 buyPrice = priceToBuy(numTokens); 121 | return (buyPrice.mul(_reservePercentage)).div(MAX_PERCENTAGE); 122 | } 123 | 124 | /* 125 | Abstract Functions 126 | */ 127 | 128 | /// @dev Sell a given number of bondedTokens for a number of collateralTokens determined by the current rate from the sell curve. 129 | /// @param numTokens The number of bondedTokens to sell 130 | /// @param minPrice Minimum total price allowable to receive in collateralTokens 131 | /// @param recipient Address to send the new bondedTokens to 132 | function sell( 133 | uint256 numTokens, 134 | uint256 minPrice, 135 | address recipient 136 | ) public returns(uint256 collateralReceived); 137 | 138 | /* 139 | Internal Functions 140 | */ 141 | 142 | function _preBuy( 143 | uint256 amount, 144 | uint256 maxPrice 145 | ) internal returns ( 146 | uint256 buyPrice, 147 | uint256 toReserve, 148 | uint256 toBeneficiary 149 | ) { 150 | require(amount > 0, REQUIRE_NON_ZERO_NUM_TOKENS); 151 | 152 | buyPrice = priceToBuy(amount); 153 | 154 | if (maxPrice != 0) { 155 | require(buyPrice <= maxPrice, MAX_PRICE_EXCEEDED); 156 | } 157 | 158 | toReserve = rewardForSell(amount); 159 | toBeneficiary = buyPrice.sub(toReserve); 160 | } 161 | 162 | function _postBuy( 163 | address buyer, 164 | address recipient, 165 | uint256 amount, 166 | uint256 buyPrice, 167 | uint256 toReserve, 168 | uint256 toBeneficiary 169 | ) internal { 170 | _reserveBalance = _reserveBalance.add(toReserve); 171 | _bondedToken.mint(recipient, amount); 172 | 173 | emit Buy(buyer, recipient, amount, buyPrice, toReserve, toBeneficiary); 174 | } 175 | 176 | /* 177 | Admin Functions 178 | */ 179 | 180 | /// @notice Set beneficiary to a new address 181 | /// @param beneficiary New beneficiary 182 | function setBeneficiary(address beneficiary) public onlyOwner { 183 | _beneficiary = beneficiary; 184 | emit BeneficiarySet(_beneficiary); 185 | } 186 | 187 | /// @notice Set buy curve to a new address 188 | /// @param buyCurve New buy curve 189 | function setBuyCurve(ICurveLogic buyCurve) public onlyOwner { 190 | _buyCurve = buyCurve; 191 | emit BuyCurveSet(address(_buyCurve)); 192 | } 193 | 194 | /// @notice Set split on buy to new value 195 | /// @param reservePercentage New split on buy value 196 | function setReservePercentage(uint256 reservePercentage) public onlyOwner { 197 | _isValidreservePercentage(reservePercentage); 198 | _reservePercentage = reservePercentage; 199 | emit ReservePercentageSet(_reservePercentage); 200 | } 201 | 202 | /// @notice Set split on pay to new value 203 | /// @param dividendPercentage New split on pay value 204 | function setDividendPercentage(uint256 dividendPercentage) public onlyOwner { 205 | _isValiddividendPercentage(dividendPercentage); 206 | _dividendPercentage = dividendPercentage; 207 | emit DividendPercentageSet(_dividendPercentage); 208 | } 209 | 210 | /* 211 | Getter Functions 212 | */ 213 | 214 | /// @notice Get bonded token contract 215 | function bondedToken() public view returns (BondedToken) { 216 | return _bondedToken; 217 | } 218 | 219 | /// @notice Get buy curve contract 220 | function buyCurve() public view returns (ICurveLogic) { 221 | return _buyCurve; 222 | } 223 | 224 | /// @notice Get beneficiary 225 | function beneficiary() public view returns (address) { 226 | return _beneficiary; 227 | } 228 | 229 | /// @notice Get reserve balance 230 | function reserveBalance() public view returns (uint256) { 231 | return _reserveBalance; 232 | } 233 | 234 | /// @notice Get split on buy parameter 235 | function reservePercentage() public view returns (uint256) { 236 | return _reservePercentage; 237 | } 238 | 239 | /// @notice Get split on pay parameter 240 | function dividendPercentage() public view returns (uint256) { 241 | return _dividendPercentage; 242 | } 243 | 244 | /// @notice Get minimum value accepted for payments 245 | function getPaymentThreshold() public view returns (uint256) { 246 | return MICRO_PAYMENT_THRESHOLD; 247 | } 248 | } -------------------------------------------------------------------------------- /contracts/BondingCurve/BondingCurveEther.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.7; 2 | 3 | import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/IERC20.sol"; 4 | import "@openzeppelin/contracts-ethereum-package/contracts/math/SafeMath.sol"; 5 | import "@openzeppelin/contracts-ethereum-package/contracts/ownership/Ownable.sol"; 6 | import "@openzeppelin/contracts-ethereum-package/contracts/lifecycle/Pausable.sol"; 7 | import "@openzeppelin/upgrades/contracts/Initializable.sol"; 8 | import "contracts/BondingCurve/BondingCurveBase.sol"; 9 | import "contracts/BondingCurve/interface/IBondingCurveEther.sol"; 10 | 11 | /// @title A bonding curve implementation for buying a selling bonding curve tokens. 12 | /// @author dOrg 13 | /// @notice Uses Ether as reserve currency 14 | contract BondingCurveEther is Initializable, BondingCurveBase, IBondingCurveEther { 15 | using SafeMath for uint256; 16 | 17 | string internal constant INSUFFICENT_ETHER = "Insufficent Ether"; 18 | string internal constant INCORRECT_ETHER_SENT = "Incorrect Ether value sent"; 19 | string internal constant MATH_ERROR_SPLITTING_COLLATERAL = "Calculated Split Invalid"; 20 | 21 | /// @dev Initialize contract 22 | /// @param owner Contract owner, can conduct administrative functions. 23 | /// @param beneficiary Recieves a proportion of incoming tokens on buy() and pay() operations. 24 | /// @param bondedToken Token native to the curve. The bondingCurve contract has exclusive rights to mint and burn tokens. 25 | /// @param buyCurve Curve logic for buy curve. 26 | /// @param reservePercentage Percentage of incoming collateralTokens distributed to beneficiary on buys. (The remainder is sent to reserve for sells) 27 | /// @param dividendPercentage Percentage of incoming collateralTokens distributed to beneficiary on payments. The remainder being distributed among current bondedToken holders. Divided by precision value. 28 | function initialize( 29 | address owner, 30 | address beneficiary, 31 | BondedToken bondedToken, 32 | ICurveLogic buyCurve, 33 | uint256 reservePercentage, 34 | uint256 dividendPercentage 35 | ) public initializer { 36 | BondingCurveBase.initialize( 37 | owner, 38 | beneficiary, 39 | bondedToken, 40 | buyCurve, 41 | reservePercentage, 42 | dividendPercentage 43 | ); 44 | } 45 | 46 | function buy( 47 | uint256 amount, 48 | uint256 maxPrice, 49 | address recipient 50 | ) public payable whenNotPaused returns ( 51 | uint256 collateralSent 52 | ) { 53 | require(maxPrice != 0 && msg.value == maxPrice, INCORRECT_ETHER_SENT); 54 | 55 | (uint256 buyPrice, uint256 toReserve, uint256 toBeneficiary) = _preBuy(amount, maxPrice); 56 | 57 | address(uint160(_beneficiary)).transfer(toBeneficiary); 58 | 59 | uint256 refund = maxPrice.sub(toReserve).sub(toBeneficiary); 60 | 61 | if (refund > 0) { 62 | msg.sender.transfer(refund); 63 | } 64 | 65 | _postBuy(msg.sender, recipient, amount, buyPrice, toReserve, toBeneficiary); 66 | return buyPrice; 67 | } 68 | 69 | function sell( 70 | uint256 amount, 71 | uint256 minReturn, 72 | address recipient 73 | ) public whenNotPaused returns ( 74 | uint256 collateralReceived 75 | ) { 76 | require(amount > 0, REQUIRE_NON_ZERO_NUM_TOKENS); 77 | require(_bondedToken.balanceOf(msg.sender) >= amount, INSUFFICENT_TOKENS); 78 | 79 | uint256 burnReward = rewardForSell(amount); 80 | require(burnReward >= minReturn, PRICE_BELOW_MIN); 81 | 82 | _reserveBalance = _reserveBalance.sub(burnReward); 83 | address(uint160(recipient)).transfer(burnReward); 84 | 85 | _bondedToken.burn(msg.sender, amount); 86 | 87 | emit Sell(msg.sender, recipient, amount, burnReward); 88 | return burnReward; 89 | } 90 | 91 | function pay(uint256 amount) public payable { 92 | require(amount > MICRO_PAYMENT_THRESHOLD, NO_MICRO_PAYMENTS); 93 | require(msg.value == amount, INCORRECT_ETHER_SENT); 94 | 95 | uint256 amountToDividendHolders = (amount.mul(_dividendPercentage)).div(MAX_PERCENTAGE); 96 | uint256 amountToBeneficiary = amount.sub(amountToDividendHolders); 97 | 98 | // outgoing funds to Beneficiary 99 | 100 | address(uint160(_beneficiary)).transfer(amountToBeneficiary); 101 | 102 | // outgoing funds to token holders as dividends (stored by BondedToken) 103 | _bondedToken.distribute(address(this), amountToDividendHolders); 104 | 105 | emit Pay(msg.sender, address(0), amount, amountToBeneficiary, amountToDividendHolders); 106 | } 107 | 108 | // // Interpret fallback as payment 109 | // function () public payable { 110 | // pay(msg.value); 111 | // } 112 | 113 | } 114 | -------------------------------------------------------------------------------- /contracts/BondingCurve/curve/BancorCurveLogic.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >= 0.4.22 <6.0.0; 2 | 3 | import "@openzeppelin/contracts-ethereum-package/contracts/math/SafeMath.sol"; 4 | import "@openzeppelin/upgrades/contracts/Initializable.sol"; 5 | import "contracts/BondingCurve/interface/ICurveLogic.sol"; 6 | import "contracts/BondingCurve/curve/BancorCurveService.sol"; 7 | 8 | /** 9 | * @title Bancor Curve Logic 10 | * @dev Curve that returns price per token according to bancor formula and specified reserve ratio 11 | */ 12 | contract BancorCurveLogic is Initializable, ICurveLogic { 13 | using SafeMath for uint256; 14 | 15 | /* 16 | * @dev reserve ratio, represented in ppm, 1-1000000 17 | * 1/3 corresponds to y= multiple * x^2 18 | * 1/2 corresponds to y= multiple * x 19 | * 2/3 corresponds to y= multiple * x^1/2 20 | * multiple will depends on contract initialization, 21 | * specificallytotalAmount and poolBalance parameters 22 | * we might want to add an 'initialize' function that will allow 23 | * the owner to send ether to the contract and mint a given amount of tokens 24 | */ 25 | 26 | BancorCurveService internal _bancorService; 27 | uint32 internal _reserveRatio; 28 | // uint256 internal _serviceParamsIndex; 29 | 30 | /// @dev Initialize contract 31 | /// @param reserveRatio The number of curve tokens to mint 32 | function initialize(BancorCurveService bancorService, uint32 reserveRatio) public initializer { 33 | _bancorService = bancorService; 34 | _reserveRatio = reserveRatio; 35 | // _serviceParamsIndex = _bancorService.addParams(reserveRatio); 36 | } 37 | 38 | /// @dev Get the price to mint tokens 39 | /// @param totalSupply The existing number of curve tokens 40 | /// @param amount The number of curve tokens to mint 41 | function calcMintPrice( 42 | uint256 totalSupply, 43 | uint256 reserveBalance, 44 | uint256 amount 45 | ) public view returns (uint256) { 46 | return _bancorService.calculatePurchaseReturn(totalSupply, reserveBalance, _reserveRatio, amount); 47 | } 48 | 49 | /// @dev Get the reward to burn tokens 50 | /// @param totalSupply The existing number of curve tokens 51 | /// @param amount The number of curve tokens to burn 52 | function calcBurnReward( 53 | uint256 totalSupply, 54 | uint256 reserveBalance, 55 | uint256 amount 56 | ) public view returns (uint256) { 57 | return _bancorService.calculateSaleReturn(totalSupply, reserveBalance, _reserveRatio, amount); 58 | } 59 | 60 | /// @notice Get reserve ratio 61 | function reserveRatio() public view returns (uint32) { 62 | return _reserveRatio; 63 | } 64 | 65 | /// @notice Get bancor service address 66 | function bancorService() public view returns (BancorCurveService) { 67 | return _bancorService; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /contracts/BondingCurve/curve/BancorCurveService.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.7; 2 | 3 | import "@openzeppelin/contracts-ethereum-package/contracts/math/SafeMath.sol"; 4 | import "@openzeppelin/upgrades/contracts/Initializable.sol"; 5 | import "contracts/BondingCurve/curve/bancor-formula/BancorFormula.sol"; 6 | 7 | /** 8 | * @title Bancor Curve Service 9 | * @dev Logic singleton 10 | * This reduces deploy costs all allows the deploy of entire 'curve ecosystems' in one transaction by only allocating storage for the exponent array once 11 | */ 12 | contract BancorCurveService is Initializable, BancorFormula { 13 | using SafeMath for uint256; 14 | 15 | // mapping (uint256 => uint32) internal _params; 16 | // uint256 _paramsCount; 17 | 18 | function initialize() public initializer { 19 | BancorFormula.initialize(); 20 | } 21 | 22 | // function addParams(uint32 params) { 23 | // _params[_paramsCount] = params; 24 | // _params.add(1); 25 | // } 26 | } -------------------------------------------------------------------------------- /contracts/BondingCurve/curve/StaticCurveLogic.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.7; 2 | 3 | import "@openzeppelin/upgrades/contracts/Initializable.sol"; 4 | import "@openzeppelin/contracts-ethereum-package/contracts/math/SafeMath.sol"; 5 | import "contracts/BondingCurve/interface/ICurveLogic.sol"; 6 | 7 | /** 8 | * @title Static Curve Logic 9 | * @dev Curve that always returns the same price per token 10 | * A potential use-case is zero-ing out the sell curve, which coupled with no split 11 | * on buy sends raised funds directly to the beneficiary 12 | */ 13 | contract StaticCurveLogic is Initializable, ICurveLogic { 14 | using SafeMath for uint256; 15 | 16 | /** 17 | Ranges from 0 to 1^18. Divide by Precision to determine ratio. 18 | */ 19 | uint256 internal _tokenRatio; 20 | uint256 internal constant PRECISION = 1000000; 21 | 22 | /// @dev Initialize contract 23 | /// @param tokenRatio Ratio of reserve tokens transfered or recieved to bonded tokens minted or burned, respectively. Divided by precison value for calculations. 24 | function initialize(uint256 tokenRatio) initializer public { 25 | _tokenRatio = tokenRatio; 26 | } 27 | 28 | /// @dev Get the price to mint tokens 29 | /// @param totalSupply The existing number of curve tokens 30 | /// @param amount The number of curve tokens to mint 31 | function calcMintPrice( 32 | uint256 totalSupply, 33 | uint256 reserveBalance, 34 | uint256 amount 35 | ) public view returns (uint256) { 36 | return amount.mul(_tokenRatio).div(PRECISION); 37 | } 38 | 39 | /// @dev Get the reward to burn tokens 40 | /// @param totalSupply The existing number of curve tokens 41 | /// @param amount The number of curve tokens to burn 42 | function calcBurnReward( 43 | uint256 totalSupply, 44 | uint256 reserveBalance, 45 | uint256 amount 46 | ) public view returns (uint256) { 47 | return amount.mul(_tokenRatio).div(PRECISION); 48 | 49 | } 50 | 51 | /// @notice Get token ratio 52 | function tokenRatio() public returns (uint256) { 53 | return _tokenRatio; 54 | } 55 | 56 | /// @notice Get precision value used for token ratio, useful for off-chain calculations 57 | function tokenRatioPrecision() public view returns (uint256) { 58 | return PRECISION; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /contracts/BondingCurve/curve/bancor-formula/BancorFormula.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >= 0.4.22 <6.0.0; 2 | 3 | import "@openzeppelin/upgrades/contracts/Initializable.sol"; 4 | import "@openzeppelin/contracts-ethereum-package/contracts/math/SafeMath.sol"; 5 | import "contracts/BondingCurve/curve/bancor-formula/Power.sol"; 6 | 7 | /** 8 | * @title Bancor formula by Bancor 9 | * @dev Modified from the original by Slava Balasanov 10 | * https://github.com/bancorprotocol/contracts 11 | * Split Power.sol out from BancorFormula.sol and replace SafeMath formulas with zeppelin's SafeMath 12 | * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements; 13 | * and to You under the Apache License, Version 2.0. " 14 | */ 15 | contract BancorFormula is Power { 16 | using SafeMath for uint256; 17 | 18 | string public version = "0.3"; 19 | uint32 private constant MAX_WEIGHT = 1000000; 20 | 21 | function initialize() public initializer { 22 | Power.initialize(); 23 | } 24 | 25 | /** 26 | * @dev given a token supply, connector balance, weight and a deposit amount (in the connector token), 27 | * calculates the return for a given conversion (in the main token) 28 | * 29 | * Formula: 30 | * Return = _supply * ((1 + _depositAmount / _connectorBalance) ^ (_connectorWeight / 1000000) - 1) 31 | * 32 | * @param _supply token total supply 33 | * @param _connectorBalance total connector balance 34 | * @param _connectorWeight connector weight, represented in ppm, 1-1000000 35 | * @param _depositAmount deposit amount, in connector token 36 | * 37 | * @return purchase return amount 38 | */ 39 | function calculatePurchaseReturn( 40 | uint256 _supply, 41 | uint256 _connectorBalance, 42 | uint32 _connectorWeight, 43 | uint256 _depositAmount) public view returns (uint256) 44 | { 45 | // validate input 46 | require(_supply > 0 && _connectorBalance > 0 && _connectorWeight > 0 && _connectorWeight <= MAX_WEIGHT); 47 | 48 | // special case for 0 deposit amount 49 | if (_depositAmount == 0) { 50 | return 0; 51 | } 52 | 53 | // special case if the weight = 100% 54 | if (_connectorWeight == MAX_WEIGHT) { 55 | return _supply.mul(_depositAmount).div(_connectorBalance); 56 | } 57 | 58 | uint256 result; 59 | uint8 precision; 60 | uint256 baseN = _depositAmount.add(_connectorBalance); 61 | (result, precision) = power(baseN, _connectorBalance, _connectorWeight, MAX_WEIGHT); 62 | uint256 temp = _supply.mul(result) >> precision; 63 | return temp - _supply; 64 | } 65 | 66 | /** 67 | * @dev given a token supply, connector balance, weight and a sell amount (in the main token), 68 | * calculates the return for a given conversion (in the connector token) 69 | * 70 | * Formula: 71 | * Return = _connectorBalance * (1 - (1 - _sellAmount / _supply) ^ (1 / (_connectorWeight / 1000000))) 72 | * 73 | * @param _supply token total supply 74 | * @param _connectorBalance total connector 75 | * @param _connectorWeight constant connector Weight, represented in ppm, 1-1000000 76 | * @param _sellAmount sell amount, in the token itself 77 | * 78 | * @return sale return amount 79 | */ 80 | function calculateSaleReturn(uint256 _supply, uint256 _connectorBalance, uint32 _connectorWeight, uint256 _sellAmount) public view returns (uint256) { 81 | // validate input 82 | require(_supply > 0 && _connectorBalance > 0 && _connectorWeight > 0 && _connectorWeight <= MAX_WEIGHT && _sellAmount <= _supply); 83 | 84 | // special case for 0 sell amount 85 | if (_sellAmount == 0) { 86 | return 0; 87 | } 88 | 89 | // special case for selling the entire supply 90 | if (_sellAmount == _supply) { 91 | return _connectorBalance; 92 | } 93 | 94 | // special case if the weight = 100% 95 | if (_connectorWeight == MAX_WEIGHT) { 96 | return _connectorBalance.mul(_sellAmount).div(_supply); 97 | } 98 | 99 | uint256 result; 100 | uint8 precision; 101 | uint256 baseD = _supply - _sellAmount; 102 | (result, precision) = power(_supply, baseD, MAX_WEIGHT, _connectorWeight); 103 | uint256 oldBalance = _connectorBalance.mul(result); 104 | uint256 newBalance = _connectorBalance << precision; 105 | return oldBalance.sub(newBalance).div(result); 106 | } 107 | } -------------------------------------------------------------------------------- /contracts/BondingCurve/dividend/PaymentPool.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.6; 2 | 3 | import "@openzeppelin/upgrades/contracts/Initializable.sol"; 4 | import "@openzeppelin/contracts-ethereum-package/contracts/math/SafeMath.sol"; 5 | import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/IERC20.sol"; 6 | import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/SafeERC20.sol"; 7 | import "@openzeppelin/contracts-ethereum-package/contracts/cryptography/MerkleProof.sol"; 8 | import "@openzeppelin/contracts-ethereum-package/contracts/ownership/Ownable.sol"; 9 | 10 | /** 11 | * @title Payment Pool 12 | * @dev Allows withdrawals of tokens according to amounts specified by lates merkle root, uploaded by the owner 13 | * Works with a single ß token 14 | */ 15 | 16 | contract PaymentPool is Initializable, Ownable { 17 | using SafeMath for uint256; 18 | using SafeERC20 for IERC20; 19 | using MerkleProof for bytes32[]; 20 | 21 | IERC20 internal _token; 22 | uint256 internal _numPaymentCycles; 23 | mapping(address => uint256) internal _withdrawals; 24 | 25 | mapping(uint256 => bytes32) internal _payeeRoots; 26 | uint256 internal _currentPaymentCycleStartBlock; 27 | 28 | event PaymentCycleEnded( 29 | uint256 paymentCycle, 30 | uint256 startBlock, 31 | uint256 endBlock 32 | ); 33 | event PayeeWithdraw(address indexed payee, uint256 amount); 34 | 35 | /// @dev Initialize contract 36 | /// @param token Token to recieve and track payments. 37 | /// @param owner Contract owner. Has exclusive rights to upload new merkle roots. 38 | function initialize(IERC20 token, address owner) public initializer { 39 | Ownable.initialize(owner); 40 | _token = token; 41 | _numPaymentCycles = 1; 42 | _currentPaymentCycleStartBlock = block.number; 43 | } 44 | 45 | function startNewPaymentCycle() internal onlyOwner returns (bool) { 46 | // disabled for hevm debugging 47 | require(block.number > _currentPaymentCycleStartBlock); 48 | 49 | emit PaymentCycleEnded( 50 | _numPaymentCycles, 51 | _currentPaymentCycleStartBlock, 52 | block.number 53 | ); 54 | 55 | _numPaymentCycles = _numPaymentCycles.add(1); 56 | _currentPaymentCycleStartBlock = block.number.add(1); 57 | 58 | return true; 59 | } 60 | 61 | function submitPayeeMerkleRoot(bytes32 payeeRoot) 62 | public 63 | onlyOwner 64 | returns (bool) 65 | { 66 | _payeeRoots[_numPaymentCycles] = payeeRoot; 67 | 68 | startNewPaymentCycle(); 69 | 70 | return true; 71 | } 72 | function balanceForProofWithAddress( 73 | address _address, 74 | uint256 cumAmount, 75 | uint256 _paymentCycle, 76 | bytes32[] memory proof 77 | ) public view returns (uint256) { 78 | if (_payeeRoots[_paymentCycle] == 0x0) { 79 | return 0; 80 | } 81 | 82 | bytes32 leaf = keccak256(abi.encodePacked(_address, cumAmount)); 83 | 84 | if ( 85 | _withdrawals[_address] < cumAmount && 86 | proof.verify(_payeeRoots[_paymentCycle], leaf) 87 | ) { 88 | return cumAmount.sub(_withdrawals[_address]); 89 | } else { 90 | return 0; 91 | } 92 | } 93 | function balanceForProof( 94 | uint256 cumAmount, 95 | uint256 _paymentCycle, 96 | bytes32[] memory proof 97 | ) public view returns (uint256) { 98 | return 99 | balanceForProofWithAddress( 100 | msg.sender, 101 | cumAmount, 102 | _paymentCycle, 103 | proof 104 | ); 105 | } 106 | function withdraw( 107 | uint256 amount, 108 | uint256 cumAmount, 109 | uint256 _paymentCycle, 110 | bytes32[] memory proof 111 | ) public returns (bool) { 112 | require(amount > 0); 113 | require(_token.balanceOf(address(this)) >= amount); 114 | 115 | uint256 balance = balanceForProof(cumAmount, _paymentCycle, proof); 116 | require(balance >= amount); 117 | 118 | _withdrawals[msg.sender] = _withdrawals[msg.sender].add(amount); 119 | _token.safeTransfer(msg.sender, amount); 120 | 121 | emit PayeeWithdraw(msg.sender, amount); 122 | } 123 | 124 | /// @notice Get payment token 125 | function token() public view returns (IERC20) { 126 | return _token; 127 | } 128 | 129 | /// @notice Get current payment cycle 130 | function numPaymentCycles() public view returns (uint256) { 131 | return _numPaymentCycles; 132 | } 133 | 134 | /// @notice Get token amount previously withdrawn by address 135 | function withdrawals(address account) public view returns (uint256) { 136 | return _withdrawals[account]; 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /contracts/BondingCurve/dividend/RewardsDistributor.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.6; 2 | 3 | import "@openzeppelin/upgrades/contracts/Initializable.sol"; 4 | import "@openzeppelin/contracts-ethereum-package/contracts/ownership/Ownable.sol"; 5 | import "@openzeppelin/contracts-ethereum-package/contracts/math/SafeMath.sol"; 6 | 7 | /** 8 | * @title RewardsDistributor - Distribute pro rata rewards (dividends) 9 | * @author Bogdan Batog (https://batog.info) 10 | * @dev Distribute pro rata rewards (dividends) to token holders in O(1) time. 11 | * Based on [1] http://batog.info/papers/scalable-reward-distribution.pdf 12 | * And on [2] https://solmaz.io/2019/02/24/scalable-reward-changing/ 13 | */ 14 | contract RewardsDistributor is Initializable, Ownable { 15 | using SafeMath for uint256; 16 | 17 | /// @notice ELIGIBLE_UNIT is the smallest eligible unit for reward. Minimum 18 | /// possible distribution is 1 (wei for Ether) PER ELIGIBLE_UNIT. 19 | /// 20 | /// Only multiple of ELIGIBLE_UNIT will be subject to reward 21 | /// distribution. Any fractional part of deposit, smaller than 22 | /// ELIGIBLE_UNIT, won't receive any reward, but it will be tracked. 23 | /// 24 | /// Recommended value 10**(decimals / 2), that is 10**9 for most ERC20. 25 | uint256 public constant ELIGIBLE_UNIT = 10 ** 9; 26 | 27 | /// @notice Stake per address. 28 | mapping(address => uint256) internal _stake; 29 | 30 | /// @notice Stake remainder per address, smaller than ELIGIBLE_UNIT. 31 | mapping(address => uint256) internal _stakeRemainder; 32 | 33 | /// @notice Total staked tokens. In ELIGIBLE_UNIT units. 34 | uint256 internal _stakeTotal; 35 | 36 | /// @notice Total accumulated reward since the beginning of time, in units 37 | /// per ELIGIBLE_UNIT. 38 | uint256 internal _rewardTotal; 39 | 40 | /// @notice Remainder from the last _distribute() call, this amount was not 41 | /// enough to award at least 1 wei to every staked ELIGIBLE_UNIT. At the 42 | /// time of last _distribute() call _rewardRemainder < _stakeTotal. 43 | /// Note that later, _stakeTotal can decrease, but _rewardRemainder will 44 | /// stay unchanged until the next call to _distribute(). 45 | uint256 internal _rewardRemainder; 46 | 47 | /// @notice Proportional rewards awarded *before* this stake was created. 48 | /// See [2] for more details. 49 | mapping(address => int256) _rewardOffset; 50 | 51 | event DepositMade(address indexed from, uint256 value); 52 | event DistributionMade(address indexed from, uint256 value); 53 | event RewardWithdrawalMade(address indexed to, uint256 value); 54 | event StakeWithdrawalMade(address indexed to, uint256 value); 55 | 56 | /// Initialize the contract. 57 | /// @param owner Contract owner, can call functions that change state. 58 | function initialize(address owner) public initializer { 59 | Ownable.initialize(owner); 60 | 61 | _stakeTotal = 0; 62 | _rewardTotal = 0; 63 | _rewardRemainder = 0; 64 | } 65 | 66 | /// @notice Deposit funds into contract. 67 | function deposit(address staker, uint256 tokens) public onlyOwner returns (bool success) { 68 | uint256 _tokensToAdd = tokens.add(_stakeRemainder[staker]); 69 | 70 | uint256 _eligibleUnitsToAdd = _tokensToAdd.div(ELIGIBLE_UNIT); 71 | 72 | // update the new remainder for this address 73 | _stakeRemainder[staker] = _tokensToAdd.mod(ELIGIBLE_UNIT); 74 | 75 | // set the current stake for this address 76 | _stake[staker] = _stake[staker].add(_eligibleUnitsToAdd); 77 | 78 | // update total eligible stake units 79 | _stakeTotal = _stakeTotal.add(_eligibleUnitsToAdd); 80 | 81 | // update reward offset 82 | _rewardOffset[staker] += (int256)(_rewardTotal * _eligibleUnitsToAdd); 83 | 84 | emit DepositMade(staker, tokens); 85 | return true; 86 | } 87 | 88 | /// @notice Distribute tokens pro rata to all stakers. 89 | function distribute(address from, uint256 tokens) public onlyOwner returns (bool success) { 90 | require(tokens > 0); 91 | 92 | // add past distribution remainder 93 | uint256 _amountToDistribute = tokens.add(_rewardRemainder); 94 | 95 | if (_stakeTotal == 0) { 96 | _rewardRemainder = _amountToDistribute; 97 | } else { 98 | // determine rewards per eligible stake 99 | uint256 _ratio = _amountToDistribute.div(_stakeTotal); 100 | 101 | // carry on remainder 102 | _rewardRemainder = _amountToDistribute.mod(_stakeTotal); 103 | 104 | // increase total rewards per stake unit 105 | _rewardTotal = _rewardTotal.add(_ratio); 106 | } 107 | emit DistributionMade(from, tokens); 108 | return true; 109 | } 110 | 111 | /// @notice Withdraw accumulated reward for the staker address. 112 | function withdrawReward(address staker) public onlyOwner returns (uint256 tokens) { 113 | uint256 _reward = getReward(staker); 114 | 115 | // refresh reward offset (so a new call to getReward returns 0) 116 | _rewardOffset[staker] = (int256)(_rewardTotal.mul(_stake[staker])); 117 | 118 | emit RewardWithdrawalMade(staker, _reward); 119 | return _reward; 120 | } 121 | 122 | /// @notice Withdraw stake for the staker address 123 | function withdrawStake(address staker, uint256 tokens) public onlyOwner returns (bool) { 124 | uint256 _currentStake = getStake(staker); 125 | 126 | require(tokens <= _currentStake); 127 | 128 | // update stake and remainder for this address 129 | uint256 _newStake = _currentStake.sub(tokens); 130 | 131 | _stakeRemainder[staker] = _newStake.mod(ELIGIBLE_UNIT); 132 | 133 | uint256 _eligibleUnitsDelta = _stake[staker].sub(_newStake.div(ELIGIBLE_UNIT)); 134 | 135 | _stake[staker] = _stake[staker].sub(_eligibleUnitsDelta); 136 | 137 | // update total stake 138 | _stakeTotal = _stakeTotal.sub(_eligibleUnitsDelta); 139 | 140 | // update reward offset 141 | _rewardOffset[staker] -= (int256)(_rewardTotal.mul(_eligibleUnitsDelta)); 142 | 143 | emit StakeWithdrawalMade(staker, tokens); 144 | return true; 145 | } 146 | 147 | /// @notice Withdraw stake for the staker address 148 | function withdrawAllStake(address staker) public onlyOwner returns (bool) { 149 | uint256 _currentStake = getStake(staker); 150 | return withdrawStake(staker, _currentStake); 151 | } 152 | 153 | /// 154 | /// READ ONLY 155 | /// 156 | 157 | /// @notice Read total stake. 158 | function getStakeTotal() public view returns (uint256) { 159 | return _stakeTotal.mul(ELIGIBLE_UNIT); 160 | } 161 | 162 | /// @notice Read current stake for address. 163 | function getStake(address staker) public view returns (uint256 tokens) { 164 | tokens = (_stake[staker].mul(ELIGIBLE_UNIT)).add(_stakeRemainder[staker]); 165 | 166 | return tokens; 167 | } 168 | 169 | /// @notice Read current accumulated reward for address. 170 | function getReward(address staker) public view returns (uint256 tokens) { 171 | int256 _tokens = ((int256)(_stake[staker].mul(_rewardTotal)) - _rewardOffset[staker]); 172 | 173 | tokens = (uint256)(_tokens); 174 | 175 | return tokens; 176 | } 177 | 178 | } 179 | -------------------------------------------------------------------------------- /contracts/BondingCurve/factory/BondingCurveFactory.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.7; 2 | pragma experimental ABIEncoderV2; 3 | 4 | import "@openzeppelin/upgrades/contracts/Initializable.sol"; 5 | import "@openzeppelin/upgrades/contracts/upgradeability/AdminUpgradeabilityProxy.sol"; 6 | import "@openzeppelin/upgrades/contracts/application/App.sol"; 7 | import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/IERC20.sol"; 8 | import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/StandaloneERC20.sol"; 9 | import "contracts/BondingCurve/BondingCurve.sol"; 10 | import "contracts/BondingCurve/BondingCurveEther.sol"; 11 | import "contracts/BondingCurve/curve/BancorCurveLogic.sol"; 12 | import "contracts/BondingCurve/curve/BancorCurveService.sol"; 13 | import "contracts/BondingCurve/curve/StaticCurveLogic.sol"; 14 | import "contracts/BondingCurve/dividend/RewardsDistributor.sol"; 15 | import "contracts/BondingCurve/token/BondedToken.sol"; 16 | import "contracts/BondingCurve/interface/ICurveLogic.sol"; 17 | 18 | /** 19 | * @title Combined Factory 20 | * @dev Allows for the deploy of a Bonding Curve and supporting components in a single transaction 21 | * This was developed to simplify the deployment process for DAOs 22 | */ 23 | contract BondingCurveFactory is Initializable { 24 | address internal _staticCurveLogicImpl; 25 | address internal _bancorCurveLogicImpl; 26 | address internal _bondedTokenImpl; 27 | address internal _bondingCurveImpl; 28 | address internal _rewardsDistributorImpl; 29 | address internal _bancorCurveServiceImpl; //Must already be initialized 30 | 31 | event ProxyCreated(address proxy); 32 | 33 | event BondingCurveDeployed( 34 | address indexed bondingCurve, 35 | address indexed bondedToken, 36 | address buyCurve, 37 | address rewardsDistributor, 38 | address indexed sender 39 | ); 40 | 41 | function initialize( 42 | address staticCurveLogicImpl, 43 | address bancorCurveLogicImpl, 44 | address bondedTokenImpl, 45 | address bondingCurveImpl, 46 | address bancorCurveServiceImpl, 47 | address rewardsDistributorImpl 48 | ) public initializer { 49 | _staticCurveLogicImpl = staticCurveLogicImpl; 50 | _bancorCurveLogicImpl = bancorCurveLogicImpl; 51 | _bondedTokenImpl = bondedTokenImpl; 52 | _bondingCurveImpl = bondingCurveImpl; 53 | _rewardsDistributorImpl = rewardsDistributorImpl; 54 | _bancorCurveServiceImpl = bancorCurveServiceImpl; 55 | } 56 | 57 | function _createProxy(address implementation, address admin, bytes memory data) 58 | internal 59 | returns (address) 60 | { 61 | AdminUpgradeabilityProxy proxy = new AdminUpgradeabilityProxy(implementation, admin, data); 62 | emit ProxyCreated(address(proxy)); 63 | return address(proxy); 64 | } 65 | 66 | function _deployStaticCurveLogic() internal returns (address) { 67 | return _createProxy(_staticCurveLogicImpl, address(0), ""); 68 | } 69 | 70 | function _deployBancorCurveLogic() internal returns (address) { 71 | return _createProxy(_staticCurveLogicImpl, address(0), ""); 72 | } 73 | 74 | function _deployBondedToken() internal returns (address) { 75 | return _createProxy(_bondedTokenImpl, address(0), ""); 76 | } 77 | 78 | function _deployBondingCurve() internal returns (address) { 79 | return _createProxy(_bondingCurveImpl, address(0), ""); 80 | } 81 | 82 | function _deployRewardsDistributor() internal returns (address) { 83 | return _createProxy(_rewardsDistributorImpl, address(0), ""); 84 | } 85 | 86 | function deployStaticEther( 87 | address owner, 88 | address beneficiary, 89 | uint256 buyCurveParams, 90 | uint256 reservePercentage, 91 | uint256 dividendPercentage, 92 | string calldata bondedTokenName, 93 | string calldata bondedTokenSymbol 94 | ) external { 95 | address[] memory proxies = new address[](4); 96 | 97 | proxies[0] = _deployStaticCurveLogic(); 98 | proxies[1] = _deployBondedToken(); 99 | proxies[2] = _deployBondingCurve(); 100 | proxies[3] = _deployRewardsDistributor(); 101 | 102 | StaticCurveLogic(proxies[0]).initialize(buyCurveParams); 103 | BondedToken(proxies[1]).initialize( 104 | bondedTokenName, 105 | bondedTokenSymbol, 106 | 18, 107 | proxies[2], // minter is the BondingCurve 108 | RewardsDistributor(proxies[3]) 109 | ); 110 | BondingCurveEther(proxies[2]).initialize( 111 | owner, 112 | beneficiary, 113 | BondedToken(proxies[1]), 114 | ICurveLogic(proxies[0]), 115 | reservePercentage, 116 | dividendPercentage 117 | ); 118 | RewardsDistributor(proxies[3]).initialize(proxies[1]); 119 | 120 | emit BondingCurveDeployed(proxies[2], proxies[1], proxies[0], proxies[3], msg.sender); 121 | } 122 | 123 | function deployStaticERC20( 124 | address owner, 125 | address beneficiary, 126 | address collateralToken, 127 | uint256 buyCurveParams, 128 | uint256 reservePercentage, 129 | uint256 dividendPercentage, 130 | string calldata bondedTokenName, 131 | string calldata bondedTokenSymbol 132 | ) external { 133 | address[] memory proxies = new address[](4); 134 | address[] memory tempCollateral = new address[](1); 135 | 136 | // Hack to avoid "Stack Too Deep" error 137 | tempCollateral[0] = collateralToken; 138 | 139 | proxies[0] = _deployStaticCurveLogic(); 140 | proxies[1] = _deployBondedToken(); 141 | proxies[2] = _deployBondingCurve(); 142 | proxies[3] = _deployRewardsDistributor(); 143 | 144 | StaticCurveLogic(proxies[0]).initialize(buyCurveParams); 145 | BondedToken(proxies[1]).initialize( 146 | bondedTokenName, 147 | bondedTokenSymbol, 148 | 18, 149 | proxies[2], // minter is the BondingCurve 150 | RewardsDistributor(proxies[3]), 151 | IERC20(tempCollateral[0]) 152 | ); 153 | BondingCurve(proxies[2]).initialize( 154 | owner, 155 | beneficiary, 156 | IERC20(collateralToken), 157 | BondedToken(proxies[1]), 158 | ICurveLogic(proxies[0]), 159 | reservePercentage, 160 | dividendPercentage 161 | ); 162 | RewardsDistributor(proxies[3]).initialize(proxies[1]); 163 | 164 | emit BondingCurveDeployed(proxies[2], proxies[1], proxies[0], proxies[3], msg.sender); 165 | } 166 | 167 | function deployBancorERC20( 168 | address owner, 169 | address beneficiary, 170 | address collateralToken, 171 | uint32 buyCurveParams, 172 | uint256 reservePercentage, 173 | uint256 dividendPercentage, 174 | string calldata bondedTokenName, 175 | string calldata bondedTokenSymbol 176 | ) external { 177 | address[] memory proxies = new address[](4); 178 | address[] memory tempCollateral = new address[](1); 179 | 180 | // Hack to avoid "Stack Too Deep" error 181 | tempCollateral[0] = collateralToken; 182 | 183 | proxies[0] = _deployBancorCurveLogic(); 184 | proxies[1] = _deployBondedToken(); 185 | proxies[2] = _deployBondingCurve(); 186 | proxies[3] = _deployRewardsDistributor(); 187 | 188 | BancorCurveLogic(proxies[0]).initialize( 189 | BancorCurveService(_bancorCurveServiceImpl), 190 | buyCurveParams 191 | ); 192 | 193 | BondedToken(proxies[1]).initialize( 194 | bondedTokenName, 195 | bondedTokenSymbol, 196 | 18, 197 | proxies[2], // minter is the BondingCurve 198 | RewardsDistributor(proxies[3]), 199 | IERC20(tempCollateral[0]) 200 | ); 201 | 202 | BondingCurve(proxies[2]).initialize( 203 | owner, 204 | beneficiary, 205 | IERC20(collateralToken), 206 | BondedToken(proxies[1]), 207 | ICurveLogic(proxies[0]), 208 | reservePercentage, 209 | dividendPercentage 210 | ); 211 | RewardsDistributor(proxies[3]).initialize(proxies[1]); 212 | 213 | emit BondingCurveDeployed(proxies[2], proxies[1], proxies[0], proxies[3], msg.sender); 214 | } 215 | 216 | function getImplementations() 217 | public 218 | view 219 | returns ( 220 | address staticCurveLogicImpl, 221 | address bancorCurveLogicImpl, 222 | address bondedTokenImpl, 223 | address bondingCurveImpl, 224 | address rewardsDistributorImpl, 225 | address bancorCurveServiceImpl 226 | ) 227 | { 228 | return ( 229 | _staticCurveLogicImpl, 230 | _bancorCurveLogicImpl, 231 | _bondedTokenImpl, 232 | _bondingCurveImpl, 233 | _rewardsDistributorImpl, 234 | _bancorCurveServiceImpl 235 | ); 236 | } 237 | 238 | } 239 | -------------------------------------------------------------------------------- /contracts/BondingCurve/interface/IBondingCurve.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | /// @title IBondingCurve - Partial bonding curve interface 4 | contract IBondingCurve { 5 | /// @dev Get the price in collateralTokens to mint bondedTokens 6 | /// @param numTokens The number of tokens to calculate price for 7 | function priceToBuy(uint256 numTokens) public view returns(uint256); 8 | 9 | /// @dev Get the reward in collateralTokens to burn bondedTokens 10 | /// @param numTokens The number of tokens to calculate reward for 11 | function rewardForSell(uint256 numTokens) public view returns(uint256); 12 | 13 | /// @dev Sell a given number of bondedTokens for a number of collateralTokens determined by the current rate from the sell curve. 14 | /// @param numTokens The number of bondedTokens to sell 15 | /// @param minPrice Minimum total price allowable to receive in collateralTokens 16 | /// @param recipient Address to send the new bondedTokens to 17 | function sell( 18 | uint256 numTokens, 19 | uint256 minPrice, 20 | address recipient 21 | ) public returns(uint256 collateralReceived); 22 | } -------------------------------------------------------------------------------- /contracts/BondingCurve/interface/IBondingCurveERC20.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | import "contracts/BondingCurve/interface/IBondingCurve.sol"; 3 | 4 | /// @title IBondingCurveERC20 - A bonding curve 5 | /// implementation that is backed by an ERC20 token. 6 | contract IBondingCurveERC20 is IBondingCurve { 7 | /// @dev Buy a given number of bondedTokens with a number of collateralTokens determined by the current rate from the buy curve. 8 | /// @param numTokens The number of bondedTokens to buy 9 | /// @param maxPrice Maximum total price allowable to pay in collateralTokens 10 | /// @param recipient Address to send the new bondedTokens to 11 | function buy( 12 | uint256 numTokens, 13 | uint256 maxPrice, 14 | address recipient 15 | ) public returns(uint256 collateralSent); 16 | 17 | /// @dev Pay tokens to the DAO. This method ensures the dividend holders get a distribution before the DAO gets the funds 18 | /// @param numTokens The number of ERC20 tokens you want to pay to the contract 19 | function pay(uint256 numTokens) public; 20 | } -------------------------------------------------------------------------------- /contracts/BondingCurve/interface/IBondingCurveEther.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | import "contracts/BondingCurve/interface/IBondingCurve.sol"; 3 | 4 | /// @title IBondingCurveEther - A bonding curve 5 | /// implementation that is backed by ether. 6 | contract IBondingCurveEther is IBondingCurve { 7 | /// @dev Buy a given number of bondedTokens with a number of ether determined by the current rate from the buy curve. 8 | /// @param numTokens The number of bondedTokens to buy 9 | /// @param maxPrice Maximum total price allowable to pay in ether 10 | /// @param recipient Address to send the new bondedTokens to 11 | function buy( 12 | uint256 numTokens, 13 | uint256 maxPrice, 14 | address recipient 15 | ) public payable returns(uint256 collateralSent); 16 | 17 | /// @dev Pay tokens to the DAO. This method ensures the dividend holders get a distribution before the DAO gets the funds 18 | /// @param numTokens The number of ERC20 tokens you want to pay to the contract 19 | function pay(uint256 numTokens) public payable; 20 | } -------------------------------------------------------------------------------- /contracts/BondingCurve/interface/ICurveLogic.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.0; 2 | 3 | /// @title Curve - A specific curve implementation used by a BondingCurvedToken. 4 | contract ICurveLogic { 5 | 6 | /// @dev Get the price to mint tokens 7 | /// @param totalSupply The existing number of curve tokens 8 | /// @param amount The number of curve tokens to mint 9 | function calcMintPrice( 10 | uint256 totalSupply, 11 | uint256 reserveBalance, 12 | uint256 amount 13 | ) public view returns (uint256); 14 | 15 | /// @dev Get the reward to burn tokens 16 | /// @param totalSupply The existing number of curve tokens 17 | /// @param amount The number of curve tokens to burn 18 | function calcBurnReward( 19 | uint256 totalSupply, 20 | uint256 reserveBalance, 21 | uint256 amount 22 | ) public view returns (uint256); 23 | } -------------------------------------------------------------------------------- /contracts/BondingCurve/token/BondedToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.7; 2 | 3 | import "@openzeppelin/upgrades/contracts/Initializable.sol"; 4 | import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/ERC20.sol"; 5 | import "contracts/BondingCurve/dividend/RewardsDistributor.sol"; 6 | import "contracts/BondingCurve/token/BondedTokenBase.sol"; 7 | 8 | /** 9 | * @title Dividend Token 10 | * @dev A standard ERC20, using Detailed & Mintable featurs. Accepts a single minter, which should be the BondingCurve. The minter also has exclusive burning rights. 11 | */ 12 | contract BondedToken is Initializable, BondedTokenBase { 13 | IERC20 _dividendToken; 14 | 15 | /// @dev Initialize contract 16 | /// @param name ERC20 token name 17 | /// @param symbol ERC20 token symbol 18 | /// @param decimals ERC20 token decimals 19 | /// @param minter Address to give exclusive minting and burning rights for token 20 | /// @param rewardsDistributor Instance for managing dividend accounting. 21 | /// @param dividendToken Instance of ERC20 in which dividends are paid. 22 | function initialize( 23 | string memory name, 24 | string memory symbol, 25 | uint8 decimals, 26 | address minter, 27 | RewardsDistributor rewardsDistributor, 28 | IERC20 dividendToken 29 | ) public initializer { 30 | BondedTokenBase.initialize(name, symbol, decimals, minter, rewardsDistributor); 31 | // TODO: uncomment below line once this contract is no longer used as PaymentToken in tests. 32 | // require(address(dividendToken) != address(0), "No dividend ERC20 contract provided."); 33 | _dividendToken = dividendToken; 34 | } 35 | 36 | function _validateComponentAddresses() internal returns (bool) { 37 | if (address(_rewardsDistributor) == address(0)) { 38 | return false; 39 | } 40 | 41 | if (address(_dividendToken) == address(0)) { 42 | return false; 43 | } 44 | 45 | return true; 46 | } 47 | 48 | /** 49 | * @dev Withdraw accumulated reward for the sender address. 50 | */ 51 | function withdrawReward() public returns (uint256) { 52 | if (!_validateComponentAddresses()) { 53 | return 0; 54 | } 55 | address payable _staker = msg.sender; 56 | uint256 _amount = _rewardsDistributor.withdrawReward(_staker); 57 | _dividendToken.transfer(_staker, _amount); 58 | return _amount; 59 | } 60 | 61 | /** 62 | * Claim and allocate provided dividend tokens to all balances greater than ELIGIBLE_UNIT. 63 | */ 64 | function distribute(address from, uint256 value) public returns (bool) { 65 | if (!_validateComponentAddresses()) { 66 | return false; 67 | } 68 | 69 | if (value == 0) { 70 | return false; 71 | } 72 | 73 | require( 74 | _dividendToken.transferFrom(from, address(this), value), 75 | "Dividend TransferFrom Failed." 76 | ); 77 | 78 | _rewardsDistributor.distribute(from, value); 79 | 80 | return true; 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /contracts/BondingCurve/token/BondedTokenBase.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.7; 2 | 3 | import "@openzeppelin/upgrades/contracts/Initializable.sol"; 4 | import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/ERC20.sol"; 5 | import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/ERC20Detailed.sol"; 6 | import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/ERC20Mintable.sol"; 7 | import "contracts/BondingCurve/dividend/RewardsDistributor.sol"; 8 | 9 | /** 10 | * @title Dividend Token 11 | * @dev A standard ERC20, using Detailed & Mintable featurs. Accepts a single minter, which should be the BondingCurve. The minter also has exclusive burning rights. 12 | */ 13 | contract BondedTokenBase is Initializable, ERC20Detailed, ERC20Mintable { 14 | RewardsDistributor _rewardsDistributor; 15 | 16 | /// @dev Initialize contract 17 | /// @param name ERC20 token name 18 | /// @param symbol ERC20 token symbol 19 | /// @param decimals ERC20 token decimals 20 | /// @param minter Address to give exclusive minting and burning rights for token 21 | /// @param rewardsDistributor Instance for managing dividend accounting. 22 | function initialize( 23 | string memory name, 24 | string memory symbol, 25 | uint8 decimals, 26 | address minter, 27 | RewardsDistributor rewardsDistributor 28 | ) public initializer { 29 | ERC20Detailed.initialize(name, symbol, decimals); 30 | ERC20Mintable.initialize(minter); 31 | _rewardsDistributor = rewardsDistributor; 32 | } 33 | 34 | /** 35 | * @dev Burns a specific amount of tokens. 36 | * @param value The amount of token to be burned. 37 | */ 38 | function burn(address from, uint256 value) public onlyMinter { 39 | _burn(from, value); 40 | 41 | if (address(_rewardsDistributor) != address(0)) { 42 | _rewardsDistributor.withdrawStake(from, value); 43 | } 44 | } 45 | 46 | /** 47 | * @dev Function to mint tokens 48 | * @param to The address that will receive the minted tokens. 49 | * @param value The amount of tokens to mint. 50 | * @return A boolean that indicates if the operation was successful. 51 | */ 52 | function mint(address to, uint256 value) public onlyMinter returns (bool) { 53 | ERC20Mintable.mint(to, value); 54 | 55 | if (address(_rewardsDistributor) != address(0)) { 56 | _rewardsDistributor.deposit(to, value); 57 | } 58 | return true; 59 | } 60 | 61 | /** 62 | * @dev Transfer token for a specified addresses 63 | * @param from The address to transfer from. 64 | * @param to The address to transfer to. 65 | * @param value The amount to be transferred. 66 | */ 67 | function _transfer(address from, address to, uint256 value) internal { 68 | ERC20._transfer(from, to, value); 69 | 70 | if (address(_rewardsDistributor) != address(0)) { 71 | _rewardsDistributor.withdrawStake(from, value); 72 | _rewardsDistributor.deposit(to, value); 73 | } 74 | } 75 | 76 | /** 77 | * @dev Reads current accumulated reward for address. 78 | * @param staker The address to query the reward balance for. 79 | */ 80 | function getReward(address staker) public view returns (uint256 tokens) { 81 | if (address(_rewardsDistributor) == address(0)) { 82 | return 0; 83 | } 84 | return _rewardsDistributor.getReward(staker); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /contracts/BondingCurve/token/BondedTokenEther.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.7; 2 | 3 | import "@openzeppelin/upgrades/contracts/Initializable.sol"; 4 | import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/ERC20.sol"; 5 | import "contracts/BondingCurve/dividend/RewardsDistributor.sol"; 6 | import "contracts/BondingCurve/token/BondedTokenBase.sol"; 7 | 8 | /** 9 | * @title Dividend Token 10 | * @dev A standard ERC20, using Detailed & Mintable featurs. Accepts a single minter, which should be the BondingCurve. The minter also has exclusive burning rights. 11 | */ 12 | contract BondedTokenEther is Initializable, BondedTokenBase { 13 | /// @dev Initialize contract 14 | /// @param name ERC20 token name 15 | /// @param symbol ERC20 token symbol 16 | /// @param decimals ERC20 token decimals 17 | /// @param minter Address to give exclusive minting and burning rights for token 18 | /// @param rewardsDistributor Instance for managing dividend accounting. 19 | function initialize( 20 | string memory name, 21 | string memory symbol, 22 | uint8 decimals, 23 | address minter, 24 | RewardsDistributor rewardsDistributor 25 | ) public initializer { 26 | BondedTokenBase.initialize(name, symbol, decimals, minter, rewardsDistributor); 27 | } 28 | 29 | function _validateComponentAddresses() internal returns (bool) { 30 | if (address(_rewardsDistributor) == address(0)) { 31 | return false; 32 | } 33 | return true; 34 | } 35 | 36 | /** 37 | * @dev Withdraw accumulated reward for the sender address. 38 | */ 39 | function withdrawReward() public returns (uint256) { 40 | if (!_validateComponentAddresses()) { 41 | return 0; 42 | } 43 | // address payable _staker = msg.sender; 44 | // uint256 _amount = _rewardsDistributor.withdrawReward(_staker); 45 | // _dividendToken.transfer(_staker, _amount); 46 | // return _amount; 47 | } 48 | 49 | /** 50 | * Claim and allocate provided dividend tokens to all balances greater than ELIGIBLE_UNIT. 51 | */ 52 | function distribute(address from, uint256 value) public payable returns (bool) { 53 | if (!_validateComponentAddresses()) { 54 | return false; 55 | } 56 | 57 | if (value == 0) { 58 | return false; 59 | } 60 | 61 | // require( 62 | // _dividendToken.transferFrom(from, address(this), value), 63 | // "Dividend TransferFrom Failed." 64 | // ); 65 | 66 | _rewardsDistributor.distribute(from, value); 67 | 68 | return true; 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /docs/Basics.md: -------------------------------------------------------------------------------- 1 | # OpenRaise 2 | 3 | > "Accountable fundraising for the next generation of decentralized organizations" 4 | 5 | Blockchain, and Ethereum in particular, promises to reshape capital formation. However, the ICO craze of 2017 demonstrated the need for accountability, giving rise to new models for fundraising such as Continuous Organizations and DAICOs. Open Raise is a modular library of smart contracts and UI components that bring these ideas to life and make it easy for organizations to run accountable fundraising campaigns. The first release will support curve bonded token sales with self-enforcing dividend claims. The roadmap (see below) consists of adding DAICO support and modularizing the code before integrating with popular DAO frameworks and supporting additional functionality. 6 | 7 | ## Fundraising Mechanisms 8 | 9 | - [Bonding Curves](./BondingCurve.md) 10 | - [DAICO](./DAICO.md) 11 | 12 | ## Token Features 13 | 14 | - [Dividend Claims](./DividendToken.md) 15 | -------------------------------------------------------------------------------- /docs/BondingCurve.md: -------------------------------------------------------------------------------- 1 | # Bonding Curve Fundraising 2 | 3 | The bonding curve fundraising module enables projects and organizations to issue tokens and raise money to fund their vision. The core of this implementation is a **bonding curve**, or automated market maker - as conceptualized by individuals such as [Simon de la Rouviere](https://medium.com/@simondlr/tokens-2-0-curved-token-bonding-in-curation-markets-1764a2e0bee5), [Billy Rennekamp](https://medium.com/@billyrennekamp/converting-between-bancor-and-bonding-curve-price-formulas-9c11309062f5) and [Thibauld Favre](https://github.com/C-ORG/whitepaper) to enable continuous funding for organizations, coupled with guaranteed liquidity for investors without relying on exchanges. 4 | 5 | Tokens issued via bonding curves can offer rights in the organization, such as **dividends** on future revenue or **governance rights**. 6 | 7 | This type of fundraising might allow for more flexibility, accountability, and alignment of incentives than alternative methods (such as ICOs or private fundraising). 8 | 9 | ## How it works 10 | 11 | - Anyone can deposit a specified collateral token (such as DAI) to purchase bonded tokens. 12 | - The **bonding curve** is an automated market maker contract that mints tokens to buyers at an algorithmically determined price. The automated market maker allows users to buy or sell tokens at any time for known prices with minimal slippage. 13 | - When a user **buys** bonded tokens with collateral tokens: The collateral tokens are split between the beneficiary treasury (to fund the organization) and the reserve (to facilitate liquidity for sells). The buyer receives bonded tokens based on the current price. 14 | 15 | ![](./diagrams/out/bonding_curve_buy_flow.png) 16 | 17 | - When a user **sells** bonded tokens: The bonded tokens are burned and the seller receives collateral tokens based on the current price. 18 | 19 | ![](./diagrams/out/bonding_curve_sell_flow.png) 20 | 21 | - When the beneficiary earns **revenue:** the income is split between direct income to the beneficiary treasury and **dividends** for the bonded token holders. 22 | 23 | ![](./diagrams/out/bonding_curve_pay_flow.png) 24 | 25 | ## Curve Economics 26 | 27 | - The specifics of curve design are an area of active research. A common general principle is to reward earlier contributors without inflating the buy price so high that it disincentivizes later participation. 28 | 29 | - Our current curve implementation is based on the **bancor formula**, which allows for a wide variety of potential curves with simple parameters. 30 | ![](./diagrams/out/bancor_curve_examples.png) 31 | 32 | [Bancor Whitepaper](https://storage.googleapis.com/website-bancor/2018/04/01ba8253-bancor_protocol_whitepaper_en.pdf) 33 | 34 | # Implementation 35 | 36 | Our initial bonding curve implementation supports: 37 | 38 | - Bancor-based curves. 39 | - Dividend distributions for bonded token holders. 40 | - A front-running guard via user-specified min and max prices. 41 | 42 | ### Key Terms 43 | 44 | - **bondingCurve**: The ‘avatar’ of the bonding curve. It serves as the external interface to interact with the curve, with automated market maker and dividend tracking functions. 45 | - **bondedToken**: Token native to the curve. The bondingCurve contract has exclusive rights to mint and burn tokens. 46 | - **collateralToken**: Token accepted as collateral by the curve. (e.g. ETH or DAI) 47 | - **reserve**: Balance of collateralTokens that the curve holds. The reserve is used to pay bondedToken holders when they want to liquidate and sell their tokens back to the curve. 48 | - **beneficiary**: Entity that receives funding from the purchase of bondedTokens. 49 | - **reservePercentage**: Percentage of incoming collateralTokens sent to reserve on buy(). The remainder is sent to beneficiary. 50 | - **dividendPercentage**: Percentage of incoming collateralTokens distributed to bondedToken holders on pay(). The remainder is sent to beneficiary. 51 | 52 | ### Key Actions 53 | 54 | The following chart describes the actions users can take to interact with the Bonding Curve: 55 | 56 | | Action | Actor | Analogy | Actor sends.. | bondedToken are.. | collateralTokens are.. | bondedToken price.. | 57 | | ---------------- | ---------------------------- | ------------ | ---------------- | -------------------------------- | ------------------------------------------------------------------------------- | ------------------- | 58 | | Buy() | Anyone, _except beneficiary_ | "Investment" | collateral token | minted to sender | split between reserve and beneficiary based on reservePercentage % | increases | 59 | | BeneficiaryBuy() | _beneficiary_ | "Investment" | collateral token | minted to sender (_beneficiary_) | fully deposited in reserve (none sent to _beneficiary_) | increases | 60 | | Sell() | Anyone | "Divestment" | bonded token | burned | transferred to specified recipient | decreases | 61 | | Pay() | Anyone | "Revenue" | collateral token | not changed | split between bondedToken holders and beneficiary based on dividendPercentage % | remains the same | 62 | 63 | #### Buy Flow 64 | 65 | ![Architecture Diagram](./diagrams/out/bonding_curve_architecture_buy.png) 66 | 67 | #### Payment Flow 68 | 69 | ![Architecture Diagram](./diagrams/out/bonding_curve_architecture_pay.png) 70 | 71 | ## Setup 72 | 73 | Bonding Curves can be deployed by an organization via a Factory. We will provide a factory for this "one-click deployment", though users can of course choose to deploy how they see fit. 74 | 75 | Bonding Curves are composed of several contracts, though the factory abstracts the need to know about them individually: 76 | 77 | - Bonding Curve 78 | - Bonded Token 79 | - Dividend Pool 80 | - Buy Curve Logic 81 | - Sell Curve Logic 82 | 83 | ## Usage 84 | 85 | ### Bonding Curve 86 | 87 | The primary point of external interaction with the curve - this is where users can buy & sell bondedTokens, and where the curve recieves revenue. 88 | 89 | [**`priceToBuy`**](./contracts/BondingCurve/BondingCurve.sol): Determine the current price in collateralTokens to buy a given number of bondedTokens. 90 | 91 | ``` 92 | function priceToBuy( 93 | uint256 numTokens 94 | ) public 95 | ``` 96 | 97 | [**`rewardForSell`**](./contracts/BondingCurve/BondingCurve.sol): Determine the current payout in collateralTokens to sell a given number of bondedTokens. 98 | 99 | ``` 100 | function rewardForSell( 101 | uint256 numTokens, 102 | ) public 103 | ``` 104 | 105 | [**`buy`**](./contracts/BondingCurve/BondingCurve.sol): Buy a given number of bondedTokens with an amount of collateralTokens determined by the current rate from the buy curve. The caller can specify the maximum total price in collateralTokens they're willing to pay, and a recipient address to transfer the new bondedTokens to. 106 | 107 | The appropriate amount of collateralTokens must have previously been approved for the bonding curve contract by the caller. 108 | 109 | ``` 110 | function buy( 111 | uint256 numTokens, 112 | uint256 maxPrice, 113 | address recipient 114 | ) public 115 | ``` 116 | 117 | [**`sell`**](./contracts/BondingCurve/BondingCurve.sol): Sell a given number of bondedTokens for an amount of collateralTokens determined by the current rate from the sell curve. The caller can set a minimum total value of collateralTokens they're willing to sell for, and a recipient to transfer the proceeds of the sale to. 118 | 119 | ``` 120 | function sell( 121 | uint256 numTokens, 122 | uint256 minPrice, 123 | address recipient 124 | ) public 125 | ``` 126 | 127 | [**`pay`**](./contracts/BondingCurve/BondingCurve.sol): Pay the beneficiary in collateralTokens. Revenue sent in this method is distributed between the beneficiary and the bondedToken holders according to the dividendPercentage parameter; 128 | 129 | ``` 130 | function pay( 131 | uint256 amount 132 | ) public 133 | ``` 134 | 135 | ### Dividend Pool 136 | 137 | Users interact with this contract to claim their dividend allocations. 138 | 139 | [**`withdraw`**](./contracts/BondingCurve/BondingCurve.sol): Withdraw collateralToken dividends sender is entitled to for a given period, in blocks. 140 | 141 | ``` 142 | function withdraw( 143 | uint start, 144 | uint end 145 | ) public 146 | ``` 147 | 148 | ## Future Plans 149 | 150 | We envision the following features may be useful to organizations implementing bonding curves. 151 | 152 | ### Financial Features 153 | 154 | - **Hatching** - An initial buying phase where selling is disabled up until a certain amount of tokens are bought. This helps ensure a certain amount of return for early investors. 155 | - **Vesting** - Vesting periods can be added to minted tokens, which helps fight against pumping and dumping. 156 | - **Taxes** - A % fee for selling back to the market can be added to encourage secondary market trading. 157 | - **Governance via BondedTokens** - Voting power can be granted to token holders, which gives investors a say in how their money is being used. 158 | - **Multicurrency Reserve** - Allow multiple tokens to be added to reserve as collateralTokens. 159 | 160 | ### Regulatory Features 161 | 162 | - **KYC / Whitelisting** - Organizations may wish to ensure that bonding curve investments only come from KYC'ed ethereum addresses. The [TPL standard](https://tplprotocol.org/) designed by Open Zeppelin offers a standard to incorporate this functionality. 163 | 164 | ### Security Features 165 | 166 | - **Additional Front-running Guards** - Several variants of order batching have been outlined in the community. In addition, maximum gas prices for transactions may offer a simple mechanic to discourage front-running. 167 | 168 | ### Technical Features 169 | 170 | - **Modularity** - We envision an "OpenZeppelin for bonding curves" - an open source repo to compose your own bonding curve from a suite of well-established components. 171 | -------------------------------------------------------------------------------- /docs/DAICO.md: -------------------------------------------------------------------------------- 1 | # DAICO Fundraising 2 | 3 | -- Coming Soon -- 4 | -------------------------------------------------------------------------------- /docs/Development.md: -------------------------------------------------------------------------------- 1 | # Developer Instructions 2 | 3 | ## `zos` workflow for local development 4 | 5 | We use [ZeppelinOS](https://docs.zeppelinos.org/docs/start.html) to develop, deploy and operate the Enable loan kit packages. The [ZeppelinOS Documentation](https://docs.zeppelinos.org/docs/start.html) is a good start. 6 | 7 | ### Setup 8 | 9 | 1. Use the proper version of node (see .nvmrc) 10 | 2. Run `yarn` to install all zeppelinOS related dependencies 11 | 3. Run `ganache-cli` (or `ganache-cli --deterministic`) to run a local blockchain 12 | 13 | ### Deploy to ganache `development` network 14 | 15 | For background: read [Publishing an EVM package](https://docs.zeppelinos.org/docs/publishing.html). 16 | 17 | 1. `zos publish --network development`. This publishes the project's app, package and provider. This updates the [zos config](https://docs.zeppelinos.org/docs/configuration.html) file with "app.address" field that is needed for tests to run. 18 | 2. `zos push --network development`. This deploys the contracts in the project. This has the same effect as running `zos create` on every contract. See [Quickstart](https://docs.zeppelinos.org/docs/first.html) for context. 19 | 20 | ### Running tests 21 | 22 | 1. `yarn test`. Also runs `zos push` (Dan: does it upgrade contracts as well?) 23 | 24 | ### Upgrading contracts 25 | 26 | For background: read [Upgrading contracts](https://docs.zeppelinos.org/docs/first.html#upgrading-your-contract) 27 | 28 | 1. `zos upgrade ` or `zos upgrade --all` based on contract changed. This should upgrade the contracts. 29 | 30 | ## Editor setup 31 | 32 | We use ESLint and Prettier to format our code. Please make sure you have the following setting turned on in VSCode (or equivalent editor). 33 | 34 | ``` 35 | editor.formatOnSave: true 36 | ``` -------------------------------------------------------------------------------- /docs/DividendToken.md: -------------------------------------------------------------------------------- 1 | # Dividend Bearing Tokens 2 | 3 | ERC20-compliant tokens that distribute pro-rata dividend payments among token holders in real time. Token holders can claim what they are entitled to at any time. When tokens are traded, the new owner gets rights to future claims. 4 | 5 | ## Current Limitations 6 | 7 | - **Dividends are only distributed on pay()** - Without hooks on ERC20 transfers, we can't execute distribution logic when ERC20 tokens are transferred to the BondingCurve via the standard transfer() method. Ideally, we could allow 'native' ERC20 payments to function just as pay() does. 8 | 9 | - We'll be incorporating ERC777 hooks, which will alleviate this issue for tokens that adopt that standard. 10 | 11 | - **Payments directly to DAO Avatar can circumvent dividends** - It's possible for actors to bypass the bonding curve and send payments directly to the DAO Avatar. If customers pay the DAO directly rather than sending payment with the pay() function to the bonding curve, then the DAO would receive 100% of the payment, effectively cutting out token holders from receiving their portion. 12 | 13 | - For instance, DutchX fees might initially be configured to hit the pay() function on the bonding curve, resulting in continuous cash-flows to both token-holders (in the form of claimable dividends) and the DAO according to **dividendPercentage**. However, the DAO might vote to re-route the fees directly to itself, avoiding the pay split with token holders. 14 | 15 | - We believe that the chances of such a coordinated attack will remain extremely low – as long as the prospects for continued funding are valued more than the present level of cash-flows. If the DAO was detected trying to "cheat" its token-holders in this way, we would expect a chain reaction of sell-offs and little to no prospect for future buys. Thus, the DAO would short-sightedly lose all ability to fundraise and would need to rely solely on its existing sources of revenue. 16 | 17 | - We have an open discussion on this issue [here](https://github.com/dOrgTech/BC-DAO/issues/4). 18 | -------------------------------------------------------------------------------- /docs/Research.md: -------------------------------------------------------------------------------- 1 | # Bonding Curve Survey 2 | 3 | [Extended Notes and other implementations](https://docs.google.com/document/d/1fBoe1enG_M7ReIycURh_uH6k9mmGz_Sp07j19mCcKCo/edit#) 4 | 5 | ### Purpose 6 | *To present a summary state of affairs of bonding curves and select the most interesting existing model(s) and implementation(s) to move forward with the Open Raise project.* 7 | 8 | We also want to take into consideration that: 9 | 10 | - Bonding curve functionality and implementations will evolve likely rapidly 11 | - We want to handle interfacing with a number of curve implementations. 12 | - We want to evaluate what will be favored by dxDAO, as we are hoping they will use the CF functionality developed in GECO. 13 | 14 | ## Bonding Curves: Feature Overview 15 | 16 | An overview of the features one would conceivably want a bonding curve to support. This will likely evolve as new ideas emerge. 17 | 18 | ### Core Curve Features 19 | 20 | - *Curve Function*: Linear, Polynomial, Logarithmic, etc. [See thorough discussion of trade-offs here](https://medium.com/thoughtchains/on-single-bonding-curves-for-continuous-token-models-a167f5ffef89) 21 | - *ReserveRatio*: % relationship between buy and sell prices 22 | - *Spread/Split* (separate buy and sell curves)**:** Difference between buy and sell curve or amount that goes to beneficiary 23 | 24 | ### Financial Features 25 | 26 | - *Taxes* - A % fee for selling back to the market can be added to encourage secondary market trading. 27 | - *Hatching* - An initial buying phase where selling is disabled up until a certain amount of tokens are bought. This helps ensure a certain amount of return for early investors. 28 | - *Vesting* - Vesting periods can be added to minted tokens, which helps fight against pumping and dumping. 29 | - *Governance* - Voting power can be given to token holders, which can help further insulate their potentially risky investment. (potentially through locking down the token) 30 | - *Dividends* - Token holders can also be given claims on cash flow of the DAO. This claim can be made on revenues the DAO realizes both in the form of direct customer payments, or via shareholder payouts the DAO chooses to distribute. 31 | - *Donations* - users are allowed to donate the reserve currency without getting tokens. (This can be withdrawn by the owner?) 32 | - *Multicurrency Support* - Allow multiple tokens to be added to reserve. 33 | 34 | ### Security Features 35 | 36 | - *Front-running Guards* (Order batching, Expected price, Max gas price) 37 | 38 | ## Relevant Implementations 39 | 40 | ### [C-Org](https://github.com/C-ORG/whitepaper) / [Fairmint](https://github.com/Fairmint/c-org) (active development) 41 | 42 | Revenue injected into curve to raise token price 43 | 44 | - buy(), sell(), burn(), pay(), close() 45 | 46 | - "split" approach (as opposed to "spread") 47 | 48 | \+ implicit dividend mechanism – revenue() 49 | 50 | \- rapidly changing (recently started) 51 | 52 | \- only supports linear curve 53 | 54 | \- no front-running guards 55 | 56 | ### [Aragon Fundraising](https://github.com/AragonBlack/fundraising) (active development) 57 | 58 | Funds withdrawn by organization through rate limited tap, token is used for governance of funds 59 | 60 | - buy(), sell(), transfer() 61 | 62 | - More info [[1]](https://github.com/1Hive/Apiary)[[2]](https://blog.aragon.org/introducing-aragon-fundraising/) 63 | 64 | \+ batched bonding front-running guard 65 | 66 | \+ multi-collateral 67 | 68 | \- No dividend mechanism; Tokens are used for governance 69 | 70 | \- Closely coupled with AragonOS 71 | 72 | ### [Batched Bonding](https://github.com/okwme/BatchedBondingCurves) (active development) 73 | 74 | Batch buy and sell orders to prevent front-running (combine all the sells into one order, then split it among the participants of the sell. Then combine all of the buy orders into one, then split it among the buyers.) 75 | 76 | - More info [[1]](https://observablehq.com/@okwme/batched-bonding-curves) 77 | 78 | ### [Milky Way](https://github.com/convergentcx/milky-way/tree/master/contracts) (archived) 79 | 80 | Composable Bonding Curve Library 81 | 82 | \+ Composable 83 | 84 | \- No dividend mechanism 85 | 86 | ### [Band Protocol](https://github.com/bandprotocol/contracts) (production) 87 | 88 | - More info [[1]](https://developer.bandprotocol.com/docs/bonding-curve.html): 89 | 90 | - has some sort of inflation/deflation mechanism 91 | 92 | - [Bonding Curve Factory](https://github.com/bandprotocol/contracts/blob/master/contracts/factory/BondingCurveFactory.sol) 93 | 94 | \+ Composable, dynamic curves 95 | 96 | \- No dividend mechanism 97 | 98 | ## Common Elements 99 | 100 | BancorPower.sol 101 | 102 | BancorFormula.sol 103 | 104 | ### Bonding Curve: Equation Considerations 105 | 106 | - Actual product adoption for successful projects will likely resemble an S-curve (sigmoid). 107 | 108 | - "when we are designing curves that span several magnitudes, price tends to stay very low for 80% of the curve, then inevitably accelerates to unmanageable and unreasonable levels very quickly." 109 | 110 | - A logarithmic curve might also be appropriate. 111 | 112 | -------------------------------------------------------------------------------- /docs/Roadmap.md: -------------------------------------------------------------------------------- 1 | # Roadmap 2 | 3 | | Phase | Description | Status | 4 | | ----- | -------------------------------------------------------------------------------------------------------------- | -------------------------------------------------- | 5 | | 1 | Bonded Dividend Token Module | Funded and underway; target completion in Feb 2020 | 6 | | 2 | DAICO support (governance, milestone disbursement, tap) + New Features (vesting, multi-token dividend support) | Seeking Funding | 7 | | 3 | DAO Framework Integrations (DAOstack, Aragon, Moloch) | Future | 8 | | 4 | Legal Compliance & Fiat On-ramps | Future | 9 | 10 | ## Phase 1 (Remaining) 11 | 12 | #### Smart Contracts 13 | 14 | - ETH as collateral 15 | - Pausable curve 16 | 17 | #### Testing and Auditing 18 | 19 | - Unit testing for new functionality 20 | - E2E testing environment with simulated user actions 21 | - Internal auditing 22 | 23 | ### Dapp ([Repo](https://github.com/levelkdev/BC-DAPP/)) 24 | 25 | - Integrate smart contract interaction for buy/sell and trade history 26 | - Graphical curve display 27 | 28 | #### Documentation 29 | 30 | - MVP Documentation improvement 31 | 32 | ## Phase 2 33 | 34 | #### Smart Contracts (DAICO Support) 35 | 36 | - Milestone based disbursement 37 | - Token based governance of disbursements (including possible withdrawal of funds) 38 | - “Tap” functionality (continuous disbursement vs. milestone disbursement) 39 | 40 | #### Smart Contracts (Other) 41 | 42 | - Refactor into Fundraise and Reward components 43 | - Add Vesting component 44 | - Multi-token dividend support 45 | 46 | #### Dapp (DAICO) 47 | 48 | - React component library 49 | - DAICO investor UI 50 | - Governance UI 51 | - DAO disbursement UI 52 | 53 | #### Dapp (Other) 54 | 55 | - Support for whitelabeling 56 | - Multi-currency collateral token support 57 | - UI testing 58 | 59 | #### Testing & Auditing 60 | 61 | - Professional audit 62 | - Curve simulations to battletest Bancor math 63 | -------------------------------------------------------------------------------- /docs/diagrams/out/admin_scheme_architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dOrgTech/OpenRaise/3d1b8cdea4994ab1f98107dcc6ecd44a835b44b4/docs/diagrams/out/admin_scheme_architecture.png -------------------------------------------------------------------------------- /docs/diagrams/out/bancor_curve_examples.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dOrgTech/OpenRaise/3d1b8cdea4994ab1f98107dcc6ecd44a835b44b4/docs/diagrams/out/bancor_curve_examples.png -------------------------------------------------------------------------------- /docs/diagrams/out/bonding_curve_architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dOrgTech/OpenRaise/3d1b8cdea4994ab1f98107dcc6ecd44a835b44b4/docs/diagrams/out/bonding_curve_architecture.png -------------------------------------------------------------------------------- /docs/diagrams/out/bonding_curve_architecture_buy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dOrgTech/OpenRaise/3d1b8cdea4994ab1f98107dcc6ecd44a835b44b4/docs/diagrams/out/bonding_curve_architecture_buy.png -------------------------------------------------------------------------------- /docs/diagrams/out/bonding_curve_architecture_pay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dOrgTech/OpenRaise/3d1b8cdea4994ab1f98107dcc6ecd44a835b44b4/docs/diagrams/out/bonding_curve_architecture_pay.png -------------------------------------------------------------------------------- /docs/diagrams/out/bonding_curve_buy_flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dOrgTech/OpenRaise/3d1b8cdea4994ab1f98107dcc6ecd44a835b44b4/docs/diagrams/out/bonding_curve_buy_flow.png -------------------------------------------------------------------------------- /docs/diagrams/out/bonding_curve_merkle_flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dOrgTech/OpenRaise/3d1b8cdea4994ab1f98107dcc6ecd44a835b44b4/docs/diagrams/out/bonding_curve_merkle_flow.png -------------------------------------------------------------------------------- /docs/diagrams/out/bonding_curve_pay_flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dOrgTech/OpenRaise/3d1b8cdea4994ab1f98107dcc6ecd44a835b44b4/docs/diagrams/out/bonding_curve_pay_flow.png -------------------------------------------------------------------------------- /docs/diagrams/out/bonding_curve_sell_flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dOrgTech/OpenRaise/3d1b8cdea4994ab1f98107dcc6ecd44a835b44b4/docs/diagrams/out/bonding_curve_sell_flow.png -------------------------------------------------------------------------------- /docs/diagrams/out/invest_scheme_architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dOrgTech/OpenRaise/3d1b8cdea4994ab1f98107dcc6ecd44a835b44b4/docs/diagrams/out/invest_scheme_architecture.png -------------------------------------------------------------------------------- /docs/diagrams/src/admin_scheme_architecture.drawio: -------------------------------------------------------------------------------- 1 | 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 -------------------------------------------------------------------------------- /docs/diagrams/src/bonding_curve_architecture_buy.drawio: -------------------------------------------------------------------------------- 1 | 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 -------------------------------------------------------------------------------- /docs/diagrams/src/bonding_curve_architecture_pay.drawio: -------------------------------------------------------------------------------- 1 | 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 -------------------------------------------------------------------------------- /docs/diagrams/src/invest_scheme_architecture.drawio: -------------------------------------------------------------------------------- 1 | 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 -------------------------------------------------------------------------------- /docs/diagrams/src/open_raise.drawio: -------------------------------------------------------------------------------- 1 | 7Zpdc9o4FIZ/jWd2L+j4O3AZIGlnp5nNbjKT7tWOwMKoERaV5QD76/fIkjDGdmIK1GkHbrCOjoR8nqNXsoXljRbrjxwt53cswtRy7WhteWPLdR3XtuFLWjbaYtuessScRNpWGB7If9g4amtGIpyWHAVjVJBl2ThlSYKnomRDnLNV2W3GaPlXlyjGFcPDFNGq9YlEYq6s/cAu7J8wiefmlx1zxwtknLUhnaOIrXZM3o3ljThjQl0t1iNMZfRMXFS724ba7cA4TkSbBo9Pz4Orv8i/o/XTV3bnDe8evv3Rc/t6cGJj7hhHEABdZFzMWcwSRG8K65CzLImw7NaGUuHzmbElGB0wfsVCbDRNlAkGprlYUF0LI+abL7L9h8AU/9Hd5YXxulTa6NKMJUJ36oZQVmOXA26MiTalLONT/FogdG4hHmPxip/jbdFB0mO2wDBAaMgxRYK8lAeCdPLFW7+CD1xoRAfg8sILroNwuYMucelRviCa6V+y3JAKHZoSx/BbxkxFL82Ddg0OTrhcF5VwFcvvv/EK8SiF+idEKQRA9wqDVB0rt0quFJkgsa7mROCHJcoDvQL9LlN/wVwQEMJrSuIEbEJmyzAFf5LEj3nqFFClM16/jrVKQTfwQi2RepHwjYauCsV1jIzOd9XWGE9OzvHOhO6RPeMEaj/BQoR5+pOT267vmlwQdk7O3MQR5FxJLke2B8+6ta3BrTVwrOG4LTkIrijjSQWHJBgxyjhYEpZIkZ4RSvdMSOObAgzMa7guSBTlCl+XD2XVr5fik7IPa2atX4PePxv54+X2Qr4Vef+9kT9erS/kW5HvvzfywYV8N2rvdU0+vJDvRu07J391Id+N2ndOftBIHp5uku9/KvuTxzu0VV8/15NY4L67JzHfqQTvl3xZ9fZLqHpyrd9B6ab3jOTqpon7e8S3s9V0oV6i6VZ7LLfDOAJvddN9jzZpC3nkGOYjmuQOMvRLOcR80MHQCsZSGAGrmrN5A6OTFM9Eu1nV88tInRNNM39PEj0vqEyzoGaWnW+SNW+AI/JSq4gSSE+HVEqijqolT0dKWuk1rpIHim5zR2q57cOKO2wU4K05v6E3Zdl+W5Z3EsMLD1ivT6HTwdtPUGFNAoVnS6DmfXRrvL5cU22VQ7XZZSpzLr1UgZG1CcvpNO7DXGtoH74yH5YC7g9OAd9oxlZDOt5W+f2aFNgLbjpHS3mZLej1VLDdzepnNMH0HvRaECbleMKEYAtwoLJiiKbPcY7GRNJyvVn+aZTyHU4sE5QkAMYc79oVWNCfnX/qie1P9lMQtMsEnUGVoPcjJ7ERlZMfV4wJxJpMMoFbH1W8i/X+VKSd/aOpfpX0oIZ0cDbSxx9vXEjXLsyD8pz2r7om7R69MCvSDQuzfulgqidbne5NlYBKJx5PfoM9i/RyR/LFSfn692oejbIU1F91DPcPv0MJzhNTFZdos1BlfcjfcnX/pZLN3Us2p2YLULsN/I5sg2LxJyP18Ff8V8u7+R8= -------------------------------------------------------------------------------- /ecosystem.deploy.sh: -------------------------------------------------------------------------------- 1 | npx oz create BondingCurveFactory --no-interactive $@ 2 | truffle exec ./ecosystem.initialize.js $@ 3 | -------------------------------------------------------------------------------- /ecosystem.initialize.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | require("dotenv").config(); 3 | 4 | const fs = require("fs"); 5 | 6 | const App = artifacts.require("App"); 7 | 8 | const contractNames = { 9 | StaticCurveLogic: "StaticCurveLogic", 10 | BancorCurveLogic: "BancorCurveLogic", 11 | BondedToken: "BondedToken", 12 | RewardsDistributor: "RewardsDistributor", 13 | BondingCurve: "BondingCurve", 14 | BondingCurveFactory: "BondingCurveFactory", 15 | BancorCurveService: "BancorCurveService" 16 | }; 17 | 18 | const BancorCurveService = artifacts.require("BancorCurveService"); 19 | const BondingCurveFactory = artifacts.require("BondingCurveFactory"); 20 | 21 | const truffleConfig = require("./truffle-config.js"); 22 | 23 | const OZ_PACKAGE = "@dorgtech/open-raise"; 24 | 25 | function activeNetwork() { 26 | const networkIndex = process.argv.lastIndexOf("--network"); 27 | if (networkIndex < 2) { 28 | return "development"; 29 | } 30 | return process.argv[networkIndex + 1]; 31 | } 32 | 33 | function activeNetworkName() { 34 | return activeNetwork() === "development" 35 | ? `dev-${App.network_id}` 36 | : activeNetwork(); 37 | } 38 | 39 | /* 40 | * Get zos config info for specified networkId. 41 | */ 42 | function getOZNetworkConfig(networkName) { 43 | const zosNetworkFile = fs.readFileSync(`./.openzeppelin/${networkName}.json`); 44 | return JSON.parse(zosNetworkFile); 45 | } 46 | 47 | function getAppAddress() { 48 | const ozNetworkConfig = getOZNetworkConfig(activeNetworkName()); 49 | return ozNetworkConfig.app.address; 50 | } 51 | 52 | function getLatestProxy(contractName) { 53 | const ozNetworkConfig = getOZNetworkConfig(activeNetworkName()); 54 | const proxies = ozNetworkConfig.proxies[`${OZ_PACKAGE}/${contractName}`]; 55 | if (!proxies || proxies.length <= 1) { 56 | throw Error(`No deployed proxies of contract ${contractName} found`); 57 | } 58 | return proxies[proxies.length - 1]; 59 | } 60 | 61 | function helpers() { 62 | return { 63 | constants: { 64 | ZERO_ADDRESS: "0x0000000000000000000000000000000000000000" 65 | } 66 | }; 67 | } 68 | 69 | async function initializeBancorCurveService(contractAddress) { 70 | const contract = await BancorCurveService.at(contractAddress); 71 | return contract.initialize(); 72 | } 73 | 74 | async function initializeBondingCurveFactory(contractAddress) { 75 | const ozNetworkConfig = getOZNetworkConfig(activeNetworkName()); 76 | const contract = await BondingCurveFactory.at(contractAddress); 77 | 78 | return contract.initialize( 79 | ozNetworkConfig.contracts.StaticCurveLogic.address, 80 | ozNetworkConfig.contracts.BancorCurveLogic.address, 81 | ozNetworkConfig.contracts.BondedToken.address, 82 | ozNetworkConfig.contracts.BondingCurve.address, 83 | ozNetworkConfig.contracts.RewardsDistributor.address, 84 | getLatestProxy(contractNames.BancorCurveService).address 85 | ); 86 | } 87 | 88 | module.exports = async () => { 89 | const { constants } = helpers(); 90 | try { 91 | const bancorCurveServiceAddress = getLatestProxy( 92 | contractNames.BancorCurveService 93 | ).address; 94 | const bondingCurveFactoryAddress = getLatestProxy( 95 | contractNames.BondingCurveFactory 96 | ).address; 97 | console.log( 98 | `${contractNames.BancorCurveService} to initialize:`, 99 | bancorCurveServiceAddress 100 | ); 101 | console.log( 102 | `${contractNames.BondingCurveFactory} to initialize:`, 103 | bondingCurveFactoryAddress 104 | ); 105 | 106 | // let initializeTx = await initializeBancorCurveService(bancorCurveServiceAddress); 107 | // console.log(`${contractNames.BancorCurveService} initialization tx:`, initializeTx.tx); 108 | 109 | let initializeTx = await initializeBondingCurveFactory( 110 | bondingCurveFactoryAddress 111 | ); 112 | console.log( 113 | `${contractNames.BondingCurveFactory} initialization tx:`, 114 | initializeTx.tx 115 | ); 116 | } catch (e) { 117 | console.error(e); 118 | } 119 | 120 | process.exit(); 121 | }; 122 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | // Required by @openzeppelin/upgrades when running from truffle 2 | global.artifacts = artifacts; 3 | global.web3 = web3; 4 | 5 | const { 6 | Contracts, 7 | SimpleProject, 8 | ZWeb3, 9 | AppProject, 10 | Package 11 | } = require("@openzeppelin/upgrades"); 12 | 13 | const StaticCurveLogic = Contracts.getFromLocal("StaticCurveLogic"); 14 | const BancorFormula = Contracts.getFromLocal("BancorFormula"); 15 | const BancorCurveLogic = Contracts.getFromLocal("BancorCurveLogic"); 16 | const BancorCurveService = Contracts.getFromLocal("BancorCurveService"); 17 | const BondingCurve = Contracts.getFromLocal("BondingCurve"); 18 | const BondingCurveFactory = Contracts.getFromLocal("BondingCurveFactory"); 19 | const BondedToken = Contracts.getFromLocal("BondedToken"); 20 | const RewardsDistributor = Contracts.getFromLocal("RewardsDistributor"); 21 | 22 | const CONTRACT_ABIS = { 23 | BondingCurve, 24 | BondingCurveFactory, 25 | BancorCurveLogic, 26 | StaticCurveLogic, 27 | BondedToken, 28 | BancorCurveService, 29 | RewardsDistributor 30 | }; 31 | 32 | const CONTRACT_NAMES = { 33 | BondingCurve: "BondingCurve", 34 | BondingCurveFactory: "BondingCurveFactory", 35 | BancorCurveLogic: "BancorCurveLogic", 36 | StaticCurveLogic: "StaticCurveLogic", 37 | BondedToken: "BondedToken", 38 | BancorCurveService: "BancorCurveService", 39 | RewardsDistributor: "RewardsDistributor" 40 | }; 41 | 42 | const PACKAGE_NAMES = { 43 | self: "example-openzeppelin-upgrades-simple" 44 | }; 45 | 46 | async function setupApp(txParams) { 47 | // On-chain, single entry point of the entire application. 48 | const initialVersion = "2.5.2"; 49 | const appProject = await AppProject.fetchOrDeploy( 50 | "example-openzeppelin-upgrades-simple", 51 | initialVersion, 52 | txParams, 53 | {} 54 | ); 55 | 56 | // Add all implementations 57 | await appProject.setImplementation(BondingCurve, CONTRACT_NAMES.BondingCurve); 58 | await appProject.setImplementation( 59 | BondingCurveFactory, 60 | CONTRACT_NAMES.BondingCurveFactory 61 | ); 62 | await appProject.setImplementation( 63 | BancorCurveLogic, 64 | CONTRACT_NAMES.BancorCurveLogic 65 | ); 66 | await appProject.setImplementation( 67 | StaticCurveLogic, 68 | CONTRACT_NAMES.StaticCurveLogic 69 | ); 70 | await appProject.setImplementation(BondedToken, CONTRACT_NAMES.BondedToken); 71 | await appProject.setImplementation( 72 | BancorCurveService, 73 | CONTRACT_NAMES.BancorCurveService 74 | ); 75 | await appProject.setImplementation( 76 | RewardsDistributor, 77 | CONTRACT_NAMES.RewardsDistributor 78 | ); 79 | 80 | return appProject; 81 | } 82 | 83 | async function deployProject() { 84 | ZWeb3.initialize(web3.currentProvider); 85 | const [creatorAddress, initializerAddress] = await ZWeb3.accounts(); 86 | const myProject = new SimpleProject("MyProject", null, { 87 | from: creatorAddress 88 | }); 89 | 90 | return myProject; 91 | } 92 | 93 | async function deployStaticCurveLogic(myProject, initArgs) { 94 | ZWeb3.initialize(web3.currentProvider); 95 | const instance = await myProject.createProxy(StaticCurveLogic, { 96 | initArgs 97 | }); 98 | return instance; 99 | } 100 | 101 | async function deployBancorFormula(myProject) { 102 | ZWeb3.initialize(web3.currentProvider); 103 | const [creatorAddress, initializerAddress] = await ZWeb3.accounts(); 104 | 105 | const instance = await myProject.createProxy(BancorFormula); 106 | await instance.methods.initialize().send({ from: initializerAddress }); 107 | return instance; 108 | } 109 | 110 | async function deployBancorCurveService(myProject) { 111 | ZWeb3.initialize(web3.currentProvider); 112 | const [creatorAddress, initializerAddress] = await ZWeb3.accounts(); 113 | 114 | const instance = await myProject.createProxy(BancorCurveService); 115 | await instance.methods.initialize().send({ from: initializerAddress }); 116 | return instance; 117 | } 118 | 119 | async function deployBancorCurveLogic(myProject, initArgs) { 120 | ZWeb3.initialize(web3.currentProvider); 121 | 122 | const instance = await myProject.createProxy(BancorCurveLogic, { 123 | initArgs 124 | }); 125 | return instance; 126 | } 127 | 128 | async function createBondingCurve(myProject) { 129 | ZWeb3.initialize(web3.currentProvider); 130 | 131 | const instance = await myProject.createProxy(BondingCurve); 132 | return instance; 133 | } 134 | 135 | async function deployBondingCurve(myProject, initArgs) { 136 | ZWeb3.initialize(web3.currentProvider); 137 | 138 | const instance = await myProject.createProxy(BondingCurve, { 139 | initArgs 140 | }); 141 | return instance; 142 | } 143 | 144 | async function deployBondingCurveFactory(myProject, initArgs) { 145 | ZWeb3.initialize(web3.currentProvider); 146 | 147 | const instance = await myProject.createProxy(BondingCurveFactory, { 148 | initArgs 149 | }); 150 | return instance; 151 | } 152 | 153 | async function createBondedToken(myProject) { 154 | ZWeb3.initialize(web3.currentProvider); 155 | 156 | const instance = await myProject.createProxy(BondedToken); 157 | return instance; 158 | } 159 | 160 | async function deployBondedToken(myProject, initArgs) { 161 | ZWeb3.initialize(web3.currentProvider); 162 | 163 | const instance = await myProject.createProxy(BondedToken, { 164 | initArgs 165 | }); 166 | return instance; 167 | } 168 | 169 | async function deployStandaloneERC20(myProject, initArgs) { 170 | ZWeb3.initialize(web3.currentProvider); 171 | 172 | const instance = await myProject.createProxy(BondedToken, { 173 | initArgs 174 | }); 175 | return instance; 176 | 177 | // const erc20Mintable = new web3.eth.Contract(erc20MintableAbi.abi); 178 | 179 | // const instance = await erc20Mintable 180 | // .deploy({ 181 | // data: erc20MintableAbi.deployedBytecode 182 | // }) 183 | // .send({ 184 | // from: creatorAddress 185 | // }); 186 | } 187 | 188 | async function deployRewardsDistributor(myProject, initArgs) { 189 | ZWeb3.initialize(web3.currentProvider); 190 | 191 | const instance = await myProject.createProxy(RewardsDistributor, { 192 | initArgs 193 | }); 194 | return instance; 195 | } 196 | 197 | async function getImplementation(project, contractName) { 198 | const directory = await project.getCurrentDirectory(); 199 | const implementation = await directory.getImplementation(contractName); 200 | return implementation; 201 | } 202 | 203 | async function getAbi(contractName) { 204 | return CONTRACT_ABIS[contractName].schema.abi; 205 | } 206 | 207 | // For truffle exec 208 | module.exports = function(callback) { 209 | main() 210 | .then(() => callback()) 211 | .catch(err => callback(err)); 212 | }; 213 | 214 | // Logging 215 | function log() { 216 | if (process.env.NODE_ENV !== "test") { 217 | console.log.apply(this, arguments); 218 | } 219 | } 220 | 221 | // Testing 222 | module.exports = { 223 | setupApp, 224 | deployProject, 225 | deployStaticCurveLogic, 226 | deployBancorFormula, 227 | deployBancorCurveService, 228 | deployBancorCurveLogic, 229 | createBondingCurve, 230 | deployBondingCurve, 231 | deployBondingCurveFactory, 232 | deployBondedToken, 233 | deployStandaloneERC20, 234 | deployRewardsDistributor, 235 | CONTRACT_NAMES, 236 | CONTRACT_ABIS, 237 | getImplementation, 238 | getAbi 239 | }; 240 | -------------------------------------------------------------------------------- /migrations/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dOrgTech/OpenRaise/3d1b8cdea4994ab1f98107dcc6ecd44a835b44b4/migrations/.gitkeep -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@dorgtech/open-raise", 3 | "version": "0.0.1-alpha", 4 | "description": "Open Raise is a Toolkit for accountable on-chain fundraising", 5 | "files": [ 6 | "contracts/", 7 | "build/contracts/", 8 | "migrations/", 9 | "test/", 10 | "truffle-config.js" 11 | ], 12 | "scripts": { 13 | "push": "npx oz publish --network development && npx oz push --network development", 14 | "test": "npx oz publish --network development && npx oz push --network development && truffle test", 15 | "compile": "truffle compile --all", 16 | "build": "rimraf build && npx oz compile", 17 | "deploy": "sh ecosystem.deploy.sh", 18 | "eslint": "./node_modules/.bin/eslint ./", 19 | "eslint:fix": "./node_modules/.bin/eslint ./ --fix", 20 | "solhint": "./node_modules/.bin/solhint contracts/**/*.sol", 21 | "prettier:solidity": "./node_modules/.bin/prettier --write contracts/**/*.sol", 22 | "solium": "./node_modules/.bin/solium -d contracts/", 23 | "solium:fix": "./node_modules/.bin/solium -d contracts/ --fix", 24 | "cli": "node ./utils/cli/index.js" 25 | }, 26 | "repository": { 27 | "type": "git", 28 | "url": "git+https://github.com/dOrgTech/OpenRaise" 29 | }, 30 | "license": "MIT", 31 | "bugs": { 32 | "url": "https://github.com/dOrgTech/OpenRaise/issues" 33 | }, 34 | "homepage": "https://github.com/dOrgTech/OpenRaise#readme", 35 | "dependencies": { 36 | "@babel/polyfill": "7.4.4", 37 | "@babel/register": "7.5.5", 38 | "@openzeppelin/contracts-ethereum-package": "2.2.3", 39 | "@openzeppelin/upgrades": "2.5.2", 40 | "chalk": "^3.0.0", 41 | "clear": "^0.1.0", 42 | "clui": "^0.3.6", 43 | "configstore": "^5.0.0", 44 | "dotenv": "6.2.0", 45 | "figlet": "^1.2.4", 46 | "inquirer": "^7.0.3", 47 | "minimist": "^1.2.0", 48 | "openzeppelin-test-helpers": "0.4.2", 49 | "truffle-hdwallet-provider": "1.0.3", 50 | "web3": "^1.2.4" 51 | }, 52 | "devDependencies": { 53 | "@babel/core": "7.5.5", 54 | "@babel/preset-env": "7.5.5", 55 | "@nomiclabs/buidler": "^1.0.1", 56 | "@nomiclabs/buidler-truffle5": "^1.0.1", 57 | "@nomiclabs/buidler-web3": "^1.0.1", 58 | "@openzeppelin/cli": "2.5.1", 59 | "chai": "4.2.0", 60 | "eslint": "5.16.0", 61 | "eslint-config-airbnb-base": "13.2.0", 62 | "eslint-config-prettier": "6.0.0", 63 | "eslint-plugin-import": "2.18.0", 64 | "eslint-plugin-mocha": "5.3.0", 65 | "eslint-plugin-prettier": "3.1.0", 66 | "eth-gas-reporter": "^0.2.12", 67 | "ethlint": "1.2.4", 68 | "husky": "3.0.0", 69 | "prettier": "1.18.2", 70 | "prettier-plugin-solidity": "1.0.0-alpha.27", 71 | "rimraf": "2.6.3", 72 | "solhint": "2.1.0", 73 | "solhint-plugin-prettier": "0.0.3", 74 | "solidity-coverage": "0.6.4", 75 | "truffle": "^5.1.9" 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /scripts/deployBondingCurve.js: -------------------------------------------------------------------------------- 1 | const {defaultTestConfig} = require('../test/helpers/ecosystemConfigs'); 2 | const {CurveEcosystem} = require("../test/helpers/CurveEcosystem"); 3 | const {CurveLogicType, TokenType} = require("../test/helpers/CurveEcosystemConfig"); 4 | const {bn} = require("../test/helpers/utils"); 5 | 6 | const config = { 7 | deployParams: { 8 | collateralType: TokenType.ERC20, 9 | curveLogicType: CurveLogicType.CONSTANT, 10 | curveParams: { 11 | reservePercentage: bn(10), 12 | dividendPercentage: bn(50), 13 | }, 14 | bondedTokenParams: { 15 | name: 'BondedToken', 16 | symbol: 'BND', 17 | decimals: bn(18) 18 | }, 19 | collateralTokenParams: { 20 | name: "PaymentToken", 21 | symbol: "Pay", 22 | decimals: 18, 23 | initialSupply: bn(1000000000) 24 | }, 25 | curveLogicParams: { 26 | tokenRatio: bn(100000000) 27 | } 28 | } 29 | } 30 | 31 | const printDeployed 32 | (contracts) 33 | { 34 | // console.log(`Bonding Curve: ${contracts.bondingCurve}`); 35 | // console.log(`Bonded Token: ${contracts.bondedToken}`); 36 | // console.log(`Buy Curve: ${contracts.bondedToken}`); 37 | } 38 | 39 | const deployBondingCurve = async (config) => { 40 | const accounts = await web3.eth.getAccounts(); 41 | // const eco = new CurveEcosystem(config); 42 | // const {bondingCurve, bondedToken, buyCurve} = await eco.init(web3);} 43 | 44 | } 45 | 46 | module.exports = async (callback) => { 47 | const deployed = await deployBondingCurve(config); 48 | printDeployed(deployed); 49 | process.exit(); 50 | } -------------------------------------------------------------------------------- /scripts/deployCurve.ts: -------------------------------------------------------------------------------- 1 | const deploy = require('./index.js'); 2 | 3 | main( 4 | 5 | ) { 6 | 7 | } -------------------------------------------------------------------------------- /scripts/helpers.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | require('dotenv').config(); 3 | 4 | const fs = require('fs'); 5 | 6 | const App = artifacts.require('App'); 7 | 8 | const contractNames = { 9 | StaticCurveLogic: 'StaticCurveLogic', 10 | BancorCurveLogic: 'BancorCurveLogic', 11 | BondedToken: 'BondedToken', 12 | RewardsDistributor: 'RewardsDistributor', 13 | BondingCurve: 'BondingCurve', 14 | BondingCurveFactory: 'BondingCurveFactory', 15 | BancorCurveService: 'BancorCurveService' 16 | }; 17 | 18 | const BancorCurveService = artifacts.require('BancorCurveService'); 19 | const BondingCurveFactory = artifacts.require('BondingCurveFactory'); 20 | 21 | const truffleConfig = require('./truffle-config.js'); 22 | 23 | const BC_DAO_PACKAGE = '@dorg/bc-dao'; 24 | 25 | function activeNetwork() { 26 | const networkIndex = process.argv.lastIndexOf('--network'); 27 | if (networkIndex < 2) { 28 | return 'development'; 29 | } 30 | return process.argv[networkIndex + 1]; 31 | } 32 | 33 | function activeNetworkName() { 34 | return activeNetwork() === 'development' ? `dev-${App.network_id}` : activeNetwork(); 35 | } 36 | 37 | /* 38 | * Get zos config info for specified networkId. 39 | */ 40 | function getOZNetworkConfig(networkName) { 41 | const zosNetworkFile = fs.readFileSync(`./.openzeppelin/${networkName}.json`); 42 | return JSON.parse(zosNetworkFile); 43 | } 44 | 45 | function getAppAddress() { 46 | const ozNetworkConfig = getOZNetworkConfig(activeNetworkName()); 47 | return ozNetworkConfig.app.address; 48 | } 49 | 50 | function getLatestProxy(contractName) { 51 | const ozNetworkConfig = getOZNetworkConfig(activeNetworkName()); 52 | const proxies = ozNetworkConfig.proxies[`${BC_DAO_PACKAGE}/${contractName}`]; 53 | if (!proxies || proxies.length <= 1) { 54 | throw Error(`No deployed proxies of contract ${contractName} found`); 55 | } 56 | return proxies[proxies.length - 1]; 57 | } 58 | 59 | function helpers() { 60 | return { 61 | constants: { 62 | ZERO_ADDRESS: '0x0000000000000000000000000000000000000000' 63 | } 64 | }; 65 | } 66 | 67 | async function initializeBancorCurveService(contractAddress) { 68 | const contract = await BancorCurveService.at(contractAddress); 69 | return contract.initialize(); 70 | } 71 | 72 | async function initializeBondingCurveFactory(contractAddress) { 73 | const ozNetworkConfig = getOZNetworkConfig(activeNetworkName()); 74 | const contract = await BondingCurveFactory.at(contractAddress); 75 | 76 | return contract.initialize( 77 | ozNetworkConfig.contracts.StaticCurveLogic.address, 78 | ozNetworkConfig.contracts.BancorCurveLogic.address, 79 | ozNetworkConfig.contracts.BondedToken.address, 80 | ozNetworkConfig.contracts.BondingCurve.address, 81 | ozNetworkConfig.contracts.RewardsDistributor.address, 82 | getLatestProxy(contractNames.BancorCurveService).address 83 | ); 84 | } 85 | 86 | module.exports = async () => { 87 | const {constants} = helpers(); 88 | try { 89 | const bancorCurveServiceAddress = getLatestProxy(contractNames.BancorCurveService).address; 90 | const bondingCurveFactoryAddress = getLatestProxy(contractNames.BondingCurveFactory).address; 91 | console.log(`${contractNames.BancorCurveService} to initialize:`, bancorCurveServiceAddress); 92 | console.log(`${contractNames.BondingCurveFactory} to initialize:`, bondingCurveFactoryAddress); 93 | 94 | // let initializeTx = await initializeBancorCurveService(bancorCurveServiceAddress); 95 | // console.log(`${contractNames.BancorCurveService} initialization tx:`, initializeTx.tx); 96 | 97 | let initializeTx = await initializeBondingCurveFactory(bondingCurveFactoryAddress); 98 | console.log(`${contractNames.BondingCurveFactory} initialization tx:`, initializeTx.tx); 99 | } catch (e) { 100 | console.error(e); 101 | } 102 | 103 | process.exit(); 104 | }; 105 | -------------------------------------------------------------------------------- /solhint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "default", 3 | "rules": { 4 | "indent": ["error", 4], 5 | 6 | "bracket-align": false, 7 | "compiler-fixed": false, 8 | "no-simple-event-func-name": false, 9 | "two-lines-top-level-separator": false 10 | } 11 | } -------------------------------------------------------------------------------- /test/appDeploy.js: -------------------------------------------------------------------------------- 1 | require("./setup"); 2 | const deploy = require("../index.js"); 3 | 4 | contract("App", ([_, owner, donor, wallet]) => { 5 | const initialVersion = "2.5.2"; 6 | const contractName = deploy.CONTRACT_NAMES.BancorCurveService; 7 | 8 | let project; 9 | let projectPackage; 10 | let directory; 11 | 12 | describe("setup", function() { 13 | beforeEach("deploying project", async function() { 14 | project = await deploy.setupApp({ owner }); 15 | }); 16 | 17 | describe("package", function() { 18 | beforeEach("loading package", async function() { 19 | projectPackage = await project.getProjectPackage(); 20 | }); 21 | 22 | describe("when queried for the initial version", function() { 23 | it("claims to have it", async function() { 24 | (await projectPackage.hasVersion(initialVersion)).should.be.true; 25 | }); 26 | }); 27 | }); 28 | }); 29 | 30 | describe("initial version", function() { 31 | beforeEach(async function() { 32 | project = await deploy.setupApp({ owner }); 33 | directory = await project.getCurrentDirectory(); 34 | this.bondingCurveService = await deploy.deployBancorCurveService(project); 35 | }); 36 | 37 | describe("directory", function() { 38 | describe("when queried for the implementation", function() { 39 | it("returns a valid address", async function() { 40 | const implementation = await directory.getImplementation( 41 | contractName 42 | ); 43 | implementation.should.be.nonzeroAddress; 44 | }); 45 | }); 46 | }); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /test/behaviors/ERC20Burnable.behavior.js: -------------------------------------------------------------------------------- 1 | const { 2 | BN, 3 | constants, 4 | expectEvent, 5 | shouldFail 6 | } = require("openzeppelin-test-helpers"); 7 | const { ZERO_ADDRESS } = constants; 8 | 9 | function shouldBehaveLikeERC20Burnable( 10 | tokenContract, 11 | owner, 12 | initialBalance, 13 | [burner] 14 | ) { 15 | describe("burn", function() { 16 | describe("when the given amount is not greater than balance of the sender", function() { 17 | context("for a zero amount", function() { 18 | shouldBurn(new BN(0)); 19 | }); 20 | 21 | context("for a non-zero amount", function() { 22 | shouldBurn(new BN(100)); 23 | }); 24 | 25 | function shouldBurn(amount) { 26 | beforeEach(async function() { 27 | ({ logs: this.logs } = await tokenContract.burn(amount, { 28 | from: owner 29 | })); 30 | }); 31 | 32 | it("burns the requested amount", async function() { 33 | (await tokenContract.balanceOf(owner)).should.be.bignumber.equal( 34 | initialBalance.sub(amount) 35 | ); 36 | }); 37 | 38 | it("emits a transfer event", async function() { 39 | expectEvent.inLogs(this.logs, "Transfer", { 40 | from: owner, 41 | to: ZERO_ADDRESS, 42 | value: amount 43 | }); 44 | }); 45 | } 46 | }); 47 | 48 | describe("when the given amount is greater than the balance of the sender", function() { 49 | const amount = initialBalance.addn(1); 50 | 51 | it("reverts", async function() { 52 | await shouldFail.reverting(tokenContract.burn(amount, { from: owner })); 53 | }); 54 | }); 55 | }); 56 | 57 | describe("burnFrom", function() { 58 | describe("on success", function() { 59 | context("for a zero amount", function() { 60 | shouldBurnFrom(new BN(0)); 61 | }); 62 | 63 | context("for a non-zero amount", function() { 64 | shouldBurnFrom(new BN(100)); 65 | }); 66 | 67 | function shouldBurnFrom(amount) { 68 | const originalAllowance = amount.muln(3); 69 | 70 | beforeEach(async function() { 71 | await tokenContract.approve(burner, originalAllowance, { 72 | from: owner 73 | }); 74 | const { logs } = await tokenContract.burnFrom(owner, amount, { 75 | from: burner 76 | }); 77 | this.logs = logs; 78 | }); 79 | 80 | it("burns the requested amount", async function() { 81 | (await tokenContract.balanceOf(owner)).should.be.bignumber.equal( 82 | initialBalance.sub(amount) 83 | ); 84 | }); 85 | 86 | it("decrements allowance", async function() { 87 | (await tokenContract.allowance( 88 | owner, 89 | burner 90 | )).should.be.bignumber.equal(originalAllowance.sub(amount)); 91 | }); 92 | 93 | it("emits a transfer event", async function() { 94 | expectEvent.inLogs(this.logs, "Transfer", { 95 | from: owner, 96 | to: ZERO_ADDRESS, 97 | value: amount 98 | }); 99 | }); 100 | } 101 | }); 102 | 103 | describe("when the given amount is greater than the balance of the sender", function() { 104 | const amount = initialBalance.addn(1); 105 | 106 | it("reverts", async function() { 107 | await tokenContract.approve(burner, amount, { from: owner }); 108 | await shouldFail.reverting( 109 | tokenContract.burnFrom(owner, amount, { from: burner }) 110 | ); 111 | }); 112 | }); 113 | 114 | describe("when the given amount is greater than the allowance", function() { 115 | const allowance = new BN(100); 116 | 117 | it("reverts", async function() { 118 | await tokenContract.approve(burner, allowance, { from: owner }); 119 | await shouldFail.reverting( 120 | tokenContract.burnFrom(owner, allowance.addn(1), { from: burner }) 121 | ); 122 | }); 123 | }); 124 | }); 125 | } 126 | 127 | module.exports = { 128 | shouldBehaveLikeERC20Burnable 129 | }; 130 | -------------------------------------------------------------------------------- /test/behaviors/bondingCurveAdmin.js: -------------------------------------------------------------------------------- 1 | const {BN, constants, shouldFail, expectRevert} = require('openzeppelin-test-helpers'); 2 | const {ZERO_ADDRESS} = constants; 3 | 4 | const BondedToken = artifacts.require("BondedToken.sol"); 5 | 6 | const expectEvent = require('../expectEvent'); 7 | 8 | const {CurveEcosystem} = require("../helpers/CurveEcosystem"); 9 | const {str, bn} = require("../helpers/utils"); 10 | 11 | // Import preferred chai flavor: both expect and should are supported 12 | const {expect} = require('chai'); 13 | const {defaultTestConfig} = require('../helpers/ecosystemConfigs'); 14 | 15 | const bondingCurveAdminTests = async (suiteName, config) => { 16 | contract('Bonding Curve Admin', async accounts => { 17 | const adminAccount = accounts[0]; 18 | const curveOwner = accounts[1]; 19 | const tokenMinter = accounts[2]; 20 | const userAccounts = accounts.slice(3, accounts.length); 21 | const miscUser = userAccounts[0]; 22 | 23 | const accountsConfig = { 24 | adminAccount, 25 | curveOwner, 26 | minter: tokenMinter, 27 | userAccounts, 28 | miscUser 29 | } 30 | 31 | describe('Curve Admin', async () => { 32 | it('should allow owner to set new beneficiary', async function () { 33 | const eco = new CurveEcosystem(accountsConfig, config); 34 | const {bondingCurve} = await eco.init(web3); 35 | 36 | let tx = await bondingCurve.setBeneficiary(userAccounts[0], { 37 | from: curveOwner 38 | }); 39 | expect(await bondingCurve.beneficiary({from: miscUser})).to.be.equal( 40 | userAccounts[0] 41 | ); 42 | }); 43 | 44 | it('should not allow non-owner to set new beneficiary', async function () { 45 | const eco = new CurveEcosystem(accountsConfig, config); 46 | const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); 47 | 48 | await expectRevert.unspecified( 49 | bondingCurve.setBeneficiary(constants.ZERO_ADDRESS, { 50 | from: miscUser 51 | }) 52 | ); 53 | }); 54 | 55 | it('should allow owner to set new owner', async function () { 56 | const eco = new CurveEcosystem(accountsConfig, config); 57 | const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); 58 | 59 | const oldOwner = curveOwner; 60 | const newOwner = userAccounts[0]; 61 | 62 | let tx = await bondingCurve.transferOwnership(newOwner, {from: oldOwner}); 63 | 64 | expect(await bondingCurve.owner({from: newOwner})).to.be.equal(newOwner); 65 | }); 66 | 67 | it('should not allow non-owner to set new owner', async function () { 68 | const eco = new CurveEcosystem(accountsConfig, config); 69 | const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); 70 | 71 | const nonOwner = userAccounts[0]; 72 | const newOwner = userAccounts[1]; 73 | 74 | await expectRevert.unspecified( 75 | bondingCurve.transferOwnership(newOwner, { 76 | from: nonOwner 77 | }) 78 | ); 79 | }); 80 | 81 | it('should not allow old owner to set new beneficiary after ownership transfer', async function () { 82 | const eco = new CurveEcosystem(accountsConfig, config); 83 | const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); 84 | 85 | const oldOwner = curveOwner; 86 | const oldBeneficiary = curveOwner; 87 | const newOwner = userAccounts[0]; 88 | const newBeneficiary = userAccounts[1]; 89 | 90 | let tx = await bondingCurve.transferOwnership(newOwner, { 91 | from: oldOwner 92 | }); 93 | 94 | let result = await bondingCurve.beneficiary({from: miscUser}); 95 | expect(result).to.be.equal(oldBeneficiary); 96 | 97 | await bondingCurve.setBeneficiary(newBeneficiary, { 98 | from: newOwner 99 | }); 100 | 101 | result = await bondingCurve.beneficiary({from: miscUser}); 102 | expect(result).to.be.equal(newBeneficiary); 103 | }); 104 | 105 | it('should allow owner to set new buy curve', async function () { 106 | const eco = new CurveEcosystem(accountsConfig, config); 107 | const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); 108 | 109 | let tx = await bondingCurve.setBuyCurve(constants.ZERO_ADDRESS, { 110 | from: curveOwner 111 | }); 112 | expect(await bondingCurve.buyCurve({from: miscUser})).to.be.equal( 113 | constants.ZERO_ADDRESS 114 | ); 115 | }); 116 | 117 | it('should not allow non-owner to set new buy curve', async function () { 118 | const eco = new CurveEcosystem(accountsConfig, config); 119 | const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); 120 | 121 | await expectRevert.unspecified( 122 | bondingCurve.setBuyCurve(constants.ZERO_ADDRESS, { 123 | from: miscUser 124 | }) 125 | ); 126 | }); 127 | 128 | it('should allow owner to set new reserve percentage', async function () { 129 | const eco = new CurveEcosystem(accountsConfig, config); 130 | const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); 131 | 132 | const newReservePercentage = '20'; 133 | 134 | let tx = await bondingCurve.setReservePercentage(newReservePercentage, { 135 | from: curveOwner 136 | }); 137 | expect(await bondingCurve.reservePercentage({from: miscUser})).to.be.bignumber.equal( 138 | newReservePercentage 139 | ); 140 | }); 141 | 142 | it('should not allow non-owner to set new reserve percentage', async function () { 143 | const eco = new CurveEcosystem(accountsConfig, config); 144 | const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); 145 | 146 | const newReservePercentage = '20'; 147 | 148 | await expectRevert.unspecified( 149 | bondingCurve.setReservePercentage(newReservePercentage, { 150 | from: miscUser 151 | }) 152 | ); 153 | }); 154 | 155 | it('should allow owner to set new dividend percentage', async function () { 156 | const eco = new CurveEcosystem(accountsConfig, config); 157 | const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); 158 | 159 | const newDividendPercentage = '20'; 160 | 161 | let tx = await bondingCurve.setDividendPercentage(newDividendPercentage, { 162 | from: curveOwner 163 | }); 164 | expect(await bondingCurve.dividendPercentage({from: miscUser})).to.be.bignumber.equal( 165 | newDividendPercentage 166 | ); 167 | }); 168 | 169 | it('should not allow non-owner to set new dividend percentage', async function () { 170 | const eco = new CurveEcosystem(accountsConfig, config); 171 | const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); 172 | 173 | const newDividendPercentage = '20'; 174 | 175 | await expectRevert.unspecified( 176 | bondingCurve.setDividendPercentage(newDividendPercentage, { 177 | from: miscUser 178 | }) 179 | ); 180 | }); 181 | }); 182 | }) 183 | } 184 | 185 | module.exports = { 186 | bondingCurveAdminTests 187 | } 188 | 189 | -------------------------------------------------------------------------------- /test/behaviors/bondingCurveDeploy.js: -------------------------------------------------------------------------------- 1 | const {BN, constants, shouldFail, expectRevert} = require('openzeppelin-test-helpers'); 2 | const {ZERO_ADDRESS} = constants; 3 | 4 | const BondedToken = artifacts.require("BondedToken.sol"); 5 | 6 | const expectEvent = require('../expectEvent'); 7 | 8 | const {CurveEcosystem} = require("../helpers/CurveEcosystem"); 9 | const {str, bn} = require("../helpers/utils"); 10 | 11 | // Import preferred chai flavor: both expect and should are supported 12 | const {expect} = require('chai'); 13 | const {defaultTestConfig} = require('../helpers/ecosystemConfigs'); 14 | 15 | const bondingCurveDeployTests = async (suiteName, config) => { 16 | contract('Bonding Curve Admin', async accounts => { 17 | const adminAccount = accounts[0]; 18 | const curveOwner = accounts[1]; 19 | const tokenMinter = accounts[2]; 20 | const userAccounts = accounts.slice(3, accounts.length); 21 | const miscUser = userAccounts[0]; 22 | 23 | const accountsConfig = { 24 | adminAccount, 25 | curveOwner, 26 | minter: tokenMinter, 27 | userAccounts, 28 | miscUser 29 | } 30 | 31 | describe("", async () => { 32 | it('should have properly initialized parameters', async function () { 33 | const eco = new CurveEcosystem(accountsConfig, config); 34 | const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); 35 | 36 | expect(await bondingCurve.owner({from: miscUser})).to.be.equal(curveOwner); 37 | expect(await bondingCurve.beneficiary({from: miscUser})).to.be.equal( 38 | curveOwner 39 | ); 40 | expect(await bondingCurve.collateralToken({from: miscUser})).to.be.equal( 41 | paymentToken.address 42 | ); 43 | expect(await bondingCurve.bondedToken({from: miscUser})).to.be.equal( 44 | bondedToken.address 45 | ); 46 | expect(await bondingCurve.buyCurve({from: miscUser})).to.be.equal( 47 | buyCurve.address 48 | ); 49 | expect( 50 | new BN(await bondingCurve.reservePercentage({from: miscUser})) 51 | ).to.be.bignumber.equal(config.deployParams.curveParams.reservePercentage); 52 | expect( 53 | new BN(await bondingCurve.dividendPercentage({from: miscUser})) 54 | ).to.be.bignumber.equal(config.deployParams.curveParams.dividendPercentage); 55 | expect(await bondingCurve.reserveBalance({from: miscUser})).to.be.bignumber.equal('0'); 56 | expect(await bondingCurve.getPaymentThreshold({from: miscUser})).to.be.bignumber.equal( 57 | '100' 58 | ); 59 | }); 60 | }) 61 | }) 62 | } 63 | 64 | module.exports = { 65 | bondingCurveDeployTests 66 | } 67 | 68 | -------------------------------------------------------------------------------- /test/behaviors/bondingCurvePayment.js: -------------------------------------------------------------------------------- 1 | const {BN, constants, shouldFail, expectRevert} = require('openzeppelin-test-helpers'); 2 | const {ZERO_ADDRESS} = constants; 3 | 4 | const BondedToken = artifacts.require("BondedToken.sol"); 5 | 6 | const expectEvent = require('../expectEvent'); 7 | 8 | const {CurveEcosystem} = require("../helpers/CurveEcosystem"); 9 | const {str, bn} = require("../helpers/utils"); 10 | 11 | // Import preferred chai flavor: both expect and should are supported 12 | const {expect} = require('chai'); 13 | const {defaultTestConfig} = require('../helpers/ecosystemConfigs'); 14 | 15 | const bondingCurvePaymentTests = async (suiteName, config) => { 16 | contract('Bonding Curve Admin', async accounts => { 17 | const adminAccount = accounts[0]; 18 | const curveOwner = accounts[1]; 19 | const tokenMinter = accounts[2]; 20 | const userAccounts = accounts.slice(3, accounts.length); 21 | const miscUser = userAccounts[0]; 22 | 23 | const accountsConfig = { 24 | adminAccount, 25 | curveOwner, 26 | minter: tokenMinter, 27 | userAccounts, 28 | miscUser 29 | } 30 | 31 | describe('Payments', async () => { 32 | const miscUser = userAccounts[0]; 33 | 34 | const userBalances = bn(100000); 35 | const paymentAmount = bn(10000); 36 | 37 | it('should not allow payments of amount 0', async function () { 38 | const eco = new CurveEcosystem(accountsConfig, config); 39 | const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); 40 | 41 | await expectRevert.unspecified(bondingCurve.pay(0, {from: curveOwner})); 42 | }); 43 | 44 | it('should register payments', async function () { 45 | const eco = new CurveEcosystem(accountsConfig, config); 46 | const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); 47 | await eco.bulkMint(paymentToken, accountsConfig.minter, [curveOwner, miscUser], userBalances); 48 | await eco.bulkApprove(paymentToken, bondingCurve.address, [curveOwner, miscUser], paymentAmount); 49 | 50 | let tx = await bondingCurve.pay(paymentAmount, {from: miscUser}); 51 | 52 | expectEvent.inLogs(tx.events, 'Pay', { 53 | from: miscUser, 54 | token: paymentToken.address, 55 | amount: paymentAmount 56 | }); 57 | 58 | tx = await bondingCurve.pay(paymentAmount, {from: curveOwner}); 59 | 60 | expectEvent.inLogs(tx.events, 'Pay', { 61 | from: curveOwner, 62 | token: paymentToken.address, 63 | amount: paymentAmount 64 | }); 65 | }); 66 | 67 | it('should not allow pay with greater amount than senders balance', async function () { 68 | const eco = new CurveEcosystem(accountsConfig, config); 69 | const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); 70 | await eco.bulkMint(paymentToken, accountsConfig.minter, [curveOwner, miscUser], userBalances); 71 | await eco.bulkApprove(paymentToken, bondingCurve.address, [curveOwner, miscUser], paymentAmount); 72 | 73 | const exceededBalance = userBalances.add(userBalances); 74 | 75 | await expectRevert.unspecified( 76 | bondingCurve.pay(exceededBalance, { 77 | from: miscUser 78 | }) 79 | ); 80 | await expectRevert.unspecified( 81 | bondingCurve.pay(exceededBalance, { 82 | from: curveOwner 83 | }) 84 | ); 85 | }); 86 | 87 | describe('Beneficiary / Dividend Split', async () => { 88 | const maxPercentage = new BN(100); 89 | const dividendSplit = maxPercentage.sub(config.deployParams.curveParams.dividendPercentage); 90 | const expectedBeneficiaryAmount = paymentAmount 91 | .mul(config.deployParams.curveParams.dividendPercentage) 92 | .div(maxPercentage); 93 | const expectedDividendAmount = paymentAmount.mul(dividendSplit).div(maxPercentage); 94 | 95 | it('should register correct split between beneficiary and dividend pool from non-curve owner', async function () { 96 | const eco = new CurveEcosystem(accountsConfig, config); 97 | const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); 98 | await eco.bulkMint(paymentToken, accountsConfig.minter, [curveOwner, miscUser], userBalances); 99 | await eco.bulkApprove(paymentToken, bondingCurve.address, [curveOwner, miscUser], paymentAmount); 100 | 101 | let tx = await bondingCurve.pay(paymentAmount, {from: miscUser}); 102 | 103 | expectEvent.inLogs(tx.events, 'Pay', { 104 | from: miscUser, 105 | token: paymentToken.address, 106 | amount: paymentAmount, 107 | beneficiaryAmount: expectedBeneficiaryAmount, 108 | dividendAmount: expectedDividendAmount 109 | }); 110 | }); 111 | 112 | it('should register correct split between beneficiary and dividend pool from curve owner', async function () { 113 | const eco = new CurveEcosystem(accountsConfig, config); 114 | const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); 115 | await eco.bulkMint(paymentToken, accountsConfig.minter, [curveOwner, miscUser], userBalances); 116 | await eco.bulkApprove(paymentToken, bondingCurve.address, [curveOwner, miscUser], paymentAmount); 117 | 118 | let tx = await bondingCurve.pay(paymentAmount, { 119 | from: curveOwner 120 | }); 121 | 122 | expectEvent.inLogs(tx.events, 'Pay', { 123 | from: curveOwner, 124 | token: paymentToken.address, 125 | amount: paymentAmount, 126 | beneficiaryAmount: expectedBeneficiaryAmount, 127 | dividendAmount: expectedDividendAmount 128 | }); 129 | }); 130 | 131 | // it('should transfer correct token amounts between beneficiary and dividend pool', async function() { 132 | // const beneficiaryBeforeBalance = new BN( 133 | // await paymentToken.balanceOf(curveOwner).call({from: miscUser}) 134 | // ); 135 | 136 | // const dividendBeforeBalance = new BN( 137 | // await paymentToken.balanceOf(dividendPool.address).call({from: miscUser}) 138 | // ); 139 | 140 | // let tx = await bondingCurve.pay(paymentAmount, { 141 | // from: miscUser 142 | // }); 143 | // const event = expectEvent.inLogs(tx.events, 'Pay'); 144 | 145 | // const beneficiaryAfterBalance = new BN( 146 | // await paymentToken.balanceOf(curveOwner).call({from: miscUser}) 147 | // ); 148 | 149 | // const dividendAfterBalance = new BN( 150 | // await paymentToken.balanceOf(dividendPool.address).call({from: miscUser}) 151 | // ); 152 | 153 | // const beneficiaryAmount = new BN(expectEvent.getParameter(event, 'beneficiaryAmount')); 154 | // const dividendAmount = new BN(expectEvent.getParameter(event, 'dividendAmount')); 155 | 156 | // expect(beneficiaryAmount).to.be.bignumber.equal( 157 | // beneficiaryAfterBalance.sub(beneficiaryBeforeBalance) 158 | // ); 159 | 160 | // expect(dividendAmount).to.be.bignumber.equal( 161 | // dividendAfterBalance.sub(dividendBeforeBalance) 162 | // ); 163 | // }); 164 | }); 165 | }); 166 | }) 167 | } 168 | 169 | module.exports = { 170 | bondingCurvePaymentTests 171 | } 172 | 173 | -------------------------------------------------------------------------------- /test/constants/bancorValues.js: -------------------------------------------------------------------------------- 1 | const { BN } = require("openzeppelin-test-helpers"); 2 | 3 | module.exports.values = [ 4 | { 5 | supply: 1, 6 | connectorBalance: 1, 7 | connectorWeight: 1000, 8 | depositAmount: 1, 9 | expectedBuyResult: new BN(0), 10 | expectedSaleResult: new BN(1) 11 | }, 12 | { 13 | supply: 1000000, 14 | connectorBalance: 10000, 15 | connectorWeight: 1000, 16 | depositAmount: 10000, 17 | expectedBuyResult: new BN(693), 18 | expectedSaleResult: new BN(9999) 19 | }, 20 | { 21 | supply: 100000000, 22 | connectorBalance: 1000000, 23 | connectorWeight: 1000, 24 | depositAmount: 10000, 25 | expectedBuyResult: new BN(995), 26 | expectedSaleResult: new BN(95167) 27 | } 28 | ]; 29 | -------------------------------------------------------------------------------- /test/constants/contractConstants.js: -------------------------------------------------------------------------------- 1 | const {BN} = require('openzeppelin-test-helpers'); 2 | 3 | module.exports = { 4 | bondingCurve: { 5 | tokenRatioPrecision: new BN(1000000), 6 | microPaymentsThreshold: new BN(100) 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /test/expectEvent.js: -------------------------------------------------------------------------------- 1 | const {expect} = require('chai'); 2 | const {web3, BN} = require('../node_modules/openzeppelin-test-helpers/src/setup'); 3 | 4 | function getParameter(event, parameterName) { 5 | const parameterList = event.returnValues; 6 | 7 | expect(parameterList[parameterName]).to.not.be.equal( 8 | undefined, 9 | `No '${parameterName}' parameter found` 10 | ); 11 | 12 | return parameterList[parameterName]; 13 | } 14 | 15 | function inLogs(logs, eventName, eventArgs = {}) { 16 | let matchingEvents = []; 17 | 18 | if (!logs) { 19 | return false; 20 | } 21 | 22 | Object.keys(logs).forEach(function eachKey(key) { 23 | if (key == eventName) { 24 | matchingEvents.push(logs[key]); 25 | } 26 | }); 27 | 28 | expect(matchingEvents.length > 0).to.equal(true, `No '${eventName}' events found`); 29 | 30 | const exception = []; 31 | 32 | const matchingEvent = matchingEvents.find(event => { 33 | Object.keys(eventArgs).forEach(key => { 34 | try { 35 | contains(event.raw, key, eventArgs[key]); 36 | } catch (error) { 37 | exception.push(error); 38 | return false; 39 | } 40 | }); 41 | return true; 42 | }); 43 | 44 | if (matchingEvent === undefined) { 45 | throw exception[0]; 46 | } 47 | 48 | return matchingEvent; 49 | // return true; 50 | } 51 | 52 | async function inConstruction(contract, eventName, eventArgs = {}) { 53 | return inTransaction(contract.transactionHash, contract.constructor, eventName, eventArgs); 54 | } 55 | 56 | async function inTransaction(txHash, emitter, eventName, eventArgs = {}) { 57 | const receipt = await web3.eth.getTransactionReceipt(txHash); 58 | const logs = emitter.decodeLogs(receipt.logs); 59 | return inLogs(logs, eventName, eventArgs); 60 | } 61 | 62 | function contains(args, key, value) { 63 | expect(key in args).to.equal(true, `Event argument '${key}' not found`); 64 | 65 | if (value === null) { 66 | expect(args[key]).to.equal(null); 67 | } else if (isBN(args[key])) { 68 | expect(args[key]).to.be.bignumber.equal(value); 69 | } else { 70 | expect(args[key]).to.be.equal(value); 71 | } 72 | } 73 | 74 | function isBN(object) { 75 | return BN.isBN(object) || object instanceof BN; 76 | } 77 | 78 | module.exports = { 79 | inLogs, 80 | inConstruction, 81 | inTransaction, 82 | getParameter 83 | }; 84 | -------------------------------------------------------------------------------- /test/helpers/CurveEcosystem.js: -------------------------------------------------------------------------------- 1 | const {CurveLogicType, TokenType} = require("../helpers/CurveEcosystemConfig"); 2 | const {bn} = require("./utils"); 3 | const {constants} = require('openzeppelin-test-helpers'); 4 | const {ZERO_ADDRESS} = constants; 5 | const deploy = require('../../index.js'); 6 | 7 | const BondedToken = artifacts.require('BondedToken.sol'); 8 | const BondingCurve = artifacts.require('BondingCurve.sol'); 9 | const BondingCurveEther = artifacts.require('BondingCurveEther.sol'); 10 | const RewardsDistributor = artifacts.require('RewardsDistributor.sol'); 11 | const BancorCurveService = artifacts.require('BancorCurveService.sol'); 12 | const BancorCurveLogic = artifacts.require('BancorCurveLogic.sol'); 13 | const StaticCurveLogic = artifacts.require('StaticCurveLogic.sol'); 14 | 15 | class CurveEcosystem { 16 | constructor(accounts, config) { 17 | this.config = config; 18 | this.accounts = accounts; 19 | this.contracts = {}; 20 | } 21 | 22 | async initStaticEther() { 23 | 24 | } 25 | 26 | async initStaticERC20() { 27 | 28 | } 29 | 30 | async initBancorEther() { 31 | 32 | } 33 | 34 | async initBancorERC20() { 35 | 36 | } 37 | 38 | async deployStaticCurveLogic() { 39 | const {curveLogicParams} = this.config.deployParams; 40 | const buyCurve = await StaticCurveLogic.new(); 41 | await buyCurve.initialize(curveLogicParams.tokenRatio); 42 | return buyCurve; 43 | } 44 | 45 | async deployBancorCurveLogic() { 46 | const {curveLogicParams} = this.config.deployParams; 47 | 48 | const bancorService = await BancorCurveService.new(); 49 | await bancorService.initialize(); 50 | const buyCurve = await BancorCurveLogic.new() 51 | await BancorCurveLogic.initialize(bancorService.address, 52 | curveLogicParams.reserveRatio); 53 | 54 | return { 55 | bancorService, 56 | buyCurve 57 | } 58 | } 59 | 60 | async deployPaymentToken() { 61 | const accounts = this.accounts; 62 | const {collateralTokenParams} = this.config.deployParams; 63 | 64 | const paymentToken = await BondedToken.new(); 65 | await paymentToken.initialize( 66 | collateralTokenParams.name, 67 | collateralTokenParams.symbol, 68 | collateralTokenParams.decimals, 69 | accounts.minter, 70 | ZERO_ADDRESS, 71 | ZERO_ADDRESS); 72 | 73 | const paymentTokenInitialBalance = bn(web3.utils.toWei('60000', 'ether')); 74 | 75 | await paymentToken.contract.methods 76 | .mint(accounts.minter, paymentTokenInitialBalance.toString()) 77 | .send({from: accounts.minter}); 78 | 79 | return paymentToken; 80 | } 81 | 82 | async init(web3) { 83 | const {collateralType, curveLogicType} = this.config.deployParams; 84 | if (collateralType === TokenType.ETHER) { 85 | return await this.initEther(); 86 | } 87 | 88 | if (collateralType === TokenType.ERC20) { 89 | return await this.initERC20(web3); 90 | } 91 | } 92 | 93 | async initEther() { 94 | const accounts = this.accounts; 95 | const {curveParams, curveLogicType, bondedTokenParams} = this.config.deployParams; 96 | 97 | const rewardsDistributor = await RewardsDistributor.new(); 98 | await rewardsDistributor.initialize(accounts.curveOwner); 99 | 100 | const bondedToken = await BondedToken.new(); 101 | await bondedToken.initialize( 102 | bondedTokenParams.name, 103 | bondedTokenParams.symbol, 104 | bondedTokenParams.decimals, 105 | accounts.minter, 106 | rewardsDistributor.address, 107 | ZERO_ADDRESS); 108 | 109 | await rewardsDistributor.contract.methods 110 | .transferOwnership(bondedToken.address) 111 | .send({from: accounts.curveOwner}); 112 | 113 | let buyCurve; 114 | 115 | if (curveLogicType === CurveLogicType.CONSTANT) { 116 | buyCurve = await this.deployStaticCurveLogic(); 117 | } else if (curveLogicType === CurveLogicType.BANCOR) { 118 | buyCurve = (await this.deployBancorCurveLogic()).buyCurve; 119 | } 120 | 121 | const bondingCurve = await BondingCurveEther.new(); 122 | await bondingCurve.initialize( 123 | accounts.curveOwner, 124 | accounts.curveOwner, 125 | bondedToken.address, 126 | buyCurve.address, 127 | curveParams.reservePercentage, 128 | curveParams.dividendPercentage 129 | ); 130 | 131 | await bondedToken.contract.methods.addMinter(bondingCurve.address).send({from: accounts.minter}); 132 | await bondedToken.contract.methods.renounceMinter().send({from: accounts.minter}); 133 | 134 | this.contracts = { 135 | bondingCurve, 136 | bondedToken, 137 | rewardsDistributor, 138 | buyCurve 139 | } 140 | 141 | return this.contracts; 142 | } 143 | 144 | async initERC20(web3) { 145 | const accounts = this.accounts; 146 | const {curveParams, curveLogicType, collateralType, bondedTokenParams, collateralTokenParams, curveLogicParams} = this.config.deployParams; 147 | // TODO: Use an ERC20Mintable instead of a BondedToken here! 148 | const paymentToken = await this.deployPaymentToken(); 149 | 150 | const rewardsDistributor = await RewardsDistributor.new(); 151 | await rewardsDistributor.initialize(accounts.curveOwner); 152 | 153 | const bondedToken = await BondedToken.new(); 154 | await bondedToken.initialize( 155 | bondedTokenParams.name, 156 | bondedTokenParams.symbol, 157 | bondedTokenParams.decimals, 158 | accounts.minter, 159 | rewardsDistributor.address, 160 | paymentToken.address); 161 | 162 | await rewardsDistributor.contract.methods 163 | .transferOwnership(bondedToken.address) 164 | .send({from: accounts.curveOwner}); 165 | 166 | let buyCurve; 167 | 168 | if (curveLogicType === CurveLogicType.CONSTANT) { 169 | buyCurve = await this.deployStaticCurveLogic(); 170 | } else if (curveLogicType === CurveLogicType.BANCOR) { 171 | buyCurve = (await this.deployBancorCurveLogic()).buyCurve; 172 | } 173 | 174 | const bondingCurve = await BondingCurve.new(); 175 | await bondingCurve.initialize(accounts.curveOwner, 176 | accounts.curveOwner, 177 | paymentToken.address, 178 | bondedToken.address, 179 | buyCurve.address, 180 | curveParams.reservePercentage, 181 | curveParams.dividendPercentage); 182 | 183 | await bondedToken.contract.methods.addMinter(bondingCurve.address).send({from: accounts.minter}); 184 | await bondedToken.contract.methods.renounceMinter().send({from: accounts.minter}); 185 | 186 | this.contracts = { 187 | bondingCurve, 188 | bondedToken, 189 | paymentToken, 190 | rewardsDistributor, 191 | buyCurve 192 | } 193 | 194 | return this.contracts; 195 | } 196 | 197 | async getBals(address) { 198 | const {bondedToken} = this.contracts; 199 | const bals = {} 200 | 201 | bals.ether = bn(await web3.eth.getBalance(address)); 202 | bals.bondedToken = bn(await bondedToken.balanceOf(address)); 203 | 204 | if (this.config.collateralType === TokenType.ERC20) { 205 | const {paymentToken} = this.contracts; 206 | bals.paymentToken = bn(await paymentToken.balanceOf(address)); 207 | } 208 | 209 | return bals; 210 | } 211 | 212 | async getBalances(accounts) { 213 | const {bondingCurve} = this.contracts 214 | const bals = {} 215 | for (const account of accounts) { 216 | bals[account] = await this.getBals(account); 217 | } 218 | 219 | bals.bondingCurve = await this.getBals(bondingCurve); 220 | 221 | return bals; 222 | } 223 | 224 | async bulkMint(token, minter, accounts, amount) { 225 | for (const account of accounts) { 226 | await token.mint(account, amount, {from: minter}); 227 | } 228 | } 229 | 230 | async bulkApprove(token, recipient, accounts, amount) { 231 | for (const account of accounts) { 232 | await token.approve(recipient, amount, {from: account}); 233 | } 234 | } 235 | } 236 | 237 | module.exports = { 238 | CurveEcosystem 239 | } -------------------------------------------------------------------------------- /test/helpers/CurveEcosystemConfig.js: -------------------------------------------------------------------------------- 1 | const CurveLogicType = { 2 | CONSTANT: 0, 3 | BANCOR: 1 4 | }; 5 | 6 | const TokenType = { 7 | ETHER: 0, 8 | ERC20: 1 9 | }; 10 | 11 | module.exports = { 12 | CurveLogicType, 13 | TokenType 14 | }; 15 | -------------------------------------------------------------------------------- /test/helpers/ecosystemConfigs.js: -------------------------------------------------------------------------------- 1 | const {CurveLogicType, TokenType} = require('./CurveEcosystemConfig'); 2 | const {bn} = require('./utils'); 3 | 4 | const defaultTestConfig = { 5 | deployParams: { 6 | collateralType: TokenType.ERC20, 7 | curveLogicType: CurveLogicType.CONSTANT, 8 | curveParams: { 9 | reservePercentage: bn(10), 10 | dividendPercentage: bn(50) 11 | }, 12 | bondedTokenParams: { 13 | name: 'BondedToken', 14 | symbol: 'BND', 15 | decimals: bn(18) 16 | }, 17 | collateralTokenParams: { 18 | name: 'PaymentToken', 19 | symbol: 'Pay', 20 | decimals: 18, 21 | initialSupply: bn(1000000000) 22 | }, 23 | curveLogicParams: { 24 | tokenRatio: bn(100000000) 25 | } 26 | } 27 | }; 28 | 29 | const defaultTestConfigERC20Static = { 30 | deployParams: { 31 | collateralType: TokenType.ERC20, 32 | curveLogicType: CurveLogicType.CONSTANT, 33 | curveParams: { 34 | reservePercentage: bn(10), 35 | dividendPercentage: bn(50) 36 | }, 37 | bondedTokenParams: { 38 | name: 'BondedToken', 39 | symbol: 'BND', 40 | decimals: bn(18) 41 | }, 42 | collateralTokenParams: { 43 | name: 'PaymentToken', 44 | symbol: 'Pay', 45 | decimals: 18, 46 | initialSupply: bn(1000000000) 47 | }, 48 | curveLogicParams: { 49 | tokenRatio: bn(100000000) 50 | } 51 | } 52 | }; 53 | 54 | const defaultTestConfigEtherStatic = { 55 | deployParams: { 56 | collateralType: TokenType.ETHER, 57 | curveLogicType: CurveLogicType.CONSTANT, 58 | curveParams: { 59 | reservePercentage: bn(10), 60 | dividendPercentage: bn(50) 61 | }, 62 | bondedTokenParams: { 63 | name: 'BondedToken', 64 | symbol: 'BND', 65 | decimals: bn(18) 66 | }, 67 | collateralTokenParams: { 68 | name: 'PaymentToken', 69 | symbol: 'Pay', 70 | decimals: 18, 71 | initialSupply: bn(1000000000) 72 | }, 73 | curveLogicParams: { 74 | tokenRatio: bn(100000000) 75 | } 76 | } 77 | }; 78 | 79 | module.exports = { 80 | defaultTestConfig 81 | }; 82 | -------------------------------------------------------------------------------- /test/helpers/utils.js: -------------------------------------------------------------------------------- 1 | const {BN} = require('openzeppelin-test-helpers'); 2 | const MAX_GAS = 0xffffffff; 3 | const MAX_UINT = web3.utils.toTwosComplement("-1"); 4 | const WAD = new BN(10).pow(new BN(18)); 5 | 6 | const str = (val) => { 7 | return val.toString(); 8 | } 9 | 10 | const bn = val => { 11 | return new BN(val.toString()); 12 | } 13 | 14 | const wad = val => { 15 | return new BN(val.toString()).mul(WAD) 16 | } 17 | 18 | module.exports = { 19 | str, bn, MAX_GAS, MAX_UINT, WAD 20 | } -------------------------------------------------------------------------------- /test/integration/e2e.js: -------------------------------------------------------------------------------- 1 | // Deploy a new ecosystem 2 | // Users buy tokens 3 | // Users sell tokens 4 | // Users trade tokens 5 | // Payments come in 6 | // Merkle root is calculated & published 7 | // Users try to withdraw 8 | // More users buy / sell / trade tokens 9 | // More payments + merkle publish 10 | -------------------------------------------------------------------------------- /test/setup.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | process.env.NODE_ENV = 'test'; 3 | 4 | import {ZWeb3, Contracts} from '@openzeppelin/upgrades'; 5 | 6 | ZWeb3.initialize(web3.currentProvider); 7 | 8 | //TODO: Have these automatically set based on network chosen 9 | //Standard network defaults 10 | Contracts.setArtifactsDefaults({ 11 | gas: 6721975, 12 | gasPrice: 100000000000 13 | }); 14 | 15 | //Solidity coverage network defaults 16 | // Contracts.setArtifactsDefaults({ 17 | // gas: 17592186044415, 18 | // gasPrice: 1 19 | // }); 20 | 21 | require('chai') 22 | .use(require('@openzeppelin/upgrades').assertions) 23 | .should(); 24 | -------------------------------------------------------------------------------- /test/unit/bancorCurveLogic.spec.js: -------------------------------------------------------------------------------- 1 | // Import all required modules from openzeppelin-test-helpers 2 | const {BN, constants, expectEvent, expectRevert} = require('openzeppelin-test-helpers'); 3 | 4 | // Import preferred chai flavor: both expect and should are supported 5 | const expect = require('chai').expect; 6 | const should = require('chai').should(); 7 | 8 | require('../setup'); 9 | const {deployProject, deployBancorCurveService, deployBancorCurveLogic} = require('../../index.js'); 10 | 11 | const {values} = require('../constants/bancorValues'); 12 | 13 | contract('BancorCurveLogic', accounts => { 14 | let tx; 15 | let project; 16 | 17 | const creator = accounts[0]; 18 | const initializer = accounts[1]; 19 | 20 | let curve; 21 | let bancorCurveService; 22 | 23 | const reserveRatio = new BN(1000); 24 | beforeEach(async function() { 25 | project = await deployProject(); 26 | bancorCurveService = await deployBancorCurveService(project); 27 | curve = await deployBancorCurveLogic(project, [ 28 | bancorCurveService.address, 29 | reserveRatio.toString() 30 | ]); 31 | }); 32 | 33 | it('initializes reserve ratio parameter correctly', async function() { 34 | const result = await curve.methods.reserveRatio().call({from: initializer}); 35 | 36 | expect(new BN(result)).to.be.bignumber.equal(reserveRatio); 37 | }); 38 | 39 | it('initializes curve service parameter correctly', async function() { 40 | const result = await curve.methods.bancorService().call({from: initializer}); 41 | 42 | expect(result).to.be.equal(bancorCurveService.address); 43 | }); 44 | 45 | it('calculates correct buy results for all value sets', async function() { 46 | for (let i = 0; i < values.length; i++) { 47 | const valueSet = values[i]; 48 | const result = await curve.methods 49 | .calcMintPrice(valueSet.supply, valueSet.connectorBalance, valueSet.depositAmount) 50 | .call({from: initializer}); 51 | 52 | expect(new BN(result)).to.be.bignumber.equal(valueSet.expectedBuyResult); 53 | } 54 | }); 55 | 56 | it('calculates correct sell results for all value sets', async function() { 57 | let valueSet = values[0]; 58 | const result = await curve.methods 59 | .calcBurnReward(valueSet.supply, valueSet.connectorBalance, valueSet.depositAmount) 60 | .call({from: initializer}); 61 | 62 | expect(new BN(result)).to.be.bignumber.equal(valueSet.expectedSaleResult); 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /test/unit/bancorFormula.spec.js: -------------------------------------------------------------------------------- 1 | // Import all required modules from openzeppelin-test-helpers 2 | const { 3 | BN, 4 | constants, 5 | expectEvent, 6 | expectRevert 7 | } = require("openzeppelin-test-helpers"); 8 | 9 | // Import preferred chai flavor: both expect and should are supported 10 | const expect = require("chai").expect; 11 | const should = require("chai").should(); 12 | 13 | require("../setup"); 14 | const { deployProject, deployBancorFormula } = require("../../index.js"); 15 | 16 | const { values } = require("../constants/bancorValues"); 17 | 18 | contract("BancorFormula", accounts => { 19 | let tx; 20 | let result; 21 | let project; 22 | 23 | const creator = accounts[0]; 24 | const initializer = accounts[1]; 25 | 26 | beforeEach(async function() { 27 | project = await deployProject(); 28 | this.bancorFormula = await deployBancorFormula(project); 29 | }); 30 | 31 | it("calculates correct buy results for all value sets", async function() { 32 | for (let i = 0; i < values.length; i++) { 33 | let valueSet = values[i]; 34 | const result = await this.bancorFormula.methods 35 | .calculatePurchaseReturn( 36 | valueSet.supply, 37 | valueSet.connectorBalance, 38 | valueSet.connectorWeight, 39 | valueSet.depositAmount 40 | ) 41 | .call({ from: initializer }); 42 | 43 | expect(new BN(result)).to.be.bignumber.equal(valueSet.expectedBuyResult); 44 | } 45 | }); 46 | 47 | it("calculates correct sale results for all value sets", async function() { 48 | for (let i = 0; i < values.length; i++) { 49 | let valueSet = values[i]; 50 | const result = await this.bancorFormula.methods 51 | .calculateSaleReturn( 52 | valueSet.supply, 53 | valueSet.connectorBalance, 54 | valueSet.connectorWeight, 55 | valueSet.depositAmount 56 | ) 57 | .call({ from: initializer }); 58 | 59 | expect(new BN(result)).to.be.bignumber.equal(valueSet.expectedSaleResult); 60 | } 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /test/unit/bondedToken.spec.js: -------------------------------------------------------------------------------- 1 | // // Import all required modules from openzeppelin-test-helpers 2 | // const { 3 | // BN, 4 | // constants, 5 | // expectEvent, 6 | // expectRevert 7 | // } = require("openzeppelin-test-helpers"); 8 | 9 | // // Import preferred chai flavor: both expect and should are supported 10 | // const expect = require("chai").expect; 11 | // const should = require("chai").should(); 12 | 13 | // require("../setup"); 14 | // const { deployProject, deployBondedToken } = require("../../index.js"); 15 | 16 | // const { bondedTokenValues } = require("../constants/tokenValues"); 17 | // const { 18 | // shouldBehaveLikeERC20Burnable 19 | // } = require("../behaviors/ERC20Burnable.behavior"); 20 | 21 | // contract("BondedToken", accounts => { 22 | // let tx; 23 | // let result; 24 | // let project; 25 | // let bondedToken; 26 | 27 | // const creator = accounts[0]; 28 | // const initializer = accounts[1]; 29 | // const otherAccounts = accounts.slice(2, accounts.length); 30 | 31 | // beforeEach(async function() { 32 | // project = await deployProject(); 33 | // bondedToken = await deployBondedToken(project, [ 34 | // bondedTokenValues.parameters.name, 35 | // bondedTokenValues.parameters.symbol, 36 | // bondedTokenValues.parameters.decimals, 37 | // initializer 38 | // ]); 39 | // console.log(bondedToken.address); 40 | // const initialBalance = new BN(10000); 41 | // await bondedToken.methods 42 | // .mint(initializer, initialBalance.toString()) 43 | // .send({ from: initializer }); 44 | // }); 45 | 46 | // it("sets parameters correctly", async function() {}); 47 | 48 | // describe("Burn", async function() { 49 | // const initialBalance = new BN(10000); 50 | // await bondedToken.methods 51 | // .mint(initializer, initialBalance.toString()) 52 | // .send({ from: initializer }); 53 | 54 | // it("sets initial balance correctly", async function() { 55 | // result = await bondedToken.methods 56 | // .balanceOf(initializer) 57 | // .call({ from: initializer }); 58 | 59 | // expect(new BN(result)).to.be.bignumber.equal(initialBalance); 60 | // }); 61 | 62 | // shouldBehaveLikeERC20Burnable( 63 | // bondedToken, 64 | // initializer, 65 | // initialBalance, 66 | // otherAccounts 67 | // ); 68 | // }); 69 | 70 | // it("allows minter to mint tokens", async function() {}); 71 | // it("allows minter to burn tokens", async function() {}); 72 | // it("forbids non-minter to mint tokens", async function() {}); 73 | // it("forbids non-minter to burn tokens", async function() {}); 74 | // }); 75 | -------------------------------------------------------------------------------- /test/unit/bondingCurve.spec.js: -------------------------------------------------------------------------------- 1 | // Import all required modules from openzeppelin-test-helpers 2 | const {bondingCurveDeployTests} = require('../behaviors/bondingCurveDeploy'); 3 | const {bondingCurveAdminTests} = require('../behaviors/bondingCurveAdmin'); 4 | const {bondingCurvePaymentTests} = require('../behaviors/bondingCurvePayment'); 5 | const {bondingCurveBuySellTests} = require('../behaviors/bondingCurveBuySell'); 6 | const {defaultTestConfig} = require('../helpers/ecosystemConfigs'); 7 | /* 8 | Uses StaticCurveLogic for simpler tests. 9 | */ 10 | 11 | bondingCurveDeployTests('Bonding Curve - Static Curve, Typical Values', defaultTestConfig); 12 | bondingCurveAdminTests('Bonding Curve - Static Curve, Typical Values', defaultTestConfig); 13 | bondingCurvePaymentTests('Bonding Curve - Static Curve, Typical Values', defaultTestConfig); 14 | bondingCurveBuySellTests('Bonding Curve - Static Curve, Typical Values', defaultTestConfig); 15 | -------------------------------------------------------------------------------- /test/unit/rewardDistributor.spec.js: -------------------------------------------------------------------------------- 1 | // Import all required modules from openzeppelin-test-helpers 2 | const {BN, constants, expectRevert} = require('openzeppelin-test-helpers'); 3 | 4 | // Import preferred chai flavor: both expect and should are supported 5 | const expect = require('chai').expect; 6 | const should = require('chai').should(); 7 | const expectEvent = require('../expectEvent'); 8 | 9 | require('../setup'); 10 | 11 | const {deployProject, deployRewardsDistributor} = require('../../index.js'); 12 | 13 | var TEN18 = new BN(String(10 ** 18)); 14 | 15 | var PPB = new BN(String(10 ** 9)); 16 | 17 | contract('RewardsDistributor', accounts => { 18 | let project; 19 | let rd; 20 | let tx; 21 | let ELIGIBLE_UNIT; 22 | 23 | let creator = accounts[1]; 24 | 25 | let acct_a = accounts[2]; 26 | let acct_b = accounts[3]; 27 | let acct_c = accounts[4]; 28 | let acct_d = accounts[5]; 29 | 30 | beforeEach(async function() { 31 | project = await deployProject(); 32 | rd = await deployRewardsDistributor(project, [creator]); 33 | ELIGIBLE_UNIT = rd.ELIGIBLE_UNIT; 34 | }); 35 | 36 | it('deploys and initializes', async function() { 37 | expect(await rd.methods.getStakeTotal().call({from: creator})).to.be.equal('0'); 38 | }); 39 | 40 | it('withdraws ZERO reward', async function() { 41 | tx = await rd.methods.withdrawReward(acct_a).send({from: creator}); 42 | expectEvent.inLogs(tx.events, 'RewardWithdrawalMade', { 43 | to: acct_a, 44 | value: new BN('0') 45 | }); 46 | }); 47 | 48 | it('reads ZERO stake', async function() { 49 | expect(await rd.methods.getStake(acct_a).call({from: creator})).to.be.equal('0'); 50 | }); 51 | 52 | it('reverts if trying to withdraw amount > stake', async function() { 53 | await expectRevert.unspecified(rd.methods.withdrawStake(acct_a, '1').send({from: creator})); 54 | }); 55 | 56 | it('updates total stake after deposit > ELIGIBLE_UNIT', async function() { 57 | var amount = new BN('100').mul(TEN18); 58 | 59 | tx = await rd.methods.deposit(acct_a, amount.toString()).send({from: creator}); 60 | 61 | expectEvent.inLogs(tx.events, 'DepositMade', { 62 | from: creator, 63 | value: amount 64 | }); 65 | 66 | expect(await rd.methods.getStakeTotal().call({from: creator})).to.be.equal(amount.toString()); 67 | }); 68 | 69 | it("doesn't update total stake after deposit < ELIGIBLE_UNIT", async function() { 70 | var amount = new BN('100'); 71 | 72 | tx = await rd.methods.deposit(acct_a, amount.toString()).send({from: creator}); 73 | 74 | expect(await rd.methods.getStakeTotal().call({from: creator})).to.be.equal('0'); 75 | }); 76 | 77 | it('deposits A, gets stake, withdraws all stake, gets stake again', async function() { 78 | var amount = new BN('100').mul(TEN18); 79 | 80 | tx = await rd.methods.deposit(acct_a, amount.toString()).send({from: creator}); 81 | 82 | expect(await rd.methods.getStake(acct_a).call({from: creator})).to.be.equal(amount.toString()); 83 | 84 | await rd.methods.withdrawAllStake(acct_a).send({from: creator}); 85 | 86 | expect(await rd.methods.getStake(acct_a).call({from: creator})).to.be.equal('0'); 87 | }); 88 | 89 | it('does no distribution if no stake >= ELIGIBLE_UNIT', async function() { 90 | await rd.methods.deposit(acct_a, '1234').send({from: creator}); 91 | 92 | await rd.methods.distribute(creator, '1234000').send({from: creator}); 93 | 94 | expect(await rd.methods.getReward(acct_a).call({from: creator})).to.be.equal('0'); 95 | }); 96 | 97 | it('does no distribution if stake becomes ineligible after withdrawl', async function() { 98 | var amountDeposit = new BN('100').mul(TEN18); 99 | var amountWithdraw = new BN('100').mul(TEN18).sub(new BN('10000')); 100 | 101 | await rd.methods.deposit(acct_a, amountDeposit.toString()).send({from: creator}); 102 | 103 | tx = await rd.methods.withdrawStake(acct_a, amountWithdraw.toString()).send({from: creator}); 104 | expectEvent.inLogs(tx.events, 'StakeWithdrawalMade', { 105 | to: acct_b, 106 | value: amountWithdraw 107 | }); 108 | 109 | await rd.methods.distribute(creator, '1234000').send({from: creator}); 110 | 111 | expect(await rd.methods.getReward(acct_a).call({from: creator})).to.be.equal('0'); 112 | 113 | // stake of A is 10000 but because it is < ELIGIBLE_UNIT total stake 114 | // should be zero 115 | expect(await rd.methods.getStake(acct_a).call({from: creator})).to.be.equal('10000'); 116 | 117 | expect(await rd.methods.getStakeTotal().call({from: creator})).to.be.equal('0'); 118 | }); 119 | 120 | it('allocates all reward to a single staker and allow its withdrawl', async function() { 121 | var amountDeposit = new BN('100').mul(TEN18); 122 | var amountDistribute = new BN('200').mul(TEN18); 123 | 124 | await rd.methods.deposit(acct_a, amountDeposit.toString()).send({from: creator}); 125 | 126 | tx = await rd.methods.distribute(creator, amountDistribute.toString()).send({from: creator}); 127 | expectEvent.inLogs(tx.events, 'DistributionMade', { 128 | from: 0, 129 | value: amountDistribute 130 | }); 131 | 132 | expect(await rd.methods.getReward(acct_a).call({from: creator})).to.be.equal( 133 | amountDistribute.toString() 134 | ); 135 | 136 | tx = await rd.methods.withdrawReward(acct_a).send({from: creator}); 137 | expectEvent.inLogs(tx.events, 'RewardWithdrawalMade', { 138 | to: acct_a, 139 | value: amountDistribute 140 | }); 141 | 142 | expect(await rd.methods.getReward(acct_a).call({from: creator})).to.be.equal('0'); 143 | }); 144 | 145 | it('allocates no reward to stake <= ELIGIBLE_UNIT', async function() { 146 | await rd.methods.deposit(acct_a, String(10 ** 9)).send({from: creator}); 147 | await rd.methods.deposit(acct_b, String(100)).send({from: creator}); 148 | 149 | var distribute1 = new BN('100').mul(TEN18); 150 | await rd.methods.distribute(creator, distribute1.toString()).send({from: creator}); 151 | 152 | // all reward goes to A 153 | expect(await rd.methods.getReward(acct_a).call({from: creator})).to.be.equal( 154 | distribute1.toString() 155 | ); 156 | 157 | expect(await rd.methods.getReward(acct_b).call({from: creator})).to.be.equal('0'); 158 | }); 159 | 160 | it('allocates 1st reward proportionally to 2 stakers and 2nd reward to remaining staker after the other withdrew', async function() { 161 | var depositA = new BN(String(10 * 10 ** 9)); // 10 ELIGIBLE_UNITS 162 | var depositB = new BN(String(30 * 10 ** 9)); 163 | var distribute1 = new BN('400'); // notice this is in wei 164 | var distribute2 = new BN('900'); 165 | 166 | await rd.methods.deposit(acct_a, depositA.toString()).send({from: creator}); 167 | await rd.methods.deposit(acct_b, depositB.toString()).send({from: creator}); 168 | await rd.methods.distribute(creator, distribute1.toString()).send({from: creator}); 169 | 170 | // second distribution after A has withdrawn entirely 171 | await rd.methods.withdrawAllStake(acct_a).send({from: creator}); 172 | await rd.methods.distribute(creator, distribute2.toString()).send({from: creator}); 173 | 174 | expect(await rd.methods.getReward(acct_b).call({from: creator})).to.be.equal('1200'); 175 | 176 | expect(await rd.methods.getReward(acct_a).call({from: creator})).to.be.equal('100'); 177 | }); 178 | 179 | it('withdraws reward after proportional reward distribution', async function() { 180 | var depositA = new BN('100').mul(TEN18); 181 | var depositB = new BN('300').mul(TEN18); 182 | var distribute1 = new BN('40').mul(TEN18); 183 | 184 | await rd.methods.deposit(acct_a, depositA.toString()).send({from: creator}); 185 | await rd.methods.deposit(acct_b, depositB.toString()).send({from: creator}); 186 | await rd.methods.distribute(creator, distribute1.toString()).send({from: creator}); 187 | 188 | tx = await rd.methods.withdrawReward(acct_b).send({from: creator}); 189 | expectEvent.inLogs(tx.events, 'RewardWithdrawalMade', { 190 | to: acct_b, 191 | value: new BN('30').mul(TEN18) 192 | }); 193 | }); 194 | 195 | it('withdraws reward after two consecutive reward distributions', async function() { 196 | var depositA = new BN('100').mul(TEN18); 197 | var depositB = new BN('300').mul(TEN18); 198 | var distribute1 = new BN('400').mul(TEN18); 199 | 200 | await rd.methods.deposit(acct_a, depositA.toString()).send({from: creator}); 201 | await rd.methods.deposit(acct_b, depositB.toString()).send({from: creator}); 202 | await rd.methods.distribute(creator, distribute1.toString()).send({from: creator}); 203 | 204 | var distribute2 = new BN('4000').mul(TEN18); 205 | await rd.methods.distribute(creator, distribute2.toString()).send({from: creator}); 206 | 207 | tx = await rd.methods.withdrawReward(acct_a).send({from: creator}); 208 | expectEvent.inLogs(tx.events, 'RewardWithdrawalMade', { 209 | to: acct_a, 210 | value: new BN('1100').mul(TEN18) 211 | }); 212 | 213 | tx = await rd.methods.withdrawReward(acct_a).send({from: creator}); 214 | expectEvent.inLogs(tx.events, 'RewardWithdrawalMade', { 215 | to: acct_a, 216 | value: new BN('0') 217 | }); 218 | 219 | tx = await rd.methods.withdrawReward(acct_b).send({from: creator}); 220 | expectEvent.inLogs(tx.events, 'RewardWithdrawalMade', { 221 | to: acct_b, 222 | value: new BN('3300').mul(TEN18) 223 | }); 224 | }); 225 | 226 | it('distributes after partial stake withdrawal and reads reward', async function() { 227 | var depositA = new BN('100').mul(TEN18); 228 | var depositB = new BN('300').mul(TEN18); 229 | var distribute1 = new BN('100').mul(TEN18); 230 | var withdrawB = new BN('200').mul(TEN18); 231 | var distribute2 = new BN('100').mul(TEN18); 232 | 233 | await rd.methods.deposit(acct_a, depositA.toString()).send({from: creator}); 234 | await rd.methods.deposit(acct_b, depositB.toString()).send({from: creator}); 235 | await rd.methods.distribute(creator, distribute1.toString()).send({from: creator}); 236 | 237 | tx = await rd.methods.withdrawStake(acct_b, withdrawB.toString()).send({from: creator}); 238 | expectEvent.inLogs(tx.events, 'StakeWithdrawalMade', { 239 | to: acct_b, 240 | value: withdrawB 241 | }); 242 | 243 | await rd.methods.distribute(creator, distribute2.toString()).send({from: creator}); 244 | 245 | expect(await rd.methods.getStakeTotal().call({from: creator})).to.be.equal( 246 | String(200 * 10 ** 18) 247 | ); 248 | 249 | expect(await rd.methods.getReward(acct_a).call({from: creator})).to.be.equal( 250 | String(75 * 10 ** 18) 251 | ); 252 | 253 | expect(await rd.methods.getReward(acct_b).call({from: creator})).to.be.equal( 254 | String(125 * 10 ** 18) 255 | ); 256 | }); 257 | 258 | it('withdraws reward after stake has been withdrawn', async function() { 259 | var depositA = new BN('100').mul(TEN18); 260 | var distribute1 = new BN('10').mul(TEN18); 261 | 262 | await rd.methods.deposit(acct_a, depositA.toString()).send({from: creator}); 263 | await rd.methods.distribute(creator, distribute1.toString()).send({from: creator}); 264 | 265 | tx = await rd.methods.withdrawStake(acct_a, depositA.toString()).send({from: creator}); 266 | expectEvent.inLogs(tx.events, 'StakeWithdrawalMade', { 267 | to: acct_b, 268 | value: depositA 269 | }); 270 | 271 | tx = await rd.methods.withdrawReward(acct_a).send({from: creator}); 272 | expectEvent.inLogs(tx.events, 'RewardWithdrawalMade', { 273 | to: acct_a, 274 | value: distribute1 275 | }); 276 | 277 | expect(await rd.methods.getStakeTotal().call({from: creator})).to.be.equal('0'); 278 | }); 279 | 280 | it('handles magnitude: A deposits 9999, B deposits 1, distribute, withdraw stake, distribute, withdraw reward', async function() { 281 | var depositA = new BN('9999').mul(TEN18); 282 | var depositB = new BN('1').mul(TEN18); 283 | var distribute1 = new BN('1').mul(TEN18); 284 | var distribute2 = new BN('2').mul(TEN18); 285 | 286 | await rd.methods.deposit(acct_a, depositA.toString()).send({from: creator}); 287 | await rd.methods.deposit(acct_b, depositB.toString()).send({from: creator}); 288 | 289 | await rd.methods.distribute(creator, distribute1.toString()).send({from: creator}); 290 | await rd.methods.withdrawAllStake(acct_a).send({from: creator}); 291 | await rd.methods.distribute(creator, distribute2.toString()).send({from: creator}); 292 | 293 | expect(await rd.methods.getReward(acct_b).call({from: creator})).to.be.equal( 294 | '2000100000000000000' 295 | ); 296 | }); 297 | 298 | it('handles magnitude: deposit 10**6 10**9 10**12 10**15, distribute, withdraw reward', async function() { 299 | await rd.methods.deposit(acct_a, String(10 ** 6)).send({from: creator}); 300 | await rd.methods.deposit(acct_b, String(10 ** 9)).send({from: creator}); 301 | await rd.methods.deposit(acct_c, String(10 ** 12)).send({from: creator}); 302 | // 10**6 is NOT substracted so B + C + D stakes sum up to 10 ** 15 303 | await rd.methods.deposit(acct_d, String(10 ** 15 - 10 ** 12 - 10 ** 9)).send({from: creator}); 304 | 305 | await rd.methods.distribute(creator, String(10 ** 9)).send({from: creator}); 306 | 307 | // A gets no reward because its stake is below ELIGIBLE_UNIT 308 | expect(await rd.methods.getReward(acct_a).call({from: creator})).to.be.equal('0'); 309 | 310 | expect(await rd.methods.getReward(acct_b).call({from: creator})).to.be.equal('1000'); 311 | 312 | expect(await rd.methods.getReward(acct_c).call({from: creator})).to.be.equal('1000000'); 313 | 314 | expect(await rd.methods.getReward(acct_d).call({from: creator})).to.be.equal( 315 | String(10 ** 9 - 10 ** 6 - 10 ** 3) 316 | ); 317 | }); 318 | 319 | it('carries remainder to second distribution and withdraws reward', async function() { 320 | await rd.methods.deposit(acct_a, String(10 ** 9)).send({from: creator}); 321 | await rd.methods.deposit(acct_b, String(9 * 10 ** 9)).send({from: creator}); 322 | 323 | // 19 wei can not be divided to 10 ELIGIBLE_UNITS; So only 10 wei 324 | // will be distributed and 9 will be stored as remainder and 325 | // added to the next distribution 326 | await rd.methods.distribute(creator, String(19)).send({from: creator}); 327 | 328 | tx = await rd.methods.withdrawReward(acct_a).send({from: creator}); 329 | expectEvent.inLogs(tx.events, 'RewardWithdrawalMade', { 330 | to: acct_a, 331 | value: 1 332 | }); 333 | 334 | tx = await rd.methods.withdrawReward(acct_b).send({from: creator}); 335 | expectEvent.inLogs(tx.events, 'RewardWithdrawalMade', { 336 | to: acct_b, 337 | value: 9 338 | }); 339 | 340 | // 9 wei remainder + 1 new wei can now be divided to 10 EILIGIBLE_UNITS 341 | await rd.methods.distribute(creator, String(1)).send({from: creator}); 342 | 343 | tx = await rd.methods.withdrawReward(acct_a).send({from: creator}); 344 | expectEvent.inLogs(tx.events, 'RewardWithdrawalMade', { 345 | to: acct_a, 346 | value: 1 347 | }); 348 | 349 | tx = await rd.methods.withdrawReward(acct_b).send({from: creator}); 350 | expectEvent.inLogs(tx.events, 'RewardWithdrawalMade', { 351 | to: acct_b, 352 | value: 9 353 | }); 354 | }); 355 | }); 356 | -------------------------------------------------------------------------------- /test/unit/staticCurveLogic.spec.js: -------------------------------------------------------------------------------- 1 | // Import all required modules from openzeppelin-test-helpers 2 | const { 3 | BN, 4 | constants, 5 | expectEvent, 6 | expectRevert 7 | } = require("openzeppelin-test-helpers"); 8 | 9 | // Import preferred chai flavor: both expect and should are supported 10 | const expect = require("chai").expect; 11 | const should = require("chai").should(); 12 | 13 | require("../setup"); 14 | const { deployProject, deployStaticCurveLogic } = require("../../index.js"); 15 | 16 | contract("StaticCurveLogic", accounts => { 17 | let tx; 18 | let result; 19 | let project; 20 | 21 | 22 | const initializer = accounts[1]; 23 | 24 | // Ratio of send tokens to minted tokens = tokenRatio / PRECISION 25 | const precision = new BN(1000000); 26 | const tokenRatio = new BN(100000000); 27 | 28 | let values = [ 29 | { 30 | totalSupply: new BN(1), 31 | reserveBalance: new BN(1), 32 | amount: new BN(1) 33 | }, 34 | { 35 | totalSupply: new BN("1000000"), 36 | reserveBalance: new BN("1000000"), 37 | amount: new BN("1000000") 38 | }, 39 | { 40 | totalSupply: new BN(Math.pow(1, 20)), 41 | reserveBalance: new BN("1000000000000000000"), 42 | amount: new BN("1000000000000000000") 43 | } 44 | ]; 45 | 46 | beforeEach(async function() { 47 | project = await deployProject(); 48 | this.curve = await deployStaticCurveLogic(project, [tokenRatio.toString()]); 49 | }); 50 | 51 | it("should set parameter correctly", async function() { 52 | result = await this.curve.methods.tokenRatio().call({ from: initializer }); 53 | expect(result).to.be.equal(tokenRatio.toString()); 54 | }); 55 | 56 | it(`calculate correct buy results for all value sets`, async function() { 57 | for (let i = 0; i < values.length; i++) { 58 | const valueSet = values[i]; 59 | result = await this.curve.methods 60 | .calcMintPrice( 61 | valueSet.totalSupply.toString(), 62 | valueSet.reserveBalance.toString(), 63 | valueSet.amount.toString() 64 | ) 65 | .call({ 66 | from: initializer 67 | }); 68 | 69 | expect(new BN(result)).to.be.bignumber.equal( 70 | tokenRatio.mul(valueSet.amount).div(precision) 71 | ); 72 | } 73 | }); 74 | 75 | it(`calculate correct sell results for all value sets`, async function() { 76 | for (let i = 0; i < values.length; i++) { 77 | const valueSet = values[i]; 78 | result = await this.curve.methods 79 | .calcBurnReward( 80 | valueSet.totalSupply.toString(), 81 | valueSet.reserveBalance.toString(), 82 | valueSet.amount.toString() 83 | ) 84 | .call({ 85 | from: initializer 86 | }); 87 | 88 | expect(new BN(result)).to.be.bignumber.equal( 89 | tokenRatio.mul(valueSet.amount).div(precision) 90 | ); 91 | } 92 | }); 93 | 94 | it(`should return correct token precision`, async function() { 95 | result = await this.curve.methods 96 | .tokenRatioPrecision() 97 | .call({ from: initializer }); 98 | expect(new BN(result)).to.be.bignumber.equal(precision); 99 | }); 100 | }); 101 | -------------------------------------------------------------------------------- /truffle-config.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | 3 | require('@babel/register'); 4 | require('@babel/polyfill'); 5 | 6 | const mnemonic = process.env.MNENOMIC; 7 | const HDWalletProvider = require('truffle-hdwallet-provider'); 8 | 9 | module.exports = { 10 | // See 11 | // to customize your Truffle configuration! 12 | networks: { 13 | development: { 14 | host: 'localhost', 15 | network_id: '*', 16 | port: 8545 17 | }, 18 | coverage: { 19 | host: 'localhost', 20 | network_id: '*', 21 | port: 8555, // <-- If you change this, also set the port option in .solcover.js. 22 | gas: 0xfffffffffff, // <-- Use this high gas value 23 | gasPrice: 0x01 // <-- Use this low gas price 24 | }, 25 | ropsten: { 26 | provider: () => { 27 | return new HDWalletProvider( 28 | mnemonic, 29 | 'https://ropsten.infura.io/v3/' + process.env.INFURA_API_KEY 30 | ); 31 | }, 32 | network_id: '3', 33 | gas: 4465030, 34 | gasPrice: 10000000000 35 | }, 36 | kovan: { 37 | provider: () => { 38 | return new HDWalletProvider( 39 | mnemonic, 40 | 'https://kovan.infura.io/v3/' + process.env.INFURA_API_KEY 41 | ); 42 | }, 43 | network_id: '42', 44 | gas: 4465030, 45 | gasPrice: 10000000000 46 | }, 47 | rinkeby: { 48 | provider: () => 49 | new HDWalletProvider( 50 | process.env.MNENOMIC, 51 | 'https://rinkeby.infura.io/v3/' + process.env.INFURA_API_KEY 52 | ), 53 | network_id: 4, 54 | gas: 3000000, 55 | gasPrice: 10000000000 56 | }, 57 | // main ethereum network(mainnet) 58 | main: { 59 | provider: () => 60 | new HDWalletProvider( 61 | process.env.MNENOMIC, 62 | 'https://mainnet.infura.io/v3/' + process.env.INFURA_API_KEY 63 | ), 64 | network_id: 1, 65 | gas: 3000000, 66 | gasPrice: 10000000000 67 | } 68 | }, 69 | compilers: { 70 | solc: { 71 | version: '0.5.7', 72 | optimizer: { 73 | enabled: true, 74 | runs: 200 75 | } 76 | } 77 | }, 78 | mocha: { 79 | reporter: 'eth-gas-reporter', 80 | } 81 | }; 82 | -------------------------------------------------------------------------------- /utils/cli/index.js: -------------------------------------------------------------------------------- 1 | const chalk = require('chalk'); 2 | const clear = require('clear'); 3 | const figlet = require('figlet'); 4 | const CLI = require('clui'); 5 | const Web3 = require('web3'); 6 | const {series} = require('async'); 7 | const Rx = require('rx'); 8 | const {exec} = require('child_process'); 9 | const inquirer = require('inquirer'); 10 | const Deployer = require('../deployer'); 11 | const {Actions, CurveTypes, CollateralTypes} = require('./lib/enums'); 12 | const runScript = require('./lib/runScript'); 13 | const truffleConfig = require('../../truffle-config'); 14 | 15 | require('dotenv').config(); 16 | 17 | const {Spinner} = CLI; 18 | const status = new Spinner('Deploying Ecosystem...'); 19 | 20 | const web3 = new Web3( 21 | new Web3.providers.HttpProvider( 22 | `http://${truffleConfig.networks.development.host}:${truffleConfig.networks.development.port}` 23 | ) 24 | ); 25 | const deployer = new Deployer(web3); 26 | 27 | const curveEcosystem = { 28 | implementation: { 29 | staticCurveLogicImpl: undefined, 30 | bancorCurveLogicImpl: undefined, 31 | bondedTokenImpl: undefined, 32 | bondingCurveImpl: undefined, 33 | rewardsDistributorImpl: undefined 34 | }, 35 | instance: { 36 | paymentToken: undefined, 37 | bancorCurveService: undefined, 38 | factoryInstance: undefined 39 | } 40 | }; 41 | 42 | const askMenuAction = () => { 43 | const questions = [ 44 | { 45 | type: 'list', 46 | name: 'action', 47 | message: 'Action:', 48 | choices: [Actions.DEPLOY_ECOSYSTEM, Actions.DEPLOY_CURVE], 49 | default: Actions.DEPLOY_ECOSYSTEM 50 | } 51 | ]; 52 | return inquirer.prompt(questions); 53 | }; 54 | 55 | const deployEcosystem = async collateralTokenParams => { 56 | console.log('deploying ecosystem...'); 57 | 58 | exec( 59 | `npx oz publish --network development && npx oz push --network development`, 60 | (error, stdout, stderr) => { 61 | if (error) { 62 | console.error(`exec error: ${error}`); 63 | return; 64 | } 65 | console.log(`${stdout}`); 66 | console.error(`${stderr}`); 67 | run(); 68 | } 69 | ); 70 | }; 71 | 72 | const deployCurve = async () => { 73 | // const curveParams = await inquirer.askCurveParameters(); 74 | // console.log(curveParams); 75 | 76 | const prompts = new Rx.Subject(); 77 | inquirer.prompt(prompts); 78 | 79 | // At some point in the future, push new questions 80 | prompts.next({ 81 | /* question... */ 82 | type: 'input', 83 | name: 'owner', 84 | message: 'Curve Beneficiary', 85 | choices: [CurveTypes.CONSTANT, CurveTypes.BANCOR], 86 | default: CurveTypes.CONSTANT 87 | }); 88 | prompts.next({ 89 | /* question... */ 90 | }); 91 | 92 | // When you're done 93 | prompts.complete(); 94 | }; 95 | 96 | const run = async () => { 97 | try { 98 | const action = await askMenuAction(); 99 | console.log(action); 100 | 101 | switch (action.action) { 102 | case Actions.DEPLOY_ECOSYSTEM: 103 | await deployEcosystem(); 104 | break; 105 | case Actions.DEPLOY_CURVE: 106 | await deployCurve(); 107 | break; 108 | default: 109 | throw new Error('Invalid action type specified'); 110 | } 111 | } catch (e) { 112 | console.log('[Error] ' + e); 113 | process.exit(); 114 | } 115 | }; 116 | 117 | clear(); 118 | console.log(chalk.yellow(figlet.textSync('OpenRaise', {horizontalLayout: 'full'}))); 119 | run(); 120 | -------------------------------------------------------------------------------- /utils/cli/lib/inquirer.js: -------------------------------------------------------------------------------- 1 | const inquirer = require('inquirer'); 2 | 3 | const actions = { 4 | DEPLOY_ECOSYSTEM: 'Deploy Ecosystem', 5 | DEPLOY_CURVE: 'Deploy Curve' 6 | }; 7 | 8 | const curveTypes = { 9 | CONSTANT: 'Constant', 10 | BANCOR: 'Bancor' 11 | }; 12 | 13 | const collateralType = { 14 | ETH: 'ETH', 15 | ERC20: 'ERC20' 16 | }; 17 | 18 | module.exports = { 19 | askMenuAction: () => { 20 | const questions = [ 21 | { 22 | type: 'list', 23 | name: 'action', 24 | message: 'Action:', 25 | choices: [actions.DEPLOY_ECOSYSTEM, actions.DEPLOY_CURVE], 26 | default: actions.DEPLOY_ECOSYSTEM 27 | } 28 | ]; 29 | return inquirer.prompt(questions); 30 | }, 31 | askCurveParameters: () => { 32 | const questions = [ 33 | { 34 | type: 'list', 35 | name: 'curveType', 36 | message: 'Curve Logic', 37 | choices: [curveTypes.CONSTANT, curveTypes.BANCOR], 38 | default: curveTypes.CONSTANT 39 | }, 40 | { 41 | type: 'list', 42 | name: 'collateral', 43 | message: 'Collateral Type', 44 | choices: [collateralType.ETH, collateralType.ERC20], 45 | default: collateralType.ETH 46 | } 47 | ]; 48 | return inquirer.prompt(questions); 49 | }, 50 | actions, 51 | curveTypes 52 | }; 53 | -------------------------------------------------------------------------------- /utils/deployer.js: -------------------------------------------------------------------------------- 1 | const {Contracts, SimpleProject, ZWeb3, AppProject, Package} = require('@openzeppelin/upgrades'); 2 | 3 | const StaticCurveLogic = Contracts.getFromLocal('StaticCurveLogic'); 4 | const BancorFormula = Contracts.getFromLocal('BancorFormula'); 5 | const BancorCurveLogic = Contracts.getFromLocal('BancorCurveLogic'); 6 | const BancorCurveService = Contracts.getFromLocal('BancorCurveService'); 7 | const BondingCurve = Contracts.getFromLocal('BondingCurve'); 8 | const BondingCurveFactory = Contracts.getFromLocal('BondingCurveFactory'); 9 | const BondedToken = Contracts.getFromLocal('BondedToken'); 10 | const RewardsDistributor = Contracts.getFromLocal('RewardsDistributor'); 11 | 12 | //TODO: Have these automatically set based on network chosen 13 | //Standard network defaults 14 | Contracts.setArtifactsDefaults({ 15 | gas: 6721975, 16 | gasPrice: 100000000000 17 | }); 18 | 19 | const CONTRACT_ABIS = { 20 | BondingCurve, 21 | BondingCurveFactory, 22 | BancorCurveLogic, 23 | StaticCurveLogic, 24 | BondedToken, 25 | BancorCurveService, 26 | RewardsDistributor 27 | }; 28 | 29 | const CONTRACT_NAMES = { 30 | BondingCurve: 'BondingCurve', 31 | BondingCurveFactory: 'BondingCurveFactory', 32 | BancorCurveLogic: 'BancorCurveLogic', 33 | StaticCurveLogic: 'StaticCurveLogic', 34 | BondedToken: 'BondedToken', 35 | BancorCurveService: 'BancorCurveService', 36 | RewardsDistributor: 'RewardsDistributor' 37 | }; 38 | 39 | const PACKAGE_NAMES = { 40 | self: 'example-openzeppelin-upgrades-simple' 41 | }; 42 | 43 | class Deployer { 44 | constructor(web3) { 45 | this.web3 = web3; 46 | } 47 | 48 | setWeb3(web3) { 49 | this.web3 = web3; 50 | } 51 | 52 | async setupApp(txParams) { 53 | // On-chain, single entry point of the entire application. 54 | const initialVersion = '2.5.2'; 55 | ZWeb3.initialize(this.web3.currentProvider); 56 | this.appProject = await AppProject.fetchOrDeploy( 57 | 'example-openzeppelin-upgrades-simple', 58 | initialVersion, 59 | txParams, 60 | {} 61 | ); 62 | 63 | // Add all implementations 64 | await this.appProject.setImplementation(BondingCurve, CONTRACT_NAMES.BondingCurve); 65 | await this.appProject.setImplementation( 66 | BondingCurveFactory, 67 | CONTRACT_NAMES.BondingCurveFactory 68 | ); 69 | await this.appProject.setImplementation(BancorCurveLogic, CONTRACT_NAMES.BancorCurveLogic); 70 | await this.appProject.setImplementation(StaticCurveLogic, CONTRACT_NAMES.StaticCurveLogic); 71 | await this.appProject.setImplementation(BondedToken, CONTRACT_NAMES.BondedToken); 72 | await this.appProject.setImplementation(BancorCurveService, CONTRACT_NAMES.BancorCurveService); 73 | await this.appProject.setImplementation(RewardsDistributor, CONTRACT_NAMES.RewardsDistributor); 74 | } 75 | 76 | async deployProject() { 77 | ZWeb3.initialize(this.web3.currentProvider); 78 | const [creatorAddress, initializerAddress] = await ZWeb3.accounts(); 79 | this.project = new SimpleProject('MyProject', null, { 80 | from: creatorAddress 81 | }); 82 | } 83 | 84 | async deployStaticCurveLogic(initArgs) { 85 | ZWeb3.initialize(this.web3.currentProvider); 86 | const instance = await this.project.createProxy(StaticCurveLogic, { 87 | initArgs 88 | }); 89 | return instance; 90 | } 91 | 92 | async deployBancorFormula() { 93 | ZWeb3.initialize(this.web3.currentProvider); 94 | const [creatorAddress, initializerAddress] = await ZWeb3.accounts(); 95 | 96 | const instance = await this.project.createProxy(BancorFormula); 97 | await instance.methods.initialize().send({from: initializerAddress}); 98 | return instance; 99 | } 100 | 101 | async deployBancorCurveService() { 102 | ZWeb3.initialize(this.web3.currentProvider); 103 | console.log(21); 104 | const [creatorAddress, initializerAddress] = await ZWeb3.accounts(); 105 | console.log(22); 106 | 107 | const instance = await this.project.createProxy(BancorCurveService); 108 | await instance.methods.initialize().send({from: initializerAddress}); 109 | return instance; 110 | } 111 | 112 | async deployBancorCurveLogic(initArgs) { 113 | ZWeb3.initialize(this.web3.currentProvider); 114 | 115 | const instance = await this.project.createProxy(BancorCurveLogic, { 116 | initArgs 117 | }); 118 | return instance; 119 | } 120 | 121 | async createBondingCurve() { 122 | ZWeb3.initialize(this.web3.currentProvider); 123 | 124 | const instance = await this.project.createProxy(BondingCurve); 125 | return instance; 126 | } 127 | 128 | async deployBondingCurve(initArgs) { 129 | ZWeb3.initialize(this.web3.currentProvider); 130 | 131 | const instance = await this.project.createProxy(BondingCurve, { 132 | initArgs 133 | }); 134 | return instance; 135 | } 136 | 137 | async deployBondingCurveFactory(initArgs) { 138 | ZWeb3.initialize(this.web3.currentProvider); 139 | 140 | const instance = await this.project.createProxy(BondingCurveFactory, { 141 | initArgs 142 | }); 143 | return instance; 144 | } 145 | 146 | async createBondedToken() { 147 | ZWeb3.initialize(this.web3.currentProvider); 148 | 149 | const instance = await this.project.createProxy(BondedToken); 150 | return instance; 151 | } 152 | 153 | async deployBondedToken(initArgs) { 154 | ZWeb3.initialize(this.web3.currentProvider); 155 | 156 | const instance = await this.project.createProxy(BondedToken, { 157 | initArgs 158 | }); 159 | return instance; 160 | } 161 | 162 | async deployStandaloneERC20(initArgs) { 163 | ZWeb3.initialize(this.web3.currentProvider); 164 | const instance = await this.project.createProxy(BondedToken, { 165 | initArgs 166 | }); 167 | return instance; 168 | } 169 | 170 | async deployRewardsDistributor(myProject, initArgs) { 171 | ZWeb3.initialize(this.web3.currentProvider); 172 | 173 | const instance = await this.project.createProxy(RewardsDistributor, { 174 | initArgs 175 | }); 176 | return instance; 177 | } 178 | 179 | async getImplementation(project, contractName) { 180 | const directory = await this.project.getCurrentDirectory(); 181 | const implementation = await directory.getImplementation(contractName); 182 | return implementation; 183 | } 184 | } 185 | 186 | module.exports = Deployer; 187 | --------------------------------------------------------------------------------